op-geth

diff: ignored:
+566
-44

This is an overview of the changes in SWC op-geth, a fork of official op-geth, part of the SWC OP-stack.

When charging gas, the EVM is extended to first charge from the Soul Gas Token (which is a soul bounded ERC20) balance; Then charge from the native balance if it’s not enough.

The txpool and gas estimator are also extended to use the sum of native balance and Soul Gas Token balance as the effective balance.

In order to fix a compatibility issue with wallets, the node is also extended to listen to another http port alongside the default one, the only different is that the eth_GetBalance API will return the sum of native balance and Soul Gas Token balance.

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()

The txpool is extended to allow blob transaction is l2 blob is enabled.

The block-building code (in the “miner” package because of Proof-Of-Work legacy of ethereum) is extended to ensure that, for blob transactions, the blobs are required when it’s sequencing, but not required when it’s deriving.

We also charge a minimum data-availability fee for each blob.

diff --git official op-geth/beacon/engine/types.go SWC op-geth/beacon/engine/types.go index 41c475ed57d04a336ccd35a128ec8f71c795a70c..967c7e161ca1942c6d5a5ac67421634fcafb83fb 100644 --- official op-geth/beacon/engine/types.go +++ SWC op-geth/beacon/engine/types.go @@ -258,6 +258,10 @@ var blobHashes = make([]common.Hash, 0, len(txs)) for _, tx := range txs { blobHashes = append(blobHashes, tx.BlobHashes()...) } + // we only want to check versionedHashes when it's not empty + if len(versionedHashes) == 0 { + versionedHashes = blobHashes + } if len(blobHashes) != len(versionedHashes) { return nil, fmt.Errorf("invalid number of versionedHashes: %v blobHashes: %v", versionedHashes, blobHashes) }
diff --git official op-geth/core/types/rollup_cost.go SWC op-geth/core/types/rollup_cost.go index f398c43af1e460aa4f24e7c464ddeb7d2ab6e2e5..0a8d65943727d8412ec809a7053e7d1dfd22522e 100644 --- official op-geth/core/types/rollup_cost.go +++ SWC op-geth/core/types/rollup_cost.go @@ -58,6 +58,8 @@ // L1BlockAddr is the address of the L1Block contract which stores the L1 gas attributes. L1BlockAddr = common.HexToAddress("0x4200000000000000000000000000000000000015")   + SoulGasTokenAddr = common.HexToAddress("0x4200000000000000000000000000000000000800") + L1BaseFeeSlot = common.BigToHash(big.NewInt(1)) OverheadSlot = common.BigToHash(big.NewInt(5)) ScalarSlot = common.BigToHash(big.NewInt(6)) @@ -89,6 +91,7 @@ // availability costs for the transaction. type RollupCostData struct { Zeroes, Ones uint64 FastLzSize uint64 + Blobs uint64 }   type StateGetter interface { @@ -103,7 +106,7 @@ // l1CostFunc is an internal version of L1CostFunc that also returns the gasUsed for use in // receipts. type l1CostFunc func(rcd RollupCostData) (fee, gasUsed *big.Int)   -func NewRollupCostData(data []byte) (out RollupCostData) { +func NewRollupCostData(data []byte, blobs int) (out RollupCostData) { for _, b := range data { if b == 0 { out.Zeroes++ @@ -111,7 +114,9 @@ } else { out.Ones++ } } + out.FastLzSize = uint64(FlzCompressLen(data)) + out.Blobs = uint64(blobs) return out }   @@ -236,6 +241,7 @@ fee = new(big.Int).Add(calldataCostPerByte, blobCostPerByte) fee = fee.Mul(fee, calldataGasUsed) fee = fee.Div(fee, ecotoneDivisor) + fee = fee.Add(fee, new(big.Int).Mul(big.NewInt(int64(costData.Blobs)), big.NewInt(params.BlobDAFee)))   return fee, calldataGasUsed } @@ -367,6 +373,7 @@ l1FeeScaled := new(big.Int).Add(calldataCostPerByte, blobCostPerByte) estimatedSize := costData.estimatedDASizeScaled() l1CostScaled := new(big.Int).Mul(estimatedSize, l1FeeScaled) l1Cost := new(big.Int).Div(l1CostScaled, fjordDivisor) + l1Cost = new(big.Int).Add(l1Cost, new(big.Int).Mul(big.NewInt(int64(costData.Blobs)), big.NewInt(params.BlobDAFee)))   calldataGasUsed = new(big.Int).Mul(estimatedSize, new(big.Int).SetUint64(params.TxDataNonZeroGasEIP2028)) calldataGasUsed.Div(calldataGasUsed, big.NewInt(1e6))
diff --git official op-geth/core/types/transaction.go SWC op-geth/core/types/transaction.go index 3c94261061476ac453aaf252e49e0b66d8635cd9..80e612a0eafe449df50cc282b56b96aa9be3c937 100644 --- official op-geth/core/types/transaction.go +++ SWC op-geth/core/types/transaction.go @@ -382,6 +382,15 @@ total.Add(total, tx.Value()) return total }   +// Cost returns (gas * gasPrice) + (blobGas * blobGasPrice). +func (tx *Transaction) GasCost() *big.Int { + total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas())) + if tx.Type() == BlobTxType { + total.Add(total, new(big.Int).Mul(tx.BlobGasFeeCap(), new(big.Int).SetUint64(tx.BlobGas()))) + } + return total +} + // RollupCostData caches the information needed to efficiently compute the data availability fee func (tx *Transaction) RollupCostData() RollupCostData { if tx.Type() == DepositTxType { @@ -390,11 +399,14 @@ } if v := tx.rollupCostData.Load(); v != nil { return v.(RollupCostData) } - data, err := tx.MarshalBinary() + // When called from commitBlobTransaction, tx contains blob. + // When called from state processor, tx doesn't contain blob. + // In order to make the result consistent, we should use the version without blob. + data, err := tx.WithoutBlobTxSidecar().MarshalBinary() if err != nil { // Silent error, invalid txs will not be marshalled/unmarshalled for batch submission anyway. log.Error("failed to encode tx for L1 cost computation", "err", err) } - out := NewRollupCostData(data) + out := NewRollupCostData(data, len(tx.BlobHashes())) tx.rollupCostData.Store(out) return out }
diff --git official op-geth/core/types/transaction_signing.go SWC op-geth/core/types/transaction_signing.go index 1238c9a6c38d7a5485f4ccf2b8c9b95ea7e5b48a..f35d30a50803be97a65b72be8f99e47030f98ed5 100644 --- official op-geth/core/types/transaction_signing.go +++ SWC op-geth/core/types/transaction_signing.go @@ -40,7 +40,7 @@ // MakeSigner returns a Signer based on the given chain config and block number. func MakeSigner(config *params.ChainConfig, blockNumber *big.Int, blockTime uint64) Signer { var signer Signer switch { - case config.IsCancun(blockNumber, blockTime) && !config.IsOptimism(): + case config.IsCancun(blockNumber, blockTime) && (!config.IsOptimism() || config.IsL2Blob(blockNumber, blockTime)): signer = NewCancunSigner(config.ChainID) case config.IsLondon(blockNumber): signer = NewLondonSigner(config.ChainID) @@ -67,7 +67,7 @@ func LatestSigner(config *params.ChainConfig) Signer { var signer Signer if config.ChainID != nil { switch { - case config.CancunTime != nil && !config.IsOptimism(): + case config.CancunTime != nil && (!config.IsOptimism() || config.L2BlobTime != nil): signer = NewCancunSigner(config.ChainID) case config.LondonBlock != nil: signer = NewLondonSigner(config.ChainID)
diff --git official op-geth/eth/api_backend.go SWC op-geth/eth/api_backend.go index bd1b623c367f5e25d66f92bc0c96790bf59e19ca..e9dca2410aabc6dfb610689cbf31eabdea223b80 100644 --- official op-geth/eth/api_backend.go +++ SWC op-geth/eth/api_backend.go @@ -289,7 +289,8 @@ return b.eth.BlockChain().SubscribeLogsEvent(ch) }   func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { - if b.ChainConfig().IsOptimism() && signedTx.Type() == types.BlobTxType { + header := b.eth.blockchain.CurrentHeader() + if signedTx.Type() == types.BlobTxType && b.ChainConfig().IsOptimism() && !b.ChainConfig().IsL2Blob(header.Number, header.Time) { return types.ErrTxTypeNotSupported } if b.eth.seqRPCService != nil {
diff --git official op-geth/eth/backend.go SWC op-geth/eth/backend.go index 79453719c642055fb1cf5c28865a1f9bb18ba12c..29d8cc600c9e28a6a40945b537c3af69a9db17ab 100644 --- official op-geth/eth/backend.go +++ SWC op-geth/eth/backend.go @@ -269,7 +269,7 @@ } legacyPool := legacypool.New(config.TxPool, eth.blockchain)   txPools := []txpool.SubPool{legacyPool} - if !eth.BlockChain().Config().IsOptimism() { + if !eth.BlockChain().Config().IsOptimism() || eth.BlockChain().Config().L2BlobTime != nil { blobPool := blobpool.New(config.BlobPool, eth.blockchain) txPools = append(txPools, blobPool) }
diff --git official op-geth/miner/worker.go SWC op-geth/miner/worker.go index a4f297c6c6b453d7558bdfdbe427413327bd2543..4e14517d1719b76830055f8f32d8bf8120fcd4f7 100644 --- official op-geth/miner/worker.go +++ SWC op-geth/miner/worker.go @@ -76,11 +76,17 @@ txs []*types.Transaction receipts []*types.Receipt sidecars []*types.BlobTxSidecar blobs int + witness *stateless.Witness   - witness *stateless.Witness - + // this is used as a flag whether it's *effectively* sequencing or deriving (es comment) noTxs bool // true if we are reproducing a block, and do not have to check interop txs rpcCtx context.Context // context to control block-building RPC work. No RPC allowed if nil. +} + +// isEffectivelySequencing is true when PayloadAttributes.NoTxPool is false, +// which only happens when it's sequencing. +func (env *environment) isEffectivelySequencing() bool { + return !env.noTxs }   const ( @@ -311,6 +317,7 @@ context := core.NewEVMBlockContext(header, miner.chain, nil, miner.chainConfig, env.state) vmenv := vm.NewEVM(context, vm.TxContext{}, env.state, miner.chainConfig, vm.Config{}) core.ProcessParentBlockHash(header.ParentHash, vmenv, env.state) } + env.noTxs = genParams.noTxs // invariant: genParams.noTxs has the same boolean value as PayloadAttributes.NoTxPool return env, nil }   @@ -382,14 +389,24 @@ }   func (miner *Miner) commitBlobTransaction(env *environment, tx *types.Transaction) error { sc := tx.BlobTxSidecar() + var nblobs int if sc == nil { - panic("blob transaction without blobs in miner") + if env.isEffectivelySequencing() /* we want to allow blob tx without blobs when it's deriving */ { + panic("blob transaction without blobs in sequencing") + } else { // deriving, which does not have the sidecar + nblobs = len(tx.BlobHashes()) + } + } else { + if !env.isEffectivelySequencing() { + panic("blob transaction with blobs in derivation") + } + nblobs = len(sc.Blobs) } // Checking against blob gas limit: It's kind of ugly to perform this check here, but there // isn't really a better place right now. The blob gas limit is checked at block validation time // and not during execution. This means core.ApplyTransaction will not return an error if the // tx has too many blobs. So we have to explicitly check it here. - if (env.blobs+len(sc.Blobs))*params.BlobTxBlobGasPerBlob > params.MaxBlobGasPerBlock { + if (env.blobs+nblobs)*params.BlobTxBlobGasPerBlob > params.MaxBlobGasPerBlock { return errors.New("max data blobs reached") } receipt, err := miner.applyTransaction(env, tx) @@ -398,8 +415,10 @@ return err } env.txs = append(env.txs, tx.WithoutBlobTxSidecar()) env.receipts = append(env.receipts, receipt) - env.sidecars = append(env.sidecars, sc) - env.blobs += len(sc.Blobs) + if sc != nil { + env.sidecars = append(env.sidecars, sc) + } + env.blobs += len(tx.BlobHashes()) *env.header.BlobGasUsed += receipt.BlobGasUsed env.tcount++ return nil
diff --git official op-geth/params/config.go SWC op-geth/params/config.go index 04cc43a8403775a9e9ba9ddf7c537535c437ced2..bc8c375a7ce541386c59cfc81732d684c4d809f0 100644 --- official op-geth/params/config.go +++ SWC op-geth/params/config.go @@ -354,6 +354,7 @@ HoloceneTime *uint64 `json:"holoceneTime,omitempty"` // Holocene switch time (nil = no fork, 0 = already on Optimism Holocene)   InteropTime *uint64 `json:"interopTime,omitempty"` // Interop switch time (nil = no fork, 0 = already on optimism interop)   + L2BlobTime *uint64 `json:"l2BlobTime,omitempty"` // L2Blob switch time (nil = no fork, 0 = already on optimism l2blob) // TerminalTotalDifficulty is the amount of total difficulty reached by // the network that triggers the consensus upgrade. TerminalTotalDifficulty *big.Int `json:"terminalTotalDifficulty,omitempty"` @@ -399,6 +400,10 @@ type OptimismConfig struct { EIP1559Elasticity uint64 `json:"eip1559Elasticity"` EIP1559Denominator uint64 `json:"eip1559Denominator"` EIP1559DenominatorCanyon *uint64 `json:"eip1559DenominatorCanyon,omitempty"` + // Flag for whether using SoulGasToken for gas fee. + UseSoulGasToken bool `json:"useSoulGasToken"` + // Whether SoulGasToken is backed by native token or minted by whitelisted miners, only effective when UseSoulGasToken is true + IsSoulBackedByNative bool `json:"isSoulBackedByNative"` }   // String implements the stringer interface, returning the optimism fee config details. @@ -518,6 +523,13 @@ } if c.InteropTime != nil { banner += fmt.Sprintf(" - Interop: @%-10v\n", *c.InteropTime) } + if c.L2BlobTime != nil { + banner += fmt.Sprintf(" - L2BLob: @%-10v\n", *c.L2BlobTime) + } + banner += "\n" + if c.Optimism != nil { + banner += fmt.Sprintf("SGT: %t, Back by native %t", c.Optimism.UseSoulGasToken, c.Optimism.IsSoulBackedByNative) + } return banner }   @@ -609,6 +621,11 @@ // IsCancun returns whether time is either equal to the Cancun fork time or greater. func (c *ChainConfig) IsCancun(num *big.Int, time uint64) bool { return c.IsLondon(num) && isTimestampForked(c.CancunTime, time) +} + +// IsL2Blob returns whether l2 blob is enabled +func (c *ChainConfig) IsL2Blob(num *big.Int, time uint64) bool { + return c.IsCancun(num, time) && c.Optimism != nil && isTimestampForked(c.L2BlobTime, time) }   // IsPrague returns whether time is either equal to the Prague fork time or greater.
diff --git official op-geth/params/protocol_params.go SWC op-geth/params/protocol_params.go index 107a9fecfcb245c0c5f223ccc8ef0f9cb2e93ff8..1afbd6fb25f6d862dddf2510616de0b483183398 100644 --- official op-geth/params/protocol_params.go +++ SWC op-geth/params/protocol_params.go @@ -47,6 +47,8 @@ QuadCoeffDiv uint64 = 512 // Divisor for the quadratic particle of the memory cost equation. LogDataGas uint64 = 8 // Per byte in a LOG* operation's data. CallStipend uint64 = 2300 // Free gas given at beginning of call.   + BlobDAFee = 2000 // Fee for storing blob to DA provider + Keccak256Gas uint64 = 30 // Once per KECCAK256 operation. Keccak256WordGas uint64 = 6 // Once per word of the KECCAK256 operation's data. InitCodeWordGas uint64 = 2 // Once per word of the init code when creating a contract.

As we’re using the QKC token (which is an ERC20 token) on L1 as the gas token, the RPCTxFeeCap is changed from 1 to 1000.

diff --git official op-geth/eth/ethconfig/config.go SWC op-geth/eth/ethconfig/config.go index 29eca7bb283e461816305242bc20c964a19fbc1a..b58eb4218f4801632ba4a17cff1e0ddf8efe0d73 100644 --- official op-geth/eth/ethconfig/config.go +++ SWC op-geth/eth/ethconfig/config.go @@ -66,7 +66,7 @@ BlobPool: blobpool.DefaultConfig, RPCGasCap: 50000000, RPCEVMTimeout: 5 * time.Second, GPO: FullNodeGPO, - RPCTxFeeCap: 1, // 1 ether + RPCTxFeeCap: 1000, // 1000 QKC }   //go:generate go run github.com/fjl/gencodec -type Config -formats toml -out gen_config.go
diff --git official op-geth/accounts/abi/util/abi.go SWC op-geth/accounts/abi/util/abi.go new file mode 100644 index 0000000000000000000000000000000000000000..43b9dee22942f683b9e573ea8f4c0444033d8df0 --- /dev/null +++ SWC op-geth/accounts/abi/util/abi.go @@ -0,0 +1,105 @@ +package util + +import ( + "fmt" + "regexp" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" +) + +var functionRegex = regexp.MustCompile(`(?:function\s+)?(\w+)\s*\((.*?)\)\s*(?:returns\s*\((.*?)\))?`) +var paramsRegex = regexp.MustCompile(`([^\s,]+)\s+([^\s,]+)`) + +// example humanReadable: swapTokensForExactBNB(uint256 amountOut, uint256 amountInMax, address[] path, address to, uint256 deadline) +func ParseFunction(humanReadable string) (sig string, in, out abi.Arguments, err error) { + + matches := functionRegex.FindAllStringSubmatch(humanReadable, -1) + if len(matches) == 0 { + err = fmt.Errorf("no matches found") + return + } + if len(matches) > 1 { + err = fmt.Errorf("too many matches found") + return + } + match := matches[0] + + funcName := strings.TrimSpace(match[1]) + inArgs := strings.TrimSpace(match[2]) + var outArgs string + if len(match) == 4 { + outArgs = strings.TrimSpace(match[3]) + } + + in, err = parseArgs(inArgs) + if err != nil { + return + } + out, err = parseArgs(outArgs) + if err != nil { + return + } + + var types []string + for _, arg := range in { + types = append(types, arg.Type.String()) + } + + sig = funcName + "(" + strings.Join(types, ",") + ")" + return +} + +func ParseFunctionsAsABI(humanReadables []string) (ab abi.ABI, err error) { + ab.Methods = make(map[string]abi.Method) + + for _, humanReadable := range humanReadables { + var ( + sig string + in, out abi.Arguments + ) + sig, in, out, err = ParseFunction(humanReadable) + if err != nil { + return + } + method := SigToMethod(sig) + + ab.Methods[method] = abi.NewMethod(method, method, abi.Function, "", false, false, in, out) + } + return +} + +func SigToMethod(sig string) string { + idx := strings.Index(sig, "(") + if idx == -1 { + return sig + } + + return sig[0:idx] +} + +func parseArgs(arg string) (args abi.Arguments, err error) { + if arg == "" { + return + } + + matches := paramsRegex.FindAllStringSubmatch(arg, -1) + if len(matches) == 0 { + return + } + + for _, match := range matches { + ty := match[1] + if ty == "uint" { + ty = "uint256" + } + var abiTy abi.Type + abiTy, err = abi.NewType(ty, "", nil) + if err != nil { + return + } + name := match[2] + args = append(args, abi.Argument{Name: name, Type: abiTy}) + } + return +}