github.com/ava-labs/avalanchego@v1.11.11/network/p2p/gossip/gossip_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 gossip 5 6 import ( 7 "context" 8 "sync" 9 "testing" 10 "time" 11 12 "github.com/prometheus/client_golang/prometheus" 13 "github.com/stretchr/testify/require" 14 "golang.org/x/exp/maps" 15 "google.golang.org/protobuf/proto" 16 17 "github.com/ava-labs/avalanchego/ids" 18 "github.com/ava-labs/avalanchego/network/p2p" 19 "github.com/ava-labs/avalanchego/proto/pb/sdk" 20 "github.com/ava-labs/avalanchego/snow/engine/enginetest" 21 "github.com/ava-labs/avalanchego/snow/validators" 22 "github.com/ava-labs/avalanchego/snow/validators/validatorstest" 23 "github.com/ava-labs/avalanchego/utils/constants" 24 "github.com/ava-labs/avalanchego/utils/logging" 25 "github.com/ava-labs/avalanchego/utils/set" 26 "github.com/ava-labs/avalanchego/utils/units" 27 ) 28 29 func TestGossiperShutdown(*testing.T) { 30 gossiper := NewPullGossiper[*testTx]( 31 logging.NoLog{}, 32 nil, 33 nil, 34 nil, 35 Metrics{}, 36 0, 37 ) 38 ctx, cancel := context.WithCancel(context.Background()) 39 40 wg := &sync.WaitGroup{} 41 wg.Add(1) 42 43 go func() { 44 Every(ctx, logging.NoLog{}, gossiper, time.Second) 45 wg.Done() 46 }() 47 48 cancel() 49 wg.Wait() 50 } 51 52 func TestGossiperGossip(t *testing.T) { 53 tests := []struct { 54 name string 55 targetResponseSize int 56 requester []*testTx // what we have 57 responder []*testTx // what the peer we're requesting gossip from has 58 expectedPossibleValues []*testTx // possible values we can have 59 expectedLen int 60 }{ 61 { 62 name: "no gossip - no one knows anything", 63 }, 64 { 65 name: "no gossip - requester knows more than responder", 66 targetResponseSize: 1024, 67 requester: []*testTx{{id: ids.ID{0}}}, 68 expectedPossibleValues: []*testTx{{id: ids.ID{0}}}, 69 expectedLen: 1, 70 }, 71 { 72 name: "no gossip - requester knows everything responder knows", 73 targetResponseSize: 1024, 74 requester: []*testTx{{id: ids.ID{0}}}, 75 responder: []*testTx{{id: ids.ID{0}}}, 76 expectedPossibleValues: []*testTx{{id: ids.ID{0}}}, 77 expectedLen: 1, 78 }, 79 { 80 name: "gossip - requester knows nothing", 81 targetResponseSize: 1024, 82 responder: []*testTx{{id: ids.ID{0}}}, 83 expectedPossibleValues: []*testTx{{id: ids.ID{0}}}, 84 expectedLen: 1, 85 }, 86 { 87 name: "gossip - requester knows less than responder", 88 targetResponseSize: 1024, 89 requester: []*testTx{{id: ids.ID{0}}}, 90 responder: []*testTx{{id: ids.ID{0}}, {id: ids.ID{1}}}, 91 expectedPossibleValues: []*testTx{{id: ids.ID{0}}, {id: ids.ID{1}}}, 92 expectedLen: 2, 93 }, 94 { 95 name: "gossip - target response size exceeded", 96 targetResponseSize: 32, 97 responder: []*testTx{{id: ids.ID{0}}, {id: ids.ID{1}}, {id: ids.ID{2}}}, 98 expectedPossibleValues: []*testTx{{id: ids.ID{0}}, {id: ids.ID{1}}, {id: ids.ID{2}}}, 99 expectedLen: 2, 100 }, 101 } 102 103 for _, tt := range tests { 104 t.Run(tt.name, func(t *testing.T) { 105 require := require.New(t) 106 ctx := context.Background() 107 108 responseSender := &enginetest.SenderStub{ 109 SentAppResponse: make(chan []byte, 1), 110 } 111 responseNetwork, err := p2p.NewNetwork(logging.NoLog{}, responseSender, prometheus.NewRegistry(), "") 112 require.NoError(err) 113 114 responseBloom, err := NewBloomFilter(prometheus.NewRegistry(), "", 1000, 0.01, 0.05) 115 require.NoError(err) 116 responseSet := &testSet{ 117 txs: make(map[ids.ID]*testTx), 118 bloom: responseBloom, 119 } 120 for _, item := range tt.responder { 121 require.NoError(responseSet.Add(item)) 122 } 123 124 metrics, err := NewMetrics(prometheus.NewRegistry(), "") 125 require.NoError(err) 126 marshaller := testMarshaller{} 127 handler := NewHandler[*testTx]( 128 logging.NoLog{}, 129 marshaller, 130 responseSet, 131 metrics, 132 tt.targetResponseSize, 133 ) 134 require.NoError(err) 135 require.NoError(responseNetwork.AddHandler(0x0, handler)) 136 137 requestSender := &enginetest.SenderStub{ 138 SentAppRequest: make(chan []byte, 1), 139 } 140 141 requestNetwork, err := p2p.NewNetwork(logging.NoLog{}, requestSender, prometheus.NewRegistry(), "") 142 require.NoError(err) 143 require.NoError(requestNetwork.Connected(context.Background(), ids.EmptyNodeID, nil)) 144 145 bloom, err := NewBloomFilter(prometheus.NewRegistry(), "", 1000, 0.01, 0.05) 146 require.NoError(err) 147 requestSet := &testSet{ 148 txs: make(map[ids.ID]*testTx), 149 bloom: bloom, 150 } 151 for _, item := range tt.requester { 152 require.NoError(requestSet.Add(item)) 153 } 154 155 requestClient := requestNetwork.NewClient(0x0) 156 157 require.NoError(err) 158 gossiper := NewPullGossiper[*testTx]( 159 logging.NoLog{}, 160 marshaller, 161 requestSet, 162 requestClient, 163 metrics, 164 1, 165 ) 166 require.NoError(err) 167 received := set.Set[*testTx]{} 168 requestSet.onAdd = func(tx *testTx) { 169 received.Add(tx) 170 } 171 172 require.NoError(gossiper.Gossip(ctx)) 173 require.NoError(responseNetwork.AppRequest(ctx, ids.EmptyNodeID, 1, time.Time{}, <-requestSender.SentAppRequest)) 174 require.NoError(requestNetwork.AppResponse(ctx, ids.EmptyNodeID, 1, <-responseSender.SentAppResponse)) 175 176 require.Len(requestSet.txs, tt.expectedLen) 177 require.Subset(tt.expectedPossibleValues, maps.Values(requestSet.txs)) 178 179 // we should not receive anything that we already had before we 180 // requested the gossip 181 for _, tx := range tt.requester { 182 require.NotContains(received, tx) 183 } 184 }) 185 } 186 } 187 188 func TestEvery(*testing.T) { 189 ctx, cancel := context.WithCancel(context.Background()) 190 calls := 0 191 gossiper := &TestGossiper{ 192 GossipF: func(context.Context) error { 193 if calls >= 10 { 194 cancel() 195 return nil 196 } 197 198 calls++ 199 return nil 200 }, 201 } 202 203 go Every(ctx, logging.NoLog{}, gossiper, time.Millisecond) 204 <-ctx.Done() 205 } 206 207 func TestValidatorGossiper(t *testing.T) { 208 require := require.New(t) 209 210 nodeID := ids.GenerateTestNodeID() 211 212 validators := testValidatorSet{ 213 validators: set.Of(nodeID), 214 } 215 216 calls := 0 217 gossiper := ValidatorGossiper{ 218 Gossiper: &TestGossiper{ 219 GossipF: func(context.Context) error { 220 calls++ 221 return nil 222 }, 223 }, 224 NodeID: nodeID, 225 Validators: validators, 226 } 227 228 // we are a validator, so we should request gossip 229 require.NoError(gossiper.Gossip(context.Background())) 230 require.Equal(1, calls) 231 232 // we are not a validator, so we should not request gossip 233 validators.validators = set.Set[ids.NodeID]{} 234 require.NoError(gossiper.Gossip(context.Background())) 235 require.Equal(2, calls) 236 } 237 238 func TestPushGossiperNew(t *testing.T) { 239 tests := []struct { 240 name string 241 gossipParams BranchingFactor 242 regossipParams BranchingFactor 243 discardedSize int 244 targetGossipSize int 245 maxRegossipFrequency time.Duration 246 expected error 247 }{ 248 { 249 name: "invalid gossip num validators", 250 gossipParams: BranchingFactor{ 251 Validators: -1, 252 }, 253 regossipParams: BranchingFactor{ 254 Peers: 1, 255 }, 256 expected: ErrInvalidNumValidators, 257 }, 258 { 259 name: "invalid gossip num non-validators", 260 gossipParams: BranchingFactor{ 261 NonValidators: -1, 262 }, 263 regossipParams: BranchingFactor{ 264 Peers: 1, 265 }, 266 expected: ErrInvalidNumNonValidators, 267 }, 268 { 269 name: "invalid gossip num peers", 270 gossipParams: BranchingFactor{ 271 Peers: -1, 272 }, 273 regossipParams: BranchingFactor{ 274 Peers: 1, 275 }, 276 expected: ErrInvalidNumPeers, 277 }, 278 { 279 name: "invalid gossip num to gossip", 280 gossipParams: BranchingFactor{}, 281 regossipParams: BranchingFactor{ 282 Peers: 1, 283 }, 284 expected: ErrInvalidNumToGossip, 285 }, 286 { 287 name: "invalid regossip num validators", 288 gossipParams: BranchingFactor{ 289 Validators: 1, 290 }, 291 regossipParams: BranchingFactor{ 292 Validators: -1, 293 }, 294 expected: ErrInvalidNumValidators, 295 }, 296 { 297 name: "invalid regossip num non-validators", 298 gossipParams: BranchingFactor{ 299 Validators: 1, 300 }, 301 regossipParams: BranchingFactor{ 302 NonValidators: -1, 303 }, 304 expected: ErrInvalidNumNonValidators, 305 }, 306 { 307 name: "invalid regossip num peers", 308 gossipParams: BranchingFactor{ 309 Validators: 1, 310 }, 311 regossipParams: BranchingFactor{ 312 Peers: -1, 313 }, 314 expected: ErrInvalidNumPeers, 315 }, 316 { 317 name: "invalid regossip num to gossip", 318 gossipParams: BranchingFactor{ 319 Validators: 1, 320 }, 321 regossipParams: BranchingFactor{}, 322 expected: ErrInvalidNumToGossip, 323 }, 324 { 325 name: "invalid discarded size", 326 gossipParams: BranchingFactor{ 327 Validators: 1, 328 }, 329 regossipParams: BranchingFactor{ 330 Validators: 1, 331 }, 332 discardedSize: -1, 333 expected: ErrInvalidDiscardedSize, 334 }, 335 { 336 name: "invalid target gossip size", 337 gossipParams: BranchingFactor{ 338 Validators: 1, 339 }, 340 regossipParams: BranchingFactor{ 341 Validators: 1, 342 }, 343 targetGossipSize: -1, 344 expected: ErrInvalidTargetGossipSize, 345 }, 346 { 347 name: "invalid max re-gossip frequency", 348 gossipParams: BranchingFactor{ 349 Validators: 1, 350 }, 351 regossipParams: BranchingFactor{ 352 Validators: 1, 353 }, 354 maxRegossipFrequency: -1, 355 expected: ErrInvalidRegossipFrequency, 356 }, 357 } 358 359 for _, tt := range tests { 360 t.Run(tt.name, func(t *testing.T) { 361 _, err := NewPushGossiper[*testTx]( 362 nil, 363 nil, 364 nil, 365 nil, 366 Metrics{}, 367 tt.gossipParams, 368 tt.regossipParams, 369 tt.discardedSize, 370 tt.targetGossipSize, 371 tt.maxRegossipFrequency, 372 ) 373 require.ErrorIs(t, err, tt.expected) 374 }) 375 } 376 } 377 378 // Tests that the outgoing gossip is equivalent to what was accumulated 379 func TestPushGossiper(t *testing.T) { 380 type cycle struct { 381 toAdd []*testTx 382 expected [][]*testTx 383 } 384 tests := []struct { 385 name string 386 cycles []cycle 387 shouldRegossip bool 388 }{ 389 { 390 name: "single cycle with regossip", 391 cycles: []cycle{ 392 { 393 toAdd: []*testTx{ 394 { 395 id: ids.ID{0}, 396 }, 397 { 398 id: ids.ID{1}, 399 }, 400 { 401 id: ids.ID{2}, 402 }, 403 }, 404 expected: [][]*testTx{ 405 { 406 { 407 id: ids.ID{0}, 408 }, 409 { 410 id: ids.ID{1}, 411 }, 412 { 413 id: ids.ID{2}, 414 }, 415 }, 416 }, 417 }, 418 }, 419 shouldRegossip: true, 420 }, 421 { 422 name: "multiple cycles with regossip", 423 cycles: []cycle{ 424 { 425 toAdd: []*testTx{ 426 { 427 id: ids.ID{0}, 428 }, 429 }, 430 expected: [][]*testTx{ 431 { 432 { 433 id: ids.ID{0}, 434 }, 435 }, 436 }, 437 }, 438 { 439 toAdd: []*testTx{ 440 { 441 id: ids.ID{1}, 442 }, 443 }, 444 expected: [][]*testTx{ 445 { 446 { 447 id: ids.ID{1}, 448 }, 449 }, 450 { 451 { 452 id: ids.ID{0}, 453 }, 454 }, 455 }, 456 }, 457 { 458 toAdd: []*testTx{ 459 { 460 id: ids.ID{2}, 461 }, 462 }, 463 expected: [][]*testTx{ 464 { 465 { 466 id: ids.ID{2}, 467 }, 468 }, 469 { 470 { 471 id: ids.ID{1}, 472 }, 473 { 474 id: ids.ID{0}, 475 }, 476 }, 477 }, 478 }, 479 }, 480 shouldRegossip: true, 481 }, 482 { 483 name: "verify that we don't gossip empty messages", 484 cycles: []cycle{ 485 { 486 toAdd: []*testTx{ 487 { 488 id: ids.ID{0}, 489 }, 490 }, 491 expected: [][]*testTx{ 492 { 493 { 494 id: ids.ID{0}, 495 }, 496 }, 497 }, 498 }, 499 { 500 toAdd: []*testTx{}, 501 expected: [][]*testTx{}, 502 }, 503 }, 504 shouldRegossip: false, 505 }, 506 } 507 508 for _, tt := range tests { 509 t.Run(tt.name, func(t *testing.T) { 510 require := require.New(t) 511 ctx := context.Background() 512 513 sender := &enginetest.SenderStub{ 514 SentAppGossip: make(chan []byte, 2), 515 } 516 network, err := p2p.NewNetwork( 517 logging.NoLog{}, 518 sender, 519 prometheus.NewRegistry(), 520 "", 521 ) 522 require.NoError(err) 523 client := network.NewClient(0) 524 validators := p2p.NewValidators( 525 &p2p.Peers{}, 526 logging.NoLog{}, 527 constants.PrimaryNetworkID, 528 &validatorstest.State{ 529 GetCurrentHeightF: func(context.Context) (uint64, error) { 530 return 1, nil 531 }, 532 GetValidatorSetF: func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { 533 return nil, nil 534 }, 535 }, 536 time.Hour, 537 ) 538 metrics, err := NewMetrics(prometheus.NewRegistry(), "") 539 require.NoError(err) 540 marshaller := testMarshaller{} 541 542 regossipTime := time.Hour 543 if tt.shouldRegossip { 544 regossipTime = time.Nanosecond 545 } 546 547 gossiper, err := NewPushGossiper[*testTx]( 548 marshaller, 549 FullSet[*testTx]{}, 550 validators, 551 client, 552 metrics, 553 BranchingFactor{ 554 Validators: 1, 555 }, 556 BranchingFactor{ 557 Validators: 1, 558 }, 559 0, // the discarded cache size doesn't matter for this test 560 units.MiB, 561 regossipTime, 562 ) 563 require.NoError(err) 564 565 for _, cycle := range tt.cycles { 566 gossiper.Add(cycle.toAdd...) 567 require.NoError(gossiper.Gossip(ctx)) 568 569 for _, expected := range cycle.expected { 570 want := &sdk.PushGossip{ 571 Gossip: make([][]byte, 0, len(expected)), 572 } 573 574 for _, gossipable := range expected { 575 bytes, err := marshaller.MarshalGossip(gossipable) 576 require.NoError(err) 577 578 want.Gossip = append(want.Gossip, bytes) 579 } 580 581 if len(want.Gossip) > 0 { 582 // remove the handler prefix 583 sentMsg := <-sender.SentAppGossip 584 got := &sdk.PushGossip{} 585 require.NoError(proto.Unmarshal(sentMsg[1:], got)) 586 587 require.Equal(want.Gossip, got.Gossip) 588 } else { 589 select { 590 case <-sender.SentAppGossip: 591 require.FailNow("unexpectedly sent gossip message") 592 default: 593 } 594 } 595 } 596 597 if tt.shouldRegossip { 598 // Ensure that subsequent calls to `time.Now()` are 599 // sufficient for regossip. 600 time.Sleep(regossipTime + time.Nanosecond) 601 } 602 } 603 }) 604 } 605 } 606 607 type testValidatorSet struct { 608 validators set.Set[ids.NodeID] 609 } 610 611 func (t testValidatorSet) Has(_ context.Context, nodeID ids.NodeID) bool { 612 return t.validators.Contains(nodeID) 613 }