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            let block_number = self
210                .get_block_number(&b.hash())?
211                .ok_or("block_number not found when recover phantom block")?;
212            let epoch_number = pivot.block_header.height();
213            let spec = self.params.spec(block_number, epoch_number);
214
215            for (id, tx) in b.transactions.iter().enumerate() {
216                match tx.space() {
217                    Space::Ethereum => {
218                        let receipt = &block_receipts[id];
219
220                        // we do not return non-executed transaction
221                        if receipt.outcome_status == TransactionStatus::Skipped
222                        {
223                            continue;
224                        }
225
226                        phantom_block.transactions.push(tx.clone());
227
228                        // sanity check: gas price must be positive
229                        if *tx.gas_price() == 0.into() {
230                            return Err("Inconsistent state: zero transaction gas price".into());
231                        }
232
233                        accumulated_gas_used =
234                            gas_used_offset + receipt.accumulated_gas_used;
235
236                        phantom_block.receipts.push(Receipt {
237                            accumulated_gas_used,
238                            outcome_status: receipt.outcome_status,
239                            ..receipt.clone()
240                        });
241
242                        phantom_block.errors.push(errors[id].clone());
243                        phantom_block.bloom.accrue_bloom(&receipt.log_bloom);
244
245                        if include_traces {
246                            phantom_block.traces.push(block_traces[id].clone());
247                        }
248                    }
249                    Space::Native => {
250                        // note: failing transactions will not produce any
251                        // phantom txs or traces
252                        if block_receipts[id].outcome_status
253                            != TransactionStatus::Success
254                        {
255                            continue;
256                        }
257
258                        // if cip90 is not enabled, there will be no phantom
259                        // txs, so we can skip the recovery process
260                        if !spec.cip90 {
261                            continue;
262                        }
263
264                        let (phantom_txs, _) = build_bloom_and_recover_phantom(
265                            &block_receipts[id].logs[..],
266                            tx.hash(),
267                        );
268
269                        if include_traces {
270                            let tx_traces = block_traces[id].clone();
271
272                            let phantom_traces =
273                                recover_phantom_traces(tx_traces, tx.hash())?;
274
275                            // sanity check: one trace for each phantom tx
276                            if phantom_txs.len() != phantom_traces.len() {
277                                error!("Inconsistent state: phantom tx and trace length mismatch, txs.len = {:?}, traces.len = {:?}", phantom_txs.len(), phantom_traces.len());
278                                return Err("Inconsistent state: phantom tx and trace length mismatch".into());
279                            }
280
281                            phantom_block.traces.extend(phantom_traces);
282                        }
283
284                        for p in phantom_txs {
285                            phantom_block.transactions.push(Arc::new(
286                                p.clone().into_eip155(evm_chain_id),
287                            ));
288
289                            // note: phantom txs consume no gas
290                            let phantom_receipt =
291                                p.into_receipt(accumulated_gas_used);
292
293                            phantom_block
294                                .bloom
295                                .accrue_bloom(&phantom_receipt.log_bloom);
296
297                            phantom_block.receipts.push(phantom_receipt);
298
299                            // note: phantom txs never fail
300                            phantom_block.errors.push("".into());
301                        }
302                    }
303                }
304            }
305        }
306
307        phantom_block.total_gas_limit = total_gas_limit;
308        Ok(Some(phantom_block))
309    }
310
311    pub fn get_phantom_block_by_hash(
312        &self, hash: &H256, include_traces: bool,
313    ) -> Result<Option<PhantomBlock>, ProviderBlockError> {
314        let epoch_num = match self.get_block_epoch_number(hash) {
315            None => return Ok(None),
316            Some(n) => n,
317        };
318
319        self.get_phantom_block_by_number(
320            EpochNumber::Number(epoch_num),
321            Some(*hash),
322            include_traces,
323        )
324    }
325}