cfx_rpc_cfx_types/
receipt.rs

1// Copyright 2019 Conflux Foundation. All rights reserved.
2// Conflux is free software and distributed under GNU General Public License.
3// See http://www.gnu.org/licenses/
4
5use crate::{Log, RpcAddress};
6use cfx_addr::Network;
7use cfx_types::{
8    address_util::AddressUtil, cal_contract_address, Bloom,
9    CreateContractAddressType, Space, SpaceMap, H256, U256, U64,
10};
11use cfx_util_macros::bail;
12use primitives::{
13    receipt::{
14        Receipt as PrimitiveReceipt, StorageChange as PrimitiveStorageChange,
15    },
16    transaction::Action,
17    SignedTransaction as PrimitiveTransaction, Transaction, TransactionIndex,
18    TransactionStatus,
19};
20use serde::{Deserialize, Serialize};
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct StorageChange {
24    pub address: RpcAddress,
25    pub collaterals: U64,
26}
27
28impl StorageChange {
29    pub fn try_from(
30        sc: PrimitiveStorageChange, network: Network,
31    ) -> Result<Self, String> {
32        Ok(Self {
33            address: RpcAddress::try_from_h160(sc.address, network)?,
34            collaterals: sc.collaterals,
35        })
36    }
37}
38
39#[derive(Debug, Serialize, Clone, Deserialize)]
40#[serde(rename_all = "camelCase")]
41pub struct Receipt {
42    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
43    pub transaction_type: Option<U64>,
44    /// Transaction hash.
45    pub transaction_hash: H256,
46    /// Transaction index within the block.
47    pub index: U64,
48    /// Block hash.
49    pub block_hash: H256,
50    /// Epoch number where this transaction was in.
51    pub epoch_number: Option<U64>,
52    /// Address of the sender.
53    pub from: RpcAddress,
54    /// Address of the receiver, null when it's a contract creation
55    /// transaction.
56    pub to: Option<RpcAddress>,
57    /// The gas used in the execution of the transaction.
58    pub gas_used: U256,
59    /// The total gas used (not gas charged) in the block following execution
60    /// of the transaction.
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub accumulated_gas_used: Option<U256>,
63    /// The gas fee charged in the execution of the transaction.
64    pub gas_fee: U256,
65    pub effective_gas_price: U256,
66    /// Address of contract created if the transaction action is create.
67    pub contract_created: Option<RpcAddress>,
68    /// Array of log objects, which this transaction generated.
69    pub logs: Vec<Log>,
70    /// Bloom filter for light clients to quickly retrieve related logs.
71    pub logs_bloom: Bloom,
72    /// State root.
73    pub state_root: H256,
74    /// Transaction outcome.
75    pub outcome_status: U64,
76    /// Detailed error message if tx execution is unsuccessful. Error message
77    /// is None if tx execution is successful or it can not be offered.
78    /// Error message can not be offered by light client.
79    pub tx_exec_error_msg: Option<String>,
80    // Whether gas costs were covered by the sponsor.
81    pub gas_covered_by_sponsor: bool,
82    // Whether storage costs were covered by the sponsor.
83    pub storage_covered_by_sponsor: bool,
84    // The amount of storage collateralized by the sender.
85    pub storage_collateralized: U64,
86    // Storage collaterals released during the execution of the transaction.
87    pub storage_released: Vec<StorageChange>,
88    /// Transaction space.
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub space: Option<Space>,
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub burnt_gas_fee: Option<U256>,
93}
94
95impl Receipt {
96    pub fn new(
97        transaction: PrimitiveTransaction, receipt: PrimitiveReceipt,
98        transaction_index: TransactionIndex, prior_gas_used: U256,
99        epoch_number: Option<u64>, maybe_base_price: Option<SpaceMap<U256>>,
100        maybe_state_root: Option<H256>, tx_exec_error_msg: Option<String>,
101        network: Network, include_eth_receipt: bool,
102        include_accumulated_gas_used: bool,
103    ) -> Result<Receipt, String> {
104        let PrimitiveReceipt {
105            accumulated_gas_used,
106            gas_fee,
107            gas_sponsor_paid,
108            log_bloom,
109            logs,
110            outcome_status,
111            storage_collateralized,
112            storage_released,
113            storage_sponsor_paid,
114            ..
115        } = receipt;
116
117        let (address, action, space) = match transaction.unsigned {
118            Transaction::Native(ref unsigned) => {
119                if Action::Create == unsigned.action()
120                    && outcome_status == TransactionStatus::Success
121                {
122                    let (mut created_address, _) = cal_contract_address(
123                        CreateContractAddressType::FromSenderNonceAndCodeHash,
124                        &transaction.sender,
125                        unsigned.nonce(),
126                        unsigned.data(),
127                    );
128                    created_address.set_contract_type_bits();
129                    let address = Some(RpcAddress::try_from_h160(
130                        created_address,
131                        network,
132                    )?);
133                    (address, unsigned.action().clone(), Space::Native)
134                } else {
135                    (None, unsigned.action().clone(), Space::Native)
136                }
137            }
138            Transaction::Ethereum(ref unsigned) => {
139                if include_eth_receipt {
140                    if Action::Create == unsigned.action()
141                        && outcome_status == TransactionStatus::Success
142                    {
143                        let (created_address, _) = cal_contract_address(
144                            CreateContractAddressType::FromSenderNonce,
145                            &transaction.sender,
146                            unsigned.nonce(),
147                            unsigned.data(),
148                        );
149                        let address = Some(RpcAddress::try_from_h160(
150                            created_address,
151                            network,
152                        )?);
153                        (address, unsigned.action().clone(), Space::Ethereum)
154                    } else {
155                        (None, unsigned.action().clone(), Space::Ethereum)
156                    }
157                } else {
158                    bail!(format!("Does not support EIP-155 transaction in Conflux space RPC. get_receipt for tx: {:?}",transaction));
159                }
160            }
161        };
162
163        // this is an array, but it will only have at most one element:
164        // the storage collateral of the sender address.
165        let storage_collateralized = storage_collateralized
166            .get(0)
167            .map(|sc| sc.collaterals)
168            .map(Into::into)
169            .unwrap_or_default();
170
171        let effective_gas_price = if let Some(base_price) = maybe_base_price {
172            let base_price = base_price[transaction.space()];
173            if *transaction.gas_price() < base_price {
174                *transaction.gas_price()
175            } else {
176                transaction.effective_gas_price(&base_price)
177            }
178        } else {
179            *transaction.gas_price()
180        };
181
182        Ok(Receipt {
183            transaction_type: Some(U64::from(transaction.type_id())),
184            transaction_hash: transaction.hash.into(),
185            index: U64::from(
186                transaction_index
187                    .rpc_index
188                    // FIXME(thegaram): this is triggered on light nodes, and
189                    // maybe in some other cases as well.
190                    // is there a better way to handle this?
191                    .unwrap_or(transaction_index.real_index),
192            ),
193            block_hash: transaction_index.block_hash.into(),
194            gas_used: (accumulated_gas_used - prior_gas_used).into(),
195            accumulated_gas_used: if include_accumulated_gas_used {
196                accumulated_gas_used.into()
197            } else {
198                None
199            },
200            gas_fee: gas_fee.into(),
201            burnt_gas_fee: receipt.burnt_gas_fee,
202            effective_gas_price,
203            from: RpcAddress::try_from_h160(transaction.sender, network)?,
204            to: match &action {
205                Action::Create => None,
206                Action::Call(address) => {
207                    Some(RpcAddress::try_from_h160(address.clone(), network)?)
208                }
209            },
210            outcome_status: U64::from(outcome_status.in_space(space)),
211            contract_created: address,
212            logs: logs
213                .into_iter()
214                .filter(|l| {
215                    if include_eth_receipt {
216                        true
217                    } else {
218                        l.space == Space::Native
219                    }
220                })
221                .map(|l| Log::try_from(l, network, include_eth_receipt))
222                .collect::<Result<_, _>>()?,
223            logs_bloom: log_bloom,
224            state_root: maybe_state_root
225                .map_or_else(Default::default, Into::into),
226            epoch_number: epoch_number.map(U64::from),
227            tx_exec_error_msg,
228            gas_covered_by_sponsor: gas_sponsor_paid,
229            storage_covered_by_sponsor: storage_sponsor_paid,
230            storage_collateralized,
231            storage_released: storage_released
232                .into_iter()
233                .map(|sc| StorageChange::try_from(sc, network))
234                .collect::<Result<_, _>>()?,
235            space: if include_eth_receipt {
236                Some(space)
237            } else {
238                None
239            },
240        })
241    }
242}