github.com/ava-labs/avalanchego@v1.11.11/network/throttling/inbound_msg_byte_throttler_test.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package throttling 5 6 import ( 7 "context" 8 "testing" 9 "time" 10 11 "github.com/prometheus/client_golang/prometheus" 12 "github.com/stretchr/testify/require" 13 14 "github.com/ava-labs/avalanchego/ids" 15 "github.com/ava-labs/avalanchego/snow/validators" 16 "github.com/ava-labs/avalanchego/utils/constants" 17 "github.com/ava-labs/avalanchego/utils/logging" 18 ) 19 20 func TestInboundMsgByteThrottlerCancelContextDeadlock(t *testing.T) { 21 require := require.New(t) 22 config := MsgByteThrottlerConfig{ 23 VdrAllocSize: 1, 24 AtLargeAllocSize: 1, 25 NodeMaxAtLargeBytes: 1, 26 } 27 vdrs := validators.NewManager() 28 vdr := ids.GenerateTestNodeID() 29 require.NoError(vdrs.AddStaker(constants.PrimaryNetworkID, vdr, nil, ids.Empty, 1)) 30 31 throttler, err := newInboundMsgByteThrottler( 32 logging.NoLog{}, 33 prometheus.NewRegistry(), 34 vdrs, 35 config, 36 ) 37 require.NoError(err) 38 39 ctx, cancel := context.WithCancel(context.Background()) 40 cancel() 41 42 nodeID := ids.GenerateTestNodeID() 43 release := throttler.Acquire(ctx, 2, nodeID) 44 release() 45 } 46 47 func TestInboundMsgByteThrottlerCancelContext(t *testing.T) { 48 require := require.New(t) 49 config := MsgByteThrottlerConfig{ 50 VdrAllocSize: 1024, 51 AtLargeAllocSize: 512, 52 NodeMaxAtLargeBytes: 1024, 53 } 54 vdrs := validators.NewManager() 55 vdr1ID := ids.GenerateTestNodeID() 56 vdr2ID := ids.GenerateTestNodeID() 57 require.NoError(vdrs.AddStaker(constants.PrimaryNetworkID, vdr1ID, nil, ids.Empty, 1)) 58 require.NoError(vdrs.AddStaker(constants.PrimaryNetworkID, vdr2ID, nil, ids.Empty, 1)) 59 60 throttler, err := newInboundMsgByteThrottler( 61 logging.NoLog{}, 62 prometheus.NewRegistry(), 63 vdrs, 64 config, 65 ) 66 require.NoError(err) 67 68 throttler.Acquire(context.Background(), config.VdrAllocSize, vdr1ID) 69 70 // Trying to take more bytes for node should block 71 vdr2Done := make(chan struct{}) 72 vdr2Context, vdr2ContextCancelFunction := context.WithCancel(context.Background()) 73 go func() { 74 throttler.Acquire(vdr2Context, config.VdrAllocSize, vdr2ID) 75 vdr2Done <- struct{}{} 76 }() 77 select { 78 case <-vdr2Done: 79 require.FailNow("should block on acquiring any more bytes") 80 case <-time.After(50 * time.Millisecond): 81 } 82 83 // ensure the throttler has recorded that vdr2 is waiting 84 throttler.lock.Lock() 85 require.Len(throttler.nodeToWaitingMsgID, 1) 86 require.Contains(throttler.nodeToWaitingMsgID, vdr2ID) 87 require.Equal(1, throttler.waitingToAcquire.Len()) 88 _, exists := throttler.waitingToAcquire.Get(throttler.nodeToWaitingMsgID[vdr2ID]) 89 require.True(exists) 90 throttler.lock.Unlock() 91 92 // cancel should cause vdr2's acquire to unblock 93 vdr2ContextCancelFunction() 94 95 select { 96 case <-vdr2Done: 97 case <-time.After(50 * time.Millisecond): 98 require.FailNow("channel should signal because ctx was cancelled") 99 } 100 101 require.NotContains(throttler.nodeToWaitingMsgID, vdr2ID) 102 } 103 104 func TestInboundMsgByteThrottler(t *testing.T) { 105 require := require.New(t) 106 config := MsgByteThrottlerConfig{ 107 VdrAllocSize: 1024, 108 AtLargeAllocSize: 1024, 109 NodeMaxAtLargeBytes: 1024, 110 } 111 vdrs := validators.NewManager() 112 vdr1ID := ids.GenerateTestNodeID() 113 vdr2ID := ids.GenerateTestNodeID() 114 require.NoError(vdrs.AddStaker(constants.PrimaryNetworkID, vdr1ID, nil, ids.Empty, 1)) 115 require.NoError(vdrs.AddStaker(constants.PrimaryNetworkID, vdr2ID, nil, ids.Empty, 1)) 116 117 throttler, err := newInboundMsgByteThrottler( 118 logging.NoLog{}, 119 prometheus.NewRegistry(), 120 vdrs, 121 config, 122 ) 123 require.NoError(err) 124 125 // Make sure NewSybilInboundMsgThrottler works 126 require.Equal(config.VdrAllocSize, throttler.maxVdrBytes) 127 require.Equal(config.VdrAllocSize, throttler.remainingVdrBytes) 128 require.Equal(config.AtLargeAllocSize, throttler.remainingAtLargeBytes) 129 require.NotNil(throttler.nodeToVdrBytesUsed) 130 require.NotNil(throttler.log) 131 require.NotNil(throttler.vdrs) 132 require.NotNil(throttler.metrics) 133 134 // Take from at-large allocation. 135 // Should return immediately. 136 throttler.Acquire(context.Background(), 1, vdr1ID) 137 require.Equal(config.AtLargeAllocSize-1, throttler.remainingAtLargeBytes) 138 require.Equal(config.VdrAllocSize, throttler.remainingVdrBytes) 139 require.Empty(throttler.nodeToVdrBytesUsed) 140 require.Len(throttler.nodeToAtLargeBytesUsed, 1) 141 require.Equal(uint64(1), throttler.nodeToAtLargeBytesUsed[vdr1ID]) 142 143 // Release the bytes 144 throttler.release(&msgMetadata{msgSize: 1}, vdr1ID) 145 require.Equal(config.AtLargeAllocSize, throttler.remainingAtLargeBytes) 146 require.Equal(config.VdrAllocSize, throttler.remainingVdrBytes) 147 require.Empty(throttler.nodeToVdrBytesUsed) 148 require.Empty(throttler.nodeToAtLargeBytesUsed) 149 150 // Use all the at-large allocation bytes and 1 of the validator allocation bytes 151 // Should return immediately. 152 throttler.Acquire(context.Background(), config.AtLargeAllocSize+1, vdr1ID) 153 // vdr1 at-large bytes used: 1024. Validator bytes used: 1 154 require.Zero(throttler.remainingAtLargeBytes) 155 require.Equal(config.VdrAllocSize-1, throttler.remainingVdrBytes) 156 require.Equal(uint64(1), throttler.nodeToVdrBytesUsed[vdr1ID]) 157 require.Len(throttler.nodeToVdrBytesUsed, 1) 158 require.Len(throttler.nodeToAtLargeBytesUsed, 1) 159 require.Equal(config.AtLargeAllocSize, throttler.nodeToAtLargeBytesUsed[vdr1ID]) 160 161 // The other validator should be able to acquire half the validator allocation. 162 // Should return immediately. 163 throttler.Acquire(context.Background(), config.AtLargeAllocSize/2, vdr2ID) 164 // vdr2 at-large bytes used: 0. Validator bytes used: 512 165 require.Equal(config.VdrAllocSize/2-1, throttler.remainingVdrBytes) 166 require.Equal(uint64(1), throttler.nodeToVdrBytesUsed[vdr1ID]) 167 require.Equal(config.VdrAllocSize/2, throttler.nodeToVdrBytesUsed[vdr2ID]) 168 require.Len(throttler.nodeToVdrBytesUsed, 2) 169 require.Len(throttler.nodeToAtLargeBytesUsed, 1) 170 require.Empty(throttler.nodeToWaitingMsgID) 171 require.Zero(throttler.waitingToAcquire.Len()) 172 173 // vdr1 should be able to acquire the rest of the validator allocation 174 // Should return immediately. 175 throttler.Acquire(context.Background(), config.VdrAllocSize/2-1, vdr1ID) 176 // vdr1 at-large bytes used: 1024. Validator bytes used: 512 177 require.Equal(config.VdrAllocSize/2, throttler.nodeToVdrBytesUsed[vdr1ID]) 178 require.Len(throttler.nodeToAtLargeBytesUsed, 1) 179 require.Equal(config.AtLargeAllocSize, throttler.nodeToAtLargeBytesUsed[vdr1ID]) 180 181 // Trying to take more bytes for either node should block 182 vdr1Done := make(chan struct{}) 183 go func() { 184 throttler.Acquire(context.Background(), 1, vdr1ID) 185 vdr1Done <- struct{}{} 186 }() 187 select { 188 case <-vdr1Done: 189 require.FailNow("should block on acquiring any more bytes") 190 case <-time.After(50 * time.Millisecond): 191 } 192 throttler.lock.Lock() 193 require.Len(throttler.nodeToWaitingMsgID, 1) 194 require.Contains(throttler.nodeToWaitingMsgID, vdr1ID) 195 require.Equal(1, throttler.waitingToAcquire.Len()) 196 _, exists := throttler.waitingToAcquire.Get(throttler.nodeToWaitingMsgID[vdr1ID]) 197 require.True(exists) 198 throttler.lock.Unlock() 199 200 vdr2Done := make(chan struct{}) 201 go func() { 202 throttler.Acquire(context.Background(), 1, vdr2ID) 203 vdr2Done <- struct{}{} 204 }() 205 select { 206 case <-vdr2Done: 207 require.FailNow("should block on acquiring any more bytes") 208 case <-time.After(50 * time.Millisecond): 209 } 210 throttler.lock.Lock() 211 require.Len(throttler.nodeToWaitingMsgID, 2) 212 213 require.Contains(throttler.nodeToWaitingMsgID, vdr2ID) 214 require.Equal(2, throttler.waitingToAcquire.Len()) 215 _, exists = throttler.waitingToAcquire.Get(throttler.nodeToWaitingMsgID[vdr2ID]) 216 require.True(exists) 217 throttler.lock.Unlock() 218 219 nonVdrID := ids.GenerateTestNodeID() 220 nonVdrDone := make(chan struct{}) 221 go func() { 222 throttler.Acquire(context.Background(), 1, nonVdrID) 223 nonVdrDone <- struct{}{} 224 }() 225 select { 226 case <-nonVdrDone: 227 require.FailNow("should block on acquiring any more bytes") 228 case <-time.After(50 * time.Millisecond): 229 } 230 throttler.lock.Lock() 231 require.Len(throttler.nodeToWaitingMsgID, 3) 232 require.Contains(throttler.nodeToWaitingMsgID, nonVdrID) 233 require.Equal(3, throttler.waitingToAcquire.Len()) 234 _, exists = throttler.waitingToAcquire.Get(throttler.nodeToWaitingMsgID[nonVdrID]) 235 require.True(exists) 236 throttler.lock.Unlock() 237 238 // Release config.MaxAtLargeBytes+1 bytes 239 // When the choice exists, bytes should be given back to the validator allocation 240 // rather than the at-large allocation. 241 throttler.release(&msgMetadata{msgSize: config.AtLargeAllocSize + 1}, vdr1ID) 242 243 // The Acquires that blocked above should have returned 244 <-vdr1Done 245 <-vdr2Done 246 <-nonVdrDone 247 248 require.Equal(config.NodeMaxAtLargeBytes/2, throttler.remainingVdrBytes) 249 require.Len(throttler.nodeToAtLargeBytesUsed, 3) // vdr1, vdr2, nonVdrID 250 require.Equal(config.AtLargeAllocSize/2, throttler.nodeToAtLargeBytesUsed[vdr1ID]) 251 require.Equal(uint64(1), throttler.nodeToAtLargeBytesUsed[vdr2ID]) 252 require.Equal(uint64(1), throttler.nodeToAtLargeBytesUsed[nonVdrID]) 253 require.Len(throttler.nodeToVdrBytesUsed, 1) 254 require.Zero(throttler.nodeToVdrBytesUsed[vdr1ID]) 255 require.Equal(config.AtLargeAllocSize/2-2, throttler.remainingAtLargeBytes) 256 require.Empty(throttler.nodeToWaitingMsgID) 257 require.Zero(throttler.waitingToAcquire.Len()) 258 259 // Non-validator should be able to take the rest of the at-large bytes 260 throttler.Acquire(context.Background(), config.AtLargeAllocSize/2-2, nonVdrID) 261 require.Zero(throttler.remainingAtLargeBytes) 262 require.Equal(config.AtLargeAllocSize/2-1, throttler.nodeToAtLargeBytesUsed[nonVdrID]) 263 require.Empty(throttler.nodeToWaitingMsgID) 264 require.Zero(throttler.waitingToAcquire.Len()) 265 266 // But should block on subsequent Acquires 267 go func() { 268 throttler.Acquire(context.Background(), 1, nonVdrID) 269 nonVdrDone <- struct{}{} 270 }() 271 select { 272 case <-nonVdrDone: 273 require.FailNow("should block on acquiring any more bytes") 274 case <-time.After(50 * time.Millisecond): 275 } 276 throttler.lock.Lock() 277 require.Contains(throttler.nodeToWaitingMsgID, nonVdrID) 278 require.Contains(throttler.nodeToWaitingMsgID, nonVdrID) 279 require.Equal(1, throttler.waitingToAcquire.Len()) 280 _, exists = throttler.waitingToAcquire.Get(throttler.nodeToWaitingMsgID[nonVdrID]) 281 require.True(exists) 282 throttler.lock.Unlock() 283 284 // Release all of vdr2's messages 285 throttler.release(&msgMetadata{msgSize: config.AtLargeAllocSize / 2}, vdr2ID) 286 throttler.release(&msgMetadata{msgSize: 1}, vdr2ID) 287 288 <-nonVdrDone 289 290 require.Zero(throttler.nodeToAtLargeBytesUsed[vdr2ID]) 291 require.Equal(config.VdrAllocSize, throttler.remainingVdrBytes) 292 require.Empty(throttler.nodeToVdrBytesUsed) 293 require.Zero(throttler.remainingAtLargeBytes) 294 require.NotContains(throttler.nodeToWaitingMsgID, nonVdrID) 295 require.Zero(throttler.waitingToAcquire.Len()) 296 297 // Release all of vdr1's messages 298 throttler.release(&msgMetadata{msgSize: 1}, vdr1ID) 299 throttler.release(&msgMetadata{msgSize: config.AtLargeAllocSize/2 - 1}, vdr1ID) 300 require.Empty(throttler.nodeToVdrBytesUsed) 301 require.Equal(config.VdrAllocSize, throttler.remainingVdrBytes) 302 require.Equal(config.AtLargeAllocSize/2, throttler.remainingAtLargeBytes) 303 require.Zero(throttler.nodeToAtLargeBytesUsed[vdr1ID]) 304 require.NotContains(throttler.nodeToWaitingMsgID, nonVdrID) 305 require.Zero(throttler.waitingToAcquire.Len()) 306 307 // Release nonVdr's messages 308 throttler.release(&msgMetadata{msgSize: 1}, nonVdrID) 309 throttler.release(&msgMetadata{msgSize: 1}, nonVdrID) 310 throttler.release(&msgMetadata{msgSize: config.AtLargeAllocSize/2 - 2}, nonVdrID) 311 require.Empty(throttler.nodeToVdrBytesUsed) 312 require.Equal(config.VdrAllocSize, throttler.remainingVdrBytes) 313 require.Equal(config.AtLargeAllocSize, throttler.remainingAtLargeBytes) 314 require.Empty(throttler.nodeToAtLargeBytesUsed) 315 require.Zero(throttler.nodeToAtLargeBytesUsed[nonVdrID]) 316 require.NotContains(throttler.nodeToWaitingMsgID, nonVdrID) 317 require.Zero(throttler.waitingToAcquire.Len()) 318 } 319 320 // Ensure that the limit on taking from the at-large allocation is enforced 321 func TestSybilMsgThrottlerMaxNonVdr(t *testing.T) { 322 require := require.New(t) 323 config := MsgByteThrottlerConfig{ 324 VdrAllocSize: 100, 325 AtLargeAllocSize: 100, 326 NodeMaxAtLargeBytes: 10, 327 } 328 vdrs := validators.NewManager() 329 vdr1ID := ids.GenerateTestNodeID() 330 require.NoError(vdrs.AddStaker(constants.PrimaryNetworkID, vdr1ID, nil, ids.Empty, 1)) 331 throttler, err := newInboundMsgByteThrottler( 332 logging.NoLog{}, 333 prometheus.NewRegistry(), 334 vdrs, 335 config, 336 ) 337 require.NoError(err) 338 nonVdrNodeID1 := ids.GenerateTestNodeID() 339 throttler.Acquire(context.Background(), config.NodeMaxAtLargeBytes, nonVdrNodeID1) 340 341 // Acquiring more should block 342 nonVdrDone := make(chan struct{}) 343 go func() { 344 throttler.Acquire(context.Background(), 1, nonVdrNodeID1) 345 nonVdrDone <- struct{}{} 346 }() 347 select { 348 case <-nonVdrDone: 349 require.FailNow("should block on acquiring any more bytes") 350 case <-time.After(50 * time.Millisecond): 351 } 352 353 // A different non-validator should be able to acquire 354 nonVdrNodeID2 := ids.GenerateTestNodeID() 355 throttler.Acquire(context.Background(), config.NodeMaxAtLargeBytes, nonVdrNodeID2) 356 357 // Validator should only be able to take [MaxAtLargeBytes] 358 throttler.Acquire(context.Background(), config.NodeMaxAtLargeBytes+1, vdr1ID) 359 require.Equal(config.NodeMaxAtLargeBytes, throttler.nodeToAtLargeBytesUsed[vdr1ID]) 360 require.Equal(uint64(1), throttler.nodeToVdrBytesUsed[vdr1ID]) 361 require.Equal(config.NodeMaxAtLargeBytes, throttler.nodeToAtLargeBytesUsed[nonVdrNodeID1]) 362 require.Equal(config.NodeMaxAtLargeBytes, throttler.nodeToAtLargeBytesUsed[nonVdrNodeID2]) 363 require.Equal(config.AtLargeAllocSize-config.NodeMaxAtLargeBytes*3, throttler.remainingAtLargeBytes) 364 } 365 366 // Test that messages waiting to be acquired by a given node execute next 367 func TestMsgThrottlerNextMsg(t *testing.T) { 368 require := require.New(t) 369 config := MsgByteThrottlerConfig{ 370 VdrAllocSize: 1024, 371 AtLargeAllocSize: 1024, 372 NodeMaxAtLargeBytes: 1024, 373 } 374 vdrs := validators.NewManager() 375 vdr1ID := ids.GenerateTestNodeID() 376 require.NoError(vdrs.AddStaker(constants.PrimaryNetworkID, vdr1ID, nil, ids.Empty, 1)) 377 nonVdrNodeID := ids.GenerateTestNodeID() 378 379 maxVdrBytes := config.VdrAllocSize + config.AtLargeAllocSize 380 maxBytes := maxVdrBytes 381 throttler, err := newInboundMsgByteThrottler( 382 logging.NoLog{}, 383 prometheus.NewRegistry(), 384 vdrs, 385 config, 386 ) 387 require.NoError(err) 388 389 // validator uses up all but 1 byte 390 throttler.Acquire(context.Background(), maxBytes-1, vdr1ID) 391 // validator uses the last byte 392 throttler.Acquire(context.Background(), 1, vdr1ID) 393 394 // validator wants to acquire a lot of bytes 395 doneVdr := make(chan struct{}) 396 go func() { 397 throttler.Acquire(context.Background(), maxBytes-1, vdr1ID) 398 doneVdr <- struct{}{} 399 }() 400 select { 401 case <-doneVdr: 402 require.FailNow("should block on acquiring any more bytes") 403 case <-time.After(50 * time.Millisecond): 404 } 405 406 // nonvalidator tries to acquire more bytes 407 done := make(chan struct{}) 408 go func() { 409 throttler.Acquire(context.Background(), 1, nonVdrNodeID) 410 done <- struct{}{} 411 }() 412 select { 413 case <-done: 414 require.FailNow("should block on acquiring any more bytes") 415 case <-time.After(50 * time.Millisecond): 416 } 417 418 // Release 1 byte 419 throttler.release(&msgMetadata{msgSize: 1}, vdr1ID) 420 421 // Byte should have gone toward next validator message 422 throttler.lock.Lock() 423 require.Equal(2, throttler.waitingToAcquire.Len()) 424 require.Contains(throttler.nodeToWaitingMsgID, vdr1ID) 425 firstMsgID := throttler.nodeToWaitingMsgID[vdr1ID] 426 firstMsg, exists := throttler.waitingToAcquire.Get(firstMsgID) 427 require.True(exists) 428 require.Equal(maxBytes-2, firstMsg.bytesNeeded) 429 throttler.lock.Unlock() 430 431 select { 432 case <-doneVdr: 433 require.FailNow("should still be blocking") 434 case <-time.After(50 * time.Millisecond): 435 } 436 437 // Release the rest of the bytes 438 throttler.release(&msgMetadata{msgSize: maxBytes - 1}, vdr1ID) 439 // next validator message should finish 440 <-doneVdr 441 throttler.release(&msgMetadata{msgSize: maxBytes - 1}, vdr1ID) 442 // next non validator message should finish 443 <-done 444 }