mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-04-30 03:01:58 -04:00
feat(storage): Implement Compact for EthereumTxEnvelope from alloy (#15122)
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -7120,6 +7120,7 @@ dependencies = [
|
|||||||
"proptest",
|
"proptest",
|
||||||
"proptest-arbitrary-interop",
|
"proptest-arbitrary-interop",
|
||||||
"reth-codecs-derive",
|
"reth-codecs-derive",
|
||||||
|
"reth-zstd-compressors",
|
||||||
"rstest",
|
"rstest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ use alloc::vec::Vec;
|
|||||||
pub use alloy_consensus::{transaction::PooledTransaction, TxType};
|
pub use alloy_consensus::{transaction::PooledTransaction, TxType};
|
||||||
use alloy_consensus::{
|
use alloy_consensus::{
|
||||||
transaction::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx},
|
transaction::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx},
|
||||||
BlobTransactionSidecar, SignableTransaction, Signed, TxEip1559, TxEip2930, TxEip4844,
|
BlobTransactionSidecar, EthereumTxEnvelope, SignableTransaction, Signed, TxEip1559, TxEip2930,
|
||||||
TxEip4844Variant, TxEip4844WithSidecar, TxEip7702, TxEnvelope, TxLegacy, Typed2718,
|
TxEip4844, TxEip4844Variant, TxEip4844WithSidecar, TxEip7702, TxEnvelope, TxLegacy, Typed2718,
|
||||||
TypedTransaction,
|
TypedTransaction,
|
||||||
};
|
};
|
||||||
use alloy_eips::{
|
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> {
|
impl From<TransactionSigned> for Signed<Transaction> {
|
||||||
fn from(value: TransactionSigned) -> Self {
|
fn from(value: TransactionSigned) -> Self {
|
||||||
let (tx, sig, hash) = value.into_parts();
|
let (tx, sig, hash) = value.into_parts();
|
||||||
@@ -1074,7 +1087,8 @@ pub(super) mod serde_bincode_compat {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use alloy_consensus::{
|
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::{
|
use alloy_eips::{
|
||||||
eip2718::{Decodable2718, Encodable2718},
|
eip2718::{Decodable2718, Encodable2718},
|
||||||
@@ -1085,10 +1099,38 @@ mod tests {
|
|||||||
U256,
|
U256,
|
||||||
};
|
};
|
||||||
use alloy_rlp::{Decodable, Encodable, Error as RlpError};
|
use alloy_rlp::{Decodable, Encodable, Error as RlpError};
|
||||||
|
use proptest::proptest;
|
||||||
|
use proptest_arbitrary_interop::arb;
|
||||||
use reth_codecs::Compact;
|
use reth_codecs::Compact;
|
||||||
use reth_primitives_traits::SignedTransaction;
|
use reth_primitives_traits::SignedTransaction;
|
||||||
use std::str::FromStr;
|
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]
|
#[test]
|
||||||
fn eip_2_reject_high_s_value() {
|
fn eip_2_reject_high_s_value() {
|
||||||
// This pre-homestead transaction has a high `s` value and should be rejected by the
|
// This pre-homestead transaction has a high `s` value and should be rejected by the
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
# reth
|
# reth
|
||||||
reth-codecs-derive.workspace = true
|
reth-codecs-derive.workspace = true
|
||||||
|
reth-zstd-compressors = { workspace = true, optional = true, default-features = false }
|
||||||
|
|
||||||
# eth
|
# eth
|
||||||
alloy-consensus = { workspace = true, optional = true }
|
alloy-consensus = { workspace = true, optional = true }
|
||||||
@@ -55,12 +56,14 @@ std = [
|
|||||||
"serde/std",
|
"serde/std",
|
||||||
"op-alloy-consensus?/std",
|
"op-alloy-consensus?/std",
|
||||||
"serde_json/std",
|
"serde_json/std",
|
||||||
|
"reth-zstd-compressors?/std",
|
||||||
]
|
]
|
||||||
alloy = [
|
alloy = [
|
||||||
"dep:alloy-consensus",
|
"dep:alloy-consensus",
|
||||||
"dep:alloy-eips",
|
"dep:alloy-eips",
|
||||||
"dep:alloy-genesis",
|
"dep:alloy-genesis",
|
||||||
"dep:alloy-trie",
|
"dep:alloy-trie",
|
||||||
|
"dep:reth-zstd-compressors",
|
||||||
]
|
]
|
||||||
op = ["alloy", "dep:op-alloy-consensus"]
|
op = ["alloy", "dep:op-alloy-consensus"]
|
||||||
test-utils = [
|
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
|
//! Compact implementation for transaction types
|
||||||
use crate::Compact;
|
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_primitives::bytes::BufMut;
|
||||||
use alloy_consensus::transaction::RlpEcdsaEncodableTx;
|
|
||||||
|
|
||||||
impl<Eip4844> Compact for EthereumTypedTransaction<Eip4844>
|
impl<Eip4844> Compact for EthereumTypedTransaction<Eip4844>
|
||||||
where
|
where
|
||||||
@@ -25,7 +27,7 @@ where
|
|||||||
|
|
||||||
fn from_compact(buf: &[u8], identifier: usize) -> (Self, &[u8]) {
|
fn from_compact(buf: &[u8], identifier: usize) -> (Self, &[u8]) {
|
||||||
let (tx_type, buf) = TxType::from_compact(buf, identifier);
|
let (tx_type, buf) = TxType::from_compact(buf, identifier);
|
||||||
|
|
||||||
match tx_type {
|
match tx_type {
|
||||||
TxType::Legacy => {
|
TxType::Legacy => {
|
||||||
let (tx, buf) = TxLegacy::from_compact(buf, buf.len());
|
let (tx, buf) = TxLegacy::from_compact(buf, buf.len());
|
||||||
@@ -51,16 +53,9 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cond_mod!(
|
cond_mod!(eip1559, eip2930, eip4844, eip7702, legacy, txtype);
|
||||||
eip1559,
|
|
||||||
eip2930,
|
|
||||||
eip4844,
|
|
||||||
eip7702,
|
|
||||||
legacy,
|
|
||||||
txtype
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
|
mod ethereum;
|
||||||
#[cfg(all(feature = "test-utils", feature = "op"))]
|
#[cfg(all(feature = "test-utils", feature = "op"))]
|
||||||
pub mod optimism;
|
pub mod optimism;
|
||||||
#[cfg(all(not(feature = "test-utils"), feature = "op"))]
|
#[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
|
// 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
|
// expand the flags field and break backwards compatibility
|
||||||
|
|
||||||
use alloy_primitives::hex;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
alloy::{header::Header, transaction::{
|
alloy::{
|
||||||
eip1559::TxEip1559, eip2930::TxEip2930, eip4844::TxEip4844, eip7702::TxEip7702,
|
header::Header,
|
||||||
legacy::TxLegacy,
|
transaction::{
|
||||||
}},
|
eip1559::TxEip1559, eip2930::TxEip2930, eip4844::TxEip4844, eip7702::TxEip7702,
|
||||||
|
legacy::TxLegacy,
|
||||||
|
},
|
||||||
|
},
|
||||||
test_utils::test_decode,
|
test_utils::test_decode,
|
||||||
};
|
};
|
||||||
|
use alloy_primitives::hex;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ensure_backwards_compatibility() {
|
fn test_ensure_backwards_compatibility() {
|
||||||
|
|||||||
Reference in New Issue
Block a user