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 }