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  }