diff --git official op-geth/cmd/geth/config.go SWC op-geth/cmd/geth/config.go
index df12a831b62fc21d4b73a6b56d50b82888b857de..24ba63799ee1fad2d21c4922d01af5b76269d888 100644
--- official op-geth/cmd/geth/config.go
+++ SWC op-geth/cmd/geth/config.go
@@ -224,6 +224,7 @@ cfg.Eth.OverrideVerkle = &v
}
backend, eth := utils.RegisterEthService(stack, &cfg.Eth)
+ stack.APIBackend = backend
// Create gauge with geth system and build information
if eth != nil { // The 'eth' backend may be nil in light mode
diff --git official op-geth/cmd/geth/main.go SWC op-geth/cmd/geth/main.go
index 30c7df3b84c347e116ddd3b74de1a9bd9dfc1c79..39928df953f4484def9432c2d416e9c84601949d 100644
--- official op-geth/cmd/geth/main.go
+++ SWC op-geth/cmd/geth/main.go
@@ -180,8 +180,11 @@ }, utils.NetworkFlags, utils.DatabaseFlags)
rpcFlags = []cli.Flag{
utils.HTTPEnabledFlag,
+ utils.HTTPSGTEnabledFlag,
utils.HTTPListenAddrFlag,
+ utils.HTTPSGTListenAddrFlag,
utils.HTTPPortFlag,
+ utils.HTTPSGTPortFlag,
utils.HTTPCORSDomainFlag,
utils.AuthListenFlag,
utils.AuthPortFlag,
diff --git official op-geth/cmd/utils/flags.go SWC op-geth/cmd/utils/flags.go
index 8eda534783632a780823c576fd0389dbdddd8148..3ba50c3e673fc842aca8ce4da17a535ce5f828e3 100644
--- official op-geth/cmd/utils/flags.go
+++ SWC op-geth/cmd/utils/flags.go
@@ -675,16 +675,33 @@ Name: "http",
Usage: "Enable the HTTP-RPC server",
Category: flags.APICategory,
}
+ HTTPSGTEnabledFlag = &cli.BoolFlag{
+ Name: "httpsgt",
+ Usage: "Enable the HTTP-RPC server for Soul Gas Token",
+ Category: flags.APICategory,
+ }
HTTPListenAddrFlag = &cli.StringFlag{
Name: "http.addr",
Usage: "HTTP-RPC server listening interface",
Value: node.DefaultHTTPHost,
Category: flags.APICategory,
}
+ HTTPSGTListenAddrFlag = &cli.StringFlag{
+ Name: "httpsgt.addr",
+ Usage: "HTTP-RPC server listening interface for Soul Gas Token",
+ Value: node.DefaultHTTPSGTHost,
+ Category: flags.APICategory,
+ }
HTTPPortFlag = &cli.IntFlag{
Name: "http.port",
Usage: "HTTP-RPC server listening port",
Value: node.DefaultHTTPPort,
+ Category: flags.APICategory,
+ }
+ HTTPSGTPortFlag = &cli.IntFlag{
+ Name: "httpsgt.port",
+ Usage: "HTTP-RPC server listening port for Soul Gas Token",
+ Value: node.DefaultHTTPSGTPort,
Category: flags.APICategory,
}
HTTPCORSDomainFlag = &cli.StringFlag{
@@ -1271,9 +1288,19 @@ if ctx.IsSet(HTTPListenAddrFlag.Name) {
cfg.HTTPHost = ctx.String(HTTPListenAddrFlag.Name)
}
}
+ if ctx.Bool(HTTPSGTEnabledFlag.Name) {
+ if ctx.IsSet(HTTPSGTListenAddrFlag.Name) {
+ cfg.HTTPSGTHost = ctx.String(HTTPSGTListenAddrFlag.Name)
+ } else if cfg.HTTPSGTHost == "" {
+ cfg.HTTPSGTHost = "127.0.0.1"
+ }
+ }
if ctx.IsSet(HTTPPortFlag.Name) {
cfg.HTTPPort = ctx.Int(HTTPPortFlag.Name)
+ }
+ if ctx.IsSet(HTTPSGTPortFlag.Name) {
+ cfg.HTTPSGTPort = ctx.Int(HTTPSGTPortFlag.Name)
}
if ctx.IsSet(AuthListenFlag.Name) {
diff --git official op-geth/core/state_transition.go SWC op-geth/core/state_transition.go
index a3b05705c9e4461b05f806c4239d27f934220a0f..46570f3d7966a22c6c5b63e257f56261feb30066 100644
--- official op-geth/core/state_transition.go
+++ SWC op-geth/core/state_transition.go
@@ -21,11 +21,13 @@ "fmt"
"math"
"math/big"
+ "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
cmath "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
@@ -231,6 +233,19 @@ gasRemaining uint64
initialGas uint64
state vm.StateDB
evm *vm.EVM
+
+ // nil means SGT is not used at all
+ usedSGTBalance *uint256.Int
+ // should not be used if usedSGTBalance is nil;
+ // only set when: 1. usedSGTBalance is non-nil 2. used native balance is gt 0
+ usedNativeBalance *uint256.Int
+
+ // these are set once for checking gas formula only
+ boughtGas *uint256.Int
+ refundedGas *uint256.Int
+ tipFee *uint256.Int
+ baseFee *uint256.Int
+ l1Fee *uint256.Int
}
// NewStateTransition initialises and returns a new state transition object.
@@ -243,6 +258,78 @@ state: evm.StateDB,
}
}
+func (st *StateTransition) checkGasFormula() error {
+ if st.boughtGas.Cmp(
+ new(uint256.Int).Add(
+ st.refundedGas, new(uint256.Int).Add(
+ st.tipFee, new(uint256.Int).Add(
+ st.baseFee, st.l1Fee)))) != 0 {
+ return fmt.Errorf("gas formula doesn't hold: boughtGas(%v) != refundedGas(%v) + tipFee(%v) + baseFee(%v) + l1Fee(%v)", st.boughtGas, st.refundedGas, st.tipFee, st.baseFee, st.l1Fee)
+ }
+ return nil
+}
+
+func (st *StateTransition) collectableNativeBalance(amount *uint256.Int) *uint256.Int {
+ // we burn the token if gas is from SoulGasToken which is not backed by native
+ if st.usedSGTBalance != nil && st.evm.ChainConfig().IsOptimism() && !st.evm.ChainConfig().Optimism.IsSoulBackedByNative {
+ _, amount = st.distributeGas(amount, st.usedSGTBalance, st.usedNativeBalance)
+ }
+ return amount
+}
+
+// distributeGas distributes the gas according to the priority:
+//
+// first pool1, then pool2.
+//
+// In more detail:
+// split amount among two pools, first pool1, then pool2, where poolx means max amount for pool x.
+// quotax is the amount distributed to pool x.
+//
+// note: the returned values are always non-nil.
+func (st *StateTransition) distributeGas(amount, pool1, pool2 *uint256.Int) (quota1, quota2 *uint256.Int) {
+ if amount == nil {
+ panic("amount should not be nil")
+ }
+ if st.usedSGTBalance == nil {
+ panic("should not happen when usedSGTBalance is nil")
+ }
+ if pool1 == nil {
+ // pool1 empty, all to pool2
+ quota1 = new(uint256.Int)
+ quota2 = amount.Clone()
+
+ pool2.Sub(pool2, quota2)
+ return
+ }
+ if pool2 == nil {
+ // pool2 empty, all to pool1
+ quota1 = amount.Clone()
+ quota2 = new(uint256.Int)
+
+ pool1.Sub(pool1, quota1)
+ return
+ }
+
+ // from here, both pool1 and pool2 are non-nil
+
+ if amount.Cmp(pool1) >= 0 {
+ // partial pool1, remaining to pool2
+ quota1 = pool1.Clone()
+ quota2 = new(uint256.Int).Sub(amount, quota1)
+
+ pool1.Clear()
+ pool2.Sub(pool2, quota2)
+ } else {
+ // all to pool1
+ quota1 = amount.Clone()
+ quota2 = new(uint256.Int)
+
+ pool1.Sub(pool1, quota1)
+ }
+
+ return
+}
+
// to returns the recipient of the message.
func (st *StateTransition) to() common.Address {
if st.msg == nil || st.msg.To == nil /* contract creation */ {
@@ -251,6 +338,98 @@ }
return *st.msg.To
}
+const (
+ // should keep it in sync with the balances field of SoulGasToken contract
+ BalancesSlot = uint64(51)
+)
+
+var (
+ slotArgs abi.Arguments
+)
+
+func init() {
+ uint64Ty, _ := abi.NewType("uint64", "", nil)
+ addressTy, _ := abi.NewType("address", "", nil)
+ slotArgs = abi.Arguments{{Name: "addr", Type: addressTy, Indexed: false}, {Name: "slot", Type: uint64Ty, Indexed: false}}
+}
+
+func TargetSGTBalanceSlot(account common.Address) (slot common.Hash) {
+ data, _ := slotArgs.Pack(account, BalancesSlot)
+ slot = crypto.Keccak256Hash(data)
+ return
+}
+
+func (st *StateTransition) GetSoulBalance(account common.Address) *uint256.Int {
+ slot := TargetSGTBalanceSlot(account)
+ value := st.state.GetState(types.SoulGasTokenAddr, slot)
+ balance := new(uint256.Int)
+ balance.SetBytes(value[:])
+ return balance
+}
+
+// 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, nil
+ }
+
+ return bal, nil
+}
+
+func GetGasBalances(state vm.StateDB, chainconfig *params.ChainConfig, account common.Address) (*uint256.Int, *uint256.Int) {
+ balance := state.GetBalance(account).Clone()
+ 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()
+}
+
+// called by buyGas
+func (st *StateTransition) subSoulBalance(account common.Address, amount *uint256.Int, reason tracing.BalanceChangeReason) (err error) {
+ current := st.GetSoulBalance(account)
+ if current.Cmp(amount) < 0 {
+ return fmt.Errorf("soul balance not enough, current:%v, expect:%v", current, amount)
+ }
+
+ value := current.Sub(current, amount).Bytes32()
+ st.state.SetState(types.SoulGasTokenAddr, TargetSGTBalanceSlot(account), value)
+
+ if st.evm.ChainConfig().IsOptimism() && st.evm.ChainConfig().Optimism.IsSoulBackedByNative {
+ st.state.SubBalance(types.SoulGasTokenAddr, amount, reason)
+ }
+
+ return
+}
+
+// called by refundGas
+func (st *StateTransition) addSoulBalance(account common.Address, amount *uint256.Int, reason tracing.BalanceChangeReason) {
+ current := st.GetSoulBalance(account)
+ value := current.Add(current, amount).Bytes32()
+ st.state.SetState(types.SoulGasTokenAddr, TargetSGTBalanceSlot(account), value)
+
+ if st.evm.ChainConfig().IsOptimism() && st.evm.ChainConfig().Optimism.IsSoulBackedByNative {
+ st.state.AddBalance(types.SoulGasTokenAddr, amount, reason)
+ }
+}
+
func (st *StateTransition) buyGas() error {
mgval := new(big.Int).SetUint64(st.msg.GasLimit)
mgval.Mul(mgval, st.msg.GasPrice)
@@ -283,13 +462,29 @@ blobFee.Mul(blobFee, st.evm.Context.BlobBaseFee)
mgval.Add(mgval, blobFee)
}
}
+
balanceCheckU256, overflow := uint256.FromBig(balanceCheck)
if overflow {
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
}
- if have, want := st.state.GetBalance(st.msg.From), balanceCheckU256; have.Cmp(want) < 0 {
- return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
+
+ nativeBalance := st.state.GetBalance(st.msg.From)
+ var soulBalance *uint256.Int
+ if st.evm.ChainConfig().IsOptimism() && st.evm.ChainConfig().Optimism.UseSoulGasToken {
+ if have, want := nativeBalance.ToBig(), st.msg.Value; have.Cmp(want) < 0 {
+ return fmt.Errorf("%w: address %v have native balance %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
+ }
+
+ soulBalance = st.GetSoulBalance(st.msg.From)
+ if have, want := new(uint256.Int).Add(nativeBalance, soulBalance), balanceCheckU256; have.Cmp(want) < 0 {
+ return fmt.Errorf("%w: address %v have total balance %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
+ }
+ } else {
+ if have, want := st.state.GetBalance(st.msg.From), balanceCheckU256; have.Cmp(want) < 0 {
+ return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
+ }
}
+
if err := st.gp.SubGas(st.msg.GasLimit); err != nil {
return err
}
@@ -300,8 +495,34 @@ }
st.gasRemaining = st.msg.GasLimit
st.initialGas = st.msg.GasLimit
+
mgvalU256, _ := uint256.FromBig(mgval)
- st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy)
+ st.boughtGas = mgvalU256.Clone()
+ if soulBalance == nil {
+ st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy)
+ } else {
+ if mgvalU256.Cmp(soulBalance) <= 0 {
+ err := st.subSoulBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy)
+ if err != nil {
+ return err
+ }
+ st.usedSGTBalance = mgvalU256
+ } else {
+ err := st.subSoulBalance(st.msg.From, soulBalance, tracing.BalanceDecreaseGasBuy)
+ if err != nil {
+ return err
+ }
+ st.usedSGTBalance = soulBalance
+ // when both SGT and native balance are used, we record both amounts for refund.
+ // the priority for refund is: first native, then SGT
+ usedNativeBalance := new(uint256.Int).Sub(mgvalU256, soulBalance)
+ if usedNativeBalance.Sign() > 0 {
+ st.state.SubBalance(st.msg.From, usedNativeBalance, tracing.BalanceDecreaseGasBuy)
+ st.usedNativeBalance = usedNativeBalance
+ }
+ }
+ }
+
return nil
}
@@ -581,13 +802,20 @@ effectiveTip = cmath.BigMin(msg.GasTipCap, new(big.Int).Sub(msg.GasFeeCap, st.evm.Context.BaseFee))
}
effectiveTipU256, _ := uint256.FromBig(effectiveTip)
+ shouldCheckGasFormula := true
if st.evm.Config.NoBaseFee && msg.GasFeeCap.Sign() == 0 && msg.GasTipCap.Sign() == 0 {
// Skip fee payment when NoBaseFee is set and the fee fields
// are 0. This avoids a negative effectiveTip being applied to
// the coinbase when simulating calls.
+ shouldCheckGasFormula = false
} else {
+
fee := new(uint256.Int).SetUint64(st.gasUsed())
fee.Mul(fee, effectiveTipU256)
+
+ st.tipFee = fee.Clone()
+
+ fee = st.collectableNativeBalance(fee)
st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee)
// add the coinbase to the witness iff the fee is greater than 0
@@ -599,19 +827,44 @@ // Check that we are post bedrock to enable op-geth to be able to create pseudo pre-bedrock blocks (these are pre-bedrock, but don't follow l2 geth rules)
// Note optimismConfig will not be nil if rules.IsOptimismBedrock is true
if optimismConfig := st.evm.ChainConfig().Optimism; optimismConfig != nil && rules.IsOptimismBedrock && !st.msg.IsDepositTx {
gasCost := new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.evm.Context.BaseFee)
+
+ if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) {
+ gasCost.Add(gasCost, new(big.Int).Mul(new(big.Int).SetUint64(st.blobGasUsed()), st.evm.Context.BlobBaseFee))
+ }
+
amtU256, overflow := uint256.FromBig(gasCost)
if overflow {
return nil, fmt.Errorf("optimism gas cost overflows U256: %d", gasCost)
}
+ if shouldCheckGasFormula {
+ st.baseFee = amtU256.Clone()
+ }
+
+ amtU256 = st.collectableNativeBalance(amtU256)
st.state.AddBalance(params.OptimismBaseFeeRecipient, amtU256, tracing.BalanceIncreaseRewardTransactionFee)
if l1Cost := st.evm.Context.L1CostFunc(st.msg.RollupCostData, st.evm.Context.Time); l1Cost != nil {
amtU256, overflow = uint256.FromBig(l1Cost)
if overflow {
return nil, fmt.Errorf("optimism l1 cost overflows U256: %d", l1Cost)
}
+ if shouldCheckGasFormula {
+ st.l1Fee = amtU256.Clone()
+ }
+
+ amtU256 = st.collectableNativeBalance(amtU256)
st.state.AddBalance(params.OptimismL1FeeRecipient, amtU256, tracing.BalanceIncreaseRewardTransactionFee)
}
+
+ if shouldCheckGasFormula {
+ if st.l1Fee == nil {
+ st.l1Fee = new(uint256.Int)
+ }
+ if err := st.checkGasFormula(); err != nil {
+ return nil, err
+ }
+ }
}
+
}
return &ExecutionResult{
@@ -638,7 +891,19 @@
// Return ETH for remaining gas, exchanged at the original rate.
remaining := uint256.NewInt(st.gasRemaining)
remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice))
- st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn)
+ st.refundedGas = remaining.Clone()
+ if st.usedSGTBalance == nil {
+ st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn)
+ } else {
+ native, sgt := st.distributeGas(remaining, st.usedNativeBalance, st.usedSGTBalance)
+ if native.Sign() > 0 {
+ st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn)
+ }
+ if sgt.Sign() > 0 {
+ st.addSoulBalance(st.msg.From, sgt, tracing.BalanceIncreaseGasReturn)
+ st.usedSGTBalance.Sub(st.usedSGTBalance, sgt)
+ }
+ }
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining > 0 {
st.evm.Config.Tracer.OnGasChange(st.gasRemaining, 0, tracing.GasChangeTxLeftOverReturned)
diff --git official op-geth/core/txpool/blobpool/blobpool.go SWC op-geth/core/txpool/blobpool/blobpool.go
index 19908d9e8fb61078ff0242728bbc89cac0c2371f..011db966998670c0efcf0bde80a8fa9400983975 100644
--- official op-geth/core/txpool/blobpool/blobpool.go
+++ SWC op-geth/core/txpool/blobpool/blobpool.go
@@ -667,10 +667,10 @@ break
}
// 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)
- 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
@@ -1092,7 +1092,8 @@ return err
}
// Ensure the transaction adheres to the stateful pool filters (nonce, balance)
stateOpts := &txpool.ValidationOptionsWithState{
- State: p.state,
+ State: p.state,
+ Chainconfig: p.chain.Config(),
FirstNonceGap: func(addr common.Address) uint64 {
// Nonce gaps are not permitted in the blob pool, the first gap will
diff --git official op-geth/core/txpool/legacypool/legacypool.go SWC op-geth/core/txpool/legacypool/legacypool.go
index 2579104e5a4e1aae06f9f3f9da0ddd50600df506..4063a7726732c8a67e78b658f948da9bada6e064 100644
--- official op-geth/core/txpool/legacypool/legacypool.go
+++ SWC op-geth/core/txpool/legacypool/legacypool.go
@@ -670,7 +670,8 @@ // 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 *LegacyPool) validateTx(tx *types.Transaction, local bool) error {
opts := &txpool.ValidationOptionsWithState{
- State: pool.currentState,
+ State: pool.currentState,
+ Chainconfig: pool.chainconfig,
FirstNonceGap: nil, // Pool allows arbitrary arrival order, don't invalidate nonce gaps
UsedAndLeftSlots: func(addr common.Address) (int, int) {
@@ -1532,7 +1533,9 @@ hash := tx.Hash()
pool.all.Remove(hash)
}
log.Trace("Removed old queued transactions", "count", len(forwards))
- balance := pool.currentState.GetBalance(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)
@@ -1735,7 +1738,9 @@ hash := tx.Hash()
pool.all.Remove(hash)
log.Trace("Removed old pending transaction", "hash", hash)
}
- balance := pool.currentState.GetBalance(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 official op-geth/core/txpool/validation.go SWC op-geth/core/txpool/validation.go
index 17de989ad309ccd58a547d7538d9df410a6d4e31..1f2ecffcf1895eda2ef159d53d878448ec31466b 100644
--- official op-geth/core/txpool/validation.go
+++ SWC op-geth/core/txpool/validation.go
@@ -81,7 +81,7 @@ // as the external engine-API user authenticates deposits.
if tx.Type() == types.DepositTxType {
return core.ErrTxTypeNotSupported
}
- if opts.Config.IsOptimism() && tx.Type() == types.BlobTxType {
+ if tx.Type() == types.BlobTxType && opts.Config.IsOptimism() && !opts.Config.IsL2Blob(head.Number, head.Time) {
return core.ErrTxTypeNotSupported
}
// Ensure transactions not implemented by the calling pool are rejected
@@ -202,7 +202,8 @@
// 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
+ State *state.StateDB // State database to check nonces and balances against
+ Chainconfig *params.ChainConfig
// 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
@@ -251,10 +252,12 @@ 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).ToBig()
- 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)
diff --git official op-geth/eth/gasestimator/gasestimator.go SWC op-geth/eth/gasestimator/gasestimator.go
index b05f9f200baff91c8122f61eee08b887f232fcf1..9e0eeabaf653217acb5e97a55b9fc11e928362cf 100644
--- official op-geth/eth/gasestimator/gasestimator.go
+++ SWC op-geth/eth/gasestimator/gasestimator.go
@@ -71,15 +71,12 @@ feeCap = common.Big0
}
// Recap the highest gas limit with account's available balance.
if feeCap.BitLen() != 0 {
- balance := opts.State.GetBalance(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)))
diff --git official op-geth/internal/ethapi/api.go SWC op-geth/internal/ethapi/api.go
index 7712d71273f3c2521f8f5944fbf58ecf6dc08cc1..83289d370b4bd659598acef20ff17f532464103d 100644
--- official op-geth/internal/ethapi/api.go
+++ SWC op-geth/internal/ethapi/api.go
@@ -638,12 +638,18 @@ }
// BlockChainAPI provides an API to access Ethereum blockchain data.
type BlockChainAPI struct {
- b Backend
+ b Backend
+ sgt bool
}
// NewBlockChainAPI creates a new Ethereum blockchain API.
func NewBlockChainAPI(b Backend) *BlockChainAPI {
- return &BlockChainAPI{b}
+ return &BlockChainAPI{b, false}
+}
+
+// NewBlockChainAPIForSGT creates a new Ethereum blockchain API for SGT.
+func NewBlockChainAPIForSGT(b Backend) *BlockChainAPI {
+ return &BlockChainAPI{b, true}
}
// ChainId is the EIP-155 replay-protection chain id for the current Ethereum chain config.
@@ -688,8 +694,12 @@ state, _, err := api.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
- b := state.GetBalance(address).ToBig()
- return (*hexutil.Big)(b), state.Error()
+ if !api.sgt {
+ b := state.GetBalance(address).ToBig()
+ return (*hexutil.Big)(b), state.Error()
+ }
+ nativeBalance, sgtBalance := core.GetGasBalancesInBig(state, api.b.ChainConfig(), address)
+ return (*hexutil.Big)(new(big.Int).Add(nativeBalance, sgtBalance)), state.Error()
}
// AccountResult structs for GetProof
diff --git official op-geth/node/config.go SWC op-geth/node/config.go
index 949db887e4e4bf4d841c0db62d31844e2671c58d..c140135a5cf231817225510684afb806454e351a 100644
--- official op-geth/node/config.go
+++ SWC op-geth/node/config.go
@@ -106,6 +106,9 @@ // HTTPHost is the host interface on which to start the HTTP RPC server. If this
// field is empty, no HTTP API endpoint will be started.
HTTPHost string
+ HTTPSGTHost string
+ HTTPSGTPort int `toml:",omitempty"`
+
// HTTPPort is the TCP port number on which to start the HTTP RPC server. The
// default zero value is/ valid and will pick a port number randomly (useful
// for ephemeral nodes).
@@ -269,7 +272,7 @@ }
// DefaultHTTPEndpoint returns the HTTP endpoint used by default.
func DefaultHTTPEndpoint() string {
- config := &Config{HTTPHost: DefaultHTTPHost, HTTPPort: DefaultHTTPPort, AuthPort: DefaultAuthPort}
+ config := &Config{HTTPHost: DefaultHTTPHost, HTTPPort: DefaultHTTPPort, HTTPSGTPort: DefaultHTTPSGTPort, AuthPort: DefaultAuthPort}
return config.HTTPEndpoint()
}
diff --git official op-geth/node/defaults.go SWC op-geth/node/defaults.go
index 307d9e186a251b7963b5fbb4266249fbfbcc6fa4..880dc20d4fa8830334b018c8cbadab8dd41f784e 100644
--- official op-geth/node/defaults.go
+++ SWC op-geth/node/defaults.go
@@ -34,6 +34,9 @@ DefaultWSHost = "localhost" // Default host interface for the websocket RPC server
DefaultWSPort = 8546 // Default TCP port for the websocket RPC server
DefaultAuthHost = "localhost" // Default host interface for the authenticated apis
DefaultAuthPort = 8551 // Default port for the authenticated apis
+
+ DefaultHTTPSGTHost = "localhost" // Default host interface for the HTTPSGT RPC server
+ DefaultHTTPSGTPort = 8645 // Default TCP port for the HTTPSGT RPC server
)
const (
@@ -56,6 +59,7 @@ // DefaultConfig contains reasonable default settings.
var DefaultConfig = Config{
DataDir: DefaultDataDir(),
HTTPPort: DefaultHTTPPort,
+ HTTPSGTPort: DefaultHTTPSGTPort,
AuthAddr: DefaultAuthHost,
AuthPort: DefaultAuthPort,
AuthVirtualHosts: DefaultAuthVhosts,
diff --git official op-geth/node/node.go SWC op-geth/node/node.go
index d8c5994be0a65cfc34514b2c09df49ce481e019e..77decb4463add2fd6184fe346d9999162bdb3941 100644
--- official op-geth/node/node.go
+++ SWC op-geth/node/node.go
@@ -36,6 +36,7 @@ "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethdb/memorydb"
"github.com/ethereum/go-ethereum/event"
+ "github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc"
@@ -60,6 +61,7 @@ lock sync.Mutex
lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle
rpcAPIs []rpc.API // List of APIs currently provided by the node
http *httpServer //
+ httpSGT *httpServer //
ws *httpServer //
httpAuth *httpServer //
wsAuth *httpServer //
@@ -67,6 +69,8 @@ ipc *ipcServer // Stores information about the ipc http server
inprocHandler *rpc.Server // In-process RPC request handler to process the API requests
databases map[*closeTrackingDB]struct{} // All open databases
+
+ APIBackend ethapi.Backend // Ethereum API backend, used for SGT
}
const (
@@ -151,6 +155,7 @@ }
// Configure RPC servers.
node.http = newHTTPServer(node.log, conf.HTTPTimeouts)
+ node.httpSGT = newHTTPServer(node.log, conf.HTTPTimeouts)
node.httpAuth = newHTTPServer(node.log, conf.HTTPTimeouts)
node.ws = newHTTPServer(node.log, rpc.DefaultHTTPTimeouts)
node.wsAuth = newHTTPServer(node.log, rpc.DefaultHTTPTimeouts)
@@ -423,6 +428,30 @@ }
servers = append(servers, server)
return nil
}
+ initHttpSGT := func(server *httpServer) error {
+ if n.APIBackend == nil {
+ panic("bug: Node.APIBackend is nil when initHttpSGT is called")
+ }
+ if err := server.setListenAddr(n.config.HTTPSGTHost, n.config.HTTPSGTPort); err != nil {
+ return err
+ }
+ // appended API will override the existing one
+ updatedOpenAPIs := append(openAPIs, rpc.API{
+ Namespace: "eth",
+ Service: ethapi.NewBlockChainAPIForSGT(n.APIBackend),
+ })
+ if err := server.enableRPC(updatedOpenAPIs, httpConfig{
+ CorsAllowedOrigins: n.config.HTTPCors,
+ Vhosts: n.config.HTTPVirtualHosts,
+ Modules: n.config.HTTPModules,
+ prefix: n.config.HTTPPathPrefix,
+ rpcEndpointConfig: rpcConfig,
+ }); err != nil {
+ return err
+ }
+ servers = append(servers, server)
+ return nil
+ }
initWS := func(port int) error {
server := n.wsServerForPort(port, false)
@@ -489,6 +518,12 @@ if err := initHttp(n.http, n.config.HTTPPort); err != nil {
return err
}
}
+ if n.config.HTTPSGTHost != "" {
+ // Configure unauthenticated HTTP for SGT.
+ if err := initHttpSGT(n.httpSGT); err != nil {
+ return err
+ }
+ }
// Configure WebSocket.
if n.config.WSHost != "" {
// legacy unauthenticated
@@ -528,6 +563,7 @@ }
func (n *Node) stopRPC() {
n.http.stop()
+ n.httpSGT.stop()
n.ws.stop()
n.httpAuth.stop()
n.wsAuth.stop()