From 16810d4716cfab9133ece03881ddba59273d417b Mon Sep 17 00:00:00 2001 From: Alex Sharov Date: Thu, 15 Jun 2023 13:11:49 +0700 Subject: [PATCH] Enforce blockReader interface (#1023) --- state/aggregator_v3.go | 2 +- txpool/mocks_test.go | 7 +- txpool/pool.go | 271 +++++++--------------------------- txpool/pool_test.go | 42 +++--- txpool/txpool_grpc_server.go | 17 ++- txpool/txpoolcfg/txpoolcfg.go | 168 +++++++++++++++++++++ 6 files changed, 257 insertions(+), 250 deletions(-) diff --git a/state/aggregator_v3.go b/state/aggregator_v3.go index e0acfb998..d4b8cbc12 100644 --- a/state/aggregator_v3.go +++ b/state/aggregator_v3.go @@ -655,7 +655,7 @@ func (a *AggregatorV3) integrateFiles(sf AggV3StaticFiles, txNumFrom, txNumTo ui a.tracesTo.integrateFiles(sf.tracesTo, txNumFrom, txNumTo) } -func (a *AggregatorV3) NeedSaveFilesListInDB() bool { +func (a *AggregatorV3) HasNewFrozenFiles() bool { return a.needSaveFilesListInDB.CompareAndSwap(true, false) } diff --git a/txpool/mocks_test.go b/txpool/mocks_test.go index 5e8e970a1..4c23a167f 100644 --- a/txpool/mocks_test.go +++ b/txpool/mocks_test.go @@ -7,6 +7,7 @@ import ( "context" "github.com/ledgerwatch/erigon-lib/gointerfaces/remote" "github.com/ledgerwatch/erigon-lib/kv" + "github.com/ledgerwatch/erigon-lib/txpool/txpoolcfg" types2 "github.com/ledgerwatch/erigon-lib/types" "sync" ) @@ -53,7 +54,7 @@ var _ Pool = &PoolMock{} // } type PoolMock struct { // AddLocalTxsFunc mocks the AddLocalTxs method. - AddLocalTxsFunc func(ctx context.Context, newTxs types2.TxSlots, tx kv.Tx) ([]DiscardReason, error) + AddLocalTxsFunc func(ctx context.Context, newTxs types2.TxSlots, tx kv.Tx) ([]txpoolcfg.DiscardReason, error) // AddNewGoodPeerFunc mocks the AddNewGoodPeer method. AddNewGoodPeerFunc func(peerID types2.PeerID) @@ -146,7 +147,7 @@ type PoolMock struct { } // AddLocalTxs calls AddLocalTxsFunc. -func (mock *PoolMock) AddLocalTxs(ctx context.Context, newTxs types2.TxSlots, tx kv.Tx) ([]DiscardReason, error) { +func (mock *PoolMock) AddLocalTxs(ctx context.Context, newTxs types2.TxSlots, tx kv.Tx) ([]txpoolcfg.DiscardReason, error) { callInfo := struct { Ctx context.Context NewTxs types2.TxSlots @@ -161,7 +162,7 @@ func (mock *PoolMock) AddLocalTxs(ctx context.Context, newTxs types2.TxSlots, tx mock.lockAddLocalTxs.Unlock() if mock.AddLocalTxsFunc == nil { var ( - discardReasonsOut []DiscardReason + discardReasonsOut []txpoolcfg.DiscardReason errOut error ) return discardReasonsOut, errOut diff --git a/txpool/pool.go b/txpool/pool.go index f8efd4550..082e15d07 100644 --- a/txpool/pool.go +++ b/txpool/pool.go @@ -47,7 +47,6 @@ import ( "github.com/ledgerwatch/erigon-lib/common/cmp" "github.com/ledgerwatch/erigon-lib/common/dbg" "github.com/ledgerwatch/erigon-lib/common/fixedgas" - emath "github.com/ledgerwatch/erigon-lib/common/math" "github.com/ledgerwatch/erigon-lib/common/u256" "github.com/ledgerwatch/erigon-lib/gointerfaces" "github.com/ledgerwatch/erigon-lib/gointerfaces/grpcutil" @@ -80,7 +79,7 @@ type Pool interface { // Handle 3 main events - new remote txs from p2p, new local txs from RPC, new blocks from execution layer AddRemoteTxs(ctx context.Context, newTxs types.TxSlots) - AddLocalTxs(ctx context.Context, newTxs types.TxSlots, tx kv.Tx) ([]DiscardReason, error) + AddLocalTxs(ctx context.Context, newTxs types.TxSlots, tx kv.Tx) ([]txpoolcfg.DiscardReason, error) OnNewBlock(ctx context.Context, stateChanges *remote.StateChangeBatch, unwindTxs, minedTxs types.TxSlots, tx kv.Tx) error // IdHashKnown check whether transaction with given Id hash is known to the pool @@ -113,87 +112,6 @@ const ( QueuedPoolBits = EnoughFeeCapProtocol ) -type DiscardReason uint8 - -const ( - NotSet DiscardReason = 0 // analog of "nil-value", means it will be set in future - Success DiscardReason = 1 - AlreadyKnown DiscardReason = 2 - Mined DiscardReason = 3 - ReplacedByHigherTip DiscardReason = 4 - UnderPriced DiscardReason = 5 - ReplaceUnderpriced DiscardReason = 6 // if a transaction is attempted to be replaced with a different one without the required price bump. - FeeTooLow DiscardReason = 7 - OversizedData DiscardReason = 8 - InvalidSender DiscardReason = 9 - NegativeValue DiscardReason = 10 // ensure no one is able to specify a transaction with a negative value. - Spammer DiscardReason = 11 - PendingPoolOverflow DiscardReason = 12 - BaseFeePoolOverflow DiscardReason = 13 - QueuedPoolOverflow DiscardReason = 14 - GasUintOverflow DiscardReason = 15 - IntrinsicGas DiscardReason = 16 - RLPTooLong DiscardReason = 17 - NonceTooLow DiscardReason = 18 - InsufficientFunds DiscardReason = 19 - NotReplaced DiscardReason = 20 // There was an existing transaction with the same sender and nonce, not enough price bump to replace - DuplicateHash DiscardReason = 21 // There was an existing transaction with the same hash - InitCodeTooLarge DiscardReason = 22 // EIP-3860 - transaction init code is too large -) - -func (r DiscardReason) String() string { - switch r { - case NotSet: - return "not set" - case Success: - return "success" - case AlreadyKnown: - return "already known" - case Mined: - return "mined" - case ReplacedByHigherTip: - return "replaced by transaction with higher tip" - case UnderPriced: - return "underpriced" - case ReplaceUnderpriced: - return "replacement transaction underpriced" - case FeeTooLow: - return "fee too low" - case OversizedData: - return "oversized data" - case InvalidSender: - return "invalid sender" - case NegativeValue: - return "negative value" - case Spammer: - return "spammer" - case PendingPoolOverflow: - return "pending sub-pool is full" - case BaseFeePoolOverflow: - return "baseFee sub-pool is full" - case QueuedPoolOverflow: - return "queued sub-pool is full" - case GasUintOverflow: - return "GasUintOverflow" - case IntrinsicGas: - return "IntrinsicGas" - case RLPTooLong: - return "RLPTooLong" - case NonceTooLow: - return "nonce too low" - case InsufficientFunds: - return "insufficient funds" - case NotReplaced: - return "could not replace existing tx" - case DuplicateHash: - return "existing tx with same hash" - case InitCodeTooLarge: - return "initcode too large" - default: - panic(fmt.Sprintf("discard reason: %d", r)) - } -} - // metaTx holds transaction and some metadata type metaTx struct { Tx *types.TxSlot @@ -278,9 +196,9 @@ type TxPool struct { // - batch notifications about new txs (reduce P2P spam to other nodes about txs propagation) // - and as a result reducing lock contention unprocessedRemoteTxs *types.TxSlots - unprocessedRemoteByHash map[string]int // to reject duplicates - byHash map[string]*metaTx // tx_hash => tx : only not committed to db yet records - discardReasonsLRU *simplelru.LRU[string, DiscardReason] // tx_hash => discard_reason : non-persisted + unprocessedRemoteByHash map[string]int // to reject duplicates + byHash map[string]*metaTx // tx_hash => tx : only not committed to db yet records + discardReasonsLRU *simplelru.LRU[string, txpoolcfg.DiscardReason] // tx_hash => discard_reason : non-persisted pending *PendingPool baseFee *SubPool queued *SubPool @@ -306,7 +224,7 @@ func New(newTxs chan types.Announcements, coreDB kv.RoDB, cfg txpoolcfg.Config, if err != nil { return nil, err } - discardHistory, err := simplelru.NewLRU[string, DiscardReason](10_000, nil) + discardHistory, err := simplelru.NewLRU[string, txpoolcfg.DiscardReason](10_000, nil) if err != nil { return nil, err } @@ -637,7 +555,7 @@ func (p *TxPool) best(n uint16, txs *types.TxsRlp, tx kv.Tx, onTopOf, availableG // make sure we have enough gas in the caller to add this transaction. // not an exact science using intrinsic gas but as close as we could hope for at // this stage - intrinsicGas, _ := CalcIntrinsicGas(uint64(mt.Tx.DataLen), uint64(mt.Tx.DataNonZeroLen), nil, mt.Tx.Creation, true, true, isShanghai) + intrinsicGas, _ := txpoolcfg.CalcIntrinsicGas(uint64(mt.Tx.DataLen), uint64(mt.Tx.DataNonZeroLen), nil, mt.Tx.Creation, true, true, isShanghai) if intrinsicGas > availableGas { // we might find another TX with a low enough intrinsic gas to include so carry on continue @@ -698,11 +616,11 @@ func (p *TxPool) AddRemoteTxs(_ context.Context, newTxs types.TxSlots) { } } -func (p *TxPool) validateTx(txn *types.TxSlot, isLocal bool, stateCache kvcache.CacheView) DiscardReason { +func (p *TxPool) validateTx(txn *types.TxSlot, isLocal bool, stateCache kvcache.CacheView) txpoolcfg.DiscardReason { isShanghai := p.isShanghai() if isShanghai { if txn.DataLen > fixedgas.MaxInitCodeSize { - return InitCodeTooLarge + return txpoolcfg.InitCodeTooLarge } } @@ -711,13 +629,13 @@ func (p *TxPool) validateTx(txn *types.TxSlot, isLocal bool, stateCache kvcache. if txn.Traced { p.logger.Info(fmt.Sprintf("TX TRACING: validateTx underpriced idHash=%x local=%t, feeCap=%d, cfg.MinFeeCap=%d", txn.IDHash, isLocal, txn.FeeCap, p.cfg.MinFeeCap)) } - return UnderPriced + return txpoolcfg.UnderPriced } - gas, reason := CalcIntrinsicGas(uint64(txn.DataLen), uint64(txn.DataNonZeroLen), nil, txn.Creation, true, true, isShanghai) + gas, reason := txpoolcfg.CalcIntrinsicGas(uint64(txn.DataLen), uint64(txn.DataNonZeroLen), nil, txn.Creation, true, true, isShanghai) if txn.Traced { p.logger.Info(fmt.Sprintf("TX TRACING: validateTx intrinsic gas idHash=%x gas=%d", txn.IDHash, gas)) } - if reason != Success { + if reason != txpoolcfg.Success { if txn.Traced { p.logger.Info(fmt.Sprintf("TX TRACING: validateTx intrinsic gas calculated failed idHash=%x reason=%s", txn.IDHash, reason)) } @@ -727,13 +645,13 @@ func (p *TxPool) validateTx(txn *types.TxSlot, isLocal bool, stateCache kvcache. if txn.Traced { p.logger.Info(fmt.Sprintf("TX TRACING: validateTx intrinsic gas > txn.gas idHash=%x gas=%d, txn.gas=%d", txn.IDHash, gas, txn.Gas)) } - return IntrinsicGas + return txpoolcfg.IntrinsicGas } if !isLocal && uint64(p.all.count(txn.SenderID)) > p.cfg.AccountSlots { if txn.Traced { log.Info(fmt.Sprintf("TX TRACING: validateTx marked as spamming idHash=%x slots=%d, limit=%d", txn.IDHash, p.all.count(txn.SenderID), p.cfg.AccountSlots)) } - return Spammer + return txpoolcfg.Spammer } // check nonce and balance @@ -742,7 +660,7 @@ func (p *TxPool) validateTx(txn *types.TxSlot, isLocal bool, stateCache kvcache. if txn.Traced { p.logger.Info(fmt.Sprintf("TX TRACING: validateTx nonce too low idHash=%x nonce in state=%d, txn.nonce=%d", txn.IDHash, senderNonce, txn.Nonce)) } - return NonceTooLow + return txpoolcfg.NonceTooLow } // Transactor should have enough funds to cover the costs total := uint256.NewInt(txn.Gas) @@ -752,9 +670,9 @@ func (p *TxPool) validateTx(txn *types.TxSlot, isLocal bool, stateCache kvcache. if txn.Traced { p.logger.Info(fmt.Sprintf("TX TRACING: validateTx insufficient funds idHash=%x balance in state=%d, txn.gas*txn.tip=%d", txn.IDHash, senderBalance, total)) } - return InsufficientFunds + return txpoolcfg.InsufficientFunds } - return Success + return txpoolcfg.Success } func (p *TxPool) isShanghai() bool { @@ -801,10 +719,10 @@ func (p *TxPool) ValidateSerializedTxn(serializedTxn []byte) error { } return nil } -func (p *TxPool) validateTxs(txs *types.TxSlots, stateCache kvcache.CacheView) (reasons []DiscardReason, goodTxs types.TxSlots, err error) { +func (p *TxPool) validateTxs(txs *types.TxSlots, stateCache kvcache.CacheView) (reasons []txpoolcfg.DiscardReason, goodTxs types.TxSlots, err error) { // reasons is pre-sized for direct indexing, with the default zero // value DiscardReason of NotSet - reasons = make([]DiscardReason, len(txs.Txs)) + reasons = make([]txpoolcfg.DiscardReason, len(txs.Txs)) if err := txs.Valid(); err != nil { return reasons, goodTxs, err @@ -813,12 +731,12 @@ func (p *TxPool) validateTxs(txs *types.TxSlots, stateCache kvcache.CacheView) ( goodCount := 0 for i, txn := range txs.Txs { reason := p.validateTx(txn, txs.IsLocal[i], stateCache) - if reason == Success { + if reason == txpoolcfg.Success { goodCount++ // Success here means no DiscardReason yet, so leave it NotSet continue } - if reason == Spammer { + if reason == txpoolcfg.Spammer { p.punishSpammer(txn.SenderID) } reasons[i] = reason @@ -828,7 +746,7 @@ func (p *TxPool) validateTxs(txs *types.TxSlots, stateCache kvcache.CacheView) ( j := 0 for i, txn := range txs.Txs { - if reasons[i] == NotSet { + if reasons[i] == txpoolcfg.NotSet { goodTxs.Txs[j] = txn goodTxs.IsLocal[j] = txs.IsLocal[i] copy(goodTxs.Senders.At(j), txs.Senders.At(i)) @@ -849,27 +767,27 @@ func (p *TxPool) punishSpammer(spammer uint64) { return count > 0 }) for _, mt := range txsToDelete { - p.discardLocked(mt, Spammer) // can't call it while iterating by all + p.discardLocked(mt, txpoolcfg.Spammer) // can't call it while iterating by all } } } -func fillDiscardReasons(reasons []DiscardReason, newTxs types.TxSlots, discardReasonsLRU *simplelru.LRU[string, DiscardReason]) []DiscardReason { +func fillDiscardReasons(reasons []txpoolcfg.DiscardReason, newTxs types.TxSlots, discardReasonsLRU *simplelru.LRU[string, txpoolcfg.DiscardReason]) []txpoolcfg.DiscardReason { for i := range reasons { - if reasons[i] != NotSet { + if reasons[i] != txpoolcfg.NotSet { continue } reason, ok := discardReasonsLRU.Get(string(newTxs.Txs[i].IDHash[:])) if ok { reasons[i] = reason } else { - reasons[i] = Success + reasons[i] = txpoolcfg.Success } } return reasons } -func (p *TxPool) AddLocalTxs(ctx context.Context, newTransactions types.TxSlots, tx kv.Tx) ([]DiscardReason, error) { +func (p *TxPool) AddLocalTxs(ctx context.Context, newTransactions types.TxSlots, tx kv.Tx) ([]txpoolcfg.DiscardReason, error) { coreTx, err := p.coreDB().BeginRo(ctx) if err != nil { return nil, err @@ -906,7 +824,7 @@ func (p *TxPool) AddLocalTxs(ctx context.Context, newTransactions types.TxSlots, p.pendingBaseFee.Load(), p.blockGasLimit.Load(), p.pending, p.baseFee, p.queued, p.all, p.byHash, p.addLocked, p.discardLocked, true, p.logger) if err == nil { for i, reason := range addReasons { - if reason != NotSet { + if reason != txpoolcfg.NotSet { reasons[i] = reason } } @@ -918,7 +836,7 @@ func (p *TxPool) AddLocalTxs(ctx context.Context, newTransactions types.TxSlots, reasons = fillDiscardReasons(reasons, newTxs, p.discardReasonsLRU) for i, reason := range reasons { - if reason == Success { + if reason == txpoolcfg.Success { txn := newTxs.Txs[i] if txn.Traced { p.logger.Info(fmt.Sprintf("TX TRACING: AddLocalTxs promotes idHash=%x, senderId=%d", txn.IDHash, txn.SenderID)) @@ -950,8 +868,8 @@ func (p *TxPool) cache() kvcache.Cache { func addTxs(blockNum uint64, cacheView kvcache.CacheView, senders *sendersBatch, newTxs types.TxSlots, pendingBaseFee, blockGasLimit uint64, pending *PendingPool, baseFee, queued *SubPool, - byNonce *BySenderAndNonce, byHash map[string]*metaTx, add func(*metaTx, *types.Announcements) DiscardReason, discard func(*metaTx, DiscardReason), collect bool, - logger log.Logger) (types.Announcements, []DiscardReason, error) { + byNonce *BySenderAndNonce, byHash map[string]*metaTx, add func(*metaTx, *types.Announcements) txpoolcfg.DiscardReason, discard func(*metaTx, txpoolcfg.DiscardReason), collect bool, + logger log.Logger) (types.Announcements, []txpoolcfg.DiscardReason, error) { protocolBaseFee := calcProtocolBaseFee(pendingBaseFee) if assert.Enable { for _, txn := range newTxs.Txs { @@ -970,11 +888,11 @@ func addTxs(blockNum uint64, cacheView kvcache.CacheView, senders *sendersBatch, // somehow the fact that certain transactions were local, needs to be remembered for some // time (up to some "immutability threshold"). sendersWithChangedState := map[uint64]struct{}{} - discardReasons := make([]DiscardReason, len(newTxs.Txs)) + discardReasons := make([]txpoolcfg.DiscardReason, len(newTxs.Txs)) announcements := types.Announcements{} for i, txn := range newTxs.Txs { if found, ok := byHash[string(txn.IDHash[:])]; ok { - discardReasons[i] = DuplicateHash + discardReasons[i] = txpoolcfg.DuplicateHash // In case if the transation is stuck, "poke" it to rebroadcast if collect && newTxs.IsLocal[i] && (found.currentSubPool == PendingSubPool || found.currentSubPool == BaseFeeSubPool) { announcements.Append(found.Tx.Type, found.Tx.Size, found.Tx.IDHash[:]) @@ -982,11 +900,11 @@ func addTxs(blockNum uint64, cacheView kvcache.CacheView, senders *sendersBatch, continue } mt := newMetaTx(txn, newTxs.IsLocal[i], blockNum) - if reason := add(mt, &announcements); reason != NotSet { + if reason := add(mt, &announcements); reason != txpoolcfg.NotSet { discardReasons[i] = reason continue } - discardReasons[i] = NotSet + discardReasons[i] = txpoolcfg.NotSet if txn.Traced { logger.Info(fmt.Sprintf("TX TRACING: schedule sendersWithChangedState idHash=%x senderId=%d", txn.IDHash, mt.Tx.SenderID)) } @@ -1010,7 +928,7 @@ func addTxs(blockNum uint64, cacheView kvcache.CacheView, senders *sendersBatch, func addTxsOnNewBlock(blockNum uint64, cacheView kvcache.CacheView, stateChanges *remote.StateChangeBatch, senders *sendersBatch, newTxs types.TxSlots, pendingBaseFee uint64, blockGasLimit uint64, pending *PendingPool, baseFee, queued *SubPool, - byNonce *BySenderAndNonce, byHash map[string]*metaTx, add func(*metaTx, *types.Announcements) DiscardReason, discard func(*metaTx, DiscardReason), + byNonce *BySenderAndNonce, byHash map[string]*metaTx, add func(*metaTx, *types.Announcements) txpoolcfg.DiscardReason, discard func(*metaTx, txpoolcfg.DiscardReason), logger log.Logger) (types.Announcements, error) { protocolBaseFee := calcProtocolBaseFee(pendingBaseFee) if assert.Enable { @@ -1036,7 +954,7 @@ func addTxsOnNewBlock(blockNum uint64, cacheView kvcache.CacheView, stateChanges continue } mt := newMetaTx(txn, newTxs.IsLocal[i], blockNum) - if reason := add(mt, &announcements); reason != NotSet { + if reason := add(mt, &announcements); reason != txpoolcfg.NotSet { discard(mt, reason) continue } @@ -1081,7 +999,7 @@ func (p *TxPool) setBaseFee(baseFee uint64) (uint64, bool) { return p.pendingBaseFee.Load(), changed } -func (p *TxPool) addLocked(mt *metaTx, announcements *types.Announcements) DiscardReason { +func (p *TxPool) addLocked(mt *metaTx, announcements *types.Announcements) txpoolcfg.DiscardReason { // Insert to pending pool, if pool doesn't have txn with same Nonce and bigger Tip found := p.all.get(mt.Tx.SenderID, mt.Tx.Nonce) if found != nil { @@ -1098,9 +1016,9 @@ func (p *TxPool) addLocked(mt *metaTx, announcements *types.Announcements) Disca announcements.Append(found.Tx.Type, found.Tx.Size, found.Tx.IDHash[:]) } if bytes.Equal(found.Tx.IDHash[:], mt.Tx.IDHash[:]) { - return NotSet + return txpoolcfg.NotSet } - return NotReplaced + return txpoolcfg.NotReplaced } switch found.currentSubPool { @@ -1114,7 +1032,7 @@ func (p *TxPool) addLocked(mt *metaTx, announcements *types.Announcements) Disca //already removed } - p.discardLocked(found, ReplacedByHigherTip) + p.discardLocked(found, txpoolcfg.ReplacedByHigherTip) } p.byHash[string(mt.Tx.IDHash[:])] = mt @@ -1130,12 +1048,12 @@ func (p *TxPool) addLocked(mt *metaTx, announcements *types.Announcements) Disca } // All transactions are first added to the queued pool and then immediately promoted from there if required p.queued.Add(mt, p.logger) - return NotSet + return txpoolcfg.NotSet } // dropping transaction from all sub-structures and from db // Important: don't call it while iterating by all -func (p *TxPool) discardLocked(mt *metaTx, reason DiscardReason) { +func (p *TxPool) discardLocked(mt *metaTx, reason txpoolcfg.DiscardReason) { delete(p.byHash, string(mt.Tx.IDHash[:])) p.deletedTxs = append(p.deletedTxs, mt) p.all.delete(mt) @@ -1159,7 +1077,7 @@ func (p *TxPool) NonceFromAddress(addr [20]byte) (nonce uint64, inPool bool) { // modify state_balance and state_nonce, potentially remove some elements (if transaction with some nonce is // included into a block), and finally, walk over the transaction records and update SubPool fields depending on // the actual presence of nonce gaps and what the balance is. -func removeMined(byNonce *BySenderAndNonce, minedTxs []*types.TxSlot, pending *PendingPool, baseFee, queued *SubPool, discard func(*metaTx, DiscardReason), logger log.Logger) error { +func removeMined(byNonce *BySenderAndNonce, minedTxs []*types.TxSlot, pending *PendingPool, baseFee, queued *SubPool, discard func(*metaTx, txpoolcfg.DiscardReason), logger log.Logger) error { noncesToRemove := map[uint64]uint64{} for _, txn := range minedTxs { nonce, ok := noncesToRemove[txn.SenderID] @@ -1198,7 +1116,7 @@ func removeMined(byNonce *BySenderAndNonce, minedTxs []*types.TxSlot, pending *P }) for _, mt := range toDel { - discard(mt, Mined) + discard(mt, txpoolcfg.Mined) } toDel = toDel[:0] } @@ -1210,7 +1128,7 @@ func removeMined(byNonce *BySenderAndNonce, minedTxs []*types.TxSlot, pending *P // nonces, and also affect other transactions from the same sender with higher nonce, it loops through all transactions // for a given senderID func onSenderStateChange(senderID uint64, senderNonce uint64, senderBalance uint256.Int, byNonce *BySenderAndNonce, - protocolBaseFee, blockGasLimit uint64, pending *PendingPool, baseFee, queued *SubPool, discard func(*metaTx, DiscardReason), logger log.Logger) { + protocolBaseFee, blockGasLimit uint64, pending *PendingPool, baseFee, queued *SubPool, discard func(*metaTx, txpoolcfg.DiscardReason), logger log.Logger) { noGapsNonce := senderNonce cumulativeRequiredBalance := uint256.NewInt(0) minFeeCap := uint256.NewInt(0).SetAllOne() @@ -1316,13 +1234,13 @@ func onSenderStateChange(senderID uint64, senderNonce uint64, senderBalance uint return true }) for _, mt := range toDel { - discard(mt, NonceTooLow) + discard(mt, txpoolcfg.NonceTooLow) } } // promote reasserts invariants of the subpool and returns the list of transactions that ended up // being promoted to the pending or basefee pool, for re-broadcasting -func promote(pending *PendingPool, baseFee, queued *SubPool, pendingBaseFee uint64, discard func(*metaTx, DiscardReason), announcements *types.Announcements, +func promote(pending *PendingPool, baseFee, queued *SubPool, pendingBaseFee uint64, discard func(*metaTx, txpoolcfg.DiscardReason), announcements *types.Announcements, logger log.Logger) { // Demote worst transactions that do not qualify for pending sub pool anymore, to other sub pools, or discard for worst := pending.Worst(); pending.Len() > 0 && (worst.subPool < BaseFeePoolBits || worst.minFeeCap.Cmp(uint256.NewInt(pendingBaseFee)) < 0); worst = pending.Worst() { @@ -1333,7 +1251,7 @@ func promote(pending *PendingPool, baseFee, queued *SubPool, pendingBaseFee uint } else if worst.subPool >= QueuedPoolBits { queued.Add(pending.PopWorst(), logger) } else { - discard(pending.PopWorst(), FeeTooLow) + discard(pending.PopWorst(), txpoolcfg.FeeTooLow) } } @@ -1349,7 +1267,7 @@ func promote(pending *PendingPool, baseFee, queued *SubPool, pendingBaseFee uint if worst.subPool >= QueuedPoolBits { queued.Add(baseFee.PopWorst(), logger) } else { - discard(baseFee.PopWorst(), FeeTooLow) + discard(baseFee.PopWorst(), txpoolcfg.FeeTooLow) } } @@ -1366,22 +1284,22 @@ func promote(pending *PendingPool, baseFee, queued *SubPool, pendingBaseFee uint // Discard worst transactions from the queued sub pool if they do not qualify for worst := queued.Worst(); queued.Len() > 0 && worst.subPool < QueuedPoolBits; worst = queued.Worst() { - discard(queued.PopWorst(), FeeTooLow) + discard(queued.PopWorst(), txpoolcfg.FeeTooLow) } // Discard worst transactions from pending pool until it is within capacity limit for pending.Len() > pending.limit { - discard(pending.PopWorst(), PendingPoolOverflow) + discard(pending.PopWorst(), txpoolcfg.PendingPoolOverflow) } // Discard worst transactions from pending sub pool until it is within capacity limits for baseFee.Len() > baseFee.limit { - discard(baseFee.PopWorst(), BaseFeePoolOverflow) + discard(baseFee.PopWorst(), txpoolcfg.BaseFeePoolOverflow) } // Discard worst transactions from the queued sub pool until it is within its capacity limits for _ = queued.Worst(); queued.Len() > queued.limit; _ = queued.Worst() { - discard(queued.PopWorst(), QueuedPoolOverflow) + discard(queued.PopWorst(), txpoolcfg.QueuedPoolOverflow) } } @@ -1684,7 +1602,7 @@ func (p *TxPool) fromDB(ctx context.Context, tx kv.Tx, coreTx kv.Tx) error { isLocalTx := p.isLocalLRU.Contains(string(k)) - if reason := p.validateTx(txn, isLocalTx, cacheView); reason != NotSet && reason != Success { + if reason := p.validateTx(txn, isLocalTx, cacheView); reason != txpoolcfg.NotSet && reason != txpoolcfg.Success { return nil } txs.Resize(uint(i + 1)) @@ -1831,87 +1749,6 @@ func (p *TxPool) deprecatedForEach(_ context.Context, f func(rlp []byte, sender }) } -// CalcIntrinsicGas computes the 'intrinsic gas' for a message with the given data. -func CalcIntrinsicGas(dataLen, dataNonZeroLen uint64, accessList types.AccessList, isContractCreation, isHomestead, isEIP2028, isShanghai bool) (uint64, DiscardReason) { - // Set the starting gas for the raw transaction - var gas uint64 - if isContractCreation && isHomestead { - gas = fixedgas.TxGasContractCreation - } else { - gas = fixedgas.TxGas - } - // Bump the required gas by the amount of transactional data - if dataLen > 0 { - // Zero and non-zero bytes are priced differently - nz := dataNonZeroLen - // Make sure we don't exceed uint64 for all data combinations - nonZeroGas := fixedgas.TxDataNonZeroGasFrontier - if isEIP2028 { - nonZeroGas = fixedgas.TxDataNonZeroGasEIP2028 - } - - product, overflow := emath.SafeMul(nz, nonZeroGas) - if overflow { - return 0, GasUintOverflow - } - gas, overflow = emath.SafeAdd(gas, product) - if overflow { - return 0, GasUintOverflow - } - - z := dataLen - nz - - product, overflow = emath.SafeMul(z, fixedgas.TxDataZeroGas) - if overflow { - return 0, GasUintOverflow - } - gas, overflow = emath.SafeAdd(gas, product) - if overflow { - return 0, GasUintOverflow - } - - if isContractCreation && isShanghai { - numWords := toWordSize(dataLen) - product, overflow = emath.SafeMul(numWords, fixedgas.InitCodeWordGas) - if overflow { - return 0, GasUintOverflow - } - gas, overflow = emath.SafeAdd(gas, product) - if overflow { - return 0, GasUintOverflow - } - } - } - if accessList != nil { - product, overflow := emath.SafeMul(uint64(len(accessList)), fixedgas.TxAccessListAddressGas) - if overflow { - return 0, GasUintOverflow - } - gas, overflow = emath.SafeAdd(gas, product) - if overflow { - return 0, GasUintOverflow - } - - product, overflow = emath.SafeMul(uint64(accessList.StorageKeys()), fixedgas.TxAccessListStorageKeyGas) - if overflow { - return 0, GasUintOverflow - } - gas, overflow = emath.SafeAdd(gas, product) - if overflow { - return 0, GasUintOverflow - } - } - return gas, Success -} - -// toWordSize returns the ceiled word size required for memory expansion. -func toWordSize(size uint64) uint64 { - if size > math.MaxUint64-31 { - return math.MaxUint64/32 + 1 - } - return (size + 31) / 32 -} - var PoolChainConfigKey = []byte("chain_config") var PoolLastSeenBlockKey = []byte("last_seen_block") var PoolPendingBaseFeeKey = []byte("pending_base_fee") diff --git a/txpool/pool_test.go b/txpool/pool_test.go index ed078353d..1c1cdefd1 100644 --- a/txpool/pool_test.go +++ b/txpool/pool_test.go @@ -93,7 +93,7 @@ func TestNonceFromAddress(t *testing.T) { reasons, err := pool.AddLocalTxs(ctx, txSlots, tx) assert.NoError(err) for _, reason := range reasons { - assert.Equal(Success, reason, reason.String()) + assert.Equal(txpoolcfg.Success, reason, reason.String()) } } @@ -118,7 +118,7 @@ func TestNonceFromAddress(t *testing.T) { reasons, err := pool.AddLocalTxs(ctx, txSlots, tx) assert.NoError(err) for _, reason := range reasons { - assert.Equal(Success, reason, reason.String()) + assert.Equal(txpoolcfg.Success, reason, reason.String()) } nonce, ok := pool.NonceFromAddress(addr) assert.True(ok) @@ -138,7 +138,7 @@ func TestNonceFromAddress(t *testing.T) { reasons, err := pool.AddLocalTxs(ctx, txSlots, tx) assert.NoError(err) for _, reason := range reasons { - assert.Equal(InsufficientFunds, reason, reason.String()) + assert.Equal(txpoolcfg.InsufficientFunds, reason, reason.String()) } } @@ -156,7 +156,7 @@ func TestNonceFromAddress(t *testing.T) { reasons, err := pool.AddLocalTxs(ctx, txSlots, tx) assert.NoError(err) for _, reason := range reasons { - assert.Equal(NonceTooLow, reason, reason.String()) + assert.Equal(txpoolcfg.NonceTooLow, reason, reason.String()) } } } @@ -213,7 +213,7 @@ func TestReplaceWithHigherFee(t *testing.T) { reasons, err := pool.AddLocalTxs(ctx, txSlots, tx) assert.NoError(err) for _, reason := range reasons { - assert.Equal(Success, reason, reason.String()) + assert.Equal(txpoolcfg.Success, reason, reason.String()) } } // Bumped only feeCap, transaction not accepted @@ -230,7 +230,7 @@ func TestReplaceWithHigherFee(t *testing.T) { reasons, err := pool.AddLocalTxs(ctx, txSlots, tx) assert.NoError(err) for _, reason := range reasons { - assert.Equal(NotReplaced, reason, reason.String()) + assert.Equal(txpoolcfg.NotReplaced, reason, reason.String()) } nonce, ok := pool.NonceFromAddress(addr) assert.True(ok) @@ -250,7 +250,7 @@ func TestReplaceWithHigherFee(t *testing.T) { reasons, err := pool.AddLocalTxs(ctx, txSlots, tx) assert.NoError(err) for _, reason := range reasons { - assert.Equal(NotReplaced, reason, reason.String()) + assert.Equal(txpoolcfg.NotReplaced, reason, reason.String()) } nonce, ok := pool.NonceFromAddress(addr) assert.True(ok) @@ -270,7 +270,7 @@ func TestReplaceWithHigherFee(t *testing.T) { reasons, err := pool.AddLocalTxs(ctx, txSlots, tx) assert.NoError(err) for _, reason := range reasons { - assert.Equal(Success, reason, reason.String()) + assert.Equal(txpoolcfg.Success, reason, reason.String()) } nonce, ok := pool.NonceFromAddress(addr) assert.True(ok) @@ -330,7 +330,7 @@ func TestReverseNonces(t *testing.T) { reasons, err := pool.AddLocalTxs(ctx, txSlots, tx) assert.NoError(err) for _, reason := range reasons { - assert.Equal(Success, reason, reason.String()) + assert.Equal(txpoolcfg.Success, reason, reason.String()) } } fmt.Printf("AFTER TX 1\n") @@ -358,7 +358,7 @@ func TestReverseNonces(t *testing.T) { reasons, err := pool.AddLocalTxs(ctx, txSlots, tx) assert.NoError(err) for _, reason := range reasons { - assert.Equal(Success, reason, reason.String()) + assert.Equal(txpoolcfg.Success, reason, reason.String()) } } fmt.Printf("AFTER TX 2\n") @@ -386,7 +386,7 @@ func TestReverseNonces(t *testing.T) { reasons, err := pool.AddLocalTxs(ctx, txSlots, tx) assert.NoError(err) for _, reason := range reasons { - assert.Equal(Success, reason, reason.String()) + assert.Equal(txpoolcfg.Success, reason, reason.String()) } } fmt.Printf("AFTER TX 3\n") @@ -459,7 +459,7 @@ func TestTxPoke(t *testing.T) { reasons, err := pool.AddLocalTxs(ctx, txSlots, tx) assert.NoError(err) for _, reason := range reasons { - assert.Equal(Success, reason, reason.String()) + assert.Equal(txpoolcfg.Success, reason, reason.String()) } } var promoted types.Announcements @@ -485,7 +485,7 @@ func TestTxPoke(t *testing.T) { reasons, err := pool.AddLocalTxs(ctx, txSlots, tx) assert.NoError(err) for _, reason := range reasons { - assert.Equal(DuplicateHash, reason, reason.String()) + assert.Equal(txpoolcfg.DuplicateHash, reason, reason.String()) } nonce, ok := pool.NonceFromAddress(addr) assert.True(ok) @@ -514,7 +514,7 @@ func TestTxPoke(t *testing.T) { reasons, err := pool.AddLocalTxs(ctx, txSlots, tx) assert.NoError(err) for _, reason := range reasons { - assert.Equal(NotReplaced, reason, reason.String()) + assert.Equal(txpoolcfg.NotReplaced, reason, reason.String()) } nonce, ok := pool.NonceFromAddress(addr) assert.True(ok) @@ -623,8 +623,8 @@ func TestShanghaiIntrinsicGas(t *testing.T) { for name, c := range cases { t.Run(name, func(t *testing.T) { - gas, reason := CalcIntrinsicGas(c.dataLen, c.dataNonZeroLen, nil, c.creation, true, true, c.isShanghai) - if reason != Success { + gas, reason := txpoolcfg.CalcIntrinsicGas(c.dataLen, c.dataNonZeroLen, nil, c.creation, true, true, c.isShanghai) + if reason != txpoolcfg.Success { t.Errorf("expected success but got reason %v", reason) } if gas != c.expected { @@ -637,27 +637,27 @@ func TestShanghaiIntrinsicGas(t *testing.T) { func TestShanghaiValidateTx(t *testing.T) { asrt := assert.New(t) tests := map[string]struct { - expected DiscardReason + expected txpoolcfg.DiscardReason dataLen int isShanghai bool }{ "no shanghai": { - expected: Success, + expected: txpoolcfg.Success, dataLen: 32, isShanghai: false, }, "shanghai within bounds": { - expected: Success, + expected: txpoolcfg.Success, dataLen: 32, isShanghai: true, }, "shanghai exactly on bound": { - expected: Success, + expected: txpoolcfg.Success, dataLen: fixedgas.MaxInitCodeSize, isShanghai: true, }, "shanghai one over bound": { - expected: InitCodeTooLarge, + expected: txpoolcfg.InitCodeTooLarge, dataLen: fixedgas.MaxInitCodeSize + 1, isShanghai: true, }, diff --git a/txpool/txpool_grpc_server.go b/txpool/txpool_grpc_server.go index 073f150e2..0f52bd2b0 100644 --- a/txpool/txpool_grpc_server.go +++ b/txpool/txpool_grpc_server.go @@ -28,6 +28,7 @@ import ( grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" "github.com/holiman/uint256" + "github.com/ledgerwatch/erigon-lib/txpool/txpoolcfg" "github.com/ledgerwatch/log/v3" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -53,7 +54,7 @@ type txPool interface { PeekBest(n uint16, txs *types.TxsRlp, tx kv.Tx, onTopOf, availableGas, availableDataGas uint64) (bool, error) GetRlp(tx kv.Tx, hash []byte) ([]byte, error) - AddLocalTxs(ctx context.Context, newTxs types.TxSlots, tx kv.Tx) ([]DiscardReason, error) + AddLocalTxs(ctx context.Context, newTxs types.TxSlots, tx kv.Tx) ([]txpoolcfg.DiscardReason, error) deprecatedForEach(_ context.Context, f func(rlp []byte, sender common.Address, t SubPoolType), tx kv.Tx) CountContent() (int, int, int) IdHashKnown(tx kv.Tx, hash []byte) (bool, error) @@ -198,10 +199,10 @@ func (s *GrpcServer) Add(ctx context.Context, in *txpool_proto.AddRequest) (*txp return nil }); err != nil { if errors.Is(err, types.ErrAlreadyKnown) { // Noop, but need to handle to not count these - reply.Errors[i] = AlreadyKnown.String() + reply.Errors[i] = txpoolcfg.AlreadyKnown.String() reply.Imported[i] = txpool_proto.ImportResult_ALREADY_EXISTS } else if errors.Is(err, types.ErrRlpTooBig) { // Noop, but need to handle to not count these - reply.Errors[i] = RLPTooLong.String() + reply.Errors[i] = txpoolcfg.RLPTooLong.String() reply.Imported[i] = txpool_proto.ImportResult_INVALID } else { reply.Errors[i] = err.Error() @@ -231,15 +232,15 @@ func (s *GrpcServer) Add(ctx context.Context, in *txpool_proto.AddRequest) (*txp return reply, nil } -func mapDiscardReasonToProto(reason DiscardReason) txpool_proto.ImportResult { +func mapDiscardReasonToProto(reason txpoolcfg.DiscardReason) txpool_proto.ImportResult { switch reason { - case Success: + case txpoolcfg.Success: return txpool_proto.ImportResult_SUCCESS - case AlreadyKnown: + case txpoolcfg.AlreadyKnown: return txpool_proto.ImportResult_ALREADY_EXISTS - case UnderPriced, ReplaceUnderpriced, FeeTooLow: + case txpoolcfg.UnderPriced, txpoolcfg.ReplaceUnderpriced, txpoolcfg.FeeTooLow: return txpool_proto.ImportResult_FEE_TOO_LOW - case InvalidSender, NegativeValue, OversizedData, InitCodeTooLarge, RLPTooLong: + case txpoolcfg.InvalidSender, txpoolcfg.NegativeValue, txpoolcfg.OversizedData, txpoolcfg.InitCodeTooLarge, txpoolcfg.RLPTooLong: return txpool_proto.ImportResult_INVALID default: return txpool_proto.ImportResult_INTERNAL_ERROR diff --git a/txpool/txpoolcfg/txpoolcfg.go b/txpool/txpoolcfg/txpoolcfg.go index 9eb2bbd45..6c697e9d4 100644 --- a/txpool/txpoolcfg/txpoolcfg.go +++ b/txpool/txpoolcfg/txpoolcfg.go @@ -1,8 +1,14 @@ package txpoolcfg import ( + "fmt" + "math" "math/big" "time" + + "github.com/ledgerwatch/erigon-lib/common/fixedgas" + emath "github.com/ledgerwatch/erigon-lib/common/math" + "github.com/ledgerwatch/erigon-lib/types" ) type Config struct { @@ -36,3 +42,165 @@ var DefaultConfig = Config{ PriceBump: 10, // Price bump percentage to replace an already existing transaction OverrideShanghaiTime: nil, } + +type DiscardReason uint8 + +const ( + NotSet DiscardReason = 0 // analog of "nil-value", means it will be set in future + Success DiscardReason = 1 + AlreadyKnown DiscardReason = 2 + Mined DiscardReason = 3 + ReplacedByHigherTip DiscardReason = 4 + UnderPriced DiscardReason = 5 + ReplaceUnderpriced DiscardReason = 6 // if a transaction is attempted to be replaced with a different one without the required price bump. + FeeTooLow DiscardReason = 7 + OversizedData DiscardReason = 8 + InvalidSender DiscardReason = 9 + NegativeValue DiscardReason = 10 // ensure no one is able to specify a transaction with a negative value. + Spammer DiscardReason = 11 + PendingPoolOverflow DiscardReason = 12 + BaseFeePoolOverflow DiscardReason = 13 + QueuedPoolOverflow DiscardReason = 14 + GasUintOverflow DiscardReason = 15 + IntrinsicGas DiscardReason = 16 + RLPTooLong DiscardReason = 17 + NonceTooLow DiscardReason = 18 + InsufficientFunds DiscardReason = 19 + NotReplaced DiscardReason = 20 // There was an existing transaction with the same sender and nonce, not enough price bump to replace + DuplicateHash DiscardReason = 21 // There was an existing transaction with the same hash + InitCodeTooLarge DiscardReason = 22 // EIP-3860 - transaction init code is too large +) + +func (r DiscardReason) String() string { + switch r { + case NotSet: + return "not set" + case Success: + return "success" + case AlreadyKnown: + return "already known" + case Mined: + return "mined" + case ReplacedByHigherTip: + return "replaced by transaction with higher tip" + case UnderPriced: + return "underpriced" + case ReplaceUnderpriced: + return "replacement transaction underpriced" + case FeeTooLow: + return "fee too low" + case OversizedData: + return "oversized data" + case InvalidSender: + return "invalid sender" + case NegativeValue: + return "negative value" + case Spammer: + return "spammer" + case PendingPoolOverflow: + return "pending sub-pool is full" + case BaseFeePoolOverflow: + return "baseFee sub-pool is full" + case QueuedPoolOverflow: + return "queued sub-pool is full" + case GasUintOverflow: + return "GasUintOverflow" + case IntrinsicGas: + return "IntrinsicGas" + case RLPTooLong: + return "RLPTooLong" + case NonceTooLow: + return "nonce too low" + case InsufficientFunds: + return "insufficient funds" + case NotReplaced: + return "could not replace existing tx" + case DuplicateHash: + return "existing tx with same hash" + case InitCodeTooLarge: + return "initcode too large" + default: + panic(fmt.Sprintf("discard reason: %d", r)) + } +} + +// CalcIntrinsicGas computes the 'intrinsic gas' for a message with the given data. +func CalcIntrinsicGas(dataLen, dataNonZeroLen uint64, accessList types.AccessList, isContractCreation, isHomestead, isEIP2028, isShanghai bool) (uint64, DiscardReason) { + // Set the starting gas for the raw transaction + var gas uint64 + if isContractCreation && isHomestead { + gas = fixedgas.TxGasContractCreation + } else { + gas = fixedgas.TxGas + } + // Bump the required gas by the amount of transactional data + if dataLen > 0 { + // Zero and non-zero bytes are priced differently + nz := dataNonZeroLen + // Make sure we don't exceed uint64 for all data combinations + nonZeroGas := fixedgas.TxDataNonZeroGasFrontier + if isEIP2028 { + nonZeroGas = fixedgas.TxDataNonZeroGasEIP2028 + } + + product, overflow := emath.SafeMul(nz, nonZeroGas) + if overflow { + return 0, GasUintOverflow + } + gas, overflow = emath.SafeAdd(gas, product) + if overflow { + return 0, GasUintOverflow + } + + z := dataLen - nz + + product, overflow = emath.SafeMul(z, fixedgas.TxDataZeroGas) + if overflow { + return 0, GasUintOverflow + } + gas, overflow = emath.SafeAdd(gas, product) + if overflow { + return 0, GasUintOverflow + } + + if isContractCreation && isShanghai { + numWords := toWordSize(dataLen) + product, overflow = emath.SafeMul(numWords, fixedgas.InitCodeWordGas) + if overflow { + return 0, GasUintOverflow + } + gas, overflow = emath.SafeAdd(gas, product) + if overflow { + return 0, GasUintOverflow + } + } + } + if accessList != nil { + product, overflow := emath.SafeMul(uint64(len(accessList)), fixedgas.TxAccessListAddressGas) + if overflow { + return 0, GasUintOverflow + } + gas, overflow = emath.SafeAdd(gas, product) + if overflow { + return 0, GasUintOverflow + } + + product, overflow = emath.SafeMul(uint64(accessList.StorageKeys()), fixedgas.TxAccessListStorageKeyGas) + if overflow { + return 0, GasUintOverflow + } + gas, overflow = emath.SafeAdd(gas, product) + if overflow { + return 0, GasUintOverflow + } + } + return gas, Success +} + +// toWordSize returns the ceiled word size required for memory expansion. +func toWordSize(size uint64) uint64 { + if size > math.MaxUint64-31 { + return math.MaxUint64/32 + 1 + } + return (size + 31) / 32 +}