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