primitives/
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_entry::LogEntry;
6use cfx_types::{Address, Bloom, Space, U256, U64};
7use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
8use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream};
9use rlp_compat::StrictBool;
10use rlp_derive::{RlpDecodable, RlpEncodable};
11
12pub const TRANSACTION_OUTCOME_SUCCESS: u8 = 0;
13pub const TRANSACTION_OUTCOME_EXCEPTION_WITH_NONCE_BUMPING: u8 = 1; // gas fee charged
14pub const TRANSACTION_OUTCOME_EXCEPTION_WITHOUT_NONCE_BUMPING: u8 = 2; // no gas fee charged
15
16pub const EVM_SPACE_FAIL: u8 = 0;
17pub const EVM_SPACE_SUCCESS: u8 = 1;
18
19#[repr(u8)]
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
21pub enum TransactionStatus {
22    #[default]
23    Success = 0,
24    Failure = 1,
25    Skipped = 2,
26}
27
28impl TransactionStatus {
29    fn as_u8(&self) -> u8 {
30        match self {
31            TransactionStatus::Success => 0,
32            TransactionStatus::Failure => 1,
33            TransactionStatus::Skipped => 2,
34        }
35    }
36}
37
38impl Encodable for TransactionStatus {
39    fn rlp_append(&self, s: &mut RlpStream) {
40        s.append_internal(&self.as_u8());
41    }
42}
43
44impl Decodable for TransactionStatus {
45    fn decode(rlp: &Rlp) -> Result<Self, DecoderError> {
46        match rlp.as_val::<u8>()? {
47            0 => Ok(TransactionStatus::Success),
48            1 => Ok(TransactionStatus::Failure),
49            2 => Ok(TransactionStatus::Skipped),
50            _ => Err(DecoderError::Custom("Unrecognized outcome status")),
51        }
52    }
53}
54
55impl TransactionStatus {
56    pub fn in_space(&self, space: Space) -> u8 {
57        match (space, self) {
58            // Conflux
59            (Space::Native, TransactionStatus::Success) => {
60                TRANSACTION_OUTCOME_SUCCESS
61            }
62            (Space::Native, TransactionStatus::Failure) => {
63                TRANSACTION_OUTCOME_EXCEPTION_WITH_NONCE_BUMPING
64            }
65            (Space::Native, TransactionStatus::Skipped) => {
66                TRANSACTION_OUTCOME_EXCEPTION_WITHOUT_NONCE_BUMPING
67            }
68
69            // EVM
70            (Space::Ethereum, TransactionStatus::Success) => EVM_SPACE_SUCCESS,
71            (Space::Ethereum, TransactionStatus::Failure) => EVM_SPACE_FAIL,
72            (Space::Ethereum, TransactionStatus::Skipped) => 0xff,
73        }
74    }
75}
76
77#[derive(Debug, Clone, PartialEq, Eq, RlpDecodable, RlpEncodable)]
78pub struct StorageChange {
79    pub address: Address,
80    /// Number of storage collateral units to deposit / refund (absolute
81    /// value).
82    pub collaterals: U64,
83}
84
85#[derive(Debug, Clone, PartialEq, Eq, Default)]
86pub struct SortedStorageChanges {
87    pub storage_collateralized: Vec<StorageChange>,
88    pub storage_released: Vec<StorageChange>,
89}
90
91/// Information describing execution of a transaction.
92#[derive(Debug, Clone, PartialEq, Eq, Default)]
93pub struct Receipt {
94    /// The total gas used (not gas charged) in the block following execution
95    /// of the transaction.
96    pub accumulated_gas_used: U256,
97    /// The gas fee charged for transaction execution.
98    pub gas_fee: U256,
99    /// The designated account to bear the gas fee, if any.
100    pub gas_sponsor_paid: bool,
101    /// The OR-wide combination of all logs' blooms for this transaction.
102    pub log_bloom: Bloom,
103    /// The logs stemming from this transaction.
104    pub logs: Vec<LogEntry>,
105    /// Transaction outcome.
106    pub outcome_status: TransactionStatus,
107    /// The designated account to bear the storage fee, if any.
108    pub storage_sponsor_paid: bool,
109    pub storage_collateralized: Vec<StorageChange>,
110    pub storage_released: Vec<StorageChange>,
111    pub burnt_gas_fee: Option<U256>,
112}
113
114impl Encodable for Receipt {
115    fn rlp_append(&self, s: &mut RlpStream) {
116        let length = if self.burnt_gas_fee.is_none() { 9 } else { 10 };
117        s.begin_list(length)
118            .append(&self.accumulated_gas_used)
119            .append(&self.gas_fee)
120            .append(&StrictBool(self.gas_sponsor_paid))
121            .append(&self.log_bloom)
122            .append_list(&self.logs)
123            .append(&self.outcome_status)
124            .append(&StrictBool(self.storage_sponsor_paid))
125            .append_list(&self.storage_collateralized)
126            .append_list(&self.storage_released);
127        if let Some(burnt_gas_fee) = self.burnt_gas_fee {
128            s.append(&burnt_gas_fee);
129        }
130    }
131}
132
133impl Decodable for Receipt {
134    fn decode(rlp: &Rlp) -> Result<Self, DecoderError> {
135        let item_count = rlp.item_count()?;
136        if !matches!(item_count, 9..=10) {
137            return Err(DecoderError::RlpIncorrectListLen);
138        }
139        Ok(Receipt {
140            accumulated_gas_used: rlp.val_at(0)?,
141            gas_fee: rlp.val_at(1)?,
142            gas_sponsor_paid: rlp.val_at::<StrictBool>(2)?.0,
143            log_bloom: rlp.val_at(3)?,
144            logs: rlp.list_at(4)?,
145            outcome_status: rlp.val_at(5)?,
146            storage_sponsor_paid: rlp.val_at::<StrictBool>(6)?.0,
147            storage_collateralized: rlp.list_at(7)?,
148            storage_released: rlp.list_at(8)?,
149            burnt_gas_fee: if item_count == 9 {
150                None
151            } else {
152                Some(rlp.val_at(9)?)
153            },
154        })
155    }
156}
157
158impl Receipt {
159    #[allow(clippy::too_many_arguments)]
160    pub fn new(
161        outcome: TransactionStatus, accumulated_gas_used: U256, gas_fee: U256,
162        gas_sponsor_paid: bool, logs: Vec<LogEntry>, log_bloom: Bloom,
163        storage_sponsor_paid: bool, storage_collateralized: Vec<StorageChange>,
164        storage_released: Vec<StorageChange>, burnt_gas_fee: Option<U256>,
165    ) -> Self {
166        Self {
167            accumulated_gas_used,
168            gas_fee,
169            gas_sponsor_paid,
170            log_bloom,
171            logs,
172            outcome_status: outcome,
173            storage_sponsor_paid,
174            storage_collateralized,
175            storage_released,
176            burnt_gas_fee,
177        }
178    }
179
180    pub fn tx_skipped(&self) -> bool {
181        self.outcome_status == TransactionStatus::Skipped
182    }
183
184    pub fn tx_success(&self) -> bool {
185        self.outcome_status == TransactionStatus::Success
186    }
187
188    pub fn accumulated_gas_used(&self) -> U256 { self.accumulated_gas_used }
189
190    pub fn logs(&self) -> &[LogEntry] { &self.logs }
191}
192
193impl MallocSizeOf for StorageChange {
194    fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { 0 }
195}
196
197impl MallocSizeOf for Receipt {
198    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
199        self.logs.size_of(ops)
200            + self.storage_released.size_of(ops)
201            + self.storage_released.size_of(ops)
202    }
203}
204
205/// Information describing execution of a block.
206#[derive(Debug, Clone, PartialEq, Eq, RlpDecodable, RlpEncodable)]
207pub struct BlockReceipts {
208    /// This is the receipts of transaction execution in this block.
209    pub receipts: Vec<Receipt>,
210    // FIXME:
211    //   These fields below do not belong to receipts root calculation.
212    pub block_number: u64,
213    /// This is the amount of secondary reward this block.
214    pub secondary_reward: U256,
215    /// The error messages for each transaction. A successful transaction has
216    /// empty error_messages.
217    pub tx_execution_error_messages: Vec<String>,
218}
219
220impl MallocSizeOf for BlockReceipts {
221    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
222        self.receipts.size_of(ops)
223    }
224}
225
226#[test]
227fn test_transaction_outcome_rlp() {
228    assert_eq!(rlp::encode(&TransactionStatus::Success), rlp::encode(&0u8));
229    assert_eq!(rlp::encode(&TransactionStatus::Failure), rlp::encode(&1u8));
230    assert_eq!(rlp::encode(&TransactionStatus::Skipped), rlp::encode(&2u8));
231}
232
233#[test]
234fn test_receipt_rlp_serde() {
235    let mut receipt = Receipt {
236        accumulated_gas_used: 189000.into(),
237        gas_fee: 60054.into(),
238        burnt_gas_fee: Some(30027.into()),
239        ..Default::default()
240    };
241    assert_eq!(receipt, Rlp::new(&receipt.rlp_bytes()).as_val().unwrap());
242
243    receipt.burnt_gas_fee = None;
244    assert_eq!(receipt, Rlp::new(&receipt.rlp_bytes()).as_val().unwrap());
245}