diff --git official op-geth/cmd/geth/config.go SWC op-geth/cmd/geth/config.go
index 837b38777b76d2890ffd7d157d7d7f63d8018b89..3ceb7ad369034bd071df570ee7fb250f40b09798 100644
--- official op-geth/cmd/geth/config.go
+++ SWC op-geth/cmd/geth/config.go
@@ -237,6 +237,7 @@ // Start metrics export if enabled
utils.SetupMetrics(&cfg.Metrics)
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 0f1974901648d77ecf2dffbc0b37965ed2e7d844..11ce2d05956ed1e3480d064f7db2d6e3226276bc 100644
--- official op-geth/cmd/geth/main.go
+++ SWC op-geth/cmd/geth/main.go
@@ -182,8 +182,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 c3be967fc0f8788d0d0ab9080a0fb4a6b4ff282e..f66b0b9ae197b22f456f1eb990f903a0dabfc7db 100644
--- official op-geth/cmd/utils/flags.go
+++ SWC op-geth/cmd/utils/flags.go
@@ -668,16 +668,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{
@@ -1199,6 +1216,8 @@ urls = params.V5OPBootnodes
} else {
urls = params.V5OPTestnetBootnodes
}
+ case ctx.Uint64(NetworkIdFlag.Name) == 3335:
+ urls = params.BetaTestnetBootnodes
}
cfg.BootstrapNodesV5 = make([]*enode.Node, 0, len(urls))
@@ -1259,9 +1278,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 cfa818a4ac4d3331c20f175bcb44e64978a143f7..4eeadb618edc815fd1878a038d1532e055a582ed 100644
--- official op-geth/core/state_transition.go
+++ SWC op-geth/core/state_transition.go
@@ -22,10 +22,12 @@ "fmt"
"math"
"math/big"
+ "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"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"
@@ -247,6 +249,25 @@ 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
+ boughtGasLimitXGasPrice *uint256.Int
+ boughtBlobFee *uint256.Int
+ boughtL1Fee *uint256.Int
+ boughtOperatorFee *uint256.Int
+ refundedGas *uint256.Int
+ refundedOperatorFee *uint256.Int
+ tipFee *uint256.Int
+ baseFee *uint256.Int
+ l1Fee *uint256.Int
+ operatorFee *uint256.Int
}
// newStateTransition initialises and returns a new state transition object.
@@ -259,6 +280,90 @@ state: evm.StateDB,
}
}
+func (st *stateTransition) checkGasFormula() error {
+ if st.boughtGas.Cmp(
+ new(uint256.Int).Add(
+ new(uint256.Int).Add(st.boughtL1Fee, st.boughtOperatorFee),
+ new(uint256.Int).Add(st.boughtGasLimitXGasPrice, st.boughtBlobFee))) != 0 {
+ return fmt.Errorf("gas formula doesn't hold: boughtGas(%v) != boughtGasLimitXGasPrice(%v) + boughtBlobFee(%v) + boughtL1Fee(%v) + boughtOperatorFee(%v)", st.boughtGas, st.boughtGasLimitXGasPrice, st.boughtBlobFee, st.boughtL1Fee, st.boughtOperatorFee)
+ }
+ sumGas := new(uint256.Int).Add(
+ st.refundedGas, new(uint256.Int).Add(
+ st.tipFee, new(uint256.Int).Add(
+ st.baseFee,
+ new(uint256.Int).Add(
+ new(uint256.Int).Add(
+ st.l1Fee, st.operatorFee),
+ st.refundedOperatorFee))))
+ if st.boughtGas.Cmp(sumGas) != 0 {
+ return fmt.Errorf("gas formula doesn't hold: boughtGas(%v) != sumGas(%v) [boughtGas = boughtGasLimitXGasPrice(%v) + boughtBlobFee(%v) + boughtL1Fee(%v) + boughtOperatorFee(%v),sumGas = refundedGas(%v) + refundedOperatorFee(%v) + tipFee(%v) + baseFee(%v) + l1Fee(%v) + operatorFee(%v)], BaseFee:%v BlobBaseFee:%v GasLimit:%v GasUsed:%v GasPrice:%v GasFeeCap:%v GasTipCap:%v", sumGas, st.boughtGas, st.boughtGasLimitXGasPrice, st.boughtBlobFee, st.boughtL1Fee, st.boughtOperatorFee, st.refundedGas, st.refundedOperatorFee, st.tipFee, st.baseFee, st.l1Fee, st.operatorFee, st.evm.Context.BaseFee, st.evm.Context.BlobBaseFee, st.initialGas, st.gasUsed(), st.msg.GasPrice, st.msg.GasFeeCap, st.msg.GasTipCap)
+ }
+ 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:
+// 1. the returned values are always non-nil.
+// 2. pool1 and pool2 are updated in-place if they are 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 */ {
@@ -267,20 +372,113 @@ }
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, targetHeight uint64) (*big.Int, error) {
+ bal, sgtBal := GetGasBalancesInBig(state, chainconfig, account, targetHeight)
+ if value == nil {
+ value = big.NewInt(0)
+ }
+ if bal.Cmp(value) < 0 {
+ return nil, ErrInsufficientFundsForTransfer
+ }
+ bal.Sub(bal, value)
+ return new(big.Int).Add(bal, sgtBal), nil
+}
+
+func GetGasBalances(state vm.StateDB, chainconfig *params.ChainConfig, account common.Address, targetHeight uint64) (*uint256.Int, *uint256.Int) {
+ balance := state.GetBalance(account).Clone()
+ if chainconfig != nil && chainconfig.IsOptimism() && chainconfig.Optimism.IsSoulGasToken(targetHeight) {
+ 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, targetHeight uint64) (*big.Int, *big.Int) {
+ bal, sgtBal := GetGasBalances(state, chainconfig, account, targetHeight)
+ 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)
+ st.boughtGasLimitXGasPrice, _ = uint256.FromBig(mgval)
+ st.boughtL1Fee = new(uint256.Int)
+ st.boughtOperatorFee = new(uint256.Int)
var l1Cost *big.Int
var operatorCost *uint256.Int
if !st.msg.SkipNonceChecks && !st.msg.SkipFromEOACheck {
if st.evm.Context.L1CostFunc != nil {
l1Cost = st.evm.Context.L1CostFunc(st.msg.RollupCostData, st.evm.Context.Time)
if l1Cost != nil {
+ st.boughtL1Fee, _ = uint256.FromBig(l1Cost)
mgval = mgval.Add(mgval, l1Cost)
}
}
if st.evm.Context.OperatorCostFunc != nil {
operatorCost = st.evm.Context.OperatorCostFunc(st.msg.GasLimit, st.evm.Context.Time)
+ st.boughtOperatorFee = operatorCost.Clone()
mgval = mgval.Add(mgval, operatorCost.ToBig())
}
}
@@ -297,6 +495,7 @@ }
}
balanceCheck.Add(balanceCheck, st.msg.Value)
+ st.boughtBlobFee = new(uint256.Int)
if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) {
if blobGas := st.blobGasUsed(); blobGas > 0 {
// Check that the user has enough funds to cover blobGasUsed * tx.BlobGasFeeCap
@@ -306,16 +505,33 @@ balanceCheck.Add(balanceCheck, blobBalanceCheck)
// Pay for blobGasUsed * actual blob fee
blobFee := new(big.Int).SetUint64(blobGas)
blobFee.Mul(blobFee, st.evm.Context.BlobBaseFee)
+ st.boughtBlobFee, _ = uint256.FromBig(blobFee)
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.IsSoulGasToken(st.evm.Context.BlockNumber.Uint64()) {
+ 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
}
@@ -326,8 +542,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
}
@@ -655,8 +897,13 @@ // 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.
} else {
+ shouldCheckGasFormula := !st.msg.SkipNonceChecks && !st.msg.SkipFromEOACheck
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
@@ -668,26 +915,59 @@ // 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 rules.IsOptimismIsthmus {
// Operator Fee refunds are only applied if Isthmus is active and the transaction is *not* a deposit.
st.refundIsthmusOperatorCost()
operatorFeeCost := st.evm.Context.OperatorCostFunc(st.gasUsed(), st.evm.Context.Time)
+ st.operatorFee = operatorFeeCost.Clone()
st.state.AddBalance(params.OptimismOperatorFeeRecipient, operatorFeeCost, tracing.BalanceIncreaseRewardTransactionFee)
}
+
+ if shouldCheckGasFormula {
+ if st.l1Fee == nil {
+ st.l1Fee = new(uint256.Int)
+ }
+ if st.operatorFee == nil {
+ st.operatorFee = new(uint256.Int)
+ }
+ if st.refundedOperatorFee == nil {
+ st.refundedOperatorFee = new(uint256.Int)
+ }
+ if err := st.checkGasFormula(); err != nil {
+ return nil, err
+ }
+ }
}
+
}
return &ExecutionResult{
@@ -780,7 +1060,8 @@ // exchanged at the original rate.
func (st *stateTransition) returnGas() {
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()
+ st.refundGas(remaining)
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)
@@ -791,6 +1072,23 @@ // available for the next transaction.
st.gp.AddGas(st.gasRemaining)
}
+// refundGas refunds a specified amount of gas to user.
+// the priority for refund is: first native, then SGT.
+// it's called by both returnGas and refundIsthmusOperatorCost.
+func (st *stateTransition) refundGas(amount *uint256.Int) {
+ if st.usedSGTBalance == nil {
+ st.state.AddBalance(st.msg.From, amount, tracing.BalanceIncreaseGasReturn)
+ } else {
+ native, sgt := st.distributeGas(amount, st.usedNativeBalance, st.usedSGTBalance)
+ if native.Sign() > 0 {
+ st.state.AddBalance(st.msg.From, native, tracing.BalanceIncreaseGasReturn)
+ }
+ if sgt.Sign() > 0 {
+ st.addSoulBalance(st.msg.From, sgt, tracing.BalanceIncreaseGasReturn)
+ }
+ }
+}
+
func (st *stateTransition) refundIsthmusOperatorCost() {
// Return ETH to transaction sender for operator cost overcharge.
operatorCostGasLimit := st.evm.Context.OperatorCostFunc(st.msg.GasLimit, st.evm.Context.Time)
@@ -800,7 +1098,9 @@ if operatorCostGasUsed.Cmp(operatorCostGasLimit) > 0 { // Sanity check.
panic(fmt.Sprintf("operator cost gas used (%d) > operator cost gas limit (%d)", operatorCostGasUsed, operatorCostGasLimit))
}
- st.state.AddBalance(st.msg.From, new(uint256.Int).Sub(operatorCostGasLimit, operatorCostGasUsed), tracing.BalanceIncreaseGasReturn)
+ refundedOperatorCost := new(uint256.Int).Sub(operatorCostGasLimit, operatorCostGasUsed)
+ st.refundedOperatorFee = refundedOperatorCost.Clone()
+ st.refundGas(refundedOperatorCost)
}
// gasUsed returns the amount of gas used up by the state transition.
diff --git official op-geth/core/txpool/blobpool/blobpool.go SWC op-geth/core/txpool/blobpool/blobpool.go
index 86b7298b17ceaeac25b234872393ed61cb1d4d4f..ccbd616e3c15085868ee47cc0d8570bf79a3ef95 100644
--- official op-geth/core/txpool/blobpool/blobpool.go
+++ SWC op-geth/core/txpool/blobpool/blobpool.go
@@ -680,10 +680,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, p.head.Number.Uint64()+1)
+ // 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
@@ -1106,7 +1106,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
@@ -1134,6 +1135,7 @@ return p.index[addr][int(nonce-next)].costCap.ToBig()
}
return nil
},
+ TargetHeight: p.head.Number.Uint64() + 1,
}
if err := txpool.ValidateTransactionWithState(tx, p.signer, stateOpts); err != nil {
return err
diff --git official op-geth/core/txpool/legacypool/legacypool.go SWC op-geth/core/txpool/legacypool/legacypool.go
index 7bb73e19f0ca4db67e8ed7c99fd77d5b52ce9d64..ec86e29b66531640c24dcac4d73a11422ea59d02 100644
--- official op-geth/core/txpool/legacypool/legacypool.go
+++ SWC op-geth/core/txpool/legacypool/legacypool.go
@@ -647,7 +647,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) 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: nil, // Pool has own mechanism to limit the number of transactions
@@ -669,6 +670,7 @@ }
return nil
},
RollupCostFn: pool.rollupCostFn,
+ TargetHeight: pool.currentHead.Load().Number.Uint64() + 1,
}
if err := txpool.ValidateTransactionWithState(tx, pool.signer, opts); err != nil {
return err
@@ -1489,7 +1491,9 @@ for _, tx := range forwards {
pool.all.Remove(tx.Hash())
}
log.Trace("Removed old queued transactions", "count", len(forwards))
- balance := pool.currentState.GetBalance(addr)
+ balance, sgtBalance := core.GetGasBalances(pool.currentState, pool.chainconfig, addr, pool.currentHead.Load().Number.Uint64()+1)
+ // TODO: we may need a better filter such as tx.value < acc.balance
+ balance = balance.Add(balance, sgtBalance)
// Drop all transactions that are too costly (low balance or out of gas)
drops, _ := list.Filter(balance, gasLimit)
for _, tx := range drops {
@@ -1678,7 +1682,10 @@ 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, pool.currentHead.Load().Number.Uint64()+1)
+ // TODO: we may need a better filter such as tx.value < acc.balance
+ balance = balance.Add(balance, sgtBalance)
+
// 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)
for _, tx := range drops {
diff --git official op-geth/core/txpool/validation.go SWC op-geth/core/txpool/validation.go
index 901568563332271e9365e114d2d02d10b672358c..9e67e6fcad237326e07c30331e77df74b47d89df 100644
--- official op-geth/core/txpool/validation.go
+++ SWC op-geth/core/txpool/validation.go
@@ -87,7 +87,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
@@ -228,7 +228,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,6 +252,8 @@ ExistingCost func(addr common.Address, nonce uint64) *big.Int
// RollupCostFn is an optional extension, to validate total rollup costs of a tx
RollupCostFn RollupCostFunc
+ // TargetHeight is the height of the block the transaction is expected to be included in
+ TargetHeight uint64
}
// ValidateTransactionWithState is a helper method to check whether a transaction
@@ -277,10 +280,13 @@ 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()
- cost256, overflow = TotalTxCost(tx, opts.RollupCostFn)
- )
+ balance, err := core.GetEffectiveGasBalance(opts.State, opts.Chainconfig, from, tx.Value(), opts.TargetHeight)
+ if err != nil {
+ return fmt.Errorf("%w: balance %v, tx value %v", err, balance, tx.Value())
+ }
+ // add tx.Value() back immediately to be compatible with ExistingExpenditure/ExistingCost below
+ balance = new(big.Int).Add(balance, tx.Value())
+ cost256, overflow := TotalTxCost(tx, opts.RollupCostFn)
if overflow {
return fmt.Errorf("%w: total tx cost overflow", core.ErrInsufficientFunds)
}
diff --git official op-geth/eth/gasestimator/gasestimator.go SWC op-geth/eth/gasestimator/gasestimator.go
index 2c50dac924034663ed94049a16bf1c3bbeeb876c..723706a18157a9c26b470c94c3df4b6ecbaa730e 100644
--- official op-geth/eth/gasestimator/gasestimator.go
+++ SWC op-geth/eth/gasestimator/gasestimator.go
@@ -73,15 +73,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, opts.Header.Number.Uint64())
+ 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 cc102e16f9efe6451bd82f7286b1e582d7449808..337057f1900a1fbc58d3813d86c4d44f2dcb62a6 100644
--- official op-geth/internal/ethapi/api.go
+++ SWC op-geth/internal/ethapi/api.go
@@ -296,12 +296,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.
@@ -346,8 +352,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, header.Number.Uint64())
+ 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 dc436876cce98bc047918630c2877bbbcbd80ecb..d81788455ed54f691c85bdcf98854d87824c967e 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 24905c128ef316605fcfd058b0566954d1a29e9a..f914101eb2cc81f187152b3fae20c16607074d15 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)
@@ -411,6 +416,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)
@@ -485,6 +514,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
@@ -524,6 +559,7 @@ }
func (n *Node) stopRPC() {
n.http.stop()
+ n.httpSGT.stop()
n.ws.stop()
n.httpAuth.stop()
n.wsAuth.stop()