diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index b360536909..9505160f72 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1589,9 +1589,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { log.Info("Using developer account", "address", developer.Address) cfg.Genesis = core.DeveloperGenesisBlock(uint64(ctx.Int(DeveloperPeriodFlag.Name)), developer.Address) - if !ctx.IsSet(MinerGasPriceFlag.Name) { - cfg.GasPrice = big.NewInt(1) - } } // VM tracing config. diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go index f04c2ed1d4..b893006924 100644 --- a/core/txpool/txpool.go +++ b/core/txpool/txpool.go @@ -93,10 +93,6 @@ var ( // transaction. Future transactions should only be able to replace other future transactions. ErrFutureReplacePending = errors.New("future transaction tries to replace pending") - // ErrOverdraft is returned if a transaction would cause the sender's balance to go negative - // thus invalidating a potential large number of transactions. - ErrOverdraft = errors.New("transaction would cause overdraft") - ErrZeroGasPrice = errors.New("zero gas price") ErrUnderMinGasPrice = errors.New("under min gas price") @@ -198,8 +194,7 @@ type Config struct { Lifetime time.Duration // Maximum amount of time non-executable transaction are queued } -// DefaultConfig contains the default configurations for the transaction -// pool. +// DefaultConfig contains the default configurations for the transaction pool. var DefaultConfig = Config{ Journal: "transactions.rlp", Rejournal: time.Hour, @@ -215,7 +210,7 @@ var DefaultConfig = Config{ Lifetime: 3 * time.Hour, } -var defaultMaxPrice = big.NewInt(1000 * params.GWei) +var defaultMaxTip = big.NewInt(1000 * params.GWei) // sanitize checks the provided user configurations and changes anything that's // unreasonable or unworkable. @@ -263,18 +258,15 @@ type TxPool struct { config Config chainconfig *params.ChainConfig chain blockChain - gasPrice *big.Int + gasTip atomic.Pointer[big.Int] txFeed event.Feed scope event.SubscriptionScope signer types.Signer mu sync.RWMutex - eip2718 atomic.Bool // Fork indicator whether we are using EIP-2718 type transactions. - eip1559 atomic.Bool // Fork indicator whether we are using EIP-1559 type transactions. - - currentState *state.StateDB // Current state in the blockchain head - pendingNonces *noncer // Pending state tracking virtual nonces - currentMaxGas atomic.Uint64 // Current gas limit for transaction caps + currentHead atomic.Pointer[types.Header] // Current head of the blockchain + currentState *state.StateDB // Current state in the blockchain head + pendingNonces *noncer // Pending state tracking virtual nonces locals *accountSet // Set of local transaction to exempt from eviction rules journal *journal // Journal of local transaction to back up to disk @@ -305,9 +297,9 @@ type txpoolResetRequest struct { oldHead, newHead *types.Header } -// NewTxPool creates a new transaction pool to gather, sort and filter inbound +// New creates a new transaction pool to gather, sort and filter inbound // transactions from the network. -func NewTxPool(config Config, chainconfig *params.ChainConfig, chain blockChain) *TxPool { +func New(config Config, chainconfig *params.ChainConfig, chain blockChain) *TxPool { // Sanitize the input to ensure no vulnerable gas prices are set config = (&config).sanitize() @@ -328,9 +320,9 @@ func NewTxPool(config Config, chainconfig *params.ChainConfig, chain blockChain) reorgDoneCh: make(chan chan struct{}), reorgShutdownCh: make(chan struct{}), initDoneCh: make(chan struct{}), - gasPrice: new(big.Int).SetUint64(config.PriceLimit), trc21FeeCapacity: map[common.Address]*big.Int{}, } + pool.gasTip.Store(new(big.Int).SetUint64(config.PriceLimit)) pool.locals = newAccountSet(pool.signer) for _, addr := range config.Locals { log.Info("Setting new local account", "address", addr) @@ -459,48 +451,40 @@ func (pool *TxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subsc return pool.scope.Track(pool.txFeed.Subscribe(ch)) } -// GasPrice returns the current gas price enforced by the transaction pool. -func (pool *TxPool) GasPrice() *big.Int { - pool.mu.RLock() - defer pool.mu.RUnlock() - - return new(big.Int).Set(pool.gasPrice) -} - -// SetGasPrice updates the minimum price required by the transaction pool for a +// SetGasTip updates the minimum gas tip required by the transaction pool for a // new transaction, and drops all transactions below this threshold. Negative // gas prices and prices exceeding 1000 GWei are considered invalid and will be // rejected without updating the threshold. -func (pool *TxPool) SetGasPrice(price *big.Int) error { +func (pool *TxPool) SetGasTip(tip *big.Int) error { pool.mu.Lock() defer pool.mu.Unlock() - if price == nil { - log.Warn("Reject nil gas price") - return errors.New("reject nil gas price") + if tip == nil { + log.Warn("Reject nil gas tip") + return errors.New("reject nil gas tip") } - if price.Sign() < 0 { - log.Warn("Reject invalid gas price", "price", price) - return fmt.Errorf("reject negative gas price: %v", price) + if tip.Sign() < 0 { + log.Warn("Reject invalid gas tip", "tip", tip) + return fmt.Errorf("reject negative gas tip: %v", tip) } - if price.Cmp(defaultMaxPrice) > 0 { - log.Warn("Reject invalid gas price", "price", price, "max", defaultMaxPrice) - return fmt.Errorf("reject too high gas price: %v, maximum: %v", price, defaultMaxPrice) + if tip.Cmp(defaultMaxTip) > 0 { + log.Warn("Reject invalid gas tip", "tip", tip, "max", defaultMaxTip) + return fmt.Errorf("reject too high gas tip: %v, maximum: %v", tip, defaultMaxTip) } - old := pool.gasPrice - pool.gasPrice = price - // if the min miner fee increased, remove transactions below the new threshold - if price.Cmp(old) > 0 { + old := pool.gasTip.Load() + pool.gasTip.Store(new(big.Int).Set(tip)) + + // If the min miner fee increased, remove transactions below the new threshold + if tip.Cmp(old) > 0 { // pool.priced is sorted by GasFeeCap, so we have to iterate through pool.all instead - drop := pool.all.RemotesBelowTip(price) + drop := pool.all.RemotesBelowTip(tip) for _, tx := range drop { pool.removeTx(tx.Hash(), false) } pool.priced.Removed(len(drop)) } - - log.Info("Transaction pool price threshold updated", "price", price) + log.Info("Transaction pool tip threshold updated", "tip", tip) return nil } @@ -588,7 +572,7 @@ func (pool *TxPool) Pending(enforceTips bool) map[common.Address]types.Transacti // If the miner requests tip enforcement, cap the lists now if enforceTips && !pool.locals.contains(addr) { for i, tx := range txs { - if !tx.IsSpecialTransaction() && tx.EffectiveGasTipIntCmp(pool.gasPrice, pool.priced.urgent.baseFee) < 0 { + if !tx.IsSpecialTransaction() && tx.EffectiveGasTipIntCmp(pool.gasTip.Load(), pool.priced.urgent.baseFee) < 0 { txs = txs[:i] break } @@ -630,155 +614,79 @@ func (pool *TxPool) local() map[common.Address]types.Transactions { // This check is meant as an early check which only needs to be performed once, // and does not require the pool mutex to be held. func (pool *TxPool) validateTxBasics(tx *types.Transaction, local bool) error { - // Accept only legacy transactions until EIP-2718/2930 activates. - if !pool.eip2718.Load() && tx.Type() != types.LegacyTxType { - return core.ErrTxTypeNotSupported + opts := &ValidationOptions{ + Config: pool.chainconfig, + Accept: 0 | + 1< txMaxSize { - return ErrOversizedData - } - // Check whether the init code size has been exceeded. - if pool.eip1559.Load() && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize { - return fmt.Errorf("%w: code size %v limit %v", core.ErrMaxInitCodeSizeExceeded, len(tx.Data()), params.MaxInitCodeSize) - } - // Transactions can't be negative. This may never happen using RLP decoded - // transactions but may occur if you create a transaction using the RPC. - if tx.Value().Sign() < 0 { - return ErrNegativeValue - } - // Ensure the transaction doesn't exceed the current block limit gas. - if pool.currentMaxGas.Load() < tx.Gas() { - return ErrGasLimit - } - // Sanity check for extremely large numbers - if tx.GasFeeCap().BitLen() > 256 { - return core.ErrFeeCapVeryHigh - } - if tx.GasTipCap().BitLen() > 256 { - return core.ErrTipVeryHigh - } - // Ensure gasFeeCap is greater than or equal to gasTipCap. - if tx.GasFeeCapIntCmp(tx.GasTipCap()) < 0 { - return core.ErrTipAboveFeeCap - } - // Make sure the transaction is signed properly. - from, err := types.Sender(pool.signer, tx) - if err != nil { - return ErrInvalidSender - } - // Limit nonce to 2^64-1 per EIP-2681 - if tx.Nonce()+1 < tx.Nonce() { - return core.ErrNonceMax - } - // Drop non-local transactions under our own minimal accepted gas price or tip - if !local && tx.GasTipCapIntCmp(pool.gasPrice) < 0 { - if !tx.IsSpecialTransaction() || (pool.IsSigner != nil && !pool.IsSigner(from)) { - return ErrUnderpriced - } - } - // Stop checking for special transactions - if tx.IsSpecialTransaction() { - return nil - } - // Ensure the transaction has more gas than the basic tx fee. - intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, true, pool.eip1559.Load()) - if err != nil { + if err := ValidateTransaction(tx, pool.currentHead.Load(), pool.signer, opts); err != nil { return err } - if tx.Gas() < intrGas { - return core.ErrIntrinsicGas - } - // Check zero gas price. - if tx.GasPrice().Sign() == 0 { - return ErrZeroGasPrice - } return nil } // validateTx checks whether a transaction is valid according to the consensus // rules and adheres to some heuristic limits of the local node (price and size). func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { - // Signature has been checked already, this cannot error. - from, _ := types.Sender(pool.signer, tx) - // Ensure the transaction adheres to nonce ordering - if pool.currentState.GetNonce(from) > tx.Nonce() { - return core.ErrNonceTooLow - } - if pool.pendingNonces.get(from)+common.LimitThresholdNonceInQueue < tx.Nonce() { - return core.ErrNonceTooHigh - } - // Get current block number - var number *big.Int = nil - if pool.chain.CurrentHeader() != nil { - number = pool.chain.CurrentHeader().Number - } - if number == nil || number.Uint64() >= common.BlackListHFNumber { - // check if sender is in black list - if common.IsInBlacklist(tx.From()) { - return fmt.Errorf("reject transaction with sender in black-list: %v", tx.From().Hex()) - } - // check if receiver is in black list - if common.IsInBlacklist(tx.To()) { - return fmt.Errorf("reject transaction with receiver in black-list: %v", tx.To().Hex()) - } - } - // Transactor should have enough funds to cover the costs - // cost == V + GP * GL - balance := pool.currentState.GetBalance(from) - cost := tx.Cost() - feeCapacity := big.NewInt(0) - if tx.To() != nil { - if value, ok := pool.trc21FeeCapacity[*tx.To()]; ok { - feeCapacity = value - if !pool.currentState.ValidateTRC21Tx(from, *tx.To(), tx.Data()) { - return core.ErrInsufficientFunds + opts := &ValidationOptionsWithState{ + State: pool.currentState, + + FirstNonceGap: nil, // Pool allows arbitrary arrival order, don't invalidate nonce gaps + ExistingExpenditure: func(addr common.Address) *big.Int { + if list := pool.pending[addr]; list != nil { + return list.totalcost } - cost = tx.TxCost(number) - } + return new(big.Int) + }, + ExistingCost: func(addr common.Address, nonce uint64) *big.Int { + if list := pool.pending[addr]; list != nil { + if tx := list.txs.Get(nonce); tx != nil { + return tx.Cost() + } + } + return nil + }, + + Trc21FeeCapacity: pool.trc21FeeCapacity, + + PendingNonce: func(addr common.Address) uint64 { + return pool.pendingNonces.get(addr) + }, + + CurrentNumber: func() *big.Int { + header := pool.currentHead.Load() + if header == nil { + return nil + } + return header.Number + }, } - newBalance := new(big.Int).Add(balance, feeCapacity) - if newBalance.Cmp(cost) < 0 { - return core.ErrInsufficientFunds + if err := ValidateTransactionWithState(tx, pool.signer, opts); err != nil { + return err } - // Verify that replacing transactions will not result in overdraft - list := pool.pending[from] - if list != nil { // Sender already has pending txs - sum := new(big.Int).Add(cost, list.totalcost) - if repl := list.txs.Get(tx.Nonce()); repl != nil { - // Deduct the cost of a transaction replaced by this - sum.Sub(sum, repl.Cost()) - } - if newBalance.Cmp(sum) < 0 { - log.Trace("Replacing transactions would overdraft", "sender", from, "balance", pool.currentState.GetBalance(from), "required", sum) - return ErrOverdraft - } - } - - if !tx.IsSpecialTransaction() { - // under min gas price - minGasPrice := common.GetMinGasPrice(number) - if tx.GasPrice().Cmp(minGasPrice) < 0 { - return ErrUnderMinGasPrice - } - } - - // validate minFee slot for XDCZ + // Validate minFee slot for XDCZ if tx.IsXDCZApplyTransaction() { - copyState := pool.currentState.Copy() + copyState := opts.State.Copy() return core.ValidateXDCZApplyTransaction(pool.chain, nil, copyState, common.BytesToAddress(tx.Data()[4:])) } - // validate balance slot, token decimal for XDCX + // Validate balance slot, token decimal for XDCX if tx.IsXDCXApplyTransaction() { - copyState := pool.currentState.Copy() + copyState := opts.State.Copy() return core.ValidateXDCXApplyTransaction(pool.chain, nil, copyState, common.BytesToAddress(tx.Data()[4:])) } + return nil } @@ -1129,7 +1037,6 @@ func (pool *TxPool) addTxs(txs []*types.Transaction, local, sync bool) []error { // Exclude transactions with basic errors, e.g invalid signatures and // insufficient intrinsic gas as soon as possible and cache senders // in transactions before obtaining lock - if err := pool.validateTxBasics(tx, local); err != nil { errs[i] = err invalidTxMeter.Mark(1) @@ -1517,20 +1424,15 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { log.Error("Failed to reset txpool state", "err", err) return } + pool.currentHead.Store(newHead) pool.currentState = statedb pool.trc21FeeCapacity = statedb.GetTRC21FeeCapacityFromStateWithCache(newHead.Root) pool.pendingNonces = newNoncer(statedb) - pool.currentMaxGas.Store(newHead.GasLimit) // Inject any transactions discarded due to reorgs log.Debug("Reinjecting stale transactions", "count", len(reinject)) core.SenderCacher.Recover(pool.signer, reinject) pool.addTxsLocked(reinject, false) - - // Update all fork indicator by next pending block number. - next := new(big.Int).Add(newHead.Number, big.NewInt(1)) - pool.eip2718.Store(pool.chainconfig.IsEIP1559(next)) - pool.eip1559.Store(pool.chainconfig.IsEIP1559(next)) } // promoteExecutables moves transactions that have become processable from the @@ -1546,6 +1448,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans var promoted []*types.Transaction // Iterate over all accounts and promote any executable transactions + gasLimit := pool.currentHead.Load().GasLimit for _, addr := range accounts { list := pool.queue[addr] if list == nil { @@ -1563,7 +1466,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans if pool.chain.CurrentHeader() != nil { number = pool.chain.CurrentHeader().Number } - drops, _ := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas.Load(), pool.trc21FeeCapacity, number) + drops, _ := list.Filter(pool.currentState.GetBalance(addr), gasLimit, pool.trc21FeeCapacity, number) for _, tx := range drops { hash := tx.Hash() pool.all.Remove(hash) @@ -1749,6 +1652,7 @@ func (pool *TxPool) truncateQueue() { // to trigger a re-heap is this function func (pool *TxPool) demoteUnexecutables() { // Iterate over all accounts and demote any non-executable transactions + gasLimit := pool.currentHead.Load().GasLimit for addr, list := range pool.pending { nonce := pool.currentState.GetNonce(addr) @@ -1764,7 +1668,7 @@ func (pool *TxPool) demoteUnexecutables() { if pool.chain.CurrentHeader() != nil { number = pool.chain.CurrentHeader().Number } - drops, invalids := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas.Load(), pool.trc21FeeCapacity, number) + drops, invalids := list.Filter(pool.currentState.GetBalance(addr), gasLimit, pool.trc21FeeCapacity, number) for _, tx := range drops { hash := tx.Hash() log.Trace("Removed unpayable pending transaction", "hash", hash) diff --git a/core/txpool/txpool2_test.go b/core/txpool/txpool2_test.go index cfd1482374..035bec9397 100644 --- a/core/txpool/txpool2_test.go +++ b/core/txpool/txpool2_test.go @@ -84,7 +84,7 @@ func TestTransactionFutureAttack(t *testing.T) { config := testTxPoolConfig config.GlobalQueue = 100 config.GlobalSlots = 100 - pool := NewTxPool(config, eip1559Config, blockchain) + pool := New(config, eip1559Config, blockchain) defer pool.Stop() fillPool(t, pool) pending, _ := pool.Stats() @@ -117,7 +117,7 @@ func TestTransactionFuture1559(t *testing.T) { // Create the pool to test the pricing enforcement with statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase())) blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) - pool := NewTxPool(testTxPoolConfig, eip1559Config, blockchain) + pool := New(testTxPoolConfig, eip1559Config, blockchain) defer pool.Stop() // Create a number of test accounts, fund them and make transactions @@ -149,7 +149,7 @@ func TestTransactionZAttack(t *testing.T) { // Create the pool to test the pricing enforcement with statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase())) blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) - pool := NewTxPool(testTxPoolConfig, eip1559Config, blockchain) + pool := New(testTxPoolConfig, eip1559Config, blockchain) defer pool.Stop() // Create a number of test accounts, fund them and make transactions fillPool(t, pool) @@ -222,7 +222,7 @@ func BenchmarkFutureAttack(b *testing.B) { config := testTxPoolConfig config.GlobalQueue = 100 config.GlobalSlots = 100 - pool := NewTxPool(config, eip1559Config, blockchain) + pool := New(config, eip1559Config, blockchain) defer pool.Stop() fillPool(b, pool) diff --git a/core/txpool/txpool_test.go b/core/txpool/txpool_test.go index f9bea9345e..7be57393a1 100644 --- a/core/txpool/txpool_test.go +++ b/core/txpool/txpool_test.go @@ -151,7 +151,7 @@ func setupPoolWithConfig(config *params.ChainConfig) (*TxPool, *ecdsa.PrivateKey blockchain := newTestBlockChain(10000000, statedb, new(event.Feed)) key, _ := crypto.GenerateKey() - pool := NewTxPool(testTxPoolConfig, config, blockchain) + pool := New(testTxPoolConfig, config, blockchain) // wait for the pool to initialize <-pool.initDoneCh @@ -270,7 +270,7 @@ func TestStateChangeDuringReset(t *testing.T) { tx0 := pricedTransaction(0, 100000, big.NewInt(250000000), key) tx1 := pricedTransaction(1, 100000, big.NewInt(250000000), key) - pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) + pool := New(testTxPoolConfig, params.TestChainConfig, blockchain) defer pool.Stop() nonce := pool.Nonce(address) @@ -335,11 +335,15 @@ func TestInvalidTransactions(t *testing.T) { t.Errorf("want %v have %v", want, err) } + // Test underpriced: set pool gasTip first, then create transaction with lower gas price + // MinGasPrice is 250000000 (0.25 Gwei), so use gas price 300000000 (0.3 Gwei) + // which is higher than MinGasPrice but lower than pool's gasTip (1 Gwei) + pool.gasTip.Store(big.NewInt(1000000000)) // Set pool gasTip to 1 Gwei (1000000000) tx = pricedTransaction(1, 100000, big.NewInt(300000000), key) - pool.gasPrice = big.NewInt(1000000000) if err, want := pool.AddRemote(tx), ErrUnderpriced; !errors.Is(err, want) { t.Errorf("want %v have %v", want, err) } + // Local transactions should be accepted even if underpriced if err := pool.AddLocal(tx); err != nil { t.Error("expected", nil, "got", err) } @@ -761,7 +765,7 @@ func TestPostponing(t *testing.T) { statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(db)) blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) - pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) + pool := New(testTxPoolConfig, params.TestChainConfig, blockchain) defer pool.Stop() // Create two test accounts to produce different gap profiles with @@ -979,7 +983,7 @@ func testQueueGlobalLimiting(t *testing.T, nolocals bool) { config.AccountQueue = 1 config.GlobalQueue = config.AccountQueue*3 - 1 // reduce the queue limits to shorten test time (-1 to make it non divisible) - pool := NewTxPool(config, params.TestChainConfig, blockchain) + pool := New(config, params.TestChainConfig, blockchain) defer pool.Stop() // Create a number of test accounts and fund them (last one will be the local) @@ -1070,7 +1074,7 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) { config.Lifetime = time.Second config.NoLocals = nolocals - pool := NewTxPool(config, params.TestChainConfig, blockchain) + pool := New(config, params.TestChainConfig, blockchain) defer pool.Stop() // Create two test accounts to ensure remotes expire but locals do not @@ -1256,7 +1260,7 @@ func TestPendingGlobalLimiting(t *testing.T) { config := testTxPoolConfig config.GlobalSlots = config.AccountSlots * 10 - pool := NewTxPool(config, params.TestChainConfig, blockchain) + pool := New(config, params.TestChainConfig, blockchain) defer pool.Stop() // Create a number of test accounts and fund them @@ -1308,7 +1312,7 @@ func TestAllowedTxSize(t *testing.T) { // // It is assumed the fields in the transaction (except of the data) are: // - nonce <= 32 bytes - // - gasPrice <= 32 bytes + // - gasTip <= 32 bytes // - gasLimit <= 32 bytes // - recipient == 20 bytes // - value <= 32 bytes @@ -1316,22 +1320,21 @@ func TestAllowedTxSize(t *testing.T) { // All those fields are summed up to at most 213 bytes. baseSize := uint64(213) dataSize := txMaxSize - baseSize - maxGas := pool.currentMaxGas.Load() // Try adding a transaction with maximal allowed size - tx := pricedDataTransaction(0, pool.currentMaxGas.Load(), big.NewInt(300000000), key, dataSize) + tx := pricedDataTransaction(0, pool.currentHead.Load().GasLimit, big.NewInt(300000000), key, dataSize) if err := pool.addRemoteSync(tx); err != nil { t.Fatalf("failed to add transaction of size %d, close to maximal: %v", int(tx.Size()), err) } // Try adding a transaction with random allowed size - if err := pool.addRemoteSync(pricedDataTransaction(1, maxGas, big.NewInt(300000000), key, uint64(rand.Intn(int(dataSize))))); err != nil { + if err := pool.addRemoteSync(pricedDataTransaction(1, pool.currentHead.Load().GasLimit, big.NewInt(300000000), key, uint64(rand.Intn(int(dataSize))))); err != nil { t.Fatalf("failed to add transaction of random allowed size: %v", err) } // Try adding a transaction of minimal not allowed size - if err := pool.addRemoteSync(pricedDataTransaction(2, maxGas, big.NewInt(300000000), key, txMaxSize)); err == nil { + if err := pool.addRemoteSync(pricedDataTransaction(2, pool.currentHead.Load().GasLimit, big.NewInt(300000000), key, txMaxSize)); err == nil { t.Fatalf("expected rejection on slightly oversize transaction") } // Try adding a transaction of random not allowed size - if err := pool.addRemoteSync(pricedDataTransaction(2, maxGas, big.NewInt(300000000), key, dataSize+1+uint64(rand.Intn(int(10*txMaxSize))))); err == nil { + if err := pool.addRemoteSync(pricedDataTransaction(2, pool.currentHead.Load().GasLimit, big.NewInt(300000000), key, dataSize+1+uint64(rand.Intn(int(10*txMaxSize))))); err == nil { t.Fatalf("expected rejection on oversize transaction") } // Run some sanity checks on the pool internals @@ -1361,7 +1364,7 @@ func TestCapClearsFromAll(t *testing.T) { config.AccountQueue = 2 config.GlobalSlots = 8 - pool := NewTxPool(config, params.TestChainConfig, blockchain) + pool := New(config, params.TestChainConfig, blockchain) defer pool.Stop() // Create a number of test accounts and fund them @@ -1394,7 +1397,7 @@ func TestPendingMinimumAllowance(t *testing.T) { config := testTxPoolConfig config.AccountSlots = 5 config.GlobalSlots = 1 - pool := NewTxPool(config, params.TestChainConfig, blockchain) + pool := New(config, params.TestChainConfig, blockchain) defer pool.Stop() // Create a number of test accounts and fund them @@ -1440,7 +1443,7 @@ func TestRepricing(t *testing.T) { statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(db)) blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) - pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) + pool := New(testTxPoolConfig, params.TestChainConfig, blockchain) defer pool.Stop() // Keep track of transaction events to ensure all executables get announced @@ -1489,7 +1492,7 @@ func TestRepricing(t *testing.T) { t.Fatalf("pool internal state corrupted: %v", err) } // Reprice the pool and check that underpriced transactions get dropped - pool.SetGasPrice(big.NewInt(500000000)) + pool.SetGasTip(big.NewInt(500000000)) pending, queued = pool.Stats() if pending != 2 { @@ -1505,13 +1508,13 @@ func TestRepricing(t *testing.T) { t.Fatalf("pool internal state corrupted: %v", err) } // Check that we can't add the old transactions back - if err := pool.AddRemote(pricedTransaction(1, 100000, big.NewInt(250000000), keys[0])); err != ErrUnderpriced { + if err := pool.AddRemote(pricedTransaction(1, 100000, big.NewInt(250000000), keys[0])); !errors.Is(err, ErrUnderpriced) { t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) } - if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(250000000), keys[1])); err != ErrUnderpriced { + if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(250000000), keys[1])); !errors.Is(err, ErrUnderpriced) { t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) } - if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(250000000), keys[2])); err != ErrUnderpriced { + if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(250000000), keys[2])); !errors.Is(err, ErrUnderpriced) { t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, ErrUnderpriced) } if err := validateEvents(events, 0); err != nil { @@ -1610,7 +1613,7 @@ func TestRepricingDynamicFee(t *testing.T) { t.Fatalf("pool internal state corrupted: %v", err) } // Reprice the pool and check that underpriced transactions get dropped - pool.SetGasPrice(big.NewInt(350000000)) + pool.SetGasTip(big.NewInt(350000000)) pending, queued = pool.Stats() if pending != 2 { @@ -1627,15 +1630,15 @@ func TestRepricingDynamicFee(t *testing.T) { } // Check that we can't add the old transactions back tx := pricedTransaction(1, 100000, big.NewInt(300000000), keys[0]) - if err := pool.AddRemote(tx); err != ErrUnderpriced { + if err := pool.AddRemote(tx); !errors.Is(err, ErrUnderpriced) { t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) } tx = dynamicFeeTx(0, 100000, big.NewInt(350000000), big.NewInt(300000000), keys[1]) - if err := pool.AddRemote(tx); err != ErrUnderpriced { + if err := pool.AddRemote(tx); !errors.Is(err, ErrUnderpriced) { t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) } tx = dynamicFeeTx(2, 100000, big.NewInt(300000000), big.NewInt(300000000), keys[2]) - if err := pool.AddRemote(tx); err != ErrUnderpriced { + if err := pool.AddRemote(tx); !errors.Is(err, ErrUnderpriced) { t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, ErrUnderpriced) } if err := validateEvents(events, 0); err != nil { @@ -1689,7 +1692,7 @@ func TestRepricingKeepsLocals(t *testing.T) { statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(db)) blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) - pool := NewTxPool(testTxPoolConfig, eip1559Config, blockchain) + pool := New(testTxPoolConfig, eip1559Config, blockchain) defer pool.Stop() // Create a number of test accounts and fund them @@ -1741,13 +1744,13 @@ func TestRepricingKeepsLocals(t *testing.T) { validate() // Reprice the pool and check that nothing is dropped - pool.SetGasPrice(big.NewInt(500000000)) + pool.SetGasTip(big.NewInt(500000000)) validate() - pool.SetGasPrice(big.NewInt(500000000)) - pool.SetGasPrice(big.NewInt(1000000000)) - pool.SetGasPrice(big.NewInt(2000000000)) - pool.SetGasPrice(big.NewInt(25000000000)) + pool.SetGasTip(big.NewInt(500000000)) + pool.SetGasTip(big.NewInt(1000000000)) + pool.SetGasTip(big.NewInt(2000000000)) + pool.SetGasTip(big.NewInt(25000000000)) validate() } @@ -1768,7 +1771,7 @@ func TestPoolUnderpricing(t *testing.T) { config.GlobalSlots = 2 config.GlobalQueue = 2 - pool := NewTxPool(config, params.TestChainConfig, blockchain) + pool := New(config, params.TestChainConfig, blockchain) defer pool.Stop() // Keep track of transaction events to ensure all executables get announced @@ -1813,7 +1816,7 @@ func TestPoolUnderpricing(t *testing.T) { t.Fatalf("pool internal state corrupted: %v", err) } // Ensure that adding an underpriced transaction on block limit fails - if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(250000000), keys[1])); err != ErrUnderpriced { + if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(250000000), keys[1])); !errors.Is(err, ErrUnderpriced) { t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) } // Replace a future transaction with a future transaction @@ -1887,7 +1890,7 @@ func TestPoolStableUnderpricing(t *testing.T) { config.GlobalQueue = 0 config.AccountSlots = config.GlobalSlots - 1 - pool := NewTxPool(config, params.TestChainConfig, blockchain) + pool := New(config, params.TestChainConfig, blockchain) defer pool.Stop() // Keep track of transaction events to ensure all executables get announced @@ -1995,7 +1998,7 @@ func TestUnderpricingDynamicFee(t *testing.T) { // Ensure that adding an underpriced transaction fails tx := dynamicFeeTx(0, 100000, big.NewInt(260000000), big.NewInt(250000000), keys[1]) - if err := pool.AddRemote(tx); err != ErrUnderpriced { // Pend K0:0, K0:1, K2:0; Que K1:1 + if err := pool.AddRemote(tx); !errors.Is(err, ErrUnderpriced) { // Pend K0:0, K0:1, K2:0; Que K1:1 t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) } @@ -2115,7 +2118,7 @@ func TestDeduplication(t *testing.T) { statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase())) blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) - pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) + pool := New(testTxPoolConfig, params.TestChainConfig, blockchain) defer pool.Stop() // Create a test account to add transactions with @@ -2182,7 +2185,7 @@ func TestReplacement(t *testing.T) { statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(db)) blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) - pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) + pool := New(testTxPoolConfig, params.TestChainConfig, blockchain) defer pool.Stop() // Keep track of transaction events to ensure all executables get announced @@ -2394,7 +2397,7 @@ func testTransactionJournaling(t *testing.T, nolocals bool) { config.Journal = journal config.Rejournal = time.Second - pool := NewTxPool(config, params.TestChainConfig, blockchain) + pool := New(config, params.TestChainConfig, blockchain) // Create two test accounts to ensure remotes expire but locals do not local, _ := crypto.GenerateKey() @@ -2431,7 +2434,7 @@ func testTransactionJournaling(t *testing.T, nolocals bool) { statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1) blockchain = newTestBlockChain(1000000, statedb, new(event.Feed)) - pool = NewTxPool(config, params.TestChainConfig, blockchain) + pool = New(config, params.TestChainConfig, blockchain) pending, queued = pool.Stats() if queued != 0 { @@ -2457,7 +2460,7 @@ func testTransactionJournaling(t *testing.T, nolocals bool) { statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1) blockchain = newTestBlockChain(1000000, statedb, new(event.Feed)) - pool = NewTxPool(config, params.TestChainConfig, blockchain) + pool = New(config, params.TestChainConfig, blockchain) pending, queued = pool.Stats() if pending != 0 { @@ -2488,7 +2491,7 @@ func TestStatusCheck(t *testing.T) { statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(db)) blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) - pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) + pool := New(testTxPoolConfig, params.TestChainConfig, blockchain) defer pool.Stop() // Create the test accounts to check various transaction statuses with @@ -2698,75 +2701,75 @@ func BenchmarkMultiAccountBatchInsert(b *testing.B) { func TestSetGasPrice(t *testing.T) { testCases := []struct { name string - price *big.Int + tip *big.Int wantErr error description string }{ // Invalid cases - should be rejected { - name: "nil gas price", - price: nil, - wantErr: errors.New("reject nil gas price"), + name: "nil gas tip", + tip: nil, + wantErr: errors.New("reject nil gas tip"), description: "nil pointer should be rejected gracefully", }, { - name: "negative gas price", - price: big.NewInt(-1), - wantErr: fmt.Errorf("reject negative gas price: %v", big.NewInt(-1)), + name: "negative gas tip", + tip: big.NewInt(-1), + wantErr: fmt.Errorf("reject negative gas tip: %v", big.NewInt(-1)), description: "negative value should be rejected", }, { name: "exceeds maximum by 1", - price: new(big.Int).Add(defaultMaxPrice, big.NewInt(1)), - wantErr: fmt.Errorf("reject too high gas price: %v, maximum: %v", new(big.Int).Add(defaultMaxPrice, big.NewInt(1)), defaultMaxPrice), + tip: new(big.Int).Add(defaultMaxTip, big.NewInt(1)), + wantErr: fmt.Errorf("reject too high gas tip: %v, maximum: %v", new(big.Int).Add(defaultMaxTip, big.NewInt(1)), defaultMaxTip), description: "value exceeding 1000 GWei should be rejected", }, { name: "exceeds maximum significantly", - price: big.NewInt(10000 * params.GWei), - wantErr: fmt.Errorf("reject too high gas price: %v, maximum: %v", big.NewInt(10000*params.GWei), defaultMaxPrice), + tip: big.NewInt(10000 * params.GWei), + wantErr: fmt.Errorf("reject too high gas tip: %v, maximum: %v", big.NewInt(10000*params.GWei), defaultMaxTip), description: "value far exceeding maximum should be rejected", }, // Valid cases - should be accepted { - name: "zero gas price", - price: big.NewInt(0), + name: "zero gas tip", + tip: big.NewInt(0), wantErr: nil, description: "zero is valid as it's not negative", }, { name: "minimum positive value", - price: big.NewInt(1), + tip: big.NewInt(1), wantErr: nil, description: "minimum positive value should be accepted", }, { name: "1 GWei", - price: big.NewInt(params.GWei), + tip: big.NewInt(params.GWei), wantErr: nil, description: "1 GWei should be accepted", }, { name: "100 GWei", - price: big.NewInt(100 * params.GWei), + tip: big.NewInt(100 * params.GWei), wantErr: nil, description: "100 GWei should be accepted", }, { name: "500 GWei", - price: big.NewInt(500 * params.GWei), + tip: big.NewInt(500 * params.GWei), wantErr: nil, description: "500 GWei should be accepted", }, { name: "just below maximum", - price: new(big.Int).Sub(defaultMaxPrice, big.NewInt(1)), + tip: new(big.Int).Sub(defaultMaxTip, big.NewInt(1)), wantErr: nil, description: "value just below maximum should be accepted", }, { name: "exactly at maximum", - price: defaultMaxPrice, + tip: defaultMaxTip, wantErr: nil, description: "exactly 1000 GWei should be accepted", }, @@ -2778,9 +2781,9 @@ func TestSetGasPrice(t *testing.T) { pool, _ := setupPool() defer pool.Stop() - oldPrice := pool.GasPrice() - haveErr := pool.SetGasPrice(tc.price) - newPrice := pool.GasPrice() + oldPrice := pool.gasTip.Load() + haveErr := pool.SetGasTip(tc.tip) + newPrice := pool.gasTip.Load() if tc.wantErr != nil { // Invalid case: should return error and price should remain unchanged @@ -2797,8 +2800,8 @@ func TestSetGasPrice(t *testing.T) { if haveErr != nil { t.Errorf("%s: Expected no error, got: %v", tc.description, haveErr) } - if newPrice.Cmp(tc.price) != 0 { - t.Errorf("%s: Expected gas price to be set to %v, got %v", tc.description, tc.price, newPrice) + if newPrice.Cmp(tc.tip) != 0 { + t.Errorf("%s: Expected gas price to be set to %v, got %v", tc.description, tc.tip, newPrice) } } }) diff --git a/core/txpool/validation.go b/core/txpool/validation.go new file mode 100644 index 0000000000..daf5b106a0 --- /dev/null +++ b/core/txpool/validation.go @@ -0,0 +1,238 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package txpool + +import ( + "fmt" + "math/big" + + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/core" + "github.com/XinFinOrg/XDPoSChain/core/state" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/log" + "github.com/XinFinOrg/XDPoSChain/params" +) + +// ValidationOptions define certain differences between transaction validation +// across the different pools without having to duplicate those checks. +type ValidationOptions struct { + Config *params.ChainConfig // Chain configuration to selectively validate based on current fork rules + + Accept uint8 // Bitmap of transaction types that should be accepted for the calling pool + MaxSize uint64 // Maximum size of a transaction that the caller can meaningfully handle + MinTip *big.Int // Minimum gas tip needed to allow a transaction into the caller pool + + NotSigner func(addr common.Address) bool +} + +// ValidateTransaction is a helper method to check whether a transaction is valid +// according to the consensus rules, but does not check state-dependent validation +// (balance, nonce, etc). +// +// This check is public to allow different transaction pools to check the basic +// rules without duplicating code and running the risk of missed updates. +func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types.Signer, opts *ValidationOptions) error { + // Ensure transactions not implemented by the calling pool are rejected + if opts.Accept&(1< opts.MaxSize { + return fmt.Errorf("%w: transaction size %v, limit %v", ErrOversizedData, tx.Size(), opts.MaxSize) + } + // Ensure only transactions that have been enabled are accepted + if !opts.Config.IsEIP1559(head.Number) && tx.Type() != types.LegacyTxType { + return fmt.Errorf("%w: type %d rejected, pool not yet in EIP1559", core.ErrTxTypeNotSupported, tx.Type()) + } + // Check whether the init code size has been exceeded + if opts.Config.IsEIP1559(head.Number) && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize { + return fmt.Errorf("%w: code size %v, limit %v", core.ErrMaxInitCodeSizeExceeded, len(tx.Data()), params.MaxInitCodeSize) + } + // Transactions can't be negative. This may never happen using RLP decoded + // transactions but may occur for transactions created using the RPC. + if tx.Value().Sign() < 0 { + return ErrNegativeValue + } + // Ensure the transaction doesn't exceed the current block limit gas + if head.GasLimit < tx.Gas() { + return ErrGasLimit + } + // Sanity check for extremely large numbers (supported by RLP or RPC) + if tx.GasFeeCap().BitLen() > 256 { + return core.ErrFeeCapVeryHigh + } + if tx.GasTipCap().BitLen() > 256 { + return core.ErrTipVeryHigh + } + // Ensure gasFeeCap is greater than or equal to gasTipCap + if tx.GasFeeCapIntCmp(tx.GasTipCap()) < 0 { + return core.ErrTipAboveFeeCap + } + // Make sure the transaction is signed properly + from, err := types.Sender(signer, tx) + if err != nil { + return ErrInvalidSender + } + // Limit nonce to 2^64-1 per EIP-2681 + if tx.Nonce()+1 < tx.Nonce() { + return core.ErrNonceMax + } + isSpecial := tx.IsSpecialTransaction() + // Ensure the gasprice is high enough to cover the requirement of + // the calling pool and/or block producer + if tx.GasTipCapIntCmp(opts.MinTip) < 0 { + // For special transactions, only check if the sender is not a signer + // For regular transactions, always check (to preserve old logic) + if !isSpecial || opts.NotSigner(from) { + return fmt.Errorf("%w: tip needed %v, tip permitted %v", ErrUnderpriced, opts.MinTip, tx.GasTipCap()) + } + } + // Skip further checks for special transactions + if isSpecial { + return nil + } + // Check zero gas price. + if tx.GasPrice().Sign() == 0 { + return ErrZeroGasPrice + } + // Ensure the transaction has more gas than the bare minimum needed to + // cover the transaction metadata + intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, true, opts.Config.IsEIP1559(head.Number)) + if err != nil { + return err + } + if tx.Gas() < intrGas { + return fmt.Errorf("%w: needed %v, allowed %v", core.ErrIntrinsicGas, intrGas, tx.Gas()) + } + return nil +} + +// ValidationOptionsWithState define certain differences between stateful transaction +// validation across the different pools without having to duplicate those checks. +type ValidationOptionsWithState struct { + State *state.StateDB // State database to check nonces and balances against + + // FirstNonceGap is an optional callback to retrieve the first nonce gap in + // the list of pooled transactions of a specific account. If this method is + // set, nonce gaps will be checked and forbidden. If this method is not set, + // nonce gaps will be ignored and permitted. + FirstNonceGap func(addr common.Address) uint64 + + // ExistingExpenditure is a mandatory callback to retrieve the cumulative + // cost of the already pooled transactions to check for overdrafts. + ExistingExpenditure func(addr common.Address) *big.Int + + // ExistingCost is a mandatory callback to retrieve an already pooled + // transaction's cost with the given nonce to check for overdrafts. + ExistingCost func(addr common.Address, nonce uint64) *big.Int + + Trc21FeeCapacity map[common.Address]*big.Int + + PendingNonce func(addr common.Address) uint64 + + CurrentNumber func() *big.Int +} + +// ValidateTransactionWithState is a helper method to check whether a transaction +// is valid according to the pool's internal state checks (balance, nonce, gaps). +// +// This check is public to allow different transaction pools to check the stateful +// rules without duplicating code and running the risk of missed updates. +func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, opts *ValidationOptionsWithState) error { + // Ensure the transaction adheres to nonce ordering + from, err := signer.Sender(tx) // already validated (and cached), but cleaner to check + if err != nil { + log.Error("Transaction sender recovery failed", "err", err) + return err + } + // Ensure the transaction nonce is in a valid range + next := opts.State.GetNonce(from) + if next > tx.Nonce() { + return fmt.Errorf("%w: next nonce %v, tx nonce %v", core.ErrNonceTooLow, next, tx.Nonce()) + } + if opts.PendingNonce(from)+common.LimitThresholdNonceInQueue < tx.Nonce() { + return core.ErrNonceTooHigh + } + // Ensure the transaction doesn't produce a nonce gap in pools that do not + // support arbitrary orderings + if opts.FirstNonceGap != nil { + if gap := opts.FirstNonceGap(from); gap < tx.Nonce() { + return fmt.Errorf("%w: tx nonce %v, gapped nonce %v", core.ErrNonceTooHigh, tx.Nonce(), gap) + } + } + // Ensure the transactor has enough funds to cover the transaction costs + var ( + balance = opts.State.GetBalance(from) + cost = tx.Cost() + feeCapacity = big.NewInt(0) + number = opts.CurrentNumber() + to = tx.To() + ) + if to != nil { + if value, ok := opts.Trc21FeeCapacity[*to]; ok { + feeCapacity = value + if !opts.State.ValidateTRC21Tx(from, *to, tx.Data()) { + return core.ErrInsufficientFunds + } + cost = tx.TxCost(number) + } + } + newBalance := new(big.Int).Add(balance, feeCapacity) + if newBalance.Cmp(cost) < 0 { + return fmt.Errorf("%w: balance %v, tx cost %v, overshot %v", core.ErrInsufficientFunds, balance, cost, new(big.Int).Sub(cost, balance)) + } + + // Ensure the transactor has enough funds to cover for replacements or nonce + // expansions without overdrafts + spent := opts.ExistingExpenditure(from) + if prev := opts.ExistingCost(from, tx.Nonce()); prev != nil { + bump := new(big.Int).Sub(cost, prev) + need := new(big.Int).Add(spent, bump) + if newBalance.Cmp(need) < 0 { + return fmt.Errorf("%w: balance %v, queued cost %v, tx bumped %v, overshot %v", core.ErrInsufficientFunds, balance, spent, bump, new(big.Int).Sub(need, newBalance)) + } + } else { + need := new(big.Int).Add(spent, cost) + if newBalance.Cmp(need) < 0 { + return fmt.Errorf("%w: balance %v, queued cost %v, tx cost %v, overshot %v", core.ErrInsufficientFunds, balance, spent, cost, new(big.Int).Sub(need, newBalance)) + } + } + + // Ensure sender and receiver are not in black list + if number == nil || number.Cmp(new(big.Int).SetUint64(common.BlackListHFNumber)) >= 0 { + // check if sender is in black list + if common.IsInBlacklist(tx.From()) { + return fmt.Errorf("reject transaction with sender in black-list: %v", tx.From().Hex()) + } + // check if receiver is in black list + if common.IsInBlacklist(to) { + return fmt.Errorf("reject transaction with receiver in black-list: %v", to.Hex()) + } + } + + // Validate gas price + if !tx.IsSpecialTransaction() { + minGasPrice := common.GetMinGasPrice(number) + if tx.GasPrice().Cmp(minGasPrice) < 0 { + return ErrUnderMinGasPrice + } + } + + return nil +} diff --git a/eth/api_miner.go b/eth/api_miner.go index b316d950da..2213058a90 100644 --- a/eth/api_miner.go +++ b/eth/api_miner.go @@ -62,10 +62,11 @@ func (api *MinerAPI) Start(threads *int) error { if !api.e.IsStaking() { // Propagate the initial price point to the transaction pool api.e.lock.RLock() + // api.e.gasPrice is from MinerGasPriceFlag price := api.e.gasPrice api.e.lock.RUnlock() - api.e.txPool.SetGasPrice(price) + api.e.txPool.SetGasTip(price) return api.e.StartStaking(true) } return nil @@ -97,7 +98,7 @@ func (api *MinerAPI) SetGasPrice(gasPrice hexutil.Big) bool { api.e.gasPrice = (*big.Int)(&gasPrice) api.e.lock.Unlock() - err := api.e.txPool.SetGasPrice((*big.Int)(&gasPrice)) + err := api.e.txPool.SetGasTip((*big.Int)(&gasPrice)) return err == nil } diff --git a/eth/backend.go b/eth/backend.go index 6095b816b3..ebe31ec139 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -265,7 +265,7 @@ func New(stack *node.Node, config *ethconfig.Config, XDCXServ *XDCx.XDCX, lendin if config.TxPool.Journal != "" { config.TxPool.Journal = stack.ResolvePath(config.TxPool.Journal) } - eth.txPool = txpool.NewTxPool(config.TxPool, eth.chainConfig, eth.blockchain) + eth.txPool = txpool.New(config.TxPool, eth.chainConfig, eth.blockchain) eth.orderPool = txpool.NewOrderPool(eth.chainConfig, eth.blockchain) eth.lendingPool = txpool.NewLendingPool(eth.chainConfig, eth.blockchain)