reth_ethereum_primitives/
transaction.rsuse alloc::vec::Vec;
use alloy_consensus::{
transaction::RlpEcdsaTx, SignableTransaction, Signed, TxEip1559, TxEip2930, TxEip4844,
TxEip7702, TxLegacy, TxType, Typed2718,
};
use alloy_eips::{
eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718},
eip2930::AccessList,
eip7702::SignedAuthorization,
};
use alloy_primitives::{
keccak256, Address, Bytes, ChainId, PrimitiveSignature as Signature, TxHash, TxKind, B256, U256,
};
use alloy_rlp::{Decodable, Encodable};
use core::hash::{Hash, Hasher};
use once_cell as _;
#[cfg(not(feature = "std"))]
use once_cell::sync::OnceCell as OnceLock;
use reth_primitives_traits::{
crypto::secp256k1::{recover_signer, recover_signer_unchecked},
InMemorySize, SignedTransaction,
};
use serde::{Deserialize, Serialize};
#[cfg(feature = "std")]
use std::sync::OnceLock;
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),*),
}
};
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, derive_more::From, Serialize, 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(TxLegacy),
Eip2930(TxEip2930),
Eip1559(TxEip1559),
Eip4844(TxEip4844),
Eip7702(TxEip7702),
}
impl 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,
}
}
}
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 access_list(&self) -> Option<&alloy_eips::eip2930::AccessList> {
delegate!(self => tx.access_list())
}
fn authorization_list(&self) -> Option<&[alloy_eips::eip7702::SignedAuthorization]> {
delegate!(self => tx.authorization_list())
}
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 blob_versioned_hashes(&self) -> Option<&[B256]> {
delegate!(self => tx.blob_versioned_hashes())
}
}
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 {
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
}
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)
}
}
}
}
#[derive(Debug, Clone, Eq, Serialize, Deserialize, derive_more::AsRef, derive_more::Deref)]
#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(rlp))]
#[serde(rename_all = "camelCase")]
pub struct TransactionSigned {
#[serde(skip)]
pub hash: OnceLock<TxHash>,
pub signature: Signature,
#[deref]
#[as_ref]
pub 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 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()
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for TransactionSigned {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
#[allow(unused_mut)]
let mut transaction = Transaction::arbitrary(u)?;
let secp = secp256k1::Secp256k1::new();
let key_pair = secp256k1::Keypair::new(&secp, &mut rand::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 length(&self) -> usize {
self.network_len()
}
fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
self.network_encode(out);
}
}
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();
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);
if cfg!(feature = "std") {
reth_zstd_compressors::TRANSACTION_COMPRESSOR.with(|compressor| {
let mut compressor = compressor.borrow_mut();
let tx_bits = self.transaction.to_compact(&mut tmp);
buf.put_slice(&compressor.compress(&tmp).expect("Failed to compress"));
tx_bits as u8
})
} else {
let mut compressor = reth_zstd_compressors::create_tx_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
};
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;
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 {
if cfg!(feature = "std") {
reth_zstd_compressors::TRANSACTION_DECOMPRESSOR.with(|decompressor| {
let mut decompressor = decompressor.borrow_mut();
let transaction_type = (bitflags & 0b110) >> 1;
let (transaction, _) =
Transaction::from_compact(decompressor.decompress(buf), transaction_type);
(transaction, buf)
})
} else {
let mut decompressor = reth_zstd_compressors::create_tx_decompressor();
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 SignedTransaction for TransactionSigned {
fn tx_hash(&self) -> &TxHash {
self.hash.get_or_init(|| self.recalculate_hash())
}
fn signature(&self) -> &Signature {
&self.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)
}
}