github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/network/p2p/test/sporking_test.go (about) 1 package p2ptest_test 2 3 import ( 4 "context" 5 "testing" 6 "time" 7 8 "github.com/libp2p/go-libp2p/core/network" 9 "github.com/libp2p/go-libp2p/core/peer" 10 "github.com/libp2p/go-libp2p/core/peerstore" 11 "github.com/stretchr/testify/assert" 12 "github.com/stretchr/testify/require" 13 14 "github.com/onflow/flow-go/config" 15 "github.com/onflow/flow-go/model/flow" 16 libp2pmessage "github.com/onflow/flow-go/model/libp2p/message" 17 "github.com/onflow/flow-go/network/message" 18 19 "github.com/onflow/flow-go/network/p2p" 20 p2ptest "github.com/onflow/flow-go/network/p2p/test" 21 22 "github.com/onflow/flow-go/network/p2p/utils" 23 24 "github.com/onflow/flow-go/module/irrecoverable" 25 "github.com/onflow/flow-go/network/channels" 26 "github.com/onflow/flow-go/network/internal/p2pfixtures" 27 flowpubsub "github.com/onflow/flow-go/network/validator/pubsub" 28 "github.com/onflow/flow-go/utils/unittest" 29 ) 30 31 // Tests in this file evaluate tests that the network layer behaves as expected after a spork. 32 // All network related sporking requirements can be supported via libp2p directly, 33 // without needing any additional support in Flow other than providing the new root block ID. 34 // Sporking can be supported by two ways: 35 // 1. Updating the network key of a node after it is moved from the old chain to the new chain 36 // 2. Updating the Flow Libp2p protocol ID suffix to prevent one-to-one messaging across sporks and 37 // updating the channel suffix to prevent one-to-K messaging (PubSub) across sporks. 38 // 1 and 2 both can independently ensure that nodes from the old chain cannot communicate with nodes on the new chain 39 // These tests are just to reconfirm the network behaviour and provide a test bed for future tests for sporking, if needed 40 41 // TestCrosstalkPreventionOnNetworkKeyChange tests that a node from the old chain cannot talk to a node in the new chain 42 // if it's network key is updated while the libp2p protocol ID remains the same 43 func TestCrosstalkPreventionOnNetworkKeyChange(t *testing.T) { 44 idProvider := unittest.NewUpdatableIDProvider(flow.IdentityList{}) 45 ctx, cancel := context.WithCancel(context.Background()) 46 defer cancel() 47 48 ctx1, cancel1 := context.WithCancel(ctx) 49 signalerCtx1 := irrecoverable.NewMockSignalerContext(t, ctx1) 50 51 ctx2, cancel2 := context.WithCancel(ctx) 52 signalerCtx2 := irrecoverable.NewMockSignalerContext(t, ctx2) 53 54 ctx2a, cancel2a := context.WithCancel(ctx) 55 signalerCtx2a := irrecoverable.NewMockSignalerContext(t, ctx2a) 56 57 // create and start node 1 on localhost and random port 58 node1key := p2pfixtures.NetworkingKeyFixtures(t) 59 sporkId := unittest.IdentifierFixture() 60 61 node1, id1 := p2ptest.NodeFixture(t, 62 sporkId, 63 "test_crosstalk_prevention_on_network_key_change", 64 idProvider, 65 p2ptest.WithNetworkingPrivateKey(node1key), 66 ) 67 idProvider.SetIdentities(flow.IdentityList{&id1}) 68 69 p2ptest.StartNode(t, signalerCtx1, node1) 70 defer p2ptest.StopNode(t, node1, cancel1) 71 72 t.Logf(" %s node started on %s", id1.NodeID.String(), id1.Address) 73 t.Logf("libp2p ID for %s: %s", id1.NodeID.String(), node1.ID()) 74 75 // create and start node 2 on localhost and random port 76 node2key := p2ptest.NetworkingKeyFixtures(t) 77 node2, id2 := p2ptest.NodeFixture(t, 78 sporkId, 79 "test_crosstalk_prevention_on_network_key_change", 80 idProvider, 81 p2ptest.WithNetworkingPrivateKey(node2key), 82 ) 83 idProvider.SetIdentities(flow.IdentityList{&id1, &id2}) 84 85 p2ptest.StartNode(t, signalerCtx2, node2) 86 87 peerInfo2, err := utils.PeerAddressInfo(id2.IdentitySkeleton) 88 require.NoError(t, err) 89 90 // create stream from node 1 to node 2 91 node1.Host().Peerstore().AddAddrs(peerInfo2.ID, peerInfo2.Addrs, peerstore.AddressTTL) 92 err = node1.OpenAndWriteOnStream(context.Background(), peerInfo2.ID, t.Name(), func(stream network.Stream) error { 93 require.NotNil(t, stream) 94 return nil 95 }) 96 require.NoError(t, err) 97 98 // Simulate a hard-spoon: node1 is on the old chain, but node2 is moved from the old chain to the new chain 99 // stop node 2 and start it again with a different networking key but on the same IP and port 100 p2ptest.StopNode(t, node2, cancel2) 101 102 // start node2 with the same name, ip and port but with the new key 103 node2keyNew := p2pfixtures.NetworkingKeyFixtures(t) 104 assert.False(t, node2key.Equals(node2keyNew)) 105 node2, id2New := p2ptest.NodeFixture(t, 106 sporkId, 107 "test_crosstalk_prevention_on_network_key_change", 108 idProvider, 109 p2ptest.WithNetworkingPrivateKey(node2keyNew), 110 p2ptest.WithNetworkingAddress(id2.Address), 111 ) 112 idProvider.SetIdentities(flow.IdentityList{&id1, &id2New}) 113 114 p2ptest.StartNode(t, signalerCtx2a, node2) 115 defer p2ptest.StopNode(t, node2, cancel2a) 116 117 // make sure the node2 indeed came up on the old ip and port 118 assert.Equal(t, id2New.Address, id2.Address) 119 120 // attempt to create a stream from node 1 (old chain) to node 2 (new chain) 121 // this time it should fail since node 2 is using a different public key 122 // (and therefore has a different libp2p node id) 123 testOneToOneMessagingFails(t, node1, peerInfo2) 124 } 125 126 // TestOneToOneCrosstalkPrevention tests that a node from the old chain cannot talk directly to a node in the new chain 127 // if the Flow libp2p protocol ID is updated while the network keys are kept the same. 128 func TestOneToOneCrosstalkPrevention(t *testing.T) { 129 idProvider := unittest.NewUpdatableIDProvider(flow.IdentityList{}) 130 ctx, cancel := context.WithCancel(context.Background()) 131 defer cancel() 132 133 ctx1, cancel1 := context.WithCancel(ctx) 134 signalerCtx1 := irrecoverable.NewMockSignalerContext(t, ctx1) 135 136 ctx2, cancel2 := context.WithCancel(ctx) 137 signalerCtx2 := irrecoverable.NewMockSignalerContext(t, ctx2) 138 139 ctx2a, cancel2a := context.WithCancel(ctx) 140 signalerCtx2a := irrecoverable.NewMockSignalerContext(t, ctx2a) 141 142 sporkId1 := unittest.IdentifierFixture() 143 144 // create and start node 1 on localhost and random port 145 node1, id1 := p2ptest.NodeFixture(t, sporkId1, "test_one_to_one_crosstalk_prevention", idProvider) 146 147 p2ptest.StartNode(t, signalerCtx1, node1) 148 defer p2ptest.StopNode(t, node1, cancel1) 149 150 peerInfo1, err := utils.PeerAddressInfo(id1.IdentitySkeleton) 151 require.NoError(t, err) 152 153 // create and start node 2 on localhost and random port 154 node2, id2 := p2ptest.NodeFixture(t, sporkId1, "test_one_to_one_crosstalk_prevention", idProvider) 155 156 idProvider.SetIdentities(flow.IdentityList{&id1, &id2}) 157 p2ptest.StartNode(t, signalerCtx2, node2) 158 159 // create stream from node 1 to node 2 160 node2.Host().Peerstore().AddAddrs(peerInfo1.ID, peerInfo1.Addrs, peerstore.AddressTTL) 161 err = node2.OpenAndWriteOnStream(context.Background(), peerInfo1.ID, t.Name(), func(stream network.Stream) error { 162 assert.NotNil(t, stream) 163 return nil 164 }) 165 require.NoError(t, err) 166 167 // Simulate a hard-spoon: node1 is on the old chain, but node2 is moved from the old chain to the new chain 168 // stop node 2 and start it again with a different libp2p protocol id to listen for 169 p2ptest.StopNode(t, node2, cancel2) 170 171 // start node2 with the same address and root key but different root block id 172 node2, id2New := p2ptest.NodeFixture(t, 173 unittest.IdentifierFixture(), // update the flow root id for node 2. node1 is still listening on the old protocol 174 "test_one_to_one_crosstalk_prevention", 175 idProvider, 176 p2ptest.WithNetworkingAddress(id2.Address), 177 ) 178 idProvider.SetIdentities(flow.IdentityList{&id1, &id2New}) 179 180 p2ptest.StartNode(t, signalerCtx2a, node2) 181 defer p2ptest.StopNode(t, node2, cancel2a) 182 183 // make sure the node2 indeed came up on the old ip and port 184 assert.Equal(t, id2New.Address, id2.Address) 185 186 // attempt to create a stream from node 2 (new chain) to node 1 (old chain) 187 // this time it should fail since node 2 is listening on a different protocol 188 testOneToOneMessagingFails(t, node2, peerInfo1) 189 } 190 191 // TestOneToKCrosstalkPrevention tests that a node from the old chain cannot talk to a node in the new chain via PubSub 192 // if the channel is updated while the network keys are kept the same. 193 func TestOneToKCrosstalkPrevention(t *testing.T) { 194 idProvider := unittest.NewUpdatableIDProvider(flow.IdentityList{}) 195 ctx, cancel := context.WithCancel(context.Background()) 196 defer cancel() 197 198 ctx1, cancel1 := context.WithCancel(ctx) 199 signalerCtx1 := irrecoverable.NewMockSignalerContext(t, ctx1) 200 201 ctx2, cancel2 := context.WithCancel(ctx) 202 signalerCtx2 := irrecoverable.NewMockSignalerContext(t, ctx2) 203 204 // root id before spork 205 previousSporkId := unittest.IdentifierFixture() 206 207 // create and start node 1 on localhost and random port 208 cfg, err := config.DefaultConfig() 209 require.NoError(t, err) 210 // cross-talk prevention is intrinsically tied to how we encode topics, peer scoring adds another layer of protection by preventing unknown identifiers 211 // from joining the mesh. As this test simulates the scenario where a node is moved from the old chain to the new chain, we disable peer scoring 212 // to allow the node to join the mesh on the new chain, otherwise the node will be disconnected from the mesh due to peer scoring penalty for unknown identifiers. 213 cfg.NetworkConfig.GossipSub.PeerScoringEnabled = false 214 cfg.NetworkConfig.GossipSub.RpcInspector.Validation.InspectionProcess.Inspect.RejectUnstakedPeers = false 215 node1, id1 := p2ptest.NodeFixture(t, 216 previousSporkId, 217 "test_one_to_k_crosstalk_prevention", 218 idProvider, 219 p2ptest.OverrideFlowConfig(cfg), 220 ) 221 222 p2ptest.StartNode(t, signalerCtx1, node1) 223 defer p2ptest.StopNode(t, node1, cancel1) 224 idProvider.SetIdentities(flow.IdentityList{&id1}) 225 226 // create and start node 2 on localhost and random port with the same root block ID 227 node2, id2 := p2ptest.NodeFixture(t, 228 previousSporkId, 229 "test_one_to_k_crosstalk_prevention", 230 idProvider, 231 ) 232 233 p2ptest.StartNode(t, signalerCtx2, node2) 234 defer p2ptest.StopNode(t, node2, cancel2) 235 236 pInfo2, err := utils.PeerAddressInfo(id2.IdentitySkeleton) 237 require.NoError(t, err) 238 239 // spork topic is derived by suffixing the channel with the root block ID 240 topicBeforeSpork := channels.TopicFromChannel(channels.TestNetworkChannel, previousSporkId) 241 242 logger := unittest.Logger() 243 topicValidator := flowpubsub.TopicValidator(logger, unittest.AllowAllPeerFilter()) 244 245 // both nodes are initially on the same spork and subscribed to the same topic 246 _, err = node1.Subscribe(topicBeforeSpork, topicValidator) 247 require.NoError(t, err) 248 sub2, err := node2.Subscribe(topicBeforeSpork, topicValidator) 249 require.NoError(t, err) 250 251 // add node 2 as a peer of node 1 252 err = node1.ConnectToPeer(ctx, pInfo2) 253 require.NoError(t, err) 254 255 // let the two nodes form the mesh 256 time.Sleep(time.Second) 257 258 // assert that node 1 can successfully send a message to node 2 via PubSub 259 outgoingMessageScope, err := message.NewOutgoingScope( 260 flow.IdentifierList{unittest.IdentifierFixture()}, 261 topicBeforeSpork, 262 &libp2pmessage.TestMessage{ 263 Text: string("hello"), 264 }, 265 unittest.NetworkCodec().Encode, 266 message.ProtocolTypePubSub) 267 require.NoError(t, err) 268 269 expectedReceivedData, err := outgoingMessageScope.Proto().Marshal() 270 require.NoError(t, err) 271 272 // send a 1-k message from source node to destination node 273 err = node1.Publish(ctx, outgoingMessageScope) 274 require.NoError(t, err) 275 276 // assert that the message is received by the destination node 277 unittest.AssertReturnsBefore(t, func() { 278 msg, err := sub2.Next(ctx) 279 require.NoError(t, err) 280 assert.Equal(t, expectedReceivedData, msg.Data) 281 }, 282 // libp2p hearbeats every second, so at most the message should take 1 second 283 2*time.Second) 284 285 // new root id after spork 286 rootIDAfterSpork := unittest.IdentifierFixture() 287 288 // topic after the spork 289 topicAfterSpork := channels.TopicFromChannel(channels.TestNetworkChannel, rootIDAfterSpork) 290 291 // mimic that node1 is now part of the new spork while node2 remains on the old spork 292 // by unsubscribing node1 from 'topicBeforeSpork' and subscribing it to 'topicAfterSpork' 293 // and keeping node2 subscribed to topic 'topicBeforeSpork' 294 err = node1.Unsubscribe(topicBeforeSpork) 295 require.NoError(t, err) 296 _, err = node1.Subscribe(topicAfterSpork, topicValidator) 297 require.NoError(t, err) 298 299 // assert that node 1 can no longer send a message to node 2 via PubSub 300 outgoingMessageScope, err = message.NewOutgoingScope( 301 flow.IdentifierList{id2.NodeID}, 302 topicAfterSpork, 303 &libp2pmessage.TestMessage{ 304 Text: string("hello"), 305 }, 306 unittest.NetworkCodec().Encode, 307 message.ProtocolTypePubSub) 308 require.NoError(t, err) 309 310 // send a 1-k message from source node to destination node 311 err = node1.Publish(ctx, outgoingMessageScope) 312 require.NoError(t, err) 313 314 // assert that the message is never received by the destination node 315 _ = unittest.RequireNeverReturnBefore(t, func() { 316 _, _ = sub2.Next(ctx) 317 }, 318 // libp2p hearbeats every second, so at most the message should take 1 second 319 2*time.Second, 320 "nodes on different sporks were able to communicate") 321 } 322 323 func testOneToOneMessagingFails(t *testing.T, sourceNode p2p.LibP2PNode, peerInfo peer.AddrInfo) { 324 // create stream from source node to destination address 325 sourceNode.Host().Peerstore().AddAddrs(peerInfo.ID, peerInfo.Addrs, peerstore.AddressTTL) 326 ctx, cancel := context.WithCancel(context.Background()) 327 defer cancel() 328 err := sourceNode.OpenAndWriteOnStream(ctx, peerInfo.ID, t.Name(), func(stream network.Stream) error { 329 // this callback should never be called 330 assert.Fail(t, "stream creation should have failed") 331 return nil 332 }) 333 // assert that stream creation failed 334 require.Error(t, err) 335 // assert that it failed with the expected error 336 assert.Regexp(t, ".*failed to negotiate security protocol.*|.*protocols not supported.*", err) 337 }