reth_rpc_eth_types/
receipt.rs

1//! RPC receipt response builder, extends a layer one receipt with layer two data.
2
3use crate::EthApiError;
4use alloy_consensus::{ReceiptEnvelope, Transaction, TxReceipt};
5use alloy_eips::eip7840::BlobParams;
6use alloy_primitives::{Address, TxKind};
7use alloy_rpc_types_eth::{Log, ReceiptWithBloom, TransactionReceipt};
8use reth_chainspec::EthChainSpec;
9use reth_ethereum_primitives::Receipt;
10use reth_primitives_traits::NodePrimitives;
11use reth_rpc_convert::transaction::{ConvertReceiptInput, ReceiptConverter};
12use std::{borrow::Cow, sync::Arc};
13
14/// Builds an [`TransactionReceipt`] obtaining the inner receipt envelope from the given closure.
15pub fn build_receipt<N, E>(
16    input: &ConvertReceiptInput<'_, N>,
17    blob_params: Option<BlobParams>,
18    build_envelope: impl FnOnce(ReceiptWithBloom<alloy_consensus::Receipt<Log>>) -> E,
19) -> TransactionReceipt<E>
20where
21    N: NodePrimitives,
22{
23    let ConvertReceiptInput { tx, meta, receipt, gas_used, next_log_index } = input;
24    let from = tx.signer();
25
26    let blob_gas_used = tx.blob_gas_used();
27    // Blob gas price should only be present if the transaction is a blob transaction
28    let blob_gas_price =
29        blob_gas_used.and_then(|_| Some(blob_params?.calc_blob_fee(meta.excess_blob_gas?)));
30
31    let status = receipt.status_or_post_state();
32    let cumulative_gas_used = receipt.cumulative_gas_used();
33    let logs_bloom = receipt.bloom();
34
35    let logs = match receipt {
36        Cow::Borrowed(r) => {
37            Log::collect_for_receipt(*next_log_index, *meta, r.logs().iter().cloned())
38        }
39        Cow::Owned(r) => Log::collect_for_receipt(*next_log_index, *meta, r.into_logs()),
40    };
41
42    let rpc_receipt = alloy_rpc_types_eth::Receipt { status, cumulative_gas_used, logs };
43
44    let (contract_address, to) = match tx.kind() {
45        TxKind::Create => (Some(from.create(tx.nonce())), None),
46        TxKind::Call(addr) => (None, Some(Address(*addr))),
47    };
48
49    TransactionReceipt {
50        inner: build_envelope(ReceiptWithBloom { receipt: rpc_receipt, logs_bloom }),
51        transaction_hash: meta.tx_hash,
52        transaction_index: Some(meta.index),
53        block_hash: Some(meta.block_hash),
54        block_number: Some(meta.block_number),
55        from,
56        to,
57        gas_used: *gas_used,
58        contract_address,
59        effective_gas_price: tx.effective_gas_price(meta.base_fee),
60        // EIP-4844 fields
61        blob_gas_price,
62        blob_gas_used,
63    }
64}
65
66/// Converter for Ethereum receipts.
67#[derive(Debug)]
68pub struct EthReceiptConverter<ChainSpec> {
69    chain_spec: Arc<ChainSpec>,
70}
71
72impl<ChainSpec> Clone for EthReceiptConverter<ChainSpec> {
73    fn clone(&self) -> Self {
74        Self { chain_spec: self.chain_spec.clone() }
75    }
76}
77
78impl<ChainSpec> EthReceiptConverter<ChainSpec> {
79    /// Creates a new converter with the given chain spec.
80    pub const fn new(chain_spec: Arc<ChainSpec>) -> Self {
81        Self { chain_spec }
82    }
83}
84
85impl<N, ChainSpec> ReceiptConverter<N> for EthReceiptConverter<ChainSpec>
86where
87    N: NodePrimitives<Receipt = Receipt>,
88    ChainSpec: EthChainSpec + 'static,
89{
90    type RpcReceipt = TransactionReceipt;
91    type Error = EthApiError;
92
93    fn convert_receipts(
94        &self,
95        inputs: Vec<ConvertReceiptInput<'_, N>>,
96    ) -> Result<Vec<Self::RpcReceipt>, Self::Error> {
97        let mut receipts = Vec::with_capacity(inputs.len());
98
99        for input in inputs {
100            let tx_type = input.receipt.tx_type;
101            let blob_params = self.chain_spec.blob_params_at_timestamp(input.meta.timestamp);
102            receipts.push(build_receipt(&input, blob_params, |receipt_with_bloom| {
103                ReceiptEnvelope::from_typed(tx_type, receipt_with_bloom)
104            }));
105        }
106
107        Ok(receipts)
108    }
109}