github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/network/p2p/connection/peerManager_test.go (about) 1 package connection_test 2 3 import ( 4 "context" 5 "math/rand" 6 "os" 7 "sync" 8 "testing" 9 "time" 10 11 "github.com/ipfs/go-log" 12 "github.com/libp2p/go-libp2p/core/peer" 13 "github.com/rs/zerolog" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/mock" 16 "github.com/stretchr/testify/require" 17 "github.com/stretchr/testify/suite" 18 19 "github.com/onflow/flow-go/module/irrecoverable" 20 "github.com/onflow/flow-go/network/internal/p2pfixtures" 21 "github.com/onflow/flow-go/network/p2p/connection" 22 "github.com/onflow/flow-go/network/p2p/keyutils" 23 mockp2p "github.com/onflow/flow-go/network/p2p/mock" 24 "github.com/onflow/flow-go/utils/unittest" 25 ) 26 27 type PeerManagerTestSuite struct { 28 suite.Suite 29 log zerolog.Logger 30 } 31 32 func TestPeerManagerTestSuite(t *testing.T) { 33 suite.Run(t, new(PeerManagerTestSuite)) 34 } 35 36 func (suite *PeerManagerTestSuite) SetupTest() { 37 suite.log = zerolog.New(os.Stderr).Level(zerolog.ErrorLevel) 38 log.SetAllLoggers(log.LevelError) 39 } 40 41 func (suite *PeerManagerTestSuite) generatePeerIDs(n int) peer.IDSlice { 42 pids := peer.IDSlice{} 43 for i := 0; i < n; i++ { 44 key := p2pfixtures.NetworkingKeyFixtures(suite.T()) 45 pid, err := keyutils.PeerIDFromFlowPublicKey(key.PublicKey()) 46 require.NoError(suite.T(), err) 47 pids = append(pids, pid) 48 } 49 50 return pids 51 } 52 53 // TestUpdatePeers tests that updatePeers calls the connector with the expected list of ids to connect and disconnect 54 // from. The tests are cumulative and ordered. 55 func (suite *PeerManagerTestSuite) TestUpdatePeers() { 56 ctx, cancel := context.WithCancel(context.Background()) 57 defer cancel() 58 59 // create some test ids 60 pids := suite.generatePeerIDs(10) 61 62 // create the connector mock to check ids requested for connect and disconnect 63 peerUpdater := mockp2p.NewPeerUpdater(suite.T()) 64 peerUpdater.On("UpdatePeers", mock.Anything, mock.AnythingOfType("peer.IDSlice")). 65 Run(func(args mock.Arguments) { 66 idArg := args[1].(peer.IDSlice) 67 assert.ElementsMatch(suite.T(), pids, idArg) 68 }). 69 Return(nil) 70 71 // create the peer manager (but don't start it) 72 pm := connection.NewPeerManager(suite.log, connection.DefaultPeerUpdateInterval, peerUpdater) 73 pm.SetPeersProvider(func() peer.IDSlice { 74 return pids 75 }) 76 77 // very first call to updatepeer 78 suite.Run("updatePeers only connects to all peers the first time", func() { 79 pm.ForceUpdatePeers(ctx) 80 peerUpdater.AssertNumberOfCalls(suite.T(), "UpdatePeers", 1) 81 }) 82 83 // a subsequent call to updatePeers should request a connector.UpdatePeers to existing ids and new ids 84 suite.Run("updatePeers connects to old and new peers", func() { 85 // create a new id 86 newPIDs := suite.generatePeerIDs(1) 87 pids = append(pids, newPIDs...) 88 89 pm.ForceUpdatePeers(ctx) 90 peerUpdater.AssertNumberOfCalls(suite.T(), "UpdatePeers", 2) 91 }) 92 93 // when ids are only excluded, connector.UpdatePeers should be called 94 suite.Run("updatePeers disconnects from extra peers", func() { 95 // delete an id 96 pids = removeRandomElement(pids) 97 98 pm.ForceUpdatePeers(ctx) 99 peerUpdater.AssertNumberOfCalls(suite.T(), "UpdatePeers", 3) 100 }) 101 102 // addition and deletion of ids should result in a call to connector.UpdatePeers 103 suite.Run("updatePeers connects to new peers and disconnects from extra peers", func() { 104 // remove a couple of ids 105 pids = removeRandomElement(pids) 106 pids = removeRandomElement(pids) 107 108 // add a couple of new ids 109 newPIDs := suite.generatePeerIDs(2) 110 pids = append(pids, newPIDs...) 111 112 pm.ForceUpdatePeers(ctx) 113 114 peerUpdater.AssertNumberOfCalls(suite.T(), "UpdatePeers", 4) 115 }) 116 } 117 118 func removeRandomElement(pids peer.IDSlice) peer.IDSlice { 119 i := rand.Intn(len(pids)) 120 pids[i] = pids[len(pids)-1] 121 return pids[:len(pids)-1] 122 } 123 124 // TestPeriodicPeerUpdate tests that the peer manager runs periodically 125 func (suite *PeerManagerTestSuite) TestPeriodicPeerUpdate() { 126 ctx, cancel := context.WithCancel(context.Background()) 127 defer cancel() 128 129 signalerCtx := irrecoverable.NewMockSignalerContext(suite.T(), ctx) 130 131 // create some test ids 132 pids := suite.generatePeerIDs(10) 133 134 peerUpdater := mockp2p.NewPeerUpdater(suite.T()) 135 wg := &sync.WaitGroup{} // keeps track of number of calls on `ConnectPeers` 136 mu := &sync.Mutex{} // provides mutual exclusion on calls to `ConnectPeers` 137 count := 0 138 times := 2 // we expect it to be called twice at least 139 wg.Add(times) 140 peerUpdater.On("UpdatePeers", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { 141 mu.Lock() 142 defer mu.Unlock() 143 144 if count < times { 145 count++ 146 wg.Done() 147 } 148 }).Return(nil) 149 150 peerUpdateInterval := 10 * time.Millisecond 151 pm := connection.NewPeerManager(suite.log, peerUpdateInterval, peerUpdater) 152 pm.SetPeersProvider(func() peer.IDSlice { 153 return pids 154 }) 155 156 pm.Start(signalerCtx) 157 unittest.RequireCloseBefore(suite.T(), pm.Ready(), 2*time.Second, "could not start peer manager") 158 159 unittest.RequireReturnsBefore(suite.T(), wg.Wait, 2*peerUpdateInterval, 160 "UpdatePeers is not running on UpdateIntervals") 161 } 162 163 // TestOnDemandPeerUpdate tests that the peer update can be requested on demand and in between the periodic runs 164 func (suite *PeerManagerTestSuite) TestOnDemandPeerUpdate() { 165 ctx, cancel := context.WithCancel(context.Background()) 166 defer cancel() 167 168 signalerCtx := irrecoverable.NewMockSignalerContext(suite.T(), ctx) 169 170 // create some test ids 171 pids := suite.generatePeerIDs(10) 172 173 // chooses peer interval rate deliberately long to capture on demand peer update 174 peerUpdateInterval := time.Hour 175 176 // creates mock peerUpdater 177 wg := &sync.WaitGroup{} // keeps track of number of calls on `ConnectPeers` 178 mu := &sync.Mutex{} // provides mutual exclusion on calls to `ConnectPeers` 179 count := 0 180 times := 2 // we expect it to be called twice overall 181 wg.Add(1) // this accounts for one invocation, the other invocation is subsequent 182 peerUpdater := mockp2p.NewPeerUpdater(suite.T()) 183 // captures the first periodic update initiated after start to complete 184 peerUpdater.On("UpdatePeers", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { 185 mu.Lock() 186 defer mu.Unlock() 187 188 if count < times { 189 count++ 190 wg.Done() 191 } 192 }).Return(nil) 193 194 pm := connection.NewPeerManager(suite.log, peerUpdateInterval, peerUpdater) 195 pm.SetPeersProvider(func() peer.IDSlice { 196 return pids 197 }) 198 pm.Start(signalerCtx) 199 unittest.RequireCloseBefore(suite.T(), pm.Ready(), 2*time.Second, "could not start peer manager") 200 201 unittest.RequireReturnsBefore(suite.T(), wg.Wait, 1*time.Second, 202 "UpdatePeers is not running on startup") 203 204 // makes a request for peer update 205 wg.Add(1) // expects a call to `ConnectPeers` by requesting peer update 206 pm.RequestPeerUpdate() 207 208 // assert that a call to connect to peers is made 209 unittest.RequireReturnsBefore(suite.T(), wg.Wait, 1*time.Second, 210 "UpdatePeers is not running on request") 211 } 212 213 // TestConcurrentOnDemandPeerUpdate tests that concurrent on-demand peer update request never block 214 func (suite *PeerManagerTestSuite) TestConcurrentOnDemandPeerUpdate() { 215 ctx, cancel := context.WithCancel(context.Background()) 216 defer cancel() 217 218 signalerCtx := irrecoverable.NewMockSignalerContext(suite.T(), ctx) 219 220 // create some test ids 221 pids := suite.generatePeerIDs(10) 222 223 peerUpdater := mockp2p.NewPeerUpdater(suite.T()) 224 // connectPeerGate channel gates the return of the peerUpdater 225 connectPeerGate := make(chan time.Time) 226 defer close(connectPeerGate) 227 228 // choose the periodic interval as a high value so that periodic runs don't interfere with this test 229 peerUpdateInterval := time.Hour 230 231 peerUpdater.On("UpdatePeers", mock.Anything, mock.Anything).Return(nil). 232 WaitUntil(connectPeerGate) // blocks call for connectPeerGate channel 233 pm := connection.NewPeerManager(suite.log, peerUpdateInterval, peerUpdater) 234 pm.SetPeersProvider(func() peer.IDSlice { 235 return pids 236 }) 237 238 // start the peer manager 239 pm.Start(signalerCtx) 240 241 // this should trigger the first update and which will block on the ConnectPeers to return 242 unittest.RequireCloseBefore(suite.T(), pm.Ready(), 2*time.Second, "could not start peer manager") 243 244 // assert that the first update started 245 assert.Eventually(suite.T(), func() bool { 246 return peerUpdater.AssertNumberOfCalls(suite.T(), "UpdatePeers", 1) 247 }, 3*time.Second, 100*time.Millisecond) 248 249 // makes 10 concurrent request for peer update 250 unittest.RequireConcurrentCallsReturnBefore(suite.T(), pm.RequestPeerUpdate, 10, time.Second, 251 "concurrent peer update requests could not return on time") 252 253 // allow the first periodic update (which should be currently blocked) to finish 254 connectPeerGate <- time.Now() 255 256 // assert that only two calls to UpdatePeers were made (one by the periodic update and one by the on-demand update) 257 assert.Eventually(suite.T(), func() bool { 258 return peerUpdater.AssertNumberOfCalls(suite.T(), "UpdatePeers", 2) 259 }, 10*time.Second, 100*time.Millisecond) 260 }