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  }