github.com/MetalBlockchain/metalgo@v1.11.9/network/ip_tracker_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 network
     5  
     6  import (
     7  	"testing"
     8  
     9  	"github.com/prometheus/client_golang/prometheus"
    10  	"github.com/prometheus/client_golang/prometheus/testutil"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/MetalBlockchain/metalgo/ids"
    14  	"github.com/MetalBlockchain/metalgo/utils/bloom"
    15  	"github.com/MetalBlockchain/metalgo/utils/ips"
    16  	"github.com/MetalBlockchain/metalgo/utils/logging"
    17  )
    18  
    19  func newTestIPTracker(t *testing.T) *ipTracker {
    20  	tracker, err := newIPTracker(logging.NoLog{}, prometheus.NewRegistry())
    21  	require.NoError(t, err)
    22  	return tracker
    23  }
    24  
    25  func newerTestIP(ip *ips.ClaimedIPPort) *ips.ClaimedIPPort {
    26  	return ips.NewClaimedIPPort(
    27  		ip.Cert,
    28  		ip.AddrPort,
    29  		ip.Timestamp+1,
    30  		ip.Signature,
    31  	)
    32  }
    33  
    34  func requireEqual(t *testing.T, expected, actual *ipTracker) {
    35  	require := require.New(t)
    36  	require.Equal(expected.manuallyTracked, actual.manuallyTracked)
    37  	require.Equal(expected.manuallyGossipable, actual.manuallyGossipable)
    38  	require.Equal(expected.mostRecentTrackedIPs, actual.mostRecentTrackedIPs)
    39  	require.Equal(expected.trackedIDs, actual.trackedIDs)
    40  	require.Equal(expected.bloomAdditions, actual.bloomAdditions)
    41  	require.Equal(expected.maxBloomCount, actual.maxBloomCount)
    42  	require.Equal(expected.connected, actual.connected)
    43  	require.Equal(expected.gossipableIndices, actual.gossipableIndices)
    44  	require.Equal(expected.gossipableIPs, actual.gossipableIPs)
    45  	require.Equal(expected.gossipableIDs, actual.gossipableIDs)
    46  }
    47  
    48  func requireMetricsConsistent(t *testing.T, tracker *ipTracker) {
    49  	require := require.New(t)
    50  	require.Equal(float64(len(tracker.mostRecentTrackedIPs)), testutil.ToFloat64(tracker.numTrackedIPs))
    51  	require.Equal(float64(len(tracker.gossipableIPs)), testutil.ToFloat64(tracker.numGossipableIPs))
    52  	require.Equal(float64(tracker.bloom.Count()), testutil.ToFloat64(tracker.bloomMetrics.Count))
    53  	require.Equal(float64(tracker.maxBloomCount), testutil.ToFloat64(tracker.bloomMetrics.MaxCount))
    54  }
    55  
    56  func TestIPTracker_ManuallyTrack(t *testing.T) {
    57  	tests := []struct {
    58  		name          string
    59  		initialState  *ipTracker
    60  		nodeID        ids.NodeID
    61  		expectedState *ipTracker
    62  	}{
    63  		{
    64  			name:         "non-connected non-validator",
    65  			initialState: newTestIPTracker(t),
    66  			nodeID:       ip.NodeID,
    67  			expectedState: func() *ipTracker {
    68  				tracker := newTestIPTracker(t)
    69  				tracker.manuallyTracked.Add(ip.NodeID)
    70  				tracker.trackedIDs.Add(ip.NodeID)
    71  				return tracker
    72  			}(),
    73  		},
    74  		{
    75  			name: "connected non-validator",
    76  			initialState: func() *ipTracker {
    77  				tracker := newTestIPTracker(t)
    78  				tracker.Connected(ip)
    79  				return tracker
    80  			}(),
    81  			nodeID: ip.NodeID,
    82  			expectedState: func() *ipTracker {
    83  				tracker := newTestIPTracker(t)
    84  				tracker.Connected(ip)
    85  				tracker.manuallyTracked.Add(ip.NodeID)
    86  				tracker.mostRecentTrackedIPs[ip.NodeID] = ip
    87  				tracker.trackedIDs.Add(ip.NodeID)
    88  				tracker.bloomAdditions[ip.NodeID] = 1
    89  				return tracker
    90  			}(),
    91  		},
    92  		{
    93  			name: "non-connected validator",
    94  			initialState: func() *ipTracker {
    95  				tracker := newTestIPTracker(t)
    96  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
    97  				return tracker
    98  			}(),
    99  			nodeID: ip.NodeID,
   100  			expectedState: func() *ipTracker {
   101  				tracker := newTestIPTracker(t)
   102  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   103  				tracker.manuallyTracked.Add(ip.NodeID)
   104  				return tracker
   105  			}(),
   106  		},
   107  		{
   108  			name: "connected validator",
   109  			initialState: func() *ipTracker {
   110  				tracker := newTestIPTracker(t)
   111  				tracker.Connected(ip)
   112  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   113  				return tracker
   114  			}(),
   115  			nodeID: ip.NodeID,
   116  			expectedState: func() *ipTracker {
   117  				tracker := newTestIPTracker(t)
   118  				tracker.Connected(ip)
   119  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   120  				tracker.manuallyTracked.Add(ip.NodeID)
   121  				return tracker
   122  			}(),
   123  		},
   124  	}
   125  	for _, test := range tests {
   126  		t.Run(test.name, func(t *testing.T) {
   127  			test.initialState.ManuallyTrack(test.nodeID)
   128  			requireEqual(t, test.expectedState, test.initialState)
   129  			requireMetricsConsistent(t, test.initialState)
   130  		})
   131  	}
   132  }
   133  
   134  func TestIPTracker_ManuallyGossip(t *testing.T) {
   135  	tests := []struct {
   136  		name          string
   137  		initialState  *ipTracker
   138  		nodeID        ids.NodeID
   139  		expectedState *ipTracker
   140  	}{
   141  		{
   142  			name:         "non-connected non-validator",
   143  			initialState: newTestIPTracker(t),
   144  			nodeID:       ip.NodeID,
   145  			expectedState: func() *ipTracker {
   146  				tracker := newTestIPTracker(t)
   147  				tracker.manuallyTracked.Add(ip.NodeID)
   148  				tracker.manuallyGossipable.Add(ip.NodeID)
   149  				tracker.trackedIDs.Add(ip.NodeID)
   150  				tracker.gossipableIDs.Add(ip.NodeID)
   151  				return tracker
   152  			}(),
   153  		},
   154  		{
   155  			name: "connected non-validator",
   156  			initialState: func() *ipTracker {
   157  				tracker := newTestIPTracker(t)
   158  				tracker.Connected(ip)
   159  				return tracker
   160  			}(),
   161  			nodeID: ip.NodeID,
   162  			expectedState: func() *ipTracker {
   163  				tracker := newTestIPTracker(t)
   164  				tracker.Connected(ip)
   165  				tracker.manuallyTracked.Add(ip.NodeID)
   166  				tracker.manuallyGossipable.Add(ip.NodeID)
   167  				tracker.mostRecentTrackedIPs[ip.NodeID] = ip
   168  				tracker.trackedIDs.Add(ip.NodeID)
   169  				tracker.bloomAdditions[ip.NodeID] = 1
   170  				tracker.gossipableIndices[ip.NodeID] = 0
   171  				tracker.gossipableIPs = []*ips.ClaimedIPPort{
   172  					ip,
   173  				}
   174  				tracker.gossipableIDs.Add(ip.NodeID)
   175  				return tracker
   176  			}(),
   177  		},
   178  		{
   179  			name: "non-connected validator",
   180  			initialState: func() *ipTracker {
   181  				tracker := newTestIPTracker(t)
   182  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   183  				return tracker
   184  			}(),
   185  			nodeID: ip.NodeID,
   186  			expectedState: func() *ipTracker {
   187  				tracker := newTestIPTracker(t)
   188  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   189  				tracker.manuallyTracked.Add(ip.NodeID)
   190  				tracker.manuallyGossipable.Add(ip.NodeID)
   191  				return tracker
   192  			}(),
   193  		},
   194  		{
   195  			name: "connected validator",
   196  			initialState: func() *ipTracker {
   197  				tracker := newTestIPTracker(t)
   198  				tracker.Connected(ip)
   199  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   200  				return tracker
   201  			}(),
   202  			nodeID: ip.NodeID,
   203  			expectedState: func() *ipTracker {
   204  				tracker := newTestIPTracker(t)
   205  				tracker.Connected(ip)
   206  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   207  				tracker.manuallyTracked.Add(ip.NodeID)
   208  				tracker.manuallyGossipable.Add(ip.NodeID)
   209  				return tracker
   210  			}(),
   211  		},
   212  	}
   213  	for _, test := range tests {
   214  		t.Run(test.name, func(t *testing.T) {
   215  			test.initialState.ManuallyGossip(test.nodeID)
   216  			requireEqual(t, test.expectedState, test.initialState)
   217  			requireMetricsConsistent(t, test.initialState)
   218  		})
   219  	}
   220  }
   221  
   222  func TestIPTracker_AddIP(t *testing.T) {
   223  	newerIP := newerTestIP(ip)
   224  	tests := []struct {
   225  		name            string
   226  		initialState    *ipTracker
   227  		ip              *ips.ClaimedIPPort
   228  		expectedUpdated bool
   229  		expectedState   *ipTracker
   230  	}{
   231  		{
   232  			name:            "non-validator",
   233  			initialState:    newTestIPTracker(t),
   234  			ip:              ip,
   235  			expectedUpdated: false,
   236  			expectedState:   newTestIPTracker(t),
   237  		},
   238  		{
   239  			name: "first known IP",
   240  			initialState: func() *ipTracker {
   241  				tracker := newTestIPTracker(t)
   242  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   243  				return tracker
   244  			}(),
   245  			ip:              ip,
   246  			expectedUpdated: true,
   247  			expectedState: func() *ipTracker {
   248  				tracker := newTestIPTracker(t)
   249  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   250  				tracker.mostRecentTrackedIPs[ip.NodeID] = ip
   251  				tracker.bloomAdditions[ip.NodeID] = 1
   252  				return tracker
   253  			}(),
   254  		},
   255  		{
   256  			name: "older IP",
   257  			initialState: func() *ipTracker {
   258  				tracker := newTestIPTracker(t)
   259  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   260  				require.True(t, tracker.AddIP(newerIP))
   261  				return tracker
   262  			}(),
   263  			ip:              ip,
   264  			expectedUpdated: false,
   265  			expectedState: func() *ipTracker {
   266  				tracker := newTestIPTracker(t)
   267  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   268  				require.True(t, tracker.AddIP(newerIP))
   269  				return tracker
   270  			}(),
   271  		},
   272  		{
   273  			name: "same IP",
   274  			initialState: func() *ipTracker {
   275  				tracker := newTestIPTracker(t)
   276  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   277  				require.True(t, tracker.AddIP(ip))
   278  				return tracker
   279  			}(),
   280  			ip:              ip,
   281  			expectedUpdated: false,
   282  			expectedState: func() *ipTracker {
   283  				tracker := newTestIPTracker(t)
   284  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   285  				require.True(t, tracker.AddIP(ip))
   286  				return tracker
   287  			}(),
   288  		},
   289  		{
   290  			name: "disconnected newer IP",
   291  			initialState: func() *ipTracker {
   292  				tracker := newTestIPTracker(t)
   293  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   294  				require.True(t, tracker.AddIP(ip))
   295  				return tracker
   296  			}(),
   297  			ip:              newerIP,
   298  			expectedUpdated: true,
   299  			expectedState: func() *ipTracker {
   300  				tracker := newTestIPTracker(t)
   301  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   302  				require.True(t, tracker.AddIP(ip))
   303  				tracker.mostRecentTrackedIPs[newerIP.NodeID] = newerIP
   304  				tracker.bloomAdditions[newerIP.NodeID] = 2
   305  				return tracker
   306  			}(),
   307  		},
   308  		{
   309  			name: "connected newer IP",
   310  			initialState: func() *ipTracker {
   311  				tracker := newTestIPTracker(t)
   312  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   313  				tracker.Connected(ip)
   314  				return tracker
   315  			}(),
   316  			ip:              newerIP,
   317  			expectedUpdated: true,
   318  			expectedState: func() *ipTracker {
   319  				tracker := newTestIPTracker(t)
   320  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   321  				tracker.Connected(ip)
   322  				tracker.mostRecentTrackedIPs[newerIP.NodeID] = newerIP
   323  				tracker.bloomAdditions[newerIP.NodeID] = 2
   324  				delete(tracker.gossipableIndices, newerIP.NodeID)
   325  				tracker.gossipableIPs = tracker.gossipableIPs[:0]
   326  				return tracker
   327  			}(),
   328  		},
   329  	}
   330  	for _, test := range tests {
   331  		t.Run(test.name, func(t *testing.T) {
   332  			updated := test.initialState.AddIP(test.ip)
   333  			require.Equal(t, test.expectedUpdated, updated)
   334  			requireEqual(t, test.expectedState, test.initialState)
   335  			requireMetricsConsistent(t, test.initialState)
   336  		})
   337  	}
   338  }
   339  
   340  func TestIPTracker_Connected(t *testing.T) {
   341  	newerIP := newerTestIP(ip)
   342  	tests := []struct {
   343  		name          string
   344  		initialState  *ipTracker
   345  		ip            *ips.ClaimedIPPort
   346  		expectedState *ipTracker
   347  	}{
   348  		{
   349  			name:         "non-validator",
   350  			initialState: newTestIPTracker(t),
   351  			ip:           ip,
   352  			expectedState: func() *ipTracker {
   353  				tracker := newTestIPTracker(t)
   354  				tracker.connected[ip.NodeID] = ip
   355  				return tracker
   356  			}(),
   357  		},
   358  		{
   359  			name: "first known IP",
   360  			initialState: func() *ipTracker {
   361  				tracker := newTestIPTracker(t)
   362  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   363  				return tracker
   364  			}(),
   365  			ip: ip,
   366  			expectedState: func() *ipTracker {
   367  				tracker := newTestIPTracker(t)
   368  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   369  				tracker.mostRecentTrackedIPs[ip.NodeID] = ip
   370  				tracker.bloomAdditions[ip.NodeID] = 1
   371  				tracker.connected[ip.NodeID] = ip
   372  				tracker.gossipableIndices[ip.NodeID] = 0
   373  				tracker.gossipableIPs = []*ips.ClaimedIPPort{
   374  					ip,
   375  				}
   376  				return tracker
   377  			}(),
   378  		},
   379  		{
   380  			name: "connected with older IP",
   381  			initialState: func() *ipTracker {
   382  				tracker := newTestIPTracker(t)
   383  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   384  				require.True(t, tracker.AddIP(newerIP))
   385  				return tracker
   386  			}(),
   387  			ip: ip,
   388  			expectedState: func() *ipTracker {
   389  				tracker := newTestIPTracker(t)
   390  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   391  				require.True(t, tracker.AddIP(newerIP))
   392  				tracker.connected[ip.NodeID] = ip
   393  				return tracker
   394  			}(),
   395  		},
   396  		{
   397  			name: "connected with newer IP",
   398  			initialState: func() *ipTracker {
   399  				tracker := newTestIPTracker(t)
   400  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   401  				require.True(t, tracker.AddIP(ip))
   402  				return tracker
   403  			}(),
   404  			ip: newerIP,
   405  			expectedState: func() *ipTracker {
   406  				tracker := newTestIPTracker(t)
   407  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   408  				require.True(t, tracker.AddIP(ip))
   409  				tracker.mostRecentTrackedIPs[newerIP.NodeID] = newerIP
   410  				tracker.bloomAdditions[newerIP.NodeID] = 2
   411  				tracker.connected[newerIP.NodeID] = newerIP
   412  				tracker.gossipableIndices[newerIP.NodeID] = 0
   413  				tracker.gossipableIPs = []*ips.ClaimedIPPort{
   414  					newerIP,
   415  				}
   416  				return tracker
   417  			}(),
   418  		},
   419  		{
   420  			name: "connected with same IP",
   421  			initialState: func() *ipTracker {
   422  				tracker := newTestIPTracker(t)
   423  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   424  				require.True(t, tracker.AddIP(ip))
   425  				return tracker
   426  			}(),
   427  			ip: ip,
   428  			expectedState: func() *ipTracker {
   429  				tracker := newTestIPTracker(t)
   430  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   431  				require.True(t, tracker.AddIP(ip))
   432  				tracker.connected[ip.NodeID] = ip
   433  				tracker.gossipableIndices[ip.NodeID] = 0
   434  				tracker.gossipableIPs = []*ips.ClaimedIPPort{
   435  					ip,
   436  				}
   437  				return tracker
   438  			}(),
   439  		},
   440  	}
   441  	for _, test := range tests {
   442  		t.Run(test.name, func(t *testing.T) {
   443  			test.initialState.Connected(test.ip)
   444  			requireEqual(t, test.expectedState, test.initialState)
   445  			requireMetricsConsistent(t, test.initialState)
   446  		})
   447  	}
   448  }
   449  
   450  func TestIPTracker_Disconnected(t *testing.T) {
   451  	tests := []struct {
   452  		name          string
   453  		initialState  *ipTracker
   454  		nodeID        ids.NodeID
   455  		expectedState *ipTracker
   456  	}{
   457  		{
   458  			name: "not tracked",
   459  			initialState: func() *ipTracker {
   460  				tracker := newTestIPTracker(t)
   461  				tracker.Connected(ip)
   462  				return tracker
   463  			}(),
   464  			nodeID:        ip.NodeID,
   465  			expectedState: newTestIPTracker(t),
   466  		},
   467  		{
   468  			name: "not gossipable",
   469  			initialState: func() *ipTracker {
   470  				tracker := newTestIPTracker(t)
   471  				tracker.Connected(ip)
   472  				tracker.ManuallyTrack(ip.NodeID)
   473  				return tracker
   474  			}(),
   475  			nodeID: ip.NodeID,
   476  			expectedState: func() *ipTracker {
   477  				tracker := newTestIPTracker(t)
   478  				tracker.Connected(ip)
   479  				tracker.ManuallyTrack(ip.NodeID)
   480  				delete(tracker.connected, ip.NodeID)
   481  				return tracker
   482  			}(),
   483  		},
   484  		{
   485  			name: "latest gossipable",
   486  			initialState: func() *ipTracker {
   487  				tracker := newTestIPTracker(t)
   488  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   489  				tracker.Connected(ip)
   490  				return tracker
   491  			}(),
   492  			nodeID: ip.NodeID,
   493  			expectedState: func() *ipTracker {
   494  				tracker := newTestIPTracker(t)
   495  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   496  				tracker.Connected(ip)
   497  				delete(tracker.connected, ip.NodeID)
   498  				delete(tracker.gossipableIndices, ip.NodeID)
   499  				tracker.gossipableIPs = tracker.gossipableIPs[:0]
   500  				return tracker
   501  			}(),
   502  		},
   503  		{
   504  			name: "non-latest gossipable",
   505  			initialState: func() *ipTracker {
   506  				tracker := newTestIPTracker(t)
   507  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   508  				tracker.Connected(ip)
   509  				tracker.OnValidatorAdded(otherIP.NodeID, nil, ids.Empty, 0)
   510  				tracker.Connected(otherIP)
   511  				return tracker
   512  			}(),
   513  			nodeID: ip.NodeID,
   514  			expectedState: func() *ipTracker {
   515  				tracker := newTestIPTracker(t)
   516  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   517  				tracker.Connected(ip)
   518  				tracker.OnValidatorAdded(otherIP.NodeID, nil, ids.Empty, 0)
   519  				tracker.Connected(otherIP)
   520  				delete(tracker.connected, ip.NodeID)
   521  				tracker.gossipableIndices = map[ids.NodeID]int{
   522  					otherIP.NodeID: 0,
   523  				}
   524  				tracker.gossipableIPs = []*ips.ClaimedIPPort{
   525  					otherIP,
   526  				}
   527  				return tracker
   528  			}(),
   529  		},
   530  	}
   531  	for _, test := range tests {
   532  		t.Run(test.name, func(t *testing.T) {
   533  			test.initialState.Disconnected(test.nodeID)
   534  			requireEqual(t, test.expectedState, test.initialState)
   535  			requireMetricsConsistent(t, test.initialState)
   536  		})
   537  	}
   538  }
   539  
   540  func TestIPTracker_OnValidatorAdded(t *testing.T) {
   541  	newerIP := newerTestIP(ip)
   542  
   543  	tests := []struct {
   544  		name          string
   545  		initialState  *ipTracker
   546  		nodeID        ids.NodeID
   547  		expectedState *ipTracker
   548  	}{
   549  		{
   550  			name: "manually tracked",
   551  			initialState: func() *ipTracker {
   552  				tracker := newTestIPTracker(t)
   553  				tracker.ManuallyTrack(ip.NodeID)
   554  				return tracker
   555  			}(),
   556  			nodeID: ip.NodeID,
   557  			expectedState: func() *ipTracker {
   558  				tracker := newTestIPTracker(t)
   559  				tracker.ManuallyTrack(ip.NodeID)
   560  				tracker.gossipableIDs.Add(ip.NodeID)
   561  				return tracker
   562  			}(),
   563  		},
   564  		{
   565  			name: "manually tracked and connected with older IP",
   566  			initialState: func() *ipTracker {
   567  				tracker := newTestIPTracker(t)
   568  				tracker.ManuallyTrack(ip.NodeID)
   569  				tracker.Connected(ip)
   570  				require.True(t, tracker.AddIP(newerIP))
   571  				return tracker
   572  			}(),
   573  			nodeID: ip.NodeID,
   574  			expectedState: func() *ipTracker {
   575  				tracker := newTestIPTracker(t)
   576  				tracker.ManuallyTrack(ip.NodeID)
   577  				tracker.Connected(ip)
   578  				require.True(t, tracker.AddIP(newerIP))
   579  				tracker.gossipableIDs.Add(ip.NodeID)
   580  				return tracker
   581  			}(),
   582  		},
   583  		{
   584  			name: "manually gossiped",
   585  			initialState: func() *ipTracker {
   586  				tracker := newTestIPTracker(t)
   587  				tracker.ManuallyGossip(ip.NodeID)
   588  				return tracker
   589  			}(),
   590  			nodeID: ip.NodeID,
   591  			expectedState: func() *ipTracker {
   592  				tracker := newTestIPTracker(t)
   593  				tracker.ManuallyGossip(ip.NodeID)
   594  				return tracker
   595  			}(),
   596  		},
   597  		{
   598  			name:         "disconnected",
   599  			initialState: newTestIPTracker(t),
   600  			nodeID:       ip.NodeID,
   601  			expectedState: func() *ipTracker {
   602  				tracker := newTestIPTracker(t)
   603  				tracker.trackedIDs.Add(ip.NodeID)
   604  				tracker.gossipableIDs.Add(ip.NodeID)
   605  				return tracker
   606  			}(),
   607  		},
   608  		{
   609  			name: "connected",
   610  			initialState: func() *ipTracker {
   611  				tracker := newTestIPTracker(t)
   612  				tracker.Connected(ip)
   613  				return tracker
   614  			}(),
   615  			nodeID: ip.NodeID,
   616  			expectedState: func() *ipTracker {
   617  				tracker := newTestIPTracker(t)
   618  				tracker.Connected(ip)
   619  				tracker.mostRecentTrackedIPs[ip.NodeID] = ip
   620  				tracker.trackedIDs.Add(ip.NodeID)
   621  				tracker.bloomAdditions[ip.NodeID] = 1
   622  				tracker.gossipableIndices[ip.NodeID] = 0
   623  				tracker.gossipableIPs = []*ips.ClaimedIPPort{
   624  					ip,
   625  				}
   626  				tracker.gossipableIDs.Add(ip.NodeID)
   627  				return tracker
   628  			}(),
   629  		},
   630  	}
   631  	for _, test := range tests {
   632  		t.Run(test.name, func(t *testing.T) {
   633  			test.initialState.OnValidatorAdded(test.nodeID, nil, ids.Empty, 0)
   634  			requireEqual(t, test.expectedState, test.initialState)
   635  			requireMetricsConsistent(t, test.initialState)
   636  		})
   637  	}
   638  }
   639  
   640  func TestIPTracker_OnValidatorRemoved(t *testing.T) {
   641  	tests := []struct {
   642  		name          string
   643  		initialState  *ipTracker
   644  		nodeID        ids.NodeID
   645  		expectedState *ipTracker
   646  	}{
   647  		{
   648  			name: "manually tracked not gossipable",
   649  			initialState: func() *ipTracker {
   650  				tracker := newTestIPTracker(t)
   651  				tracker.ManuallyTrack(ip.NodeID)
   652  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   653  				require.True(t, tracker.AddIP(ip))
   654  				return tracker
   655  			}(),
   656  			nodeID: ip.NodeID,
   657  			expectedState: func() *ipTracker {
   658  				tracker := newTestIPTracker(t)
   659  				tracker.ManuallyTrack(ip.NodeID)
   660  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   661  				require.True(t, tracker.AddIP(ip))
   662  				tracker.gossipableIDs.Remove(ip.NodeID)
   663  				return tracker
   664  			}(),
   665  		},
   666  		{
   667  			name: "manually tracked latest gossipable",
   668  			initialState: func() *ipTracker {
   669  				tracker := newTestIPTracker(t)
   670  				tracker.ManuallyTrack(ip.NodeID)
   671  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   672  				tracker.Connected(ip)
   673  				return tracker
   674  			}(),
   675  			nodeID: ip.NodeID,
   676  			expectedState: func() *ipTracker {
   677  				tracker := newTestIPTracker(t)
   678  				tracker.ManuallyTrack(ip.NodeID)
   679  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   680  				tracker.Connected(ip)
   681  				delete(tracker.gossipableIndices, ip.NodeID)
   682  				tracker.gossipableIPs = tracker.gossipableIPs[:0]
   683  				tracker.gossipableIDs.Remove(ip.NodeID)
   684  				return tracker
   685  			}(),
   686  		},
   687  		{
   688  			name: "manually gossiped",
   689  			initialState: func() *ipTracker {
   690  				tracker := newTestIPTracker(t)
   691  				tracker.ManuallyGossip(ip.NodeID)
   692  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   693  				tracker.Connected(ip)
   694  				return tracker
   695  			}(),
   696  			nodeID: ip.NodeID,
   697  			expectedState: func() *ipTracker {
   698  				tracker := newTestIPTracker(t)
   699  				tracker.ManuallyGossip(ip.NodeID)
   700  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   701  				tracker.Connected(ip)
   702  				return tracker
   703  			}(),
   704  		},
   705  		{
   706  			name: "not gossipable",
   707  			initialState: func() *ipTracker {
   708  				tracker := newTestIPTracker(t)
   709  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   710  				require.True(t, tracker.AddIP(ip))
   711  				return tracker
   712  			}(),
   713  			nodeID: ip.NodeID,
   714  			expectedState: func() *ipTracker {
   715  				tracker := newTestIPTracker(t)
   716  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   717  				require.True(t, tracker.AddIP(ip))
   718  				delete(tracker.mostRecentTrackedIPs, ip.NodeID)
   719  				tracker.trackedIDs.Remove(ip.NodeID)
   720  				tracker.gossipableIDs.Remove(ip.NodeID)
   721  				return tracker
   722  			}(),
   723  		},
   724  		{
   725  			name: "latest gossipable",
   726  			initialState: func() *ipTracker {
   727  				tracker := newTestIPTracker(t)
   728  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   729  				tracker.Connected(ip)
   730  				return tracker
   731  			}(),
   732  			nodeID: ip.NodeID,
   733  			expectedState: func() *ipTracker {
   734  				tracker := newTestIPTracker(t)
   735  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   736  				tracker.Connected(ip)
   737  				delete(tracker.mostRecentTrackedIPs, ip.NodeID)
   738  				tracker.trackedIDs.Remove(ip.NodeID)
   739  				delete(tracker.gossipableIndices, ip.NodeID)
   740  				tracker.gossipableIPs = tracker.gossipableIPs[:0]
   741  				tracker.gossipableIDs.Remove(ip.NodeID)
   742  				return tracker
   743  			}(),
   744  		},
   745  		{
   746  			name: "non-latest gossipable",
   747  			initialState: func() *ipTracker {
   748  				tracker := newTestIPTracker(t)
   749  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   750  				tracker.Connected(ip)
   751  				tracker.OnValidatorAdded(otherIP.NodeID, nil, ids.Empty, 0)
   752  				tracker.Connected(otherIP)
   753  				return tracker
   754  			}(),
   755  			nodeID: ip.NodeID,
   756  			expectedState: func() *ipTracker {
   757  				tracker := newTestIPTracker(t)
   758  				tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   759  				tracker.Connected(ip)
   760  				tracker.OnValidatorAdded(otherIP.NodeID, nil, ids.Empty, 0)
   761  				tracker.Connected(otherIP)
   762  				delete(tracker.mostRecentTrackedIPs, ip.NodeID)
   763  				tracker.trackedIDs.Remove(ip.NodeID)
   764  				tracker.gossipableIndices = map[ids.NodeID]int{
   765  					otherIP.NodeID: 0,
   766  				}
   767  				tracker.gossipableIPs = []*ips.ClaimedIPPort{
   768  					otherIP,
   769  				}
   770  				tracker.gossipableIDs.Remove(ip.NodeID)
   771  				return tracker
   772  			}(),
   773  		},
   774  	}
   775  	for _, test := range tests {
   776  		t.Run(test.name, func(t *testing.T) {
   777  			test.initialState.OnValidatorRemoved(test.nodeID, 0)
   778  			requireEqual(t, test.expectedState, test.initialState)
   779  			requireMetricsConsistent(t, test.initialState)
   780  		})
   781  	}
   782  }
   783  
   784  func TestIPTracker_GetGossipableIPs(t *testing.T) {
   785  	require := require.New(t)
   786  
   787  	tracker := newTestIPTracker(t)
   788  	tracker.Connected(ip)
   789  	tracker.Connected(otherIP)
   790  	tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   791  	tracker.OnValidatorAdded(otherIP.NodeID, nil, ids.Empty, 0)
   792  
   793  	gossipableIPs := tracker.GetGossipableIPs(ids.EmptyNodeID, bloom.EmptyFilter, nil, 2)
   794  	require.ElementsMatch([]*ips.ClaimedIPPort{ip, otherIP}, gossipableIPs)
   795  
   796  	gossipableIPs = tracker.GetGossipableIPs(ip.NodeID, bloom.EmptyFilter, nil, 2)
   797  	require.Equal([]*ips.ClaimedIPPort{otherIP}, gossipableIPs)
   798  
   799  	gossipableIPs = tracker.GetGossipableIPs(ids.EmptyNodeID, bloom.FullFilter, nil, 2)
   800  	require.Empty(gossipableIPs)
   801  
   802  	filter, err := bloom.New(8, 1024)
   803  	require.NoError(err)
   804  	bloom.Add(filter, ip.GossipID[:], nil)
   805  
   806  	readFilter, err := bloom.Parse(filter.Marshal())
   807  	require.NoError(err)
   808  
   809  	gossipableIPs = tracker.GetGossipableIPs(ip.NodeID, readFilter, nil, 2)
   810  	require.Equal([]*ips.ClaimedIPPort{otherIP}, gossipableIPs)
   811  }
   812  
   813  func TestIPTracker_BloomFiltersEverything(t *testing.T) {
   814  	require := require.New(t)
   815  
   816  	tracker := newTestIPTracker(t)
   817  	tracker.Connected(ip)
   818  	tracker.Connected(otherIP)
   819  	tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   820  	tracker.OnValidatorAdded(otherIP.NodeID, nil, ids.Empty, 0)
   821  
   822  	bloomBytes, salt := tracker.Bloom()
   823  	readFilter, err := bloom.Parse(bloomBytes)
   824  	require.NoError(err)
   825  
   826  	gossipableIPs := tracker.GetGossipableIPs(ids.EmptyNodeID, readFilter, salt, 2)
   827  	require.Empty(gossipableIPs)
   828  
   829  	require.NoError(tracker.ResetBloom())
   830  }
   831  
   832  func TestIPTracker_BloomGrows(t *testing.T) {
   833  	tests := []struct {
   834  		name string
   835  		add  func(tracker *ipTracker)
   836  	}{
   837  		{
   838  			name: "Add Validator",
   839  			add: func(tracker *ipTracker) {
   840  				tracker.OnValidatorAdded(ids.GenerateTestNodeID(), nil, ids.Empty, 0)
   841  			},
   842  		},
   843  		{
   844  			name: "Manually Track",
   845  			add: func(tracker *ipTracker) {
   846  				tracker.ManuallyTrack(ids.GenerateTestNodeID())
   847  			},
   848  		},
   849  	}
   850  	for _, test := range tests {
   851  		t.Run(test.name, func(t *testing.T) {
   852  			require := require.New(t)
   853  
   854  			tracker := newTestIPTracker(t)
   855  			initialMaxBloomCount := tracker.maxBloomCount
   856  			for i := 0; i < 2048; i++ {
   857  				test.add(tracker)
   858  			}
   859  			requireMetricsConsistent(t, tracker)
   860  
   861  			require.NoError(tracker.ResetBloom())
   862  			require.Greater(tracker.maxBloomCount, initialMaxBloomCount)
   863  			requireMetricsConsistent(t, tracker)
   864  		})
   865  	}
   866  }
   867  
   868  func TestIPTracker_BloomResetsDynamically(t *testing.T) {
   869  	require := require.New(t)
   870  
   871  	tracker := newTestIPTracker(t)
   872  	tracker.Connected(ip)
   873  	tracker.OnValidatorAdded(ip.NodeID, nil, ids.Empty, 0)
   874  	tracker.OnValidatorRemoved(ip.NodeID, 0)
   875  	tracker.maxBloomCount = 1
   876  	tracker.Connected(otherIP)
   877  	tracker.OnValidatorAdded(otherIP.NodeID, nil, ids.Empty, 0)
   878  	requireMetricsConsistent(t, tracker)
   879  
   880  	bloomBytes, salt := tracker.Bloom()
   881  	readFilter, err := bloom.Parse(bloomBytes)
   882  	require.NoError(err)
   883  
   884  	require.False(bloom.Contains(readFilter, ip.GossipID[:], salt))
   885  	require.True(bloom.Contains(readFilter, otherIP.GossipID[:], salt))
   886  }
   887  
   888  func TestIPTracker_PreventBloomFilterAddition(t *testing.T) {
   889  	require := require.New(t)
   890  
   891  	newerIP := newerTestIP(ip)
   892  	newestIP := newerTestIP(newerIP)
   893  
   894  	tracker := newTestIPTracker(t)
   895  	tracker.ManuallyGossip(ip.NodeID)
   896  	require.True(tracker.AddIP(ip))
   897  	require.True(tracker.AddIP(newerIP))
   898  	require.True(tracker.AddIP(newestIP))
   899  	require.Equal(maxIPEntriesPerNode, tracker.bloomAdditions[ip.NodeID])
   900  	requireMetricsConsistent(t, tracker)
   901  }
   902  
   903  func TestIPTracker_ShouldVerifyIP(t *testing.T) {
   904  	require := require.New(t)
   905  
   906  	newerIP := newerTestIP(ip)
   907  
   908  	tracker := newTestIPTracker(t)
   909  	require.False(tracker.ShouldVerifyIP(ip))
   910  	tracker.ManuallyTrack(ip.NodeID)
   911  	require.True(tracker.ShouldVerifyIP(ip))
   912  	tracker.ManuallyGossip(ip.NodeID)
   913  	require.True(tracker.ShouldVerifyIP(ip))
   914  	require.True(tracker.AddIP(ip))
   915  	require.False(tracker.ShouldVerifyIP(ip))
   916  	require.True(tracker.ShouldVerifyIP(newerIP))
   917  }