cfxcore/consensus/consensus_graph/rpc_api/
phantom_block_provider.rs

1use cfx_execute_helper::{
2    exec_tracer::recover_phantom_traces,
3    phantom_tx::build_bloom_and_recover_phantom,
4};
5use cfx_rpc_cfx_types::PhantomBlock;
6use cfx_types::{Bloom, Space, H256, U256};
7use cfxcore_errors::ProviderBlockError;
8use primitives::{receipt::Receipt, EpochNumber, TransactionStatus};
9use std::sync::Arc;
10
11use super::super::ConsensusGraph;
12
13impl ConsensusGraph {
14    pub fn get_phantom_block_bloom_filter(
15        &self, block_num: EpochNumber, pivot_assumption: H256,
16    ) -> Result<Option<Bloom>, ProviderBlockError> {
17        let hashes = self.get_block_hashes_by_epoch(block_num)?;
18
19        // sanity check: epoch is not empty
20        let pivot = match hashes.last() {
21            Some(p) => p,
22            None => return Err("Inconsistent state: empty epoch".into()),
23        };
24
25        if *pivot != pivot_assumption {
26            return Ok(None);
27        }
28
29        // special handling for genesis block
30        let genesis_hash = self.data_manager().true_genesis.hash();
31
32        if hashes.last() == Some(&genesis_hash) {
33            return Ok(Some(Bloom::zero()));
34        }
35
36        let mut bloom = Bloom::zero();
37
38        for h in &hashes {
39            let exec_info = match self
40                .data_manager()
41                .block_execution_result_by_hash_with_epoch(
42                    h, pivot, false, // update_pivot_assumption
43                    false, // update_cache
44                ) {
45                None => return Ok(None),
46                Some(r) => r,
47            };
48
49            for receipt in exec_info.block_receipts.receipts.iter() {
50                if receipt.outcome_status == TransactionStatus::Skipped {
51                    continue;
52                }
53
54                // FIXME(thegaram): receipt does not contain `space`
55                // so we combine blooms log by log.
56                for log in &receipt.logs {
57                    if log.space == Space::Ethereum {
58                        bloom.accrue_bloom(&log.bloom());
59                    }
60                }
61            }
62        }
63
64        Ok(Some(bloom))
65    }
66
67    pub fn get_phantom_block_pivot_by_number(
68        &self, block_num: EpochNumber, pivot_assumption: Option<H256>,
69        include_traces: bool,
70    ) -> Result<Option<PhantomBlock>, ProviderBlockError> {
71        self.get_phantom_block_by_number_inner(
72            block_num,
73            pivot_assumption,
74            include_traces,
75            true,
76        )
77    }
78
79    pub fn get_phantom_block_by_number(
80        &self, block_num: EpochNumber, pivot_assumption: Option<H256>,
81        include_traces: bool,
82    ) -> Result<Option<PhantomBlock>, ProviderBlockError> {
83        self.get_phantom_block_by_number_inner(
84            block_num,
85            pivot_assumption,
86            include_traces,
87            false,
88        )
89    }
90
91    fn get_phantom_block_by_number_inner(
92        &self, block_num: EpochNumber, pivot_assumption: Option<H256>,
93        include_traces: bool, only_pivot: bool,
94    ) -> Result<Option<PhantomBlock>, ProviderBlockError> {
95        let hashes = self.get_block_hashes_by_epoch(block_num)?;
96
97        // special handling for genesis block
98        let genesis = self.data_manager().true_genesis.clone();
99
100        if hashes.last() == Some(&genesis.hash()) {
101            return Ok(Some(PhantomBlock {
102                pivot_header: genesis.block_header.clone(),
103                transactions: vec![],
104                receipts: vec![],
105                errors: vec![],
106                bloom: Bloom::zero(),
107                traces: vec![],
108                total_gas_limit: U256::from(0),
109            }));
110        }
111
112        let blocks = match self
113            .data_manager()
114            .blocks_by_hash_list(&hashes, false /* update_cache */)
115        {
116            None => return Ok(None),
117            Some(b) => b,
118        };
119
120        // sanity check: epoch is not empty
121        let pivot = match blocks.last() {
122            Some(p) => p,
123            None => return Err("Inconsistent state: empty epoch".into()),
124        };
125
126        if matches!(pivot_assumption, Some(h) if h != pivot.hash()) {
127            return Ok(None);
128        }
129
130        let mut phantom_block = PhantomBlock {
131            pivot_header: pivot.block_header.clone(),
132            transactions: vec![],
133            receipts: vec![],
134            errors: vec![],
135            bloom: Default::default(),
136            traces: vec![],
137            total_gas_limit: U256::from(0),
138        };
139
140        let mut accumulated_gas_used = U256::from(0);
141        let mut gas_used_offset;
142        let mut total_gas_limit = U256::from(0);
143
144        let iter_blocks = if only_pivot {
145            &blocks[blocks.len() - 1..]
146        } else {
147            &blocks[..]
148        };
149
150        for b in iter_blocks {
151            gas_used_offset = accumulated_gas_used;
152            // note: we need the receipts to reconstruct a phantom block.
153            // as a result, we cannot return unexecuted blocks in eth_* RPCs.
154            let exec_info = match self
155                .data_manager()
156                .block_execution_result_by_hash_with_epoch(
157                    &b.hash(),
158                    &pivot.hash(),
159                    false, // update_pivot_assumption
160                    false, // update_cache
161                ) {
162                None => return Ok(None),
163                Some(r) => r,
164            };
165
166            // note: we only include gas limit for blocks that will pack eSpace
167            // tx(multiples of 5)
168            total_gas_limit += b.block_header.espace_gas_limit(
169                self.params
170                    .can_pack_evm_transaction(b.block_header.height()),
171            );
172
173            let block_receipts = &exec_info.block_receipts.receipts;
174            let errors = &exec_info.block_receipts.tx_execution_error_messages;
175
176            let block_traces = if include_traces {
177                match self.data_manager().block_tx_traces_by_hash(&b.hash()) {
178                    None => {
179                        return Err("Error while creating phantom block: state is ready but traces not found, did you enable 'executive_trace'?".into());
180                    }
181                    Some((pivot_hash, block_traces)) => {
182                        // sanity check: transaction and trace length
183                        if b.transactions.len() != block_traces.len() {
184                            return Err("Inconsistent state: transactions and traces length mismatch".into());
185                        }
186
187                        // sanity check: no pivot reorg during processing
188                        if pivot_hash != pivot.hash() {
189                            return Err(
190                                "Inconsistent state: pivot hash mismatch"
191                                    .into(),
192                            );
193                        }
194
195                        block_traces
196                    }
197                }
198            } else {
199                vec![]
200            };
201
202            // sanity check: transaction and receipt length
203            if b.transactions.len() != block_receipts.len() {
204                return Err("Inconsistent state: transactions and receipts length mismatch".into());
205            }
206
207            let evm_chain_id = self.best_chain_id().in_evm_space();
208
209            for (id, tx) in b.transactions.iter().enumerate() {
210                match tx.space() {
211                    Space::Ethereum => {
212                        let receipt = &block_receipts[id];
213
214                        // we do not return non-executed transaction
215                        if receipt.outcome_status == TransactionStatus::Skipped
216                        {
217                            continue;
218                        }
219
220                        phantom_block.transactions.push(tx.clone());
221
222                        // sanity check: gas price must be positive
223                        if *tx.gas_price() == 0.into() {
224                            return Err("Inconsistent state: zero transaction gas price".into());
225                        }
226
227                        accumulated_gas_used =
228                            gas_used_offset + receipt.accumulated_gas_used;
229
230                        phantom_block.receipts.push(Receipt {
231                            accumulated_gas_used,
232                            outcome_status: receipt.outcome_status,
233                            ..receipt.clone()
234                        });
235
236                        phantom_block.errors.push(errors[id].clone());
237                        phantom_block.bloom.accrue_bloom(&receipt.log_bloom);
238
239                        if include_traces {
240                            phantom_block.traces.push(block_traces[id].clone());
241                        }
242                    }
243                    Space::Native => {
244                        // note: failing transactions will not produce any
245                        // phantom txs or traces
246                        if block_receipts[id].outcome_status
247                            != TransactionStatus::Success
248                        {
249                            continue;
250                        }
251
252                        let (phantom_txs, _) = build_bloom_and_recover_phantom(
253                            &block_receipts[id].logs[..],
254                            tx.hash(),
255                        );
256
257                        if include_traces {
258                            let tx_traces = block_traces[id].clone();
259
260                            let phantom_traces =
261                                recover_phantom_traces(tx_traces, tx.hash())?;
262
263                            // sanity check: one trace for each phantom tx
264                            if phantom_txs.len() != phantom_traces.len() {
265                                error!("Inconsistent state: phantom tx and trace length mismatch, txs.len = {:?}, traces.len = {:?}", phantom_txs.len(), phantom_traces.len());
266                                return Err("Inconsistent state: phantom tx and trace length mismatch".into());
267                            }
268
269                            phantom_block.traces.extend(phantom_traces);
270                        }
271
272                        for p in phantom_txs {
273                            phantom_block.transactions.push(Arc::new(
274                                p.clone().into_eip155(evm_chain_id),
275                            ));
276
277                            // note: phantom txs consume no gas
278                            let phantom_receipt =
279                                p.into_receipt(accumulated_gas_used);
280
281                            phantom_block
282                                .bloom
283                                .accrue_bloom(&phantom_receipt.log_bloom);
284
285                            phantom_block.receipts.push(phantom_receipt);
286
287                            // note: phantom txs never fail
288                            phantom_block.errors.push("".into());
289                        }
290                    }
291                }
292            }
293        }
294
295        phantom_block.total_gas_limit = total_gas_limit;
296        Ok(Some(phantom_block))
297    }
298
299    pub fn get_phantom_block_by_hash(
300        &self, hash: &H256, include_traces: bool,
301    ) -> Result<Option<PhantomBlock>, ProviderBlockError> {
302        let epoch_num = match self.get_block_epoch_number(hash) {
303            None => return Ok(None),
304            Some(n) => n,
305        };
306
307        self.get_phantom_block_by_number(
308            EpochNumber::Number(epoch_num),
309            Some(*hash),
310            include_traces,
311        )
312    }
313}