chore: rm pooledtx element type (#13286)

This commit is contained in:
Matthias Seitz
2024-12-13 13:58:40 +01:00
committed by GitHub
parent 088925c08a
commit acc125a528
13 changed files with 214 additions and 993 deletions

View File

@ -45,9 +45,9 @@ pub use static_file::StaticFileSegment;
pub use transaction::{
util::secp256k1::{public_key_to_address, recover_signer_unchecked, sign_message},
BlobTransaction, InvalidTransactionError, PooledTransactionsElement,
PooledTransactionsElementEcRecovered, RecoveredTx, Transaction, TransactionMeta,
TransactionSigned, TransactionSignedEcRecovered, TxType,
InvalidTransactionError, PooledTransactionsElement, PooledTransactionsElementEcRecovered,
RecoveredTx, Transaction, TransactionMeta, TransactionSigned, TransactionSignedEcRecovered,
TxType,
};
pub use alloy_consensus::ReceiptWithBloom;

View File

@ -2,20 +2,24 @@
use alloc::vec::Vec;
use alloy_consensus::{
transaction::RlpEcdsaTx, SignableTransaction, Signed, Transaction as _, TxEip1559, TxEip2930,
TxEip4844, TxEip4844Variant, TxEip7702, TxLegacy, Typed2718, TypedTransaction,
transaction::{PooledTransaction, RlpEcdsaTx},
SignableTransaction, Signed, Transaction as _, TxEip1559, TxEip2930, TxEip4844,
TxEip4844Variant, TxEip4844WithSidecar, TxEip7702, TxLegacy, Typed2718, TypedTransaction,
};
use alloy_eips::{
eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718},
eip2930::AccessList,
eip4844::BlobTransactionSidecar,
eip7702::SignedAuthorization,
};
use alloy_primitives::{
keccak256, Address, Bytes, ChainId, PrimitiveSignature as Signature, TxHash, TxKind, B256, U256,
};
use alloy_rlp::{Decodable, Encodable, Error as RlpError, Header};
pub use compat::FillTxEnv;
use core::hash::{Hash, Hasher};
use derive_more::{AsRef, Deref};
pub use meta::TransactionMeta;
use once_cell as _;
#[cfg(not(feature = "std"))]
use once_cell::sync::{Lazy as LazyLock, OnceCell as OnceLock};
@ -23,24 +27,20 @@ use once_cell::sync::{Lazy as LazyLock, OnceCell as OnceLock};
use op_alloy_consensus::DepositTransaction;
#[cfg(feature = "optimism")]
use op_alloy_consensus::TxDeposit;
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
use reth_primitives_traits::{InMemorySize, SignedTransaction};
use revm_primitives::{AuthorizationList, TxEnv};
use serde::{Deserialize, Serialize};
#[cfg(feature = "std")]
use std::sync::{LazyLock, OnceLock};
pub use compat::FillTxEnv;
pub use meta::TransactionMeta;
pub use pooled::{PooledTransactionsElement, PooledTransactionsElementEcRecovered};
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
pub use reth_primitives_traits::{
transaction::error::{
InvalidTransactionError, TransactionConversionError, TryFromRecoveredTransactionError,
},
WithEncoded,
};
pub use sidecar::BlobTransaction;
use reth_primitives_traits::{InMemorySize, SignedTransaction};
use revm_primitives::{AuthorizationList, TxEnv};
use serde::{Deserialize, Serialize};
pub use signature::{recover_signer, recover_signer_unchecked};
#[cfg(feature = "std")]
use std::sync::{LazyLock, OnceLock};
pub use tx_type::TxType;
/// Handling transaction signature operations, including signature recovery,
@ -52,7 +52,6 @@ pub(crate) mod access_list;
mod compat;
mod meta;
mod pooled;
mod sidecar;
mod tx_type;
#[cfg(any(test, feature = "reth-codec"))]
@ -857,7 +856,7 @@ impl TransactionSigned {
/// Tries to convert a [`TransactionSigned`] into a [`PooledTransactionsElement`].
///
/// This function used as a helper to convert from a decoded p2p broadcast message to
/// [`PooledTransactionsElement`]. Since [`BlobTransaction`] is disallowed to be broadcasted on
/// [`PooledTransactionsElement`]. Since EIP4844 variants are disallowed to be broadcasted on
/// p2p, return an err if `tx` is [`Transaction::Eip4844`].
pub fn try_into_pooled(self) -> Result<PooledTransactionsElement, Self> {
let hash = self.hash();
@ -882,6 +881,32 @@ impl TransactionSigned {
}
}
/// Converts from an EIP-4844 [`RecoveredTx`] to a
/// [`PooledTransactionsElementEcRecovered`] with the given sidecar.
///
/// Returns an `Err` containing the original `TransactionSigned` if the transaction is not
/// EIP-4844.
pub fn try_into_pooled_eip4844(
self,
sidecar: BlobTransactionSidecar,
) -> Result<PooledTransactionsElement, Self> {
let hash = self.hash();
Ok(match self {
// If the transaction is an EIP-4844 transaction...
Self { transaction: Transaction::Eip4844(tx), signature, .. } => {
// Construct a pooled eip488 tx with the provided sidecar.
PooledTransactionsElement::Eip4844(Signed::new_unchecked(
TxEip4844WithSidecar { tx, sidecar },
signature,
hash,
))
}
// If the transaction is not EIP-4844, return an error with the original
// transaction.
_ => return Err(self),
})
}
/// Transaction hash. Used to identify transaction.
pub fn hash(&self) -> TxHash {
*self.tx_hash()
@ -1165,6 +1190,26 @@ impl From<RecoveredTx> for TransactionSigned {
}
}
impl TryFrom<TransactionSigned> for PooledTransaction {
type Error = TransactionConversionError;
fn try_from(tx: TransactionSigned) -> Result<Self, Self::Error> {
tx.try_into_pooled().map_err(|_| TransactionConversionError::UnsupportedForP2P)
}
}
impl From<PooledTransaction> for TransactionSigned {
fn from(tx: PooledTransaction) -> Self {
match tx {
PooledTransaction::Legacy(signed) => signed.into(),
PooledTransaction::Eip2930(signed) => signed.into(),
PooledTransaction::Eip1559(signed) => signed.into(),
PooledTransaction::Eip4844(signed) => signed.into(),
PooledTransaction::Eip7702(signed) => signed.into(),
}
}
}
impl Encodable for TransactionSigned {
/// This encodes the transaction _with_ the signature, and an rlp header.
///
@ -1406,6 +1451,13 @@ impl From<Signed<Transaction>> for TransactionSigned {
}
}
impl From<Signed<TxEip4844WithSidecar>> for TransactionSigned {
fn from(value: Signed<TxEip4844WithSidecar>) -> Self {
let (tx, sig, hash) = value.into_parts();
Self::new(tx.tx.into(), sig, hash)
}
}
impl From<TransactionSigned> for Signed<Transaction> {
fn from(value: TransactionSigned) -> Self {
let (tx, sig, hash) = value.into_parts();

View File

@ -1,670 +1,12 @@
//! Defines the types for blob transactions, legacy, and other EIP-2718 transactions included in a
//! response to `GetPooledTransactions`.
use super::{
recover_signer_unchecked, signature::recover_signer, TransactionConversionError, TxEip7702,
};
use crate::{BlobTransaction, RecoveredTx, Transaction, TransactionSigned};
use alloc::vec::Vec;
use alloy_consensus::{
constants::EIP4844_TX_TYPE_ID,
transaction::{RlpEcdsaTx, TxEip1559, TxEip2930, TxEip4844, TxLegacy},
SignableTransaction, Signed, TxEip4844WithSidecar, Typed2718,
};
use alloy_eips::{
eip2718::{Decodable2718, Eip2718Result, Encodable2718},
eip2930::AccessList,
eip4844::BlobTransactionSidecar,
eip7702::SignedAuthorization,
};
use alloy_primitives::{
Address, Bytes, ChainId, PrimitiveSignature as Signature, TxHash, TxKind, B256, U256,
};
use alloy_rlp::{Decodable, Encodable, Error as RlpError, Header};
use bytes::Buf;
use core::hash::{Hash, Hasher};
use reth_primitives_traits::{InMemorySize, SignedTransaction};
use revm_primitives::keccak256;
use serde::{Deserialize, Serialize};
use crate::RecoveredTx;
use alloy_eips::eip4844::BlobTransactionSidecar;
use reth_primitives_traits::transaction::error::TransactionConversionError;
/// A response to `GetPooledTransactions`. This can include either a blob transaction, or a
/// non-4844 signed transaction.
#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum PooledTransactionsElement {
/// An untagged [`TxLegacy`].
Legacy(Signed<TxLegacy>),
/// A [`TxEip2930`] tagged with type 1.
Eip2930(Signed<TxEip2930>),
/// A [`TxEip1559`] tagged with type 2.
Eip1559(Signed<TxEip1559>),
/// A [`TxEip7702`] tagged with type 4.
Eip7702(Signed<TxEip7702>),
/// A blob transaction, which includes the transaction, blob data, commitments, and proofs.
BlobTransaction(BlobTransaction),
}
impl PooledTransactionsElement {
/// Converts from an EIP-4844 [`RecoveredTx`] to a
/// [`PooledTransactionsElementEcRecovered`] with the given sidecar.
///
/// Returns an `Err` containing the original `TransactionSigned` if the transaction is not
/// EIP-4844.
pub fn try_from_blob_transaction(
tx: TransactionSigned,
sidecar: BlobTransactionSidecar,
) -> Result<Self, TransactionSigned> {
let hash = tx.hash();
Ok(match tx {
// If the transaction is an EIP-4844 transaction...
TransactionSigned { transaction: Transaction::Eip4844(tx), signature, .. } => {
// Construct a `PooledTransactionsElement::BlobTransaction` with provided sidecar.
Self::BlobTransaction(BlobTransaction(Signed::new_unchecked(
TxEip4844WithSidecar { tx, sidecar },
signature,
hash,
)))
}
// If the transaction is not EIP-4844, return an error with the original
// transaction.
_ => return Err(tx),
})
}
/// Heavy operation that return signature hash over rlp encoded transaction.
/// It is only for signature signing or signer recovery.
pub fn signature_hash(&self) -> B256 {
match self {
Self::Legacy(tx) => tx.signature_hash(),
Self::Eip2930(tx) => tx.signature_hash(),
Self::Eip1559(tx) => tx.signature_hash(),
Self::Eip7702(tx) => tx.signature_hash(),
Self::BlobTransaction(tx) => tx.signature_hash(),
}
}
/// Reference to transaction hash. Used to identify transaction.
pub const fn hash(&self) -> &TxHash {
match self {
Self::Legacy(tx) => tx.hash(),
Self::Eip2930(tx) => tx.hash(),
Self::Eip1559(tx) => tx.hash(),
Self::Eip7702(tx) => tx.hash(),
Self::BlobTransaction(tx) => tx.0.hash(),
}
}
/// Returns the signature of the transaction.
pub const fn signature(&self) -> &Signature {
match self {
Self::Legacy(tx) => tx.signature(),
Self::Eip2930(tx) => tx.signature(),
Self::Eip1559(tx) => tx.signature(),
Self::Eip7702(tx) => tx.signature(),
Self::BlobTransaction(tx) => tx.0.signature(),
}
}
/// Recover signer from signature and hash.
///
/// Returns `None` if the transaction's signature is invalid, see also [`Self::recover_signer`].
pub fn recover_signer(&self) -> Option<Address> {
recover_signer(self.signature(), self.signature_hash())
}
/// Tries to recover signer and return [`PooledTransactionsElementEcRecovered`].
///
/// Returns `Err(Self)` if the transaction's signature is invalid, see also
/// [`Self::recover_signer`].
pub fn try_into_ecrecovered(self) -> Result<PooledTransactionsElementEcRecovered, Self> {
match self.recover_signer() {
None => Err(self),
Some(signer) => Ok(RecoveredTx { signed_transaction: self, signer }),
}
}
/// This encodes the transaction _without_ the signature, and is only suitable for creating a
/// hash intended for signing.
pub fn encode_for_signing(&self, out: &mut dyn bytes::BufMut) {
match self {
Self::Legacy(tx) => tx.tx().encode_for_signing(out),
Self::Eip2930(tx) => tx.tx().encode_for_signing(out),
Self::Eip1559(tx) => tx.tx().encode_for_signing(out),
Self::BlobTransaction(tx) => tx.tx().encode_for_signing(out),
Self::Eip7702(tx) => tx.tx().encode_for_signing(out),
}
}
/// Create [`RecoveredTx`] by converting this transaction into
/// [`TransactionSigned`] and [`Address`] of the signer.
pub fn into_ecrecovered_transaction(self, signer: Address) -> RecoveredTx {
RecoveredTx::from_signed_transaction(self.into_transaction(), signer)
}
/// Returns the inner [`TransactionSigned`].
pub fn into_transaction(self) -> TransactionSigned {
match self {
Self::Legacy(tx) => tx.into(),
Self::Eip2930(tx) => tx.into(),
Self::Eip1559(tx) => tx.into(),
Self::Eip7702(tx) => tx.into(),
Self::BlobTransaction(blob_tx) => blob_tx.into_parts().0,
}
}
/// Returns true if the transaction is an EIP-4844 transaction.
#[inline]
pub const fn is_eip4844(&self) -> bool {
matches!(self, Self::BlobTransaction(_))
}
/// Returns the [`TxLegacy`] variant if the transaction is a legacy transaction.
pub const fn as_legacy(&self) -> Option<&TxLegacy> {
match self {
Self::Legacy(tx) => Some(tx.tx()),
_ => None,
}
}
/// Returns the [`TxEip2930`] variant if the transaction is an EIP-2930 transaction.
pub const fn as_eip2930(&self) -> Option<&TxEip2930> {
match self {
Self::Eip2930(tx) => Some(tx.tx()),
_ => None,
}
}
/// Returns the [`TxEip1559`] variant if the transaction is an EIP-1559 transaction.
pub const fn as_eip1559(&self) -> Option<&TxEip1559> {
match self {
Self::Eip1559(tx) => Some(tx.tx()),
_ => None,
}
}
/// Returns the [`TxEip4844`] variant if the transaction is an EIP-4844 transaction.
pub const fn as_eip4844(&self) -> Option<&TxEip4844> {
match self {
Self::BlobTransaction(tx) => Some(tx.0.tx().tx()),
_ => None,
}
}
/// Returns the [`TxEip7702`] variant if the transaction is an EIP-7702 transaction.
pub const fn as_eip7702(&self) -> Option<&TxEip7702> {
match self {
Self::Eip7702(tx) => Some(tx.tx()),
_ => None,
}
}
/// Returns the blob gas used for all blobs of the EIP-4844 transaction if it is an EIP-4844
/// transaction.
///
/// This is the number of blobs times the
/// [`DATA_GAS_PER_BLOB`](alloy_eips::eip4844::DATA_GAS_PER_BLOB) a single blob consumes.
pub fn blob_gas_used(&self) -> Option<u64> {
self.as_eip4844().map(TxEip4844::blob_gas)
}
}
impl Hash for PooledTransactionsElement {
fn hash<H: Hasher>(&self, state: &mut H) {
self.trie_hash().hash(state);
}
}
impl Encodable for PooledTransactionsElement {
/// This encodes the transaction _with_ the signature, and an rlp header.
///
/// For legacy transactions, it encodes the transaction data:
/// `rlp(tx-data)`
///
/// For EIP-2718 typed transactions, it encodes the transaction type followed by the rlp of the
/// transaction:
/// `rlp(tx-type || rlp(tx-data))`
fn encode(&self, out: &mut dyn bytes::BufMut) {
self.network_encode(out);
}
fn length(&self) -> usize {
let mut payload_length = self.encode_2718_len();
if !Encodable2718::is_legacy(self) {
payload_length += Header { list: false, payload_length }.length();
}
payload_length
}
}
impl Decodable for PooledTransactionsElement {
/// Decodes an enveloped post EIP-4844 [`PooledTransactionsElement`].
///
/// CAUTION: this expects that `buf` is `rlp(tx_type || rlp(tx-data))`
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
// From the EIP-4844 spec:
// Blob transactions have two network representations. During transaction gossip responses
// (`PooledTransactions`), the EIP-2718 `TransactionPayload` of the blob transaction is
// wrapped to become:
//
// `rlp([tx_payload_body, blobs, commitments, proofs])`
//
// This means the full wire encoding is:
// `rlp(tx_type || rlp([transaction_payload_body, blobs, commitments, proofs]))`
//
// First, we check whether or not the transaction is a legacy transaction.
if buf.is_empty() {
return Err(RlpError::InputTooShort)
}
// keep the original buf around for legacy decoding
let mut original_encoding = *buf;
// If the header is a list header, it is a legacy transaction. Otherwise, it is a typed
// transaction
let header = Header::decode(buf)?;
// Check if the tx is a list
if header.list {
// decode as legacy transaction
let tx = Self::fallback_decode(&mut original_encoding)?;
// advance the buffer by however long the legacy transaction decoding advanced the
// buffer
*buf = original_encoding;
Ok(tx)
} else {
// decode the type byte, only decode BlobTransaction if it is a 4844 transaction
let tx_type = *buf.first().ok_or(RlpError::InputTooShort)?;
let remaining_len = buf.len();
// Advance the buffer past the type byte
buf.advance(1);
let tx = Self::typed_decode(tx_type, buf).map_err(RlpError::from)?;
// check that the bytes consumed match the payload length
let bytes_consumed = remaining_len - buf.len();
if bytes_consumed != header.payload_length {
return Err(RlpError::UnexpectedLength)
}
Ok(tx)
}
}
}
impl Encodable2718 for PooledTransactionsElement {
fn type_flag(&self) -> Option<u8> {
match self {
Self::Legacy(_) => None,
Self::Eip2930(_) => Some(0x01),
Self::Eip1559(_) => Some(0x02),
Self::BlobTransaction(_) => Some(0x03),
Self::Eip7702(_) => Some(0x04),
}
}
fn encode_2718_len(&self) -> usize {
match self {
Self::Legacy(tx) => tx.eip2718_encoded_length(),
Self::Eip2930(tx) => tx.eip2718_encoded_length(),
Self::Eip1559(tx) => tx.eip2718_encoded_length(),
Self::Eip7702(tx) => tx.eip2718_encoded_length(),
Self::BlobTransaction(tx) => tx.eip2718_encoded_length(),
}
}
fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) {
match self {
Self::Legacy(tx) => tx.eip2718_encode(out),
Self::Eip2930(tx) => tx.eip2718_encode(out),
Self::Eip1559(tx) => tx.eip2718_encode(out),
Self::Eip7702(tx) => tx.eip2718_encode(out),
Self::BlobTransaction(tx) => tx.eip2718_encode(out),
}
}
fn trie_hash(&self) -> B256 {
*self.hash()
}
}
impl Decodable2718 for PooledTransactionsElement {
fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
match ty {
EIP4844_TX_TYPE_ID => {
// Recall that the blob transaction response `TransactionPayload` is encoded like
// this: `rlp([tx_payload_body, blobs, commitments, proofs])`
//
// Note that `tx_payload_body` is a list:
// `[chain_id, nonce, max_priority_fee_per_gas, ..., y_parity, r, s]`
//
// This makes the full encoding:
// `tx_type (0x03) || rlp([[chain_id, nonce, ...], blobs, commitments, proofs])`
// Now, we decode the inner blob transaction:
// `rlp([[chain_id, nonce, ...], blobs, commitments, proofs])`
let blob_tx = BlobTransaction::decode_inner(buf)?;
Ok(Self::BlobTransaction(blob_tx))
}
tx_type => {
let typed_tx = TransactionSigned::typed_decode(tx_type, buf)?;
let hash = typed_tx.hash();
match typed_tx.transaction {
Transaction::Legacy(_) => Err(RlpError::Custom(
"legacy transactions should not be a result of typed decoding",
).into()),
// because we checked the tx type, we can be sure that the transaction is not a
// blob transaction
Transaction::Eip4844(_) => Err(RlpError::Custom(
"EIP-4844 transactions can only be decoded with transaction type 0x03",
).into()),
Transaction::Eip2930(tx) => Ok(Self::Eip2930 (
Signed::new_unchecked(tx, typed_tx.signature, hash)
)),
Transaction::Eip1559(tx) => Ok(Self::Eip1559( Signed::new_unchecked(tx, typed_tx.signature, hash))),
Transaction::Eip7702(tx) => Ok(Self::Eip7702( Signed::new_unchecked(tx, typed_tx.signature, hash))),
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => Err(RlpError::Custom("Optimism deposit transaction cannot be decoded to PooledTransactionsElement").into())
}
}
}
}
fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
Ok(Self::Legacy(TxLegacy::rlp_decode_signed(buf)?))
}
}
impl Typed2718 for PooledTransactionsElement {
fn ty(&self) -> u8 {
match self {
Self::Legacy(tx) => tx.tx().ty(),
Self::Eip2930(tx) => tx.tx().ty(),
Self::Eip1559(tx) => tx.tx().ty(),
Self::BlobTransaction(tx) => tx.tx().ty(),
Self::Eip7702(tx) => tx.tx().ty(),
}
}
}
impl alloy_consensus::Transaction for PooledTransactionsElement {
fn chain_id(&self) -> Option<ChainId> {
match self {
Self::Legacy(tx) => tx.tx().chain_id(),
Self::Eip2930(tx) => tx.tx().chain_id(),
Self::Eip1559(tx) => tx.tx().chain_id(),
Self::Eip7702(tx) => tx.tx().chain_id(),
Self::BlobTransaction(tx) => tx.tx().chain_id(),
}
}
fn nonce(&self) -> u64 {
match self {
Self::Legacy(tx) => tx.tx().nonce(),
Self::Eip2930(tx) => tx.tx().nonce(),
Self::Eip1559(tx) => tx.tx().nonce(),
Self::Eip7702(tx) => tx.tx().nonce(),
Self::BlobTransaction(tx) => tx.tx().nonce(),
}
}
fn gas_limit(&self) -> u64 {
match self {
Self::Legacy(tx) => tx.tx().gas_limit(),
Self::Eip2930(tx) => tx.tx().gas_limit(),
Self::Eip1559(tx) => tx.tx().gas_limit(),
Self::Eip7702(tx) => tx.tx().gas_limit(),
Self::BlobTransaction(tx) => tx.tx().gas_limit(),
}
}
fn gas_price(&self) -> Option<u128> {
match self {
Self::Legacy(tx) => tx.tx().gas_price(),
Self::Eip2930(tx) => tx.tx().gas_price(),
Self::Eip1559(tx) => tx.tx().gas_price(),
Self::Eip7702(tx) => tx.tx().gas_price(),
Self::BlobTransaction(tx) => tx.tx().gas_price(),
}
}
fn max_fee_per_gas(&self) -> u128 {
match self {
Self::Legacy(tx) => tx.tx().max_fee_per_gas(),
Self::Eip2930(tx) => tx.tx().max_fee_per_gas(),
Self::Eip1559(tx) => tx.tx().max_fee_per_gas(),
Self::Eip7702(tx) => tx.tx().max_fee_per_gas(),
Self::BlobTransaction(tx) => tx.tx().max_fee_per_gas(),
}
}
fn max_priority_fee_per_gas(&self) -> Option<u128> {
match self {
Self::Legacy(tx) => tx.tx().max_priority_fee_per_gas(),
Self::Eip2930(tx) => tx.tx().max_priority_fee_per_gas(),
Self::Eip1559(tx) => tx.tx().max_priority_fee_per_gas(),
Self::Eip7702(tx) => tx.tx().max_priority_fee_per_gas(),
Self::BlobTransaction(tx) => tx.tx().max_priority_fee_per_gas(),
}
}
fn max_fee_per_blob_gas(&self) -> Option<u128> {
match self {
Self::Legacy(tx) => tx.tx().max_fee_per_blob_gas(),
Self::Eip2930(tx) => tx.tx().max_fee_per_blob_gas(),
Self::Eip1559(tx) => tx.tx().max_fee_per_blob_gas(),
Self::Eip7702(tx) => tx.tx().max_fee_per_blob_gas(),
Self::BlobTransaction(tx) => tx.tx().max_fee_per_blob_gas(),
}
}
fn priority_fee_or_price(&self) -> u128 {
match self {
Self::Legacy(tx) => tx.tx().priority_fee_or_price(),
Self::Eip2930(tx) => tx.tx().priority_fee_or_price(),
Self::Eip1559(tx) => tx.tx().priority_fee_or_price(),
Self::Eip7702(tx) => tx.tx().priority_fee_or_price(),
Self::BlobTransaction(tx) => tx.tx().priority_fee_or_price(),
}
}
fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
match self {
Self::Legacy(tx) => tx.tx().effective_gas_price(base_fee),
Self::Eip2930(tx) => tx.tx().effective_gas_price(base_fee),
Self::Eip1559(tx) => tx.tx().effective_gas_price(base_fee),
Self::Eip7702(tx) => tx.tx().effective_gas_price(base_fee),
Self::BlobTransaction(tx) => tx.tx().effective_gas_price(base_fee),
}
}
fn is_dynamic_fee(&self) -> bool {
match self {
Self::Legacy(tx) => tx.tx().is_dynamic_fee(),
Self::Eip2930(tx) => tx.tx().is_dynamic_fee(),
Self::Eip1559(tx) => tx.tx().is_dynamic_fee(),
Self::Eip7702(tx) => tx.tx().is_dynamic_fee(),
Self::BlobTransaction(tx) => tx.tx().is_dynamic_fee(),
}
}
fn kind(&self) -> TxKind {
match self {
Self::Legacy(tx) => tx.tx().kind(),
Self::Eip2930(tx) => tx.tx().kind(),
Self::Eip1559(tx) => tx.tx().kind(),
Self::Eip7702(tx) => tx.tx().kind(),
Self::BlobTransaction(tx) => tx.tx().kind(),
}
}
fn is_create(&self) -> bool {
match self {
Self::Legacy(tx) => tx.tx().is_create(),
Self::Eip2930(tx) => tx.tx().is_create(),
Self::Eip1559(tx) => tx.tx().is_create(),
Self::Eip7702(tx) => tx.tx().is_create(),
Self::BlobTransaction(tx) => tx.tx().is_create(),
}
}
fn value(&self) -> U256 {
match self {
Self::Legacy(tx) => tx.tx().value(),
Self::Eip2930(tx) => tx.tx().value(),
Self::Eip1559(tx) => tx.tx().value(),
Self::Eip7702(tx) => tx.tx().value(),
Self::BlobTransaction(tx) => tx.tx().value(),
}
}
fn input(&self) -> &Bytes {
match self {
Self::Legacy(tx) => tx.tx().input(),
Self::Eip2930(tx) => tx.tx().input(),
Self::Eip1559(tx) => tx.tx().input(),
Self::Eip7702(tx) => tx.tx().input(),
Self::BlobTransaction(tx) => tx.tx().input(),
}
}
fn access_list(&self) -> Option<&AccessList> {
match self {
Self::Legacy(tx) => tx.tx().access_list(),
Self::Eip2930(tx) => tx.tx().access_list(),
Self::Eip1559(tx) => tx.tx().access_list(),
Self::Eip7702(tx) => tx.tx().access_list(),
Self::BlobTransaction(tx) => tx.tx().access_list(),
}
}
fn blob_versioned_hashes(&self) -> Option<&[B256]> {
match self {
Self::Legacy(tx) => tx.tx().blob_versioned_hashes(),
Self::Eip2930(tx) => tx.tx().blob_versioned_hashes(),
Self::Eip1559(tx) => tx.tx().blob_versioned_hashes(),
Self::Eip7702(tx) => tx.tx().blob_versioned_hashes(),
Self::BlobTransaction(tx) => tx.tx().blob_versioned_hashes(),
}
}
fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
match self {
Self::Legacy(tx) => tx.tx().authorization_list(),
Self::Eip2930(tx) => tx.tx().authorization_list(),
Self::Eip1559(tx) => tx.tx().authorization_list(),
Self::Eip7702(tx) => tx.tx().authorization_list(),
Self::BlobTransaction(tx) => tx.tx().authorization_list(),
}
}
}
impl SignedTransaction for PooledTransactionsElement {
fn tx_hash(&self) -> &TxHash {
match self {
Self::Legacy(tx) => tx.hash(),
Self::Eip2930(tx) => tx.hash(),
Self::Eip1559(tx) => tx.hash(),
Self::Eip7702(tx) => tx.hash(),
Self::BlobTransaction(tx) => tx.hash(),
}
}
fn signature(&self) -> &Signature {
match self {
Self::Legacy(tx) => tx.signature(),
Self::Eip2930(tx) => tx.signature(),
Self::Eip1559(tx) => tx.signature(),
Self::Eip7702(tx) => tx.signature(),
Self::BlobTransaction(tx) => tx.signature(),
}
}
fn recover_signer(&self) -> Option<Address> {
let signature_hash = self.signature_hash();
recover_signer(self.signature(), signature_hash)
}
fn recover_signer_unchecked_with_buf(&self, buf: &mut Vec<u8>) -> Option<Address> {
self.encode_for_signing(buf);
let signature_hash = keccak256(buf);
recover_signer_unchecked(self.signature(), signature_hash)
}
}
impl InMemorySize for PooledTransactionsElement {
fn size(&self) -> usize {
match self {
Self::Legacy(tx) => tx.size(),
Self::Eip2930(tx) => tx.size(),
Self::Eip1559(tx) => tx.size(),
Self::Eip7702(tx) => tx.size(),
Self::BlobTransaction(tx) => tx.size(),
}
}
}
impl From<PooledTransactionsElementEcRecovered> for PooledTransactionsElement {
fn from(recovered: PooledTransactionsElementEcRecovered) -> Self {
recovered.into_signed()
}
}
impl TryFrom<TransactionSigned> for PooledTransactionsElement {
type Error = TransactionConversionError;
fn try_from(tx: TransactionSigned) -> Result<Self, Self::Error> {
tx.try_into_pooled().map_err(|_| TransactionConversionError::UnsupportedForP2P)
}
}
impl From<PooledTransactionsElement> for TransactionSigned {
fn from(element: PooledTransactionsElement) -> Self {
match element {
PooledTransactionsElement::Legacy(tx) => tx.into(),
PooledTransactionsElement::Eip2930(tx) => tx.into(),
PooledTransactionsElement::Eip1559(tx) => tx.into(),
PooledTransactionsElement::Eip7702(tx) => tx.into(),
PooledTransactionsElement::BlobTransaction(blob_tx) => blob_tx.into_parts().0,
}
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for PooledTransactionsElement {
/// Generates an arbitrary `PooledTransactionsElement`.
///
/// This function generates an arbitrary `PooledTransactionsElement` by creating a transaction
/// and, if applicable, generating a sidecar for blob transactions.
///
/// It handles the generation of sidecars and constructs the resulting
/// `PooledTransactionsElement`.
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
// Attempt to create a `TransactionSigned` with arbitrary data.
let tx_signed = TransactionSigned::arbitrary(u)?;
// Attempt to create a `PooledTransactionsElement` with arbitrary data, handling the Result.
match tx_signed.try_into_pooled() {
Ok(tx) => Ok(tx),
Err(tx) => {
let (tx, sig, hash) = tx.into_parts();
match tx {
Transaction::Eip4844(tx) => {
let sidecar = BlobTransactionSidecar::arbitrary(u)?;
Ok(Self::BlobTransaction(BlobTransaction(Signed::new_unchecked(
TxEip4844WithSidecar { tx, sidecar },
sig,
hash,
))))
}
_ => Err(arbitrary::Error::IncorrectFormat),
}
}
}
}
}
/// Tmp alias for the transaction type.
pub type PooledTransactionsElement = alloy_consensus::transaction::PooledTransaction;
/// A signed pooled transaction with recovered signer.
pub type PooledTransactionsElementEcRecovered<T = PooledTransactionsElement> = RecoveredTx<T>;
@ -673,7 +15,7 @@ impl PooledTransactionsElementEcRecovered {
/// Transform back to [`RecoveredTx`]
pub fn into_ecrecovered_transaction(self) -> RecoveredTx {
let (tx, signer) = self.to_components();
tx.into_ecrecovered_transaction(signer)
RecoveredTx::from_signed_transaction(tx.into(), signer)
}
/// Converts from an EIP-4844 [`RecoveredTx`] to a
@ -685,9 +27,9 @@ impl PooledTransactionsElementEcRecovered {
sidecar: BlobTransactionSidecar,
) -> Result<Self, RecoveredTx> {
let RecoveredTx { signer, signed_transaction } = tx;
let transaction =
PooledTransactionsElement::try_from_blob_transaction(signed_transaction, sidecar)
.map_err(|tx| RecoveredTx { signer, signed_transaction: tx })?;
let transaction = signed_transaction
.try_into_pooled_eip4844(sidecar)
.map_err(|tx| RecoveredTx { signer, signed_transaction: tx })?;
Ok(Self::from_signed_transaction(transaction, signer))
}
}
@ -709,8 +51,10 @@ impl TryFrom<RecoveredTx> for PooledTransactionsElementEcRecovered {
#[cfg(test)]
mod tests {
use super::*;
use alloy_consensus::Transaction as _;
use alloy_consensus::{transaction::RlpEcdsaTx, Transaction as _, TxLegacy};
use alloy_eips::eip2718::Decodable2718;
use alloy_primitives::{address, hex};
use alloy_rlp::Decodable;
use assert_matches::assert_matches;
use bytes::Bytes;
@ -761,10 +105,7 @@ mod tests {
);
let res = PooledTransactionsElement::decode_2718(&mut &data[..]).unwrap();
assert_eq!(
res.into_transaction().to(),
Some(address!("714b6a4ea9b94a8a7d9fd362ed72630688c8898c"))
);
assert_eq!(res.to(), Some(address!("714b6a4ea9b94a8a7d9fd362ed72630688c8898c")));
}
#[test]

View File

@ -1,247 +0,0 @@
#![cfg_attr(docsrs, doc(cfg(feature = "c-kzg")))]
use crate::{Transaction, TransactionSigned};
use alloy_consensus::{transaction::RlpEcdsaTx, Signed, TxEip4844WithSidecar};
use alloy_eips::eip4844::BlobTransactionSidecar;
use derive_more::Deref;
use reth_primitives_traits::InMemorySize;
use serde::{Deserialize, Serialize};
/// A response to `GetPooledTransactions` that includes blob data, their commitments, and their
/// corresponding proofs.
///
/// This is defined in [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#networking) as an element
/// of a `PooledTransactions` response.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Deref)]
pub struct BlobTransaction(pub Signed<TxEip4844WithSidecar>);
impl BlobTransaction {
/// Constructs a new [`BlobTransaction`] from a [`TransactionSigned`] and a
/// [`BlobTransactionSidecar`].
///
/// Returns an error if the signed transaction is not [`Transaction::Eip4844`]
pub fn try_from_signed(
tx: TransactionSigned,
sidecar: BlobTransactionSidecar,
) -> Result<Self, (TransactionSigned, BlobTransactionSidecar)> {
let hash = tx.hash();
let TransactionSigned { transaction, signature, .. } = tx;
match transaction {
Transaction::Eip4844(transaction) => Ok(Self(Signed::new_unchecked(
TxEip4844WithSidecar { tx: transaction, sidecar },
signature,
hash,
))),
transaction => {
let tx = TransactionSigned::new(transaction, signature, hash);
Err((tx, sidecar))
}
}
}
/// Splits the [`BlobTransaction`] into its [`TransactionSigned`] and [`BlobTransactionSidecar`]
/// components.
pub fn into_parts(self) -> (TransactionSigned, BlobTransactionSidecar) {
let (transaction, signature, hash) = self.0.into_parts();
let (transaction, sidecar) = transaction.into_parts();
let transaction = TransactionSigned::new(transaction.into(), signature, hash);
(transaction, sidecar)
}
/// Decodes a [`BlobTransaction`] from RLP. This expects the encoding to be:
/// `rlp([transaction_payload_body, blobs, commitments, proofs])`
///
/// where `transaction_payload_body` is a list:
/// `[chain_id, nonce, max_priority_fee_per_gas, ..., y_parity, r, s]`
///
/// Note: this should be used only when implementing other RLP decoding methods, and does not
/// represent the full RLP decoding of the `PooledTransactionsElement` type.
pub(crate) fn decode_inner(data: &mut &[u8]) -> alloy_rlp::Result<Self> {
let (transaction, signature, hash) =
TxEip4844WithSidecar::rlp_decode_signed(data)?.into_parts();
Ok(Self(Signed::new_unchecked(transaction, signature, hash)))
}
}
impl InMemorySize for BlobTransaction {
fn size(&self) -> usize {
// TODO(mattsse): replace with next alloy bump
self.0.hash().size() +
self.0.signature().size() +
self.0.tx().tx().size() +
self.0.tx().sidecar.size()
}
}
#[cfg(all(test, feature = "c-kzg"))]
mod tests {
use super::*;
use crate::{kzg::Blob, PooledTransactionsElement};
use alloc::vec::Vec;
use alloy_eips::{
eip2718::{Decodable2718, Encodable2718},
eip4844::Bytes48,
};
use alloy_primitives::hex;
use std::{fs, path::PathBuf, str::FromStr};
#[test]
fn test_blob_transaction_sidecar_generation() {
// Read the contents of the JSON file into a string.
let json_content = fs::read_to_string(
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/transaction/blob_data/blob1.json"),
)
.expect("Failed to read the blob data file");
// Parse the JSON contents into a serde_json::Value
let json_value: serde_json::Value =
serde_json::from_str(&json_content).expect("Failed to deserialize JSON");
// Extract blob data from JSON and convert it to Blob
let blobs: Vec<Blob> = vec![Blob::from_hex(
json_value.get("data").unwrap().as_str().expect("Data is not a valid string"),
)
.unwrap()];
// Generate a BlobTransactionSidecar from the blobs
let sidecar = BlobTransactionSidecar::try_from_blobs(blobs).unwrap();
// Assert commitment equality
assert_eq!(
sidecar.commitments,
vec![
Bytes48::from_str(json_value.get("commitment").unwrap().as_str().unwrap()).unwrap()
]
);
}
#[test]
fn test_blob_transaction_sidecar_size() {
// Vector to store blob data from each file
let mut blobs: Vec<Blob> = Vec::new();
// Iterate over each file in the folder
for entry in fs::read_dir(
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/transaction/blob_data/"),
)
.expect("Failed to read blob_data folder")
{
let entry = entry.expect("Failed to read directory entry");
let file_path = entry.path();
// Ensure the entry is a file and not a directory
if !file_path.is_file() || file_path.extension().unwrap_or_default() != "json" {
continue
}
// Read the contents of the JSON file into a string.
let json_content =
fs::read_to_string(file_path).expect("Failed to read the blob data file");
// Parse the JSON contents into a serde_json::Value
let json_value: serde_json::Value =
serde_json::from_str(&json_content).expect("Failed to deserialize JSON");
// Extract blob data from JSON and convert it to Blob
if let Some(data) = json_value.get("data") {
if let Some(data_str) = data.as_str() {
if let Ok(blob) = Blob::from_hex(data_str) {
blobs.push(blob);
}
}
}
}
// Generate a BlobTransactionSidecar from the blobs
let sidecar = BlobTransactionSidecar::try_from_blobs(blobs).unwrap();
// Assert sidecar size
assert_eq!(sidecar.size(), 524672);
}
#[test]
fn test_blob_transaction_sidecar_rlp_encode() {
// Read the contents of the JSON file into a string.
let json_content = fs::read_to_string(
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/transaction/blob_data/blob1.json"),
)
.expect("Failed to read the blob data file");
// Parse the JSON contents into a serde_json::Value
let json_value: serde_json::Value =
serde_json::from_str(&json_content).expect("Failed to deserialize JSON");
// Extract blob data from JSON and convert it to Blob
let blobs: Vec<Blob> = vec![Blob::from_hex(
json_value.get("data").unwrap().as_str().expect("Data is not a valid string"),
)
.unwrap()];
// Generate a BlobTransactionSidecar from the blobs
let sidecar = BlobTransactionSidecar::try_from_blobs(blobs).unwrap();
// Create a vector to store the encoded RLP
let mut encoded_rlp = Vec::new();
// Encode the inner data of the BlobTransactionSidecar into RLP
sidecar.rlp_encode_fields(&mut encoded_rlp);
// Assert the equality between the expected RLP from the JSON and the encoded RLP
assert_eq!(json_value.get("rlp").unwrap().as_str().unwrap(), hex::encode(&encoded_rlp));
}
#[test]
fn test_blob_transaction_sidecar_rlp_decode() {
// Read the contents of the JSON file into a string.
let json_content = fs::read_to_string(
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/transaction/blob_data/blob1.json"),
)
.expect("Failed to read the blob data file");
// Parse the JSON contents into a serde_json::Value
let json_value: serde_json::Value =
serde_json::from_str(&json_content).expect("Failed to deserialize JSON");
// Extract blob data from JSON and convert it to Blob
let blobs: Vec<Blob> = vec![Blob::from_hex(
json_value.get("data").unwrap().as_str().expect("Data is not a valid string"),
)
.unwrap()];
// Generate a BlobTransactionSidecar from the blobs
let sidecar = BlobTransactionSidecar::try_from_blobs(blobs).unwrap();
// Create a vector to store the encoded RLP
let mut encoded_rlp = Vec::new();
// Encode the inner data of the BlobTransactionSidecar into RLP
sidecar.rlp_encode_fields(&mut encoded_rlp);
// Decode the RLP-encoded data back into a BlobTransactionSidecar
let decoded_sidecar =
BlobTransactionSidecar::rlp_decode_fields(&mut encoded_rlp.as_slice()).unwrap();
// Assert the equality between the original BlobTransactionSidecar and the decoded one
assert_eq!(sidecar, decoded_sidecar);
}
#[test]
fn decode_encode_raw_4844_rlp() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/4844rlp");
let dir = fs::read_dir(path).expect("Unable to read folder");
for entry in dir {
let entry = entry.unwrap();
let content = fs::read_to_string(entry.path()).unwrap();
let raw = hex::decode(content.trim()).unwrap();
let tx = PooledTransactionsElement::decode_2718(&mut raw.as_ref())
.map_err(|err| {
panic!("Failed to decode transaction: {:?} {:?}", err, entry.path());
})
.unwrap();
// We want to test only EIP-4844 transactions
assert!(tx.is_eip4844());
let encoded = tx.encoded_2718();
assert_eq!(encoded.as_slice(), &raw[..], "{:?}", entry.path());
}
}
}