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 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 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 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 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 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 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 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 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 = ¶ms.transition_numbers;
491
492 let cip94_start = transition_numbers.cip94n;
493 let period = params.params_dao_vote_period;
494 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 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 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}