github.com/MetalBlockchain/metalgo@v1.11.9/vms/components/chain/state_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 chain 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "testing" 11 12 "github.com/prometheus/client_golang/prometheus" 13 "github.com/stretchr/testify/require" 14 15 "github.com/MetalBlockchain/metalgo/database" 16 "github.com/MetalBlockchain/metalgo/ids" 17 "github.com/MetalBlockchain/metalgo/snow/choices" 18 "github.com/MetalBlockchain/metalgo/snow/consensus/snowman" 19 "github.com/MetalBlockchain/metalgo/snow/consensus/snowman/snowmantest" 20 "github.com/MetalBlockchain/metalgo/utils/hashing" 21 ) 22 23 var ( 24 errCantBuildBlock = errors.New("can't build new block") 25 errVerify = errors.New("verify failed") 26 errAccept = errors.New("accept failed") 27 errReject = errors.New("reject failed") 28 errUnexpectedBlockBytes = errors.New("unexpected block bytes") 29 ) 30 31 // NewTestBlock returns a new test block with height, bytes, and ID derived from [i] 32 // and using [parentID] as the parent block ID 33 func NewTestBlock(i uint64, parentID ids.ID) *snowmantest.Block { 34 b := []byte{byte(i)} 35 id := hashing.ComputeHash256Array(b) 36 return &snowmantest.Block{ 37 TestDecidable: choices.TestDecidable{ 38 IDV: id, 39 StatusV: choices.Unknown, 40 }, 41 HeightV: i, 42 ParentV: parentID, 43 BytesV: b, 44 } 45 } 46 47 // NewTestBlocks generates [numBlocks] consecutive blocks 48 func NewTestBlocks(numBlocks uint64) []*snowmantest.Block { 49 blks := make([]*snowmantest.Block, 0, numBlocks) 50 parentID := ids.Empty 51 for i := uint64(0); i < numBlocks; i++ { 52 blks = append(blks, NewTestBlock(i, parentID)) 53 parent := blks[len(blks)-1] 54 parentID = parent.ID() 55 } 56 57 return blks 58 } 59 60 func createInternalBlockFuncs(blks []*snowmantest.Block) ( 61 func(ctx context.Context, blkID ids.ID) (snowman.Block, error), 62 func(ctx context.Context, b []byte) (snowman.Block, error), 63 func(ctx context.Context, height uint64) (ids.ID, error), 64 ) { 65 blkMap := make(map[ids.ID]*snowmantest.Block) 66 blkBytesMap := make(map[string]*snowmantest.Block) 67 for _, blk := range blks { 68 blkMap[blk.ID()] = blk 69 blkBytes := blk.Bytes() 70 blkBytesMap[string(blkBytes)] = blk 71 } 72 73 getBlock := func(_ context.Context, id ids.ID) (snowman.Block, error) { 74 blk, ok := blkMap[id] 75 if !ok || !blk.Status().Fetched() { 76 return nil, database.ErrNotFound 77 } 78 79 return blk, nil 80 } 81 82 parseBlk := func(_ context.Context, b []byte) (snowman.Block, error) { 83 blk, ok := blkBytesMap[string(b)] 84 if !ok { 85 return nil, fmt.Errorf("%w: %x", errUnexpectedBlockBytes, b) 86 } 87 if blk.Status() == choices.Unknown { 88 blk.SetStatus(choices.Processing) 89 } 90 blkMap[blk.ID()] = blk 91 92 return blk, nil 93 } 94 getAcceptedBlockIDAtHeight := func(_ context.Context, height uint64) (ids.ID, error) { 95 for _, blk := range blks { 96 if blk.Height() != height { 97 continue 98 } 99 100 if blk.Status() == choices.Accepted { 101 return blk.ID(), nil 102 } 103 } 104 105 return ids.Empty, database.ErrNotFound 106 } 107 108 return getBlock, parseBlk, getAcceptedBlockIDAtHeight 109 } 110 111 func cantBuildBlock(context.Context) (snowman.Block, error) { 112 return nil, errCantBuildBlock 113 } 114 115 // checkProcessingBlock checks that [blk] is of the correct type and is 116 // correctly uniquified when calling GetBlock and ParseBlock. 117 func checkProcessingBlock(t *testing.T, s *State, blk snowman.Block) { 118 require := require.New(t) 119 120 require.IsType(&BlockWrapper{}, blk) 121 122 parsedBlk, err := s.ParseBlock(context.Background(), blk.Bytes()) 123 require.NoError(err) 124 require.Equal(blk.ID(), parsedBlk.ID()) 125 require.Equal(blk.Bytes(), parsedBlk.Bytes()) 126 require.Equal(choices.Processing, parsedBlk.Status()) 127 require.Equal(blk, parsedBlk) 128 129 getBlk, err := s.GetBlock(context.Background(), blk.ID()) 130 require.NoError(err) 131 require.Equal(parsedBlk, getBlk) 132 } 133 134 // checkDecidedBlock asserts that [blk] is returned with the correct status by ParseBlock 135 // and GetBlock. 136 func checkDecidedBlock(t *testing.T, s *State, blk snowman.Block, expectedStatus choices.Status, cached bool) { 137 require := require.New(t) 138 139 require.IsType(&BlockWrapper{}, blk) 140 141 parsedBlk, err := s.ParseBlock(context.Background(), blk.Bytes()) 142 require.NoError(err) 143 require.Equal(blk.ID(), parsedBlk.ID()) 144 require.Equal(blk.Bytes(), parsedBlk.Bytes()) 145 require.Equal(expectedStatus, parsedBlk.Status()) 146 147 // If the block should be in the cache, assert that the returned block is identical to [blk] 148 if cached { 149 require.Equal(blk, parsedBlk) 150 } 151 152 getBlk, err := s.GetBlock(context.Background(), blk.ID()) 153 require.NoError(err) 154 require.Equal(blk.ID(), getBlk.ID()) 155 require.Equal(blk.Bytes(), getBlk.Bytes()) 156 require.Equal(expectedStatus, getBlk.Status()) 157 158 // Since ParseBlock should have triggered a cache hit, assert that the block is identical 159 // to the parsed block. 160 require.Equal(parsedBlk, getBlk) 161 } 162 163 func checkAcceptedBlock(t *testing.T, s *State, blk snowman.Block, cached bool) { 164 checkDecidedBlock(t, s, blk, choices.Accepted, cached) 165 } 166 167 func checkRejectedBlock(t *testing.T, s *State, blk snowman.Block, cached bool) { 168 checkDecidedBlock(t, s, blk, choices.Rejected, cached) 169 } 170 171 func TestState(t *testing.T) { 172 require := require.New(t) 173 174 testBlks := NewTestBlocks(3) 175 genesisBlock := testBlks[0] 176 genesisBlock.SetStatus(choices.Accepted) 177 blk1 := testBlks[1] 178 blk2 := testBlks[2] 179 // Need to create a block with a different bytes and hash here 180 // to generate a conflict with blk2 181 blk3 := snowmantest.BuildChild(blk1) 182 testBlks = append(testBlks, blk3) 183 184 getBlock, parseBlock, getCanonicalBlockID := createInternalBlockFuncs(testBlks) 185 chainState := NewState(&Config{ 186 DecidedCacheSize: 2, 187 MissingCacheSize: 2, 188 UnverifiedCacheSize: 2, 189 BytesToIDCacheSize: 2, 190 LastAcceptedBlock: genesisBlock, 191 GetBlock: getBlock, 192 UnmarshalBlock: parseBlock, 193 BuildBlock: cantBuildBlock, 194 GetBlockIDAtHeight: getCanonicalBlockID, 195 }) 196 197 lastAccepted, err := chainState.LastAccepted(context.Background()) 198 require.NoError(err) 199 require.Equal(genesisBlock.ID(), lastAccepted) 200 201 wrappedGenesisBlk, err := chainState.GetBlock(context.Background(), genesisBlock.ID()) 202 require.NoError(err) 203 204 // Check that a cache miss on a block is handled correctly 205 _, err = chainState.GetBlock(context.Background(), blk1.ID()) 206 require.ErrorIs(err, database.ErrNotFound) 207 208 // Parse and verify blk1 and blk2 209 parsedBlk1, err := chainState.ParseBlock(context.Background(), blk1.Bytes()) 210 require.NoError(err) 211 require.NoError(parsedBlk1.Verify(context.Background())) 212 213 parsedBlk2, err := chainState.ParseBlock(context.Background(), blk2.Bytes()) 214 require.NoError(err) 215 require.NoError(parsedBlk2.Verify(context.Background())) 216 217 // Check that the verified blocks have been placed in the processing map 218 require.Len(chainState.verifiedBlocks, 2) 219 220 parsedBlk3, err := chainState.ParseBlock(context.Background(), blk3.Bytes()) 221 require.NoError(err) 222 getBlk3, err := chainState.GetBlock(context.Background(), blk3.ID()) 223 require.NoError(err) 224 require.Equal(parsedBlk3.ID(), getBlk3.ID(), "State GetBlock returned the wrong block") 225 226 // Check that parsing blk3 does not add it to processing blocks since it has 227 // not been verified. 228 require.Len(chainState.verifiedBlocks, 2) 229 230 require.NoError(parsedBlk3.Verify(context.Background())) 231 // Check that blk3 has been added to processing blocks. 232 require.Len(chainState.verifiedBlocks, 3) 233 234 // Decide the blocks and ensure they are removed from the processing blocks map 235 require.NoError(parsedBlk1.Accept(context.Background())) 236 require.NoError(parsedBlk2.Accept(context.Background())) 237 require.NoError(parsedBlk3.Reject(context.Background())) 238 239 require.Empty(chainState.verifiedBlocks) 240 241 // Check that the last accepted block was updated correctly 242 lastAcceptedID, err := chainState.LastAccepted(context.Background()) 243 require.NoError(err) 244 require.Equal(blk2.ID(), lastAcceptedID) 245 require.Equal(blk2.ID(), chainState.LastAcceptedBlock().ID()) 246 247 // Flush the caches to ensure decided blocks are handled correctly on cache misses. 248 chainState.Flush() 249 checkAcceptedBlock(t, chainState, wrappedGenesisBlk, false) 250 checkAcceptedBlock(t, chainState, parsedBlk1, false) 251 checkAcceptedBlock(t, chainState, parsedBlk2, false) 252 checkRejectedBlock(t, chainState, parsedBlk3, false) 253 } 254 255 func TestBuildBlock(t *testing.T) { 256 require := require.New(t) 257 258 testBlks := NewTestBlocks(2) 259 genesisBlock := testBlks[0] 260 genesisBlock.SetStatus(choices.Accepted) 261 blk1 := testBlks[1] 262 263 getBlock, parseBlock, getCanonicalBlockID := createInternalBlockFuncs(testBlks) 264 buildBlock := func(context.Context) (snowman.Block, error) { 265 // Once the block is built, mark it as processing 266 blk1.SetStatus(choices.Processing) 267 return blk1, nil 268 } 269 270 chainState := NewState(&Config{ 271 DecidedCacheSize: 2, 272 MissingCacheSize: 2, 273 UnverifiedCacheSize: 2, 274 BytesToIDCacheSize: 2, 275 LastAcceptedBlock: genesisBlock, 276 GetBlock: getBlock, 277 UnmarshalBlock: parseBlock, 278 BuildBlock: buildBlock, 279 GetBlockIDAtHeight: getCanonicalBlockID, 280 }) 281 282 builtBlk, err := chainState.BuildBlock(context.Background()) 283 require.NoError(err) 284 require.Empty(chainState.verifiedBlocks) 285 286 require.NoError(builtBlk.Verify(context.Background())) 287 require.Len(chainState.verifiedBlocks, 1) 288 289 checkProcessingBlock(t, chainState, builtBlk) 290 291 require.NoError(builtBlk.Accept(context.Background())) 292 293 checkAcceptedBlock(t, chainState, builtBlk, true) 294 } 295 296 func TestStateDecideBlock(t *testing.T) { 297 require := require.New(t) 298 299 testBlks := NewTestBlocks(4) 300 genesisBlock := testBlks[0] 301 genesisBlock.SetStatus(choices.Accepted) 302 badAcceptBlk := testBlks[1] 303 badAcceptBlk.AcceptV = errAccept 304 badVerifyBlk := testBlks[2] 305 badVerifyBlk.VerifyV = errVerify 306 badRejectBlk := testBlks[3] 307 badRejectBlk.RejectV = errReject 308 getBlock, parseBlock, getCanonicalBlockID := createInternalBlockFuncs(testBlks) 309 chainState := NewState(&Config{ 310 DecidedCacheSize: 2, 311 MissingCacheSize: 2, 312 UnverifiedCacheSize: 2, 313 BytesToIDCacheSize: 2, 314 LastAcceptedBlock: genesisBlock, 315 GetBlock: getBlock, 316 UnmarshalBlock: parseBlock, 317 BuildBlock: cantBuildBlock, 318 GetBlockIDAtHeight: getCanonicalBlockID, 319 }) 320 321 // Parse badVerifyBlk (which should fail verification) 322 badBlk, err := chainState.ParseBlock(context.Background(), badVerifyBlk.Bytes()) 323 require.NoError(err) 324 err = badBlk.Verify(context.Background()) 325 require.ErrorIs(err, errVerify) 326 // Ensure a block that fails verification is not marked as processing 327 require.Empty(chainState.verifiedBlocks) 328 329 // Ensure that an error during block acceptance is propagated correctly 330 badBlk, err = chainState.ParseBlock(context.Background(), badAcceptBlk.Bytes()) 331 require.NoError(err) 332 require.NoError(badBlk.Verify(context.Background())) 333 require.Len(chainState.verifiedBlocks, 1) 334 335 err = badBlk.Accept(context.Background()) 336 require.ErrorIs(err, errAccept) 337 338 // Ensure that an error during block reject is propagated correctly 339 badBlk, err = chainState.ParseBlock(context.Background(), badRejectBlk.Bytes()) 340 require.NoError(err) 341 require.NoError(badBlk.Verify(context.Background())) 342 // Note: an error during block Accept/Reject is fatal, so it is undefined whether 343 // the block that failed on Accept should be removed from processing or not. We allow 344 // either case here to make this test more flexible. 345 numProcessing := len(chainState.verifiedBlocks) 346 require.Contains([]int{1, 2}, numProcessing) 347 348 err = badBlk.Reject(context.Background()) 349 require.ErrorIs(err, errReject) 350 } 351 352 func TestStateParent(t *testing.T) { 353 require := require.New(t) 354 355 testBlks := NewTestBlocks(3) 356 genesisBlock := testBlks[0] 357 genesisBlock.SetStatus(choices.Accepted) 358 blk1 := testBlks[1] 359 blk2 := testBlks[2] 360 361 getBlock, parseBlock, getCanonicalBlockID := createInternalBlockFuncs(testBlks) 362 chainState := NewState(&Config{ 363 DecidedCacheSize: 2, 364 MissingCacheSize: 2, 365 UnverifiedCacheSize: 2, 366 BytesToIDCacheSize: 2, 367 LastAcceptedBlock: genesisBlock, 368 GetBlock: getBlock, 369 UnmarshalBlock: parseBlock, 370 BuildBlock: cantBuildBlock, 371 GetBlockIDAtHeight: getCanonicalBlockID, 372 }) 373 374 parsedBlk2, err := chainState.ParseBlock(context.Background(), blk2.Bytes()) 375 require.NoError(err) 376 377 missingBlk1ID := parsedBlk2.Parent() 378 379 _, err = chainState.GetBlock(context.Background(), missingBlk1ID) 380 require.ErrorIs(err, database.ErrNotFound) 381 382 parsedBlk1, err := chainState.ParseBlock(context.Background(), blk1.Bytes()) 383 require.NoError(err) 384 385 genesisBlkParentID := parsedBlk1.Parent() 386 genesisBlkParent, err := chainState.GetBlock(context.Background(), genesisBlkParentID) 387 require.NoError(err) 388 checkAcceptedBlock(t, chainState, genesisBlkParent, true) 389 390 parentBlk1ID := parsedBlk2.Parent() 391 parentBlk1, err := chainState.GetBlock(context.Background(), parentBlk1ID) 392 require.NoError(err) 393 checkProcessingBlock(t, chainState, parentBlk1) 394 } 395 396 func TestGetBlockInternal(t *testing.T) { 397 require := require.New(t) 398 testBlks := NewTestBlocks(1) 399 genesisBlock := testBlks[0] 400 genesisBlock.SetStatus(choices.Accepted) 401 402 getBlock, parseBlock, getCanonicalBlockID := createInternalBlockFuncs(testBlks) 403 chainState := NewState(&Config{ 404 DecidedCacheSize: 2, 405 MissingCacheSize: 2, 406 UnverifiedCacheSize: 2, 407 BytesToIDCacheSize: 2, 408 LastAcceptedBlock: genesisBlock, 409 GetBlock: getBlock, 410 UnmarshalBlock: parseBlock, 411 BuildBlock: cantBuildBlock, 412 GetBlockIDAtHeight: getCanonicalBlockID, 413 }) 414 415 genesisBlockInternal := chainState.LastAcceptedBlockInternal() 416 require.IsType(&snowmantest.Block{}, genesisBlockInternal) 417 require.Equal(genesisBlock.ID(), genesisBlockInternal.ID()) 418 419 blk, err := chainState.GetBlockInternal(context.Background(), genesisBlock.ID()) 420 require.NoError(err) 421 422 require.IsType(&snowmantest.Block{}, blk) 423 require.Equal(genesisBlock.ID(), blk.ID()) 424 } 425 426 func TestGetBlockError(t *testing.T) { 427 require := require.New(t) 428 429 testBlks := NewTestBlocks(2) 430 genesisBlock := testBlks[0] 431 genesisBlock.SetStatus(choices.Accepted) 432 blk1 := testBlks[1] 433 434 getBlock, parseBlock, getCanonicalBlockID := createInternalBlockFuncs(testBlks) 435 wrappedGetBlock := func(ctx context.Context, id ids.ID) (snowman.Block, error) { 436 blk, err := getBlock(ctx, id) 437 if err != nil { 438 return nil, fmt.Errorf("wrapping error to prevent caching miss: %w", err) 439 } 440 return blk, nil 441 } 442 chainState := NewState(&Config{ 443 DecidedCacheSize: 2, 444 MissingCacheSize: 2, 445 UnverifiedCacheSize: 2, 446 BytesToIDCacheSize: 2, 447 LastAcceptedBlock: genesisBlock, 448 GetBlock: wrappedGetBlock, 449 UnmarshalBlock: parseBlock, 450 BuildBlock: cantBuildBlock, 451 GetBlockIDAtHeight: getCanonicalBlockID, 452 }) 453 454 _, err := chainState.GetBlock(context.Background(), blk1.ID()) 455 require.ErrorIs(err, database.ErrNotFound) 456 457 // Update the status to Processing, so that it will be returned by the internal get block 458 // function. 459 blk1.SetStatus(choices.Processing) 460 blk, err := chainState.GetBlock(context.Background(), blk1.ID()) 461 require.NoError(err) 462 require.Equal(blk1.ID(), blk.ID()) 463 checkProcessingBlock(t, chainState, blk) 464 } 465 466 func TestParseBlockError(t *testing.T) { 467 testBlks := NewTestBlocks(1) 468 genesisBlock := testBlks[0] 469 genesisBlock.SetStatus(choices.Accepted) 470 471 getBlock, parseBlock, getCanonicalBlockID := createInternalBlockFuncs(testBlks) 472 chainState := NewState(&Config{ 473 DecidedCacheSize: 2, 474 MissingCacheSize: 2, 475 UnverifiedCacheSize: 2, 476 BytesToIDCacheSize: 2, 477 LastAcceptedBlock: genesisBlock, 478 GetBlock: getBlock, 479 UnmarshalBlock: parseBlock, 480 BuildBlock: cantBuildBlock, 481 GetBlockIDAtHeight: getCanonicalBlockID, 482 }) 483 484 _, err := chainState.ParseBlock(context.Background(), []byte{255}) 485 require.ErrorIs(t, err, errUnexpectedBlockBytes) 486 } 487 488 func TestBuildBlockError(t *testing.T) { 489 testBlks := NewTestBlocks(1) 490 genesisBlock := testBlks[0] 491 genesisBlock.SetStatus(choices.Accepted) 492 493 getBlock, parseBlock, getCanonicalBlockID := createInternalBlockFuncs(testBlks) 494 chainState := NewState(&Config{ 495 DecidedCacheSize: 2, 496 MissingCacheSize: 2, 497 UnverifiedCacheSize: 2, 498 BytesToIDCacheSize: 2, 499 LastAcceptedBlock: genesisBlock, 500 GetBlock: getBlock, 501 UnmarshalBlock: parseBlock, 502 BuildBlock: cantBuildBlock, 503 GetBlockIDAtHeight: getCanonicalBlockID, 504 }) 505 506 _, err := chainState.BuildBlock(context.Background()) 507 require.ErrorIs(t, err, errCantBuildBlock) 508 } 509 510 func TestMeteredCache(t *testing.T) { 511 require := require.New(t) 512 513 registry := prometheus.NewRegistry() 514 515 testBlks := NewTestBlocks(1) 516 genesisBlock := testBlks[0] 517 genesisBlock.SetStatus(choices.Accepted) 518 519 getBlock, parseBlock, getCanonicalBlockID := createInternalBlockFuncs(testBlks) 520 config := &Config{ 521 DecidedCacheSize: 2, 522 MissingCacheSize: 2, 523 UnverifiedCacheSize: 2, 524 BytesToIDCacheSize: 2, 525 LastAcceptedBlock: genesisBlock, 526 GetBlock: getBlock, 527 UnmarshalBlock: parseBlock, 528 BuildBlock: cantBuildBlock, 529 GetBlockIDAtHeight: getCanonicalBlockID, 530 } 531 _, err := NewMeteredState(registry, config) 532 require.NoError(err) 533 _, err = NewMeteredState(registry, config) 534 require.Error(err) //nolint:forbidigo // error is not exported https://github.com/prometheus/client_golang/blob/main/prometheus/registry.go#L315 535 } 536 537 // Test the bytesToIDCache 538 func TestStateBytesToIDCache(t *testing.T) { 539 require := require.New(t) 540 541 testBlks := NewTestBlocks(3) 542 genesisBlock := testBlks[0] 543 genesisBlock.SetStatus(choices.Accepted) 544 blk1 := testBlks[1] 545 blk2 := testBlks[2] 546 547 getBlock, parseBlock, getCanonicalBlockID := createInternalBlockFuncs(testBlks) 548 buildBlock := func(context.Context) (snowman.Block, error) { 549 require.FailNow("shouldn't have been called") 550 return nil, nil 551 } 552 553 chainState := NewState(&Config{ 554 DecidedCacheSize: 0, 555 MissingCacheSize: 0, 556 UnverifiedCacheSize: 0, 557 BytesToIDCacheSize: 1 + ids.IDLen, // Size of one block 558 LastAcceptedBlock: genesisBlock, 559 GetBlock: getBlock, 560 UnmarshalBlock: parseBlock, 561 BuildBlock: buildBlock, 562 GetBlockIDAtHeight: getCanonicalBlockID, 563 }) 564 565 // Shouldn't have blk1 ID to start with 566 _, err := chainState.GetBlock(context.Background(), blk1.ID()) 567 require.ErrorIs(err, database.ErrNotFound) 568 _, ok := chainState.bytesToIDCache.Get(string(blk1.Bytes())) 569 require.False(ok) 570 571 // Parse blk1 from bytes 572 _, err = chainState.ParseBlock(context.Background(), blk1.Bytes()) 573 require.NoError(err) 574 575 // blk1 should be in cache now 576 _, ok = chainState.bytesToIDCache.Get(string(blk1.Bytes())) 577 require.True(ok) 578 579 // Parse another block 580 _, err = chainState.ParseBlock(context.Background(), blk2.Bytes()) 581 require.NoError(err) 582 583 // Should have bumped blk1 from cache 584 _, ok = chainState.bytesToIDCache.Get(string(blk2.Bytes())) 585 require.True(ok) 586 _, ok = chainState.bytesToIDCache.Get(string(blk1.Bytes())) 587 require.False(ok) 588 } 589 590 // TestSetLastAcceptedBlock ensures chainState's last accepted block 591 // can be updated by calling [SetLastAcceptedBlock]. 592 func TestSetLastAcceptedBlock(t *testing.T) { 593 require := require.New(t) 594 595 testBlks := NewTestBlocks(1) 596 genesisBlock := testBlks[0] 597 genesisBlock.SetStatus(choices.Accepted) 598 599 postSetBlk1ParentID := hashing.ComputeHash256Array([]byte{byte(199)}) 600 postSetBlk1 := NewTestBlock(200, postSetBlk1ParentID) 601 postSetBlk2 := NewTestBlock(201, postSetBlk1.ID()) 602 603 // note we do not need to parse postSetBlk1 so it is omitted here 604 testBlks = append(testBlks, postSetBlk2) 605 606 getBlock, parseBlock, getCanonicalBlockID := createInternalBlockFuncs(testBlks) 607 chainState := NewState(&Config{ 608 LastAcceptedBlock: genesisBlock, 609 GetBlock: getBlock, 610 UnmarshalBlock: parseBlock, 611 BuildBlock: cantBuildBlock, 612 GetBlockIDAtHeight: getCanonicalBlockID, 613 }) 614 lastAcceptedID, err := chainState.LastAccepted(context.Background()) 615 require.NoError(err) 616 require.Equal(genesisBlock.ID(), lastAcceptedID) 617 618 // call SetLastAcceptedBlock for postSetBlk1 619 require.NoError(chainState.SetLastAcceptedBlock(postSetBlk1)) 620 lastAcceptedID, err = chainState.LastAccepted(context.Background()) 621 require.NoError(err) 622 require.Equal(postSetBlk1.ID(), lastAcceptedID) 623 require.Equal(postSetBlk1.ID(), chainState.LastAcceptedBlock().ID()) 624 625 // ensure further blocks can be accepted 626 parsedpostSetBlk2, err := chainState.ParseBlock(context.Background(), postSetBlk2.Bytes()) 627 require.NoError(err) 628 require.NoError(parsedpostSetBlk2.Verify(context.Background())) 629 require.NoError(parsedpostSetBlk2.Accept(context.Background())) 630 lastAcceptedID, err = chainState.LastAccepted(context.Background()) 631 require.NoError(err) 632 require.Equal(postSetBlk2.ID(), lastAcceptedID) 633 require.Equal(postSetBlk2.ID(), chainState.LastAcceptedBlock().ID()) 634 635 checkAcceptedBlock(t, chainState, parsedpostSetBlk2, false) 636 } 637 638 func TestSetLastAcceptedBlockWithProcessingBlocksErrors(t *testing.T) { 639 require := require.New(t) 640 641 testBlks := NewTestBlocks(5) 642 genesisBlock := testBlks[0] 643 genesisBlock.SetStatus(choices.Accepted) 644 blk1 := testBlks[1] 645 resetBlk := testBlks[4] 646 647 getBlock, parseBlock, getCanonicalBlockID := createInternalBlockFuncs(testBlks) 648 buildBlock := func(context.Context) (snowman.Block, error) { 649 // Once the block is built, mark it as processing 650 blk1.SetStatus(choices.Processing) 651 return blk1, nil 652 } 653 654 chainState := NewState(&Config{ 655 DecidedCacheSize: 2, 656 MissingCacheSize: 2, 657 UnverifiedCacheSize: 2, 658 BytesToIDCacheSize: 2, 659 LastAcceptedBlock: genesisBlock, 660 GetBlock: getBlock, 661 UnmarshalBlock: parseBlock, 662 BuildBlock: buildBlock, 663 GetBlockIDAtHeight: getCanonicalBlockID, 664 }) 665 666 builtBlk, err := chainState.BuildBlock(context.Background()) 667 require.NoError(err) 668 require.Empty(chainState.verifiedBlocks) 669 670 require.NoError(builtBlk.Verify(context.Background())) 671 require.Len(chainState.verifiedBlocks, 1) 672 673 checkProcessingBlock(t, chainState, builtBlk) 674 675 err = chainState.SetLastAcceptedBlock(resetBlk) 676 require.ErrorIs(err, errSetAcceptedWithProcessing) 677 } 678 679 func TestStateParseTransitivelyAcceptedBlock(t *testing.T) { 680 require := require.New(t) 681 682 testBlks := NewTestBlocks(3) 683 genesisBlock := testBlks[0] 684 genesisBlock.SetStatus(choices.Accepted) 685 blk1 := testBlks[1] 686 blk2 := testBlks[2] 687 blk2.SetStatus(choices.Accepted) 688 689 getBlock, parseBlock, getCanonicalBlockID := createInternalBlockFuncs(testBlks) 690 chainState := NewState(&Config{ 691 DecidedCacheSize: 2, 692 MissingCacheSize: 2, 693 UnverifiedCacheSize: 2, 694 BytesToIDCacheSize: 2, 695 LastAcceptedBlock: blk2, 696 GetBlock: getBlock, 697 UnmarshalBlock: parseBlock, 698 BuildBlock: cantBuildBlock, 699 GetBlockIDAtHeight: getCanonicalBlockID, 700 }) 701 702 parsedBlk1, err := chainState.ParseBlock(context.Background(), blk1.Bytes()) 703 require.NoError(err) 704 require.Equal(blk1.Height(), parsedBlk1.Height()) 705 } 706 707 func TestIsProcessing(t *testing.T) { 708 require := require.New(t) 709 710 testBlks := NewTestBlocks(2) 711 genesisBlock := testBlks[0] 712 genesisBlock.SetStatus(choices.Accepted) 713 blk1 := testBlks[1] 714 715 getBlock, parseBlock, getCanonicalBlockID := createInternalBlockFuncs(testBlks) 716 chainState := NewState(&Config{ 717 DecidedCacheSize: 2, 718 MissingCacheSize: 2, 719 UnverifiedCacheSize: 2, 720 BytesToIDCacheSize: 2, 721 LastAcceptedBlock: genesisBlock, 722 GetBlock: getBlock, 723 UnmarshalBlock: parseBlock, 724 BuildBlock: cantBuildBlock, 725 GetBlockIDAtHeight: getCanonicalBlockID, 726 }) 727 728 // Parse blk1 729 parsedBlk1, err := chainState.ParseBlock(context.Background(), blk1.Bytes()) 730 require.NoError(err) 731 732 // Check that it is not processing in consensus 733 require.False(chainState.IsProcessing(parsedBlk1.ID())) 734 735 // Verify blk1 736 require.NoError(parsedBlk1.Verify(context.Background())) 737 738 // Check that it is processing in consensus 739 require.True(chainState.IsProcessing(parsedBlk1.ID())) 740 741 // Accept blk1 742 require.NoError(parsedBlk1.Accept(context.Background())) 743 744 // Check that it is no longer processing in consensus 745 require.False(chainState.IsProcessing(parsedBlk1.ID())) 746 }