github.com/MetalBlockchain/subnet-evm@v0.4.9/plugin/evm/gossiper.go (about) 1 // (c) 2019-2021, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package evm 5 6 import ( 7 "math/big" 8 "sync" 9 "time" 10 11 "github.com/MetalBlockchain/metalgo/codec" 12 13 "github.com/MetalBlockchain/subnet-evm/peer" 14 15 "github.com/MetalBlockchain/metalgo/cache" 16 "github.com/MetalBlockchain/metalgo/ids" 17 "github.com/MetalBlockchain/metalgo/snow" 18 19 "github.com/ethereum/go-ethereum/common" 20 "github.com/ethereum/go-ethereum/log" 21 "github.com/ethereum/go-ethereum/rlp" 22 23 "github.com/MetalBlockchain/subnet-evm/core" 24 "github.com/MetalBlockchain/subnet-evm/core/state" 25 "github.com/MetalBlockchain/subnet-evm/core/types" 26 "github.com/MetalBlockchain/subnet-evm/plugin/evm/message" 27 ) 28 29 const ( 30 // We allow [recentCacheSize] to be fairly large because we only store hashes 31 // in the cache, not entire transactions. 32 recentCacheSize = 512 33 34 // [txsGossipInterval] is how often we attempt to gossip newly seen 35 // transactions to other nodes. 36 txsGossipInterval = 500 * time.Millisecond 37 ) 38 39 // Gossiper handles outgoing gossip of transactions 40 type Gossiper interface { 41 // GossipTxs sends AppGossip message containing the given [txs] 42 GossipTxs(txs []*types.Transaction) error 43 } 44 45 // pushGossiper is used to gossip transactions to the network 46 type pushGossiper struct { 47 ctx *snow.Context 48 gossipActivationTime time.Time 49 config Config 50 51 client peer.NetworkClient 52 blockchain *core.BlockChain 53 txPool *core.TxPool 54 55 // We attempt to batch transactions we need to gossip to avoid runaway 56 // amplification of mempol chatter. 57 txsToGossipChan chan []*types.Transaction 58 txsToGossip map[common.Hash]*types.Transaction 59 lastGossiped time.Time 60 shutdownChan chan struct{} 61 shutdownWg *sync.WaitGroup 62 63 // [recentTxs] prevent us from over-gossiping the 64 // same transaction in a short period of time. 65 recentTxs *cache.LRU[common.Hash, interface{}] 66 67 codec codec.Manager 68 signer types.Signer 69 stats GossipSentStats 70 } 71 72 // createGossiper constructs and returns a pushGossiper or noopGossiper 73 // based on whether vm.chainConfig.SubnetEVMTimestamp is set 74 func (vm *VM) createGossiper(stats GossipStats) Gossiper { 75 if vm.chainConfig.SubnetEVMTimestamp == nil { 76 return &noopGossiper{} 77 } 78 net := &pushGossiper{ 79 ctx: vm.ctx, 80 gossipActivationTime: time.Unix(vm.chainConfig.SubnetEVMTimestamp.Int64(), 0), 81 config: vm.config, 82 client: vm.client, 83 blockchain: vm.blockChain, 84 txPool: vm.txPool, 85 txsToGossipChan: make(chan []*types.Transaction), 86 txsToGossip: make(map[common.Hash]*types.Transaction), 87 shutdownChan: vm.shutdownChan, 88 shutdownWg: &vm.shutdownWg, 89 recentTxs: &cache.LRU[common.Hash, interface{}]{Size: recentCacheSize}, 90 codec: vm.networkCodec, 91 signer: types.LatestSigner(vm.blockChain.Config()), 92 stats: stats, 93 } 94 net.awaitEthTxGossip() 95 return net 96 } 97 98 // addrStatus used to track the metadata of addresses being queued for 99 // regossip. 100 type addrStatus struct { 101 nonce uint64 102 txsAdded int 103 } 104 105 // queueExecutableTxs attempts to select up to [maxTxs] from the tx pool for 106 // regossiping (with at most [maxAcctTxs] per account). 107 // 108 // We assume that [txs] contains an array of nonce-ordered transactions for a given 109 // account. This array of transactions can have gaps and start at a nonce lower 110 // than the current state of an account. 111 func (n *pushGossiper) queueExecutableTxs( 112 state *state.StateDB, 113 baseFee *big.Int, 114 txs map[common.Address]types.Transactions, 115 regossipFrequency Duration, 116 maxTxs int, 117 maxAcctTxs int, 118 ) types.Transactions { 119 var ( 120 stxs = types.NewTransactionsByPriceAndNonce(n.signer, txs, baseFee) 121 statuses = make(map[common.Address]*addrStatus) 122 queued = make([]*types.Transaction, 0, maxTxs) 123 ) 124 125 // Iterate over possible transactions until there are none left or we have 126 // hit the regossip target. 127 for len(queued) < maxTxs { 128 next := stxs.Peek() 129 if next == nil { 130 break 131 } 132 133 sender, _ := types.Sender(n.signer, next) 134 status, ok := statuses[sender] 135 if !ok { 136 status = &addrStatus{ 137 nonce: state.GetNonce(sender), 138 } 139 statuses[sender] = status 140 } 141 142 // The tx pool may be out of sync with current state, so we iterate 143 // through the account transactions until we get to one that is 144 // executable. 145 switch { 146 case next.Nonce() < status.nonce: 147 stxs.Shift() 148 continue 149 case next.Nonce() > status.nonce, time.Since(next.FirstSeen()) < regossipFrequency.Duration, 150 status.txsAdded >= maxAcctTxs: 151 stxs.Pop() 152 continue 153 } 154 queued = append(queued, next) 155 status.nonce++ 156 status.txsAdded++ 157 stxs.Shift() 158 } 159 return queued 160 } 161 162 // queueRegossipTxs finds the best non-priority transactions in the mempool and adds up to 163 // [RegossipMaxTxs] of them to [txsToGossip]. 164 func (n *pushGossiper) queueRegossipTxs() types.Transactions { 165 // Fetch all pending transactions 166 pending := n.txPool.Pending(true) 167 168 // Split the pending transactions into locals and remotes 169 localTxs := make(map[common.Address]types.Transactions) 170 remoteTxs := pending 171 for _, account := range n.txPool.Locals() { 172 if txs := remoteTxs[account]; len(txs) > 0 { 173 delete(remoteTxs, account) 174 localTxs[account] = txs 175 } 176 } 177 178 // Add best transactions to be gossiped (preferring local txs) 179 tip := n.blockchain.CurrentBlock() 180 state, err := n.blockchain.StateAt(tip.Root()) 181 if err != nil || state == nil { 182 log.Debug( 183 "could not get state at tip", 184 "tip", tip.Hash(), 185 "err", err, 186 ) 187 return nil 188 } 189 rgFrequency := n.config.RegossipFrequency 190 rgMaxTxs := n.config.RegossipMaxTxs 191 rgTxsPerAddr := n.config.RegossipTxsPerAddress 192 localQueued := n.queueExecutableTxs(state, tip.BaseFee(), localTxs, rgFrequency, rgMaxTxs, rgTxsPerAddr) 193 localCount := len(localQueued) 194 n.stats.IncEthTxsRegossipQueuedLocal(localCount) 195 if localCount >= rgMaxTxs { 196 n.stats.IncEthTxsRegossipQueued() 197 return localQueued 198 } 199 remoteQueued := n.queueExecutableTxs(state, tip.BaseFee(), remoteTxs, rgFrequency, rgMaxTxs-localCount, rgTxsPerAddr) 200 n.stats.IncEthTxsRegossipQueuedRemote(len(remoteQueued)) 201 if localCount+len(remoteQueued) > 0 { 202 // only increment the regossip stat when there are any txs queued 203 n.stats.IncEthTxsRegossipQueued() 204 } 205 return append(localQueued, remoteQueued...) 206 } 207 208 // queueRegossipTxs finds the best priority transactions in the mempool and adds up to 209 // [PriorityRegossipMaxTxs] of them to [txsToGossip]. 210 func (n *pushGossiper) queuePriorityRegossipTxs() types.Transactions { 211 // Fetch all pending transactions from the priority addresses 212 priorityTxs := n.txPool.PendingFrom(n.config.PriorityRegossipAddresses, true) 213 214 // Add best transactions to be gossiped 215 tip := n.blockchain.CurrentBlock() 216 state, err := n.blockchain.StateAt(tip.Root()) 217 if err != nil || state == nil { 218 log.Debug( 219 "could not get state at tip", 220 "tip", tip.Hash(), 221 "err", err, 222 ) 223 return nil 224 } 225 return n.queueExecutableTxs( 226 state, tip.BaseFee(), priorityTxs, 227 n.config.PriorityRegossipFrequency, 228 n.config.PriorityRegossipMaxTxs, 229 n.config.PriorityRegossipTxsPerAddress, 230 ) 231 } 232 233 // awaitEthTxGossip periodically gossips transactions that have been queued for 234 // gossip at least once every [txsGossipInterval]. 235 func (n *pushGossiper) awaitEthTxGossip() { 236 n.shutdownWg.Add(1) 237 go n.ctx.Log.RecoverAndPanic(func() { 238 defer n.shutdownWg.Done() 239 240 var ( 241 gossipTicker = time.NewTicker(txsGossipInterval) 242 regossipTicker = time.NewTicker(n.config.RegossipFrequency.Duration) 243 priorityRegossipTicker = time.NewTicker(n.config.PriorityRegossipFrequency.Duration) 244 ) 245 246 for { 247 select { 248 case <-gossipTicker.C: 249 if attempted, err := n.gossipTxs(false); err != nil { 250 log.Warn( 251 "failed to send eth transactions", 252 "len(txs)", attempted, 253 "err", err, 254 ) 255 } 256 case <-regossipTicker.C: 257 for _, tx := range n.queueRegossipTxs() { 258 n.txsToGossip[tx.Hash()] = tx 259 } 260 if attempted, err := n.gossipTxs(true); err != nil { 261 log.Warn( 262 "failed to regossip eth transactions", 263 "len(txs)", attempted, 264 "err", err, 265 ) 266 } 267 case <-priorityRegossipTicker.C: 268 for _, tx := range n.queuePriorityRegossipTxs() { 269 n.txsToGossip[tx.Hash()] = tx 270 } 271 if attempted, err := n.gossipTxs(true); err != nil { 272 log.Warn( 273 "failed to regossip priority eth transactions", 274 "len(txs)", attempted, 275 "err", err, 276 ) 277 } 278 case txs := <-n.txsToGossipChan: 279 for _, tx := range txs { 280 n.txsToGossip[tx.Hash()] = tx 281 } 282 if attempted, err := n.gossipTxs(false); err != nil { 283 log.Warn( 284 "failed to send eth transactions", 285 "len(txs)", attempted, 286 "err", err, 287 ) 288 } 289 case <-n.shutdownChan: 290 return 291 } 292 } 293 }) 294 } 295 296 func (n *pushGossiper) sendTxs(txs []*types.Transaction) error { 297 if len(txs) == 0 { 298 return nil 299 } 300 301 txBytes, err := rlp.EncodeToBytes(txs) 302 if err != nil { 303 return err 304 } 305 msg := message.TxsGossip{ 306 Txs: txBytes, 307 } 308 msgBytes, err := message.BuildGossipMessage(n.codec, msg) 309 if err != nil { 310 return err 311 } 312 log.Trace( 313 "gossiping eth txs", 314 "len(txs)", len(txs), 315 "size(txs)", len(msg.Txs), 316 ) 317 n.stats.IncEthTxsGossipSent() 318 return n.client.Gossip(msgBytes) 319 } 320 321 func (n *pushGossiper) gossipTxs(force bool) (int, error) { 322 if (!force && time.Since(n.lastGossiped) < txsGossipInterval) || len(n.txsToGossip) == 0 { 323 return 0, nil 324 } 325 n.lastGossiped = time.Now() 326 txs := make([]*types.Transaction, 0, len(n.txsToGossip)) 327 for _, tx := range n.txsToGossip { 328 txs = append(txs, tx) 329 delete(n.txsToGossip, tx.Hash()) 330 } 331 332 selectedTxs := make([]*types.Transaction, 0) 333 for _, tx := range txs { 334 txHash := tx.Hash() 335 txStatus := n.txPool.Status([]common.Hash{txHash})[0] 336 if txStatus != core.TxStatusPending { 337 continue 338 } 339 340 if n.config.RemoteGossipOnlyEnabled && n.txPool.HasLocal(txHash) { 341 continue 342 } 343 344 // We check [force] outside of the if statement to avoid an unnecessary 345 // cache lookup. 346 if !force { 347 if _, has := n.recentTxs.Get(txHash); has { 348 continue 349 } 350 } 351 n.recentTxs.Put(txHash, nil) 352 353 selectedTxs = append(selectedTxs, tx) 354 } 355 356 if len(selectedTxs) == 0 { 357 return 0, nil 358 } 359 360 // Attempt to gossip [selectedTxs] 361 msgTxs := make([]*types.Transaction, 0) 362 msgTxsSize := common.StorageSize(0) 363 for _, tx := range selectedTxs { 364 size := tx.Size() 365 if msgTxsSize+size > message.TxMsgSoftCapSize { 366 if err := n.sendTxs(msgTxs); err != nil { 367 return len(selectedTxs), err 368 } 369 msgTxs = msgTxs[:0] 370 msgTxsSize = 0 371 } 372 msgTxs = append(msgTxs, tx) 373 msgTxsSize += size 374 } 375 376 // Send any remaining [msgTxs] 377 return len(selectedTxs), n.sendTxs(msgTxs) 378 } 379 380 // GossipTxs enqueues the provided [txs] for gossiping. At some point, the 381 // [pushGossiper] will attempt to gossip the provided txs to other nodes 382 // (usually right away if not under load). 383 // 384 // NOTE: We never return a non-nil error from this function but retain the 385 // option to do so in case it becomes useful. 386 func (n *pushGossiper) GossipTxs(txs []*types.Transaction) error { 387 if time.Now().Before(n.gossipActivationTime) { 388 log.Trace( 389 "not gossiping eth txs before the gossiping activation time", 390 "len(txs)", len(txs), 391 ) 392 return nil 393 } 394 395 select { 396 case n.txsToGossipChan <- txs: 397 case <-n.shutdownChan: 398 } 399 return nil 400 } 401 402 // GossipHandler handles incoming gossip messages 403 type GossipHandler struct { 404 vm *VM 405 txPool *core.TxPool 406 stats GossipReceivedStats 407 } 408 409 func NewGossipHandler(vm *VM, stats GossipReceivedStats) *GossipHandler { 410 return &GossipHandler{ 411 vm: vm, 412 txPool: vm.txPool, 413 stats: stats, 414 } 415 } 416 417 func (h *GossipHandler) HandleTxs(nodeID ids.NodeID, msg message.TxsGossip) error { 418 log.Trace( 419 "AppGossip called with TxsGossip", 420 "peerID", nodeID, 421 "size(txs)", len(msg.Txs), 422 ) 423 424 if len(msg.Txs) == 0 { 425 log.Trace( 426 "AppGossip received empty TxsGossip Message", 427 "peerID", nodeID, 428 ) 429 return nil 430 } 431 432 // The maximum size of this encoded object is enforced by the codec. 433 txs := make([]*types.Transaction, 0) 434 if err := rlp.DecodeBytes(msg.Txs, &txs); err != nil { 435 log.Trace( 436 "AppGossip provided invalid txs", 437 "peerID", nodeID, 438 "err", err, 439 ) 440 return nil 441 } 442 h.stats.IncEthTxsGossipReceived() 443 errs := h.txPool.AddRemotes(txs) 444 for i, err := range errs { 445 if err != nil { 446 log.Trace( 447 "AppGossip failed to add to mempool", 448 "err", err, 449 "tx", txs[i].Hash(), 450 ) 451 if err == core.ErrAlreadyKnown { 452 h.stats.IncEthTxsGossipReceivedKnown() 453 } 454 continue 455 } 456 h.stats.IncEthTxsGossipReceivedNew() 457 } 458 return nil 459 } 460 461 // noopGossiper should be used when gossip communication is not supported 462 type noopGossiper struct{} 463 464 func (n *noopGossiper) GossipTxs([]*types.Transaction) error { 465 return nil 466 }