chore(primitives): remove legacy transaction roundtrip tests (#22292)

Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Georgios Konstantopoulos
2026-02-17 13:15:14 -08:00
committed by GitHub
parent 477fed7a11
commit aeb2c6e731
4 changed files with 12 additions and 749 deletions

28
Cargo.lock generated
View File

@@ -419,9 +419,9 @@ dependencies = [
[[package]]
name = "alloy-node-bindings"
version = "1.6.3"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ce6930ce52e43b0768dc99ceeff5cb9e673e8c9f87d926914cd028b2e3f7233"
checksum = "e5a65d1ef42da862d7a528e95c170fa3066a81f23fbb9e778c213cf5e4063a0f"
dependencies = [
"alloy-genesis",
"alloy-hardforks 0.2.13",
@@ -2211,9 +2211,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.58"
version = "4.5.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806"
checksum = "c5caf74d17c3aec5495110c34cc3f78644bfa89af6c8993ed4de2790e49b6499"
dependencies = [
"clap_builder",
"clap_derive",
@@ -2221,9 +2221,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.58"
version = "4.5.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2"
checksum = "370daa45065b80218950227371916a1633217ae42b2715b2287b606dcd618e24"
dependencies = [
"anstream",
"anstyle",
@@ -7270,9 +7270,9 @@ dependencies = [
[[package]]
name = "rapidhash"
version = "4.3.0"
version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84816e4c99c467e92cf984ee6328caa976dfecd33a673544489d79ca2caaefe5"
checksum = "111325c42c4bafae99e777cd77b40dea9a2b30c69e9d8c74b6eccd7fba4337de"
dependencies = [
"rand 0.9.2",
"rustversion",
@@ -8708,17 +8708,13 @@ dependencies = [
"alloy-primitives",
"alloy-rlp",
"alloy-rpc-types-eth",
"alloy-serde",
"arbitrary",
"derive_more",
"proptest",
"proptest-arbitrary-interop",
"rand 0.8.5",
"rand 0.9.2",
"reth-codecs",
"reth-primitives-traits",
"reth-zstd-compressors",
"secp256k1 0.30.0",
"serde",
"serde_json",
"serde_with",
@@ -12320,9 +12316,9 @@ dependencies = [
[[package]]
name = "toml_parser"
version = "1.0.8+spec-1.1.0"
version = "1.0.9+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc"
checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4"
dependencies = [
"winnow",
]
@@ -12770,9 +12766,9 @@ checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
[[package]]
name = "unicode-ident"
version = "1.0.23"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-segmentation"

View File

@@ -26,17 +26,13 @@ alloy-rpc-types-eth = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
[dev-dependencies]
alloy-serde.workspace = true
derive_more.workspace = true
arbitrary.workspace = true
proptest.workspace = true
proptest-arbitrary-interop.workspace = true
rand_08.workspace = true
rand.workspace = true
alloy-rlp.workspace = true
reth-codecs = { workspace = true, features = ["test-utils"] }
reth-zstd-compressors.workspace = true
secp256k1 = { workspace = true, features = ["rand"] }
alloy-consensus = { workspace = true, features = ["serde", "arbitrary"] }
serde_json.workspace = true
serde_with.workspace = true
@@ -55,10 +51,7 @@ std = [
"alloy-eips/std",
"alloy-rpc-types-eth?/std",
"alloy-rlp/std",
"alloy-serde/std",
"derive_more/std",
"reth-zstd-compressors/std",
"secp256k1/std",
"serde_json/std",
"serde_with/std",
]
@@ -75,7 +68,6 @@ arbitrary = [
"reth-primitives-traits/arbitrary",
"alloy-eips/arbitrary",
"alloy-rpc-types-eth?/arbitrary",
"alloy-serde/arbitrary",
]
serde-bincode-compat = [
"alloy-consensus/serde-bincode-compat",
@@ -91,8 +83,6 @@ serde = [
"reth-codecs?/serde",
"reth-primitives-traits/serde",
"alloy-rpc-types-eth?/serde",
"rand_08/serde",
"rand/serde",
"secp256k1/serde",
]
rpc = ["dep:alloy-rpc-types-eth"]

View File

@@ -16,10 +16,6 @@ use reth_codecs as _;
mod receipt;
pub use receipt::*;
/// Kept for consistency tests
#[cfg(test)]
mod transaction;
pub use alloy_consensus::{transaction::PooledTransaction, TxType};
use alloy_consensus::{TxEip4844, TxEip4844WithSidecar};
use alloy_eips::eip7594::BlobTransactionSidecarVariant;

View File

@@ -1,719 +0,0 @@
//! This file contains the legacy reth `TransactionSigned` type that has been replaced with
//! alloy's TxEnvelope To test for consistency this is kept
extern crate alloc;
use alloc::vec::Vec;
use alloy_consensus::{
transaction::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx, SignerRecoverable, TxHashRef},
EthereumTxEnvelope, SignableTransaction, Signed, TxEip1559, TxEip2930, TxEip4844, TxEip7702,
TxLegacy, TxType, Typed2718,
};
use alloy_eips::{
eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718, IsTyped2718},
eip2930::AccessList,
eip7702::SignedAuthorization,
};
use alloy_primitives::{
bytes::BufMut, keccak256, Address, Bytes, ChainId, Signature, TxHash, TxKind, B256, U256,
};
use alloy_rlp::{Decodable, Encodable};
use core::hash::{Hash, Hasher};
use reth_primitives_traits::{
crypto::secp256k1::{recover_signer, recover_signer_unchecked},
sync::OnceLock,
transaction::signed::RecoveryError,
InMemorySize, SignedTransaction,
};
macro_rules! delegate {
($self:expr => $tx:ident.$method:ident($($arg:expr),*)) => {
match $self {
Transaction::Legacy($tx) => $tx.$method($($arg),*),
Transaction::Eip2930($tx) => $tx.$method($($arg),*),
Transaction::Eip1559($tx) => $tx.$method($($arg),*),
Transaction::Eip4844($tx) => $tx.$method($($arg),*),
Transaction::Eip7702($tx) => $tx.$method($($arg),*),
}
};
}
/// A raw transaction.
///
/// Transaction types were introduced in [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718).
#[derive(Debug, Clone, PartialEq, Eq, Hash, derive_more::From)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(compact))]
pub enum Transaction {
/// Legacy transaction (type `0x0`).
///
/// Traditional Ethereum transactions, containing parameters `nonce`, `gasPrice`, `gasLimit`,
/// `to`, `value`, `data`, `v`, `r`, and `s`.
///
/// These transactions do not utilize access lists nor do they incorporate EIP-1559 fee market
/// changes.
Legacy(TxLegacy),
/// Transaction with an [`AccessList`] ([EIP-2930](https://eips.ethereum.org/EIPS/eip-2930)), type `0x1`.
///
/// The `accessList` specifies an array of addresses and storage keys that the transaction
/// plans to access, enabling gas savings on cross-contract calls by pre-declaring the accessed
/// contract and storage slots.
Eip2930(TxEip2930),
/// A transaction with a priority fee ([EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)), type `0x2`.
///
/// Unlike traditional transactions, EIP-1559 transactions use an in-protocol, dynamically
/// changing base fee per gas, adjusted at each block to manage network congestion.
///
/// - `maxPriorityFeePerGas`, specifying the maximum fee above the base fee the sender is
/// willing to pay
/// - `maxFeePerGas`, setting the maximum total fee the sender is willing to pay.
///
/// The base fee is burned, while the priority fee is paid to the miner who includes the
/// transaction, incentivizing miners to include transactions with higher priority fees per
/// gas.
Eip1559(TxEip1559),
/// Shard Blob Transactions ([EIP-4844](https://eips.ethereum.org/EIPS/eip-4844)), type `0x3`.
///
/// Shard Blob Transactions introduce a new transaction type called a blob-carrying transaction
/// to reduce gas costs. These transactions are similar to regular Ethereum transactions but
/// include additional data called a blob.
///
/// Blobs are larger (~125 kB) and cheaper than the current calldata, providing an immutable
/// and read-only memory for storing transaction data.
///
/// EIP-4844, also known as proto-danksharding, implements the framework and logic of
/// danksharding, introducing new transaction formats and verification rules.
Eip4844(TxEip4844),
/// EOA Set Code Transactions ([EIP-7702](https://eips.ethereum.org/EIPS/eip-7702)), type `0x4`.
///
/// EOA Set Code Transactions give the ability to set contract code for an EOA in perpetuity
/// until re-assigned by the same EOA. This allows for adding smart contract functionality to
/// the EOA.
Eip7702(TxEip7702),
}
impl Transaction {
/// Returns [`TxType`] of the transaction.
pub const fn tx_type(&self) -> TxType {
match self {
Self::Legacy(_) => TxType::Legacy,
Self::Eip2930(_) => TxType::Eip2930,
Self::Eip1559(_) => TxType::Eip1559,
Self::Eip4844(_) => TxType::Eip4844,
Self::Eip7702(_) => TxType::Eip7702,
}
}
#[cfg(test)]
const fn input_mut(&mut self) -> &mut Bytes {
match self {
Self::Legacy(tx) => &mut tx.input,
Self::Eip2930(tx) => &mut tx.input,
Self::Eip1559(tx) => &mut tx.input,
Self::Eip4844(tx) => &mut tx.input,
Self::Eip7702(tx) => &mut tx.input,
}
}
}
impl Typed2718 for Transaction {
fn ty(&self) -> u8 {
delegate!(self => tx.ty())
}
}
impl alloy_consensus::Transaction for Transaction {
fn chain_id(&self) -> Option<ChainId> {
delegate!(self => tx.chain_id())
}
fn nonce(&self) -> u64 {
delegate!(self => tx.nonce())
}
fn gas_limit(&self) -> u64 {
delegate!(self => tx.gas_limit())
}
fn gas_price(&self) -> Option<u128> {
delegate!(self => tx.gas_price())
}
fn max_fee_per_gas(&self) -> u128 {
delegate!(self => tx.max_fee_per_gas())
}
fn max_priority_fee_per_gas(&self) -> Option<u128> {
delegate!(self => tx.max_priority_fee_per_gas())
}
fn max_fee_per_blob_gas(&self) -> Option<u128> {
delegate!(self => tx.max_fee_per_blob_gas())
}
fn priority_fee_or_price(&self) -> u128 {
delegate!(self => tx.priority_fee_or_price())
}
fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
delegate!(self => tx.effective_gas_price(base_fee))
}
fn is_dynamic_fee(&self) -> bool {
delegate!(self => tx.is_dynamic_fee())
}
fn kind(&self) -> alloy_primitives::TxKind {
delegate!(self => tx.kind())
}
fn is_create(&self) -> bool {
delegate!(self => tx.is_create())
}
fn value(&self) -> alloy_primitives::U256 {
delegate!(self => tx.value())
}
fn input(&self) -> &alloy_primitives::Bytes {
delegate!(self => tx.input())
}
fn access_list(&self) -> Option<&alloy_eips::eip2930::AccessList> {
delegate!(self => tx.access_list())
}
fn blob_versioned_hashes(&self) -> Option<&[B256]> {
delegate!(self => tx.blob_versioned_hashes())
}
fn authorization_list(&self) -> Option<&[alloy_eips::eip7702::SignedAuthorization]> {
delegate!(self => tx.authorization_list())
}
}
impl SignableTransaction<Signature> for Transaction {
fn set_chain_id(&mut self, chain_id: alloy_primitives::ChainId) {
delegate!(self => tx.set_chain_id(chain_id))
}
fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
delegate!(self => tx.encode_for_signing(out))
}
fn payload_len_for_signature(&self) -> usize {
delegate!(self => tx.payload_len_for_signature())
}
fn into_signed(self, signature: Signature) -> Signed<Self> {
let tx_hash = delegate!(&self => tx.tx_hash(&signature));
Signed::new_unchecked(self, signature, tx_hash)
}
}
impl InMemorySize for Transaction {
fn size(&self) -> usize {
delegate!(self => tx.size())
}
}
#[cfg(any(test, feature = "reth-codec"))]
impl reth_codecs::Compact for Transaction {
// Serializes the TxType to the buffer if necessary, returning 2 bits of the type as an
// identifier instead of the length.
fn to_compact<B>(&self, buf: &mut B) -> usize
where
B: alloy_rlp::bytes::BufMut + AsMut<[u8]>,
{
let identifier = self.tx_type().to_compact(buf);
delegate!(self => tx.to_compact(buf));
identifier
}
// For backwards compatibility purposes, only 2 bits of the type are encoded in the identifier
// parameter. In the case of a [`COMPACT_EXTENDED_IDENTIFIER_FLAG`], the full transaction type
// is read from the buffer as a single byte.
//
// # Panics
//
// A panic will be triggered if an identifier larger than 3 is passed from the database. For
// optimism an identifier with value [`DEPOSIT_TX_TYPE_ID`] is allowed.
fn from_compact(buf: &[u8], identifier: usize) -> (Self, &[u8]) {
let (tx_type, buf) = TxType::from_compact(buf, identifier);
match tx_type {
TxType::Legacy => {
let (tx, buf) = TxLegacy::from_compact(buf, buf.len());
(Self::Legacy(tx), buf)
}
TxType::Eip2930 => {
let (tx, buf) = TxEip2930::from_compact(buf, buf.len());
(Self::Eip2930(tx), buf)
}
TxType::Eip1559 => {
let (tx, buf) = TxEip1559::from_compact(buf, buf.len());
(Self::Eip1559(tx), buf)
}
TxType::Eip4844 => {
let (tx, buf) = TxEip4844::from_compact(buf, buf.len());
(Self::Eip4844(tx), buf)
}
TxType::Eip7702 => {
let (tx, buf) = TxEip7702::from_compact(buf, buf.len());
(Self::Eip7702(tx), buf)
}
}
}
}
impl RlpEcdsaEncodableTx for Transaction {
fn rlp_encoded_fields_length(&self) -> usize {
delegate!(self => tx.rlp_encoded_fields_length())
}
fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
delegate!(self => tx.rlp_encode_fields(out))
}
fn eip2718_encode_with_type(&self, signature: &Signature, _ty: u8, out: &mut dyn BufMut) {
delegate!(self => tx.eip2718_encode_with_type(signature, tx.ty(), out))
}
fn eip2718_encode(&self, signature: &Signature, out: &mut dyn BufMut) {
delegate!(self => tx.eip2718_encode(signature, out))
}
fn network_encode_with_type(&self, signature: &Signature, _ty: u8, out: &mut dyn BufMut) {
delegate!(self => tx.network_encode_with_type(signature, tx.ty(), out))
}
fn network_encode(&self, signature: &Signature, out: &mut dyn BufMut) {
delegate!(self => tx.network_encode(signature, out))
}
fn tx_hash_with_type(&self, signature: &Signature, _ty: u8) -> TxHash {
delegate!(self => tx.tx_hash_with_type(signature, tx.ty()))
}
fn tx_hash(&self, signature: &Signature) -> TxHash {
delegate!(self => tx.tx_hash(signature))
}
}
/// Signed Ethereum transaction.
#[derive(Debug, Clone, Eq, derive_more::AsRef, derive_more::Deref)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(rlp))]
#[cfg_attr(feature = "test-utils", derive(derive_more::DerefMut))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct TransactionSigned {
/// Transaction hash
#[cfg_attr(feature = "serde", serde(skip))]
hash: OnceLock<TxHash>,
/// The transaction signature values
signature: Signature,
/// Raw transaction info
#[deref]
#[as_ref]
#[cfg_attr(feature = "test-utils", deref_mut)]
transaction: Transaction,
}
impl TransactionSigned {
fn recalculate_hash(&self) -> B256 {
keccak256(self.encoded_2718())
}
}
impl Hash for TransactionSigned {
fn hash<H: Hasher>(&self, state: &mut H) {
self.signature.hash(state);
self.transaction.hash(state);
}
}
impl PartialEq for TransactionSigned {
fn eq(&self, other: &Self) -> bool {
self.signature == other.signature &&
self.transaction == other.transaction &&
self.tx_hash() == other.tx_hash()
}
}
impl TransactionSigned {
/// Creates a new signed transaction from the given transaction, signature and hash.
pub fn new(transaction: Transaction, signature: Signature, hash: B256) -> Self {
Self { hash: hash.into(), signature, transaction }
}
/// Returns the transaction hash.
#[inline]
pub fn hash(&self) -> &B256 {
self.hash.get_or_init(|| self.recalculate_hash())
}
/// Splits the transaction into parts.
pub fn into_parts(self) -> (Transaction, Signature, B256) {
let hash = *self.hash.get_or_init(|| self.recalculate_hash());
(self.transaction, self.signature, hash)
}
}
impl Typed2718 for TransactionSigned {
fn ty(&self) -> u8 {
self.transaction.ty()
}
}
impl alloy_consensus::Transaction for TransactionSigned {
fn chain_id(&self) -> Option<ChainId> {
self.transaction.chain_id()
}
fn nonce(&self) -> u64 {
self.transaction.nonce()
}
fn gas_limit(&self) -> u64 {
self.transaction.gas_limit()
}
fn gas_price(&self) -> Option<u128> {
self.transaction.gas_price()
}
fn max_fee_per_gas(&self) -> u128 {
self.transaction.max_fee_per_gas()
}
fn max_priority_fee_per_gas(&self) -> Option<u128> {
self.transaction.max_priority_fee_per_gas()
}
fn max_fee_per_blob_gas(&self) -> Option<u128> {
self.transaction.max_fee_per_blob_gas()
}
fn priority_fee_or_price(&self) -> u128 {
self.transaction.priority_fee_or_price()
}
fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
self.transaction.effective_gas_price(base_fee)
}
fn is_dynamic_fee(&self) -> bool {
self.transaction.is_dynamic_fee()
}
fn kind(&self) -> TxKind {
self.transaction.kind()
}
fn is_create(&self) -> bool {
self.transaction.is_create()
}
fn value(&self) -> U256 {
self.transaction.value()
}
fn input(&self) -> &Bytes {
self.transaction.input()
}
fn access_list(&self) -> Option<&AccessList> {
self.transaction.access_list()
}
fn blob_versioned_hashes(&self) -> Option<&[B256]> {
self.transaction.blob_versioned_hashes()
}
fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
self.transaction.authorization_list()
}
}
impl From<Signed<Transaction>> for TransactionSigned {
fn from(value: Signed<Transaction>) -> Self {
let (tx, sig, hash) = value.into_parts();
Self::new(tx, sig, hash)
}
}
impl From<TransactionSigned> for EthereumTxEnvelope<TxEip4844> {
fn from(value: TransactionSigned) -> Self {
let (tx, signature, hash) = value.into_parts();
match tx {
Transaction::Legacy(tx) => Signed::new_unchecked(tx, signature, hash).into(),
Transaction::Eip2930(tx) => Signed::new_unchecked(tx, signature, hash).into(),
Transaction::Eip1559(tx) => Signed::new_unchecked(tx, signature, hash).into(),
Transaction::Eip4844(tx) => Signed::new_unchecked(tx, signature, hash).into(),
Transaction::Eip7702(tx) => Signed::new_unchecked(tx, signature, hash).into(),
}
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for TransactionSigned {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
#[expect(unused_mut)]
let mut transaction = Transaction::arbitrary(u)?;
let secp = secp256k1::Secp256k1::new();
let key_pair = secp256k1::Keypair::new(&secp, &mut rand_08::thread_rng());
let signature = reth_primitives_traits::crypto::secp256k1::sign_message(
B256::from_slice(&key_pair.secret_bytes()[..]),
transaction.signature_hash(),
)
.unwrap();
Ok(Self { transaction, signature, hash: Default::default() })
}
}
impl InMemorySize for TransactionSigned {
fn size(&self) -> usize {
let Self { hash: _, signature, transaction } = self;
self.tx_hash().size() + signature.size() + transaction.size()
}
}
impl Encodable2718 for TransactionSigned {
fn type_flag(&self) -> Option<u8> {
(!self.transaction.is_legacy()).then(|| self.ty())
}
fn encode_2718_len(&self) -> usize {
delegate!(&self.transaction => tx.eip2718_encoded_length(&self.signature))
}
fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) {
delegate!(&self.transaction => tx.eip2718_encode(&self.signature, out))
}
fn trie_hash(&self) -> B256 {
*self.tx_hash()
}
}
impl Decodable2718 for TransactionSigned {
fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
match ty.try_into().map_err(|_| Eip2718Error::UnexpectedType(ty))? {
TxType::Legacy => Err(Eip2718Error::UnexpectedType(0)),
TxType::Eip2930 => {
let (tx, signature) = TxEip2930::rlp_decode_with_signature(buf)?;
Ok(Self {
transaction: Transaction::Eip2930(tx),
signature,
hash: Default::default(),
})
}
TxType::Eip1559 => {
let (tx, signature) = TxEip1559::rlp_decode_with_signature(buf)?;
Ok(Self {
transaction: Transaction::Eip1559(tx),
signature,
hash: Default::default(),
})
}
TxType::Eip4844 => {
let (tx, signature) = TxEip4844::rlp_decode_with_signature(buf)?;
Ok(Self {
transaction: Transaction::Eip4844(tx),
signature,
hash: Default::default(),
})
}
TxType::Eip7702 => {
let (tx, signature) = TxEip7702::rlp_decode_with_signature(buf)?;
Ok(Self {
transaction: Transaction::Eip7702(tx),
signature,
hash: Default::default(),
})
}
}
}
fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
let (tx, signature) = TxLegacy::rlp_decode_with_signature(buf)?;
Ok(Self { transaction: Transaction::Legacy(tx), signature, hash: Default::default() })
}
}
impl Encodable for TransactionSigned {
fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
self.network_encode(out);
}
fn length(&self) -> usize {
self.network_len()
}
}
impl Decodable for TransactionSigned {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
Self::network_decode(buf).map_err(Into::into)
}
}
#[cfg(any(test, feature = "reth-codec"))]
impl reth_codecs::Compact for TransactionSigned {
fn to_compact<B>(&self, buf: &mut B) -> usize
where
B: alloy_rlp::bytes::BufMut + AsMut<[u8]>,
{
use alloy_consensus::Transaction;
let start = buf.as_mut().len();
// Placeholder for bitflags.
// The first byte uses 4 bits as flags: IsCompressed[1bit], TxType[2bits], Signature[1bit]
buf.put_u8(0);
let sig_bit = self.signature.to_compact(buf) as u8;
let zstd_bit = self.transaction.input().len() >= 32;
let tx_bits = if zstd_bit {
let mut tmp = Vec::with_capacity(256);
reth_zstd_compressors::with_tx_compressor(|compressor| {
let tx_bits = self.transaction.to_compact(&mut tmp);
buf.put_slice(&compressor.compress(&tmp).expect("Failed to compress"));
tx_bits as u8
})
} else {
self.transaction.to_compact(buf) as u8
};
// Replace bitflags with the actual values
buf.as_mut()[start] = sig_bit | (tx_bits << 1) | ((zstd_bit as u8) << 3);
buf.as_mut().len() - start
}
fn from_compact(mut buf: &[u8], _len: usize) -> (Self, &[u8]) {
use alloy_rlp::bytes::Buf;
// The first byte uses 4 bits as flags: IsCompressed[1], TxType[2], Signature[1]
let bitflags = buf.get_u8() as usize;
let sig_bit = bitflags & 1;
let (signature, buf) = Signature::from_compact(buf, sig_bit);
let zstd_bit = bitflags >> 3;
let (transaction, buf) = if zstd_bit != 0 {
reth_zstd_compressors::with_tx_decompressor(|decompressor| {
// TODO: enforce that zstd is only present at a "top" level type
let transaction_type = (bitflags & 0b110) >> 1;
let (transaction, _) =
Transaction::from_compact(decompressor.decompress(buf), transaction_type);
(transaction, buf)
})
} else {
let transaction_type = bitflags >> 1;
Transaction::from_compact(buf, transaction_type)
};
(Self { signature, transaction, hash: Default::default() }, buf)
}
}
impl SignerRecoverable for TransactionSigned {
fn recover_signer(&self) -> Result<Address, RecoveryError> {
let signature_hash = self.signature_hash();
recover_signer(&self.signature, signature_hash)
}
fn recover_signer_unchecked(&self) -> Result<Address, RecoveryError> {
let signature_hash = self.signature_hash();
recover_signer_unchecked(&self.signature, signature_hash)
}
fn recover_unchecked_with_buf(&self, buf: &mut Vec<u8>) -> Result<Address, RecoveryError> {
self.encode_for_signing(buf);
let signature_hash = keccak256(buf);
recover_signer_unchecked(&self.signature, signature_hash)
}
}
impl TxHashRef for TransactionSigned {
fn tx_hash(&self) -> &TxHash {
self.hash.get_or_init(|| self.recalculate_hash())
}
}
impl IsTyped2718 for TransactionSigned {
fn is_type(type_id: u8) -> bool {
<alloy_consensus::TxEnvelope as IsTyped2718>::is_type(type_id)
}
}
impl SignedTransaction for TransactionSigned {}
#[cfg(test)]
mod tests {
use super::*;
use alloy_consensus::EthereumTxEnvelope;
use proptest::proptest;
use proptest_arbitrary_interop::arb;
use reth_codecs::Compact;
proptest! {
#[test]
fn test_roundtrip_compact_encode_envelope(reth_tx in arb::<TransactionSigned>()) {
let mut expected_buf = Vec::<u8>::new();
let expected_len = reth_tx.to_compact(&mut expected_buf);
let mut actual_but = Vec::<u8>::new();
let alloy_tx = EthereumTxEnvelope::<TxEip4844>::from(reth_tx);
let actual_len = alloy_tx.to_compact(&mut actual_but);
assert_eq!(actual_but, expected_buf);
assert_eq!(actual_len, expected_len);
}
#[test]
fn test_roundtrip_compact_decode_envelope(reth_tx in arb::<TransactionSigned>()) {
let mut buf = Vec::<u8>::new();
let len = reth_tx.to_compact(&mut buf);
let (actual_tx, _) = EthereumTxEnvelope::<TxEip4844>::from_compact(&buf, len);
let expected_tx = EthereumTxEnvelope::<TxEip4844>::from(reth_tx);
assert_eq!(actual_tx, expected_tx);
}
#[test]
fn test_roundtrip_compact_encode_envelope_zstd(mut reth_tx in arb::<TransactionSigned>()) {
// zstd only kicks in if the input is large enough
*reth_tx.transaction.input_mut() = vec![0;33].into();
let mut expected_buf = Vec::<u8>::new();
let expected_len = reth_tx.to_compact(&mut expected_buf);
let mut actual_but = Vec::<u8>::new();
let alloy_tx = EthereumTxEnvelope::<TxEip4844>::from(reth_tx);
let actual_len = alloy_tx.to_compact(&mut actual_but);
assert_eq!(actual_but, expected_buf);
assert_eq!(actual_len, expected_len);
}
#[test]
fn test_roundtrip_compact_decode_envelope_zstd(mut reth_tx in arb::<TransactionSigned>()) {
// zstd only kicks in if the input is large enough
*reth_tx.transaction.input_mut() = vec![0;33].into();
let mut buf = Vec::<u8>::new();
let len = reth_tx.to_compact(&mut buf);
let (actual_tx, _) = EthereumTxEnvelope::<TxEip4844>::from_compact(&buf, len);
let expected_tx = EthereumTxEnvelope::<TxEip4844>::from(reth_tx);
assert_eq!(actual_tx, expected_tx);
}
}
}