From 357510617b9279351100f98c0d802d9587357bc0 Mon Sep 17 00:00:00 2001 From: yperbasis Date: Thu, 2 Jul 2026 22:34:21 +0200 Subject: [PATCH 1/7] execution/types: pin tx codec goldens for all transaction types Deterministic instances of every tx type (legacy incl. pre-EIP-155, access-list, dynamic-fee, blob incl. wrapped v0/v1, set-code with and without authorizations, account-abstraction; both contract-creation and non-nil To where the type allows it) with hex-pinned MarshalBinary, EncodeRLP, Hash, SigningHash and EncodingSize outputs, plus byte-identical decode/re-encode round-trips. These freeze the canonical wire and hash format ahead of deduplicating the codec implementations. --- execution/types/codec_golden_test.go | 449 +++++++++++++++++++++++++++ 1 file changed, 449 insertions(+) create mode 100644 execution/types/codec_golden_test.go 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) + } + }) + } +} From 7a4672aa8ce536f7b98b5fbbc6b6e85e31e275c3 Mon Sep 17 00:00:00 2001 From: yperbasis Date: Thu, 2 Jul 2026 22:35:20 +0200 Subject: [PATCH 2/7] execution/types: promote cachedSender to TransactionMisc, share Sender recovery The cachedSender bodies were identical across LegacyTx, AccessListTx, DynamicFeeTransaction and BlobTx and only touch TransactionMisc.from, so promote a single implementation from the embedded struct. Sender still needs the concrete type for signer.Sender, so each type keeps a one-line method delegating to a shared recoverSender helper (this also removes the cosmetic nested-if variant in AccessListTx, which was semantically identical). AccountAbstractionTransaction and BlobTxWrapper keep their intentionally different implementations. --- execution/types/access_list_tx.go | 21 +-------------------- execution/types/blob_tx.go | 19 +------------------ execution/types/dynamic_fee_tx.go | 18 +----------------- execution/types/legacy_tx.go | 19 +------------------ execution/types/set_code_tx.go | 11 +---------- execution/types/transaction.go | 21 +++++++++++++++++++++ 6 files changed, 26 insertions(+), 83 deletions(-) diff --git a/execution/types/access_list_tx.go b/execution/types/access_list_tx.go index 5470485a3f6..7f74fd365cf 100644 --- a/execution/types/access_list_tx.go +++ b/execution/types/access_list_tx.go @@ -452,25 +452,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..06a0fca7f0f 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 { diff --git a/execution/types/dynamic_fee_tx.go b/execution/types/dynamic_fee_tx.go index b4543e6f673..11440ebef0d 100644 --- a/execution/types/dynamic_fee_tx.go +++ b/execution/types/dynamic_fee_tx.go @@ -363,24 +363,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..b5ae3b1e84e 100644 --- a/execution/types/legacy_tx.go +++ b/execution/types/legacy_tx.go @@ -407,23 +407,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..4b7638bd0b4 100644 --- a/execution/types/set_code_tx.go +++ b/execution/types/set_code_tx.go @@ -159,16 +159,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 { diff --git a/execution/types/transaction.go b/execution/types/transaction.go index 391801311f7..0fdcb5a01c9 100644 --- a/execution/types/transaction.go +++ b/execution/types/transaction.go @@ -105,6 +105,27 @@ 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 +} + // 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 { From d66fa54e1ff62c35fd5c87de5274944ab8497522 Mon Sep 17 00:00:00 2001 From: yperbasis Date: Thu, 2 Jul 2026 22:36:11 +0200 Subject: [PATCH 3/7] execution/types: share typed-tx WithSignature signature application AccessListTx, DynamicFeeTransaction, BlobTx and SetCodeTransaction all applied SignatureValues + ChainID to their copy with the same code; route them through one applySignature helper. LegacyTx stays as is since it does not set ChainID. --- execution/types/access_list_tx.go | 7 +------ execution/types/blob_tx.go | 7 +------ execution/types/dynamic_fee_tx.go | 7 +------ execution/types/set_code_tx.go | 8 +------- execution/types/transaction.go | 12 ++++++++++++ 5 files changed, 16 insertions(+), 25 deletions(-) diff --git a/execution/types/access_list_tx.go b/execution/types/access_list_tx.go index 7f74fd365cf..6e991a264a5 100644 --- a/execution/types/access_list_tx.go +++ b/execution/types/access_list_tx.go @@ -392,14 +392,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 } diff --git a/execution/types/blob_tx.go b/execution/types/blob_tx.go index 06a0fca7f0f..60bab9809ea 100644 --- a/execution/types/blob_tx.go +++ b/execution/types/blob_tx.go @@ -169,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 } diff --git a/execution/types/dynamic_fee_tx.go b/execution/types/dynamic_fee_tx.go index 11440ebef0d..737dd210f0b 100644 --- a/execution/types/dynamic_fee_tx.go +++ b/execution/types/dynamic_fee_tx.go @@ -114,14 +114,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 } diff --git a/execution/types/set_code_tx.go b/execution/types/set_code_tx.go index 4b7638bd0b4..34b57514dd1 100644 --- a/execution/types/set_code_tx.go +++ b/execution/types/set_code_tx.go @@ -84,15 +84,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 } diff --git a/execution/types/transaction.go b/execution/types/transaction.go index 0fdcb5a01c9..fcb52e23f39 100644 --- a/execution/types/transaction.go +++ b/execution/types/transaction.go @@ -126,6 +126,18 @@ func recoverSender(txn Transaction, tm *TransactionMisc, signer Signer) (account 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 { From 1379c76c6b1d0d18c8bd0b25d9d624a80353e67c Mon Sep 17 00:00:00 2001 From: yperbasis Date: Thu, 2 Jul 2026 22:37:10 +0200 Subject: [PATCH 4/7] execution/types: extract CommonTx encodeVRS/decodeVRS All five tx types ended their payload encoding with the same V, R, S writes; move that tail onto the embedded CommonTx. The typed decoders (dynamic-fee, blob, set-code) get the matching decodeVRS. LegacyTx and AccessListTx keep their inline V/R/S decoding because they wrap each field error with its own context. --- execution/types/access_list_tx.go | 15 +-------------- execution/types/blob_tx.go | 23 ++-------------------- execution/types/dynamic_fee_tx.go | 23 ++-------------------- execution/types/legacy_tx.go | 32 ++++++++++++++++++++----------- execution/types/set_code_tx.go | 24 ++--------------------- 5 files changed, 28 insertions(+), 89 deletions(-) diff --git a/execution/types/access_list_tx.go b/execution/types/access_list_tx.go index 6e991a264a5..fa75e05d1d8 100644 --- a/execution/types/access_list_tx.go +++ b/execution/types/access_list_tx.go @@ -230,20 +230,7 @@ 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 diff --git a/execution/types/blob_tx.go b/execution/types/blob_tx.go index 60bab9809ea..cc1a0954825 100644 --- a/execution/types/blob_tx.go +++ b/execution/types/blob_tx.go @@ -270,19 +270,7 @@ func (stx *BlobTx) encodePayload(w io.Writer, b []byte, payloadSize, accessListL 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 { @@ -381,14 +369,7 @@ func (stx *BlobTx) DecodeRLP(s *rlp.Stream) error { 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/dynamic_fee_tx.go b/execution/types/dynamic_fee_tx.go index 737dd210f0b..ed40b7041c2 100644 --- a/execution/types/dynamic_fee_tx.go +++ b/execution/types/dynamic_fee_tx.go @@ -183,19 +183,7 @@ func (tx *DynamicFeeTransaction) encodePayload(w io.Writer, b []byte, payloadSiz 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) } func (tx *DynamicFeeTransaction) EncodeRLP(w io.Writer) error { @@ -253,14 +241,7 @@ func (tx *DynamicFeeTransaction) DecodeRLP(s *rlp.Stream) error { 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 err - } - if err = s.ReadUint256(&tx.S); err != nil { + if err = tx.decodeVRS(s); err != nil { return err } return s.ListEnd() diff --git a/execution/types/legacy_tx.go b/execution/types/legacy_tx.go index b5ae3b1e84e..a3da56664b5 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,17 +267,7 @@ 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 { diff --git a/execution/types/set_code_tx.go b/execution/types/set_code_tx.go index 34b57514dd1..135f18412f9 100644 --- a/execution/types/set_code_tx.go +++ b/execution/types/set_code_tx.go @@ -268,14 +268,7 @@ func (tx *SetCodeTransaction) DecodeRLP(s *rlp.Stream) error { return err } - // decode V - if err = s.ReadUint256(&tx.V); 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() @@ -334,20 +327,7 @@ func (tx *SetCodeTransaction) encodePayload(w io.Writer, b []byte, payloadSize, 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. From c94d9702c8fd569a7532b39b5f71a0f5be97c3f8 Mon Sep 17 00:00:00 2001 From: yperbasis Date: Thu, 2 Jul 2026 22:38:13 +0200 Subject: [PATCH 5/7] execution/types: extract DynamicFeeTransaction encode1559Prefix DynamicFeeTransaction, BlobTx and SetCodeTransaction encoded the same leading payload fields (list prefix, ChainID, Nonce, TipCap, FeeCap, GasLimit, To, Value, Data, AccessList) byte-for-byte; hoist them into one method on the embedded DynamicFeeTransaction that the per-type encodePayload composes with its own trailing fields. --- execution/types/blob_tx.go | 46 +------------------------------ execution/types/dynamic_fee_tx.go | 22 ++++++--------- execution/types/set_code_tx.go | 45 +----------------------------- 3 files changed, 11 insertions(+), 102 deletions(-) diff --git a/execution/types/blob_tx.go b/execution/types/blob_tx.go index cc1a0954825..04a477862c1 100644 --- a/execution/types/blob_tx.go +++ b/execution/types/blob_tx.go @@ -214,59 +214,15 @@ 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 { + if err := stx.encode1559Prefix(w, b, payloadSize, accessListLen); 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 { - 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 } diff --git a/execution/types/dynamic_fee_tx.go b/execution/types/dynamic_fee_tx.go index ed40b7041c2..fb137d5c919 100644 --- a/execution/types/dynamic_fee_tx.go +++ b/execution/types/dynamic_fee_tx.go @@ -138,49 +138,45 @@ func (tx *DynamicFeeTransaction) MarshalBinary(w io.Writer) error { return nil } -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 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 tx.encodeVRS(w, b) diff --git a/execution/types/set_code_tx.go b/execution/types/set_code_tx.go index 135f18412f9..a9ccbed6c6d 100644 --- a/execution/types/set_code_tx.go +++ b/execution/types/set_code_tx.go @@ -275,55 +275,12 @@ func (tx *SetCodeTransaction) DecodeRLP(s *rlp.Stream) error { } func (tx *SetCodeTransaction) encodePayload(w io.Writer, b []byte, payloadSize, accessListLen, authorizationsLen int) error { - // prefix - if err := rlp.EncodeListPrefix(payloadSize, w, b); err != nil { + if err := tx.encode1559Prefix(w, b, payloadSize, accessListLen); 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 - } - // 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 } From c0bf57610e9b37ded5f774b08f066e00bbef793f Mon Sep 17 00:00:00 2001 From: yperbasis Date: Thu, 2 Jul 2026 22:39:27 +0200 Subject: [PATCH 6/7] execution/types: extract DynamicFeeTransaction decode1559Prefix Mirror of encode1559Prefix for the decode side. DynamicFeeTransaction, BlobTx and SetCodeTransaction read the same leading payload fields; the only difference is that blob and set-code transactions require a 20-byte To (no contract creation), expressed via the strictTo flag. --- execution/types/blob_tx.go | 48 +++---------------------------- execution/types/dynamic_fee_tx.go | 29 +++++++++++++++---- execution/types/set_code_tx.go | 48 ++----------------------------- 3 files changed, 31 insertions(+), 94 deletions(-) diff --git a/execution/types/blob_tx.go b/execution/types/blob_tx.go index 04a477862c1..38497515d29 100644 --- a/execution/types/blob_tx.go +++ b/execution/types/blob_tx.go @@ -272,60 +272,20 @@ func (stx *BlobTx) MarshalBinary(w io.Writer) error { } 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 { - return err - } - stx.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(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 { + if err := stx.decode1559Prefix(s, true); err != nil { return err } - // decode MaxFeePerBlobGas - if err = s.ReadUint256(&stx.MaxFeePerBlobGas); err != nil { + 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") } - if err = stx.decodeVRS(s); err != nil { + if err := stx.decodeVRS(s); err != nil { return err } return s.ListEnd() diff --git a/execution/types/dynamic_fee_tx.go b/execution/types/dynamic_fee_tx.go index fb137d5c919..a424055f41f 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" @@ -203,7 +204,10 @@ func (tx *DynamicFeeTransaction) EncodeRLP(w io.Writer) error { return nil } -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 @@ -223,7 +227,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 { @@ -232,12 +248,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 decodeAccessList(&tx.AccessList, s) +} + +func (tx *DynamicFeeTransaction) DecodeRLP(s *rlp.Stream) error { + if err := tx.decode1559Prefix(s, false); err != nil { return err } - if err = tx.decodeVRS(s); err != nil { + if err := tx.decodeVRS(s); err != nil { return err } return s.ListEnd() diff --git a/execution/types/set_code_tx.go b/execution/types/set_code_tx.go index a9ccbed6c6d..cee0bdd75a3 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" @@ -220,55 +219,14 @@ func (tx *SetCodeTransaction) EncodeRLP(w io.Writer) error { } func (tx *SetCodeTransaction) DecodeRLP(s *rlp.Stream) error { - _, err := s.List() - if err != nil { + if err := tx.decode1559Prefix(s, true); err != nil { return err } - if err = s.ReadUint256(&tx.ChainID); 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 { + if err := decodeAuthorizations(&tx.Authorizations, s); err != nil { return err } - - if err = tx.decodeVRS(s); err != nil { + if err := tx.decodeVRS(s); err != nil { return err } return s.ListEnd() From ab39073675d3151ce3bae175c5c0d3ee750530ad Mon Sep 17 00:00:00 2001 From: yperbasis Date: Thu, 2 Jul 2026 22:42:05 +0200 Subject: [PATCH 7/7] execution/types: share typed-tx MarshalBinary/EncodeRLP envelope wrappers Every typed transaction wrapped its payload the same way: type byte for the canonical encoding, RLP string prefix + type byte for EncodeRLP. Hoist both envelopes into marshalTyped/encodeRLPTyped taking an encodePayload closure, mirroring the shape Hash already uses via prefixedPayloadHash. LegacyTx's EncodeRLP now delegates to its identical MarshalBinary. The closures do not escape (encode benchmarks stay at 0 allocs/op). --- execution/types/aa_transaction.go | 35 ++++++------------------------- execution/types/access_list_tx.go | 34 ++++++------------------------ execution/types/blob_tx.go | 34 ++++++------------------------ execution/types/dynamic_fee_tx.go | 34 ++++++------------------------ execution/types/legacy_tx.go | 8 +------ execution/types/set_code_tx.go | 31 ++++++--------------------- execution/types/transaction.go | 26 +++++++++++++++++++++++ 7 files changed, 57 insertions(+), 145 deletions(-) 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 fa75e05d1d8..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 { @@ -236,23 +228,9 @@ func (tx *AccessListTx) encodePayload(w io.Writer, b []byte, payloadSize, access // 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 { diff --git a/execution/types/blob_tx.go b/execution/types/blob_tx.go index 38497515d29..69ff03cbda4 100644 --- a/execution/types/blob_tx.go +++ b/execution/types/blob_tx.go @@ -234,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 { @@ -258,17 +244,9 @@ 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 { diff --git a/execution/types/dynamic_fee_tx.go b/execution/types/dynamic_fee_tx.go index a424055f41f..f9dfccde674 100644 --- a/execution/types/dynamic_fee_tx.go +++ b/execution/types/dynamic_fee_tx.go @@ -126,17 +126,9 @@ 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) + }) } // encode1559Prefix writes the payload list prefix and the leading fields shared @@ -185,23 +177,9 @@ func (tx *DynamicFeeTransaction) encodePayload(w io.Writer, b []byte, payloadSiz 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) + }) } // decode1559Prefix reads the payload fields shared by all EIP-1559-style diff --git a/execution/types/legacy_tx.go b/execution/types/legacy_tx.go index a3da56664b5..842286588c2 100644 --- a/execution/types/legacy_tx.go +++ b/execution/types/legacy_tx.go @@ -271,13 +271,7 @@ func (tx *LegacyTx) encodePayload(w io.Writer, b []byte, payloadSize int) error } 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 { diff --git a/execution/types/set_code_tx.go b/execution/types/set_code_tx.go index cee0bdd75a3..99dc6bda4f6 100644 --- a/execution/types/set_code_tx.go +++ b/execution/types/set_code_tx.go @@ -94,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) { @@ -202,20 +194,9 @@ 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 { diff --git a/execution/types/transaction.go b/execution/types/transaction.go index fcb52e23f39..6dd5e27e19a 100644 --- a/execution/types/transaction.go +++ b/execution/types/transaction.go @@ -310,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