op-geth

+3392
-177

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

The OP-stack architecture is modular, following the Consensus/Execution split of post-Merge Ethereum L1:

  • op-node implements most rollup-specific functionality as Consensus-Layer, similar to a L1 beacon-node.
  • op-geth implements the Execution-Layer, with minimal changes for a secure Ethereum-equivalent application environment.

Related op-stack specifications:

The Bedrock upgrade introduces a Deposit transaction-type (0x7E) to enable both users and the rollup system itself to change the L2 state based on L1 events and system rules as specified.

diff --git go-ethereum/core/types/deposit_tx.go op-geth/core/types/deposit_tx.go new file mode 100644 index 0000000000000000000000000000000000000000..7ce326f5c320e976af22b56fed5a495362b43dba --- /dev/null +++ op-geth/core/types/deposit_tx.go @@ -0,0 +1,96 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. + +package types + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +const DepositTxType = 0x7E + +type DepositTx struct { + // SourceHash uniquely identifies the source of the deposit + SourceHash common.Hash + // From is exposed through the types.Signer, not through TxData + From common.Address + // nil means contract creation + To *common.Address `rlp:"nil"` + // Mint is minted on L2, locked on L1, nil if no minting. + Mint *big.Int `rlp:"nil"` + // Value is transferred from L2 balance, executed after Mint (if any) + Value *big.Int + // gas limit + Gas uint64 + // Field indicating if this transaction is exempt from the L2 gas limit. + IsSystemTransaction bool + // Normal Tx data + Data []byte +} + +// copy creates a deep copy of the transaction data and initializes all fields. +func (tx *DepositTx) copy() TxData { + cpy := &DepositTx{ + SourceHash: tx.SourceHash, + From: tx.From, + To: copyAddressPtr(tx.To), + Mint: nil, + Value: new(big.Int), + Gas: tx.Gas, + IsSystemTransaction: tx.IsSystemTransaction, + Data: common.CopyBytes(tx.Data), + } + if tx.Mint != nil { + cpy.Mint = new(big.Int).Set(tx.Mint) + } + if tx.Value != nil { + cpy.Value.Set(tx.Value) + } + return cpy +} + +// accessors for innerTx. +func (tx *DepositTx) txType() byte { return DepositTxType } +func (tx *DepositTx) chainID() *big.Int { return common.Big0 } +func (tx *DepositTx) accessList() AccessList { return nil } +func (tx *DepositTx) data() []byte { return tx.Data } +func (tx *DepositTx) gas() uint64 { return tx.Gas } +func (tx *DepositTx) gasFeeCap() *big.Int { return new(big.Int) } +func (tx *DepositTx) gasTipCap() *big.Int { return new(big.Int) } +func (tx *DepositTx) gasPrice() *big.Int { return new(big.Int) } +func (tx *DepositTx) value() *big.Int { return tx.Value } +func (tx *DepositTx) nonce() uint64 { return 0 } +func (tx *DepositTx) to() *common.Address { return tx.To } +func (tx *DepositTx) blobGas() uint64 { return 0 } +func (tx *DepositTx) blobGasFeeCap() *big.Int { return nil } +func (tx *DepositTx) blobHashes() []common.Hash { return nil } +func (tx *DepositTx) isSystemTx() bool { return tx.IsSystemTransaction } + +func (tx *DepositTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { + return dst.Set(new(big.Int)) +} + +func (tx *DepositTx) effectiveNonce() *uint64 { return nil } + +func (tx *DepositTx) rawSignatureValues() (v, r, s *big.Int) { + return common.Big0, common.Big0, common.Big0 +} + +func (tx *DepositTx) setSignatureValues(chainID, v, r, s *big.Int) { + // this is a noop for deposit transactions +}
diff --git go-ethereum/core/types/transaction_marshalling.go op-geth/core/types/transaction_marshalling.go index 6c6c50d498d7290439f2e7abf627002bcd2d9356..c5b04ce9dad47048c960e43a182171856d57c775 100644 --- go-ethereum/core/types/transaction_marshalling.go +++ op-geth/core/types/transaction_marshalling.go @@ -19,10 +19,12 @@ import ( "encoding/json" "errors" + "io" "math/big"   "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rlp" "github.com/holiman/uint256" )   @@ -45,6 +47,12 @@ BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"` V *hexutil.Big `json:"v"` R *hexutil.Big `json:"r"` S *hexutil.Big `json:"s"` + + // Deposit transaction fields + SourceHash *common.Hash `json:"sourceHash,omitempty"` + From *common.Address `json:"from,omitempty"` + Mint *hexutil.Big `json:"mint,omitempty"` + IsSystemTx *bool `json:"isSystemTx,omitempty"`   // Only used for encoding: Hash common.Hash `json:"hash"` @@ -112,6 +120,19 @@ enc.To = tx.To() enc.V = (*hexutil.Big)(itx.V.ToBig()) enc.R = (*hexutil.Big)(itx.R.ToBig()) enc.S = (*hexutil.Big)(itx.S.ToBig()) + + case *DepositTx: + enc.Gas = (*hexutil.Uint64)(&itx.Gas) + enc.Value = (*hexutil.Big)(itx.Value) + enc.Input = (*hexutil.Bytes)(&itx.Data) + enc.To = tx.To() + enc.SourceHash = &itx.SourceHash + enc.From = &itx.From + if itx.Mint != nil { + enc.Mint = (*hexutil.Big)(itx.Mint) + } + enc.IsSystemTx = &itx.IsSystemTransaction + // other fields will show up as null. } return json.Marshal(&enc) } @@ -343,6 +364,54 @@ return err } }   + case DepositTxType: + if dec.AccessList != nil || dec.MaxFeePerGas != nil || + dec.MaxPriorityFeePerGas != nil { + return errors.New("unexpected field(s) in deposit transaction") + } + if dec.GasPrice != nil && dec.GasPrice.ToInt().Cmp(common.Big0) != 0 { + return errors.New("deposit transaction GasPrice must be 0") + } + if (dec.V != nil && dec.V.ToInt().Cmp(common.Big0) != 0) || + (dec.R != nil && dec.R.ToInt().Cmp(common.Big0) != 0) || + (dec.S != nil && dec.S.ToInt().Cmp(common.Big0) != 0) { + return errors.New("deposit transaction signature must be 0 or unset") + } + var itx DepositTx + inner = &itx + if dec.To != nil { + itx.To = dec.To + } + if dec.Gas == nil { + return errors.New("missing required field 'gas' for txdata") + } + itx.Gas = uint64(*dec.Gas) + if dec.Value == nil { + return errors.New("missing required field 'value' in transaction") + } + itx.Value = (*big.Int)(dec.Value) + // mint may be omitted or nil if there is nothing to mint. + itx.Mint = (*big.Int)(dec.Mint) + if dec.Input == nil { + return errors.New("missing required field 'input' in transaction") + } + itx.Data = *dec.Input + if dec.From == nil { + return errors.New("missing required field 'from' in transaction") + } + itx.From = *dec.From + if dec.SourceHash == nil { + return errors.New("missing required field 'sourceHash' in transaction") + } + itx.SourceHash = *dec.SourceHash + // IsSystemTx may be omitted. Defaults to false. + if dec.IsSystemTx != nil { + itx.IsSystemTransaction = *dec.IsSystemTx + } + + if dec.Nonce != nil { + inner = &depositTxWithNonce{DepositTx: itx, EffectiveNonce: uint64(*dec.Nonce)} + } default: return ErrTxTypeNotSupported } @@ -353,3 +422,15 @@ // TODO: check hash here? return nil } + +type depositTxWithNonce struct { + DepositTx + EffectiveNonce uint64 +} + +// EncodeRLP ensures that RLP encoding this transaction excludes the nonce. Otherwise, the tx Hash would change +func (tx *depositTxWithNonce) EncodeRLP(w io.Writer) error { + return rlp.Encode(w, tx.DepositTx) +} + +func (tx *depositTxWithNonce) effectiveNonce() *uint64 { return &tx.EffectiveNonce }
diff --git go-ethereum/core/types/transaction_signing.go op-geth/core/types/transaction_signing.go index 59dd2e76eb86ce46352585b0ddfb814228ce34c2..68ad7719059a201e5d620354b32cf910acec05a1 100644 --- go-ethereum/core/types/transaction_signing.go +++ op-geth/core/types/transaction_signing.go @@ -256,6 +256,14 @@ return londonSigner{eip2930Signer{NewEIP155Signer(chainId)}} }   func (s londonSigner) Sender(tx *Transaction) (common.Address, error) { + if tx.Type() == DepositTxType { + switch tx.inner.(type) { + case *DepositTx: + return tx.inner.(*DepositTx).From, nil + case *depositTxWithNonce: + return tx.inner.(*depositTxWithNonce).From, nil + } + } if tx.Type() != DynamicFeeTxType { return s.eip2930Signer.Sender(tx) } @@ -275,6 +283,9 @@ return ok && x.chainId.Cmp(s.chainId) == 0 }   func (s londonSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { + if tx.Type() == DepositTxType { + return nil, nil, nil, fmt.Errorf("deposits do not have a signature") + } txdata, ok := tx.inner.(*DynamicFeeTx) if !ok { return s.eip2930Signer.SignatureValues(tx, sig) @@ -292,6 +303,9 @@ // Hash returns the hash to be signed by the sender. // It does not uniquely identify the transaction. func (s londonSigner) Hash(tx *Transaction) common.Hash { + if tx.Type() == DepositTxType { + panic("deposits cannot be signed and do not have a signing hash") + } if tx.Type() != DynamicFeeTxType { return s.eip2930Signer.Hash(tx) }

The Transaction type now exposes the deposit-transaction and L1-cost properties required for the rollup.

diff --git go-ethereum/core/types/transaction.go op-geth/core/types/transaction.go index b7cb36b6026f4fb61ac41b48931ae58afe7864ee..23f7613fe02c7b3f1fdd777459225de5bebdb35c 100644 --- go-ethereum/core/types/transaction.go +++ op-geth/core/types/transaction.go @@ -28,6 +28,7 @@ "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" )   @@ -57,6 +58,9 @@ // caches hash atomic.Value size atomic.Value from atomic.Value + + // cache of RollupGasData details to compute the gas the tx takes on L1 for its share of rollup data + rollupGas atomic.Value }   // NewTx creates a new transaction. @@ -86,6 +90,7 @@ to() *common.Address blobGas() uint64 blobGasFeeCap() *big.Int blobHashes() []common.Hash + isSystemTx() bool   rawSignatureValues() (v, r, s *big.Int) setSignatureValues(chainID, v, r, s *big.Int) @@ -200,6 +205,10 @@ case BlobTxType: var inner BlobTx err := rlp.DecodeBytes(b[1:], &inner) return &inner, err + case DepositTxType: + var inner DepositTx + err := rlp.DecodeBytes(b[1:], &inner) + return &inner, err default: return nil, ErrTxTypeNotSupported } @@ -304,12 +313,56 @@ // Nonce returns the sender account nonce of the transaction. func (tx *Transaction) Nonce() uint64 { return tx.inner.nonce() }   +// EffectiveNonce returns the nonce that was actually used as part of transaction execution +// Returns nil if the effective nonce is not known +func (tx *Transaction) EffectiveNonce() *uint64 { + type txWithEffectiveNonce interface { + effectiveNonce() *uint64 + } + + if itx, ok := tx.inner.(txWithEffectiveNonce); ok { + return itx.effectiveNonce() + } + nonce := tx.inner.nonce() + return &nonce +} + // To returns the recipient address of the transaction. // For contract-creation transactions, To returns nil. func (tx *Transaction) To() *common.Address { return copyAddressPtr(tx.inner.to()) }   +// SourceHash returns the hash that uniquely identifies the source of the deposit tx, +// e.g. a user deposit event, or a L1 info deposit included in a specific L2 block height. +// Non-deposit transactions return a zeroed hash. +func (tx *Transaction) SourceHash() common.Hash { + if dep, ok := tx.inner.(*DepositTx); ok { + return dep.SourceHash + } + return common.Hash{} +} + +// Mint returns the ETH to mint in the deposit tx. +// This returns nil if there is nothing to mint, or if this is not a deposit tx. +func (tx *Transaction) Mint() *big.Int { + if dep, ok := tx.inner.(*DepositTx); ok { + return dep.Mint + } + return nil +} + +// IsDepositTx returns true if the transaction is a deposit tx type. +func (tx *Transaction) IsDepositTx() bool { + return tx.Type() == DepositTxType +} + +// IsSystemTx returns true for deposits that are system transactions. These transactions +// are executed in an unmetered environment & do not contribute to the block gas limit. +func (tx *Transaction) IsSystemTx() bool { + return tx.inner.isSystemTx() +} + // Cost returns (gas * gasPrice) + (blobGas * blobGasPrice) + value. func (tx *Transaction) Cost() *big.Int { total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas())) @@ -320,6 +373,30 @@ total.Add(total, tx.Value()) return total }   +// RollupDataGas is the amount of gas it takes to confirm the tx on L1 as a rollup +func (tx *Transaction) RollupDataGas() RollupGasData { + if tx.Type() == DepositTxType { + return RollupGasData{} + } + if v := tx.rollupGas.Load(); v != nil { + return v.(RollupGasData) + } + data, err := tx.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) + } + var out RollupGasData + for _, byt := range data { + if byt == 0 { + out.Zeroes++ + } else { + out.Ones++ + } + } + tx.rollupGas.Store(out) + return out +} + // RawSignatureValues returns the V, R, S signature values of the transaction. // The return values should not be modified by the caller. func (tx *Transaction) RawSignatureValues() (v, r, s *big.Int) { @@ -350,6 +427,9 @@ // EffectiveGasTip returns the effective miner gasTipCap for the given base fee. // Note: if the effective gasTipCap is negative, this method returns both error // the actual negative value, _and_ ErrGasFeeCapTooLow func (tx *Transaction) EffectiveGasTip(baseFee *big.Int) (*big.Int, error) { + if tx.Type() == DepositTxType { + return new(big.Int), nil + } if baseFee == nil { return tx.GasTipCap(), nil }
diff --git go-ethereum/core/types/tx_access_list.go op-geth/core/types/tx_access_list.go index 7ce9da73ecbe4a34d91803738c15b501dbaed134..a1c2fbb9bb242adc914e4f34ee1b6abcecb40570 100644 --- go-ethereum/core/types/tx_access_list.go +++ op-geth/core/types/tx_access_list.go @@ -108,6 +108,7 @@ func (tx *AccessListTx) to() *common.Address { return tx.To } func (tx *AccessListTx) blobGas() uint64 { return 0 } func (tx *AccessListTx) blobGasFeeCap() *big.Int { return nil } func (tx *AccessListTx) blobHashes() []common.Hash { return nil } +func (tx *AccessListTx) isSystemTx() bool { return false }   func (tx *AccessListTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { return dst.Set(tx.GasPrice)
diff --git go-ethereum/core/types/tx_dynamic_fee.go op-geth/core/types/tx_dynamic_fee.go index 47b870abbf5c028fef7984dc00b320a1b5848ec8..989093404e926386bb3e5af4d69f9cfba09ba5b1 100644 --- go-ethereum/core/types/tx_dynamic_fee.go +++ op-geth/core/types/tx_dynamic_fee.go @@ -97,6 +97,7 @@ func (tx *DynamicFeeTx) to() *common.Address { return tx.To } func (tx *DynamicFeeTx) blobGas() uint64 { return 0 } func (tx *DynamicFeeTx) blobGasFeeCap() *big.Int { return nil } func (tx *DynamicFeeTx) blobHashes() []common.Hash { return nil } +func (tx *DynamicFeeTx) isSystemTx() bool { return false }   func (tx *DynamicFeeTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { if baseFee == nil {
diff --git go-ethereum/core/types/tx_legacy.go op-geth/core/types/tx_legacy.go index 902e70cf94b9607bb1a6b667e4a81070813f5e01..69cf5951d973af38c141e01a7a51c7d0667879ec 100644 --- go-ethereum/core/types/tx_legacy.go +++ op-geth/core/types/tx_legacy.go @@ -105,6 +105,7 @@ func (tx *LegacyTx) to() *common.Address { return tx.To } func (tx *LegacyTx) blobGas() uint64 { return 0 } func (tx *LegacyTx) blobGasFeeCap() *big.Int { return nil } func (tx *LegacyTx) blobHashes() []common.Hash { return nil } +func (tx *LegacyTx) isSystemTx() bool { return false }   func (tx *LegacyTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { return dst.Set(tx.GasPrice)

Transactions must pay an additional L1 cost based on the amount of rollup-data-gas they consume, estimated based on gas-price-oracle information and encoded tx size.”

diff --git go-ethereum/core/vm/evm.go op-geth/core/vm/evm.go index 01017572d178c2111d9728a7938bf9c890d632a3..d070a8230ed168b0412ba43fd04065a40e66e4d0 100644 --- go-ethereum/core/vm/evm.go +++ op-geth/core/vm/evm.go @@ -20,10 +20,12 @@ import ( "math/big" "sync/atomic"   + "github.com/holiman/uint256" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" - "github.com/holiman/uint256" )   // emptyCodeHash is used by create to ensure deployment is disallowed to already @@ -66,6 +68,8 @@ // Transfer transfers ether from one account to the other Transfer TransferFunc // GetHash returns the hash corresponding to n GetHash GetHashFunc + // L1CostFunc returns the L1 cost of the rollup message, the function may be nil, or return nil + L1CostFunc types.L1CostFunc   // Block information Coinbase common.Address // Provides information for COINBASE
diff --git go-ethereum/core/types/rollup_l1_cost.go op-geth/core/types/rollup_l1_cost.go new file mode 100644 index 0000000000000000000000000000000000000000..521dfc10d7a7f086456cea4d16b2a756a0dbc777 --- /dev/null +++ op-geth/core/types/rollup_l1_cost.go @@ -0,0 +1,83 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. + +package types + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" +) + +type RollupGasData struct { + Zeroes, Ones uint64 +} + +func (r RollupGasData) DataGas(time uint64, cfg *params.ChainConfig) (gas uint64) { + gas = r.Zeroes * params.TxDataZeroGas + if cfg.IsRegolith(time) { + gas += r.Ones * params.TxDataNonZeroGasEIP2028 + } else { + gas += (r.Ones + 68) * params.TxDataNonZeroGasEIP2028 + } + return gas +} + +type StateGetter interface { + GetState(common.Address, common.Hash) common.Hash +} + +// L1CostFunc is used in the state transition to determine the cost of a rollup message. +// Returns nil if there is no cost. +type L1CostFunc func(blockNum uint64, blockTime uint64, dataGas RollupGasData, isDepositTx bool) *big.Int + +var ( + L1BaseFeeSlot = common.BigToHash(big.NewInt(1)) + OverheadSlot = common.BigToHash(big.NewInt(5)) + ScalarSlot = common.BigToHash(big.NewInt(6)) +) + +var L1BlockAddr = common.HexToAddress("0x4200000000000000000000000000000000000015") + +// NewL1CostFunc returns a function used for calculating L1 fee cost. +// This depends on the oracles because gas costs can change over time. +// It returns nil if there is no applicable cost function. +func NewL1CostFunc(config *params.ChainConfig, statedb StateGetter) L1CostFunc { + cacheBlockNum := ^uint64(0) + var l1BaseFee, overhead, scalar *big.Int + return func(blockNum uint64, blockTime uint64, dataGas RollupGasData, isDepositTx bool) *big.Int { + rollupDataGas := dataGas.DataGas(blockTime, config) // Only fake txs for RPC view-calls are 0. + if config.Optimism == nil || isDepositTx || rollupDataGas == 0 { + return nil + } + if blockNum != cacheBlockNum { + l1BaseFee = statedb.GetState(L1BlockAddr, L1BaseFeeSlot).Big() + overhead = statedb.GetState(L1BlockAddr, OverheadSlot).Big() + scalar = statedb.GetState(L1BlockAddr, ScalarSlot).Big() + cacheBlockNum = blockNum + } + return L1Cost(rollupDataGas, l1BaseFee, overhead, scalar) + } +} + +func L1Cost(rollupDataGas uint64, l1BaseFee, overhead, scalar *big.Int) *big.Int { + l1GasUsed := new(big.Int).SetUint64(rollupDataGas) + l1GasUsed = l1GasUsed.Add(l1GasUsed, overhead) + l1Cost := l1GasUsed.Mul(l1GasUsed, l1BaseFee) + l1Cost = l1Cost.Mul(l1Cost, scalar) + return l1Cost.Div(l1Cost, big.NewInt(1_000_000)) +}
diff --git go-ethereum/core/state_processor.go op-geth/core/state_processor.go index 03de673e19c2bc6fa9ff23532c3e9381d22d5396..92c7c4b8c4dbc38bc0129ae89fa7f8dcd771f149 100644 --- go-ethereum/core/state_processor.go +++ op-geth/core/state_processor.go @@ -71,7 +71,7 @@ if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { misc.ApplyDAOHardFork(statedb) } var ( - context = NewEVMBlockContext(header, p.bc, nil) + context = NewEVMBlockContext(header, p.bc, nil, p.config, statedb) vmenv = vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg) signer = types.MakeSigner(p.config, header.Number, header.Time) ) @@ -105,6 +105,11 @@ // Create a new context to be used in the EVM environment. txContext := NewEVMTxContext(msg) evm.Reset(txContext, statedb)   + nonce := tx.Nonce() + if msg.IsDepositTx && config.IsOptimismRegolith(evm.Context.Time) { + nonce = statedb.GetNonce(msg.From) + } + // Apply the transaction to the current state (included in the env). result, err := ApplyMessage(evm, msg, gp) if err != nil { @@ -131,9 +136,15 @@ } receipt.TxHash = tx.Hash() receipt.GasUsed = result.UsedGas   + if msg.IsDepositTx && config.IsOptimismRegolith(evm.Context.Time) { + // The actual nonce for deposit transactions is only recorded from Regolith onwards. + // Before the Regolith fork the DepositNonce must remain nil + receipt.DepositNonce = &nonce + } + // If the transaction created a contract, store the creation address in the receipt. if msg.To == nil { - receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) + receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, nonce) }   // Set the receipt logs and create the bloom filter. @@ -155,7 +166,7 @@ if err != nil { return nil, err } // Create a new context to be used in the EVM environment - blockContext := NewEVMBlockContext(header, bc, author) + blockContext := NewEVMBlockContext(header, bc, author, config, statedb) vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg) return applyTransaction(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv) }
diff --git go-ethereum/core/state_prefetcher.go op-geth/core/state_prefetcher.go index ff867309de302b90dc3e071e97cbe3705d34c970..bb6835202b9758e49611f8da677ddee3edc346f1 100644 --- go-ethereum/core/state_prefetcher.go +++ op-geth/core/state_prefetcher.go @@ -51,7 +51,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, cfg vm.Config, interrupt *atomic.Bool) { var ( header = block.Header() gaspool = new(GasPool).AddGas(block.GasLimit()) - blockContext = NewEVMBlockContext(header, p.bc, nil) + blockContext = NewEVMBlockContext(header, p.bc, nil, p.config, statedb) evm = vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) signer = types.MakeSigner(p.config, header.Number, header.Time) )

Deposit transactions have special processing rules: gas is pre-paid on L1, and deposits with EVM-failure are included with rolled back changes (except mint). For regular transactions, at the end of the transition, the 1559 burn and L1 cost are routed to vaults.

diff --git go-ethereum/core/state_transition.go op-geth/core/state_transition.go index 72f975775c1b63dd6ea1085be12ee1f578f6db9b..e242f63aea84464b5d457ddc0ab5534344b67225 100644 --- go-ethereum/core/state_transition.go +++ op-geth/core/state_transition.go @@ -140,20 +140,30 @@ // 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. // This field will be set to true for operations like RPC eth_call. SkipAccountChecks 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. + RollupDataGas types.RollupGasData // RollupDataGas indicates the rollup cost of the message, 0 if not a rollup or no cost. }   // 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(), + IsSystemTx: tx.IsSystemTx(), + IsDepositTx: tx.IsDepositTx(), + Mint: tx.Mint(), + RollupDataGas: tx.RollupDataGas(), + SkipAccountChecks: false, } // If baseFee provided, set gasPrice to effectiveGasPrice. @@ -228,11 +238,21 @@ func (st *StateTransition) buyGas() error { mgval := new(big.Int).SetUint64(st.msg.GasLimit) mgval = mgval.Mul(mgval, st.msg.GasPrice) + var l1Cost *big.Int + if st.evm.Context.L1CostFunc != nil && !st.msg.SkipAccountChecks { + l1Cost = st.evm.Context.L1CostFunc(st.evm.Context.BlockNumber.Uint64(), st.evm.Context.Time, st.msg.RollupDataGas, st.msg.IsDepositTx) + } + if l1Cost != nil { + mgval = mgval.Add(mgval, l1Cost) + } balanceCheck := mgval if st.msg.GasFeeCap != nil { balanceCheck = new(big.Int).SetUint64(st.msg.GasLimit) balanceCheck = balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap) balanceCheck.Add(balanceCheck, st.msg.Value) + if l1Cost != nil { + balanceCheck.Add(balanceCheck, l1Cost) + } } if have, want := st.state.GetBalance(st.msg.From), balanceCheck; have.Cmp(want) < 0 { return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want) @@ -248,6 +268,21 @@ return nil }   func (st *StateTransition) preCheck() error { + if st.msg.IsDepositTx { + // No fee fields to check, no nonce to check, and no need to check if EOA (L1 already verified it for us) + // Gas is free, but no refunds! + st.initialGas = st.msg.GasLimit + st.gasRemaining += st.msg.GasLimit // Add gas here in order to be able to execute calls. + // Don't touch the gas pool for system transactions + if st.msg.IsSystemTx { + if st.evm.ChainConfig().IsOptimismRegolith(st.evm.Context.Time) { + return fmt.Errorf("%w: address %v", ErrSystemTxNotSupported, + st.msg.From.Hex()) + } + return nil + } + return st.gp.SubGas(st.msg.GasLimit) // gas used by deposits may not be used by other txs + } // Only check transactions that are not fake msg := st.msg if !msg.SkipAccountChecks { @@ -309,6 +344,37 @@ // // However if any consensus issue encountered, return the error directly with // nil evm execution result. func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { + if mint := st.msg.Mint; mint != nil { + st.state.AddBalance(st.msg.From, mint) + } + snap := st.state.Snapshot() + + result, err := st.innerTransitionDb() + // Failed deposits must still be included. Unless we cannot produce the block at all due to the gas limit. + // On deposit failure, we rewind any state changes from after the minting, and increment the nonce. + if err != nil && err != ErrGasLimitReached && st.msg.IsDepositTx { + st.state.RevertToSnapshot(snap) + // Even though we revert the state changes, always increment the nonce for the next deposit transaction + st.state.SetNonce(st.msg.From, st.state.GetNonce(st.msg.From)+1) + // Record deposits as using all their gas (matches the gas pool) + // System Transactions are special & are not recorded as using any gas (anywhere) + // Regolith changes this behaviour so the actual gas used is reported. + // In this case the tx is invalid so is recorded as using all gas. + gasUsed := st.msg.GasLimit + if st.msg.IsSystemTx && !st.evm.ChainConfig().IsRegolith(st.evm.Context.Time) { + gasUsed = 0 + } + result = &ExecutionResult{ + UsedGas: gasUsed, + Err: fmt.Errorf("failed deposit: %w", err), + ReturnData: nil, + } + err = nil + } + return result, err +} + +func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { // First check this message satisfies all consensus rules before // applying the message. The rules include these clauses // @@ -375,6 +441,24 @@ st.state.SetNonce(msg.From, st.state.GetNonce(sender.Address())+1) ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, msg.Value) }   + // if deposit: skip refunds, skip tipping coinbase + // Regolith changes this behaviour to report the actual gasUsed instead of always reporting all gas used. + if st.msg.IsDepositTx && !rules.IsOptimismRegolith { + // Record deposits as using all their gas (matches the gas pool) + // System Transactions are special & are not recorded as using any gas (anywhere) + gasUsed := st.msg.GasLimit + if st.msg.IsSystemTx { + gasUsed = 0 + } + return &ExecutionResult{ + UsedGas: gasUsed, + Err: vmerr, + ReturnData: ret, + }, nil + } + // Note for deposit tx there is no ETH refunded for unused gas, but that's taken care of by the fact that gasPrice + // is always 0 for deposit tx. So calling refundGas will ensure the gasUsed accounting is correct without actually + // changing the sender's balance if !rules.IsLondon { // Before EIP-3529: refunds were capped to gasUsed / 2 st.refundGas(params.RefundQuotient) @@ -382,6 +466,14 @@ } else { // After EIP-3529: refunds are capped to gasUsed / 5 st.refundGas(params.RefundQuotientEIP3529) } + if st.msg.IsDepositTx && rules.IsOptimismRegolith { + // Skip coinbase payments for deposit tx in Regolith + return &ExecutionResult{ + UsedGas: st.gasUsed(), + Err: vmerr, + ReturnData: ret, + }, nil + } effectiveTip := msg.GasPrice if rules.IsLondon { effectiveTip = cmath.BigMin(msg.GasTipCap, new(big.Int).Sub(msg.GasFeeCap, st.evm.Context.BaseFee)) @@ -395,6 +487,15 @@ } else { fee := new(big.Int).SetUint64(st.gasUsed()) fee.Mul(fee, effectiveTip) st.state.AddBalance(st.evm.Context.Coinbase, fee) + } + + // 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.state.AddBalance(params.OptimismBaseFeeRecipient, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.evm.Context.BaseFee)) + if cost := st.evm.Context.L1CostFunc(st.evm.Context.BlockNumber.Uint64(), st.evm.Context.Time, st.msg.RollupDataGas, st.msg.IsDepositTx); cost != nil { + st.state.AddBalance(params.OptimismL1FeeRecipient, cost) + } }   return &ExecutionResult{

The gaslimit is free to be set by the Engine API caller, instead of enforcing adjustments of the gaslimit in increments of 11024 of the previous gaslimit. The gaslimit is changed (and limited) through the SystemConfig contract.

diff --git go-ethereum/consensus/misc/eip1559.go op-geth/consensus/misc/eip1559.go index fbaf9eec76b22a0daa49893533027b289e5c0c40..3162f6bbb943008c03b173ffba5b4db8e9164168 100644 --- go-ethereum/consensus/misc/eip1559.go +++ op-geth/consensus/misc/eip1559.go @@ -36,8 +36,10 @@ parentGasLimit := parent.GasLimit if !config.IsLondon(parent.Number) { parentGasLimit = parent.GasLimit * config.ElasticityMultiplier() } - if err := VerifyGaslimit(parentGasLimit, header.GasLimit); err != nil { - return err + if config.Optimism == nil { // gasLimit can adjust instantly in optimism + if err := VerifyGaslimit(parentGasLimit, header.GasLimit); err != nil { + return err + } } // Verify the header is not malformed if header.BaseFee == nil {

The Engine API is activated at the Merge transition, with a Total Terminal Difficulty (TTD). The rollup starts post-merge, and thus sets the TTD to 0.

diff --git go-ethereum/consensus/beacon/consensus.go op-geth/consensus/beacon/consensus.go index 75f1b65efa3046fc2079ff9a11fc8d738e303854..03046fdba7207f70b3e6131690b6ac506a62a432 100644 --- go-ethereum/consensus/beacon/consensus.go +++ op-geth/consensus/beacon/consensus.go @@ -452,6 +452,9 @@ func IsTTDReached(chain consensus.ChainHeaderReader, parentHash common.Hash, parentNumber uint64) (bool, error) { if chain.Config().TerminalTotalDifficulty == nil { return false, nil } + if common.Big0.Cmp(chain.Config().TerminalTotalDifficulty) == 0 { // in case TTD is reached at genesis. + return true, nil + } td := chain.GetTd(parentHash, parentNumber) if td == nil { return false, consensus.ErrUnknownAncestor

The rollup functionality is enabled with the optimism field in the chain config. The EIP-1559 parameters are configurable to adjust for faster more frequent and smaller blocks. The parameters can be overriden for testing.

diff --git go-ethereum/params/config.go op-geth/params/config.go index 08e5ea0be03d4d5b02cc6876d64e249ef01544e3..c07e0d15435854c92998df86d85bb3745cbeb0e9 100644 --- go-ethereum/params/config.go +++ op-geth/params/config.go @@ -31,6 +31,20 @@ RinkebyGenesisHash = common.HexToHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177") GoerliGenesisHash = common.HexToHash("0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a") )   +const ( + OPMainnetChainID = 10 + OPGoerliChainID = 420 + BaseGoerliChainID = 84531 +) + +// OP Stack chain config +var ( + // March 17, 2023 @ 7:00:00 pm UTC + OptimismGoerliRegolithTime = uint64(1679079600) + // May 4, 2023 @ 5:00:00 pm UTC + BaseGoerliRegolithTime = uint64(1683219600) +) + func newUint64(val uint64) *uint64 { return &val }   var ( @@ -244,6 +258,16 @@ Ethash: new(EthashConfig), Clique: nil, } TestRules = TestChainConfig.Rules(new(big.Int), false, 0) + + // This is an Optimism chain config with bedrock starting a block 5, introduced for historical endpoint testing, largely based on the clique config + OptimismTestConfig = func() *ChainConfig { + conf := *AllCliqueProtocolChanges // copy the config + conf.Clique = nil + conf.TerminalTotalDifficultyPassed = true + conf.BedrockBlock = big.NewInt(5) + conf.Optimism = &OptimismConfig{EIP1559Elasticity: 50, EIP1559Denominator: 10} + return &conf + }() )   // NetworkNames are user friendly names to use in the chain spec banner. @@ -289,6 +313,9 @@ ShanghaiTime *uint64 `json:"shanghaiTime,omitempty"` // Shanghai switch time (nil = no fork, 0 = already on shanghai) CancunTime *uint64 `json:"cancunTime,omitempty"` // Cancun switch time (nil = no fork, 0 = already on cancun) PragueTime *uint64 `json:"pragueTime,omitempty"` // Prague switch time (nil = no fork, 0 = already on prague)   + BedrockBlock *big.Int `json:"bedrockBlock,omitempty"` // Bedrock switch block (nil = no fork, 0 = already on optimism bedrock) + RegolithTime *uint64 `json:"regolithTime,omitempty"` // Regolith switch time (nil = no fork, 0 = already on optimism regolith) + // TerminalTotalDifficulty is the amount of total difficulty reached by // the network that triggers the consensus upgrade. TerminalTotalDifficulty *big.Int `json:"terminalTotalDifficulty,omitempty"` @@ -301,6 +328,9 @@ // Various consensus engines Ethash *EthashConfig `json:"ethash,omitempty"` Clique *CliqueConfig `json:"clique,omitempty"` + + // Optimism config, nil if not active + Optimism *OptimismConfig `json:"optimism,omitempty"` }   // EthashConfig is the consensus engine configs for proof-of-work based sealing. @@ -322,6 +352,17 @@ func (c *CliqueConfig) String() string { return "clique" }   +// OptimismConfig is the optimism config. +type OptimismConfig struct { + EIP1559Elasticity uint64 `json:"eip1559Elasticity"` + EIP1559Denominator uint64 `json:"eip1559Denominator"` +} + +// String implements the stringer interface, returning the optimism fee config details. +func (o *OptimismConfig) String() string { + return "optimism" +} + // Description returns a human-readable description of ChainConfig. func (c *ChainConfig) Description() string { var banner string @@ -333,6 +374,8 @@ network = "unknown" } banner += fmt.Sprintf("Chain ID: %v (%s)\n", c.ChainID, network) switch { + case c.Optimism != nil: + banner += "Consensus: Optimism\n" case c.Ethash != nil: if c.TerminalTotalDifficulty == nil { banner += "Consensus: Ethash (proof-of-work)\n" @@ -407,6 +450,9 @@ banner += fmt.Sprintf(" - Cancun: @%-10v\n", *c.CancunTime) } if c.PragueTime != nil { banner += fmt.Sprintf(" - Prague: @%-10v\n", *c.PragueTime) + } + if c.RegolithTime != nil { + banner += fmt.Sprintf(" - Regolith: @%-10v\n", *c.RegolithTime) } return banner } @@ -506,6 +552,34 @@ func (c *ChainConfig) IsPrague(num *big.Int, time uint64) bool { return c.IsLondon(num) && isTimestampForked(c.PragueTime, time) }   +// IsBedrock returns whether num is either equal to the Bedrock fork block or greater. +func (c *ChainConfig) IsBedrock(num *big.Int) bool { + return isBlockForked(c.BedrockBlock, num) +} + +func (c *ChainConfig) IsRegolith(time uint64) bool { + return isTimestampForked(c.RegolithTime, time) +} + +// IsOptimism returns whether the node is an optimism node or not. +func (c *ChainConfig) IsOptimism() bool { + return c.Optimism != nil +} + +// IsOptimismBedrock returns true iff this is an optimism node & bedrock is active +func (c *ChainConfig) IsOptimismBedrock(num *big.Int) bool { + return c.IsOptimism() && c.IsBedrock(num) +} + +func (c *ChainConfig) IsOptimismRegolith(time uint64) bool { + return c.IsOptimism() && c.IsRegolith(time) +} + +// IsOptimismPreBedrock returns true iff this is an optimism node & bedrock is not yet active +func (c *ChainConfig) IsOptimismPreBedrock(num *big.Int) bool { + return c.IsOptimism() && !c.IsBedrock(num) +} + // CheckCompatible checks whether scheduled fork transitions have been imported // with a mismatching chain configuration. func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64, time uint64) *ConfigCompatError { @@ -668,11 +742,17 @@ }   // BaseFeeChangeDenominator bounds the amount the base fee can change between blocks. func (c *ChainConfig) BaseFeeChangeDenominator() uint64 { + if c.Optimism != nil { + return c.Optimism.EIP1559Denominator + } return DefaultBaseFeeChangeDenominator }   // ElasticityMultiplier bounds the maximum gas limit an EIP-1559 block may have. func (c *ChainConfig) ElasticityMultiplier() uint64 { + if c.Optimism != nil { + return c.Optimism.EIP1559Elasticity + } return DefaultElasticityMultiplier }   @@ -808,6 +888,7 @@ IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool IsBerlin, IsLondon bool IsMerge, IsShanghai, IsCancun, IsPrague bool + IsOptimismBedrock, IsOptimismRegolith bool }   // Rules ensures c's ChainID is not nil. @@ -832,5 +913,8 @@ IsMerge: isMerge, IsShanghai: c.IsShanghai(num, timestamp), IsCancun: c.IsCancun(num, timestamp), IsPrague: c.IsPrague(num, timestamp), + // Optimism + IsOptimismBedrock: c.IsOptimismBedrock(num), + IsOptimismRegolith: c.IsOptimismRegolith(timestamp), } }
diff --git go-ethereum/params/protocol_params.go op-geth/params/protocol_params.go index 1fb258c1fc1ef5649858e271408a528c58694012..0b82438fd6a8297c544301abe2e73f0966fce277 100644 --- go-ethereum/params/protocol_params.go +++ op-geth/params/protocol_params.go @@ -16,7 +16,18 @@ // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.   package params   -import "math/big" +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +var ( + // The base fee portion of the transaction fee accumulates at this predeploy + OptimismBaseFeeRecipient = common.HexToAddress("0x4200000000000000000000000000000000000019") + // The L1 portion of the transaction fee accumulates at this predeploy + OptimismL1FeeRecipient = common.HexToAddress("0x420000000000000000000000000000000000001A") +)   const ( GasLimitBoundDivisor uint64 = 1024 // The bound divisor of the gas limit, used in update calculations.
diff --git go-ethereum/core/genesis.go op-geth/core/genesis.go index 68eeec216151ffc4c39c334dcc057d32d32cdd0d..0df7a63b65a676df17cac95c6af4cb868a23aa43 100644 --- go-ethereum/core/genesis.go +++ op-geth/core/genesis.go @@ -63,6 +63,11 @@ Number uint64 `json:"number"` GasUsed uint64 `json:"gasUsed"` ParentHash common.Hash `json:"parentHash"` BaseFee *big.Int `json:"baseFeePerGas"` + + // StateHash represents the genesis state, to allow instantiation of a chain with missing initial state. + // Chains with history pruning, or extraordinarily large genesis allocation (e.g. after a regenesis event) + // may utilize this to get started, and then state-sync the latest state, while still verifying the header chain. + StateHash *common.Hash `json:"stateHash,omitempty"` }   func ReadGenesis(db ethdb.Database) (*Genesis, error) { @@ -268,6 +273,10 @@ // ChainOverrides contains the changes to chain config. type ChainOverrides struct { OverrideCancun *uint64 + // optimism + OverrideOptimismBedrock *big.Int + OverrideOptimismRegolith *uint64 + OverrideOptimism *bool }   // SetupGenesisBlock writes or updates the genesis block in db. @@ -293,8 +302,30 @@ return params.AllEthashProtocolChanges, common.Hash{}, errGenesisNoConfig } applyOverrides := func(config *params.ChainConfig) { if config != nil { + if config.IsOptimism() && config.ChainID != nil && config.ChainID.Cmp(big.NewInt(params.OPGoerliChainID)) == 0 { + // Apply Optimism Goerli regolith time + config.RegolithTime = &params.OptimismGoerliRegolithTime + } + if config.IsOptimism() && config.ChainID != nil && config.ChainID.Cmp(big.NewInt(params.BaseGoerliChainID)) == 0 { + // Apply Base Goerli regolith time + config.RegolithTime = &params.BaseGoerliRegolithTime + } if overrides != nil && overrides.OverrideCancun != nil { config.CancunTime = overrides.OverrideCancun + } + if overrides != nil && overrides.OverrideOptimismBedrock != nil { + config.BedrockBlock = overrides.OverrideOptimismBedrock + } + if overrides != nil && overrides.OverrideOptimismRegolith != nil { + config.RegolithTime = overrides.OverrideOptimismRegolith + } + if overrides != nil && overrides.OverrideOptimism != nil { + if *overrides.OverrideOptimism { + config.Optimism = &params.OptimismConfig{ + EIP1559Elasticity: 10, + EIP1559Denominator: 50, + } + } } } } @@ -431,8 +462,15 @@ }   // ToBlock returns the genesis block according to genesis specification. func (g *Genesis) ToBlock() *types.Block { - root, err := g.Alloc.deriveHash() - if err != nil { + var root common.Hash + var err error + if g.StateHash != nil { + if len(g.Alloc) > 0 { + panic(fmt.Errorf("cannot both have genesis hash %s "+ + "and non-empty state-allocation", *g.StateHash)) + } + root = *g.StateHash + } else if root, err = g.Alloc.deriveHash(); err != nil { panic(err) } head := &types.Header{

The optimism Goerli testnet used clique-config data to make geth internals accept blocks. Post-bedrock the beacon-consensus (i.e. follow Engine API) is now used, and the clique config is removed.

diff --git go-ethereum/core/rawdb/accessors_metadata.go op-geth/core/rawdb/accessors_metadata.go index 2ff29d1add93938a5f76f621ce6b47af179d9aca..45ced36a4fd400cc84f60a9a9dffedd19cb7e1cf 100644 --- go-ethereum/core/rawdb/accessors_metadata.go +++ op-geth/core/rawdb/accessors_metadata.go @@ -64,6 +64,9 @@ if err := json.Unmarshal(data, &config); err != nil { log.Error("Invalid chain config JSON", "hash", hash, "err", err) return nil } + if config.Optimism != nil { + config.Clique = nil // get rid of legacy clique data in chain config (optimism goerli issue) + } return &config }

The Engine API is extended to insert transactions into the block and optionally exclude the tx-pool, to reproduce the exact block of the sequencer from just the inputs, as derived from L1 by the rollup-node. See L2 execution engine specs.

diff --git go-ethereum/beacon/engine/types.go op-geth/beacon/engine/types.go index 07ebe544b438f4c0d356efc4362928c37de8062b..00128cf777744fba0aec75a29d5566ca968d20ce 100644 --- go-ethereum/beacon/engine/types.go +++ op-geth/beacon/engine/types.go @@ -34,12 +34,23 @@ type PayloadAttributes struct { Timestamp uint64 `json:"timestamp" gencodec:"required"` Random common.Hash `json:"prevRandao" gencodec:"required"` SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` - Withdrawals []*types.Withdrawal `json:"withdrawals"` + Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty" gencodec:"optional"` + + // Transactions is a field for rollups: the transactions list is forced into the block + Transactions [][]byte `json:"transactions,omitempty" gencodec:"optional"` + // NoTxPool is a field for rollups: if true, the no transactions are taken out of the tx-pool, + // only transactions from the above Transactions list will be included. + NoTxPool bool `json:"noTxPool,omitempty" gencodec:"optional"` + // GasLimit is a field for rollups: if set, this sets the exact gas limit the block produced with. + GasLimit *uint64 `json:"gasLimit,omitempty" gencodec:"optional"` }   // JSON type overrides for PayloadAttributes. type payloadAttributesMarshaling struct { Timestamp hexutil.Uint64 + + Transactions []hexutil.Bytes + GasLimit *hexutil.Uint64 }   //go:generate go run github.com/fjl/gencodec -type ExecutableData -field-override executableDataMarshaling -out gen_ed.go
diff --git go-ethereum/beacon/engine/gen_blockparams.go op-geth/beacon/engine/gen_blockparams.go index 0dd2b52597ad5ddc7fc46d97c6c8878edf29d471..0a76475841a12886ef2f9eaadba0172f2708d2b4 100644 --- go-ethereum/beacon/engine/gen_blockparams.go +++ op-geth/beacon/engine/gen_blockparams.go @@ -19,13 +19,24 @@ type PayloadAttributes struct { Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"` Random common.Hash `json:"prevRandao" gencodec:"required"` SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` - Withdrawals []*types.Withdrawal `json:"withdrawals"` + Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty" gencodec:"optional"` + Transactions []hexutil.Bytes `json:"transactions,omitempty" gencodec:"optional"` + NoTxPool bool `json:"noTxPool,omitempty" gencodec:"optional"` + GasLimit *hexutil.Uint64 `json:"gasLimit,omitempty" gencodec:"optional"` } var enc PayloadAttributes enc.Timestamp = hexutil.Uint64(p.Timestamp) enc.Random = p.Random enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient enc.Withdrawals = p.Withdrawals + if p.Transactions != nil { + enc.Transactions = make([]hexutil.Bytes, len(p.Transactions)) + for k, v := range p.Transactions { + enc.Transactions[k] = v + } + } + enc.NoTxPool = p.NoTxPool + enc.GasLimit = (*hexutil.Uint64)(p.GasLimit) return json.Marshal(&enc) }   @@ -35,7 +46,10 @@ type PayloadAttributes struct { Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"` Random *common.Hash `json:"prevRandao" gencodec:"required"` SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"` - Withdrawals []*types.Withdrawal `json:"withdrawals"` + Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty" gencodec:"optional"` + Transactions []hexutil.Bytes `json:"transactions,omitempty" gencodec:"optional"` + NoTxPool *bool `json:"noTxPool,omitempty" gencodec:"optional"` + GasLimit *hexutil.Uint64 `json:"gasLimit,omitempty" gencodec:"optional"` } var dec PayloadAttributes if err := json.Unmarshal(input, &dec); err != nil { @@ -55,6 +69,18 @@ } p.SuggestedFeeRecipient = *dec.SuggestedFeeRecipient if dec.Withdrawals != nil { p.Withdrawals = dec.Withdrawals + } + if dec.Transactions != nil { + p.Transactions = make([][]byte, len(dec.Transactions)) + for k, v := range dec.Transactions { + p.Transactions[k] = v + } + } + if dec.NoTxPool != nil { + p.NoTxPool = *dec.NoTxPool + } + if dec.GasLimit != nil { + p.GasLimit = (*uint64)(dec.GasLimit) } return nil }
diff --git go-ethereum/eth/catalyst/api.go op-geth/eth/catalyst/api.go index 456bd7741327f33c5a98b6545b853224a9c0f3b7..b1b5b5d072384fbc4e84e667d5d8035458069119 100644 --- go-ethereum/eth/catalyst/api.go +++ op-geth/eth/catalyst/api.go @@ -144,6 +144,9 @@ invalidBlocksHits: make(map[common.Hash]int), invalidTipsets: make(map[common.Hash]*types.Header), } eth.Downloader().SetBadBlockCallback(api.setInvalidAncestor) + if api.eth.BlockChain().Config().Optimism != nil { // don't start the api heartbeat, there is no transition + return api + } go api.heartbeat()   return api @@ -293,7 +296,7 @@ } else if api.eth.BlockChain().CurrentBlock().Hash() == update.HeadBlockHash { // If the specified head matches with our local head, do nothing and keep // generating the payload. It's a special corner case that a few slots are // missing and we are requested to generate the payload in slot. - } else { + } else if api.eth.BlockChain().Config().Optimism == nil { // minor Engine API divergence: allow proposers to reorg their own chain // If the head block is already in our canonical chain, the beacon client is // probably resyncing. Ignore the update. log.Info("Ignoring beacon update to old head", "number", block.NumberU64(), "hash", update.HeadBlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)), "have", api.eth.BlockChain().CurrentBlock().Number) @@ -337,12 +340,26 @@ // If payload generation was requested, create a new block to be potentially // sealed by the beacon client. The payload will be requested later, and we // will replace it arbitrarily many times in between. if payloadAttributes != nil { + if api.eth.BlockChain().Config().Optimism != nil && payloadAttributes.GasLimit == nil { + return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("gasLimit parameter is required")) + } + transactions := make(types.Transactions, 0, len(payloadAttributes.Transactions)) + for i, otx := range payloadAttributes.Transactions { + var tx types.Transaction + if err := tx.UnmarshalBinary(otx); err != nil { + return engine.STATUS_INVALID, fmt.Errorf("transaction %d is not valid: %v", i, err) + } + transactions = append(transactions, &tx) + } args := &miner.BuildPayloadArgs{ Parent: update.HeadBlockHash, Timestamp: payloadAttributes.Timestamp, FeeRecipient: payloadAttributes.SuggestedFeeRecipient, Random: payloadAttributes.Random, Withdrawals: payloadAttributes.Withdrawals, + NoTxPool: payloadAttributes.NoTxPool, + Transactions: transactions, + GasLimit: payloadAttributes.GasLimit, } id := args.Id() // If we already are busy generating this work, then we do not need

The block-building code (in the “miner” package because of Proof-Of-Work legacy of ethereum) implements the changes to support the transaction-inclusion, tx-pool toggle and gaslimit parameters of the Engine API.

diff --git go-ethereum/miner/worker_test.go op-geth/miner/worker_test.go index 683d019d2d551b9abd3f1453fb885925122fe876..34851e8e750e447cd27ec2d988766d959ef7d283 100644 --- go-ethereum/miner/worker_test.go +++ op-geth/miner/worker_test.go @@ -472,7 +472,8 @@ min := float64(3 * time.Second.Nanoseconds()) estimate = estimate*(1-intervalAdjustRatio) + intervalAdjustRatio*(min-intervalAdjustBias) wantMinInterval, wantRecommitInterval = 3*time.Second, time.Duration(estimate)*time.Nanosecond case 3: - wantMinInterval, wantRecommitInterval = time.Second, time.Second + // lower than upstream test, since enforced min recommit interval is lower + wantMinInterval, wantRecommitInterval = 500*time.Millisecond, 500*time.Millisecond }   // Check interval @@ -631,7 +632,7 @@ }   // This API should work even when the automatic sealing is not enabled for _, c := range cases { - block, _, err := w.getSealingBlock(c.parent, timestamp, c.coinbase, c.random, nil, false) + block, _, err := w.getSealingBlock(c.parent, timestamp, c.coinbase, c.random, nil, false, nil, nil) if c.expectErr { if err == nil { t.Error("Expect error but get nil") @@ -647,7 +648,7 @@ // This API should work even when the automatic sealing is enabled w.start() for _, c := range cases { - block, _, err := w.getSealingBlock(c.parent, timestamp, c.coinbase, c.random, nil, false) + block, _, err := w.getSealingBlock(c.parent, timestamp, c.coinbase, c.random, nil, false, nil, nil) if c.expectErr { if err == nil { t.Error("Expect error but get nil")
diff --git go-ethereum/miner/worker.go op-geth/miner/worker.go index 936a9e74a5b6d6ff68c76a399424ca0fa61f7909..59a2d5c2b98c01a9af22f283c27628b8bbcd9a31 100644 --- go-ethereum/miner/worker.go +++ op-geth/miner/worker.go @@ -17,6 +17,7 @@ package miner   import ( + "context" "errors" "fmt" "math/big" @@ -31,6 +32,7 @@ "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" @@ -59,7 +61,7 @@ sealingLogAtDepth = 7   // minRecommitInterval is the minimal time interval to recreate the sealing block with // any newly arrived transactions. - minRecommitInterval = 1 * time.Second + minRecommitInterval = 100 * time.Millisecond   // maxRecommitInterval is the maximum time interval to recreate the sealing block with // any newly arrived transactions. @@ -382,6 +384,9 @@ }   // pending returns the pending state and corresponding block. func (w *worker) pending() (*types.Block, *state.StateDB) { + if w.chainConfig.Optimism != nil && !w.config.RollupComputePendingBlock { + return nil, nil // when not computing the pending block, there is never a pending state + } // return a snapshot to avoid contention on currentMu mutex w.snapshotMu.RLock() defer w.snapshotMu.RUnlock() @@ -393,6 +398,12 @@ }   // pendingBlock returns pending block. func (w *worker) pendingBlock() *types.Block { + if w.chainConfig.Optimism != nil && !w.config.RollupComputePendingBlock { + // For compatibility when not computing a pending block, we serve the latest block as "pending" + headHeader := w.eth.BlockChain().CurrentHeader() + headBlock := w.eth.BlockChain().GetBlock(headHeader.Hash(), headHeader.Number.Uint64()) + return headBlock + } // return a snapshot to avoid contention on currentMu mutex w.snapshotMu.RLock() defer w.snapshotMu.RUnlock() @@ -401,6 +412,9 @@ }   // pendingBlockAndReceipts returns pending block and corresponding receipts. func (w *worker) pendingBlockAndReceipts() (*types.Block, types.Receipts) { + if w.chainConfig.Optimism != nil && !w.config.RollupComputePendingBlock { + return nil, nil // when not computing the pending block, there are no pending receipts, and thus no pending logs + } // return a snapshot to avoid contention on currentMu mutex w.snapshotMu.RLock() defer w.snapshotMu.RUnlock() @@ -456,6 +470,19 @@ // newWorkLoop is a standalone goroutine to submit new sealing work upon received events. func (w *worker) newWorkLoop(recommit time.Duration) { defer w.wg.Done() + if w.chainConfig.Optimism != nil && !w.config.RollupComputePendingBlock { + for { // do not update the pending-block, instead drain work without doing it, to keep producers from blocking. + select { + case <-w.startCh: + case <-w.chainHeadCh: + case <-w.resubmitIntervalCh: + case <-w.resubmitAdjustCh: + case <-w.exitCh: + return + } + } + } + var ( interrupt *atomic.Int32 minRecommit = recommit // minimal resubmit interval specified by user. @@ -618,6 +645,9 @@ } }   case ev := <-w.txsCh: + if w.chainConfig.Optimism != nil && !w.config.RollupComputePendingBlock { + continue // don't update the pending-block snapshot if we are not computing the pending block + } // Apply transactions to the pending state if we're not sealing // // Note all transactions received may not be continuous with transactions @@ -794,6 +824,15 @@ func (w *worker) makeEnv(parent *types.Header, header *types.Header, coinbase common.Address) (*environment, error) { // Retrieve the parent state to execute on top and start a prefetcher for // the miner to speed block sealing up a bit. state, err := w.chain.StateAt(parent.Root) + if err != nil && w.chainConfig.Optimism != nil { // Allow the miner to reorg its own chain arbitrarily deep + if historicalBackend, ok := w.eth.(BackendWithHistoricalState); ok { + var release tracers.StateReleaseFunc + parentBlock := w.eth.BlockChain().GetBlockByHash(parent.Hash()) + state, release, err = historicalBackend.StateAtBlock(context.Background(), parentBlock, ^uint64(0), nil, false, false) + state = state.Copy() + release() + } + } if err != nil { return nil, err } @@ -964,6 +1003,9 @@ random common.Hash // The randomness generated by beacon chain, empty before the merge withdrawals types.Withdrawals // List of withdrawals to include in block. noUncle bool // Flag whether the uncle block inclusion is allowed noTxs bool // Flag whether an empty block without any transaction is expected + + txs types.Transactions // Deposit transactions to include at the start of the block + gasLimit *uint64 // Optional gas limit override }   // prepareWork constructs the sealing task according to the given parameters, @@ -1000,7 +1042,7 @@ Time: timestamp, Coinbase: genParams.coinbase, } // Set the extra field. - if len(w.extra) != 0 { + if len(w.extra) != 0 && w.chainConfig.Optimism == nil { // Optimism chains must not set any extra data. header.Extra = w.extra } // Set the randomness field from the beacon chain if it's available. @@ -1015,6 +1057,12 @@ parentGasLimit := parent.GasLimit * w.chainConfig.ElasticityMultiplier() header.GasLimit = core.CalcGasLimit(parentGasLimit, w.config.GasCeil) } } + if genParams.gasLimit != nil { // override gas limit if specified + header.GasLimit = *genParams.gasLimit + } else if w.chain.Config().Optimism != nil && w.config.GasCeil != 0 { + // configure the gas limit of pending blocks with the miner gas limit config when using optimism + header.GasLimit = w.config.GasCeil + } // Run the consensus preparation with the default or customized consensus engine. if err := w.engine.Prepare(w.chain, header); err != nil { log.Error("Failed to prepare header for sealing", "err", err) @@ -1079,14 +1127,28 @@ return nil }   // generateWork generates a sealing block based on the given parameters. -func (w *worker) generateWork(params *generateParams) (*types.Block, *big.Int, error) { - work, err := w.prepareWork(params) +func (w *worker) generateWork(genParams *generateParams) (*types.Block, *big.Int, error) { + work, err := w.prepareWork(genParams) if err != nil { return nil, nil, err } defer work.discard() + if work.gasPool == nil { + work.gasPool = new(core.GasPool).AddGas(work.header.GasLimit) + }   - if !params.noTxs { + for _, tx := range genParams.txs { + from, _ := types.Sender(work.signer, tx) + work.state.SetTxContext(tx.Hash(), work.tcount) + _, err := w.commitTransaction(work, tx) + if err != nil { + return nil, nil, fmt.Errorf("failed to force-include tx: %s type: %d sender: %s nonce: %d, err: %w", tx.Hash(), tx.Type(), from, tx.Nonce(), err) + } + work.tcount++ + } + + // forced transactions done, fill rest of block with transactions + if !genParams.noTxs { interrupt := new(atomic.Int32) timer := time.AfterFunc(w.newpayloadTimeout, func() { interrupt.Store(commitInterruptTimeout) @@ -1098,7 +1160,7 @@ if errors.Is(err, errBlockInterruptedByTimeout) { log.Warn("Block building is interrupted", "allowance", common.PrettyDuration(w.newpayloadTimeout)) } } - block, err := w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, work.unclelist(), work.receipts, params.withdrawals) + block, err := w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, work.unclelist(), work.receipts, genParams.withdrawals) if err != nil { return nil, nil, err } @@ -1215,7 +1277,7 @@ // getSealingBlock generates the sealing block based on the given parameters. // The generation result will be passed back via the given channel no matter // the generation itself succeeds or not. -func (w *worker) getSealingBlock(parent common.Hash, timestamp uint64, coinbase common.Address, random common.Hash, withdrawals types.Withdrawals, noTxs bool) (*types.Block, *big.Int, error) { +func (w *worker) getSealingBlock(parent common.Hash, timestamp uint64, coinbase common.Address, random common.Hash, withdrawals types.Withdrawals, noTxs bool, transactions types.Transactions, gasLimit *uint64) (*types.Block, *big.Int, error) { req := &getWorkReq{ params: &generateParams{ timestamp: timestamp, @@ -1226,6 +1288,8 @@ random: random, withdrawals: withdrawals, noUncle: true, noTxs: noTxs, + txs: transactions, + gasLimit: gasLimit, }, result: make(chan *newPayloadResult, 1), }
diff --git go-ethereum/miner/miner.go op-geth/miner/miner.go index b1d1f7c4cbfe2a9b34f4f68ce345e0d09f209d9c..0c651042a2e31d8330ecf6be91cb2c662806d95b 100644 --- go-ethereum/miner/miner.go +++ op-geth/miner/miner.go @@ -18,6 +18,7 @@ // Package miner implements Ethereum block creation and mining. package miner   import ( + "context" "fmt" "math/big" "sync" @@ -31,6 +32,7 @@ "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" @@ -43,6 +45,10 @@ BlockChain() *core.BlockChain TxPool() *txpool.TxPool }   +type BackendWithHistoricalState interface { + StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, tracers.StateReleaseFunc, error) +} + // Config is the configuration parameters of mining. type Config struct { Etherbase common.Address `toml:",omitempty"` // Public address for block mining rewards @@ -53,6 +59,8 @@ GasPrice *big.Int // Minimum gas price for mining a transaction Recommit time.Duration // The time interval for miner to re-create mining work.   NewPayloadTimeout time.Duration // The maximum time allowance for creating a new payload + + RollupComputePendingBlock bool // Compute the pending block from tx-pool, instead of copying the latest-block }   // DefaultConfig contains default settings for miner.
diff --git go-ethereum/miner/payload_building.go op-geth/miner/payload_building.go index f84d908e86d68dbb132e2cd75ba5e21887fc018c..26e89eef31cab52754e0a41bb540639b4f58067f 100644 --- go-ethereum/miner/payload_building.go +++ op-geth/miner/payload_building.go @@ -40,6 +40,10 @@ Timestamp uint64 // The provided timestamp of generated payload FeeRecipient common.Address // The provided recipient address for collecting transaction fee Random common.Hash // The provided randomness value Withdrawals types.Withdrawals // The provided withdrawals + + NoTxPool bool // Optimism addition: option to disable tx pool contents from being included + Transactions []*types.Transaction // Optimism addition: txs forced into the block via engine API + GasLimit *uint64 // Optimism addition: override gas limit of the block to build }   // Id computes an 8-byte identifier by hashing the components of the payload arguments. @@ -51,6 +55,19 @@ binary.Write(hasher, binary.BigEndian, args.Timestamp) hasher.Write(args.Random[:]) hasher.Write(args.FeeRecipient[:]) rlp.Encode(hasher, args.Withdrawals) + + if args.NoTxPool || len(args.Transactions) > 0 { // extend if extra payload attributes are used + binary.Write(hasher, binary.BigEndian, args.NoTxPool) + binary.Write(hasher, binary.BigEndian, uint64(len(args.Transactions))) + for _, tx := range args.Transactions { + h := tx.Hash() + hasher.Write(h[:]) + } + } + if args.GasLimit != nil { + binary.Write(hasher, binary.BigEndian, *args.GasLimit) + } + var out engine.PayloadID copy(out[:], hasher.Sum(nil)[:8]) return out @@ -156,12 +173,15 @@ func (w *worker) buildPayload(args *BuildPayloadArgs) (*Payload, error) { // Build the initial version with no transaction included. It should be fast // enough to run. The empty payload can at least make sure there is something // to deliver for not missing slot. - empty, _, err := w.getSealingBlock(args.Parent, args.Timestamp, args.FeeRecipient, args.Random, args.Withdrawals, true) + empty, _, err := w.getSealingBlock(args.Parent, args.Timestamp, args.FeeRecipient, args.Random, args.Withdrawals, true, args.Transactions, args.GasLimit) if err != nil { return nil, err } // Construct a payload object for return. payload := newPayload(empty, args.Id()) + if args.NoTxPool { // don't start the background payload updating job if there is no tx pool to pull from + return payload, nil + }   // Spin up a routine for updating the payload in background. This strategy // can maximum the revenue for including transactions with highest fee. @@ -180,7 +200,7 @@ for { select { case <-timer.C: start := time.Now() - block, fees, err := w.getSealingBlock(args.Parent, args.Timestamp, args.FeeRecipient, args.Random, args.Withdrawals, false) + block, fees, err := w.getSealingBlock(args.Parent, args.Timestamp, args.FeeRecipient, args.Random, args.Withdrawals, false, args.Transactions, args.GasLimit) if err == nil { payload.update(block, fees, time.Since(start)) }

Transaction queueing and inclusion needs to account for the L1 cost component.

diff --git go-ethereum/core/txpool/txpool_test.go op-geth/core/txpool/txpool_test.go index 22e106aaf8c5565ff763b85d1cf57debe347f69e..38fcdb364306f500774747bb022875863ec00369 100644 --- go-ethereum/core/txpool/txpool_test.go +++ op-geth/core/txpool/txpool_test.go @@ -2255,10 +2255,13 @@ }   // Tests that local transactions are journaled to disk, but remote transactions // get discarded between restarts. -func TestJournaling(t *testing.T) { testJournaling(t, false) } -func TestJournalingNoLocals(t *testing.T) { testJournaling(t, true) } +func TestJournaling(t *testing.T) { testJournaling(t, false, false) } +func TestJournalingNoLocals(t *testing.T) { testJournaling(t, true, false) } + +func TestJournalingRemotes(t *testing.T) { testJournaling(t, false, true) } +func TestJournalingRemotesNoLocals(t *testing.T) { testJournaling(t, true, true) }   -func testJournaling(t *testing.T, nolocals bool) { +func testJournaling(t *testing.T, nolocals bool, journalRemotes bool) { t.Parallel()   // Create a temporary file for the journal @@ -2279,6 +2282,7 @@ blockchain := newTestBlockChain(1000000, statedb, new(event.Feed))   config := testTxPoolConfig config.NoLocals = nolocals + config.JournalRemote = journalRemotes config.Journal = journal config.Rejournal = time.Second   @@ -2325,10 +2329,14 @@ pending, queued = pool.Stats() if queued != 0 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) } - if nolocals { + if nolocals && !journalRemotes { if pending != 0 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) } + } else if journalRemotes { + if pending != 3 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) + } } else { if pending != 2 { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) @@ -2348,10 +2356,16 @@ blockchain = newTestBlockChain(1000000, statedb, new(event.Feed)) pool = NewTxPool(config, params.TestChainConfig, blockchain)   pending, queued = pool.Stats() - if pending != 0 { - t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) + if journalRemotes { + if pending != 1 { // Remove the 2 replaced local transactions, but preserve the remote + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 1) + } + } else { + if pending != 0 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) + } } - if nolocals { + if nolocals && !journalRemotes { if queued != 0 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) }
diff --git go-ethereum/core/txpool/list.go op-geth/core/txpool/list.go index fae7c2fcac2dacf40797517e947e66d7076e1c91..c8d9835335451132cfd723b894ab9ca8b8f1c050 100644 --- go-ethereum/core/txpool/list.go +++ op-geth/core/txpool/list.go @@ -246,6 +246,15 @@ cache := m.flatten() return cache[len(cache)-1] }   +// FirstElement returns the first element from the heap (guaranteed to be lowest), thus, the +// transaction with the lowest nonce. Returns nil if there are no elements. +func (m *sortedMap) FirstElement() *types.Transaction { + if m.Len() == 0 { + return nil + } + return m.Get((*m.index)[0]) +} + // list is a "list" of transactions belonging to an account, sorted by account // nonce. The same type can be used both for storing contiguous transactions for // the executable/pending queue; and for storing gapped transactions for the non-
diff --git go-ethereum/core/txpool/txpool.go op-geth/core/txpool/txpool.go index cbb8cc287a036878fef53785f9f63e301f63df17..d8e40d7972d4f97099ff02cfa27c548e581d772c 100644 --- go-ethereum/core/txpool/txpool.go +++ op-geth/core/txpool/txpool.go @@ -100,6 +100,11 @@ var ( evictionInterval = time.Minute // Time interval to check for evictable transactions statsReportInterval = 8 * time.Second // Time interval to report transaction pool stats + + // L1 Info Gas Overhead is the amount of gas the the L1 info deposit consumes. + // It is removed from the tx pool max gas to better indicate that L2 transactions + // are not able to consume all of the gas in a L2 block as the L1 info deposit is always present. + l1InfoGasOverhead = uint64(70_000) )   var ( @@ -167,6 +172,10 @@ NoLocals bool // Whether local transaction handling should be disabled Journal string // Journal of local transactions to survive node restarts Rejournal time.Duration // Time interval to regenerate the local transaction journal   + // JournalRemote controls whether journaling includes remote transactions or not. + // When true, all transactions loaded from the journal are treated as remote. + JournalRemote bool + PriceLimit uint64 // Minimum gas price to enforce for acceptance into the pool PriceBump uint64 // Minimum price bump percentage to replace an already existing transaction (nonce)   @@ -260,6 +269,8 @@ currentState *state.StateDB // Current state in the blockchain head pendingNonces *noncer // Pending state tracking virtual nonces currentMaxGas atomic.Uint64 // Current gas limit for transaction caps   + l1CostFn func(dataGas types.RollupGasData, isDepositTx bool) *big.Int // Current L1 fee cost function + locals *accountSet // Set of local transaction to exempt from eviction rules journal *journal // Journal of local transaction to back up to disk   @@ -323,14 +334,18 @@ // Start the reorg loop early so it can handle requests generated during journal loading. pool.wg.Add(1) go pool.scheduleReorgLoop()   - // If local transactions and journaling is enabled, load from disk - if !config.NoLocals && config.Journal != "" { + // If journaling is enabled and has transactions to journal, load from disk + if (!config.NoLocals || config.JournalRemote) && config.Journal != "" { pool.journal = newTxJournal(config.Journal)   - if err := pool.journal.load(pool.AddLocals); err != nil { + add := pool.AddLocals + if config.JournalRemote { + add = pool.AddRemotesSync // Use sync version to match pool.AddLocals + } + if err := pool.journal.load(add); err != nil { log.Warn("Failed to load transaction journal", "err", err) } - if err := pool.journal.rotate(pool.local()); err != nil { + if err := pool.journal.rotate(pool.toJournal()); err != nil { log.Warn("Failed to rotate transaction journal", "err", err) } } @@ -413,7 +428,7 @@ // Handle local transaction journal rotation case <-journal.C: if pool.journal != nil { pool.mu.Lock() - if err := pool.journal.rotate(pool.local()); err != nil { + if err := pool.journal.rotate(pool.toJournal()); err != nil { log.Warn("Failed to rotate local tx journal", "err", err) } pool.mu.Unlock() @@ -593,11 +608,34 @@ } return txs }   +// toJournal retrieves all transactions that should be included in the journal, +// grouped by origin account and sorted by nonce. +// The returned transaction set is a copy and can be freely modified by calling code. +func (pool *TxPool) toJournal() map[common.Address]types.Transactions { + if !pool.config.JournalRemote { + return pool.local() + } + txs := make(map[common.Address]types.Transactions) + for addr, pending := range pool.pending { + txs[addr] = append(txs[addr], pending.Flatten()...) + } + for addr, queued := range pool.queue { + txs[addr] = append(txs[addr], queued.Flatten()...) + } + return txs +} + // validateTxBasics checks whether a transaction is valid according to the consensus // rules, but does not check state-dependent validation such as sufficient balance. // This check is meant as an early check which only needs to be performed once, // and does not require the pool mutex to be held. func (pool *TxPool) validateTxBasics(tx *types.Transaction, local bool) error { + // No unauthenticated deposits allowed in the transaction pool. + // This is for spam protection, not consensus, + // as the external engine-API user authenticates deposits. + if tx.Type() == types.DepositTxType { + return core.ErrTxTypeNotSupported + } // Accept only legacy transactions until EIP-2718/2930 activates. if !pool.eip2718.Load() && tx.Type() != types.LegacyTxType { return core.ErrTxTypeNotSupported @@ -668,18 +706,26 @@ return core.ErrNonceTooLow } // Transactor should have enough funds to cover the costs // cost == V + GP * GL + cost := tx.Cost() + if l1Cost := pool.l1CostFn(tx.RollupDataGas(), tx.IsDepositTx()); l1Cost != nil { // add rollup cost + cost = cost.Add(cost, l1Cost) + } balance := pool.currentState.GetBalance(from) - if balance.Cmp(tx.Cost()) < 0 { + if balance.Cmp(cost) < 0 { return core.ErrInsufficientFunds }   // Verify that replacing transactions will not result in overdraft list := pool.pending[from] if list != nil { // Sender already has pending txs - sum := new(big.Int).Add(tx.Cost(), list.totalcost) + sum := new(big.Int).Add(cost, list.totalcost) if repl := list.txs.Get(tx.Nonce()); repl != nil { // Deduct the cost of a transaction replaced by this - sum.Sub(sum, repl.Cost()) + replL1Cost := repl.Cost() + if l1Cost := pool.l1CostFn(tx.RollupDataGas(), tx.IsDepositTx()); l1Cost != nil { // add rollup cost + replL1Cost = replL1Cost.Add(cost, l1Cost) + } + sum.Sub(sum, replL1Cost) } if balance.Cmp(sum) < 0 { log.Trace("Replacing transactions would overdraft", "sender", from, "balance", pool.currentState.GetBalance(from), "required", sum) @@ -887,7 +933,7 @@ // journalTx adds the specified transaction to the local disk journal if it is // deemed to have been sent from a local account. func (pool *TxPool) journalTx(from common.Address, tx *types.Transaction) { // Only journal if it's enabled and the transaction is local - if pool.journal == nil || !pool.locals.contains(from) { + if pool.journal == nil || (!pool.config.JournalRemote && !pool.locals.contains(from)) { return } if err := pool.journal.insert(tx); err != nil { @@ -1370,6 +1416,16 @@ log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash()) return } } + // Do not insert deposit txs back into the pool + // (validateTx would still catch it if not filtered, but no need to re-inject in the first place). + j := 0 + for _, tx := range discarded { + if tx.Type() != types.DepositTxType { + discarded[j] = tx + j++ + } + } + discarded = discarded[:j] reinject = types.TxDifference(discarded, included) } } @@ -1385,7 +1441,16 @@ return } pool.currentState = statedb pool.pendingNonces = newNoncer(statedb) - pool.currentMaxGas.Store(newHead.GasLimit) + if !pool.chainconfig.IsOptimism() { + pool.currentMaxGas.Store(newHead.GasLimit) + } else { + pool.currentMaxGas.Store(newHead.GasLimit - l1InfoGasOverhead) + } + + costFn := types.NewL1CostFunc(pool.chainconfig, statedb) + pool.l1CostFn = func(dataGas types.RollupGasData, isDepositTx bool) *big.Int { + return costFn(newHead.Number.Uint64(), newHead.Time, dataGas, isDepositTx) + }   // Inject any transactions discarded due to reorgs log.Debug("Reinjecting stale transactions", "count", len(reinject)) @@ -1420,8 +1485,16 @@ hash := tx.Hash() pool.all.Remove(hash) } log.Trace("Removed old queued transactions", "count", len(forwards)) + balance := pool.currentState.GetBalance(addr) + if !list.Empty() { + // Reduce the cost-cap by L1 rollup cost of the first tx if necessary. Other txs will get filtered out afterwards. + el := list.txs.FirstElement() + if l1Cost := pool.l1CostFn(el.RollupDataGas(), el.IsDepositTx()); l1Cost != nil { + balance = new(big.Int).Sub(balance, l1Cost) // negative big int is fine + } + } // Drop all transactions that are too costly (low balance or out of gas) - drops, _ := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas.Load()) + drops, _ := list.Filter(balance, pool.currentMaxGas.Load()) for _, tx := range drops { hash := tx.Hash() pool.all.Remove(hash) @@ -1617,8 +1690,16 @@ hash := tx.Hash() pool.all.Remove(hash) log.Trace("Removed old pending transaction", "hash", hash) } + balance := pool.currentState.GetBalance(addr) + if !list.Empty() { + // Reduce the cost-cap by L1 rollup cost of the first tx if necessary. Other txs will get filtered out afterwards. + el := list.txs.FirstElement() + if l1Cost := pool.l1CostFn(el.RollupDataGas(), el.IsDepositTx()); l1Cost != nil { + balance = new(big.Int).Sub(balance, l1Cost) // negative big int is fine + } + } // Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later - drops, invalids := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas.Load()) + drops, invalids := list.Filter(balance, pool.currentMaxGas.Load()) for _, tx := range drops { hash := tx.Hash() log.Trace("Removed unpayable pending transaction", "hash", hash)

Changes to the node configuration and services.

Flag changes: - Transactions can be forwarded to an RPC for sequencing. - Historical calls can be forwarded to a legacy node. - The tx pool propagation can be enabled/disabled. - The Optimism bedrock fork activation can be changed for testing.

diff --git go-ethereum/cmd/utils/flags.go op-geth/cmd/utils/flags.go index b67671e03b9867ea4aeb51df001b8342ff49bb0e..e92f824479bbacf08e590c3819dfa2355a1899b6 100644 --- go-ethereum/cmd/utils/flags.go +++ op-geth/cmd/utils/flags.go @@ -34,6 +34,10 @@ "strconv" "strings" "time"   + pcsclite "github.com/gballet/go-libpcsclite" + gopsutil "github.com/shirou/gopsutil/mem" + "github.com/urfave/cli/v2" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" @@ -74,9 +78,6 @@ "github.com/ethereum/go-ethereum/p2p/netutil" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" - pcsclite "github.com/gballet/go-libpcsclite" - gopsutil "github.com/shirou/gopsutil/mem" - "github.com/urfave/cli/v2" )   // These are all the command line flags we support. @@ -157,6 +158,11 @@ Name: "sepolia", Usage: "Sepolia network: pre-configured proof-of-work test network", Category: flags.EthCategory, } + BetaOPNetworkFlag = &cli.StringFlag{ + Name: "beta.op-network", + Usage: "Beta feature: pick an OP Stack network configuration", + Category: flags.EthCategory, + }   // Dev mode DeveloperFlag = &cli.BoolFlag{ @@ -273,6 +279,21 @@ Name: "override.cancun", Usage: "Manually specify the Cancun fork timestamp, overriding the bundled setting", Category: flags.EthCategory, } + OverrideOptimismBedrock = &flags.BigFlag{ + Name: "override.bedrock", + Usage: "Manually specify OptimsimBedrock, overriding the bundled setting", + Category: flags.EthCategory, + } + OverrideOptimismRegolith = &flags.BigFlag{ + Name: "override.regolith", + Usage: "Manually specify the OptimsimRegolith fork timestamp, overriding the bundled setting", + Category: flags.EthCategory, + } + OverrideOptimism = &cli.BoolFlag{ + Name: "override.optimism", + Usage: "Manually specify optimism", + Category: flags.EthCategory, + } // Light server and client settings LightServeFlag = &cli.IntFlag{ Name: "light.serve", @@ -340,6 +361,11 @@ TxPoolJournalFlag = &cli.StringFlag{ Name: "txpool.journal", Usage: "Disk journal for local transaction to survive node restarts", Value: txpool.DefaultConfig.Journal, + Category: flags.TxPoolCategory, + } + TxPoolJournalRemotesFlag = &cli.BoolFlag{ + Name: "txpool.journalremotes", + Usage: "Includes remote transactions in the journal", Category: flags.TxPoolCategory, } TxPoolRejournalFlag = &cli.DurationFlag{ @@ -831,6 +857,48 @@ Usage: "Gas price below which gpo will ignore transactions", Value: ethconfig.Defaults.GPO.IgnorePrice.Int64(), Category: flags.GasPriceCategory, } + GpoMinSuggestedPriorityFeeFlag = &cli.Int64Flag{ + Name: "gpo.minsuggestedpriorityfee", + Usage: "Minimum transaction priority fee to suggest. Used on OP chains when blocks are not full.", + Value: ethconfig.Defaults.GPO.MinSuggestedPriorityFee.Int64(), + Category: flags.GasPriceCategory, + } + + // Rollup Flags + RollupSequencerHTTPFlag = &cli.StringFlag{ + Name: "rollup.sequencerhttp", + Usage: "HTTP endpoint for the sequencer mempool", + Category: flags.RollupCategory, + } + + RollupHistoricalRPCFlag = &cli.StringFlag{ + Name: "rollup.historicalrpc", + Usage: "RPC endpoint for historical data.", + Category: flags.RollupCategory, + } + + RollupHistoricalRPCTimeoutFlag = &cli.StringFlag{ + Name: "rollup.historicalrpctimeout", + Usage: "Timeout for historical RPC requests.", + Value: "5s", + Category: flags.RollupCategory, + } + + RollupDisableTxPoolGossipFlag = &cli.BoolFlag{ + Name: "rollup.disabletxpoolgossip", + Usage: "Disable transaction pool gossip.", + Category: flags.RollupCategory, + } + RollupEnableTxPoolAdmissionFlag = &cli.BoolFlag{ + Name: "rollup.enabletxpooladmission", + Usage: "Add RPC-submitted transactions to the txpool (on by default if --rollup.sequencerhttp is not set).", + Category: flags.RollupCategory, + } + RollupComputePendingBlock = &cli.BoolFlag{ + Name: "rollup.computependingblock", + Usage: "By default the pending block equals the latest block to save resources and not leak txs from the tx-pool, this flag enables computing of the pending block from the tx-pool instead.", + Category: flags.RollupCategory, + }   // Metrics flags MetricsEnabledFlag = &cli.BoolFlag{ @@ -936,7 +1004,7 @@ GoerliFlag, SepoliaFlag, } // NetworkFlags is the flag group of all built-in supported networks. - NetworkFlags = append([]cli.Flag{MainnetFlag}, TestnetFlags...) + NetworkFlags = append([]cli.Flag{MainnetFlag, BetaOPNetworkFlag}, TestnetFlags...)   // DatabasePathFlags is the flag group of all database path flags. DatabasePathFlags = []cli.Flag{ @@ -966,6 +1034,9 @@ return filepath.Join(path, "goerli") } if ctx.Bool(SepoliaFlag.Name) { return filepath.Join(path, "sepolia") + } + if ctx.IsSet(BetaOPNetworkFlag.Name) { + return filepath.Join(path, ctx.String(BetaOPNetworkFlag.Name)) } return path } @@ -1470,6 +1541,8 @@ case ctx.Bool(GoerliFlag.Name) && cfg.DataDir == node.DefaultDataDir(): cfg.DataDir = filepath.Join(node.DefaultDataDir(), "goerli") case ctx.Bool(SepoliaFlag.Name) && cfg.DataDir == node.DefaultDataDir(): cfg.DataDir = filepath.Join(node.DefaultDataDir(), "sepolia") + case ctx.IsSet(BetaOPNetworkFlag.Name) && cfg.DataDir == node.DefaultDataDir(): + cfg.DataDir = filepath.Join(node.DefaultDataDir(), ctx.String(BetaOPNetworkFlag.Name)) } }   @@ -1491,6 +1564,9 @@ } if ctx.IsSet(GpoIgnoreGasPriceFlag.Name) { cfg.IgnorePrice = big.NewInt(ctx.Int64(GpoIgnoreGasPriceFlag.Name)) } + if ctx.IsSet(GpoMinSuggestedPriorityFeeFlag.Name) { + cfg.MinSuggestedPriorityFee = big.NewInt(ctx.Int64(GpoMinSuggestedPriorityFeeFlag.Name)) + } }   func setTxPool(ctx *cli.Context, cfg *txpool.Config) { @@ -1510,6 +1586,9 @@ } if ctx.IsSet(TxPoolJournalFlag.Name) { cfg.Journal = ctx.String(TxPoolJournalFlag.Name) } + if ctx.IsSet(TxPoolJournalRemotesFlag.Name) { + cfg.JournalRemote = ctx.Bool(TxPoolJournalRemotesFlag.Name) + } if ctx.IsSet(TxPoolRejournalFlag.Name) { cfg.Rejournal = ctx.Duration(TxPoolRejournalFlag.Name) } @@ -1551,6 +1630,9 @@ cfg.Recommit = ctx.Duration(MinerRecommitIntervalFlag.Name) } if ctx.IsSet(MinerNewPayloadTimeout.Name) { cfg.NewPayloadTimeout = ctx.Duration(MinerNewPayloadTimeout.Name) + } + if ctx.IsSet(RollupComputePendingBlock.Name) { + cfg.RollupComputePendingBlock = ctx.Bool(RollupComputePendingBlock.Name) } }   @@ -1626,7 +1708,7 @@ // SetEthConfig applies eth-related command line flags to the config. func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { // Avoid conflicting network flags - CheckExclusive(ctx, MainnetFlag, DeveloperFlag, RinkebyFlag, GoerliFlag, SepoliaFlag) + CheckExclusive(ctx, MainnetFlag, DeveloperFlag, RinkebyFlag, GoerliFlag, SepoliaFlag, BetaOPNetworkFlag) CheckExclusive(ctx, LightServeFlag, SyncModeFlag, "light") CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer if ctx.String(GCModeFlag.Name) == "archive" && ctx.Uint64(TxLookupLimitFlag.Name) != 0 { @@ -1754,6 +1836,18 @@ } else { cfg.EthDiscoveryURLs = SplitAndTrim(urls) } } + // Only configure sequencer http flag if we're running in verifier mode i.e. --mine is disabled. + if ctx.IsSet(RollupSequencerHTTPFlag.Name) && !ctx.IsSet(MiningEnabledFlag.Name) { + cfg.RollupSequencerHTTP = ctx.String(RollupSequencerHTTPFlag.Name) + } + if ctx.IsSet(RollupHistoricalRPCFlag.Name) { + cfg.RollupHistoricalRPC = ctx.String(RollupHistoricalRPCFlag.Name) + } + if ctx.IsSet(RollupHistoricalRPCTimeoutFlag.Name) { + cfg.RollupHistoricalRPCTimeout = ctx.Duration(RollupHistoricalRPCTimeoutFlag.Name) + } + cfg.RollupDisableTxPoolGossip = ctx.Bool(RollupDisableTxPoolGossipFlag.Name) + cfg.RollupDisableTxPoolAdmission = cfg.RollupSequencerHTTP != "" && !ctx.Bool(RollupEnableTxPoolAdmissionFlag.Name) // Override any default configs for hard coded networks. switch { case ctx.Bool(MainnetFlag.Name): @@ -1858,6 +1952,12 @@ } if !ctx.IsSet(MinerGasPriceFlag.Name) { cfg.Miner.GasPrice = big.NewInt(1) } + case ctx.IsSet(BetaOPNetworkFlag.Name): + genesis := MakeGenesis(ctx) + if !ctx.IsSet(NetworkIdFlag.Name) { + cfg.NetworkId = genesis.Config.ChainID.Uint64() + } + cfg.Genesis = genesis default: if cfg.NetworkId == 1 { SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash) @@ -2118,6 +2218,17 @@ case ctx.Bool(RinkebyFlag.Name): genesis = core.DefaultRinkebyGenesisBlock() case ctx.Bool(GoerliFlag.Name): genesis = core.DefaultGoerliGenesisBlock() + case ctx.IsSet(BetaOPNetworkFlag.Name): + name := ctx.String(BetaOPNetworkFlag.Name) + ch, err := params.OPStackChainIDByName(name) + if err != nil { + Fatalf("failed to load OP-Stack chain %q: %v", name, err) + } + genesis, err := core.LoadOPStackGenesis(ch) + if err != nil { + Fatalf("failed to load genesis for OP-Stack chain %q (%d): %v", name, ch, err) + } + return genesis case ctx.Bool(DeveloperFlag.Name): Fatalf("Developer chains are ephemeral") }
diff --git go-ethereum/cmd/geth/main.go op-geth/cmd/geth/main.go index b4a7b5fa95c26c5ce1cb487992f8b8058b6aec6f..1db36b59a79d28f5b15c2a655b7840a20bc813fc 100644 --- go-ethereum/cmd/geth/main.go +++ op-geth/cmd/geth/main.go @@ -66,9 +66,13 @@ utils.USBFlag, utils.SmartCardDaemonPathFlag, utils.OverrideCancun, utils.EnablePersonal, + utils.OverrideOptimismBedrock, + utils.OverrideOptimismRegolith, + utils.OverrideOptimism, utils.TxPoolLocalsFlag, utils.TxPoolNoLocalsFlag, utils.TxPoolJournalFlag, + utils.TxPoolJournalRemotesFlag, utils.TxPoolRejournalFlag, utils.TxPoolPriceLimitFlag, utils.TxPoolPriceBumpFlag, @@ -137,6 +141,12 @@ utils.GpoBlocksFlag, utils.GpoPercentileFlag, utils.GpoMaxGasPriceFlag, utils.GpoIgnoreGasPriceFlag, + utils.GpoMinSuggestedPriorityFeeFlag, + utils.RollupSequencerHTTPFlag, + utils.RollupHistoricalRPCFlag, + utils.RollupHistoricalRPCTimeoutFlag, + utils.RollupDisableTxPoolGossipFlag, + utils.RollupComputePendingBlock, configFileFlag, }, utils.NetworkFlags, utils.DatabasePathFlags)
diff --git go-ethereum/internal/flags/categories.go op-geth/internal/flags/categories.go index c2db6c6c1d25c115c77c8a829d481133a932faa7..f2b368775867174abef2e16d6c64d10b890ca9ff 100644 --- go-ethereum/internal/flags/categories.go +++ op-geth/internal/flags/categories.go @@ -31,6 +31,7 @@ NetworkingCategory = "NETWORKING" MinerCategory = "MINER" GasPriceCategory = "GAS PRICE ORACLE" VMCategory = "VIRTUAL MACHINE" + RollupCategory = "ROLLUP NODE" LoggingCategory = "LOGGING AND DEBUGGING" MetricsCategory = "METRICS AND STATS" MiscCategory = "MISC"
diff --git go-ethereum/cmd/geth/config.go op-geth/cmd/geth/config.go index 42ded493224bba679b25a404dd413c3a88ad57f9..4ac647d1708170da84e451e0c4d7078c97cc80f8 100644 --- go-ethereum/cmd/geth/config.go +++ op-geth/cmd/geth/config.go @@ -170,6 +170,19 @@ if ctx.IsSet(utils.OverrideCancun.Name) { v := ctx.Uint64(utils.OverrideCancun.Name) cfg.Eth.OverrideCancun = &v } + + if ctx.IsSet(utils.OverrideOptimismBedrock.Name) { + cfg.Eth.OverrideOptimismBedrock = flags.GlobalBig(ctx, utils.OverrideOptimismBedrock.Name) + } + if ctx.IsSet(utils.OverrideOptimismRegolith.Name) { + v := ctx.Uint64(utils.OverrideOptimismRegolith.Name) + cfg.Eth.OverrideOptimismRegolith = &v + } + if ctx.IsSet(utils.OverrideOptimism.Name) { + override := ctx.Bool(utils.OverrideOptimism.Name) + cfg.Eth.OverrideOptimism = &override + } + backend, eth := utils.RegisterEthService(stack, &cfg.Eth)   // Configure log filter RPC API.

List the op-geth and upstream go-ethereum versions.

diff --git go-ethereum/cmd/geth/misccmd.go op-geth/cmd/geth/misccmd.go index f3530c30fb69fe08024d9f62a1a377d416c684d9..e7fb108ebfb6a065eb1232ced8fcdf1c49eee4c5 100644 --- go-ethereum/cmd/geth/misccmd.go +++ op-geth/cmd/geth/misccmd.go @@ -80,6 +80,7 @@ } if git.Date != "" { fmt.Println("Git Commit Date:", git.Date) } + fmt.Println("Upstream Version:", params.GethVersionWithMeta) fmt.Println("Architecture:", runtime.GOARCH) fmt.Println("Go Version:", runtime.Version()) fmt.Println("Operating System:", runtime.GOOS)
diff --git go-ethereum/params/version.go op-geth/params/version.go index 33c5399d11edaaed88030cb9ebd92437e2cacefe..ff34aafebf371d1e9a446dbc3320c13e561a81ef 100644 --- go-ethereum/params/version.go +++ op-geth/params/version.go @@ -18,8 +18,11 @@ package params   import ( "fmt" + "regexp" + "strconv" )   +// Version is the version of upstream geth const ( VersionMajor = 1 // Major version component of the current release VersionMinor = 12 // Minor version component of the current release @@ -27,14 +30,56 @@ VersionPatch = 0 // Patch version component of the current release VersionMeta = "stable" // Version metadata to append to the version string )   +// OPVersion is the version of op-geth +var ( + OPVersionMajor = 0 // Major version component of the current release + OPVersionMinor = 1 // Minor version component of the current release + OPVersionPatch = 0 // Patch version component of the current release + OPVersionMeta = "unstable" // Version metadata to append to the version string +) + +// This is set at build-time by the linker when the build is done by build/ci.go. +var gitTag string + +// Override the version variables if the gitTag was set at build time. +var _ = func() (_ string) { + semver := regexp.MustCompile(`^v([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?$`) + version := semver.FindStringSubmatch(gitTag) + if version == nil { + return + } + if version[4] == "" { + version[4] = "stable" + } + OPVersionMajor, _ = strconv.Atoi(version[1]) + OPVersionMinor, _ = strconv.Atoi(version[2]) + OPVersionPatch, _ = strconv.Atoi(version[3]) + OPVersionMeta = version[4] + return +}() + // Version holds the textual version string. var Version = func() string { - return fmt.Sprintf("%d.%d.%d", VersionMajor, VersionMinor, VersionPatch) + return fmt.Sprintf("%d.%d.%d", OPVersionMajor, OPVersionMinor, OPVersionPatch) }()   // VersionWithMeta holds the textual version string including the metadata. var VersionWithMeta = func() string { v := Version + if OPVersionMeta != "" { + v += "-" + OPVersionMeta + } + return v +}() + +// GethVersion holds the textual geth version string. +var GethVersion = func() string { + return fmt.Sprintf("%d.%d.%d", VersionMajor, VersionMinor, VersionPatch) +}() + +// GethVersionWithMeta holds the textual geth version string including the metadata. +var GethVersionWithMeta = func() string { + v := GethVersion if VersionMeta != "" { v += "-" + VersionMeta } @@ -46,8 +91,8 @@ // "1.8.11-dea1ce05" for stable releases, or "1.8.13-unstable-21c059b6" for unstable // releases. func ArchiveVersion(gitCommit string) string { vsn := Version - if VersionMeta != "stable" { - vsn += "-" + VersionMeta + if OPVersionMeta != "stable" { + vsn += "-" + OPVersionMeta } if len(gitCommit) >= 8 { vsn += "-" + gitCommit[:8] @@ -60,7 +105,7 @@ vsn := VersionWithMeta if len(gitCommit) >= 8 { vsn += "-" + gitCommit[:8] } - if (VersionMeta != "stable") && (gitDate != "") { + if (OPVersionMeta != "stable") && (gitDate != "") { vsn += "-" + gitDate } return vsn
diff --git go-ethereum/build/ci.go op-geth/build/ci.go index 7933724d7416ed28f518eed3a9213984ce0acbd1..596192206f6ad07e96fa3f757fe27777ad106b55 100644 --- go-ethereum/build/ci.go +++ op-geth/build/ci.go @@ -251,6 +251,9 @@ if env.Commit != "" { ld = append(ld, "-X", "github.com/ethereum/go-ethereum/internal/version.gitCommit="+env.Commit) ld = append(ld, "-X", "github.com/ethereum/go-ethereum/internal/version.gitDate="+env.Date) } + if env.Tag != "" { + ld = append(ld, "-X", "github.com/ethereum/go-ethereum/params.gitTag="+env.Tag) + } // Strip DWARF on darwin. This used to be required for certain things, // and there is no downside to this, so we just keep doing it. if runtime.GOOS == "darwin" { @@ -507,7 +510,7 @@ switch { case env.Branch == "master": tags = []string{"latest"} case strings.HasPrefix(env.Tag, "v1."): - tags = []string{"stable", fmt.Sprintf("release-1.%d", params.VersionMinor), "v" + params.Version} + tags = []string{"stable", fmt.Sprintf("release-1.%d", params.OPVersionMinor), "v" + params.Version} } // If architecture specific image builds are requested, build and push them if *image {
diff --git go-ethereum/eth/ethconfig/config.go op-geth/eth/ethconfig/config.go index a98d9ee4aaffd25349e678f4e1c0c415ef540604..015442adf16879515d045408d843f9c74ed4b0a1 100644 --- go-ethereum/eth/ethconfig/config.go +++ op-geth/eth/ethconfig/config.go @@ -19,6 +19,7 @@ package ethconfig   import ( "errors" + "math/big" "time"   "github.com/ethereum/go-ethereum/common" @@ -37,12 +38,13 @@ )   // FullNodeGPO contains default gasprice oracle settings for full node. var FullNodeGPO = gasprice.Config{ - Blocks: 20, - Percentile: 60, - MaxHeaderHistory: 1024, - MaxBlockHistory: 1024, - MaxPrice: gasprice.DefaultMaxPrice, - IgnorePrice: gasprice.DefaultIgnorePrice, + Blocks: 20, + Percentile: 60, + MaxHeaderHistory: 1024, + MaxBlockHistory: 1024, + MaxPrice: gasprice.DefaultMaxPrice, + IgnorePrice: gasprice.DefaultIgnorePrice, + MinSuggestedPriorityFee: gasprice.DefaultMinSuggestedPriorityFee, }   // LightClientGPO contains default gasprice oracle settings for light client. @@ -162,6 +164,16 @@ RPCTxFeeCap float64   // OverrideCancun (TODO: remove after the fork) OverrideCancun *uint64 `toml:",omitempty"` + + OverrideOptimismBedrock *big.Int + OverrideOptimismRegolith *uint64 `toml:",omitempty"` + OverrideOptimism *bool + + RollupSequencerHTTP string + RollupHistoricalRPC string + RollupHistoricalRPCTimeout time.Duration + RollupDisableTxPoolGossip bool + RollupDisableTxPoolAdmission bool }   // CreateConsensusEngine creates a consensus engine for the given chain config.
diff --git go-ethereum/eth/handler.go op-geth/eth/handler.go index f0b043166efbfcc3f3e959dc6c958f4628d6fd6d..a951bcc19e6e47b5874c730b8749bb082e90d7f4 100644 --- go-ethereum/eth/handler.go +++ op-geth/eth/handler.go @@ -85,6 +85,7 @@ Sync downloader.SyncMode // Whether to snap or full sync BloomCache uint64 // Megabytes to alloc for snap sync bloom EventMux *event.TypeMux // Legacy event mux, deprecate for `feed` RequiredBlocks map[uint64]common.Hash // Hard coded map of required block hashes for sync challenges + NoTxGossip bool // Disable P2P transaction gossip }   type handler struct { @@ -98,6 +99,8 @@ database ethdb.Database txpool txPool chain *core.BlockChain maxPeers int + + noTxGossip bool   downloader *downloader.Downloader blockFetcher *fetcher.BlockFetcher @@ -132,6 +135,7 @@ forkFilter: forkid.NewFilter(config.Chain), eventMux: config.EventMux, database: config.Database, txpool: config.TxPool, + noTxGossip: config.NoTxGossip, chain: config.Chain, peers: newPeerSet(), merger: config.Merger, @@ -153,8 +157,10 @@ h.snapSync.Store(true) log.Warn("Switch sync mode from full sync to snap sync") } } else { - if h.chain.CurrentBlock().Number.Uint64() > 0 { + blockNumber := h.chain.CurrentBlock().Number + if blockNumber.Uint64() > 0 && (!config.Chain.Config().IsOptimism() || blockNumber.Cmp(config.Chain.Config().BedrockBlock) != 0) { // Print warning log if database is not empty to run snap sync. + // For OP chains, snap sync from bedrock block is allowed. log.Warn("Switch sync mode from snap sync to full sync") } else { // If snap sync was requested and our database is empty, grant it
diff --git go-ethereum/eth/handler_eth.go op-geth/eth/handler_eth.go index 00be022d9e9c1580abf11559a83b7bfcf32c2d5b..72843fd6ff33033f360769b707467d11176e2b8b 100644 --- go-ethereum/eth/handler_eth.go +++ op-geth/eth/handler_eth.go @@ -33,7 +33,20 @@ // packets that are sent as replies or broadcasts. type ethHandler handler   func (h *ethHandler) Chain() *core.BlockChain { return h.chain } -func (h *ethHandler) TxPool() eth.TxPool { return h.txpool } + +// NilPool satisfies the TxPool interface but does not return any tx in the +// pool. It is used to disable transaction gossip. +type NilPool struct{} + +// NilPool Get always returns nil +func (n NilPool) Get(hash common.Hash) *types.Transaction { return nil } + +func (h *ethHandler) TxPool() eth.TxPool { + if h.noTxGossip { + return &NilPool{} + } + return h.txpool +}   // RunPeer is invoked when a peer joins on the `eth` protocol. func (h *ethHandler) RunPeer(peer *eth.Peer, hand eth.Handler) error { @@ -51,6 +64,9 @@ // AcceptTxs retrieves whether transaction processing is enabled on the node // or if inbound transactions should simply be dropped. func (h *ethHandler) AcceptTxs() bool { + if h.noTxGossip { + return false + } return h.acceptTxs.Load() }

Encode the Deposit Tx properties, the L1 costs, and daisy-chain RPC-calls for pre-Bedrock historical data

Pre-Bedrock L1-cost receipt data is loaded from the database if available, and post-Bedrock the L1-cost metadata is hydrated on-the-fly based on the L1 fee information in the corresponding block.

diff --git go-ethereum/core/types/receipt.go op-geth/core/types/receipt.go index 8fc9ec9894e5e6ad444814b06645e5eb23a8bacb..e7c88db3d600916c726dab66e453d7aa4dd60b5d 100644 --- go-ethereum/core/types/receipt.go +++ op-geth/core/types/receipt.go @@ -58,17 +58,28 @@ CumulativeGasUsed uint64 `json:"cumulativeGasUsed" gencodec:"required"` Bloom Bloom `json:"logsBloom" gencodec:"required"` Logs []*Log `json:"logs" gencodec:"required"`   - // Implementation fields: These fields are added by geth when processing a transaction. + // Implementation fields: These fields are added by geth when processing a transaction or retrieving a receipt. + // gencodec annotated fields: these are stored in the chain database. TxHash common.Hash `json:"transactionHash" gencodec:"required"` ContractAddress common.Address `json:"contractAddress"` GasUsed uint64 `json:"gasUsed" gencodec:"required"` EffectiveGasPrice *big.Int `json:"effectiveGasPrice"` // required, but tag omitted for backwards compatibility + + // DepositNonce was introduced in Regolith to store the actual nonce used by deposit transactions + // The state transition process ensures this is only set for Regolith deposit transactions. + DepositNonce *uint64 `json:"depositNonce,omitempty"`   // Inclusion information: These fields provide information about the inclusion of the // transaction corresponding to this receipt. BlockHash common.Hash `json:"blockHash,omitempty"` BlockNumber *big.Int `json:"blockNumber,omitempty"` TransactionIndex uint `json:"transactionIndex"` + + // OVM legacy: extend receipts with their L1 price (if a rollup tx) + L1GasPrice *big.Int `json:"l1GasPrice,omitempty"` + L1GasUsed *big.Int `json:"l1GasUsed,omitempty"` + L1Fee *big.Int `json:"l1Fee,omitempty"` + FeeScalar *big.Float `json:"l1FeeScalar,omitempty"` }   type receiptMarshaling struct { @@ -80,6 +91,12 @@ GasUsed hexutil.Uint64 EffectiveGasPrice *hexutil.Big BlockNumber *hexutil.Big TransactionIndex hexutil.Uint + + // Optimism: extend receipts with their L1 price (if a rollup tx) + L1GasPrice *hexutil.Big + L1GasUsed *hexutil.Big + L1Fee *hexutil.Big + FeeScalar *big.Float }   // receiptRLP is the consensus encoding of a receipt. @@ -90,11 +107,90 @@ Bloom Bloom Logs []*Log }   +type depositReceiptRlp struct { + PostStateOrStatus []byte + CumulativeGasUsed uint64 + Bloom Bloom + Logs []*Log + // DepositNonce was introduced in Regolith to store the actual nonce used by deposit transactions. + // Must be nil for any transactions prior to Regolith or that aren't deposit transactions. + DepositNonce *uint64 `rlp:"optional"` +} + // storedReceiptRLP is the storage encoding of a receipt. type storedReceiptRLP struct { PostStateOrStatus []byte CumulativeGasUsed uint64 Logs []*Log + // DepositNonce was introduced in Regolith to store the actual nonce used by deposit transactions. + // Must be nil for any transactions prior to Regolith or that aren't deposit transactions. + DepositNonce *uint64 `rlp:"optional"` +} + +// LegacyOptimismStoredReceiptRLP is the pre bedrock storage encoding of a +// receipt. It will only exist in the database if it was migrated using the +// migration tool. Nodes that sync using snap-sync will not have any of these +// entries. +type LegacyOptimismStoredReceiptRLP struct { + PostStateOrStatus []byte + CumulativeGasUsed uint64 + Logs []*LogForStorage + L1GasUsed *big.Int + L1GasPrice *big.Int + L1Fee *big.Int + FeeScalar string +} + +// LogForStorage is a wrapper around a Log that handles +// backward compatibility with prior storage formats. +type LogForStorage Log + +// EncodeRLP implements rlp.Encoder. +func (l *LogForStorage) EncodeRLP(w io.Writer) error { + rl := rlpLog{Address: l.Address, Topics: l.Topics, Data: l.Data} + return rlp.Encode(w, &rl) +} + +type legacyRlpStorageLog struct { + Address common.Address + Topics []common.Hash + Data []byte + BlockNumber uint64 + TxHash common.Hash + TxIndex uint + BlockHash common.Hash + Index uint +} + +// DecodeRLP implements rlp.Decoder. +// +// Note some redundant fields(e.g. block number, tx hash etc) will be assembled later. +func (l *LogForStorage) DecodeRLP(s *rlp.Stream) error { + blob, err := s.Raw() + if err != nil { + return err + } + var dec rlpLog + err = rlp.DecodeBytes(blob, &dec) + if err == nil { + *l = LogForStorage{ + Address: dec.Address, + Topics: dec.Topics, + Data: dec.Data, + } + } else { + // Try to decode log with previous definition. + var dec legacyRlpStorageLog + err = rlp.DecodeBytes(blob, &dec) + if err == nil { + *l = LogForStorage{ + Address: dec.Address, + Topics: dec.Topics, + Data: dec.Data, + } + } + } + return err }   // NewReceipt creates a barebone transaction receipt, copying the init fields. @@ -132,7 +228,13 @@ // encodeTyped writes the canonical encoding of a typed receipt to w. func (r *Receipt) encodeTyped(data *receiptRLP, w *bytes.Buffer) error { w.WriteByte(r.Type) - return rlp.Encode(w, data) + switch r.Type { + case DepositTxType: + withNonce := depositReceiptRlp{data.PostStateOrStatus, data.CumulativeGasUsed, data.Bloom, data.Logs, r.DepositNonce} + return rlp.Encode(w, withNonce) + default: + return rlp.Encode(w, data) + } }   // MarshalBinary returns the consensus encoding of the receipt. @@ -202,6 +304,15 @@ return err } r.Type = b[0] return r.setFromRLP(data) + case DepositTxType: + var data depositReceiptRlp + err := rlp.DecodeBytes(b[1:], &data) + if err != nil { + return err + } + r.Type = b[0] + r.DepositNonce = data.DepositNonce + return r.setFromRLP(receiptRLP{data.PostStateOrStatus, data.CumulativeGasUsed, data.Bloom, data.Logs}) default: return ErrTxTypeNotSupported } @@ -265,6 +376,9 @@ return err } } w.ListEnd(logList) + if r.DepositNonce != nil { + w.WriteUint64(*r.DepositNonce) + } w.ListEnd(outerList) return w.Flush() } @@ -272,8 +386,51 @@ // DecodeRLP implements rlp.Decoder, and loads both consensus and implementation // fields of a receipt from an RLP stream. func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error { + // Retrieve the entire receipt blob as we need to try multiple decoders + blob, err := s.Raw() + if err != nil { + return err + } + // First try to decode the latest receipt database format, try the pre-bedrock Optimism legacy format otherwise. + if err := decodeStoredReceiptRLP(r, blob); err == nil { + return nil + } + return decodeLegacyOptimismReceiptRLP(r, blob) +} + +func decodeLegacyOptimismReceiptRLP(r *ReceiptForStorage, blob []byte) error { + var stored LegacyOptimismStoredReceiptRLP + if err := rlp.DecodeBytes(blob, &stored); err != nil { + return err + } + if err := (*Receipt)(r).setStatus(stored.PostStateOrStatus); err != nil { + return err + } + r.CumulativeGasUsed = stored.CumulativeGasUsed + r.Logs = make([]*Log, len(stored.Logs)) + for i, log := range stored.Logs { + r.Logs[i] = (*Log)(log) + } + r.Bloom = CreateBloom(Receipts{(*Receipt)(r)}) + // UsingOVM + scalar := new(big.Float) + if stored.FeeScalar != "" { + var ok bool + scalar, ok = scalar.SetString(stored.FeeScalar) + if !ok { + return errors.New("cannot parse fee scalar") + } + } + r.L1GasUsed = stored.L1GasUsed + r.L1GasPrice = stored.L1GasPrice + r.L1Fee = stored.L1Fee + r.FeeScalar = scalar + return nil +} + +func decodeStoredReceiptRLP(r *ReceiptForStorage, blob []byte) error { var stored storedReceiptRLP - if err := s.Decode(&stored); err != nil { + if err := rlp.DecodeBytes(blob, &stored); err != nil { return err } if err := (*Receipt)(r).setStatus(stored.PostStateOrStatus); err != nil { @@ -282,7 +439,9 @@ } r.CumulativeGasUsed = stored.CumulativeGasUsed r.Logs = stored.Logs r.Bloom = CreateBloom(Receipts{(*Receipt)(r)}) - + if stored.DepositNonce != nil { + r.DepositNonce = stored.DepositNonce + } return nil }   @@ -304,6 +463,9 @@ w.WriteByte(AccessListTxType) rlp.Encode(w, data) case DynamicFeeTxType: w.WriteByte(DynamicFeeTxType) + rlp.Encode(w, data) + case DepositTxType: + w.WriteByte(DepositTxType) rlp.Encode(w, data) default: // For unsupported types, write nothing. Since this is for @@ -314,7 +476,7 @@ }   // DeriveFields fills the receipts with their computed fields based on consensus // data and contextual infos like containing block and transactions. -func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, number uint64, time uint64, baseFee *big.Int, txs []*Transaction) error { +func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, number uint64, time uint64, baseFee *big.Int, txs Transactions) error { signer := MakeSigner(config, new(big.Int).SetUint64(number), time)   logIndex := uint(0) @@ -337,7 +499,11 @@ // The contract address can be derived from the transaction itself if txs[i].To() == nil { // Deriving the signer is expensive, only do if it's actually needed from, _ := Sender(signer, txs[i]) - rs[i].ContractAddress = crypto.CreateAddress(from, txs[i].Nonce()) + nonce := txs[i].Nonce() + if rs[i].DepositNonce != nil { + nonce = *rs[i].DepositNonce + } + rs[i].ContractAddress = crypto.CreateAddress(from, nonce) } else { rs[i].ContractAddress = common.Address{} } @@ -359,5 +525,28 @@ rs[i].Logs[j].Index = logIndex logIndex++ } } + if config.Optimism != nil && len(txs) >= 2 { // need at least an info tx and a non-info tx + if data := txs[0].Data(); len(data) >= 4+32*8 { // function selector + 8 arguments to setL1BlockValues + l1Basefee := new(big.Int).SetBytes(data[4+32*2 : 4+32*3]) // arg index 2 + overhead := new(big.Int).SetBytes(data[4+32*6 : 4+32*7]) // arg index 6 + scalar := new(big.Int).SetBytes(data[4+32*7 : 4+32*8]) // arg index 7 + fscalar := new(big.Float).SetInt(scalar) // legacy: format fee scalar as big Float + fdivisor := new(big.Float).SetUint64(1_000_000) // 10**6, i.e. 6 decimals + feeScalar := new(big.Float).Quo(fscalar, fdivisor) + for i := 0; i < len(rs); i++ { + if !txs[i].IsDepositTx() { + gas := txs[i].RollupDataGas().DataGas(time, config) + rs[i].L1GasPrice = l1Basefee + // GasUsed reported in receipt should include the overhead + rs[i].L1GasUsed = new(big.Int).Add(new(big.Int).SetUint64(gas), overhead) + rs[i].L1Fee = L1Cost(gas, l1Basefee, overhead, scalar) + rs[i].FeeScalar = feeScalar + } + } + } else { + return fmt.Errorf("L1 info tx only has %d bytes, cannot read gas price parameters", len(data)) + } + } + return nil }
diff --git go-ethereum/core/types/gen_receipt_json.go op-geth/core/types/gen_receipt_json.go index d83be1447744a64ae8772b7459e4404b21d75eae..06558f495d3f2f930c4883162737982255aee64f 100644 --- go-ethereum/core/types/gen_receipt_json.go +++ op-geth/core/types/gen_receipt_json.go @@ -29,6 +29,10 @@ EffectiveGasPrice *hexutil.Big `json:"effectiveGasPrice"` BlockHash common.Hash `json:"blockHash,omitempty"` BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` TransactionIndex hexutil.Uint `json:"transactionIndex"` + L1GasPrice *hexutil.Big `json:"l1GasPrice,omitempty"` + L1GasUsed *hexutil.Big `json:"l1GasUsed,omitempty"` + L1Fee *hexutil.Big `json:"l1Fee,omitempty"` + FeeScalar *big.Float `json:"l1FeeScalar,omitempty"` } var enc Receipt enc.Type = hexutil.Uint64(r.Type) @@ -44,6 +48,10 @@ enc.EffectiveGasPrice = (*hexutil.Big)(r.EffectiveGasPrice) enc.BlockHash = r.BlockHash enc.BlockNumber = (*hexutil.Big)(r.BlockNumber) enc.TransactionIndex = hexutil.Uint(r.TransactionIndex) + enc.L1GasPrice = (*hexutil.Big)(r.L1GasPrice) + enc.L1GasUsed = (*hexutil.Big)(r.L1GasUsed) + enc.L1Fee = (*hexutil.Big)(r.L1Fee) + enc.FeeScalar = r.FeeScalar return json.Marshal(&enc) }   @@ -63,6 +71,11 @@ EffectiveGasPrice *hexutil.Big `json:"effectiveGasPrice"` BlockHash *common.Hash `json:"blockHash,omitempty"` BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` TransactionIndex *hexutil.Uint `json:"transactionIndex"` + L1GasPrice *hexutil.Big `json:"l1GasPrice,omitempty"` + L1GasUsed *hexutil.Big `json:"l1GasUsed,omitempty"` + L1Fee *hexutil.Big `json:"l1Fee,omitempty"` + FeeScalar *big.Float `json:"l1FeeScalar,omitempty"` + DepositNonce *hexutil.Uint64 `json:"depositNonce,omitempty"` } var dec Receipt if err := json.Unmarshal(input, &dec); err != nil { @@ -111,6 +124,21 @@ r.BlockNumber = (*big.Int)(dec.BlockNumber) } if dec.TransactionIndex != nil { r.TransactionIndex = uint(*dec.TransactionIndex) + } + if dec.L1GasPrice != nil { + r.L1GasPrice = (*big.Int)(dec.L1GasPrice) + } + if dec.L1GasUsed != nil { + r.L1GasUsed = (*big.Int)(dec.L1GasUsed) + } + if dec.L1Fee != nil { + r.L1Fee = (*big.Int)(dec.L1Fee) + } + if dec.FeeScalar != nil { + r.FeeScalar = dec.FeeScalar + } + if dec.DepositNonce != nil { + r.DepositNonce = (*uint64)(dec.DepositNonce) } return nil }

Forward transactions to the sequencer if configured.

diff --git go-ethereum/eth/api_backend.go op-geth/eth/api_backend.go index 1789f106e9f2be41d58259206fd59112abf007f3..1351c009ffb0fc8d0545a6536a610a006e35452e 100644 --- go-ethereum/eth/api_backend.go +++ op-geth/eth/api_backend.go @@ -19,12 +19,14 @@ import ( "context" "errors" + "fmt" "math/big" "time"   "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" @@ -37,6 +39,7 @@ "github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" @@ -46,6 +49,7 @@ // EthAPIBackend implements ethapi.Backend for full nodes type EthAPIBackend struct { extRPCEnabled bool allowUnprotectedTxs bool + disableTxPool bool eth *Ethereum gpo *gasprice.Oracle } @@ -196,7 +200,11 @@ func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) { // Pending state is only known by the miner if number == rpc.PendingBlockNumber { block, state := b.eth.miner.Pending() - return state, block.Header(), nil + if block != nil { + return state, block.Header(), nil + } else { + number = rpc.LatestBlockNumber + } } // Otherwise resolve the block number and return its state header, err := b.HeaderByNumber(ctx, number) @@ -204,7 +212,7 @@ if err != nil { return nil, nil, err } if header == nil { - return nil, nil, errors.New("header not found") + return nil, nil, fmt.Errorf("header %w", ethereum.NotFound) } stateDb, err := b.eth.BlockChain().StateAt(header.Root) return stateDb, header, err @@ -220,7 +228,7 @@ if err != nil { return nil, nil, err } if header == nil { - return nil, nil, errors.New("header for hash not found") + return nil, nil, fmt.Errorf("header for hash %w", ethereum.NotFound) } if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash { return nil, nil, errors.New("hash is not currently canonical") @@ -255,7 +263,7 @@ var context vm.BlockContext if blockCtx != nil { context = *blockCtx } else { - context = core.NewEVMBlockContext(header, b.eth.BlockChain(), nil) + context = core.NewEVMBlockContext(header, b.eth.BlockChain(), nil, b.eth.blockchain.Config(), state) } return vm.NewEVM(context, txContext, state, b.eth.blockchain.Config(), *vmConfig), state.Error } @@ -284,8 +292,28 @@ func (b *EthAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { return b.eth.BlockChain().SubscribeLogsEvent(ch) }   -func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { - return b.eth.txPool.AddLocal(signedTx) +func (b *EthAPIBackend) SendTx(ctx context.Context, tx *types.Transaction) error { + if b.eth.seqRPCService != nil { + data, err := tx.MarshalBinary() + if err != nil { + return err + } + if err := b.eth.seqRPCService.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(data)); err != nil { + return err + } + if b.disableTxPool { + return nil + } + // Retain tx in local tx pool after forwarding, for local RPC usage. + if err := b.eth.txPool.AddLocal(tx); err != nil { + log.Warn("successfully sent tx to sequencer, but failed to persist in local tx pool", "err", err, "tx", tx.Hash()) + } + return nil + } + if b.disableTxPool { + return nil + } + return b.eth.txPool.AddLocal(tx) }   func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) { @@ -408,3 +436,11 @@ func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) { return b.eth.stateAtTransaction(ctx, block, txIndex, reexec) } + +func (b *EthAPIBackend) HistoricalRPCService() *rpc.Client { + return b.eth.historicalRPCService +} + +func (b *EthAPIBackend) Genesis() *types.Block { + return b.eth.blockchain.Genesis() +}
diff --git go-ethereum/eth/backend.go op-geth/eth/backend.go index 4ba8df951b1b709f548e73ab3cf3d07ce8295cd8..294fb4e12e37746351a67375f76dfeafacfa9918 100644 --- go-ethereum/eth/backend.go +++ op-geth/eth/backend.go @@ -18,11 +18,13 @@ // Package eth implements the Ethereum protocol. package eth   import ( + "context" "errors" "fmt" "math/big" "runtime" "sync" + "time"   "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" @@ -72,6 +74,9 @@ handler *handler ethDialCandidates enode.Iterator snapDialCandidates enode.Iterator merger *consensus.Merger + + seqRPCService *rpc.Client + historicalRPCService *rpc.Client   // DB interfaces chainDb ethdb.Database // Block chain database @@ -164,7 +169,6 @@ var dbVer = "<nil>" if bcVersion != nil { dbVer = fmt.Sprintf("%d", *bcVersion) } - log.Info("Initialising Ethereum protocol", "network", config.NetworkId, "dbversion", dbVer)   if !config.SkipBcVersionCheck { if bcVersion != nil && *bcVersion > core.BlockChainVersion { @@ -197,10 +201,29 @@ var overrides core.ChainOverrides if config.OverrideCancun != nil { overrides.OverrideCancun = config.OverrideCancun } + if config.OverrideOptimismBedrock != nil { + overrides.OverrideOptimismBedrock = config.OverrideOptimismBedrock + } + if config.OverrideOptimismRegolith != nil { + overrides.OverrideOptimismRegolith = config.OverrideOptimismRegolith + } + if config.OverrideOptimism != nil { + overrides.OverrideOptimism = config.OverrideOptimism + } eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, config.Genesis, &overrides, eth.engine, vmConfig, eth.shouldPreserve, &config.TxLookupLimit) if err != nil { return nil, err } + if chainConfig := eth.blockchain.Config(); chainConfig.Optimism != nil { // config.Genesis.Config.ChainID cannot be used because it's based on CLI flags only, thus default to mainnet L1 + config.NetworkId = chainConfig.ChainID.Uint64() // optimism defaults eth network ID to chain ID + eth.networkID = config.NetworkId + } + log.Info("Initialising Ethereum protocol", "network", config.NetworkId, "dbversion", dbVer) + + if eth.blockchain.Config().Optimism != nil { // Optimism Bedrock depends on Merge functionality + eth.merger.FinalizePoS() + } + eth.bloomIndexer.Start(eth.blockchain)   if config.TxPool.Journal != "" { @@ -220,6 +243,7 @@ Sync: config.SyncMode, BloomCache: uint64(cacheLimit), EventMux: eth.eventMux, RequiredBlocks: config.RequiredBlocks, + NoTxGossip: config.RollupDisableTxPoolGossip, }); err != nil { return nil, err } @@ -227,7 +251,7 @@ eth.miner = miner.New(eth, &config.Miner, eth.blockchain.Config(), eth.EventMux(), eth.engine, eth.isLocalBlock) eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))   - eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil} + eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, config.RollupDisableTxPoolAdmission, eth, nil} if eth.APIBackend.allowUnprotectedTxs { log.Info("Unprotected transactions allowed") } @@ -248,6 +272,26 @@ if err != nil { return nil, err }   + if config.RollupSequencerHTTP != "" { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + client, err := rpc.DialContext(ctx, config.RollupSequencerHTTP) + cancel() + if err != nil { + return nil, err + } + eth.seqRPCService = client + } + + if config.RollupHistoricalRPC != "" { + ctx, cancel := context.WithTimeout(context.Background(), config.RollupHistoricalRPCTimeout) + client, err := rpc.DialContext(ctx, config.RollupHistoricalRPC) + cancel() + if err != nil { + return nil, err + } + eth.historicalRPCService = client + } + // Start the RPC service eth.netRPCService = ethapi.NewNetAPI(eth.p2pServer, config.NetworkId)   @@ -266,7 +310,7 @@ func makeExtraData(extra []byte) []byte { if len(extra) == 0 { // create default extradata extra, _ = rlp.EncodeToBytes([]interface{}{ - uint(params.VersionMajor<<16 | params.VersionMinor<<8 | params.VersionPatch), + uint(params.OPVersionMajor<<16 | params.OPVersionMinor<<8 | params.OPVersionPatch), "geth", runtime.Version(), runtime.GOOS, @@ -516,6 +560,12 @@ s.txPool.Stop() s.miner.Close() s.blockchain.Stop() s.engine.Close() + if s.seqRPCService != nil { + s.seqRPCService.Close() + } + if s.historicalRPCService != nil { + s.historicalRPCService.Close() + }   // Clean shutdown marker as the last thing before closing db s.shutdownTracker.Stop()
diff --git go-ethereum/internal/ethapi/backend.go op-geth/internal/ethapi/backend.go index 918b3b630922687fff53c7ae8940ef896b5bc8b3..f4124257e498c1fa58fb587218d27b06cc0ce900 100644 --- go-ethereum/internal/ethapi/backend.go +++ op-geth/internal/ethapi/backend.go @@ -86,6 +86,8 @@ SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription   ChainConfig() *params.ChainConfig Engine() consensus.Engine + HistoricalRPCService() *rpc.Client + Genesis() *types.Block   // This is copied from filters.Backend // eth/filters needs to be initialized from this backend type, so methods needed by
diff --git go-ethereum/eth/state_accessor.go op-geth/eth/state_accessor.go index 1b0ab0c5450976e70c8e3ff3e98dbe24872e3a49..a499cd60234c4379d61c09f4724be845ff1eaad7 100644 --- go-ethereum/eth/state_accessor.go +++ op-geth/eth/state_accessor.go @@ -214,7 +214,7 @@ for idx, tx := range block.Transactions() { // Assemble the transaction call message and return if the requested offset msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee()) txContext := core.NewEVMTxContext(msg) - context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil) + context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil, eth.blockchain.Config(), statedb) if idx == txIndex { return msg, context, statedb, release, nil }

Format deposit and L1-cost data in transaction responses. Add debug_chainConfig API.

diff --git go-ethereum/internal/ethapi/api.go op-geth/internal/ethapi/api.go index 69b5bf1304bbea7466df73b1f392dcfe68da4c22..87f9b8030c8a6ab102dca4099674a323cc50e2d7 100644 --- go-ethereum/internal/ethapi/api.go +++ op-geth/internal/ethapi/api.go @@ -25,7 +25,11 @@ "math/big" "strings" "time"   + "github.com/ethereum/go-ethereum" + "github.com/davecgh/go-spew/spew" + "github.com/tyler-smith/go-bip39" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/keystore" @@ -46,7 +50,6 @@ "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" - "github.com/tyler-smith/go-bip39" )   // EthereumAPI provides an API to access Ethereum related information. @@ -631,6 +634,24 @@ // GetBalance returns the amount of wei for the given address in the state of the // given block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta // block numbers are also allowed. func (s *BlockChainAPI) GetBalance(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Big, error) { + header, err := headerByNumberOrHash(ctx, s.b, blockNrOrHash) + if err != nil { + return nil, err + } + + if s.b.ChainConfig().IsOptimismPreBedrock(header.Number) { + if s.b.HistoricalRPCService() != nil { + var res hexutil.Big + err := s.b.HistoricalRPCService().CallContext(ctx, &res, "eth_getBalance", address, blockNrOrHash) + if err != nil { + return nil, fmt.Errorf("historical backend error: %w", err) + } + return &res, nil + } else { + return nil, rpc.ErrNoHistoricalFallback + } + } + state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if state == nil || err != nil { return nil, err @@ -657,6 +678,22 @@ }   // GetProof returns the Merkle-proof for a given account and optionally some storage keys. func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNrOrHash rpc.BlockNumberOrHash) (*AccountResult, error) { + header, err := headerByNumberOrHash(ctx, s.b, blockNrOrHash) + if err != nil { + return nil, err + } + if s.b.ChainConfig().IsOptimismPreBedrock(header.Number) { + if s.b.HistoricalRPCService() != nil { + var res AccountResult + err := s.b.HistoricalRPCService().CallContext(ctx, &res, "eth_getProof", address, storageKeys, blockNrOrHash) + if err != nil { + return nil, fmt.Errorf("historical backend error: %w", err) + } + return &res, nil + } else { + return nil, rpc.ErrNoHistoricalFallback + } + } state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if state == nil || err != nil { return nil, err @@ -737,7 +774,7 @@ func (s *BlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (map[string]interface{}, error) { header, err := s.b.HeaderByNumber(ctx, number) if header != nil && err == nil { response := s.rpcMarshalHeader(ctx, header) - if number == rpc.PendingBlockNumber { + if number == rpc.PendingBlockNumber && s.b.ChainConfig().Optimism == nil { // don't remove info if optimism // Pending header need to nil out a few fields for _, field := range []string{"hash", "nonce", "miner"} { response[field] = nil @@ -766,7 +803,7 @@ func (s *BlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { block, err := s.b.BlockByNumber(ctx, number) if block != nil && err == nil { response, err := s.rpcMarshalBlock(ctx, block, true, fullTx) - if err == nil && number == rpc.PendingBlockNumber { + if err == nil && number == rpc.PendingBlockNumber && s.b.ChainConfig().Optimism == nil { // don't remove info if optimism // Pending blocks need to nil out a few fields for _, field := range []string{"hash", "nonce", "miner"} { response[field] = nil @@ -837,10 +874,29 @@ }   // GetCode returns the code stored at the given address in the state for the given block number. func (s *BlockChainAPI) GetCode(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) { + header, err := headerByNumberOrHash(ctx, s.b, blockNrOrHash) + if err != nil { + return nil, err + } + + if s.b.ChainConfig().IsOptimismPreBedrock(header.Number) { + if s.b.HistoricalRPCService() != nil { + var res hexutil.Bytes + err := s.b.HistoricalRPCService().CallContext(ctx, &res, "eth_getCode", address, blockNrOrHash) + if err != nil { + return nil, fmt.Errorf("historical backend error: %w", err) + } + return res, nil + } else { + return nil, rpc.ErrNoHistoricalFallback + } + } + state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if state == nil || err != nil { return nil, err } + code := state.GetCode(address) return code, state.Error() } @@ -849,10 +905,29 @@ // GetStorageAt returns the storage from the state at the given address, key and // block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta block // numbers are also allowed. func (s *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Address, hexKey string, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) { + header, err := headerByNumberOrHash(ctx, s.b, blockNrOrHash) + if err != nil { + return nil, err + } + + if s.b.ChainConfig().IsOptimismPreBedrock(header.Number) { + if s.b.HistoricalRPCService() != nil { + var res hexutil.Bytes + err := s.b.HistoricalRPCService().CallContext(ctx, &res, "eth_getStorageAt", address, hexKey, blockNrOrHash) + if err != nil { + return nil, fmt.Errorf("historical backend error: %w", err) + } + return res, nil + } else { + return nil, rpc.ErrNoHistoricalFallback + } + } + state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if state == nil || err != nil { return nil, err } + key, err := decodeHash(hexKey) if err != nil { return nil, fmt.Errorf("unable to decode storage key: %s", err) @@ -861,6 +936,18 @@ res := state.GetState(address, key) return res[:], state.Error() }   +// The HeaderByNumberOrHash method returns a nil error and nil header +// if the header is not found, but only for nonexistent block numbers. This is +// different from StateAndHeaderByNumberOrHash. To account for this discrepancy, +// headerOrNumberByHash will properly convert the error into an ethereum.NotFound. +func headerByNumberOrHash(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) { + header, err := b.HeaderByNumberOrHash(ctx, blockNrOrHash) + if header == nil { + return nil, fmt.Errorf("header %w", ethereum.NotFound) + } + return header, err +} + // OverrideAccount indicates the overriding fields of account during the execution // of a message call. // Note, state and stateDiff can't be specified at the same time. If state is @@ -1015,7 +1102,7 @@ msg, err := args.ToMessage(globalGasCap, header.BaseFee) if err != nil { return nil, err } - blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil) + blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil, b.ChainConfig(), state) if blockOverrides != nil { blockOverrides.Apply(&blockCtx) } @@ -1082,6 +1169,24 @@ // // Note, this function doesn't make and changes in the state/blockchain and is // useful to execute and retrieve values. func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, blockOverrides *BlockOverrides) (hexutil.Bytes, error) { + header, err := headerByNumberOrHash(ctx, s.b, blockNrOrHash) + if err != nil { + return nil, err + } + + if s.b.ChainConfig().IsOptimismPreBedrock(header.Number) { + if s.b.HistoricalRPCService() != nil { + var res hexutil.Bytes + err := s.b.HistoricalRPCService().CallContext(ctx, &res, "eth_call", args, blockNrOrHash, overrides) + if err != nil { + return nil, fmt.Errorf("historical backend error: %w", err) + } + return res, nil + } else { + return nil, rpc.ErrNoHistoricalFallback + } + } + result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, blockOverrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap()) if err != nil { return nil, err @@ -1220,6 +1325,25 @@ bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) if blockNrOrHash != nil { bNrOrHash = *blockNrOrHash } + + header, err := headerByNumberOrHash(ctx, s.b, bNrOrHash) + if err != nil { + return 0, err + } + + if s.b.ChainConfig().IsOptimismPreBedrock(header.Number) { + if s.b.HistoricalRPCService() != nil { + var res hexutil.Uint64 + err := s.b.HistoricalRPCService().CallContext(ctx, &res, "eth_estimateGas", args, blockNrOrHash) + if err != nil { + return 0, fmt.Errorf("historical backend error: %w", err) + } + return res, nil + } else { + return 0, rpc.ErrNoHistoricalFallback + } + } + return DoEstimateGas(ctx, s.b, args, bNrOrHash, s.b.RPCGasCap()) }   @@ -1259,7 +1383,7 @@ // RPCMarshalBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are // returned. When fullTx is true the returned block contains full transaction details, otherwise it will only contain // transaction hashes. -func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool, config *params.ChainConfig) (map[string]interface{}, error) { +func RPCMarshalBlock(ctx context.Context, block *types.Block, inclTx bool, fullTx bool, config *params.ChainConfig, backend Backend) (map[string]interface{}, error) { fields := RPCMarshalHeader(block.Header()) fields["size"] = hexutil.Uint64(block.Size())   @@ -1269,7 +1393,7 @@ return tx.Hash() } if fullTx { formatTx = func(idx int, tx *types.Transaction) interface{} { - return newRPCTransactionFromBlockIndex(block, uint64(idx), config) + return newRPCTransactionFromBlockIndex(ctx, block, uint64(idx), config, backend) } } txs := block.Transactions() @@ -1302,7 +1426,7 @@ // rpcMarshalBlock uses the generalized output filler, then adds the total difficulty field, which requires // a `BlockchainAPI`. func (s *BlockChainAPI) rpcMarshalBlock(ctx context.Context, b *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) { - fields, err := RPCMarshalBlock(b, inclTx, fullTx, s.b.ChainConfig()) + fields, err := RPCMarshalBlock(ctx, b, inclTx, fullTx, s.b.ChainConfig(), s.b) if err != nil { return nil, err } @@ -1333,11 +1457,16 @@ ChainID *hexutil.Big `json:"chainId,omitempty"` V *hexutil.Big `json:"v"` R *hexutil.Big `json:"r"` S *hexutil.Big `json:"s"` + + // deposit-tx only + SourceHash *common.Hash `json:"sourceHash,omitempty"` + Mint *hexutil.Big `json:"mint,omitempty"` + IsSystemTx *bool `json:"isSystemTx,omitempty"` }   // newRPCTransaction returns a transaction that will serialize to the RPC // representation, with the given location metadata set (if available). -func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, blockTime uint64, index uint64, baseFee *big.Int, config *params.ChainConfig) *RPCTransaction { +func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, blockTime uint64, index uint64, baseFee *big.Int, config *params.ChainConfig, receipt *types.Receipt) *RPCTransaction { signer := types.MakeSigner(config, new(big.Int).SetUint64(blockNumber), blockTime) from, _ := types.Sender(signer, tx) v, r, s := tx.RawSignatureValues() @@ -1360,8 +1489,25 @@ result.BlockHash = &blockHash result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) result.TransactionIndex = (*hexutil.Uint64)(&index) } + switch tx.Type() { + case types.DepositTxType: + srcHash := tx.SourceHash() + isSystemTx := tx.IsSystemTx() + result.SourceHash = &srcHash + if isSystemTx { + // Only include IsSystemTx when true + result.IsSystemTx = &isSystemTx + } + result.Mint = (*hexutil.Big)(tx.Mint()) + if receipt != nil && receipt.DepositNonce != nil { + result.Nonce = hexutil.Uint64(*receipt.DepositNonce) + } case types.LegacyTxType: + if v.Sign() == 0 && r.Sign() == 0 && s.Sign() == 0 { // pre-bedrock relayed tx does not have a signature + result.ChainID = (*hexutil.Big)(new(big.Int).Set(config.ChainID)) + break + } // if a legacy transaction has an EIP-155 chain id, include it explicitly if id := tx.ChainId(); id.Sign() != 0 { result.ChainID = (*hexutil.Big)(id) @@ -1400,16 +1546,32 @@ baseFee = misc.CalcBaseFee(config, current) blockNumber = current.Number.Uint64() blockTime = current.Time } - return newRPCTransaction(tx, common.Hash{}, blockNumber, blockTime, 0, baseFee, config) + return newRPCTransaction(tx, common.Hash{}, blockNumber, blockTime, 0, baseFee, config, nil) }   // newRPCTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation. -func newRPCTransactionFromBlockIndex(b *types.Block, index uint64, config *params.ChainConfig) *RPCTransaction { +func newRPCTransactionFromBlockIndex(ctx context.Context, b *types.Block, index uint64, config *params.ChainConfig, backend Backend) *RPCTransaction { txs := b.Transactions() if index >= uint64(len(txs)) { return nil } - return newRPCTransaction(txs[index], b.Hash(), b.NumberU64(), b.Time(), index, b.BaseFee(), config) + tx := txs[index] + rcpt := depositTxReceipt(ctx, b.Hash(), index, backend, tx) + return newRPCTransaction(tx, b.Hash(), b.NumberU64(), b.Time(), index, b.BaseFee(), config, rcpt) +} + +func depositTxReceipt(ctx context.Context, blockHash common.Hash, index uint64, backend Backend, tx *types.Transaction) *types.Receipt { + if tx.Type() != types.DepositTxType { + return nil + } + receipts, err := backend.GetReceipts(ctx, blockHash) + if err != nil { + return nil + } + if index >= uint64(len(receipts)) { + return nil + } + return receipts[index] }   // newRPCRawTransactionFromBlockIndex returns the bytes of a transaction given a block and a transaction index. @@ -1438,6 +1600,21 @@ bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) if blockNrOrHash != nil { bNrOrHash = *blockNrOrHash } + + header, err := headerByNumberOrHash(ctx, s.b, bNrOrHash) + if err == nil && header != nil && s.b.ChainConfig().IsOptimismPreBedrock(header.Number) { + if s.b.HistoricalRPCService() != nil { + var res accessListResult + err := s.b.HistoricalRPCService().CallContext(ctx, &res, "eth_createAccessList", args, blockNrOrHash) + if err != nil { + return nil, fmt.Errorf("historical backend error: %w", err) + } + return &res, nil + } else { + return nil, rpc.ErrNoHistoricalFallback + } + } + acl, gasUsed, vmerr, err := AccessList(ctx, s.b, bNrOrHash, args) if err != nil { return nil, err @@ -1548,7 +1725,7 @@ // GetTransactionByBlockNumberAndIndex returns the transaction for the given block number and index. func (s *TransactionAPI) GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) *RPCTransaction { if block, _ := s.b.BlockByNumber(ctx, blockNr); block != nil { - return newRPCTransactionFromBlockIndex(block, uint64(index), s.b.ChainConfig()) + return newRPCTransactionFromBlockIndex(ctx, block, uint64(index), s.b.ChainConfig(), s.b) } return nil } @@ -1556,7 +1733,7 @@ // GetTransactionByBlockHashAndIndex returns the transaction for the given block hash and index. func (s *TransactionAPI) GetTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) *RPCTransaction { if block, _ := s.b.BlockByHash(ctx, blockHash); block != nil { - return newRPCTransactionFromBlockIndex(block, uint64(index), s.b.ChainConfig()) + return newRPCTransactionFromBlockIndex(ctx, block, uint64(index), s.b.ChainConfig(), s.b) } return nil } @@ -1588,10 +1765,29 @@ } return (*hexutil.Uint64)(&nonce), nil } // Resolve block number and use its state to ask for the nonce + header, err := headerByNumberOrHash(ctx, s.b, blockNrOrHash) + if err != nil { + return nil, err + } + + if s.b.ChainConfig().IsOptimismPreBedrock(header.Number) { + if s.b.HistoricalRPCService() != nil { + var res hexutil.Uint64 + err := s.b.HistoricalRPCService().CallContext(ctx, &res, "eth_getTransactionCount", address, blockNrOrHash) + if err != nil { + return nil, fmt.Errorf("historical backend error: %w", err) + } + return &res, nil + } else { + return nil, rpc.ErrNoHistoricalFallback + } + } + state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if state == nil || err != nil { return nil, err } + nonce := state.GetNonce(address) return (*hexutil.Uint64)(&nonce), state.Error() } @@ -1608,7 +1804,8 @@ header, err := s.b.HeaderByHash(ctx, blockHash) if err != nil { return nil, err } - return newRPCTransaction(tx, blockHash, blockNumber, header.Time, index, header.BaseFee, s.b.ChainConfig()), nil + rcpt := depositTxReceipt(ctx, blockHash, index, s.b, tx) + return newRPCTransaction(tx, blockHash, blockNumber, header.Time, index, header.BaseFee, s.b.ChainConfig(), rcpt), nil } // No finalized transaction, try to retrieve it from the pool if tx := s.b.GetPoolTransaction(hash); tx != nil { @@ -1675,6 +1872,16 @@ "logs": receipt.Logs, "logsBloom": receipt.Bloom, "type": hexutil.Uint(tx.Type()), "effectiveGasPrice": (*hexutil.Big)(receipt.EffectiveGasPrice), + } + + if s.b.ChainConfig().Optimism != nil && !tx.IsDepositTx() { + fields["l1GasPrice"] = (*hexutil.Big)(receipt.L1GasPrice) + fields["l1GasUsed"] = (*hexutil.Big)(receipt.L1GasUsed) + fields["l1Fee"] = (*hexutil.Big)(receipt.L1Fee) + fields["l1FeeScalar"] = receipt.FeeScalar.String() + } + if s.b.ChainConfig().Optimism != nil && tx.IsDepositTx() && receipt.DepositNonce != nil { + fields["depositNonce"] = hexutil.Uint64(*receipt.DepositNonce) }   // Assign receipt status or post state. @@ -2063,6 +2270,10 @@ // SetHead rewinds the head of the blockchain to a previous block. func (api *DebugAPI) SetHead(number hexutil.Uint64) { api.b.SetHead(uint64(number)) +} + +func (api *DebugAPI) ChainConfig() *params.ChainConfig { + return api.b.ChainConfig() }   // NetAPI offers network related RPC methods
diff --git go-ethereum/rpc/errors.go op-geth/rpc/errors.go index 7188332d551ebdcd541438d7a58daad996b5c70f..4a2dab3d9836494a8618d56bfa204e2339a12491 100644 --- go-ethereum/rpc/errors.go +++ op-geth/rpc/errors.go @@ -69,6 +69,16 @@ const ( errMsgTimeout = "request timed out" )   +var ErrNoHistoricalFallback = NoHistoricalFallbackError{} + +type NoHistoricalFallbackError struct{} + +func (e NoHistoricalFallbackError) ErrorCode() int { return -32801 } + +func (e NoHistoricalFallbackError) Error() string { + return "no historical RPC is available for this historical (pre-bedrock) execution request" +} + type methodNotFoundError struct{ method string }   func (e *methodNotFoundError) ErrorCode() int { return -32601 }

Forward pre-bedrock tracing calls to legacy node.

diff --git go-ethereum/eth/tracers/api.go op-geth/eth/tracers/api.go index 5e90180df8d5b603279d52a5a0e0221f12514911..d634ff59fd917bac52b4e5c4e6222a2be4891b39 100644 --- go-ethereum/eth/tracers/api.go +++ op-geth/eth/tracers/api.go @@ -23,6 +23,7 @@ "context" "encoding/json" "errors" "fmt" + "math/big" "os" "runtime" "sync" @@ -68,8 +69,6 @@ // trace states exceed this limit. maximumPendingTraceStates = 128 )   -var errTxNotFound = errors.New("transaction not found") - // StateReleaseFunc is used to deallocate resources held by constructing a // historical state for tracing purposes. type StateReleaseFunc func() @@ -88,6 +87,7 @@ Engine() consensus.Engine ChainDb() ethdb.Database StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, StateReleaseFunc, error) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, StateReleaseFunc, error) + HistoricalRPCService() *rpc.Client }   // API is the collection of tracing APIs exposed over the private debugging endpoint. @@ -208,6 +208,7 @@ // TraceChain returns the structured logs created during the execution of EVM // between two blocks (excluding start) and returns them as a JSON object. func (api *API) TraceChain(ctx context.Context, start, end rpc.BlockNumber, config *TraceConfig) (*rpc.Subscription, error) { // Fetch the block interval that we want to trace + // TODO: Need to implement a fallback for this from, err := api.blockByNumber(ctx, start) if err != nil { return nil, err @@ -266,7 +267,7 @@ // Fetch and execute the block trace taskCh for task := range taskCh { var ( signer = types.MakeSigner(api.backend.ChainConfig(), task.block.Number(), task.block.Time()) - blockCtx = core.NewEVMBlockContext(task.block.Header(), api.chainContext(ctx), nil) + blockCtx = core.NewEVMBlockContext(task.block.Header(), api.chainContext(ctx), nil, api.backend.ChainConfig(), task.statedb) ) // Trace all the transactions contained within for i, tx := range task.block.Transactions() { @@ -436,6 +437,20 @@ block, err := api.blockByNumber(ctx, number) if err != nil { return nil, err } + + if api.backend.ChainConfig().IsOptimismPreBedrock(block.Number()) { + if api.backend.HistoricalRPCService() != nil { + var histResult []*txTraceResult + err = api.backend.HistoricalRPCService().CallContext(ctx, &histResult, "debug_traceBlockByNumber", number, config) + if err != nil { + return nil, fmt.Errorf("historical backend error: %w", err) + } + return histResult, nil + } else { + return nil, rpc.ErrNoHistoricalFallback + } + } + return api.traceBlock(ctx, block, config) }   @@ -446,6 +461,20 @@ block, err := api.blockByHash(ctx, hash) if err != nil { return nil, err } + + if api.backend.ChainConfig().IsOptimismPreBedrock(block.Number()) { + if api.backend.HistoricalRPCService() != nil { + var histResult []*txTraceResult + err = api.backend.HistoricalRPCService().CallContext(ctx, &histResult, "debug_traceBlockByHash", hash, config) + if err != nil { + return nil, fmt.Errorf("historical backend error: %w", err) + } + return histResult, nil + } else { + return nil, rpc.ErrNoHistoricalFallback + } + } + return api.traceBlock(ctx, block, config) }   @@ -495,6 +524,7 @@ // IntermediateRoots executes a block (bad- or canon- or side-), and returns a list // of intermediate roots: the stateroot after each transaction. func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config *TraceConfig) ([]common.Hash, error) { block, _ := api.blockByHash(ctx, hash) + // TODO: Cannot get intermediate roots for pre-bedrock block without daisy chain if block == nil { // Check in the bad blocks block = rawdb.ReadBadBlock(api.backend.ChainDb(), hash) @@ -523,7 +553,7 @@ var ( roots []common.Hash signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time()) chainConfig = api.backend.ChainConfig() - vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) + vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, chainConfig, statedb) deleteEmptyObjects = chainConfig.IsEIP158(block.Number()) ) for i, tx := range block.Transactions() { @@ -599,7 +629,7 @@ var ( txs = block.Transactions() blockHash = block.Hash() is158 = api.backend.ChainConfig().IsEIP158(block.Number()) - blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) + blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, api.backend.ChainConfig(), statedb) signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time()) results = make([]*txTraceResult, len(txs)) ) @@ -632,7 +662,6 @@ // Execute all the transaction contained within the block concurrently var ( txs = block.Transactions() blockHash = block.Hash() - blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time()) results = make([]*txTraceResult, len(txs)) pend sync.WaitGroup @@ -648,6 +677,7 @@ go func() { defer pend.Done() // Fetch and execute the next transaction trace tasks for task := range jobs { + blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, api.backend.ChainConfig(), task.statedb) msg, _ := core.TransactionToMessage(txs[task.index], signer, block.BaseFee()) txctx := &Context{ BlockHash: blockHash, @@ -667,6 +697,7 @@ }   // Feed the transactions into the tracers and return var failed error + blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, api.backend.ChainConfig(), statedb) txloop: for i, tx := range txs { // Send the trace task over for execution @@ -744,7 +775,7 @@ var ( dumps []string signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time()) chainConfig = api.backend.ChainConfig() - vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) + vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, chainConfig, statedb) canon = true ) // Check if there are any overrides: the caller may wish to enable a future @@ -826,14 +857,25 @@ // TraceTransaction returns the structured logs created during the execution of EVM // and returns them as a JSON object. func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) { - tx, blockHash, blockNumber, index, err := api.backend.GetTransaction(ctx, hash) + // GetTransaction returns 0 for the blocknumber if the transaction is not found + _, blockHash, blockNumber, index, err := api.backend.GetTransaction(ctx, hash) if err != nil { return nil, err } - // Only mined txes are supported - if tx == nil { - return nil, errTxNotFound + + if api.backend.ChainConfig().IsOptimismPreBedrock(new(big.Int).SetUint64(blockNumber)) { + if api.backend.HistoricalRPCService() != nil { + var histResult json.RawMessage + err := api.backend.HistoricalRPCService().CallContext(ctx, &histResult, "debug_traceTransaction", hash, config) + if err != nil { + return nil, fmt.Errorf("historical backend error: %w", err) + } + return histResult, nil + } else { + return nil, rpc.ErrNoHistoricalFallback + } } + // It shouldn't happen in practice. if blockNumber == 0 { return nil, errors.New("genesis is not traceable") @@ -888,6 +930,11 @@ } if err != nil { return nil, err } + + if api.backend.ChainConfig().IsOptimismPreBedrock(block.Number()) { + return nil, errors.New("l2geth does not have a debug_traceCall method") + } + // try to recompute the state reexec := defaultTraceReexec if config != nil && config.Reexec != nil { @@ -899,7 +946,7 @@ return nil, err } defer release()   - vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) + vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, api.backend.ChainConfig(), statedb) // Apply the customization rules if required. if config != nil { if err := config.StateOverrides.Apply(statedb); err != nil {

Match the RPC changes in the LES RPC

diff --git go-ethereum/les/state_accessor.go op-geth/les/state_accessor.go index 9a8214ac2f8f044493965fe989c54316a690a883..9383c2cc35019ff747ae8ec24edbac4a39121658 100644 --- go-ethereum/les/state_accessor.go +++ op-geth/les/state_accessor.go @@ -62,7 +62,7 @@ for idx, tx := range block.Transactions() { // Assemble the transaction call message and return if the requested offset msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee()) txContext := core.NewEVMTxContext(msg) - context := core.NewEVMBlockContext(block.Header(), leth.blockchain, nil) + context := core.NewEVMBlockContext(block.Header(), leth.blockchain, nil, leth.blockchain.Config(), statedb) statedb.SetTxContext(tx.Hash(), idx) if idx == txIndex { return msg, context, statedb, release, nil
diff --git go-ethereum/les/odr_test.go op-geth/les/odr_test.go index 2db9acd4335e7d381298d4850e86a79218465dad..2e64094bfb232cc7d0c6e93d2aeb3eafc2b6e89a 100644 --- go-ethereum/les/odr_test.go +++ op-geth/les/odr_test.go @@ -144,7 +144,7 @@ Data: data, SkipAccountChecks: true, }   - context := core.NewEVMBlockContext(header, bc, nil) + context := core.NewEVMBlockContext(header, bc, nil, config, statedb) txContext := core.NewEVMTxContext(msg) vmenv := vm.NewEVM(context, txContext, statedb, config, vm.Config{NoBaseFee: true})   @@ -168,7 +168,7 @@ GasTipCap: new(big.Int), Data: data, SkipAccountChecks: true, } - context := core.NewEVMBlockContext(header, lc, nil) + context := core.NewEVMBlockContext(header, lc, nil, config, state) txContext := core.NewEVMTxContext(msg) vmenv := vm.NewEVM(context, txContext, state, config, vm.Config{NoBaseFee: true}) gp := new(core.GasPool).AddGas(math.MaxUint64)
diff --git go-ethereum/les/client.go op-geth/les/client.go index bd6667429e54b3c2867f5725a1a77223b144971f..69f9cae3aa75e8d364c416979cee90808abd5c2c 100644 --- go-ethereum/les/client.go +++ op-geth/les/client.go @@ -18,6 +18,7 @@ // Package les implements the Light Ethereum Subprotocol. package les   import ( + "context" "errors" "strings" "time" @@ -66,6 +67,9 @@ serverPool *vfc.ServerPool serverPoolIterator enode.Iterator pruner *pruner merger *consensus.Merger + + seqRPCService *rpc.Client + historicalRPCService *rpc.Client   bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports @@ -189,6 +193,27 @@ } leth.ApiBackend.gpo = gasprice.NewOracle(leth.ApiBackend, gpoParams)   leth.handler = newClientHandler(config.UltraLightServers, config.UltraLightFraction, leth) + + if config.RollupSequencerHTTP != "" { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + client, err := rpc.DialContext(ctx, config.RollupSequencerHTTP) + cancel() + if err != nil { + return nil, err + } + leth.seqRPCService = client + } + + if config.RollupHistoricalRPC != "" { + ctx, cancel := context.WithTimeout(context.Background(), config.RollupHistoricalRPCTimeout) + client, err := rpc.DialContext(ctx, config.RollupHistoricalRPC) + cancel() + if err != nil { + return nil, err + } + leth.historicalRPCService = client + } + leth.netRPCService = ethapi.NewNetAPI(leth.p2pServer, leth.config.NetworkId)   // Register the backend on the node
diff --git go-ethereum/les/api_backend.go op-geth/les/api_backend.go index 97665757b19a6793fb34c6aa78eb6c453f97e272..838e08d37627cdaeeb6160bc5ac98ae0131003f2 100644 --- go-ethereum/les/api_backend.go +++ op-geth/les/api_backend.go @@ -189,7 +189,7 @@ if vmConfig == nil { vmConfig = new(vm.Config) } txContext := core.NewEVMTxContext(msg) - context := core.NewEVMBlockContext(header, b.eth.blockchain, nil) + context := core.NewEVMBlockContext(header, b.eth.blockchain, nil, b.eth.chainConfig, state) if blockCtx != nil { context = *blockCtx } @@ -336,3 +336,11 @@ func (b *LesApiBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) { return b.eth.stateAtTransaction(ctx, block, txIndex, reexec) } + +func (b *LesApiBackend) HistoricalRPCService() *rpc.Client { + return b.eth.historicalRPCService +} + +func (b *LesApiBackend) Genesis() *types.Block { + return b.eth.blockchain.Genesis() +}
diff --git go-ethereum/internal/ethapi/transaction_args_test.go op-geth/internal/ethapi/transaction_args_test.go index 0868f8762cb521dd2c5d68d5815ee12f93ed59ab..14663f4c2edbed6c51f2e4571b9a85d86bc95414 100644 --- go-ethereum/internal/ethapi/transaction_args_test.go +++ op-geth/internal/ethapi/transaction_args_test.go @@ -342,4 +342,6 @@ func (b *backendMock) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { return nil }   -func (b *backendMock) Engine() consensus.Engine { return nil } +func (b *backendMock) Engine() consensus.Engine { return nil } +func (b *backendMock) HistoricalRPCService() *rpc.Client { return nil } +func (b *backendMock) Genesis() *types.Block { return nil }
diff --git go-ethereum/ethclient/ethclient_test.go op-geth/ethclient/ethclient_test.go index 385676075655352d334d54e6004e5cd0fabfcaab..66db4f8ebd24fb296fc7b819bcecc3a28a7a8b3e 100644 --- go-ethereum/ethclient/ethclient_test.go +++ op-geth/ethclient/ethclient_test.go @@ -20,10 +20,16 @@ import ( "bytes" "context" "errors" + "fmt" "math/big" + "net" + "net/http" "reflect" "testing" "time" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/internal/ethapi"   "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" @@ -193,6 +199,14 @@ Timestamp: 9000, BaseFee: big.NewInt(params.InitialBaseFee), }   +var genesisForHistorical = &core.Genesis{ + Config: params.OptimismTestConfig, + Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}}, + ExtraData: []byte("test genesis"), + Timestamp: 9000, + BaseFee: big.NewInt(params.InitialBaseFee), +} + var testTx1 = types.MustSignNewTx(testKey, types.LatestSigner(genesis.Config), &types.LegacyTx{ Nonce: 0, Value: big.NewInt(12), @@ -209,9 +223,64 @@ Gas: params.TxGas, To: &common.Address{2}, })   -func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { +type mockHistoricalBackend struct{} + +func (m *mockHistoricalBackend) Call(ctx context.Context, args ethapi.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *ethapi.StateOverride) (hexutil.Bytes, error) { + num, ok := blockNrOrHash.Number() + if ok && num == 1 { + return hexutil.Bytes("test"), nil + } + return nil, ethereum.NotFound +} + +func (m *mockHistoricalBackend) EstimateGas(ctx context.Context, args ethapi.TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash) (hexutil.Uint64, error) { + num, ok := blockNrOrHash.Number() + if ok && num == 1 { + return hexutil.Uint64(12345), nil + } + return 0, ethereum.NotFound +} + +func newMockHistoricalBackend(t *testing.T) string { + s := rpc.NewServer() + err := node.RegisterApis([]rpc.API{ + { + Namespace: "eth", + Service: new(mockHistoricalBackend), + Public: true, + Authenticated: false, + }, + }, nil, s) + if err != nil { + t.Fatalf("error creating mock historical backend: %v", err) + } + + hdlr := node.NewHTTPHandlerStack(s, []string{"*"}, []string{"*"}, nil) + mux := http.NewServeMux() + mux.Handle("/", hdlr) + + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("error creating mock historical backend listener: %v", err) + } + + go func() { + httpS := &http.Server{Handler: mux} + httpS.Serve(listener) + + t.Cleanup(func() { + httpS.Shutdown(context.Background()) + }) + }() + + return fmt.Sprintf("http://%s", listener.Addr().String()) +} + +func newTestBackend(t *testing.T, enableHistoricalState bool) (*node.Node, []*types.Block) { + histAddr := newMockHistoricalBackend(t) + // Generate test chain. - blocks := generateTestChain() + blocks := generateTestChain(enableHistoricalState)   // Create node n, err := node.New(&node.Config{}) @@ -219,7 +288,17 @@ if err != nil { t.Fatalf("can't create new node: %v", err) } // Create Ethereum Service - config := &ethconfig.Config{Genesis: genesis} + var actualGenesis *core.Genesis + if enableHistoricalState { + actualGenesis = genesisForHistorical + } else { + actualGenesis = genesis + } + config := &ethconfig.Config{Genesis: actualGenesis} + if enableHistoricalState { + config.RollupHistoricalRPC = histAddr + config.RollupHistoricalRPCTimeout = time.Second * 5 + } ethservice, err := eth.New(n, config) if err != nil { t.Fatalf("can't create new ethereum service: %v", err) @@ -234,7 +313,7 @@ } return n, blocks }   -func generateTestChain() []*types.Block { +func generateTestChain(enableHistoricalState bool) []*types.Block { generate := func(i int, g *core.BlockGen) { g.OffsetTime(5) g.SetExtra([]byte("test")) @@ -244,12 +323,27 @@ g.AddTx(testTx1) g.AddTx(testTx2) } } - _, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 2, generate) - return append([]*types.Block{genesis.ToBlock()}, blocks...) + var actualGenesis *core.Genesis + if enableHistoricalState { + actualGenesis = genesisForHistorical + } else { + actualGenesis = genesis + } + _, blocks, _ := core.GenerateChainWithGenesis(actualGenesis, ethash.NewFaker(), 2, generate) + return append([]*types.Block{actualGenesis.ToBlock()}, blocks...) +} + +func TestEthClientHistoricalBackend(t *testing.T) { + backend, _ := newTestBackend(t, true) + client, _ := backend.Attach() + defer backend.Close() + defer client.Close() + + testHistoricalRPC(t, client) }   func TestEthClient(t *testing.T) { - backend, chain := newTestBackend(t) + backend, chain := newTestBackend(t, false) client, _ := backend.Attach() defer backend.Close() defer client.Close() @@ -286,6 +380,9 @@ func(t *testing.T) { testAtFunctions(t, client) }, }, "TransactionSender": { func(t *testing.T) { testTransactionSender(t, client) }, + }, + "EstimateGas": { + func(t *testing.T) { testEstimateGas(t, client) }, }, }   @@ -682,6 +779,54 @@ t.Fatal(err) } if sender2 != testAddr { t.Fatal("wrong sender:", sender2) + } +} + +func testEstimateGas(t *testing.T, client *rpc.Client) { + ec := NewClient(client) + + // EstimateGas + msg := ethereum.CallMsg{ + From: testAddr, + To: &common.Address{}, + Gas: 21000, + Value: big.NewInt(1), + } + gas, err := ec.EstimateGas(context.Background(), msg) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if gas != 21000 { + t.Fatalf("unexpected gas price: %v", gas) + } +} + +func testHistoricalRPC(t *testing.T, client *rpc.Client) { + ec := NewClient(client) + + // Estimate Gas RPC + msg := ethereum.CallMsg{ + From: testAddr, + To: &common.Address{}, + Gas: 21000, + Value: big.NewInt(1), + } + var res hexutil.Uint64 + err := client.CallContext(context.Background(), &res, "eth_estimateGas", toCallArg(msg), rpc.BlockNumberOrHashWithNumber(1)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if res != 12345 { + t.Fatalf("invalid result: %d", res) + } + + // Call Contract RPC + histVal, err := ec.CallContract(context.Background(), msg, big.NewInt(1)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if string(histVal) != "test" { + t.Fatalf("expected %s to equal test", string(histVal)) } }
diff --git go-ethereum/eth/tracers/api_test.go op-geth/eth/tracers/api_test.go index fdb02b18950e99555328c69692178cdc21093212..04fdaab7fa0f1b37c9a08bcd72457ff4b33fca96 100644 --- go-ethereum/eth/tracers/api_test.go +++ op-geth/eth/tracers/api_test.go @@ -24,12 +24,15 @@ "encoding/json" "errors" "fmt" "math/big" + "net" + "net/http" "reflect" "sort" "sync/atomic" "testing" "time"   + "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus" @@ -43,15 +46,78 @@ "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" + "github.com/stretchr/testify/mock" )   var ( - errStateNotFound = errors.New("state not found") - errBlockNotFound = errors.New("block not found") + errStateNotFound = errors.New("state not found") + errBlockNotFound = errors.New("block not found") + errTransactionNotFound = errors.New("transaction not found") )   +type mockHistoricalBackend struct { + mock.Mock +} + +// mockHistoricalBackend does not have a TraceCall, because pre-bedrock there is no debug_traceCall available + +func (m *mockHistoricalBackend) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *TraceConfig) ([]*txTraceResult, error) { + ret := m.Mock.MethodCalled("TraceBlockByNumber", number, config) + return ret[0].([]*txTraceResult), *ret[1].(*error) +} + +func (m *mockHistoricalBackend) ExpectTraceBlockByNumber(number rpc.BlockNumber, config *TraceConfig, out []*txTraceResult, err error) { + m.Mock.On("TraceBlockByNumber", number, config).Once().Return(out, &err) +} + +func (m *mockHistoricalBackend) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) { + ret := m.Mock.MethodCalled("TraceTransaction", hash, config) + return ret[0], *ret[1].(*error) +} + +func (m *mockHistoricalBackend) ExpectTraceTransaction(hash common.Hash, config *TraceConfig, out interface{}, err error) { + jsonOut, _ := json.Marshal(out) + m.Mock.On("TraceTransaction", hash, config).Once().Return(json.RawMessage(jsonOut), &err) +} + +func newMockHistoricalBackend(t *testing.T, backend *mockHistoricalBackend) string { + s := rpc.NewServer() + err := node.RegisterApis([]rpc.API{ + { + Namespace: "debug", + Service: backend, + Public: true, + Authenticated: false, + }, + }, nil, s) + if err != nil { + t.Fatalf("error creating mock historical backend: %v", err) + } + + hdlr := node.NewHTTPHandlerStack(s, []string{"*"}, []string{"*"}, nil) + mux := http.NewServeMux() + mux.Handle("/", hdlr) + + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("error creating mock historical backend listener: %v", err) + } + + go func() { + httpS := &http.Server{Handler: mux} + httpS.Serve(listener) + + t.Cleanup(func() { + httpS.Shutdown(context.Background()) + }) + }() + + return fmt.Sprintf("http://%s", listener.Addr().String()) +} + type testBackend struct { chainConfig *params.ChainConfig engine consensus.Engine @@ -60,15 +126,28 @@ chain *core.BlockChain   refHook func() // Hook is invoked when the requested state is referenced relHook func() // Hook is invoked when the requested state is released + + historical *rpc.Client + mockHistorical *mockHistoricalBackend }   // testBackend creates a new test backend. OBS: After test is done, teardown must be // invoked in order to release associated resources. func newTestBackend(t *testing.T, n int, gspec *core.Genesis, generator func(i int, b *core.BlockGen)) *testBackend { + mock := new(mockHistoricalBackend) + historicalAddr := newMockHistoricalBackend(t, mock) + + historicalClient, err := rpc.Dial(historicalAddr) + if err != nil { + t.Fatalf("error making historical client: %v", err) + } + backend := &testBackend{ - chainConfig: gspec.Config, - engine: ethash.NewFaker(), - chaindb: rawdb.NewMemoryDatabase(), + chainConfig: gspec.Config, + engine: ethash.NewFaker(), + chaindb: rawdb.NewMemoryDatabase(), + historical: historicalClient, + mockHistorical: mock, } // Generate blocks for testing _, blocks, _ := core.GenerateChainWithGenesis(gspec, backend.engine, n, generator) @@ -116,6 +195,9 @@ }   func (b *testBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) { tx, hash, blockNumber, index := rawdb.ReadTransaction(b.chaindb, txHash) + if tx == nil { + return nil, common.Hash{}, 0, 0, errTransactionNotFound + } return tx, hash, blockNumber, index, nil }   @@ -173,7 +255,7 @@ signer := types.MakeSigner(b.chainConfig, block.Number(), block.Time()) for idx, tx := range block.Transactions() { msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee()) txContext := core.NewEVMTxContext(msg) - context := core.NewEVMBlockContext(block.Header(), b.chain, nil) + context := core.NewEVMBlockContext(block.Header(), b.chain, nil, b.chainConfig, statedb) if idx == txIndex { return msg, context, statedb, release, nil } @@ -184,6 +266,10 @@ } statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) } return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) +} + +func (b *testBackend) HistoricalRPCService() *rpc.Client { + return b.historical }   func TestTraceCall(t *testing.T) { @@ -361,11 +447,58 @@ StructLogs: []logger.StructLogRes{}, }) { t.Error("Transaction tracing result is different") } +} +func TestTraceTransactionHistorical(t *testing.T) { + t.Parallel()   - // Test non-existent transaction - _, err = api.TraceTransaction(context.Background(), common.Hash{42}, nil) - if !errors.Is(err, errTxNotFound) { - t.Fatalf("want %v, have %v", errTxNotFound, err) + // Initialize test accounts + accounts := newAccounts(2) + genesis := &core.Genesis{ + Config: params.OptimismTestConfig, + Alloc: core.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + accounts[1].addr: {Balance: big.NewInt(params.Ether)}, + }, + } + target := common.Hash{} + signer := types.HomesteadSigner{} + backend := newTestBackend(t, 1, genesis, func(i int, b *core.BlockGen) { + // Transfer from account[0] to account[1] + // value: 1000 wei + // fee: 0 wei + tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[0].key) + b.AddTx(tx) + target = tx.Hash() + }) + defer backend.mockHistorical.AssertExpectations(t) + defer backend.chain.Stop() + backend.mockHistorical.ExpectTraceTransaction( + target, + nil, + logger.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []logger.StructLogRes{}, + }, + nil) + api := NewAPI(backend) + result, err := api.TraceTransaction(context.Background(), target, nil) + if err != nil { + t.Errorf("Failed to trace transaction %v", err) + } + var have *logger.ExecutionResult + spew.Dump(result) + if err := json.Unmarshal(result.(json.RawMessage), &have); err != nil { + t.Errorf("failed to unmarshal result %v", err) + } + if !reflect.DeepEqual(have, &logger.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []logger.StructLogRes{}, + }) { + t.Error("Transaction tracing result is different") } }   @@ -449,6 +582,59 @@ want := tc.want if string(have) != want { t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, string(have), want) } + } +} + +func TestTraceBlockHistorical(t *testing.T) { + t.Parallel() + + // Initialize test accounts + accounts := newAccounts(3) + genesis := &core.Genesis{ + Config: params.OptimismTestConfig, + Alloc: core.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + accounts[1].addr: {Balance: big.NewInt(params.Ether)}, + accounts[2].addr: {Balance: big.NewInt(params.Ether)}, + }, + } + genBlocks := 10 + signer := types.HomesteadSigner{} + backend := newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { + // Transfer from account[0] to account[1] + // value: 1000 wei + // fee: 0 wei + tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[0].key) + b.AddTx(tx) + }) + defer backend.mockHistorical.AssertExpectations(t) + defer backend.chain.Stop() + api := NewAPI(backend) + + var expectErr error + var config *TraceConfig + blockNumber := rpc.BlockNumber(3) + want := `[{"txHash":"0x0000000000000000000000000000000000000000000000000000000000000000","result":{"failed":false,"gas":21000,"returnValue":"","structLogs":[]}}]` + var ret []*txTraceResult + _ = json.Unmarshal([]byte(want), &ret) + + backend.mockHistorical.ExpectTraceBlockByNumber(blockNumber, config, ret, nil) + + result, err := api.TraceBlockByNumber(context.Background(), blockNumber, config) + if expectErr != nil { + if err == nil { + t.Errorf("want error %v", expectErr) + } + if !reflect.DeepEqual(err, expectErr) { + t.Errorf("error mismatch, want %v, get %v", expectErr, err) + } + } + if err != nil { + t.Errorf("want no error, have %v", err) + } + have, _ := json.Marshal(result) + if string(have) != want { + t.Errorf("result mismatch, have\n%v\n, want\n%v\n", string(have), want) } }

Extend the tools available in geth to improve external testing and tooling.

diff --git go-ethereum/accounts/abi/bind/backends/simulated.go op-geth/accounts/abi/bind/backends/simulated.go index 0af520b5d927915c190b7044a898c6b1df8c0a1d..bd59ca783b955a8a95489e30cb4d0f06c8f11a59 100644 --- go-ethereum/accounts/abi/bind/backends/simulated.go +++ op-geth/accounts/abi/bind/backends/simulated.go @@ -30,6 +30,7 @@ "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" @@ -63,6 +64,8 @@ type SimulatedBackend struct { database ethdb.Database // In memory database to store our testing data blockchain *core.BlockChain // Ethereum blockchain to handle the consensus   + consensus consensus.Engine + mu sync.Mutex pendingBlock *types.Block // Currently pending block that will be imported on request pendingState *state.StateDB // Currently pending state that will be the active on request @@ -78,20 +81,93 @@ // NewSimulatedBackendWithDatabase creates a new binding backend based on the given database // and uses a simulated blockchain for testing purposes. // A simulated backend always uses chainID 1337. func NewSimulatedBackendWithDatabase(database ethdb.Database, alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend { - genesis := core.Genesis{ - Config: params.AllEthashProtocolChanges, - GasLimit: gasLimit, - Alloc: alloc, + return NewSimulatedBackendWithOpts(WithDatabase(database), WithAlloc(alloc), WithGasLimit(gasLimit)) +} + +// NewSimulatedBackend creates a new binding backend using a simulated blockchain +// for testing purposes. +// A simulated backend always uses chainID 1337. +func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend { + return NewSimulatedBackendWithOpts(WithGasLimit(gasLimit), WithAlloc(alloc)) +} + +type simulatedBackendConfig struct { + genesis core.Genesis + cacheConfig *core.CacheConfig + database ethdb.Database + vmConfig vm.Config + consensus consensus.Engine +} + +type SimulatedBackendOpt func(s *simulatedBackendConfig) + +func WithDatabase(database ethdb.Database) SimulatedBackendOpt { + return func(s *simulatedBackendConfig) { + s.database = database + } +} + +func WithGasLimit(gasLimit uint64) SimulatedBackendOpt { + return func(s *simulatedBackendConfig) { + s.genesis.GasLimit = gasLimit + } +} + +func WithAlloc(alloc core.GenesisAlloc) SimulatedBackendOpt { + return func(s *simulatedBackendConfig) { + s.genesis.Alloc = alloc + } +} + +func WithCacheConfig(cacheConfig *core.CacheConfig) SimulatedBackendOpt { + return func(s *simulatedBackendConfig) { + s.cacheConfig = cacheConfig + } +} + +func WithGenesis(genesis core.Genesis) SimulatedBackendOpt { + return func(s *simulatedBackendConfig) { + s.genesis = genesis + } +} + +func WithVMConfig(vmConfig vm.Config) SimulatedBackendOpt { + return func(s *simulatedBackendConfig) { + s.vmConfig = vmConfig + } +} + +func WithConsensus(consensus consensus.Engine) SimulatedBackendOpt { + return func(s *simulatedBackendConfig) { + s.consensus = consensus + } +} + +// NewSimulatedBackendWithOpts creates a new binding backend based on the given database +// and uses a simulated blockchain for testing purposes. It exposes additional configuration +// options that are useful to +func NewSimulatedBackendWithOpts(opts ...SimulatedBackendOpt) *SimulatedBackend { + config := &simulatedBackendConfig{ + genesis: core.Genesis{Config: params.AllEthashProtocolChanges, GasLimit: 100000000, Alloc: make(core.GenesisAlloc)}, + database: rawdb.NewMemoryDatabase(), + consensus: ethash.NewFaker(), + } + + for _, opt := range opts { + opt(config) } - blockchain, _ := core.NewBlockChain(database, nil, &genesis, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + + config.genesis.MustCommit(config.database) + blockchain, _ := core.NewBlockChain(config.database, config.cacheConfig, &config.genesis, nil, config.consensus, config.vmConfig, nil, nil)   backend := &SimulatedBackend{ - database: database, + database: config.database, blockchain: blockchain, - config: genesis.Config, + config: config.genesis.Config, + consensus: config.consensus, }   - filterBackend := &filterBackend{database, blockchain, backend} + filterBackend := &filterBackend{config.database, blockchain, backend} backend.filterSystem = filters.NewFilterSystem(filterBackend, filters.Config{}) backend.events = filters.NewEventSystem(backend.filterSystem, false)   @@ -102,13 +178,6 @@ backend.rollback(block) return backend }   -// NewSimulatedBackend creates a new binding backend using a simulated blockchain -// for testing purposes. -// A simulated backend always uses chainID 1337. -func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend { - return NewSimulatedBackendWithDatabase(rawdb.NewMemoryDatabase(), alloc, gasLimit) -} - // Close terminates the underlying blockchain's update loop. func (b *SimulatedBackend) Close() error { b.blockchain.Stop() @@ -124,6 +193,8 @@ if _, err := b.blockchain.InsertChain([]*types.Block{b.pendingBlock}); err != nil { panic(err) // This cannot happen unless the simulator is wrong, fail in that case } + // Don't wait for the async tx indexing + rawdb.WriteTxLookupEntriesByBlock(b.database, b.pendingBlock) blockHash := b.pendingBlock.Hash()   // Using the last inserted block here makes it possible to build on a side @@ -145,7 +216,7 @@ b.rollback(block) }   func (b *SimulatedBackend) rollback(parent *types.Block) { - blocks, _ := core.GenerateChain(b.config, parent, ethash.NewFaker(), b.database, 1, func(int, *core.BlockGen) {}) + blocks, _ := core.GenerateChain(b.config, parent, b.consensus, b.database, 1, func(int, *core.BlockGen) {})   b.pendingBlock = blocks[0] b.pendingState, _ = state.New(b.pendingBlock.Root(), b.blockchain.StateCache(), nil) @@ -666,7 +737,7 @@ // Create a new environment which holds all relevant information // about the transaction and calling mechanisms. txContext := core.NewEVMTxContext(msg) - evmContext := core.NewEVMBlockContext(header, b.blockchain, nil) + evmContext := core.NewEVMBlockContext(header, b.blockchain, nil, b.config, stateDB) vmEnv := vm.NewEVM(evmContext, txContext, stateDB, b.config, vm.Config{NoBaseFee: true}) gasPool := new(core.GasPool).AddGas(math.MaxUint64)   @@ -694,7 +765,7 @@ if tx.Nonce() != nonce { return fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce) } // Include tx in chain - blocks, receipts := core.GenerateChain(b.config, block, ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) { + blocks, receipts := core.GenerateChain(b.config, block, b.consensus, b.database, 1, func(number int, block *core.BlockGen) { for _, tx := range b.pendingBlock.Transactions() { block.AddTxWithChain(b.blockchain, tx) } @@ -818,7 +889,7 @@ if block == nil { return fmt.Errorf("could not find parent") }   - blocks, _ := core.GenerateChain(b.config, block, ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) { + blocks, _ := core.GenerateChain(b.config, block, b.consensus, b.database, 1, func(number int, block *core.BlockGen) { block.OffsetTime(int64(adjustment.Seconds())) }) stateDB, _ := b.blockchain.State()

Most of the op-geth changes are tested in the Optimism Monorepo and not part of the geth diff, but some testing like the Deposit TX encoding and API interactions are embedded in the op-geth diff instead.

diff --git go-ethereum/core/types/transaction_marshalling_test.go op-geth/core/types/transaction_marshalling_test.go new file mode 100644 index 0000000000000000000000000000000000000000..4ab93a539d19bbcffa0293738b2e4dcc3ebe7ad2 --- /dev/null +++ op-geth/core/types/transaction_marshalling_test.go @@ -0,0 +1,103 @@ +package types + +import ( + "encoding/json" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestTransactionUnmarshalJsonDeposit(t *testing.T) { + tx := NewTx(&DepositTx{ + SourceHash: common.HexToHash("0x1234"), + IsSystemTransaction: true, + Mint: big.NewInt(34), + }) + json, err := tx.MarshalJSON() + require.NoError(t, err, "Failed to marshal tx JSON") + + got := &Transaction{} + err = got.UnmarshalJSON(json) + require.NoError(t, err, "Failed to unmarshal tx JSON") + require.Equal(t, tx.Hash(), got.Hash()) +} + +func TestTransactionUnmarshalJSON(t *testing.T) { + tests := []struct { + name string + json string + expectedError string + }{ + { + name: "No gas", + json: `{"type":"0x7e","nonce":null,"gasPrice":null,"maxPriorityFeePerGas":null,"maxFeePerGas":null,"value":"0x1","input":"0x616263646566","v":null,"r":null,"s":null,"to":null,"sourceHash":"0x0000000000000000000000000000000000000000000000000000000000000000","from":"0x0000000000000000000000000000000000000001","isSystemTx":false,"hash":"0xa4341f3db4363b7ca269a8538bd027b2f8784f84454ca917668642d5f6dffdf9"}`, + expectedError: "missing required field 'gas'", + }, + { + name: "No value", + json: `{"type":"0x7e","nonce":null,"gas": "0x1234", "gasPrice":null,"maxPriorityFeePerGas":null,"maxFeePerGas":null,"input":"0x616263646566","v":null,"r":null,"s":null,"to":null,"sourceHash":"0x0000000000000000000000000000000000000000000000000000000000000000","from":"0x0000000000000000000000000000000000000001","isSystemTx":false,"hash":"0xa4341f3db4363b7ca269a8538bd027b2f8784f84454ca917668642d5f6dffdf9"}`, + expectedError: "missing required field 'value'", + }, + { + name: "No input", + json: `{"type":"0x7e","nonce":null,"gas": "0x1234", "gasPrice":null,"maxPriorityFeePerGas":null,"maxFeePerGas":null,"value":"0x1","v":null,"r":null,"s":null,"to":null,"sourceHash":"0x0000000000000000000000000000000000000000000000000000000000000000","from":"0x0000000000000000000000000000000000000001","isSystemTx":false,"hash":"0xa4341f3db4363b7ca269a8538bd027b2f8784f84454ca917668642d5f6dffdf9"}`, + expectedError: "missing required field 'input'", + }, + { + name: "No from", + json: `{"type":"0x7e","nonce":null,"gas": "0x1234", "gasPrice":null,"maxPriorityFeePerGas":null,"maxFeePerGas":null,"value":"0x1","input":"0x616263646566","v":null,"r":null,"s":null,"to":null,"sourceHash":"0x0000000000000000000000000000000000000000000000000000000000000000","isSystemTx":false,"hash":"0xa4341f3db4363b7ca269a8538bd027b2f8784f84454ca917668642d5f6dffdf9"}`, + expectedError: "missing required field 'from'", + }, + { + name: "No sourceHash", + json: `{"type":"0x7e","nonce":null,"gas": "0x1234", "gasPrice":null,"maxPriorityFeePerGas":null,"maxFeePerGas":null,"value":"0x1","input":"0x616263646566","v":null,"r":null,"s":null,"to":null,"from":"0x0000000000000000000000000000000000000001","isSystemTx":false,"hash":"0xa4341f3db4363b7ca269a8538bd027b2f8784f84454ca917668642d5f6dffdf9"}`, + expectedError: "missing required field 'sourceHash'", + }, + { + name: "No mint", + json: `{"type":"0x7e","nonce":null,"gas": "0x1234", "gasPrice":null,"maxPriorityFeePerGas":null,"maxFeePerGas":null,"value":"0x1","input":"0x616263646566","v":null,"r":null,"s":null,"to":null,"sourceHash":"0x0000000000000000000000000000000000000000000000000000000000000000","from":"0x0000000000000000000000000000000000000001","isSystemTx":false,"hash":"0xa4341f3db4363b7ca269a8538bd027b2f8784f84454ca917668642d5f6dffdf9"}`, + // Allowed + }, + { + name: "No IsSystemTx", + json: `{"type":"0x7e","nonce":null,"gas": "0x1234", "gasPrice":null,"maxPriorityFeePerGas":null,"maxFeePerGas":null,"value":"0x1","input":"0x616263646566","v":null,"r":null,"s":null,"to":null,"sourceHash":"0x0000000000000000000000000000000000000000000000000000000000000000","from":"0x0000000000000000000000000000000000000001","hash":"0xa4341f3db4363b7ca269a8538bd027b2f8784f84454ca917668642d5f6dffdf9"}`, + // Allowed + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var parsedTx = &Transaction{} + err := json.Unmarshal([]byte(test.json), &parsedTx) + if test.expectedError == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, test.expectedError) + } + }) + } + + tests = []struct { + name string + json string + expectedError string + }{ + { + name: "Valid deposit sender", + json: `{"type":"0x7e","nonce":"0x1","gas": "0x1234", "gasPrice":null,"maxPriorityFeePerGas":null,"maxFeePerGas":null,"value":"0x1","input":"0x616263646566","v":null,"r":null,"s":null,"to":null,"sourceHash":"0x0000000000000000000000000000000000000000000000000000000000000000","from":"0x0000000000000000000000000000000000000001","hash":"0xa4341f3db4363b7ca269a8538bd027b2f8784f84454ca917668642d5f6dffdf9"}`, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var parsedTx = &Transaction{} + err := json.Unmarshal([]byte(test.json), &parsedTx) + require.NoError(t, err) + + signer := NewLondonSigner(big.NewInt(123)) + sender, err := signer.Sender(parsedTx) + require.NoError(t, err) + require.Equal(t, common.HexToAddress("0x1"), sender) + }) + } +}
diff --git go-ethereum/internal/ethapi/api_test.go op-geth/internal/ethapi/api_test.go index 61024900b20fd5368e6740df5e8dff8938f87167..9970e2a1ac02b4e9caca6e622fdf5e7c85beab33 100644 --- go-ethereum/internal/ethapi/api_test.go +++ op-geth/internal/ethapi/api_test.go @@ -46,9 +46,125 @@ "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" + "github.com/stretchr/testify/require" "golang.org/x/crypto/sha3" )   +func TestNewRPCTransactionDepositTx(t *testing.T) { + tx := types.NewTx(&types.DepositTx{ + SourceHash: common.HexToHash("0x1234"), + IsSystemTransaction: true, + Mint: big.NewInt(34), + }) + nonce := uint64(7) + receipt := &types.Receipt{ + DepositNonce: &nonce, + } + got := newRPCTransaction(tx, common.Hash{}, uint64(12), uint64(1234), uint64(1), big.NewInt(0), &params.ChainConfig{}, receipt) + // Should provide zero values for unused fields that are required in other transactions + require.Equal(t, got.GasPrice, (*hexutil.Big)(big.NewInt(0)), "newRPCTransaction().GasPrice = %v, want 0x0", got.GasPrice) + require.Equal(t, got.V, (*hexutil.Big)(big.NewInt(0)), "newRPCTransaction().V = %v, want 0x0", got.V) + require.Equal(t, got.R, (*hexutil.Big)(big.NewInt(0)), "newRPCTransaction().R = %v, want 0x0", got.R) + require.Equal(t, got.S, (*hexutil.Big)(big.NewInt(0)), "newRPCTransaction().S = %v, want 0x0", got.S) + + // Should include deposit tx specific fields + require.Equal(t, *got.SourceHash, tx.SourceHash(), "newRPCTransaction().SourceHash = %v, want %v", got.SourceHash, tx.SourceHash()) + require.Equal(t, *got.IsSystemTx, tx.IsSystemTx(), "newRPCTransaction().IsSystemTx = %v, want %v", got.IsSystemTx, tx.IsSystemTx()) + require.Equal(t, got.Mint, (*hexutil.Big)(tx.Mint()), "newRPCTransaction().Mint = %v, want %v", got.Mint, tx.Mint()) + require.Equal(t, got.Nonce, (hexutil.Uint64)(nonce), "newRPCTransaction().Mint = %v, want %v", got.Nonce, nonce) +} + +func TestNewRPCTransactionOmitIsSystemTxFalse(t *testing.T) { + tx := types.NewTx(&types.DepositTx{ + IsSystemTransaction: false, + }) + got := newRPCTransaction(tx, common.Hash{}, uint64(12), uint64(1234), uint64(1), big.NewInt(0), &params.ChainConfig{}, nil) + + require.Nil(t, got.IsSystemTx, "should omit IsSystemTx when false") +} + +func TestUnmarshalRpcDepositTx(t *testing.T) { + tests := []struct { + name string + modifier func(tx *RPCTransaction) + valid bool + }{ + { + name: "Unmodified", + modifier: func(tx *RPCTransaction) {}, + valid: true, + }, + { + name: "Zero Values", + modifier: func(tx *RPCTransaction) { + tx.V = (*hexutil.Big)(common.Big0) + tx.R = (*hexutil.Big)(common.Big0) + tx.S = (*hexutil.Big)(common.Big0) + tx.GasPrice = (*hexutil.Big)(common.Big0) + }, + valid: true, + }, + { + name: "Nil Values", + modifier: func(tx *RPCTransaction) { + tx.V = nil + tx.R = nil + tx.S = nil + tx.GasPrice = nil + }, + valid: true, + }, + { + name: "Non-Zero GasPrice", + modifier: func(tx *RPCTransaction) { + tx.GasPrice = (*hexutil.Big)(big.NewInt(43)) + }, + valid: false, + }, + { + name: "Non-Zero V", + modifier: func(tx *RPCTransaction) { + tx.V = (*hexutil.Big)(big.NewInt(43)) + }, + valid: false, + }, + { + name: "Non-Zero R", + modifier: func(tx *RPCTransaction) { + tx.R = (*hexutil.Big)(big.NewInt(43)) + }, + valid: false, + }, + { + name: "Non-Zero S", + modifier: func(tx *RPCTransaction) { + tx.S = (*hexutil.Big)(big.NewInt(43)) + }, + valid: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + tx := types.NewTx(&types.DepositTx{ + SourceHash: common.HexToHash("0x1234"), + IsSystemTransaction: true, + Mint: big.NewInt(34), + }) + rpcTx := newRPCTransaction(tx, common.Hash{}, uint64(12), uint64(1234), uint64(1), big.NewInt(0), &params.ChainConfig{}, nil) + test.modifier(rpcTx) + json, err := json.Marshal(rpcTx) + require.NoError(t, err, "marshalling failed: %w", err) + parsed := &types.Transaction{} + err = parsed.UnmarshalJSON(json) + if test.valid { + require.NoError(t, err, "unmarshal failed: %w", err) + } else { + require.Error(t, err, "unmarshal should have failed but did not") + } + }) + } +} + func TestTransaction_RoundTripRpcJSON(t *testing.T) { var ( config = params.AllEthashProtocolChanges @@ -73,7 +189,7 @@ t.Fatalf("test %d: stx changed, want %x have %x", i, want, have) }   // rpcTransaction - rpcTx := newRPCTransaction(tx, common.Hash{}, 0, 0, 0, nil, config) + rpcTx := newRPCTransaction(tx, common.Hash{}, 0, 0, 0, nil, config, nil) if data, err := json.Marshal(rpcTx); err != nil { t.Fatalf("test %d: marshalling failed; %v", i, err) } else if err = tx2.UnmarshalJSON(data); err != nil { @@ -237,7 +353,11 @@ func (b testBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { panic("implement me") } func (b testBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) { - panic("implement me") + if blockNrOrHash.BlockHash != nil { + return b.chain.GetHeaderByHash(*blockNrOrHash.BlockHash), nil + } else { + return b.HeaderByNumber(ctx, *blockNrOrHash.BlockNumber) + } } func (b testBackend) CurrentHeader() *types.Header { panic("implement me") } func (b testBackend) CurrentBlock() *types.Header { panic("implement me") } @@ -291,7 +411,7 @@ if vmConfig == nil { vmConfig = b.chain.GetVMConfig() } txContext := core.NewEVMTxContext(msg) - context := core.NewEVMBlockContext(header, b.chain, nil) + context := core.NewEVMBlockContext(header, b.chain, nil, b.ChainConfig(), state) if blockContext != nil { context = *blockContext } @@ -345,6 +465,12 @@ func (b testBackend) BloomStatus() (uint64, uint64) { panic("implement me") } func (b testBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) { panic("implement me") } +func (b testBackend) HistoricalRPCService() *rpc.Client { + panic("implement me") +} +func (b testBackend) Genesis() *types.Block { + panic("implement me") +}   func TestEstimateGas(t *testing.T) { t.Parallel() @@ -489,7 +615,7 @@ From: &accounts[0].addr, To: &accounts[1].addr, Value: (*hexutil.Big)(big.NewInt(1000)), }, - expectErr: errors.New("header not found"), + expectErr: ethereum.NotFound, }, // transfer on the latest block { @@ -705,12 +831,12 @@ // full tx details { inclTx: true, fullTx: true, - want: `{"difficulty":"0x0","extraData":"0x","gasLimit":"0x0","gasUsed":"0x0","hash":"0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","number":"0x64","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x296","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","transactions":[{"blockHash":"0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee","blockNumber":"0x64","from":"0x0000000000000000000000000000000000000000","gas":"0x457","gasPrice":"0x2b67","hash":"0x7d39df979e34172322c64983a9ad48302c2b889e55bda35324afecf043a77605","input":"0x111111","nonce":"0x1","to":"0x0000000000000000000000000000000000000011","transactionIndex":"0x0","value":"0x6f","type":"0x1","accessList":[],"chainId":"0x539","v":"0x0","r":"0x0","s":"0x0"},{"blockHash":"0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee","blockNumber":"0x64","from":"0x0000000000000000000000000000000000000000","gas":"0x457","gasPrice":"0x2b67","hash":"0x9bba4c34e57c875ff57ac8d172805a26ae912006985395dc1bdf8f44140a7bf4","input":"0x111111","nonce":"0x2","to":"0x0000000000000000000000000000000000000011","transactionIndex":"0x1","value":"0x6f","type":"0x0","chainId":"0x7fffffffffffffee","v":"0x0","r":"0x0","s":"0x0"},{"blockHash":"0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee","blockNumber":"0x64","from":"0x0000000000000000000000000000000000000000","gas":"0x457","gasPrice":"0x2b67","hash":"0x98909ea1ff040da6be56bc4231d484de1414b3c1dac372d69293a4beb9032cb5","input":"0x111111","nonce":"0x3","to":"0x0000000000000000000000000000000000000011","transactionIndex":"0x2","value":"0x6f","type":"0x1","accessList":[],"chainId":"0x539","v":"0x0","r":"0x0","s":"0x0"},{"blockHash":"0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee","blockNumber":"0x64","from":"0x0000000000000000000000000000000000000000","gas":"0x457","gasPrice":"0x2b67","hash":"0x12e1f81207b40c3bdcc13c0ee18f5f86af6d31754d57a0ea1b0d4cfef21abef1","input":"0x111111","nonce":"0x4","to":"0x0000000000000000000000000000000000000011","transactionIndex":"0x3","value":"0x6f","type":"0x0","chainId":"0x7fffffffffffffee","v":"0x0","r":"0x0","s":"0x0"}],"transactionsRoot":"0x661a9febcfa8f1890af549b874faf9fa274aede26ef489d9db0b25daa569450e","uncles":[]}`, + want: `{"difficulty":"0x0","extraData":"0x","gasLimit":"0x0","gasUsed":"0x0","hash":"0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","number":"0x64","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x296","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","transactions":[{"blockHash":"0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee","blockNumber":"0x64","from":"0x0000000000000000000000000000000000000000","gas":"0x457","gasPrice":"0x2b67","hash":"0x7d39df979e34172322c64983a9ad48302c2b889e55bda35324afecf043a77605","input":"0x111111","nonce":"0x1","to":"0x0000000000000000000000000000000000000011","transactionIndex":"0x0","value":"0x6f","type":"0x1","accessList":[],"chainId":"0x539","v":"0x0","r":"0x0","s":"0x0"},{"blockHash":"0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee","blockNumber":"0x64","from":"0x0000000000000000000000000000000000000000","gas":"0x457","gasPrice":"0x2b67","hash":"0x9bba4c34e57c875ff57ac8d172805a26ae912006985395dc1bdf8f44140a7bf4","input":"0x111111","nonce":"0x2","to":"0x0000000000000000000000000000000000000011","transactionIndex":"0x1","value":"0x6f","type":"0x0","chainId":"0x1","v":"0x0","r":"0x0","s":"0x0"},{"blockHash":"0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee","blockNumber":"0x64","from":"0x0000000000000000000000000000000000000000","gas":"0x457","gasPrice":"0x2b67","hash":"0x98909ea1ff040da6be56bc4231d484de1414b3c1dac372d69293a4beb9032cb5","input":"0x111111","nonce":"0x3","to":"0x0000000000000000000000000000000000000011","transactionIndex":"0x2","value":"0x6f","type":"0x1","accessList":[],"chainId":"0x539","v":"0x0","r":"0x0","s":"0x0"},{"blockHash":"0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee","blockNumber":"0x64","from":"0x0000000000000000000000000000000000000000","gas":"0x457","gasPrice":"0x2b67","hash":"0x12e1f81207b40c3bdcc13c0ee18f5f86af6d31754d57a0ea1b0d4cfef21abef1","input":"0x111111","nonce":"0x4","to":"0x0000000000000000000000000000000000000011","transactionIndex":"0x3","value":"0x6f","type":"0x0","chainId":"0x1","v":"0x0","r":"0x0","s":"0x0"}],"transactionsRoot":"0x661a9febcfa8f1890af549b874faf9fa274aede26ef489d9db0b25daa569450e","uncles":[]}`, }, }   for i, tc := range testSuite { - resp, err := RPCMarshalBlock(block, tc.inclTx, tc.fullTx, params.MainnetChainConfig) + resp, err := RPCMarshalBlock(context.Background(), block, tc.inclTx, tc.fullTx, params.MainnetChainConfig, testBackend{}) if err != nil { t.Errorf("test %d: got error %v", i, err) continue
diff --git go-ethereum/cmd/devp2p/internal/ethtest/snap.go op-geth/cmd/devp2p/internal/ethtest/snap.go index f947e4bc9bae791bc1a0058fcadf7fae094632b8..8fa03660cfcc9f8e979cb9d9bdec20b0af4a215b 100644 --- go-ethereum/cmd/devp2p/internal/ethtest/snap.go +++ op-geth/cmd/devp2p/internal/ethtest/snap.go @@ -249,11 +249,11 @@ for i, tc := range []byteCodesTest{ // A few stateroots { nBytes: 10000, hashes: []common.Hash{s.chain.RootAt(0), s.chain.RootAt(999)}, - expHashes: 0, + expHashes: 1, // 32-byte keys are detected as code, even if not code (like genesis hash), in legacy lookups. }, { nBytes: 10000, hashes: []common.Hash{s.chain.RootAt(0), s.chain.RootAt(0)}, - expHashes: 0, + expHashes: 2, // 32-byte keys are detected as code, even if not code (like genesis hash), in legacy lookups. }, // Empties {
diff --git go-ethereum/core/blockchain.go op-geth/core/blockchain.go index 659b2f02ea9ef7acdfbc24ed3ef2b3ba0b1a30ce..67658ec752a681bb88b6fb1776837701c9c58434 100644 --- go-ethereum/core/blockchain.go +++ op-geth/core/blockchain.go @@ -254,6 +254,10 @@ } log.Info(strings.Repeat("-", 153)) log.Info("")   + if chainConfig.IsOptimism() && chainConfig.RegolithTime == nil { + log.Warn("Optimism RegolithTime has not been set") + } + bc := &BlockChain{ chainConfig: chainConfig, cacheConfig: cacheConfig,
diff --git go-ethereum/core/blockchain_reader.go op-geth/core/blockchain_reader.go index fd65cb2db32f8faac481355cb7e0e0761adb5370..c0ea4bef3b445cab57ee8b18a0b53f130226cbb7 100644 --- go-ethereum/core/blockchain_reader.go +++ op-geth/core/blockchain_reader.go @@ -311,6 +311,14 @@ } return bc.stateCache.(codeReader).ContractCodeWithPrefix(common.Hash{}, hash) }   +// ContractCode retrieves a blob of data associated with a contract hash +// either from ephemeral in-memory cache, or from persistent storage. +// This is a legacy-method, replaced by ContractCodeWithPrefix, +// but required for old databases to serve snap-sync. +func (bc *BlockChain) ContractCode(hash common.Hash) ([]byte, error) { + return bc.stateCache.ContractCode(common.Hash{}, hash) +} + // State returns a new mutable state based on the current HEAD block. func (bc *BlockChain) State() (*state.StateDB, error) { return bc.StateAt(bc.CurrentBlock().Root)
diff --git go-ethereum/core/error.go op-geth/core/error.go index 872ba8d365d8c152641f2dcd32b39343a285bd4d..d99b85e2490725b35c5797c93060ac7009b909d8 100644 --- go-ethereum/core/error.go +++ op-geth/core/error.go @@ -100,4 +100,7 @@ ErrFeeCapTooLow = errors.New("max fee per gas less than block base fee")   // ErrSenderNoEOA is returned if the sender of a transaction is a contract. ErrSenderNoEOA = errors.New("sender not an eoa") + + // ErrSystemTxNotSupported is returned for any deposit tx with IsSystemTx=true after the Regolith fork + ErrSystemTxNotSupported = errors.New("system tx not supported") )
diff --git go-ethereum/core/evm.go op-geth/core/evm.go index bd4f2b0e55f1cf039549830eed93514272962171..527a81ad29d6d1b2faedb37579fce28d2ad24efd 100644 --- go-ethereum/core/evm.go +++ op-geth/core/evm.go @@ -23,6 +23,7 @@ "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/params" )   // ChainContext supports retrieving headers and consensus parameters from the @@ -36,7 +37,7 @@ GetHeader(common.Hash, uint64) *types.Header }   // NewEVMBlockContext creates a new context for use in the EVM. -func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext { +func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address, config *params.ChainConfig, statedb types.StateGetter) vm.BlockContext { var ( beneficiary common.Address baseFee *big.Int @@ -66,6 +67,7 @@ Difficulty: new(big.Int).Set(header.Difficulty), BaseFee: baseFee, GasLimit: header.GasLimit, Random: random, + L1CostFunc: types.NewL1CostFunc(config, statedb), } }
diff --git go-ethereum/core/gen_genesis.go op-geth/core/gen_genesis.go index 4e0844e889ab4e470790529515a0d5317b4a7a12..9a682b64ac54ed56a82757d745c40a017f4cadef 100644 --- go-ethereum/core/gen_genesis.go +++ op-geth/core/gen_genesis.go @@ -31,6 +31,7 @@ Number math.HexOrDecimal64 `json:"number"` GasUsed math.HexOrDecimal64 `json:"gasUsed"` ParentHash common.Hash `json:"parentHash"` BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` + StateHash *common.Hash `json:"stateHash,omitempty"` } var enc Genesis enc.Config = g.Config @@ -51,6 +52,7 @@ enc.Number = math.HexOrDecimal64(g.Number) enc.GasUsed = math.HexOrDecimal64(g.GasUsed) enc.ParentHash = g.ParentHash enc.BaseFee = (*math.HexOrDecimal256)(g.BaseFee) + enc.StateHash = g.StateHash return json.Marshal(&enc) }   @@ -70,6 +72,7 @@ Number *math.HexOrDecimal64 `json:"number"` GasUsed *math.HexOrDecimal64 `json:"gasUsed"` ParentHash *common.Hash `json:"parentHash"` BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` + StateHash *common.Hash `json:"stateHash,omitempty"` } var dec Genesis if err := json.Unmarshal(input, &dec); err != nil { @@ -119,6 +122,9 @@ g.ParentHash = *dec.ParentHash } if dec.BaseFee != nil { g.BaseFee = (*big.Int)(dec.BaseFee) + } + if dec.StateHash != nil { + g.StateHash = dec.StateHash } return nil }
diff --git go-ethereum/core/rawdb/accessors_chain.go op-geth/core/rawdb/accessors_chain.go index b479655b0b7469c47db053dd8591a418e865b495..de3337dfe0de2e03ab345aa2170b4fea9a0db7be 100644 --- go-ethereum/core/rawdb/accessors_chain.go +++ op-geth/core/rawdb/accessors_chain.go @@ -637,13 +637,11 @@ log.Error("Missing body but have receipt", "hash", hash, "number", number) return nil } header := ReadHeader(db, hash, number) - var baseFee *big.Int if header == nil { - baseFee = big.NewInt(0) - } else { - baseFee = header.BaseFee + log.Error("Missing header but have receipt", "hash", hash, "number", number) + return nil } - if err := receipts.DeriveFields(config, hash, number, time, baseFee, body.Transactions); err != nil { + if err := receipts.DeriveFields(config, hash, number, time, header.BaseFee, body.Transactions); err != nil { log.Error("Failed to derive block receipts fields", "hash", hash, "number", number, "err", err) return nil } @@ -681,6 +679,15 @@ type storedReceiptRLP struct { PostStateOrStatus []byte CumulativeGasUsed uint64 Logs []*types.Log + + // Remaining fields are declared to allow the receipt RLP to be parsed without errors. + // However, they must not be used as they may not be populated correctly due to multiple receipt formats + // being combined into a single list of optional fields which can be mistaken for each other. + // DepositNonce (*uint64) from Regolith deposit tx receipts will be parsed into L1GasUsed + L1GasUsed *big.Int `rlp:"optional"` // OVM legacy + L1GasPrice *big.Int `rlp:"optional"` // OVM legacy + L1Fee *big.Int `rlp:"optional"` // OVM legacy + FeeScalar string `rlp:"optional"` // OVM legacy }   // ReceiptLogs is a barebone version of ReceiptForStorage which only keeps
diff --git go-ethereum/core/rawdb/accessors_chain_test.go op-geth/core/rawdb/accessors_chain_test.go index 32e38a81ce4da94a86de28d07361913612e60b7d..9eb4103f0ae058431a7c9625c2c80750b9a52d82 100644 --- go-ethereum/core/rawdb/accessors_chain_test.go +++ op-geth/core/rawdb/accessors_chain_test.go @@ -27,10 +27,12 @@ "reflect" "testing"   "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" + "github.com/stretchr/testify/require" "golang.org/x/crypto/sha3" )   @@ -347,6 +349,9 @@ // Create a live block since we need metadata to reconstruct the receipt tx1 := types.NewTransaction(1, common.HexToAddress("0x1"), big.NewInt(1), 1, big.NewInt(1), nil) tx2 := types.NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil)   + header := &types.Header{ + Number: big.NewInt(0), + } body := &types.Body{Transactions: types.Transactions{tx1, tx2}}   // Create the two receipts to manage afterwards @@ -378,12 +383,15 @@ receipt2.Bloom = types.CreateBloom(types.Receipts{receipt2}) receipts := []*types.Receipt{receipt1, receipt2}   // Check that no receipt entries are in a pristine database - hash := common.BytesToHash([]byte{0x03, 0x14}) + hash := header.Hash() if rs := ReadReceipts(db, hash, 0, 0, params.TestChainConfig); len(rs) != 0 { t.Fatalf("non existent receipts returned: %v", rs) } // Insert the body that corresponds to the receipts WriteBody(db, hash, 0, body) + + // Insert the header that corresponds to the receipts + WriteHeader(db, header)   // Insert the receipt slice into the database and check presence WriteReceipts(db, hash, 0, receipts) @@ -698,6 +706,7 @@ body := &types.Body{Transactions: types.Transactions{tx1, tx2}}   // Create the two receipts to manage afterwards + depositNonce := uint64(math.MaxUint64) receipt1 := &types.Receipt{ Status: types.ReceiptStatusFailed, CumulativeGasUsed: 1, @@ -708,6 +717,7 @@ }, TxHash: tx1.Hash(), ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}), GasUsed: 111111, + DepositNonce: &depositNonce, } receipt1.Bloom = types.CreateBloom(types.Receipts{receipt1})   @@ -765,6 +775,36 @@ t.Fatalf("receipt #%d: receipt mismatch: have %s, want %s", i, hex.EncodeToString(rlpHave), hex.EncodeToString(rlpWant)) } } } +} + +func TestParseLegacyReceiptRLP(t *testing.T) { + // Create a gasUsed value greater than a uint64 can represent + gasUsed := big.NewInt(0) + gasUsed = gasUsed.SetUint64(math.MaxUint64) + gasUsed = gasUsed.Add(gasUsed, big.NewInt(math.MaxInt64)) + sanityCheck := (&big.Int{}).SetUint64(gasUsed.Uint64()) + require.NotEqual(t, gasUsed, sanityCheck) + receipt := types.LegacyOptimismStoredReceiptRLP{ + CumulativeGasUsed: 1, + Logs: []*types.LogForStorage{ + {Address: common.BytesToAddress([]byte{0x11})}, + {Address: common.BytesToAddress([]byte{0x01, 0x11})}, + }, + L1GasUsed: gasUsed, + L1GasPrice: gasUsed, + L1Fee: gasUsed, + FeeScalar: "6", + } + + data, err := rlp.EncodeToBytes(receipt) + require.NoError(t, err) + var result storedReceiptRLP + err = rlp.DecodeBytes(data, &result) + require.NoError(t, err) + require.Equal(t, receipt.L1GasUsed, result.L1GasUsed) + require.Equal(t, receipt.L1GasPrice, result.L1GasPrice) + require.Equal(t, receipt.L1Fee, result.L1Fee) + require.Equal(t, receipt.FeeScalar, result.FeeScalar) }   func TestDeriveLogFields(t *testing.T) {
diff --git go-ethereum/core/superchain.go op-geth/core/superchain.go new file mode 100644 index 0000000000000000000000000000000000000000..96f68cedcf437f251d18e35764e4d49f4ddcab6b --- /dev/null +++ op-geth/core/superchain.go @@ -0,0 +1,99 @@ +package core + +import ( + "fmt" + "math/big" + + "github.com/ethereum-optimism/superchain-registry/superchain" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" +) + +func LoadOPStackGenesis(chainID uint64) (*Genesis, error) { + chConfig, ok := superchain.OPChains[chainID] + if !ok { + return nil, fmt.Errorf("unknown chain ID: %d", chainID) + } + + cfg, err := params.LoadOPStackChainConfig(chainID) + if err != nil { + return nil, fmt.Errorf("failed to load params.ChainConfig for chain %d: %w", chainID, err) + } + + gen, err := superchain.LoadGenesis(chainID) + if err != nil { + return nil, fmt.Errorf("failed to load genesis definition for chain %d: %w", chainID, err) + } + + genesis := &Genesis{ + Config: cfg, + Nonce: gen.Nonce, + Timestamp: gen.Timestamp, + ExtraData: gen.ExtraData, + GasLimit: gen.GasLimit, + Difficulty: (*big.Int)(gen.Difficulty), + Mixhash: common.Hash(gen.Mixhash), + Coinbase: common.Address(gen.Coinbase), + Alloc: make(GenesisAlloc), + Number: gen.Number, + GasUsed: gen.GasUsed, + ParentHash: common.Hash(gen.ParentHash), + BaseFee: (*big.Int)(gen.BaseFee), + } + + for addr, acc := range gen.Alloc { + var code []byte + if acc.CodeHash != ([32]byte{}) { + dat, err := superchain.LoadContractBytecode(acc.CodeHash) + if err != nil { + return nil, fmt.Errorf("failed to load bytecode %s of address %s in chain %d: %w", acc.CodeHash, addr, chainID, err) + } + code = dat + } + var storage map[common.Hash]common.Hash + if len(acc.Storage) > 0 { + storage = make(map[common.Hash]common.Hash) + for k, v := range acc.Storage { + storage[common.Hash(k)] = common.Hash(v) + } + } + bal := common.Big0 + if acc.Balance != nil { + bal = (*big.Int)(acc.Balance) + } + genesis.Alloc[common.Address(addr)] = GenesisAccount{ + Code: code, + Storage: storage, + Balance: bal, + Nonce: acc.Nonce, + } + } + if gen.StateHash != nil { + if len(gen.Alloc) > 0 { + return nil, fmt.Errorf("chain definition unexpectedly contains both allocation (%d) and state-hash %s", len(gen.Alloc), *gen.StateHash) + } + genesis.StateHash = (*common.Hash)(gen.StateHash) + } + + genesisBlock := genesis.ToBlock() + genesisBlockHash := genesisBlock.Hash() + expectedHash := common.Hash([32]byte(chConfig.Genesis.L2.Hash)) + + // Verify we correctly produced the genesis config by recomputing the genesis-block-hash, + // and check the genesis matches the chain genesis definition. + if chConfig.Genesis.L2.Number != genesisBlock.NumberU64() { + switch chainID { + case params.OPMainnetChainID: + expectedHash = common.HexToHash("0x7ca38a1916c42007829c55e69d3e9a73265554b586a499015373241b8a3fa48b") + case params.OPGoerliChainID: + expectedHash = common.HexToHash("0xc1fc15cd51159b1f1e5cbc4b82e85c1447ddfa33c52cf1d98d14fba0d6354be1") + default: + return nil, fmt.Errorf("unknown stateless genesis definition for chain %d", chainID) + } + } + if expectedHash != genesisBlockHash { + return nil, fmt.Errorf("produced genesis with hash %s but expected %s", genesisBlockHash, expectedHash) + } + return genesis, nil +}
diff --git go-ethereum/core/superchain_test.go op-geth/core/superchain_test.go new file mode 100644 index 0000000000000000000000000000000000000000..fa3246250ac32aebea1410295f946fd10f25252d --- /dev/null +++ op-geth/core/superchain_test.go @@ -0,0 +1,17 @@ +package core + +import ( + "testing" + + "github.com/ethereum-optimism/superchain-registry/superchain" +) + +func TestOPStackGenesis(t *testing.T) { + for id := range superchain.OPChains { + gen, err := LoadOPStackGenesis(id) + if err != nil { + t.Fatal(err) + } + t.Logf("chain: %d, genesis block hash: %s", id, gen.ToBlock().Hash()) + } +}
diff --git go-ethereum/core/types/receipt_test.go op-geth/core/types/receipt_test.go index 168f36208b9eb5e7ef7fd01c947105122b51c31b..809540715e7de6acc5d9a1688aac5705df5203bf 100644 --- go-ethereum/core/types/receipt_test.go +++ op-geth/core/types/receipt_test.go @@ -19,6 +19,7 @@ import ( "bytes" "encoding/json" + "fmt" "math" "math/big" "reflect" @@ -29,6 +30,7 @@ "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/holiman/uint256" "github.com/kylelemons/godebug/diff" + "github.com/stretchr/testify/require" )   var ( @@ -82,6 +84,42 @@ }, }, Type: DynamicFeeTxType, } + depositReceiptNoNonce = &Receipt{ + Status: ReceiptStatusFailed, + CumulativeGasUsed: 1, + Logs: []*Log{ + { + Address: common.BytesToAddress([]byte{0x11}), + Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, + Data: []byte{0x01, 0x00, 0xff}, + }, + { + Address: common.BytesToAddress([]byte{0x01, 0x11}), + Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, + Data: []byte{0x01, 0x00, 0xff}, + }, + }, + Type: DepositTxType, + } + nonce = uint64(1234) + depositReceiptWithNonce = &Receipt{ + Status: ReceiptStatusFailed, + CumulativeGasUsed: 1, + DepositNonce: &nonce, + Logs: []*Log{ + { + Address: common.BytesToAddress([]byte{0x11}), + Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, + Data: []byte{0x01, 0x00, 0xff}, + }, + { + Address: common.BytesToAddress([]byte{0x01, 0x11}), + Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, + Data: []byte{0x01, 0x00, 0xff}, + }, + }, + Type: DepositTxType, + }   // Create a few transactions to have receipts for to2 = common.HexToAddress("0x2") @@ -147,8 +185,13 @@ GasTipCap: uint256.NewInt(77), GasFeeCap: uint256.NewInt(1077), BlobFeeCap: uint256.NewInt(100077), }), + NewTx(&DepositTx{ + To: nil, // contract creation + Value: big.NewInt(6), + Gas: 50, + }), } - + depNonce = uint64(7) blockNumber = big.NewInt(1) blockTime = uint64(2) blockHash = common.BytesToHash([]byte{0x03, 0x14}) @@ -287,6 +330,41 @@ BlockHash: blockHash, BlockNumber: blockNumber, TransactionIndex: 6, }, + &Receipt{ + Type: DepositTxType, + PostState: common.Hash{5}.Bytes(), + CumulativeGasUsed: 50 + 28, + Logs: []*Log{ + { + Address: common.BytesToAddress([]byte{0x33}), + Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, + // derived fields: + BlockNumber: blockNumber.Uint64(), + TxHash: txs[7].Hash(), + TxIndex: 7, + BlockHash: blockHash, + Index: 4, + }, + { + Address: common.BytesToAddress([]byte{0x03, 0x33}), + Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, + // derived fields: + BlockNumber: blockNumber.Uint64(), + TxHash: txs[7].Hash(), + TxIndex: 7, + BlockHash: blockHash, + Index: 5, + }, + }, + TxHash: txs[7].Hash(), + ContractAddress: common.HexToAddress("0x3bb898b4bbe24f68a4e9be46cfe72d1787fd74f4"), + GasUsed: 50, + EffectiveGasPrice: big.NewInt(0), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 7, + DepositNonce: &depNonce, + }, } )   @@ -517,3 +595,205 @@ l[i] = &cpy } return l } + +func TestDeriveOptimismTxReceipt(t *testing.T) { + to4 := common.HexToAddress("0x4") + // Create a few transactions to have receipts for + txs := Transactions{ + NewTx(&DepositTx{ + To: nil, // contract creation + Value: big.NewInt(6), + Gas: 50, + // System config with L1Scalar=2_000_000 (becomes 2 after division), L1Overhead=2500, L1BaseFee=5000 + Data: common.Hex2Bytes("015d8eb900000000000000000000000000000000000000000000000026b39534042076f70000000000000000000000000000000000000000000000007e33b7c4995967580000000000000000000000000000000000000000000000000000000000001388547dea8ff339566349ed0ef6384876655d1b9b955e36ac165c6b8ab69b9af5cd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000123400000000000000000000000000000000000000000000000000000000000009c400000000000000000000000000000000000000000000000000000000001e8480"), + }), + NewTx(&DynamicFeeTx{ + To: &to4, + Nonce: 4, + Value: big.NewInt(4), + Gas: 4, + GasTipCap: big.NewInt(44), + GasFeeCap: big.NewInt(1045), + Data: []byte{0, 1, 255, 0}, + }), + } + depNonce := uint64(7) + blockNumber := big.NewInt(1) + blockHash := common.BytesToHash([]byte{0x03, 0x14}) + + // 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: &depNonce, + }, + &Receipt{ + Type: DynamicFeeTxType, + PostState: common.Hash{4}.Bytes(), + CumulativeGasUsed: 10, + Logs: []*Log{}, + // derived fields: + TxHash: txs[1].Hash(), + GasUsed: 18446744073709551561, + EffectiveGasPrice: big.NewInt(1044), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 1, + L1GasPrice: big.NewInt(5000), + L1GasUsed: big.NewInt(3976), + L1Fee: big.NewInt(39760000), + FeeScalar: big.NewFloat(2), + }, + } + + // Re-derive receipts. + basefee := big.NewInt(1000) + derivedReceipts := clearComputedFieldsOnReceipts(receipts) + err := Receipts(derivedReceipts).DeriveFields(params.OptimismTestConfig, blockHash, blockNumber.Uint64(), 0, basefee, txs) + if err != nil { + t.Fatalf("DeriveFields(...) = %v, want <nil>", err) + } + + // Check diff of receipts against derivedReceipts. + r1, err := json.MarshalIndent(receipts, "", " ") + if err != nil { + t.Fatal("error marshaling input receipts:", err) + } + r2, err := json.MarshalIndent(derivedReceipts, "", " ") + if err != nil { + t.Fatal("error marshaling derived receipts:", err) + } + d := diff.Diff(string(r1), string(r2)) + if d != "" { + t.Fatal("receipts differ:", d) + } + + // Check that we preserved the invariant: l1Fee = l1GasPrice * l1GasUsed * l1FeeScalar + // but with more difficult int math... + l2Rcpt := derivedReceipts[1] + l1GasCost := new(big.Int).Mul(l2Rcpt.L1GasPrice, l2Rcpt.L1GasUsed) + l1Fee := new(big.Float).Mul(new(big.Float).SetInt(l1GasCost), l2Rcpt.FeeScalar) + require.Equal(t, new(big.Float).SetInt(l2Rcpt.L1Fee), l1Fee) +} + +func TestBedrockDepositReceiptUnchanged(t *testing.T) { + expectedRlp := common.FromHex("7EF90156A003000000000000000000000000000000000000000000000000000000000000000AB9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F0D7940000000000000000000000000000000000000033C001D7940000000000000000000000000000000000000333C002") + // Deposit receipt with no nonce + receipt := &Receipt{ + Type: DepositTxType, + PostState: common.Hash{3}.Bytes(), + CumulativeGasUsed: 10, + Logs: []*Log{ + {Address: common.BytesToAddress([]byte{0x33}), Data: []byte{1}, Topics: []common.Hash{}}, + {Address: common.BytesToAddress([]byte{0x03, 0x33}), Data: []byte{2}, Topics: []common.Hash{}}, + }, + TxHash: common.Hash{}, + ContractAddress: common.BytesToAddress([]byte{0x03, 0x33, 0x33}), + GasUsed: 4, + } + + rlp, err := receipt.MarshalBinary() + require.NoError(t, err) + require.Equal(t, expectedRlp, rlp) + + // Consensus values should be unchanged after reparsing + parsed := new(Receipt) + err = parsed.UnmarshalBinary(rlp) + require.NoError(t, err) + require.Equal(t, receipt.Status, parsed.Status) + require.Equal(t, receipt.CumulativeGasUsed, parsed.CumulativeGasUsed) + require.Equal(t, receipt.Bloom, parsed.Bloom) + require.EqualValues(t, receipt.Logs, parsed.Logs) + // And still shouldn't have a nonce + require.Nil(t, parsed.DepositNonce) +} + +func TestRoundTripReceipt(t *testing.T) { + tests := []struct { + name string + rcpt *Receipt + }{ + {name: "Legacy", rcpt: legacyReceipt}, + {name: "AccessList", rcpt: accessListReceipt}, + {name: "EIP1559", rcpt: eip1559Receipt}, + {name: "DepositNoNonce", rcpt: depositReceiptNoNonce}, + {name: "DepositWithNonce", rcpt: depositReceiptWithNonce}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + data, err := test.rcpt.MarshalBinary() + require.NoError(t, err) + + d := &Receipt{} + err = d.UnmarshalBinary(data) + require.NoError(t, err) + require.Equal(t, test.rcpt, d) + }) + + t.Run(fmt.Sprintf("%sRejectExtraData", test.name), func(t *testing.T) { + data, err := test.rcpt.MarshalBinary() + require.NoError(t, err) + data = append(data, 1, 2, 3, 4) + d := &Receipt{} + err = d.UnmarshalBinary(data) + require.Error(t, err) + }) + } +} + +func TestRoundTripReceiptForStorage(t *testing.T) { + tests := []struct { + name string + rcpt *Receipt + }{ + {name: "Legacy", rcpt: legacyReceipt}, + {name: "AccessList", rcpt: accessListReceipt}, + {name: "EIP1559", rcpt: eip1559Receipt}, + {name: "DepositNoNonce", rcpt: depositReceiptNoNonce}, + {name: "DepositWithNonce", rcpt: depositReceiptWithNonce}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + data, err := rlp.EncodeToBytes((*ReceiptForStorage)(test.rcpt)) + require.NoError(t, err) + + d := &ReceiptForStorage{} + err = rlp.DecodeBytes(data, d) + require.NoError(t, err) + // Only check the stored fields - the others are derived later + require.Equal(t, test.rcpt.Status, d.Status) + require.Equal(t, test.rcpt.CumulativeGasUsed, d.CumulativeGasUsed) + require.Equal(t, test.rcpt.Logs, d.Logs) + require.Equal(t, test.rcpt.DepositNonce, d.DepositNonce) + }) + } +}
diff --git go-ethereum/core/types/rollup_l1_cost_test.go op-geth/core/types/rollup_l1_cost_test.go new file mode 100644 index 0000000000000000000000000000000000000000..e43ea967ee87a257cfc2afa58396c2eb6685ed76 --- /dev/null +++ op-geth/core/types/rollup_l1_cost_test.go @@ -0,0 +1,30 @@ +package types + +import ( + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/params" + "github.com/stretchr/testify/require" +) + +func TestRollupGasData(t *testing.T) { + for i := 0; i < 100; i++ { + zeroes := rand.Uint64() + ones := rand.Uint64() + + r := RollupGasData{ + Zeroes: zeroes, + Ones: ones, + } + time := uint64(1) + cfg := &params.ChainConfig{ + RegolithTime: &time, + } + gasPreRegolith := r.DataGas(0, cfg) + gasPostRegolith := r.DataGas(1, cfg) + + require.Equal(t, r.Zeroes*params.TxDataZeroGas+(r.Ones+68)*params.TxDataNonZeroGasEIP2028, gasPreRegolith) + require.Equal(t, r.Zeroes*params.TxDataZeroGas+r.Ones*params.TxDataNonZeroGasEIP2028, gasPostRegolith) + } +}
diff --git go-ethereum/core/types/tx_blob.go op-geth/core/types/tx_blob.go index 58065d0017d314c13e0047d19203f37592aff33a..4b6caa5a996046344acccdce0be107c5749d1db4 100644 --- go-ethereum/core/types/tx_blob.go +++ op-geth/core/types/tx_blob.go @@ -108,6 +108,7 @@ func (tx *BlobTx) to() *common.Address { return tx.To } func (tx *BlobTx) blobGas() uint64 { return params.BlobTxDataGasPerBlob * uint64(len(tx.BlobHashes)) } func (tx *BlobTx) blobGasFeeCap() *big.Int { return tx.BlobFeeCap.ToBig() } func (tx *BlobTx) blobHashes() []common.Hash { return tx.BlobHashes } +func (tx *BlobTx) isSystemTx() bool { return false }   func (tx *BlobTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { if baseFee == nil {
diff --git go-ethereum/eth/api.go op-geth/eth/api.go index 00ad48fbfc8f03597db7a3802c887390cb8dc4f6..96c20d60206b586510202fac10152cfa5f6e0439 100644 --- go-ethereum/eth/api.go +++ op-geth/eth/api.go @@ -261,6 +261,9 @@ // If we're dumping the pending state, we need to request // both the pending block as well as the pending state from // the miner and operate on those _, stateDb := api.eth.miner.Pending() + if stateDb == nil { + return state.Dump{}, errors.New("no pending state") + } return stateDb.RawDump(opts), nil } var header *types.Header @@ -320,7 +323,7 @@ blockRlp = err.Error() // Hacky, but hey, it works } else { blockRlp = fmt.Sprintf("%#x", rlpBytes) } - if blockJSON, err = ethapi.RPCMarshalBlock(block, true, true, api.eth.APIBackend.ChainConfig()); err != nil { + if blockJSON, err = ethapi.RPCMarshalBlock(ctx, block, true, true, api.eth.APIBackend.ChainConfig(), api.eth.APIBackend); err != nil { blockJSON = map[string]interface{}{"error": err.Error()} } results = append(results, &BadBlockArgs{ @@ -346,6 +349,9 @@ // If we're dumping the pending state, we need to request // both the pending block as well as the pending state from // the miner and operate on those _, stateDb = api.eth.miner.Pending() + if stateDb == nil { + return state.IteratorDump{}, errors.New("no pending state") + } } else { var header *types.Header if number == rpc.LatestBlockNumber {
diff --git go-ethereum/eth/gasprice/gasprice.go op-geth/eth/gasprice/gasprice.go index a3dd83d79fb20f25ad005189c2350472353999f1..41d6bf38b1a91bbd335897cde47847b3526cce44 100644 --- go-ethereum/eth/gasprice/gasprice.go +++ op-geth/eth/gasprice/gasprice.go @@ -37,6 +37,8 @@ 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 )   type Config struct { @@ -47,6 +49,8 @@ MaxBlockHistory uint64 Default *big.Int `toml:",omitempty"` MaxPrice *big.Int `toml:",omitempty"` IgnorePrice *big.Int `toml:",omitempty"` + + MinSuggestedPriorityFee *big.Int `toml:",omitempty"` // for Optimism fee suggestion }   // OracleBackend includes all necessary background APIs for oracle. @@ -74,6 +78,8 @@ checkBlocks, percentile int maxHeaderHistory, maxBlockHistory uint64   historyCache *lru.Cache[cacheKey, processedFees] + + minSuggestedPriorityFee *big.Int // for Optimism fee suggestion }   // NewOracle returns a new gasprice oracle which can recommend suitable @@ -128,7 +134,7 @@ lastHead = ev.Block.Hash() } }()   - return &Oracle{ + r := &Oracle{ backend: backend, lastPrice: params.Default, maxPrice: maxPrice, @@ -139,6 +145,17 @@ maxHeaderHistory: maxHeaderHistory, maxBlockHistory: maxBlockHistory, historyCache: cache, } + + if backend.ChainConfig().IsOptimism() { + r.minSuggestedPriorityFee = params.MinSuggestedPriorityFee + if r.minSuggestedPriorityFee == nil || r.minSuggestedPriorityFee.Int64() <= 0 { + r.minSuggestedPriorityFee = DefaultMinSuggestedPriorityFee + log.Warn("Sanitizing invalid optimism gasprice oracle min priority fee suggestion", + "provided", params.MinSuggestedPriorityFee, + "updated", r.minSuggestedPriorityFee) + } + } + return r }   // SuggestTipCap returns a tip cap so that newly created transaction can have a @@ -168,6 +185,11 @@ oracle.cacheLock.RUnlock() if headHash == lastHead { return new(big.Int).Set(lastPrice), nil } + + if oracle.backend.ChainConfig().IsOptimism() { + return oracle.SuggestOptimismPriorityFee(ctx, head, headHash), nil + } + var ( sent, exp int number = head.Number.Uint64()
diff --git go-ethereum/eth/gasprice/optimism-gasprice.go op-geth/eth/gasprice/optimism-gasprice.go new file mode 100644 index 0000000000000000000000000000000000000000..71cd021f637f92e4b6d99ec5fc337d27edf547e5 --- /dev/null +++ op-geth/eth/gasprice/optimism-gasprice.go @@ -0,0 +1,110 @@ +package gasprice + +import ( + "context" + "math/big" + "sort" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" +) + +// SuggestOptimismPriorityFee returns a max priority fee value that can be used such that newly +// created transactions have a very high chance to be included in the following blocks, using a +// simplified and more predictable algorithm appropriate for chains like Optimism with a single +// known block builder. +// +// In the typical case, which results whenever the last block had room for more transactions, this +// function returns a minimum suggested priority fee value. Otherwise it returns the higher of this +// minimum suggestion or 10% over the median effective priority fee from the last block. +// +// Rationale: For a chain such as Optimism where there is a single block builder whose behavior is +// known, we know priority fee (as long as it is non-zero) has no impact on the probability for tx +// inclusion as long as there is capacity for it in the block. In this case then, there's no reason +// to return any value higher than some fixed minimum. Blocks typically reach capacity only under +// extreme events such as airdrops, meaning predicting whether the next block is going to be at +// capacity is difficult *except* in the case where we're already experiencing the increased demand +// from such an event. We therefore expect whether the last known block is at capacity to be one of +// the best predictors of whether the next block is likely to be at capacity. (An even better +// predictor is to look at the state of the transaction pool, but we want an algorithm that works +// even if the txpool is private or unavailable.) +// +// In the event the next block may be at capacity, the algorithm should allow for average fees to +// rise in order to reach a market price that appropriately reflects demand. We accomplish this by +// returning a suggestion that is a significant amount (10%) higher than the median effective +// priority fee from the previous block. +func (oracle *Oracle) SuggestOptimismPriorityFee(ctx context.Context, h *types.Header, headHash common.Hash) *big.Int { + suggestion := new(big.Int).Set(oracle.minSuggestedPriorityFee) + + // find the maximum gas used by any of the transactions in the block to use as the capacity + // margin + receipts, err := oracle.backend.GetReceipts(ctx, headHash) + if receipts == nil || err != nil { + log.Error("failed to get block receipts", "err", err) + return suggestion + } + var maxTxGasUsed uint64 + for i := range receipts { + gu := receipts[i].GasUsed + if gu > maxTxGasUsed { + maxTxGasUsed = gu + } + } + + // sanity check the max gas used value + if maxTxGasUsed > h.GasLimit { + log.Error("found tx consuming more gas than the block limit", "gas", maxTxGasUsed) + return suggestion + } + + if h.GasUsed+maxTxGasUsed > h.GasLimit { + // A block is "at capacity" if, when it is built, there is a pending tx in the txpool that + // could not be included because the block's gas limit would be exceeded. Since we don't + // have access to the txpool, we instead adopt the following heuristic: consider a block as + // at capacity if the total gas consumed by its transactions is within max-tx-gas-used of + // the block limit, where max-tx-gas-used is the most gas used by any one transaction + // within the block. This heuristic is almost perfectly accurate when transactions always + // consume the same amount of gas, but becomes less accurate as tx gas consumption begins + // to vary. The typical error is we assume a block is at capacity when it was not because + // max-tx-gas-used will in most cases over-estimate the "capacity margin". But it's better + // to err on the side of returning a higher-than-needed suggestion than a lower-than-needed + // one in order to satisfy our desire for high chance of inclusion and rising fees under + // high demand. + block, err := oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(h.Number.Int64())) + if block == nil || err != nil { + log.Error("failed to get last block", "err", err) + return suggestion + } + baseFee := block.BaseFee() + txs := block.Transactions() + if len(txs) == 0 { + log.Error("block was at capacity but doesn't have transactions") + return suggestion + } + tips := bigIntArray(make([]*big.Int, len(txs))) + for i := range txs { + tips[i] = txs[i].EffectiveGasTipValue(baseFee) + } + sort.Sort(tips) + median := tips[len(tips)/2] + newSuggestion := new(big.Int).Add(median, new(big.Int).Div(median, big.NewInt(10))) + // use the new suggestion only if it's bigger than the minimum + if newSuggestion.Cmp(suggestion) > 0 { + suggestion = newSuggestion + } + } + + // the suggestion should be capped by oracle.maxPrice + if suggestion.Cmp(oracle.maxPrice) > 0 { + suggestion.Set(oracle.maxPrice) + } + + oracle.cacheLock.Lock() + oracle.lastHead = headHash + oracle.lastPrice = suggestion + oracle.cacheLock.Unlock() + + return new(big.Int).Set(suggestion) +}
diff --git go-ethereum/eth/gasprice/optimism-gasprice_test.go op-geth/eth/gasprice/optimism-gasprice_test.go new file mode 100644 index 0000000000000000000000000000000000000000..dd0140839d91e45be16d06faae9b714ad53265fa --- /dev/null +++ op-geth/eth/gasprice/optimism-gasprice_test.go @@ -0,0 +1,142 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. + +package gasprice + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/trie" +) + +const ( + blockGasLimit = params.TxGas * 3 +) + +type testTxData struct { + priorityFee int64 + gasLimit uint64 +} + +type opTestBackend struct { + block *types.Block + receipts []*types.Receipt +} + +func (b *opTestBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { + panic("not implemented") +} + +func (b *opTestBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { + return b.block, nil +} + +func (b *opTestBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { + return b.receipts, nil +} + +func (b *opTestBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { + panic("not implemented") +} + +func (b *opTestBackend) ChainConfig() *params.ChainConfig { + return params.OptimismTestConfig +} + +func (b *opTestBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { + return nil +} + +func newOpTestBackend(t *testing.T, txs []testTxData) *opTestBackend { + var ( + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + signer = types.LatestSigner(params.TestChainConfig) + ) + // only the most recent block is considered for optimism priority fee suggestions, so this is + // where we add the test transactions + ts := []*types.Transaction{} + rs := []*types.Receipt{} + header := types.Header{} + header.GasLimit = blockGasLimit + var nonce uint64 + for _, tx := range txs { + txdata := &types.DynamicFeeTx{ + ChainID: params.TestChainConfig.ChainID, + Nonce: nonce, + To: &common.Address{}, + Gas: params.TxGas, + GasFeeCap: big.NewInt(100 * params.GWei), + GasTipCap: big.NewInt(tx.priorityFee), + Data: []byte{}, + } + t := types.MustSignNewTx(key, signer, txdata) + ts = append(ts, t) + r := types.Receipt{} + r.GasUsed = tx.gasLimit + header.GasUsed += r.GasUsed + rs = append(rs, &r) + nonce++ + } + hasher := trie.NewStackTrie(nil) + b := types.NewBlock(&header, ts, nil, nil, hasher) + return &opTestBackend{block: b, receipts: rs} +} + +func TestSuggestOptimismPriorityFee(t *testing.T) { + minSuggestion := new(big.Int).SetUint64(1e8 * params.Wei) + var cases = []struct { + txdata []testTxData + want *big.Int + }{ + { + // block well under capacity, expect min priority fee suggestion + txdata: []testTxData{testTxData{params.GWei, 21000}}, + want: minSuggestion, + }, + { + // 2 txs, still under capacity, expect min priority fee suggestion + txdata: []testTxData{testTxData{params.GWei, 21000}, testTxData{params.GWei, 21000}}, + want: minSuggestion, + }, + { + // 2 txs w same priority fee (1 gwei), but second tx puts it right over capacity + txdata: []testTxData{testTxData{params.GWei, 21000}, testTxData{params.GWei, 21001}}, + want: big.NewInt(1100000000), // 10 percent over 1 gwei, the median + }, + { + // 3 txs, full block. return 10% over the median tx (10 gwei * 10% == 11 gwei) + txdata: []testTxData{testTxData{10 * params.GWei, 21000}, testTxData{1 * params.GWei, 21000}, testTxData{100 * params.GWei, 21000}}, + want: big.NewInt(11 * params.GWei), + }, + } + for i, c := range cases { + backend := newOpTestBackend(t, c.txdata) + oracle := NewOracle(backend, Config{MinSuggestedPriorityFee: minSuggestion}) + got := oracle.SuggestOptimismPriorityFee(context.Background(), backend.block.Header(), backend.block.Hash()) + if got.Cmp(c.want) != 0 { + t.Errorf("Gas price mismatch for test case %d: want %d, got %d", i, c.want, got) + } + } +}
diff --git go-ethereum/eth/protocols/snap/handler.go op-geth/eth/protocols/snap/handler.go index 55781ac54b74c1b9d111548a83feac79d385e37b..c57ac5e2e386c56c6775265c54cd5facbf18a215 100644 --- go-ethereum/eth/protocols/snap/handler.go +++ op-geth/eth/protocols/snap/handler.go @@ -469,7 +469,7 @@ if hash == types.EmptyCodeHash { // Peers should not request the empty code, but if they do, at // least sent them back a correct response without db lookups codes = append(codes, []byte{}) - } else if blob, err := chain.ContractCodeWithPrefix(hash); err == nil { + } else if blob, err := chain.ContractCode(hash); err == nil { codes = append(codes, blob) bytes += uint64(len(blob)) }
diff --git go-ethereum/light/odr_test.go op-geth/light/odr_test.go index 0df1fbf2f5b29c12c5b80e0fca4372b6dec17d07..6d58f9aab8bf319379786dba02b58e71ce1c0545 100644 --- go-ethereum/light/odr_test.go +++ op-geth/light/odr_test.go @@ -212,7 +212,7 @@ Data: data, SkipAccountChecks: true, } txContext := core.NewEVMTxContext(msg) - context := core.NewEVMBlockContext(header, chain, nil) + context := core.NewEVMBlockContext(header, chain, nil, config, st) vmenv := vm.NewEVM(context, txContext, st, config, vm.Config{NoBaseFee: true}) gp := new(core.GasPool).AddGas(math.MaxUint64) result, _ := core.ApplyMessage(vmenv, msg, gp)
diff --git go-ethereum/p2p/server.go op-geth/p2p/server.go index f7bf948b6901249e49e06c04e08597e16b6ea260..6aa5d29d8ee301b7a77b8b6812a38d0d4a8ef7dd 100644 --- go-ethereum/p2p/server.go +++ op-geth/p2p/server.go @@ -621,6 +621,7 @@ } if err != nil { return err } + srv.discmix.AddSource(srv.DiscV5.RandomNodes()) } return nil }
diff --git go-ethereum/params/config_test.go op-geth/params/config_test.go index bf8ce2fc5e247242615ff478a455785f9f07f88b..14d7f833bb978c2bb7cefc70cff2015079c9a04a 100644 --- go-ethereum/params/config_test.go +++ op-geth/params/config_test.go @@ -137,3 +137,22 @@ if r := c.Rules(big.NewInt(0), true, stamp); !r.IsShanghai { t.Errorf("expected %v to be shanghai", stamp) } } + +func TestConfigRulesRegolith(t *testing.T) { + c := &ChainConfig{ + RegolithTime: newUint64(500), + Optimism: &OptimismConfig{}, + } + var stamp uint64 + if r := c.Rules(big.NewInt(0), true, stamp); r.IsOptimismRegolith { + t.Errorf("expected %v to not be regolith", stamp) + } + stamp = 500 + if r := c.Rules(big.NewInt(0), true, stamp); !r.IsOptimismRegolith { + t.Errorf("expected %v to be regolith", stamp) + } + stamp = math.MaxInt64 + if r := c.Rules(big.NewInt(0), true, stamp); !r.IsOptimismRegolith { + t.Errorf("expected %v to be regolith", stamp) + } +}
diff --git go-ethereum/params/superchain.go op-geth/params/superchain.go new file mode 100644 index 0000000000000000000000000000000000000000..f46c4eb0451d8660eaf4ed56875dd789b5c400aa --- /dev/null +++ op-geth/params/superchain.go @@ -0,0 +1,96 @@ +package params + +import ( + "fmt" + "math/big" + + "github.com/ethereum-optimism/superchain-registry/superchain" + "github.com/ethereum/go-ethereum/common" +) + +func init() { + for id, ch := range superchain.OPChains { + NetworkNames[fmt.Sprintf("%d", id)] = ch.Name + } +} + +func OPStackChainIDByName(name string) (uint64, error) { + for id, ch := range superchain.OPChains { + if ch.Chain+"-"+ch.Superchain == name { + return id, nil + } + } + return 0, fmt.Errorf("unknown chain %q", name) +} + +func LoadOPStackChainConfig(chainID uint64) (*ChainConfig, error) { + chConfig, ok := superchain.OPChains[chainID] + if !ok { + return nil, fmt.Errorf("unknown chain ID: %d", chainID) + } + superchainConfig, ok := superchain.Superchains[chConfig.Superchain] + if !ok { + return nil, fmt.Errorf("unknown superchain %q", chConfig.Superchain) + } + + genesisActivation := uint64(0) + out := &ChainConfig{ + ChainID: new(big.Int).SetUint64(chainID), + HomesteadBlock: common.Big0, + DAOForkBlock: nil, + DAOForkSupport: false, + EIP150Block: common.Big0, + EIP155Block: common.Big0, + EIP158Block: common.Big0, + ByzantiumBlock: common.Big0, + ConstantinopleBlock: common.Big0, + PetersburgBlock: common.Big0, + IstanbulBlock: common.Big0, + MuirGlacierBlock: common.Big0, + BerlinBlock: common.Big0, + LondonBlock: common.Big0, + ArrowGlacierBlock: common.Big0, + GrayGlacierBlock: common.Big0, + MergeNetsplitBlock: common.Big0, + ShanghaiTime: nil, + CancunTime: nil, + PragueTime: nil, + BedrockBlock: common.Big0, + RegolithTime: &genesisActivation, + TerminalTotalDifficulty: common.Big0, + TerminalTotalDifficultyPassed: true, + Ethash: nil, + Clique: nil, + Optimism: &OptimismConfig{ + EIP1559Elasticity: 6, + EIP1559Denominator: 50, + }, + } + + // note: no actual parameters are being loaded, yet. + // Future superchain upgrades are loaded from the superchain chConfig and applied to the geth ChainConfig here. + _ = superchainConfig.Config + + // special overrides for OP-Stack chains with pre-Regolith upgrade history + switch chainID { + case OPGoerliChainID: + out.LondonBlock = big.NewInt(4061224) + out.ArrowGlacierBlock = big.NewInt(4061224) + out.GrayGlacierBlock = big.NewInt(4061224) + out.MergeNetsplitBlock = big.NewInt(4061224) + out.BedrockBlock = big.NewInt(4061224) + out.RegolithTime = &OptimismGoerliRegolithTime + out.Optimism.EIP1559Elasticity = 10 + case OPMainnetChainID: + out.BerlinBlock = big.NewInt(3950000) + out.LondonBlock = big.NewInt(105235063) + out.ArrowGlacierBlock = big.NewInt(105235063) + out.GrayGlacierBlock = big.NewInt(105235063) + out.MergeNetsplitBlock = big.NewInt(105235063) + out.BedrockBlock = big.NewInt(105235063) + case BaseGoerliChainID: + out.RegolithTime = &BaseGoerliRegolithTime + } + + return out, nil +}
diff --git go-ethereum/tests/state_test.go op-geth/tests/state_test.go index 9d3862e1dc7c2c243982dd08808d487f803e3779..2f54ccdbc74f6d7210ae1a32d777ad469d3604cf 100644 --- go-ethereum/tests/state_test.go +++ op-geth/tests/state_test.go @@ -221,7 +221,7 @@ }   // Prepare the EVM. txContext := core.NewEVMTxContext(msg) - context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase) + context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase, config, statedb) context.GetHash = vmTestBlockHash context.BaseFee = baseFee evm := vm.NewEVM(context, txContext, statedb, config, vmconfig)
diff --git go-ethereum/tests/state_test_util.go op-geth/tests/state_test_util.go index eec91b67a7d461b0264ab4347c6407993d71379e..83b68da5769ca3b14ea2bf1675281a97ca9a8b62 100644 --- go-ethereum/tests/state_test_util.go +++ op-geth/tests/state_test_util.go @@ -247,7 +247,7 @@ }   // Prepare the EVM. txContext := core.NewEVMTxContext(msg) - context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase) + context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase, config, statedb) context.GetHash = vmTestBlockHash context.BaseFee = baseFee context.Random = nil
diff --git go-ethereum/.circleci/check-releases.sh op-geth/.circleci/check-releases.sh new file mode 100755 index 0000000000000000000000000000000000000000..f3595e1320377bea4b17c13326d87342083071c0 --- /dev/null +++ op-geth/.circleci/check-releases.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -euo pipefail + +LATEST_RELEASE=$(curl -s --fail -L \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/ethereum/go-ethereum/releases \ + | jq -r '(.[] | select(.draft==false) | select(.prerelease==false)).tag_name' | head -n 1) + +echo "Detected latest go-ethereum release as ${LATEST_RELEASE}" + +git remote add upstream https://github.com/ethereum/go-ethereum +git fetch upstream > /dev/null + +if git branch --contains "${LATEST_RELEASE}" 2>&1 | grep -e '^[ *]*optimism$' > /dev/null +then + echo "Up to date with latest release. Great job! 🎉" +else + echo "Release has not been merged" + exit 1 +fi
diff --git go-ethereum/.circleci/ci-docker-tag-op-geth-release.sh op-geth/.circleci/ci-docker-tag-op-geth-release.sh new file mode 100755 index 0000000000000000000000000000000000000000..7b66e789a4d7e50699f2d22ff037117c0d7a3a35 --- /dev/null +++ op-geth/.circleci/ci-docker-tag-op-geth-release.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +set -euo pipefail + +DOCKER_REPO=$1 +GIT_TAG=$2 +GIT_SHA=$3 + +IMAGE_NAME="op-geth" +IMAGE_TAG=$GIT_TAG + +SOURCE_IMAGE_TAG="$DOCKER_REPO/$IMAGE_NAME:$GIT_SHA" +TARGET_IMAGE_TAG="$DOCKER_REPO/$IMAGE_NAME:$IMAGE_TAG" +TARGET_IMAGE_TAG_LATEST="$DOCKER_REPO/$IMAGE_NAME:latest" + +echo "Checking if docker images exist for '$IMAGE_NAME'" +echo "" +tags=$(gcloud container images list-tags "$DOCKER_REPO/$IMAGE_NAME" --limit 1 --format json) +if [ "$tags" = "[]" ]; then + echo "No existing docker images were found for '$IMAGE_NAME'. The code tagged with '$GIT_TAG' may not have an associated dockerfile or docker build job." + echo "If this service has a dockerfile, add a docker-publish job for it in the circleci config." + echo "" + echo "Exiting" + exit 0 +fi + +echo "Tagging $SOURCE_IMAGE_TAG with '$IMAGE_TAG'" +gcloud container images add-tag -q "$SOURCE_IMAGE_TAG" "$TARGET_IMAGE_TAG" + +# Do not tag with latest if the release is a release candidate. +if [[ "$IMAGE_TAG" == *"rc"* ]]; then + echo "Not tagging with 'latest' because the release is a release candidate." + exit 0 +fi + +echo "Tagging $SOURCE_IMAGE_TAG with 'latest'" +gcloud container images add-tag -q "$SOURCE_IMAGE_TAG" "$TARGET_IMAGE_TAG_LATEST" +
diff --git go-ethereum/.circleci/config.yml op-geth/.circleci/config.yml new file mode 100644 index 0000000000000000000000000000000000000000..0c9b7cda88004459f3635bc7e86704fc3df4f898 --- /dev/null +++ op-geth/.circleci/config.yml @@ -0,0 +1,198 @@ +version: 2.1 + +orbs: + gcp-cli: circleci/gcp-cli@3.0.1 + slack: circleci/slack@4.10.1 + +commands: + gcp-oidc-authenticate: + description: "Authenticate with GCP using a CircleCI OIDC token." + parameters: + project_id: + type: env_var_name + default: GCP_PROJECT_ID + workload_identity_pool_id: + type: env_var_name + default: GCP_WIP_ID + workload_identity_pool_provider_id: + type: env_var_name + default: GCP_WIP_PROVIDER_ID + service_account_email: + type: env_var_name + default: GCP_SERVICE_ACCOUNT_EMAIL + gcp_cred_config_file_path: + type: string + default: /home/circleci/gcp_cred_config.json + oidc_token_file_path: + type: string + default: /home/circleci/oidc_token.json + steps: + - run: + name: "Create OIDC credential configuration" + command: | + # Store OIDC token in temp file + echo $CIRCLE_OIDC_TOKEN > << parameters.oidc_token_file_path >> + # Create a credential configuration for the generated OIDC ID Token + gcloud iam workload-identity-pools create-cred-config \ + "projects/${<< parameters.project_id >>}/locations/global/workloadIdentityPools/${<< parameters.workload_identity_pool_id >>}/providers/${<< parameters.workload_identity_pool_provider_id >>}"\ + --output-file="<< parameters.gcp_cred_config_file_path >>" \ + --service-account="${<< parameters.service_account_email >>}" \ + --credential-source-file=<< parameters.oidc_token_file_path >> + - run: + name: "Authenticate with GCP using OIDC" + command: | + # Configure gcloud to leverage the generated credential configuration + gcloud auth login --brief --cred-file "<< parameters.gcp_cred_config_file_path >>" + # Configure ADC + echo "export GOOGLE_APPLICATION_CREDENTIALS='<< parameters.gcp_cred_config_file_path >>'" | tee -a "$BASH_ENV" + +jobs: + docker-release: + environment: + DOCKER_BUILDKIT: 1 + parameters: + docker_name: + description: Docker image name + type: string + default: "op-geth" + docker_tags: + description: Docker image tags as csv + type: string + registry: + description: Docker registry + type: string + default: "us-docker.pkg.dev" + repo: + description: Docker repo + type: string + default: "oplabs-tools-artifacts/images" + push_tags: + description: Push release push tags + type: boolean + default: false + machine: + image: ubuntu-2204:2022.07.1 + resource_class: xlarge + steps: + - gcp-cli/install + - gcp-oidc-authenticate + - checkout + - run: + name: Configure Docker + command: | + gcloud auth configure-docker <<parameters.registry>> + - run: + name: Build and push + command: | + RAW_TAGS="<<parameters.docker_tags>>" + if [ "$CIRCLE_BRANCH" = "optimism" ]; then + RAW_TAGS="$RAW_TAGS,optimism" + fi + IMAGE_BASE="<<parameters.registry>>/<<parameters.repo>>/<<parameters.docker_name>>" + DOCKER_TAGS=$(echo -ne "$RAW_TAGS" | sed "s/,/\n/g" | sed "s/[^a-zA-Z0-9\n.]/-/g" | sed -e "s|^|-t ${IMAGE_BASE}:|") + docker context create buildx-build + docker buildx create --use buildx-build + docker buildx build --push \ + $(echo -ne $DOCKER_TAGS | tr '\n' ' ') \ + --platform=linux/arm64,linux/amd64 \ + --build-arg VERSION=$CIRCLE_TAG \ + --build-arg COMMIT=$CIRCLE_SHA \ + --build-arg BUILDNUM=$CIRCLE_BUILD_NUM \ + --progress plain \ + -f Dockerfile . + - when: + condition: + equal: [ true, <<parameters.push_tags>> ] + steps: + - run: + name: Tag + command: | + ./.circleci/ci-docker-tag-op-geth-release.sh <<parameters.registry>>/<<parameters.repo>> $CIRCLE_TAG $CIRCLE_SHA1 + + + build-geth: + docker: + - image: cimg/go:1.19 + resource_class: xlarge + steps: + - checkout + - run: + command: go run build/ci.go install + unit-test: + resource_class: xlarge + docker: + - image: cimg/go:1.19 + steps: + - checkout + - run: + command: go run build/ci.go test + lint-geth: + resource_class: medium + docker: + - image: cimg/go:1.19 + steps: + - checkout + - run: + command: go run build/ci.go lint + check-releases: + docker: + - image: cimg/go:1.19 + steps: + - checkout + - run: + command: .circleci/check-releases.sh + - slack/notify: + channel: C03N11M0BBN + branch_pattern: optimism + event: fail + template: basic_fail_1 + + +workflows: + main: + jobs: + - build-geth: + name: Build geth + - unit-test: + name: Run unit tests for geth + - lint-geth: + name: Run linter over geth + - docker-release: + name: Push to Docker + docker_tags: <<pipeline.git.revision>> + context: + - oplabs-gcr + release: + jobs: + - hold: + type: approval + filters: + tags: + only: /^v.*/ + branches: + ignore: /.*/ + - docker-release: + name: Push to Docker (release) + filters: + tags: + only: /^v.*/ + branches: + ignore: /.*/ + docker_tags: <<pipeline.git.revision>>,<<pipeline.git.tag>> + push_tags: true + context: + - oplabs-gcr-release + requires: + - hold + scheduled: + triggers: + - schedule: + # run daily + cron: "0 0 * * *" + filters: + branches: + only: [ "optimism" ] + jobs: + - check-releases: + name: Check for new upstream releases + context: slack
diff --git go-ethereum/.github/workflows/pages.yaml op-geth/.github/workflows/pages.yaml new file mode 100644 index 0000000000000000000000000000000000000000..6973b227ce779be6138ee2084e71bb5a5cdd57de --- /dev/null +++ op-geth/.github/workflows/pages.yaml @@ -0,0 +1,36 @@ +name: Build and publish forkdiff github-pages +permissions: + contents: write +on: + push: + branches: + - optimism +jobs: + deploy: + concurrency: ci-${{ github.ref }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 1000 # make sure to fetch the old commit we diff against + + - name: Build forkdiff + uses: "docker://protolambda/forkdiff:latest" + with: + args: -repo=/github/workspace -fork=/github/workspace/fork.yaml -out=/github/workspace/index.html + + - name: Build pages + run: | + mkdir -p tmp/pages + mv index.html tmp/pages/index.html + touch tmp/pages/.nojekyll + if [ "$GITHUB_REPOSITORY" == "ethereum-optimism/op-geth" ]; then + echo "op-geth.optimism.io" > tmp/pages/CNAME + fi; + + - name: Deploy + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: tmp/pages + clean: true
(new)
+203
-0
diff --git go-ethereum/fork.yaml op-geth/fork.yaml new file mode 100644 index 0000000000000000000000000000000000000000..061e8fd7c71840a05e60b911fb5dbe548a1839fb --- /dev/null +++ op-geth/fork.yaml @@ -0,0 +1,203 @@ +title: "op-geth - go-ethereum fork diff overview" +footer: | + Fork-diff overview of [`op-geth`](https://github.com/ethereum-optimism/op-geth), a fork of [`go-ethereum`](https://github.com/ethereum/go-ethereum). + and execution-engine of the [OP-stack](https://github.com/ethereum-optimism/optimism). +base: + name: go-ethereum + url: https://github.com/ethereum/go-ethereum + hash: e501b3b05db8e169f67dc78b7b59bc352b3c638d +fork: + name: op-geth + url: https://github.com/ethereum-optimism/op-geth + ref: refs/heads/optimism +def: + title: "op-geth" + description: | + This is an overview of the changes in [`op-geth`](https://github.com/ethereum-optimism/op-geth), + a fork of [`go-ethereum`](https://github.com/ethereum/go-ethereum), part of the OP-stack. + + The OP-stack architecture is modular, following the Consensus/Execution split of post-Merge Ethereum L1: + + - [`op-node`](https://github.com/ethereum-optimism/optimism/tree/develop/op-node) implements most rollup-specific functionality as Consensus-Layer, similar to a L1 beacon-node. + - [`op-geth`](https://github.com/ethereum-optimism/op-geth) implements the Execution-Layer, with **minimal changes** for a secure Ethereum-equivalent application environment. + + Related [op-stack specifications](https://github.com/ethereum-optimism/optimism/tree/develop/specs): + + - [L2 Execution Engine spec](https://github.com/ethereum-optimism/optimism/blob/develop/specs/exec-engine.md) + - [Deposit Transaction spec](https://github.com/ethereum-optimism/optimism/blob/develop/specs/deposits.md) + sub: + - title: "Core modifications" + sub: + - title: "State-transition modifications" + description: "" + sub: + - title: "Deposit Transaction type" + description: | + The Bedrock upgrade introduces a `Deposit` transaction-type (`0x7E`) to enable both users and the + rollup system itself to change the L2 state based on L1 events and system rules as + [specified](https://github.com/ethereum-optimism/optimism/blob/develop/specs/deposits.md). + globs: + - "core/types/deposit_tx.go" + - "core/types/transaction_marshalling.go" + - "core/types/transaction_signing.go" + - title: "Transaction properties" + description: | + The `Transaction` type now exposes the deposit-transaction and L1-cost properties required for the rollup. + globs: + - "core/types/transaction.go" + - "core/types/tx_access_list.go" + - "core/types/tx_dynamic_fee.go" + - "core/types/tx_legacy.go" + - title: "L1 cost computation" + description: | + Transactions must pay an additional L1 cost based on the amount of rollup-data-gas they consume, + estimated based on gas-price-oracle information and encoded tx size." + globs: + - "core/vm/evm.go" + - "core/types/rollup_l1_cost.go" + - "core/state_processor.go" + - "core/state_prefetcher.go" + - title: Transaction processing + description: | + Deposit transactions have special processing rules: gas is pre-paid on L1, + and deposits with EVM-failure are included with rolled back changes (except mint). + For regular transactions, at the end of the transition, the 1559 burn and L1 cost are routed to vaults. + globs: + - "core/state_transition.go" + - title: "Gaslimit" + description: | + The gaslimit is free to be set by the Engine API caller, instead of enforcing adjustments of the + gaslimit in increments of 1/1024 of the previous gaslimit. + The gaslimit is changed (and limited) through the `SystemConfig` contract. + globs: + - "consensus/misc/eip1559.go" + - title: "Consensus tweaks" + description: | + The Engine API is activated at the Merge transition, with a Total Terminal Difficulty (TTD). + The rollup starts post-merge, and thus sets the TTD to 0. + globs: + - "consensus/beacon/consensus.go" + - title: "Chain config" + description: | + The rollup functionality is enabled with the `optimism` field in the chain config. + The EIP-1559 parameters are configurable to adjust for faster more frequent and smaller blocks. + The parameters can be overriden for testing. + globs: + - "params/config.go" + - "params/protocol_params.go" + - "core/genesis.go" + - title: "Chain config cleanup" + description: | + The optimism Goerli testnet used clique-config data to make geth internals accept blocks. + Post-bedrock the beacon-consensus (i.e. follow Engine API) is now used, and the clique config is removed. + globs: + - "core/rawdb/accessors_metadata.go" + - title: "Engine API modifications" + description: | + The Engine API is extended to insert transactions into the block and optionally exclude the tx-pool, + to reproduce the exact block of the sequencer from just the inputs, as derived from L1 by the rollup-node. + See [L2 execution engine specs](https://github.com/ethereum-optimism/optimism/blob/develop/specs/exec-engine.md). + globs: + - "beacon/engine/types.go" + - "beacon/engine/gen_blockparams.go" + - "eth/catalyst/api.go" + - title: "Block-building modifications" + description: | + The block-building code (in the "miner" package because of Proof-Of-Work legacy of ethereum) implements the + changes to support the transaction-inclusion, tx-pool toggle and gaslimit parameters of the Engine API. + globs: + - "miner/*" + - title: "Tx-pool tx cost updates" + description: | + Transaction queueing and inclusion needs to account for the L1 cost component. + globs: + - "core/txpool/*" + - title: "Node modifications" + description: Changes to the node configuration and services. + sub: + - title: "CLI" + sub: + - title: "Flags" + description: | + Flag changes: + - Transactions can be forwarded to an RPC for sequencing. + - Historical calls can be forwarded to a legacy node. + - The tx pool propagation can be enabled/disabled. + - The Optimism bedrock fork activation can be changed for testing. + globs: + - "cmd/utils/flags.go" + - "cmd/geth/main.go" + - "internal/flags/categories.go" + - "cmd/geth/config.go" + - title: "Versioning" + description: List the op-geth and upstream go-ethereum versions. + globs: + - "cmd/geth/misccmd.go" + - "params/version.go" + - "build/ci.go" + - title: Node config + globs: + - "eth/ethconfig/config.go" + - title: Tx gossip disable option + globs: + - "eth/handler.go" + - "eth/handler_eth.go" + - title: "User API enhancements" + description: "Encode the Deposit Tx properties, the L1 costs, and daisy-chain RPC-calls for pre-Bedrock historical data" + sub: + - title: "Receipts metadata" + description: | + Pre-Bedrock L1-cost receipt data is loaded from the database if available, and post-Bedrock the L1-cost + metadata is hydrated on-the-fly based on the L1 fee information in the corresponding block. + globs: + - "core/types/receipt.go" + - "core/types/gen_receipt_json.go" + - title: "API Backend" + description: | + Forward transactions to the sequencer if configured. + globs: + - "eth/api_backend.go" + - "eth/backend.go" + - "internal/ethapi/backend.go" + - title: "Apply L1 cost in API responses" + globs: + - "eth/state_accessor.go" + - title: API frontend + description: Format deposit and L1-cost data in transaction responses. Add `debug_chainConfig` API. + globs: + - "internal/ethapi/api.go" + - "rpc/errors.go" + - title: Tracer RPC daisy-chain + description: Forward pre-bedrock tracing calls to legacy node. + globs: + - "eth/tracers/api.go" + - title: "Light Ethereum Subprotocol (LES) RPC" + description: Match the RPC changes in the LES RPC + globs: + - "les/*" + - title: "Daisy Chain tests" + globs: + - "internal/ethapi/transaction_args_test.go" + - "ethclient/ethclient_test.go" + - "eth/tracers/api_test.go" + - title: "Geth extras" + description: Extend the tools available in geth to improve external testing and tooling. + sub: + - title: Simulated Backend + globs: + - "accounts/abi/bind/backends/simulated.go" + - title: diff-included testing testing + description: | + Most of the op-geth changes are tested in the Optimism Monorepo and not part of the geth diff, + but some testing like the Deposit TX encoding and API interactions are embedded in the op-geth diff instead. + globs: + - "core/types/transaction_marshalling_test.go" + - "internal/ethapi/api_test.go" + +# ignored globally, does not count towards line count +ignore: + - ".circleci/*" + - "*.sum" + - "go.mod" + - "fork.yaml" + - ".github/workflows/*"
diff --git go-ethereum/go.mod op-geth/go.mod index ef4ea6ee74086e53704b77facbeb8642416051bd..22d85d04083e34c00f2290410b2f9c57d3764693 100644 --- go-ethereum/go.mod +++ op-geth/go.mod @@ -19,6 +19,7 @@ github.com/davecgh/go-spew v1.1.1 github.com/deckarep/golang-set/v2 v2.1.0 github.com/docker/docker v1.6.2 github.com/dop251/goja v0.0.0-20230122112309-96b1610dd4f7 + github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20230817174831-5d3ca1966435 github.com/ethereum/c-kzg-4844 v0.2.0 github.com/fatih/color v1.7.0 github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e @@ -119,6 +120,7 @@ github.com/prometheus/common v0.39.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/stretchr/objx v0.5.0 // indirect github.com/tklauser/go-sysconf v0.3.5 // indirect github.com/tklauser/numcpus v0.2.2 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
diff --git go-ethereum/go.sum op-geth/go.sum index e02153096313fb634fac20c9488426d5f894e8f4..54d75d3b8ba3f3d2cbb42e5ae12d56aeaba6e51b 100644 --- go-ethereum/go.sum +++ op-geth/go.sum @@ -122,6 +122,8 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20230817174831-5d3ca1966435 h1:2CzkJkkTLuVyoVFkoW5w6vDB2Q7eJzxXw/ybA17xjqM= +github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20230817174831-5d3ca1966435/go.mod h1:v2YpePbdGBF0Gr6VWq49MFFmcTW0kRYZ2ingBJYWEwg= github.com/ethereum/c-kzg-4844 v0.2.0 h1:+cUvymlnoDDQgMInp25Bo3OmLajmmY8mLJ/tLjqd77Q= github.com/ethereum/c-kzg-4844 v0.2.0/go.mod h1:WI2Nd82DMZAAZI1wV2neKGost9EKjvbpQR9OqE5Qqa8= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= @@ -407,6 +409,7 @@ github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=