From adb84ec9cb005761b84b5644b464301401028148 Mon Sep 17 00:00:00 2001 From: Pana Date: Tue, 4 Jun 2024 09:48:42 +0800 Subject: [PATCH 01/21] fix zero gas_price estimate error issue move core call_request into cfx folder --- crates/client/src/rpc/impls/eth/eth_handler.rs | 6 +++++- crates/client/src/rpc/types.rs | 14 +++++++++----- .../client/src/rpc/types/{ => cfx}/call_request.rs | 0 crates/client/src/rpc/types/cfx/mod.rs | 1 + crates/client/src/rpc/types/eth/call_request.rs | 8 ++++++++ 5 files changed, 23 insertions(+), 6 deletions(-) rename crates/client/src/rpc/types/{ => cfx}/call_request.rs (100%) diff --git a/crates/client/src/rpc/impls/eth/eth_handler.rs b/crates/client/src/rpc/impls/eth/eth_handler.rs index 36dad68811..73a92198b9 100644 --- a/crates/client/src/rpc/impls/eth/eth_handler.rs +++ b/crates/client/src/rpc/impls/eth/eth_handler.rs @@ -187,7 +187,8 @@ fn block_tx_by_index( impl EthHandler { fn exec_transaction( - &self, request: CallRequest, block_number_or_hash: Option, + &self, mut request: CallRequest, + block_number_or_hash: Option, ) -> CfxRpcResult<(ExecutionOutcome, EstimateExt)> { let consensus_graph = self.consensus_graph(); @@ -213,6 +214,9 @@ impl EthHandler { epoch => epoch.try_into()?, }; + // if gas_price is zero, it is considered as not set + request.unset_zero_gas_price(); + let estimate_request = EstimateRequest { has_sender: request.from.is_some(), has_gas_limit: request.gas.is_some(), diff --git a/crates/client/src/rpc/types.rs b/crates/client/src/rpc/types.rs index a5b53b75ee..7f01e6f696 100644 --- a/crates/client/src/rpc/types.rs +++ b/crates/client/src/rpc/types.rs @@ -6,7 +6,6 @@ mod account; mod blame_info; mod block; mod bytes; -pub mod call_request; pub mod cfx; mod consensus_graph_states; mod epoch_number; @@ -40,11 +39,16 @@ pub use self::{ blame_info::BlameInfo, block::{Block, BlockTransactions, Header}, bytes::Bytes, - call_request::{ - sign_call, CallRequest, CheckBalanceAgainstTransactionResponse, - EstimateGasAndCollateralResponse, SendTxRequest, MAX_GAS_CALL_REQUEST, + cfx::{ + address, + address::RpcAddress, + call_request::{ + self, sign_call, CallRequest, + CheckBalanceAgainstTransactionResponse, + EstimateGasAndCollateralResponse, SendTxRequest, + MAX_GAS_CALL_REQUEST, + }, }, - cfx::{address, address::RpcAddress}, consensus_graph_states::ConsensusGraphStates, epoch_number::{BlockHashOrEpochNumber, EpochNumber}, fee_history::FeeHistory, diff --git a/crates/client/src/rpc/types/call_request.rs b/crates/client/src/rpc/types/cfx/call_request.rs similarity index 100% rename from crates/client/src/rpc/types/call_request.rs rename to crates/client/src/rpc/types/cfx/call_request.rs diff --git a/crates/client/src/rpc/types/cfx/mod.rs b/crates/client/src/rpc/types/cfx/mod.rs index 89d8fb6a46..11b5dd6de4 100644 --- a/crates/client/src/rpc/types/cfx/mod.rs +++ b/crates/client/src/rpc/types/cfx/mod.rs @@ -1,5 +1,6 @@ mod access_list; pub mod address; +pub mod call_request; pub use access_list::*; pub use address::RpcAddress; diff --git a/crates/client/src/rpc/types/eth/call_request.rs b/crates/client/src/rpc/types/eth/call_request.rs index d9d7906cea..3d694209b1 100644 --- a/crates/client/src/rpc/types/eth/call_request.rs +++ b/crates/client/src/rpc/types/eth/call_request.rs @@ -48,3 +48,11 @@ pub struct CallRequest { #[serde(rename = "type")] pub transaction_type: Option, } + +impl CallRequest { + pub fn unset_zero_gas_price(&mut self) { + if self.gas_price == Some(U256::zero()) { + self.gas_price = None; + } + } +} From 235b1d582cc2bb319ce6f07ceb4aed6755634c0b Mon Sep 17 00:00:00 2001 From: Chenxing Li Date: Tue, 4 Jun 2024 17:08:01 +0800 Subject: [PATCH 02/21] Fix incorrect base price in constructing receipt for non-pivot block --- .../client/src/rpc/impls/cfx/cfx_handler.rs | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/crates/client/src/rpc/impls/cfx/cfx_handler.rs b/crates/client/src/rpc/impls/cfx/cfx_handler.rs index d49a200936..b2cadc9721 100644 --- a/crates/client/src/rpc/impls/cfx/cfx_handler.rs +++ b/crates/client/src/rpc/impls/cfx/cfx_handler.rs @@ -47,7 +47,7 @@ use network::{ }; use parking_lot::Mutex; use primitives::{ - filter::LogFilter, receipt::EVM_SPACE_SUCCESS, Account, Block, + filter::LogFilter, receipt::EVM_SPACE_SUCCESS, Account, Block, BlockHeader, BlockReceipts, DepositInfo, SignedTransaction, StorageKey, StorageRoot, StorageValue, Transaction, TransactionIndex, TransactionStatus, TransactionWithSignature, VoteStakeInfo, @@ -115,7 +115,7 @@ pub(crate) struct BlockExecInfo { pub(crate) block: Arc, pub(crate) epoch_number: u64, pub(crate) maybe_state_root: Option, - pub(crate) pivot_hash: H256, + pub(crate) pivot_header: Arc, } pub struct RpcImpl { @@ -790,12 +790,23 @@ impl RpcImpl { bail!("Inconsistent state"); } + let pivot_header = if let Some(x) = self + .consensus + .get_data_manager() + .block_header_by_hash(&pivot_hash) + { + x + } else { + warn!("Cannot find pivot header when get block execution info: pivot hash {:?}", pivot_hash); + return Ok(None); + }; + Ok(Some(BlockExecInfo { block_receipts, block, epoch_number, maybe_state_root, - pivot_hash, + pivot_header, })) } @@ -840,7 +851,7 @@ impl RpcImpl { prior_gas_used, Some(exec_info.epoch_number), exec_info.block_receipts.block_number, - exec_info.block.block_header.base_price(), + exec_info.pivot_header.base_price(), exec_info.maybe_state_root.clone(), tx_exec_error_msg, *self.sync.network.get_network_type(), @@ -900,10 +911,10 @@ impl RpcImpl { }; // pivot chain reorg - if pivot_assumption != exec_info.pivot_hash { + if pivot_assumption != exec_info.pivot_header.hash() { bail!(pivot_assumption_failed( pivot_assumption, - exec_info.pivot_hash + exec_info.pivot_header.hash() )); } From 2bd9b5d806fd99889b837d3d18cb6e72e5bfd407 Mon Sep 17 00:00:00 2001 From: darwintree <17946284+darwintree@users.noreply.github.com> Date: Tue, 28 May 2024 15:21:46 +0800 Subject: [PATCH 03/21] add test for ignored transaction in non-pivot block by cip-137 --- tests/cip137_test.py | 158 +++++++++++++++++++++++++++++++++++++++++++ tests/conflux/rpc.py | 19 +++++- 2 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 tests/cip137_test.py diff --git a/tests/cip137_test.py b/tests/cip137_test.py new file mode 100644 index 0000000000..2aaf323f94 --- /dev/null +++ b/tests/cip137_test.py @@ -0,0 +1,158 @@ +from conflux.rpc import RpcClient +from test_framework.util import ( + assert_equal, +) + +from cfx_account import Account as CfxAccount + +from test_framework.test_framework import ConfluxTestFramework + +class CIP137Test(ConfluxTestFramework): + def set_test_params(self): + self.num_nodes = 1 + + def setup_network(self): + self.add_nodes(self.num_nodes) + self.start_node(0, ["--archive"]) + self.rpc = RpcClient(self.nodes[0]) + + # We need to ensure that the tx in B block + + # --- --- --- --- --- + # .- | A | <--- | C | <--- | D | <--- | E | <--- | F | <--- ... + # --- | --- --- --- --- --- + # ... <--- | P | <-* . + # --- | --- . + # .- | B | <........................................ + # --- + + # continuously fill transaction in C to F to increase base gas fee for F epoch + # then transaction in B block will fail + def run_test(self): + def base_fee_per_gas(epoch: str = "latest_mined"): + return self.rpc.fee_history(1, epoch)['base_fee_per_gas'][0] + + acct1 = CfxAccount.create() + acct2 = CfxAccount.create() + gas_price_level_1 = 2 + gas_price_level_2 = 10 + burnt_ratio = 0.5 + + assert base_fee_per_gas() < gas_price_level_1 * burnt_ratio + + # send 1000 CFX to each account + self.rpc.send_tx(self.rpc.new_tx(receiver=acct1.address, value=10**21,), True) + self.rpc.send_tx(self.rpc.new_tx(receiver=acct2.address, value=10**21), True) + block_p = self.rpc.block_by_epoch("latest_mined")["hash"] + + acct1_txs = [ + self.rpc.new_tx(receiver=self.rpc.rand_addr(), priv_key=acct1.key, nonce=0, gas_price=gas_price_level_2), # expected to succeed + self.rpc.new_tx(receiver=self.rpc.rand_addr(), priv_key=acct1.key, nonce=1, gas_price=gas_price_level_1), # expected to be ignored and can be resend later + self.rpc.new_tx(receiver=self.rpc.rand_addr(), priv_key=acct1.key, nonce=2, gas_price=gas_price_level_2) # expected to be ignored + ] + + acct2_txs = [ + self.rpc.new_tx(receiver=self.rpc.rand_addr(), priv_key=acct2.key, nonce=0, gas_price=gas_price_level_2), # expected to succeed + self.rpc.new_tx(receiver=self.rpc.rand_addr(), priv_key=acct2.key, nonce=1, gas_price=gas_price_level_2), # expected to succeed + self.rpc.new_tx(receiver=self.rpc.rand_addr(), priv_key=acct2.key, nonce=2, gas_price=gas_price_level_2) # expected to succeed + ] + + + block_b = self.rpc.generate_custom_block( + parent_hash=block_p, referee=[], txs=[*acct1_txs, *acct2_txs] + ) + + genesis_nonce = self.rpc.get_nonce(self.rpc.GENESIS_ADDR) + + # block a, c, d, e + block_a = self.rpc.generate_custom_block( + parent_hash=block_p, + referee=[], + txs=[ + self.rpc.new_tx( + receiver=self.rpc.rand_addr(), + gas=15000000, + nonce=(genesis_nonce := genesis_nonce + 1), + ) + for i in range(4) + ], + ) + block_c = self.rpc.generate_custom_block( + parent_hash=block_a, + referee=[], + txs=[ + self.rpc.new_tx( + receiver=self.rpc.rand_addr(), + gas=15000000, + nonce=(genesis_nonce := genesis_nonce + 1), + ) + for i in range(4) + ], + ) + block_d = self.rpc.generate_custom_block( + parent_hash=block_c, + referee=[], + txs=[ + self.rpc.new_tx( + receiver=self.rpc.rand_addr(), + gas=15000000, + nonce=(genesis_nonce := genesis_nonce + 1), + ) + for i in range(4) + ], + ) + block_e = self.rpc.generate_custom_block( + parent_hash=block_d, + referee=[], + txs=[ + self.rpc.new_tx( + receiver=self.rpc.rand_addr(), + gas=15000000, + nonce=(genesis_nonce := genesis_nonce + 1), + ) + for i in range(4) + ], + ) + block_f = self.rpc.generate_custom_block( + parent_hash=block_e, + referee=[block_b], + txs=[ + self.rpc.new_tx( + receiver=self.rpc.rand_addr(), + gas=15000000, + nonce=(genesis_nonce := genesis_nonce + 1), + ) + for i in range(4) + ], + ) + assert gas_price_level_2 > base_fee_per_gas() * burnt_ratio + assert gas_price_level_1 < base_fee_per_gas() * burnt_ratio + + # wait for epoch of block f executed + parent_block = block_f + for _ in range(30): + block = self.rpc.generate_custom_block(parent_hash = parent_block, referee = [], txs = []) + parent_block = block + + assert_equal(self.rpc.get_nonce(acct1.address), 1) + assert_equal(self.rpc.get_nonce(acct2.address), 3) + focusing_block = self.rpc.block_by_hash(block_b, True) + assert_equal(focusing_block["transactions"][0]["status"] ,"0x0") + assert_equal(focusing_block["transactions"][1]["status"] , None) + assert_equal(focusing_block["transactions"][1]["blockHash"] , None) + assert_equal(focusing_block["transactions"][2]["status"] , None) + assert_equal(focusing_block["transactions"][2]["blockHash"] , None) + + # as comparison + assert_equal(focusing_block["transactions"][3]["status"] , "0x0") + assert_equal(focusing_block["transactions"][4]["status"] , "0x0") + assert_equal(focusing_block["transactions"][5]["status"] , "0x0") + + self.rpc.generate_blocks(20, 5) + + # transactions shall be sent back to txpool and then get packed + assert_equal(self.rpc.get_nonce(acct1.address), 3) + + +if __name__ == "__main__": + CIP137Test().main() diff --git a/tests/conflux/rpc.py b/tests/conflux/rpc.py index 66efb76676..4e76f6b6b7 100644 --- a/tests/conflux/rpc.py +++ b/tests/conflux/rpc.py @@ -1,6 +1,6 @@ import os import random -from typing import Optional, Union +from typing import Optional, Union, TypedDict from web3 import Web3 import eth_utils @@ -34,6 +34,11 @@ "to": b'', } +class CfxFeeHistoryResponse(TypedDict): + base_fee_per_gas: list[int] + gas_used_ratio: list[float] + reward: list[list[str]] # does not convert it currently + def convert_b32_address_field_to_hex(original_dict: dict, field_name: str): if original_dict is not None and field_name in original_dict and original_dict[field_name] not in [None, "null"]: @@ -580,6 +585,18 @@ def get_transaction_trace(self, tx_hash: str): def filter_trace(self, filter: dict): return self.node.trace_filter(filter) + + def fee_history(self, epoch_count: int, last_epoch: Union[int, str], reward_percentiles: Optional[list[float]]=None) -> CfxFeeHistoryResponse: + if reward_percentiles is None: + reward_percentiles = [50]*epoch_count + if isinstance(last_epoch, int): + last_epoch = hex(last_epoch) + rtn = self.node.cfx_feeHistory(hex(epoch_count), last_epoch, reward_percentiles) + rtn[ + 'base_fee_per_gas' + ] = [ int(v, 16) for v in rtn['base_fee_per_gas'] ] + return rtn + def wait_for_pos_register(self, priv_key=None, stake_value=2_000_000, voting_power=None, legacy=True, should_fail=False): if priv_key is None: From b69754978dd47e50b69fcd0c8797ed19e4c461a1 Mon Sep 17 00:00:00 2001 From: darwintree <17946284+darwintree@users.noreply.github.com> Date: Tue, 28 May 2024 16:26:27 +0800 Subject: [PATCH 04/21] chore(deps): add cfx-account dependency --- dev-support/dep_pip3.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/dev-support/dep_pip3.sh b/dev-support/dep_pip3.sh index 40f7f2fe47..62559d494d 100755 --- a/dev-support/dep_pip3.sh +++ b/dev-support/dep_pip3.sh @@ -8,6 +8,7 @@ function install() { fi } +install git+https://github.com/conflux-fans/cfx-account.git@v1.1.0-beta.2 # install cfx-account lib and prepare for CIP-1559 tests install eth-utils install rlp==1.2.0 install py-ecc==5.2.0 From 58f9943f86d699080c14b1951e3795a5102bd788 Mon Sep 17 00:00:00 2001 From: darwintree <17946284+darwintree@users.noreply.github.com> Date: Tue, 28 May 2024 16:33:27 +0800 Subject: [PATCH 05/21] refactor: force rpc client new tx with param name --- tests/conflux/rpc.py | 2 +- tests/tx_consistency_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/conflux/rpc.py b/tests/conflux/rpc.py index 4e76f6b6b7..eec1095f4f 100644 --- a/tests/conflux/rpc.py +++ b/tests/conflux/rpc.py @@ -365,7 +365,7 @@ def get_tx(self, tx_hash: str) -> dict: convert_b32_address_field_to_hex(tx, "contractCreated") return tx - def new_tx(self, sender=None, receiver=None, nonce=None, gas_price=1, gas=21000, value=100, data=b'', sign=True, + def new_tx(self, *, sender=None, receiver=None, nonce=None, gas_price=1, gas=21000, value=100, data=b'', sign=True, priv_key=None, storage_limit=None, epoch_height=None, chain_id=DEFAULT_PY_TEST_CHAIN_ID): if priv_key is None: priv_key = default_config["GENESIS_PRI_KEY"] diff --git a/tests/tx_consistency_test.py b/tests/tx_consistency_test.py index f3eead320b..a5db4ce784 100755 --- a/tests/tx_consistency_test.py +++ b/tests/tx_consistency_test.py @@ -148,7 +148,7 @@ def sample_node_indices(self): # randomly select N nodes to send tx. def send_tx(self, sender: Account, receiver: Account): client = RpcClient(self.nodes[0]) - tx = client.new_tx(sender.address, receiver.address, sender.nonce, value=9000, priv_key=sender.priv_key) + tx = client.new_tx(sender=sender.address, receiver=receiver.address, nonce=sender.nonce, value=9000, priv_key=sender.priv_key) def ensure_send_tx(node, tx): tx_hash = RpcClient(node).send_tx(tx) From 7dfb511e1894c41b7beedd654be6696bef72acfb Mon Sep 17 00:00:00 2001 From: darwintree <17946284+darwintree@users.noreply.github.com> Date: Thu, 30 May 2024 17:23:55 +0800 Subject: [PATCH 06/21] tests: add cip-1559 tests --- tests/cip1559_test.py | 178 +++++++++++++++++++++++ tests/conflux/rpc.py | 70 ++++++++- tests/test_framework/simple_rpc_proxy.py | 3 +- tests/test_framework/test_framework.py | 2 +- tests/test_framework/test_node.py | 2 +- 5 files changed, 244 insertions(+), 11 deletions(-) create mode 100644 tests/cip1559_test.py diff --git a/tests/cip1559_test.py b/tests/cip1559_test.py new file mode 100644 index 0000000000..bc50fcb889 --- /dev/null +++ b/tests/cip1559_test.py @@ -0,0 +1,178 @@ +from conflux.rpc import RpcClient +from test_framework.util import ( + assert_equal, +) +from decimal import Decimal + +from cfx_account import Account as CfxAccount +from cfx_account.signers.local import LocalAccount as CfxLocalAccount + +from test_framework.test_framework import ConfluxTestFramework + +CORE_BLOCK_GAS_TARGET = 270000 +BURNT_RATIO = 0.5 +MIN_NATIVE_BASE_PRICE = 10000 + +class CIP1559Test(ConfluxTestFramework): + def set_test_params(self): + self.num_nodes = 1 + # self.conf_parameters["executive_trace"] = "true" + # self.conf_parameters["cip1559_transition_height"] = str(1) + self.conf_parameters["min_native_base_price"] = MIN_NATIVE_BASE_PRICE + + + def setup_network(self): + self.add_nodes(self.num_nodes) + self.start_node(0, ["--archive"]) + self.rpc = RpcClient(self.nodes[0]) + + # acct should have cfx + def increase_base_fee(self, acct: CfxLocalAccount=None, block_count=10, tx_per_block=4, gas_per_tx=13500000): + if acct is None: + acct = self.init_acct_with_cfx() + starting_nonce = self.rpc.get_nonce(acct.hex_address) + + for block_count in range(block_count): + self.rpc.generate_custom_block( + txs=[ + self.rpc.new_tx( + priv_key=acct.key, + receiver=CfxAccount.create().address, + gas=gas_per_tx, + nonce=starting_nonce + i + block_count * tx_per_block, + gas_price=self.rpc.base_fee_per_gas()*2 # give enough gas price to make the tx valid + ) + for i in range(tx_per_block) + ], + parent_hash=self.rpc.block_by_epoch("latest_mined")["hash"], + referee=[], + ) + + def test_block_base_fee_change(self, acct: CfxLocalAccount, epoch_to_test:int, tx_per_block=4, gas_per_tx=13500000): + starting_epoch = self.rpc.epoch_number() + self.increase_base_fee(acct, epoch_to_test, tx_per_block, gas_per_tx) + expected_base_fee_change_delta_rate = self.get_expected_base_fee_change_delta_rate(tx_per_block * gas_per_tx, 27000000) + + for i in range(starting_epoch+1, self.rpc.epoch_number()): + expected_current_base_fee = self.rpc.base_fee_per_gas(i-1) + int(self.rpc.base_fee_per_gas(i-1) * expected_base_fee_change_delta_rate) + assert_equal(self.rpc.base_fee_per_gas(i), expected_current_base_fee) + + + def get_expected_base_fee_change_delta_rate(self, sum_tx_gas_limit: int, block_target_gas_limit: int = None) -> Decimal: + if block_target_gas_limit is None: + block_target_gas_limit = CORE_BLOCK_GAS_TARGET + BASE_FEE_MAX_CHANGE_DENOMINATOR = 8 + return ((sum_tx_gas_limit-block_target_gas_limit) / Decimal(block_target_gas_limit)) / BASE_FEE_MAX_CHANGE_DENOMINATOR + + # default to 1000 CFX + def init_acct_with_cfx(self, drip: int=10**21) -> CfxLocalAccount: + self.rpc.send_tx( + self.rpc.new_tx( + receiver=(acct:=CfxAccount.create()).address, + value=drip, + gas_price=max(self.rpc.base_fee_per_gas()*2,MIN_NATIVE_BASE_PRICE) # avoid genisis zero gas price + ), + True, + ) + return acct + + def get_gas_charged(self, tx_hash: str) -> int: + gas_limit = int(self.rpc.get_tx(tx_hash)["gas"], 16) + gas_used = int(self.rpc.get_transaction_receipt(tx_hash)["gasUsed"], 16) + return max(int(3/4*gas_limit), gas_used) + + # TODO: currently only assert transaction in pivot block + # the transactions in non-pivot block is different + def assert_expected_effective_gas_fee(self, tx_hash: str): + data = self.rpc.get_tx(tx_hash) + max_fee_per_gas = int(data["maxFeePerGas"], 16) + max_priority_fee_per_gas = int(data["maxPriorityFeePerGas"], 16) + receipt = self.rpc.get_transaction_receipt(tx_hash) + effective_gas_price = int(receipt["effectiveGasPrice"], 16) + transaction_epoch = int(receipt["epochNumber"],16) + base_fee_per_gas = self.rpc.base_fee_per_gas(transaction_epoch) + # computed gas price + priority_fee_per_gas = effective_gas_price - base_fee_per_gas + assert_equal(priority_fee_per_gas, min(max_priority_fee_per_gas, max_fee_per_gas - base_fee_per_gas)) + assert_equal(int(receipt["gasFee"], 16), effective_gas_price*self.get_gas_charged(tx_hash)) + + def test_balance_change(self, acct: CfxLocalAccount): + acct_balance = self.rpc.get_balance(acct.address) + h = self.rpc.send_tx( + self.rpc.new_typed_tx( + priv_key=acct.key.hex(), + receiver=CfxAccount.create().address, + max_fee_per_gas=self.rpc.base_fee_per_gas(), + max_priority_fee_per_gas=self.rpc.base_fee_per_gas(), + value=100, + ), + wait_for_receipt=True, + ) + receipt = self.rpc.get_transaction_receipt(h) + acct_new_balance = self.rpc.get_balance(acct.address) + assert_equal(acct_new_balance, acct_balance - int(receipt["gasFee"], 16) - 100) + + def test_max_fee_not_enough_for_current_base_fee(self): + self.increase_base_fee(block_count=10) + initial_base_fee = self.rpc.base_fee_per_gas() + + self.log.info(f"initla base fee: {initial_base_fee}") + + # 112.5% ^ 10 + self.increase_base_fee(block_count=10) + self.log.info(f"increase base fee by 112.5% ^ 10") + self.log.info(f"new base fee: {self.rpc.base_fee_per_gas()}") + assert self.rpc.base_fee_per_gas() > initial_base_fee + self.log.info(f"sending new transaction with max_fee_per_gas: {initial_base_fee}") + # as the transaction's max fee per gas is not enough for current base fee, + # the transaction will become pending until the base fee drops + # we will observe the base fee of the block the transaction is in + h = self.rpc.send_tx( + self.rpc.new_typed_tx( + receiver=CfxAccount.create().address, + max_fee_per_gas=initial_base_fee, + ), + wait_for_receipt=True, + ) + + tx_base_fee = self.rpc.base_fee_per_gas(self.rpc.get_transaction_receipt(h)["epochNumber"]) + self.log.info(f"epoch base fee for transaction accepted: {tx_base_fee}") + assert tx_base_fee <= initial_base_fee + + def test_type_2_tx(self): + + self.assert_expected_effective_gas_fee(self.rpc.send_tx( + self.rpc.new_typed_tx( + receiver=CfxAccount.create().address, + max_fee_per_gas=self.rpc.base_fee_per_gas(), + max_priority_fee_per_gas=self.rpc.base_fee_per_gas(), + ), + wait_for_receipt=True, + )) + self.assert_expected_effective_gas_fee(self.rpc.send_tx( + self.rpc.new_typed_tx( + receiver=CfxAccount.create().address, + max_fee_per_gas=self.rpc.base_fee_per_gas(), + max_priority_fee_per_gas=0, + ), + wait_for_receipt=True, + )) + self.test_balance_change(self.init_acct_with_cfx()) + + def run_test(self): + self.rpc.generate_blocks(5) + + # test fee increasing + self.test_block_base_fee_change(self.init_acct_with_cfx(), 20, 4, 13500000) + self.test_block_base_fee_change(self.init_acct_with_cfx(), 20, 6, 8000000) + self.test_block_base_fee_change(self.init_acct_with_cfx(), 20, 3, 13500000) + # note: as min base fee is provided, we use less epochs + self.test_block_base_fee_change(self.init_acct_with_cfx(), 10, 1, 13500000) + self.test_block_base_fee_change(self.init_acct_with_cfx(), 10, 2, 10000000) + + self.test_type_2_tx() + self.test_max_fee_not_enough_for_current_base_fee() + + +if __name__ == "__main__": + CIP1559Test().main() diff --git a/tests/conflux/rpc.py b/tests/conflux/rpc.py index eec1095f4f..6ab44a9289 100644 --- a/tests/conflux/rpc.py +++ b/tests/conflux/rpc.py @@ -1,13 +1,15 @@ import os import random -from typing import Optional, Union, TypedDict +from typing import cast, Optional, Union, TypedDict, Any from web3 import Web3 import eth_utils +from cfx_account import Account as CfxAccount +from eth_account.datastructures import SignedTransaction import rlp import json -from .address import hex_to_b32_address, b32_address_to_hex +from .address import hex_to_b32_address, b32_address_to_hex, DEFAULT_PY_TEST_CHAIN_ID from .config import DEFAULT_PY_TEST_CHAIN_ID, default_config from .transactions import CONTRACT_DEFAULT_GAS, Transaction, UnsignedTransaction from .filter import Filter @@ -25,6 +27,7 @@ assert_equal, wait_until, checktx, get_contract_instance ) +from test_framework.test_node import TestNode file_dir = os.path.dirname(os.path.realpath(__file__)) REQUEST_BASE = { @@ -38,6 +41,8 @@ class CfxFeeHistoryResponse(TypedDict): base_fee_per_gas: list[int] gas_used_ratio: list[float] reward: list[list[str]] # does not convert it currently + +# class TransactionReceipt(TypedDict): def convert_b32_address_field_to_hex(original_dict: dict, field_name: str): @@ -46,8 +51,8 @@ def convert_b32_address_field_to_hex(original_dict: dict, field_name: str): class RpcClient: - def __init__(self, node=None, auto_restart=False, log=None): - self.node = node + def __init__(self, node: Optional[TestNode]=None, auto_restart=False, log=None): + self.node: TestNode = node # type: ignore self.auto_restart = auto_restart self.log = log @@ -134,12 +139,13 @@ def generate_block_with_parent(self, parent_hash: str, referee: list = None, num assert_is_hash_string(block_hash) return block_hash - def generate_custom_block(self, parent_hash: str, referee: list, txs: list) -> str: + def generate_custom_block(self, parent_hash: str, referee: list, txs: list[Union[Transaction, SignedTransaction]]) -> str: assert_is_hash_string(parent_hash) for r in referee: assert_is_hash_string(r) + encoded_txs = eth_utils.encode_hex(rlp.encode(txs)) block_hash = self.node.test_generatecustomblock(parent_hash, referee, encoded_txs) @@ -193,6 +199,9 @@ def get_code(self, address: str, epoch: Union[str, dict] = None) -> str: def gas_price(self) -> int: return int(self.node.cfx_gasPrice(), 0) + def base_fee_per_gas(self, epoch: Union[int,str] = "latest_mined"): + return self.fee_history(1, epoch)['base_fee_per_gas'][0] + def get_block_reward_info(self, epoch: str): reward = self.node.cfx_getBlockRewardInfo(epoch) convert_b32_address_field_to_hex(reward, "author") @@ -301,8 +310,12 @@ def send_raw_tx(self, raw_tx: str, wait_for_catchup=True) -> str: def clear_tx_pool(self): self.node.txpool_clear() - def send_tx(self, tx: Transaction, wait_for_receipt=False, wait_for_catchup=True) -> str: - encoded = eth_utils.encode_hex(rlp.encode(tx)) + # a temporary patch for transaction compatibity + def send_tx(self, tx: Union[Transaction, SignedTransaction], wait_for_receipt=False, wait_for_catchup=True) -> str: + if isinstance(tx, SignedTransaction): + encoded = cast(str, tx.rawTransaction.hex()) + else: + encoded = eth_utils.encode_hex(rlp.encode(tx)) tx_hash = self.send_raw_tx(encoded, wait_for_catchup=wait_for_catchup) if wait_for_receipt: @@ -391,6 +404,47 @@ def new_tx(self, *, sender=None, receiver=None, nonce=None, gas_price=1, gas=210 return tx.sign(priv_key) else: return tx + + def new_typed_tx(self, *, type_=2, receiver=None, nonce=None, max_fee_per_gas=None,max_priority_fee_per_gas=0, access_list=[], gas=21000, value=100, data=b'', + priv_key=None, storage_limit=0, epoch_height=None, chain_id=DEFAULT_PY_TEST_CHAIN_ID + ) -> SignedTransaction: + + if priv_key: + acct = CfxAccount.from_key(priv_key, DEFAULT_PY_TEST_CHAIN_ID) + else: + acct = CfxAccount.from_key(default_config["GENESIS_PRI_KEY"], DEFAULT_PY_TEST_CHAIN_ID) + tx = {} + tx["type"] = type_ + tx["gas"] = gas + tx["storageLimit"] = storage_limit + tx["value"] = value + tx["data"] = data + tx["maxPriorityFeePerGas"] = max_priority_fee_per_gas + tx["chainId"] = chain_id + tx["to"] = receiver + + if nonce is None: + nonce = self.get_nonce(acct.hex_address) + tx["nonce"] = nonce + + if access_list != []: + def format_access_list(a_list): + rtn = [] + for item in a_list: + rtn.append({"address": item['address'], "storageKeys": item['storage_keys']}) + + access_list = format_access_list(access_list) + tx["accessList"] = access_list + + if epoch_height is None: + epoch_height = self.epoch_number() + tx["epochHeight"] = epoch_height + + # ensuring transaction can be sent + if max_fee_per_gas is None: + max_fee_per_gas = self.base_fee_per_gas('latest_mined') + 1 + tx["maxFeePerGas"] = max_fee_per_gas + return acct.sign_transaction(tx) def new_contract_tx(self, receiver: Optional[str], data_hex: str = None, sender=None, priv_key=None, nonce=None, gas_price=1, @@ -455,7 +509,7 @@ def disconnect_peer(self, node_id: str, node_op: str = None) -> int: def chain(self) -> list: return self.node.cfx_getChain() - def get_transaction_receipt(self, tx_hash: str) -> dict: + def get_transaction_receipt(self, tx_hash: str) -> dict[str, Any]: assert_is_hash_string(tx_hash) r = self.node.cfx_getTransactionReceipt(tx_hash) if r is None: diff --git a/tests/test_framework/simple_rpc_proxy.py b/tests/test_framework/simple_rpc_proxy.py index 6105f26032..71cd2d7bb1 100644 --- a/tests/test_framework/simple_rpc_proxy.py +++ b/tests/test_framework/simple_rpc_proxy.py @@ -1,4 +1,5 @@ import time +from typing import Any import jsonrpcclient.client from jsonrpcclient.exceptions import ReceivedErrorResponseError @@ -25,7 +26,7 @@ def __init__(self, client, method, timeout, node): self.timeout = timeout self.node = node - def __call__(self, *args, **argsn): + def __call__(self, *args, **argsn) -> Any: if argsn: raise ValueError('json rpc 2 only supports array arguments') from jsonrpcclient.requests import Request diff --git a/tests/test_framework/test_framework.py b/tests/test_framework/test_framework.py index 2abc46690c..b66c0957fa 100644 --- a/tests/test_framework/test_framework.py +++ b/tests/test_framework/test_framework.py @@ -73,7 +73,7 @@ class ConfluxTestFramework: def __init__(self): """Sets test framework defaults. Do not override this method. Instead, override the set_test_params() method""" self.setup_clean_chain = True - self.nodes = [] + self.nodes: list[TestNode] = [] self.network_thread = None self.mocktime = 0 self.rpc_timewait = CONFLUX_RPC_WAIT_TIMEOUT diff --git a/tests/test_framework/test_node.py b/tests/test_framework/test_node.py index 05a240739d..2cbac1ce5b 100644 --- a/tests/test_framework/test_node.py +++ b/tests/test_framework/test_node.py @@ -74,7 +74,7 @@ def __init__(self, index, datadir, rpchost, confluxd, rpc_timeout=None, remote=F self.running = False self.process = None self.rpc_connected = False - self.rpc = None + self.rpc: SimpleRpcProxy = None # type: ignore self.ethrpc = None self.ethrpc_connected = False self.log = logging.getLogger('TestFramework.node%d' % index) From 00e07cb9b77178c105c48c424c5568028a8ebe3c Mon Sep 17 00:00:00 2001 From: darwintree <17946284+darwintree@users.noreply.github.com> Date: Thu, 30 May 2024 18:22:10 +0800 Subject: [PATCH 07/21] refactor: extract base fee manipulation interface --- tests/cip1559_test.py | 28 +++++------------------ tests/test_framework/util.py | 44 +++++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 23 deletions(-) diff --git a/tests/cip1559_test.py b/tests/cip1559_test.py index bc50fcb889..ad62cb257d 100644 --- a/tests/cip1559_test.py +++ b/tests/cip1559_test.py @@ -8,6 +8,7 @@ from cfx_account.signers.local import LocalAccount as CfxLocalAccount from test_framework.test_framework import ConfluxTestFramework +from test_framework.util import generate_blocks_for_base_fee_manipulation CORE_BLOCK_GAS_TARGET = 270000 BURNT_RATIO = 0.5 @@ -16,8 +17,6 @@ class CIP1559Test(ConfluxTestFramework): def set_test_params(self): self.num_nodes = 1 - # self.conf_parameters["executive_trace"] = "true" - # self.conf_parameters["cip1559_transition_height"] = str(1) self.conf_parameters["min_native_base_price"] = MIN_NATIVE_BASE_PRICE @@ -27,30 +26,15 @@ def setup_network(self): self.rpc = RpcClient(self.nodes[0]) # acct should have cfx - def increase_base_fee(self, acct: CfxLocalAccount=None, block_count=10, tx_per_block=4, gas_per_tx=13500000): + def change_base_fee(self, acct: CfxLocalAccount=None, block_count=10, tx_per_block=4, gas_per_tx=13500000): if acct is None: acct = self.init_acct_with_cfx() - starting_nonce = self.rpc.get_nonce(acct.hex_address) + generate_blocks_for_base_fee_manipulation(self.rpc, acct, block_count, tx_per_block, gas_per_tx) - for block_count in range(block_count): - self.rpc.generate_custom_block( - txs=[ - self.rpc.new_tx( - priv_key=acct.key, - receiver=CfxAccount.create().address, - gas=gas_per_tx, - nonce=starting_nonce + i + block_count * tx_per_block, - gas_price=self.rpc.base_fee_per_gas()*2 # give enough gas price to make the tx valid - ) - for i in range(tx_per_block) - ], - parent_hash=self.rpc.block_by_epoch("latest_mined")["hash"], - referee=[], - ) def test_block_base_fee_change(self, acct: CfxLocalAccount, epoch_to_test:int, tx_per_block=4, gas_per_tx=13500000): starting_epoch = self.rpc.epoch_number() - self.increase_base_fee(acct, epoch_to_test, tx_per_block, gas_per_tx) + self.change_base_fee(acct, epoch_to_test, tx_per_block, gas_per_tx) expected_base_fee_change_delta_rate = self.get_expected_base_fee_change_delta_rate(tx_per_block * gas_per_tx, 27000000) for i in range(starting_epoch+1, self.rpc.epoch_number()): @@ -113,13 +97,13 @@ def test_balance_change(self, acct: CfxLocalAccount): assert_equal(acct_new_balance, acct_balance - int(receipt["gasFee"], 16) - 100) def test_max_fee_not_enough_for_current_base_fee(self): - self.increase_base_fee(block_count=10) + self.change_base_fee(block_count=10) initial_base_fee = self.rpc.base_fee_per_gas() self.log.info(f"initla base fee: {initial_base_fee}") # 112.5% ^ 10 - self.increase_base_fee(block_count=10) + self.change_base_fee(block_count=10) self.log.info(f"increase base fee by 112.5% ^ 10") self.log.info(f"new base fee: {self.rpc.base_fee_per_gas()}") assert self.rpc.base_fee_per_gas() > initial_base_fee diff --git a/tests/test_framework/util.py b/tests/test_framework/util.py index c8b8aac316..8126c3133b 100644 --- a/tests/test_framework/util.py +++ b/tests/test_framework/util.py @@ -10,12 +10,14 @@ import re from subprocess import CalledProcessError, check_output import time -from typing import Optional, Callable, List, TYPE_CHECKING, cast +from typing import Optional, Callable, List, TYPE_CHECKING, cast, Tuple, Union import socket import threading import jsonrpcclient.exceptions import solcx import web3 +from cfx_account import Account as CfxAccount +from cfx_account.signers.local import LocalAccount as CfxLocalAccount from sys import platform import yaml import shutil @@ -806,3 +808,43 @@ def test_rpc_call_with_block_object(client: "RpcClient", txs: List, rpc_call: Ca assert(expected_result_lambda(result1)) assert_equal(result2, result1) + +# acct should have cfx +# create a chain of blocks with specified transfer tx with specified num and gas +# return the last block's hash and acct nonce +def generate_blocks_for_base_fee_manipulation(rpc: "RpcClient", acct: Union[CfxLocalAccount, str], block_count=10, tx_per_block=4, gas_per_tx=13500000,initial_parent_hash:str = None) -> Tuple[str, int]: + if isinstance(acct, str): + acct = CfxAccount.from_key(acct) + starting_nonce: int = rpc.get_nonce(acct.hex_address) + + if initial_parent_hash is None: + initial_parent_hash = cast(str, rpc.block_by_epoch("latest_mined")["hash"]) + + block_pointer = initial_parent_hash + for block_count in range(block_count): + block_pointer, starting_nonce = generate_single_block_for_base_fee_manipulation(rpc, acct, tx_per_block=tx_per_block, gas_per_tx=gas_per_tx,parent_hash=block_pointer, starting_nonce=starting_nonce) + + return block_pointer, starting_nonce + block_count * tx_per_block + +def generate_single_block_for_base_fee_manipulation(rpc: "RpcClient", acct: CfxLocalAccount, referee:list[str] =[], tx_per_block=4, gas_per_tx=13500000,parent_hash:str = None, starting_nonce: int = None) -> Tuple[str, int]: + if starting_nonce is None: + starting_nonce = cast(int, rpc.get_nonce(acct.hex_address)) + + if parent_hash is None: + parent_hash = cast(str, rpc.block_by_epoch("latest_mined")["hash"]) + + new_block = rpc.generate_custom_block( + txs=[ + rpc.new_tx( + priv_key=acct.key, + receiver=acct.address, + gas=gas_per_tx, + nonce=starting_nonce + i , + gas_price=rpc.base_fee_per_gas()*2 # give enough gas price to make the tx valid + ) + for i in range(tx_per_block) + ], + parent_hash=parent_hash, + referee=referee, + ) + return new_block, starting_nonce + tx_per_block From 7c7d5a8de2a92cac4a00b7fbeea9437780131692 Mon Sep 17 00:00:00 2001 From: darwintree <17946284+darwintree@users.noreply.github.com> Date: Fri, 31 May 2024 14:05:21 +0800 Subject: [PATCH 08/21] refactor: cip137 test refactor --- tests/cip137_test.py | 121 +++++++++++++++---------------------------- 1 file changed, 43 insertions(+), 78 deletions(-) diff --git a/tests/cip137_test.py b/tests/cip137_test.py index 2aaf323f94..e4e54296e8 100644 --- a/tests/cip137_test.py +++ b/tests/cip137_test.py @@ -1,15 +1,23 @@ -from conflux.rpc import RpcClient +from typing import Union, Tuple +from conflux.rpc import RpcClient, default_config from test_framework.util import ( assert_equal, ) from cfx_account import Account as CfxAccount +from cfx_account.signers.local import LocalAccount as CfxLocalAccount from test_framework.test_framework import ConfluxTestFramework +from test_framework.util import generate_blocks_for_base_fee_manipulation, generate_single_block_for_base_fee_manipulation + +MIN_NATIVE_BASE_PRICE = 10000 +BURNT_RATIO = 0.5 + class CIP137Test(ConfluxTestFramework): def set_test_params(self): self.num_nodes = 1 + self.conf_parameters["min_native_base_price"] = MIN_NATIVE_BASE_PRICE def setup_network(self): self.add_nodes(self.num_nodes) @@ -17,7 +25,7 @@ def setup_network(self): self.rpc = RpcClient(self.nodes[0]) # We need to ensure that the tx in B block - + # B and ending block will be in the same epoch # --- --- --- --- --- # .- | A | <--- | C | <--- | D | <--- | E | <--- | F | <--- ... # --- | --- --- --- --- --- @@ -25,26 +33,47 @@ def setup_network(self): # --- | --- . # .- | B | <........................................ # --- + # ensures txs to be included in B block and the ending block (e.g. F) base gas price is greater than the specified target_minimum_base_fee (not guaranteed to be the first block) + # returns the ending block hash + def construct_non_pivot_block(self, acct: CfxLocalAccount, txs: list, starting_block_hash: str=None, epoch_delta: int=5) -> Tuple[str, str]: + + if epoch_delta <=0: + raise ValueError("epoch_delta must be positive") + + if starting_block_hash is None: + starting_block_hash = self.rpc.block_by_epoch("latest_mined")["hash"] + + # create the non-pivot block + non_pivot_block = self.rpc.generate_custom_block(parent_hash=starting_block_hash, txs=txs, referee=[]) + ending_but_two_block, account_next_nonce = generate_blocks_for_base_fee_manipulation( + self.rpc, acct, epoch_delta, initial_parent_hash=starting_block_hash + ) + ending_block, _ = generate_single_block_for_base_fee_manipulation( + self.rpc, acct, [non_pivot_block], parent_hash=ending_but_two_block, starting_nonce=account_next_nonce + ) + return non_pivot_block, ending_block + + + + # TODO: test in pivot block transaction will not be included if burnt_ratio*base_gas_fee_per_gas < max_fee_per_gas < base_gas_fee_per_gas # continuously fill transaction in C to F to increase base gas fee for F epoch # then transaction in B block will fail def run_test(self): - def base_fee_per_gas(epoch: str = "latest_mined"): - return self.rpc.fee_history(1, epoch)['base_fee_per_gas'][0] acct1 = CfxAccount.create() acct2 = CfxAccount.create() - gas_price_level_1 = 2 - gas_price_level_2 = 10 - burnt_ratio = 0.5 - assert base_fee_per_gas() < gas_price_level_1 * burnt_ratio + # assert self.rpc.base_fee_per_gas() < gas_price_level_1 * burnt_ratio # send 1000 CFX to each account - self.rpc.send_tx(self.rpc.new_tx(receiver=acct1.address, value=10**21,), True) - self.rpc.send_tx(self.rpc.new_tx(receiver=acct2.address, value=10**21), True) + self.rpc.send_tx(self.rpc.new_tx(receiver=acct1.address, value=10**21, gas_price=MIN_NATIVE_BASE_PRICE), True) + self.rpc.send_tx(self.rpc.new_tx(receiver=acct2.address, value=10**21, gas_price=MIN_NATIVE_BASE_PRICE), True) block_p = self.rpc.block_by_epoch("latest_mined")["hash"] + gas_price_level_1 = MIN_NATIVE_BASE_PRICE + gas_price_level_2 = self.rpc.base_fee_per_gas() * 10 + acct1_txs = [ self.rpc.new_tx(receiver=self.rpc.rand_addr(), priv_key=acct1.key, nonce=0, gas_price=gas_price_level_2), # expected to succeed self.rpc.new_tx(receiver=self.rpc.rand_addr(), priv_key=acct1.key, nonce=1, gas_price=gas_price_level_1), # expected to be ignored and can be resend later @@ -57,76 +86,12 @@ def base_fee_per_gas(epoch: str = "latest_mined"): self.rpc.new_tx(receiver=self.rpc.rand_addr(), priv_key=acct2.key, nonce=2, gas_price=gas_price_level_2) # expected to succeed ] - - block_b = self.rpc.generate_custom_block( - parent_hash=block_p, referee=[], txs=[*acct1_txs, *acct2_txs] + block_b, block_f = self.construct_non_pivot_block( + CfxAccount.from_key(default_config["GENESIS_PRI_KEY"]), [*acct1_txs, *acct2_txs], starting_block_hash=block_p, epoch_delta=5 ) - genesis_nonce = self.rpc.get_nonce(self.rpc.GENESIS_ADDR) - - # block a, c, d, e - block_a = self.rpc.generate_custom_block( - parent_hash=block_p, - referee=[], - txs=[ - self.rpc.new_tx( - receiver=self.rpc.rand_addr(), - gas=15000000, - nonce=(genesis_nonce := genesis_nonce + 1), - ) - for i in range(4) - ], - ) - block_c = self.rpc.generate_custom_block( - parent_hash=block_a, - referee=[], - txs=[ - self.rpc.new_tx( - receiver=self.rpc.rand_addr(), - gas=15000000, - nonce=(genesis_nonce := genesis_nonce + 1), - ) - for i in range(4) - ], - ) - block_d = self.rpc.generate_custom_block( - parent_hash=block_c, - referee=[], - txs=[ - self.rpc.new_tx( - receiver=self.rpc.rand_addr(), - gas=15000000, - nonce=(genesis_nonce := genesis_nonce + 1), - ) - for i in range(4) - ], - ) - block_e = self.rpc.generate_custom_block( - parent_hash=block_d, - referee=[], - txs=[ - self.rpc.new_tx( - receiver=self.rpc.rand_addr(), - gas=15000000, - nonce=(genesis_nonce := genesis_nonce + 1), - ) - for i in range(4) - ], - ) - block_f = self.rpc.generate_custom_block( - parent_hash=block_e, - referee=[block_b], - txs=[ - self.rpc.new_tx( - receiver=self.rpc.rand_addr(), - gas=15000000, - nonce=(genesis_nonce := genesis_nonce + 1), - ) - for i in range(4) - ], - ) - assert gas_price_level_2 > base_fee_per_gas() * burnt_ratio - assert gas_price_level_1 < base_fee_per_gas() * burnt_ratio + assert gas_price_level_2 > self.rpc.base_fee_per_gas() * BURNT_RATIO + assert gas_price_level_1 < self.rpc.base_fee_per_gas() * BURNT_RATIO, f"gas_price_level_1 {gas_price_level_1} should be less than {self.rpc.base_fee_per_gas() * BURNT_RATIO}" # wait for epoch of block f executed parent_block = block_f From ccf44b724e4cc9a1d822bf394820e69e8fe48e1a Mon Sep 17 00:00:00 2001 From: darwintree <17946284+darwintree@users.noreply.github.com> Date: Mon, 3 Jun 2024 10:26:57 +0800 Subject: [PATCH 09/21] test: non-pivot block transaction fee setting --- tests/cip137_test.py | 214 ++++++++++++++++++++++++++++++++---------- tests/cip1559_test.py | 2 + tests/conflux/rpc.py | 10 +- 3 files changed, 176 insertions(+), 50 deletions(-) diff --git a/tests/cip137_test.py b/tests/cip137_test.py index e4e54296e8..80dc1c81e2 100644 --- a/tests/cip137_test.py +++ b/tests/cip137_test.py @@ -8,7 +8,10 @@ from cfx_account.signers.local import LocalAccount as CfxLocalAccount from test_framework.test_framework import ConfluxTestFramework -from test_framework.util import generate_blocks_for_base_fee_manipulation, generate_single_block_for_base_fee_manipulation +from test_framework.util import ( + generate_blocks_for_base_fee_manipulation, + generate_single_block_for_base_fee_manipulation, +) MIN_NATIVE_BASE_PRICE = 10000 BURNT_RATIO = 0.5 @@ -26,97 +29,210 @@ def setup_network(self): # We need to ensure that the tx in B block # B and ending block will be in the same epoch - # --- --- --- --- --- - # .- | A | <--- | C | <--- | D | <--- | E | <--- | F | <--- ... - # --- | --- --- --- --- --- - # ... <--- | P | <-* . - # --- | --- . - # .- | B | <........................................ + # --- --- --- --- --- --- + # .- | A | <--- | C | <--- | D | <--- | E | <--- | F | <--- | G | ... + # --- | --- --- --- --- --- --- + # ... <--- | P | <-* . + # --- | --- . + # .- | B | <................................................... # --- # ensures txs to be included in B block and the ending block (e.g. F) base gas price is greater than the specified target_minimum_base_fee (not guaranteed to be the first block) # returns the ending block hash - def construct_non_pivot_block(self, acct: CfxLocalAccount, txs: list, starting_block_hash: str=None, epoch_delta: int=5) -> Tuple[str, str]: - - if epoch_delta <=0: + def construct_non_pivot_block( + self, + acct: CfxLocalAccount, + txs: list, + starting_block_hash: str = None, + epoch_delta: int = 6, # 1.125^6 -> 2.027 which would make the initial tx invalid + ) -> Tuple[str, str]: + + if epoch_delta <= 0: raise ValueError("epoch_delta must be positive") - + if starting_block_hash is None: starting_block_hash = self.rpc.block_by_epoch("latest_mined")["hash"] - + # create the non-pivot block - non_pivot_block = self.rpc.generate_custom_block(parent_hash=starting_block_hash, txs=txs, referee=[]) - ending_but_two_block, account_next_nonce = generate_blocks_for_base_fee_manipulation( - self.rpc, acct, epoch_delta, initial_parent_hash=starting_block_hash + non_pivot_block = self.rpc.generate_custom_block( + parent_hash=starting_block_hash, txs=txs, referee=[] + ) + ending_but_two_block, account_next_nonce = ( + generate_blocks_for_base_fee_manipulation( + self.rpc, acct, epoch_delta-1, initial_parent_hash=starting_block_hash + ) ) ending_block, _ = generate_single_block_for_base_fee_manipulation( - self.rpc, acct, [non_pivot_block], parent_hash=ending_but_two_block, starting_nonce=account_next_nonce + self.rpc, + acct, + [non_pivot_block], + parent_hash=ending_but_two_block, + starting_nonce=account_next_nonce, ) return non_pivot_block, ending_block - - - + + def init_acct_with_cfx(self, drip: int = 10**21) -> CfxLocalAccount: + self.rpc.send_tx( + self.rpc.new_tx( + receiver=(acct := CfxAccount.create()).address, + value=drip, + gas_price=max( + self.rpc.base_fee_per_gas() * 2, MIN_NATIVE_BASE_PRICE + ), # avoid genisis zero gas price + ), + True, + ) + return acct + # TODO: test in pivot block transaction will not be included if burnt_ratio*base_gas_fee_per_gas < max_fee_per_gas < base_gas_fee_per_gas + # low max_fee means the transaction max fee is less than the epoch's base fee * burnt ratio + # def test_transaction_ignored_in_non_pivot_block_if_low_max_fee(self): + + def log_tx_fee_info(self, tx_hash: str): + self.log.info(f'tx hash: {tx_hash}') + data = self.rpc.get_tx(tx_hash) + + max_fee_per_gas = int(data["maxFeePerGas"], 16) + max_priority_fee_per_gas = int(data["maxPriorityFeePerGas"], 16) + receipt = self.rpc.get_transaction_receipt(tx_hash) + # print(receipt) + + # effective_gas_price = int(receipt["effectiveGasPrice"], 16) + # transaction_epoch = int(receipt["epochNumber"],16) + # base_fee_per_gas = self.rpc.base_fee_per_gas(transaction_epoch) + # computed gas price + # priority_fee_per_gas = effective_gas_price - base_fee_per_gas + self.log.info(f'max fee per gas: {max_fee_per_gas}') + self.log.info(f'max priority fee per gas: {max_priority_fee_per_gas}') + self.log.info(f'tx status: {data["status"]}') + if receipt: + self.log.info(f'tx block hash: {receipt["blockHash"]}') + self.log.info(f'tx outcome status: {receipt["outcomeStatus"]}') + else: + self.log.info(f'tx receipt is None: {receipt is None}') + # self.log.info(f'effective gas price: {effective_gas_price}') + # self.log.info(f'base fee per gas: {base_fee_per_gas}') # continuously fill transaction in C to F to increase base gas fee for F epoch # then transaction in B block will fail def run_test(self): - acct1 = CfxAccount.create() - acct2 = CfxAccount.create() - + acct1 = self.init_acct_with_cfx() + acct2 = self.init_acct_with_cfx() + # assert self.rpc.base_fee_per_gas() < gas_price_level_1 * burnt_ratio - # send 1000 CFX to each account - self.rpc.send_tx(self.rpc.new_tx(receiver=acct1.address, value=10**21, gas_price=MIN_NATIVE_BASE_PRICE), True) - self.rpc.send_tx(self.rpc.new_tx(receiver=acct2.address, value=10**21, gas_price=MIN_NATIVE_BASE_PRICE), True) block_p = self.rpc.block_by_epoch("latest_mined")["hash"] - + gas_price_level_1 = MIN_NATIVE_BASE_PRICE + gas_price_level_1_5 = int(MIN_NATIVE_BASE_PRICE * 1.5) gas_price_level_2 = self.rpc.base_fee_per_gas() * 10 - + acct1_txs = [ - self.rpc.new_tx(receiver=self.rpc.rand_addr(), priv_key=acct1.key, nonce=0, gas_price=gas_price_level_2), # expected to succeed - self.rpc.new_tx(receiver=self.rpc.rand_addr(), priv_key=acct1.key, nonce=1, gas_price=gas_price_level_1), # expected to be ignored and can be resend later - self.rpc.new_tx(receiver=self.rpc.rand_addr(), priv_key=acct1.key, nonce=2, gas_price=gas_price_level_2) # expected to be ignored + self.rpc.new_typed_tx( + receiver=self.rpc.rand_addr(), + priv_key=acct1.key, + nonce=0, + max_fee_per_gas=gas_price_level_2, + ), # expected to succeed + self.rpc.new_typed_tx( + receiver=self.rpc.rand_addr(), + priv_key=acct1.key, + nonce=1, + max_fee_per_gas=gas_price_level_1_5, + ), # expected to succeed with max fee less than epoch base gas fee + self.rpc.new_tx( + receiver=self.rpc.rand_addr(), + priv_key=acct1.key, + nonce=2, + gas_price=gas_price_level_1, + ), # expected to be ignored and can be resend later + self.rpc.new_tx( + receiver=self.rpc.rand_addr(), + priv_key=acct1.key, + nonce=3, + gas_price=gas_price_level_2, + ), # expected to be ignored ] - + acct2_txs = [ - self.rpc.new_tx(receiver=self.rpc.rand_addr(), priv_key=acct2.key, nonce=0, gas_price=gas_price_level_2), # expected to succeed - self.rpc.new_tx(receiver=self.rpc.rand_addr(), priv_key=acct2.key, nonce=1, gas_price=gas_price_level_2), # expected to succeed - self.rpc.new_tx(receiver=self.rpc.rand_addr(), priv_key=acct2.key, nonce=2, gas_price=gas_price_level_2) # expected to succeed + self.rpc.new_tx( + receiver=self.rpc.rand_addr(), + priv_key=acct2.key, + nonce=0, + gas_price=gas_price_level_2, + ), # expected to succeed + self.rpc.new_tx( + receiver=self.rpc.rand_addr(), + priv_key=acct2.key, + nonce=1, + gas_price=gas_price_level_2, + ), # expected to succeed + self.rpc.new_tx( + receiver=self.rpc.rand_addr(), + priv_key=acct2.key, + nonce=2, + gas_price=gas_price_level_2, + ), # expected to succeed ] - + block_b, block_f = self.construct_non_pivot_block( - CfxAccount.from_key(default_config["GENESIS_PRI_KEY"]), [*acct1_txs, *acct2_txs], starting_block_hash=block_p, epoch_delta=5 + CfxAccount.from_key(default_config["GENESIS_PRI_KEY"]), + [*acct1_txs, *acct2_txs], + starting_block_hash=block_p, + epoch_delta=6, # 1.125^6 -> 2.03 ) + + self.log.info(f"current base fee per gas: {self.rpc.base_fee_per_gas()}") + # we are ensuring the gas price order: + # gas_price_level_1 < current_base_fee * burnt_ratio < gas_price_level_1_5 < current_base_fee < gas_price_level_2 assert gas_price_level_2 > self.rpc.base_fee_per_gas() * BURNT_RATIO - assert gas_price_level_1 < self.rpc.base_fee_per_gas() * BURNT_RATIO, f"gas_price_level_1 {gas_price_level_1} should be less than {self.rpc.base_fee_per_gas() * BURNT_RATIO}" + assert ( + gas_price_level_1 < self.rpc.base_fee_per_gas() * BURNT_RATIO + ), f"gas_price_level_1 {gas_price_level_1} should be less than {self.rpc.base_fee_per_gas() * BURNT_RATIO}" # wait for epoch of block f executed parent_block = block_f for _ in range(30): - block = self.rpc.generate_custom_block(parent_hash = parent_block, referee = [], txs = []) + block = self.rpc.generate_custom_block( + parent_hash=parent_block, referee=[], txs=[] + ) parent_block = block - assert_equal(self.rpc.get_nonce(acct1.address), 1) + assert_equal(self.rpc.get_nonce(acct1.address), 2) assert_equal(self.rpc.get_nonce(acct2.address), 3) focusing_block = self.rpc.block_by_hash(block_b, True) - assert_equal(focusing_block["transactions"][0]["status"] ,"0x0") - assert_equal(focusing_block["transactions"][1]["status"] , None) - assert_equal(focusing_block["transactions"][1]["blockHash"] , None) - assert_equal(focusing_block["transactions"][2]["status"] , None) - assert_equal(focusing_block["transactions"][2]["blockHash"] , None) + epoch = int(focusing_block["epochNumber"],16) + + self.log.info(f"epoch of block b: {epoch}") + self.log.info(f"heigth of block b: {int(focusing_block['height'], 16)}") + self.log.info(f"base_fee_per_gas for epoch {epoch}: {self.rpc.base_fee_per_gas(epoch)}") + self.log.info(f"burnt_fee_per_gas for epoch {epoch}: {self.rpc.base_fee_per_gas(epoch) * 0.5}") + self.log.info(f"least base fee for epoch {epoch}: {self.rpc.base_fee_per_gas(epoch) * BURNT_RATIO}") + self.log.info(f"transactions in block b: {self.rpc.block_by_hash(block_b)['transactions']}") + self.log_tx_fee_info(self.rpc.block_by_hash(block_b)['transactions'][0]) + self.log_tx_fee_info(self.rpc.block_by_hash(block_b)['transactions'][1]) + + # print(focusing_block["transactions"]) + assert_equal(focusing_block["transactions"][0]["status"], "0x0") + self.log_tx_fee_info(focusing_block["transactions"][1]["hash"]) + assert_equal(focusing_block["transactions"][1]["status"], "0x0") + # assert_equal(focusing_block["transactions"][1]["blockHash"], None) + assert_equal(focusing_block["transactions"][2]["status"], None) + assert_equal(focusing_block["transactions"][2]["blockHash"], None) + assert_equal(focusing_block["transactions"][3]["status"], None) + assert_equal(focusing_block["transactions"][3]["blockHash"], None) + # as comparison - assert_equal(focusing_block["transactions"][3]["status"] , "0x0") - assert_equal(focusing_block["transactions"][4]["status"] , "0x0") - assert_equal(focusing_block["transactions"][5]["status"] , "0x0") + assert_equal(focusing_block["transactions"][4]["status"], "0x0") + assert_equal(focusing_block["transactions"][5]["status"], "0x0") + assert_equal(focusing_block["transactions"][6]["status"], "0x0") self.rpc.generate_blocks(20, 5) - + # transactions shall be sent back to txpool and then get packed - assert_equal(self.rpc.get_nonce(acct1.address), 3) + assert_equal(self.rpc.get_nonce(acct1.address), 4) if __name__ == "__main__": diff --git a/tests/cip1559_test.py b/tests/cip1559_test.py index ad62cb257d..1472a3d3da 100644 --- a/tests/cip1559_test.py +++ b/tests/cip1559_test.py @@ -96,6 +96,8 @@ def test_balance_change(self, acct: CfxLocalAccount): acct_new_balance = self.rpc.get_balance(acct.address) assert_equal(acct_new_balance, acct_balance - int(receipt["gasFee"], 16) - 100) + # this tests the case for pivot blocks + # as for non-pivot blocks, the tests are in ./cip137_test.py def test_max_fee_not_enough_for_current_base_fee(self): self.change_base_fee(block_count=10) initial_base_fee = self.rpc.base_fee_per_gas() diff --git a/tests/conflux/rpc.py b/tests/conflux/rpc.py index 6ab44a9289..3d5bab1cb2 100644 --- a/tests/conflux/rpc.py +++ b/tests/conflux/rpc.py @@ -145,8 +145,16 @@ def generate_custom_block(self, parent_hash: str, referee: list, txs: list[Union for r in referee: assert_is_hash_string(r) + raw_txs = [] + for tx in txs: + if isinstance(tx, SignedTransaction): + raw_txs.append(tx.rawTransaction) + elif isinstance(tx, Transaction): + raw_txs.append(rlp.encode(tx)) + else: + raise Exception("Unknown transaction type") - encoded_txs = eth_utils.encode_hex(rlp.encode(txs)) + encoded_txs = eth_utils.encode_hex(rlp.encode(raw_txs)) block_hash = self.node.test_generatecustomblock(parent_hash, referee, encoded_txs) assert_is_hash_string(block_hash) From f939b79c80be9a2d2a9bcc7d86c73586db5f35bf Mon Sep 17 00:00:00 2001 From: darwintree <17946284+darwintree@users.noreply.github.com> Date: Mon, 3 Jun 2024 11:13:13 +0800 Subject: [PATCH 10/21] tests: fix unknow tx type rlp encoding --- tests/conflux/rpc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/conflux/rpc.py b/tests/conflux/rpc.py index 3d5bab1cb2..3050c48ced 100644 --- a/tests/conflux/rpc.py +++ b/tests/conflux/rpc.py @@ -152,7 +152,8 @@ def generate_custom_block(self, parent_hash: str, referee: list, txs: list[Union elif isinstance(tx, Transaction): raw_txs.append(rlp.encode(tx)) else: - raise Exception("Unknown transaction type") + raw_txs.append(rlp.encode(tx)) + # raise Exception(f"Unknown transaction type {repr(tx.__class__)}") encoded_txs = eth_utils.encode_hex(rlp.encode(raw_txs)) From 6fb9f8d11d0787883512926b3c0402026cbf4970 Mon Sep 17 00:00:00 2001 From: darwintree <17946284+darwintree@users.noreply.github.com> Date: Mon, 3 Jun 2024 14:33:35 +0800 Subject: [PATCH 11/21] test: add param to cip137 test --- tests/cip137_test.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/tests/cip137_test.py b/tests/cip137_test.py index 80dc1c81e2..a82ff69ddf 100644 --- a/tests/cip137_test.py +++ b/tests/cip137_test.py @@ -21,6 +21,8 @@ class CIP137Test(ConfluxTestFramework): def set_test_params(self): self.num_nodes = 1 self.conf_parameters["min_native_base_price"] = MIN_NATIVE_BASE_PRICE + self.conf_parameters["next_hardfork_transition_height"] = 1 + self.conf_parameters["next_hardfork_transition_number"] = 1 def setup_network(self): self.add_nodes(self.num_nodes) @@ -94,13 +96,7 @@ def log_tx_fee_info(self, tx_hash: str): max_fee_per_gas = int(data["maxFeePerGas"], 16) max_priority_fee_per_gas = int(data["maxPriorityFeePerGas"], 16) receipt = self.rpc.get_transaction_receipt(tx_hash) - # print(receipt) - - # effective_gas_price = int(receipt["effectiveGasPrice"], 16) - # transaction_epoch = int(receipt["epochNumber"],16) - # base_fee_per_gas = self.rpc.base_fee_per_gas(transaction_epoch) - # computed gas price - # priority_fee_per_gas = effective_gas_price - base_fee_per_gas + self.log.info(f'max fee per gas: {max_fee_per_gas}') self.log.info(f'max priority fee per gas: {max_priority_fee_per_gas}') self.log.info(f'tx status: {data["status"]}') @@ -109,8 +105,6 @@ def log_tx_fee_info(self, tx_hash: str): self.log.info(f'tx outcome status: {receipt["outcomeStatus"]}') else: self.log.info(f'tx receipt is None: {receipt is None}') - # self.log.info(f'effective gas price: {effective_gas_price}') - # self.log.info(f'base fee per gas: {base_fee_per_gas}') # continuously fill transaction in C to F to increase base gas fee for F epoch # then transaction in B block will fail @@ -214,11 +208,9 @@ def run_test(self): self.log_tx_fee_info(self.rpc.block_by_hash(block_b)['transactions'][0]) self.log_tx_fee_info(self.rpc.block_by_hash(block_b)['transactions'][1]) - # print(focusing_block["transactions"]) assert_equal(focusing_block["transactions"][0]["status"], "0x0") self.log_tx_fee_info(focusing_block["transactions"][1]["hash"]) assert_equal(focusing_block["transactions"][1]["status"], "0x0") - # assert_equal(focusing_block["transactions"][1]["blockHash"], None) assert_equal(focusing_block["transactions"][2]["status"], None) assert_equal(focusing_block["transactions"][2]["blockHash"], None) assert_equal(focusing_block["transactions"][3]["status"], None) From ad1b5e184f3e62b94055157a01a5af8021c15737 Mon Sep 17 00:00:00 2001 From: darwintree <17946284+darwintree@users.noreply.github.com> Date: Mon, 3 Jun 2024 17:55:05 +0800 Subject: [PATCH 12/21] chore: clean code comments --- tests/cip137_test.py | 10 ++-------- tests/conflux/rpc.py | 3 --- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/tests/cip137_test.py b/tests/cip137_test.py index a82ff69ddf..08ec90e0e0 100644 --- a/tests/cip137_test.py +++ b/tests/cip137_test.py @@ -85,10 +85,7 @@ def init_acct_with_cfx(self, drip: int = 10**21) -> CfxLocalAccount: ) return acct - # TODO: test in pivot block transaction will not be included if burnt_ratio*base_gas_fee_per_gas < max_fee_per_gas < base_gas_fee_per_gas - # low max_fee means the transaction max fee is less than the epoch's base fee * burnt ratio - # def test_transaction_ignored_in_non_pivot_block_if_low_max_fee(self): - + def log_tx_fee_info(self, tx_hash: str): self.log.info(f'tx hash: {tx_hash}') data = self.rpc.get_tx(tx_hash) @@ -106,15 +103,12 @@ def log_tx_fee_info(self, tx_hash: str): else: self.log.info(f'tx receipt is None: {receipt is None}') - # continuously fill transaction in C to F to increase base gas fee for F epoch - # then transaction in B block will fail + def run_test(self): acct1 = self.init_acct_with_cfx() acct2 = self.init_acct_with_cfx() - # assert self.rpc.base_fee_per_gas() < gas_price_level_1 * burnt_ratio - block_p = self.rpc.block_by_epoch("latest_mined")["hash"] gas_price_level_1 = MIN_NATIVE_BASE_PRICE diff --git a/tests/conflux/rpc.py b/tests/conflux/rpc.py index 3050c48ced..0e82337b66 100644 --- a/tests/conflux/rpc.py +++ b/tests/conflux/rpc.py @@ -41,8 +41,6 @@ class CfxFeeHistoryResponse(TypedDict): base_fee_per_gas: list[int] gas_used_ratio: list[float] reward: list[list[str]] # does not convert it currently - -# class TransactionReceipt(TypedDict): def convert_b32_address_field_to_hex(original_dict: dict, field_name: str): @@ -153,7 +151,6 @@ def generate_custom_block(self, parent_hash: str, referee: list, txs: list[Union raw_txs.append(rlp.encode(tx)) else: raw_txs.append(rlp.encode(tx)) - # raise Exception(f"Unknown transaction type {repr(tx.__class__)}") encoded_txs = eth_utils.encode_hex(rlp.encode(raw_txs)) From 10399b68f26b1246a0f80b51a9a7041acb9539fe Mon Sep 17 00:00:00 2001 From: darwintree <17946284+darwintree@users.noreply.github.com> Date: Tue, 4 Jun 2024 14:41:02 +0800 Subject: [PATCH 13/21] refactor: check all transaction fee change computation --- tests/cip137_test.py | 33 ++++++++------------------ tests/cip1559_test.py | 30 +++++++---------------- tests/test_framework/util.py | 46 ++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 44 deletions(-) diff --git a/tests/cip137_test.py b/tests/cip137_test.py index 08ec90e0e0..687f5ddec2 100644 --- a/tests/cip137_test.py +++ b/tests/cip137_test.py @@ -1,3 +1,4 @@ +import math from typing import Union, Tuple from conflux.rpc import RpcClient, default_config from test_framework.util import ( @@ -11,6 +12,7 @@ from test_framework.util import ( generate_blocks_for_base_fee_manipulation, generate_single_block_for_base_fee_manipulation, + assert_correct_fee_computation_for_core_tx, ) MIN_NATIVE_BASE_PRICE = 10000 @@ -84,25 +86,11 @@ def init_acct_with_cfx(self, drip: int = 10**21) -> CfxLocalAccount: True, ) return acct - - - def log_tx_fee_info(self, tx_hash: str): - self.log.info(f'tx hash: {tx_hash}') - data = self.rpc.get_tx(tx_hash) - - max_fee_per_gas = int(data["maxFeePerGas"], 16) - max_priority_fee_per_gas = int(data["maxPriorityFeePerGas"], 16) - receipt = self.rpc.get_transaction_receipt(tx_hash) - - self.log.info(f'max fee per gas: {max_fee_per_gas}') - self.log.info(f'max priority fee per gas: {max_priority_fee_per_gas}') - self.log.info(f'tx status: {data["status"]}') - if receipt: - self.log.info(f'tx block hash: {receipt["blockHash"]}') - self.log.info(f'tx outcome status: {receipt["outcomeStatus"]}') - else: - self.log.info(f'tx receipt is None: {receipt is None}') - + + def get_gas_charged(self, tx_hash: str) -> int: + gas_limit = int(self.rpc.get_tx(tx_hash)["gas"], 16) + gas_used = int(self.rpc.get_transaction_receipt(tx_hash)["gasUsed"], 16) + return max(int(3/4*gas_limit), gas_used) def run_test(self): @@ -198,12 +186,8 @@ def run_test(self): self.log.info(f"burnt_fee_per_gas for epoch {epoch}: {self.rpc.base_fee_per_gas(epoch) * 0.5}") self.log.info(f"least base fee for epoch {epoch}: {self.rpc.base_fee_per_gas(epoch) * BURNT_RATIO}") self.log.info(f"transactions in block b: {self.rpc.block_by_hash(block_b)['transactions']}") - - self.log_tx_fee_info(self.rpc.block_by_hash(block_b)['transactions'][0]) - self.log_tx_fee_info(self.rpc.block_by_hash(block_b)['transactions'][1]) assert_equal(focusing_block["transactions"][0]["status"], "0x0") - self.log_tx_fee_info(focusing_block["transactions"][1]["hash"]) assert_equal(focusing_block["transactions"][1]["status"], "0x0") assert_equal(focusing_block["transactions"][2]["status"], None) assert_equal(focusing_block["transactions"][2]["blockHash"], None) @@ -214,6 +198,9 @@ def run_test(self): assert_equal(focusing_block["transactions"][4]["status"], "0x0") assert_equal(focusing_block["transactions"][5]["status"], "0x0") assert_equal(focusing_block["transactions"][6]["status"], "0x0") + + for tx_hash in self.rpc.block_by_hash(block_b)['transactions']: + assert_correct_fee_computation_for_core_tx(self.rpc, tx_hash, BURNT_RATIO) self.rpc.generate_blocks(20, 5) diff --git a/tests/cip1559_test.py b/tests/cip1559_test.py index 1472a3d3da..bc84e37164 100644 --- a/tests/cip1559_test.py +++ b/tests/cip1559_test.py @@ -8,7 +8,7 @@ from cfx_account.signers.local import LocalAccount as CfxLocalAccount from test_framework.test_framework import ConfluxTestFramework -from test_framework.util import generate_blocks_for_base_fee_manipulation +from test_framework.util import generate_blocks_for_base_fee_manipulation, assert_correct_fee_computation_for_core_tx CORE_BLOCK_GAS_TARGET = 270000 BURNT_RATIO = 0.5 @@ -18,6 +18,8 @@ class CIP1559Test(ConfluxTestFramework): def set_test_params(self): self.num_nodes = 1 self.conf_parameters["min_native_base_price"] = MIN_NATIVE_BASE_PRICE + self.conf_parameters["next_hardfork_transition_height"] = 1 + self.conf_parameters["next_hardfork_transition_number"] = 1 def setup_network(self): @@ -64,22 +66,8 @@ def get_gas_charged(self, tx_hash: str) -> int: gas_limit = int(self.rpc.get_tx(tx_hash)["gas"], 16) gas_used = int(self.rpc.get_transaction_receipt(tx_hash)["gasUsed"], 16) return max(int(3/4*gas_limit), gas_used) - - # TODO: currently only assert transaction in pivot block - # the transactions in non-pivot block is different - def assert_expected_effective_gas_fee(self, tx_hash: str): - data = self.rpc.get_tx(tx_hash) - max_fee_per_gas = int(data["maxFeePerGas"], 16) - max_priority_fee_per_gas = int(data["maxPriorityFeePerGas"], 16) - receipt = self.rpc.get_transaction_receipt(tx_hash) - effective_gas_price = int(receipt["effectiveGasPrice"], 16) - transaction_epoch = int(receipt["epochNumber"],16) - base_fee_per_gas = self.rpc.base_fee_per_gas(transaction_epoch) - # computed gas price - priority_fee_per_gas = effective_gas_price - base_fee_per_gas - assert_equal(priority_fee_per_gas, min(max_priority_fee_per_gas, max_fee_per_gas - base_fee_per_gas)) - assert_equal(int(receipt["gasFee"], 16), effective_gas_price*self.get_gas_charged(tx_hash)) - + + def test_balance_change(self, acct: CfxLocalAccount): acct_balance = self.rpc.get_balance(acct.address) h = self.rpc.send_tx( @@ -125,9 +113,9 @@ def test_max_fee_not_enough_for_current_base_fee(self): self.log.info(f"epoch base fee for transaction accepted: {tx_base_fee}") assert tx_base_fee <= initial_base_fee - def test_type_2_tx(self): + def test_type_2_tx_fees(self): - self.assert_expected_effective_gas_fee(self.rpc.send_tx( + assert_correct_fee_computation_for_core_tx(self.rpc, self.rpc.send_tx( self.rpc.new_typed_tx( receiver=CfxAccount.create().address, max_fee_per_gas=self.rpc.base_fee_per_gas(), @@ -135,7 +123,7 @@ def test_type_2_tx(self): ), wait_for_receipt=True, )) - self.assert_expected_effective_gas_fee(self.rpc.send_tx( + assert_correct_fee_computation_for_core_tx(self.rpc, self.rpc.send_tx( self.rpc.new_typed_tx( receiver=CfxAccount.create().address, max_fee_per_gas=self.rpc.base_fee_per_gas(), @@ -156,7 +144,7 @@ def run_test(self): self.test_block_base_fee_change(self.init_acct_with_cfx(), 10, 1, 13500000) self.test_block_base_fee_change(self.init_acct_with_cfx(), 10, 2, 10000000) - self.test_type_2_tx() + self.test_type_2_tx_fees() self.test_max_fee_not_enough_for_current_base_fee() diff --git a/tests/test_framework/util.py b/tests/test_framework/util.py index 8126c3133b..ad45317fd4 100644 --- a/tests/test_framework/util.py +++ b/tests/test_framework/util.py @@ -21,6 +21,7 @@ from sys import platform import yaml import shutil +import math from test_framework.simple_rpc_proxy import SimpleRpcProxy from . import coverage @@ -848,3 +849,48 @@ def generate_single_block_for_base_fee_manipulation(rpc: "RpcClient", acct: CfxL referee=referee, ) return new_block, starting_nonce + tx_per_block + +# for transactions in either pivot/non-pivot block +# checks priority fee is calculated as expeted +def assert_correct_fee_computation_for_core_tx(rpc: "RpcClient", tx_hash: str, burnt_ratio=0.5): + def get_gas_charged(rpc: "RpcClient", tx_hash: str) -> int: + gas_limit = int(rpc.get_tx(tx_hash)["gas"], 16) + gas_used = int(rpc.get_transaction_receipt(tx_hash)["gasUsed"], 16) + return max(int(3/4*gas_limit), gas_used) + + receipt = rpc.get_transaction_receipt(tx_hash) + # The transaction is not executed + if receipt is None: + return + + tx_data = rpc.get_tx(tx_hash) + tx_type = int(tx_data["type"], 16) + if tx_type == 2: + # original tx fields + max_fee_per_gas = int(tx_data["maxFeePerGas"], 16) + max_priority_fee_per_gas = int(tx_data["maxPriorityFeePerGas"], 16) + else: + max_fee_per_gas = int(tx_data["gasPrice"], 16) + max_priority_fee_per_gas = int(tx_data["gasPrice"], 16) + + effective_gas_price = int(receipt["effectiveGasPrice"], 16) + transaction_epoch = int(receipt["epochNumber"],16) + is_in_pivot_block = rpc.block_by_epoch(transaction_epoch)["hash"] == receipt["blockHash"] + base_fee_per_gas = rpc.base_fee_per_gas(transaction_epoch) + burnt_fee_per_gas = math.ceil(base_fee_per_gas * burnt_ratio) + + # check gas fee computation + assert_equal(int(receipt["gasFee"], 16), effective_gas_price*get_gas_charged(rpc, tx_hash)) + # check burnt fee computation + assert_equal(int(receipt["burntGasFee"], 16), burnt_fee_per_gas*get_gas_charged(rpc, tx_hash)) + + # if max_fee_per_gas >= base_fee_per_gas, it shall follow the computation, regardless of transaction in pivot block or not + if max_fee_per_gas >= base_fee_per_gas: + priority_fee_per_gas = effective_gas_price - base_fee_per_gas + # check priority fee computation + assert_equal(priority_fee_per_gas, min(max_priority_fee_per_gas, max_fee_per_gas - base_fee_per_gas)) + else: + # max fee per gas should be greater than burnt fee per gas + assert is_in_pivot_block == False, "Transaction should be in non-pivot block" + assert max_fee_per_gas >= burnt_fee_per_gas + From d6e84806a29f90a068c856b07832e87c435ecc94 Mon Sep 17 00:00:00 2001 From: darwintree <17946284+darwintree@users.noreply.github.com> Date: Tue, 4 Jun 2024 14:45:14 +0800 Subject: [PATCH 14/21] test: log info --- tests/test_framework/util.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_framework/util.py b/tests/test_framework/util.py index ad45317fd4..760cd5a61f 100644 --- a/tests/test_framework/util.py +++ b/tests/test_framework/util.py @@ -880,6 +880,9 @@ def get_gas_charged(rpc: "RpcClient", tx_hash: str) -> int: burnt_fee_per_gas = math.ceil(base_fee_per_gas * burnt_ratio) # check gas fee computation + print("effective gas price: ", effective_gas_price) + print("gas charged: ", get_gas_charged(rpc, tx_hash)) + print("gas fee", int(receipt["gasFee"], 16)) assert_equal(int(receipt["gasFee"], 16), effective_gas_price*get_gas_charged(rpc, tx_hash)) # check burnt fee computation assert_equal(int(receipt["burntGasFee"], 16), burnt_fee_per_gas*get_gas_charged(rpc, tx_hash)) From 76fc0fd115ef7a7cc83490f313ec430c6a8806a4 Mon Sep 17 00:00:00 2001 From: darwintree <17946284+darwintree@users.noreply.github.com> Date: Tue, 4 Jun 2024 15:43:00 +0800 Subject: [PATCH 15/21] fix: feeHistory field name --- tests/conflux/rpc.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/conflux/rpc.py b/tests/conflux/rpc.py index 0e82337b66..6e86bf0ac5 100644 --- a/tests/conflux/rpc.py +++ b/tests/conflux/rpc.py @@ -38,8 +38,8 @@ } class CfxFeeHistoryResponse(TypedDict): - base_fee_per_gas: list[int] - gas_used_ratio: list[float] + baseFeePerGas: list[int] + gasUsedRatio: list[float] reward: list[list[str]] # does not convert it currently @@ -206,7 +206,7 @@ def gas_price(self) -> int: return int(self.node.cfx_gasPrice(), 0) def base_fee_per_gas(self, epoch: Union[int,str] = "latest_mined"): - return self.fee_history(1, epoch)['base_fee_per_gas'][0] + return self.fee_history(1, epoch)['baseFeePerGas'][-1] def get_block_reward_info(self, epoch: str): reward = self.node.cfx_getBlockRewardInfo(epoch) @@ -653,8 +653,8 @@ def fee_history(self, epoch_count: int, last_epoch: Union[int, str], reward_perc last_epoch = hex(last_epoch) rtn = self.node.cfx_feeHistory(hex(epoch_count), last_epoch, reward_percentiles) rtn[ - 'base_fee_per_gas' - ] = [ int(v, 16) for v in rtn['base_fee_per_gas'] ] + 'baseFeePerGas' + ] = [ int(v, 16) for v in rtn['baseFeePerGas'] ] return rtn From 882722d190e54bd7e26e6f06eb9d547e7eb88f6f Mon Sep 17 00:00:00 2001 From: darwintree <17946284+darwintree@users.noreply.github.com> Date: Wed, 5 Jun 2024 11:53:44 +0800 Subject: [PATCH 16/21] test: add balance relating tests for 1559 --- tests/cip1559_test.py | 61 ++++++++++++++++++++++++++++++++++++ tests/conflux/rpc.py | 2 ++ tests/test_framework/util.py | 21 ++++++++++--- 3 files changed, 80 insertions(+), 4 deletions(-) diff --git a/tests/cip1559_test.py b/tests/cip1559_test.py index bc84e37164..1c08166e27 100644 --- a/tests/cip1559_test.py +++ b/tests/cip1559_test.py @@ -3,6 +3,7 @@ assert_equal, ) from decimal import Decimal +from typing import Literal from cfx_account import Account as CfxAccount from cfx_account.signers.local import LocalAccount as CfxLocalAccount @@ -133,6 +134,63 @@ def test_type_2_tx_fees(self): )) self.test_balance_change(self.init_acct_with_cfx()) + def test_balance_not_enough_for_base_fee(self): + # ensuring acct does not have enough balance to pay for base fee + initial_value = 21000*(MIN_NATIVE_BASE_PRICE-1) + acct = self.init_acct_with_cfx(initial_value) + block = self.rpc.generate_custom_block(parent_hash=self.rpc.block_by_epoch("latest_mined")["hash"], referee=[], txs=[ + self.rpc.new_typed_tx(value=0, gas=21000, priv_key=acct.key.hex()) + ]) + self.rpc.generate_blocks(20, 5) + # self. + # h = self.rpc.send_tx( + # self.rpc.new_typed_tx( + # priv_key=acct.key.hex(), + # max_fee_per_gas=self.rpc.base_fee_per_gas(), + # max_priority_fee_per_gas=self.rpc.base_fee_per_gas(), + # value=0, + # ), + # wait_for_receipt=True, + # ) + tx_data = self.rpc.block_by_hash(block, True)["transactions"][0] + tx_receipt = self.rpc.get_transaction_receipt(tx_data["hash"]) + gas_fee = int(tx_receipt["gasFee"],16) + assert_equal(gas_fee, initial_value) + assert_equal(tx_data["status"],"0x1") + # account balance is all consumed + assert_equal(self.rpc.get_balance(acct.address),0) + + # two cases to test based on balance enough for max priority fee per gas + # maxPriorityFeePerGas = maxFeePerGas <- will fail because balance is not enough for effective_gas_price * gas_charged + # maxPriorityFeePerGas = 0 <- succeed + def test_balance_enough_for_base_fee_but_not_for_max_fee_per_gas(self, priority_fee_setting: Literal["MAX", "ZERO"]): + # ensuring acct does not have enough balance to pay for base fee + self.log.info(f"current base fee: {self.rpc.base_fee_per_gas()}") + assert_equal(self.rpc.base_fee_per_gas(), MIN_NATIVE_BASE_PRICE) + # allow extra 1 priority fee + initial_value = 21000*(MIN_NATIVE_BASE_PRICE+1) + acct = self.init_acct_with_cfx(initial_value) + max_fee_per_gas = MIN_NATIVE_BASE_PRICE+2 + max_priority_fee: int + if priority_fee_setting == "MAX": + max_priority_fee = max_fee_per_gas + elif priority_fee_setting == "ZERO": + max_priority_fee = 0 + block = self.rpc.generate_custom_block(parent_hash=self.rpc.block_by_epoch("latest_mined")["hash"], referee=[], txs=[ + self.rpc.new_typed_tx(value=0, gas=21000, priv_key=acct.key.hex(), max_fee_per_gas=max_fee_per_gas, max_priority_fee_per_gas=max_priority_fee) + ]) + self.rpc.generate_blocks(20, 5) + + tx_data = self.rpc.block_by_hash(block, True)["transactions"][0] + assert_correct_fee_computation_for_core_tx(self.rpc, tx_data["hash"], BURNT_RATIO) + + if priority_fee_setting == "MAX": + # extra test to assert gas fee equal to all of the balance + tx_receipt = self.rpc.get_transaction_receipt(tx_data["hash"]) + gas_fee = int(tx_receipt["gasFee"],16) + assert_equal(gas_fee, initial_value) + + def run_test(self): self.rpc.generate_blocks(5) @@ -146,6 +204,9 @@ def run_test(self): self.test_type_2_tx_fees() self.test_max_fee_not_enough_for_current_base_fee() + self.test_balance_not_enough_for_base_fee() + self.test_balance_enough_for_base_fee_but_not_for_max_fee_per_gas("ZERO") + self.test_balance_enough_for_base_fee_but_not_for_max_fee_per_gas("MAX") if __name__ == "__main__": diff --git a/tests/conflux/rpc.py b/tests/conflux/rpc.py index 6e86bf0ac5..9eb1592ac2 100644 --- a/tests/conflux/rpc.py +++ b/tests/conflux/rpc.py @@ -419,6 +419,8 @@ def new_typed_tx(self, *, type_=2, receiver=None, nonce=None, max_fee_per_gas=No acct = CfxAccount.from_key(priv_key, DEFAULT_PY_TEST_CHAIN_ID) else: acct = CfxAccount.from_key(default_config["GENESIS_PRI_KEY"], DEFAULT_PY_TEST_CHAIN_ID) + if receiver is None: + receiver = self.COINBASE_ADDR tx = {} tx["type"] = type_ tx["gas"] = gas diff --git a/tests/test_framework/util.py b/tests/test_framework/util.py index 760cd5a61f..762cbab472 100644 --- a/tests/test_framework/util.py +++ b/tests/test_framework/util.py @@ -878,14 +878,27 @@ def get_gas_charged(rpc: "RpcClient", tx_hash: str) -> int: is_in_pivot_block = rpc.block_by_epoch(transaction_epoch)["hash"] == receipt["blockHash"] base_fee_per_gas = rpc.base_fee_per_gas(transaction_epoch) burnt_fee_per_gas = math.ceil(base_fee_per_gas * burnt_ratio) + gas_fee = int(receipt["gasFee"], 16) + burnt_gas_fee = int(receipt["burntGasFee"], 16) + gas_charged = get_gas_charged(rpc, tx_hash) # check gas fee computation print("effective gas price: ", effective_gas_price) print("gas charged: ", get_gas_charged(rpc, tx_hash)) - print("gas fee", int(receipt["gasFee"], 16)) - assert_equal(int(receipt["gasFee"], 16), effective_gas_price*get_gas_charged(rpc, tx_hash)) - # check burnt fee computation - assert_equal(int(receipt["burntGasFee"], 16), burnt_fee_per_gas*get_gas_charged(rpc, tx_hash)) + print("gas fee", gas_fee) + + # check gas fee and burnt gas fee computation + if receipt["outcomeStatus"] == "0x1": # tx fails becuase of not enough cash + assert "NotEnoughCash" in receipt["txExecErrorMsg"] + # all gas is charged + print(rpc.get_transaction_trace(tx_hash)) + assert_equal(rpc.get_balance(tx_data["from"], receipt["epochNumber"]), 0) + # gas fee less than effective gas price + assert gas_fee < effective_gas_price*gas_charged + else: + assert_equal(gas_fee, effective_gas_price*gas_charged) + # check burnt fee computation + assert_equal(burnt_gas_fee, burnt_fee_per_gas*gas_charged) # if max_fee_per_gas >= base_fee_per_gas, it shall follow the computation, regardless of transaction in pivot block or not if max_fee_per_gas >= base_fee_per_gas: From 7a04491c0e0e4d9ad9773b583b6e72c4526208d9 Mon Sep 17 00:00:00 2001 From: darwintree <17946284+darwintree@users.noreply.github.com> Date: Wed, 5 Jun 2024 17:44:45 +0800 Subject: [PATCH 17/21] test: fix fee_history rpc implementation --- tests/conflux/rpc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conflux/rpc.py b/tests/conflux/rpc.py index 9eb1592ac2..9e7e789dec 100644 --- a/tests/conflux/rpc.py +++ b/tests/conflux/rpc.py @@ -650,7 +650,7 @@ def filter_trace(self, filter: dict): def fee_history(self, epoch_count: int, last_epoch: Union[int, str], reward_percentiles: Optional[list[float]]=None) -> CfxFeeHistoryResponse: if reward_percentiles is None: - reward_percentiles = [50]*epoch_count + reward_percentiles = [50] if isinstance(last_epoch, int): last_epoch = hex(last_epoch) rtn = self.node.cfx_feeHistory(hex(epoch_count), last_epoch, reward_percentiles) From 9140181339f38e88199aa0645358f93bd990c71e Mon Sep 17 00:00:00 2001 From: darwintree <17946284+darwintree@users.noreply.github.com> Date: Thu, 6 Jun 2024 10:22:18 +0800 Subject: [PATCH 18/21] test: comment debug info --- tests/test_framework/util.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_framework/util.py b/tests/test_framework/util.py index 762cbab472..e2ac3329d9 100644 --- a/tests/test_framework/util.py +++ b/tests/test_framework/util.py @@ -883,15 +883,14 @@ def get_gas_charged(rpc: "RpcClient", tx_hash: str) -> int: gas_charged = get_gas_charged(rpc, tx_hash) # check gas fee computation - print("effective gas price: ", effective_gas_price) - print("gas charged: ", get_gas_charged(rpc, tx_hash)) - print("gas fee", gas_fee) + # print("effective gas price: ", effective_gas_price) + # print("gas charged: ", get_gas_charged(rpc, tx_hash)) + # print("gas fee", gas_fee) # check gas fee and burnt gas fee computation if receipt["outcomeStatus"] == "0x1": # tx fails becuase of not enough cash assert "NotEnoughCash" in receipt["txExecErrorMsg"] # all gas is charged - print(rpc.get_transaction_trace(tx_hash)) assert_equal(rpc.get_balance(tx_data["from"], receipt["epochNumber"]), 0) # gas fee less than effective gas price assert gas_fee < effective_gas_price*gas_charged From d3abe82587899907e726c99ca50f2e0b943e44b0 Mon Sep 17 00:00:00 2001 From: darwintree <17946284+darwintree@users.noreply.github.com> Date: Thu, 6 Jun 2024 11:39:03 +0800 Subject: [PATCH 19/21] test: improve base_fee_per_gas test interface implementation --- tests/conflux/rpc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conflux/rpc.py b/tests/conflux/rpc.py index 9e7e789dec..6f66faa6e0 100644 --- a/tests/conflux/rpc.py +++ b/tests/conflux/rpc.py @@ -206,7 +206,7 @@ def gas_price(self) -> int: return int(self.node.cfx_gasPrice(), 0) def base_fee_per_gas(self, epoch: Union[int,str] = "latest_mined"): - return self.fee_history(1, epoch)['baseFeePerGas'][-1] + return int(self.block_by_epoch(epoch).get("baseFeePerGas", "0x0"), 16) def get_block_reward_info(self, epoch: str): reward = self.node.cfx_getBlockRewardInfo(epoch) From 4076aa8063da006b2ae824a27c42659b622dc634 Mon Sep 17 00:00:00 2001 From: Pana Date: Thu, 6 Jun 2024 16:05:30 +0800 Subject: [PATCH 20/21] fix feeHistory additional block base_fee_per_gas --- crates/client/src/rpc.rs | 3 +- .../client/src/rpc/impls/cfx/cfx_handler.rs | 4 +- crates/client/src/rpc/impls/cfx/common.rs | 35 +++++++++++----- crates/client/src/rpc/impls/cfx/light.rs | 42 +++++++++++-------- .../client/src/rpc/impls/eth/eth_handler.rs | 14 +++++-- crates/client/src/rpc/traits/cfx_space/cfx.rs | 6 +-- crates/client/src/rpc/types.rs | 2 +- .../client/src/rpc/types/cfx/fee_history.rs | 37 ++++++++++++++++ crates/client/src/rpc/types/cfx/mod.rs | 2 + crates/client/src/rpc/types/fee_history.rs | 18 ++++---- 10 files changed, 114 insertions(+), 49 deletions(-) create mode 100644 crates/client/src/rpc/types/cfx/fee_history.rs diff --git a/crates/client/src/rpc.rs b/crates/client/src/rpc.rs index 89ddb3d90a..5d3b0bee85 100644 --- a/crates/client/src/rpc.rs +++ b/crates/client/src/rpc.rs @@ -36,7 +36,8 @@ mod traits; pub mod types; pub use cfxcore::rpc_errors::{ - BoxFuture as RpcBoxFuture, Error as RpcError, ErrorKind as RpcErrorKind, + invalid_params, invalid_params_check, BoxFuture as RpcBoxFuture, + Error as RpcError, ErrorKind as RpcErrorKind, ErrorKind::JsonRpcError as JsonRpcErrorKind, Result as RpcResult, }; diff --git a/crates/client/src/rpc/impls/cfx/cfx_handler.rs b/crates/client/src/rpc/impls/cfx/cfx_handler.rs index d49a200936..41822ae99e 100644 --- a/crates/client/src/rpc/impls/cfx/cfx_handler.rs +++ b/crates/client/src/rpc/impls/cfx/cfx_handler.rs @@ -6,7 +6,7 @@ use crate::rpc::{ error_codes::{internal_error_msg, invalid_params_msg}, types::{ call_request::rpc_call_request_network, - errors::check_rpc_address_network, pos::PoSEpochReward, FeeHistory, + errors::check_rpc_address_network, pos::PoSEpochReward, CfxFeeHistory, PoSEconomics, RpcAddress, SponsorInfo, StatOnGasLoad, TokenSupplyInfo, VoteParamsInfo, WrapTransaction, U64 as HexU64, }, @@ -2272,7 +2272,7 @@ impl Cfx for CfxHandler { fn account_pending_info(&self, addr: RpcAddress) -> BoxFuture>; fn account_pending_transactions(&self, address: RpcAddress, maybe_start_nonce: Option, maybe_limit: Option) -> BoxFuture; fn get_pos_reward_by_epoch(&self, epoch: EpochNumber) -> JsonRpcResult>; - fn fee_history(&self, block_count: HexU64, newest_block: EpochNumber, reward_percentiles: Vec) -> BoxFuture; + fn fee_history(&self, block_count: HexU64, newest_block: EpochNumber, reward_percentiles: Vec) -> BoxFuture; fn max_priority_fee_per_gas(&self) -> BoxFuture; } diff --git a/crates/client/src/rpc/impls/cfx/common.rs b/crates/client/src/rpc/impls/cfx/common.rs index b9ca7168d7..836794ea27 100644 --- a/crates/client/src/rpc/impls/cfx/common.rs +++ b/crates/client/src/rpc/impls/cfx/common.rs @@ -14,10 +14,10 @@ use crate::rpc::{ types::{ errors::check_rpc_address_network, pos::PoSEpochReward, AccountPendingInfo, AccountPendingTransactions, Block as RpcBlock, - BlockHashOrEpochNumber, Bytes, CheckBalanceAgainstTransactionResponse, - EpochNumber, FeeHistory, RpcAddress, Status as RpcStatus, - Transaction as RpcTransaction, TxPoolPendingNonceRange, TxPoolStatus, - TxWithPoolInfo, U64 as HexU64, + BlockHashOrEpochNumber, Bytes, CfxFeeHistory, + CheckBalanceAgainstTransactionResponse, EpochNumber, FeeHistory, + RpcAddress, Status as RpcStatus, Transaction as RpcTransaction, + TxPoolPendingNonceRange, TxPoolStatus, TxWithPoolInfo, U64 as HexU64, }, RpcErrorKind, RpcResult, }; @@ -529,17 +529,25 @@ impl RpcImpl { ) } + // TODO: cache the history to improve performance pub fn fee_history( &self, block_count: HexU64, newest_block: EpochNumber, reward_percentiles: Vec, - ) -> RpcResult { + ) -> RpcResult { + if newest_block == EpochNumber::LatestMined { + return Err(RpcError::invalid_params( + "newestBlock cannot be 'LatestMined'", + ) + .into()); + } + info!( "RPC Request: cfx_feeHistory: block_count={}, newest_block={:?}, reward_percentiles={:?}", block_count, newest_block, reward_percentiles ); if block_count.as_u64() == 0 { - return Ok(FeeHistory::new()); + return Ok(CfxFeeHistory::from(FeeHistory::new())); } // keep read lock to ensure consistent view let inner = self.consensus_graph().inner.read(); @@ -594,21 +602,26 @@ impl RpcImpl { .map_err(|_| RpcError::internal_error())?; if current_height == 0 { - fee_history.finish(0, None, Space::Native); - return Ok(fee_history); + break; } else { current_height -= 1; } } - let block = fetch_block(current_height)?; + // Fetch the block after the last block in the history + let block = fetch_block(start_height + 1)?; + let oldest_block = if current_height == 0 { + 0 + } else { + current_height + 1 + }; fee_history.finish( - current_height + 1, + oldest_block, block.block_header.base_price().as_ref(), Space::Native, ); - Ok(fee_history) + Ok(CfxFeeHistory::from(fee_history)) } pub fn max_priority_fee_per_gas(&self) -> RpcResult { diff --git a/crates/client/src/rpc/impls/cfx/light.rs b/crates/client/src/rpc/impls/cfx/light.rs index 1fb19b00e8..ead3360fa1 100644 --- a/crates/client/src/rpc/impls/cfx/light.rs +++ b/crates/client/src/rpc/impls/cfx/light.rs @@ -42,15 +42,15 @@ use crate::{ pos::{Block as PosBlock, PoSEpochReward}, Account as RpcAccount, AccountPendingInfo, AccountPendingTransactions, BlameInfo, Block as RpcBlock, - BlockHashOrEpochNumber, Bytes, CallRequest, CfxRpcLogFilter, - CheckBalanceAgainstTransactionResponse, ConsensusGraphStates, - EpochNumber, EstimateGasAndCollateralResponse, FeeHistory, - Log as RpcLog, PoSEconomics, Receipt as RpcReceipt, - RewardInfo as RpcRewardInfo, RpcAddress, SendTxRequest, - SponsorInfo, StatOnGasLoad, Status as RpcStatus, - StorageCollateralInfo, SyncGraphStates, TokenSupplyInfo, - Transaction as RpcTransaction, VoteParamsInfo, WrapTransaction, - U64 as HexU64, + BlockHashOrEpochNumber, Bytes, CallRequest, CfxFeeHistory, + CfxRpcLogFilter, CheckBalanceAgainstTransactionResponse, + ConsensusGraphStates, EpochNumber, + EstimateGasAndCollateralResponse, FeeHistory, Log as RpcLog, + PoSEconomics, Receipt as RpcReceipt, RewardInfo as RpcRewardInfo, + RpcAddress, SendTxRequest, SponsorInfo, StatOnGasLoad, + Status as RpcStatus, StorageCollateralInfo, SyncGraphStates, + TokenSupplyInfo, Transaction as RpcTransaction, VoteParamsInfo, + WrapTransaction, U64 as HexU64, }, RpcBoxFuture, RpcResult, }, @@ -1094,14 +1094,18 @@ impl RpcImpl { fn fee_history( &self, block_count: HexU64, newest_block: EpochNumber, reward_percentiles: Vec, - ) -> RpcBoxFuture { + ) -> RpcBoxFuture { info!( "RPC Request: cfx_feeHistory: block_count={}, newest_block={:?}, reward_percentiles={:?}", block_count, newest_block, reward_percentiles ); if block_count.as_u64() == 0 { - return Box::new(async { Ok(FeeHistory::new()) }.boxed().compat()); + return Box::new( + async { Ok(CfxFeeHistory::from(FeeHistory::new())) } + .boxed() + .compat(), + ); } // clone to avoid lifetime issues due to capturing `self` @@ -1145,8 +1149,7 @@ impl RpcImpl { .map_err(|_| RpcError::internal_error())?; if current_height == 0 { - fee_history.finish(0, None, Space::Native); - return Ok(fee_history); + break; } else { current_height -= 1; } @@ -1155,15 +1158,20 @@ impl RpcImpl { let block = fetch_block_for_fee_history( consensus_graph.clone(), light.clone(), - current_height, + start_height + 1, ) .await?; + let oldest_block = if current_height == 0 { + 0 + } else { + current_height + 1 + }; fee_history.finish( - current_height + 1, + oldest_block, block.block_header.base_price().as_ref(), Space::Native, ); - Ok(fee_history) + Ok(CfxFeeHistory::from(fee_history)) }; Box::new(fut.boxed().compat()) @@ -1240,7 +1248,7 @@ impl Cfx for CfxHandler { fn transaction_by_hash(&self, hash: H256) -> BoxFuture>; fn transaction_receipt(&self, tx_hash: H256) -> BoxFuture>; fn vote_list(&self, address: RpcAddress, num: Option) -> BoxFuture>; - fn fee_history(&self, block_count: HexU64, newest_block: EpochNumber, reward_percentiles: Vec) -> BoxFuture; + fn fee_history(&self, block_count: HexU64, newest_block: EpochNumber, reward_percentiles: Vec) -> BoxFuture; } } diff --git a/crates/client/src/rpc/impls/eth/eth_handler.rs b/crates/client/src/rpc/impls/eth/eth_handler.rs index 36dad68811..ad89d6a581 100644 --- a/crates/client/src/rpc/impls/eth/eth_handler.rs +++ b/crates/client/src/rpc/impls/eth/eth_handler.rs @@ -967,16 +967,22 @@ impl Eth for EthHandler { .map_err(|_| RpcError::internal_error())?; if current_height == 0 { - fee_history.finish(0, None, Space::Ethereum); - return Ok(fee_history); + // fee_history.finish(0, None, Space::Ethereum); + // return Ok(fee_history); + break; } else { current_height -= 1; } } - let block = fetch_block(current_height)?; + let block = fetch_block(start_height + 1)?; + let oldest_block = if current_height == 0 { + 0 + } else { + current_height + 1 + }; fee_history.finish( - current_height + 1, + oldest_block, block.pivot_header.base_price().as_ref(), Space::Ethereum, ); diff --git a/crates/client/src/rpc/traits/cfx_space/cfx.rs b/crates/client/src/rpc/traits/cfx_space/cfx.rs index 95de945952..5fe7e9071e 100644 --- a/crates/client/src/rpc/traits/cfx_space/cfx.rs +++ b/crates/client/src/rpc/traits/cfx_space/cfx.rs @@ -5,9 +5,9 @@ use crate::rpc::types::{ pos::PoSEpochReward, Account as RpcAccount, AccountPendingInfo, AccountPendingTransactions, Block, BlockHashOrEpochNumber, Bytes, - CallRequest, CfxFilterChanges, CfxRpcLogFilter, + CallRequest, CfxFeeHistory, CfxFilterChanges, CfxRpcLogFilter, CheckBalanceAgainstTransactionResponse, EpochNumber, - EstimateGasAndCollateralResponse, FeeHistory, Log as RpcLog, PoSEconomics, + EstimateGasAndCollateralResponse, Log as RpcLog, PoSEconomics, Receipt as RpcReceipt, RewardInfo as RpcRewardInfo, RpcAddress, SponsorInfo, Status as RpcStatus, StorageCollateralInfo, TokenSupplyInfo, Transaction, VoteParamsInfo, U64 as HexU64, @@ -205,7 +205,7 @@ pub trait Cfx { fn fee_history( &self, block_count: HexU64, newest_block: EpochNumber, reward_percentiles: Vec, - ) -> BoxFuture; + ) -> BoxFuture; /// Check if user balance is enough for the transaction. #[rpc(name = "cfx_checkBalanceAgainstTransaction")] diff --git a/crates/client/src/rpc/types.rs b/crates/client/src/rpc/types.rs index a5b53b75ee..ef15c2c5b7 100644 --- a/crates/client/src/rpc/types.rs +++ b/crates/client/src/rpc/types.rs @@ -44,7 +44,7 @@ pub use self::{ sign_call, CallRequest, CheckBalanceAgainstTransactionResponse, EstimateGasAndCollateralResponse, SendTxRequest, MAX_GAS_CALL_REQUEST, }, - cfx::{address, address::RpcAddress}, + cfx::{address, address::RpcAddress, CfxFeeHistory}, consensus_graph_states::ConsensusGraphStates, epoch_number::{BlockHashOrEpochNumber, EpochNumber}, fee_history::FeeHistory, diff --git a/crates/client/src/rpc/types/cfx/fee_history.rs b/crates/client/src/rpc/types/cfx/fee_history.rs new file mode 100644 index 0000000000..aafd3a0280 --- /dev/null +++ b/crates/client/src/rpc/types/cfx/fee_history.rs @@ -0,0 +1,37 @@ +use crate::rpc::types::FeeHistory; +use cfx_types::U256; +use std::collections::VecDeque; + +#[derive(Serialize, Debug, Default)] +#[serde(rename_all = "camelCase")] +pub struct CfxFeeHistory { + /// Oldest epoch + oldest_epoch: U256, + /// An array of pivot block base fees per gas. This includes one block + /// earlier than the oldest block. Zeroes are returned for pre-EIP-1559 + /// blocks. + base_fee_per_gas: VecDeque, + /// In Conflux, 1559 is adjusted by the current block's gas limit of total + /// transactions, instead of parent's gas used + gas_used_ratio: VecDeque, + /// A two-dimensional array of effective priority fees per gas at the + /// requested block percentiles. + reward: VecDeque>, +} + +impl CfxFeeHistory { + pub fn new(fee_history: FeeHistory) -> Self { fee_history.into() } + + pub fn reward(&self) -> &VecDeque> { &self.reward } +} + +impl From for CfxFeeHistory { + fn from(fee_history: FeeHistory) -> Self { + Self { + oldest_epoch: fee_history.oldest_block, + base_fee_per_gas: fee_history.base_fee_per_gas, + gas_used_ratio: fee_history.gas_used_ratio, + reward: fee_history.reward, + } + } +} diff --git a/crates/client/src/rpc/types/cfx/mod.rs b/crates/client/src/rpc/types/cfx/mod.rs index 89d8fb6a46..40b328acb8 100644 --- a/crates/client/src/rpc/types/cfx/mod.rs +++ b/crates/client/src/rpc/types/cfx/mod.rs @@ -1,5 +1,7 @@ mod access_list; pub mod address; +mod fee_history; pub use access_list::*; pub use address::RpcAddress; +pub use fee_history::*; diff --git a/crates/client/src/rpc/types/fee_history.rs b/crates/client/src/rpc/types/fee_history.rs index 50a68f1344..55db223be2 100644 --- a/crates/client/src/rpc/types/fee_history.rs +++ b/crates/client/src/rpc/types/fee_history.rs @@ -7,16 +7,16 @@ use primitives::{transaction::SignedTransaction, BlockHeader}; #[serde(rename_all = "camelCase")] pub struct FeeHistory { /// Oldest Block - oldest_block: U256, + pub oldest_block: U256, /// An array of block base fees per gas. This includes one block earlier /// than the oldest block. Zeroes are returned for pre-EIP-1559 blocks. - base_fee_per_gas: VecDeque, + pub base_fee_per_gas: VecDeque, /// In Conflux, 1559 is adjusted by the current block's gas limit of total /// transactions, instead of parent's gas used - gas_used_ratio: VecDeque, + pub gas_used_ratio: VecDeque, /// A two-dimensional array of effective priority fees per gas at the /// requested block percentiles. - reward: VecDeque>, + pub reward: VecDeque>, } impl FeeHistory { @@ -41,9 +41,7 @@ impl FeeHistory { return Ok(()); }; - self.base_fee_per_gas.push_front( - pivot_header.base_price().map_or(U256::zero(), |x| x[space]), - ); + self.base_fee_per_gas.push_front(base_price); let gas_limit: U256 = match space { Space::Native => pivot_header.gas_limit() * 9 / 10, @@ -74,12 +72,12 @@ impl FeeHistory { } pub fn finish( - &mut self, oldest_block: u64, - parent_base_price: Option<&SpaceMap>, space: Space, + &mut self, oldest_block: u64, last_base_price: Option<&SpaceMap>, + space: Space, ) { self.oldest_block = oldest_block.into(); self.base_fee_per_gas - .push_front(parent_base_price.map_or(U256::zero(), |x| x[space])); + .push_back(last_base_price.map_or(U256::zero(), |x| x[space])); } } From 972cd3ec8e2a7d35b31aee0278620a9a1d9754a0 Mon Sep 17 00:00:00 2001 From: Pana Date: Thu, 6 Jun 2024 16:22:51 +0800 Subject: [PATCH 21/21] remove comment --- crates/client/src/rpc/impls/cfx/common.rs | 4 ++-- crates/client/src/rpc/impls/cfx/light.rs | 4 ++-- .../client/src/rpc/impls/eth/eth_handler.rs | 2 -- crates/client/src/rpc/types.rs | 2 +- .../client/src/rpc/types/cfx/fee_history.rs | 24 +++++++++---------- crates/client/src/rpc/types/cfx/mod.rs | 2 +- crates/client/src/rpc/types/fee_history.rs | 19 +++++++++++---- 7 files changed, 32 insertions(+), 25 deletions(-) diff --git a/crates/client/src/rpc/impls/cfx/common.rs b/crates/client/src/rpc/impls/cfx/common.rs index 836794ea27..c4939cfcf9 100644 --- a/crates/client/src/rpc/impls/cfx/common.rs +++ b/crates/client/src/rpc/impls/cfx/common.rs @@ -547,7 +547,7 @@ impl RpcImpl { ); if block_count.as_u64() == 0 { - return Ok(CfxFeeHistory::from(FeeHistory::new())); + return Ok(FeeHistory::new().to_cfx_fee_history()); } // keep read lock to ensure consistent view let inner = self.consensus_graph().inner.read(); @@ -621,7 +621,7 @@ impl RpcImpl { Space::Native, ); - Ok(CfxFeeHistory::from(fee_history)) + Ok(fee_history.to_cfx_fee_history()) } pub fn max_priority_fee_per_gas(&self) -> RpcResult { diff --git a/crates/client/src/rpc/impls/cfx/light.rs b/crates/client/src/rpc/impls/cfx/light.rs index ead3360fa1..3c3c210b81 100644 --- a/crates/client/src/rpc/impls/cfx/light.rs +++ b/crates/client/src/rpc/impls/cfx/light.rs @@ -1102,7 +1102,7 @@ impl RpcImpl { if block_count.as_u64() == 0 { return Box::new( - async { Ok(CfxFeeHistory::from(FeeHistory::new())) } + async { Ok(FeeHistory::new().to_cfx_fee_history()) } .boxed() .compat(), ); @@ -1171,7 +1171,7 @@ impl RpcImpl { block.block_header.base_price().as_ref(), Space::Native, ); - Ok(CfxFeeHistory::from(fee_history)) + Ok(fee_history.to_cfx_fee_history()) }; Box::new(fut.boxed().compat()) diff --git a/crates/client/src/rpc/impls/eth/eth_handler.rs b/crates/client/src/rpc/impls/eth/eth_handler.rs index 5d44559b34..79f94460a3 100644 --- a/crates/client/src/rpc/impls/eth/eth_handler.rs +++ b/crates/client/src/rpc/impls/eth/eth_handler.rs @@ -971,8 +971,6 @@ impl Eth for EthHandler { .map_err(|_| RpcError::internal_error())?; if current_height == 0 { - // fee_history.finish(0, None, Space::Ethereum); - // return Ok(fee_history); break; } else { current_height -= 1; diff --git a/crates/client/src/rpc/types.rs b/crates/client/src/rpc/types.rs index bb55adfb92..02405f90d2 100644 --- a/crates/client/src/rpc/types.rs +++ b/crates/client/src/rpc/types.rs @@ -48,7 +48,7 @@ pub use self::{ EstimateGasAndCollateralResponse, SendTxRequest, MAX_GAS_CALL_REQUEST, }, - CfxFeeHistory + CfxFeeHistory, }, consensus_graph_states::ConsensusGraphStates, epoch_number::{BlockHashOrEpochNumber, EpochNumber}, diff --git a/crates/client/src/rpc/types/cfx/fee_history.rs b/crates/client/src/rpc/types/cfx/fee_history.rs index aafd3a0280..9a0696597d 100644 --- a/crates/client/src/rpc/types/cfx/fee_history.rs +++ b/crates/client/src/rpc/types/cfx/fee_history.rs @@ -1,4 +1,3 @@ -use crate::rpc::types::FeeHistory; use cfx_types::U256; use std::collections::VecDeque; @@ -20,18 +19,17 @@ pub struct CfxFeeHistory { } impl CfxFeeHistory { - pub fn new(fee_history: FeeHistory) -> Self { fee_history.into() } - - pub fn reward(&self) -> &VecDeque> { &self.reward } -} - -impl From for CfxFeeHistory { - fn from(fee_history: FeeHistory) -> Self { - Self { - oldest_epoch: fee_history.oldest_block, - base_fee_per_gas: fee_history.base_fee_per_gas, - gas_used_ratio: fee_history.gas_used_ratio, - reward: fee_history.reward, + pub fn new( + oldest_epoch: U256, base_fee_per_gas: VecDeque, + gas_used_ratio: VecDeque, reward: VecDeque>, + ) -> Self { + CfxFeeHistory { + oldest_epoch, + base_fee_per_gas, + gas_used_ratio, + reward, } } + + pub fn reward(&self) -> &VecDeque> { &self.reward } } diff --git a/crates/client/src/rpc/types/cfx/mod.rs b/crates/client/src/rpc/types/cfx/mod.rs index 2c9a8a2ccf..4a15fecffb 100644 --- a/crates/client/src/rpc/types/cfx/mod.rs +++ b/crates/client/src/rpc/types/cfx/mod.rs @@ -1,7 +1,7 @@ mod access_list; pub mod address; -mod fee_history; pub mod call_request; +mod fee_history; pub use access_list::*; pub use address::RpcAddress; diff --git a/crates/client/src/rpc/types/fee_history.rs b/crates/client/src/rpc/types/fee_history.rs index 55db223be2..d530701597 100644 --- a/crates/client/src/rpc/types/fee_history.rs +++ b/crates/client/src/rpc/types/fee_history.rs @@ -3,20 +3,22 @@ use std::collections::VecDeque; use cfx_types::{Space, SpaceMap, U256}; use primitives::{transaction::SignedTransaction, BlockHeader}; +use super::CfxFeeHistory; + #[derive(Serialize, Debug, Default)] #[serde(rename_all = "camelCase")] pub struct FeeHistory { /// Oldest Block - pub oldest_block: U256, + oldest_block: U256, /// An array of block base fees per gas. This includes one block earlier /// than the oldest block. Zeroes are returned for pre-EIP-1559 blocks. - pub base_fee_per_gas: VecDeque, + base_fee_per_gas: VecDeque, /// In Conflux, 1559 is adjusted by the current block's gas limit of total /// transactions, instead of parent's gas used - pub gas_used_ratio: VecDeque, + gas_used_ratio: VecDeque, /// A two-dimensional array of effective priority fees per gas at the /// requested block percentiles. - pub reward: VecDeque>, + reward: VecDeque>, } impl FeeHistory { @@ -24,6 +26,15 @@ impl FeeHistory { pub fn reward(&self) -> &VecDeque> { &self.reward } + pub fn to_cfx_fee_history(self) -> CfxFeeHistory { + CfxFeeHistory::new( + self.oldest_block, + self.base_fee_per_gas, + self.gas_used_ratio, + self.reward, + ) + } + pub fn push_front_block<'a, I>( &mut self, space: Space, percentiles: &Vec, pivot_header: &BlockHeader, transactions: I,