Skip to content

Commit

Permalink
Fix L1 cost function (ethereum#61)
Browse files Browse the repository at this point in the history
The gas price oracle calculates L1 data costs using an unsigned transaction. However, `op-geth` was calculating L1 data costs using a _signed_ transaction, which included an additional 68 unnecessary bytes (1088 gas) in the cost.

This PR removes those extra bytes as part of the Regolith hardfork.

Co-authored-by: protolambda <proto@protolambda.com>
  • Loading branch information
mslipper and protolambda authored Mar 4, 2023
1 parent 428dc6e commit cde9c26
Show file tree
Hide file tree
Showing 12 changed files with 96 additions and 43 deletions.
9 changes: 5 additions & 4 deletions accounts/abi/bind/backends/simulated.go
Original file line number Diff line number Diff line change
Expand Up @@ -903,10 +903,11 @@ func (m callMsg) Gas() uint64 { return m.CallMsg.Gas }
func (m callMsg) Value() *big.Int { return m.CallMsg.Value }
func (m callMsg) Data() []byte { return m.CallMsg.Data }
func (m callMsg) AccessList() types.AccessList { return m.CallMsg.AccessList }
func (m callMsg) IsSystemTx() bool { return false }
func (m callMsg) IsDepositTx() bool { return false }
func (m callMsg) Mint() *big.Int { return nil }
func (m callMsg) RollupDataGas() uint64 { return 0 }

func (m callMsg) IsSystemTx() bool { return false }
func (m callMsg) IsDepositTx() bool { return false }
func (m callMsg) Mint() *big.Int { return nil }
func (m callMsg) RollupDataGas() types.RollupGasData { return types.RollupGasData{} }

// filterBackend implements filters.Backend to support filtering for logs without
// taking bloom-bits acceleration structures into account.
Expand Down
2 changes: 1 addition & 1 deletion core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -2035,7 +2035,7 @@ func (bc *BlockChain) recoverAncestors(block *types.Block) (common.Hash, error)
// the processing of a block. These logs are later announced as deleted or reborn.
func (bc *BlockChain) collectLogs(b *types.Block, removed bool) []*types.Log {
receipts := rawdb.ReadRawReceipts(bc.db, b.Hash(), b.NumberU64())
receipts.DeriveFields(bc.chainConfig, b.Hash(), b.NumberU64(), b.Transactions())
receipts.DeriveFields(bc.chainConfig, b.Hash(), b.NumberU64(), b.Time(), b.Transactions())

var logs []*types.Log
for _, receipt := range receipts {
Expand Down
7 changes: 6 additions & 1 deletion core/rawdb/accessors_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,12 @@ func ReadReceipts(db ethdb.Reader, hash common.Hash, number uint64, config *para
log.Error("Missing body but have receipt", "hash", hash, "number", number)
return nil
}
if err := receipts.DeriveFields(config, hash, number, body.Transactions); err != nil {
header := ReadHeader(db, hash, number)
if header == nil {
log.Error("Missing header but have receipt", "hash", hash, "number", number)
return nil
}
if err := receipts.DeriveFields(config, hash, number, header.Time, body.Transactions); err != nil {
log.Error("Failed to derive block receipts fields", "hash", hash, "number", number, "err", err)
return nil
}
Expand Down
8 changes: 7 additions & 1 deletion core/rawdb/accessors_chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,9 @@ func TestBlockReceiptStorage(t *testing.T) {
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
Expand Down Expand Up @@ -378,13 +381,16 @@ func TestBlockReceiptStorage(t *testing.T) {
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, 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)
if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); len(rs) == 0 {
Expand Down
12 changes: 6 additions & 6 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ type Message interface {
Gas() uint64
Value() *big.Int

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() uint64 // RollupDataGas indicates the rollup cost of the message, 0 if not a rollup or no cost.
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.

Nonce() uint64
IsFake() bool
Expand Down Expand Up @@ -224,7 +224,7 @@ func (st *StateTransition) buyGas() error {
mgval = mgval.Mul(mgval, st.gasPrice)
var l1Cost *big.Int
if st.evm.Context.L1CostFunc != nil {
l1Cost = st.evm.Context.L1CostFunc(st.evm.Context.BlockNumber.Uint64(), st.msg)
l1Cost = st.evm.Context.L1CostFunc(st.evm.Context.BlockNumber.Uint64(), st.evm.Context.Time, st.msg)
}
if l1Cost != nil {
mgval = mgval.Add(mgval, l1Cost)
Expand Down Expand Up @@ -474,7 +474,7 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) {
// 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.msg); cost != nil {
if cost := st.evm.Context.L1CostFunc(st.evm.Context.BlockNumber.Uint64(), st.evm.Context.Time, st.msg); cost != nil {
st.state.AddBalance(params.OptimismL1FeeRecipient, cost)
}
}
Expand Down
2 changes: 1 addition & 1 deletion core/txpool/txpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -1326,7 +1326,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {

costFn := types.NewL1CostFunc(pool.chainconfig, statedb)
pool.l1CostFn = func(message types.RollupMessage) *big.Int {
return costFn(newHead.Number.Uint64(), message)
return costFn(newHead.Number.Uint64(), newHead.Time, message)
}

// Inject any transactions discarded due to reorgs
Expand Down
7 changes: 4 additions & 3 deletions core/types/receipt.go
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) {

// 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, txs Transactions) error {
func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, number uint64, time uint64, txs Transactions) error {
signer := MakeSigner(config, new(big.Int).SetUint64(number))

logIndex := uint(0)
Expand Down Expand Up @@ -526,9 +526,10 @@ func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, nu
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
rs[i].L1GasUsed = new(big.Int).SetUint64(txs[i].RollupDataGas())
rs[i].L1Fee = L1Cost(txs[i].RollupDataGas(), l1Basefee, overhead, scalar)
rs[i].L1GasUsed = new(big.Int).SetUint64(gas)
rs[i].L1Fee = L1Cost(gas, l1Basefee, overhead, scalar)
rs[i].FeeScalar = feeScalar
}
}
Expand Down
2 changes: 1 addition & 1 deletion core/types/receipt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ func TestDeriveFields(t *testing.T) {
hash := common.BytesToHash([]byte{0x03, 0x14})

clearComputedFieldsOnReceipts(t, receipts)
if err := receipts.DeriveFields(params.TestChainConfig, hash, number.Uint64(), txs); err != nil {
if err := receipts.DeriveFields(params.TestChainConfig, hash, number.Uint64(), 0, txs); err != nil {
t.Fatalf("DeriveFields(...) = %v, want <nil>", err)
}
// Iterate over all the computed fields and check that they're correct
Expand Down
22 changes: 18 additions & 4 deletions core/types/rollup_l1_cost.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,22 @@ import (
"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 RollupMessage interface {
RollupDataGas() uint64
RollupDataGas() RollupGasData
IsDepositTx() bool
}

Expand All @@ -34,7 +48,7 @@ type StateGetter interface {

// 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, msg RollupMessage) *big.Int
type L1CostFunc func(blockNum uint64, blockTime uint64, msg RollupMessage) *big.Int

var (
L1BaseFeeSlot = common.BigToHash(big.NewInt(1))
Expand All @@ -50,8 +64,8 @@ var L1BlockAddr = common.HexToAddress("0x420000000000000000000000000000000000001
func NewL1CostFunc(config *params.ChainConfig, statedb StateGetter) L1CostFunc {
cacheBlockNum := ^uint64(0)
var l1BaseFee, overhead, scalar *big.Int
return func(blockNum uint64, msg RollupMessage) *big.Int {
rollupDataGas := msg.RollupDataGas() // Only fake txs for RPC view-calls are 0.
return func(blockNum uint64, blockTime uint64, msg RollupMessage) *big.Int {
rollupDataGas := msg.RollupDataGas().DataGas(blockTime, config) // Only fake txs for RPC view-calls are 0.
if config.Optimism == nil || msg.IsDepositTx() || rollupDataGas == 0 {
return nil
}
Expand Down
30 changes: 30 additions & 0 deletions core/types/rollup_l1_cost_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
36 changes: 16 additions & 20 deletions core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import (
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
)

Expand Down Expand Up @@ -59,7 +58,7 @@ type Transaction struct {
size atomic.Value
from atomic.Value

// cache how much gas the tx takes on L1 for its share of rollup data
// cache of RollupGasData details to compute the gas the tx takes on L1 for its share of rollup data
rollupGas atomic.Value
}

Expand Down Expand Up @@ -347,31 +346,27 @@ func (tx *Transaction) Cost() *big.Int {
}

// RollupDataGas is the amount of gas it takes to confirm the tx on L1 as a rollup
func (tx *Transaction) RollupDataGas() uint64 {
func (tx *Transaction) RollupDataGas() RollupGasData {
if tx.Type() == DepositTxType {
return 0
return RollupGasData{}
}
if v := tx.rollupGas.Load(); v != nil {
return v.(uint64)
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 zeroes uint64
var ones uint64
var out RollupGasData
for _, byt := range data {
if byt == 0 {
zeroes++
out.Zeroes++
} else {
ones++
out.Ones++
}
}
zeroesGas := zeroes * params.TxDataZeroGas
onesGas := (ones + 68) * params.TxDataNonZeroGasEIP2028
total := zeroesGas + onesGas
tx.rollupGas.Store(total)
return total
tx.rollupGas.Store(out)
return out
}

// RawSignatureValues returns the V, R, S signature values of the transaction.
Expand Down Expand Up @@ -685,7 +680,7 @@ type Message struct {
isSystemTx bool
isDepositTx bool
mint *big.Int
l1CostGas uint64
l1CostGas RollupGasData
}

func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice, gasFeeCap, gasTipCap *big.Int, data []byte, accessList AccessList, isFake bool) Message {
Expand All @@ -705,7 +700,7 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b
isSystemTx: false,
isDepositTx: false,
mint: nil,
l1CostGas: 0,
l1CostGas: RollupGasData{},
}
}

Expand Down Expand Up @@ -748,10 +743,11 @@ func (m Message) Nonce() uint64 { return m.nonce }
func (m Message) Data() []byte { return m.data }
func (m Message) AccessList() AccessList { return m.accessList }
func (m Message) IsFake() bool { return m.isFake }
func (m Message) IsSystemTx() bool { return m.isSystemTx }
func (m Message) IsDepositTx() bool { return m.isDepositTx }
func (m Message) Mint() *big.Int { return m.mint }
func (m Message) RollupDataGas() uint64 { return m.l1CostGas }

func (m Message) IsSystemTx() bool { return m.isSystemTx }
func (m Message) IsDepositTx() bool { return m.isDepositTx }
func (m Message) Mint() *big.Int { return m.mint }
func (m Message) RollupDataGas() RollupGasData { return m.l1CostGas }

// copyAddressPtr copies an address.
func copyAddressPtr(a *common.Address) *common.Address {
Expand Down
2 changes: 1 addition & 1 deletion light/odr_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash, num
genesis := rawdb.ReadCanonicalHash(odr.Database(), 0)
config := rawdb.ReadChainConfig(odr.Database(), genesis)

if err := receipts.DeriveFields(config, block.Hash(), block.NumberU64(), block.Transactions()); err != nil {
if err := receipts.DeriveFields(config, block.Hash(), block.NumberU64(), block.Time(), block.Transactions()); err != nil {
return nil, err
}
rawdb.WriteReceipts(odr.Database(), hash, number, receipts)
Expand Down

0 comments on commit cde9c26

Please sign in to comment.