github.com/ava-labs/avalanchego@v1.11.11/network/throttling/outbound_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 "errors" 8 9 "github.com/prometheus/client_golang/prometheus" 10 "go.uber.org/zap" 11 12 "github.com/ava-labs/avalanchego/ids" 13 "github.com/ava-labs/avalanchego/message" 14 "github.com/ava-labs/avalanchego/snow/validators" 15 "github.com/ava-labs/avalanchego/utils/constants" 16 "github.com/ava-labs/avalanchego/utils/logging" 17 ) 18 19 var ( 20 _ OutboundMsgThrottler = (*outboundMsgThrottler)(nil) 21 _ OutboundMsgThrottler = (*noOutboundMsgThrottler)(nil) 22 ) 23 24 // Rate-limits outgoing messages 25 type OutboundMsgThrottler interface { 26 // Returns true if we can queue the message [msg] to be sent to node [nodeID]. 27 // Returns false if the message should be dropped (not sent to [nodeID]). 28 // If this method returns true, Release([msg], [nodeID]) must be called (!) when 29 // the message is sent (or when we give up trying to send the message, if applicable.) 30 // If this method returns false, do not make a corresponding call to Release. 31 Acquire(msg message.OutboundMessage, nodeID ids.NodeID) bool 32 33 // Mark that a message [msg] has been sent to [nodeID] or we have given up 34 // sending the message. Must correspond to a previous call to 35 // Acquire([msg], [nodeID]) that returned true. 36 Release(msg message.OutboundMessage, nodeID ids.NodeID) 37 } 38 39 type outboundMsgThrottler struct { 40 commonMsgThrottler 41 metrics outboundMsgThrottlerMetrics 42 } 43 44 func NewSybilOutboundMsgThrottler( 45 log logging.Logger, 46 registerer prometheus.Registerer, 47 vdrs validators.Manager, 48 config MsgByteThrottlerConfig, 49 ) (OutboundMsgThrottler, error) { 50 t := &outboundMsgThrottler{ 51 commonMsgThrottler: commonMsgThrottler{ 52 log: log, 53 vdrs: vdrs, 54 maxVdrBytes: config.VdrAllocSize, 55 remainingVdrBytes: config.VdrAllocSize, 56 remainingAtLargeBytes: config.AtLargeAllocSize, 57 nodeMaxAtLargeBytes: config.NodeMaxAtLargeBytes, 58 nodeToVdrBytesUsed: make(map[ids.NodeID]uint64), 59 nodeToAtLargeBytesUsed: make(map[ids.NodeID]uint64), 60 }, 61 } 62 return t, t.metrics.initialize(registerer) 63 } 64 65 func (t *outboundMsgThrottler) Acquire(msg message.OutboundMessage, nodeID ids.NodeID) bool { 66 // no need to acquire for this message 67 if msg.BypassThrottling() { 68 return true 69 } 70 71 t.lock.Lock() 72 defer t.lock.Unlock() 73 74 // Take as many bytes as we can from the at-large allocation. 75 bytesNeeded := uint64(len(msg.Bytes())) 76 atLargeBytesUsed := min( 77 // only give as many bytes as needed 78 bytesNeeded, 79 // don't exceed per-node limit 80 t.nodeMaxAtLargeBytes-t.nodeToAtLargeBytesUsed[nodeID], 81 // don't give more bytes than are in the allocation 82 t.remainingAtLargeBytes, 83 ) 84 bytesNeeded -= atLargeBytesUsed 85 86 // Take as many bytes as we can from [nodeID]'s validator allocation. 87 // Calculate [nodeID]'s validator allocation size based on its weight 88 vdrAllocationSize := uint64(0) 89 weight := t.vdrs.GetWeight(constants.PrimaryNetworkID, nodeID) 90 if weight != 0 { 91 totalWeight, err := t.vdrs.TotalWeight(constants.PrimaryNetworkID) 92 if err != nil { 93 t.log.Error("Failed to get total weight of primary network validators", 94 zap.Error(err), 95 ) 96 } else { 97 vdrAllocationSize = uint64(float64(t.maxVdrBytes) * float64(weight) / float64(totalWeight)) 98 } 99 } 100 vdrBytesAlreadyUsed := t.nodeToVdrBytesUsed[nodeID] 101 // [vdrBytesAllowed] is the number of bytes this node 102 // may take from its validator allocation. 103 vdrBytesAllowed := vdrAllocationSize 104 if vdrBytesAlreadyUsed >= vdrAllocationSize { 105 // We're already using all the bytes we can from the validator allocation 106 vdrBytesAllowed = 0 107 } else { 108 vdrBytesAllowed -= vdrBytesAlreadyUsed 109 } 110 vdrBytesUsed := min(t.remainingVdrBytes, bytesNeeded, vdrBytesAllowed) 111 bytesNeeded -= vdrBytesUsed 112 if bytesNeeded != 0 { 113 // Can't acquire enough bytes to queue this message to be sent 114 t.metrics.acquireFailures.Inc() 115 return false 116 } 117 // Can acquire enough bytes to queue this message to be sent. 118 // Update the state. 119 if atLargeBytesUsed > 0 { 120 t.remainingAtLargeBytes -= atLargeBytesUsed 121 t.nodeToAtLargeBytesUsed[nodeID] += atLargeBytesUsed 122 t.metrics.remainingAtLargeBytes.Set(float64(t.remainingAtLargeBytes)) 123 } 124 if vdrBytesUsed > 0 { 125 // Mark that [nodeID] used [vdrBytesUsed] from its validator allocation 126 t.remainingVdrBytes -= vdrBytesUsed 127 t.nodeToVdrBytesUsed[nodeID] += vdrBytesUsed 128 t.metrics.remainingVdrBytes.Set(float64(t.remainingVdrBytes)) 129 } 130 t.metrics.acquireSuccesses.Inc() 131 t.metrics.awaitingRelease.Inc() 132 return true 133 } 134 135 func (t *outboundMsgThrottler) Release(msg message.OutboundMessage, nodeID ids.NodeID) { 136 // no need to release for this message 137 if msg.BypassThrottling() { 138 return 139 } 140 141 t.lock.Lock() 142 defer func() { 143 t.metrics.remainingAtLargeBytes.Set(float64(t.remainingAtLargeBytes)) 144 t.metrics.remainingVdrBytes.Set(float64(t.remainingVdrBytes)) 145 t.metrics.awaitingRelease.Dec() 146 t.lock.Unlock() 147 }() 148 149 // [vdrBytesToReturn] is the number of bytes from [msgSize] 150 // that will be given back to [nodeID]'s validator allocation. 151 vdrBytesUsed := t.nodeToVdrBytesUsed[nodeID] 152 msgSize := uint64(len(msg.Bytes())) 153 vdrBytesToReturn := min(msgSize, vdrBytesUsed) 154 t.nodeToVdrBytesUsed[nodeID] -= vdrBytesToReturn 155 if t.nodeToVdrBytesUsed[nodeID] == 0 { 156 delete(t.nodeToVdrBytesUsed, nodeID) 157 } 158 t.remainingVdrBytes += vdrBytesToReturn 159 160 // [atLargeBytesToReturn] is the number of bytes from [msgSize] 161 // that will be given to the at-large allocation. 162 atLargeBytesToReturn := msgSize - vdrBytesToReturn 163 // Mark that [nodeID] has released these bytes. 164 t.remainingAtLargeBytes += atLargeBytesToReturn 165 t.nodeToAtLargeBytesUsed[nodeID] -= atLargeBytesToReturn 166 if t.nodeToAtLargeBytesUsed[nodeID] == 0 { 167 delete(t.nodeToAtLargeBytesUsed, nodeID) 168 } 169 } 170 171 type outboundMsgThrottlerMetrics struct { 172 acquireSuccesses prometheus.Counter 173 acquireFailures prometheus.Counter 174 remainingAtLargeBytes prometheus.Gauge 175 remainingVdrBytes prometheus.Gauge 176 awaitingRelease prometheus.Gauge 177 } 178 179 func (m *outboundMsgThrottlerMetrics) initialize(registerer prometheus.Registerer) error { 180 m.acquireSuccesses = prometheus.NewCounter(prometheus.CounterOpts{ 181 Name: "throttler_outbound_acquire_successes", 182 Help: "Outbound messages not dropped due to rate-limiting", 183 }) 184 m.acquireFailures = prometheus.NewCounter(prometheus.CounterOpts{ 185 Name: "throttler_outbound_acquire_failures", 186 Help: "Outbound messages dropped due to rate-limiting", 187 }) 188 m.remainingAtLargeBytes = prometheus.NewGauge(prometheus.GaugeOpts{ 189 Name: "throttler_outbound_remaining_at_large_bytes", 190 Help: "Bytes remaining in the at large byte allocation", 191 }) 192 m.remainingVdrBytes = prometheus.NewGauge(prometheus.GaugeOpts{ 193 Name: "throttler_outbound_remaining_validator_bytes", 194 Help: "Bytes remaining in the validator byte allocation", 195 }) 196 m.awaitingRelease = prometheus.NewGauge(prometheus.GaugeOpts{ 197 Name: "throttler_outbound_awaiting_release", 198 Help: "Number of messages waiting to be sent", 199 }) 200 return errors.Join( 201 registerer.Register(m.acquireSuccesses), 202 registerer.Register(m.acquireFailures), 203 registerer.Register(m.remainingAtLargeBytes), 204 registerer.Register(m.remainingVdrBytes), 205 registerer.Register(m.awaitingRelease), 206 ) 207 } 208 209 func NewNoOutboundThrottler() OutboundMsgThrottler { 210 return &noOutboundMsgThrottler{} 211 } 212 213 // [Acquire] always returns true. [Release] does nothing. 214 type noOutboundMsgThrottler struct{} 215 216 func (*noOutboundMsgThrottler) Acquire(message.OutboundMessage, ids.NodeID) bool { 217 return true 218 } 219 220 func (*noOutboundMsgThrottler) Release(message.OutboundMessage, ids.NodeID) {}