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, BlockHeader,
13    BlockNumber, 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    pub(super) fn safety_guard(&self, pivot_block_header: &BlockHeader) {
135        fn safety_guard_is_active(
136            block_height: u64, hn_fix_height: u64,
137        ) -> bool {
138            // Roughly two months of block heights. The upgrade cycle is about
139            // one month, so this window covers the rollout period
140            // without applying the temporary guard too far into
141            // history. The guard thresholds were also checked against
142            // the latest year of historical data and did not trigger.
143            const SAFETY_GUARD_HEIGHT_WINDOW: u64 = 4_000_000;
144
145            let start_height =
146                hn_fix_height.saturating_sub(SAFETY_GUARD_HEIGHT_WINDOW);
147            block_height >= start_height && block_height < hn_fix_height
148        }
149        if !safety_guard_is_active(
150            pivot_block_header.height(),
151            self.machine.params().transition_heights.cip_hn_fix,
152        ) {
153            return;
154        }
155
156        if let Some(max_difficulty) = self.machine.params().max_difficulty_guard
157        {
158            assert!(
159                pivot_block_header.difficulty() < &max_difficulty,
160                "too large difficulty"
161            )
162        }
163
164        let pos_id = pivot_block_header.pos_reference().as_ref();
165        let pivot_decision_epoch = pos_id
166            .and_then(|id| self.pos_verifier.get_pivot_decision(id))
167            .and_then(|hash| self.data_man.block_header_by_hash(&hash))
168            .map(|header| header.height());
169
170        if let (Some(finalized_epoch), Some(confirm_offset)) = (
171            pivot_decision_epoch,
172            self.machine.params().max_finalize_confirmation_guard,
173        ) {
174            if pivot_block_header
175                .height()
176                .checked_sub(finalized_epoch)
177                .is_some_and(|x| x > confirm_offset)
178            {
179                panic!("Block finalization is larger than expected time range");
180            }
181        }
182    }
183
184    fn prefetch_storage_for_execution(
185        &self, state: &mut State, epoch_blocks: &Vec<Arc<Block>>,
186    ) {
187        // Prefetch accounts for transactions.
188        // The return value _prefetch_join_handles is used to join all threads
189        // before the exit of this function.
190        let pool = if let Some(prefetcher) =
191            self.execution_state_prefetcher.as_ref()
192        {
193            prefetcher
194        } else {
195            return;
196        };
197
198        let mut accounts = BTreeSet::new();
199        for block in epoch_blocks.iter() {
200            for transaction in block.transactions.iter() {
201                let space = transaction.space();
202                accounts.insert(transaction.sender.with_space(space));
203                if let Action::Call(ref address) = transaction.action() {
204                    accounts.insert(address.with_space(space));
205                }
206                if let Some(access_list) = transaction.access_list() {
207                    for AccessListItem { address, .. } in access_list.iter() {
208                        accounts.insert(address.with_space(space));
209                    }
210                }
211            }
212        }
213        // Due to an existing bug and special handling of the genesis account,
214        // it can not be prefetched.
215        accounts.remove(&GENESIS_ACCOUNT_ADDRESS.with_native_space());
216        let res = state.prefetch_accounts(accounts, pool);
217        if let Err(e) = res {
218            warn!("Fail to prefetch account {:?}", e);
219        }
220    }
221
222    fn make_block_env(&self, block_context: &BlockProcessContext) -> Env {
223        let BlockProcessContext {
224            epoch_context:
225                &EpochProcessContext {
226                    pivot_block,
227                    base_gas_price,
228                    burnt_gas_price,
229                    ..
230                },
231            block,
232            block_number,
233            last_hash,
234        } = *block_context;
235
236        let last_block_header = &self.data_man.block_header_by_hash(&last_hash);
237
238        let pos_id = last_block_header
239            .as_ref()
240            .and_then(|header| header.pos_reference().as_ref());
241        let pos_view_number =
242            pos_id.and_then(|id| self.pos_verifier.get_pos_view(id));
243        let pivot_decision_epoch = pos_id
244            .and_then(|id| self.pos_verifier.get_pivot_decision(id))
245            .and_then(|hash| self.data_man.block_header_by_hash(&hash))
246            .map(|header| header.height());
247
248        let epoch_height = pivot_block.block_header.height();
249        let chain_id = self.machine.params().chain_id_map(epoch_height);
250        Env {
251            chain_id,
252            number: block_number,
253            author: block.block_header.author().clone(),
254            timestamp: pivot_block.block_header.timestamp(),
255            difficulty: block.block_header.difficulty().clone(),
256            accumulated_gas_used: U256::zero(),
257            last_hash,
258            gas_limit: U256::from(block.block_header.gas_limit()),
259            epoch_height,
260            pos_view: pos_view_number,
261            finalized_epoch: pivot_decision_epoch,
262            transaction_epoch_bound: self
263                .verification_config
264                .transaction_epoch_bound,
265            base_gas_price,
266            burnt_gas_price,
267            // Temporarily set `transaction_hash` to zero; it will be updated
268            // with the actual transaction hash for each transaction.
269            transaction_hash: H256::zero(),
270            ..Default::default()
271        }
272    }
273
274    fn process_block_transactions(
275        &self, block_context: &BlockProcessContext, state: &mut State,
276        epoch_recorder: &mut EpochProcessRecorder,
277    ) -> DbResult<()> {
278        let BlockProcessContext {
279            epoch_context: &EpochProcessContext { on_local_pivot, .. },
280            block,
281            block_number,
282            ..
283        } = *block_context;
284
285        debug!(
286            "process txs in block: hash={:?}, tx count={:?}",
287            block.hash(),
288            block.transactions.len()
289        );
290
291        // TODO: ideally, this function should not have return value.
292        // However, the previous implementation read `secondary_reward` in an
293        // intermediate step. Since we are not sure which steps will influnce
294        // `secondary_reward`, we must `secondary_reward` at the same point to
295        // keep the backward compatible.
296        let secondary_reward =
297            self.before_block_execution(state, block_number, block)?;
298
299        let mut env = self.make_block_env(block_context);
300
301        let mut block_recorder =
302            BlockProcessRecorder::new(epoch_recorder.evm_tx_idx);
303
304        for (idx, transaction) in block.transactions.iter().enumerate() {
305            self.process_transaction(
306                idx,
307                transaction,
308                block_context,
309                state,
310                &mut env,
311                on_local_pivot,
312                &mut block_recorder,
313            )?;
314        }
315
316        block_recorder.finish_block(
317            &self.data_man,
318            epoch_recorder,
319            block_context,
320            secondary_reward,
321        );
322
323        Ok(())
324    }
325
326    fn process_transaction(
327        &self, idx: usize, transaction: &Arc<SignedTransaction>,
328        block_context: &BlockProcessContext, state: &mut State, env: &mut Env,
329        on_local_pivot: bool, recorder: &mut BlockProcessRecorder,
330    ) -> DbResult<()> {
331        let rpc_index = recorder.tx_idx[transaction.space()];
332
333        let block = &block_context.block;
334        let dry_run = block_context.epoch_context.dry_run;
335
336        let machine = self.machine.as_ref();
337
338        let spec = machine.spec(env.number, env.epoch_height);
339
340        let options = TransactOptions {
341            observer: self.make_observer(transaction, block_context),
342            settings: TransactSettings::all_checks(),
343        };
344
345        env.transaction_hash = transaction.hash();
346        let execution_outcome =
347            ExecutiveContext::new(state, env, machine, &spec)
348                .transact(transaction, options)?;
349        state.update_state_post_tx_execution(!spec.cip645.fix_eip1153);
350        execution_outcome.log(transaction, &block_context.block.hash());
351
352        if let Some(burnt_fee) = execution_outcome
353            .try_as_executed()
354            .and_then(|e| e.burnt_fee)
355        {
356            state.burn_by_cip1559(burnt_fee);
357        };
358
359        let r = make_process_tx_outcome(
360            execution_outcome,
361            &mut env.accumulated_gas_used,
362            transaction.hash,
363            &spec,
364        );
365
366        if r.receipt.tx_success() {
367            GOOD_TPS_METER.mark(1);
368        }
369
370        let tx_skipped = r.receipt.tx_skipped();
371        let phantom_txs = r.phantom_txs.clone();
372
373        recorder.receive_tx_outcome(r, transaction, block_context);
374
375        if !on_local_pivot || tx_skipped || dry_run {
376            // Skip transaction index persist
377            return Ok(());
378        }
379
380        let hash = transaction.hash();
381
382        self.data_man.insert_transaction_index(
383            &hash,
384            &TransactionIndex {
385                block_hash: block.hash(),
386                real_index: idx,
387                is_phantom: false,
388                rpc_index: Some(rpc_index),
389            },
390        );
391
392        // persist tx index for phantom transactions.
393        // note: in some cases, pivot chain reorgs will result in
394        // different phantom txs (with different hashes) for the
395        // same Conflux space tx. we do not remove invalidated
396        // hashes here, but leave it up to the RPC layer to handle
397        // this instead.
398        let evm_chain_id = env.chain_id[&Space::Ethereum];
399        let evm_tx_index = &mut recorder.tx_idx[Space::Ethereum];
400
401        for ptx in phantom_txs {
402            self.data_man.insert_transaction_index(
403                &ptx.into_eip155(evm_chain_id).hash(),
404                &TransactionIndex {
405                    block_hash: block.hash(),
406                    real_index: idx,
407                    is_phantom: true,
408                    rpc_index: Some(*evm_tx_index),
409                },
410            );
411
412            *evm_tx_index += 1;
413        }
414
415        Ok(())
416    }
417
418    fn make_observer(
419        &self, transaction: &Arc<SignedTransaction>,
420        block_context: &BlockProcessContext,
421    ) -> Observer {
422        use alloy_rpc_types_trace::geth::{
423            GethDebugBuiltInTracerType::*, GethDebugTracerType::BuiltInTracer,
424        };
425
426        let mut observer = if self.config.executive_trace {
427            Observer::with_tracing()
428        } else {
429            Observer::with_no_tracing()
430        };
431
432        if let Some(VirtualCall::GethTrace(ref task)) =
433            block_context.epoch_context.virtual_call
434        {
435            let need_trace =
436                task.tx_hash.map_or(true, |hash| transaction.hash() == hash);
437            let support_tracer = matches!(
438                task.opts.tracer,
439                Some(BuiltInTracer(
440                    FourByteTracer | CallTracer | PreStateTracer | NoopTracer
441                )) | None
442            );
443            let tx_gas_limit = transaction.gas_limit().as_u64();
444
445            if need_trace && support_tracer {
446                observer.geth_tracer = Some(GethTracer::new(
447                    TxExecContext {
448                        tx_gas_limit,
449                        block_height: block_context
450                            .epoch_context
451                            .pivot_block
452                            .block_header
453                            .height(),
454                        block_number: block_context.block_number,
455                    },
456                    Arc::clone(&self.machine),
457                    task.opts.clone(),
458                ))
459            }
460        }
461        observer
462    }
463
464    fn before_epoch_execution(
465        &self, state: &mut State, pivot_block: &Block,
466    ) -> DbResult<()> {
467        let params = self.machine.params();
468
469        let epoch_number = pivot_block.block_header.height();
470        let hash = pivot_block.hash();
471        let parent_hash = pivot_block.block_header.parent_hash();
472
473        if epoch_number >= params.transition_heights.cip133e {
474            state.set_system_storage(
475                epoch_hash_slot(epoch_number).into(),
476                U256::from_big_endian(&hash.0),
477            )?;
478        }
479
480        if epoch_number >= params.transition_heights.eip2935 {
481            state.set_eip2935_storage(epoch_number - 1, *parent_hash)?;
482        }
483        Ok(())
484    }
485
486    pub fn before_block_execution(
487        &self, state: &mut State, block_number: BlockNumber, block: &Block,
488    ) -> DbResult<U256> {
489        let params = self.machine.params();
490        let transition_numbers = &params.transition_numbers;
491
492        let cip94_start = transition_numbers.cip94n;
493        let period = params.params_dao_vote_period;
494        // Update/initialize parameters before processing rewards.
495        if block_number >= cip94_start
496            && (block_number - cip94_start) % period == 0
497        {
498            let set_pos_staking = block_number > transition_numbers.cip105;
499            initialize_or_update_dao_voted_params(state, set_pos_staking)?;
500        }
501
502        // Initialize old_storage_point_prop_ratio in the state.
503        // The time may not be in the vote period boundary, so this is not
504        // integrated with `initialize_or_update_dao_voted_params`, but
505        // that function will update the value after cip107 is enabled
506        // here.
507        if block_number == transition_numbers.cip107 {
508            initialize_cip107(state)?;
509        }
510
511        if block_number >= transition_numbers.cip133b {
512            state.set_system_storage(
513                block_hash_slot(block_number).into(),
514                U256::from_big_endian(&block.hash().0),
515            )?;
516        }
517
518        if block_number == transition_numbers.cip137 {
519            initialize_cip137(state);
520        }
521
522        if block_number < transition_numbers.cip43a {
523            state.bump_block_number_accumulate_interest();
524        }
525
526        let secondary_reward = state.secondary_reward();
527
528        state.inc_distributable_pos_interest(block_number)?;
529
530        initialize_internal_contract_accounts(
531            state,
532            self.machine
533                .internal_contracts()
534                .initialized_at(block_number),
535        )?;
536
537        state.commit_cache(false);
538
539        Ok(secondary_reward)
540    }
541}
542
543struct EpochProcessContext<'a> {
544    on_local_pivot: bool,
545    executive_trace: bool,
546    virtual_call: Option<VirtualCall<'a>>,
547    dry_run: bool,
548
549    pivot_block: &'a Block,
550
551    base_gas_price: SpaceMap<U256>,
552    burnt_gas_price: SpaceMap<U256>,
553}
554
555struct BlockProcessContext<'a, 'b> {
556    epoch_context: &'b EpochProcessContext<'a>,
557    block: &'b Block,
558    block_number: u64,
559    last_hash: H256,
560}
561
562impl<'a, 'b> BlockProcessContext<'a, 'b> {
563    fn first_block(
564        epoch_context: &'b EpochProcessContext<'a>, block: &'b Block,
565        start_block_number: u64,
566    ) -> Self {
567        let EpochProcessContext { pivot_block, .. } = *epoch_context;
568        let last_hash = *pivot_block.block_header.parent_hash();
569        Self {
570            epoch_context,
571            block,
572            block_number: start_block_number,
573            last_hash,
574        }
575    }
576
577    fn next_block(&mut self, block: &'b Block) {
578        self.last_hash = self.block.hash();
579        self.block_number += 1;
580        self.block = block;
581    }
582}
583
584#[derive(Default)]
585struct EpochProcessRecorder {
586    receipts: Vec<Arc<BlockReceipts>>,
587    staking_events: Vec<StakingEvent>,
588    repack_tx: Vec<Arc<SignedTransaction>>,
589    geth_traces: Vec<GethTraceWithHash>,
590
591    evm_tx_idx: usize,
592}
593
594impl EpochProcessRecorder {
595    fn new() -> Self { Default::default() }
596}
597
598struct BlockProcessRecorder {
599    receipt: Vec<Receipt>,
600    tx_error_msg: Vec<String>,
601    traces: Vec<TransactionExecTraces>,
602    geth_traces: Vec<GethTraceWithHash>,
603    repack_tx: Vec<Arc<SignedTransaction>>,
604    staking_events: Vec<StakingEvent>,
605
606    tx_idx: SpaceMap<usize>,
607}
608
609impl BlockProcessRecorder {
610    fn new(evm_tx_idx: usize) -> BlockProcessRecorder {
611        let mut tx_idx = SpaceMap::default();
612        tx_idx[Space::Ethereum] = evm_tx_idx;
613        Self {
614            receipt: vec![],
615            tx_error_msg: vec![],
616            traces: vec![],
617            geth_traces: vec![],
618            repack_tx: vec![],
619            staking_events: vec![],
620            tx_idx,
621        }
622    }
623
624    fn receive_tx_outcome(
625        &mut self, r: ProcessTxOutcome, tx: &Arc<SignedTransaction>,
626        block_context: &BlockProcessContext,
627    ) {
628        let EpochProcessContext {
629            on_local_pivot,
630            executive_trace,
631            ..
632        } = *block_context.epoch_context;
633
634        if on_local_pivot && r.consider_repacked {
635            self.repack_tx.push(tx.clone())
636        }
637
638        let not_skipped = !r.receipt.tx_skipped();
639
640        if executive_trace {
641            self.traces.push(r.tx_traces.into());
642        }
643
644        self.receipt.push(r.receipt);
645        self.tx_error_msg.push(r.tx_exec_error_msg);
646        self.staking_events.extend(r.tx_staking_events);
647
648        if let Some(trace) = r.geth_trace {
649            self.geth_traces.push(GethTraceWithHash {
650                trace,
651                tx_hash: tx.hash(),
652                space: tx.space(),
653            });
654        }
655
656        match tx.space() {
657            Space::Native => {
658                self.tx_idx[Space::Native] += 1;
659            }
660            Space::Ethereum if not_skipped => {
661                self.tx_idx[Space::Ethereum] += 1;
662            }
663            _ => {}
664        };
665    }
666
667    fn finish_block(
668        self, data_man: &BlockDataManager,
669        epoch_recorder: &mut EpochProcessRecorder,
670        block_context: &BlockProcessContext, secondary_reward: U256,
671    ) {
672        let BlockProcessContext {
673            epoch_context:
674                &EpochProcessContext {
675                    on_local_pivot,
676                    executive_trace,
677                    pivot_block,
678                    dry_run,
679                    ..
680                },
681            block,
682            block_number,
683            ..
684        } = *block_context;
685
686        let block_receipts = Arc::new(BlockReceipts {
687            receipts: self.receipt,
688            // An existing bug makes the block_number is one larger than the
689            // actual.
690            block_number: block_number + 1,
691            secondary_reward,
692            tx_execution_error_messages: self.tx_error_msg,
693        });
694
695        epoch_recorder.receipts.push(block_receipts.clone());
696        epoch_recorder.staking_events.extend(self.staking_events);
697        epoch_recorder.repack_tx.extend(self.repack_tx);
698        epoch_recorder.geth_traces.extend(self.geth_traces);
699
700        epoch_recorder.evm_tx_idx = self.tx_idx[Space::Ethereum];
701
702        if dry_run {
703            return;
704        }
705
706        if executive_trace {
707            data_man.insert_block_traces(
708                block.hash(),
709                self.traces.into(),
710                pivot_block.hash(),
711                on_local_pivot,
712            );
713        }
714
715        data_man.insert_block_execution_result(
716            block.hash(),
717            pivot_block.hash(),
718            block_receipts.clone(),
719            on_local_pivot,
720        );
721    }
722}