diff --git a/go.mod b/go.mod index 13bee99..268a8fd 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/kr/pretty v0.1.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 981f65c..3125676 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/restricted/types/access_list_tx.go b/restricted/types/access_list_tx.go index 7c92bbb..1d5b0cd 100644 --- a/restricted/types/access_list_tx.go +++ b/restricted/types/access_list_tx.go @@ -1,4 +1,4 @@ -// Copyright 2020 The go-ethereum Authors +// 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 @@ -22,7 +22,7 @@ import ( "github.com/openrelayxyz/plugeth-utils/core" ) -//go:generate gencodec -type AccessTuple -out gen_access_tuple.go +//go:generate go run github.com/fjl/gencodec -type AccessTuple -out gen_access_tuple.go // AccessList is an EIP-2930 access list. type AccessList []AccessTuple @@ -44,22 +44,22 @@ func (al AccessList) StorageKeys() int { // AccessListTx is the data of EIP-2930 access list transactions. type AccessListTx struct { - ChainID *big.Int // destination chain ID - Nonce uint64 // nonce of sender account - GasPrice *big.Int // wei per gas - Gas uint64 // gas limit + ChainID *big.Int // destination chain ID + Nonce uint64 // nonce of sender account + GasPrice *big.Int // wei per gas + Gas uint64 // gas limit To *core.Address `rlp:"nil"` // nil means contract creation - Value *big.Int // wei amount - Data []byte // contract invocation input data - AccessList AccessList // EIP-2930 access list - V, R, S *big.Int // signature values + Value *big.Int // wei amount + Data []byte // contract invocation input data + AccessList AccessList // EIP-2930 access list + V, R, S *big.Int // signature values } // copy creates a deep copy of the transaction data and initializes all fields. func (tx *AccessListTx) copy() TxData { cpy := &AccessListTx{ Nonce: tx.Nonce, - To: tx.To, // TODO: copy pointed-to address + To: copyAddressPtr(tx.To), Data: core.CopyBytes(tx.Data), Gas: tx.Gas, // These are copied below. @@ -96,7 +96,6 @@ func (tx *AccessListTx) copy() TxData { // accessors for innerTx. func (tx *AccessListTx) txType() byte { return AccessListTxType } func (tx *AccessListTx) chainID() *big.Int { return tx.ChainID } -func (tx *AccessListTx) protected() bool { return true } func (tx *AccessListTx) accessList() AccessList { return tx.AccessList } func (tx *AccessListTx) data() []byte { return tx.Data } func (tx *AccessListTx) gas() uint64 { return tx.Gas } @@ -105,7 +104,11 @@ func (tx *AccessListTx) gasTipCap() *big.Int { return tx.GasPrice } func (tx *AccessListTx) gasFeeCap() *big.Int { return tx.GasPrice } func (tx *AccessListTx) value() *big.Int { return tx.Value } func (tx *AccessListTx) nonce() uint64 { return tx.Nonce } -func (tx *AccessListTx) to() *core.Address { return tx.To } +func (tx *AccessListTx) to() *core.Address { return tx.To } + +func (tx *AccessListTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { + return dst.Set(tx.GasPrice) +} func (tx *AccessListTx) rawSignatureValues() (v, r, s *big.Int) { return tx.V, tx.R, tx.S diff --git a/restricted/types/dynamic_fee_tx.go b/restricted/types/dynamic_fee_tx.go index fcf763f..70ef615 100644 --- a/restricted/types/dynamic_fee_tx.go +++ b/restricted/types/dynamic_fee_tx.go @@ -25,8 +25,8 @@ import ( type DynamicFeeTx struct { ChainID *big.Int Nonce uint64 - GasTipCap *big.Int - GasFeeCap *big.Int + GasTipCap *big.Int // a.k.a. maxPriorityFeePerGas + GasFeeCap *big.Int // a.k.a. maxFeePerGas Gas uint64 To *core.Address `rlp:"nil"` // nil means contract creation Value *big.Int @@ -43,7 +43,7 @@ type DynamicFeeTx struct { func (tx *DynamicFeeTx) copy() TxData { cpy := &DynamicFeeTx{ Nonce: tx.Nonce, - To: tx.To, // TODO: copy pointed-to address + To: copyAddressPtr(tx.To), Data: core.CopyBytes(tx.Data), Gas: tx.Gas, // These are copied below. @@ -84,7 +84,6 @@ func (tx *DynamicFeeTx) copy() TxData { // accessors for innerTx. func (tx *DynamicFeeTx) txType() byte { return DynamicFeeTxType } func (tx *DynamicFeeTx) chainID() *big.Int { return tx.ChainID } -func (tx *DynamicFeeTx) protected() bool { return true } func (tx *DynamicFeeTx) accessList() AccessList { return tx.AccessList } func (tx *DynamicFeeTx) data() []byte { return tx.Data } func (tx *DynamicFeeTx) gas() uint64 { return tx.Gas } @@ -93,7 +92,18 @@ func (tx *DynamicFeeTx) gasTipCap() *big.Int { return tx.GasTipCap } func (tx *DynamicFeeTx) gasPrice() *big.Int { return tx.GasFeeCap } func (tx *DynamicFeeTx) value() *big.Int { return tx.Value } func (tx *DynamicFeeTx) nonce() uint64 { return tx.Nonce } -func (tx *DynamicFeeTx) to() *core.Address { return tx.To } +func (tx *DynamicFeeTx) to() *core.Address { return tx.To } + +func (tx *DynamicFeeTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { + if baseFee == nil { + return dst.Set(tx.GasFeeCap) + } + tip := dst.Sub(tx.GasFeeCap, baseFee) + if tip.Cmp(tx.GasTipCap) > 0 { + tip.Set(tx.GasTipCap) + } + return tip.Add(tip, baseFee) +} func (tx *DynamicFeeTx) rawSignatureValues() (v, r, s *big.Int) { return tx.V, tx.R, tx.S diff --git a/restricted/types/gen_receipt_json.go b/restricted/types/gen_receipt_json.go index 9317fcb..ff2fa9b 100644 --- a/restricted/types/gen_receipt_json.go +++ b/restricted/types/gen_receipt_json.go @@ -22,10 +22,11 @@ func (r Receipt) MarshalJSON() ([]byte, error) { CumulativeGasUsed hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"` Bloom Bloom `json:"logsBloom" gencodec:"required"` Logs []*Log `json:"logs" gencodec:"required"` - TxHash core.Hash `json:"transactionHash" gencodec:"required"` - ContractAddress core.Address `json:"contractAddress"` + TxHash core.Hash `json:"transactionHash" gencodec:"required"` + ContractAddress core.Address `json:"contractAddress"` GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` - BlockHash core.Hash `json:"blockHash,omitempty"` + EffectiveGasPrice *hexutil.Big `json:"effectiveGasPrice,omitempty"` + BlockHash core.Hash `json:"blockHash,omitempty"` BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` TransactionIndex hexutil.Uint `json:"transactionIndex"` } @@ -39,6 +40,7 @@ func (r Receipt) MarshalJSON() ([]byte, error) { enc.TxHash = r.TxHash enc.ContractAddress = r.ContractAddress enc.GasUsed = hexutil.Uint64(r.GasUsed) + enc.EffectiveGasPrice = (*hexutil.Big)(r.EffectiveGasPrice) enc.BlockHash = r.BlockHash enc.BlockNumber = (*hexutil.Big)(r.BlockNumber) enc.TransactionIndex = hexutil.Uint(r.TransactionIndex) @@ -54,10 +56,11 @@ func (r *Receipt) UnmarshalJSON(input []byte) error { CumulativeGasUsed *hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"` Bloom *Bloom `json:"logsBloom" gencodec:"required"` Logs []*Log `json:"logs" gencodec:"required"` - TxHash *core.Hash `json:"transactionHash" gencodec:"required"` - ContractAddress *core.Address `json:"contractAddress"` + TxHash *core.Hash `json:"transactionHash" gencodec:"required"` + ContractAddress *core.Address `json:"contractAddress"` GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` - BlockHash *core.Hash `json:"blockHash,omitempty"` + EffectiveGasPrice *hexutil.Big `json:"effectiveGasPrice,omitempty"` + BlockHash *core.Hash `json:"blockHash,omitempty"` BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` TransactionIndex *hexutil.Uint `json:"transactionIndex"` } @@ -97,6 +100,9 @@ func (r *Receipt) UnmarshalJSON(input []byte) error { return errors.New("missing required field 'gasUsed' for Receipt") } r.GasUsed = uint64(*dec.GasUsed) + if dec.EffectiveGasPrice != nil { + r.EffectiveGasPrice = (*big.Int)(dec.EffectiveGasPrice) + } if dec.BlockHash != nil { r.BlockHash = *dec.BlockHash } diff --git a/restricted/types/legacy_tx.go b/restricted/types/legacy_tx.go index 01b5144..8318df0 100644 --- a/restricted/types/legacy_tx.go +++ b/restricted/types/legacy_tx.go @@ -1,4 +1,4 @@ -// Copyright 2020 The go-ethereum Authors +// 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 @@ -24,13 +24,13 @@ import ( // LegacyTx is the transaction data of regular Ethereum transactions. type LegacyTx struct { - Nonce uint64 // nonce of sender account - GasPrice *big.Int // wei per gas - Gas uint64 // gas limit + Nonce uint64 // nonce of sender account + GasPrice *big.Int // wei per gas + Gas uint64 // gas limit To *core.Address `rlp:"nil"` // nil means contract creation - Value *big.Int // wei amount - Data []byte // contract invocation input data - V, R, S *big.Int // signature values + Value *big.Int // wei amount + Data []byte // contract invocation input data + V, R, S *big.Int // signature values } // NewTransaction creates an unsigned legacy transaction. @@ -62,7 +62,7 @@ func NewContractCreation(nonce uint64, amount *big.Int, gasLimit uint64, gasPric func (tx *LegacyTx) copy() TxData { cpy := &LegacyTx{ Nonce: tx.Nonce, - To: tx.To, // TODO: copy pointed-to address + To: copyAddressPtr(tx.To), Data: core.CopyBytes(tx.Data), Gas: tx.Gas, // These are initialized below. @@ -101,7 +101,11 @@ func (tx *LegacyTx) gasTipCap() *big.Int { return tx.GasPrice } func (tx *LegacyTx) gasFeeCap() *big.Int { return tx.GasPrice } func (tx *LegacyTx) value() *big.Int { return tx.Value } func (tx *LegacyTx) nonce() uint64 { return tx.Nonce } -func (tx *LegacyTx) to() *core.Address { return tx.To } +func (tx *LegacyTx) to() *core.Address { return tx.To } + +func (tx *LegacyTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { + return dst.Set(tx.GasPrice) +} func (tx *LegacyTx) rawSignatureValues() (v, r, s *big.Int) { return tx.V, tx.R, tx.S diff --git a/restricted/types/receipt.go b/restricted/types/receipt.go index 91835d5..2856c03 100644 --- a/restricted/types/receipt.go +++ b/restricted/types/receipt.go @@ -31,7 +31,7 @@ import ( "github.com/openrelayxyz/plugeth-utils/restricted/rlp" ) -//go:generate gencodec -type Receipt -field-override receiptMarshaling -out gen_receipt_json.go +//go:generate go run github.com/fjl/gencodec -type Receipt -field-override receiptMarshaling -out gen_receipt_json.go var ( receiptStatusFailedRLP = []byte{} @@ -60,10 +60,10 @@ type Receipt struct { Logs []*Log `json:"logs" gencodec:"required"` // Implementation fields: These fields are added by geth when processing a transaction. - // They are stored in the chain database. - TxHash core.Hash `json:"transactionHash" gencodec:"required"` - ContractAddress core.Address `json:"contractAddress"` - GasUsed uint64 `json:"gasUsed" gencodec:"required"` + TxHash core.Hash `json:"transactionHash" gencodec:"required"` + ContractAddress core.Address `json:"contractAddress"` + GasUsed uint64 `json:"gasUsed" gencodec:"required"` + EffectiveGasPrice *big.Int `json:"effectiveGasPrice"` // Inclusion information: These fields provide information about the inclusion of the // transaction corresponding to this receipt. @@ -115,6 +115,9 @@ func NewReceipt(root []byte, failed bool, cumulativeGasUsed uint64) *Receipt { // EncodeRLP implements rlp.Encoder, and flattens the consensus fields of a receipt // into an RLP stream. If no post state is present, byzantium fork is assumed. +// For a legacy Receipt this returns RLP([PostStateOrStatus, CumulativeGasUsed, Bloom, Logs]) +// For a EIP-2718 Receipt this returns RLP(TxType || ReceiptPayload) +// For a EIP-2930 Receipt, TxType == 0x01 and ReceiptPayload == RLP([PostStateOrStatus, CumulativeGasUsed, Bloom, Logs]) func (r *Receipt) EncodeRLP(w io.Writer) error { data := &receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs} if r.Type == LegacyTxType { @@ -161,26 +164,13 @@ func (r *Receipt) DecodeRLP(s *rlp.Stream) error { } r.Type = LegacyTxType return r.setFromRLP(dec) - case kind == rlp.String: + default: // It's an EIP-2718 typed tx receipt. b, err := s.Bytes() if err != nil { return err } - if len(b) == 0 { - return errEmptyTypedReceipt - } - r.Type = b[0] - if r.Type == AccessListTxType || r.Type == DynamicFeeTxType { - var dec receiptRLP - if err := rlp.DecodeBytes(b[1:], &dec); err != nil { - return err - } - return r.setFromRLP(dec) - } - return ErrTxTypeNotSupported - default: - return rlp.ErrExpectedList + return r.decodeTyped(b) } } @@ -260,8 +250,8 @@ func (r *Receipt) Size() float64 { return size } -// ReceiptForStorage is a wrapper around a Receipt that flattens and parses the -// entire content of a receipt, as opposed to only the consensus fields originally. +// ReceiptForStorage is a wrapper around a Receipt with RLP serialization +// that omits the Bloom field and deserialization that re-computes it. type ReceiptForStorage Receipt // EncodeRLP implements rlp.Encoder. @@ -274,7 +264,8 @@ func (r *ReceiptForStorage) EncodeRLP(w io.Writer) error { return rlp.Encode(w, enc) } -// DecodeRLP implements rlp.Decoder. +// 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 { var stored storedReceiptRLP if err := s.Decode(&stored); err != nil { @@ -286,6 +277,7 @@ func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error { r.CumulativeGasUsed = stored.CumulativeGasUsed r.Logs = stored.Logs r.Bloom = CreateBloom(Receipts{(*Receipt)(r)}) + return nil } @@ -317,42 +309,48 @@ 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 (r Receipts) DeriveFields(config *params.ChainConfig, hash core.Hash, number uint64, txs Transactions) error { +func (rs Receipts) DeriveFields(config *params.ChainConfig, hash core.Hash, number uint64, baseFee *big.Int, txs []*Transaction) error { signer := MakeSigner(config, new(big.Int).SetUint64(number)) logIndex := uint(0) - if len(txs) != len(r) { + if len(txs) != len(rs) { return errors.New("transaction and receipt count mismatch") } - for i := 0; i < len(r); i++ { + for i := 0; i < len(rs); i++ { // The transaction type and hash can be retrieved from the transaction itself - r[i].Type = txs[i].Type() - r[i].TxHash = txs[i].Hash() + rs[i].Type = txs[i].Type() + rs[i].TxHash = txs[i].Hash() + + rs[i].EffectiveGasPrice = txs[i].inner.effectiveGasPrice(new(big.Int), baseFee) // block location fields - r[i].BlockHash = hash - r[i].BlockNumber = new(big.Int).SetUint64(number) - r[i].TransactionIndex = uint(i) + rs[i].BlockHash = hash + rs[i].BlockNumber = new(big.Int).SetUint64(number) + rs[i].TransactionIndex = uint(i) // 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]) - r[i].ContractAddress = crypto.CreateAddress(from, txs[i].Nonce()) + rs[i].ContractAddress = crypto.CreateAddress(from, txs[i].Nonce()) + } else { + rs[i].ContractAddress = core.Address{} } + // The used gas can be calculated based on previous r if i == 0 { - r[i].GasUsed = r[i].CumulativeGasUsed + rs[i].GasUsed = rs[i].CumulativeGasUsed } else { - r[i].GasUsed = r[i].CumulativeGasUsed - r[i-1].CumulativeGasUsed + rs[i].GasUsed = rs[i].CumulativeGasUsed - rs[i-1].CumulativeGasUsed } + // The derived log fields can simply be set from the block and transaction - for j := 0; j < len(r[i].Logs); j++ { - r[i].Logs[j].BlockNumber = number - r[i].Logs[j].BlockHash = hash - r[i].Logs[j].TxHash = r[i].TxHash - r[i].Logs[j].TxIndex = uint(i) - r[i].Logs[j].Index = logIndex + for j := 0; j < len(rs[i].Logs); j++ { + rs[i].Logs[j].BlockNumber = number + rs[i].Logs[j].BlockHash = hash + rs[i].Logs[j].TxHash = rs[i].TxHash + rs[i].Logs[j].TxIndex = uint(i) + rs[i].Logs[j].Index = logIndex logIndex++ } } diff --git a/restricted/types/receipt_test.go b/restricted/types/receipt_test.go index a3b8878..4f8685d 100644 --- a/restricted/types/receipt_test.go +++ b/restricted/types/receipt_test.go @@ -19,13 +19,15 @@ package types import ( "bytes" "encoding/hex" + "encoding/json" "math" "math/big" "reflect" "testing" + "github.com/kylelemons/godebug/diff" + "github.com/openrelayxyz/plugeth-utils/core" - "github.com/openrelayxyz/plugeth-utils/restricted/crypto" "github.com/openrelayxyz/plugeth-utils/restricted/params" "github.com/openrelayxyz/plugeth-utils/restricted/rlp" ) @@ -97,125 +99,177 @@ func TestDeriveFields(t *testing.T) { // Create a few transactions to have receipts for to2 := core.HexToAddress("0x2") to3 := core.HexToAddress("0x3") + to4 := core.HexToAddress("0x4") + to5 := core.HexToAddress("0x5") txs := Transactions{ NewTx(&LegacyTx{ Nonce: 1, Value: big.NewInt(1), Gas: 1, - GasPrice: big.NewInt(1), + GasPrice: big.NewInt(11), }), NewTx(&LegacyTx{ To: &to2, Nonce: 2, Value: big.NewInt(2), Gas: 2, - GasPrice: big.NewInt(2), + GasPrice: big.NewInt(22), }), NewTx(&AccessListTx{ To: &to3, Nonce: 3, Value: big.NewInt(3), Gas: 3, - GasPrice: big.NewInt(3), + GasPrice: big.NewInt(33), + }), + // EIP-1559 transactions. + NewTx(&DynamicFeeTx{ + To: &to4, + Nonce: 4, + Value: big.NewInt(4), + Gas: 4, + GasTipCap: big.NewInt(44), + GasFeeCap: big.NewInt(1045), + }), + NewTx(&DynamicFeeTx{ + To: &to5, + Nonce: 5, + Value: big.NewInt(5), + Gas: 5, + GasTipCap: big.NewInt(56), + GasFeeCap: big.NewInt(1055), }), } + + blockNumber := big.NewInt(1) + blockHash := core.BytesToHash([]byte{0x03, 0x14}) + // Create the corresponding receipts receipts := Receipts{ &Receipt{ Status: ReceiptStatusFailed, CumulativeGasUsed: 1, Logs: []*Log{ - {Address: core.BytesToAddress([]byte{0x11})}, - {Address: core.BytesToAddress([]byte{0x01, 0x11})}, + { + Address: core.BytesToAddress([]byte{0x11}), + // derived fields: + BlockNumber: blockNumber.Uint64(), + TxHash: txs[0].Hash(), + TxIndex: 0, + BlockHash: blockHash, + Index: 0, + }, + { + Address: core.BytesToAddress([]byte{0x01, 0x11}), + // derived fields: + BlockNumber: blockNumber.Uint64(), + TxHash: txs[0].Hash(), + TxIndex: 0, + BlockHash: blockHash, + Index: 1, + }, }, - TxHash: txs[0].Hash(), - ContractAddress: core.BytesToAddress([]byte{0x01, 0x11, 0x11}), - GasUsed: 1, + // derived fields: + TxHash: txs[0].Hash(), + ContractAddress: core.HexToAddress("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"), + GasUsed: 1, + EffectiveGasPrice: big.NewInt(11), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 0, }, &Receipt{ PostState: core.Hash{2}.Bytes(), CumulativeGasUsed: 3, Logs: []*Log{ - {Address: core.BytesToAddress([]byte{0x22})}, - {Address: core.BytesToAddress([]byte{0x02, 0x22})}, + { + Address: core.BytesToAddress([]byte{0x22}), + // derived fields: + BlockNumber: blockNumber.Uint64(), + TxHash: txs[1].Hash(), + TxIndex: 1, + BlockHash: blockHash, + Index: 2, + }, + { + Address: core.BytesToAddress([]byte{0x02, 0x22}), + // derived fields: + BlockNumber: blockNumber.Uint64(), + TxHash: txs[1].Hash(), + TxIndex: 1, + BlockHash: blockHash, + Index: 3, + }, }, - TxHash: txs[1].Hash(), - ContractAddress: core.BytesToAddress([]byte{0x02, 0x22, 0x22}), - GasUsed: 2, + // derived fields: + TxHash: txs[1].Hash(), + GasUsed: 2, + EffectiveGasPrice: big.NewInt(22), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 1, }, &Receipt{ Type: AccessListTxType, PostState: core.Hash{3}.Bytes(), CumulativeGasUsed: 6, - Logs: []*Log{ - {Address: core.BytesToAddress([]byte{0x33})}, - {Address: core.BytesToAddress([]byte{0x03, 0x33})}, - }, - TxHash: txs[2].Hash(), - ContractAddress: core.BytesToAddress([]byte{0x03, 0x33, 0x33}), - GasUsed: 3, + Logs: []*Log{}, + // derived fields: + TxHash: txs[2].Hash(), + GasUsed: 3, + EffectiveGasPrice: big.NewInt(33), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 2, + }, + &Receipt{ + Type: DynamicFeeTxType, + PostState: core.Hash{4}.Bytes(), + CumulativeGasUsed: 10, + Logs: []*Log{}, + // derived fields: + TxHash: txs[3].Hash(), + GasUsed: 4, + EffectiveGasPrice: big.NewInt(1044), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 3, + }, + &Receipt{ + Type: DynamicFeeTxType, + PostState: core.Hash{5}.Bytes(), + CumulativeGasUsed: 15, + Logs: []*Log{}, + // derived fields: + TxHash: txs[4].Hash(), + GasUsed: 5, + EffectiveGasPrice: big.NewInt(1055), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 4, }, } - // Clear all the computed fields and re-derive them - number := big.NewInt(1) - hash := core.BytesToHash([]byte{0x03, 0x14}) - clearComputedFieldsOnReceipts(t, receipts) - if err := receipts.DeriveFields(params.TestChainConfig, hash, number.Uint64(), txs); err != nil { + // Re-derive receipts. + basefee := big.NewInt(1000) + derivedReceipts := clearComputedFieldsOnReceipts(receipts) + err := Receipts(derivedReceipts).DeriveFields(params.TestChainConfig, blockHash, blockNumber.Uint64(), basefee, txs) + if err != nil { t.Fatalf("DeriveFields(...) = %v, want ", err) } - // Iterate over all the computed fields and check that they're correct - signer := MakeSigner(params.TestChainConfig, number) - logIndex := uint(0) - for i := range receipts { - if receipts[i].Type != txs[i].Type() { - t.Errorf("receipts[%d].Type = %d, want %d", i, receipts[i].Type, txs[i].Type()) - } - if receipts[i].TxHash != txs[i].Hash() { - t.Errorf("receipts[%d].TxHash = %s, want %s", i, receipts[i].TxHash.String(), txs[i].Hash().String()) - } - if receipts[i].BlockHash != hash { - t.Errorf("receipts[%d].BlockHash = %s, want %s", i, receipts[i].BlockHash.String(), hash.String()) - } - if receipts[i].BlockNumber.Cmp(number) != 0 { - t.Errorf("receipts[%c].BlockNumber = %s, want %s", i, receipts[i].BlockNumber.String(), number.String()) - } - if receipts[i].TransactionIndex != uint(i) { - t.Errorf("receipts[%d].TransactionIndex = %d, want %d", i, receipts[i].TransactionIndex, i) - } - if receipts[i].GasUsed != txs[i].Gas() { - t.Errorf("receipts[%d].GasUsed = %d, want %d", i, receipts[i].GasUsed, txs[i].Gas()) - } - if txs[i].To() != nil && receipts[i].ContractAddress != (core.Address{}) { - t.Errorf("receipts[%d].ContractAddress = %s, want %s", i, receipts[i].ContractAddress.String(), (core.Address{}).String()) - } - from, _ := Sender(signer, txs[i]) - contractAddress := crypto.CreateAddress(from, txs[i].Nonce()) - if txs[i].To() == nil && receipts[i].ContractAddress != contractAddress { - t.Errorf("receipts[%d].ContractAddress = %s, want %s", i, receipts[i].ContractAddress.String(), contractAddress.String()) - } - for j := range receipts[i].Logs { - if receipts[i].Logs[j].BlockNumber != number.Uint64() { - t.Errorf("receipts[%d].Logs[%d].BlockNumber = %d, want %d", i, j, receipts[i].Logs[j].BlockNumber, number.Uint64()) - } - if receipts[i].Logs[j].BlockHash != hash { - t.Errorf("receipts[%d].Logs[%d].BlockHash = %s, want %s", i, j, receipts[i].Logs[j].BlockHash.String(), hash.String()) - } - if receipts[i].Logs[j].TxHash != txs[i].Hash() { - t.Errorf("receipts[%d].Logs[%d].TxHash = %s, want %s", i, j, receipts[i].Logs[j].TxHash.String(), txs[i].Hash().String()) - } - if receipts[i].Logs[j].TxHash != txs[i].Hash() { - t.Errorf("receipts[%d].Logs[%d].TxHash = %s, want %s", i, j, receipts[i].Logs[j].TxHash.String(), txs[i].Hash().String()) - } - if receipts[i].Logs[j].TxIndex != uint(i) { - t.Errorf("receipts[%d].Logs[%d].TransactionIndex = %d, want %d", i, j, receipts[i].Logs[j].TxIndex, i) - } - if receipts[i].Logs[j].Index != logIndex { - t.Errorf("receipts[%d].Logs[%d].Index = %d, want %d", i, j, receipts[i].Logs[j].Index, logIndex) - } - logIndex++ - } + // 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) } } @@ -246,6 +300,7 @@ func TestTypedReceiptEncodingDecoding(t *testing.T) { check(bundle) } } + func TestReceiptMarshalBinary(t *testing.T) { // Legacy Receipt legacyReceipt.Bloom = CreateBloom(Receipts{legacyReceipt}) @@ -345,41 +400,36 @@ func TestReceiptUnmarshalBinary(t *testing.T) { } } -func clearComputedFieldsOnReceipts(t *testing.T, receipts Receipts) { - t.Helper() - - for _, receipt := range receipts { - clearComputedFieldsOnReceipt(t, receipt) +func clearComputedFieldsOnReceipts(receipts []*Receipt) []*Receipt { + r := make([]*Receipt, len(receipts)) + for i, receipt := range receipts { + r[i] = clearComputedFieldsOnReceipt(receipt) } + return r } -func clearComputedFieldsOnReceipt(t *testing.T, receipt *Receipt) { - t.Helper() - - receipt.TxHash = core.Hash{} - receipt.BlockHash = core.Hash{} - receipt.BlockNumber = big.NewInt(math.MaxUint32) - receipt.TransactionIndex = math.MaxUint32 - receipt.ContractAddress = core.Address{} - receipt.GasUsed = 0 - - clearComputedFieldsOnLogs(t, receipt.Logs) +func clearComputedFieldsOnReceipt(receipt *Receipt) *Receipt { + cpy := *receipt + cpy.TxHash = core.Hash{0xff, 0xff, 0x11} + cpy.BlockHash = core.Hash{0xff, 0xff, 0x22} + cpy.BlockNumber = big.NewInt(math.MaxUint32) + cpy.TransactionIndex = math.MaxUint32 + cpy.ContractAddress = core.Address{0xff, 0xff, 0x33} + cpy.GasUsed = 0xffffffff + cpy.Logs = clearComputedFieldsOnLogs(receipt.Logs) + return &cpy } -func clearComputedFieldsOnLogs(t *testing.T, logs []*Log) { - t.Helper() - - for _, log := range logs { - clearComputedFieldsOnLog(t, log) +func clearComputedFieldsOnLogs(logs []*Log) []*Log { + l := make([]*Log, len(logs)) + for i, log := range logs { + cpy := *log + cpy.BlockNumber = math.MaxUint32 + cpy.BlockHash = core.Hash{} + cpy.TxHash = core.Hash{} + cpy.TxIndex = math.MaxUint32 + cpy.Index = math.MaxUint32 + l[i] = &cpy } -} - -func clearComputedFieldsOnLog(t *testing.T, log *Log) { - t.Helper() - - log.BlockNumber = math.MaxUint32 - log.BlockHash = core.Hash{} - log.TxHash = core.Hash{} - log.TxIndex = math.MaxUint32 - log.Index = math.MaxUint32 + return l } diff --git a/restricted/types/transaction.go b/restricted/types/transaction.go index 3d122ee..2397e15 100644 --- a/restricted/types/transaction.go +++ b/restricted/types/transaction.go @@ -89,9 +89,20 @@ type TxData interface { rawSignatureValues() (v, r, s *big.Int) setSignatureValues(chainID, v, r, s *big.Int) + + // effectiveGasPrice computes the gas price paid by the transaction, given + // the inclusion block baseFee. + // + // Unlike other TxData methods, the returned *big.Int should be an independent + // copy of the computed value, i.e. callers are allowed to mutate the result. + // Method implementations can use 'dst' to store the result. + effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int } // EncodeRLP implements rlp.Encoder +// For a legacy Transaction this returns RLP([AccountNonce, GasPrice, GasLimit, Recipient, Amount, Data, V, R, S]) +// For a EIP-2718 Transaction this returns RLP(TxType || TxPayload) +// For a EIP-2930 Transaction, TxType == 0x01 and TxPayload == RLP([ChainID, AccountNonce, GasPrice, GasLimit, Recipient, Amount, Data, AccessList, V, R, S] func (tx *Transaction) EncodeRLP(w io.Writer) error { if tx.Type() == LegacyTxType { return rlp.Encode(w, tx.inner) @@ -112,9 +123,10 @@ func (tx *Transaction) encodeTyped(w *bytes.Buffer) error { return rlp.Encode(w, tx.inner) } -// MarshalBinary returns the canonical encoding of the transaction. -// For legacy transactions, it returns the RLP encoding. For EIP-2718 typed -// transactions, it returns the type and payload. +// MarshalBinary returns the canonical consensus encoding of the transaction. +// For a legacy Transaction this returns RLP([AccountNonce, GasPrice, GasLimit, Recipient, Amount, Data, V, R, S]) +// For a EIP-2718 Transaction this returns TxType || TxPayload +// For a EIP-2930 Transaction, TxType == 0x01 and TxPayload == RLP([ChainID, AccountNonce, GasPrice, GasLimit, Recipient, Amount, Data, AccessList, V, R, S] func (tx *Transaction) MarshalBinary() ([]byte, error) { if tx.Type() == LegacyTxType { return rlp.EncodeToBytes(tx.inner) @@ -135,10 +147,10 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error { var inner LegacyTx err := s.Decode(&inner) if err == nil { - tx.setDecoded(&inner, int(rlp.ListSize(size))) + tx.setDecoded(&inner, rlp.ListSize(size)) } return err - case kind == rlp.String: + default: // It's an EIP-2718 typed TX envelope. var b []byte if b, err = s.Bytes(); err != nil { @@ -146,11 +158,9 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error { } inner, err := tx.decodeTyped(b) if err == nil { - tx.setDecoded(inner, len(b)) + tx.setDecoded(inner, uint64(len(b))) } return err - default: - return rlp.ErrExpectedList } } @@ -164,7 +174,7 @@ func (tx *Transaction) UnmarshalBinary(b []byte) error { if err != nil { return err } - tx.setDecoded(&data, len(b)) + tx.setDecoded(&data, uint64(len(b))) return nil } // It's an EIP2718 typed transaction envelope. @@ -172,13 +182,13 @@ func (tx *Transaction) UnmarshalBinary(b []byte) error { if err != nil { return err } - tx.setDecoded(inner, len(b)) + tx.setDecoded(inner, uint64(len(b))) return nil } // decodeTyped decodes a typed transaction from the canonical format. func (tx *Transaction) decodeTyped(b []byte) (TxData, error) { - if len(b) == 0 { + if len(b) <= 1 { return nil, errEmptyTypedTx } switch b[0] { @@ -196,11 +206,11 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) { } // setDecoded sets the inner transaction and size after decoding. -func (tx *Transaction) setDecoded(inner TxData, size int) { +func (tx *Transaction) setDecoded(inner TxData, size uint64) { tx.inner = inner tx.time = time.Now() if size > 0 { - tx.size.Store(float64(size)) + tx.size.Store(size) } } @@ -288,13 +298,7 @@ func (tx *Transaction) Nonce() uint64 { return tx.inner.nonce() } // To returns the recipient address of the transaction. // For contract-creation transactions, To returns nil. func (tx *Transaction) To() *core.Address { - // Copy the pointed-to address. - ito := tx.inner.to() - if ito == nil { - return nil - } - cpy := *ito - return &cpy + return copyAddressPtr(tx.inner.to()) } // Cost returns gas * gasPrice + value. @@ -384,16 +388,21 @@ func (tx *Transaction) Hash() core.Hash { return h } -// Size returns the true RLP encoded storage size of the transaction, either by -// encoding and returning it, or returning a previously cached value. -func (tx *Transaction) Size() float64 { +// Size returns the true encoded storage size of the transaction, either by encoding +// and returning it, or returning a previously cached value. +func (tx *Transaction) Size() uint64 { if size := tx.size.Load(); size != nil { - return size.(float64) + return size.(uint64) } c := writeCounter(0) rlp.Encode(&c, &tx.inner) - tx.size.Store(float64(c)) - return float64(c) + + size := uint64(c) + if tx.Type() != LegacyTxType { + size += 1 // type byte + } + tx.size.Store(size) + return size } // WithSignature returns a new transaction with the given signature. @@ -444,6 +453,24 @@ func TxDifference(a, b Transactions) Transactions { return keep } +// HashDifference returns a new set which is the difference between a and b. +func HashDifference(a, b []core.Hash) []core.Hash { + keep := make([]core.Hash, 0, len(a)) + + remove := make(map[core.Hash]struct{}) + for _, hash := range b { + remove[hash] = struct{}{} + } + + for _, hash := range a { + if _, ok := remove[hash]; !ok { + keep = append(keep, hash) + } + } + + return keep +} + // TxByNonce implements the sort interface to allow sorting a list of transactions // by their nonces. This is usually only useful for sorting transactions from a // single account, otherwise a nonce comparison doesn't make much sense. @@ -497,6 +524,7 @@ func (s *TxByPriceAndTime) Pop() interface{} { old := *s n := len(old) x := old[n-1] + old[n-1] = nil *s = old[0 : n-1] return x } @@ -506,9 +534,9 @@ func (s *TxByPriceAndTime) Pop() interface{} { // entire batches of transactions for non-executable accounts. type TransactionsByPriceAndNonce struct { txs map[core.Address]Transactions // Per account nonce-sorted list of transactions - heads TxByPriceAndTime // Next transaction for each unique account (price heap) - signer Signer // Signer for the set of transactions - baseFee *big.Int // Current base fee + heads TxByPriceAndTime // Next transaction for each unique account (price heap) + signer Signer // Signer for the set of transactions + baseFee *big.Int // Current base fee } // NewTransactionsByPriceAndNonce creates a transaction set that can retrieve @@ -569,70 +597,11 @@ func (t *TransactionsByPriceAndNonce) Pop() { heap.Pop(&t.heads) } -// Message is a fully derived transaction and implements core.Message -// -// NOTE: In a future PR this will be removed. -type Message struct { - to *core.Address - from core.Address - nonce uint64 - amount *big.Int - gasLimit uint64 - gasPrice *big.Int - gasFeeCap *big.Int - gasTipCap *big.Int - data []byte - accessList AccessList - checkNonce bool -} - -func NewMessage(from core.Address, to *core.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice, gasFeeCap, gasTipCap *big.Int, data []byte, accessList AccessList, checkNonce bool) Message { - return Message{ - from: from, - to: to, - nonce: nonce, - amount: amount, - gasLimit: gasLimit, - gasPrice: gasPrice, - gasFeeCap: gasFeeCap, - gasTipCap: gasTipCap, - data: data, - accessList: accessList, - checkNonce: checkNonce, +// copyAddressPtr copies an address. +func copyAddressPtr(a *core.Address) *core.Address { + if a == nil { + return nil } + cpy := *a + return &cpy } - -// AsMessage returns the transaction as a core.Message. -func (tx *Transaction) AsMessage(s 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(), - amount: tx.Value(), - data: tx.Data(), - accessList: tx.AccessList(), - checkNonce: true, - } - // If baseFee provided, set gasPrice to effectiveGasPrice. - if baseFee != nil { - msg.gasPrice = bigMin(msg.gasPrice.Add(msg.gasTipCap, baseFee), msg.gasFeeCap) - } - var err error - msg.from, err = Sender(s, tx) - return msg, err -} - -func (m Message) From() core.Address { return m.from } -func (m Message) To() *core.Address { return m.to } -func (m Message) GasPrice() *big.Int { return m.gasPrice } -func (m Message) GasFeeCap() *big.Int { return m.gasFeeCap } -func (m Message) GasTipCap() *big.Int { return m.gasTipCap } -func (m Message) Value() *big.Int { return m.amount } -func (m Message) Gas() uint64 { return m.gasLimit } -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) CheckNonce() bool { return m.checkNonce }