From 67b54be85b70b0b86472b4a01b38f619e842e2d3 Mon Sep 17 00:00:00 2001 From: Yuwen Zhang Date: Tue, 24 Sep 2024 11:41:39 -0700 Subject: [PATCH 01/18] added types + tests --- core/types/gen_receipt_json.go | 12 ++++ core/types/receipt.go | 8 ++- core/types/receipt_test.go | 105 +++++++++++++++++++++++++++++++++ core/types/rollup_cost.go | 64 ++++++++++++++++++-- core/types/rollup_cost_test.go | 65 +++++++++++++++++++- 5 files changed, 245 insertions(+), 9 deletions(-) diff --git a/core/types/gen_receipt_json.go b/core/types/gen_receipt_json.go index 4e544a0b52..cd527d7d40 100644 --- a/core/types/gen_receipt_json.go +++ b/core/types/gen_receipt_json.go @@ -40,6 +40,8 @@ func (r Receipt) MarshalJSON() ([]byte, error) { FeeScalar *big.Float `json:"l1FeeScalar,omitempty"` L1BaseFeeScalar *hexutil.Uint64 `json:"l1BaseFeeScalar,omitempty"` L1BlobBaseFeeScalar *hexutil.Uint64 `json:"l1BlobBaseFeeScalar,omitempty"` + OperatorFeeScalar *hexutil.Uint64 `json:"operatorFeeScalar,omitempty"` + OperatorFeeConstant *hexutil.Uint64 `json:"operatorFeeConstant,omitempty"` } var enc Receipt enc.Type = hexutil.Uint64(r.Type) @@ -66,6 +68,8 @@ func (r Receipt) MarshalJSON() ([]byte, error) { enc.FeeScalar = r.FeeScalar enc.L1BaseFeeScalar = (*hexutil.Uint64)(r.L1BaseFeeScalar) enc.L1BlobBaseFeeScalar = (*hexutil.Uint64)(r.L1BlobBaseFeeScalar) + enc.OperatorFeeScalar = (*hexutil.Uint64)(r.OperatorFeeScalar) + enc.OperatorFeeConstant = (*hexutil.Uint64)(r.OperatorFeeConstant) return json.Marshal(&enc) } @@ -96,6 +100,8 @@ func (r *Receipt) UnmarshalJSON(input []byte) error { FeeScalar *big.Float `json:"l1FeeScalar,omitempty"` L1BaseFeeScalar *hexutil.Uint64 `json:"l1BaseFeeScalar,omitempty"` L1BlobBaseFeeScalar *hexutil.Uint64 `json:"l1BlobBaseFeeScalar,omitempty"` + OperatorFeeScalar *hexutil.Uint64 `json:"operatorFeeScalar,omitempty"` + OperatorFeeConstant *hexutil.Uint64 `json:"operatorFeeConstant,omitempty"` } var dec Receipt if err := json.Unmarshal(input, &dec); err != nil { @@ -178,5 +184,11 @@ func (r *Receipt) UnmarshalJSON(input []byte) error { if dec.L1BlobBaseFeeScalar != nil { r.L1BlobBaseFeeScalar = (*uint64)(dec.L1BlobBaseFeeScalar) } + if dec.OperatorFeeScalar != nil { + r.OperatorFeeScalar = (*uint64)(dec.OperatorFeeScalar) + } + if dec.OperatorFeeConstant != nil { + r.OperatorFeeConstant = (*uint64)(dec.OperatorFeeConstant) + } return nil } diff --git a/core/types/receipt.go b/core/types/receipt.go index 4bc83bf988..990334aace 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -84,7 +84,7 @@ type Receipt struct { BlockNumber *big.Int `json:"blockNumber,omitempty"` TransactionIndex uint `json:"transactionIndex"` - // Optimism: extend receipts with L1 fee info + // Optimism: extend receipts with L1 and operator fee info L1GasPrice *big.Int `json:"l1GasPrice,omitempty"` // Present from pre-bedrock. L1 Basefee after Bedrock L1BlobBaseFee *big.Int `json:"l1BlobBaseFee,omitempty"` // Always nil prior to the Ecotone hardfork L1GasUsed *big.Int `json:"l1GasUsed,omitempty"` // Present from pre-bedrock, deprecated as of Fjord @@ -92,6 +92,8 @@ type Receipt struct { FeeScalar *big.Float `json:"l1FeeScalar,omitempty"` // Present from pre-bedrock to Ecotone. Nil after Ecotone L1BaseFeeScalar *uint64 `json:"l1BaseFeeScalar,omitempty"` // Always nil prior to the Ecotone hardfork L1BlobBaseFeeScalar *uint64 `json:"l1BlobBaseFeeScalar,omitempty"` // Always nil prior to the Ecotone hardfork + OperatorFeeScalar *uint64 `json:"operatorFeeScalar,omitempty"` // Always nil prior to the Holocene hardfork + OperatorFeeConstant *uint64 `json:"operatorFeeConstant,omitempty"` // Always nil prior to the Holocene hardfork } type receiptMarshaling struct { @@ -116,6 +118,8 @@ type receiptMarshaling struct { L1BlobBaseFeeScalar *hexutil.Uint64 DepositNonce *hexutil.Uint64 DepositReceiptVersion *hexutil.Uint64 + OperatorFeeScalar *hexutil.Uint64 + OperatorFeeConstant *hexutil.Uint64 } // receiptRLP is the consensus encoding of a receipt. @@ -590,6 +594,8 @@ func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, nu rs[i].FeeScalar = gasParams.feeScalar rs[i].L1BaseFeeScalar = u32ptrTou64ptr(gasParams.l1BaseFeeScalar) rs[i].L1BlobBaseFeeScalar = u32ptrTou64ptr(gasParams.l1BlobBaseFeeScalar) + rs[i].OperatorFeeScalar = u32ptrTou64ptr(gasParams.operatorFeeScalar) + rs[i].OperatorFeeConstant = gasParams.operatorFeeConstant } } return nil diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go index 76599fdd32..be687e8008 100644 --- a/core/types/receipt_test.go +++ b/core/types/receipt_test.go @@ -48,6 +48,12 @@ var ( conf.EcotoneTime = &time return &conf }() + holoceneTestConfig = func() *params.ChainConfig { + conf := *bedrockGenesisTestConfig // copy the config + time := uint64(0) + conf.HoloceneTime = &time + return &conf + }() legacyReceipt = &Receipt{ Status: ReceiptStatusFailed, @@ -768,6 +774,79 @@ func getOptimismEcotoneTxReceipts(l1AttributesPayload []byte, l1GasPrice, l1Blob return txs, receipts } +func getOptimismHoloceneTxReceipts(l1AttributesPayload []byte, l1GasPrice, l1BlobGasPrice, l1GasUsed, l1Fee *big.Int, baseFeeScalar, blobBaseFeeScalar, operatorFeeScalar, operatorFeeConstant *uint64) ([]*Transaction, []*Receipt) { + // Create a few transactions to have receipts for + txs := Transactions{ + NewTx(&DepositTx{ + To: nil, // contract creation + Value: big.NewInt(6), + Gas: 50, + Data: l1AttributesPayload, + }), + emptyTx, + } + + // Create the corresponding receipts + receipts := Receipts{ + &Receipt{ + Type: DepositTxType, + PostState: common.Hash{5}.Bytes(), + CumulativeGasUsed: 50 + 15, + Logs: []*Log{ + { + Address: common.BytesToAddress([]byte{0x33}), + // derived fields: + BlockNumber: blockNumber.Uint64(), + TxHash: txs[0].Hash(), + TxIndex: 0, + BlockHash: blockHash, + Index: 0, + }, + { + Address: common.BytesToAddress([]byte{0x03, 0x33}), + // derived fields: + BlockNumber: blockNumber.Uint64(), + TxHash: txs[0].Hash(), + TxIndex: 0, + BlockHash: blockHash, + Index: 1, + }, + }, + TxHash: txs[0].Hash(), + ContractAddress: common.HexToAddress("0x3bb898b4bbe24f68a4e9be46cfe72d1787fd74f4"), + GasUsed: 65, + EffectiveGasPrice: big.NewInt(0), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 0, + DepositNonce: &depNonce1, + // Don't need a loto fo other stuff because this is a deposit transaction + }, + &Receipt{ + Type: LegacyTxType, + EffectiveGasPrice: big.NewInt(0), + PostState: common.Hash{4}.Bytes(), + CumulativeGasUsed: 10, + Logs: []*Log{}, + // derived fields: + TxHash: txs[1].Hash(), + GasUsed: 18446744073709551561, + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 1, + L1GasPrice: l1GasPrice, + L1BlobBaseFee: l1BlobGasPrice, + L1GasUsed: l1GasUsed, + L1Fee: l1Fee, + L1BaseFeeScalar: baseFeeScalar, + L1BlobBaseFeeScalar: blobBaseFeeScalar, + OperatorFeeScalar: operatorFeeScalar, + OperatorFeeConstant: operatorFeeConstant, + }, + } + return txs, receipts +} + func getOptimismTxReceipts(l1AttributesPayload []byte, l1GasPrice, l1GasUsed, l1Fee *big.Int, feeScalar *big.Float) ([]*Transaction, []*Receipt) { // Create a few transactions to have receipts for txs := Transactions{ @@ -888,6 +967,32 @@ func TestDeriveOptimismEcotoneTxReceipts(t *testing.T) { diffReceipts(t, receipts, derivedReceipts) } +func TestDeriveOptimismHoloceneTxReceipts(t *testing.T) { + // Holocene style l1 attributes with baseFeeScalar=2, blobBaseFeeScalar=3, baseFee=1000*1e6, blobBaseFee=10*1e6, eip1559Denominator=0, eip1559Elasticity=0, operatorFeeScalar=7, operatorFeeConstant=9 + payload := common.Hex2Bytes("d1fbe15b000000020000000300000000000004d200000000000004d200000000000004d2000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000000098968000000000000000000000000000000000000000000000000000000000000004d200000000000000000000000000000000000000000000000000000000000004d20000000000000000000000000000000000000007000000000000000900000000") + // the parameters we use below are defined in rollup_test.go + baseFeeScalarUint64 := baseFeeScalar.Uint64() + blobBaseFeeScalarUint64 := blobBaseFeeScalar.Uint64() + operatorFeeScalarUint64 := operatorFeeScalar.Uint64() + operatorFeeConstantUint64 := operatorFeeConstant.Uint64() + txs, receipts := getOptimismHoloceneTxReceipts(payload, baseFee, blobBaseFee, minimumFjordGas, fjordFee, &baseFeeScalarUint64, &blobBaseFeeScalarUint64, &operatorFeeScalarUint64, &operatorFeeConstantUint64) + + // Re-derive receipts. + baseFee := big.NewInt(1000) + derivedReceipts := clearComputedFieldsOnReceipts(receipts) + // Should error out if we try to process this with a pre-Holocene config + err := Receipts(derivedReceipts).DeriveFields(bedrockGenesisTestConfig, blockHash, blockNumber.Uint64(), 0, baseFee, nil, txs) + if err == nil { + t.Fatalf("expected error from deriving holocene receipts with pre-holocene config, got none") + } + + err = Receipts(derivedReceipts).DeriveFields(holoceneTestConfig, blockHash, blockNumber.Uint64(), 0, baseFee, nil, txs) + if err != nil { + t.Fatalf("DeriveFields(...) = %v, want ", err) + } + diffReceipts(t, receipts, derivedReceipts) +} + func diffReceipts(t *testing.T, receipts, derivedReceipts []*Receipt) { // Check diff of receipts against derivedReceipts. r1, err := json.MarshalIndent(receipts, "", " ") diff --git a/core/types/rollup_cost.go b/core/types/rollup_cost.go index e0bc26d92f..582c1e88e4 100644 --- a/core/types/rollup_cost.go +++ b/core/types/rollup_cost.go @@ -248,6 +248,8 @@ type gasParams struct { feeScalar *big.Float // pre-ecotone l1BaseFeeScalar *uint32 // post-ecotone l1BlobBaseFeeScalar *uint32 // post-ecotone + operatorFeeScalar *uint32 // post-holocene + operatorFeeConstant *uint64 // post-holocene } // intToScaledFloat returns scalar/10e6 as a float @@ -259,11 +261,23 @@ func intToScaledFloat(scalar *big.Int) *big.Float { // 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) { - // edge case: for the very first Ecotone block we still need to use the Bedrock - // function. We detect this edge case by seeing if the function selector is the old one - // If so, fall through to the pre-ecotone format - // Both Ecotone and Fjord use the same function selector - if config.IsEcotone(time) && len(data) >= 4 && !bytes.Equal(data[0:4], BedrockL1AttributesSelector) { + if config.IsHolocene(time) { + p, err := extractL1GasParamsPostHolocene(data) + if err != nil { + return gasParams{}, err + } + p.costFunc = NewL1CostFuncFjord( + p.l1BaseFee, + p.l1BlobBaseFee, + big.NewInt(int64(*p.l1BaseFeeScalar)), + big.NewInt(int64(*p.l1BlobBaseFeeScalar)), + ) + return p, nil + } else if config.IsEcotone(time) && len(data) >= 4 && !bytes.Equal(data[0:4], BedrockL1AttributesSelector) { + // edge case: for the very first Ecotone block we still need to use the Bedrock + // function. We detect this edge case by seeing if the function selector is the old one + // If so, fall through to the pre-ecotone format + // Both Ecotone and Fjord use the same function selector p, err := extractL1GasParamsPostEcotone(data) if err != nil { return gasParams{}, err @@ -338,6 +352,46 @@ func extractL1GasParamsPostEcotone(data []byte) (gasParams, error) { }, nil } +// extractL1GasParamsPostHolocene extracts the gas parameters necessary to compute gas from L1 attribute +// info calldata after the Holocene upgrade, but not for the very first Holocene block. +func extractL1GasParamsPostHolocene(data []byte) (gasParams, error) { + if len(data) != 196 { + return gasParams{}, fmt.Errorf("expected 196 L1 info bytes, got %d", len(data)) + } + // data layout assumed for Holocene: + // offset type varname + // 0 + // 4 uint32 _basefeeScalar + // 8 uint32 _blobBaseFeeScalar + // 12 uint64 _sequenceNumber, + // 20 uint64 _timestamp, + // 28 uint64 _l1BlockNumber + // 36 uint256 _basefee, + // 68 uint256 _blobBaseFee, + // 100 bytes32 _hash, + // 132 bytes32 _batcherHash, + // 164 uint64 _eip1559Denominator + // 172 uint64 _eip1559Elasticity + // 180 uint32 _operatorFeeScalar + // 184 uint64 _operatorFeeConstant + // 192 uint32 UNUSED + l1BaseFee := new(big.Int).SetBytes(data[36:68]) + l1BlobBaseFee := new(big.Int).SetBytes(data[68:100]) + l1BaseFeeScalar := binary.BigEndian.Uint32(data[4:8]) + l1BlobBaseFeeScalar := binary.BigEndian.Uint32(data[8:12]) + operatorFeeScalar := binary.BigEndian.Uint32(data[180:184]) + operatorFeeConstant := binary.BigEndian.Uint64(data[184:192]) + + return gasParams{ + l1BaseFee: l1BaseFee, + l1BlobBaseFee: l1BlobBaseFee, + l1BaseFeeScalar: &l1BaseFeeScalar, + l1BlobBaseFeeScalar: &l1BlobBaseFeeScalar, + operatorFeeScalar: &operatorFeeScalar, + operatorFeeConstant: &operatorFeeConstant, + }, nil +} + // L1Cost computes the the data availability fee for transactions in blocks prior to the Ecotone // upgrade. It is used by e2e tests so must remain exported. func L1Cost(rollupDataGas uint64, l1BaseFee, overhead, scalar *big.Int) *big.Int { diff --git a/core/types/rollup_cost_test.go b/core/types/rollup_cost_test.go index 8d0f1e9218..68669025fc 100644 --- a/core/types/rollup_cost_test.go +++ b/core/types/rollup_cost_test.go @@ -18,9 +18,11 @@ var ( overhead = big.NewInt(50) scalar = big.NewInt(7 * 1e6) - blobBaseFee = big.NewInt(10 * 1e6) - baseFeeScalar = big.NewInt(2) - blobBaseFeeScalar = big.NewInt(3) + blobBaseFee = big.NewInt(10 * 1e6) + baseFeeScalar = big.NewInt(2) + blobBaseFeeScalar = big.NewInt(3) + operatorFeeScalar = big.NewInt(7) + operatorFeeConstant = big.NewInt(9) // below are the expected cost func outcomes for the above parameter settings on the emptyTx // which is defined in transaction_test.go @@ -206,6 +208,39 @@ func TestExtractFjordGasParams(t *testing.T) { require.Equal(t, fjordFee, c) } +func TestExtractHoloceneGasParams(t *testing.T) { + zeroTime := uint64(0) + // create a config where holocene is active + config := ¶ms.ChainConfig{ + Optimism: params.OptimismTestConfig.Optimism, + RegolithTime: &zeroTime, + EcotoneTime: &zeroTime, + FjordTime: &zeroTime, + HoloceneTime: &zeroTime, + } + require.True(t, config.IsOptimismHolocene(zeroTime)) + + data := getHoloceneL1Attributes( + baseFee, + blobBaseFee, + baseFeeScalar, + blobBaseFeeScalar, + operatorFeeScalar, + operatorFeeConstant, + ) + + gasparams, err := extractL1GasParams(config, zeroTime, data) + require.NoError(t, err) + costFunc := gasparams.costFunc + + c, g := costFunc(emptyTx.RollupCostData()) + + require.Equal(t, minimumFjordGas, g) + require.Equal(t, fjordFee, c) + require.Equal(t, operatorFeeScalar.Uint64(), uint64(*gasparams.operatorFeeScalar)) + require.Equal(t, operatorFeeConstant.Uint64(), *gasparams.operatorFeeConstant) +} + // make sure the first block of the ecotone upgrade is properly detected, and invokes the bedrock // cost function appropriately func TestFirstBlockEcotoneGasParams(t *testing.T) { @@ -263,6 +298,30 @@ func getEcotoneL1Attributes(baseFee, blobBaseFee, baseFeeScalar, blobBaseFeeScal return data } +func getHoloceneL1Attributes(baseFee, blobBaseFee, baseFeeScalar, blobBaseFeeScalar, operatorFeeScalar, operatorFeeConstant *big.Int) []byte { + ignored := big.NewInt(1234) + data := []byte{} + uint256Slice := make([]byte, 32) + uint64Slice := make([]byte, 8) + uint32Slice := make([]byte, 4) + data = append(data, EcotoneL1AttributesSelector...) + data = append(data, baseFeeScalar.FillBytes(uint32Slice)...) + data = append(data, blobBaseFeeScalar.FillBytes(uint32Slice)...) + data = append(data, ignored.FillBytes(uint64Slice)...) + data = append(data, ignored.FillBytes(uint64Slice)...) + data = append(data, ignored.FillBytes(uint64Slice)...) + data = append(data, baseFee.FillBytes(uint256Slice)...) + data = append(data, blobBaseFee.FillBytes(uint256Slice)...) + data = append(data, ignored.FillBytes(uint256Slice)...) + data = append(data, ignored.FillBytes(uint256Slice)...) + data = append(data, ignored.FillBytes(uint64Slice)...) + data = append(data, ignored.FillBytes(uint64Slice)...) + data = append(data, operatorFeeScalar.FillBytes(uint32Slice)...) + data = append(data, operatorFeeConstant.FillBytes(uint64Slice)...) + data = append(data, ignored.FillBytes(uint32Slice)...) + return data +} + type testStateGetter struct { baseFee, blobBaseFee, overhead, scalar *big.Int baseFeeScalar, blobBaseFeeScalar uint32 From 35d33ca0df27f7a2d2f9596a6fc1fbf21b20fdd2 Mon Sep 17 00:00:00 2001 From: Yuwen Zhang Date: Tue, 24 Sep 2024 16:15:26 -0700 Subject: [PATCH 02/18] added logic to state_transition, passes all existing tests --- core/evm.go | 25 +++++++-------- core/state_transition.go | 30 ++++++++++++++++++ core/types/receipt_test.go | 1 - core/types/rollup_cost.go | 56 +++++++++++++++++++++++++++++++--- core/types/rollup_cost_test.go | 2 +- core/vm/evm.go | 2 ++ internal/ethapi/api.go | 7 +++++ 7 files changed, 104 insertions(+), 19 deletions(-) diff --git a/core/evm.go b/core/evm.go index e8016fbb78..2eab83c030 100644 --- a/core/evm.go +++ b/core/evm.go @@ -64,18 +64,19 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common random = &header.MixDigest } return vm.BlockContext{ - CanTransfer: CanTransfer, - Transfer: Transfer, - GetHash: GetHashFn(header, chain), - Coinbase: beneficiary, - BlockNumber: new(big.Int).Set(header.Number), - Time: header.Time, - Difficulty: new(big.Int).Set(header.Difficulty), - BaseFee: baseFee, - BlobBaseFee: blobBaseFee, - GasLimit: header.GasLimit, - Random: random, - L1CostFunc: types.NewL1CostFunc(config, statedb), + CanTransfer: CanTransfer, + Transfer: Transfer, + GetHash: GetHashFn(header, chain), + Coinbase: beneficiary, + BlockNumber: new(big.Int).Set(header.Number), + Time: header.Time, + Difficulty: new(big.Int).Set(header.Difficulty), + BaseFee: baseFee, + BlobBaseFee: blobBaseFee, + GasLimit: header.GasLimit, + Random: random, + L1CostFunc: types.NewL1CostFunc(config, statedb), + OperatorCostFunc: types.NewOperatorCostFunc(config, statedb), } } diff --git a/core/state_transition.go b/core/state_transition.go index af53eae272..b3b9e587e0 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -253,6 +253,14 @@ func (st *StateTransition) buyGas() error { mgval = mgval.Add(mgval, l1Cost) } } + var operatorCost *big.Int + if st.evm.Context.OperatorCostFunc != nil && !st.msg.SkipAccountChecks { + operatorCost = st.evm.Context.OperatorCostFunc(new(big.Int).SetUint64(st.msg.GasLimit), true, st.evm.Context.Time) + if operatorCost != nil { + mgval = mgval.Add(mgval, operatorCost) + } + } + balanceCheck := new(big.Int).Set(mgval) if st.msg.GasFeeCap != nil { balanceCheck.SetUint64(st.msg.GasLimit) @@ -262,6 +270,9 @@ func (st *StateTransition) buyGas() error { if l1Cost != nil { balanceCheck.Add(balanceCheck, l1Cost) } + if operatorCost != nil { + balanceCheck.Add(balanceCheck, operatorCost) + } if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) { if blobGas := st.blobGasUsed(); blobGas > 0 { @@ -595,6 +606,15 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { } st.state.AddBalance(params.OptimismL1FeeRecipient, amtU256, tracing.BalanceIncreaseRewardTransactionFee) } + + // Additionally pay the coinbase according for the configurable fee. + if operatorCost := st.evm.Context.OperatorCostFunc(new(big.Int).SetUint64(st.gasUsed()), true, st.evm.Context.Time); operatorCost != nil { + amtU256, overflow = uint256.FromBig(operatorCost) + if overflow { + return nil, fmt.Errorf("optimism operator cost overflows U256: %d", operatorCost) + } + st.state.AddBalance(st.evm.Context.Coinbase, amtU256, tracing.BalanceIncreaseRewardTransactionFee) + } } return &ExecutionResult{ @@ -623,6 +643,16 @@ func (st *StateTransition) refundGas(refundQuotient uint64) uint64 { remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice)) st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn) + if optimismConfig := st.evm.ChainConfig().Optimism; optimismConfig != nil && !st.msg.IsDepositTx { + // Return ETH for operator cost overcharge. + if operatorCost := st.evm.Context.OperatorCostFunc(new(big.Int).SetUint64(st.gasRemaining), false, st.evm.Context.Time); operatorCost != nil { + amtU256, overflow := uint256.FromBig(operatorCost) + if !overflow { + st.state.AddBalance(st.evm.Context.Coinbase, amtU256, tracing.BalanceIncreaseRewardTransactionFee) + } + } + } + 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 a/core/types/receipt_test.go b/core/types/receipt_test.go index be687e8008..922a94e619 100644 --- a/core/types/receipt_test.go +++ b/core/types/receipt_test.go @@ -820,7 +820,6 @@ func getOptimismHoloceneTxReceipts(l1AttributesPayload []byte, l1GasPrice, l1Blo BlockNumber: blockNumber, TransactionIndex: 0, DepositNonce: &depNonce1, - // Don't need a loto fo other stuff because this is a deposit transaction }, &Receipt{ Type: LegacyTxType, diff --git a/core/types/rollup_cost.go b/core/types/rollup_cost.go index 582c1e88e4..2bacbf03cb 100644 --- a/core/types/rollup_cost.go +++ b/core/types/rollup_cost.go @@ -36,10 +36,20 @@ const ( BaseFeeScalarSlotOffset = 12 // bytes [16:20) of the slot BlobBaseFeeScalarSlotOffset = 8 // bytes [20:24) of the slot - // scalarSectionStart is the beginning of the scalar values segment in the slot + // The Holocene fee scalar values are also packed into the same storage slot as the Ecotone + // fee scalars. + OperatorFeeScalarSlotOffset = 16 // bytes [12:16) of the slot + OperatorFeeConstantSlotOffset = 24 // bytes [4:12) of the slot + + // ecotoneScalarSectionStart is the beginning of the scalar values segment in the slot // array. baseFeeScalar is in the first four bytes of the segment, blobBaseFeeScalar the next - // four. - scalarSectionStart = 32 - BaseFeeScalarSlotOffset - 4 + // four, operatorFeeScalar the next four, and operatorFeeConstant the next eight. + ecotoneScalarSectionStart = 32 - BaseFeeScalarSlotOffset - 4 + + // operatorScalarSectionStart is the beginning of the scalar values segment in the slot + // array. operatorFeeScalar is in the first four bytes of the segment, and operatorFeeConstant + // the next eight. + operatorScalarSectionStart = 32 - OperatorFeeConstantSlotOffset - 4 ) func init() { @@ -68,6 +78,8 @@ var ( // L1FeeScalarsSlot as of the Ecotone upgrade stores the 32-bit basefeeScalar and // blobBaseFeeScalar L1 gas attributes at offsets `BaseFeeScalarSlotOffset` and // `BlobBaseFeeScalarSlotOffset` respectively. + // As of the Holocene upgrade, L1FeeScalarsSlot additionally stores the 32-bit + // operatorFeeScalar and 64-bit operatorFeeConstant L1 gas attributes. L1FeeScalarsSlot = common.BigToHash(big.NewInt(3)) oneMillion = big.NewInt(1_000_000) @@ -99,6 +111,11 @@ type StateGetter interface { // sender of non-Deposit transactions. It returns nil if no data availability fee is charged. type L1CostFunc func(rcd RollupCostData, blockTime uint64) *big.Int +// OperatorCostFunc is used in the state transition to determine the operator fee charged to the +// sender of non-Deposit transactions. It returns nil if no data availability fee is charged. +// The `includeConstant` parameter is used to calculate refunds. +type OperatorCostFunc func(gasUsed *big.Int, includeConstant bool, blockTime uint64) *big.Int + // 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) @@ -141,7 +158,7 @@ func NewL1CostFunc(config *params.ChainConfig, statedb StateGetter) L1CostFunc { // in the buffer and l1BaseFeeScalar comes first. We need to check this prior to // other forks, as the first block of Fjord and Ecotone could be the same block. firstEcotoneBlock := l1BlobBaseFee.BitLen() == 0 && - bytes.Equal(emptyScalars, l1FeeScalars[scalarSectionStart:scalarSectionStart+8]) + bytes.Equal(emptyScalars, l1FeeScalars[ecotoneScalarSectionStart:ecotoneScalarSectionStart+8]) if firstEcotoneBlock { log.Info("using bedrock l1 cost func for first Ecotone block", "time", blockTime) return newL1CostFuncBedrock(config, statedb, blockTime) @@ -179,6 +196,28 @@ func NewL1CostFunc(config *params.ChainConfig, statedb StateGetter) L1CostFunc { } } +// NewOperatorCostFunc returns a function used for calculating operator fees, or nil if this is +// not an op-stack chain. +func NewOperatorCostFunc(config *params.ChainConfig, statedb StateGetter) OperatorCostFunc { + if config.Optimism == nil { + return nil + } + return func(gasUsed *big.Int, includeConstant bool, blockTime uint64) *big.Int { + if !config.IsOptimismHolocene(blockTime) { + return big.NewInt(0) + } + l1FeeScalars := statedb.GetState(L1BlockAddr, L1FeeScalarsSlot).Bytes() + + operatorFeeScalar, operatorFeeConstant := extractOperatorFeeParams(l1FeeScalars) + product := operatorFeeScalar.Mul(gasUsed, operatorFeeScalar) + if !includeConstant { + return product + } else { + return product.Add(gasUsed, operatorFeeConstant) + } + } +} + // newL1CostFuncBedrock returns an L1 cost function suitable for Bedrock, Regolith, and the first // block only of the Ecotone upgrade. func newL1CostFuncBedrock(config *params.ChainConfig, statedb StateGetter, blockTime uint64) l1CostFunc { @@ -437,12 +476,19 @@ func NewL1CostFuncFjord(l1BaseFee, l1BlobBaseFee, baseFeeScalar, blobFeeScalar * } func extractEcotoneFeeParams(l1FeeParams []byte) (l1BaseFeeScalar, l1BlobBaseFeeScalar *big.Int) { - offset := scalarSectionStart + offset := ecotoneScalarSectionStart l1BaseFeeScalar = new(big.Int).SetBytes(l1FeeParams[offset : offset+4]) l1BlobBaseFeeScalar = new(big.Int).SetBytes(l1FeeParams[offset+4 : offset+8]) return } +func extractOperatorFeeParams(l1FeeParams []byte) (operatorFeeScalar, operatorFeeConstant *big.Int) { + offset := operatorScalarSectionStart + operatorFeeConstant = new(big.Int).SetBytes(l1FeeParams[offset : offset+8]) + operatorFeeScalar = new(big.Int).SetBytes(l1FeeParams[offset+8 : offset+12]) + return +} + func bedrockCalldataGasUsed(costData RollupCostData) (calldataGasUsed *big.Int) { calldataGas := (costData.Zeroes * params.TxDataZeroGas) + (costData.Ones * params.TxDataNonZeroGasEIP2028) return new(big.Int).SetUint64(calldataGas) diff --git a/core/types/rollup_cost_test.go b/core/types/rollup_cost_test.go index 68669025fc..224421f869 100644 --- a/core/types/rollup_cost_test.go +++ b/core/types/rollup_cost_test.go @@ -340,7 +340,7 @@ func (sg *testStateGetter) GetState(addr common.Address, slot common.Hash) commo sg.blobBaseFee.FillBytes(buf[:]) case L1FeeScalarsSlot: // fetch Ecotone fee sclars - offset := scalarSectionStart + offset := ecotoneScalarSectionStart binary.BigEndian.PutUint32(buf[offset:offset+4], sg.baseFeeScalar) binary.BigEndian.PutUint32(buf[offset+4:offset+8], sg.blobBaseFeeScalar) default: diff --git a/core/vm/evm.go b/core/vm/evm.go index 248dd85925..619de999d0 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -82,6 +82,8 @@ type BlockContext struct { GetHash GetHashFunc // L1CostFunc returns the L1 cost of the rollup message, the function may be nil, or return nil L1CostFunc types.L1CostFunc + // OperatorCostFunc returns the operator cost. The function may be nil, or return nil + OperatorCostFunc types.OperatorCostFunc // Block information Coinbase common.Address // Provides information for COINBASE diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index fa652d0cdd..0c8e29a113 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1947,6 +1947,13 @@ func marshalReceipt(receipt *types.Receipt, blockHash common.Hash, blockNumber u if receipt.L1BlobBaseFeeScalar != nil { fields["l1BlobBaseFeeScalar"] = hexutil.Uint64(*receipt.L1BlobBaseFeeScalar) } + // Fields added in Holocene + if receipt.OperatorFeeScalar != nil { + fields["operatorFeeScalar"] = hexutil.Uint64(*receipt.OperatorFeeScalar) + } + if receipt.OperatorFeeConstant != nil { + fields["operatorFeeConstant"] = hexutil.Uint64(*receipt.OperatorFeeConstant) + } } if chainConfig.Optimism != nil && tx.IsDepositTx() && receipt.DepositNonce != nil { fields["depositNonce"] = hexutil.Uint64(*receipt.DepositNonce) From 53a60830ecc88ec1fb54ff275fb8db724a1b291b Mon Sep 17 00:00:00 2001 From: Yuwen Zhang Date: Wed, 25 Sep 2024 14:04:49 -0700 Subject: [PATCH 03/18] fix small typo, add scaling factor --- core/types/rollup_cost.go | 3 ++- tests/testdata | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/types/rollup_cost.go b/core/types/rollup_cost.go index 2bacbf03cb..70624c81f0 100644 --- a/core/types/rollup_cost.go +++ b/core/types/rollup_cost.go @@ -210,10 +210,11 @@ func NewOperatorCostFunc(config *params.ChainConfig, statedb StateGetter) Operat operatorFeeScalar, operatorFeeConstant := extractOperatorFeeParams(l1FeeScalars) product := operatorFeeScalar.Mul(gasUsed, operatorFeeScalar) + product = product.Div(product, oneMillion) if !includeConstant { return product } else { - return product.Add(gasUsed, operatorFeeConstant) + return product.Add(product, operatorFeeConstant) } } } diff --git a/tests/testdata b/tests/testdata index faf33b4714..9201075490 160000 --- a/tests/testdata +++ b/tests/testdata @@ -1 +1 @@ -Subproject commit faf33b471465d3c6cdc3d04fbd690895f78d33f2 +Subproject commit 9201075490807f58811078e9bb5ec895b4ac01a5 From a91c47bff1614cca4fe6b9c6998ab51395b7fbf2 Mon Sep 17 00:00:00 2001 From: Yuwen Zhang Date: Wed, 25 Sep 2024 14:55:28 -0700 Subject: [PATCH 04/18] clean up some comments --- core/state_transition.go | 6 +++--- core/types/rollup_cost.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index b3b9e587e0..bdd78d204a 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -607,7 +607,7 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { st.state.AddBalance(params.OptimismL1FeeRecipient, amtU256, tracing.BalanceIncreaseRewardTransactionFee) } - // Additionally pay the coinbase according for the configurable fee. + // Additionally pay the coinbase according for the operator fee. if operatorCost := st.evm.Context.OperatorCostFunc(new(big.Int).SetUint64(st.gasUsed()), true, st.evm.Context.Time); operatorCost != nil { amtU256, overflow = uint256.FromBig(operatorCost) if overflow { @@ -644,11 +644,11 @@ func (st *StateTransition) refundGas(refundQuotient uint64) uint64 { st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn) if optimismConfig := st.evm.ChainConfig().Optimism; optimismConfig != nil && !st.msg.IsDepositTx { - // Return ETH for operator cost overcharge. + // Return ETH to transaction sender for operator cost overcharge. if operatorCost := st.evm.Context.OperatorCostFunc(new(big.Int).SetUint64(st.gasRemaining), false, st.evm.Context.Time); operatorCost != nil { amtU256, overflow := uint256.FromBig(operatorCost) if !overflow { - st.state.AddBalance(st.evm.Context.Coinbase, amtU256, tracing.BalanceIncreaseRewardTransactionFee) + st.state.AddBalance(st.msg.From, amtU256, tracing.BalanceIncreaseRewardTransactionFee) } } } diff --git a/core/types/rollup_cost.go b/core/types/rollup_cost.go index 70624c81f0..7524023329 100644 --- a/core/types/rollup_cost.go +++ b/core/types/rollup_cost.go @@ -113,7 +113,7 @@ type L1CostFunc func(rcd RollupCostData, blockTime uint64) *big.Int // OperatorCostFunc is used in the state transition to determine the operator fee charged to the // sender of non-Deposit transactions. It returns nil if no data availability fee is charged. -// The `includeConstant` parameter is used to calculate refunds. +// The `includeConstant` parameter is usually true, unless calculating a refund. type OperatorCostFunc func(gasUsed *big.Int, includeConstant bool, blockTime uint64) *big.Int // l1CostFunc is an internal version of L1CostFunc that also returns the gasUsed for use in From c3c8f0b2257efab0753d1ee88dd6217810330ec3 Mon Sep 17 00:00:00 2001 From: Yuwen Zhang Date: Wed, 25 Sep 2024 15:17:16 -0700 Subject: [PATCH 05/18] reset testdata --- tests/testdata | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testdata b/tests/testdata index 9201075490..faf33b4714 160000 --- a/tests/testdata +++ b/tests/testdata @@ -1 +1 @@ -Subproject commit 9201075490807f58811078e9bb5ec895b4ac01a5 +Subproject commit faf33b471465d3c6cdc3d04fbd690895f78d33f2 From 0e608d3b94b4a01cfd454f52177472d90d4a22d3 Mon Sep 17 00:00:00 2001 From: Yuwen Zhang Date: Thu, 26 Sep 2024 17:27:38 -0700 Subject: [PATCH 06/18] add HoloceneL1AttributesSelector --- core/types/rollup_cost.go | 2 ++ core/types/rollup_cost_test.go | 2 +- tests/testdata | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/types/rollup_cost.go b/core/types/rollup_cost.go index 7524023329..39b4243dcf 100644 --- a/core/types/rollup_cost.go +++ b/core/types/rollup_cost.go @@ -64,6 +64,8 @@ var ( BedrockL1AttributesSelector = []byte{0x01, 0x5d, 0x8e, 0xb9} // EcotoneL1AttributesSelector is the selector indicating Ecotone style L1 gas attributes. EcotoneL1AttributesSelector = []byte{0x44, 0x0a, 0x5e, 0x20} + // HoloceneL1AttributesSelector is the selector indicating Holocene style L1 gas attributes. + HoloceneL1AttributesSelector = []byte{0xd1, 0xfb, 0xe1, 0x5b} // L1BlockAddr is the address of the L1Block contract which stores the L1 gas attributes. L1BlockAddr = common.HexToAddress("0x4200000000000000000000000000000000000015") diff --git a/core/types/rollup_cost_test.go b/core/types/rollup_cost_test.go index 224421f869..6903f5f2e0 100644 --- a/core/types/rollup_cost_test.go +++ b/core/types/rollup_cost_test.go @@ -304,7 +304,7 @@ func getHoloceneL1Attributes(baseFee, blobBaseFee, baseFeeScalar, blobBaseFeeSca uint256Slice := make([]byte, 32) uint64Slice := make([]byte, 8) uint32Slice := make([]byte, 4) - data = append(data, EcotoneL1AttributesSelector...) + data = append(data, HoloceneL1AttributesSelector...) data = append(data, baseFeeScalar.FillBytes(uint32Slice)...) data = append(data, blobBaseFeeScalar.FillBytes(uint32Slice)...) data = append(data, ignored.FillBytes(uint64Slice)...) diff --git a/tests/testdata b/tests/testdata index faf33b4714..9201075490 160000 --- a/tests/testdata +++ b/tests/testdata @@ -1 +1 @@ -Subproject commit faf33b471465d3c6cdc3d04fbd690895f78d33f2 +Subproject commit 9201075490807f58811078e9bb5ec895b4ac01a5 From f50ef906c1e684c3bd40fe778e2654297905fdfb Mon Sep 17 00:00:00 2001 From: Yuwen Zhang Date: Thu, 26 Sep 2024 17:30:27 -0700 Subject: [PATCH 07/18] reset testdata --- tests/testdata | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testdata b/tests/testdata index 9201075490..faf33b4714 160000 --- a/tests/testdata +++ b/tests/testdata @@ -1 +1 @@ -Subproject commit 9201075490807f58811078e9bb5ec895b4ac01a5 +Subproject commit faf33b471465d3c6cdc3d04fbd690895f78d33f2 From add692763ce53f3634f6d0d5f8630cb41426d0d6 Mon Sep 17 00:00:00 2001 From: Yuwen Zhang Date: Mon, 30 Sep 2024 14:56:09 -0700 Subject: [PATCH 08/18] fixed test --- core/types/receipt_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go index 922a94e619..57ed7f100a 100644 --- a/core/types/receipt_test.go +++ b/core/types/receipt_test.go @@ -968,7 +968,7 @@ func TestDeriveOptimismEcotoneTxReceipts(t *testing.T) { func TestDeriveOptimismHoloceneTxReceipts(t *testing.T) { // Holocene style l1 attributes with baseFeeScalar=2, blobBaseFeeScalar=3, baseFee=1000*1e6, blobBaseFee=10*1e6, eip1559Denominator=0, eip1559Elasticity=0, operatorFeeScalar=7, operatorFeeConstant=9 - payload := common.Hex2Bytes("d1fbe15b000000020000000300000000000004d200000000000004d200000000000004d2000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000000098968000000000000000000000000000000000000000000000000000000000000004d200000000000000000000000000000000000000000000000000000000000004d20000000000000000000000000000000000000007000000000000000900000000") + payload := common.Hex2Bytes("d1fbe15b000000020000000300000000000004d200000000000004d200000000000004d2000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000000098968000000000000000000000000000000000000000000000000000000000000004d200000000000000000000000000000000000000000000000000000000000004d200000000000000000000000000000000000000070000000000000009") // the parameters we use below are defined in rollup_test.go baseFeeScalarUint64 := baseFeeScalar.Uint64() blobBaseFeeScalarUint64 := blobBaseFeeScalar.Uint64() From 98335d0503849d7612e0bf17814f939f9c265a18 Mon Sep 17 00:00:00 2001 From: Yuwen Zhang Date: Mon, 30 Sep 2024 15:36:08 -0700 Subject: [PATCH 09/18] fix to rollup_cost_test --- core/types/rollup_cost_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/core/types/rollup_cost_test.go b/core/types/rollup_cost_test.go index 6903f5f2e0..b6684a4c38 100644 --- a/core/types/rollup_cost_test.go +++ b/core/types/rollup_cost_test.go @@ -318,7 +318,6 @@ func getHoloceneL1Attributes(baseFee, blobBaseFee, baseFeeScalar, blobBaseFeeSca data = append(data, ignored.FillBytes(uint64Slice)...) data = append(data, operatorFeeScalar.FillBytes(uint32Slice)...) data = append(data, operatorFeeConstant.FillBytes(uint64Slice)...) - data = append(data, ignored.FillBytes(uint32Slice)...) return data } From 60a5bae177c9b5af51c6a111e729b1d96c90318b Mon Sep 17 00:00:00 2001 From: Yuwen Zhang Date: Wed, 2 Oct 2024 10:55:32 -0700 Subject: [PATCH 10/18] fees go to operatorFeeVault --- core/state_transition.go | 2 +- params/protocol_params.go | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/core/state_transition.go b/core/state_transition.go index bdd78d204a..c38976d8a1 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -613,7 +613,7 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { if overflow { return nil, fmt.Errorf("optimism operator cost overflows U256: %d", operatorCost) } - st.state.AddBalance(st.evm.Context.Coinbase, amtU256, tracing.BalanceIncreaseRewardTransactionFee) + st.state.AddBalance(params.OptimismOperatorFeeRecipient, amtU256, tracing.BalanceIncreaseRewardTransactionFee) } } diff --git a/params/protocol_params.go b/params/protocol_params.go index b07ab17dd9..cba1028c30 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -27,6 +27,8 @@ var ( OptimismBaseFeeRecipient = common.HexToAddress("0x4200000000000000000000000000000000000019") // The L1 portion of the transaction fee accumulates at this predeploy OptimismL1FeeRecipient = common.HexToAddress("0x420000000000000000000000000000000000001A") + // The operator fee portion of the transaction fee accumulates at this predeploy + OptimismOperatorFeeRecipient = common.HexToAddress("0x420000000000000000000000000000000000001B") ) const ( From 327135d1989311dbbc50ed81aa33173846ed72ee Mon Sep 17 00:00:00 2001 From: Yuwen Zhang Date: Wed, 2 Oct 2024 18:32:21 -0700 Subject: [PATCH 11/18] moved operator fee scalars into separate slot --- core/types/receipt_test.go | 4 +-- core/types/rollup_cost.go | 47 +++++++++++++--------------------- core/types/rollup_cost_test.go | 12 ++++++--- 3 files changed, 28 insertions(+), 35 deletions(-) diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go index 57ed7f100a..cd194044f5 100644 --- a/core/types/receipt_test.go +++ b/core/types/receipt_test.go @@ -967,8 +967,8 @@ func TestDeriveOptimismEcotoneTxReceipts(t *testing.T) { } func TestDeriveOptimismHoloceneTxReceipts(t *testing.T) { - // Holocene style l1 attributes with baseFeeScalar=2, blobBaseFeeScalar=3, baseFee=1000*1e6, blobBaseFee=10*1e6, eip1559Denominator=0, eip1559Elasticity=0, operatorFeeScalar=7, operatorFeeConstant=9 - payload := common.Hex2Bytes("d1fbe15b000000020000000300000000000004d200000000000004d200000000000004d2000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000000098968000000000000000000000000000000000000000000000000000000000000004d200000000000000000000000000000000000000000000000000000000000004d200000000000000000000000000000000000000070000000000000009") + // Holocene style l1 attributes with baseFeeScalar=2, blobBaseFeeScalar=3, baseFee=1000*1e6, blobBaseFee=10*1e6, operatorFeeScalar=7, operatorFeeConstant=9 + payload := common.Hex2Bytes("d1fbe15b000000020000000300000000000004d200000000000004d200000000000004d2000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000000098968000000000000000000000000000000000000000000000000000000000000004d200000000000000000000000000000000000000000000000000000000000004d2000000070000000000000009") // the parameters we use below are defined in rollup_test.go baseFeeScalarUint64 := baseFeeScalar.Uint64() blobBaseFeeScalarUint64 := blobBaseFeeScalar.Uint64() diff --git a/core/types/rollup_cost.go b/core/types/rollup_cost.go index b93e0b2557..2e396cdbb8 100644 --- a/core/types/rollup_cost.go +++ b/core/types/rollup_cost.go @@ -36,20 +36,10 @@ const ( BaseFeeScalarSlotOffset = 12 // bytes [16:20) of the slot BlobBaseFeeScalarSlotOffset = 8 // bytes [20:24) of the slot - // The Holocene fee scalar values are also packed into the same storage slot as the Ecotone - // fee scalars. - OperatorFeeScalarSlotOffset = 16 // bytes [12:16) of the slot - OperatorFeeConstantSlotOffset = 24 // bytes [4:12) of the slot - // ecotoneScalarSectionStart is the beginning of the scalar values segment in the slot // array. baseFeeScalar is in the first four bytes of the segment, blobBaseFeeScalar the next // four, operatorFeeScalar the next four, and operatorFeeConstant the next eight. - ecotoneScalarSectionStart = 32 - BaseFeeScalarSlotOffset - 4 - - // operatorScalarSectionStart is the beginning of the scalar values segment in the slot - // array. operatorFeeScalar is in the first four bytes of the segment, and operatorFeeConstant - // the next eight. - operatorScalarSectionStart = 32 - OperatorFeeConstantSlotOffset - 4 + scalarSectionStart = 32 - BaseFeeScalarSlotOffset - 4 ) func init() { @@ -80,10 +70,12 @@ var ( // L1FeeScalarsSlot as of the Ecotone upgrade stores the 32-bit basefeeScalar and // blobBaseFeeScalar L1 gas attributes at offsets `BaseFeeScalarSlotOffset` and // `BlobBaseFeeScalarSlotOffset` respectively. - // As of the Holocene upgrade, L1FeeScalarsSlot additionally stores the 32-bit - // operatorFeeScalar and 64-bit operatorFeeConstant L1 gas attributes. L1FeeScalarsSlot = common.BigToHash(big.NewInt(3)) + // OperatorFeeParamsSlot stores the operatorFeeScalar and operatorFeeConstant L1 gas + // attributes + OperatorFeeParamsSlot = common.BigToHash(big.NewInt(8)) + oneMillion = big.NewInt(1_000_000) ecotoneDivisor = big.NewInt(1_000_000 * 16) fjordDivisor = big.NewInt(1_000_000_000_000) @@ -160,7 +152,7 @@ func NewL1CostFunc(config *params.ChainConfig, statedb StateGetter) L1CostFunc { // in the buffer and l1BaseFeeScalar comes first. We need to check this prior to // other forks, as the first block of Fjord and Ecotone could be the same block. firstEcotoneBlock := l1BlobBaseFee.BitLen() == 0 && - bytes.Equal(emptyScalars, l1FeeScalars[ecotoneScalarSectionStart:ecotoneScalarSectionStart+8]) + bytes.Equal(emptyScalars, l1FeeScalars[scalarSectionStart:scalarSectionStart+8]) if firstEcotoneBlock { log.Info("using bedrock l1 cost func for first Ecotone block", "time", blockTime) return newL1CostFuncBedrock(config, statedb, blockTime) @@ -208,9 +200,9 @@ func NewOperatorCostFunc(config *params.ChainConfig, statedb StateGetter) Operat if !config.IsOptimismHolocene(blockTime) { return big.NewInt(0) } - l1FeeScalars := statedb.GetState(L1BlockAddr, L1FeeScalarsSlot).Bytes() + operatorFeeParams := statedb.GetState(L1BlockAddr, OperatorFeeParamsSlot).Bytes() - operatorFeeScalar, operatorFeeConstant := extractOperatorFeeParams(l1FeeScalars) + operatorFeeScalar, operatorFeeConstant := extractOperatorFeeParams(operatorFeeParams) product := operatorFeeScalar.Mul(gasUsed, operatorFeeScalar) product = product.Div(product, oneMillion) if !includeConstant { @@ -397,8 +389,8 @@ func extractL1GasParamsPostEcotone(data []byte) (gasParams, error) { // extractL1GasParamsPostHolocene extracts the gas parameters necessary to compute gas from L1 attribute // info calldata after the Holocene upgrade, but not for the very first Holocene block. func extractL1GasParamsPostHolocene(data []byte) (gasParams, error) { - if len(data) != 192 { - return gasParams{}, fmt.Errorf("expected 192 L1 info bytes, got %d", len(data)) + if len(data) != 176 { + return gasParams{}, fmt.Errorf("expected 176 L1 info bytes, got %d", len(data)) } // data layout assumed for Holocene: // offset type varname @@ -412,16 +404,14 @@ func extractL1GasParamsPostHolocene(data []byte) (gasParams, error) { // 68 uint256 _blobBaseFee, // 100 bytes32 _hash, // 132 bytes32 _batcherHash, - // 164 uint64 _eip1559Denominator - // 172 uint64 _eip1559Elasticity - // 180 uint32 _operatorFeeScalar - // 184 uint64 _operatorFeeConstant + // 164 uint32 _operatorFeeScalar + // 168 uint64 _operatorFeeConstant l1BaseFee := new(big.Int).SetBytes(data[36:68]) l1BlobBaseFee := new(big.Int).SetBytes(data[68:100]) l1BaseFeeScalar := binary.BigEndian.Uint32(data[4:8]) l1BlobBaseFeeScalar := binary.BigEndian.Uint32(data[8:12]) - operatorFeeScalar := binary.BigEndian.Uint32(data[180:184]) - operatorFeeConstant := binary.BigEndian.Uint64(data[184:192]) + operatorFeeScalar := binary.BigEndian.Uint32(data[164:168]) + operatorFeeConstant := binary.BigEndian.Uint64(data[168:176]) return gasParams{ l1BaseFee: l1BaseFee, @@ -478,16 +468,15 @@ func NewL1CostFuncFjord(l1BaseFee, l1BlobBaseFee, baseFeeScalar, blobFeeScalar * } func extractEcotoneFeeParams(l1FeeParams []byte) (l1BaseFeeScalar, l1BlobBaseFeeScalar *big.Int) { - offset := ecotoneScalarSectionStart + offset := scalarSectionStart l1BaseFeeScalar = new(big.Int).SetBytes(l1FeeParams[offset : offset+4]) l1BlobBaseFeeScalar = new(big.Int).SetBytes(l1FeeParams[offset+4 : offset+8]) return } -func extractOperatorFeeParams(l1FeeParams []byte) (operatorFeeScalar, operatorFeeConstant *big.Int) { - offset := operatorScalarSectionStart - operatorFeeConstant = new(big.Int).SetBytes(l1FeeParams[offset : offset+8]) - operatorFeeScalar = new(big.Int).SetBytes(l1FeeParams[offset+8 : offset+12]) +func extractOperatorFeeParams(operatorFeeParams []byte) (operatorFeeScalar, operatorFeeConstant *big.Int) { + operatorFeeScalar = new(big.Int).SetBytes(operatorFeeParams[0:4]) + operatorFeeConstant = new(big.Int).SetBytes(operatorFeeParams[4:12]) return } diff --git a/core/types/rollup_cost_test.go b/core/types/rollup_cost_test.go index b6684a4c38..943d07250d 100644 --- a/core/types/rollup_cost_test.go +++ b/core/types/rollup_cost_test.go @@ -314,8 +314,6 @@ func getHoloceneL1Attributes(baseFee, blobBaseFee, baseFeeScalar, blobBaseFeeSca data = append(data, blobBaseFee.FillBytes(uint256Slice)...) data = append(data, ignored.FillBytes(uint256Slice)...) data = append(data, ignored.FillBytes(uint256Slice)...) - data = append(data, ignored.FillBytes(uint64Slice)...) - data = append(data, ignored.FillBytes(uint64Slice)...) data = append(data, operatorFeeScalar.FillBytes(uint32Slice)...) data = append(data, operatorFeeConstant.FillBytes(uint64Slice)...) return data @@ -324,6 +322,8 @@ func getHoloceneL1Attributes(baseFee, blobBaseFee, baseFeeScalar, blobBaseFeeSca type testStateGetter struct { baseFee, blobBaseFee, overhead, scalar *big.Int baseFeeScalar, blobBaseFeeScalar uint32 + operatorFeeScalar uint32 + operatorFeeConstant uint64 } func (sg *testStateGetter) GetState(addr common.Address, slot common.Hash) common.Hash { @@ -338,10 +338,14 @@ func (sg *testStateGetter) GetState(addr common.Address, slot common.Hash) commo case L1BlobBaseFeeSlot: sg.blobBaseFee.FillBytes(buf[:]) case L1FeeScalarsSlot: - // fetch Ecotone fee sclars - offset := ecotoneScalarSectionStart + // fetch Ecotone fee scalars + offset := scalarSectionStart binary.BigEndian.PutUint32(buf[offset:offset+4], sg.baseFeeScalar) binary.BigEndian.PutUint32(buf[offset+4:offset+8], sg.blobBaseFeeScalar) + case OperatorFeeParamsSlot: + // fetch operator fee scalars + binary.BigEndian.PutUint32(buf[0:4], sg.operatorFeeScalar) + binary.BigEndian.PutUint64(buf[4:12], sg.operatorFeeConstant) default: panic("unknown slot") } From 4d7990af140298590b190c9159e9f67e462595c9 Mon Sep 17 00:00:00 2001 From: Yuwen Zhang Date: Tue, 12 Nov 2024 10:45:25 -0800 Subject: [PATCH 12/18] start change to isthmus + new design --- core/types/rollup_cost.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/core/types/rollup_cost.go b/core/types/rollup_cost.go index 2e396cdbb8..1b77bc4f18 100644 --- a/core/types/rollup_cost.go +++ b/core/types/rollup_cost.go @@ -54,8 +54,8 @@ var ( BedrockL1AttributesSelector = []byte{0x01, 0x5d, 0x8e, 0xb9} // EcotoneL1AttributesSelector is the selector indicating Ecotone style L1 gas attributes. EcotoneL1AttributesSelector = []byte{0x44, 0x0a, 0x5e, 0x20} - // HoloceneL1AttributesSelector is the selector indicating Holocene style L1 gas attributes. - HoloceneL1AttributesSelector = []byte{0xd1, 0xfb, 0xe1, 0x5b} + // IsthmusL1AttributesSelector is the selector indicating Isthmus style L1 gas attributes. + IsthmusL1AttributesSelector = []byte{0xd1, 0xfb, 0xe1, 0x5b} // L1BlockAddr is the address of the L1Block contract which stores the L1 gas attributes. L1BlockAddr = common.HexToAddress("0x4200000000000000000000000000000000000015") @@ -197,7 +197,7 @@ func NewOperatorCostFunc(config *params.ChainConfig, statedb StateGetter) Operat return nil } return func(gasUsed *big.Int, includeConstant bool, blockTime uint64) *big.Int { - if !config.IsOptimismHolocene(blockTime) { + if !config.IsOptimismIsthmus(blockTime) { return big.NewInt(0) } operatorFeeParams := statedb.GetState(L1BlockAddr, OperatorFeeParamsSlot).Bytes() @@ -282,8 +282,8 @@ type gasParams struct { feeScalar *big.Float // pre-ecotone l1BaseFeeScalar *uint32 // post-ecotone l1BlobBaseFeeScalar *uint32 // post-ecotone - operatorFeeScalar *uint32 // post-holocene - operatorFeeConstant *uint64 // post-holocene + operatorFeeScalar *uint32 // post-Isthmus + operatorFeeConstant *uint64 // post-Isthmus } // intToScaledFloat returns scalar/10e6 as a float @@ -295,8 +295,8 @@ func intToScaledFloat(scalar *big.Int) *big.Float { // 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) { - if config.IsHolocene(time) { - p, err := extractL1GasParamsPostHolocene(data) + if config.IsIsthmus(time) { + p, err := extractL1GasParamsPostIsthmus(data) if err != nil { return gasParams{}, err } @@ -386,13 +386,13 @@ func extractL1GasParamsPostEcotone(data []byte) (gasParams, error) { }, nil } -// extractL1GasParamsPostHolocene extracts the gas parameters necessary to compute gas from L1 attribute -// info calldata after the Holocene upgrade, but not for the very first Holocene block. -func extractL1GasParamsPostHolocene(data []byte) (gasParams, error) { +// extractL1GasParamsPostIsthmus extracts the gas parameters necessary to compute gas from L1 attribute +// info calldata after the Isthmus upgrade, but not for the very first Isthmus block. +func extractL1GasParamsPostIsthmus(data []byte) (gasParams, error) { if len(data) != 176 { return gasParams{}, fmt.Errorf("expected 176 L1 info bytes, got %d", len(data)) } - // data layout assumed for Holocene: + // data layout assumed for Isthmus: // offset type varname // 0 // 4 uint32 _basefeeScalar From 80831625c95f69a952263b376c38fe79cb4a190b Mon Sep 17 00:00:00 2001 From: Yuwen Zhang Date: Tue, 12 Nov 2024 11:51:02 -0800 Subject: [PATCH 13/18] finish merge --- core/state_transition.go | 112 +++++++++++++++++++++----------------- core/types/rollup_cost.go | 8 +-- 2 files changed, 66 insertions(+), 54 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index cd2635c229..210c69054a 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -142,37 +142,43 @@ type Message struct { BlobGasFeeCap *big.Int BlobHashes []common.Hash - // When SkipAccountChecks is true, the message nonce is not checked against the - // account nonce in state. It also disables checking that the sender is an EOA. + // When SkipNonceChecks is true, the message nonce is not checked against the + // account nonce in state. // This field will be set to true for operations like RPC eth_call. - SkipAccountChecks bool + SkipNonceChecks bool + + // When SkipFromEOACheck is true, the message sender is not checked to be an EOA. + SkipFromEOACheck bool IsSystemTx bool // IsSystemTx indicates the message, if also a deposit, does not emit gas usage. IsDepositTx bool // IsDepositTx indicates the message is force-included and can persist a mint. Mint *big.Int // Mint is the amount to mint before EVM processing, or nil if there is no minting. RollupCostData types.RollupCostData // RollupCostData caches data to compute the fee we charge for data availability + + PostValidation func(evm *vm.EVM, result *ExecutionResult) error } // TransactionToMessage converts a transaction into a Message. func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) { msg := &Message{ - Nonce: tx.Nonce(), - GasLimit: tx.Gas(), - GasPrice: new(big.Int).Set(tx.GasPrice()), - GasFeeCap: new(big.Int).Set(tx.GasFeeCap()), - GasTipCap: new(big.Int).Set(tx.GasTipCap()), - To: tx.To(), - Value: tx.Value(), - Data: tx.Data(), - AccessList: tx.AccessList(), + Nonce: tx.Nonce(), + GasLimit: tx.Gas(), + GasPrice: new(big.Int).Set(tx.GasPrice()), + GasFeeCap: new(big.Int).Set(tx.GasFeeCap()), + GasTipCap: new(big.Int).Set(tx.GasTipCap()), + To: tx.To(), + Value: tx.Value(), + Data: tx.Data(), + AccessList: tx.AccessList(), + SkipNonceChecks: false, + SkipFromEOACheck: false, + BlobHashes: tx.BlobHashes(), + BlobGasFeeCap: tx.BlobGasFeeCap(), + IsSystemTx: tx.IsSystemTx(), IsDepositTx: tx.IsDepositTx(), Mint: tx.Mint(), RollupCostData: tx.RollupCostData(), - - SkipAccountChecks: false, - BlobHashes: tx.BlobHashes(), - BlobGasFeeCap: tx.BlobGasFeeCap(), } // If baseFee provided, set gasPrice to effectiveGasPrice. if baseFee != nil { @@ -247,20 +253,19 @@ func (st *StateTransition) buyGas() error { mgval := new(big.Int).SetUint64(st.msg.GasLimit) mgval.Mul(mgval, st.msg.GasPrice) var l1Cost *big.Int - if st.evm.Context.L1CostFunc != nil && !st.msg.SkipAccountChecks { + if st.evm.Context.L1CostFunc != nil && !st.msg.SkipNonceChecks && !st.msg.SkipFromEOACheck { l1Cost = st.evm.Context.L1CostFunc(st.msg.RollupCostData, st.evm.Context.Time) if l1Cost != nil { mgval = mgval.Add(mgval, l1Cost) } } var operatorCost *big.Int - if st.evm.Context.OperatorCostFunc != nil && !st.msg.SkipAccountChecks { - operatorCost = st.evm.Context.OperatorCostFunc(new(big.Int).SetUint64(st.msg.GasLimit), true, st.evm.Context.Time) + if st.evm.Context.OperatorCostFunc != nil && !st.msg.SkipNonceChecks && !st.msg.SkipFromEOACheck { + operatorCost = st.evm.Context.OperatorCostFunc(new(big.Int).SetUint64(st.msg.GasLimit), false, st.evm.Context.Time) if operatorCost != nil { mgval = mgval.Add(mgval, operatorCost) } } - balanceCheck := new(big.Int).Set(mgval) if st.msg.GasFeeCap != nil { balanceCheck.SetUint64(st.msg.GasLimit) @@ -326,7 +331,7 @@ func (st *StateTransition) preCheck() error { } // Only check transactions that are not fake msg := st.msg - if !msg.SkipAccountChecks { + if !msg.SkipNonceChecks { // Make sure this transaction's nonce is correct. stNonce := st.state.GetNonce(msg.From) if msgNonce := msg.Nonce; stNonce < msgNonce { @@ -339,6 +344,8 @@ func (st *StateTransition) preCheck() error { return fmt.Errorf("%w: address %v, nonce: %d", ErrNonceMax, msg.From.Hex(), stNonce) } + } + if !msg.SkipFromEOACheck { // Make sure the sender is an EOA codeHash := st.state.GetCodeHash(msg.From) if codeHash != (common.Hash{}) && codeHash != types.EmptyCodeHash { @@ -452,6 +459,13 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { } err = nil } + + if st.msg.PostValidation != nil { + if err := st.msg.PostValidation(st.evm, result); err != nil { + return nil, err + } + } + return result, err } @@ -586,34 +600,32 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { // add the coinbase to the witness iff the fee is greater than 0 if rules.IsEIP4762 && fee.Sign() != 0 { - st.evm.AccessEvents.BalanceGas(st.evm.Context.Coinbase, true) + st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true) } - } - // 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) - amtU256, overflow := uint256.FromBig(gasCost) - if overflow { - return nil, fmt.Errorf("optimism gas cost overflows U256: %d", gasCost) - } - 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) + // 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) + amtU256, overflow := uint256.FromBig(gasCost) if overflow { - return nil, fmt.Errorf("optimism l1 cost overflows U256: %d", l1Cost) + return nil, fmt.Errorf("optimism gas cost overflows U256: %d", gasCost) } - st.state.AddBalance(params.OptimismL1FeeRecipient, amtU256, tracing.BalanceIncreaseRewardTransactionFee) - } - - // Additionally pay the coinbase according for the operator fee. - if operatorCost := st.evm.Context.OperatorCostFunc(new(big.Int).SetUint64(st.gasUsed()), true, st.evm.Context.Time); operatorCost != nil { - amtU256, overflow = uint256.FromBig(operatorCost) - if overflow { - return nil, fmt.Errorf("optimism operator cost overflows U256: %d", operatorCost) + 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) + } + st.state.AddBalance(params.OptimismL1FeeRecipient, amtU256, tracing.BalanceIncreaseRewardTransactionFee) + } + if operatorCost := st.evm.Context.OperatorCostFunc(new(big.Int).SetUint64(st.msg.GasLimit), false, st.evm.Context.Time); operatorCost != nil { + amtU256, overflow = uint256.FromBig(operatorCost) + if overflow { + return nil, fmt.Errorf("optimism operator cost overflows U256: %d", operatorCost) + } + st.state.AddBalance(params.OptimismOperatorFeeRecipient, amtU256, tracing.BalanceIncreaseRewardTransactionFee) } - st.state.AddBalance(params.OptimismOperatorFeeRecipient, amtU256, tracing.BalanceIncreaseRewardTransactionFee) } } @@ -643,20 +655,20 @@ func (st *StateTransition) refundGas(refundQuotient uint64) uint64 { remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice)) st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn) + 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) + } + if optimismConfig := st.evm.ChainConfig().Optimism; optimismConfig != nil && !st.msg.IsDepositTx { // Return ETH to transaction sender for operator cost overcharge. - if operatorCost := st.evm.Context.OperatorCostFunc(new(big.Int).SetUint64(st.gasRemaining), false, st.evm.Context.Time); operatorCost != nil { + if operatorCost := st.evm.Context.OperatorCostFunc(new(big.Int).SetUint64(st.gasRemaining), true, st.evm.Context.Time); operatorCost != nil { amtU256, overflow := uint256.FromBig(operatorCost) if !overflow { - st.state.AddBalance(st.msg.From, amtU256, tracing.BalanceIncreaseRewardTransactionFee) + st.state.AddBalance(st.msg.From, amtU256, tracing.BalanceIncreaseGasReturn) } } } - 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) - } - // Also return remaining gas to the block gas counter so it is // available for the next transaction. st.gp.AddGas(st.gasRemaining) diff --git a/core/types/rollup_cost.go b/core/types/rollup_cost.go index a28d222feb..a8b510d42f 100644 --- a/core/types/rollup_cost.go +++ b/core/types/rollup_cost.go @@ -107,8 +107,8 @@ type L1CostFunc func(rcd RollupCostData, blockTime uint64) *big.Int // OperatorCostFunc is used in the state transition to determine the operator fee charged to the // sender of non-Deposit transactions. It returns nil if no data availability fee is charged. -// The `includeConstant` parameter is usually true, unless calculating a refund. -type OperatorCostFunc func(gasUsed *big.Int, includeConstant bool, blockTime uint64) *big.Int +// The `isRefund` parameter is true if calculating a refund. +type OperatorCostFunc func(gasUsed *big.Int, isRefund bool, blockTime uint64) *big.Int // l1CostFunc is an internal version of L1CostFunc that also returns the gasUsed for use in // receipts. @@ -196,7 +196,7 @@ func NewOperatorCostFunc(config *params.ChainConfig, statedb StateGetter) Operat if config.Optimism == nil { return nil } - return func(gasUsed *big.Int, includeConstant bool, blockTime uint64) *big.Int { + return func(gasUsed *big.Int, isRefund bool, blockTime uint64) *big.Int { if !config.IsOptimismIsthmus(blockTime) { return big.NewInt(0) } @@ -205,7 +205,7 @@ func NewOperatorCostFunc(config *params.ChainConfig, statedb StateGetter) Operat operatorFeeScalar, operatorFeeConstant := extractOperatorFeeParams(operatorFeeParams) product := operatorFeeScalar.Mul(gasUsed, operatorFeeScalar) product = product.Div(product, oneMillion) - if !includeConstant { + if isRefund { return product } else { return product.Add(product, operatorFeeConstant) From a71c3e2ceb641aa854825a42b3f3b3aa6ccf0251 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien?= <3535019+leruaa@users.noreply.github.com> Date: Mon, 9 Dec 2024 10:43:45 -0800 Subject: [PATCH 14/18] Update to Isthmus (#2) * Update to Isthmus * Allow to set IsthmusTime * update setL1BlockValuesIsthmus function selector --- cmd/geth/config.go | 5 +++++ cmd/geth/main.go | 1 + cmd/utils/flags.go | 5 +++++ core/genesis.go | 4 ++++ core/types/receipt.go | 4 ++-- core/types/receipt_test.go | 20 ++++++++++---------- eth/backend.go | 3 +++ eth/ethconfig/config.go | 2 ++ eth/ethconfig/gen_config.go | 6 ++++++ 9 files changed, 38 insertions(+), 12 deletions(-) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index df12a831b6..718244f588 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -213,6 +213,11 @@ func makeFullNode(ctx *cli.Context) *node.Node { cfg.Eth.OverrideOptimismHolocene = &v } + if ctx.IsSet(utils.OverrideOptimismIsthmus.Name) { + v := ctx.Uint64(utils.OverrideOptimismIsthmus.Name) + cfg.Eth.OverrideOptimismIsthmus = &v + } + if ctx.IsSet(utils.OverrideOptimismInterop.Name) { v := ctx.Uint64(utils.OverrideOptimismInterop.Name) cfg.Eth.OverrideOptimismInterop = &v diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 30c7df3b84..063d54f96d 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -71,6 +71,7 @@ var ( utils.OverrideOptimismFjord, utils.OverrideOptimismGranite, utils.OverrideOptimismHolocene, + utils.OverrideOptimismIsthmus, utils.OverrideOptimismInterop, utils.EnablePersonal, utils.TxPoolLocalsFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 8eda534783..f944d24713 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -284,6 +284,11 @@ var ( Usage: "Manually specify the Optimism Holocene fork timestamp, overriding the bundled setting", Category: flags.EthCategory, } + OverrideOptimismIsthmus = &cli.Uint64Flag{ + Name: "override.isthmus", + Usage: "Manually specify the Optimism Isthmus fork timestamp, overriding the bundled setting", + Category: flags.EthCategory, + } OverrideOptimismInterop = &cli.Uint64Flag{ Name: "override.interop", Usage: "Manually specify the Optimsim Interop feature-set fork timestamp, overriding the bundled setting", diff --git a/core/genesis.go b/core/genesis.go index f803250733..78b9b1c4a8 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -256,6 +256,7 @@ type ChainOverrides struct { OverrideOptimismFjord *uint64 OverrideOptimismGranite *uint64 OverrideOptimismHolocene *uint64 + OverrideOptimismIsthmus *uint64 OverrideOptimismInterop *uint64 ApplySuperchainUpgrades bool } @@ -322,6 +323,9 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *triedb.Database, g if overrides != nil && overrides.OverrideOptimismHolocene != nil { config.HoloceneTime = overrides.OverrideOptimismHolocene } + if overrides != nil && overrides.OverrideOptimismIsthmus != nil { + config.IsthmusTime = overrides.OverrideOptimismIsthmus + } if overrides != nil && overrides.OverrideOptimismInterop != nil { config.InteropTime = overrides.OverrideOptimismInterop } diff --git a/core/types/receipt.go b/core/types/receipt.go index 990334aace..b81339cc88 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -92,8 +92,8 @@ type Receipt struct { FeeScalar *big.Float `json:"l1FeeScalar,omitempty"` // Present from pre-bedrock to Ecotone. Nil after Ecotone L1BaseFeeScalar *uint64 `json:"l1BaseFeeScalar,omitempty"` // Always nil prior to the Ecotone hardfork L1BlobBaseFeeScalar *uint64 `json:"l1BlobBaseFeeScalar,omitempty"` // Always nil prior to the Ecotone hardfork - OperatorFeeScalar *uint64 `json:"operatorFeeScalar,omitempty"` // Always nil prior to the Holocene hardfork - OperatorFeeConstant *uint64 `json:"operatorFeeConstant,omitempty"` // Always nil prior to the Holocene hardfork + OperatorFeeScalar *uint64 `json:"operatorFeeScalar,omitempty"` // Always nil prior to the Isthmus hardfork + OperatorFeeConstant *uint64 `json:"operatorFeeConstant,omitempty"` // Always nil prior to the Isthmus hardfork } type receiptMarshaling struct { diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go index cd194044f5..743be22b86 100644 --- a/core/types/receipt_test.go +++ b/core/types/receipt_test.go @@ -48,10 +48,10 @@ var ( conf.EcotoneTime = &time return &conf }() - holoceneTestConfig = func() *params.ChainConfig { + isthmusTestConfig = func() *params.ChainConfig { conf := *bedrockGenesisTestConfig // copy the config time := uint64(0) - conf.HoloceneTime = &time + conf.IsthmusTime = &time return &conf }() @@ -774,7 +774,7 @@ func getOptimismEcotoneTxReceipts(l1AttributesPayload []byte, l1GasPrice, l1Blob return txs, receipts } -func getOptimismHoloceneTxReceipts(l1AttributesPayload []byte, l1GasPrice, l1BlobGasPrice, l1GasUsed, l1Fee *big.Int, baseFeeScalar, blobBaseFeeScalar, operatorFeeScalar, operatorFeeConstant *uint64) ([]*Transaction, []*Receipt) { +func getOptimismIsthmusTxReceipts(l1AttributesPayload []byte, l1GasPrice, l1BlobGasPrice, l1GasUsed, l1Fee *big.Int, baseFeeScalar, blobBaseFeeScalar, operatorFeeScalar, operatorFeeConstant *uint64) ([]*Transaction, []*Receipt) { // Create a few transactions to have receipts for txs := Transactions{ NewTx(&DepositTx{ @@ -966,26 +966,26 @@ func TestDeriveOptimismEcotoneTxReceipts(t *testing.T) { diffReceipts(t, receipts, derivedReceipts) } -func TestDeriveOptimismHoloceneTxReceipts(t *testing.T) { - // Holocene style l1 attributes with baseFeeScalar=2, blobBaseFeeScalar=3, baseFee=1000*1e6, blobBaseFee=10*1e6, operatorFeeScalar=7, operatorFeeConstant=9 - payload := common.Hex2Bytes("d1fbe15b000000020000000300000000000004d200000000000004d200000000000004d2000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000000098968000000000000000000000000000000000000000000000000000000000000004d200000000000000000000000000000000000000000000000000000000000004d2000000070000000000000009") +func TestDeriveOptimismIsthmusTxReceipts(t *testing.T) { + // Isthmus style l1 attributes with baseFeeScalar=2, blobBaseFeeScalar=3, baseFee=1000*1e6, blobBaseFee=10*1e6, operatorFeeScalar=7, operatorFeeConstant=9 + payload := common.Hex2Bytes("098999be000000020000000300000000000004d200000000000004d200000000000004d2000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000000098968000000000000000000000000000000000000000000000000000000000000004d200000000000000000000000000000000000000000000000000000000000004d2000000070000000000000009") // the parameters we use below are defined in rollup_test.go baseFeeScalarUint64 := baseFeeScalar.Uint64() blobBaseFeeScalarUint64 := blobBaseFeeScalar.Uint64() operatorFeeScalarUint64 := operatorFeeScalar.Uint64() operatorFeeConstantUint64 := operatorFeeConstant.Uint64() - txs, receipts := getOptimismHoloceneTxReceipts(payload, baseFee, blobBaseFee, minimumFjordGas, fjordFee, &baseFeeScalarUint64, &blobBaseFeeScalarUint64, &operatorFeeScalarUint64, &operatorFeeConstantUint64) + txs, receipts := getOptimismIsthmusTxReceipts(payload, baseFee, blobBaseFee, minimumFjordGas, fjordFee, &baseFeeScalarUint64, &blobBaseFeeScalarUint64, &operatorFeeScalarUint64, &operatorFeeConstantUint64) // Re-derive receipts. baseFee := big.NewInt(1000) derivedReceipts := clearComputedFieldsOnReceipts(receipts) - // Should error out if we try to process this with a pre-Holocene config + // Should error out if we try to process this with a pre-Isthmus config err := Receipts(derivedReceipts).DeriveFields(bedrockGenesisTestConfig, blockHash, blockNumber.Uint64(), 0, baseFee, nil, txs) if err == nil { - t.Fatalf("expected error from deriving holocene receipts with pre-holocene config, got none") + t.Fatalf("expected error from deriving isthmus receipts with pre-isthmus config, got none") } - err = Receipts(derivedReceipts).DeriveFields(holoceneTestConfig, blockHash, blockNumber.Uint64(), 0, baseFee, nil, txs) + err = Receipts(derivedReceipts).DeriveFields(isthmusTestConfig, blockHash, blockNumber.Uint64(), 0, baseFee, nil, txs) if err != nil { t.Fatalf("DeriveFields(...) = %v, want ", err) } diff --git a/eth/backend.go b/eth/backend.go index 79453719c6..0976280254 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -242,6 +242,9 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if config.OverrideOptimismHolocene != nil { overrides.OverrideOptimismHolocene = config.OverrideOptimismHolocene } + if config.OverrideOptimismIsthmus != nil { + overrides.OverrideOptimismIsthmus = config.OverrideOptimismIsthmus + } if config.OverrideOptimismInterop != nil { overrides.OverrideOptimismInterop = config.OverrideOptimismInterop } diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 29eca7bb28..4bd822c83b 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -168,6 +168,8 @@ type Config struct { OverrideOptimismHolocene *uint64 `toml:",omitempty"` + OverrideOptimismIsthmus *uint64 `toml:",omitempty"` + OverrideOptimismInterop *uint64 `toml:",omitempty"` // ApplySuperchainUpgrades requests the node to load chain-configuration from the superchain-registry. diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go index 72f0e1dc73..7a6f1e234e 100644 --- a/eth/ethconfig/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -57,6 +57,7 @@ func (c Config) MarshalTOML() (interface{}, error) { OverrideOptimismFjord *uint64 `toml:",omitempty"` OverrideOptimismGranite *uint64 `toml:",omitempty"` OverrideOptimismHolocene *uint64 `toml:",omitempty"` + OverrideOptimismIsthmus *uint64 `toml:",omitempty"` OverrideOptimismInterop *uint64 `toml:",omitempty"` ApplySuperchainUpgrades bool `toml:",omitempty"` RollupSequencerHTTP string @@ -111,6 +112,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.OverrideOptimismFjord = c.OverrideOptimismFjord enc.OverrideOptimismGranite = c.OverrideOptimismGranite enc.OverrideOptimismHolocene = c.OverrideOptimismHolocene + enc.OverrideOptimismIsthmus = c.OverrideOptimismIsthmus enc.OverrideOptimismInterop = c.OverrideOptimismInterop enc.ApplySuperchainUpgrades = c.ApplySuperchainUpgrades enc.RollupSequencerHTTP = c.RollupSequencerHTTP @@ -169,6 +171,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { OverrideOptimismFjord *uint64 `toml:",omitempty"` OverrideOptimismGranite *uint64 `toml:",omitempty"` OverrideOptimismHolocene *uint64 `toml:",omitempty"` + OverrideOptimismIsthmus *uint64 `toml:",omitempty"` OverrideOptimismInterop *uint64 `toml:",omitempty"` ApplySuperchainUpgrades *bool `toml:",omitempty"` RollupSequencerHTTP *string @@ -306,6 +309,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.OverrideOptimismHolocene != nil { c.OverrideOptimismHolocene = dec.OverrideOptimismHolocene } + if dec.OverrideOptimismIsthmus != nil { + c.OverrideOptimismIsthmus = dec.OverrideOptimismIsthmus + } if dec.OverrideOptimismInterop != nil { c.OverrideOptimismInterop = dec.OverrideOptimismInterop } From 0f997c3bc41734ebc4ac3f25d221bbbb9cb075f2 Mon Sep 17 00:00:00 2001 From: leruaa Date: Wed, 18 Dec 2024 07:00:51 -0800 Subject: [PATCH 15/18] include Isthmus in the banner --- params/config.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/params/config.go b/params/config.go index 1b4368e76d..250b297ebf 100644 --- a/params/config.go +++ b/params/config.go @@ -515,7 +515,10 @@ func (c *ChainConfig) Description() string { banner += fmt.Sprintf(" - Granite: @%-10v\n", *c.GraniteTime) } if c.HoloceneTime != nil { - banner += fmt.Sprintf(" - Holocene: @%-10v\n", *c.HoloceneTime) + banner += fmt.Sprintf(" - Holocene: @%-10v\n", *c.HoloceneTime) + } + if c.IsthmusTime != nil { + banner += fmt.Sprintf(" - Isthmus: @%-10v\n", *c.IsthmusTime) } if c.IsthmusTime != nil { banner += fmt.Sprintf(" - Isthmus: @%-10v\n", *c.IsthmusTime) From b9e6d2d452def181ac7d0c66219971145b91db39 Mon Sep 17 00:00:00 2001 From: leruaa Date: Wed, 18 Dec 2024 07:03:08 -0800 Subject: [PATCH 16/18] Revert "include Isthmus in the banner" This reverts commit 0f997c3bc41734ebc4ac3f25d221bbbb9cb075f2. --- params/config.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/params/config.go b/params/config.go index 250b297ebf..1b4368e76d 100644 --- a/params/config.go +++ b/params/config.go @@ -515,10 +515,7 @@ func (c *ChainConfig) Description() string { banner += fmt.Sprintf(" - Granite: @%-10v\n", *c.GraniteTime) } if c.HoloceneTime != nil { - banner += fmt.Sprintf(" - Holocene: @%-10v\n", *c.HoloceneTime) - } - if c.IsthmusTime != nil { - banner += fmt.Sprintf(" - Isthmus: @%-10v\n", *c.IsthmusTime) + banner += fmt.Sprintf(" - Holocene: @%-10v\n", *c.HoloceneTime) } if c.IsthmusTime != nil { banner += fmt.Sprintf(" - Isthmus: @%-10v\n", *c.IsthmusTime) From 84613588d800f757352a9c4626a67d211c72f278 Mon Sep 17 00:00:00 2001 From: leruaa Date: Fri, 20 Dec 2024 02:25:56 -0800 Subject: [PATCH 17/18] fix operator fee params storage ordering --- core/types/rollup_cost.go | 4 ++-- internal/ethapi/api.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/types/rollup_cost.go b/core/types/rollup_cost.go index a8b510d42f..37222466eb 100644 --- a/core/types/rollup_cost.go +++ b/core/types/rollup_cost.go @@ -487,8 +487,8 @@ func extractEcotoneFeeParams(l1FeeParams []byte) (l1BaseFeeScalar, l1BlobBaseFee } func extractOperatorFeeParams(operatorFeeParams []byte) (operatorFeeScalar, operatorFeeConstant *big.Int) { - operatorFeeScalar = new(big.Int).SetBytes(operatorFeeParams[0:4]) - operatorFeeConstant = new(big.Int).SetBytes(operatorFeeParams[4:12]) + operatorFeeScalar = new(big.Int).SetBytes(operatorFeeParams[28:32]) + operatorFeeConstant = new(big.Int).SetBytes(operatorFeeParams[20:28]) return } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 80014adb44..d5a113e02f 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -2070,7 +2070,7 @@ func marshalReceipt(receipt *types.Receipt, blockHash common.Hash, blockNumber u if receipt.L1BlobBaseFeeScalar != nil { fields["l1BlobBaseFeeScalar"] = hexutil.Uint64(*receipt.L1BlobBaseFeeScalar) } - // Fields added in Holocene + // Fields added in Isthmus if receipt.OperatorFeeScalar != nil { fields["operatorFeeScalar"] = hexutil.Uint64(*receipt.OperatorFeeScalar) } From cb76ebd22e1964233951fb164f0a0b38eb32d9dc Mon Sep 17 00:00:00 2001 From: leruaa Date: Thu, 26 Dec 2024 11:28:21 -0800 Subject: [PATCH 18/18] fix operator fee params decoding --- core/types/rollup_cost.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/types/rollup_cost.go b/core/types/rollup_cost.go index 37222466eb..2946f2c6e9 100644 --- a/core/types/rollup_cost.go +++ b/core/types/rollup_cost.go @@ -487,8 +487,8 @@ func extractEcotoneFeeParams(l1FeeParams []byte) (l1BaseFeeScalar, l1BlobBaseFee } func extractOperatorFeeParams(operatorFeeParams []byte) (operatorFeeScalar, operatorFeeConstant *big.Int) { - operatorFeeScalar = new(big.Int).SetBytes(operatorFeeParams[28:32]) - operatorFeeConstant = new(big.Int).SetBytes(operatorFeeParams[20:28]) + operatorFeeScalar = new(big.Int).SetBytes(operatorFeeParams[20:24]) + operatorFeeConstant = new(big.Int).SetBytes(operatorFeeParams[24:32]) return }