github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/network/internal/p2pfixtures/fixtures.go (about)

     1  package p2pfixtures
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"context"
     7  	"errors"
     8  	"fmt"
     9  	"net"
    10  	"testing"
    11  	"time"
    12  
    13  	addrutil "github.com/libp2p/go-addr-util"
    14  	pubsub "github.com/libp2p/go-libp2p-pubsub"
    15  	"github.com/libp2p/go-libp2p/core/host"
    16  	"github.com/libp2p/go-libp2p/core/network"
    17  	"github.com/libp2p/go-libp2p/core/peerstore"
    18  	"github.com/libp2p/go-libp2p/core/routing"
    19  	"github.com/multiformats/go-multiaddr"
    20  	manet "github.com/multiformats/go-multiaddr/net"
    21  	"github.com/onflow/crypto"
    22  	"github.com/rs/zerolog"
    23  	"github.com/stretchr/testify/require"
    24  
    25  	"github.com/onflow/flow-go/config"
    26  	"github.com/onflow/flow-go/model/flow"
    27  	"github.com/onflow/flow-go/module/id"
    28  	"github.com/onflow/flow-go/module/metrics"
    29  	flownet "github.com/onflow/flow-go/network"
    30  	"github.com/onflow/flow-go/network/channels"
    31  	"github.com/onflow/flow-go/network/internal/p2putils"
    32  	"github.com/onflow/flow-go/network/message"
    33  	"github.com/onflow/flow-go/network/p2p"
    34  	p2pbuilder "github.com/onflow/flow-go/network/p2p/builder"
    35  	p2pbuilderconfig "github.com/onflow/flow-go/network/p2p/builder/config"
    36  	p2pdht "github.com/onflow/flow-go/network/p2p/dht"
    37  	"github.com/onflow/flow-go/network/p2p/unicast/protocols"
    38  	"github.com/onflow/flow-go/network/p2p/utils"
    39  	"github.com/onflow/flow-go/utils/unittest"
    40  )
    41  
    42  // NetworkingKeyFixtures is a test helper that generates a ECDSA flow key pair.
    43  func NetworkingKeyFixtures(t *testing.T) crypto.PrivateKey {
    44  	seed := unittest.SeedFixture(48)
    45  	key, err := crypto.GeneratePrivateKey(crypto.ECDSASecp256k1, seed)
    46  	require.NoError(t, err)
    47  	return key
    48  }
    49  
    50  // SilentNodeFixture returns a TCP listener and a node which never replies
    51  func SilentNodeFixture(t *testing.T) (net.Listener, flow.Identity) {
    52  	key := NetworkingKeyFixtures(t)
    53  
    54  	lst, err := net.Listen("tcp4", unittest.DefaultAddress)
    55  	require.NoError(t, err)
    56  
    57  	addr, err := manet.FromNetAddr(lst.Addr())
    58  	require.NoError(t, err)
    59  
    60  	addrs := []multiaddr.Multiaddr{addr}
    61  	addrs, err = addrutil.ResolveUnspecifiedAddresses(addrs, nil)
    62  	require.NoError(t, err)
    63  
    64  	go acceptAndHang(t, lst)
    65  
    66  	ip, port, err := p2putils.IPPortFromMultiAddress(addrs...)
    67  	require.NoError(t, err)
    68  
    69  	identity := unittest.IdentityFixture(unittest.WithNetworkingKey(key.PublicKey()), unittest.WithAddress(ip+":"+port))
    70  	return lst, *identity
    71  }
    72  
    73  func acceptAndHang(t *testing.T, l net.Listener) {
    74  	conns := make([]net.Conn, 0, 10)
    75  	for {
    76  		c, err := l.Accept()
    77  		if err != nil {
    78  			break
    79  		}
    80  		if c != nil {
    81  			conns = append(conns, c)
    82  		}
    83  	}
    84  	for _, c := range conns {
    85  		require.NoError(t, c.Close())
    86  	}
    87  }
    88  
    89  type nodeOpt func(p2p.NodeBuilder)
    90  
    91  func WithSubscriptionFilter(filter pubsub.SubscriptionFilter) nodeOpt {
    92  	return func(builder p2p.NodeBuilder) {
    93  		builder.SetSubscriptionFilter(filter)
    94  	}
    95  }
    96  
    97  // TODO: this should be replaced by node fixture: https://github.com/onflow/flow-go/blob/master/network/p2p/test/fixtures.go
    98  func CreateNode(t *testing.T, networkKey crypto.PrivateKey, sporkID flow.Identifier, logger zerolog.Logger, nodeIds flow.IdentityList, opts ...nodeOpt) p2p.LibP2PNode {
    99  	idProvider := id.NewFixedIdentityProvider(nodeIds)
   100  	defaultFlowConfig, err := config.DefaultConfig()
   101  	require.NoError(t, err)
   102  
   103  	builder := p2pbuilder.NewNodeBuilder(
   104  		logger,
   105  		&defaultFlowConfig.NetworkConfig.GossipSub,
   106  		&p2pbuilderconfig.MetricsConfig{
   107  			HeroCacheFactory: metrics.NewNoopHeroCacheMetricsFactory(),
   108  			Metrics:          metrics.NewNoopCollector(),
   109  		},
   110  		flownet.PrivateNetwork,
   111  		unittest.DefaultAddress,
   112  		networkKey,
   113  		sporkID,
   114  		idProvider,
   115  		&defaultFlowConfig.NetworkConfig.ResourceManager,
   116  		p2pbuilderconfig.PeerManagerDisableConfig(),
   117  		&p2p.DisallowListCacheConfig{
   118  			MaxSize: uint32(1000),
   119  			Metrics: metrics.NewNoopCollector(),
   120  		},
   121  		&p2pbuilderconfig.UnicastConfig{
   122  			Unicast: defaultFlowConfig.NetworkConfig.Unicast,
   123  		}).
   124  		SetRoutingSystem(func(c context.Context, h host.Host) (routing.Routing, error) {
   125  			return p2pdht.NewDHT(c, h, protocols.FlowDHTProtocolID(sporkID), zerolog.Nop(), metrics.NewNoopCollector())
   126  		}).
   127  		SetResourceManager(&network.NullResourceManager{})
   128  
   129  	for _, opt := range opts {
   130  		opt(builder)
   131  	}
   132  
   133  	libp2pNode, err := builder.Build()
   134  	require.NoError(t, err)
   135  
   136  	return libp2pNode
   137  }
   138  
   139  // SubMustEventuallyStopReceivingAnyMessage checks that the subscription eventually stops receiving any messages within the given timeout by the context.
   140  // This func uses the publish callback to continually publish messages to the subscription, this ensures that the subscription indeed stops receiving the messages.
   141  func SubMustEventuallyStopReceivingAnyMessage(t *testing.T, ctx context.Context, sub p2p.Subscription, publish func(t *testing.T)) {
   142  	done := make(chan struct{})
   143  	ticker := time.NewTicker(500 * time.Millisecond)
   144  	defer func() {
   145  		close(done)
   146  		ticker.Stop()
   147  	}()
   148  
   149  	go func() {
   150  		for {
   151  			select {
   152  			case <-done:
   153  				return
   154  			case <-ticker.C:
   155  				publish(t)
   156  			}
   157  		}
   158  	}()
   159  
   160  	// eventually we should stop receiving messages on the sub
   161  	require.Eventually(t, func() bool {
   162  		_, err := sub.Next(ctx)
   163  		return errors.Is(err, context.DeadlineExceeded)
   164  	}, 10*time.Second, 100*time.Millisecond)
   165  
   166  	// after we stop receiving messages on sub we should continue to not receiving messages
   167  	// despite messages continuing to be published
   168  	_, err := sub.Next(ctx)
   169  	require.Error(t, err)
   170  	require.ErrorIs(t, err, context.DeadlineExceeded)
   171  }
   172  
   173  // SubMustNeverReceiveAnyMessage checks that the subscription never receives any message within the given timeout by the context.
   174  func SubMustNeverReceiveAnyMessage(t *testing.T, ctx context.Context, sub p2p.Subscription) {
   175  	timeouted := make(chan struct{})
   176  	go func() {
   177  		_, err := sub.Next(ctx)
   178  		require.Error(t, err)
   179  		require.ErrorIs(t, err, context.DeadlineExceeded)
   180  		close(timeouted)
   181  	}()
   182  
   183  	// wait for the timeout, we choose the timeout to be long enough to make sure that
   184  	// on a happy path the timeout never happens, and short enough to make sure that
   185  	// the test doesn't take too long in case of a failure.
   186  	unittest.RequireCloseBefore(t, timeouted, 10*time.Second, "timeout did not happen on receiving expected pubsub message")
   187  }
   188  
   189  func SubsMustEventuallyStopReceivingAnyMessage(t *testing.T, ctx context.Context, subs []p2p.Subscription, send func(t *testing.T)) {
   190  	for _, sub := range subs {
   191  		SubMustEventuallyStopReceivingAnyMessage(t, ctx, sub, send)
   192  	}
   193  }
   194  
   195  // HasSubReceivedMessage checks that the subscription have received the given message within the given timeout by the context.
   196  // It returns true if the subscription has received the message, false otherwise.
   197  func HasSubReceivedMessage(t *testing.T, ctx context.Context, expectedMessage []byte, sub p2p.Subscription) bool {
   198  	received := make(chan struct{})
   199  	go func() {
   200  		msg, err := sub.Next(ctx)
   201  		if err != nil {
   202  			require.ErrorIs(t, err, context.DeadlineExceeded)
   203  			return
   204  		}
   205  		if !bytes.Equal(expectedMessage, msg.Data) {
   206  			return
   207  		}
   208  		close(received)
   209  	}()
   210  
   211  	select {
   212  	case <-received:
   213  		return true
   214  	case <-ctx.Done():
   215  		return false
   216  	}
   217  }
   218  
   219  // SubsMustNeverReceiveAnyMessage checks that all subscriptions never receive any message within the given timeout by the context.
   220  func SubsMustNeverReceiveAnyMessage(t *testing.T, ctx context.Context, subs []p2p.Subscription) {
   221  	for _, sub := range subs {
   222  		SubMustNeverReceiveAnyMessage(t, ctx, sub)
   223  	}
   224  }
   225  
   226  // AddNodesToEachOthersPeerStore adds the dialing address of all nodes to the peer store of all other nodes.
   227  // However, it does not connect them to each other.
   228  func AddNodesToEachOthersPeerStore(t *testing.T, nodes []p2p.LibP2PNode, ids flow.IdentityList) {
   229  	for _, node := range nodes {
   230  		for i, other := range nodes {
   231  			if node == other {
   232  				continue
   233  			}
   234  			otherPInfo, err := utils.PeerAddressInfo(ids[i].IdentitySkeleton)
   235  			require.NoError(t, err)
   236  			node.Host().Peerstore().AddAddrs(otherPInfo.ID, otherPInfo.Addrs, peerstore.AddressTTL)
   237  		}
   238  	}
   239  }
   240  
   241  // EnsureNotConnected ensures that no connection exists from "from" nodes to "to" nodes.
   242  func EnsureNotConnected(t *testing.T, ctx context.Context, from []p2p.LibP2PNode, to []p2p.LibP2PNode) {
   243  	for _, this := range from {
   244  		for _, other := range to {
   245  			if this == other {
   246  				require.Fail(t, "overlapping nodes in from and to lists")
   247  			}
   248  			thisId := this.ID()
   249  			// we intentionally do not check the error here, with libp2p v0.24 connection gating at the "InterceptSecured" level
   250  			// does not cause the nodes to complain about the connection being rejected at the dialer side.
   251  			// Hence, we instead check for any trace of the connection being established in the receiver side.
   252  			_ = this.Host().Connect(ctx, other.Host().Peerstore().PeerInfo(other.ID()))
   253  			// ensures that other node has never received a connection from this node.
   254  			require.Equal(t, network.NotConnected, other.Host().Network().Connectedness(thisId))
   255  			require.Empty(t, other.Host().Network().ConnsToPeer(thisId))
   256  		}
   257  	}
   258  }
   259  
   260  // EnsureMessageExchangeOverUnicast ensures that the given nodes exchange arbitrary messages on through unicasting (i.e., stream creation).
   261  // It fails the test if any of the nodes does not receive the message from the other nodes.
   262  // The "inbounds" parameter specifies the inbound channel of the nodes on which the messages are received.
   263  // The "messageFactory" parameter specifies the function that creates unique messages to be sent.
   264  func EnsureMessageExchangeOverUnicast(t *testing.T, ctx context.Context, nodes []p2p.LibP2PNode, inbounds []chan string, messageFactory func() string) {
   265  	for _, this := range nodes {
   266  		msg := messageFactory()
   267  
   268  		// send the message to all other nodes
   269  		for _, other := range nodes {
   270  			if this == other {
   271  				continue
   272  			}
   273  			err := this.OpenAndWriteOnStream(ctx, other.ID(), t.Name(), func(stream network.Stream) error {
   274  				rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))
   275  				_, err := rw.WriteString(msg)
   276  				require.NoError(t, err)
   277  
   278  				// Flush the stream
   279  				require.NoError(t, rw.Flush())
   280  
   281  				return nil
   282  			})
   283  			require.NoError(t, err)
   284  
   285  		}
   286  
   287  		// wait for the message to be received by all other nodes
   288  		for i, other := range nodes {
   289  			if this == other {
   290  				continue
   291  			}
   292  
   293  			select {
   294  			case rcv := <-inbounds[i]:
   295  				require.Equal(t, msg, rcv)
   296  			case <-time.After(3 * time.Second):
   297  				require.Fail(t, fmt.Sprintf("did not receive message from node %d", i))
   298  			}
   299  		}
   300  	}
   301  }
   302  
   303  // EnsureNoStreamCreationBetweenGroups ensures that no stream is created between the given groups of nodes.
   304  func EnsureNoStreamCreationBetweenGroups(t *testing.T, ctx context.Context, groupA []p2p.LibP2PNode, groupB []p2p.LibP2PNode, errorCheckers ...func(*testing.T, error)) {
   305  	// no stream from groupA -> groupB
   306  	EnsureNoStreamCreation(t, ctx, groupA, groupB, errorCheckers...)
   307  	// no stream from groupB -> groupA
   308  	EnsureNoStreamCreation(t, ctx, groupB, groupA, errorCheckers...)
   309  }
   310  
   311  // EnsureNoStreamCreation ensures that no stream is created "from" the given nodes "to" the given nodes.
   312  func EnsureNoStreamCreation(t *testing.T, ctx context.Context, from []p2p.LibP2PNode, to []p2p.LibP2PNode, errorCheckers ...func(*testing.T, error)) {
   313  	for _, this := range from {
   314  		for _, other := range to {
   315  			if this == other {
   316  				// should not happen, unless the test is misconfigured.
   317  				require.Fail(t, "node is in both from and to lists")
   318  			}
   319  
   320  			// we intentionally do not check the error here, with libp2p v0.24 connection gating at the "InterceptSecured" level
   321  			// does not cause the nodes to complain about the connection being rejected at the dialer side.
   322  			// Hence, we instead check for any trace of the connection being established in the receiver side.
   323  			otherId := other.ID()
   324  			thisId := this.ID()
   325  
   326  			// closes all connections from other node to this node in order to isolate the connection attempt.
   327  			for _, conn := range other.Host().Network().ConnsToPeer(thisId) {
   328  				require.NoError(t, conn.Close())
   329  			}
   330  			require.Empty(t, other.Host().Network().ConnsToPeer(thisId))
   331  
   332  			err := this.OpenAndWriteOnStream(ctx, otherId, t.Name(), func(stream network.Stream) error {
   333  				// no-op as the stream is never created.
   334  				return nil
   335  			})
   336  			// ensures that other node has never received a connection from this node.
   337  			require.Equal(t, network.NotConnected, other.Host().Network().Connectedness(thisId))
   338  			// a stream is established on top of a connection, so if there is no connection, there should be no stream.
   339  			require.Empty(t, other.Host().Network().ConnsToPeer(thisId))
   340  			// runs the error checkers if any.
   341  			for _, check := range errorCheckers {
   342  				check(t, err)
   343  			}
   344  		}
   345  	}
   346  }
   347  
   348  // EnsureStreamCreation ensures that a stream is created between each of the  "from" nodes to each of the "to" nodes.
   349  func EnsureStreamCreation(t *testing.T, ctx context.Context, from []p2p.LibP2PNode, to []p2p.LibP2PNode) {
   350  	for _, this := range from {
   351  		for _, other := range to {
   352  			if this == other {
   353  				// should not happen, unless the test is misconfigured.
   354  				require.Fail(t, "node is in both from and to lists")
   355  			}
   356  			// stream creation should pass without error
   357  			err := this.OpenAndWriteOnStream(ctx, other.ID(), t.Name(), func(stream network.Stream) error {
   358  				require.NotNil(t, stream)
   359  				return nil
   360  			})
   361  			require.NoError(t, err)
   362  		}
   363  	}
   364  }
   365  
   366  // LongStringMessageFactoryFixture returns a function that creates a long unique string message.
   367  func LongStringMessageFactoryFixture(t *testing.T) func() string {
   368  	return func() string {
   369  		msg := "this is an intentionally long MESSAGE to be bigger than buffer size of most of stream compressors"
   370  		require.Greater(t, len(msg), 10, "we must stress test with longer than 10 bytes messages")
   371  		return fmt.Sprintf("%s %d \n", msg, time.Now().UnixNano()) // add timestamp to make sure we don't send the same message twice
   372  	}
   373  }
   374  
   375  // MustEncodeEvent encodes and returns the given event and fails the test if it faces any issue while encoding.
   376  func MustEncodeEvent(t *testing.T, v interface{}, channel channels.Channel) []byte {
   377  	bz, err := unittest.NetworkCodec().Encode(v)
   378  	require.NoError(t, err)
   379  
   380  	msg := message.Message{
   381  		ChannelID: channel.String(),
   382  		Payload:   bz,
   383  	}
   384  	data, err := msg.Marshal()
   385  	require.NoError(t, err)
   386  
   387  	return data
   388  }
   389  
   390  // SubMustReceiveMessage checks that the subscription have received the given message within the given timeout by the context.
   391  func SubMustReceiveMessage(t *testing.T, ctx context.Context, expectedMessage []byte, sub p2p.Subscription) {
   392  	received := make(chan struct{})
   393  	go func() {
   394  		msg, err := sub.Next(ctx)
   395  		require.NoError(t, err)
   396  		require.Equal(t, expectedMessage, msg.Data)
   397  		close(received)
   398  	}()
   399  
   400  	select {
   401  	case <-received:
   402  		return
   403  	case <-ctx.Done():
   404  		require.Fail(t, "timeout on receiving expected pubsub message")
   405  	}
   406  }
   407  
   408  // SubsMustReceiveMessage checks that all subscriptions receive the given message within the given timeout by the context.
   409  func SubsMustReceiveMessage(t *testing.T, ctx context.Context, expectedMessage []byte, subs []p2p.Subscription) {
   410  	for _, sub := range subs {
   411  		SubMustReceiveMessage(t, ctx, expectedMessage, sub)
   412  	}
   413  }