github.com/ava-labs/avalanchego@v1.11.11/vms/proposervm/state_syncable_vm_test.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package proposervm 5 6 import ( 7 "context" 8 "testing" 9 10 "github.com/prometheus/client_golang/prometheus" 11 "github.com/stretchr/testify/require" 12 13 "github.com/ava-labs/avalanchego/database" 14 "github.com/ava-labs/avalanchego/database/memdb" 15 "github.com/ava-labs/avalanchego/database/prefixdb" 16 "github.com/ava-labs/avalanchego/ids" 17 "github.com/ava-labs/avalanchego/snow" 18 "github.com/ava-labs/avalanchego/snow/consensus/snowman" 19 "github.com/ava-labs/avalanchego/snow/consensus/snowman/snowmantest" 20 "github.com/ava-labs/avalanchego/snow/engine/common" 21 "github.com/ava-labs/avalanchego/snow/engine/enginetest" 22 "github.com/ava-labs/avalanchego/snow/engine/snowman/block" 23 "github.com/ava-labs/avalanchego/snow/engine/snowman/block/blocktest" 24 "github.com/ava-labs/avalanchego/snow/snowtest" 25 "github.com/ava-labs/avalanchego/upgrade/upgradetest" 26 "github.com/ava-labs/avalanchego/vms/proposervm/summary" 27 28 statelessblock "github.com/ava-labs/avalanchego/vms/proposervm/block" 29 ) 30 31 func helperBuildStateSyncTestObjects(t *testing.T) (*fullVM, *VM) { 32 require := require.New(t) 33 34 innerVM := &fullVM{ 35 VM: &blocktest.VM{ 36 VM: enginetest.VM{ 37 T: t, 38 }, 39 }, 40 StateSyncableVM: &blocktest.StateSyncableVM{ 41 T: t, 42 }, 43 } 44 45 // load innerVM expectations 46 innerVM.InitializeF = func(context.Context, *snow.Context, database.Database, 47 []byte, []byte, []byte, chan<- common.Message, 48 []*common.Fx, common.AppSender, 49 ) error { 50 return nil 51 } 52 innerVM.LastAcceptedF = snowmantest.MakeLastAcceptedBlockF( 53 []*snowmantest.Block{snowmantest.Genesis}, 54 ) 55 innerVM.GetBlockF = func(context.Context, ids.ID) (snowman.Block, error) { 56 return snowmantest.Genesis, nil 57 } 58 59 // create the VM 60 vm := New( 61 innerVM, 62 Config{ 63 Upgrades: upgradetest.GetConfig(upgradetest.Latest), 64 MinBlkDelay: DefaultMinBlockDelay, 65 NumHistoricalBlocks: DefaultNumHistoricalBlocks, 66 StakingLeafSigner: pTestSigner, 67 StakingCertLeaf: pTestCert, 68 Registerer: prometheus.NewRegistry(), 69 }, 70 ) 71 72 ctx := snowtest.Context(t, snowtest.CChainID) 73 ctx.NodeID = ids.NodeIDFromCert(pTestCert) 74 75 require.NoError(vm.Initialize( 76 context.Background(), 77 ctx, 78 prefixdb.New([]byte{}, memdb.New()), 79 snowmantest.GenesisBytes, 80 nil, 81 nil, 82 nil, 83 nil, 84 nil, 85 )) 86 87 return innerVM, vm 88 } 89 90 func TestStateSyncEnabled(t *testing.T) { 91 require := require.New(t) 92 93 innerVM, vm := helperBuildStateSyncTestObjects(t) 94 defer func() { 95 require.NoError(vm.Shutdown(context.Background())) 96 }() 97 98 // ProposerVM State Sync disabled if innerVM State sync is disabled 99 innerVM.StateSyncEnabledF = func(context.Context) (bool, error) { 100 return false, nil 101 } 102 enabled, err := vm.StateSyncEnabled(context.Background()) 103 require.NoError(err) 104 require.False(enabled) 105 106 // ProposerVM State Sync enabled if innerVM State sync is enabled 107 innerVM.StateSyncEnabledF = func(context.Context) (bool, error) { 108 return true, nil 109 } 110 enabled, err = vm.StateSyncEnabled(context.Background()) 111 require.NoError(err) 112 require.True(enabled) 113 } 114 115 func TestStateSyncGetOngoingSyncStateSummary(t *testing.T) { 116 require := require.New(t) 117 118 innerVM, vm := helperBuildStateSyncTestObjects(t) 119 defer func() { 120 require.NoError(vm.Shutdown(context.Background())) 121 }() 122 123 innerSummary := &blocktest.StateSummary{ 124 IDV: ids.ID{'s', 'u', 'm', 'm', 'a', 'r', 'y', 'I', 'D'}, 125 HeightV: uint64(2022), 126 BytesV: []byte{'i', 'n', 'n', 'e', 'r'}, 127 } 128 129 // No ongoing state summary case 130 innerVM.GetOngoingSyncStateSummaryF = func(context.Context) (block.StateSummary, error) { 131 return nil, database.ErrNotFound 132 } 133 summary, err := vm.GetOngoingSyncStateSummary(context.Background()) 134 require.ErrorIs(err, database.ErrNotFound) 135 require.Nil(summary) 136 137 // Pre fork summary case, fork height not reached hence not set yet 138 innerVM.GetOngoingSyncStateSummaryF = func(context.Context) (block.StateSummary, error) { 139 return innerSummary, nil 140 } 141 _, err = vm.GetForkHeight() 142 require.ErrorIs(err, database.ErrNotFound) 143 summary, err = vm.GetOngoingSyncStateSummary(context.Background()) 144 require.NoError(err) 145 require.Equal(innerSummary.ID(), summary.ID()) 146 require.Equal(innerSummary.Height(), summary.Height()) 147 require.Equal(innerSummary.Bytes(), summary.Bytes()) 148 149 // Pre fork summary case, fork height already reached 150 innerVM.GetOngoingSyncStateSummaryF = func(context.Context) (block.StateSummary, error) { 151 return innerSummary, nil 152 } 153 require.NoError(vm.SetForkHeight(innerSummary.Height() + 1)) 154 summary, err = vm.GetOngoingSyncStateSummary(context.Background()) 155 require.NoError(err) 156 require.Equal(innerSummary.ID(), summary.ID()) 157 require.Equal(innerSummary.Height(), summary.Height()) 158 require.Equal(innerSummary.Bytes(), summary.Bytes()) 159 160 // Post fork summary case 161 require.NoError(vm.SetForkHeight(innerSummary.Height() - 1)) 162 163 // store post fork block associated with summary 164 innerBlk := &snowmantest.Block{ 165 BytesV: []byte{1}, 166 ParentV: ids.GenerateTestID(), 167 HeightV: innerSummary.Height(), 168 } 169 innerVM.ParseBlockF = func(_ context.Context, b []byte) (snowman.Block, error) { 170 require.Equal(innerBlk.Bytes(), b) 171 return innerBlk, nil 172 } 173 174 slb, err := statelessblock.Build( 175 vm.preferred, 176 innerBlk.Timestamp(), 177 100, // pChainHeight, 178 vm.StakingCertLeaf, 179 innerBlk.Bytes(), 180 vm.ctx.ChainID, 181 vm.StakingLeafSigner, 182 ) 183 require.NoError(err) 184 proBlk := &postForkBlock{ 185 SignedBlock: slb, 186 postForkCommonComponents: postForkCommonComponents{ 187 vm: vm, 188 innerBlk: innerBlk, 189 }, 190 } 191 require.NoError(vm.acceptPostForkBlock(proBlk)) 192 193 summary, err = vm.GetOngoingSyncStateSummary(context.Background()) 194 require.NoError(err) 195 require.Equal(innerSummary.Height(), summary.Height()) 196 } 197 198 func TestStateSyncGetLastStateSummary(t *testing.T) { 199 require := require.New(t) 200 201 innerVM, vm := helperBuildStateSyncTestObjects(t) 202 defer func() { 203 require.NoError(vm.Shutdown(context.Background())) 204 }() 205 206 innerSummary := &blocktest.StateSummary{ 207 IDV: ids.ID{'s', 'u', 'm', 'm', 'a', 'r', 'y', 'I', 'D'}, 208 HeightV: uint64(2022), 209 BytesV: []byte{'i', 'n', 'n', 'e', 'r'}, 210 } 211 212 // No last state summary case 213 innerVM.GetLastStateSummaryF = func(context.Context) (block.StateSummary, error) { 214 return nil, database.ErrNotFound 215 } 216 summary, err := vm.GetLastStateSummary(context.Background()) 217 require.ErrorIs(err, database.ErrNotFound) 218 require.Nil(summary) 219 220 // Pre fork summary case, fork height not reached hence not set yet 221 innerVM.GetLastStateSummaryF = func(context.Context) (block.StateSummary, error) { 222 return innerSummary, nil 223 } 224 _, err = vm.GetForkHeight() 225 require.ErrorIs(err, database.ErrNotFound) 226 summary, err = vm.GetLastStateSummary(context.Background()) 227 require.NoError(err) 228 require.Equal(innerSummary.ID(), summary.ID()) 229 require.Equal(innerSummary.Height(), summary.Height()) 230 require.Equal(innerSummary.Bytes(), summary.Bytes()) 231 232 // Pre fork summary case, fork height already reached 233 innerVM.GetLastStateSummaryF = func(context.Context) (block.StateSummary, error) { 234 return innerSummary, nil 235 } 236 require.NoError(vm.SetForkHeight(innerSummary.Height() + 1)) 237 summary, err = vm.GetLastStateSummary(context.Background()) 238 require.NoError(err) 239 require.Equal(innerSummary.ID(), summary.ID()) 240 require.Equal(innerSummary.Height(), summary.Height()) 241 require.Equal(innerSummary.Bytes(), summary.Bytes()) 242 243 // Post fork summary case 244 require.NoError(vm.SetForkHeight(innerSummary.Height() - 1)) 245 246 // store post fork block associated with summary 247 innerBlk := &snowmantest.Block{ 248 BytesV: []byte{1}, 249 ParentV: ids.GenerateTestID(), 250 HeightV: innerSummary.Height(), 251 } 252 innerVM.ParseBlockF = func(_ context.Context, b []byte) (snowman.Block, error) { 253 require.Equal(innerBlk.Bytes(), b) 254 return innerBlk, nil 255 } 256 257 slb, err := statelessblock.Build( 258 vm.preferred, 259 innerBlk.Timestamp(), 260 100, // pChainHeight, 261 vm.StakingCertLeaf, 262 innerBlk.Bytes(), 263 vm.ctx.ChainID, 264 vm.StakingLeafSigner, 265 ) 266 require.NoError(err) 267 proBlk := &postForkBlock{ 268 SignedBlock: slb, 269 postForkCommonComponents: postForkCommonComponents{ 270 vm: vm, 271 innerBlk: innerBlk, 272 }, 273 } 274 require.NoError(vm.acceptPostForkBlock(proBlk)) 275 276 summary, err = vm.GetLastStateSummary(context.Background()) 277 require.NoError(err) 278 require.Equal(innerSummary.Height(), summary.Height()) 279 } 280 281 func TestStateSyncGetStateSummary(t *testing.T) { 282 require := require.New(t) 283 284 innerVM, vm := helperBuildStateSyncTestObjects(t) 285 defer func() { 286 require.NoError(vm.Shutdown(context.Background())) 287 }() 288 reqHeight := uint64(1969) 289 290 innerSummary := &blocktest.StateSummary{ 291 IDV: ids.ID{'s', 'u', 'm', 'm', 'a', 'r', 'y', 'I', 'D'}, 292 HeightV: reqHeight, 293 BytesV: []byte{'i', 'n', 'n', 'e', 'r'}, 294 } 295 296 // No state summary case 297 innerVM.GetStateSummaryF = func(context.Context, uint64) (block.StateSummary, error) { 298 return nil, database.ErrNotFound 299 } 300 summary, err := vm.GetStateSummary(context.Background(), reqHeight) 301 require.ErrorIs(err, database.ErrNotFound) 302 require.Nil(summary) 303 304 // Pre fork summary case, fork height not reached hence not set yet 305 innerVM.GetStateSummaryF = func(_ context.Context, h uint64) (block.StateSummary, error) { 306 require.Equal(reqHeight, h) 307 return innerSummary, nil 308 } 309 _, err = vm.GetForkHeight() 310 require.ErrorIs(err, database.ErrNotFound) 311 summary, err = vm.GetStateSummary(context.Background(), reqHeight) 312 require.NoError(err) 313 require.Equal(innerSummary.ID(), summary.ID()) 314 require.Equal(innerSummary.Height(), summary.Height()) 315 require.Equal(innerSummary.Bytes(), summary.Bytes()) 316 317 // Pre fork summary case, fork height already reached 318 innerVM.GetStateSummaryF = func(_ context.Context, h uint64) (block.StateSummary, error) { 319 require.Equal(reqHeight, h) 320 return innerSummary, nil 321 } 322 require.NoError(vm.SetForkHeight(innerSummary.Height() + 1)) 323 summary, err = vm.GetStateSummary(context.Background(), reqHeight) 324 require.NoError(err) 325 require.Equal(innerSummary.ID(), summary.ID()) 326 require.Equal(innerSummary.Height(), summary.Height()) 327 require.Equal(innerSummary.Bytes(), summary.Bytes()) 328 329 // Post fork summary case 330 require.NoError(vm.SetForkHeight(innerSummary.Height() - 1)) 331 332 // store post fork block associated with summary 333 innerBlk := &snowmantest.Block{ 334 BytesV: []byte{1}, 335 ParentV: ids.GenerateTestID(), 336 HeightV: innerSummary.Height(), 337 } 338 innerVM.ParseBlockF = func(_ context.Context, b []byte) (snowman.Block, error) { 339 require.Equal(innerBlk.Bytes(), b) 340 return innerBlk, nil 341 } 342 343 slb, err := statelessblock.Build( 344 vm.preferred, 345 innerBlk.Timestamp(), 346 100, // pChainHeight, 347 vm.StakingCertLeaf, 348 innerBlk.Bytes(), 349 vm.ctx.ChainID, 350 vm.StakingLeafSigner, 351 ) 352 require.NoError(err) 353 proBlk := &postForkBlock{ 354 SignedBlock: slb, 355 postForkCommonComponents: postForkCommonComponents{ 356 vm: vm, 357 innerBlk: innerBlk, 358 }, 359 } 360 require.NoError(vm.acceptPostForkBlock(proBlk)) 361 362 summary, err = vm.GetStateSummary(context.Background(), reqHeight) 363 require.NoError(err) 364 require.Equal(innerSummary.Height(), summary.Height()) 365 } 366 367 func TestParseStateSummary(t *testing.T) { 368 require := require.New(t) 369 innerVM, vm := helperBuildStateSyncTestObjects(t) 370 defer func() { 371 require.NoError(vm.Shutdown(context.Background())) 372 }() 373 reqHeight := uint64(1969) 374 375 innerSummary := &blocktest.StateSummary{ 376 IDV: ids.ID{'s', 'u', 'm', 'm', 'a', 'r', 'y', 'I', 'D'}, 377 HeightV: reqHeight, 378 BytesV: []byte{'i', 'n', 'n', 'e', 'r'}, 379 } 380 innerVM.ParseStateSummaryF = func(_ context.Context, summaryBytes []byte) (block.StateSummary, error) { 381 require.Equal(summaryBytes, innerSummary.Bytes()) 382 return innerSummary, nil 383 } 384 innerVM.GetStateSummaryF = func(_ context.Context, h uint64) (block.StateSummary, error) { 385 require.Equal(reqHeight, h) 386 return innerSummary, nil 387 } 388 389 // Get a pre fork block than parse it 390 require.NoError(vm.SetForkHeight(innerSummary.Height() + 1)) 391 summary, err := vm.GetStateSummary(context.Background(), reqHeight) 392 require.NoError(err) 393 394 parsedSummary, err := vm.ParseStateSummary(context.Background(), summary.Bytes()) 395 require.NoError(err) 396 require.Equal(summary.ID(), parsedSummary.ID()) 397 require.Equal(summary.Height(), parsedSummary.Height()) 398 require.Equal(summary.Bytes(), parsedSummary.Bytes()) 399 400 // Get a post fork block than parse it 401 require.NoError(vm.SetForkHeight(innerSummary.Height() - 1)) 402 403 // store post fork block associated with summary 404 innerBlk := &snowmantest.Block{ 405 BytesV: []byte{1}, 406 ParentV: ids.GenerateTestID(), 407 HeightV: innerSummary.Height(), 408 } 409 innerVM.ParseBlockF = func(_ context.Context, b []byte) (snowman.Block, error) { 410 require.Equal(innerBlk.Bytes(), b) 411 return innerBlk, nil 412 } 413 414 slb, err := statelessblock.Build( 415 vm.preferred, 416 innerBlk.Timestamp(), 417 100, // pChainHeight, 418 vm.StakingCertLeaf, 419 innerBlk.Bytes(), 420 vm.ctx.ChainID, 421 vm.StakingLeafSigner, 422 ) 423 require.NoError(err) 424 proBlk := &postForkBlock{ 425 SignedBlock: slb, 426 postForkCommonComponents: postForkCommonComponents{ 427 vm: vm, 428 innerBlk: innerBlk, 429 }, 430 } 431 require.NoError(vm.acceptPostForkBlock(proBlk)) 432 require.NoError(vm.SetForkHeight(innerSummary.Height() - 1)) 433 summary, err = vm.GetStateSummary(context.Background(), reqHeight) 434 require.NoError(err) 435 436 parsedSummary, err = vm.ParseStateSummary(context.Background(), summary.Bytes()) 437 require.NoError(err) 438 require.Equal(summary.ID(), parsedSummary.ID()) 439 require.Equal(summary.Height(), parsedSummary.Height()) 440 require.Equal(summary.Bytes(), parsedSummary.Bytes()) 441 } 442 443 func TestStateSummaryAccept(t *testing.T) { 444 require := require.New(t) 445 446 innerVM, vm := helperBuildStateSyncTestObjects(t) 447 defer func() { 448 require.NoError(vm.Shutdown(context.Background())) 449 }() 450 reqHeight := uint64(1969) 451 452 innerSummary := &blocktest.StateSummary{ 453 IDV: ids.ID{'s', 'u', 'm', 'm', 'a', 'r', 'y', 'I', 'D'}, 454 HeightV: reqHeight, 455 BytesV: []byte{'i', 'n', 'n', 'e', 'r'}, 456 } 457 458 require.NoError(vm.SetForkHeight(innerSummary.Height() - 1)) 459 460 // store post fork block associated with summary 461 innerBlk := &snowmantest.Block{ 462 BytesV: []byte{1}, 463 ParentV: ids.GenerateTestID(), 464 HeightV: innerSummary.Height(), 465 } 466 467 slb, err := statelessblock.Build( 468 vm.preferred, 469 innerBlk.Timestamp(), 470 100, // pChainHeight, 471 vm.StakingCertLeaf, 472 innerBlk.Bytes(), 473 vm.ctx.ChainID, 474 vm.StakingLeafSigner, 475 ) 476 require.NoError(err) 477 478 statelessSummary, err := summary.Build(innerSummary.Height()-1, slb.Bytes(), innerSummary.Bytes()) 479 require.NoError(err) 480 481 innerVM.ParseStateSummaryF = func(_ context.Context, summaryBytes []byte) (block.StateSummary, error) { 482 require.Equal(innerSummary.BytesV, summaryBytes) 483 return innerSummary, nil 484 } 485 innerVM.ParseBlockF = func(_ context.Context, b []byte) (snowman.Block, error) { 486 require.Equal(innerBlk.Bytes(), b) 487 return innerBlk, nil 488 } 489 490 summary, err := vm.ParseStateSummary(context.Background(), statelessSummary.Bytes()) 491 require.NoError(err) 492 493 // test Accept accepted 494 innerSummary.AcceptF = func(context.Context) (block.StateSyncMode, error) { 495 return block.StateSyncStatic, nil 496 } 497 status, err := summary.Accept(context.Background()) 498 require.NoError(err) 499 require.Equal(block.StateSyncStatic, status) 500 501 // test Accept skipped 502 innerSummary.AcceptF = func(context.Context) (block.StateSyncMode, error) { 503 return block.StateSyncSkipped, nil 504 } 505 status, err = summary.Accept(context.Background()) 506 require.NoError(err) 507 require.Equal(block.StateSyncSkipped, status) 508 } 509 510 func TestStateSummaryAcceptOlderBlock(t *testing.T) { 511 require := require.New(t) 512 513 innerVM, vm := helperBuildStateSyncTestObjects(t) 514 defer func() { 515 require.NoError(vm.Shutdown(context.Background())) 516 }() 517 reqHeight := uint64(1969) 518 519 innerSummary := &blocktest.StateSummary{ 520 IDV: ids.ID{'s', 'u', 'm', 'm', 'a', 'r', 'y', 'I', 'D'}, 521 HeightV: reqHeight, 522 BytesV: []byte{'i', 'n', 'n', 'e', 'r'}, 523 } 524 525 require.NoError(vm.SetForkHeight(innerSummary.Height() - 1)) 526 527 // Set the last accepted block height to be higher that the state summary 528 // we are going to attempt to accept 529 vm.lastAcceptedHeight = innerSummary.Height() + 1 530 531 // store post fork block associated with summary 532 innerBlk := &snowmantest.Block{ 533 BytesV: []byte{1}, 534 ParentV: ids.GenerateTestID(), 535 HeightV: innerSummary.Height(), 536 } 537 innerVM.GetStateSummaryF = func(_ context.Context, h uint64) (block.StateSummary, error) { 538 require.Equal(reqHeight, h) 539 return innerSummary, nil 540 } 541 innerVM.ParseBlockF = func(_ context.Context, b []byte) (snowman.Block, error) { 542 require.Equal(innerBlk.Bytes(), b) 543 return innerBlk, nil 544 } 545 546 slb, err := statelessblock.Build( 547 vm.preferred, 548 innerBlk.Timestamp(), 549 100, // pChainHeight, 550 vm.StakingCertLeaf, 551 innerBlk.Bytes(), 552 vm.ctx.ChainID, 553 vm.StakingLeafSigner, 554 ) 555 require.NoError(err) 556 proBlk := &postForkBlock{ 557 SignedBlock: slb, 558 postForkCommonComponents: postForkCommonComponents{ 559 vm: vm, 560 innerBlk: innerBlk, 561 }, 562 } 563 require.NoError(vm.acceptPostForkBlock(proBlk)) 564 565 summary, err := vm.GetStateSummary(context.Background(), reqHeight) 566 require.NoError(err) 567 568 // test Accept skipped 569 innerSummary.AcceptF = func(context.Context) (block.StateSyncMode, error) { 570 return block.StateSyncStatic, nil 571 } 572 status, err := summary.Accept(context.Background()) 573 require.NoError(err) 574 require.Equal(block.StateSyncSkipped, status) 575 }