1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
use super::super::ConsensusGraph;

use crate::{
    block_data_manager::BlockExecutionResultWithEpoch,
    errors::Result as CoreResult,
};
use cfx_execute_helper::estimation::{EstimateExt, EstimateRequest};
use cfx_executor::executive::ExecutionOutcome;
use cfx_parameters::rpc::{
    GAS_PRICE_BLOCK_SAMPLE_SIZE, GAS_PRICE_DEFAULT_VALUE,
    GAS_PRICE_TRANSACTION_SAMPLE_SIZE,
};
use cfx_rpc_eth_types::EvmOverrides;
use cfx_types::{Space, H256, U256};
use primitives::{EpochNumber, SignedTransaction};

impl ConsensusGraph {
    /// Get the average gas price of the last GAS_PRICE_TRANSACTION_SAMPLE_SIZE
    /// blocks
    pub fn gas_price(&self, space: Space) -> Option<U256> {
        let inner = self.inner.read();
        let mut last_epoch_number = inner.best_epoch_number();
        let (
            number_of_tx_to_sample,
            mut number_of_blocks_to_sample,
            block_gas_ratio,
        ) = (
            GAS_PRICE_TRANSACTION_SAMPLE_SIZE,
            GAS_PRICE_BLOCK_SAMPLE_SIZE,
            1,
        );
        let mut prices = Vec::new();
        let mut total_block_gas_limit: u64 = 0;
        let mut total_tx_gas_limit: u64 = 0;

        loop {
            if number_of_blocks_to_sample == 0 || last_epoch_number == 0 {
                break;
            }
            if prices.len() == number_of_tx_to_sample {
                break;
            }
            let mut hashes = inner
                .block_hashes_by_epoch(last_epoch_number.into())
                .unwrap();
            hashes.reverse();
            last_epoch_number -= 1;

            for hash in hashes {
                let block = self
                    .data_man
                    .block_by_hash(&hash, false /* update_cache */)
                    .unwrap();
                total_block_gas_limit +=
                    block.block_header.gas_limit().as_u64() * block_gas_ratio;
                for tx in block.transactions.iter() {
                    if space == Space::Native && tx.space() != Space::Native {
                        // For cfx_gasPrice, we only count Native transactions.
                        continue;
                    }
                    // add the tx.gas() to total_tx_gas_limit even it is packed
                    // multiple times because these tx all
                    // will occupy block's gas space
                    total_tx_gas_limit += tx.transaction.gas().as_u64();
                    prices.push(tx.gas_price().clone());
                    if prices.len() == number_of_tx_to_sample {
                        break;
                    }
                }
                number_of_blocks_to_sample -= 1;
                if number_of_blocks_to_sample == 0
                    || prices.len() == number_of_tx_to_sample
                {
                    break;
                }
            }
        }

        prices.sort();
        if prices.is_empty() || total_tx_gas_limit == 0 {
            Some(U256::from(GAS_PRICE_DEFAULT_VALUE))
        } else {
            let average_gas_limit_multiple =
                total_block_gas_limit / total_tx_gas_limit;
            if average_gas_limit_multiple > 5 {
                // used less than 20%
                Some(U256::from(GAS_PRICE_DEFAULT_VALUE))
            } else if average_gas_limit_multiple >= 2 {
                // used less than 50%
                Some(prices[prices.len() / 8])
            } else {
                // used more than 50%
                Some(prices[prices.len() / 2])
            }
        }
    }

    pub fn get_block_execution_info(
        &self, block_hash: &H256,
    ) -> Option<(BlockExecutionResultWithEpoch, Option<H256>)> {
        let results_with_epoch = self
            .inner
            .read_recursive()
            .block_execution_results_by_hash(block_hash, true)?;

        let pivot_hash = results_with_epoch.0;

        let maybe_state_root = match self.executor.wait_for_result(pivot_hash) {
            Ok(execution_commitment) => {
                // We already has transaction address with epoch_hash executed,
                // so we can always get the state_root with
                // `wait_for_result`
                Some(
                    execution_commitment
                        .state_root_with_aux_info
                        .aux_info
                        .state_root_hash,
                )
            }
            Err(msg) => {
                warn!("get_transaction_receipt_and_block_info() gets the following error from ConsensusExecutor: {}", msg);
                None
            }
        };

        Some((results_with_epoch, maybe_state_root))
    }

    pub fn call_virtual(
        &self, tx: &SignedTransaction, epoch: EpochNumber,
        request: EstimateRequest, evm_overrides: EvmOverrides,
    ) -> CoreResult<(ExecutionOutcome, EstimateExt)> {
        // only allow to call against stated epoch
        self.validate_stated_epoch(&epoch)?;
        let (epoch_id, epoch_size) = if let Ok(v) =
            self.get_block_hashes_by_epoch(epoch)
        {
            (v.last().expect("pivot block always exist").clone(), v.len())
        } else {
            bail!("cannot get block hashes in the specified epoch, maybe it does not exist?");
        };
        self.executor.call_virtual(
            tx,
            &epoch_id,
            epoch_size,
            request,
            evm_overrides,
        )
    }
}