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