github.com/koko1123/flow-go-1@v0.29.6/network/internal/testutils/testUtil.go (about) 1 package testutils 2 3 import ( 4 "context" 5 "reflect" 6 "runtime" 7 "strings" 8 "sync" 9 "testing" 10 "time" 11 12 dht "github.com/libp2p/go-libp2p-kad-dht" 13 "github.com/libp2p/go-libp2p/core/connmgr" 14 "github.com/libp2p/go-libp2p/core/host" 15 p2pNetwork "github.com/libp2p/go-libp2p/core/network" 16 "github.com/libp2p/go-libp2p/core/peer" 17 pc "github.com/libp2p/go-libp2p/core/protocol" 18 "github.com/libp2p/go-libp2p/core/routing" 19 "github.com/rs/zerolog" 20 "github.com/stretchr/testify/require" 21 22 "github.com/koko1123/flow-go-1/model/flow" 23 "github.com/koko1123/flow-go-1/model/flow/filter" 24 libp2pmessage "github.com/koko1123/flow-go-1/model/libp2p/message" 25 "github.com/koko1123/flow-go-1/module" 26 "github.com/koko1123/flow-go-1/module/id" 27 "github.com/koko1123/flow-go-1/module/irrecoverable" 28 "github.com/koko1123/flow-go-1/module/metrics" 29 "github.com/koko1123/flow-go-1/module/mock" 30 "github.com/koko1123/flow-go-1/module/observable" 31 "github.com/koko1123/flow-go-1/network" 32 netcache "github.com/koko1123/flow-go-1/network/cache" 33 "github.com/koko1123/flow-go-1/network/channels" 34 "github.com/koko1123/flow-go-1/network/codec/cbor" 35 "github.com/koko1123/flow-go-1/network/p2p" 36 "github.com/koko1123/flow-go-1/network/p2p/connection" 37 p2pdht "github.com/koko1123/flow-go-1/network/p2p/dht" 38 "github.com/koko1123/flow-go-1/network/p2p/middleware" 39 "github.com/koko1123/flow-go-1/network/p2p/p2pbuilder" 40 "github.com/koko1123/flow-go-1/network/p2p/subscription" 41 "github.com/koko1123/flow-go-1/network/p2p/translator" 42 "github.com/koko1123/flow-go-1/network/p2p/unicast" 43 "github.com/koko1123/flow-go-1/network/p2p/unicast/ratelimit" 44 "github.com/koko1123/flow-go-1/network/slashing" 45 "github.com/koko1123/flow-go-1/utils/unittest" 46 "github.com/onflow/flow-go/crypto" 47 ) 48 49 var sporkID = unittest.IdentifierFixture() 50 51 type PeerTag struct { 52 Peer peer.ID 53 Tag string 54 } 55 56 type TagWatchingConnManager struct { 57 *connection.ConnManager 58 observers map[observable.Observer]struct{} 59 obsLock sync.RWMutex 60 } 61 62 func (cwcm *TagWatchingConnManager) Subscribe(observer observable.Observer) { 63 cwcm.obsLock.Lock() 64 defer cwcm.obsLock.Unlock() 65 var void struct{} 66 cwcm.observers[observer] = void 67 } 68 69 func (cwcm *TagWatchingConnManager) Unsubscribe(observer observable.Observer) { 70 cwcm.obsLock.Lock() 71 defer cwcm.obsLock.Unlock() 72 delete(cwcm.observers, observer) 73 } 74 75 func (cwcm *TagWatchingConnManager) Protect(id peer.ID, tag string) { 76 cwcm.obsLock.RLock() 77 defer cwcm.obsLock.RUnlock() 78 cwcm.ConnManager.Protect(id, tag) 79 for obs := range cwcm.observers { 80 go obs.OnNext(PeerTag{Peer: id, Tag: tag}) 81 } 82 } 83 84 func (cwcm *TagWatchingConnManager) Unprotect(id peer.ID, tag string) bool { 85 cwcm.obsLock.RLock() 86 defer cwcm.obsLock.RUnlock() 87 res := cwcm.ConnManager.Unprotect(id, tag) 88 for obs := range cwcm.observers { 89 go obs.OnNext(PeerTag{Peer: id, Tag: tag}) 90 } 91 return res 92 } 93 94 func NewTagWatchingConnManager(log zerolog.Logger, metrics module.LibP2PConnectionMetrics) *TagWatchingConnManager { 95 cm := connection.NewConnManager(log, metrics) 96 return &TagWatchingConnManager{ 97 ConnManager: cm, 98 observers: make(map[observable.Observer]struct{}), 99 obsLock: sync.RWMutex{}, 100 } 101 } 102 103 // GenerateIDs is a test helper that generate flow identities with a valid port and libp2p nodes. 104 func GenerateIDs(t *testing.T, logger zerolog.Logger, n int, opts ...func(*optsConfig)) (flow.IdentityList, 105 []p2p.LibP2PNode, 106 []observable.Observable) { 107 libP2PNodes := make([]p2p.LibP2PNode, n) 108 tagObservables := make([]observable.Observable, n) 109 110 o := &optsConfig{peerUpdateInterval: connection.DefaultPeerUpdateInterval} 111 for _, opt := range opts { 112 opt(o) 113 } 114 115 identities := unittest.IdentityListFixture(n, unittest.WithAllRoles()) 116 117 for _, identity := range identities { 118 for _, idOpt := range o.idOpts { 119 idOpt(identity) 120 } 121 } 122 123 idProvider := id.NewFixedIdentityProvider(identities) 124 125 // generates keys and address for the node 126 for i, id := range identities { 127 // generate key 128 key, err := generateNetworkingKey(id.NodeID) 129 require.NoError(t, err) 130 131 var opts []nodeBuilderOption 132 133 opts = append(opts, withDHT(o.dhtPrefix, o.dhtOpts...)) 134 opts = append(opts, withPeerManagerOptions(connection.ConnectionPruningEnabled, o.peerUpdateInterval)) 135 136 libP2PNodes[i], tagObservables[i] = generateLibP2PNode(t, logger, key, idProvider, opts...) 137 138 _, port, err := libP2PNodes[i].GetIPPort() 139 require.NoError(t, err) 140 141 identities[i].Address = unittest.IPPort(port) 142 identities[i].NetworkPubKey = key.PublicKey() 143 } 144 145 return identities, libP2PNodes, tagObservables 146 } 147 148 // GenerateMiddlewares creates and initializes middleware instances for all the identities 149 func GenerateMiddlewares(t *testing.T, 150 logger zerolog.Logger, 151 identities flow.IdentityList, 152 libP2PNodes []p2p.LibP2PNode, 153 codec network.Codec, 154 consumer slashing.ViolationsConsumer, 155 opts ...func(*optsConfig)) ([]network.Middleware, []*UpdatableIDProvider) { 156 mws := make([]network.Middleware, len(identities)) 157 idProviders := make([]*UpdatableIDProvider, len(identities)) 158 bitswapmet := metrics.NewNoopCollector() 159 o := &optsConfig{ 160 peerUpdateInterval: connection.DefaultPeerUpdateInterval, 161 unicastRateLimiters: ratelimit.NoopRateLimiters(), 162 networkMetrics: metrics.NewNoopCollector(), 163 } 164 165 for _, opt := range opts { 166 opt(o) 167 } 168 169 total := len(identities) 170 for i := 0; i < total; i++ { 171 // casts libP2PNode instance to a local variable to avoid closure 172 node := libP2PNodes[i] 173 nodeId := identities[i].NodeID 174 175 idProviders[i] = NewUpdatableIDProvider(identities) 176 177 // creating middleware of nodes 178 mws[i] = middleware.NewMiddleware( 179 logger, 180 node, 181 nodeId, 182 bitswapmet, 183 sporkID, 184 middleware.DefaultUnicastTimeout, 185 translator.NewIdentityProviderIDTranslator(idProviders[i]), 186 codec, 187 consumer, 188 middleware.WithUnicastRateLimiters(o.unicastRateLimiters)) 189 } 190 return mws, idProviders 191 } 192 193 // GenerateNetworks generates the network for the given middlewares 194 func GenerateNetworks(t *testing.T, 195 log zerolog.Logger, 196 ids flow.IdentityList, 197 mws []network.Middleware, 198 sms []network.SubscriptionManager) []network.Network { 199 count := len(ids) 200 nets := make([]network.Network, 0) 201 202 for i := 0; i < count; i++ { 203 204 // creates and mocks me 205 me := &mock.Local{} 206 me.On("NodeID").Return(ids[i].NodeID) 207 me.On("NotMeFilter").Return(filter.Not(filter.HasNodeID(me.NodeID()))) 208 me.On("Address").Return(ids[i].Address) 209 210 receiveCache := netcache.NewHeroReceiveCache(p2p.DefaultReceiveCacheSize, log, metrics.NewNoopCollector()) 211 212 // create the network 213 net, err := p2p.NewNetwork(&p2p.NetworkParameters{ 214 Logger: log, 215 Codec: cbor.NewCodec(), 216 Me: me, 217 MiddlewareFactory: func() (network.Middleware, error) { return mws[i], nil }, 218 Topology: unittest.NetworkTopology(), 219 SubscriptionManager: sms[i], 220 Metrics: metrics.NewNoopCollector(), 221 IdentityProvider: id.NewFixedIdentityProvider(ids), 222 ReceiveCache: receiveCache, 223 }) 224 require.NoError(t, err) 225 226 nets = append(nets, net) 227 } 228 229 return nets 230 } 231 232 // GenerateIDsAndMiddlewares returns nodeIDs, libp2pNodes, middlewares, and observables which can be subscirbed to in order to witness protect events from pubsub 233 func GenerateIDsAndMiddlewares(t *testing.T, 234 n int, 235 logger zerolog.Logger, 236 codec network.Codec, 237 consumer slashing.ViolationsConsumer, 238 opts ...func(*optsConfig)) (flow.IdentityList, []p2p.LibP2PNode, []network.Middleware, []observable.Observable, []*UpdatableIDProvider) { 239 240 ids, libP2PNodes, protectObservables := GenerateIDs(t, logger, n, opts...) 241 mws, providers := GenerateMiddlewares(t, logger, ids, libP2PNodes, codec, consumer, opts...) 242 return ids, libP2PNodes, mws, protectObservables, providers 243 } 244 245 type optsConfig struct { 246 idOpts []func(*flow.Identity) 247 dhtPrefix string 248 dhtOpts []dht.Option 249 unicastRateLimiters *ratelimit.RateLimiters 250 peerUpdateInterval time.Duration 251 networkMetrics module.NetworkMetrics 252 } 253 254 func WithIdentityOpts(idOpts ...func(*flow.Identity)) func(*optsConfig) { 255 return func(o *optsConfig) { 256 o.idOpts = idOpts 257 } 258 } 259 260 func WithDHT(prefix string, dhtOpts ...dht.Option) func(*optsConfig) { 261 return func(o *optsConfig) { 262 o.dhtPrefix = prefix 263 o.dhtOpts = dhtOpts 264 } 265 } 266 267 func WithPeerUpdateInterval(interval time.Duration) func(*optsConfig) { 268 return func(o *optsConfig) { 269 o.peerUpdateInterval = interval 270 } 271 } 272 273 func WithUnicastRateLimiters(limiters *ratelimit.RateLimiters) func(*optsConfig) { 274 return func(o *optsConfig) { 275 o.unicastRateLimiters = limiters 276 } 277 } 278 279 func WithNetworkMetrics(m module.NetworkMetrics) func(*optsConfig) { 280 return func(o *optsConfig) { 281 o.networkMetrics = m 282 } 283 } 284 285 func GenerateIDsMiddlewaresNetworks(t *testing.T, 286 n int, 287 log zerolog.Logger, 288 codec network.Codec, 289 consumer slashing.ViolationsConsumer, 290 opts ...func(*optsConfig)) (flow.IdentityList, []p2p.LibP2PNode, []network.Middleware, []network.Network, []observable.Observable) { 291 ids, libp2pNodes, mws, observables, _ := GenerateIDsAndMiddlewares(t, n, log, codec, consumer, opts...) 292 sms := GenerateSubscriptionManagers(t, mws) 293 networks := GenerateNetworks(t, log, ids, mws, sms) 294 295 return ids, libp2pNodes, mws, networks, observables 296 } 297 298 // GenerateEngines generates MeshEngines for the given networks 299 func GenerateEngines(t *testing.T, nets []network.Network) []*MeshEngine { 300 count := len(nets) 301 engs := make([]*MeshEngine, count) 302 for i, n := range nets { 303 eng := NewMeshEngine(t, n, 100, channels.TestNetworkChannel) 304 engs[i] = eng 305 } 306 return engs 307 } 308 309 // StartNodesAndNetworks starts the provided networks and libp2p nodes, returning the irrecoverable error channel 310 func StartNodesAndNetworks(ctx irrecoverable.SignalerContext, t *testing.T, nodes []p2p.LibP2PNode, nets []network.Network, duration time.Duration) { 311 // start up networks (this will implicitly start middlewares) 312 for _, net := range nets { 313 net.Start(ctx) 314 unittest.RequireComponentsReadyBefore(t, duration, net) 315 } 316 317 // start up nodes and Peer managers 318 StartNodes(ctx, t, nodes, duration) 319 } 320 321 // StartNodes starts the provided nodes and their peer managers using the provided irrecoverable context 322 func StartNodes(ctx irrecoverable.SignalerContext, t *testing.T, nodes []p2p.LibP2PNode, duration time.Duration) { 323 for _, node := range nodes { 324 node.Start(ctx) 325 unittest.RequireComponentsReadyBefore(t, duration, node) 326 327 pm := node.PeerManagerComponent() 328 pm.Start(ctx) 329 unittest.RequireComponentsReadyBefore(t, duration, pm) 330 } 331 } 332 333 // StopComponents stops ReadyDoneAware instances in parallel and fails the test if they could not be stopped within the 334 // duration. 335 func StopComponents[R module.ReadyDoneAware](t *testing.T, rda []R, duration time.Duration) { 336 comps := make([]module.ReadyDoneAware, 0, len(rda)) 337 for _, c := range rda { 338 comps = append(comps, c) 339 } 340 341 unittest.RequireComponentsDoneBefore(t, duration, comps...) 342 } 343 344 type nodeBuilderOption func(p2pbuilder.NodeBuilder) 345 346 func withDHT(prefix string, dhtOpts ...dht.Option) nodeBuilderOption { 347 return func(nb p2pbuilder.NodeBuilder) { 348 nb.SetRoutingSystem(func(c context.Context, h host.Host) (routing.Routing, error) { 349 return p2pdht.NewDHT(c, h, pc.ID(unicast.FlowDHTProtocolIDPrefix+prefix), zerolog.Nop(), metrics.NewNoopCollector(), dhtOpts...) 350 }) 351 } 352 } 353 354 func withPeerManagerOptions(connectionPruning bool, updateInterval time.Duration) nodeBuilderOption { 355 return func(nb p2pbuilder.NodeBuilder) { 356 nb.SetPeerManagerOptions(connectionPruning, updateInterval) 357 } 358 } 359 360 // generateLibP2PNode generates a `LibP2PNode` on localhost using a port assigned by the OS 361 func generateLibP2PNode(t *testing.T, 362 logger zerolog.Logger, 363 key crypto.PrivateKey, 364 idProvider module.IdentityProvider, 365 opts ...nodeBuilderOption) (p2p.LibP2PNode, observable.Observable) { 366 367 noopMetrics := metrics.NewNoopCollector() 368 369 // Inject some logic to be able to observe connections of this node 370 connManager := NewTagWatchingConnManager(logger, noopMetrics) 371 372 builder := p2pbuilder.NewNodeBuilder( 373 logger, 374 metrics.NewNoopCollector(), 375 unittest.DefaultAddress, 376 key, 377 sporkID, 378 p2pbuilder.DefaultResourceManagerConfig()). 379 SetConnectionManager(connManager). 380 SetResourceManager(NewResourceManager(t)) 381 382 for _, opt := range opts { 383 opt(builder) 384 } 385 386 libP2PNode, err := builder.Build() 387 require.NoError(t, err) 388 389 return libP2PNode, connManager 390 } 391 392 // OptionalSleep introduces a sleep to allow nodes to heartbeat and discover each other (only needed when using PubSub) 393 func OptionalSleep(send ConduitSendWrapperFunc) { 394 sendFuncName := runtime.FuncForPC(reflect.ValueOf(send).Pointer()).Name() 395 if strings.Contains(sendFuncName, "Multicast") || strings.Contains(sendFuncName, "Publish") { 396 time.Sleep(2 * time.Second) 397 } 398 } 399 400 // generateNetworkingKey generates a Flow ECDSA key using the given seed 401 func generateNetworkingKey(s flow.Identifier) (crypto.PrivateKey, error) { 402 seed := make([]byte, crypto.KeyGenSeedMinLenECDSASecp256k1) 403 copy(seed, s[:]) 404 return crypto.GeneratePrivateKey(crypto.ECDSASecp256k1, seed) 405 } 406 407 // GenerateSubscriptionManagers creates and returns a ChannelSubscriptionManager for each middleware object. 408 func GenerateSubscriptionManagers(t *testing.T, mws []network.Middleware) []network.SubscriptionManager { 409 require.NotEmpty(t, mws) 410 411 sms := make([]network.SubscriptionManager, len(mws)) 412 for i, mw := range mws { 413 sms[i] = subscription.NewChannelSubscriptionManager(mw) 414 } 415 return sms 416 } 417 418 // NetworkPayloadFixture creates a blob of random bytes with the given size (in bytes) and returns it. 419 // The primary goal of utilizing this helper function is to apply stress tests on the network layer by 420 // sending large messages to transmit. 421 func NetworkPayloadFixture(t *testing.T, size uint) []byte { 422 // reserves 1000 bytes for the message headers, encoding overhead, and libp2p message overhead. 423 overhead := 1000 424 require.Greater(t, int(size), overhead, "could not generate message below size threshold") 425 emptyEvent := &libp2pmessage.TestMessage{ 426 Text: "", 427 } 428 429 // encodes the message 430 codec := cbor.NewCodec() 431 empty, err := codec.Encode(emptyEvent) 432 require.NoError(t, err) 433 434 // max possible payload size 435 payloadSize := int(size) - overhead - len(empty) 436 payload := make([]byte, payloadSize) 437 438 // populates payload with random bytes 439 for i := range payload { 440 payload[i] = 'a' // a utf-8 char that translates to 1-byte when converted to a string 441 } 442 443 event := emptyEvent 444 event.Text = string(payload) 445 // encode Event the way the network would encode it to get the size of the message 446 // just to do the size check 447 encodedEvent, err := codec.Encode(event) 448 require.NoError(t, err) 449 450 require.InDelta(t, len(encodedEvent), int(size), float64(overhead)) 451 452 return payload 453 } 454 455 // NewResourceManager creates a new resource manager for testing with no limits. 456 func NewResourceManager(t *testing.T) p2pNetwork.ResourceManager { 457 return p2pNetwork.NullResourceManager 458 } 459 460 // NewConnectionGater creates a new connection gater for testing with given allow listing filter. 461 func NewConnectionGater(allowListFilter p2p.PeerFilter) connmgr.ConnectionGater { 462 filters := []p2p.PeerFilter{allowListFilter} 463 return connection.NewConnGater(unittest.Logger(), 464 connection.WithOnInterceptPeerDialFilters(filters), 465 connection.WithOnInterceptSecuredFilters(filters)) 466 }