github.com/MetalBlockchain/metalgo@v1.11.9/snow/engine/avalanche/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/memdb" 17 "github.com/MetalBlockchain/metalgo/database/prefixdb" 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/avalanche" 23 "github.com/MetalBlockchain/metalgo/snow/consensus/snowstorm" 24 "github.com/MetalBlockchain/metalgo/snow/engine/avalanche/bootstrap/queue" 25 "github.com/MetalBlockchain/metalgo/snow/engine/avalanche/getter" 26 "github.com/MetalBlockchain/metalgo/snow/engine/avalanche/vertex" 27 "github.com/MetalBlockchain/metalgo/snow/engine/common" 28 "github.com/MetalBlockchain/metalgo/snow/engine/common/tracker" 29 "github.com/MetalBlockchain/metalgo/snow/snowtest" 30 "github.com/MetalBlockchain/metalgo/snow/validators" 31 "github.com/MetalBlockchain/metalgo/utils/constants" 32 "github.com/MetalBlockchain/metalgo/utils/logging" 33 "github.com/MetalBlockchain/metalgo/utils/set" 34 "github.com/MetalBlockchain/metalgo/version" 35 36 p2ppb "github.com/MetalBlockchain/metalgo/proto/pb/p2p" 37 ) 38 39 var ( 40 errUnknownVertex = errors.New("unknown vertex") 41 errParsedUnknownVertex = errors.New("parsed unknown vertex") 42 errUnknownTx = errors.New("unknown tx") 43 ) 44 45 type testTx struct { 46 snowstorm.Tx 47 48 tx *snowstorm.TestTx 49 } 50 51 func (t *testTx) Accept(ctx context.Context) error { 52 if err := t.Tx.Accept(ctx); err != nil { 53 return err 54 } 55 t.tx.DependenciesV = nil 56 return nil 57 } 58 59 func newConfig(t *testing.T) (Config, ids.NodeID, *common.SenderTest, *vertex.TestManager, *vertex.TestVM) { 60 require := require.New(t) 61 62 snowCtx := snowtest.Context(t, snowtest.CChainID) 63 ctx := snowtest.ConsensusContext(snowCtx) 64 65 vdrs := validators.NewManager() 66 db := memdb.New() 67 sender := &common.SenderTest{T: t} 68 manager := vertex.NewTestManager(t) 69 vm := &vertex.TestVM{} 70 vm.T = t 71 72 sender.Default(true) 73 manager.Default(true) 74 vm.Default(true) 75 76 peer := ids.GenerateTestNodeID() 77 require.NoError(vdrs.AddStaker(constants.PrimaryNetworkID, peer, nil, ids.Empty, 1)) 78 79 vtxBlocker, err := queue.NewWithMissing(prefixdb.New([]byte("vtx"), db), "vtx", prometheus.NewRegistry()) 80 require.NoError(err) 81 82 txBlocker, err := queue.New(prefixdb.New([]byte("tx"), db), "tx", prometheus.NewRegistry()) 83 require.NoError(err) 84 85 peerTracker := tracker.NewPeers() 86 totalWeight, err := vdrs.TotalWeight(constants.PrimaryNetworkID) 87 require.NoError(err) 88 startupTracker := tracker.NewStartup(peerTracker, totalWeight/2+1) 89 vdrs.RegisterSetCallbackListener(constants.PrimaryNetworkID, startupTracker) 90 91 avaGetHandler, err := getter.New(manager, sender, ctx.Log, time.Second, 2000, prometheus.NewRegistry()) 92 require.NoError(err) 93 94 p2pTracker, err := p2p.NewPeerTracker( 95 logging.NoLog{}, 96 "", 97 prometheus.NewRegistry(), 98 nil, 99 version.CurrentApp, 100 ) 101 require.NoError(err) 102 103 p2pTracker.Connected(peer, version.CurrentApp) 104 105 return Config{ 106 AllGetsServer: avaGetHandler, 107 Ctx: ctx, 108 StartupTracker: startupTracker, 109 Sender: sender, 110 PeerTracker: p2pTracker, 111 AncestorsMaxContainersReceived: 2000, 112 VtxBlocked: vtxBlocker, 113 TxBlocked: txBlocker, 114 Manager: manager, 115 VM: vm, 116 }, peer, sender, manager, vm 117 } 118 119 // Three vertices in the accepted frontier. None have parents. No need to fetch 120 // anything 121 func TestBootstrapperSingleFrontier(t *testing.T) { 122 require := require.New(t) 123 124 config, _, _, manager, vm := newConfig(t) 125 126 vtxID0 := ids.Empty.Prefix(0) 127 vtxID1 := ids.Empty.Prefix(1) 128 vtxID2 := ids.Empty.Prefix(2) 129 130 vtxBytes0 := []byte{0} 131 vtxBytes1 := []byte{1} 132 vtxBytes2 := []byte{2} 133 134 vtx0 := &avalanche.TestVertex{ 135 TestDecidable: choices.TestDecidable{ 136 IDV: vtxID0, 137 StatusV: choices.Processing, 138 }, 139 HeightV: 0, 140 BytesV: vtxBytes0, 141 } 142 vtx1 := &avalanche.TestVertex{ 143 TestDecidable: choices.TestDecidable{ 144 IDV: vtxID1, 145 StatusV: choices.Processing, 146 }, 147 ParentsV: []avalanche.Vertex{ 148 vtx0, 149 }, 150 HeightV: 1, 151 BytesV: vtxBytes1, 152 } 153 vtx2 := &avalanche.TestVertex{ // vtx2 is the stop vertex 154 TestDecidable: choices.TestDecidable{ 155 IDV: vtxID2, 156 StatusV: choices.Processing, 157 }, 158 ParentsV: []avalanche.Vertex{ 159 vtx1, 160 }, 161 HeightV: 2, 162 BytesV: vtxBytes2, 163 } 164 165 config.StopVertexID = vtxID2 166 bs, err := New( 167 config, 168 func(context.Context, uint32) error { 169 config.Ctx.State.Set(snow.EngineState{ 170 Type: p2ppb.EngineType_ENGINE_TYPE_AVALANCHE, 171 State: snow.NormalOp, 172 }) 173 return nil 174 }, 175 prometheus.NewRegistry(), 176 ) 177 require.NoError(err) 178 179 manager.GetVtxF = func(_ context.Context, vtxID ids.ID) (avalanche.Vertex, error) { 180 switch vtxID { 181 case vtxID0: 182 return vtx0, nil 183 case vtxID1: 184 return vtx1, nil 185 case vtxID2: 186 return vtx2, nil 187 default: 188 require.FailNow(errUnknownVertex.Error()) 189 return nil, errUnknownVertex 190 } 191 } 192 193 manager.ParseVtxF = func(_ context.Context, vtxBytes []byte) (avalanche.Vertex, error) { 194 switch { 195 case bytes.Equal(vtxBytes, vtxBytes0): 196 return vtx0, nil 197 case bytes.Equal(vtxBytes, vtxBytes1): 198 return vtx1, nil 199 case bytes.Equal(vtxBytes, vtxBytes2): 200 return vtx2, nil 201 default: 202 require.FailNow(errParsedUnknownVertex.Error()) 203 return nil, errParsedUnknownVertex 204 } 205 } 206 207 manager.StopVertexAcceptedF = func(context.Context) (bool, error) { 208 return vtx2.Status() == choices.Accepted, nil 209 } 210 211 manager.EdgeF = func(context.Context) []ids.ID { 212 require.Equal(choices.Accepted, vtx2.Status()) 213 return []ids.ID{vtxID2} 214 } 215 216 vm.LinearizeF = func(_ context.Context, stopVertexID ids.ID) error { 217 require.Equal(vtxID2, stopVertexID) 218 return nil 219 } 220 221 vm.CantSetState = false 222 require.NoError(bs.Start(context.Background(), 0)) 223 require.Equal(snow.NormalOp, config.Ctx.State.Get().State) 224 require.Equal(choices.Accepted, vtx0.Status()) 225 require.Equal(choices.Accepted, vtx1.Status()) 226 require.Equal(choices.Accepted, vtx2.Status()) 227 } 228 229 // Accepted frontier has one vertex, which has one vertex as a dependency. 230 // Requests again and gets an unexpected vertex. Requests again and gets the 231 // expected vertex and an additional vertex that should not be accepted. 232 func TestBootstrapperByzantineResponses(t *testing.T) { 233 require := require.New(t) 234 235 config, peerID, sender, manager, vm := newConfig(t) 236 237 vtxID0 := ids.Empty.Prefix(0) 238 vtxID1 := ids.Empty.Prefix(1) 239 vtxID2 := ids.Empty.Prefix(2) 240 241 vtxBytes0 := []byte{0} 242 vtxBytes1 := []byte{1} 243 vtxBytes2 := []byte{2} 244 245 vtx0 := &avalanche.TestVertex{ 246 TestDecidable: choices.TestDecidable{ 247 IDV: vtxID0, 248 StatusV: choices.Unknown, 249 }, 250 HeightV: 0, 251 BytesV: vtxBytes0, 252 } 253 vtx1 := &avalanche.TestVertex{ // vtx1 is the stop vertex 254 TestDecidable: choices.TestDecidable{ 255 IDV: vtxID1, 256 StatusV: choices.Processing, 257 }, 258 ParentsV: []avalanche.Vertex{vtx0}, 259 HeightV: 1, 260 BytesV: vtxBytes1, 261 } 262 // Should not receive transitive votes from [vtx1] 263 vtx2 := &avalanche.TestVertex{ 264 TestDecidable: choices.TestDecidable{ 265 IDV: vtxID2, 266 StatusV: choices.Unknown, 267 }, 268 HeightV: 0, 269 BytesV: vtxBytes2, 270 } 271 272 config.StopVertexID = vtxID1 273 bs, err := New( 274 config, 275 func(context.Context, uint32) error { 276 config.Ctx.State.Set(snow.EngineState{ 277 Type: p2ppb.EngineType_ENGINE_TYPE_AVALANCHE, 278 State: snow.NormalOp, 279 }) 280 return nil 281 }, 282 prometheus.NewRegistry(), 283 ) 284 require.NoError(err) 285 286 manager.GetVtxF = func(_ context.Context, vtxID ids.ID) (avalanche.Vertex, error) { 287 switch vtxID { 288 case vtxID1: 289 return vtx1, nil 290 case vtxID0: 291 return nil, errUnknownVertex 292 default: 293 require.FailNow(errUnknownVertex.Error()) 294 return nil, errUnknownVertex 295 } 296 } 297 298 requestID := new(uint32) 299 reqVtxID := ids.Empty 300 sender.SendGetAncestorsF = func(_ context.Context, vdr ids.NodeID, reqID uint32, vtxID ids.ID) { 301 require.Equal(peerID, vdr) 302 require.Equal(vtxID0, vtxID) 303 304 *requestID = reqID 305 reqVtxID = vtxID 306 } 307 308 manager.ParseVtxF = func(_ context.Context, vtxBytes []byte) (avalanche.Vertex, error) { 309 switch { 310 case bytes.Equal(vtxBytes, vtxBytes0): 311 vtx0.StatusV = choices.Processing 312 return vtx0, nil 313 case bytes.Equal(vtxBytes, vtxBytes1): 314 vtx1.StatusV = choices.Processing 315 return vtx1, nil 316 case bytes.Equal(vtxBytes, vtxBytes2): 317 vtx2.StatusV = choices.Processing 318 return vtx2, nil 319 default: 320 require.FailNow(errParsedUnknownVertex.Error()) 321 return nil, errParsedUnknownVertex 322 } 323 } 324 325 vm.CantSetState = false 326 require.NoError(bs.Start(context.Background(), 0)) // should request vtx0 327 require.Equal(vtxID0, reqVtxID) 328 329 oldReqID := *requestID 330 require.NoError(bs.Ancestors(context.Background(), peerID, *requestID, [][]byte{vtxBytes2})) // send unexpected vertex 331 require.NotEqual(oldReqID, *requestID) // should have sent a new request 332 333 oldReqID = *requestID 334 manager.GetVtxF = func(_ context.Context, vtxID ids.ID) (avalanche.Vertex, error) { 335 switch vtxID { 336 case vtxID1: 337 return vtx1, nil 338 case vtxID0: 339 return vtx0, nil 340 default: 341 require.FailNow(errUnknownVertex.Error()) 342 return nil, errUnknownVertex 343 } 344 } 345 346 manager.StopVertexAcceptedF = func(context.Context) (bool, error) { 347 return vtx1.Status() == choices.Accepted, nil 348 } 349 350 manager.EdgeF = func(context.Context) []ids.ID { 351 require.Equal(choices.Accepted, vtx1.Status()) 352 return []ids.ID{vtxID1} 353 } 354 355 vm.LinearizeF = func(_ context.Context, stopVertexID ids.ID) error { 356 require.Equal(vtxID1, stopVertexID) 357 return nil 358 } 359 360 require.NoError(bs.Ancestors(context.Background(), peerID, *requestID, [][]byte{vtxBytes0, vtxBytes2})) // send expected vertex and vertex that should not be accepted 361 require.Equal(oldReqID, *requestID) // shouldn't have sent a new request 362 require.Equal(snow.NormalOp, config.Ctx.State.Get().State) 363 require.Equal(choices.Accepted, vtx0.Status()) 364 require.Equal(choices.Accepted, vtx1.Status()) 365 require.Equal(choices.Processing, vtx2.Status()) 366 } 367 368 // Vertex has a dependency and tx has a dependency 369 func TestBootstrapperTxDependencies(t *testing.T) { 370 require := require.New(t) 371 372 config, peerID, sender, manager, vm := newConfig(t) 373 374 txID0 := ids.GenerateTestID() 375 txID1 := ids.GenerateTestID() 376 377 txBytes0 := []byte{0} 378 txBytes1 := []byte{1} 379 380 innerTx0 := &snowstorm.TestTx{ 381 TestDecidable: choices.TestDecidable{ 382 IDV: txID0, 383 StatusV: choices.Processing, 384 }, 385 BytesV: txBytes0, 386 } 387 388 // Depends on tx0 389 tx1 := &snowstorm.TestTx{ 390 TestDecidable: choices.TestDecidable{ 391 IDV: txID1, 392 StatusV: choices.Processing, 393 }, 394 DependenciesV: set.Of(innerTx0.IDV), 395 BytesV: txBytes1, 396 } 397 398 tx0 := &testTx{ 399 Tx: innerTx0, 400 tx: tx1, 401 } 402 403 vtxID0 := ids.GenerateTestID() 404 vtxID1 := ids.GenerateTestID() 405 406 vtxBytes0 := []byte{2} 407 vtxBytes1 := []byte{3} 408 vm.ParseTxF = func(_ context.Context, b []byte) (snowstorm.Tx, error) { 409 switch { 410 case bytes.Equal(b, txBytes0): 411 return tx0, nil 412 case bytes.Equal(b, txBytes1): 413 return tx1, nil 414 default: 415 return nil, errUnknownTx 416 } 417 } 418 419 vtx0 := &avalanche.TestVertex{ 420 TestDecidable: choices.TestDecidable{ 421 IDV: vtxID0, 422 StatusV: choices.Unknown, 423 }, 424 HeightV: 0, 425 TxsV: []snowstorm.Tx{tx1}, 426 BytesV: vtxBytes0, 427 } 428 vtx1 := &avalanche.TestVertex{ // vtx1 is the stop vertex 429 TestDecidable: choices.TestDecidable{ 430 IDV: vtxID1, 431 StatusV: choices.Processing, 432 }, 433 ParentsV: []avalanche.Vertex{vtx0}, // Depends on vtx0 434 HeightV: 1, 435 TxsV: []snowstorm.Tx{tx0}, 436 BytesV: vtxBytes1, 437 } 438 439 config.StopVertexID = vtxID1 440 bs, err := New( 441 config, 442 func(context.Context, uint32) error { 443 config.Ctx.State.Set(snow.EngineState{ 444 Type: p2ppb.EngineType_ENGINE_TYPE_AVALANCHE, 445 State: snow.NormalOp, 446 }) 447 return nil 448 }, 449 prometheus.NewRegistry(), 450 ) 451 require.NoError(err) 452 453 manager.ParseVtxF = func(_ context.Context, vtxBytes []byte) (avalanche.Vertex, error) { 454 switch { 455 case bytes.Equal(vtxBytes, vtxBytes1): 456 return vtx1, nil 457 case bytes.Equal(vtxBytes, vtxBytes0): 458 return vtx0, nil 459 default: 460 require.FailNow(errParsedUnknownVertex.Error()) 461 return nil, errParsedUnknownVertex 462 } 463 } 464 manager.GetVtxF = func(_ context.Context, vtxID ids.ID) (avalanche.Vertex, error) { 465 switch vtxID { 466 case vtxID1: 467 return vtx1, nil 468 case vtxID0: 469 return nil, errUnknownVertex 470 default: 471 require.FailNow(errUnknownVertex.Error()) 472 return nil, errUnknownVertex 473 } 474 } 475 476 reqIDPtr := new(uint32) 477 sender.SendGetAncestorsF = func(_ context.Context, vdr ids.NodeID, reqID uint32, vtxID ids.ID) { 478 require.Equal(peerID, vdr) 479 require.Equal(vtxID0, vtxID) 480 481 *reqIDPtr = reqID 482 } 483 484 vm.CantSetState = false 485 require.NoError(bs.Start(context.Background(), 0)) 486 487 manager.ParseVtxF = func(_ context.Context, vtxBytes []byte) (avalanche.Vertex, error) { 488 switch { 489 case bytes.Equal(vtxBytes, vtxBytes1): 490 return vtx1, nil 491 case bytes.Equal(vtxBytes, vtxBytes0): 492 vtx0.StatusV = choices.Processing 493 return vtx0, nil 494 default: 495 require.FailNow(errParsedUnknownVertex.Error()) 496 return nil, errParsedUnknownVertex 497 } 498 } 499 500 manager.StopVertexAcceptedF = func(context.Context) (bool, error) { 501 return vtx1.Status() == choices.Accepted, nil 502 } 503 504 manager.EdgeF = func(context.Context) []ids.ID { 505 require.Equal(choices.Accepted, vtx1.Status()) 506 return []ids.ID{vtxID1} 507 } 508 509 vm.LinearizeF = func(_ context.Context, stopVertexID ids.ID) error { 510 require.Equal(vtxID1, stopVertexID) 511 return nil 512 } 513 514 require.NoError(bs.Ancestors(context.Background(), peerID, *reqIDPtr, [][]byte{vtxBytes0})) 515 require.Equal(snow.NormalOp, config.Ctx.State.Get().State) 516 require.Equal(choices.Accepted, tx0.Status()) 517 require.Equal(choices.Accepted, tx1.Status()) 518 require.Equal(choices.Accepted, vtx0.Status()) 519 require.Equal(choices.Accepted, vtx1.Status()) 520 } 521 522 // Ancestors only contains 1 of the two needed vertices; have to issue another GetAncestors 523 func TestBootstrapperIncompleteAncestors(t *testing.T) { 524 require := require.New(t) 525 526 config, peerID, sender, manager, vm := newConfig(t) 527 528 vtxID0 := ids.Empty.Prefix(0) 529 vtxID1 := ids.Empty.Prefix(1) 530 vtxID2 := ids.Empty.Prefix(2) 531 532 vtxBytes0 := []byte{0} 533 vtxBytes1 := []byte{1} 534 vtxBytes2 := []byte{2} 535 536 vtx0 := &avalanche.TestVertex{ 537 TestDecidable: choices.TestDecidable{ 538 IDV: vtxID0, 539 StatusV: choices.Unknown, 540 }, 541 HeightV: 0, 542 BytesV: vtxBytes0, 543 } 544 vtx1 := &avalanche.TestVertex{ 545 TestDecidable: choices.TestDecidable{ 546 IDV: vtxID1, 547 StatusV: choices.Unknown, 548 }, 549 ParentsV: []avalanche.Vertex{vtx0}, 550 HeightV: 1, 551 BytesV: vtxBytes1, 552 } 553 vtx2 := &avalanche.TestVertex{ // vtx2 is the stop vertex 554 TestDecidable: choices.TestDecidable{ 555 IDV: vtxID2, 556 StatusV: choices.Processing, 557 }, 558 ParentsV: []avalanche.Vertex{vtx1}, 559 HeightV: 2, 560 BytesV: vtxBytes2, 561 } 562 563 config.StopVertexID = vtxID2 564 bs, err := New( 565 config, 566 func(context.Context, uint32) error { 567 config.Ctx.State.Set(snow.EngineState{ 568 Type: p2ppb.EngineType_ENGINE_TYPE_AVALANCHE, 569 State: snow.NormalOp, 570 }) 571 return nil 572 }, 573 prometheus.NewRegistry(), 574 ) 575 require.NoError(err) 576 577 manager.GetVtxF = func(_ context.Context, vtxID ids.ID) (avalanche.Vertex, error) { 578 switch { 579 case vtxID == vtxID0: 580 return nil, errUnknownVertex 581 case vtxID == vtxID1: 582 return nil, errUnknownVertex 583 case vtxID == vtxID2: 584 return vtx2, nil 585 default: 586 require.FailNow(errUnknownVertex.Error()) 587 return nil, errUnknownVertex 588 } 589 } 590 manager.ParseVtxF = func(_ context.Context, vtxBytes []byte) (avalanche.Vertex, error) { 591 switch { 592 case bytes.Equal(vtxBytes, vtxBytes0): 593 vtx0.StatusV = choices.Processing 594 return vtx0, nil 595 case bytes.Equal(vtxBytes, vtxBytes1): 596 vtx1.StatusV = choices.Processing 597 return vtx1, nil 598 case bytes.Equal(vtxBytes, vtxBytes2): 599 return vtx2, nil 600 default: 601 require.FailNow(errParsedUnknownVertex.Error()) 602 return nil, errParsedUnknownVertex 603 } 604 } 605 reqIDPtr := new(uint32) 606 requested := ids.Empty 607 sender.SendGetAncestorsF = func(_ context.Context, vdr ids.NodeID, reqID uint32, vtxID ids.ID) { 608 require.Equal(peerID, vdr) 609 require.Contains([]ids.ID{vtxID1, vtxID0}, vtxID) 610 611 *reqIDPtr = reqID 612 requested = vtxID 613 } 614 615 vm.CantSetState = false 616 require.NoError(bs.Start(context.Background(), 0)) // should request vtx1 617 require.Equal(vtxID1, requested) 618 619 require.NoError(bs.Ancestors(context.Background(), peerID, *reqIDPtr, [][]byte{vtxBytes1})) // Provide vtx1; should request vtx0 620 require.Equal(snow.Bootstrapping, bs.Context().State.Get().State) 621 require.Equal(vtxID0, requested) 622 623 manager.StopVertexAcceptedF = func(context.Context) (bool, error) { 624 return vtx2.Status() == choices.Accepted, nil 625 } 626 627 manager.EdgeF = func(context.Context) []ids.ID { 628 require.Equal(choices.Accepted, vtx2.Status()) 629 return []ids.ID{vtxID2} 630 } 631 632 vm.LinearizeF = func(_ context.Context, stopVertexID ids.ID) error { 633 require.Equal(vtxID2, stopVertexID) 634 return nil 635 } 636 637 require.NoError(bs.Ancestors(context.Background(), peerID, *reqIDPtr, [][]byte{vtxBytes0})) // Provide vtx0; can finish now 638 require.Equal(snow.NormalOp, bs.Context().State.Get().State) 639 require.Equal(choices.Accepted, vtx0.Status()) 640 require.Equal(choices.Accepted, vtx1.Status()) 641 require.Equal(choices.Accepted, vtx2.Status()) 642 }