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  }