github.com/koko1123/flow-go-1@v0.29.6/network/p2p/test/fixtures.go (about)

     1  package p2ptest
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"testing"
     7  	"time"
     8  
     9  	dht "github.com/libp2p/go-libp2p-kad-dht"
    10  	"github.com/libp2p/go-libp2p/core/connmgr"
    11  	"github.com/libp2p/go-libp2p/core/host"
    12  	"github.com/libp2p/go-libp2p/core/network"
    13  	"github.com/libp2p/go-libp2p/core/peer"
    14  	"github.com/libp2p/go-libp2p/core/protocol"
    15  	"github.com/libp2p/go-libp2p/core/routing"
    16  	"github.com/rs/zerolog"
    17  	"github.com/stretchr/testify/require"
    18  
    19  	"github.com/koko1123/flow-go-1/model/flow"
    20  	"github.com/koko1123/flow-go-1/module"
    21  	"github.com/koko1123/flow-go-1/module/irrecoverable"
    22  	"github.com/koko1123/flow-go-1/module/metrics"
    23  	"github.com/koko1123/flow-go-1/network/channels"
    24  	"github.com/koko1123/flow-go-1/network/internal/p2pfixtures"
    25  	"github.com/koko1123/flow-go-1/network/internal/testutils"
    26  	"github.com/koko1123/flow-go-1/network/p2p"
    27  	"github.com/koko1123/flow-go-1/network/p2p/connection"
    28  	p2pdht "github.com/koko1123/flow-go-1/network/p2p/dht"
    29  	"github.com/koko1123/flow-go-1/network/p2p/p2pbuilder"
    30  	"github.com/koko1123/flow-go-1/network/p2p/scoring"
    31  	"github.com/koko1123/flow-go-1/network/p2p/unicast"
    32  	"github.com/koko1123/flow-go-1/network/p2p/utils"
    33  	validator "github.com/koko1123/flow-go-1/network/validator/pubsub"
    34  	"github.com/koko1123/flow-go-1/utils/logging"
    35  	"github.com/koko1123/flow-go-1/utils/unittest"
    36  	"github.com/onflow/flow-go/crypto"
    37  )
    38  
    39  // NetworkingKeyFixtures is a test helper that generates a ECDSA flow key pair.
    40  func NetworkingKeyFixtures(t *testing.T) crypto.PrivateKey {
    41  	seed := unittest.SeedFixture(48)
    42  	key, err := crypto.GeneratePrivateKey(crypto.ECDSASecp256k1, seed)
    43  	require.NoError(t, err)
    44  	return key
    45  }
    46  
    47  // NodeFixture is a test fixture that creates a single libp2p node with the given key, spork id, and options.
    48  // It returns the node and its identity.
    49  func NodeFixture(
    50  	t *testing.T,
    51  	sporkID flow.Identifier,
    52  	dhtPrefix string,
    53  	opts ...NodeFixtureParameterOption,
    54  ) (p2p.LibP2PNode, flow.Identity) {
    55  	// default parameters
    56  	parameters := &NodeFixtureParameters{
    57  		HandlerFunc: func(network.Stream) {},
    58  		Unicasts:    nil,
    59  		Key:         NetworkingKeyFixtures(t),
    60  		Address:     unittest.DefaultAddress,
    61  		Logger:      unittest.Logger().Level(zerolog.ErrorLevel),
    62  		Role:        flow.RoleCollection,
    63  	}
    64  
    65  	for _, opt := range opts {
    66  		opt(parameters)
    67  	}
    68  
    69  	identity := unittest.IdentityFixture(
    70  		unittest.WithNetworkingKey(parameters.Key.PublicKey()),
    71  		unittest.WithAddress(parameters.Address),
    72  		unittest.WithRole(parameters.Role))
    73  
    74  	logger := parameters.Logger.With().Hex("node_id", logging.ID(identity.NodeID)).Logger()
    75  
    76  	noopMetrics := metrics.NewNoopCollector()
    77  	connManager := connection.NewConnManager(logger, noopMetrics)
    78  	resourceManager := testutils.NewResourceManager(t)
    79  
    80  	builder := p2pbuilder.NewNodeBuilder(
    81  		logger,
    82  		metrics.NewNoopCollector(),
    83  		parameters.Address,
    84  		parameters.Key,
    85  		sporkID,
    86  		p2pbuilder.DefaultResourceManagerConfig()).
    87  		SetConnectionManager(connManager).
    88  		SetRoutingSystem(func(c context.Context, h host.Host) (routing.Routing, error) {
    89  			return p2pdht.NewDHT(c, h,
    90  				protocol.ID(unicast.FlowDHTProtocolIDPrefix+sporkID.String()+"/"+dhtPrefix),
    91  				logger,
    92  				noopMetrics,
    93  				parameters.DhtOptions...,
    94  			)
    95  		}).
    96  		SetResourceManager(resourceManager).
    97  		SetCreateNode(p2pbuilder.DefaultCreateNodeFunc)
    98  
    99  	if parameters.ConnGater != nil {
   100  		builder.SetConnectionGater(parameters.ConnGater)
   101  	}
   102  
   103  	if parameters.PeerScoringEnabled {
   104  		scoreOptionParams := make([]scoring.PeerScoreParamsOption, 0)
   105  		if parameters.AppSpecificScore != nil {
   106  			scoreOptionParams = append(scoreOptionParams, scoring.WithAppSpecificScoreFunction(parameters.AppSpecificScore))
   107  		}
   108  		builder.EnableGossipSubPeerScoring(parameters.IdProvider, scoreOptionParams...)
   109  	}
   110  
   111  	if parameters.UpdateInterval != 0 {
   112  		require.NotNil(t, parameters.PeerProvider)
   113  		builder.SetPeerManagerOptions(parameters.ConnectionPruning, parameters.UpdateInterval)
   114  	}
   115  
   116  	if parameters.GossipSubFactory != nil && parameters.GossipSubConfig != nil {
   117  		builder.SetGossipSubFactory(parameters.GossipSubFactory, parameters.GossipSubConfig)
   118  	}
   119  
   120  	n, err := builder.Build()
   121  	require.NoError(t, err)
   122  
   123  	err = n.WithDefaultUnicastProtocol(parameters.HandlerFunc, parameters.Unicasts)
   124  	require.NoError(t, err)
   125  
   126  	// get the actual IP and port that have been assigned by the subsystem
   127  	ip, port, err := n.GetIPPort()
   128  	require.NoError(t, err)
   129  	identity.Address = ip + ":" + port
   130  
   131  	if parameters.PeerProvider != nil {
   132  		n.WithPeersProvider(parameters.PeerProvider)
   133  	}
   134  	return n, *identity
   135  }
   136  
   137  type NodeFixtureParameterOption func(*NodeFixtureParameters)
   138  
   139  type NodeFixtureParameters struct {
   140  	HandlerFunc        network.StreamHandler
   141  	Unicasts           []unicast.ProtocolName
   142  	Key                crypto.PrivateKey
   143  	Address            string
   144  	DhtOptions         []dht.Option
   145  	Role               flow.Role
   146  	Logger             zerolog.Logger
   147  	PeerScoringEnabled bool
   148  	IdProvider         module.IdentityProvider
   149  	AppSpecificScore   func(peer.ID) float64 // overrides GossipSub scoring for sake of testing.
   150  	ConnectionPruning  bool                  // peer manager parameter
   151  	UpdateInterval     time.Duration         // peer manager parameter
   152  	PeerProvider       p2p.PeersProvider     // peer manager parameter
   153  	ConnGater          connmgr.ConnectionGater
   154  	GossipSubFactory   p2pbuilder.GossipSubFactoryFunc
   155  	GossipSubConfig    p2pbuilder.GossipSubAdapterConfigFunc
   156  }
   157  
   158  func WithPeerScoringEnabled(idProvider module.IdentityProvider) NodeFixtureParameterOption {
   159  	return func(p *NodeFixtureParameters) {
   160  		p.PeerScoringEnabled = true
   161  		p.IdProvider = idProvider
   162  	}
   163  }
   164  
   165  func WithDefaultStreamHandler(handler network.StreamHandler) NodeFixtureParameterOption {
   166  	return func(p *NodeFixtureParameters) {
   167  		p.HandlerFunc = handler
   168  	}
   169  }
   170  
   171  func WithPeerManagerEnabled(connectionPruning bool, updateInterval time.Duration, peerProvider p2p.PeersProvider) NodeFixtureParameterOption {
   172  	return func(p *NodeFixtureParameters) {
   173  		p.ConnectionPruning = connectionPruning
   174  		p.UpdateInterval = updateInterval
   175  		p.PeerProvider = peerProvider
   176  	}
   177  }
   178  
   179  func WithPreferredUnicasts(unicasts []unicast.ProtocolName) NodeFixtureParameterOption {
   180  	return func(p *NodeFixtureParameters) {
   181  		p.Unicasts = unicasts
   182  	}
   183  }
   184  
   185  func WithNetworkingPrivateKey(key crypto.PrivateKey) NodeFixtureParameterOption {
   186  	return func(p *NodeFixtureParameters) {
   187  		p.Key = key
   188  	}
   189  }
   190  
   191  func WithNetworkingAddress(address string) NodeFixtureParameterOption {
   192  	return func(p *NodeFixtureParameters) {
   193  		p.Address = address
   194  	}
   195  }
   196  
   197  func WithDHTOptions(opts ...dht.Option) NodeFixtureParameterOption {
   198  	return func(p *NodeFixtureParameters) {
   199  		p.DhtOptions = opts
   200  	}
   201  }
   202  
   203  func WithConnectionGater(connGater connmgr.ConnectionGater) NodeFixtureParameterOption {
   204  	return func(p *NodeFixtureParameters) {
   205  		p.ConnGater = connGater
   206  	}
   207  }
   208  
   209  func WithRole(role flow.Role) NodeFixtureParameterOption {
   210  	return func(p *NodeFixtureParameters) {
   211  		p.Role = role
   212  	}
   213  }
   214  
   215  func WithAppSpecificScore(score func(peer.ID) float64) NodeFixtureParameterOption {
   216  	return func(p *NodeFixtureParameters) {
   217  		p.AppSpecificScore = score
   218  	}
   219  }
   220  
   221  func WithLogger(logger zerolog.Logger) NodeFixtureParameterOption {
   222  	return func(p *NodeFixtureParameters) {
   223  		p.Logger = logger
   224  	}
   225  }
   226  
   227  // NodesFixture is a test fixture that creates a number of libp2p nodes with the given callback function for stream handling.
   228  // It returns the nodes and their identities.
   229  func NodesFixture(t *testing.T, sporkID flow.Identifier, dhtPrefix string, count int, opts ...NodeFixtureParameterOption) ([]p2p.LibP2PNode,
   230  	flow.IdentityList) {
   231  	var nodes []p2p.LibP2PNode
   232  
   233  	// creating nodes
   234  	var identities flow.IdentityList
   235  	for i := 0; i < count; i++ {
   236  		// create a node on localhost with a random port assigned by the OS
   237  		node, identity := NodeFixture(t, sporkID, dhtPrefix, opts...)
   238  		nodes = append(nodes, node)
   239  		identities = append(identities, &identity)
   240  	}
   241  
   242  	return nodes, identities
   243  }
   244  
   245  // StartNodes start all nodes in the input slice using the provided context, timing out if nodes are
   246  // not all Ready() before duration expires
   247  func StartNodes(t *testing.T, ctx irrecoverable.SignalerContext, nodes []p2p.LibP2PNode, timeout time.Duration) {
   248  	rdas := make([]module.ReadyDoneAware, 0, len(nodes))
   249  	for _, node := range nodes {
   250  		node.Start(ctx)
   251  		rdas = append(rdas, node)
   252  
   253  		if peerManager := node.PeerManagerComponent(); peerManager != (*connection.PeerManager)(nil) {
   254  			// we need to start the peer manager post the node startup (if such component exists).
   255  			peerManager.Start(ctx)
   256  			rdas = append(rdas, peerManager)
   257  		}
   258  	}
   259  	unittest.RequireComponentsReadyBefore(t, timeout, rdas...)
   260  }
   261  
   262  // StartNode start a single node using the provided context, timing out if nodes are not all Ready()
   263  // before duration expires
   264  func StartNode(t *testing.T, ctx irrecoverable.SignalerContext, node p2p.LibP2PNode, timeout time.Duration) {
   265  	node.Start(ctx)
   266  	unittest.RequireComponentsReadyBefore(t, timeout, node)
   267  }
   268  
   269  // StopNodes stops all nodes in the input slice using the provided cancel func, timing out if nodes are
   270  // not all Done() before duration expires
   271  func StopNodes(t *testing.T, nodes []p2p.LibP2PNode, cancel context.CancelFunc, timeout time.Duration) {
   272  	cancel()
   273  	for _, node := range nodes {
   274  		unittest.RequireComponentsDoneBefore(t, timeout, node)
   275  	}
   276  }
   277  
   278  // StopNode stops a single node using the provided cancel func, timing out if nodes are not all Done()
   279  // before duration expires
   280  func StopNode(t *testing.T, node p2p.LibP2PNode, cancel context.CancelFunc, timeout time.Duration) {
   281  	cancel()
   282  	unittest.RequireComponentsDoneBefore(t, timeout, node)
   283  }
   284  
   285  // StreamHandlerFixture returns a stream handler that writes the received message to the given channel.
   286  func StreamHandlerFixture(t *testing.T) (func(s network.Stream), chan string) {
   287  	ch := make(chan string, 1) // channel to receive messages
   288  
   289  	return func(s network.Stream) {
   290  		rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s))
   291  		str, err := rw.ReadString('\n')
   292  		require.NoError(t, err)
   293  		ch <- str
   294  	}, ch
   295  }
   296  
   297  // LetNodesDiscoverEachOther connects all nodes to each other on the pubsub mesh.
   298  func LetNodesDiscoverEachOther(t *testing.T, ctx context.Context, nodes []p2p.LibP2PNode, ids flow.IdentityList) {
   299  	for _, node := range nodes {
   300  		for i, other := range nodes {
   301  			if node == other {
   302  				continue
   303  			}
   304  			otherPInfo, err := utils.PeerAddressInfo(*ids[i])
   305  			require.NoError(t, err)
   306  			require.NoError(t, node.AddPeer(ctx, otherPInfo))
   307  		}
   308  	}
   309  }
   310  
   311  // EnsureConnected ensures that the given nodes are connected to each other.
   312  // It fails the test if any of the nodes is not connected to any other node.
   313  func EnsureConnected(t *testing.T, ctx context.Context, nodes []p2p.LibP2PNode) {
   314  	for _, node := range nodes {
   315  		for _, other := range nodes {
   316  			if node == other {
   317  				continue
   318  			}
   319  			require.NoError(t, node.Host().Connect(ctx, other.Host().Peerstore().PeerInfo(other.Host().ID())))
   320  			require.Equal(t, node.Host().Network().Connectedness(other.Host().ID()), network.Connected)
   321  		}
   322  	}
   323  }
   324  
   325  // EnsureStreamCreationInBothDirections ensure that between each pair of nodes in the given list, a stream is created in both directions.
   326  func EnsureStreamCreationInBothDirections(t *testing.T, ctx context.Context, nodes []p2p.LibP2PNode) {
   327  	for _, this := range nodes {
   328  		for _, other := range nodes {
   329  			if this == other {
   330  				continue
   331  			}
   332  			// stream creation should pass without error
   333  			s, err := this.CreateStream(ctx, other.Host().ID())
   334  			require.NoError(t, err)
   335  			require.NotNil(t, s)
   336  		}
   337  	}
   338  }
   339  
   340  // EnsurePubsubMessageExchange ensures that the given connected nodes exchange the given message on the given channel through pubsub.
   341  // Note: EnsureConnected() must be called to connect all nodes before calling this function.
   342  func EnsurePubsubMessageExchange(t *testing.T, ctx context.Context, nodes []p2p.LibP2PNode, messageFactory func() (interface{}, channels.Topic)) {
   343  	_, topic := messageFactory()
   344  
   345  	subs := make([]p2p.Subscription, len(nodes))
   346  	slashingViolationsConsumer := unittest.NetworkSlashingViolationsConsumer(unittest.Logger(), metrics.NewNoopCollector())
   347  	for i, node := range nodes {
   348  		ps, err := node.Subscribe(
   349  			topic,
   350  			validator.TopicValidator(
   351  				unittest.Logger(),
   352  				unittest.NetworkCodec(),
   353  				slashingViolationsConsumer,
   354  				unittest.AllowAllPeerFilter()))
   355  		require.NoError(t, err)
   356  		subs[i] = ps
   357  	}
   358  
   359  	// let subscriptions propagate
   360  	time.Sleep(1 * time.Second)
   361  
   362  	channel, ok := channels.ChannelFromTopic(topic)
   363  	require.True(t, ok)
   364  
   365  	for _, node := range nodes {
   366  		// creates a unique message to be published by the node
   367  		msg, _ := messageFactory()
   368  		data := p2pfixtures.MustEncodeEvent(t, msg, channel)
   369  		require.NoError(t, node.Publish(ctx, topic, data))
   370  
   371  		// wait for the message to be received by all nodes
   372  		ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
   373  		p2pfixtures.SubsMustReceiveMessage(t, ctx, data, subs)
   374  		cancel()
   375  	}
   376  }