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