feat(core/types,internal/ethapi): In regolith, record the actual nonce used for deposit transactions

The nonce is available as DepositNonce on the receipt and is used to calculate the correct ContractAddress.
This commit is contained in:
Adrian Sutton 2023-02-23 15:03:32 +10:00 committed by Adrian Sutton
parent 2864f90ac0
commit bc0cca3223
5 changed files with 220 additions and 6 deletions

View File

@ -103,6 +103,11 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, gp *GasPool
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 {
@ -129,9 +134,15 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, gp *GasPool
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.

View File

@ -72,6 +72,7 @@ func (r *Receipt) UnmarshalJSON(input []byte) error {
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 {
@ -130,5 +131,8 @@ func (r *Receipt) UnmarshalJSON(input []byte) error {
if dec.FeeScalar != nil {
r.FeeScalar = dec.FeeScalar
}
if dec.DepositNonce != nil {
r.DepositNonce = (*uint64)(dec.DepositNonce)
}
return nil
}

View File

@ -63,6 +63,9 @@ type Receipt struct {
TxHash common.Hash `json:"transactionHash" gencodec:"required"`
ContractAddress common.Address `json:"contractAddress"`
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
// 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.
@ -101,11 +104,24 @@ type receiptRLP struct {
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
@ -209,7 +225,13 @@ func (r *Receipt) EncodeRLP(w io.Writer) error {
// 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.
@ -271,7 +293,7 @@ func (r *Receipt) decodeTyped(b []byte) error {
return errShortTypedReceipt
}
switch b[0] {
case DynamicFeeTxType, AccessListTxType, DepositTxType:
case DynamicFeeTxType, AccessListTxType:
var data receiptRLP
err := rlp.DecodeBytes(b[1:], &data)
if err != nil {
@ -279,6 +301,15 @@ func (r *Receipt) decodeTyped(b []byte) error {
}
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
}
@ -342,6 +373,9 @@ func (r *ReceiptForStorage) EncodeRLP(_w io.Writer) error {
}
}
w.ListEnd(logList)
if r.DepositNonce != nil {
w.WriteUint64(*r.DepositNonce)
}
w.ListEnd(outerList)
return w.Flush()
}
@ -402,7 +436,9 @@ func decodeStoredReceiptRLP(r *ReceiptForStorage, blob []byte) error {
r.CumulativeGasUsed = stored.CumulativeGasUsed
r.Logs = stored.Logs
r.Bloom = CreateBloom(Receipts{(*Receipt)(r)})
if stored.DepositNonce != nil {
r.DepositNonce = stored.DepositNonce
}
return nil
}
@ -458,7 +494,11 @@ func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, nu
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)
}
// The used gas can be calculated based on previous r
if i == 0 {

View File

@ -18,6 +18,7 @@ package types
import (
"bytes"
"fmt"
"math"
"math/big"
"reflect"
@ -27,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/require"
)
var (
@ -80,6 +82,42 @@ var (
},
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,
}
)
func TestDecodeEmptyTypedReceipt(t *testing.T) {
@ -117,7 +155,12 @@ func TestDeriveFields(t *testing.T) {
Gas: 3,
GasPrice: big.NewInt(3),
}),
NewTx(&DepositTx{
Value: big.NewInt(3),
Gas: 4,
}),
}
depNonce := uint64(7)
// Create the corresponding receipts
receipts := Receipts{
&Receipt{
@ -154,6 +197,26 @@ func TestDeriveFields(t *testing.T) {
ContractAddress: common.BytesToAddress([]byte{0x03, 0x33, 0x33}),
GasUsed: 3,
},
&Receipt{
Type: DepositTxType,
PostState: common.Hash{3}.Bytes(),
CumulativeGasUsed: 10,
Logs: []*Log{
{Address: common.BytesToAddress([]byte{0x33})},
{Address: common.BytesToAddress([]byte{0x03, 0x33})},
},
TxHash: txs[3].Hash(),
ContractAddress: common.BytesToAddress([]byte{0x03, 0x33, 0x33}),
GasUsed: 4,
DepositNonce: &depNonce,
},
}
nonces := []uint64{
txs[0].Nonce(),
txs[1].Nonce(),
txs[2].Nonce(),
*receipts[3].DepositNonce, // Deposit tx should use deposit nonce
}
// Clear all the computed fields and re-derive them
number := big.NewInt(1)
@ -190,7 +253,7 @@ func TestDeriveFields(t *testing.T) {
t.Errorf("receipts[%d].ContractAddress = %s, want %s", i, receipts[i].ContractAddress.String(), (common.Address{}).String())
}
from, _ := Sender(signer, txs[i])
contractAddress := crypto.CreateAddress(from, txs[i].Nonce())
contractAddress := crypto.CreateAddress(from, nonces[i])
if txs[i].To() == nil && receipts[i].ContractAddress != contractAddress {
t.Errorf("receipts[%d].ContractAddress = %s, want %s", i, receipts[i].ContractAddress.String(), contractAddress.String())
}
@ -342,6 +405,38 @@ func TestReceiptUnmarshalBinary(t *testing.T) {
}
}
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 clearComputedFieldsOnReceipts(t *testing.T, receipts Receipts) {
t.Helper()
@ -380,3 +475,64 @@ func clearComputedFieldsOnLog(t *testing.T, log *Log) {
log.TxIndex = math.MaxUint32
log.Index = math.MaxUint32
}
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)
})
}
}

View File

@ -1826,6 +1826,9 @@ func (s *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common.
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 the effective gas price paid
if !s.b.ChainConfig().IsLondon(bigblock) {
fields["effectiveGasPrice"] = hexutil.Uint64(tx.GasPrice().Uint64())