use super::{error::TransactionConversionError, signature::recover_signer, TxEip7702};
use crate::{BlobTransaction, Transaction, TransactionSigned, TransactionSignedEcRecovered};
use alloy_eips::eip4844::BlobTransactionSidecar;
use alloy_consensus::{
constants::EIP4844_TX_TYPE_ID,
transaction::{RlpEcdsaTx, TxEip1559, TxEip2930, TxEip4844, TxLegacy},
SignableTransaction, TxEip4844WithSidecar,
};
use alloy_eips::eip2718::{Decodable2718, Eip2718Result, Encodable2718};
use alloy_primitives::{Address, PrimitiveSignature as Signature, TxHash, B256};
use alloy_rlp::{Decodable, Encodable, Error as RlpError, Header};
use bytes::Buf;
use derive_more::{AsRef, Deref};
use serde::{Deserialize, Serialize};
#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum PooledTransactionsElement {
Legacy {
transaction: TxLegacy,
signature: Signature,
hash: TxHash,
},
Eip2930 {
transaction: TxEip2930,
signature: Signature,
hash: TxHash,
},
Eip1559 {
transaction: TxEip1559,
signature: Signature,
hash: TxHash,
},
Eip7702 {
transaction: TxEip7702,
signature: Signature,
hash: TxHash,
},
BlobTransaction(BlobTransaction),
}
impl PooledTransactionsElement {
pub fn try_from_broadcast(tx: TransactionSigned) -> Result<Self, TransactionSigned> {
match tx {
TransactionSigned { transaction: Transaction::Legacy(tx), signature, hash } => {
Ok(Self::Legacy { transaction: tx, signature, hash })
}
TransactionSigned { transaction: Transaction::Eip2930(tx), signature, hash } => {
Ok(Self::Eip2930 { transaction: tx, signature, hash })
}
TransactionSigned { transaction: Transaction::Eip1559(tx), signature, hash } => {
Ok(Self::Eip1559 { transaction: tx, signature, hash })
}
TransactionSigned { transaction: Transaction::Eip7702(tx), signature, hash } => {
Ok(Self::Eip7702 { transaction: tx, signature, hash })
}
tx @ TransactionSigned { transaction: Transaction::Eip4844(_), .. } => Err(tx),
#[cfg(feature = "optimism")]
tx @ TransactionSigned { transaction: Transaction::Deposit(_), .. } => Err(tx),
}
}
pub fn try_from_blob_transaction(
tx: TransactionSigned,
sidecar: BlobTransactionSidecar,
) -> Result<Self, TransactionSigned> {
Ok(match tx {
TransactionSigned { transaction: Transaction::Eip4844(tx), signature, hash } => {
Self::BlobTransaction(BlobTransaction {
signature,
hash,
transaction: TxEip4844WithSidecar { tx, sidecar },
})
}
_ => return Err(tx),
})
}
pub fn signature_hash(&self) -> B256 {
match self {
Self::Legacy { transaction, .. } => transaction.signature_hash(),
Self::Eip2930 { transaction, .. } => transaction.signature_hash(),
Self::Eip1559 { transaction, .. } => transaction.signature_hash(),
Self::Eip7702 { transaction, .. } => transaction.signature_hash(),
Self::BlobTransaction(blob_tx) => blob_tx.transaction.signature_hash(),
}
}
pub const fn hash(&self) -> &TxHash {
match self {
Self::Legacy { hash, .. } |
Self::Eip2930 { hash, .. } |
Self::Eip1559 { hash, .. } |
Self::Eip7702 { hash, .. } => hash,
Self::BlobTransaction(tx) => &tx.hash,
}
}
pub const fn signature(&self) -> &Signature {
match self {
Self::Legacy { signature, .. } |
Self::Eip2930 { signature, .. } |
Self::Eip1559 { signature, .. } |
Self::Eip7702 { signature, .. } => signature,
Self::BlobTransaction(blob_tx) => &blob_tx.signature,
}
}
pub const fn nonce(&self) -> u64 {
match self {
Self::Legacy { transaction, .. } => transaction.nonce,
Self::Eip2930 { transaction, .. } => transaction.nonce,
Self::Eip1559 { transaction, .. } => transaction.nonce,
Self::Eip7702 { transaction, .. } => transaction.nonce,
Self::BlobTransaction(blob_tx) => blob_tx.transaction.tx.nonce,
}
}
pub fn recover_signer(&self) -> Option<Address> {
recover_signer(self.signature(), self.signature_hash())
}
pub fn try_into_ecrecovered(self) -> Result<PooledTransactionsElementEcRecovered, Self> {
match self.recover_signer() {
None => Err(self),
Some(signer) => Ok(PooledTransactionsElementEcRecovered { transaction: self, signer }),
}
}
pub fn into_ecrecovered_transaction(self, signer: Address) -> TransactionSignedEcRecovered {
TransactionSignedEcRecovered::from_signed_transaction(self.into_transaction(), signer)
}
pub fn into_transaction(self) -> TransactionSigned {
match self {
Self::Legacy { transaction, signature, hash } => {
TransactionSigned { transaction: Transaction::Legacy(transaction), signature, hash }
}
Self::Eip2930 { transaction, signature, hash } => TransactionSigned {
transaction: Transaction::Eip2930(transaction),
signature,
hash,
},
Self::Eip1559 { transaction, signature, hash } => TransactionSigned {
transaction: Transaction::Eip1559(transaction),
signature,
hash,
},
Self::Eip7702 { transaction, signature, hash } => TransactionSigned {
transaction: Transaction::Eip7702(transaction),
signature,
hash,
},
Self::BlobTransaction(blob_tx) => blob_tx.into_parts().0,
}
}
#[inline]
pub const fn is_eip4844(&self) -> bool {
matches!(self, Self::BlobTransaction(_))
}
pub const fn as_legacy(&self) -> Option<&TxLegacy> {
match self {
Self::Legacy { transaction, .. } => Some(transaction),
_ => None,
}
}
pub const fn as_eip2930(&self) -> Option<&TxEip2930> {
match self {
Self::Eip2930 { transaction, .. } => Some(transaction),
_ => None,
}
}
pub const fn as_eip1559(&self) -> Option<&TxEip1559> {
match self {
Self::Eip1559 { transaction, .. } => Some(transaction),
_ => None,
}
}
pub const fn as_eip4844(&self) -> Option<&TxEip4844> {
match self {
Self::BlobTransaction(tx) => Some(&tx.transaction.tx),
_ => None,
}
}
pub const fn as_eip7702(&self) -> Option<&TxEip7702> {
match self {
Self::Eip7702 { transaction, .. } => Some(transaction),
_ => None,
}
}
pub fn blob_gas_used(&self) -> Option<u64> {
self.as_eip4844().map(TxEip4844::blob_gas)
}
pub const fn max_fee_per_blob_gas(&self) -> Option<u128> {
match self {
Self::BlobTransaction(tx) => Some(tx.transaction.tx.max_fee_per_blob_gas),
_ => None,
}
}
pub const fn max_priority_fee_per_gas(&self) -> Option<u128> {
match self {
Self::Legacy { .. } | Self::Eip2930 { .. } => None,
Self::Eip1559 { transaction, .. } => Some(transaction.max_priority_fee_per_gas),
Self::Eip7702 { transaction, .. } => Some(transaction.max_priority_fee_per_gas),
Self::BlobTransaction(tx) => Some(tx.transaction.tx.max_priority_fee_per_gas),
}
}
pub const fn max_fee_per_gas(&self) -> u128 {
match self {
Self::Legacy { transaction, .. } => transaction.gas_price,
Self::Eip2930 { transaction, .. } => transaction.gas_price,
Self::Eip1559 { transaction, .. } => transaction.max_fee_per_gas,
Self::Eip7702 { transaction, .. } => transaction.max_fee_per_gas,
Self::BlobTransaction(tx) => tx.transaction.tx.max_fee_per_gas,
}
}
}
impl Encodable for PooledTransactionsElement {
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 !self.is_legacy() {
payload_length += Header { list: false, payload_length }.length();
}
payload_length
}
}
impl Decodable for PooledTransactionsElement {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
if buf.is_empty() {
return Err(RlpError::InputTooShort)
}
let mut original_encoding = *buf;
let header = Header::decode(buf)?;
if header.list {
let tx = Self::fallback_decode(&mut original_encoding)?;
*buf = original_encoding;
Ok(tx)
} else {
let tx_type = *buf.first().ok_or(RlpError::InputTooShort)?;
let remaining_len = buf.len();
buf.advance(1);
let tx = Self::typed_decode(tx_type, buf).map_err(RlpError::from)?;
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 { transaction, signature, .. } => {
transaction.eip2718_encoded_length(signature)
}
Self::Eip2930 { transaction, signature, .. } => {
transaction.eip2718_encoded_length(signature)
}
Self::Eip1559 { transaction, signature, .. } => {
transaction.eip2718_encoded_length(signature)
}
Self::Eip7702 { transaction, signature, .. } => {
transaction.eip2718_encoded_length(signature)
}
Self::BlobTransaction(BlobTransaction { transaction, signature, .. }) => {
transaction.eip2718_encoded_length(signature)
}
}
}
fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) {
match self {
Self::Legacy { transaction, signature, .. } => {
transaction.eip2718_encode(signature, out)
}
Self::Eip2930 { transaction, signature, .. } => {
transaction.eip2718_encode(signature, out)
}
Self::Eip1559 { transaction, signature, .. } => {
transaction.eip2718_encode(signature, out)
}
Self::Eip7702 { transaction, signature, .. } => {
transaction.eip2718_encode(signature, out)
}
Self::BlobTransaction(BlobTransaction { transaction, signature, .. }) => {
transaction.eip2718_encode(signature, 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 => {
let blob_tx = BlobTransaction::decode_inner(buf)?;
Ok(Self::BlobTransaction(blob_tx))
}
tx_type => {
let typed_tx = TransactionSigned::typed_decode(tx_type, buf)?;
match typed_tx.transaction {
Transaction::Legacy(_) => Err(RlpError::Custom(
"legacy transactions should not be a result of typed decoding",
).into()),
Transaction::Eip4844(_) => Err(RlpError::Custom(
"EIP-4844 transactions can only be decoded with transaction type 0x03",
).into()),
Transaction::Eip2930(tx) => Ok(Self::Eip2930 {
transaction: tx,
signature: typed_tx.signature,
hash: typed_tx.hash,
}),
Transaction::Eip1559(tx) => Ok(Self::Eip1559 {
transaction: tx,
signature: typed_tx.signature,
hash: typed_tx.hash,
}),
Transaction::Eip7702(tx) => Ok(Self::Eip7702 {
transaction: tx,
signature: typed_tx.signature,
hash: typed_tx.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> {
let (transaction, hash, signature) =
TransactionSigned::decode_rlp_legacy_transaction_tuple(buf)?;
Ok(Self::Legacy { transaction, signature, hash })
}
}
impl TryFrom<TransactionSigned> for PooledTransactionsElement {
type Error = TransactionConversionError;
fn try_from(tx: TransactionSigned) -> Result<Self, Self::Error> {
Self::try_from_broadcast(tx).map_err(|_| TransactionConversionError::UnsupportedForP2P)
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for PooledTransactionsElement {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let tx_signed = TransactionSigned::arbitrary(u)?;
match Self::try_from(tx_signed) {
Ok(Self::BlobTransaction(mut tx)) => {
tx.transaction.sidecar = alloy_eips::eip4844::BlobTransactionSidecar::arbitrary(u)?;
Ok(Self::BlobTransaction(tx))
}
Ok(tx) => Ok(tx), Err(_) => Err(arbitrary::Error::IncorrectFormat), }
}
}
#[derive(Debug, Clone, PartialEq, Eq, AsRef, Deref)]
pub struct PooledTransactionsElementEcRecovered {
signer: Address,
#[deref]
#[as_ref]
transaction: PooledTransactionsElement,
}
impl PooledTransactionsElementEcRecovered {
pub const fn signer(&self) -> Address {
self.signer
}
pub fn into_transaction(self) -> PooledTransactionsElement {
self.transaction
}
pub fn into_ecrecovered_transaction(self) -> TransactionSignedEcRecovered {
let (tx, signer) = self.into_components();
tx.into_ecrecovered_transaction(signer)
}
pub fn into_components(self) -> (PooledTransactionsElement, Address) {
(self.transaction, self.signer)
}
pub const fn from_signed_transaction(
transaction: PooledTransactionsElement,
signer: Address,
) -> Self {
Self { transaction, signer }
}
pub fn try_from_blob_transaction(
tx: TransactionSignedEcRecovered,
sidecar: BlobTransactionSidecar,
) -> Result<Self, TransactionSignedEcRecovered> {
let TransactionSignedEcRecovered { signer, signed_transaction } = tx;
let transaction =
PooledTransactionsElement::try_from_blob_transaction(signed_transaction, sidecar)
.map_err(|tx| TransactionSignedEcRecovered { signer, signed_transaction: tx })?;
Ok(Self { transaction, signer })
}
}
impl TryFrom<TransactionSignedEcRecovered> for PooledTransactionsElementEcRecovered {
type Error = TransactionConversionError;
fn try_from(tx: TransactionSignedEcRecovered) -> Result<Self, Self::Error> {
match PooledTransactionsElement::try_from(tx.signed_transaction) {
Ok(pooled_transaction) => {
Ok(Self { transaction: pooled_transaction, signer: tx.signer })
}
Err(_) => Err(TransactionConversionError::UnsupportedForP2P),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::{address, hex};
use assert_matches::assert_matches;
use bytes::Bytes;
#[test]
fn invalid_legacy_pooled_decoding_input_too_short() {
let input_too_short = [
&hex!("d90b0280808bc5cd028083c5cdfd9e407c56565656")[..],
&hex!("c10b02808083c5cd028883c5cdfd9e407c56565656"),
&hex!("c10b0280808bc5cd028083c5cdfd9e407c56565656"),
&hex!("d40b02808083c5cdeb8783c5acfd9e407c5656565656"),
&hex!("d30102808083c5cd02887dc5cdfd9e64fd9e407c56"),
];
for hex_data in &input_too_short {
let input_rlp = &mut &hex_data[..];
let res = PooledTransactionsElement::decode(input_rlp);
assert!(
res.is_err(),
"expected err after decoding rlp input: {:x?}",
Bytes::copy_from_slice(hex_data)
);
let input_rlp = &mut &hex_data[..];
let res = PooledTransactionsElement::decode_2718(input_rlp);
assert!(
res.is_err(),
"expected err after decoding enveloped rlp input: {:x?}",
Bytes::copy_from_slice(hex_data)
);
}
}
#[test]
fn decode_eip1559_enveloped() {
let data = hex!("02f903d382426882ba09832dc6c0848674742682ed9694714b6a4ea9b94a8a7d9fd362ed72630688c8898c80b90364492d24749189822d8512430d3f3ff7a2ede675ac08265c08e2c56ff6fdaa66dae1cdbe4a5d1d7809f3e99272d067364e597542ac0c369d69e22a6399c3e9bee5da4b07e3f3fdc34c32c3d88aa2268785f3e3f8086df0934b10ef92cfffc2e7f3d90f5e83302e31382e302d64657600000000000000000000000000000000000000000000569e75fc77c1a856f6daaf9e69d8a9566ca34aa47f9133711ce065a571af0cfd000000000000000000000000e1e210594771824dad216568b91c9cb4ceed361c00000000000000000000000000000000000000000000000000000000000546e00000000000000000000000000000000000000000000000000000000000e4e1c00000000000000000000000000000000000000000000000000000000065d6750c00000000000000000000000000000000000000000000000000000000000f288000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002cf600000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000000f1628e56fa6d8c50e5b984a58c0df14de31c7b857ce7ba499945b99252976a93d06dcda6776fc42167fbe71cb59f978f5ef5b12577a90b132d14d9c6efa528076f0161d7bf03643cfc5490ec5084f4a041db7f06c50bd97efa08907ba79ddcac8b890f24d12d8db31abbaaf18985d54f400449ee0559a4452afe53de5853ce090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000064ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000c080a01428023fc54a27544abc421d5d017b9a7c5936ad501cbdecd0d9d12d04c1a033a0753104bbf1c87634d6ff3f0ffa0982710612306003eb022363b57994bdef445a"
);
let res = PooledTransactionsElement::decode_2718(&mut &data[..]).unwrap();
assert_eq!(
res.into_transaction().to(),
Some(address!("714b6a4ea9b94a8a7d9fd362ed72630688c8898c"))
);
}
#[test]
fn legacy_valid_pooled_decoding() {
let data = &hex!("d30b02808083c5cdeb8783c5acfd9e407c565656")[..];
let input_rlp = &mut &data[..];
let res = PooledTransactionsElement::decode(input_rlp);
assert_matches!(res, Ok(_tx));
assert!(input_rlp.is_empty());
let input_rlp = &mut &data[..];
let res = TransactionSigned::decode_rlp_legacy_transaction_tuple(input_rlp);
assert_matches!(res, Ok(_tx));
assert!(input_rlp.is_empty());
let res = PooledTransactionsElement::decode_2718(&mut &data[..]);
assert_matches!(res, Ok(_tx));
}
}