github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/network/p2p/node/resourceManager_test.go (about) 1 package p2pnode_test 2 3 import ( 4 "context" 5 "fmt" 6 "math" 7 "sync" 8 "testing" 9 "time" 10 11 "github.com/libp2p/go-libp2p" 12 "github.com/libp2p/go-libp2p/core/network" 13 rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" 14 "github.com/stretchr/testify/require" 15 16 "github.com/onflow/flow-go/config" 17 "github.com/onflow/flow-go/model/flow" 18 "github.com/onflow/flow-go/module/irrecoverable" 19 mockmodule "github.com/onflow/flow-go/module/mock" 20 "github.com/onflow/flow-go/network/internal/p2putils" 21 "github.com/onflow/flow-go/network/p2p" 22 p2ptest "github.com/onflow/flow-go/network/p2p/test" 23 "github.com/onflow/flow-go/network/p2p/unicast/protocols" 24 "github.com/onflow/flow-go/utils/unittest" 25 ) 26 27 // TestCreateStream_InboundConnResourceLimit ensures that the setting the resource limit config for 28 // PeerDefaultLimits.ConnsInbound restricts the number of inbound connections created from a peer to the configured value. 29 // NOTE: If this test becomes flaky, it indicates a violation of the single inbound connection guarantee. 30 // In such cases the test should not be quarantined but requires immediate resolution. 31 func TestCreateStream_InboundConnResourceLimit(t *testing.T) { 32 idProvider := mockmodule.NewIdentityProvider(t) 33 ctx, cancel := context.WithCancel(context.Background()) 34 defer cancel() 35 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 36 37 cfg, err := config.DefaultConfig() 38 require.NoError(t, err) 39 cfg.NetworkConfig.Unicast.UnicastManager.CreateStreamBackoffDelay = 10 * time.Millisecond 40 41 sporkID := unittest.IdentifierFixture() 42 43 sender, id1 := p2ptest.NodeFixture( 44 t, 45 sporkID, 46 t.Name(), 47 idProvider, 48 p2ptest.WithDefaultResourceManager(), 49 p2ptest.OverrideFlowConfig(cfg)) 50 51 receiver, id2 := p2ptest.NodeFixture( 52 t, 53 sporkID, 54 t.Name(), 55 idProvider, 56 p2ptest.WithDefaultResourceManager(), 57 p2ptest.OverrideFlowConfig(cfg)) 58 59 idProvider.On("ByPeerID", sender.ID()).Return(&id1, true).Maybe() 60 idProvider.On("ByPeerID", receiver.ID()).Return(&id2, true).Maybe() 61 62 p2ptest.StartNodes(t, signalerCtx, []p2p.LibP2PNode{sender, receiver}) 63 defer p2ptest.StopNodes(t, []p2p.LibP2PNode{sender, receiver}, cancel) 64 65 p2ptest.LetNodesDiscoverEachOther(t, signalerCtx, []p2p.LibP2PNode{sender, receiver}, flow.IdentityList{&id1, &id2}) 66 67 var allStreamsCreated sync.WaitGroup 68 // at this point both nodes have discovered each other and we can now create an 69 // arbitrary number of streams from sender -> receiver. This will force libp2p 70 // to create multiple streams concurrently and attempt to reuse the single pairwise 71 // connection. If more than one connection is established while creating the conccurent 72 // streams this indicates a bug in the libp2p PeerBaseLimitConnsInbound limit. 73 defaultProtocolID := protocols.FlowProtocolID(sporkID) 74 expectedNumOfStreams := int64(50) 75 for i := int64(0); i < expectedNumOfStreams; i++ { 76 allStreamsCreated.Add(1) 77 go func() { 78 defer allStreamsCreated.Done() 79 require.NoError(t, sender.Host().Connect(ctx, receiver.Host().Peerstore().PeerInfo(receiver.ID()))) 80 _, err := sender.Host().NewStream(ctx, receiver.ID(), defaultProtocolID) 81 require.NoError(t, err) 82 }() 83 } 84 85 unittest.RequireReturnsBefore(t, allStreamsCreated.Wait, 2*time.Second, "could not create streams on time") 86 require.Len(t, receiver.Host().Network().ConnsToPeer(sender.ID()), 1) 87 actualNumOfStreams := p2putils.CountStream(sender.Host(), receiver.ID(), p2putils.Protocol(defaultProtocolID), p2putils.Direction(network.DirOutbound)) 88 require.Equal(t, 89 expectedNumOfStreams, 90 int64(actualNumOfStreams), 91 fmt.Sprintf("expected to create %d number of streams got %d", expectedNumOfStreams, actualNumOfStreams)) 92 } 93 94 type testPeerLimitConfig struct { 95 // nodeCount is the number of nodes in the test. 96 nodeCount int 97 98 // maxInboundPeerStream is the maximum number of inbound streams from a single peer to the receiver. 99 maxInboundPeerStream int 100 101 // maxInboundStreamProtocol is the maximum number of inbound streams at the receiver using a specific protocol; it accumulates all streams from all senders. 102 maxInboundStreamProtocol int 103 104 // maxInboundStreamPeerProtocol is the maximum number of inbound streams at the receiver from a single peer using a specific protocol. 105 maxInboundStreamPeerProtocol int 106 107 // maxInboundStreamTransient is the maximum number of inbound transient streams at the receiver; it accumulates all streams from all senders across all protocols. 108 // transient streams are those that are not associated fully with a peer and protocol. 109 maxInboundStreamTransient int 110 111 // maxInboundStreamSystem is the maximum number of inbound streams at the receiver; it accumulates all streams from all senders across all protocols. 112 maxInboundStreamSystem int 113 114 // unknownProtocol when set to true will cause senders to use an unknown protocol ID when creating streams. 115 unknownProtocol bool 116 } 117 118 // maxLimit returns the maximum limit across all limits. 119 func (t testPeerLimitConfig) maxLimit() int { 120 max := 0 121 if t.maxInboundPeerStream > max && t.maxInboundPeerStream != math.MaxInt { 122 max = t.maxInboundPeerStream 123 } 124 if t.maxInboundStreamProtocol > max && t.maxInboundStreamProtocol != math.MaxInt { 125 max = t.maxInboundStreamProtocol 126 } 127 if t.maxInboundStreamPeerProtocol > max && t.maxInboundStreamPeerProtocol != math.MaxInt { 128 max = t.maxInboundStreamPeerProtocol 129 } 130 if t.maxInboundStreamTransient > max && t.maxInboundStreamTransient != math.MaxInt { 131 max = t.maxInboundStreamTransient 132 } 133 if t.maxInboundStreamSystem > max && t.maxInboundStreamSystem != math.MaxInt { 134 max = t.maxInboundStreamSystem 135 } 136 return max 137 } 138 139 // baseCreateStreamInboundStreamResourceLimitConfig returns a testPeerLimitConfig with default values. 140 func baseCreateStreamInboundStreamResourceLimitConfig() *testPeerLimitConfig { 141 return &testPeerLimitConfig{ 142 nodeCount: 10, 143 maxInboundPeerStream: 100, 144 maxInboundStreamProtocol: 100, 145 maxInboundStreamPeerProtocol: 100, 146 maxInboundStreamTransient: 100, 147 maxInboundStreamSystem: 100, 148 } 149 } 150 151 func TestCreateStream_DefaultConfig(t *testing.T) { 152 testCreateStreamInboundStreamResourceLimits(t, baseCreateStreamInboundStreamResourceLimitConfig()) 153 } 154 155 func TestCreateStream_MinPeerLimit(t *testing.T) { 156 base := baseCreateStreamInboundStreamResourceLimitConfig() 157 base.maxInboundPeerStream = 1 158 testCreateStreamInboundStreamResourceLimits(t, base) 159 } 160 161 func TestCreateStream_MaxPeerLimit(t *testing.T) { 162 163 base := baseCreateStreamInboundStreamResourceLimitConfig() 164 base.maxInboundPeerStream = math.MaxInt 165 testCreateStreamInboundStreamResourceLimits(t, base) 166 } 167 168 func TestCreateStream_MinProtocolLimit(t *testing.T) { 169 // max inbound protocol is not preserved; can be partially due to count stream not counting inbound streams on a protocol 170 unittest.SkipUnless(t, unittest.TEST_TODO, "broken test") 171 base := baseCreateStreamInboundStreamResourceLimitConfig() 172 base.maxInboundStreamProtocol = 1 173 testCreateStreamInboundStreamResourceLimits(t, base) 174 } 175 176 func TestCreateStream_MaxProtocolLimit(t *testing.T) { 177 base := baseCreateStreamInboundStreamResourceLimitConfig() 178 base.maxInboundStreamProtocol = math.MaxInt 179 testCreateStreamInboundStreamResourceLimits(t, base) 180 } 181 182 func TestCreateStream_MinPeerProtocolLimit(t *testing.T) { 183 // max inbound stream peer protocol is not preserved; can be partially due to count stream not counting inbound streams on a protocol 184 unittest.SkipUnless(t, unittest.TEST_TODO, "broken test") 185 base := baseCreateStreamInboundStreamResourceLimitConfig() 186 base.maxInboundStreamPeerProtocol = 1 187 testCreateStreamInboundStreamResourceLimits(t, base) 188 } 189 190 func TestCreateStream_MaxPeerProtocolLimit(t *testing.T) { 191 base := baseCreateStreamInboundStreamResourceLimitConfig() 192 base.maxInboundStreamPeerProtocol = math.MaxInt 193 testCreateStreamInboundStreamResourceLimits(t, base) 194 } 195 196 func TestCreateStream_MinTransientLimit(t *testing.T) { 197 base := baseCreateStreamInboundStreamResourceLimitConfig() 198 base.maxInboundStreamTransient = 1 199 testCreateStreamInboundStreamResourceLimits(t, base) 200 } 201 202 func TestCreateStream_MaxTransientLimit(t *testing.T) { 203 base := baseCreateStreamInboundStreamResourceLimitConfig() 204 base.maxInboundStreamTransient = math.MaxInt 205 testCreateStreamInboundStreamResourceLimits(t, base) 206 } 207 208 func TestCreateStream_MinSystemLimit(t *testing.T) { 209 base := baseCreateStreamInboundStreamResourceLimitConfig() 210 base.maxInboundStreamSystem = 1 211 testCreateStreamInboundStreamResourceLimits(t, base) 212 } 213 214 func TestCreateStream_MaxSystemLimit(t *testing.T) { 215 // max inbound stream protocol is not preserved; can be partially due to count stream not counting inbound streams on a protocol 216 unittest.SkipUnless(t, unittest.TEST_TODO, "broken test") 217 base := baseCreateStreamInboundStreamResourceLimitConfig() 218 base.maxInboundStreamSystem = math.MaxInt 219 testCreateStreamInboundStreamResourceLimits(t, base) 220 } 221 222 func TestCreateStream_DefaultConfigWithUnknownProtocol(t *testing.T) { 223 // limits are not enforced when using an unknown protocol ID 224 unittest.SkipUnless(t, unittest.TEST_TODO, "broken test") 225 base := baseCreateStreamInboundStreamResourceLimitConfig() 226 base.unknownProtocol = true 227 testCreateStreamInboundStreamResourceLimits(t, base) 228 } 229 230 func TestCreateStream_PeerLimitLessThanPeerProtocolLimit(t *testing.T) { 231 // the case where peer-level limit is lower than the peer-protocol-level limit. 232 base := baseCreateStreamInboundStreamResourceLimitConfig() 233 base.maxInboundPeerStream = 5 // each peer can only create 5 streams. 234 base.maxInboundStreamPeerProtocol = 10 // each peer can create 10 streams on a specific protocol (but should still be limited by the peer-level limit). 235 testCreateStreamInboundStreamResourceLimits(t, base) 236 } 237 238 func TestCreateStream_PeerLimitGreaterThanPeerProtocolLimit(t *testing.T) { 239 // the case where peer-level limit is higher than the peer-protocol-level limit. 240 // max inbound stream peer protocol is not preserved; can be partially due to count stream not counting inbound streams on a protocol 241 unittest.SkipUnless(t, unittest.TEST_TODO, "broken test") 242 base := baseCreateStreamInboundStreamResourceLimitConfig() 243 base.maxInboundPeerStream = 10 // each peer can create 10 streams. 244 base.maxInboundStreamPeerProtocol = 5 // each peer can create 5 streams on a specific protocol. 245 base.maxInboundStreamProtocol = 100 // overall limit is 100 streams on a specific protocol (across all peers). 246 base.maxInboundStreamTransient = 1000 // overall limit is 1000 transient streams. 247 base.maxInboundStreamSystem = 1000 // overall limit is 1000 system-wide streams. 248 testCreateStreamInboundStreamResourceLimits(t, base) 249 } 250 251 func TestCreateStream_ProtocolLimitLessThanPeerProtocolLimit(t *testing.T) { 252 // max inbound stream peer protocol is not preserved; can be partially due to count stream not counting inbound streams on a protocol 253 unittest.SkipUnless(t, 254 unittest.TEST_TODO, "broken test") 255 // the case where protocol-level limit is lower than the peer-protocol-level limit. 256 base := baseCreateStreamInboundStreamResourceLimitConfig() 257 base.maxInboundStreamProtocol = 5 // each peer can create 5 streams on a specific protocol. 258 base.maxInboundStreamPeerProtocol = 10 // each peer can create 10 streams on a specific protocol (but should still be limited by the protocol-level limit). 259 testCreateStreamInboundStreamResourceLimits(t, base) 260 } 261 262 func TestCreateStream_ProtocolLimitGreaterThanPeerProtocolLimit(t *testing.T) { 263 // TODO: with libp2p upgrade to v0.32.2; this test is failing as the peer protocol limit is not being enforced, and 264 // rather the protocol limit is being enforced, this test expects each peer not to be allowed more than 5 streams on a specific protocol. 265 // However, the maximum number of streams on a specific protocol (and specific peer) are being enforced instead. 266 // A quick investigation shows that it may be due to the way libp2p treats our unicast protocol (it is not a limit-enforcing protocol). 267 // But further investigation is required to confirm this. 268 unittest.SkipUnless(t, unittest.TEST_TODO, "broken test") 269 // the case where protocol-level limit is higher than the peer-protocol-level limit. 270 base := baseCreateStreamInboundStreamResourceLimitConfig() 271 base.maxInboundStreamProtocol = 10 // overall limit is 10 streams on a specific protocol (across all peers). 272 base.maxInboundStreamPeerProtocol = 5 // each peer can create 5 streams on a specific protocol. 273 base.maxInboundStreamTransient = 1000 // overall limit is 1000 transient streams. 274 base.maxInboundStreamSystem = 1000 // overall limit is 1000 system-wide streams. 275 testCreateStreamInboundStreamResourceLimits(t, base) 276 } 277 278 func TestCreateStream_TransientLimitLessThanPeerProtocolLimit(t *testing.T) { 279 // the case where transient-level limit is lower than the peer-protocol-level limit. 280 base := baseCreateStreamInboundStreamResourceLimitConfig() 281 base.maxInboundStreamTransient = 5 // overall limit is 5 transient streams (across all peers). 282 base.maxInboundStreamPeerProtocol = 10 // each peer can create 10 streams on a specific protocol (but should still be limited by the transient-level limit). 283 testCreateStreamInboundStreamResourceLimits(t, base) 284 } 285 286 // testCreateStreamInboundStreamResourceLimits tests the inbound stream limits for a given testPeerLimitConfig. It creates 287 // a number of senders and a single receiver. The receiver will have a resource manager with the given limits. 288 // The senders will have a resource manager with infinite limits to ensure that they can create as many streams as they want. 289 // The test will create a number of streams from each sender to the receiver. The test will then check that the limits are 290 // being enforced correctly. 291 // The number of streams is determined by the maxLimit() of the testPeerLimitConfig, which is the maximum limit across all limits (peer, protocol, transient, system), excluding 292 // the math.MaxInt limits. 293 func testCreateStreamInboundStreamResourceLimits(t *testing.T, cfg *testPeerLimitConfig) { 294 idProvider := mockmodule.NewIdentityProvider(t) 295 ctx, cancel := context.WithCancel(context.Background()) 296 defer cancel() 297 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 298 sporkID := unittest.IdentifierFixture() 299 300 flowCfg, err := config.DefaultConfig() 301 require.NoError(t, err) 302 flowCfg.NetworkConfig.Unicast.UnicastManager.CreateStreamBackoffDelay = 10 * time.Millisecond 303 304 // sender nodes will have infinite stream limit to ensure that they can create as many streams as they want. 305 resourceManagerSnd, err := rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(rcmgr.InfiniteLimits)) 306 require.NoError(t, err) 307 senders, senderIds := p2ptest.NodesFixture(t, 308 sporkID, 309 t.Name(), cfg.nodeCount, 310 idProvider, 311 p2ptest.WithResourceManager(resourceManagerSnd), 312 p2ptest.OverrideFlowConfig(flowCfg)) 313 314 // receiver node will run with default limits and no scaling. 315 limits := rcmgr.DefaultLimits 316 libp2p.SetDefaultServiceLimits(&limits) 317 l := limits.Scale(0, 0) 318 partial := rcmgr.PartialLimitConfig{ 319 System: rcmgr.ResourceLimits{ 320 StreamsInbound: rcmgr.LimitVal(cfg.maxInboundStreamSystem), 321 ConnsInbound: rcmgr.LimitVal(cfg.nodeCount), 322 }, 323 Transient: rcmgr.ResourceLimits{ 324 ConnsInbound: rcmgr.LimitVal(cfg.nodeCount), 325 StreamsInbound: rcmgr.LimitVal(cfg.maxInboundStreamTransient), 326 }, 327 ProtocolDefault: rcmgr.ResourceLimits{ 328 StreamsInbound: rcmgr.LimitVal(cfg.maxInboundStreamProtocol), 329 }, 330 ProtocolPeerDefault: rcmgr.ResourceLimits{ 331 StreamsInbound: rcmgr.LimitVal(cfg.maxInboundStreamPeerProtocol), 332 }, 333 PeerDefault: rcmgr.ResourceLimits{ 334 StreamsInbound: rcmgr.LimitVal(cfg.maxInboundPeerStream), 335 }, 336 Conn: rcmgr.ResourceLimits{ 337 StreamsInbound: rcmgr.LimitVal(cfg.maxInboundPeerStream), 338 }, 339 Stream: rcmgr.ResourceLimits{ 340 StreamsInbound: rcmgr.LimitVal(cfg.maxInboundPeerStream), 341 }, 342 } 343 l = partial.Build(l) 344 resourceManagerRcv, err := rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(l)) 345 require.NoError(t, err) 346 receiver, id2 := p2ptest.NodeFixture(t, 347 sporkID, 348 t.Name(), 349 idProvider, 350 p2ptest.WithResourceManager(resourceManagerRcv), 351 p2ptest.OverrideFlowConfig(flowCfg)) 352 353 for i, sender := range senders { 354 idProvider.On("ByPeerID", sender.ID()).Return(senderIds[i], true).Maybe() 355 } 356 idProvider.On("ByPeerID", receiver.ID()).Return(&id2, true).Maybe() 357 358 nodes := append(senders, receiver) 359 ids := append(senderIds, &id2) 360 361 p2ptest.StartNodes(t, signalerCtx, nodes) 362 defer p2ptest.StopNodes(t, nodes, cancel) 363 364 p2ptest.LetNodesDiscoverEachOther(t, signalerCtx, nodes, ids) 365 366 var allStreamsCreated sync.WaitGroup 367 368 protocolID := protocols.FlowProtocolID(sporkID) 369 if cfg.unknownProtocol { 370 protocolID = protocols.FlowProtocolID(unittest.IdentifierFixture()) 371 } 372 373 loadLimit := cfg.maxLimit() 374 require.Greaterf(t, loadLimit, 0, "test limit must be greater than 0; got %d", loadLimit) 375 376 streamListMu := sync.Mutex{} // mutex to protect the streamsList. 377 streamsList := make([]network.Stream, 0) // list of all streams created to avoid garbage collection. 378 for sIndex := range senders { 379 for i := 0; i < loadLimit; i++ { 380 allStreamsCreated.Add(1) 381 go func(sIndex int) { 382 defer allStreamsCreated.Done() 383 sender := senders[sIndex] 384 s, err := sender.Host().NewStream(ctx, receiver.ID(), protocolID) 385 if err != nil { 386 // we don't care about the error here; as we are trying to break a limit; so we expect some of the stream creations to fail. 387 return 388 } 389 390 require.NotNil(t, s) 391 streamListMu.Lock() 392 streamsList = append(streamsList, s) 393 streamListMu.Unlock() 394 }(sIndex) 395 } 396 } 397 398 unittest.RequireReturnsBefore(t, allStreamsCreated.Wait, 2*time.Second, "could not create streams on time") 399 400 // transient sanity-check 401 require.NoError(t, resourceManagerRcv.ViewTransient(func(scope network.ResourceScope) error { 402 // number of in-transient streams must be less than or equal to the max transient limit 403 require.LessOrEqual(t, int64(scope.Stat().NumStreamsInbound), int64(cfg.maxInboundStreamTransient)) 404 405 // number of in-transient streams must be less than or equal the total number of streams created. 406 require.LessOrEqual(t, int64(scope.Stat().NumStreamsInbound), int64(len(streamsList))) 407 return nil 408 })) 409 410 // system-wide limit sanity-check 411 require.NoError(t, resourceManagerRcv.ViewSystem(func(scope network.ResourceScope) error { 412 require.LessOrEqual(t, int64(scope.Stat().NumStreamsInbound), int64(cfg.maxInboundStreamSystem), "system-wide limit is not being enforced") 413 return nil 414 })) 415 416 totalInboundStreams := 0 417 for _, sender := range senders { 418 actualNumOfStreams := p2putils.CountStream(receiver.Host(), sender.ID(), p2putils.Direction(network.DirInbound)) 419 // number of inbound streams must be less than or equal to the peer-level limit for each sender. 420 require.LessOrEqual(t, int64(actualNumOfStreams), int64(cfg.maxInboundPeerStream)) 421 require.LessOrEqual(t, int64(actualNumOfStreams), int64(cfg.maxInboundStreamPeerProtocol)) 422 totalInboundStreams += actualNumOfStreams 423 } 424 // sanity check; the total number of inbound streams must be less than or equal to the system-wide limit. 425 // TODO: this must be a hard equal check; but falls short; to be shared with libp2p community. 426 // Failing at this line means the system-wide limit is not being enforced. 427 require.LessOrEqual(t, totalInboundStreams, cfg.maxInboundStreamSystem) 428 // sanity check; the total number of inbound streams must be less than or equal to the protocol-level limit. 429 require.LessOrEqual(t, totalInboundStreams, cfg.maxInboundStreamProtocol) 430 }