github.com/onflow/flow-go@v0.33.17/network/internal/p2pfixtures/fixtures.go (about)

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