github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/network/p2p/node/libp2pNode_test.go (about) 1 package p2pnode_test 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "testing" 8 "time" 9 10 "github.com/libp2p/go-libp2p/core/network" 11 "github.com/libp2p/go-libp2p/core/peer" 12 "github.com/libp2p/go-libp2p/core/peerstore" 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 16 "github.com/onflow/flow-go/model/flow" 17 "github.com/onflow/flow-go/module/irrecoverable" 18 mockmodule "github.com/onflow/flow-go/module/mock" 19 "github.com/onflow/flow-go/network/channels" 20 "github.com/onflow/flow-go/network/internal/p2pfixtures" 21 "github.com/onflow/flow-go/network/internal/p2putils" 22 "github.com/onflow/flow-go/network/p2p" 23 p2plogging "github.com/onflow/flow-go/network/p2p/logging" 24 p2ptest "github.com/onflow/flow-go/network/p2p/test" 25 "github.com/onflow/flow-go/network/p2p/utils" 26 validator "github.com/onflow/flow-go/network/validator/pubsub" 27 "github.com/onflow/flow-go/utils/unittest" 28 ) 29 30 // TestMultiAddress evaluates correct translations from 31 // dns and ip4 to libp2p multi-address 32 func TestMultiAddress(t *testing.T) { 33 key := p2pfixtures.NetworkingKeyFixtures(t) 34 35 tt := []struct { 36 identity *flow.Identity 37 multiaddress string 38 }{ 39 { 40 // ip4 test case 41 identity: unittest.IdentityFixture(unittest.WithNetworkingKey(key.PublicKey()), unittest.WithAddress("172.16.254.1:72")), 42 multiaddress: "/ip4/172.16.254.1/tcp/72", 43 }, 44 { 45 // dns test case 46 identity: unittest.IdentityFixture(unittest.WithNetworkingKey(key.PublicKey()), unittest.WithAddress("consensus:2222")), 47 multiaddress: "/dns4/consensus/tcp/2222", 48 }, 49 { 50 // dns test case 51 identity: unittest.IdentityFixture(unittest.WithNetworkingKey(key.PublicKey()), unittest.WithAddress("flow.com:3333")), 52 multiaddress: "/dns4/flow.com/tcp/3333", 53 }, 54 } 55 56 for _, tc := range tt { 57 ip, port, _, err := p2putils.NetworkingInfo(tc.identity.IdentitySkeleton) 58 require.NoError(t, err) 59 60 actualAddress := utils.MultiAddressStr(ip, port) 61 assert.Equal(t, tc.multiaddress, actualAddress, "incorrect multi-address translation") 62 } 63 64 } 65 66 // TestSingleNodeLifeCycle evaluates correct lifecycle translation from start to stop the node 67 func TestSingleNodeLifeCycle(t *testing.T) { 68 ctx, cancel := context.WithCancel(context.Background()) 69 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 70 idProvider := mockmodule.NewIdentityProvider(t) 71 node, _ := p2ptest.NodeFixture(t, unittest.IdentifierFixture(), "test_single_node_life_cycle", idProvider) 72 73 node.Start(signalerCtx) 74 unittest.RequireComponentsReadyBefore(t, 100*time.Millisecond, node) 75 76 cancel() 77 unittest.RequireComponentsDoneBefore(t, 100*time.Millisecond, node) 78 } 79 80 // TestGetPeerInfo evaluates the deterministic translation between the nodes address and 81 // their libp2p info. It generates an address, and checks whether repeated translations 82 // yields the same info or not. 83 func TestGetPeerInfo(t *testing.T) { 84 for i := 0; i < 10; i++ { 85 key := p2pfixtures.NetworkingKeyFixtures(t) 86 87 // creates node-i identity 88 identity := unittest.IdentityFixture(unittest.WithNetworkingKey(key.PublicKey()), unittest.WithAddress("1.1.1.1:0")) 89 90 // translates node-i address into info 91 info, err := utils.PeerAddressInfo(identity.IdentitySkeleton) 92 require.NoError(t, err) 93 94 // repeats the translation for node-i 95 for j := 0; j < 10; j++ { 96 rinfo, err := utils.PeerAddressInfo(identity.IdentitySkeleton) 97 require.NoError(t, err) 98 assert.Equal(t, rinfo.String(), info.String(), "inconsistent id generated") 99 } 100 } 101 } 102 103 // TestAddPeers checks if nodes can be added as peers to a given node 104 func TestAddPeers(t *testing.T) { 105 count := 3 106 ctx, cancel := context.WithCancel(context.Background()) 107 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 108 idProvider := unittest.NewUpdatableIDProvider(flow.IdentityList{}) 109 110 nodes, identities := p2ptest.NodesFixture(t, unittest.IdentifierFixture(), "test_add_peers", count, idProvider) 111 p2ptest.StartNodes(t, signalerCtx, nodes) 112 defer p2ptest.StopNodes(t, nodes, cancel) 113 114 // add the remaining nodes to the first node as its set of peers 115 for _, identity := range identities[1:] { 116 peerInfo, err := utils.PeerAddressInfo(identity.IdentitySkeleton) 117 require.NoError(t, err) 118 require.NoError(t, nodes[0].ConnectToPeer(ctx, peerInfo)) 119 } 120 121 // Checks if both of the other nodes have been added as peers to the first node 122 assert.Len(t, nodes[0].Host().Network().Peers(), count-1) 123 } 124 125 // TestRemovePeers checks if nodes can be removed as peers from a given node 126 func TestRemovePeers(t *testing.T) { 127 count := 3 128 ctx, cancel := context.WithCancel(context.Background()) 129 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 130 idProvider := unittest.NewUpdatableIDProvider(flow.IdentityList{}) 131 // create nodes 132 nodes, identities := p2ptest.NodesFixture(t, unittest.IdentifierFixture(), "test_remove_peers", count, idProvider) 133 peerInfos, errs := utils.PeerInfosFromIDs(identities) 134 assert.Len(t, errs, 0) 135 136 p2ptest.StartNodes(t, signalerCtx, nodes) 137 defer p2ptest.StopNodes(t, nodes, cancel) 138 139 // add nodes two and three to the first node as its peers 140 for _, pInfo := range peerInfos[1:] { 141 require.NoError(t, nodes[0].ConnectToPeer(ctx, pInfo)) 142 } 143 144 // check if all other nodes have been added as peers to the first node 145 assert.Len(t, nodes[0].Host().Network().Peers(), count-1) 146 147 // disconnect from each peer and assert that the connection no longer exists 148 for _, pInfo := range peerInfos[1:] { 149 require.NoError(t, nodes[0].RemovePeer(pInfo.ID)) 150 assert.Equal(t, network.NotConnected, nodes[0].Host().Network().Connectedness(pInfo.ID)) 151 } 152 } 153 154 func TestConnGater(t *testing.T) { 155 ctx, cancel := context.WithCancel(context.Background()) 156 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 157 158 sporkID := unittest.IdentifierFixture() 159 idProvider := mockmodule.NewIdentityProvider(t) 160 161 node1Peers := unittest.NewProtectedMap[peer.ID, struct{}]() 162 node1, identity1 := p2ptest.NodeFixture(t, sporkID, t.Name(), idProvider, p2ptest.WithConnectionGater(p2ptest.NewConnectionGater(idProvider, func(pid peer.ID) error { 163 if !node1Peers.Has(pid) { 164 return fmt.Errorf("peer id not found: %s", p2plogging.PeerId(pid)) 165 } 166 return nil 167 }))) 168 idProvider.On("ByPeerID", node1.ID()).Return(&identity1, true).Maybe() 169 170 p2ptest.StartNode(t, signalerCtx, node1) 171 defer p2ptest.StopNode(t, node1, cancel) 172 173 node1Info, err := utils.PeerAddressInfo(identity1.IdentitySkeleton) 174 assert.NoError(t, err) 175 176 node2Peers := unittest.NewProtectedMap[peer.ID, struct{}]() 177 node2, identity2 := p2ptest.NodeFixture(t, sporkID, t.Name(), idProvider, p2ptest.WithConnectionGater(p2ptest.NewConnectionGater(idProvider, func(pid peer.ID) error { 178 if !node2Peers.Has(pid) { 179 return fmt.Errorf("id not found: %s", p2plogging.PeerId(pid)) 180 } 181 return nil 182 }))) 183 idProvider.On("ByPeerID", node2.ID()).Return(&identity2, 184 185 true).Maybe() 186 187 p2ptest.StartNode(t, signalerCtx, node2) 188 defer p2ptest.StopNode(t, node2, cancel) 189 190 node2Info, err := utils.PeerAddressInfo(identity2.IdentitySkeleton) 191 assert.NoError(t, err) 192 193 node1.Host().Peerstore().AddAddrs(node2Info.ID, node2Info.Addrs, peerstore.PermanentAddrTTL) 194 node2.Host().Peerstore().AddAddrs(node1Info.ID, node1Info.Addrs, peerstore.PermanentAddrTTL) 195 196 err = node1.OpenAndWriteOnStream(ctx, node2Info.ID, t.Name(), func(stream network.Stream) error { 197 // no-op, as the connection should not be possible 198 return nil 199 }) 200 require.ErrorContains(t, err, "target node is not on the approved list of nodes") 201 202 err = node2.OpenAndWriteOnStream(ctx, node1Info.ID, t.Name(), func(stream network.Stream) error { 203 // no-op, as the connection should not be possible 204 return nil 205 }) 206 require.ErrorContains(t, err, "target node is not on the approved list of nodes") 207 208 node1Peers.Add(node2Info.ID, struct{}{}) 209 err = node1.OpenAndWriteOnStream(ctx, node2Info.ID, t.Name(), func(stream network.Stream) error { 210 // no-op, as the connection should not be possible 211 return nil 212 }) 213 require.Error(t, err) 214 215 node2Peers.Add(node1Info.ID, struct{}{}) 216 err = node1.OpenAndWriteOnStream(ctx, node2Info.ID, t.Name(), func(stream network.Stream) error { 217 // no-op, as the connection should not be possible 218 return nil 219 }) 220 require.NoError(t, err) 221 } 222 223 // TestNode_HasSubscription checks that when a node subscribes to a topic HasSubscription should return true. 224 func TestNode_HasSubscription(t *testing.T) { 225 ctx, cancel := context.WithCancel(context.Background()) 226 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 227 idProvider := unittest.NewUpdatableIDProvider(flow.IdentityList{}) 228 sporkID := unittest.IdentifierFixture() 229 node, _ := p2ptest.NodeFixture(t, sporkID, "test_has_subscription", idProvider) 230 231 p2ptest.StartNode(t, signalerCtx, node) 232 defer p2ptest.StopNode(t, node, cancel) 233 234 logger := unittest.Logger() 235 236 topicValidator := validator.TopicValidator(logger, func(id peer.ID) error { 237 return nil 238 }) 239 240 // create test topic 241 topic := channels.TopicFromChannel(channels.TestNetworkChannel, unittest.IdentifierFixture()) 242 _, err := node.Subscribe(topic, topicValidator) 243 require.NoError(t, err) 244 245 require.True(t, node.HasSubscription(topic)) 246 247 // create topic with no subscription 248 topic = channels.TopicFromChannel(channels.ConsensusCommittee, unittest.IdentifierFixture()) 249 require.False(t, node.HasSubscription(topic)) 250 } 251 252 // TestCreateStream_SinglePairwiseConnection ensures that despite the number of concurrent streams created from peer -> peer, only a single 253 // connection will ever be created between two peers on initial peer dialing and subsequent streams will reuse that connection. 254 func TestCreateStream_SinglePairwiseConnection(t *testing.T) { 255 sporkId := unittest.IdentifierFixture() 256 nodeCount := 3 257 ctx, cancel := context.WithCancel(context.Background()) 258 defer cancel() 259 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 260 idProvider := unittest.NewUpdatableIDProvider(flow.IdentityList{}) 261 nodes, ids := p2ptest.NodesFixture(t, sporkId, "test_create_stream_single_pairwise_connection", nodeCount, idProvider, p2ptest.WithDefaultResourceManager()) 262 idProvider.SetIdentities(ids) 263 264 p2ptest.StartNodes(t, signalerCtx, nodes) 265 defer p2ptest.StopNodes(t, nodes, cancel) 266 267 ctxWithTimeout, cancel := context.WithTimeout(ctx, 3*time.Second) 268 defer cancel() 269 done := make(chan struct{}) 270 numOfStreamsPerNode := 100 // create large number of streamChan per node per connection to ensure the resource manager does not cause starvation of resources 271 expectedTotalNumOfStreams := 600 272 273 // create a number of streamChan concurrently between each node 274 streamChan := make(chan network.Stream, expectedTotalNumOfStreams) 275 276 go createConcurrentStreams(t, ctxWithTimeout, nodes, ids, numOfStreamsPerNode, streamChan, done) 277 unittest.RequireCloseBefore(t, done, 5*time.Second, "could not create streamChan on time") 278 require.Len(t, 279 streamChan, 280 expectedTotalNumOfStreams, 281 fmt.Sprintf("expected %d total number of streamChan created got %d", expectedTotalNumOfStreams, len(streamChan))) 282 283 // ensure only a single connection exists between all nodes 284 ensureSinglePairwiseConnection(t, nodes) 285 close(streamChan) 286 } 287 288 // createStreams will attempt to create n number of streams concurrently between each combination of node pairs. 289 func createConcurrentStreams(t *testing.T, ctx context.Context, nodes []p2p.LibP2PNode, ids flow.IdentityList, n int, streams chan network.Stream, done chan struct{}) { 290 defer close(done) 291 var wg sync.WaitGroup 292 for _, this := range nodes { 293 for i, other := range nodes { 294 if this == other { 295 continue 296 } 297 298 pInfo, err := utils.PeerAddressInfo(ids[i].IdentitySkeleton) 299 require.NoError(t, err) 300 this.Host().Peerstore().AddAddrs(pInfo.ID, pInfo.Addrs, peerstore.AddressTTL) 301 302 for j := 0; j < n; j++ { 303 wg.Add(1) 304 go func(sender p2p.LibP2PNode) { 305 defer wg.Done() 306 err := sender.OpenAndWriteOnStream(ctx, pInfo.ID, t.Name(), func(stream network.Stream) error { 307 streams <- stream 308 309 // wait for the done signal to close the stream 310 <-ctx.Done() 311 return nil 312 }) 313 require.NoError(t, err) 314 }(this) 315 } 316 } 317 // brief sleep to prevent sender and receiver dialing each other at the same time if separate goroutines resulting 318 // in 2 connections 1 created by each node, this happens because we are calling CreateStream concurrently. 319 time.Sleep(500 * time.Millisecond) 320 } 321 unittest.RequireReturnsBefore(t, wg.Wait, 3*time.Second, "could not create streams on time") 322 } 323 324 // ensureSinglePairwiseConnection ensure each node in the list has exactly one connection to every other node in the list. 325 func ensureSinglePairwiseConnection(t *testing.T, nodes []p2p.LibP2PNode) { 326 for _, this := range nodes { 327 for _, other := range nodes { 328 if this == other { 329 continue 330 } 331 require.Len(t, this.Host().Network().ConnsToPeer(other.ID()), 1) 332 } 333 } 334 }