op-geth

diff: ignored:
+737
-71
+65
-0

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

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 b165686fcd352004cfd6084cc782e1080f6995b1..0cd305104fa919cf1da6a40d8a7f4356132eb784 100644 --- official op-geth/beacon/engine/types.go +++ SWC op-geth/beacon/engine/types.go @@ -277,6 +277,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/consensus/misc/eip4844/eip4844.go SWC op-geth/consensus/misc/eip4844/eip4844.go index a7df6f2cbc4a679645aa4d7803affcfe6cd8a84a..0e82afa1548b995e9c60624eb1192b242583ac1a 100644 --- official op-geth/consensus/misc/eip4844/eip4844.go +++ SWC op-geth/consensus/misc/eip4844/eip4844.go @@ -136,15 +136,21 @@ func CalcExcessBlobGas(config *params.ChainConfig, parent *types.Header, headTimestamp uint64) uint64 { // OP-Stack chains don't support blobs, but still set the excessBlobGas field (always to zero). // So this function is called in many places for OP-Stack chains too. In order to not require // a blob schedule in the chain config, we short circuit here. - if config.IsOptimism() { - if config.BlobScheduleConfig != nil { - panic("OP-Stack: CalcBlobFee: unexpected blob schedule or excess blob gas") - } - return 0 - } + // if config.IsOptimism() { + // if config.BlobScheduleConfig != nil { + // panic("OP-Stack: CalcBlobFee: unexpected blob schedule or excess blob gas") + // } + // return 0 + // }   isOsaka := config.IsOsaka(config.LondonBlock, headTimestamp) bcfg := latestBlobConfig(config, headTimestamp) + if bcfg == nil { + if config.IsL2Blob(config.LondonBlock, headTimestamp) { + panic("failed to load blob config for L2Blob") + } + return 0 + } return calcExcessBlobGas(isOsaka, bcfg, parent) }   @@ -186,16 +192,19 @@ func CalcBlobFee(config *params.ChainConfig, header *types.Header) *big.Int { // OP-Stack chains don't support blobs, but still set the excessBlobGas field (always to zero). // So this function is called in many places for OP-Stack chains too. In order to not require // a blob schedule in the chain config, we short circuit here. - if config.IsOptimism() { - if config.BlobScheduleConfig != nil || header.ExcessBlobGas == nil || *header.ExcessBlobGas != 0 { - panic("OP-Stack: CalcBlobFee: unexpected blob schedule or excess blob gas") - } - return minBlobGasPrice - } + // if config.IsOptimism() { + // if config.BlobScheduleConfig != nil || header.ExcessBlobGas == nil || *header.ExcessBlobGas != 0 { + // panic("OP-Stack: CalcBlobFee: unexpected blob schedule or excess blob gas") + // } + // return minBlobGasPrice + // }   blobConfig := latestBlobConfig(config, header.Time) if blobConfig == nil { - panic("calculating blob fee on unsupported fork") + if config.IsL2Blob(config.LondonBlock, header.Time) { + panic("failed to load blob config for L2Blob") + } + return minBlobGasPrice } return blobConfig.blobBaseFee(*header.ExcessBlobGas) }
diff --git official op-geth/core/block_validator.go SWC op-geth/core/block_validator.go index 05c03235eda53da7b49b1c9776621719e97c262c..a6a598fc71563451cf0821dbdb60be5973c81eb2 100644 --- official op-geth/core/block_validator.go +++ SWC op-geth/core/block_validator.go @@ -106,7 +106,7 @@ // happens in state transition. }   // Check blob gas usage. - if !v.config.IsOptimism() && header.BlobGasUsed != nil { + if (!v.config.IsOptimism() || v.config.IsL2Blob(header.Number, header.Time)) && header.BlobGasUsed != nil { if want := *header.BlobGasUsed / params.BlobTxBlobGasPerBlob; uint64(blobs) != want { // div because the header is surely good vs the body might be bloated return fmt.Errorf("blob gas used mismatch (header %v, calculated %v)", *header.BlobGasUsed, blobs*params.BlobTxBlobGasPerBlob) }
diff --git official op-geth/core/types/rollup_cost.go SWC op-geth/core/types/rollup_cost.go index ffbdd2098f6e620ebab2e137086244a26893f269..053951448a1e08f4f192215e8baff06a0fe2ca25 100644 --- official op-geth/core/types/rollup_cost.go +++ SWC op-geth/core/types/rollup_cost.go @@ -67,6 +67,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)) @@ -103,6 +105,7 @@ // availability costs for the transaction. type RollupCostData struct { Zeroes, Ones uint64 FastLzSize uint64 + Blobs uint64 }   type StateGetter interface { @@ -134,7 +137,7 @@ // operatorCostFunc is an internal version of OperatorCostFunc that is used for caching. type operatorCostFunc func(gasUsed uint64) *uint256.Int   -func NewRollupCostData(data []byte) (out RollupCostData) { +func NewRollupCostData(data []byte, blobs int) (out RollupCostData) { for _, b := range data { if b == 0 { out.Zeroes++ @@ -142,7 +145,9 @@ } else { out.Ones++ } } + out.FastLzSize = uint64(FlzCompressLen(data)) + out.Blobs = uint64(blobs) return out }   @@ -181,11 +186,14 @@ l1BaseFeeScalar, l1BlobBaseFeeScalar := ExtractEcotoneFeeParams(l1FeeScalars)   if config.IsOptimismFjord(blockTime) { + l1BaseFeeScalarMultiplier, l1BlobBaseFeeScalarMultiplier := config.Optimism.L1ScalarMultipliers(blockTime) return NewL1CostFuncFjord( l1BaseFee, l1BlobBaseFee, l1BaseFeeScalar, l1BlobBaseFeeScalar, + l1BaseFeeScalarMultiplier, + l1BlobBaseFeeScalarMultiplier, ) } else { return newL1CostFuncEcotone(l1BaseFee, l1BlobBaseFee, l1BaseFeeScalar, l1BlobBaseFeeScalar) @@ -343,6 +351,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 } @@ -408,6 +417,8 @@ }   // extractL1GasParams extracts the gas parameters necessary to compute gas costs from L1 block info func extractL1GasParams(config *params.ChainConfig, time uint64, data []byte) (gasParams, error) { + l1BaseFeeScalarMultiplier, l1BlobBaseFeeScalarMultiplier := config.Optimism.L1ScalarMultipliers(time) + if config.IsIsthmus(time) && len(data) >= 4 && !bytes.Equal(data[0:4], EcotoneL1AttributesSelector) { // edge case: for the very first Isthmus block we still need to use the Ecotone // function. We detect this edge case by seeing if the function selector is the old one @@ -416,11 +427,13 @@ p, err := extractL1GasParamsPostIsthmus(data) if err != nil { return gasParams{}, err } + p.costFunc = NewL1CostFuncFjord( p.l1BaseFee, p.l1BlobBaseFee, big.NewInt(int64(*p.l1BaseFeeScalar)), big.NewInt(int64(*p.l1BlobBaseFeeScalar)), + l1BaseFeeScalarMultiplier, l1BlobBaseFeeScalarMultiplier, ) return p, nil } else if config.IsEcotone(time) && len(data) >= 4 && !bytes.Equal(data[0:4], BedrockL1AttributesSelector) { @@ -439,6 +452,7 @@ p.l1BaseFee, p.l1BlobBaseFee, big.NewInt(int64(*p.l1BaseFeeScalar)), big.NewInt(int64(*p.l1BlobBaseFeeScalar)), + l1BaseFeeScalarMultiplier, l1BlobBaseFeeScalarMultiplier, ) } else { p.costFunc = newL1CostFuncEcotone( @@ -605,20 +619,21 @@ return fee }   // NewL1CostFuncFjord returns an l1 cost function suitable for the Fjord upgrade -func NewL1CostFuncFjord(l1BaseFee, l1BlobBaseFee, baseFeeScalar, blobFeeScalar *big.Int) l1CostFunc { +func NewL1CostFuncFjord(l1BaseFee, l1BlobBaseFee, baseFeeScalar, blobFeeScalar, l1BaseFeeScalarMultiplier, l1BlobBaseFeeScalarMultiplier *big.Int) l1CostFunc { return func(costData RollupCostData) (fee, calldataGasUsed *big.Int) { // Fjord L1 cost function: // l1FeeScaled = baseFeeScalar*l1BaseFee*16 + blobFeeScalar*l1BlobBaseFee // estimatedSize = max(minTransactionSize, intercept + fastlzCoef*fastlzSize) // l1Cost = estimatedSize * l1FeeScaled / 1e12   - scaledL1BaseFee := new(big.Int).Mul(baseFeeScalar, l1BaseFee) + scaledL1BaseFee := new(big.Int).Mul(new(big.Int).Mul(baseFeeScalar, l1BaseFeeScalarMultiplier), l1BaseFee) calldataCostPerByte := new(big.Int).Mul(scaledL1BaseFee, sixteen) - blobCostPerByte := new(big.Int).Mul(blobFeeScalar, l1BlobBaseFee) + blobCostPerByte := new(big.Int).Mul(new(big.Int).Mul(blobFeeScalar, l1BlobBaseFeeScalarMultiplier), l1BlobBaseFee) 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 3e67a4f45e9b9899575a373549825e6d13b5faea..19b975aabb13abcadc6021b67e8d8f8614f58f0c 100644 --- official op-geth/core/types/transaction.go +++ SWC op-geth/core/types/transaction.go @@ -397,11 +397,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 d0c21d346937e064cd5fe3cb93c27e5326c56047..371d70279f400692f57a5345a57078e0ebc649ea 100644 --- official op-geth/core/types/transaction_signing.go +++ SWC op-geth/core/types/transaction_signing.go @@ -43,7 +43,7 @@ func MakeSigner(config *params.ChainConfig, blockNumber *big.Int, blockTime uint64) Signer { var signer Signer switch { case config.IsIsthmus(blockTime): - signer = NewIsthmusSigner(config.ChainID) + signer = NewIsthmusSigner(config.ChainID, config.IsL2Blob(blockNumber, blockTime)) case config.IsPrague(blockNumber, blockTime) && !config.IsOptimism(): signer = NewPragueSigner(config.ChainID) case config.IsCancun(blockNumber, blockTime) && !config.IsOptimism(): @@ -74,7 +74,7 @@ var signer Signer if config.ChainID != nil { switch { case config.IsthmusTime != nil: - signer = NewIsthmusSigner(config.ChainID) + signer = NewIsthmusSigner(config.ChainID, config.IsOptimism() && config.Optimism.L2BlobTime != nil) case config.PragueTime != nil && !config.IsOptimism(): signer = NewPragueSigner(config.ChainID) case config.CancunTime != nil && !config.IsOptimism(): @@ -299,10 +299,12 @@ // - EIP-2930 access list transactions, // - EIP-155 replay protected transactions, and // - legacy Homestead transactions. // OP-Stack addition -func NewIsthmusSigner(chainId *big.Int) Signer { +func NewIsthmusSigner(chainId *big.Int, enableL2Blob bool) Signer { s := newModernSigner(chainId, forks.Prague).(*modernSigner) // OP-Stack: remove blob tx support - delete(s.txtypes, BlobTxType) + if !enableL2Blob { + delete(s.txtypes, BlobTxType) + } return s }
diff --git official op-geth/eth/api_backend.go SWC op-geth/eth/api_backend.go index d13ebfe1e787bdff5c41b84b1b7dcb44404bfcab..be641c63a15b68b3c8a282ba72fb8e424512de5c 100644 --- official op-geth/eth/api_backend.go +++ SWC op-geth/eth/api_backend.go @@ -325,7 +325,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 }
diff --git official op-geth/eth/backend.go SWC op-geth/eth/backend.go index 064f58c24a2102b7a01a3671a6614779013a434c..5bbc5fc757968710638ed465c3d2469e3a2917bb 100644 --- official op-geth/eth/backend.go +++ SWC op-geth/eth/backend.go @@ -346,7 +346,7 @@ if config.BlobPool.Datadir != "" { config.BlobPool.Datadir = stack.ResolvePath(config.BlobPool.Datadir) } txPools := []txpool.SubPool{legacyPool} - if !eth.BlockChain().Config().IsOptimism() { + if !eth.BlockChain().Config().IsOptimism() || eth.BlockChain().Config().Optimism.L2BlobTime != nil { eth.blobTxPool = blobpool.New(config.BlobPool, eth.blockchain, legacyPool.HasPendingAuth) txPools = append(txPools, eth.blobTxPool) }
diff --git official op-geth/miner/worker.go SWC op-geth/miner/worker.go index 2bab122a202151b1d83e0a079da0c9cac39a68ca..c98a69bb0ebf51d135f0b110b663591cb8e68cdd 100644 --- official op-geth/miner/worker.go +++ SWC op-geth/miner/worker.go @@ -82,11 +82,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 }   // txFits reports whether the transaction fits into the block size limit. @@ -370,6 +376,7 @@ } if miner.chainConfig.IsPrague(header.Number, header.Time) { core.ProcessParentBlockHash(header.ParentHash, env.evm) } + env.noTxs = genParams.noTxs // invariant: genParams.noTxs has the same boolean value as PayloadAttributes.NoTxPool return env, nil }   @@ -455,15 +462,25 @@ }   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. maxBlobs := eip4844.MaxBlobsPerBlock(miner.chainConfig, env.header.Time) - if env.blobs+len(sc.Blobs) > maxBlobs { + if env.blobs+nblobs > maxBlobs { return errors.New("max data blobs reached") } receipt, err := miner.applyTransaction(env, tx) @@ -473,8 +490,10 @@ } txNoBlob := tx.WithoutBlobTxSidecar() env.txs = append(env.txs, txNoBlob) 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.size += txNoBlob.Size() *env.header.BlobGasUsed += receipt.BlobGasUsed env.tcount++
diff --git official op-geth/params/config.go SWC op-geth/params/config.go index e585e66d8cff3998ba055a1c93c1c1bb071015c4..2ada8f0e61402f5b98b1c10fad8d2c79575263c7 100644 --- official op-geth/params/config.go +++ SWC op-geth/params/config.go @@ -566,6 +566,17 @@ type OptimismConfig struct { EIP1559Elasticity uint64 `json:"eip1559Elasticity"` EIP1559Denominator uint64 `json:"eip1559Denominator"` EIP1559DenominatorCanyon *uint64 `json:"eip1559DenominatorCanyon,omitempty"` + L2BlobTime *uint64 `json:"l2BlobTime,omitempty"` // L2Blob switch time (nil = no fork, 0 = already on optimism l2blob) + // Flag for when to activate SoulGasToken for gas fee. + SoulGasTokenTime *uint64 `json:"soulGasTokenTime,omitempty"` + // Whether SoulGasToken is backed by native token or minted by whitelisted miners, only effective when SoulGasTokenTime is non-nil + IsSoulBackedByNative bool `json:"isSoulBackedByNative"` + // The multiplier of the L1BaseFeeScalar, used to keep the L1BaseFeeScalar size compatible with uint32 and calculate the effective L1BaseFeeScalar; + // Only effective when the value is non-zero + L1BaseFeeScalarMultiplier uint64 `json:"l1BaseFeeScalarMultiplier,omitempty"` + // The multiplier of the L1BlobBaseFeeScalar, used to keep the L1BlobBaseFeeScalar size compatible with uint32 and calculate the effective L1BlobBaseFeeScalar; + // Only effective when the value is non-zero + L1BlobBaseFeeScalarMultiplier uint64 `json:"l1BlobBaseFeeScalarMultiplier,omitempty"` }   // String implements the stringer interface, returning the optimism fee config details. @@ -573,6 +584,37 @@ func (o *OptimismConfig) String() string { return "optimism" }   +func (o *OptimismConfig) IsSoulGasToken(targetTime uint64) bool { + return o.SoulGasTokenTime != nil && *o.SoulGasTokenTime <= targetTime +} + +// this flag is only true in test +var L1ScalarMultipliersTestFlag bool +var L1ScalarMultipliersCalled bool + +// L1ScalarMultipliers returns the scalar multipliers to make the L1BaseFeeScalar and L1BlobBaseFeeScalar compatible with uint32. +// It needs to be applied for all future changes to the L1 cost. +func (o *OptimismConfig) L1ScalarMultipliers(blockTime uint64) (*big.Int, *big.Int) { + // blockTime is not used in the current implementation, but it is kept here for future compatibility + + // this code block is only used for test + if L1ScalarMultipliersTestFlag && !L1ScalarMultipliersCalled { + L1ScalarMultipliersCalled = true + } + + // default values for the scalar multipliers + l1BaseFeeScalarMultiplier := int64(1) + l1BlobBaseFeeScalarMultiplier := int64(1) + + if o.L1BaseFeeScalarMultiplier != 0 { + l1BaseFeeScalarMultiplier = int64(o.L1BaseFeeScalarMultiplier) + } + if o.L1BlobBaseFeeScalarMultiplier != 0 { + l1BlobBaseFeeScalarMultiplier = int64(o.L1BlobBaseFeeScalarMultiplier) + } + return big.NewInt(l1BaseFeeScalarMultiplier), big.NewInt(l1BlobBaseFeeScalarMultiplier) +} + // Description returns a human-readable description of ChainConfig. func (c *ChainConfig) Description() string { var banner string @@ -691,6 +733,17 @@ banner += fmt.Sprintf(" - Jovian: @%-10v\n", *c.JovianTime) } if c.InteropTime != nil { banner += fmt.Sprintf(" - Interop: @%-10v\n", *c.InteropTime) + } + banner += "\n" + if c.Optimism != nil { + if c.Optimism.L2BlobTime != nil { + banner += fmt.Sprintf(" - L2BLob: @%-10v\n", *c.Optimism.L2BlobTime) + } + if c.Optimism.SoulGasTokenTime == nil { + banner += "SGT: false" + } else { + banner += fmt.Sprintf("SGT: @%-10v, Back by native %t", *c.Optimism.SoulGasTokenTime, c.Optimism.IsSoulBackedByNative) + } } return banner } @@ -805,6 +858,11 @@ 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.Optimism.L2BlobTime, time) +} + // IsPrague returns whether time is either equal to the Prague fork time or greater. func (c *ChainConfig) IsPrague(num *big.Int, time uint64) bool { return c.IsLondon(num) && isTimestampForked(c.PragueTime, time) @@ -1053,9 +1111,7 @@ // OP-Stack chains don't support blobs, and must have a nil BlobScheduleConfig. if c.IsOptimism() { if c.BlobScheduleConfig == nil { - return nil - } else { - return errors.New("OP-Stack chains must have empty blob configuration") + c.BlobScheduleConfig = DefaultBlobSchedule } }   @@ -1161,6 +1217,14 @@ return newBlockCompatError("Gray Glacier fork block", c.GrayGlacierBlock, newcfg.GrayGlacierBlock) } if isForkBlockIncompatible(c.MergeNetsplitBlock, newcfg.MergeNetsplitBlock, headNumber) { return newBlockCompatError("Merge netsplit fork block", c.MergeNetsplitBlock, newcfg.MergeNetsplitBlock) + } + if c.Optimism != nil && newcfg.Optimism != nil { + if isForkTimestampIncompatible(c.Optimism.L2BlobTime, newcfg.Optimism.L2BlobTime, headTimestamp, genesisTimestamp) { + return newTimestampCompatError("L2Blob fork timestamp", c.Optimism.L2BlobTime, newcfg.Optimism.L2BlobTime) + } + if isForkTimestampIncompatible(c.Optimism.SoulGasTokenTime, newcfg.Optimism.SoulGasTokenTime, headTimestamp, genesisTimestamp) { + return newTimestampCompatError("SGT fork timestamp", c.Optimism.SoulGasTokenTime, newcfg.Optimism.SoulGasTokenTime) + } } if isForkTimestampIncompatible(c.ShanghaiTime, newcfg.ShanghaiTime, headTimestamp, genesisTimestamp) { return newTimestampCompatError("Shanghai fork timestamp", c.ShanghaiTime, newcfg.ShanghaiTime)
diff --git official op-geth/params/protocol_params.go SWC op-geth/params/protocol_params.go index cd49a521e061590393bdb6972c12444da35bc516..95f291e881193a9736141d00b278f62cbb2668b8 100644 --- official op-geth/params/protocol_params.go +++ SWC op-geth/params/protocol_params.go @@ -53,6 +53,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.
diff --git official op-geth/params/superchain.go SWC op-geth/params/superchain.go index 48820d196b11a5ad38b8592cefed76687f79eb56..e4ef51b32720fe3b8e9d263c0a7b830d1d053ad8 100644 --- official op-geth/params/superchain.go +++ SWC op-geth/params/superchain.go @@ -70,6 +70,15 @@ } if chConfig.Optimism.EIP1559DenominatorCanyon != nil { out.Optimism.EIP1559DenominatorCanyon = uint64ptr(*chConfig.Optimism.EIP1559DenominatorCanyon) } + if chConfig.Optimism.L2BlobTime != nil { + out.Optimism.L2BlobTime = uint64ptr(*chConfig.Optimism.L2BlobTime) + } + if chConfig.Optimism.SoulGasTokenTime != nil { + out.Optimism.SoulGasTokenTime = uint64ptr(*chConfig.Optimism.SoulGasTokenTime) + } + out.Optimism.IsSoulBackedByNative = chConfig.Optimism.IsSoulBackedByNative + out.Optimism.L1BaseFeeScalarMultiplier = chConfig.Optimism.L1BaseFeeScalarMultiplier + out.Optimism.L1BlobBaseFeeScalarMultiplier = chConfig.Optimism.L1BlobBaseFeeScalarMultiplier }   // special overrides for OP-Stack chains with pre-Regolith upgrade history
diff --git official op-geth/superchain/types.go SWC op-geth/superchain/types.go index 67b68b3a241ab3daf995b41994cb560975aa19bc..ed34a881d6b03cadda2ea41ed435b2fcdc7208a0 100644 --- official op-geth/superchain/types.go +++ SWC op-geth/superchain/types.go @@ -4,6 +4,10 @@ import ( "github.com/ethereum/go-ethereum/common" )   +type InboxContractConfig struct { + UseInboxContract bool `toml:"use_inbox_contract,omitempty"` +} + type ChainConfig struct { Name string `toml:"name"` PublicRPC string `toml:"public_rpc"` @@ -24,6 +28,8 @@ GasPayingToken *common.Address `toml:"gas_paying_token"` Hardforks HardforkConfig `toml:"hardforks"` Interop *Interop `toml:"interop,omitempty"` Optimism *OptimismConfig `toml:"optimism,omitempty"` + + InboxContractConfig *InboxContractConfig `toml:"inbox_contract_config,omitempty"`   AltDA *AltDAConfig `toml:"alt_da,omitempty"`   @@ -55,9 +61,14 @@ PectraBlobScheduleTime *uint64 `toml:"pectra_blob_schedule_time,omitempty"` }   type OptimismConfig struct { - EIP1559Elasticity uint64 `toml:"eip1559_elasticity"` - EIP1559Denominator uint64 `toml:"eip1559_denominator"` - EIP1559DenominatorCanyon *uint64 `toml:"eip1559_denominator_canyon"` + EIP1559Elasticity uint64 `toml:"eip1559_elasticity"` + EIP1559Denominator uint64 `toml:"eip1559_denominator"` + EIP1559DenominatorCanyon *uint64 `toml:"eip1559_denominator_canyon"` + L2BlobTime *uint64 `toml:"l2_blob_time"` + SoulGasTokenTime *uint64 `toml:"soul_gas_token_time"` + IsSoulBackedByNative bool `toml:"is_soul_backed_by_native"` + L1BaseFeeScalarMultiplier uint64 `toml:"l1_base_fee_scalar_multiplier"` + L1BlobBaseFeeScalarMultiplier uint64 `toml:"l1_blob_base_fee_scalar_multiplier"` }   type AltDAConfig struct {

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 79d7377a2fa68403717ad2a05fd23451a6aac5d0..164438975aed9d974d6b5521cf3369b8130cafb3 100644 --- official op-geth/eth/ethconfig/config.go +++ SWC op-geth/eth/ethconfig/config.go @@ -69,7 +69,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/eth/gasprice/gasprice.go SWC op-geth/eth/gasprice/gasprice.go index 58a59cbdb930c96585807a39f0eca88ca2f04082..5f8776a722b568788cc6037f5312a7fb046d484f 100644 --- official op-geth/eth/gasprice/gasprice.go +++ SWC op-geth/eth/gasprice/gasprice.go @@ -39,7 +39,7 @@ var ( DefaultMaxPrice = big.NewInt(500 * params.GWei) DefaultIgnorePrice = big.NewInt(2 * params.Wei)   - DefaultMinSuggestedPriorityFee = big.NewInt(1e6 * params.Wei) // 0.001 gwei, for Optimism fee suggestion + DefaultMinSuggestedPriorityFee = big.NewInt(1e9 * params.Wei) // 1 gwei, for Optimism fee suggestion )   type Config struct {
diff --git official op-geth/core/types/rollup_cost_test.go SWC op-geth/core/types/rollup_cost_test.go index c07b53f023725f1758df86e09d9bff6b38ecc317..f2f0035720613df40a63d8bbdf32ef4961b5bc64 100644 --- official op-geth/core/types/rollup_cost_test.go +++ SWC op-geth/core/types/rollup_cost_test.go @@ -7,6 +7,8 @@ "encoding/hex" "math/big" "testing"   + math2 "math" + "github.com/holiman/uint256" "github.com/stretchr/testify/require"   @@ -70,6 +72,8 @@ baseFee, blobBaseFee, baseFeeScalar, blobBaseFeeScalar, + big.NewInt(1), + big.NewInt(1), )   // Minimum size transactions: @@ -107,6 +111,8 @@ big.NewInt(2*1e6), big.NewInt(3*1e6), big.NewInt(20), big.NewInt(15), + big.NewInt(1), + big.NewInt(1), )   c0, g0 := costFunc(RollupCostData{ @@ -354,6 +360,32 @@ default: panic("unknown slot") } return buf +} + +// This test checks that the L1 scalar multipliers are used in NewL1CostFunc +// for latest HF, but still need to manually check the usage in extractL1GasParams. +// it's best-effort only. +func TestL1ScalarMultipliersAreUsedInLatestHF(t *testing.T) { + + // Note: may still need to manually turn on the latest HF flag if OP hasn't do that yet + config := params.OptimismTestConfig + statedb := &testStateGetter{ + baseFee: baseFee, + overhead: overhead, + scalar: scalar, + blobBaseFee: blobBaseFee, + baseFeeScalar: uint32(baseFeeScalar.Uint64()), + blobBaseFeeScalar: uint32(blobBaseFeeScalar.Uint64()), + } + + params.L1ScalarMultipliersCalled = false + params.L1ScalarMultipliersTestFlag = true + costFunc := NewL1CostFunc(config, statedb) + require.NotNil(t, costFunc) + + costFunc(emptyTx.RollupCostData(), math2.MaxUint64-1 /*math2.MaxUint64 is used as a special mark in NewL1CostFunc, so minus 1*/) + + require.Equal(t, params.L1ScalarMultipliersCalled, true) }   // TestNewL1CostFunc tests that the appropriate cost function is selected based on the
diff --git official op-geth/params/bootnodes.go SWC op-geth/params/bootnodes.go index 167cf800db6aead39813daf872c2fec9bea175de..f91c33f77ab0988684605f92e6a5f6d0c8b7506f 100644 --- official op-geth/params/bootnodes.go +++ SWC op-geth/params/bootnodes.go @@ -56,6 +56,11 @@ "enode://10d62eff032205fcef19497f35ca8477bea0eadfff6d769a147e895d8b2b8f8ae6341630c645c30f5df6e67547c03494ced3d9c5764e8622a26587b083b028e8@139.59.49.206:30303", // sepolia-bootnode-1-blr1 "enode://9e9492e2e8836114cc75f5b929784f4f46c324ad01daf87d956f98b3b6c5fcba95524d6e5cf9861dc96a2c8a171ea7105bb554a197455058de185fa870970c7c@138.68.123.152:30303", // sepolia-bootnode-1-ams3 }   +var BetaTestnetBootnodes = []string{ + "enode://8c83b5f320d499e6b92d26b986931780d35ae97e6621fc3cafce0ced5b33ead4fd06aef00e7a4dca11fee0ebcd168898d2b8ac94ca642fba87d73bd54b004050@176.9.142.29:30303", + "enode://4e9d3ba7d41cf1a42456c3e9d024808128027ead5d887a1365fd6181f75b4a7ca748acfac8f6b492aaa8fca349fbc0b07226b1772b6380608536494917a62f03@46.4.99.154:30303", +} + var V5Bootnodes = []string{ // Teku team's bootnode "enr:-KG4QMOEswP62yzDjSwWS4YEjtTZ5PO6r65CPqYBkgTTkrpaedQ8uEUo1uMALtJIvb2w_WWEVmg5yt1UAuK1ftxUU7QDhGV0aDKQu6TalgMAAAD__________4JpZIJ2NIJpcIQEnfA2iXNlY3AyNTZrMaEDfol8oLr6XJ7FsdAYE7lpJhKMls4G_v6qQOGKJUWGb_uDdGNwgiMog3VkcIIjKA", // # 4.157.240.54 | azure-us-east-virginia
diff --git official op-geth/qkc-fork.yaml SWC op-geth/qkc-fork.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e97a08d5f6aa93d7c9400eec8d22f87cc73890f2 --- /dev/null +++ SWC op-geth/qkc-fork.yaml @@ -0,0 +1,80 @@ +title: "SWC-op-geth - official-op-geth fork diff overview" +footer: | + Fork-diff overview of [`SWC op-geth`](https://github.com/Quarkchain/op-geth), a fork of [`official op-geth`](https://github.com/ethereum-optimism/op-geth). + and execution-engine of the [SWC OP-stack](https://github.com/Quarkchain/optimism). +base: + name: official op-geth + url: https://github.com/ethereum-optimism/op-geth + hash: 8da5bf081a47206ee75bb88c90bf64fed4bc8e97 +fork: + name: SWC op-geth + url: https://github.com/Quarkchain/op-geth + ref: refs/heads/op-es +def: + title: "op-geth" + description: | + This is an overview of the changes in [`SWC op-geth`](https://github.com/Quarkchain/op-geth), + a fork of [`official op-geth`](https://github.com/ethereum-optimism/op-geth), part of the [SWC OP-stack](https://github.com/Quarkchain/optimism). + sub: + - title: "Soul Gas Token modifications" + description: | + 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](https://github.com/ethstorage/optimism/issues/148) 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. + globs: + - "core/state_transition.go" + - "core/txpool/legacypool/legacypool.go" + - "core/txpool/blobpool/blobpool.go" + - "core/txpool/validation.go" + - "eth/gasestimator/gasestimator.go" + - "internal/ethapi/api.go" + - "node/config.go" + - "node/defaults.go" + - "node/node.go" + - "cmd/geth/main.go" + - "cmd/utils/flags.go" + - "cmd/geth/config.go" + - title: "L2 Blob modifications" + description: | + 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. + globs: + - "miner/worker.go" + - "eth/api_backend.go" + - "eth/backend.go" + - "core/txpool/validation.go" + - "core/types/transaction.go" + - "core/types/transaction_signing.go" + - "params/config.go" + - "params/protocol_params.go" + - "beacon/engine/types.go" + - "core/types/rollup_cost.go" + - "consensus/misc/eip4844/eip4844.go" + - title: "Custom Gas Token modification" + description: | + 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. + globs: + - "eth/ethconfig/config.go" + + +# ignored globally, does not count towards line count +ignore: + - ".circleci/*" + - "*.sum" + - "go.mod" + - "fork.yaml" + - "Makefile" + - ".golangci.yml" + - ".github/**" + - "**/*.gob" # data asset, not code + - "core/vm/testdata/precompiles/p256Verify.json" # data asset, not code + - "eth/tracers/internal/tracetest/testdata/**/*.json"
diff --git official op-geth/.github/workflows/publish-op-geth.yml SWC op-geth/.github/workflows/publish-op-geth.yml new file mode 100644 index 0000000000000000000000000000000000000000..cd56364d6c9d49694d17f35a8f75a31ae9b1eab2 --- /dev/null +++ SWC op-geth/.github/workflows/publish-op-geth.yml @@ -0,0 +1,65 @@ +# This workflow will publish a github release for op-geth + +name: Publish +run-name: ${{ github.actor }} is publishing an op-geth release 🚀 +on: + push: + tags: + - 'v*' + +# Always wait for previous release to finish before releasing again +concurrency: ${{ github.workflow }}-${{ github.ref }} + + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + arch: [amd64, arm64] + exclude: + - os: ubuntu-latest + arch: arm64 + env: + BUILD_DIR: op-geth.${{ github.ref_name }} + BIN_DIR: op-geth.${{ github.ref_name }}/build/bin + FILE_NAME: op-geth.${{ github.ref_name }}.${{ matrix.os == 'ubuntu-latest' && 'linux' || 'darwin' }}-${{ matrix.arch }}.tar.gz + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup go + uses: actions/setup-go@v5 + with: + go-version: '1.22.7' + + - name: Build + run: | + CGO_ENABLED=0 TARGETOS=${{ matrix.os == 'ubuntu-latest' && 'linux' || 'darwin' }} TARGETARCH=${{ matrix.arch }} make geth + mkdir -p ${{ env.BIN_DIR }} + mv build/bin/geth ${{ env.BIN_DIR }}/ + tar -czvf ${{ env.FILE_NAME }} ${{ env.BUILD_DIR }} + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ env.FILE_NAME }} + path: ${{ env.FILE_NAME }} + + release: + needs: build + runs-on: ubuntu-latest + steps: + - name: Download artifact + uses: actions/download-artifact@v4 + + - name: Create release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.ref }} + name: Release ${{ github.ref_name }} + files: | + **/* + fail_on_unmatched_files: true + generate_release_notes: true