github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/network/p2p/connection/connection_gater_test.go (about)

     1  package connection_test
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/libp2p/go-libp2p/core/control"
    10  	"github.com/libp2p/go-libp2p/core/network"
    11  	"github.com/libp2p/go-libp2p/core/peer"
    12  	"github.com/stretchr/testify/mock"
    13  	"github.com/stretchr/testify/require"
    14  
    15  	"github.com/onflow/flow-go/model/flow"
    16  	"github.com/onflow/flow-go/module/irrecoverable"
    17  	mockmodule "github.com/onflow/flow-go/module/mock"
    18  	"github.com/onflow/flow-go/network/channels"
    19  	"github.com/onflow/flow-go/network/internal/p2pfixtures"
    20  	"github.com/onflow/flow-go/network/p2p"
    21  	p2pbuilderconfig "github.com/onflow/flow-go/network/p2p/builder/config"
    22  	"github.com/onflow/flow-go/network/p2p/connection"
    23  	p2plogging "github.com/onflow/flow-go/network/p2p/logging"
    24  	mockp2p "github.com/onflow/flow-go/network/p2p/mock"
    25  	p2ptest "github.com/onflow/flow-go/network/p2p/test"
    26  	"github.com/onflow/flow-go/network/p2p/unicast/stream"
    27  	"github.com/onflow/flow-go/utils/unittest"
    28  )
    29  
    30  // TestConnectionGating tests node allow listing by peer ID.
    31  func TestConnectionGating(t *testing.T) {
    32  	ctx, cancel := context.WithCancel(context.Background())
    33  	signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx)
    34  
    35  	sporkID := unittest.IdentifierFixture()
    36  	idProvider := mockmodule.NewIdentityProvider(t)
    37  	// create 2 nodes
    38  	node1Peers := unittest.NewProtectedMap[peer.ID, struct{}]()
    39  	node1, node1Id := p2ptest.NodeFixture(
    40  		t,
    41  		sporkID,
    42  		t.Name(),
    43  		idProvider,
    44  		p2ptest.WithConnectionGater(p2ptest.NewConnectionGater(idProvider, func(p peer.ID) error {
    45  			if !node1Peers.Has(p) {
    46  				return fmt.Errorf("id not found: %s", p2plogging.PeerId(p))
    47  			}
    48  			return nil
    49  		})))
    50  	idProvider.On("ByPeerID", node1.ID()).Return(&node1Id, true).Maybe()
    51  
    52  	node2Peers := unittest.NewProtectedMap[peer.ID, struct{}]()
    53  	node2, node2Id := p2ptest.NodeFixture(
    54  		t,
    55  		sporkID,
    56  		t.Name(),
    57  		idProvider,
    58  		p2ptest.WithConnectionGater(p2ptest.NewConnectionGater(idProvider, func(p peer.ID) error {
    59  			if !node2Peers.Has(p) {
    60  				return fmt.Errorf("id not found: %s", p2plogging.PeerId(p))
    61  			}
    62  			return nil
    63  		})))
    64  	idProvider.On("ByPeerID", node2.ID()).Return(&node2Id, true).Maybe()
    65  
    66  	nodes := []p2p.LibP2PNode{node1, node2}
    67  	ids := flow.IdentityList{&node1Id, &node2Id}
    68  	p2ptest.StartNodes(t, signalerCtx, nodes)
    69  	defer p2ptest.StopNodes(t, nodes, cancel)
    70  
    71  	p2pfixtures.AddNodesToEachOthersPeerStore(t, nodes, ids)
    72  
    73  	t.Run("outbound connection to a disallowed node is rejected", func(t *testing.T) {
    74  		// although nodes have each other addresses, they are not in the allow-lists of each other.
    75  		// so they should not be able to connect to each other.
    76  		p2pfixtures.EnsureNoStreamCreationBetweenGroups(t, ctx, []p2p.LibP2PNode{node1}, []p2p.LibP2PNode{node2}, func(t *testing.T, err error) {
    77  			require.Truef(t, stream.IsErrGaterDisallowedConnection(err), "expected ErrGaterDisallowedConnection, got: %v", err)
    78  		})
    79  	})
    80  
    81  	t.Run("inbound connection from an allowed node is rejected", func(t *testing.T) {
    82  		// for an inbound connection to be established both nodes should be in each other's allow-lists.
    83  		// the connection gater on the dialing node is checking the allow-list upon dialing.
    84  		// the connection gater on the listening node is checking the allow-list upon accepting the connection.
    85  
    86  		// add node2 to node1's allow list, but not the other way around.
    87  		node1Peers.Add(node2.ID(), struct{}{})
    88  
    89  		// from node2 -> node1 should also NOT work, since node 1 is not in node2's allow list for dialing!
    90  		p2pfixtures.EnsureNoStreamCreation(t, ctx, []p2p.LibP2PNode{node2}, []p2p.LibP2PNode{node1}, func(t *testing.T, err error) {
    91  			// dialing node-1 by node-2 should fail locally at the connection gater of node-2.
    92  			require.Truef(t, stream.IsErrGaterDisallowedConnection(err), "expected ErrGaterDisallowedConnection, got: %v", err)
    93  		})
    94  
    95  		// now node2 should be able to connect to node1.
    96  		// from node1 -> node2 shouldn't work
    97  		p2pfixtures.EnsureNoStreamCreation(t, ctx, []p2p.LibP2PNode{node1}, []p2p.LibP2PNode{node2})
    98  	})
    99  
   100  	t.Run("outbound connection to an approved node is allowed", func(t *testing.T) {
   101  		// adding both nodes to each other's allow lists.
   102  		node1Peers.Add(node2.ID(), struct{}{})
   103  		node2Peers.Add(node1.ID(), struct{}{})
   104  
   105  		// now both nodes should be able to connect to each other.
   106  		p2ptest.EnsureStreamCreationInBothDirections(t, ctx, []p2p.LibP2PNode{node1, node2})
   107  	})
   108  }
   109  
   110  // TestConnectionGating_ResourceAllocation_AllowListing tests resource allocation when a connection from an allow-listed node is established.
   111  // The test directly mocks the underlying resource manager metrics of the libp2p native resource manager to ensure that the
   112  // expected set of resources are allocated for the connection upon establishment.
   113  func TestConnectionGating_ResourceAllocation_AllowListing(t *testing.T) {
   114  	unittest.SkipUnless(t, unittest.TEST_FLAKY, "flakey tests")
   115  	ctx, cancel := context.WithCancel(context.Background())
   116  	signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx)
   117  
   118  	sporkID := unittest.IdentifierFixture()
   119  	idProvider := mockmodule.NewIdentityProvider(t)
   120  
   121  	node1, node1Id := p2ptest.NodeFixture(
   122  		t,
   123  		sporkID,
   124  		t.Name(),
   125  		idProvider,
   126  		p2ptest.WithRole(flow.RoleConsensus))
   127  
   128  	node2Metrics := mockmodule.NewNetworkMetrics(t)
   129  	// libp2p native resource manager metrics:
   130  	// we expect exactly 1 connection to be established from node1 to node2 (inbound for node 2).
   131  	node2Metrics.On("AllowConn", network.DirInbound, true).Return().Once()
   132  	// we expect the libp2p.identify service to be used to establish the connection.
   133  	node2Metrics.On("AllowService", "libp2p.identify").Return()
   134  	// we expect the node2 attaching node1 to the incoming connection.
   135  	node2Metrics.On("AllowPeer", node1.ID()).Return()
   136  	// we expect node2 allocate memory for the incoming connection.
   137  	node2Metrics.On("AllowMemory", mock.Anything)
   138  	// we expect node2 to allow the stream to be created.
   139  	node2Metrics.On("AllowStream", node1.ID(), mock.Anything)
   140  	// we expect node2 to attach protocol to the created stream.
   141  	node2Metrics.On("AllowProtocol", mock.Anything).Return()
   142  
   143  	// Flow-level resource allocation metrics:
   144  	// We expect both of the following to be called as they are called together in the same function.
   145  	node2Metrics.On("InboundConnections", mock.Anything).Return()
   146  	node2Metrics.On("OutboundConnections", mock.Anything).Return()
   147  
   148  	// Libp2p control message validation metrics, these may or may not be called depending on the machine the test is running on and how long
   149  	// the nodes in the test run for.
   150  	node2Metrics.On("BlockingPreProcessingStarted", mock.Anything, mock.Anything).Maybe()
   151  	node2Metrics.On("BlockingPreProcessingFinished", mock.Anything, mock.Anything, mock.Anything).Maybe()
   152  	node2Metrics.On("AsyncProcessingStarted", mock.Anything).Maybe()
   153  	node2Metrics.On("AsyncProcessingFinished", mock.Anything, mock.Anything).Maybe()
   154  
   155  	// we create node2 with a connection gater that allows all connections and the mocked metrics collector.
   156  	node2, node2Id := p2ptest.NodeFixture(
   157  		t,
   158  		sporkID,
   159  		t.Name(),
   160  		idProvider,
   161  		p2ptest.WithRole(flow.RoleConsensus),
   162  		p2ptest.WithMetricsCollector(node2Metrics),
   163  		// we use default resource manager rather than the test resource manager to ensure that the metrics are called.
   164  		p2ptest.WithDefaultResourceManager(),
   165  		p2ptest.WithConnectionGater(p2ptest.NewConnectionGater(idProvider, func(p peer.ID) error {
   166  			return nil // allow all connections.
   167  		})))
   168  	idProvider.On("ByPeerID", node1.ID()).Return(&node1Id, true).Maybe()
   169  	idProvider.On("ByPeerID", node2.ID()).Return(&node2Id, true).Maybe()
   170  
   171  	nodes := []p2p.LibP2PNode{node1, node2}
   172  	ids := flow.IdentityList{&node1Id, &node2Id}
   173  	p2ptest.StartNodes(t, signalerCtx, nodes)
   174  	defer p2ptest.StopNodes(t, nodes, cancel)
   175  
   176  	p2pfixtures.AddNodesToEachOthersPeerStore(t, nodes, ids)
   177  
   178  	// now node-1 should be able to connect to node-2.
   179  	p2pfixtures.EnsureStreamCreation(t, ctx, []p2p.LibP2PNode{node1}, []p2p.LibP2PNode{node2})
   180  
   181  	node2Metrics.AssertExpectations(t)
   182  }
   183  
   184  // TestConnectionGating_ResourceAllocation_DisAllowListing tests resource allocation when a connection from an allow-listed node is established.
   185  func TestConnectionGating_ResourceAllocation_DisAllowListing(t *testing.T) {
   186  	ctx, cancel := context.WithCancel(context.Background())
   187  	signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx)
   188  
   189  	sporkID := unittest.IdentifierFixture()
   190  	idProvider := mockmodule.NewIdentityProvider(t)
   191  
   192  	node1, node1Id := p2ptest.NodeFixture(
   193  		t,
   194  		sporkID,
   195  		t.Name(),
   196  		idProvider,
   197  		p2ptest.WithRole(flow.RoleConsensus))
   198  
   199  	node2Metrics := mockmodule.NewNetworkMetrics(t)
   200  	node2Metrics.On("AllowConn", network.DirInbound, true).Return()
   201  	node2, node2Id := p2ptest.NodeFixture(
   202  		t,
   203  		sporkID,
   204  		t.Name(),
   205  		idProvider,
   206  		p2ptest.WithRole(flow.RoleConsensus),
   207  		p2ptest.WithMetricsCollector(node2Metrics),
   208  		// we use default resource manager rather than the test resource manager to ensure that the metrics are called.
   209  		p2ptest.WithDefaultResourceManager(),
   210  		p2ptest.WithConnectionGater(p2ptest.NewConnectionGater(idProvider, func(p peer.ID) error {
   211  			return fmt.Errorf("disallowed connection") // rejecting all connections.
   212  		})))
   213  	idProvider.On("ByPeerID", node1.ID()).Return(&node1Id, true).Maybe()
   214  	idProvider.On("ByPeerID", node2.ID()).Return(&node2Id, true).Maybe()
   215  
   216  	nodes := []p2p.LibP2PNode{node1, node2}
   217  	ids := flow.IdentityList{&node1Id, &node2Id}
   218  	p2ptest.StartNodes(t, signalerCtx, nodes)
   219  	defer p2ptest.StopNodes(t, nodes, cancel)
   220  
   221  	p2pfixtures.AddNodesToEachOthersPeerStore(t, nodes, ids)
   222  
   223  	// now node2 should be able to connect to node1.
   224  	p2pfixtures.EnsureNoStreamCreation(t, ctx, []p2p.LibP2PNode{node1}, []p2p.LibP2PNode{node2})
   225  
   226  	// as node-2 connection gater is rejecting all connections, none of the following resource allocation methods should be called.
   227  	node2Metrics.AssertNotCalled(t, "AllowService", mock.Anything)               // no service is allowed, e.g., libp2p.identify
   228  	node2Metrics.AssertNotCalled(t, "AllowPeer", mock.Anything)                  // no peer is allowed to be attached to the connection.
   229  	node2Metrics.AssertNotCalled(t, "AllowMemory", mock.Anything)                // no memory is EVER allowed to be used during the test.
   230  	node2Metrics.AssertNotCalled(t, "AllowStream", mock.Anything, mock.Anything) // no stream is allowed to be created.
   231  }
   232  
   233  // TestConnectionGater_InterceptUpgrade tests the connection gater only upgrades the connections to the allow-listed peers.
   234  // Upgrading a connection means that the connection is the last phase of the connection establishment process.
   235  // It means that the connection is ready to be used for sending and receiving messages.
   236  // It checks that no disallowed peer can upgrade the connection.
   237  func TestConnectionGater_InterceptUpgrade(t *testing.T) {
   238  	unittest.SkipUnless(t, unittest.TEST_FLAKY, "fails locally and on CI regularly")
   239  	ctx, cancel := context.WithCancel(context.Background())
   240  	signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx)
   241  	sporkId := unittest.IdentifierFixture()
   242  	defer cancel()
   243  
   244  	count := 5
   245  	nodes := make([]p2p.LibP2PNode, 0, count)
   246  	inbounds := make([]chan string, 0, count)
   247  	identities := make(flow.IdentityList, 0, count)
   248  
   249  	disallowedPeerIds := unittest.NewProtectedMap[peer.ID, struct{}]()
   250  	allPeerIds := make(peer.IDSlice, 0, count)
   251  	idProvider := mockmodule.NewIdentityProvider(t)
   252  	connectionGater := mockp2p.NewConnectionGater(t)
   253  	for i := 0; i < count; i++ {
   254  		handler, inbound := p2ptest.StreamHandlerFixture(t)
   255  		node, id := p2ptest.NodeFixture(
   256  			t,
   257  			sporkId,
   258  			t.Name(),
   259  			idProvider,
   260  			p2ptest.WithRole(flow.RoleConsensus),
   261  			p2ptest.WithDefaultStreamHandler(handler),
   262  			// enable peer manager, with a 1-second refresh rate, and connection pruning enabled.
   263  			p2ptest.WithPeerManagerEnabled(&p2pbuilderconfig.PeerManagerConfig{
   264  				ConnectionPruning: true,
   265  				UpdateInterval:    1 * time.Second,
   266  				ConnectorFactory:  connection.DefaultLibp2pBackoffConnectorFactory(),
   267  			}, func() peer.IDSlice {
   268  				list := make(peer.IDSlice, 0)
   269  				for _, pid := range allPeerIds {
   270  					if !disallowedPeerIds.Has(pid) {
   271  						list = append(list, pid)
   272  					}
   273  				}
   274  				return list
   275  			}),
   276  			p2ptest.WithConnectionGater(connectionGater))
   277  		idProvider.On("ByPeerID", node.ID()).Return(&id, true).Maybe()
   278  		nodes = append(nodes, node)
   279  		identities = append(identities, &id)
   280  		allPeerIds = append(allPeerIds, node.ID())
   281  		inbounds = append(inbounds, inbound)
   282  	}
   283  
   284  	connectionGater.On("InterceptSecured", mock.Anything, mock.Anything, mock.Anything).
   285  		Return(func(_ network.Direction, p peer.ID, _ network.ConnMultiaddrs) bool {
   286  			return !disallowedPeerIds.Has(p)
   287  		})
   288  
   289  	connectionGater.On("InterceptPeerDial", mock.Anything).Return(func(p peer.ID) bool {
   290  		return !disallowedPeerIds.Has(p)
   291  	})
   292  
   293  	// we don't inspect connections during "accept" and "dial" phases as the peer IDs are not available at those phases.
   294  	connectionGater.On("InterceptAddrDial", mock.Anything, mock.Anything).Return(true)
   295  	connectionGater.On("InterceptAccept", mock.Anything).Return(true)
   296  
   297  	// adds first node to disallowed list
   298  	disallowedPeerIds.Add(nodes[0].ID(), struct{}{})
   299  
   300  	// starts the nodes
   301  	p2ptest.StartNodes(t, signalerCtx, nodes)
   302  	defer p2ptest.StopNodes(t, nodes, cancel)
   303  
   304  	// Checks that only an allowed REMOTE node can establish an upgradable connection.
   305  	connectionGater.On("InterceptUpgraded", mock.Anything).Run(func(args mock.Arguments) {
   306  		conn, ok := args.Get(0).(network.Conn)
   307  		require.True(t, ok)
   308  
   309  		// we don't check for the local peer as with v0.24 of libp2p, the local peer may be able to upgrade an empty connection
   310  		// even though the remote peer has already disconnected and rejected the connection.
   311  		remote := conn.RemotePeer()
   312  		require.False(t, disallowedPeerIds.Has(remote))
   313  	}).Return(true, control.DisconnectReason(0))
   314  
   315  	ensureCommunicationSilenceAmongGroups(t, ctx, sporkId, nodes[:1], identities[:1].NodeIDs(), nodes[1:], identities[1:].NodeIDs())
   316  	ensureCommunicationOverAllProtocols(t, ctx, sporkId, nodes[1:], inbounds[1:])
   317  }
   318  
   319  // TestConnectionGater_Disallow_Integration tests that when a peer is disallowed, it is disconnected from all other peers, and
   320  // cannot connect, exchange unicast, or pubsub messages to any other peers.
   321  // It also checked that the allowed peers can still communicate with each other.
   322  func TestConnectionGater_Disallow_Integration(t *testing.T) {
   323  	ctx, cancel := context.WithCancel(context.Background())
   324  	signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx)
   325  	sporkId := unittest.IdentifierFixture()
   326  	idProvider := mockmodule.NewIdentityProvider(t)
   327  	defer cancel()
   328  
   329  	count := 5
   330  	nodes := make([]p2p.LibP2PNode, 0, 5)
   331  	ids := flow.IdentityList{}
   332  	inbounds := make([]chan string, 0, 5)
   333  
   334  	disallowedList := unittest.NewProtectedMap[*flow.Identity, struct{}]()
   335  
   336  	for i := 0; i < count; i++ {
   337  		handler, inbound := p2ptest.StreamHandlerFixture(t)
   338  		node, id := p2ptest.NodeFixture(
   339  			t,
   340  			sporkId,
   341  			t.Name(),
   342  			idProvider,
   343  			p2ptest.WithRole(flow.RoleConsensus),
   344  			p2ptest.WithDefaultStreamHandler(handler),
   345  			// enable peer manager, with a 1-second refresh rate, and connection pruning enabled.
   346  			p2ptest.WithPeerManagerEnabled(&p2pbuilderconfig.PeerManagerConfig{
   347  				ConnectionPruning: true,
   348  				UpdateInterval:    1 * time.Second,
   349  				ConnectorFactory:  connection.DefaultLibp2pBackoffConnectorFactory(),
   350  			}, func() peer.IDSlice {
   351  				list := make(peer.IDSlice, 0)
   352  				for _, id := range ids {
   353  					if disallowedList.Has(id) {
   354  						continue
   355  					}
   356  
   357  					pid, err := unittest.PeerIDFromFlowID(id)
   358  					require.NoError(t, err)
   359  
   360  					list = append(list, pid)
   361  				}
   362  				return list
   363  			}),
   364  			p2ptest.WithConnectionGater(p2ptest.NewConnectionGater(idProvider, func(pid peer.ID) error {
   365  				return disallowedList.ForEach(func(id *flow.Identity, _ struct{}) error {
   366  					bid, err := unittest.PeerIDFromFlowID(id)
   367  					require.NoError(t, err)
   368  					if bid == pid {
   369  						return fmt.Errorf("disallow-listed")
   370  					}
   371  					return nil
   372  				})
   373  			})))
   374  		idProvider.On("ByPeerID", node.ID()).Return(&id, true).Maybe()
   375  
   376  		nodes = append(nodes, node)
   377  		ids = append(ids, &id)
   378  		inbounds = append(inbounds, inbound)
   379  	}
   380  
   381  	p2ptest.StartNodes(t, signalerCtx, nodes)
   382  	defer p2ptest.StopNodes(t, nodes, cancel)
   383  
   384  	p2ptest.LetNodesDiscoverEachOther(t, ctx, nodes, ids)
   385  
   386  	// ensures that all nodes are connected to each other, and they can exchange messages over the pubsub and unicast.
   387  	ensureCommunicationOverAllProtocols(t, ctx, sporkId, nodes, inbounds)
   388  
   389  	// now we add one of the nodes (the last node) to the disallow-list.
   390  	disallowedList.Add(ids[len(ids)-1], struct{}{})
   391  	// let peer manager prune the connections to the disallow-listed node.
   392  	time.Sleep(1 * time.Second)
   393  	// ensures no connection, unicast, or pubsub going to or coming from the disallow-listed node.
   394  	ensureCommunicationSilenceAmongGroups(t, ctx, sporkId, nodes[:count-1], ids[:count-1].NodeIDs(), nodes[count-1:], ids[count-1:].NodeIDs())
   395  
   396  	// now we add another node (the second last node) to the disallowed list.
   397  	disallowedList.Add(ids[len(ids)-2], struct{}{})
   398  	// let peer manager prune the connections to the disallow-listed node.
   399  	time.Sleep(1 * time.Second)
   400  	// ensures no connection, unicast, or pubsub going to and coming from the disallow-listed nodes.
   401  	ensureCommunicationSilenceAmongGroups(t, ctx, sporkId, nodes[:count-2], ids[:count-2].NodeIDs(), nodes[count-2:], ids[count-2:].NodeIDs())
   402  	// ensures that all nodes are other non-disallow-listed nodes can exchange messages over the pubsub and unicast.
   403  	ensureCommunicationOverAllProtocols(t, ctx, sporkId, nodes[:count-2], inbounds[:count-2])
   404  }
   405  
   406  // ensureCommunicationSilenceAmongGroups ensures no connection, unicast, or pubsub going to or coming from between the two groups of nodes.
   407  func ensureCommunicationSilenceAmongGroups(
   408  	t *testing.T,
   409  	ctx context.Context,
   410  	sporkId flow.Identifier,
   411  	groupANodes []p2p.LibP2PNode,
   412  	groupAIdentifiers flow.IdentifierList,
   413  	groupBNodes []p2p.LibP2PNode,
   414  	groupBIdentifiers flow.IdentifierList) {
   415  	// ensures no connection, unicast, or pubsub going to the disallow-listed nodes
   416  	p2ptest.EnsureNotConnectedBetweenGroups(t, ctx, groupANodes, groupBNodes)
   417  
   418  	blockTopic := channels.TopicFromChannel(channels.PushBlocks, sporkId)
   419  	p2ptest.EnsureNoPubsubExchangeBetweenGroups(
   420  		t,
   421  		ctx,
   422  		groupANodes,
   423  		groupAIdentifiers,
   424  		groupBNodes,
   425  		groupBIdentifiers,
   426  		blockTopic,
   427  		1,
   428  		func() interface{} {
   429  			return unittest.ProposalFixture()
   430  		})
   431  	p2pfixtures.EnsureNoStreamCreationBetweenGroups(t, ctx, groupANodes, groupBNodes)
   432  }
   433  
   434  // ensureCommunicationOverAllProtocols ensures that all nodes are connected to each other, and they can exchange messages over the pubsub and unicast.
   435  func ensureCommunicationOverAllProtocols(t *testing.T, ctx context.Context, sporkId flow.Identifier, nodes []p2p.LibP2PNode, inbounds []chan string) {
   436  	blockTopic := channels.TopicFromChannel(channels.PushBlocks, sporkId)
   437  	p2ptest.TryConnectionAndEnsureConnected(t, ctx, nodes)
   438  	p2ptest.EnsurePubsubMessageExchange(t, ctx, nodes, blockTopic, 1, func() interface{} {
   439  		return unittest.ProposalFixture()
   440  	})
   441  	p2pfixtures.EnsureMessageExchangeOverUnicast(t, ctx, nodes, inbounds, p2pfixtures.LongStringMessageFactoryFixture(t))
   442  }