github.com/MetalBlockchain/metalgo@v1.11.9/snow/engine/snowman/bootstrap/bootstrapper_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 bootstrap 5 6 import ( 7 "bytes" 8 "context" 9 "errors" 10 "testing" 11 "time" 12 13 "github.com/prometheus/client_golang/prometheus" 14 "github.com/stretchr/testify/require" 15 16 "github.com/MetalBlockchain/metalgo/database" 17 "github.com/MetalBlockchain/metalgo/database/memdb" 18 "github.com/MetalBlockchain/metalgo/ids" 19 "github.com/MetalBlockchain/metalgo/network/p2p" 20 "github.com/MetalBlockchain/metalgo/snow" 21 "github.com/MetalBlockchain/metalgo/snow/choices" 22 "github.com/MetalBlockchain/metalgo/snow/consensus/snowman" 23 "github.com/MetalBlockchain/metalgo/snow/consensus/snowman/snowmantest" 24 "github.com/MetalBlockchain/metalgo/snow/engine/common" 25 "github.com/MetalBlockchain/metalgo/snow/engine/common/tracker" 26 "github.com/MetalBlockchain/metalgo/snow/engine/snowman/block" 27 "github.com/MetalBlockchain/metalgo/snow/engine/snowman/bootstrap/interval" 28 "github.com/MetalBlockchain/metalgo/snow/engine/snowman/getter" 29 "github.com/MetalBlockchain/metalgo/snow/snowtest" 30 "github.com/MetalBlockchain/metalgo/snow/validators" 31 "github.com/MetalBlockchain/metalgo/utils/set" 32 "github.com/MetalBlockchain/metalgo/version" 33 34 p2ppb "github.com/MetalBlockchain/metalgo/proto/pb/p2p" 35 ) 36 37 var errUnknownBlock = errors.New("unknown block") 38 39 func newConfig(t *testing.T) (Config, ids.NodeID, *common.SenderTest, *block.TestVM) { 40 require := require.New(t) 41 42 snowCtx := snowtest.Context(t, snowtest.CChainID) 43 ctx := snowtest.ConsensusContext(snowCtx) 44 45 vdrs := validators.NewManager() 46 47 sender := &common.SenderTest{} 48 vm := &block.TestVM{} 49 50 sender.T = t 51 vm.T = t 52 53 sender.Default(true) 54 vm.Default(true) 55 56 isBootstrapped := false 57 bootstrapTracker := &common.BootstrapTrackerTest{ 58 T: t, 59 IsBootstrappedF: func() bool { 60 return isBootstrapped 61 }, 62 BootstrappedF: func(ids.ID) { 63 isBootstrapped = true 64 }, 65 } 66 67 sender.CantSendGetAcceptedFrontier = false 68 69 peer := ids.GenerateTestNodeID() 70 require.NoError(vdrs.AddStaker(ctx.SubnetID, peer, nil, ids.Empty, 1)) 71 72 totalWeight, err := vdrs.TotalWeight(ctx.SubnetID) 73 require.NoError(err) 74 startupTracker := tracker.NewStartup(tracker.NewPeers(), totalWeight/2+1) 75 vdrs.RegisterSetCallbackListener(ctx.SubnetID, startupTracker) 76 77 require.NoError(startupTracker.Connected(context.Background(), peer, version.CurrentApp)) 78 79 snowGetHandler, err := getter.New(vm, sender, ctx.Log, time.Second, 2000, ctx.Registerer) 80 require.NoError(err) 81 82 peerTracker, err := p2p.NewPeerTracker( 83 ctx.Log, 84 "", 85 prometheus.NewRegistry(), 86 nil, 87 nil, 88 ) 89 require.NoError(err) 90 91 peerTracker.Connected(peer, version.CurrentApp) 92 93 return Config{ 94 AllGetsServer: snowGetHandler, 95 Ctx: ctx, 96 Beacons: vdrs, 97 SampleK: vdrs.Count(ctx.SubnetID), 98 StartupTracker: startupTracker, 99 PeerTracker: peerTracker, 100 Sender: sender, 101 BootstrapTracker: bootstrapTracker, 102 Timer: &common.TimerTest{}, 103 AncestorsMaxContainersReceived: 2000, 104 DB: memdb.New(), 105 VM: vm, 106 }, peer, sender, vm 107 } 108 109 func TestBootstrapperStartsOnlyIfEnoughStakeIsConnected(t *testing.T) { 110 require := require.New(t) 111 112 sender := &common.SenderTest{T: t} 113 vm := &block.TestVM{ 114 TestVM: common.TestVM{T: t}, 115 } 116 117 sender.Default(true) 118 vm.Default(true) 119 snowCtx := snowtest.Context(t, snowtest.CChainID) 120 ctx := snowtest.ConsensusContext(snowCtx) 121 // create boostrapper configuration 122 peers := validators.NewManager() 123 sampleK := 2 124 alpha := uint64(10) 125 startupAlpha := alpha 126 127 startupTracker := tracker.NewStartup(tracker.NewPeers(), startupAlpha) 128 peers.RegisterSetCallbackListener(ctx.SubnetID, startupTracker) 129 130 snowGetHandler, err := getter.New(vm, sender, ctx.Log, time.Second, 2000, ctx.Registerer) 131 require.NoError(err) 132 133 peerTracker, err := p2p.NewPeerTracker( 134 ctx.Log, 135 "", 136 prometheus.NewRegistry(), 137 nil, 138 nil, 139 ) 140 require.NoError(err) 141 142 cfg := Config{ 143 AllGetsServer: snowGetHandler, 144 Ctx: ctx, 145 Beacons: peers, 146 SampleK: sampleK, 147 StartupTracker: startupTracker, 148 PeerTracker: peerTracker, 149 Sender: sender, 150 BootstrapTracker: &common.BootstrapTrackerTest{}, 151 Timer: &common.TimerTest{}, 152 AncestorsMaxContainersReceived: 2000, 153 DB: memdb.New(), 154 VM: vm, 155 } 156 157 vm.CantLastAccepted = false 158 vm.LastAcceptedF = func(context.Context) (ids.ID, error) { 159 return snowmantest.GenesisID, nil 160 } 161 vm.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { 162 require.Equal(snowmantest.GenesisID, blkID) 163 return snowmantest.Genesis, nil 164 } 165 166 // create bootstrapper 167 dummyCallback := func(context.Context, uint32) error { 168 cfg.Ctx.State.Set(snow.EngineState{ 169 Type: p2ppb.EngineType_ENGINE_TYPE_SNOWMAN, 170 State: snow.NormalOp, 171 }) 172 return nil 173 } 174 bs, err := New(cfg, dummyCallback) 175 require.NoError(err) 176 177 vm.CantSetState = false 178 vm.CantConnected = true 179 vm.ConnectedF = func(context.Context, ids.NodeID, *version.Application) error { 180 return nil 181 } 182 183 frontierRequested := false 184 sender.CantSendGetAcceptedFrontier = false 185 sender.SendGetAcceptedFrontierF = func(context.Context, set.Set[ids.NodeID], uint32) { 186 frontierRequested = true 187 } 188 189 // attempt starting bootstrapper with no stake connected. Bootstrapper should stall. 190 require.NoError(bs.Start(context.Background(), 0)) 191 require.False(frontierRequested) 192 193 // attempt starting bootstrapper with not enough stake connected. Bootstrapper should stall. 194 vdr0 := ids.GenerateTestNodeID() 195 require.NoError(peers.AddStaker(ctx.SubnetID, vdr0, nil, ids.Empty, startupAlpha/2)) 196 197 peerTracker.Connected(vdr0, version.CurrentApp) 198 require.NoError(bs.Connected(context.Background(), vdr0, version.CurrentApp)) 199 200 require.NoError(bs.Start(context.Background(), 0)) 201 require.False(frontierRequested) 202 203 // finally attempt starting bootstrapper with enough stake connected. Frontiers should be requested. 204 vdr := ids.GenerateTestNodeID() 205 require.NoError(peers.AddStaker(ctx.SubnetID, vdr, nil, ids.Empty, startupAlpha)) 206 207 peerTracker.Connected(vdr, version.CurrentApp) 208 require.NoError(bs.Connected(context.Background(), vdr, version.CurrentApp)) 209 require.True(frontierRequested) 210 } 211 212 // Single node in the accepted frontier; no need to fetch parent 213 func TestBootstrapperSingleFrontier(t *testing.T) { 214 require := require.New(t) 215 216 config, _, _, vm := newConfig(t) 217 218 blks := snowmantest.BuildChain(1) 219 initializeVMWithBlockchain(vm, blks) 220 221 bs, err := New( 222 config, 223 func(context.Context, uint32) error { 224 config.Ctx.State.Set(snow.EngineState{ 225 Type: p2ppb.EngineType_ENGINE_TYPE_SNOWMAN, 226 State: snow.NormalOp, 227 }) 228 return nil 229 }, 230 ) 231 require.NoError(err) 232 233 require.NoError(bs.Start(context.Background(), 0)) 234 235 require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[0:1]))) 236 require.Equal(snow.NormalOp, config.Ctx.State.Get().State) 237 } 238 239 // Requests the unknown block and gets back a Ancestors with unexpected block. 240 // Requests again and gets the expected block. 241 func TestBootstrapperUnknownByzantineResponse(t *testing.T) { 242 require := require.New(t) 243 244 config, peerID, sender, vm := newConfig(t) 245 246 blks := snowmantest.BuildChain(2) 247 initializeVMWithBlockchain(vm, blks) 248 249 bs, err := New( 250 config, 251 func(context.Context, uint32) error { 252 config.Ctx.State.Set(snow.EngineState{ 253 Type: p2ppb.EngineType_ENGINE_TYPE_SNOWMAN, 254 State: snow.NormalOp, 255 }) 256 return nil 257 }, 258 ) 259 require.NoError(err) 260 261 require.NoError(bs.Start(context.Background(), 0)) 262 263 var requestID uint32 264 sender.SendGetAncestorsF = func(_ context.Context, nodeID ids.NodeID, reqID uint32, blkID ids.ID) { 265 require.Equal(peerID, nodeID) 266 require.Equal(blks[1].ID(), blkID) 267 requestID = reqID 268 } 269 270 require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[1:2]))) // should request blk1 271 272 oldReqID := requestID 273 require.NoError(bs.Ancestors(context.Background(), peerID, requestID, blocksToBytes(blks[0:1]))) // respond with wrong block 274 require.NotEqual(oldReqID, requestID) 275 276 require.NoError(bs.Ancestors(context.Background(), peerID, requestID, blocksToBytes(blks[1:2]))) 277 278 require.Equal(snow.Bootstrapping, config.Ctx.State.Get().State) 279 requireStatusIs(require, blks, choices.Accepted) 280 281 require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[1:2]))) 282 require.Equal(snow.NormalOp, config.Ctx.State.Get().State) 283 } 284 285 // There are multiple needed blocks and multiple Ancestors are required 286 func TestBootstrapperPartialFetch(t *testing.T) { 287 require := require.New(t) 288 289 config, peerID, sender, vm := newConfig(t) 290 291 blks := snowmantest.BuildChain(4) 292 initializeVMWithBlockchain(vm, blks) 293 294 bs, err := New( 295 config, 296 func(context.Context, uint32) error { 297 config.Ctx.State.Set(snow.EngineState{ 298 Type: p2ppb.EngineType_ENGINE_TYPE_SNOWMAN, 299 State: snow.NormalOp, 300 }) 301 return nil 302 }, 303 ) 304 require.NoError(err) 305 306 require.NoError(bs.Start(context.Background(), 0)) 307 308 var ( 309 requestID uint32 310 requested ids.ID 311 ) 312 sender.SendGetAncestorsF = func(_ context.Context, nodeID ids.NodeID, reqID uint32, blkID ids.ID) { 313 require.Equal(peerID, nodeID) 314 require.Contains([]ids.ID{blks[1].ID(), blks[3].ID()}, blkID) 315 requestID = reqID 316 requested = blkID 317 } 318 319 require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[3:4]))) // should request blk3 320 require.Equal(blks[3].ID(), requested) 321 322 require.NoError(bs.Ancestors(context.Background(), peerID, requestID, blocksToBytes(blks[2:4]))) // respond with blk3 and blk2 323 require.Equal(blks[1].ID(), requested) 324 325 require.NoError(bs.Ancestors(context.Background(), peerID, requestID, blocksToBytes(blks[1:2]))) // respond with blk1 326 327 require.Equal(snow.Bootstrapping, config.Ctx.State.Get().State) 328 requireStatusIs(require, blks, choices.Accepted) 329 330 require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[3:4]))) 331 require.Equal(snow.NormalOp, config.Ctx.State.Get().State) 332 } 333 334 // There are multiple needed blocks and some validators do not have all the 335 // blocks. 336 func TestBootstrapperEmptyResponse(t *testing.T) { 337 require := require.New(t) 338 339 config, peerID, sender, vm := newConfig(t) 340 341 blks := snowmantest.BuildChain(2) 342 initializeVMWithBlockchain(vm, blks) 343 344 bs, err := New( 345 config, 346 func(context.Context, uint32) error { 347 config.Ctx.State.Set(snow.EngineState{ 348 Type: p2ppb.EngineType_ENGINE_TYPE_SNOWMAN, 349 State: snow.NormalOp, 350 }) 351 return nil 352 }, 353 ) 354 require.NoError(err) 355 356 require.NoError(bs.Start(context.Background(), 0)) 357 358 var ( 359 requestedNodeID ids.NodeID 360 requestID uint32 361 ) 362 sender.SendGetAncestorsF = func(_ context.Context, nodeID ids.NodeID, reqID uint32, blkID ids.ID) { 363 require.Equal(blks[1].ID(), blkID) 364 requestedNodeID = nodeID 365 requestID = reqID 366 } 367 368 require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[1:2]))) 369 require.Equal(requestedNodeID, peerID) 370 371 // Add another peer to allow a new node to be selected. A new node should be 372 // sampled if the prior response was empty. 373 bs.PeerTracker.Connected(ids.GenerateTestNodeID(), version.CurrentApp) 374 375 require.NoError(bs.Ancestors(context.Background(), requestedNodeID, requestID, nil)) // respond with empty 376 require.NotEqual(requestedNodeID, peerID) 377 378 require.NoError(bs.Ancestors(context.Background(), requestedNodeID, requestID, blocksToBytes(blks[1:2]))) 379 require.Equal(snow.Bootstrapping, config.Ctx.State.Get().State) 380 requireStatusIs(require, blks, choices.Accepted) 381 } 382 383 // There are multiple needed blocks and Ancestors returns all at once 384 func TestBootstrapperAncestors(t *testing.T) { 385 require := require.New(t) 386 387 config, peerID, sender, vm := newConfig(t) 388 389 blks := snowmantest.BuildChain(4) 390 initializeVMWithBlockchain(vm, blks) 391 392 bs, err := New( 393 config, 394 func(context.Context, uint32) error { 395 config.Ctx.State.Set(snow.EngineState{ 396 Type: p2ppb.EngineType_ENGINE_TYPE_SNOWMAN, 397 State: snow.NormalOp, 398 }) 399 return nil 400 }, 401 ) 402 require.NoError(err) 403 404 require.NoError(bs.Start(context.Background(), 0)) 405 406 var ( 407 requestID uint32 408 requested ids.ID 409 ) 410 sender.SendGetAncestorsF = func(_ context.Context, nodeID ids.NodeID, reqID uint32, blkID ids.ID) { 411 require.Equal(peerID, nodeID) 412 require.Equal(blks[3].ID(), blkID) 413 requestID = reqID 414 requested = blkID 415 } 416 417 require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[3:4]))) // should request blk3 418 require.Equal(blks[3].ID(), requested) 419 420 require.NoError(bs.Ancestors(context.Background(), peerID, requestID, blocksToBytes(blks))) // respond with all the blocks 421 422 require.Equal(snow.Bootstrapping, config.Ctx.State.Get().State) 423 requireStatusIs(require, blks, choices.Accepted) 424 425 require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[3:4]))) 426 require.Equal(snow.NormalOp, config.Ctx.State.Get().State) 427 } 428 429 func TestBootstrapperFinalized(t *testing.T) { 430 require := require.New(t) 431 432 config, peerID, sender, vm := newConfig(t) 433 434 blks := snowmantest.BuildChain(3) 435 initializeVMWithBlockchain(vm, blks) 436 437 bs, err := New( 438 config, 439 func(context.Context, uint32) error { 440 config.Ctx.State.Set(snow.EngineState{ 441 Type: p2ppb.EngineType_ENGINE_TYPE_SNOWMAN, 442 State: snow.NormalOp, 443 }) 444 return nil 445 }, 446 ) 447 require.NoError(err) 448 449 require.NoError(bs.Start(context.Background(), 0)) 450 451 requestIDs := map[ids.ID]uint32{} 452 sender.SendGetAncestorsF = func(_ context.Context, nodeID ids.NodeID, reqID uint32, blkID ids.ID) { 453 require.Equal(peerID, nodeID) 454 requestIDs[blkID] = reqID 455 } 456 457 require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[1:3]))) // should request blk1 and blk2 458 459 reqIDBlk2, ok := requestIDs[blks[2].ID()] 460 require.True(ok) 461 462 require.NoError(bs.Ancestors(context.Background(), peerID, reqIDBlk2, blocksToBytes(blks[1:3]))) 463 464 require.Equal(snow.Bootstrapping, config.Ctx.State.Get().State) 465 requireStatusIs(require, blks, choices.Accepted) 466 467 require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[2:3]))) 468 require.Equal(snow.NormalOp, config.Ctx.State.Get().State) 469 } 470 471 func TestRestartBootstrapping(t *testing.T) { 472 require := require.New(t) 473 474 config, peerID, sender, vm := newConfig(t) 475 476 blks := snowmantest.BuildChain(5) 477 initializeVMWithBlockchain(vm, blks) 478 479 bs, err := New( 480 config, 481 func(context.Context, uint32) error { 482 config.Ctx.State.Set(snow.EngineState{ 483 Type: p2ppb.EngineType_ENGINE_TYPE_SNOWMAN, 484 State: snow.NormalOp, 485 }) 486 return nil 487 }, 488 ) 489 require.NoError(err) 490 491 require.NoError(bs.Start(context.Background(), 0)) 492 493 requestIDs := map[ids.ID]uint32{} 494 sender.SendGetAncestorsF = func(_ context.Context, nodeID ids.NodeID, reqID uint32, blkID ids.ID) { 495 require.Equal(peerID, nodeID) 496 requestIDs[blkID] = reqID 497 } 498 499 require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[3:4]))) // should request blk3 500 501 reqID, ok := requestIDs[blks[3].ID()] 502 require.True(ok) 503 504 require.NoError(bs.Ancestors(context.Background(), peerID, reqID, blocksToBytes(blks[2:4]))) 505 require.Contains(requestIDs, blks[1].ID()) 506 507 // Remove request, so we can restart bootstrapping via startSyncing 508 _, removed := bs.outstandingRequests.DeleteValue(blks[1].ID()) 509 require.True(removed) 510 clear(requestIDs) 511 512 require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[4:5]))) 513 514 blk1RequestID, ok := requestIDs[blks[1].ID()] 515 require.True(ok) 516 blk4RequestID, ok := requestIDs[blks[4].ID()] 517 require.True(ok) 518 519 require.NoError(bs.Ancestors(context.Background(), peerID, blk1RequestID, blocksToBytes(blks[1:2]))) 520 require.Equal(snow.Bootstrapping, config.Ctx.State.Get().State) 521 require.Equal(choices.Accepted, blks[0].Status()) 522 requireStatusIs(require, blks[1:], choices.Processing) 523 524 require.NoError(bs.Ancestors(context.Background(), peerID, blk4RequestID, blocksToBytes(blks[4:5]))) 525 require.Equal(snow.Bootstrapping, config.Ctx.State.Get().State) 526 requireStatusIs(require, blks, choices.Accepted) 527 528 require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[4:5]))) 529 require.Equal(snow.NormalOp, config.Ctx.State.Get().State) 530 } 531 532 func TestBootstrapOldBlockAfterStateSync(t *testing.T) { 533 require := require.New(t) 534 535 config, peerID, sender, vm := newConfig(t) 536 537 blks := snowmantest.BuildChain(2) 538 initializeVMWithBlockchain(vm, blks) 539 540 blks[0].StatusV = choices.Processing 541 require.NoError(blks[1].Accept(context.Background())) 542 543 bs, err := New( 544 config, 545 func(context.Context, uint32) error { 546 config.Ctx.State.Set(snow.EngineState{ 547 Type: p2ppb.EngineType_ENGINE_TYPE_SNOWMAN, 548 State: snow.NormalOp, 549 }) 550 return nil 551 }, 552 ) 553 require.NoError(err) 554 555 require.NoError(bs.Start(context.Background(), 0)) 556 557 requestIDs := map[ids.ID]uint32{} 558 sender.SendGetAncestorsF = func(_ context.Context, nodeID ids.NodeID, reqID uint32, blkID ids.ID) { 559 require.Equal(peerID, nodeID) 560 requestIDs[blkID] = reqID 561 } 562 563 // Force Accept, the already transitively accepted, blk0 564 require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[0:1]))) // should request blk0 565 566 reqID, ok := requestIDs[blks[0].ID()] 567 require.True(ok) 568 569 require.NoError(bs.Ancestors(context.Background(), peerID, reqID, blocksToBytes(blks[0:1]))) 570 require.Equal(snow.NormalOp, config.Ctx.State.Get().State) 571 require.Equal(choices.Processing, blks[0].Status()) 572 require.Equal(choices.Accepted, blks[1].Status()) 573 } 574 575 func TestBootstrapContinueAfterHalt(t *testing.T) { 576 require := require.New(t) 577 578 config, _, _, vm := newConfig(t) 579 580 blks := snowmantest.BuildChain(2) 581 initializeVMWithBlockchain(vm, blks) 582 583 bs, err := New( 584 config, 585 func(context.Context, uint32) error { 586 config.Ctx.State.Set(snow.EngineState{ 587 Type: p2ppb.EngineType_ENGINE_TYPE_SNOWMAN, 588 State: snow.NormalOp, 589 }) 590 return nil 591 }, 592 ) 593 require.NoError(err) 594 595 getBlockF := vm.GetBlockF 596 vm.GetBlockF = func(ctx context.Context, blkID ids.ID) (snowman.Block, error) { 597 bs.Halt(ctx) 598 return getBlockF(ctx, blkID) 599 } 600 601 require.NoError(bs.Start(context.Background(), 0)) 602 603 require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[1:2]))) 604 require.Equal(1, bs.missingBlockIDs.Len()) 605 } 606 607 func TestBootstrapNoParseOnNew(t *testing.T) { 608 require := require.New(t) 609 610 snowCtx := snowtest.Context(t, snowtest.CChainID) 611 ctx := snowtest.ConsensusContext(snowCtx) 612 peers := validators.NewManager() 613 614 sender := &common.SenderTest{} 615 vm := &block.TestVM{} 616 617 sender.T = t 618 vm.T = t 619 620 sender.Default(true) 621 vm.Default(true) 622 623 isBootstrapped := false 624 bootstrapTracker := &common.BootstrapTrackerTest{ 625 T: t, 626 IsBootstrappedF: func() bool { 627 return isBootstrapped 628 }, 629 BootstrappedF: func(ids.ID) { 630 isBootstrapped = true 631 }, 632 } 633 634 sender.CantSendGetAcceptedFrontier = false 635 636 peer := ids.GenerateTestNodeID() 637 require.NoError(peers.AddStaker(ctx.SubnetID, peer, nil, ids.Empty, 1)) 638 639 totalWeight, err := peers.TotalWeight(ctx.SubnetID) 640 require.NoError(err) 641 startupTracker := tracker.NewStartup(tracker.NewPeers(), totalWeight/2+1) 642 peers.RegisterSetCallbackListener(ctx.SubnetID, startupTracker) 643 require.NoError(startupTracker.Connected(context.Background(), peer, version.CurrentApp)) 644 645 snowGetHandler, err := getter.New(vm, sender, ctx.Log, time.Second, 2000, ctx.Registerer) 646 require.NoError(err) 647 648 blk1 := snowmantest.BuildChild(snowmantest.Genesis) 649 650 vm.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { 651 require.Equal(snowmantest.GenesisID, blkID) 652 return snowmantest.Genesis, nil 653 } 654 655 intervalDB := memdb.New() 656 tree, err := interval.NewTree(intervalDB) 657 require.NoError(err) 658 _, err = interval.Add(intervalDB, tree, 0, blk1.Height(), blk1.Bytes()) 659 require.NoError(err) 660 661 vm.GetBlockF = nil 662 663 peerTracker, err := p2p.NewPeerTracker( 664 ctx.Log, 665 "", 666 prometheus.NewRegistry(), 667 nil, 668 nil, 669 ) 670 require.NoError(err) 671 672 peerTracker.Connected(peer, version.CurrentApp) 673 674 config := Config{ 675 AllGetsServer: snowGetHandler, 676 Ctx: ctx, 677 Beacons: peers, 678 SampleK: peers.Count(ctx.SubnetID), 679 StartupTracker: startupTracker, 680 PeerTracker: peerTracker, 681 Sender: sender, 682 BootstrapTracker: bootstrapTracker, 683 Timer: &common.TimerTest{}, 684 AncestorsMaxContainersReceived: 2000, 685 DB: intervalDB, 686 VM: vm, 687 } 688 689 _, err = New( 690 config, 691 func(context.Context, uint32) error { 692 config.Ctx.State.Set(snow.EngineState{ 693 Type: p2ppb.EngineType_ENGINE_TYPE_SNOWMAN, 694 State: snow.NormalOp, 695 }) 696 return nil 697 }, 698 ) 699 require.NoError(err) 700 } 701 702 func TestBootstrapperReceiveStaleAncestorsMessage(t *testing.T) { 703 require := require.New(t) 704 705 config, peerID, sender, vm := newConfig(t) 706 707 blks := snowmantest.BuildChain(3) 708 initializeVMWithBlockchain(vm, blks) 709 710 bs, err := New( 711 config, 712 func(context.Context, uint32) error { 713 config.Ctx.State.Set(snow.EngineState{ 714 Type: p2ppb.EngineType_ENGINE_TYPE_SNOWMAN, 715 State: snow.NormalOp, 716 }) 717 return nil 718 }, 719 ) 720 require.NoError(err) 721 722 require.NoError(bs.Start(context.Background(), 0)) 723 724 requestIDs := map[ids.ID]uint32{} 725 sender.SendGetAncestorsF = func(_ context.Context, nodeID ids.NodeID, reqID uint32, blkID ids.ID) { 726 require.Equal(peerID, nodeID) 727 requestIDs[blkID] = reqID 728 } 729 730 require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[1:3]))) // should request blk1 and blk2 731 732 reqIDBlk1, ok := requestIDs[blks[1].ID()] 733 require.True(ok) 734 reqIDBlk2, ok := requestIDs[blks[2].ID()] 735 require.True(ok) 736 737 require.NoError(bs.Ancestors(context.Background(), peerID, reqIDBlk2, blocksToBytes(blks[1:3]))) 738 require.Equal(snow.Bootstrapping, config.Ctx.State.Get().State) 739 requireStatusIs(require, blks, choices.Accepted) 740 741 require.NoError(bs.Ancestors(context.Background(), peerID, reqIDBlk1, blocksToBytes(blks[1:2]))) 742 require.Equal(snow.Bootstrapping, config.Ctx.State.Get().State) 743 } 744 745 func TestBootstrapperRollbackOnSetState(t *testing.T) { 746 require := require.New(t) 747 748 config, _, _, vm := newConfig(t) 749 750 blks := snowmantest.BuildChain(2) 751 initializeVMWithBlockchain(vm, blks) 752 753 blks[1].StatusV = choices.Accepted 754 755 bs, err := New( 756 config, 757 func(context.Context, uint32) error { 758 config.Ctx.State.Set(snow.EngineState{ 759 Type: p2ppb.EngineType_ENGINE_TYPE_SNOWMAN, 760 State: snow.NormalOp, 761 }) 762 return nil 763 }, 764 ) 765 require.NoError(err) 766 767 vm.SetStateF = func(context.Context, snow.State) error { 768 blks[1].StatusV = choices.Processing 769 return nil 770 } 771 772 require.NoError(bs.Start(context.Background(), 0)) 773 require.Equal(blks[0].HeightV, bs.startingHeight) 774 } 775 776 func initializeVMWithBlockchain(vm *block.TestVM, blocks []*snowmantest.Block) { 777 vm.CantSetState = false 778 vm.LastAcceptedF = func(context.Context) (ids.ID, error) { 779 var ( 780 lastAcceptedID ids.ID 781 lastAcceptedHeight uint64 782 ) 783 for _, blk := range blocks { 784 height := blk.Height() 785 if blk.Status() == choices.Accepted && height >= lastAcceptedHeight { 786 lastAcceptedID = blk.ID() 787 lastAcceptedHeight = height 788 } 789 } 790 return lastAcceptedID, nil 791 } 792 vm.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { 793 for _, blk := range blocks { 794 if blk.Status() == choices.Accepted && blk.ID() == blkID { 795 return blk, nil 796 } 797 } 798 return nil, database.ErrNotFound 799 } 800 vm.ParseBlockF = func(_ context.Context, blkBytes []byte) (snowman.Block, error) { 801 for _, blk := range blocks { 802 if bytes.Equal(blk.Bytes(), blkBytes) { 803 return blk, nil 804 } 805 } 806 return nil, errUnknownBlock 807 } 808 } 809 810 func requireStatusIs(require *require.Assertions, blocks []*snowmantest.Block, status choices.Status) { 811 for i, blk := range blocks { 812 require.Equal(status, blk.Status(), i) 813 } 814 } 815 816 func blocksToIDs(blocks []*snowmantest.Block) []ids.ID { 817 blkIDs := make([]ids.ID, len(blocks)) 818 for i, blk := range blocks { 819 blkIDs[i] = blk.ID() 820 } 821 return blkIDs 822 } 823 824 func blocksToBytes(blocks []*snowmantest.Block) [][]byte { 825 numBlocks := len(blocks) 826 blkBytes := make([][]byte, numBlocks) 827 for i, blk := range blocks { 828 blkBytes[numBlocks-i-1] = blk.Bytes() 829 } 830 return blkBytes 831 }