github.com/MetalBlockchain/metalgo@v1.11.9/network/throttling/inbound_msg_throttler.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  
     9  	"github.com/prometheus/client_golang/prometheus"
    10  
    11  	"github.com/MetalBlockchain/metalgo/ids"
    12  	"github.com/MetalBlockchain/metalgo/snow/networking/tracker"
    13  	"github.com/MetalBlockchain/metalgo/snow/validators"
    14  	"github.com/MetalBlockchain/metalgo/utils/logging"
    15  )
    16  
    17  var _ InboundMsgThrottler = (*inboundMsgThrottler)(nil)
    18  
    19  // InboundMsgThrottler rate-limits inbound messages from the network.
    20  type InboundMsgThrottler interface {
    21  	// Blocks until [nodeID] can read a message of size [msgSize].
    22  	// AddNode([nodeID], ...) must have been called since
    23  	// the last time RemoveNode([nodeID]) was called, if any.
    24  	// It's safe for multiple goroutines to concurrently call Acquire.
    25  	// Returns immediately if [ctx] is canceled.  The returned release function
    26  	// needs to be called so that any allocated resources will be released
    27  	// invariant: There should be a maximum of 1 blocking call to Acquire for a
    28  	//            given nodeID. Callers must enforce this invariant.
    29  	Acquire(ctx context.Context, msgSize uint64, nodeID ids.NodeID) ReleaseFunc
    30  
    31  	// Add a new node to this throttler.
    32  	// Must be called before Acquire(..., [nodeID]) is called.
    33  	// RemoveNode([nodeID]) must have been called since the last time
    34  	// AddNode([nodeID], ...) was called, if any.
    35  	AddNode(nodeID ids.NodeID)
    36  
    37  	// Remove a node from this throttler.
    38  	// AddNode([nodeID], ...) must have been called since
    39  	// the last time RemoveNode([nodeID]) was called, if any.
    40  	// Must be called when we stop reading messages from [nodeID].
    41  	// It's safe for multiple goroutines to concurrently call RemoveNode.
    42  	RemoveNode(nodeID ids.NodeID)
    43  }
    44  
    45  type InboundMsgThrottlerConfig struct {
    46  	MsgByteThrottlerConfig   `json:"byteThrottlerConfig"`
    47  	BandwidthThrottlerConfig `json:"bandwidthThrottlerConfig"`
    48  	CPUThrottlerConfig       SystemThrottlerConfig `json:"cpuThrottlerConfig"`
    49  	DiskThrottlerConfig      SystemThrottlerConfig `json:"diskThrottlerConfig"`
    50  	MaxProcessingMsgsPerNode uint64                `json:"maxProcessingMsgsPerNode"`
    51  }
    52  
    53  // Returns a new, sybil-safe inbound message throttler.
    54  func NewInboundMsgThrottler(
    55  	log logging.Logger,
    56  	registerer prometheus.Registerer,
    57  	vdrs validators.Manager,
    58  	throttlerConfig InboundMsgThrottlerConfig,
    59  	resourceTracker tracker.ResourceTracker,
    60  	cpuTargeter tracker.Targeter,
    61  	diskTargeter tracker.Targeter,
    62  ) (InboundMsgThrottler, error) {
    63  	byteThrottler, err := newInboundMsgByteThrottler(
    64  		log,
    65  		registerer,
    66  		vdrs,
    67  		throttlerConfig.MsgByteThrottlerConfig,
    68  	)
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  	bufferThrottler, err := newInboundMsgBufferThrottler(
    73  		registerer,
    74  		throttlerConfig.MaxProcessingMsgsPerNode,
    75  	)
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  	bandwidthThrottler, err := newBandwidthThrottler(
    80  		log,
    81  		registerer,
    82  		throttlerConfig.BandwidthThrottlerConfig,
    83  	)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	cpuThrottler, err := NewSystemThrottler(
    88  		"cpu",
    89  		registerer,
    90  		throttlerConfig.CPUThrottlerConfig,
    91  		resourceTracker.CPUTracker(),
    92  		cpuTargeter,
    93  	)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  	diskThrottler, err := NewSystemThrottler(
    98  		"disk",
    99  		registerer,
   100  		throttlerConfig.DiskThrottlerConfig,
   101  		resourceTracker.DiskTracker(),
   102  		diskTargeter,
   103  	)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  	return &inboundMsgThrottler{
   108  		byteThrottler:      byteThrottler,
   109  		bufferThrottler:    bufferThrottler,
   110  		bandwidthThrottler: bandwidthThrottler,
   111  		cpuThrottler:       cpuThrottler,
   112  		diskThrottler:      diskThrottler,
   113  	}, nil
   114  }
   115  
   116  // A sybil-safe inbound message throttler.
   117  // Rate-limits reading of inbound messages to prevent peers from consuming
   118  // excess resources.
   119  // The three resources considered are:
   120  //
   121  //  1. An inbound message buffer, where each message that we're currently
   122  //     processing takes up 1 unit of space on the buffer.
   123  //  2. An inbound message byte buffer, where a message of length n
   124  //     that we're currently processing takes up n units of space on the buffer.
   125  //  3. Bandwidth. The bandwidth rate-limiting is implemented using a token
   126  //     bucket, where each token is 1 byte. See BandwidthThrottler.
   127  //
   128  // A call to Acquire([msgSize], [nodeID]) blocks until we've secured
   129  // enough of both these resources to read a message of size [msgSize] from
   130  // [nodeID].
   131  type inboundMsgThrottler struct {
   132  	// Rate-limits based on number of messages from a given node that we're
   133  	// currently processing.
   134  	bufferThrottler *inboundMsgBufferThrottler
   135  	// Rate-limits based on recent bandwidth usage
   136  	bandwidthThrottler bandwidthThrottler
   137  	// Rate-limits based on size of all messages from a given
   138  	// node that we're currently processing.
   139  	byteThrottler *inboundMsgByteThrottler
   140  	// Rate-limits based on CPU usage caused by a given node.
   141  	cpuThrottler SystemThrottler
   142  	// Rate-limits based on disk usage caused by a given node.
   143  	diskThrottler SystemThrottler
   144  }
   145  
   146  // Returns when we can read a message of size [msgSize] from node [nodeID].
   147  // Release([msgSize], [nodeID]) must be called (!) when done with the message
   148  // or when we give up trying to read the message, if applicable.
   149  // Even if [ctx] is canceled, The returned release function
   150  // needs to be called so that any allocated resources will be released.
   151  func (t *inboundMsgThrottler) Acquire(ctx context.Context, msgSize uint64, nodeID ids.NodeID) ReleaseFunc {
   152  	// Acquire space on the inbound message buffer
   153  	bufferRelease := t.bufferThrottler.Acquire(ctx, nodeID)
   154  	// Acquire bandwidth
   155  	t.bandwidthThrottler.Acquire(ctx, msgSize, nodeID)
   156  	// Wait until our CPU usage drops to an acceptable level.
   157  	t.cpuThrottler.Acquire(ctx, nodeID)
   158  	// Wait until our disk usage drops to an acceptable level.
   159  	t.diskThrottler.Acquire(ctx, nodeID)
   160  	// Acquire space on the inbound message byte buffer
   161  	byteRelease := t.byteThrottler.Acquire(ctx, msgSize, nodeID)
   162  	return func() {
   163  		bufferRelease()
   164  		byteRelease()
   165  	}
   166  }
   167  
   168  // See BandwidthThrottler.
   169  func (t *inboundMsgThrottler) AddNode(nodeID ids.NodeID) {
   170  	t.bandwidthThrottler.AddNode(nodeID)
   171  }
   172  
   173  // See BandwidthThrottler.
   174  func (t *inboundMsgThrottler) RemoveNode(nodeID ids.NodeID) {
   175  	t.bandwidthThrottler.RemoveNode(nodeID)
   176  }