github.com/dim4egster/coreth@v0.10.2/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 "container/heap" 8 "math/big" 9 "sync" 10 "time" 11 12 "github.com/dim4egster/qmallgo/codec" 13 14 "github.com/dim4egster/coreth/peer" 15 16 "github.com/dim4egster/qmallgo/cache" 17 "github.com/dim4egster/qmallgo/ids" 18 "github.com/dim4egster/qmallgo/snow" 19 "github.com/dim4egster/qmallgo/utils/wrappers" 20 21 "github.com/ethereum/go-ethereum/common" 22 "github.com/ethereum/go-ethereum/log" 23 "github.com/ethereum/go-ethereum/rlp" 24 25 "github.com/dim4egster/coreth/core" 26 "github.com/dim4egster/coreth/core/state" 27 "github.com/dim4egster/coreth/core/types" 28 "github.com/dim4egster/coreth/plugin/evm/message" 29 ) 30 31 const ( 32 // We allow [recentCacheSize] to be fairly large because we only store hashes 33 // in the cache, not entire transactions. 34 recentCacheSize = 512 35 36 // [ethTxsGossipInterval] is how often we attempt to gossip newly seen 37 // transactions to other nodes. 38 ethTxsGossipInterval = 500 * time.Millisecond 39 ) 40 41 // Gossiper handles outgoing gossip of transactions 42 type Gossiper interface { 43 // GossipAtomicTxs sends AppGossip message containing the given [txs] 44 GossipAtomicTxs(txs []*Tx) error 45 // GossipEthTxs sends AppGossip message containing the given [txs] 46 GossipEthTxs(txs []*types.Transaction) error 47 } 48 49 // pushGossiper is used to gossip transactions to the network 50 type pushGossiper struct { 51 ctx *snow.Context 52 gossipActivationTime time.Time 53 config Config 54 55 client peer.NetworkClient 56 blockchain *core.BlockChain 57 txPool *core.TxPool 58 atomicMempool *Mempool 59 60 // We attempt to batch transactions we need to gossip to avoid runaway 61 // amplification of mempol chatter. 62 ethTxsToGossipChan chan []*types.Transaction 63 ethTxsToGossip map[common.Hash]*types.Transaction 64 lastGossiped time.Time 65 shutdownChan chan struct{} 66 shutdownWg *sync.WaitGroup 67 68 // [recentAtomicTxs] and [recentEthTxs] prevent us from over-gossiping the 69 // same transaction in a short period of time. 70 recentAtomicTxs *cache.LRU 71 recentEthTxs *cache.LRU 72 73 codec codec.Manager 74 stats GossipSentStats 75 } 76 77 // createGossiper constructs and returns a pushGossiper or noopGossiper 78 // based on whether vm.chainConfig.ApricotPhase4BlockTimestamp is set 79 func (vm *VM) createGossiper(stats GossipStats) Gossiper { 80 if vm.chainConfig.ApricotPhase4BlockTimestamp == nil { 81 return &noopGossiper{} 82 } 83 84 net := &pushGossiper{ 85 ctx: vm.ctx, 86 gossipActivationTime: time.Unix(vm.chainConfig.ApricotPhase4BlockTimestamp.Int64(), 0), 87 config: vm.config, 88 client: vm.client, 89 blockchain: vm.blockChain, 90 txPool: vm.txPool, 91 atomicMempool: vm.mempool, 92 ethTxsToGossipChan: make(chan []*types.Transaction), 93 ethTxsToGossip: make(map[common.Hash]*types.Transaction), 94 shutdownChan: vm.shutdownChan, 95 shutdownWg: &vm.shutdownWg, 96 recentAtomicTxs: &cache.LRU{Size: recentCacheSize}, 97 recentEthTxs: &cache.LRU{Size: recentCacheSize}, 98 codec: vm.networkCodec, 99 stats: stats, 100 } 101 net.awaitEthTxGossip() 102 return net 103 } 104 105 // queueExecutableTxs attempts to select up to [maxTxs] from the tx pool for 106 // regossiping. 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(state *state.StateDB, baseFee *big.Int, txs map[common.Address]types.Transactions, maxTxs int) types.Transactions { 112 // Setup heap for transactions 113 heads := make(types.TxByPriceAndTime, 0, len(txs)) 114 for addr, accountTxs := range txs { 115 // Short-circuit here to avoid performing an unnecessary state lookup 116 if len(accountTxs) == 0 { 117 continue 118 } 119 120 // Ensure any transactions regossiped are immediately executable 121 var ( 122 currentNonce = state.GetNonce(addr) 123 tx *types.Transaction 124 ) 125 for _, accountTx := range accountTxs { 126 // The tx pool may be out of sync with current state, so we iterate 127 // through the account transactions until we get to one that is 128 // executable. 129 if accountTx.Nonce() == currentNonce { 130 tx = accountTx 131 break 132 } 133 // There may be gaps in the tx pool and we could jump past the nonce we'd 134 // like to execute. 135 if accountTx.Nonce() > currentNonce { 136 break 137 } 138 } 139 if tx == nil { 140 continue 141 } 142 143 // Don't try to regossip a transaction too frequently 144 if time.Since(tx.FirstSeen()) < n.config.TxRegossipFrequency.Duration { 145 continue 146 } 147 148 // Ensure the fee the transaction pays is valid at tip 149 wrapped, err := types.NewTxWithMinerFee(tx, baseFee) 150 if err != nil { 151 log.Debug( 152 "not queuing tx for regossip", 153 "tx", tx.Hash(), 154 "err", err, 155 ) 156 continue 157 } 158 159 heads = append(heads, wrapped) 160 } 161 heap.Init(&heads) 162 163 // Add up to [maxTxs] transactions to be gossiped 164 queued := make([]*types.Transaction, 0, maxTxs) 165 for len(heads) > 0 && len(queued) < maxTxs { 166 tx := heads[0].Tx 167 queued = append(queued, tx) 168 heap.Pop(&heads) 169 } 170 171 return queued 172 } 173 174 // queueRegossipTxs finds the best transactions in the mempool and adds up to 175 // [TxRegossipMaxSize] of them to [ethTxsToGossip]. 176 func (n *pushGossiper) queueRegossipTxs() types.Transactions { 177 // Fetch all pending transactions 178 pending := n.txPool.Pending(true) 179 180 // Split the pending transactions into locals and remotes 181 localTxs := make(map[common.Address]types.Transactions) 182 remoteTxs := pending 183 for _, account := range n.txPool.Locals() { 184 if txs := remoteTxs[account]; len(txs) > 0 { 185 delete(remoteTxs, account) 186 localTxs[account] = txs 187 } 188 } 189 190 // Add best transactions to be gossiped (preferring local txs) 191 tip := n.blockchain.CurrentBlock() 192 state, err := n.blockchain.StateAt(tip.Root()) 193 if err != nil || state == nil { 194 log.Debug( 195 "could not get state at tip", 196 "tip", tip.Hash(), 197 "err", err, 198 ) 199 return nil 200 } 201 localQueued := n.queueExecutableTxs(state, tip.BaseFee(), localTxs, n.config.TxRegossipMaxSize) 202 localCount := len(localQueued) 203 n.stats.IncEthTxsRegossipQueuedLocal(localCount) 204 if localCount >= n.config.TxRegossipMaxSize { 205 n.stats.IncEthTxsRegossipQueued() 206 return localQueued 207 } 208 remoteQueued := n.queueExecutableTxs(state, tip.BaseFee(), remoteTxs, n.config.TxRegossipMaxSize-localCount) 209 n.stats.IncEthTxsRegossipQueuedRemote(len(remoteQueued)) 210 if localCount+len(remoteQueued) > 0 { 211 // only increment the regossip stat when there are any txs queued 212 n.stats.IncEthTxsRegossipQueued() 213 } 214 return append(localQueued, remoteQueued...) 215 } 216 217 // awaitEthTxGossip periodically gossips transactions that have been queued for 218 // gossip at least once every [ethTxsGossipInterval]. 219 func (n *pushGossiper) awaitEthTxGossip() { 220 n.shutdownWg.Add(1) 221 go n.ctx.Log.RecoverAndPanic(func() { 222 defer n.shutdownWg.Done() 223 224 var ( 225 gossipTicker = time.NewTicker(ethTxsGossipInterval) 226 regossipTicker = time.NewTicker(n.config.TxRegossipFrequency.Duration) 227 ) 228 229 for { 230 select { 231 case <-gossipTicker.C: 232 if attempted, err := n.gossipEthTxs(false); err != nil { 233 log.Warn( 234 "failed to send eth transactions", 235 "len(txs)", attempted, 236 "err", err, 237 ) 238 } 239 case <-regossipTicker.C: 240 for _, tx := range n.queueRegossipTxs() { 241 n.ethTxsToGossip[tx.Hash()] = tx 242 } 243 if attempted, err := n.gossipEthTxs(true); err != nil { 244 log.Warn( 245 "failed to send eth transactions", 246 "len(txs)", attempted, 247 "err", err, 248 ) 249 } 250 case txs := <-n.ethTxsToGossipChan: 251 for _, tx := range txs { 252 n.ethTxsToGossip[tx.Hash()] = tx 253 } 254 if attempted, err := n.gossipEthTxs(false); err != nil { 255 log.Warn( 256 "failed to send eth transactions", 257 "len(txs)", attempted, 258 "err", err, 259 ) 260 } 261 case <-n.shutdownChan: 262 return 263 } 264 } 265 }) 266 } 267 268 func (n *pushGossiper) GossipAtomicTxs(txs []*Tx) error { 269 if time.Now().Before(n.gossipActivationTime) { 270 log.Trace( 271 "not gossiping atomic tx before the gossiping activation time", 272 "txs", txs, 273 ) 274 return nil 275 } 276 277 errs := wrappers.Errs{} 278 for _, tx := range txs { 279 errs.Add(n.gossipAtomicTx(tx)) 280 } 281 return errs.Err 282 } 283 284 func (n *pushGossiper) gossipAtomicTx(tx *Tx) error { 285 txID := tx.ID() 286 // Don't gossip transaction if it has been recently gossiped. 287 if _, has := n.recentAtomicTxs.Get(txID); has { 288 return nil 289 } 290 // If the transaction is not pending according to the mempool 291 // then there is no need to gossip it further. 292 if _, pending := n.atomicMempool.GetPendingTx(txID); !pending { 293 return nil 294 } 295 n.recentAtomicTxs.Put(txID, nil) 296 297 msg := message.AtomicTxGossip{ 298 Tx: tx.SignedBytes(), 299 } 300 msgBytes, err := message.BuildGossipMessage(n.codec, msg) 301 if err != nil { 302 return err 303 } 304 305 log.Trace( 306 "gossiping atomic tx", 307 "txID", txID, 308 ) 309 n.stats.IncAtomicGossipSent() 310 return n.client.Gossip(msgBytes) 311 } 312 313 func (n *pushGossiper) sendEthTxs(txs []*types.Transaction) error { 314 if len(txs) == 0 { 315 return nil 316 } 317 318 txBytes, err := rlp.EncodeToBytes(txs) 319 if err != nil { 320 return err 321 } 322 msg := message.EthTxsGossip{ 323 Txs: txBytes, 324 } 325 msgBytes, err := message.BuildGossipMessage(n.codec, msg) 326 if err != nil { 327 return err 328 } 329 330 log.Trace( 331 "gossiping eth txs", 332 "len(txs)", len(txs), 333 "size(txs)", len(msg.Txs), 334 ) 335 n.stats.IncEthTxsGossipSent() 336 return n.client.Gossip(msgBytes) 337 } 338 339 func (n *pushGossiper) gossipEthTxs(force bool) (int, error) { 340 if (!force && time.Since(n.lastGossiped) < ethTxsGossipInterval) || len(n.ethTxsToGossip) == 0 { 341 return 0, nil 342 } 343 n.lastGossiped = time.Now() 344 txs := make([]*types.Transaction, 0, len(n.ethTxsToGossip)) 345 for _, tx := range n.ethTxsToGossip { 346 txs = append(txs, tx) 347 delete(n.ethTxsToGossip, tx.Hash()) 348 } 349 350 selectedTxs := make([]*types.Transaction, 0) 351 for _, tx := range txs { 352 txHash := tx.Hash() 353 txStatus := n.txPool.Status([]common.Hash{txHash})[0] 354 if txStatus != core.TxStatusPending { 355 continue 356 } 357 358 if n.config.RemoteTxGossipOnlyEnabled && n.txPool.HasLocal(txHash) { 359 continue 360 } 361 362 // We check [force] outside of the if statement to avoid an unnecessary 363 // cache lookup. 364 if !force { 365 if _, has := n.recentEthTxs.Get(txHash); has { 366 continue 367 } 368 } 369 n.recentEthTxs.Put(txHash, nil) 370 371 selectedTxs = append(selectedTxs, tx) 372 } 373 374 if len(selectedTxs) == 0 { 375 return 0, nil 376 } 377 378 // Attempt to gossip [selectedTxs] 379 msgTxs := make([]*types.Transaction, 0) 380 msgTxsSize := common.StorageSize(0) 381 for _, tx := range selectedTxs { 382 size := tx.Size() 383 if msgTxsSize+size > message.EthMsgSoftCapSize { 384 if err := n.sendEthTxs(msgTxs); err != nil { 385 return len(selectedTxs), err 386 } 387 msgTxs = msgTxs[:0] 388 msgTxsSize = 0 389 } 390 msgTxs = append(msgTxs, tx) 391 msgTxsSize += size 392 } 393 394 // Send any remaining [msgTxs] 395 return len(selectedTxs), n.sendEthTxs(msgTxs) 396 } 397 398 // GossipEthTxs enqueues the provided [txs] for gossiping. At some point, the 399 // [pushGossiper] will attempt to gossip the provided txs to other nodes 400 // (usually right away if not under load). 401 // 402 // NOTE: We never return a non-nil error from this function but retain the 403 // option to do so in case it becomes useful. 404 func (n *pushGossiper) GossipEthTxs(txs []*types.Transaction) error { 405 if time.Now().Before(n.gossipActivationTime) { 406 log.Trace( 407 "not gossiping eth txs before the gossiping activation time", 408 "len(txs)", len(txs), 409 ) 410 return nil 411 } 412 413 select { 414 case n.ethTxsToGossipChan <- txs: 415 case <-n.shutdownChan: 416 } 417 return nil 418 } 419 420 // GossipHandler handles incoming gossip messages 421 type GossipHandler struct { 422 vm *VM 423 atomicMempool *Mempool 424 txPool *core.TxPool 425 stats GossipReceivedStats 426 } 427 428 func NewGossipHandler(vm *VM, stats GossipReceivedStats) *GossipHandler { 429 return &GossipHandler{ 430 vm: vm, 431 atomicMempool: vm.mempool, 432 txPool: vm.txPool, 433 stats: stats, 434 } 435 } 436 437 func (h *GossipHandler) HandleAtomicTx(nodeID ids.NodeID, msg message.AtomicTxGossip) error { 438 log.Trace( 439 "AppGossip called with AtomicTxGossip", 440 "peerID", nodeID, 441 ) 442 443 if len(msg.Tx) == 0 { 444 log.Trace( 445 "AppGossip received empty AtomicTxGossip Message", 446 "peerID", nodeID, 447 ) 448 return nil 449 } 450 451 // In the case that the gossip message contains a transaction, 452 // attempt to parse it and add it as a remote. 453 tx := Tx{} 454 if _, err := Codec.Unmarshal(msg.Tx, &tx); err != nil { 455 log.Trace( 456 "AppGossip provided invalid tx", 457 "err", err, 458 ) 459 return nil 460 } 461 unsignedBytes, err := Codec.Marshal(codecVersion, &tx.UnsignedAtomicTx) 462 if err != nil { 463 log.Trace( 464 "AppGossip failed to marshal unsigned tx", 465 "err", err, 466 ) 467 return nil 468 } 469 tx.Initialize(unsignedBytes, msg.Tx) 470 471 txID := tx.ID() 472 h.stats.IncAtomicGossipReceived() 473 if _, dropped, found := h.atomicMempool.GetTx(txID); found { 474 h.stats.IncAtomicGossipReceivedKnown() 475 return nil 476 } else if dropped { 477 h.stats.IncAtomicGossipReceivedDropped() 478 return nil 479 } 480 481 h.stats.IncAtomicGossipReceivedNew() 482 if err := h.vm.issueTx(&tx, false /*=local*/); err != nil { 483 log.Trace( 484 "AppGossip provided invalid transaction", 485 "peerID", nodeID, 486 "err", err, 487 ) 488 } 489 490 return nil 491 } 492 493 func (h *GossipHandler) HandleEthTxs(nodeID ids.NodeID, msg message.EthTxsGossip) error { 494 log.Trace( 495 "AppGossip called with EthTxsGossip", 496 "peerID", nodeID, 497 "size(txs)", len(msg.Txs), 498 ) 499 500 if len(msg.Txs) == 0 { 501 log.Trace( 502 "AppGossip received empty EthTxsGossip Message", 503 "peerID", nodeID, 504 ) 505 return nil 506 } 507 508 // The maximum size of this encoded object is enforced by the codec. 509 txs := make([]*types.Transaction, 0) 510 if err := rlp.DecodeBytes(msg.Txs, &txs); err != nil { 511 log.Trace( 512 "AppGossip provided invalid txs", 513 "peerID", nodeID, 514 "err", err, 515 ) 516 return nil 517 } 518 h.stats.IncEthTxsGossipReceived() 519 errs := h.txPool.AddRemotes(txs) 520 for i, err := range errs { 521 if err != nil { 522 log.Trace( 523 "AppGossip failed to add to mempool", 524 "err", err, 525 "tx", txs[i].Hash(), 526 ) 527 if err == core.ErrAlreadyKnown { 528 h.stats.IncEthTxsGossipReceivedKnown() 529 } else { 530 h.stats.IncAtomicGossipReceivedError() 531 } 532 continue 533 } 534 h.stats.IncEthTxsGossipReceivedNew() 535 } 536 return nil 537 } 538 539 // noopGossiper should be used when gossip communication is not supported 540 type noopGossiper struct{} 541 542 func (n *noopGossiper) GossipAtomicTxs([]*Tx) error { 543 return nil 544 } 545 func (n *noopGossiper) GossipEthTxs([]*types.Transaction) error { 546 return nil 547 }