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  }