mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-01-28 00:28:20 -05:00
feat(storage): Implement Compact for EthereumTxEnvelope from alloy (#15122)
This commit is contained in:
@@ -2,8 +2,8 @@ use alloc::vec::Vec;
|
||||
pub use alloy_consensus::{transaction::PooledTransaction, TxType};
|
||||
use alloy_consensus::{
|
||||
transaction::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx},
|
||||
BlobTransactionSidecar, SignableTransaction, Signed, TxEip1559, TxEip2930, TxEip4844,
|
||||
TxEip4844Variant, TxEip4844WithSidecar, TxEip7702, TxEnvelope, TxLegacy, Typed2718,
|
||||
BlobTransactionSidecar, EthereumTxEnvelope, SignableTransaction, Signed, TxEip1559, TxEip2930,
|
||||
TxEip4844, TxEip4844Variant, TxEip4844WithSidecar, TxEip7702, TxEnvelope, TxLegacy, Typed2718,
|
||||
TypedTransaction,
|
||||
};
|
||||
use alloy_eips::{
|
||||
@@ -594,6 +594,19 @@ impl From<TransactionSigned> for TxEnvelope {
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TransactionSigned> for Signed<Transaction> {
|
||||
fn from(value: TransactionSigned) -> Self {
|
||||
let (tx, sig, hash) = value.into_parts();
|
||||
@@ -1074,7 +1087,8 @@ pub(super) mod serde_bincode_compat {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use alloy_consensus::{
|
||||
constants::LEGACY_TX_TYPE_ID, Block, Transaction as _, TxEip1559, TxLegacy,
|
||||
constants::LEGACY_TX_TYPE_ID, Block, EthereumTxEnvelope, Transaction as _, TxEip1559,
|
||||
TxLegacy,
|
||||
};
|
||||
use alloy_eips::{
|
||||
eip2718::{Decodable2718, Encodable2718},
|
||||
@@ -1085,10 +1099,38 @@ mod tests {
|
||||
U256,
|
||||
};
|
||||
use alloy_rlp::{Decodable, Encodable, Error as RlpError};
|
||||
use proptest::proptest;
|
||||
use proptest_arbitrary_interop::arb;
|
||||
use reth_codecs::Compact;
|
||||
use reth_primitives_traits::SignedTransaction;
|
||||
use std::str::FromStr;
|
||||
|
||||
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 eip_2_reject_high_s_value() {
|
||||
// This pre-homestead transaction has a high `s` value and should be rejected by the
|
||||
|
||||
@@ -13,6 +13,7 @@ workspace = true
|
||||
[dependencies]
|
||||
# reth
|
||||
reth-codecs-derive.workspace = true
|
||||
reth-zstd-compressors = { workspace = true, optional = true, default-features = false }
|
||||
|
||||
# eth
|
||||
alloy-consensus = { workspace = true, optional = true }
|
||||
@@ -55,12 +56,14 @@ std = [
|
||||
"serde/std",
|
||||
"op-alloy-consensus?/std",
|
||||
"serde_json/std",
|
||||
"reth-zstd-compressors?/std",
|
||||
]
|
||||
alloy = [
|
||||
"dep:alloy-consensus",
|
||||
"dep:alloy-eips",
|
||||
"dep:alloy-genesis",
|
||||
"dep:alloy-trie",
|
||||
"dep:reth-zstd-compressors",
|
||||
]
|
||||
op = ["alloy", "dep:op-alloy-consensus"]
|
||||
test-utils = [
|
||||
|
||||
179
crates/storage/codecs/src/alloy/transaction/ethereum.rs
Normal file
179
crates/storage/codecs/src/alloy/transaction/ethereum.rs
Normal file
@@ -0,0 +1,179 @@
|
||||
use crate::{Compact, Vec};
|
||||
use alloy_consensus::{
|
||||
transaction::RlpEcdsaEncodableTx, EthereumTxEnvelope, Signed, Transaction, TxEip1559,
|
||||
TxEip2930, TxEip7702, TxLegacy, TxType,
|
||||
};
|
||||
use alloy_primitives::PrimitiveSignature;
|
||||
use bytes::{Buf, BufMut};
|
||||
|
||||
/// A trait for extracting transaction without type and signature and serializing it using
|
||||
/// [`Compact`] encoding.
|
||||
///
|
||||
/// It is not a responsibility of this trait to encode transaction type and signature. Likely this
|
||||
/// will be a part of a serialization scenario with a greater scope where these values are
|
||||
/// serialized separately.
|
||||
///
|
||||
/// See [`ToTxCompact::to_tx_compact`].
|
||||
trait ToTxCompact {
|
||||
/// Serializes inner transaction using [`Compact`] encoding. Writes the result into `buf`.
|
||||
///
|
||||
/// The written bytes do not contain signature and transaction type. This information be needs
|
||||
/// to be serialized extra if needed.
|
||||
fn to_tx_compact(&self, buf: &mut (impl BufMut + AsMut<[u8]>));
|
||||
}
|
||||
|
||||
/// A trait for deserializing transaction without type and signature using [`Compact`] encoding.
|
||||
///
|
||||
/// It is not a responsibility of this trait to extract transaction type and signature, but both
|
||||
/// are needed to create the value. While these values can come from anywhere, likely this will be
|
||||
/// a part of a deserialization scenario with a greater scope where these values are deserialized
|
||||
/// separately.
|
||||
///
|
||||
/// See [`FromTxCompact::from_tx_compact`].
|
||||
trait FromTxCompact {
|
||||
/// Deserializes inner transaction using [`Compact`] encoding. The concrete type is determined
|
||||
/// by `tx_type`. The `signature` is added to create typed and signed transaction.
|
||||
///
|
||||
/// Returns a tuple of 2 elements. The first element is the deserialized value and the second
|
||||
/// is a byte slice created from `buf` with a starting position advanced by the exact amount
|
||||
/// of bytes consumed for this process.
|
||||
fn from_tx_compact(buf: &[u8], tx_type: TxType, signature: PrimitiveSignature) -> (Self, &[u8])
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
impl<Eip4844: Compact + Transaction> ToTxCompact for EthereumTxEnvelope<Eip4844> {
|
||||
fn to_tx_compact(&self, buf: &mut (impl BufMut + AsMut<[u8]>)) {
|
||||
match self {
|
||||
Self::Legacy(tx) => tx.tx().to_compact(buf),
|
||||
Self::Eip2930(tx) => tx.tx().to_compact(buf),
|
||||
Self::Eip1559(tx) => tx.tx().to_compact(buf),
|
||||
Self::Eip4844(tx) => tx.tx().to_compact(buf),
|
||||
Self::Eip7702(tx) => tx.tx().to_compact(buf),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl<Eip4844: Compact + Transaction> FromTxCompact for EthereumTxEnvelope<Eip4844> {
|
||||
fn from_tx_compact(
|
||||
buf: &[u8],
|
||||
tx_type: TxType,
|
||||
signature: PrimitiveSignature,
|
||||
) -> (Self, &[u8]) {
|
||||
match tx_type {
|
||||
TxType::Legacy => {
|
||||
let (tx, buf) = TxLegacy::from_compact(buf, buf.len());
|
||||
let tx = Signed::new_unhashed(tx, signature);
|
||||
(Self::Legacy(tx), buf)
|
||||
}
|
||||
TxType::Eip2930 => {
|
||||
let (tx, buf) = TxEip2930::from_compact(buf, buf.len());
|
||||
let tx = Signed::new_unhashed(tx, signature);
|
||||
(Self::Eip2930(tx), buf)
|
||||
}
|
||||
TxType::Eip1559 => {
|
||||
let (tx, buf) = TxEip1559::from_compact(buf, buf.len());
|
||||
let tx = Signed::new_unhashed(tx, signature);
|
||||
(Self::Eip1559(tx), buf)
|
||||
}
|
||||
TxType::Eip4844 => {
|
||||
let (tx, buf) = Eip4844::from_compact(buf, buf.len());
|
||||
let tx = Signed::new_unhashed(tx, signature);
|
||||
(Self::Eip4844(tx), buf)
|
||||
}
|
||||
TxType::Eip7702 => {
|
||||
let (tx, buf) = TxEip7702::from_compact(buf, buf.len());
|
||||
let tx = Signed::new_unhashed(tx, signature);
|
||||
(Self::Eip7702(tx), buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Eip4844: Compact + RlpEcdsaEncodableTx + Transaction + Send + Sync> Compact
|
||||
for EthereumTxEnvelope<Eip4844>
|
||||
{
|
||||
fn to_compact<B>(&self, buf: &mut B) -> usize
|
||||
where
|
||||
B: BufMut + AsMut<[u8]>,
|
||||
{
|
||||
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.input().len() >= 32;
|
||||
let tx_bits = self.tx_type().to_compact(buf) as u8;
|
||||
let flags = sig_bit | (tx_bits << 1) | ((zstd_bit as u8) << 3);
|
||||
|
||||
buf.as_mut()[start] = flags;
|
||||
|
||||
if zstd_bit {
|
||||
let mut tx_buf = Vec::with_capacity(256);
|
||||
|
||||
self.to_tx_compact(&mut tx_buf);
|
||||
|
||||
buf.put_slice(
|
||||
&{
|
||||
#[cfg(feature = "std")]
|
||||
{
|
||||
reth_zstd_compressors::TRANSACTION_COMPRESSOR.with(|compressor| {
|
||||
let mut compressor = compressor.borrow_mut();
|
||||
compressor.compress(&tx_buf)
|
||||
})
|
||||
}
|
||||
#[cfg(not(feature = "std"))]
|
||||
{
|
||||
let mut compressor = reth_zstd_compressors::create_tx_compressor();
|
||||
compressor.compress(&tx_buf)
|
||||
}
|
||||
}
|
||||
.expect("Failed to compress"),
|
||||
);
|
||||
} else {
|
||||
self.to_tx_compact(buf);
|
||||
};
|
||||
|
||||
buf.as_mut().len() - start
|
||||
}
|
||||
|
||||
fn from_compact(mut buf: &[u8], _len: usize) -> (Self, &[u8]) {
|
||||
let flags = buf.get_u8() as usize;
|
||||
|
||||
let sig_bit = flags & 1;
|
||||
let tx_bits = (flags & 0b110) >> 1;
|
||||
let zstd_bit = flags >> 3;
|
||||
|
||||
let (signature, buf) = PrimitiveSignature::from_compact(buf, sig_bit);
|
||||
let (tx_type, buf) = TxType::from_compact(buf, tx_bits);
|
||||
|
||||
let (transaction, buf) = if zstd_bit != 0 {
|
||||
#[cfg(feature = "std")]
|
||||
{
|
||||
reth_zstd_compressors::TRANSACTION_DECOMPRESSOR.with(|decompressor| {
|
||||
let mut decompressor = decompressor.borrow_mut();
|
||||
|
||||
let (tx, _) =
|
||||
Self::from_tx_compact(decompressor.decompress(buf), tx_type, signature);
|
||||
|
||||
(tx, buf)
|
||||
})
|
||||
}
|
||||
#[cfg(not(feature = "std"))]
|
||||
{
|
||||
let mut decompressor = reth_zstd_compressors::create_tx_decompressor();
|
||||
|
||||
let (tx, _) =
|
||||
Self::from_tx_compact(decompressor.decompress(buf), tx_type, signature);
|
||||
|
||||
(tx, buf)
|
||||
}
|
||||
} else {
|
||||
Self::from_tx_compact(buf, tx_type, signature)
|
||||
};
|
||||
|
||||
(transaction, buf)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
//! Compact implementation for transaction types
|
||||
use crate::Compact;
|
||||
use alloy_consensus::{EthereumTypedTransaction, TxType, transaction::{TxEip7702, TxEip1559, TxEip2930, TxLegacy}};
|
||||
use alloy_consensus::{
|
||||
transaction::{RlpEcdsaEncodableTx, TxEip1559, TxEip2930, TxEip7702, TxLegacy},
|
||||
EthereumTypedTransaction, TxType,
|
||||
};
|
||||
use alloy_primitives::bytes::BufMut;
|
||||
use alloy_consensus::transaction::RlpEcdsaEncodableTx;
|
||||
|
||||
impl<Eip4844> Compact for EthereumTypedTransaction<Eip4844>
|
||||
where
|
||||
@@ -25,7 +27,7 @@ where
|
||||
|
||||
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());
|
||||
@@ -51,16 +53,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
cond_mod!(
|
||||
eip1559,
|
||||
eip2930,
|
||||
eip4844,
|
||||
eip7702,
|
||||
legacy,
|
||||
txtype
|
||||
);
|
||||
|
||||
cond_mod!(eip1559, eip2930, eip4844, eip7702, legacy, txtype);
|
||||
|
||||
mod ethereum;
|
||||
#[cfg(all(feature = "test-utils", feature = "op"))]
|
||||
pub mod optimism;
|
||||
#[cfg(all(not(feature = "test-utils"), feature = "op"))]
|
||||
@@ -75,14 +70,17 @@ mod tests {
|
||||
// this check is to ensure we do not inadvertently add too many fields to a struct which would
|
||||
// expand the flags field and break backwards compatibility
|
||||
|
||||
use alloy_primitives::hex;
|
||||
use crate::{
|
||||
alloy::{header::Header, transaction::{
|
||||
eip1559::TxEip1559, eip2930::TxEip2930, eip4844::TxEip4844, eip7702::TxEip7702,
|
||||
legacy::TxLegacy,
|
||||
}},
|
||||
alloy::{
|
||||
header::Header,
|
||||
transaction::{
|
||||
eip1559::TxEip1559, eip2930::TxEip2930, eip4844::TxEip4844, eip7702::TxEip7702,
|
||||
legacy::TxLegacy,
|
||||
},
|
||||
},
|
||||
test_utils::test_decode,
|
||||
};
|
||||
use alloy_primitives::hex;
|
||||
|
||||
#[test]
|
||||
fn test_ensure_backwards_compatibility() {
|
||||
|
||||
Reference in New Issue
Block a user