github.com/onflow/flow-go@v0.33.17/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) 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) 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 node1, id1 := p2ptest.NodeFixture(t, 215 previousSporkId, 216 "test_one_to_k_crosstalk_prevention", 217 idProvider, 218 p2ptest.OverrideFlowConfig(cfg), 219 ) 220 221 p2ptest.StartNode(t, signalerCtx1, node1) 222 defer p2ptest.StopNode(t, node1, cancel1) 223 idProvider.SetIdentities(flow.IdentityList{&id1}) 224 225 // create and start node 2 on localhost and random port with the same root block ID 226 node2, id2 := p2ptest.NodeFixture(t, 227 previousSporkId, 228 "test_one_to_k_crosstalk_prevention", 229 idProvider, 230 ) 231 232 p2ptest.StartNode(t, signalerCtx2, node2) 233 defer p2ptest.StopNode(t, node2, cancel2) 234 235 pInfo2, err := utils.PeerAddressInfo(id2) 236 require.NoError(t, err) 237 238 // spork topic is derived by suffixing the channel with the root block ID 239 topicBeforeSpork := channels.TopicFromChannel(channels.TestNetworkChannel, previousSporkId) 240 241 logger := unittest.Logger() 242 topicValidator := flowpubsub.TopicValidator(logger, unittest.AllowAllPeerFilter()) 243 244 // both nodes are initially on the same spork and subscribed to the same topic 245 _, err = node1.Subscribe(topicBeforeSpork, topicValidator) 246 require.NoError(t, err) 247 sub2, err := node2.Subscribe(topicBeforeSpork, topicValidator) 248 require.NoError(t, err) 249 250 // add node 2 as a peer of node 1 251 err = node1.ConnectToPeer(ctx, pInfo2) 252 require.NoError(t, err) 253 254 // let the two nodes form the mesh 255 time.Sleep(time.Second) 256 257 // assert that node 1 can successfully send a message to node 2 via PubSub 258 outgoingMessageScope, err := message.NewOutgoingScope( 259 flow.IdentifierList{unittest.IdentifierFixture()}, 260 topicBeforeSpork, 261 &libp2pmessage.TestMessage{ 262 Text: string("hello"), 263 }, 264 unittest.NetworkCodec().Encode, 265 message.ProtocolTypePubSub) 266 require.NoError(t, err) 267 268 expectedReceivedData, err := outgoingMessageScope.Proto().Marshal() 269 require.NoError(t, err) 270 271 // send a 1-k message from source node to destination node 272 err = node1.Publish(ctx, outgoingMessageScope) 273 require.NoError(t, err) 274 275 // assert that the message is received by the destination node 276 unittest.AssertReturnsBefore(t, func() { 277 msg, err := sub2.Next(ctx) 278 require.NoError(t, err) 279 assert.Equal(t, expectedReceivedData, msg.Data) 280 }, 281 // libp2p hearbeats every second, so at most the message should take 1 second 282 2*time.Second) 283 284 // new root id after spork 285 rootIDAfterSpork := unittest.IdentifierFixture() 286 287 // topic after the spork 288 topicAfterSpork := channels.TopicFromChannel(channels.TestNetworkChannel, rootIDAfterSpork) 289 290 // mimic that node1 is now part of the new spork while node2 remains on the old spork 291 // by unsubscribing node1 from 'topicBeforeSpork' and subscribing it to 'topicAfterSpork' 292 // and keeping node2 subscribed to topic 'topicBeforeSpork' 293 err = node1.Unsubscribe(topicBeforeSpork) 294 require.NoError(t, err) 295 _, err = node1.Subscribe(topicAfterSpork, topicValidator) 296 require.NoError(t, err) 297 298 // assert that node 1 can no longer send a message to node 2 via PubSub 299 outgoingMessageScope, err = message.NewOutgoingScope( 300 flow.IdentifierList{id2.NodeID}, 301 topicAfterSpork, 302 &libp2pmessage.TestMessage{ 303 Text: string("hello"), 304 }, 305 unittest.NetworkCodec().Encode, 306 message.ProtocolTypePubSub) 307 require.NoError(t, err) 308 309 // send a 1-k message from source node to destination node 310 err = node1.Publish(ctx, outgoingMessageScope) 311 require.NoError(t, err) 312 313 // assert that the message is never received by the destination node 314 _ = unittest.RequireNeverReturnBefore(t, func() { 315 _, _ = sub2.Next(ctx) 316 }, 317 // libp2p hearbeats every second, so at most the message should take 1 second 318 2*time.Second, 319 "nodes on different sporks were able to communicate") 320 } 321 322 func testOneToOneMessagingFails(t *testing.T, sourceNode p2p.LibP2PNode, peerInfo peer.AddrInfo) { 323 // create stream from source node to destination address 324 sourceNode.Host().Peerstore().AddAddrs(peerInfo.ID, peerInfo.Addrs, peerstore.AddressTTL) 325 ctx, cancel := context.WithCancel(context.Background()) 326 defer cancel() 327 err := sourceNode.OpenAndWriteOnStream(ctx, peerInfo.ID, t.Name(), func(stream network.Stream) error { 328 // this callback should never be called 329 assert.Fail(t, "stream creation should have failed") 330 return nil 331 }) 332 // assert that stream creation failed 333 require.Error(t, err) 334 // assert that it failed with the expected error 335 assert.Regexp(t, ".*failed to negotiate security protocol.*|.*protocols not supported.*", err) 336 }