From 39f0ef9eba0c68a084c4962aefd88d3e46d10430 Mon Sep 17 00:00:00 2001 From: Qi Zhou Date: Thu, 24 Oct 2024 06:43:09 +0000 Subject: [PATCH 1/3] fix gas balance validation issue when balance < gas cost < SGT balance --- core/state_transition.go | 27 +++++++++++++++++++++++++++ core/txpool/blobpool/blobpool.go | 2 +- core/txpool/legacypool/legacypool.go | 4 ++-- core/txpool/validation.go | 12 ++---------- eth/gasestimator/gasestimator.go | 2 +- 5 files changed, 33 insertions(+), 14 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index 9112cc8026..99aef6cbf9 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -279,6 +279,33 @@ func (st *StateTransition) GetSoulBalance(account common.Address) *uint256.Int { return balance } +func GetEffectiveGasBalance(state vm.StateDB, chainconfig *params.ChainConfig, account common.Address) *uint256.Int { + bal, sgtBal := GetGasBalances(state, chainconfig, account) + if bal.Cmp(sgtBal) < 0 { + return sgtBal + } + + return bal +} + +func GetGasBalances(state vm.StateDB, chainconfig *params.ChainConfig, account common.Address) (*uint256.Int, *uint256.Int) { + balance := state.GetBalance(account) + if chainconfig != nil && chainconfig.IsOptimism() && chainconfig.Optimism.UseSoulGasToken { + sgtBalanceSlot := TargetSGTBalanceSlot(account) + sgtBalanceValue := state.GetState(types.SoulGasTokenAddr, sgtBalanceSlot) + sgtBalance := new(uint256.Int).SetBytes(sgtBalanceValue[:]) + + return balance, sgtBalance + } + + return balance, uint256.NewInt(0) +} + +func GetGasBalancesInBig(state vm.StateDB, chainconfig *params.ChainConfig, account common.Address) (*big.Int, *big.Int) { + bal, sgtBal := GetGasBalances(state, chainconfig, account) + return bal.ToBig(), sgtBal.ToBig() +} + func (st *StateTransition) SubSoulBalance(account common.Address, amount *big.Int, reason tracing.BalanceChangeReason) (err error) { current := st.GetSoulBalance(account).ToBig() if current.Cmp(amount) < 0 { diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index e7bb8edad1..05733b7202 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -668,7 +668,7 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6 // Ensure that there's no over-draft, this is expected to happen when some // transactions get included without publishing on the network var ( - balance = p.state.GetBalance(addr) + balance = core.GetEffectiveGasBalance(p.state, p.chain.Config(), addr) spent = p.spent[addr] ) if spent.Cmp(balance) > 0 { diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 55a4f70379..6adc23402f 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -1520,7 +1520,7 @@ func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.T pool.all.Remove(hash) } log.Trace("Removed old queued transactions", "count", len(forwards)) - balance := pool.currentState.GetBalance(addr) + balance := core.GetEffectiveGasBalance(pool.currentState, pool.chainconfig, addr) balance = pool.reduceBalanceByL1Cost(list, balance) // Drop all transactions that are too costly (low balance or out of gas) drops, _ := list.Filter(balance, gasLimit) @@ -1723,7 +1723,7 @@ func (pool *LegacyPool) demoteUnexecutables() { pool.all.Remove(hash) log.Trace("Removed old pending transaction", "hash", hash) } - balance := pool.currentState.GetBalance(addr) + balance := core.GetEffectiveGasBalance(pool.currentState, pool.chainconfig, addr) balance = pool.reduceBalanceByL1Cost(list, balance) // Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later drops, invalids := list.Filter(balance, gasLimit) diff --git a/core/txpool/validation.go b/core/txpool/validation.go index d9e36fbee5..6e76523ec7 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -29,7 +29,6 @@ import ( "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" - "github.com/holiman/uint256" ) // L1 Info Gas Overhead is the amount of gas the the L1 info deposit consumes. @@ -254,17 +253,10 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op } // Ensure the transactor has enough funds to cover the transaction costs var ( - balance = opts.State.GetBalance(from).ToBig() - cost = tx.Cost() + balance, sgtBalance = core.GetGasBalancesInBig(opts.State, opts.Chainconfig, from) + cost = tx.Cost() ) - sgtBalance := new(big.Int) - if opts.Chainconfig != nil && opts.Chainconfig.IsOptimism() && opts.Chainconfig.Optimism.UseSoulGasToken { - sgtBalanceSlot := core.TargetSGTBalanceSlot(from) - sgtBalanceValue := opts.State.GetState(types.SoulGasTokenAddr, sgtBalanceSlot) - sgtBalance = new(uint256.Int).SetBytes(sgtBalanceValue[:]).ToBig() - } - if opts.L1CostFn != nil { if l1Cost := opts.L1CostFn(tx.RollupCostData()); l1Cost != nil { // add rollup cost cost = cost.Add(cost, l1Cost) diff --git a/eth/gasestimator/gasestimator.go b/eth/gasestimator/gasestimator.go index b05f9f200b..3037c5549b 100644 --- a/eth/gasestimator/gasestimator.go +++ b/eth/gasestimator/gasestimator.go @@ -71,7 +71,7 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin } // Recap the highest gas limit with account's available balance. if feeCap.BitLen() != 0 { - balance := opts.State.GetBalance(call.From).ToBig() + balance := core.GetEffectiveGasBalance(opts.State, opts.Config, call.From).ToBig() available := balance if call.Value != nil { From 80a5dd334c877a696747d35d02d33acdd749fe42 Mon Sep 17 00:00:00 2001 From: Qi Zhou Date: Thu, 24 Oct 2024 16:22:49 +0000 Subject: [PATCH 2/3] fix validation with tx.value > 0 --- core/state_transition.go | 16 ++++++++++++---- core/txpool/blobpool/blobpool.go | 5 +++-- core/txpool/legacypool/legacypool.go | 8 ++++++-- core/txpool/validation.go | 21 +++++++++++---------- core/types/transaction.go | 9 +++++++++ eth/gasestimator/gasestimator.go | 11 ++++------- 6 files changed, 45 insertions(+), 25 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index 99aef6cbf9..7b541b5f34 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -279,13 +279,21 @@ func (st *StateTransition) GetSoulBalance(account common.Address) *uint256.Int { return balance } -func GetEffectiveGasBalance(state vm.StateDB, chainconfig *params.ChainConfig, account common.Address) *uint256.Int { - bal, sgtBal := GetGasBalances(state, chainconfig, account) +// Get the effective balance to pay gas +func GetEffectiveGasBalance(state vm.StateDB, chainconfig *params.ChainConfig, account common.Address, value *big.Int) (*big.Int, error) { + bal, sgtBal := GetGasBalancesInBig(state, chainconfig, account) + if value == nil { + value = big.NewInt(0) + } + if bal.Cmp(value) < 0 { + return nil, ErrInsufficientFundsForTransfer + } + bal.Sub(bal, value) if bal.Cmp(sgtBal) < 0 { - return sgtBal + return sgtBal, nil } - return bal + return bal, nil } func GetGasBalances(state vm.StateDB, chainconfig *params.ChainConfig, account common.Address) (*uint256.Int, *uint256.Int) { diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 05733b7202..38f46868ad 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -668,8 +668,9 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6 // Ensure that there's no over-draft, this is expected to happen when some // transactions get included without publishing on the network var ( - balance = core.GetEffectiveGasBalance(p.state, p.chain.Config(), addr) - spent = p.spent[addr] + balanceInBig, _ = core.GetEffectiveGasBalance(p.state, p.chain.Config(), addr, nil) + balance = uint256.MustFromBig(balanceInBig) + spent = p.spent[addr] ) if spent.Cmp(balance) > 0 { // Evict the highest nonce transactions until the pending set falls under diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 6adc23402f..9849fab929 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -1520,7 +1520,9 @@ func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.T pool.all.Remove(hash) } log.Trace("Removed old queued transactions", "count", len(forwards)) - balance := core.GetEffectiveGasBalance(pool.currentState, pool.chainconfig, addr) + balance, sgtBalance := core.GetGasBalances(pool.currentState, pool.chainconfig, addr) + // TODO: we may need a better filter such as tx.value < acc.balance + balance = balance.Add(balance, sgtBalance) balance = pool.reduceBalanceByL1Cost(list, balance) // Drop all transactions that are too costly (low balance or out of gas) drops, _ := list.Filter(balance, gasLimit) @@ -1723,7 +1725,9 @@ func (pool *LegacyPool) demoteUnexecutables() { pool.all.Remove(hash) log.Trace("Removed old pending transaction", "hash", hash) } - balance := core.GetEffectiveGasBalance(pool.currentState, pool.chainconfig, addr) + balance, sgtBalance := core.GetGasBalances(pool.currentState, pool.chainconfig, addr) + // TODO: we may need a better filter such as tx.value < acc.balance + balance = balance.Add(balance, sgtBalance) balance = pool.reduceBalanceByL1Cost(list, balance) // Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later drops, invalids := list.Filter(balance, gasLimit) diff --git a/core/txpool/validation.go b/core/txpool/validation.go index 6e76523ec7..1f2ecffcf1 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -252,18 +252,19 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op } } // Ensure the transactor has enough funds to cover the transaction costs - var ( - balance, sgtBalance = core.GetGasBalancesInBig(opts.State, opts.Chainconfig, from) - cost = tx.Cost() - ) + balance, err := core.GetEffectiveGasBalance(opts.State, opts.Chainconfig, from, tx.Value()) + if err != nil { + return fmt.Errorf("%w: balance %v, tx value %v", err, balance, tx.Value()) + } + cost := tx.GasCost() if opts.L1CostFn != nil { if l1Cost := opts.L1CostFn(tx.RollupCostData()); l1Cost != nil { // add rollup cost cost = cost.Add(cost, l1Cost) } } - if balance.Cmp(cost) < 0 && sgtBalance.Cmp(cost) < 0 { - return fmt.Errorf("%w: balance %v, sgt balance:%v, tx cost %v, overshot %v", core.ErrInsufficientFunds, balance, sgtBalance, cost, new(big.Int).Sub(cost, balance)) + if balance.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 @@ -271,13 +272,13 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op if prev := opts.ExistingCost(from, tx.Nonce()); prev != nil { bump := new(big.Int).Sub(cost, prev) need := new(big.Int).Add(spent, bump) - if balance.Cmp(need) < 0 && sgtBalance.Cmp(need) < 0 { - return fmt.Errorf("%w: balance %v, sgt balance:%v, queued cost %v, tx bumped %v, overshot %v", core.ErrInsufficientFunds, balance, sgtBalance, spent, bump, new(big.Int).Sub(need, balance)) + if balance.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, balance)) } } else { need := new(big.Int).Add(spent, cost) - if balance.Cmp(need) < 0 && sgtBalance.Cmp(need) < 0 { - return fmt.Errorf("%w: balance %v, sgt balance:%v, queued cost %v, tx cost %v, overshot %v", core.ErrInsufficientFunds, balance, sgtBalance, spent, cost, new(big.Int).Sub(need, balance)) + if balance.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, balance)) } // Transaction takes a new nonce value out of the pool. Ensure it doesn't // overflow the number of permitted transactions from a single account diff --git a/core/types/transaction.go b/core/types/transaction.go index aa641a22ce..80e612a0ea 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -382,6 +382,15 @@ func (tx *Transaction) Cost() *big.Int { return total } +// Cost returns (gas * gasPrice) + (blobGas * blobGasPrice). +func (tx *Transaction) GasCost() *big.Int { + total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas())) + if tx.Type() == BlobTxType { + total.Add(total, new(big.Int).Mul(tx.BlobGasFeeCap(), new(big.Int).SetUint64(tx.BlobGas()))) + } + return total +} + // RollupCostData caches the information needed to efficiently compute the data availability fee func (tx *Transaction) RollupCostData() RollupCostData { if tx.Type() == DepositTxType { diff --git a/eth/gasestimator/gasestimator.go b/eth/gasestimator/gasestimator.go index 3037c5549b..9e0eeabaf6 100644 --- a/eth/gasestimator/gasestimator.go +++ b/eth/gasestimator/gasestimator.go @@ -71,15 +71,12 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin } // Recap the highest gas limit with account's available balance. if feeCap.BitLen() != 0 { - balance := core.GetEffectiveGasBalance(opts.State, opts.Config, call.From).ToBig() + balance, err := core.GetEffectiveGasBalance(opts.State, opts.Config, call.From, call.Value) + if err != nil { + return 0, nil, err + } available := balance - if call.Value != nil { - if call.Value.Cmp(available) >= 0 { - return 0, nil, core.ErrInsufficientFundsForTransfer - } - available.Sub(available, call.Value) - } if opts.Config.IsCancun(opts.Header.Number, opts.Header.Time) && len(call.BlobHashes) > 0 { blobGasPerBlob := new(big.Int).SetInt64(params.BlobTxBlobGasPerBlob) blobBalanceUsage := new(big.Int).SetInt64(int64(len(call.BlobHashes))) From dcbe97a473fdcc753756d1ba59d70c1c75193a6b Mon Sep 17 00:00:00 2001 From: Qi Zhou Date: Thu, 24 Oct 2024 23:50:06 +0000 Subject: [PATCH 3/3] fix blobpool filtering & add more info during start --- core/txpool/blobpool/blobpool.go | 9 ++++----- params/config.go | 7 +++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 38f46868ad..011db96699 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -667,11 +667,10 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6 } // Ensure that there's no over-draft, this is expected to happen when some // transactions get included without publishing on the network - var ( - balanceInBig, _ = core.GetEffectiveGasBalance(p.state, p.chain.Config(), addr, nil) - balance = uint256.MustFromBig(balanceInBig) - spent = p.spent[addr] - ) + balance, sgtBalance := core.GetGasBalances(p.state, p.chain.Config(), addr) + // TODO: we may need a better filter such as tx.value < acc.balance + balance = balance.Add(balance, sgtBalance) + spent := p.spent[addr] if spent.Cmp(balance) > 0 { // Evict the highest nonce transactions until the pending set falls under // the account's available balance diff --git a/params/config.go b/params/config.go index 5c15dcf131..bc8c375a7c 100644 --- a/params/config.go +++ b/params/config.go @@ -523,6 +523,13 @@ func (c *ChainConfig) Description() string { if c.InteropTime != nil { banner += fmt.Sprintf(" - Interop: @%-10v\n", *c.InteropTime) } + if c.L2BlobTime != nil { + banner += fmt.Sprintf(" - L2BLob: @%-10v\n", *c.L2BlobTime) + } + banner += "\n" + if c.Optimism != nil { + banner += fmt.Sprintf("SGT: %t, Back by native %t", c.Optimism.UseSoulGasToken, c.Optimism.IsSoulBackedByNative) + } return banner }