cfxcore/consensus/consensus_inner/consensus_executor/
epoch_execution.rs

1use super::ConsensusExecutionHandler;
2use std::{collections::BTreeSet, convert::From, sync::Arc};
3
4use alloy_rpc_types_trace::geth::GethDebugTracingOptions;
5use cfx_parameters::genesis::GENESIS_ACCOUNT_ADDRESS;
6use geth_tracer::{GethTraceWithHash, GethTracer, TxExecContext};
7use pow_types::StakingEvent;
8
9use cfx_statedb::{Error as DbErrorKind, Result as DbResult};
10use cfx_types::{AddressSpaceUtil, Space, SpaceMap, H256, U256};
11use primitives::{
12    receipt::BlockReceipts, AccessListItem, Action, Block, BlockNumber,
13    Receipt, SignedTransaction, TransactionIndex,
14};
15
16use crate::{
17    block_data_manager::BlockDataManager,
18    consensus::consensus_inner::consensus_executor::GOOD_TPS_METER,
19};
20use cfx_execute_helper::{
21    exec_tracer::TransactionExecTraces,
22    observer::Observer,
23    tx_outcome::{make_process_tx_outcome, ProcessTxOutcome},
24};
25use cfx_executor::{
26    executive::{ExecutiveContext, TransactOptions, TransactSettings},
27    internal_contract::{
28        block_hash_slot, epoch_hash_slot, initialize_internal_contract_accounts,
29    },
30    state::{
31        initialize_cip107, initialize_cip137,
32        initialize_or_update_dao_voted_params, State,
33    },
34};
35use cfx_vm_types::Env;
36
37pub enum VirtualCall<'a> {
38    GethTrace(GethTask<'a>),
39}
40
41pub struct GethTask<'a> {
42    pub(super) tx_hash: Option<H256>,
43    pub(super) opts: GethDebugTracingOptions,
44    pub(super) answer: &'a mut Vec<GethTraceWithHash>,
45}
46
47impl ConsensusExecutionHandler {
48    pub(super) fn process_epoch_transactions<'a>(
49        &self, state: &mut State, epoch_blocks: &Vec<Arc<Block>>,
50        start_block_number: u64, on_local_pivot: bool,
51        virtual_call: Option<VirtualCall<'a>>,
52    ) -> DbResult<Vec<Arc<BlockReceipts>>> {
53        self.prefetch_storage_for_execution(state, epoch_blocks);
54
55        let pivot_block = epoch_blocks.last().expect("Epoch not empty");
56
57        let dry_run = virtual_call.is_some();
58
59        self.before_epoch_execution(state, &*pivot_block)?;
60
61        let base_gas_price =
62            pivot_block.block_header.base_price().unwrap_or_default();
63
64        let burnt_gas_price =
65            base_gas_price.map_all(|x| state.burnt_gas_price(x));
66        let context = EpochProcessContext {
67            on_local_pivot,
68            executive_trace: self.config.executive_trace,
69            dry_run,
70            virtual_call,
71            pivot_block,
72            base_gas_price,
73            burnt_gas_price,
74        };
75
76        let mut epoch_recorder = EpochProcessRecorder::new();
77
78        let mut block_context = BlockProcessContext::first_block(
79            &context,
80            epoch_blocks.first().unwrap(),
81            start_block_number,
82        );
83
84        for (idx, block) in epoch_blocks.iter().enumerate() {
85            if idx > 0 {
86                block_context.next_block(block);
87            }
88
89            self.process_block_transactions(
90                &block_context,
91                state,
92                &mut epoch_recorder,
93            )?;
94        }
95
96        if let Some(VirtualCall::GethTrace(task)) = context.virtual_call {
97            std::mem::swap(&mut epoch_recorder.geth_traces, task.answer);
98        }
99
100        // Persist staking events only from canonical (local-pivot) execution,
101        // so every saved row can be trusted as canonical on read.
102        if !dry_run
103            && on_local_pivot
104            && self.pos_verifier.pos_option().is_some()
105        {
106            debug!(
107                "put_staking_events: {:?} height={} len={}",
108                pivot_block.hash(),
109                pivot_block.block_header.height(),
110                epoch_recorder.staking_events.len()
111            );
112            self.pos_verifier
113                .consensus_db()
114                .put_staking_events(
115                    pivot_block.block_header.height(),
116                    pivot_block.hash(),
117                    epoch_recorder.staking_events,
118                )
119                .map_err(|e| {
120                    cfx_statedb::Error::from(DbErrorKind::PosDatabaseError(
121                        format!("{:?}", e),
122                    ))
123                })?;
124        }
125
126        if !dry_run && on_local_pivot {
127            self.tx_pool.recycle_transactions(epoch_recorder.repack_tx);
128        }
129
130        debug!("Finish processing tx for epoch");
131        Ok(epoch_recorder.receipts)
132    }
133
134    fn prefetch_storage_for_execution(
135        &self, state: &mut State, epoch_blocks: &Vec<Arc<Block>>,
136    ) {
137        // Prefetch accounts for transactions.
138        // The return value _prefetch_join_handles is used to join all threads
139        // before the exit of this function.
140        let pool = if let Some(prefetcher) =
141            self.execution_state_prefetcher.as_ref()
142        {
143            prefetcher
144        } else {
145            return;
146        };
147
148        let mut accounts = BTreeSet::new();
149        for block in epoch_blocks.iter() {
150            for transaction in block.transactions.iter() {
151                let space = transaction.space();
152                accounts.insert(transaction.sender.with_space(space));
153                if let Action::Call(ref address) = transaction.action() {
154                    accounts.insert(address.with_space(space));
155                }
156                if let Some(access_list) = transaction.access_list() {
157                    for AccessListItem { address, .. } in access_list.iter() {
158                        accounts.insert(address.with_space(space));
159                    }
160                }
161            }
162        }
163        // Due to an existing bug and special handling of the genesis account,
164        // it can not be prefetched.
165        accounts.remove(&GENESIS_ACCOUNT_ADDRESS.with_native_space());
166        let res = state.prefetch_accounts(accounts, pool);
167        if let Err(e) = res {
168            warn!("Fail to prefetch account {:?}", e);
169        }
170    }
171
172    fn make_block_env(&self, block_context: &BlockProcessContext) -> Env {
173        let BlockProcessContext {
174            epoch_context:
175                &EpochProcessContext {
176                    pivot_block,
177                    base_gas_price,
178                    burnt_gas_price,
179                    ..
180                },
181            block,
182            block_number,
183            last_hash,
184        } = *block_context;
185
186        let last_block_header = &self.data_man.block_header_by_hash(&last_hash);
187
188        let pos_id = last_block_header
189            .as_ref()
190            .and_then(|header| header.pos_reference().as_ref());
191        let pos_view_number =
192            pos_id.and_then(|id| self.pos_verifier.get_pos_view(id));
193        let pivot_decision_epoch = pos_id
194            .and_then(|id| self.pos_verifier.get_pivot_decision(id))
195            .and_then(|hash| self.data_man.block_header_by_hash(&hash))
196            .map(|header| header.height());
197
198        let epoch_height = pivot_block.block_header.height();
199        let chain_id = self.machine.params().chain_id_map(epoch_height);
200        Env {
201            chain_id,
202            number: block_number,
203            author: block.block_header.author().clone(),
204            timestamp: pivot_block.block_header.timestamp(),
205            difficulty: block.block_header.difficulty().clone(),
206            accumulated_gas_used: U256::zero(),
207            last_hash,
208            gas_limit: U256::from(block.block_header.gas_limit()),
209            epoch_height,
210            pos_view: pos_view_number,
211            finalized_epoch: pivot_decision_epoch,
212            transaction_epoch_bound: self
213                .verification_config
214                .transaction_epoch_bound,
215            base_gas_price,
216            burnt_gas_price,
217            // Temporarily set `transaction_hash` to zero; it will be updated
218            // with the actual transaction hash for each transaction.
219            transaction_hash: H256::zero(),
220            ..Default::default()
221        }
222    }
223
224    fn process_block_transactions(
225        &self, block_context: &BlockProcessContext, state: &mut State,
226        epoch_recorder: &mut EpochProcessRecorder,
227    ) -> DbResult<()> {
228        let BlockProcessContext {
229            epoch_context: &EpochProcessContext { on_local_pivot, .. },
230            block,
231            block_number,
232            ..
233        } = *block_context;
234
235        debug!(
236            "process txs in block: hash={:?}, tx count={:?}",
237            block.hash(),
238            block.transactions.len()
239        );
240
241        // TODO: ideally, this function should not have return value.
242        // However, the previous implementation read `secondary_reward` in an
243        // intermediate step. Since we are not sure which steps will influnce
244        // `secondary_reward`, we must `secondary_reward` at the same point to
245        // keep the backward compatible.
246        let secondary_reward =
247            self.before_block_execution(state, block_number, block)?;
248
249        let mut env = self.make_block_env(block_context);
250
251        let mut block_recorder =
252            BlockProcessRecorder::new(epoch_recorder.evm_tx_idx);
253
254        for (idx, transaction) in block.transactions.iter().enumerate() {
255            self.process_transaction(
256                idx,
257                transaction,
258                block_context,
259                state,
260                &mut env,
261                on_local_pivot,
262                &mut block_recorder,
263            )?;
264        }
265
266        block_recorder.finish_block(
267            &self.data_man,
268            epoch_recorder,
269            block_context,
270            secondary_reward,
271        );
272
273        Ok(())
274    }
275
276    fn process_transaction(
277        &self, idx: usize, transaction: &Arc<SignedTransaction>,
278        block_context: &BlockProcessContext, state: &mut State, env: &mut Env,
279        on_local_pivot: bool, recorder: &mut BlockProcessRecorder,
280    ) -> DbResult<()> {
281        let rpc_index = recorder.tx_idx[transaction.space()];
282
283        let block = &block_context.block;
284        let dry_run = block_context.epoch_context.dry_run;
285
286        let machine = self.machine.as_ref();
287
288        let spec = machine.spec(env.number, env.epoch_height);
289
290        let options = TransactOptions {
291            observer: self.make_observer(transaction, block_context),
292            settings: TransactSettings::all_checks(),
293        };
294
295        env.transaction_hash = transaction.hash();
296        let execution_outcome =
297            ExecutiveContext::new(state, env, machine, &spec)
298                .transact(transaction, options)?;
299        state.update_state_post_tx_execution(!spec.cip645.fix_eip1153);
300        execution_outcome.log(transaction, &block_context.block.hash());
301
302        if let Some(burnt_fee) = execution_outcome
303            .try_as_executed()
304            .and_then(|e| e.burnt_fee)
305        {
306            state.burn_by_cip1559(burnt_fee);
307        };
308
309        let r = make_process_tx_outcome(
310            execution_outcome,
311            &mut env.accumulated_gas_used,
312            transaction.hash,
313            &spec,
314        );
315
316        if r.receipt.tx_success() {
317            GOOD_TPS_METER.mark(1);
318        }
319
320        let tx_skipped = r.receipt.tx_skipped();
321        let phantom_txs = r.phantom_txs.clone();
322
323        recorder.receive_tx_outcome(r, transaction, block_context);
324
325        if !on_local_pivot || tx_skipped || dry_run {
326            // Skip transaction index persist
327            return Ok(());
328        }
329
330        let hash = transaction.hash();
331
332        self.data_man.insert_transaction_index(
333            &hash,
334            &TransactionIndex {
335                block_hash: block.hash(),
336                real_index: idx,
337                is_phantom: false,
338                rpc_index: Some(rpc_index),
339            },
340        );
341
342        // persist tx index for phantom transactions.
343        // note: in some cases, pivot chain reorgs will result in
344        // different phantom txs (with different hashes) for the
345        // same Conflux space tx. we do not remove invalidated
346        // hashes here, but leave it up to the RPC layer to handle
347        // this instead.
348        let evm_chain_id = env.chain_id[&Space::Ethereum];
349        let evm_tx_index = &mut recorder.tx_idx[Space::Ethereum];
350
351        for ptx in phantom_txs {
352            self.data_man.insert_transaction_index(
353                &ptx.into_eip155(evm_chain_id).hash(),
354                &TransactionIndex {
355                    block_hash: block.hash(),
356                    real_index: idx,
357                    is_phantom: true,
358                    rpc_index: Some(*evm_tx_index),
359                },
360            );
361
362            *evm_tx_index += 1;
363        }
364
365        Ok(())
366    }
367
368    fn make_observer(
369        &self, transaction: &Arc<SignedTransaction>,
370        block_context: &BlockProcessContext,
371    ) -> Observer {
372        use alloy_rpc_types_trace::geth::{
373            GethDebugBuiltInTracerType::*, GethDebugTracerType::BuiltInTracer,
374        };
375
376        let mut observer = if self.config.executive_trace {
377            Observer::with_tracing()
378        } else {
379            Observer::with_no_tracing()
380        };
381
382        if let Some(VirtualCall::GethTrace(ref task)) =
383            block_context.epoch_context.virtual_call
384        {
385            let need_trace =
386                task.tx_hash.map_or(true, |hash| transaction.hash() == hash);
387            let support_tracer = matches!(
388                task.opts.tracer,
389                Some(BuiltInTracer(
390                    FourByteTracer | CallTracer | PreStateTracer | NoopTracer
391                )) | None
392            );
393            let tx_gas_limit = transaction.gas_limit().as_u64();
394
395            if need_trace && support_tracer {
396                observer.geth_tracer = Some(GethTracer::new(
397                    TxExecContext {
398                        tx_gas_limit,
399                        block_height: block_context
400                            .epoch_context
401                            .pivot_block
402                            .block_header
403                            .height(),
404                        block_number: block_context.block_number,
405                    },
406                    Arc::clone(&self.machine),
407                    task.opts.clone(),
408                ))
409            }
410        }
411        observer
412    }
413
414    fn before_epoch_execution(
415        &self, state: &mut State, pivot_block: &Block,
416    ) -> DbResult<()> {
417        let params = self.machine.params();
418
419        let epoch_number = pivot_block.block_header.height();
420        let hash = pivot_block.hash();
421        let parent_hash = pivot_block.block_header.parent_hash();
422
423        if epoch_number >= params.transition_heights.cip133e {
424            state.set_system_storage(
425                epoch_hash_slot(epoch_number).into(),
426                U256::from_big_endian(&hash.0),
427            )?;
428        }
429
430        if epoch_number >= params.transition_heights.eip2935 {
431            state.set_eip2935_storage(epoch_number - 1, *parent_hash)?;
432        }
433        Ok(())
434    }
435
436    pub fn before_block_execution(
437        &self, state: &mut State, block_number: BlockNumber, block: &Block,
438    ) -> DbResult<U256> {
439        let params = self.machine.params();
440        let transition_numbers = &params.transition_numbers;
441
442        let cip94_start = transition_numbers.cip94n;
443        let period = params.params_dao_vote_period;
444        // Update/initialize parameters before processing rewards.
445        if block_number >= cip94_start
446            && (block_number - cip94_start) % period == 0
447        {
448            let set_pos_staking = block_number > transition_numbers.cip105;
449            initialize_or_update_dao_voted_params(state, set_pos_staking)?;
450        }
451
452        // Initialize old_storage_point_prop_ratio in the state.
453        // The time may not be in the vote period boundary, so this is not
454        // integrated with `initialize_or_update_dao_voted_params`, but
455        // that function will update the value after cip107 is enabled
456        // here.
457        if block_number == transition_numbers.cip107 {
458            initialize_cip107(state)?;
459        }
460
461        if block_number >= transition_numbers.cip133b {
462            state.set_system_storage(
463                block_hash_slot(block_number).into(),
464                U256::from_big_endian(&block.hash().0),
465            )?;
466        }
467
468        if block_number == transition_numbers.cip137 {
469            initialize_cip137(state);
470        }
471
472        if block_number < transition_numbers.cip43a {
473            state.bump_block_number_accumulate_interest();
474        }
475
476        let secondary_reward = state.secondary_reward();
477
478        state.inc_distributable_pos_interest(block_number)?;
479
480        initialize_internal_contract_accounts(
481            state,
482            self.machine
483                .internal_contracts()
484                .initialized_at(block_number),
485        )?;
486
487        state.commit_cache(false);
488
489        Ok(secondary_reward)
490    }
491}
492
493struct EpochProcessContext<'a> {
494    on_local_pivot: bool,
495    executive_trace: bool,
496    virtual_call: Option<VirtualCall<'a>>,
497    dry_run: bool,
498
499    pivot_block: &'a Block,
500
501    base_gas_price: SpaceMap<U256>,
502    burnt_gas_price: SpaceMap<U256>,
503}
504
505struct BlockProcessContext<'a, 'b> {
506    epoch_context: &'b EpochProcessContext<'a>,
507    block: &'b Block,
508    block_number: u64,
509    last_hash: H256,
510}
511
512impl<'a, 'b> BlockProcessContext<'a, 'b> {
513    fn first_block(
514        epoch_context: &'b EpochProcessContext<'a>, block: &'b Block,
515        start_block_number: u64,
516    ) -> Self {
517        let EpochProcessContext { pivot_block, .. } = *epoch_context;
518        let last_hash = *pivot_block.block_header.parent_hash();
519        Self {
520            epoch_context,
521            block,
522            block_number: start_block_number,
523            last_hash,
524        }
525    }
526
527    fn next_block(&mut self, block: &'b Block) {
528        self.last_hash = self.block.hash();
529        self.block_number += 1;
530        self.block = block;
531    }
532}
533
534#[derive(Default)]
535struct EpochProcessRecorder {
536    receipts: Vec<Arc<BlockReceipts>>,
537    staking_events: Vec<StakingEvent>,
538    repack_tx: Vec<Arc<SignedTransaction>>,
539    geth_traces: Vec<GethTraceWithHash>,
540
541    evm_tx_idx: usize,
542}
543
544impl EpochProcessRecorder {
545    fn new() -> Self { Default::default() }
546}
547
548struct BlockProcessRecorder {
549    receipt: Vec<Receipt>,
550    tx_error_msg: Vec<String>,
551    traces: Vec<TransactionExecTraces>,
552    geth_traces: Vec<GethTraceWithHash>,
553    repack_tx: Vec<Arc<SignedTransaction>>,
554    staking_events: Vec<StakingEvent>,
555
556    tx_idx: SpaceMap<usize>,
557}
558
559impl BlockProcessRecorder {
560    fn new(evm_tx_idx: usize) -> BlockProcessRecorder {
561        let mut tx_idx = SpaceMap::default();
562        tx_idx[Space::Ethereum] = evm_tx_idx;
563        Self {
564            receipt: vec![],
565            tx_error_msg: vec![],
566            traces: vec![],
567            geth_traces: vec![],
568            repack_tx: vec![],
569            staking_events: vec![],
570            tx_idx,
571        }
572    }
573
574    fn receive_tx_outcome(
575        &mut self, r: ProcessTxOutcome, tx: &Arc<SignedTransaction>,
576        block_context: &BlockProcessContext,
577    ) {
578        let EpochProcessContext {
579            on_local_pivot,
580            executive_trace,
581            ..
582        } = *block_context.epoch_context;
583
584        if on_local_pivot && r.consider_repacked {
585            self.repack_tx.push(tx.clone())
586        }
587
588        let not_skipped = !r.receipt.tx_skipped();
589
590        if executive_trace {
591            self.traces.push(r.tx_traces.into());
592        }
593
594        self.receipt.push(r.receipt);
595        self.tx_error_msg.push(r.tx_exec_error_msg);
596        self.staking_events.extend(r.tx_staking_events);
597
598        if let Some(trace) = r.geth_trace {
599            self.geth_traces.push(GethTraceWithHash {
600                trace,
601                tx_hash: tx.hash(),
602                space: tx.space(),
603            });
604        }
605
606        match tx.space() {
607            Space::Native => {
608                self.tx_idx[Space::Native] += 1;
609            }
610            Space::Ethereum if not_skipped => {
611                self.tx_idx[Space::Ethereum] += 1;
612            }
613            _ => {}
614        };
615    }
616
617    fn finish_block(
618        self, data_man: &BlockDataManager,
619        epoch_recorder: &mut EpochProcessRecorder,
620        block_context: &BlockProcessContext, secondary_reward: U256,
621    ) {
622        let BlockProcessContext {
623            epoch_context:
624                &EpochProcessContext {
625                    on_local_pivot,
626                    executive_trace,
627                    pivot_block,
628                    dry_run,
629                    ..
630                },
631            block,
632            block_number,
633            ..
634        } = *block_context;
635
636        let block_receipts = Arc::new(BlockReceipts {
637            receipts: self.receipt,
638            // An existing bug makes the block_number is one larger than the
639            // actual.
640            block_number: block_number + 1,
641            secondary_reward,
642            tx_execution_error_messages: self.tx_error_msg,
643        });
644
645        epoch_recorder.receipts.push(block_receipts.clone());
646        epoch_recorder.staking_events.extend(self.staking_events);
647        epoch_recorder.repack_tx.extend(self.repack_tx);
648        epoch_recorder.geth_traces.extend(self.geth_traces);
649
650        epoch_recorder.evm_tx_idx = self.tx_idx[Space::Ethereum];
651
652        if dry_run {
653            return;
654        }
655
656        if executive_trace {
657            data_man.insert_block_traces(
658                block.hash(),
659                self.traces.into(),
660                pivot_block.hash(),
661                on_local_pivot,
662            );
663        }
664
665        data_man.insert_block_execution_result(
666            block.hash(),
667            pivot_block.hash(),
668            block_receipts.clone(),
669            on_local_pivot,
670        );
671    }
672}