Commit 65289e63 authored by Mark Tyneway's avatar Mark Tyneway Committed by Kelvin Fichter

l2geth: add L1 gas fields to transaction receipt

The response of the RPC endpoint `eth_getTransactionReceipt`
will now return 4 new fields.

- `l1GasPrice`
- `l1GasUsed`
- `l1Fee`
- `l1FeeScalar`

These fields are added to the database as part of the receipt
itself. This means that it is a consensus change as the
serialization of the receipt has been updated. This impacts
the blockhash because the block header commits to a merkle root
of all of the receipts in the block.

Each of the new fields on the receipt exist in the state but
would require an archive node to query for as the values can
change over time.
parent 09eb322e
---
'@eth-optimism/l2geth': minor
---
Add optimistic ethereum specific fields to the receipt. These fields are related to the L1 portion of the fee. Note that this is a consensus change as it will impact the blockhash through the receipts root
...@@ -25,6 +25,7 @@ import ( ...@@ -25,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rollup/fees"
"github.com/ethereum/go-ethereum/rollup/rcfg" "github.com/ethereum/go-ethereum/rollup/rcfg"
) )
...@@ -107,11 +108,22 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo ...@@ -107,11 +108,22 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
// Create a new environment which holds all relevant information // Create a new environment which holds all relevant information
// about the transaction and calling mechanisms. // about the transaction and calling mechanisms.
vmenv := vm.NewEVM(context, statedb, config, cfg) vmenv := vm.NewEVM(context, statedb, config, cfg)
// UsingOVM
// Compute the fee related information that is to be included
// on the receipt. This must happen before the state transition
// to ensure that the correct information is used.
l1Fee, l1GasPrice, l1GasUsed, scalar, err := fees.DeriveL1GasInfo(msg, statedb)
if err != nil {
return nil, err
}
// Apply the transaction to the current state (included in the env) // Apply the transaction to the current state (included in the env)
_, gas, failed, err := ApplyMessage(vmenv, msg, gp) _, gas, failed, err := ApplyMessage(vmenv, msg, gp)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Update the state with pending changes // Update the state with pending changes
var root []byte var root []byte
if config.IsByzantium(header.Number) { if config.IsByzantium(header.Number) {
...@@ -124,6 +136,10 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo ...@@ -124,6 +136,10 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
// Create a new receipt for the transaction, storing the intermediate root and gas used by the tx // Create a new receipt for the transaction, storing the intermediate root and gas used by the tx
// based on the eip phase, we're passing whether the root touch-delete accounts. // based on the eip phase, we're passing whether the root touch-delete accounts.
receipt := types.NewReceipt(root, failed, *usedGas) receipt := types.NewReceipt(root, failed, *usedGas)
receipt.L1GasPrice = l1GasPrice
receipt.L1GasUsed = l1GasUsed
receipt.L1Fee = l1Fee
receipt.FeeScalar = scalar
receipt.TxHash = tx.Hash() receipt.TxHash = tx.Hash()
receipt.GasUsed = gas receipt.GasUsed = gas
// if the transaction created a contract, store the creation address in the receipt. // if the transaction created a contract, store the creation address in the receipt.
......
...@@ -27,6 +27,10 @@ func (r Receipt) MarshalJSON() ([]byte, error) { ...@@ -27,6 +27,10 @@ func (r Receipt) MarshalJSON() ([]byte, error) {
BlockHash common.Hash `json:"blockHash,omitempty"` BlockHash common.Hash `json:"blockHash,omitempty"`
BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` BlockNumber *hexutil.Big `json:"blockNumber,omitempty"`
TransactionIndex hexutil.Uint `json:"transactionIndex"` TransactionIndex hexutil.Uint `json:"transactionIndex"`
L1GasPrice *big.Int `json:"l1GasPrice" gencodec:"required"`
L1GasUsed *big.Int `json:"l1GasUsed" gencodec:"required"`
L1Fee *big.Int `json:"l1Fee" gencodec:"required"`
FeeScalar *big.Float `json:"l1FeeScalar" gencodec:"required"`
} }
var enc Receipt var enc Receipt
enc.PostState = r.PostState enc.PostState = r.PostState
...@@ -40,6 +44,10 @@ func (r Receipt) MarshalJSON() ([]byte, error) { ...@@ -40,6 +44,10 @@ func (r Receipt) MarshalJSON() ([]byte, error) {
enc.BlockHash = r.BlockHash enc.BlockHash = r.BlockHash
enc.BlockNumber = (*hexutil.Big)(r.BlockNumber) enc.BlockNumber = (*hexutil.Big)(r.BlockNumber)
enc.TransactionIndex = hexutil.Uint(r.TransactionIndex) enc.TransactionIndex = hexutil.Uint(r.TransactionIndex)
enc.L1GasPrice = r.L1GasPrice
enc.L1GasUsed = r.L1GasUsed
enc.L1Fee = r.L1Fee
enc.FeeScalar = r.FeeScalar
return json.Marshal(&enc) return json.Marshal(&enc)
} }
...@@ -57,6 +65,10 @@ func (r *Receipt) UnmarshalJSON(input []byte) error { ...@@ -57,6 +65,10 @@ func (r *Receipt) UnmarshalJSON(input []byte) error {
BlockHash *common.Hash `json:"blockHash,omitempty"` BlockHash *common.Hash `json:"blockHash,omitempty"`
BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` BlockNumber *hexutil.Big `json:"blockNumber,omitempty"`
TransactionIndex *hexutil.Uint `json:"transactionIndex"` TransactionIndex *hexutil.Uint `json:"transactionIndex"`
L1GasPrice *big.Int `json:"l1GasPrice" gencodec:"required"`
L1GasUsed *big.Int `json:"l1GasUsed" gencodec:"required"`
L1Fee *big.Int `json:"l1Fee" gencodec:"required"`
FeeScalar *big.Float `json:"l1FeeScalar" gencodec:"required"`
} }
var dec Receipt var dec Receipt
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
...@@ -100,5 +112,21 @@ func (r *Receipt) UnmarshalJSON(input []byte) error { ...@@ -100,5 +112,21 @@ func (r *Receipt) UnmarshalJSON(input []byte) error {
if dec.TransactionIndex != nil { if dec.TransactionIndex != nil {
r.TransactionIndex = uint(*dec.TransactionIndex) r.TransactionIndex = uint(*dec.TransactionIndex)
} }
if dec.L1GasPrice == nil {
return errors.New("missing required field 'l1GasPrice' for Receipt")
}
r.L1GasPrice = dec.L1GasPrice
if dec.L1GasUsed == nil {
return errors.New("missing required field 'l1GasUsed' for Receipt")
}
r.L1GasUsed = dec.L1GasUsed
if dec.L1Fee == nil {
return errors.New("missing required field 'l1Fee' for Receipt")
}
r.L1Fee = dec.L1Fee
if dec.FeeScalar == nil {
return errors.New("missing required field 'l1FeeScalar' for Receipt")
}
r.FeeScalar = dec.FeeScalar
return nil return nil
} }
...@@ -66,6 +66,12 @@ type Receipt struct { ...@@ -66,6 +66,12 @@ type Receipt struct {
BlockHash common.Hash `json:"blockHash,omitempty"` BlockHash common.Hash `json:"blockHash,omitempty"`
BlockNumber *big.Int `json:"blockNumber,omitempty"` BlockNumber *big.Int `json:"blockNumber,omitempty"`
TransactionIndex uint `json:"transactionIndex"` TransactionIndex uint `json:"transactionIndex"`
// UsingOVM
L1GasPrice *big.Int `json:"l1GasPrice" gencodec:"required"`
L1GasUsed *big.Int `json:"l1GasUsed" gencodec:"required"`
L1Fee *big.Int `json:"l1Fee" gencodec:"required"`
FeeScalar *big.Float `json:"l1FeeScalar" gencodec:"required"`
} }
type receiptMarshaling struct { type receiptMarshaling struct {
...@@ -90,6 +96,11 @@ type storedReceiptRLP struct { ...@@ -90,6 +96,11 @@ type storedReceiptRLP struct {
PostStateOrStatus []byte PostStateOrStatus []byte
CumulativeGasUsed uint64 CumulativeGasUsed uint64
Logs []*LogForStorage Logs []*LogForStorage
// UsingOVM
L1GasUsed *big.Int
L1GasPrice *big.Int
L1Fee *big.Int
FeeScalar string
} }
// v4StoredReceiptRLP is the storage encoding of a receipt used in database version 4. // v4StoredReceiptRLP is the storage encoding of a receipt used in database version 4.
...@@ -191,6 +202,10 @@ func (r *ReceiptForStorage) EncodeRLP(w io.Writer) error { ...@@ -191,6 +202,10 @@ func (r *ReceiptForStorage) EncodeRLP(w io.Writer) error {
PostStateOrStatus: (*Receipt)(r).statusEncoding(), PostStateOrStatus: (*Receipt)(r).statusEncoding(),
CumulativeGasUsed: r.CumulativeGasUsed, CumulativeGasUsed: r.CumulativeGasUsed,
Logs: make([]*LogForStorage, len(r.Logs)), Logs: make([]*LogForStorage, len(r.Logs)),
L1GasUsed: r.L1GasUsed,
L1GasPrice: r.L1GasPrice,
L1Fee: r.L1Fee,
FeeScalar: r.FeeScalar.String(),
} }
for i, log := range r.Logs { for i, log := range r.Logs {
enc.Logs[i] = (*LogForStorage)(log) enc.Logs[i] = (*LogForStorage)(log)
...@@ -233,6 +248,16 @@ func decodeStoredReceiptRLP(r *ReceiptForStorage, blob []byte) error { ...@@ -233,6 +248,16 @@ func decodeStoredReceiptRLP(r *ReceiptForStorage, blob []byte) error {
} }
r.Bloom = CreateBloom(Receipts{(*Receipt)(r)}) r.Bloom = CreateBloom(Receipts{(*Receipt)(r)})
// UsingOVM
scalar, ok := new(big.Float).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 return nil
} }
......
...@@ -1438,6 +1438,11 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, ha ...@@ -1438,6 +1438,11 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, ha
"contractAddress": nil, "contractAddress": nil,
"logs": receipt.Logs, "logs": receipt.Logs,
"logsBloom": receipt.Bloom, "logsBloom": receipt.Bloom,
// UsingOVM
"l1GasPrice": (*hexutil.Big)(receipt.L1GasPrice),
"l1GasUsed": (*hexutil.Big)(receipt.L1GasUsed),
"l1Fee": (*hexutil.Big)(receipt.L1Fee),
"l1FeeScalar": receipt.FeeScalar.String(),
} }
// Assign receipt status or post state. // Assign receipt status or post state.
......
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rollup/rcfg" "github.com/ethereum/go-ethereum/rollup/rcfg"
...@@ -153,6 +154,22 @@ func CalculateL1GasUsed(data []byte, overhead *big.Int) *big.Int { ...@@ -153,6 +154,22 @@ func CalculateL1GasUsed(data []byte, overhead *big.Int) *big.Int {
return new(big.Int).Add(l1Gas, overhead) return new(big.Int).Add(l1Gas, overhead)
} }
// DeriveL1GasInfo reads L1 gas related information to be included
// on the receipt
func DeriveL1GasInfo(msg Message, state StateDB) (*big.Int, *big.Int, *big.Int, *big.Float, error) {
tx := asTransaction(msg)
raw, err := rlpEncode(tx)
fmt.Println(hexutil.Encode(raw))
if err != nil {
return nil, nil, nil, nil, err
}
l1GasPrice, overhead, scalar := readGPOStorageSlots(rcfg.L2GasPriceOracleAddress, state)
l1GasUsed := CalculateL1GasUsed(raw, overhead)
l1Fee := CalculateL1Fee(raw, overhead, l1GasPrice, scalar)
return l1Fee, l1GasPrice, l1GasUsed, scalar, nil
}
func readGPOStorageSlots(addr common.Address, state StateDB) (*big.Int, *big.Int, *big.Float) { func readGPOStorageSlots(addr common.Address, state StateDB) (*big.Int, *big.Int, *big.Float) {
l1GasPrice := state.GetState(addr, rcfg.L1GasPriceSlot) l1GasPrice := state.GetState(addr, rcfg.L1GasPriceSlot)
overhead := state.GetState(addr, rcfg.OverheadSlot) overhead := state.GetState(addr, rcfg.OverheadSlot)
...@@ -183,7 +200,7 @@ func rlpEncode(tx *types.Transaction) ([]byte, error) { ...@@ -183,7 +200,7 @@ func rlpEncode(tx *types.Transaction) ([]byte, error) {
r, v, s := tx.RawSignatureValues() r, v, s := tx.RawSignatureValues()
if r.Cmp(common.Big0) != 0 || v.Cmp(common.Big0) != 0 || s.Cmp(common.Big0) != 0 { if r.Cmp(common.Big0) != 0 || v.Cmp(common.Big0) != 0 || s.Cmp(common.Big0) != 0 {
return []byte{}, errTransactionSigned return nil, errTransactionSigned
} }
// Slice off the 0 bytes representing the signature // Slice off the 0 bytes representing the signature
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment