diff --git a/execution/types/aa_transaction.go b/execution/types/aa_transaction.go index e1776f3de95..8ca33ab4da3 100644 --- a/execution/types/aa_transaction.go +++ b/execution/types/aa_transaction.go @@ -243,24 +243,9 @@ func (tx *AccountAbstractionTransaction) EncodingSize() int { func (tx *AccountAbstractionTransaction) EncodeRLP(w io.Writer) error { payloadSize, accessListLen, authorizationsLen := tx.payloadSize() - envelopSize := 1 + rlp.ListPrefixLen(payloadSize) + payloadSize - b := rlp.NewEncodingBuf() - defer b.Release() - // encode envelope size - if err := rlp.EncodeStringPrefix(envelopSize, w, b[:]); err != nil { - return err - } - // encode TxType - b[0] = AccountAbstractionTxType - if _, err := w.Write(b[:1]); err != nil { - return err - } - - if err := tx.encodePayload(w, b[:], payloadSize, accessListLen, authorizationsLen); err != nil { - return err - } - - return nil + return encodeRLPTyped(w, AccountAbstractionTxType, payloadSize, func(w io.Writer, b []byte) error { + return tx.encodePayload(w, b, payloadSize, accessListLen, authorizationsLen) + }) } func (tx *AccountAbstractionTransaction) encodePayload(w io.Writer, b []byte, payloadSize, accessListLen, authorizationsLen int) error { @@ -461,17 +446,9 @@ func (tx *AccountAbstractionTransaction) DecodeRLP(s *rlp.Stream) error { func (tx *AccountAbstractionTransaction) MarshalBinary(w io.Writer) error { payloadSize, accessListLen, authorizationsLen := tx.payloadSize() - b := rlp.NewEncodingBuf() - defer b.Release() - // encode TxType - b[0] = AccountAbstractionTxType - if _, err := w.Write(b[:1]); err != nil { - return err - } - if err := tx.encodePayload(w, b[:], payloadSize, accessListLen, authorizationsLen); err != nil { - return err - } - return nil + return marshalTyped(w, AccountAbstractionTxType, func(w io.Writer, b []byte) error { + return tx.encodePayload(w, b, payloadSize, accessListLen, authorizationsLen) + }) } func (tx *AccountAbstractionTransaction) PreTransactionGasCost(rules *chain.Rules, hasEIP3860 bool) (uint64, error) { diff --git a/execution/types/access_list_tx.go b/execution/types/access_list_tx.go index 5470485a3f6..3343fdc7859 100644 --- a/execution/types/access_list_tx.go +++ b/execution/types/access_list_tx.go @@ -176,17 +176,9 @@ func encodeAccessList(al AccessList, w io.Writer, b []byte) error { // transactions, it returns the type and payload. func (tx *AccessListTx) MarshalBinary(w io.Writer) error { payloadSize, accessListLen := tx.payloadSize() - b := rlp.NewEncodingBuf() - defer b.Release() - // encode TxType - b[0] = AccessListTxType - if _, err := w.Write(b[:1]); err != nil { - return err - } - if err := tx.encodePayload(w, b[:], payloadSize, accessListLen); err != nil { - return err - } - return nil + return marshalTyped(w, AccessListTxType, func(w io.Writer, b []byte) error { + return tx.encodePayload(w, b, payloadSize, accessListLen) + }) } func (tx *AccessListTx) encodePayload(w io.Writer, b []byte, payloadSize, accessListLen int) error { @@ -230,42 +222,15 @@ func (tx *AccessListTx) encodePayload(w io.Writer, b []byte, payloadSize, access if err := encodeAccessList(tx.AccessList, w, b); err != nil { return err } - // encode V - if err := rlp.EncodeUint256(tx.V, w, b); err != nil { - return err - } - // encode R - if err := rlp.EncodeUint256(tx.R, w, b); err != nil { - return err - } - // encode S - if err := rlp.EncodeUint256(tx.S, w, b); err != nil { - return err - } - return nil - + return tx.encodeVRS(w, b) } // EncodeRLP implements rlp.Encoder func (tx *AccessListTx) EncodeRLP(w io.Writer) error { payloadSize, accessListLen := tx.payloadSize() - // size of struct prefix and TxType - envelopeSize := 1 + rlp.ListPrefixLen(payloadSize) + payloadSize - b := rlp.NewEncodingBuf() - defer b.Release() - // envelope - if err := rlp.EncodeStringPrefix(envelopeSize, w, b[:]); err != nil { - return err - } - // encode TxType - b[0] = AccessListTxType - if _, err := w.Write(b[:1]); err != nil { - return err - } - if err := tx.encodePayload(w, b[:], payloadSize, accessListLen); err != nil { - return err - } - return nil + return encodeRLPTyped(w, AccessListTxType, payloadSize, func(w io.Writer, b []byte) error { + return tx.encodePayload(w, b, payloadSize, accessListLen) + }) } func decodeAccessList(al *AccessList, s *rlp.Stream) error { @@ -392,14 +357,9 @@ func (tx *AccessListTx) AsMessage(s Signer, _ *uint256.Int, rules *chain.Rules) func (tx *AccessListTx) WithSignature(signer Signer, sig []byte) (Transaction, error) { cpy := tx.copy() - r, s, v, err := signer.SignatureValues(tx, sig) - if err != nil { + if err := applySignature(signer, tx, sig, &cpy.CommonTx, &cpy.ChainID); err != nil { return nil, err } - cpy.R.Set(r) - cpy.S.Set(s) - cpy.V.Set(v) - cpy.ChainID = *signer.ChainID() return cpy, nil } @@ -452,25 +412,6 @@ func (tx *AccessListTx) GetChainID() *uint256.Int { return &tx.ChainID } -func (tx *AccessListTx) cachedSender() (sender accounts.Address, ok bool) { - s := tx.from - if s.IsNil() { - return sender, false - } - return s, true -} - func (tx *AccessListTx) Sender(signer Signer) (accounts.Address, error) { - if from := tx.from; !from.IsNil() { - if !from.IsZero() { // Sender address can never be zero in a transaction with a valid signer - return from, nil - } - } - - addr, err := signer.Sender(tx) - if err != nil { - return accounts.ZeroAddress, err - } - tx.from = addr - return addr, nil + return recoverSender(tx, &tx.TransactionMisc, signer) } diff --git a/execution/types/blob_tx.go b/execution/types/blob_tx.go index e664bd4bc94..69ff03cbda4 100644 --- a/execution/types/blob_tx.go +++ b/execution/types/blob_tx.go @@ -119,25 +119,8 @@ func (stx *BlobTx) AsMessage(s Signer, baseFee *uint256.Int, rules *chain.Rules) return &msg, nil } -func (stx *BlobTx) cachedSender() (sender accounts.Address, ok bool) { - s := stx.from - if s.IsNil() { - return sender, false - } - return s, true -} - func (stx *BlobTx) Sender(signer Signer) (accounts.Address, error) { - if from := stx.from; !from.IsNil() && !from.IsZero() { - // Sender address can never be zero in a transaction with a valid signer - return from, nil - } - addr, err := signer.Sender(stx) - if err != nil { - return accounts.ZeroAddress, err - } - stx.from = addr - return addr, nil + return recoverSender(stx, &stx.TransactionMisc, signer) } func (stx *BlobTx) Hash() common.Hash { @@ -186,14 +169,9 @@ func (stx *BlobTx) SigningHash(chainID *uint256.Int) common.Hash { func (stx *BlobTx) WithSignature(signer Signer, sig []byte) (Transaction, error) { cpy := stx.copy() - r, s, v, err := signer.SignatureValues(stx, sig) - if err != nil { + if err := applySignature(signer, stx, sig, &cpy.CommonTx, &cpy.ChainID); err != nil { return nil, err } - cpy.R.Set(r) - cpy.S.Set(s) - cpy.V.Set(v) - cpy.ChainID = *signer.ChainID() return cpy, nil } @@ -236,75 +214,19 @@ func encodeBlobVersionedHashes(hashes []common.Hash, w io.Writer, b []byte) erro } func (stx *BlobTx) encodePayload(w io.Writer, b []byte, payloadSize, accessListLen, blobHashesLen int) error { - // prefix - if err := rlp.EncodeListPrefix(payloadSize, w, b); err != nil { - return err - } - // encode ChainID - if err := rlp.EncodeUint256(stx.ChainID, w, b); err != nil { - return err - } - // encode Nonce - if err := rlp.EncodeU64(stx.Nonce, w, b); err != nil { - return err - } - // encode MaxPriorityFeePerGas - if err := rlp.EncodeUint256(stx.TipCap, w, b); err != nil { - return err - } - // encode MaxFeePerGas - if err := rlp.EncodeUint256(stx.FeeCap, w, b); err != nil { - return err - } - // encode GasLimit - if err := rlp.EncodeU64(stx.GasLimit, w, b); err != nil { - return err - } - // encode To - if err := EncodeOptionalAddress(stx.To, w, b); err != nil { + if err := stx.encode1559Prefix(w, b, payloadSize, accessListLen); err != nil { return err } - // encode Value - if err := rlp.EncodeUint256(stx.Value, w, b); err != nil { - return err - } - // encode Data - if err := rlp.EncodeString(stx.Data, w, b); err != nil { - return err - } - // prefix - if err := rlp.EncodeListPrefix(accessListLen, w, b); err != nil { - return err - } - // encode AccessList - if err := encodeAccessList(stx.AccessList, w, b); err != nil { - return err - } - // encode MaxFeePerBlobGas if err := rlp.EncodeUint256(stx.MaxFeePerBlobGas, w, b); err != nil { return err } - // prefix if err := rlp.EncodeListPrefix(blobHashesLen, w, b); err != nil { return err } - // encode BlobVersionedHashes if err := encodeBlobVersionedHashes(stx.BlobVersionedHashes, w, b); err != nil { return err } - // encode V - if err := rlp.EncodeUint256(stx.V, w, b); err != nil { - return err - } - // encode R - if err := rlp.EncodeUint256(stx.R, w, b); err != nil { - return err - } - // encode S - if err := rlp.EncodeUint256(stx.S, w, b); err != nil { - return err - } - return nil + return stx.encodeVRS(w, b) } func (stx *BlobTx) EncodeRLP(w io.Writer) error { @@ -312,23 +234,9 @@ func (stx *BlobTx) EncodeRLP(w io.Writer) error { return ErrNilToFieldTx } payloadSize, accessListLen, blobHashesLen := stx.payloadSize() - // size of struct prefix and TxType - envelopeSize := 1 + rlp.ListPrefixLen(payloadSize) + payloadSize - b := rlp.NewEncodingBuf() - defer b.Release() - // envelope - if err := rlp.EncodeStringPrefix(envelopeSize, w, b[:]); err != nil { - return err - } - // encode TxType - b[0] = BlobTxType - if _, err := w.Write(b[:1]); err != nil { - return err - } - if err := stx.encodePayload(w, b[:], payloadSize, accessListLen, blobHashesLen); err != nil { - return err - } - return nil + return encodeRLPTyped(w, BlobTxType, payloadSize, func(w io.Writer, b []byte) error { + return stx.encodePayload(w, b, payloadSize, accessListLen, blobHashesLen) + }) } func (stx *BlobTx) MarshalBinary(w io.Writer) error { @@ -336,81 +244,26 @@ func (stx *BlobTx) MarshalBinary(w io.Writer) error { return ErrNilToFieldTx } payloadSize, accessListLen, blobHashesLen := stx.payloadSize() - b := rlp.NewEncodingBuf() - defer b.Release() - // encode TxType - b[0] = BlobTxType - if _, err := w.Write(b[:1]); err != nil { - return err - } - if err := stx.encodePayload(w, b[:], payloadSize, accessListLen, blobHashesLen); err != nil { - return err - } - return nil + return marshalTyped(w, BlobTxType, func(w io.Writer, b []byte) error { + return stx.encodePayload(w, b, payloadSize, accessListLen, blobHashesLen) + }) } func (stx *BlobTx) DecodeRLP(s *rlp.Stream) error { - _, err := s.List() - if err != nil { - return err - } - if err = s.ReadUint256(&stx.ChainID); err != nil { - return err - } - if stx.Nonce, err = s.Uint64(); err != nil { - return err - } - if err = s.ReadUint256(&stx.TipCap); err != nil { - return err - } - if err = s.ReadUint256(&stx.FeeCap); err != nil { - return err - } - if stx.GasLimit, err = s.Uint64(); err != nil { + if err := stx.decode1559Prefix(s, true); err != nil { return err } - stx.To = &common.Address{} - if kind, size, err := s.Kind(); err != nil { + if err := s.ReadUint256(&stx.MaxFeePerBlobGas); err != nil { return err - } else if kind == rlp.Byte { - return fmt.Errorf("wrong size for To: 1") - } else if size != 20 { - return fmt.Errorf("wrong size for To: %d", size) } - if err = s.ReadBytes(stx.To[:]); err != nil { - return err - } - if err = s.ReadUint256(&stx.Value); err != nil { - return err - } - if stx.Data, err = s.Bytes(); err != nil { - return err - } - // decode AccessList - stx.AccessList = AccessList{} - if err = decodeAccessList(&stx.AccessList, s); err != nil { - return err - } - // decode MaxFeePerBlobGas - if err = s.ReadUint256(&stx.MaxFeePerBlobGas); err != nil { - return err - } - // decode BlobVersionedHashes stx.BlobVersionedHashes = []common.Hash{} - if err = decodeBlobVersionedHashes(&stx.BlobVersionedHashes, s); err != nil { + if err := decodeBlobVersionedHashes(&stx.BlobVersionedHashes, s); err != nil { return err } if len(stx.BlobVersionedHashes) == 0 { return errors.New("a blob stx must contain at least one blob") } - // decode V - if err = s.ReadUint256(&stx.V); err != nil { - return err - } - if err = s.ReadUint256(&stx.R); err != nil { - return err - } - if err = s.ReadUint256(&stx.S); err != nil { + if err := stx.decodeVRS(s); err != nil { return err } return s.ListEnd() diff --git a/execution/types/codec_golden_test.go b/execution/types/codec_golden_test.go new file mode 100644 index 00000000000..c20bcc3bef1 --- /dev/null +++ b/execution/types/codec_golden_test.go @@ -0,0 +1,449 @@ +// Copyright 2026 The Erigon Authors +// This file is part of Erigon. +// +// Erigon 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. +// +// Erigon 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 Erigon. If not, see . + +package types + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/holiman/uint256" + + "github.com/erigontech/erigon/common" + "github.com/erigontech/erigon/common/crypto" + "github.com/erigontech/erigon/execution/rlp" + "github.com/erigontech/erigon/execution/types/accounts" +) + +const goldenChainID = 1337 + +func goldenU256(s string) uint256.Int { + return *new(uint256.Int).SetBytes(common.FromHex(s)) +} + +func goldenTo() *common.Address { + to := common.HexToAddress("0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae") + return &to +} + +func goldenCommonTx(to *common.Address, v uint64) CommonTx { + return CommonTx{ + Nonce: 42, + GasLimit: 123457, + To: to, + Value: *uint256.NewInt(1000000000000000000), + Data: common.FromHex("0x600160010160005500"), + V: *uint256.NewInt(v), + R: goldenU256("0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"), + S: goldenU256("0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"), + } +} + +func goldenAccessListData() AccessList { + return AccessList{ + { + Address: common.HexToAddress("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + StorageKeys: []common.Hash{ + common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001"), + common.HexToHash("0xfe00000000000000000000000000000000000000000000000000000000000000"), + }, + }, + { + Address: common.HexToAddress("0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"), + StorageKeys: []common.Hash{ + common.HexToHash("0x00000000000000000000000000000000000000000000000000000000000000ff"), + }, + }, + } +} + +func goldenAuthorizations() []Authorization { + return []Authorization{ + { + ChainID: *uint256.NewInt(goldenChainID), + Address: common.HexToAddress("0xcccccccccccccccccccccccccccccccccccccccc"), + Nonce: 7, + YParity: 1, + R: goldenU256("0x1111111111111111111111111111111111111111111111111111111111111111"), + S: goldenU256("0x2222222222222222222222222222222222222222222222222222222222222222"), + }, + { + ChainID: *uint256.NewInt(1), + Address: common.HexToAddress("0xdddddddddddddddddddddddddddddddddddddddd"), + Nonce: 8, + YParity: 0, + R: *uint256.NewInt(3), + S: *uint256.NewInt(4), + }, + } +} + +func goldenLegacyTx(to *common.Address, v uint64) *LegacyTx { + return &LegacyTx{ + CommonTx: goldenCommonTx(to, v), + GasPrice: *uint256.NewInt(20000000000), + } +} + +func goldenAccessListTx(to *common.Address) *AccessListTx { + return &AccessListTx{ + LegacyTx: LegacyTx{ + CommonTx: goldenCommonTx(to, 1), + GasPrice: *uint256.NewInt(20000000000), + }, + ChainID: *uint256.NewInt(goldenChainID), + AccessList: goldenAccessListData(), + } +} + +func goldenDynFeeTxData(to *common.Address) DynamicFeeTransaction { + return DynamicFeeTransaction{ + CommonTx: goldenCommonTx(to, 1), + ChainID: *uint256.NewInt(goldenChainID), + TipCap: *uint256.NewInt(2000000000), + FeeCap: *uint256.NewInt(100000000000), + AccessList: goldenAccessListData(), + } +} + +func goldenDynFeeTx(to *common.Address) *DynamicFeeTransaction { + txn := goldenDynFeeTxData(to) + return &txn +} + +func goldenBlobTx() *BlobTx { + return &BlobTx{ + DynamicFeeTransaction: goldenDynFeeTxData(goldenTo()), + MaxFeePerBlobGas: *uint256.NewInt(123456789), + BlobVersionedHashes: []common.Hash{ + common.HexToHash("0x0100000000000000000000000000000000000000000000000000000000000aaa"), + common.HexToHash("0x0100000000000000000000000000000000000000000000000000000000000bbb"), + }, + } +} + +func goldenSetCodeTx(auths []Authorization) *SetCodeTransaction { + return &SetCodeTransaction{ + DynamicFeeTransaction: goldenDynFeeTxData(goldenTo()), + Authorizations: auths, + } +} + +func goldenAATx() *AccountAbstractionTransaction { + paymaster := common.HexToAddress("0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee") + deployer := common.HexToAddress("0xffffffffffffffffffffffffffffffffffffff01") + return &AccountAbstractionTransaction{ + Nonce: 42, + ChainID: uint256.NewInt(goldenChainID), + Tip: uint256.NewInt(2000000000), + FeeCap: uint256.NewInt(100000000000), + GasLimit: 123457, + AccessList: goldenAccessListData(), + SenderAddress: accounts.InternAddress(common.HexToAddress("0x9999999999999999999999999999999999999999")), + SenderValidationData: common.FromHex("0x0badc0de"), + ExecutionData: common.FromHex("0x600160010160005500"), + Paymaster: &paymaster, + PaymasterData: common.FromHex("0x1badc0de"), + Deployer: &deployer, + DeployerData: common.FromHex("0x2badc0de"), + BuilderFee: uint256.NewInt(777), + ValidationGasLimit: 50000, + PaymasterValidationGasLimit: 60000, + PostOpGasLimit: 70000, + Authorizations: goldenAuthorizations(), + NonceKey: uint256.NewInt(11), + } +} + +func goldenBlobTxWrapper(version byte) *BlobTxWrapper { + var blob Blob + for i := range blob { + blob[i] = byte(i % 251) + } + var commitment KZGCommitment + for i := range commitment { + commitment[i] = byte(i + 1) + } + var proof0, proof1 KZGProof + for i := range proof0 { + proof0[i] = byte(2*i + 1) + proof1[i] = byte(3*i + 2) + } + return &BlobTxWrapper{ + Tx: goldenBlobTx().copyData(), + WrapperVersion: version, + Commitments: BlobKzgs{commitment}, + Blobs: Blobs{blob}, + Proofs: KZGProofs{proof0, proof1}, + } +} + +type namedGoldenTx struct { + name string + txn Transaction +} + +func goldenCodecTxs() []namedGoldenTx { + return []namedGoldenTx{ + {"legacy-eip155", goldenLegacyTx(goldenTo(), 2*goldenChainID+36)}, + {"legacy-pre-eip155-create", goldenLegacyTx(nil, 28)}, + {"access-list", goldenAccessListTx(goldenTo())}, + {"access-list-create", goldenAccessListTx(nil)}, + {"dynamic-fee", goldenDynFeeTx(goldenTo())}, + {"dynamic-fee-create", goldenDynFeeTx(nil)}, + {"blob", goldenBlobTx()}, + {"set-code", goldenSetCodeTx(goldenAuthorizations())}, + {"set-code-no-auths", goldenSetCodeTx([]Authorization{})}, + {"account-abstraction", goldenAATx()}, + } +} + +type txCodecGolden struct { + marshalBinary string + encodeRLP string + hash string + signingHash string + encodingSize int +} + +type blobWrapperGolden struct { + wrappedSize int + wrappedKeccak string + innerHash string +} + +var txCodecGoldens = map[string]txCodecGolden{ + "legacy-eip155": { + marshalBinary: "f8782a8504a817c8008301e24194de0b295669a9fd93d5f28d9ec85e40f4cb697bae880de0b6b3a764000089600160010160005500820a96a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20a0202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + encodeRLP: "f8782a8504a817c8008301e24194de0b295669a9fd93d5f28d9ec85e40f4cb697bae880de0b6b3a764000089600160010160005500820a96a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20a0202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + hash: "e0c6ce68c7bf152006f9390c766eaacb0aafca988806e3887007c1e9cfaab656", + signingHash: "b7bf2cb062f921c4895cf438d9e1ce52c9b91e2b4694e897d489d4d3c772aed7", + encodingSize: 120, + }, + "legacy-pre-eip155-create": { + marshalBinary: "f8622a8504a817c8008301e24180880de0b6b3a7640000896001600101600055001ca00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20a0202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + encodeRLP: "f8622a8504a817c8008301e24180880de0b6b3a7640000896001600101600055001ca00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20a0202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + hash: "24836d05c6b9143f7ace9c01150b6ec965b4f04a1870ec8be8edbad96f56b47c", + signingHash: "2ca0630fdc395d51e7e8e3295dace494937a2e99efc216088c82218773f3ef94", + encodingSize: 98, + }, + "access-list": { + marshalBinary: "01f9010e8205392a8504a817c8008301e24194de0b295669a9fd93d5f28d9ec85e40f4cb697bae880de0b6b3a764000089600160010160005500f893f85994aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaf842a00000000000000000000000000000000000000000000000000000000000000001a0fe00000000000000000000000000000000000000000000000000000000000000f794bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbe1a000000000000000000000000000000000000000000000000000000000000000ff01a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20a0202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + encodeRLP: "b9011201f9010e8205392a8504a817c8008301e24194de0b295669a9fd93d5f28d9ec85e40f4cb697bae880de0b6b3a764000089600160010160005500f893f85994aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaf842a00000000000000000000000000000000000000000000000000000000000000001a0fe00000000000000000000000000000000000000000000000000000000000000f794bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbe1a000000000000000000000000000000000000000000000000000000000000000ff01a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20a0202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + hash: "3e59507ec16ad8a44a2280023c1618b54d821ce0bcef0f6ac714169d29b60f0f", + signingHash: "79db98c4ac795460b87ec740bf5589d44ded215a7303c929001565fa0f3b85c7", + encodingSize: 274, + }, + "access-list-create": { + marshalBinary: "01f8fa8205392a8504a817c8008301e24180880de0b6b3a764000089600160010160005500f893f85994aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaf842a00000000000000000000000000000000000000000000000000000000000000001a0fe00000000000000000000000000000000000000000000000000000000000000f794bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbe1a000000000000000000000000000000000000000000000000000000000000000ff01a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20a0202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + encodeRLP: "b8fd01f8fa8205392a8504a817c8008301e24180880de0b6b3a764000089600160010160005500f893f85994aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaf842a00000000000000000000000000000000000000000000000000000000000000001a0fe00000000000000000000000000000000000000000000000000000000000000f794bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbe1a000000000000000000000000000000000000000000000000000000000000000ff01a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20a0202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + hash: "69b1920fca67943a9da148803e5074feaa9e3038e4f4b900a4af80c4da7463d5", + signingHash: "4a5400c797498925a7483e5b1a16e5213f91cfd68082b5babf6207a354d3c22f", + encodingSize: 253, + }, + "dynamic-fee": { + marshalBinary: "02f901138205392a847735940085174876e8008301e24194de0b295669a9fd93d5f28d9ec85e40f4cb697bae880de0b6b3a764000089600160010160005500f893f85994aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaf842a00000000000000000000000000000000000000000000000000000000000000001a0fe00000000000000000000000000000000000000000000000000000000000000f794bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbe1a000000000000000000000000000000000000000000000000000000000000000ff01a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20a0202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + encodeRLP: "b9011702f901138205392a847735940085174876e8008301e24194de0b295669a9fd93d5f28d9ec85e40f4cb697bae880de0b6b3a764000089600160010160005500f893f85994aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaf842a00000000000000000000000000000000000000000000000000000000000000001a0fe00000000000000000000000000000000000000000000000000000000000000f794bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbe1a000000000000000000000000000000000000000000000000000000000000000ff01a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20a0202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + hash: "bbd74dd9e1950e2c6826dcf48915bfb6c750133056e9b94f266497e38c3d9392", + signingHash: "ac8477fb5d649719ddf0500f8275c1eda0fdc78abdda24923b3035b395db27d1", + encodingSize: 279, + }, + "dynamic-fee-create": { + marshalBinary: "02f8ff8205392a847735940085174876e8008301e24180880de0b6b3a764000089600160010160005500f893f85994aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaf842a00000000000000000000000000000000000000000000000000000000000000001a0fe00000000000000000000000000000000000000000000000000000000000000f794bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbe1a000000000000000000000000000000000000000000000000000000000000000ff01a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20a0202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + encodeRLP: "b9010202f8ff8205392a847735940085174876e8008301e24180880de0b6b3a764000089600160010160005500f893f85994aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaf842a00000000000000000000000000000000000000000000000000000000000000001a0fe00000000000000000000000000000000000000000000000000000000000000f794bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbe1a000000000000000000000000000000000000000000000000000000000000000ff01a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20a0202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + hash: "a9b8114832a64cc29ca7f71e627f21128d14fff6789a2adfe0cf8c6f84be78dd", + signingHash: "46fb7ec210d43665f813682022e052c8a01093d2355bbaf108fdbfae6724aa9c", + encodingSize: 258, + }, + "blob": { + marshalBinary: "03f9015c8205392a847735940085174876e8008301e24194de0b295669a9fd93d5f28d9ec85e40f4cb697bae880de0b6b3a764000089600160010160005500f893f85994aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaf842a00000000000000000000000000000000000000000000000000000000000000001a0fe00000000000000000000000000000000000000000000000000000000000000f794bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbe1a000000000000000000000000000000000000000000000000000000000000000ff84075bcd15f842a00100000000000000000000000000000000000000000000000000000000000aaaa00100000000000000000000000000000000000000000000000000000000000bbb01a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20a0202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + encodeRLP: "b9016003f9015c8205392a847735940085174876e8008301e24194de0b295669a9fd93d5f28d9ec85e40f4cb697bae880de0b6b3a764000089600160010160005500f893f85994aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaf842a00000000000000000000000000000000000000000000000000000000000000001a0fe00000000000000000000000000000000000000000000000000000000000000f794bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbe1a000000000000000000000000000000000000000000000000000000000000000ff84075bcd15f842a00100000000000000000000000000000000000000000000000000000000000aaaa00100000000000000000000000000000000000000000000000000000000000bbb01a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20a0202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + hash: "8adf24f0a9514fabbf9545219753b9079409dd86966b60fe473eb552520e6b23", + signingHash: "651ba3c9d41f67a58215d0143fa6d4773680bf69a832549a5e9bca3c76eec325", + encodingSize: 352, + }, + "set-code": { + marshalBinary: "04f9018e8205392a847735940085174876e8008301e24194de0b295669a9fd93d5f28d9ec85e40f4cb697bae880de0b6b3a764000089600160010160005500f893f85994aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaf842a00000000000000000000000000000000000000000000000000000000000000001a0fe00000000000000000000000000000000000000000000000000000000000000f794bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbe1a000000000000000000000000000000000000000000000000000000000000000fff879f85c82053994cccccccccccccccccccccccccccccccccccccccc0701a01111111111111111111111111111111111111111111111111111111111111111a02222222222222222222222222222222222222222222222222222222222222222da0194dddddddddddddddddddddddddddddddddddddddd0880030401a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20a0202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + encodeRLP: "b9019204f9018e8205392a847735940085174876e8008301e24194de0b295669a9fd93d5f28d9ec85e40f4cb697bae880de0b6b3a764000089600160010160005500f893f85994aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaf842a00000000000000000000000000000000000000000000000000000000000000001a0fe00000000000000000000000000000000000000000000000000000000000000f794bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbe1a000000000000000000000000000000000000000000000000000000000000000fff879f85c82053994cccccccccccccccccccccccccccccccccccccccc0701a01111111111111111111111111111111111111111111111111111111111111111a02222222222222222222222222222222222222222222222222222222222222222da0194dddddddddddddddddddddddddddddddddddddddd0880030401a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20a0202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + hash: "bcd118c9d0fa59d15557121ba0a7e0cb5775ce5733c269ea7cc989866820caa7", + signingHash: "aca6c69f60ac1c785b18d059f7f40bb2b8ace6fe17d673e3f0b6801719b463a1", + encodingSize: 402, + }, + "set-code-no-auths": { + marshalBinary: "04f901148205392a847735940085174876e8008301e24194de0b295669a9fd93d5f28d9ec85e40f4cb697bae880de0b6b3a764000089600160010160005500f893f85994aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaf842a00000000000000000000000000000000000000000000000000000000000000001a0fe00000000000000000000000000000000000000000000000000000000000000f794bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbe1a000000000000000000000000000000000000000000000000000000000000000ffc001a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20a0202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + encodeRLP: "b9011804f901148205392a847735940085174876e8008301e24194de0b295669a9fd93d5f28d9ec85e40f4cb697bae880de0b6b3a764000089600160010160005500f893f85994aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaf842a00000000000000000000000000000000000000000000000000000000000000001a0fe00000000000000000000000000000000000000000000000000000000000000f794bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbe1a000000000000000000000000000000000000000000000000000000000000000ffc001a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20a0202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + hash: "dbd52213d5d3ccbae3686bd4782a1052686cd5c145562d24e269186fc29a3180", + signingHash: "07d6e6d13c100d0dadc13eadb6f57f63845751d51b8f451867afcf9f4b464d90", + encodingSize: 280, + }, + "account-abstraction": { + marshalBinary: "05f901898205390b2a949999999999999999999999999999999999999999840badc0de94ffffffffffffffffffffffffffffffffffffff01842badc0de94eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee841badc0de89600160010160005500820309847735940085174876e80082c35082ea60830111708301e241f893f85994aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaf842a00000000000000000000000000000000000000000000000000000000000000001a0fe00000000000000000000000000000000000000000000000000000000000000f794bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbe1a000000000000000000000000000000000000000000000000000000000000000fff879f85c82053994cccccccccccccccccccccccccccccccccccccccc0701a01111111111111111111111111111111111111111111111111111111111111111a02222222222222222222222222222222222222222222222222222222222222222da0194dddddddddddddddddddddddddddddddddddddddd08800304", + encodeRLP: "b9018d05f901898205390b2a949999999999999999999999999999999999999999840badc0de94ffffffffffffffffffffffffffffffffffffff01842badc0de94eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee841badc0de89600160010160005500820309847735940085174876e80082c35082ea60830111708301e241f893f85994aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaf842a00000000000000000000000000000000000000000000000000000000000000001a0fe00000000000000000000000000000000000000000000000000000000000000f794bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbe1a000000000000000000000000000000000000000000000000000000000000000fff879f85c82053994cccccccccccccccccccccccccccccccccccccccc0701a01111111111111111111111111111111111111111111111111111111111111111a02222222222222222222222222222222222222222222222222222222222222222da0194dddddddddddddddddddddddddddddddddddddddd08800304", + hash: "b595c7deb680a67a1c8e3b9c75cde26b84f2e1f89bb4113b5ec2fd383d61c13b", + signingHash: "2dd6cd2886279f18b5df0b3936fbe92f4e6936e0130bcd61684539c3036512bc", + encodingSize: 397, + }, +} + +var blobWrapperGoldens = map[byte]blobWrapperGolden{ + 0: { + wrappedSize: 131586, + wrappedKeccak: "d90b9d4ba9a17f897aea81781180947857705cda159049528733b76b7b831d1a", + innerHash: "8adf24f0a9514fabbf9545219753b9079409dd86966b60fe473eb552520e6b23", + }, + 1: { + wrappedSize: 131587, + wrappedKeccak: "f2a9826163ad2bc26c2861cf68493a4d99452ebc664320de2f0bb0bb7899f0c6", + innerHash: "8adf24f0a9514fabbf9545219753b9079409dd86966b60fe473eb552520e6b23", + }, +} + +func TestTxCodecGoldens(t *testing.T) { + chainID := uint256.NewInt(goldenChainID) + for _, tc := range goldenCodecTxs() { + t.Run(tc.name, func(t *testing.T) { + want, ok := txCodecGoldens[tc.name] + if !ok { + t.Fatalf("no golden pinned for %q", tc.name) + } + + var mb bytes.Buffer + if err := tc.txn.MarshalBinary(&mb); err != nil { + t.Fatalf("MarshalBinary: %v", err) + } + if got := hex.EncodeToString(mb.Bytes()); got != want.marshalBinary { + t.Errorf("MarshalBinary drift:\n got %s\nwant %s", got, want.marshalBinary) + } + + var eb bytes.Buffer + if err := tc.txn.EncodeRLP(&eb); err != nil { + t.Fatalf("EncodeRLP: %v", err) + } + if got := hex.EncodeToString(eb.Bytes()); got != want.encodeRLP { + t.Errorf("EncodeRLP drift:\n got %s\nwant %s", got, want.encodeRLP) + } + + hash := tc.txn.Hash() + if got := hex.EncodeToString(hash[:]); got != want.hash { + t.Errorf("Hash drift: got %s want %s", got, want.hash) + } + signingHash := tc.txn.SigningHash(chainID) + if got := hex.EncodeToString(signingHash[:]); got != want.signingHash { + t.Errorf("SigningHash drift: got %s want %s", got, want.signingHash) + } + if got := tc.txn.EncodingSize(); got != want.encodingSize { + t.Errorf("EncodingSize drift: got %d want %d", got, want.encodingSize) + } + + decodedBin, err := UnmarshalTransactionFromBinary(mb.Bytes(), false) + if err != nil { + t.Fatalf("UnmarshalTransactionFromBinary: %v", err) + } + var mb2 bytes.Buffer + if err := decodedBin.MarshalBinary(&mb2); err != nil { + t.Fatalf("re-MarshalBinary: %v", err) + } + if !bytes.Equal(mb.Bytes(), mb2.Bytes()) { + t.Errorf("MarshalBinary round-trip not byte-identical:\n got %x\nwant %x", mb2.Bytes(), mb.Bytes()) + } + if got := decodedBin.Hash(); got != hash { + t.Errorf("decoded Hash mismatch: got %x want %x", got, hash) + } + + s := rlp.NewStream(bytes.NewReader(eb.Bytes()), 0) + decodedRLP, err := DecodeRLPTransaction(s, false) + if err != nil { + t.Fatalf("DecodeRLPTransaction: %v", err) + } + var eb2 bytes.Buffer + if err := decodedRLP.EncodeRLP(&eb2); err != nil { + t.Fatalf("re-EncodeRLP: %v", err) + } + if !bytes.Equal(eb.Bytes(), eb2.Bytes()) { + t.Errorf("EncodeRLP round-trip not byte-identical:\n got %x\nwant %x", eb2.Bytes(), eb.Bytes()) + } + if got := decodedRLP.Hash(); got != hash { + t.Errorf("RLP-decoded Hash mismatch: got %x want %x", got, hash) + } + }) + } +} + +func TestBlobTxWrapperCodecGoldens(t *testing.T) { + for _, version := range []byte{0, 1} { + t.Run(hex.EncodeToString([]byte{version}), func(t *testing.T) { + want, ok := blobWrapperGoldens[version] + if !ok { + t.Fatalf("no golden pinned for wrapper version %d", version) + } + txw := goldenBlobTxWrapper(version) + + var wrapped bytes.Buffer + if err := txw.MarshalBinaryWrapped(&wrapped); err != nil { + t.Fatalf("MarshalBinaryWrapped: %v", err) + } + if got := wrapped.Len(); got != want.wrappedSize { + t.Errorf("wrapped size drift: got %d want %d", got, want.wrappedSize) + } + keccak := crypto.HashData(wrapped.Bytes()) + if got := hex.EncodeToString(keccak[:]); got != want.wrappedKeccak { + t.Errorf("wrapped bytes drift (keccak): got %s want %s", got, want.wrappedKeccak) + } + innerHash := txw.Hash() + if got := hex.EncodeToString(innerHash[:]); got != want.innerHash { + t.Errorf("inner tx hash drift: got %s want %s", got, want.innerHash) + } + + var mb bytes.Buffer + if err := txw.MarshalBinary(&mb); err != nil { + t.Fatalf("MarshalBinary: %v", err) + } + if got := hex.EncodeToString(mb.Bytes()); got != txCodecGoldens["blob"].marshalBinary { + t.Errorf("wrapper MarshalBinary must match unwrapped blob golden:\n got %s\nwant %s", got, txCodecGoldens["blob"].marshalBinary) + } + var eb bytes.Buffer + if err := txw.EncodeRLP(&eb); err != nil { + t.Fatalf("EncodeRLP: %v", err) + } + if got := hex.EncodeToString(eb.Bytes()); got != txCodecGoldens["blob"].encodeRLP { + t.Errorf("wrapper EncodeRLP must match unwrapped blob golden:\n got %s\nwant %s", got, txCodecGoldens["blob"].encodeRLP) + } + + decoded, err := DecodeWrappedTransaction(wrapped.Bytes()) + if err != nil { + t.Fatalf("DecodeWrappedTransaction: %v", err) + } + decodedWrapper, ok := decoded.(*BlobTxWrapper) + if !ok { + t.Fatalf("expected *BlobTxWrapper, got %T", decoded) + } + if decodedWrapper.WrapperVersion != version { + t.Errorf("WrapperVersion mismatch: got %d want %d", decodedWrapper.WrapperVersion, version) + } + var wrapped2 bytes.Buffer + if err := decodedWrapper.MarshalBinaryWrapped(&wrapped2); err != nil { + t.Fatalf("re-MarshalBinaryWrapped: %v", err) + } + if !bytes.Equal(wrapped.Bytes(), wrapped2.Bytes()) { + t.Errorf("wrapped round-trip not byte-identical (%d vs %d bytes)", wrapped2.Len(), wrapped.Len()) + } + if got := decodedWrapper.Hash(); got != innerHash { + t.Errorf("decoded inner hash mismatch: got %x want %x", got, innerHash) + } + }) + } +} diff --git a/execution/types/dynamic_fee_tx.go b/execution/types/dynamic_fee_tx.go index b4543e6f673..f9dfccde674 100644 --- a/execution/types/dynamic_fee_tx.go +++ b/execution/types/dynamic_fee_tx.go @@ -21,6 +21,7 @@ package types import ( "errors" + "fmt" "io" "github.com/holiman/uint256" @@ -114,14 +115,9 @@ func (tx *DynamicFeeTransaction) payloadSize() (payloadSize int, accessListLen i func (tx *DynamicFeeTransaction) WithSignature(signer Signer, sig []byte) (Transaction, error) { cpy := tx.copy() - r, s, v, err := signer.SignatureValues(tx, sig) - if err != nil { + if err := applySignature(signer, tx, sig, &cpy.CommonTx, &cpy.ChainID); err != nil { return nil, err } - cpy.R.Set(r) - cpy.S.Set(s) - cpy.V.Set(v) - cpy.ChainID = *signer.ChainID() return cpy, nil } @@ -130,101 +126,66 @@ func (tx *DynamicFeeTransaction) WithSignature(signer Signer, sig []byte) (Trans // transactions, it returns the type and payload. func (tx *DynamicFeeTransaction) MarshalBinary(w io.Writer) error { payloadSize, accessListLen := tx.payloadSize() - b := rlp.NewEncodingBuf() - defer b.Release() - // encode TxType - b[0] = DynamicFeeTxType - if _, err := w.Write(b[:1]); err != nil { - return err - } - if err := tx.encodePayload(w, b[:], payloadSize, accessListLen); err != nil { - return err - } - return nil + return marshalTyped(w, DynamicFeeTxType, func(w io.Writer, b []byte) error { + return tx.encodePayload(w, b, payloadSize, accessListLen) + }) } -func (tx *DynamicFeeTransaction) encodePayload(w io.Writer, b []byte, payloadSize, accessListLen int) error { - // prefix +// encode1559Prefix writes the payload list prefix and the leading fields shared +// by all EIP-1559-style payloads: ChainID, Nonce, TipCap, FeeCap, GasLimit, To, +// Value, Data and AccessList. +func (tx *DynamicFeeTransaction) encode1559Prefix(w io.Writer, b []byte, payloadSize, accessListLen int) error { if err := rlp.EncodeListPrefix(payloadSize, w, b); err != nil { return err } - // encode ChainID if err := rlp.EncodeUint256(tx.ChainID, w, b); err != nil { return err } - // encode Nonce if err := rlp.EncodeU64(tx.Nonce, w, b); err != nil { return err } - // encode MaxPriorityFeePerGas if err := rlp.EncodeUint256(tx.TipCap, w, b); err != nil { return err } - // encode MaxFeePerGas if err := rlp.EncodeUint256(tx.FeeCap, w, b); err != nil { return err } - // encode GasLimit if err := rlp.EncodeU64(tx.GasLimit, w, b); err != nil { return err } - // encode To if err := EncodeOptionalAddress(tx.To, w, b); err != nil { return err } - // encode Value if err := rlp.EncodeUint256(tx.Value, w, b); err != nil { return err } - // encode Data if err := rlp.EncodeString(tx.Data, w, b); err != nil { return err } - // prefix if err := rlp.EncodeListPrefix(accessListLen, w, b); err != nil { return err } - // encode AccessList - if err := encodeAccessList(tx.AccessList, w, b); err != nil { - return err - } - // encode V - if err := rlp.EncodeUint256(tx.V, w, b); err != nil { - return err - } - // encode R - if err := rlp.EncodeUint256(tx.R, w, b); err != nil { - return err - } - // encode S - if err := rlp.EncodeUint256(tx.S, w, b); err != nil { + return encodeAccessList(tx.AccessList, w, b) +} + +func (tx *DynamicFeeTransaction) encodePayload(w io.Writer, b []byte, payloadSize, accessListLen int) error { + if err := tx.encode1559Prefix(w, b, payloadSize, accessListLen); err != nil { return err } - return nil + return tx.encodeVRS(w, b) } func (tx *DynamicFeeTransaction) EncodeRLP(w io.Writer) error { payloadSize, accessListLen := tx.payloadSize() - // size of struct prefix and TxType - envelopeSize := 1 + rlp.ListPrefixLen(payloadSize) + payloadSize - b := rlp.NewEncodingBuf() - defer b.Release() - // envelope - if err := rlp.EncodeStringPrefix(envelopeSize, w, b[:]); err != nil { - return err - } - // encode TxType - b[0] = DynamicFeeTxType - if _, err := w.Write(b[:1]); err != nil { - return err - } - if err := tx.encodePayload(w, b[:], payloadSize, accessListLen); err != nil { - return err - } - return nil + return encodeRLPTyped(w, DynamicFeeTxType, payloadSize, func(w io.Writer, b []byte) error { + return tx.encodePayload(w, b, payloadSize, accessListLen) + }) } -func (tx *DynamicFeeTransaction) DecodeRLP(s *rlp.Stream) error { +// decode1559Prefix reads the payload fields shared by all EIP-1559-style +// payloads, mirroring encode1559Prefix. With strictTo the To field must be a +// 20-byte address (no contract creation). +func (tx *DynamicFeeTransaction) decode1559Prefix(s *rlp.Stream, strictTo bool) error { _, err := s.List() if err != nil { return err @@ -244,7 +205,19 @@ func (tx *DynamicFeeTransaction) DecodeRLP(s *rlp.Stream) error { if tx.GasLimit, err = s.Uint64(); err != nil { return err } - if err = DecodeOptionalAddress(&tx.To, s); err != nil { + if strictTo { + tx.To = &common.Address{} + if kind, size, err := s.Kind(); err != nil { + return err + } else if kind == rlp.Byte { + return errors.New("wrong size for To: 1") + } else if size != 20 { + return fmt.Errorf("wrong size for To: %d", size) + } + if err = s.ReadBytes(tx.To[:]); err != nil { + return err + } + } else if err = DecodeOptionalAddress(&tx.To, s); err != nil { return err } if err = s.ReadUint256(&tx.Value); err != nil { @@ -253,19 +226,15 @@ func (tx *DynamicFeeTransaction) DecodeRLP(s *rlp.Stream) error { if tx.Data, err = s.Bytes(); err != nil { return err } - // decode AccessList tx.AccessList = AccessList{} - if err = decodeAccessList(&tx.AccessList, s); err != nil { - return err - } - // decode V - if err = s.ReadUint256(&tx.V); err != nil { - return err - } - if err = s.ReadUint256(&tx.R); err != nil { + return decodeAccessList(&tx.AccessList, s) +} + +func (tx *DynamicFeeTransaction) DecodeRLP(s *rlp.Stream) error { + if err := tx.decode1559Prefix(s, false); err != nil { return err } - if err = s.ReadUint256(&tx.S); err != nil { + if err := tx.decodeVRS(s); err != nil { return err } return s.ListEnd() @@ -363,24 +332,8 @@ func (tx *DynamicFeeTransaction) GetChainID() *uint256.Int { return &tx.ChainID } -func (tx *DynamicFeeTransaction) cachedSender() (sender accounts.Address, ok bool) { - s := tx.from - if s.IsNil() { - return sender, false - } - return s, true -} func (tx *DynamicFeeTransaction) Sender(signer Signer) (accounts.Address, error) { - if from := tx.from; !from.IsNil() && !from.IsZero() { - // Sender address can never be zero in a transaction with a valid signer - return from, nil - } - addr, err := signer.Sender(tx) - if err != nil { - return accounts.ZeroAddress, err - } - tx.from = addr - return addr, nil + return recoverSender(tx, &tx.TransactionMisc, signer) } // NewEIP1559Transaction creates an unsigned eip1559 transaction. diff --git a/execution/types/legacy_tx.go b/execution/types/legacy_tx.go index 6ecbf215033..842286588c2 100644 --- a/execution/types/legacy_tx.go +++ b/execution/types/legacy_tx.go @@ -107,6 +107,26 @@ func (ct *CommonTx) GetBlobHashes() []common.Hash { return []common.Hash{} } +func (ct *CommonTx) encodeVRS(w io.Writer, b []byte) error { + if err := rlp.EncodeUint256(ct.V, w, b); err != nil { + return err + } + if err := rlp.EncodeUint256(ct.R, w, b); err != nil { + return err + } + return rlp.EncodeUint256(ct.S, w, b) +} + +func (ct *CommonTx) decodeVRS(s *rlp.Stream) error { + if err := s.ReadUint256(&ct.V); err != nil { + return err + } + if err := s.ReadUint256(&ct.R); err != nil { + return err + } + return s.ReadUint256(&ct.S) +} + // LegacyTx is the transaction data of regular Ethereum transactions. type LegacyTx struct { CommonTx @@ -247,27 +267,11 @@ func (tx *LegacyTx) encodePayload(w io.Writer, b []byte, payloadSize int) error if err := rlp.EncodeString(tx.Data, w, b); err != nil { return err } - if err := rlp.EncodeUint256(tx.V, w, b); err != nil { - return err - } - if err := rlp.EncodeUint256(tx.R, w, b); err != nil { - return err - } - if err := rlp.EncodeUint256(tx.S, w, b); err != nil { - return err - } - return nil - + return tx.encodeVRS(w, b) } func (tx *LegacyTx) EncodeRLP(w io.Writer) error { - payloadSize := tx.payloadSize() - b := rlp.NewEncodingBuf() - defer b.Release() - if err := tx.encodePayload(w, b[:], payloadSize); err != nil { - return err - } - return nil + return tx.MarshalBinary(w) } func (tx *LegacyTx) DecodeRLP(s *rlp.Stream) error { @@ -407,23 +411,6 @@ func (tx *LegacyTx) GetChainID() *uint256.Int { return DeriveChainId(&tx.V) } -func (tx *LegacyTx) cachedSender() (sender accounts.Address, ok bool) { - s := tx.from - if s.IsNil() { - return sender, false - } - return s, true -} func (tx *LegacyTx) Sender(signer Signer) (accounts.Address, error) { - if from := tx.from; !from.IsNil() && !from.IsZero() { - // Sender address can never be zero in a transaction with a valid signer - return from, nil - } - - addr, err := signer.Sender(tx) - if err != nil { - return accounts.ZeroAddress, err - } - tx.from = addr - return addr, nil + return recoverSender(tx, &tx.TransactionMisc, signer) } diff --git a/execution/types/set_code_tx.go b/execution/types/set_code_tx.go index 96c5fcb728b..99dc6bda4f6 100644 --- a/execution/types/set_code_tx.go +++ b/execution/types/set_code_tx.go @@ -19,7 +19,6 @@ package types import ( "bytes" "errors" - "fmt" "io" "github.com/holiman/uint256" @@ -84,15 +83,9 @@ func (tx *SetCodeTransaction) payloadSize() (payloadSize, accessListLen, authori func (tx *SetCodeTransaction) WithSignature(signer Signer, sig []byte) (Transaction, error) { cpy := tx.copy() - r, s, v, err := signer.SignatureValues(tx, sig) - if err != nil { + if err := applySignature(signer, tx, sig, &cpy.CommonTx, &cpy.ChainID); err != nil { return nil, err } - - cpy.R.Set(r) - cpy.S.Set(s) - cpy.V.Set(v) - cpy.ChainID = *signer.ChainID() return cpy, nil } @@ -101,17 +94,9 @@ func (tx *SetCodeTransaction) MarshalBinary(w io.Writer) error { return ErrNilToFieldTx } payloadSize, accessListLen, authorizationsLen := tx.payloadSize() - b := rlp.NewEncodingBuf() - defer b.Release() - // encode TxType - b[0] = SetCodeTxType - if _, err := w.Write(b[:1]); err != nil { - return err - } - if err := tx.encodePayload(w, b[:], payloadSize, accessListLen, authorizationsLen); err != nil { - return err - } - return nil + return marshalTyped(w, SetCodeTxType, func(w io.Writer, b []byte) error { + return tx.encodePayload(w, b, payloadSize, accessListLen, authorizationsLen) + }) } func (tx *SetCodeTransaction) AsMessage(s Signer, baseFee *uint256.Int, rules *chain.Rules) (*Message, error) { @@ -159,16 +144,7 @@ func (tx *SetCodeTransaction) AsMessage(s Signer, baseFee *uint256.Int, rules *c } func (tx *SetCodeTransaction) Sender(signer Signer) (accounts.Address, error) { - if from := tx.from; !from.IsNil() && !from.IsZero() { - // Sender address can never be zero in a transaction with a valid signer - return from, nil - } - addr, err := signer.Sender(tx) - if err != nil { - return accounts.ZeroAddress, err - } - tx.from = addr - return addr, nil + return recoverSender(tx, &tx.TransactionMisc, signer) } func (tx *SetCodeTransaction) Hash() common.Hash { @@ -218,151 +194,36 @@ func (tx *SetCodeTransaction) EncodeRLP(w io.Writer) error { return ErrNilToFieldTx } payloadSize, accessListLen, authorizationsLen := tx.payloadSize() - envelopSize := 1 + rlp.ListPrefixLen(payloadSize) + payloadSize - b := rlp.NewEncodingBuf() - defer b.Release() - // encode envelope size - if err := rlp.EncodeStringPrefix(envelopSize, w, b[:]); err != nil { - return err - } - // encode TxType - b[0] = SetCodeTxType - if _, err := w.Write(b[:1]); err != nil { - return err - } - - return tx.encodePayload(w, b[:], payloadSize, accessListLen, authorizationsLen) + return encodeRLPTyped(w, SetCodeTxType, payloadSize, func(w io.Writer, b []byte) error { + return tx.encodePayload(w, b, payloadSize, accessListLen, authorizationsLen) + }) } func (tx *SetCodeTransaction) DecodeRLP(s *rlp.Stream) error { - _, err := s.List() - if err != nil { - return err - } - if err = s.ReadUint256(&tx.ChainID); err != nil { + if err := tx.decode1559Prefix(s, true); err != nil { return err } - if tx.Nonce, err = s.Uint64(); err != nil { - return err - } - if err = s.ReadUint256(&tx.TipCap); err != nil { - return err - } - if err = s.ReadUint256(&tx.FeeCap); err != nil { - return err - } - if tx.GasLimit, err = s.Uint64(); err != nil { - return err - } - tx.To = &common.Address{} - if kind, size, err := s.Kind(); err != nil { - return err - } else if kind == rlp.Byte { - return fmt.Errorf("wrong size for To: 1") - } else if size != 20 { - return fmt.Errorf("wrong size for To: %d", size) - } - if err = s.ReadBytes(tx.To[:]); err != nil { - return err - } - if err = s.ReadUint256(&tx.Value); err != nil { - return err - } - if tx.Data, err = s.Bytes(); err != nil { - return err - } - // decode AccessList - tx.AccessList = AccessList{} - if err = decodeAccessList(&tx.AccessList, s); err != nil { - return err - } - - // decode authorizations tx.Authorizations = make([]Authorization, 0) - if err = decodeAuthorizations(&tx.Authorizations, s); err != nil { - return err - } - - // decode V - if err = s.ReadUint256(&tx.V); err != nil { + if err := decodeAuthorizations(&tx.Authorizations, s); err != nil { return err } - if err = s.ReadUint256(&tx.R); err != nil { - return err - } - if err = s.ReadUint256(&tx.S); err != nil { + if err := tx.decodeVRS(s); err != nil { return err } return s.ListEnd() } func (tx *SetCodeTransaction) encodePayload(w io.Writer, b []byte, payloadSize, accessListLen, authorizationsLen int) error { - // prefix - if err := rlp.EncodeListPrefix(payloadSize, w, b); err != nil { - return err - } - // encode ChainID - if err := rlp.EncodeUint256(tx.ChainID, w, b); err != nil { - return err - } - // encode Nonce - if err := rlp.EncodeU64(tx.Nonce, w, b); err != nil { - return err - } - // encode MaxPriorityFeePerGas - if err := rlp.EncodeUint256(tx.TipCap, w, b); err != nil { - return err - } - // encode MaxFeePerGas - if err := rlp.EncodeUint256(tx.FeeCap, w, b); err != nil { - return err - } - // encode GasLimit - if err := rlp.EncodeU64(tx.GasLimit, w, b); err != nil { + if err := tx.encode1559Prefix(w, b, payloadSize, accessListLen); err != nil { return err } - // encode To - if err := EncodeOptionalAddress(tx.To, w, b); err != nil { - return err - } - // encode Value - if err := rlp.EncodeUint256(tx.Value, w, b); err != nil { - return err - } - // encode Data - if err := rlp.EncodeString(tx.Data, w, b); err != nil { - return err - } - // prefix - if err := rlp.EncodeListPrefix(accessListLen, w, b); err != nil { - return err - } - // encode AccessList - if err := encodeAccessList(tx.AccessList, w, b); err != nil { - return err - } - // prefix if err := rlp.EncodeListPrefix(authorizationsLen, w, b); err != nil { return err } - // encode Authorizations if err := encodeAuthorizations(tx.Authorizations, w, b); err != nil { return err } - // encode V - if err := rlp.EncodeUint256(tx.V, w, b); err != nil { - return err - } - // encode R - if err := rlp.EncodeUint256(tx.R, w, b); err != nil { - return err - } - // encode S - if err := rlp.EncodeUint256(tx.S, w, b); err != nil { - return err - } - return nil - + return tx.encodeVRS(w, b) } // ParseDelegation tries to parse the address from a delegation slice. diff --git a/execution/types/transaction.go b/execution/types/transaction.go index 391801311f7..6dd5e27e19a 100644 --- a/execution/types/transaction.go +++ b/execution/types/transaction.go @@ -105,6 +105,39 @@ type TransactionMisc struct { from accounts.Address } +func (tm *TransactionMisc) cachedSender() (sender accounts.Address, ok bool) { + s := tm.from + if s.IsNil() { + return sender, false + } + return s, true +} + +func recoverSender(txn Transaction, tm *TransactionMisc, signer Signer) (accounts.Address, error) { + if from := tm.from; !from.IsNil() && !from.IsZero() { + // Sender address can never be zero in a transaction with a valid signer + return from, nil + } + addr, err := signer.Sender(txn) + if err != nil { + return accounts.ZeroAddress, err + } + tm.from = addr + return addr, nil +} + +func applySignature(signer Signer, txn Transaction, sig []byte, cpy *CommonTx, cpyChainID *uint256.Int) error { + r, s, v, err := signer.SignatureValues(txn, sig) + if err != nil { + return err + } + cpy.R.Set(r) + cpy.S.Set(s) + cpy.V.Set(v) + *cpyChainID = *signer.ChainID() + return nil +} + // CalcEffectiveGasTip computes the effective gas tip given a transaction's tip/fee caps and a base fee. // Shared logic used by all transaction types that implement GetEffectiveGasTip. func CalcEffectiveGasTip(baseFee *uint256.Int, getTipCap func() *uint256.Int, getFeeCap func() *uint256.Int) uint256.Int { @@ -277,6 +310,32 @@ func UnwrapTxPlayloadRlp(blobTxRlp []byte) ([]byte, error) { return blobTxRlp, nil } +// marshalTyped writes the canonical EIP-2718 encoding: the type byte followed by the payload. +func marshalTyped(w io.Writer, txType byte, encodePayload func(w io.Writer, b []byte) error) error { + b := rlp.NewEncodingBuf() + defer b.Release() + b[0] = txType + if _, err := w.Write(b[:1]); err != nil { + return err + } + return encodePayload(w, b[:]) +} + +// encodeRLPTyped writes the canonical EIP-2718 encoding wrapped into an RLP string. +func encodeRLPTyped(w io.Writer, txType byte, payloadSize int, encodePayload func(w io.Writer, b []byte) error) error { + envelopeSize := 1 + rlp.ListPrefixLen(payloadSize) + payloadSize + b := rlp.NewEncodingBuf() + defer b.Release() + if err := rlp.EncodeStringPrefix(envelopeSize, w, b[:]); err != nil { + return err + } + b[0] = txType + if _, err := w.Write(b[:1]); err != nil { + return err + } + return encodePayload(w, b[:]) +} + func MarshalTransactionsBinary(txs Transactions) ([][]byte, error) { var err error var buf bytes.Buffer