reth_rpc_eth_types/
receipt.rs

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