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