diff --git official op-geth/cmd/geth/config.go SWC op-geth/cmd/geth/config.go
index eebb932576f27ec7882f9c195b680ee315e3fb8c..94433e1d2e737d4a0dd9c8bbd633059667898d93 100644
--- official op-geth/cmd/geth/config.go
+++ SWC op-geth/cmd/geth/config.go
@@ -277,6 +277,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 e18d6fdd7b4a1481132b9368e89dd524c986e117..c6c0fc45f79ebc5ae08909cd124eadb1013d129e 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 5e1688d336d7d8a7049d9d4a419501fe21d1ae89..098fe3077f568f0c473f5a78485ba29ab88bc379 100644
--- official op-geth/cmd/utils/flags.go
+++ SWC op-geth/cmd/utils/flags.go
@@ -713,16 +713,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{
@@ -1260,6 +1277,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))
@@ -1320,9 +1339,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 688d57404a2c1a910178847d4c8d2ac8e3e3e6a0..5620d7da5d34ab7cfe3a1b54c5e9166e3319b71d 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"
@@ -249,6 +251,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.
@@ -261,6 +282,100 @@ 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) collectNativeBalance(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 = deductGasFrom(amount, st.usedSGTBalance, st.usedNativeBalance)
+ }
+ return amount
+}
+
+// deductGasFrom deducts the gas according to the pool priority:
+//
+// first pool1, then pool2.
+//
+// To be specific:
+// - split amount among two pools, first pool1, then pool2, where poolx means max amount for pool x.
+// - quotax is the amount deducted from pool x.
+//
+// note:
+// 1. the returned values are always non-nil.
+// 2. pool1 and pool2 are updated in-place if they are non-nil.
+// 3. panic if amount > pool1 + pool2
+func deductGasFrom(amount, pool1, pool2 *uint256.Int) (quota1, quota2 *uint256.Int) {
+ if amount == nil {
+ panic("amount should not be nil")
+ }
+ if pool1 == nil && pool2 == nil {
+ panic("both pool1 and pool2 are nil")
+ }
+ if pool1 == nil {
+ // pool1 empty, all to pool2
+ quota1 = new(uint256.Int)
+ quota2 = amount.Clone()
+
+ pool2.Sub(pool2, quota2)
+ if pool2.Sign() < 0 {
+ panic("distribute gas cannot be greater than pre-bought gas")
+ }
+ return
+ }
+ if pool2 == nil {
+ // pool2 empty, all to pool1
+ quota1 = amount.Clone()
+ quota2 = new(uint256.Int)
+
+ pool1.Sub(pool1, quota1)
+ if pool1.Sign() < 0 {
+ panic("distribute gas cannot be greater than pre-bought gas")
+ }
+ 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)
+ if pool2.Sign() < 0 {
+ panic("distribute gas cannot be greater than pre-bought gas")
+ }
+ } 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 */ {
@@ -269,20 +384,115 @@ }
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
+}
+
+// GetEffectiveGasBalance gets the effective balance to pay gas.
+func GetEffectiveGasBalance(state vm.StateDB, chainconfig *params.ChainConfig, account common.Address, value *big.Int, targetTime uint64) (*big.Int, error) {
+ bal, sgtBal := GetGasBalancesInBig(state, chainconfig, account, targetTime)
+ 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
+}
+
+// GetGasBalances gets the native and SGT balance of the account. The returned values can be safely modified.
+func GetGasBalances(state vm.StateDB, chainconfig *params.ChainConfig, account common.Address, targetTime uint64) (*uint256.Int, *uint256.Int) {
+ balance := state.GetBalance(account).Clone()
+ if chainconfig != nil && chainconfig.IsOptimism() && chainconfig.Optimism.IsSoulGasToken(targetTime) {
+ sgtBalanceSlot := TargetSGTBalanceSlot(account)
+ sgtBalanceValue := state.GetState(types.SoulGasTokenAddr, sgtBalanceSlot)
+ sgtBalance := new(uint256.Int).SetBytes(sgtBalanceValue[:])
+
+ return balance, sgtBalance
+ }
+
+ return balance, uint256.NewInt(0)
+}
+
+// GetGasBalancesInBig gets the native and SGT balance of the account. The returned values can be safely modified.
+func GetGasBalancesInBig(state vm.StateDB, chainconfig *params.ChainConfig, account common.Address, targetTime uint64) (*big.Int, *big.Int) {
+ bal, sgtBal := GetGasBalances(state, chainconfig, account, targetTime)
+ 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())
}
}
@@ -299,6 +509,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
@@ -308,16 +519,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.Time) {
+ 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
}
@@ -328,8 +556,33 @@ }
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
+ // st.usedNativeBalance is nil in this case.
+ } 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)
+ st.state.SubBalance(st.msg.From, usedNativeBalance, tracing.BalanceDecreaseGasBuy)
+ st.usedNativeBalance = usedNativeBalance
+ }
+ }
+
return nil
}
@@ -668,8 +921,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.collectNativeBalance(fee)
st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee)
// add the coinbase to the witness iff the fee is greater than 0
@@ -681,26 +939,60 @@ // 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.collectNativeBalance(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.collectNativeBalance(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()
+ operatorFeeCost = st.collectNativeBalance(operatorFeeCost)
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{
@@ -793,7 +1085,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)
@@ -804,6 +1097,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 := deductGasFrom(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)
@@ -813,7 +1123,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 0e9c258ac45807f83d29288470ff44757cef1428..197e8985ef971bbc88a7cfecc6a1fae02a331b33 100644
--- official op-geth/core/txpool/blobpool/blobpool.go
+++ SWC op-geth/core/txpool/blobpool/blobpool.go
@@ -702,10 +702,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.Time+2) // QKC TODO: replace 2 with `BlockTime` after the constant is introduced
+ // 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
@@ -1167,7 +1167,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
@@ -1195,6 +1196,7 @@ return p.index[addr][int(nonce-next)].costCap.ToBig()
}
return nil
},
+ TargetTime: p.head.Time + 2, // QKC TODO: replace 2 with `BlockTime` after the constant is introduced
}
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 9202479274f19ab94fe0dfe157a790f30566315f..b3a91ef60f8f77bdc33f0b1a93405101ca441618 100644
--- official op-geth/core/txpool/legacypool/legacypool.go
+++ SWC op-geth/core/txpool/legacypool/legacypool.go
@@ -660,7 +660,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
@@ -682,6 +683,7 @@ }
return nil
},
RollupCostFn: pool.rollupCostFn,
+ TargetTime: pool.currentHead.Load().Time + 2, // QKC TODO: replace 2 with `BlockTime` after the constant is introduced
}
if err := txpool.ValidateTransactionWithState(tx, pool.signer, opts); err != nil {
return err
@@ -1563,7 +1565,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().Time+2) // QKC TODO: replace 2 with `BlockTime` after the constant is introduced
+ // 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 {
@@ -1752,7 +1756,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().Time+2) // QKC TODO: replace 2 with `BlockTime` after the constant is introduced
+ // 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 d76af9cab5ea704821eac580d0335c776d20f9a4..a73d62885d8f55242a6e63fb609f60b0078e734b 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
@@ -245,7 +245,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
@@ -268,6 +269,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
+ // QKC diff: TargetTime is the time the transaction is expected to be included in
+ TargetTime uint64
}
// ValidateTransactionWithState is a helper method to check whether a transaction
@@ -294,10 +297,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.TargetTime)
+ 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 24b298dfc9ff9b07d5b6c7ea8257d60b87295c12..c343a1dfc03345368290ed1b4aefdb68c4308555 100644
--- official op-geth/eth/gasestimator/gasestimator.go
+++ SWC op-geth/eth/gasestimator/gasestimator.go
@@ -90,15 +90,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.Time)
+ 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 fc0b6067160192a952ab8c7595585beea45a2ae2..8297056c8cb7ed6e01be5702e0774649cbb91b09 100644
--- official op-geth/internal/ethapi/api.go
+++ SWC op-geth/internal/ethapi/api.go
@@ -298,12 +298,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.
@@ -348,8 +354,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.Time)
+ 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 e307a6ba480a0fe78366058283a141fde19ba8ed..2a3b124f72770aad4d689888e712f1bcbb395caa 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()