reth_rpc_eth_types/
receipt.rs

1//! RPC receipt response builder, extends a layer one receipt with layer two data.
2
3use super::EthResult;
4use alloy_consensus::{transaction::TransactionMeta, ReceiptEnvelope, TxReceipt};
5use alloy_eips::eip7840::BlobParams;
6use alloy_primitives::{Address, TxKind};
7use alloy_rpc_types_eth::{Log, ReceiptWithBloom, TransactionReceipt};
8use reth_ethereum_primitives::{Receipt, TransactionSigned, TxType};
9use reth_primitives_traits::SignedTransaction;
10
11/// Builds an [`TransactionReceipt`] obtaining the inner receipt envelope from the given closure.
12pub fn build_receipt<R, T, E>(
13    transaction: &T,
14    meta: TransactionMeta,
15    receipt: &R,
16    all_receipts: &[R],
17    blob_params: Option<BlobParams>,
18    build_envelope: impl FnOnce(ReceiptWithBloom<alloy_consensus::Receipt<Log>>) -> E,
19) -> EthResult<TransactionReceipt<E>>
20where
21    R: TxReceipt<Log = alloy_primitives::Log>,
22    T: SignedTransaction,
23{
24    // Note: we assume this transaction is valid, because it's mined (or part of pending block)
25    // and we don't need to check for pre EIP-2
26    let from = transaction.recover_signer_unchecked()?;
27
28    // get the previous transaction cumulative gas used
29    let gas_used = if meta.index == 0 {
30        receipt.cumulative_gas_used()
31    } else {
32        let prev_tx_idx = (meta.index - 1) as usize;
33        all_receipts
34            .get(prev_tx_idx)
35            .map(|prev_receipt| receipt.cumulative_gas_used() - prev_receipt.cumulative_gas_used())
36            .unwrap_or_default()
37    };
38
39    let blob_gas_used = transaction.blob_gas_used();
40    // Blob gas price should only be present if the transaction is a blob transaction
41    let blob_gas_price =
42        blob_gas_used.and_then(|_| Some(blob_params?.calc_blob_fee(meta.excess_blob_gas?)));
43
44    let logs_bloom = receipt.bloom();
45
46    // get number of logs in the block
47    let mut num_logs = 0;
48    for prev_receipt in all_receipts.iter().take(meta.index as usize) {
49        num_logs += prev_receipt.logs().len();
50    }
51
52    let logs: Vec<Log> = receipt
53        .logs()
54        .iter()
55        .enumerate()
56        .map(|(tx_log_idx, log)| Log {
57            inner: log.clone(),
58            block_hash: Some(meta.block_hash),
59            block_number: Some(meta.block_number),
60            block_timestamp: Some(meta.timestamp),
61            transaction_hash: Some(meta.tx_hash),
62            transaction_index: Some(meta.index),
63            log_index: Some((num_logs + tx_log_idx) as u64),
64            removed: false,
65        })
66        .collect();
67
68    let rpc_receipt = alloy_rpc_types_eth::Receipt {
69        status: receipt.status_or_post_state(),
70        cumulative_gas_used: receipt.cumulative_gas_used(),
71        logs,
72    };
73
74    let (contract_address, to) = match transaction.kind() {
75        TxKind::Create => (Some(from.create(transaction.nonce())), None),
76        TxKind::Call(addr) => (None, Some(Address(*addr))),
77    };
78
79    Ok(TransactionReceipt {
80        inner: build_envelope(ReceiptWithBloom { receipt: rpc_receipt, logs_bloom }),
81        transaction_hash: meta.tx_hash,
82        transaction_index: Some(meta.index),
83        block_hash: Some(meta.block_hash),
84        block_number: Some(meta.block_number),
85        from,
86        to,
87        gas_used,
88        contract_address,
89        effective_gas_price: transaction.effective_gas_price(meta.base_fee),
90        // EIP-4844 fields
91        blob_gas_price,
92        blob_gas_used,
93    })
94}
95
96/// Receipt response builder.
97#[derive(Debug)]
98pub struct EthReceiptBuilder {
99    /// The base response body, contains L1 fields.
100    pub base: TransactionReceipt,
101}
102
103impl EthReceiptBuilder {
104    /// Returns a new builder with the base response body (L1 fields) set.
105    ///
106    /// Note: This requires _all_ block receipts because we need to calculate the gas used by the
107    /// transaction.
108    pub fn new(
109        transaction: &TransactionSigned,
110        meta: TransactionMeta,
111        receipt: &Receipt,
112        all_receipts: &[Receipt],
113        blob_params: Option<BlobParams>,
114    ) -> EthResult<Self> {
115        let base = build_receipt(
116            transaction,
117            meta,
118            receipt,
119            all_receipts,
120            blob_params,
121            |receipt_with_bloom| match receipt.tx_type {
122                TxType::Legacy => ReceiptEnvelope::Legacy(receipt_with_bloom),
123                TxType::Eip2930 => ReceiptEnvelope::Eip2930(receipt_with_bloom),
124                TxType::Eip1559 => ReceiptEnvelope::Eip1559(receipt_with_bloom),
125                TxType::Eip4844 => ReceiptEnvelope::Eip4844(receipt_with_bloom),
126                TxType::Eip7702 => ReceiptEnvelope::Eip7702(receipt_with_bloom),
127                #[allow(unreachable_patterns)]
128                _ => unreachable!(),
129            },
130        )?;
131
132        Ok(Self { base })
133    }
134
135    /// Builds a receipt response from the base response body, and any set additional fields.
136    pub fn build(self) -> TransactionReceipt {
137        self.base
138    }
139}