diff --git a/download.py b/download.py index 7f07af4..81286cf 100644 --- a/download.py +++ b/download.py @@ -11,7 +11,7 @@ import requests from tqdm import tqdm -versions = ['0.109.0', '0.110.2', '0.111.0','0.112.0-rc2'] # Replace with your versions +versions = ['0.109.0', '0.110.2', '0.111.0','0.112.0-rc3'] # Replace with your versions DOWNLOAD_DIR = "download" SYSTEMS = { 'Windows': { diff --git a/framework/helper/miner.py b/framework/helper/miner.py index 81a22e5..35941a5 100644 --- a/framework/helper/miner.py +++ b/framework/helper/miner.py @@ -21,7 +21,7 @@ def make_tip_height_number(node, number): assert current_tip_number == number -def miner_until_tx_committed(node, tx_hash): +def miner_until_tx_committed(node, tx_hash, with_unknown=False): for i in range(100): tx_response = node.getClient().get_transaction(tx_hash) if tx_response['tx_status']['status'] == "committed": @@ -30,6 +30,10 @@ def miner_until_tx_committed(node, tx_hash): miner_with_version(node, "0x0") time.sleep(1) continue + if with_unknown and tx_response['tx_status']['status'] == "unknown": + miner_with_version(node, "0x0") + time.sleep(1) + continue if tx_response['tx_status']['status'] == "rejected" or tx_response['tx_status']['status'] == "unknown": raise Exception(f"status:{tx_response['tx_status']['status']},reason:{tx_response['tx_status']['reason']}") @@ -87,3 +91,28 @@ def get_hex_timestamp(): timestamp = int(time.time() * 1000) hex_timestamp = hex(timestamp) return hex_timestamp + + + + +def compact_to_target(compact): + exponent = compact >> 24 + mantissa = compact & 0x00ffffff + rtn = 0 + if (exponent <= 3): + mantissa >>= (8 * (3 - exponent)) + rtn = mantissa + else: + rtn = mantissa + rtn <<= (8 * (exponent - 3)) + overflow = mantissa != 0 and (exponent > 32) + return rtn, overflow + + +def target_to_compact(target): + bits = (target).bit_length() + exponent = ((bits + 7) // 8) + compact = target << ( + 8 * (3 - exponent)) if exponent <= 3 else (target >> (8 * (exponent - 3))) + compact = (compact | (exponent << 24)) + return compact \ No newline at end of file diff --git a/framework/helper/tx.py b/framework/helper/tx.py index 0b25bed..d0783eb 100644 --- a/framework/helper/tx.py +++ b/framework/helper/tx.py @@ -5,7 +5,7 @@ def send_transfer_self_tx_with_input(input_tx_hash_list, input_tx_index_list, sign_private, data="0x", fee=5000, output_count=1, - api_url="http://127.0.0.1:8114"): + api_url="http://127.0.0.1:8114", dep_cells=[]): # tx file init tmp_tx_file = f"/tmp/demo{time.time()}-{random.randint(0, 100000000)}.json" @@ -34,6 +34,8 @@ def send_transfer_self_tx_with_input(input_tx_hash_list, input_tx_index_list, si tx_add_type_out_put(input_cell_template["lock"]["code_hash"], input_cell_template["lock"]["hash_type"], input_cell_template["lock"]["args"], hex(output_cell_capacity), data, tmp_tx_file, False) + for i in range(len(dep_cells)): + tx_add_cell_dep(dep_cells[i]['tx_hash'],dep_cells[i]['index_hex'],tmp_tx_file) # sign sign_data = tx_sign_inputs(sign_private, tmp_tx_file, api_url) diff --git a/framework/rpc.py b/framework/rpc.py index e2afdaf..8fc074a 100644 --- a/framework/rpc.py +++ b/framework/rpc.py @@ -58,6 +58,9 @@ def get_fee_rate_statics(self, target=None): def generate_epochs(self, epoch): return self.call("generate_epochs", [epoch]) + def generate_block(self): + return self.call("generate_block",[]) + def get_deployments_info(self): return self.call("get_deployments_info", []) diff --git a/test_cases/tx_pool_refactor/test_01_tx_replace_rule.py b/test_cases/tx_pool_refactor/test_01_tx_replace_rule.py index ee4d94f..032c03b 100644 --- a/test_cases/tx_pool_refactor/test_01_tx_replace_rule.py +++ b/test_cases/tx_pool_refactor/test_01_tx_replace_rule.py @@ -1,10 +1,10 @@ +import json import time import pytest from framework.basic import CkbTest - class TestTxReplaceRule(CkbTest): @classmethod @@ -40,23 +40,26 @@ def test_transaction_replacement_with_unconfirmed_inputs_failure(self): TEST_PRIVATE_1 = "0x98400f6a67af07025f5959af35ed653d649f745b8f54bf3f07bef9bd605ee941" account = self.Ckb_cli.util_key_info_by_private_key(TEST_PRIVATE_1) - cell_a_hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.MINER_PRIVATE_1, account["address"]["testnet"], 100, - self.node.getClient().url, "1500") + cell_a_hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.MINER_PRIVATE_1, + account["address"]["testnet"], 100, + self.node.getClient().url, "1500") - cell_c_hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.ACCOUNT_PRIVATE_1, account["address"]["testnet"], 100, - self.node.getClient().url, "1500") + cell_c_hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.ACCOUNT_PRIVATE_1, + account["address"]["testnet"], 100, + self.node.getClient().url, "1500") tx_a_to_b = self.Tx.send_transfer_self_tx_with_input([cell_a_hash], ['0x0'], TEST_PRIVATE_1, data="0x", - fee=5000, output_count=1, - api_url=self.node.getClient().url) + fee=5000, output_count=1, + api_url=self.node.getClient().url) tx_c_to_d = self.Tx.send_transfer_self_tx_with_input([cell_c_hash], ['0x0'], TEST_PRIVATE_1, data="0x", - fee=5000, output_count=1, - api_url=self.node.getClient().url) + fee=5000, output_count=1, + api_url=self.node.getClient().url) with pytest.raises(Exception) as exc_info: - tx_ad_to_b = self.Tx.send_transfer_self_tx_with_input([cell_a_hash, tx_c_to_d], ['0x0', '0x0'], TEST_PRIVATE_1, - data="0x", - fee=5000, output_count=1, - api_url=self.node.getClient().url) + tx_ad_to_b = self.Tx.send_transfer_self_tx_with_input([cell_a_hash, tx_c_to_d], ['0x0', '0x0'], + TEST_PRIVATE_1, + data="0x", + fee=5000, output_count=1, + api_url=self.node.getClient().url) expected_error_message = "RBF rejected: new Tx contains unconfirmed inputs`" assert expected_error_message in exc_info.value.args[0], \ f"Expected substring '{expected_error_message}' " \ @@ -72,22 +75,25 @@ def test_transaction_replacement_with_confirmed_inputs_successful(self): TEST_PRIVATE_1 = "0x98400f6a67af07025f5959af35ed653d649f745b8f54bf3f07bef9bd605ee941" account = self.Ckb_cli.util_key_info_by_private_key(TEST_PRIVATE_1) - cell_a_hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.MINER_PRIVATE_1, account["address"]["testnet"], 100, - self.node.getClient().url, "1500") + cell_a_hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.MINER_PRIVATE_1, + account["address"]["testnet"], 100, + self.node.getClient().url, "1500") - cell_c_hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.ACCOUNT_PRIVATE_1, account["address"]["testnet"], 100, - self.node.getClient().url, "1500") + cell_c_hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.ACCOUNT_PRIVATE_1, + account["address"]["testnet"], 100, + self.node.getClient().url, "1500") tx_a_to_b = self.Tx.send_transfer_self_tx_with_input([cell_a_hash], ['0x0'], TEST_PRIVATE_1, data="0x", - fee=5000, output_count=1, - api_url=self.node.getClient().url) + fee=5000, output_count=1, + api_url=self.node.getClient().url) tx_c_to_d = self.Tx.send_transfer_self_tx_with_input([cell_c_hash], ['0x0'], TEST_PRIVATE_1, data="0x", - fee=5000, output_count=1, - api_url=self.node.getClient().url) + fee=5000, output_count=1, + api_url=self.node.getClient().url) - tx_ac_to_b = self.Tx.send_transfer_self_tx_with_input([cell_a_hash, cell_c_hash], ['0x0', '0x0'], TEST_PRIVATE_1, - data="0x", - fee=15000, output_count=1, - api_url=self.node.getClient().url) + tx_ac_to_b = self.Tx.send_transfer_self_tx_with_input([cell_a_hash, cell_c_hash], ['0x0', '0x0'], + TEST_PRIVATE_1, + data="0x", + fee=15000, output_count=1, + api_url=self.node.getClient().url) tx_a_to_b_response = self.node.getClient().get_transaction(tx_a_to_b) assert tx_a_to_b_response['tx_status']['status'] == 'rejected' @@ -112,12 +118,13 @@ def test_transaction_fee_equal_to_old_fee(self): :return: """ account = self.Ckb_cli.util_key_info_by_private_key(self.Config.MINER_PRIVATE_1) - hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.MINER_PRIVATE_1, account["address"]["testnet"], 100, - self.node.getClient().url, "5000") + hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.MINER_PRIVATE_1, account["address"]["testnet"], + 100, + self.node.getClient().url, "5000") self.node.getClient().get_raw_tx_pool(True) with pytest.raises(Exception) as exc_info: self.Ckb_cli.wallet_transfer_by_private_key(self.Config.MINER_PRIVATE_1, account["address"]["testnet"], 101, - self.node.getClient().url, "5000") + self.node.getClient().url, "5000") self.node.getClient().get_raw_tx_pool(True) expected_error_message = "PoolRejectedRBF" @@ -147,13 +154,16 @@ def test_transaction_replacement_higher_fee(self): :return: """ account = self.Ckb_cli.util_key_info_by_private_key(self.Config.MINER_PRIVATE_1) - tx_hash1 = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.MINER_PRIVATE_1, account["address"]["testnet"], 100, - self.node.getClient().url, "1500") - tx_hash2 = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.MINER_PRIVATE_1, account["address"]["testnet"], 200, - self.node.getClient().url, "3000") - - tx_hash3 = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.MINER_PRIVATE_1, account["address"]["testnet"], 300, - self.node.getClient().url, "6000") + tx_hash1 = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.MINER_PRIVATE_1, + account["address"]["testnet"], 100, + self.node.getClient().url, "1500") + tx_hash2 = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.MINER_PRIVATE_1, + account["address"]["testnet"], 200, + self.node.getClient().url, "3000") + + tx_hash3 = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.MINER_PRIVATE_1, + account["address"]["testnet"], 300, + self.node.getClient().url, "6000") tx1_response = self.node.getClient().get_transaction(tx_hash1) tx2_response = self.node.getClient().get_transaction(tx_hash2) tx3_response = self.node.getClient().get_transaction(tx_hash3) @@ -177,15 +187,16 @@ def test_transaction_replacement_min_replace_fee(self): :return: """ account = self.Ckb_cli.util_key_info_by_private_key(self.Config.MINER_PRIVATE_1) - tx_hash1 = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.MINER_PRIVATE_1, account["address"]["testnet"], 100, - self.node.getClient().url, "1500") + tx_hash1 = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.MINER_PRIVATE_1, + account["address"]["testnet"], 100, + self.node.getClient().url, "1500") tx_hash2 = self.Tx.send_transfer_self_tx_with_input([tx_hash1], ['0x0'], self.Config.MINER_PRIVATE_1, fee=1500, - api_url=self.node.getClient().url) + api_url=self.node.getClient().url) transaction = self.node.getClient().get_transaction(tx_hash2) tx_hash3 = self.Tx.send_transfer_self_tx_with_input([tx_hash1], ['0x0'], self.Config.MINER_PRIVATE_1, - fee=int(transaction['min_replace_fee'], 16), - api_url=self.node.getClient().url) + fee=int(transaction['min_replace_fee'], 16), + api_url=self.node.getClient().url) tx2_response = self.node.getClient().get_transaction(tx_hash2) tx3_response = self.node.getClient().get_transaction(tx_hash3) @@ -211,26 +222,29 @@ def test_tx_conflict_too_many_txs(self): :return: """ account = self.Ckb_cli.util_key_info_by_private_key(self.Config.ACCOUNT_PRIVATE_1) - tx_hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.ACCOUNT_PRIVATE_1, account["address"]["testnet"], 360000, - self.node.getClient().url, "2800") + tx_hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.ACCOUNT_PRIVATE_1, + account["address"]["testnet"], 360000, + self.node.getClient().url, "2800") first_hash = tx_hash self.Node.wait_get_transaction(self.node, tx_hash, "pending") tx_list = [] for i in range(100): - tx_hash = self.Tx.send_transfer_self_tx_with_input([tx_hash], ["0x0"], self.Config.ACCOUNT_PRIVATE_1, fee=1000, - api_url=self.node.getClient().url) + tx_hash = self.Tx.send_transfer_self_tx_with_input([tx_hash], ["0x0"], self.Config.ACCOUNT_PRIVATE_1, + fee=1000, + api_url=self.node.getClient().url) tx_list.append(tx_hash) self.Node.wait_get_transaction(self.node, tx_hash, "pending") with pytest.raises(Exception) as exc_info: - self.Ckb_cli.wallet_transfer_by_private_key(self.Config.ACCOUNT_PRIVATE_1, account["address"]["testnet"], 360000, - self.node.getClient().url, "8800") + self.Ckb_cli.wallet_transfer_by_private_key(self.Config.ACCOUNT_PRIVATE_1, account["address"]["testnet"], + 360000, + self.node.getClient().url, "8800") expected_error_message = "Server error: PoolRejectedRBF: RBF rejected: Tx conflict too many txs, conflict txs count: 101" assert expected_error_message in exc_info.value.args[0], \ f"Expected substring '{expected_error_message}' " \ f"not found in actual string '{exc_info.value.args[0]}'" self.Tx.send_transfer_self_tx_with_input([first_hash], ["0x0"], self.Config.ACCOUNT_PRIVATE_1, fee=5000, - api_url=self.node.getClient().url) + api_url=self.node.getClient().url) tx_pool = self.node.getClient().get_raw_tx_pool(True) assert len(tx_pool['pending'].keys()) == 2 @@ -250,12 +264,14 @@ def test_replace_pending_transaction_successful(self): """ account = self.Ckb_cli.util_key_info_by_private_key(self.Config.MINER_PRIVATE_1) - tx_a = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.MINER_PRIVATE_1, account["address"]["testnet"], 100, - self.node.getClient().url, "1500") + tx_a = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.MINER_PRIVATE_1, account["address"]["testnet"], + 100, + self.node.getClient().url, "1500") tx_a_response = self.node.getClient().get_transaction(tx_a) assert tx_a_response['tx_status']['status'] == 'pending' - tx_b = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.MINER_PRIVATE_1, account["address"]["testnet"], 200, - self.node.getClient().url, "12000") + tx_b = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.MINER_PRIVATE_1, account["address"]["testnet"], + 200, + self.node.getClient().url, "12000") tx_b_response = self.node.getClient().get_transaction(tx_b) assert tx_b_response['tx_status']['status'] == 'pending' @@ -270,21 +286,30 @@ def test_replace_proposal_transaction_failure(self): 1. Send a transaction and submit it to the proposal. Query the transaction status as 'proposal.' 2. Replace the transaction for that proposal. - ERROR: RBF rejected: all conflict Txs should be in Pending status + successful + 3. get_block_template + contains proposal that is removed + 4. generate empty block + successful + 5. get_block_template + cant contains proposal that is removed :return: """ account = self.Ckb_cli.util_key_info_by_private_key(self.Config.ACCOUNT_PRIVATE_2) - tx_hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.ACCOUNT_PRIVATE_2, account["address"]["testnet"], 360000, - api_url=self.node.getClient().url, fee_rate="1000") + tx_hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.ACCOUNT_PRIVATE_2, + account["address"]["testnet"], 360000, + api_url=self.node.getClient().url, fee_rate="1000") - tx_hash = self.Tx.send_transfer_self_tx_with_input([tx_hash], ["0x0"], self.Config.ACCOUNT_PRIVATE_2, output_count=1, - fee=1000, - api_url=self.node.getClient().url) + tx_hash = self.Tx.send_transfer_self_tx_with_input([tx_hash], ["0x0"], self.Config.ACCOUNT_PRIVATE_2, + output_count=1, + fee=1000, + api_url=self.node.getClient().url) self.Node.wait_get_transaction(self.node, tx_hash, 'pending') tx_list = [tx_hash] for i in range(50): - tx_hash = self.Tx.send_transfer_self_tx_with_input([tx_hash], ['0x0'], self.Config.ACCOUNT_PRIVATE_2, output_count=1, fee=1000, - api_url=self.node.getClient().url) + tx_hash = self.Tx.send_transfer_self_tx_with_input([tx_hash], ['0x0'], self.Config.ACCOUNT_PRIVATE_2, + output_count=1, fee=1000, + api_url=self.node.getClient().url) tx_list.append(tx_hash) self.Miner.miner_with_version(self.node, "0x0") self.Miner.miner_with_version(self.node, "0x0") @@ -298,16 +323,28 @@ def test_replace_proposal_transaction_failure(self): tx_response = self.node.getClient().get_transaction(tx) assert tx_response['tx_status']['status'] == 'proposed' tx_response = self.node.getClient().get_transaction(proposal_txs[0]) - with pytest.raises(Exception) as exc_info: - self.Tx.send_transfer_self_tx_with_input( - [tx_response['transaction']['inputs'][0]['previous_output']['tx_hash']], ['0x0'], self.Config.ACCOUNT_PRIVATE_2, - output_count=1, - fee=2000, - api_url=self.node.getClient().url) - expected_error_message = "RBF rejected: all conflict Txs should be in Pending status" - assert expected_error_message in exc_info.value.args[0], \ - f"Expected substring '{expected_error_message}' " \ - f"not found in actual string '{exc_info.value.args[0]}'" + # with pytest.raises(Exception) as exc_info: + + # expected_error_message = "RBF rejected: all conflict Txs should be in Pending status" + # assert expected_error_message in exc_info.value.args[0], \ + # f"Expected substring '{expected_error_message}' " \ + # f"not found in actual string '{exc_info.value.args[0]}'" + + replace_proposal_hash = self.Tx.send_transfer_self_tx_with_input( + [tx_response['transaction']['inputs'][0]['previous_output']['tx_hash']], ['0x0'], + self.Config.ACCOUNT_PRIVATE_2, + output_count=1, + fee=2000, + api_url=self.node.getClient().url) + + time.sleep(5) + block_template = self.node.getClient().get_block_template() + print(block_template) + tx_response = self.node.getClient().get_transaction(proposal_txs[0]) + assert "RBFRejected" in tx_response['tx_status']['reason'] + self.node.getClient().generate_block() + block_template = self.node.getClient().get_block_template() + assert not proposal_txs[0] in json.dumps(block_template) # proposal_tx_response = self.node.getClient().get_transaction(proposal_txs[0]) # tx_response = self.node.getClient().get_transaction(tx_hash) @@ -335,21 +372,26 @@ def test_send_transaction_duplicate_input_with_son_tx(self): :return: """ account = self.Ckb_cli.util_key_info_by_private_key(self.Config.ACCOUNT_PRIVATE_1) - tx_hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.ACCOUNT_PRIVATE_1, account["address"]["testnet"], 360000, - api_url=self.node.getClient().url, fee_rate="1000") + tx_hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.ACCOUNT_PRIVATE_1, + account["address"]["testnet"], 360000, + api_url=self.node.getClient().url, fee_rate="1000") first_tx_hash = tx_hash tx_list = [first_tx_hash] - tx_hash = self.Tx.send_transfer_self_tx_with_input([tx_hash], ["0x0"], self.Config.ACCOUNT_PRIVATE_1, output_count=1, - fee=1000, - api_url=self.node.getClient().url) + tx_hash = self.Tx.send_transfer_self_tx_with_input([tx_hash], ["0x0"], self.Config.ACCOUNT_PRIVATE_1, + output_count=1, + fee=1000, + api_url=self.node.getClient().url) tx_list.append(tx_hash) self.Node.wait_get_transaction(self.node, tx_hash, 'pending') for i in range(5): - tx_hash = self.Tx.send_transfer_self_tx_with_input([tx_hash], ['0x0'], self.Config.ACCOUNT_PRIVATE_1, output_count=1, fee=1000, - api_url=self.node.getClient().url) + tx_hash = self.Tx.send_transfer_self_tx_with_input([tx_hash], ['0x0'], self.Config.ACCOUNT_PRIVATE_1, + output_count=1, fee=1000, + api_url=self.node.getClient().url) tx_list.append(tx_hash) - replace_tx_hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.ACCOUNT_PRIVATE_1, account["address"]["testnet"], 360000, - api_url=self.node.getClient().url, fee_rate="10000") + replace_tx_hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.ACCOUNT_PRIVATE_1, + account["address"]["testnet"], 360000, + api_url=self.node.getClient().url, + fee_rate="10000") tx_pool = self.node.getClient().get_raw_tx_pool(True) assert len(tx_pool['pending']) == 1 @@ -378,21 +420,22 @@ def test_min_replace_fee_unchanged_with_child_tx(self): :return: """ account = self.Ckb_cli.util_key_info_by_private_key(self.Config.ACCOUNT_PRIVATE_1) - tx_hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.ACCOUNT_PRIVATE_1, account["address"]["testnet"], 360000, - self.node.getClient().url, "2800") + tx_hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.ACCOUNT_PRIVATE_1, + account["address"]["testnet"], 360000, + self.node.getClient().url, "2800") first_hash = tx_hash self.Node.wait_get_transaction(self.node, tx_hash, "pending") tx_list = [] tx_hash1 = self.Tx.send_transfer_self_tx_with_input([tx_hash], ["0x0"], self.Config.ACCOUNT_PRIVATE_1, fee=1000, - api_url=self.node.getClient().url) + api_url=self.node.getClient().url) transaction1 = self.node.getClient().get_transaction(tx_hash1) self.Tx.send_transfer_self_tx_with_input([tx_hash1], ["0x0"], self.Config.ACCOUNT_PRIVATE_1, fee=1000, - api_url=self.node.getClient().url) + api_url=self.node.getClient().url) after_transaction1 = self.node.getClient().get_transaction(tx_hash1) assert after_transaction1['min_replace_fee'] == transaction1['min_replace_fee'] replace_tx_hash = self.Tx.send_transfer_self_tx_with_input([tx_hash], ["0x0"], self.Config.ACCOUNT_PRIVATE_1, - fee=int(transaction1['min_replace_fee'], 16), - api_url=self.node.getClient().url) + fee=int(transaction1['min_replace_fee'], 16), + api_url=self.node.getClient().url) transaction = self.node.getClient().get_transaction(replace_tx_hash) assert transaction['tx_status']['status'] == 'pending' @@ -405,12 +448,14 @@ def test_min_replace_fee_exceeds_1_ckb(self): :return: """ account = self.Tx.util_key_info_by_private_key(self.Config.ACCOUNT_PRIVATE_1) - tx_hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.ACCOUNT_PRIVATE_1, account["address"]["testnet"], 360000, - self.node.getClient().url, "2800") + tx_hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.ACCOUNT_PRIVATE_1, + account["address"]["testnet"], 360000, + self.node.getClient().url, "2800") first_hash = tx_hash self.Node.wait_get_transaction(self.node, tx_hash, "pending") tx_list = [] - tx_hash1 = self.Tx.send_transfer_self_tx_with_input([tx_hash], ["0x0"], self.Config.ACCOUNT_PRIVATE_1, fee=99999999, - api_url=self.node.getClient().url) + tx_hash1 = self.Tx.send_transfer_self_tx_with_input([tx_hash], ["0x0"], self.Config.ACCOUNT_PRIVATE_1, + fee=99999999, + api_url=self.node.getClient().url) transaction = self.node.getClient().get_transaction(tx_hash1) assert int(transaction['min_replace_fee'], 16) > 99999999 diff --git a/test_cases/tx_pool_refactor/test_07_replace_dep_father_tx.py b/test_cases/tx_pool_refactor/test_07_replace_dep_father_tx.py new file mode 100644 index 0000000..bfc5970 --- /dev/null +++ b/test_cases/tx_pool_refactor/test_07_replace_dep_father_tx.py @@ -0,0 +1,429 @@ +import time + +import pytest + +from framework.basic import CkbTest +from framework.util import run_command + + +class TestDepTx(CkbTest): + + @classmethod + def setup_class(cls): + cls.node1 = cls.CkbNode.init_dev_by_port(cls.CkbNodeConfigPath.CURRENT_TEST, "node/node1", 8114, 8115) + cls.node2 = cls.CkbNode.init_dev_by_port(cls.CkbNodeConfigPath.CURRENT_TEST, "node/node2", 8116, 8117) + + cls.node1.prepare() + cls.node1.start() + cls.node2.prepare() + cls.node2.start() + cls.node2.connected(cls.node1) + cls.Miner.make_tip_height_number(cls.node1, 300) + cls.Node.wait_node_height(cls.node2, 300, 300) + + @classmethod + def teardown_class(cls): + cls.node1.stop() + cls.node1.clean() + cls.node2.stop() + cls.node2.clean() + + def test_01_send_sub_transaction_replace_dep_fail(self): + """ + 发送一笔子交易,dep 的cell 为会被替换的子交易,替换失败 + 1. 发送一笔父交易 + 2. 发送一笔子交易 + 3. 发送另外一笔子交易,dep 为会被替换的子交易 + 发送失败 + Exception: Send transaction error: jsonrpc error: `Server error: PoolRejectedRBF: RBF rejected: new Tx contains cell deps from conflicts` + + """ + # 发送一笔交易 + account = self.Ckb_cli.util_key_info_by_private_key(self.Config.ACCOUNT_PRIVATE_1) + account_private = self.Config.ACCOUNT_PRIVATE_1 + tx1_hash = self.Ckb_cli.wallet_transfer_by_private_key(account_private, + account["address"]["testnet"], 1000000, + self.node1.getClient().url, "1500000") + + # 发送一笔子交易 + tx_hash = self.Tx.send_transfer_self_tx_with_input([tx1_hash], ["0x0"], account_private, + output_count=2, + fee=1090, + api_url=self.node1.getClient().url) + + # 发送另外一笔子交易,dep 为会被替换的子交易 + with pytest.raises(Exception) as exc_info: + self.Tx.send_transfer_self_tx_with_input([tx1_hash], ["0x0"], account_private, + output_count=2, + fee=20900, + api_url=self.node1.getClient().url, + dep_cells=[{ + "tx_hash": tx_hash, "index_hex": "0x0" + }] + ) + + expected_error_message = "new Tx contains cell deps from conflicts" + assert expected_error_message in exc_info.value.args[0], \ + f"Expected substring '{expected_error_message}' " \ + f"not found in actual string '{exc_info.value.args[0]}'" + self.node1.getClient().clear_tx_pool() + + def test_send_child_transaction_with_unreplaced_dep(self): + """ + Test case to send a child transaction with an unspent cell (dep) and successfully replace it + + 1. Send 2 transactions: tx1 and tx2 + send successful + 2. Send a child transaction: tx11 + send successful + 3. Send a transaction with dep.cell = tx2.output and replace child transaction tx11 + send successful + 4. query tx11 status + return rejected + """ + # 1. Send 2 transactions: tx1 and tx2 + account = self.Ckb_cli.util_key_info_by_private_key(self.Config.ACCOUNT_PRIVATE_1) + account_private = self.Config.ACCOUNT_PRIVATE_1 + tx1_hash = self.Ckb_cli.wallet_transfer_by_private_key(account_private, + account["address"]["testnet"], 1000000, + self.node1.getClient().url, "1500000") + + account2 = self.Ckb_cli.util_key_info_by_private_key(self.Config.ACCOUNT_PRIVATE_2) + account_private2 = self.Config.ACCOUNT_PRIVATE_2 + tx2_hash = self.Ckb_cli.wallet_transfer_by_private_key(account_private2, + account2["address"]["testnet"], 1000000, + self.node1.getClient().url, "1500000") + + # 2. Send a child transaction: tx11 + tx_hash = self.Tx.send_transfer_self_tx_with_input([tx1_hash], ["0x0"], account_private, + output_count=2, + fee=1090, + api_url=self.node1.getClient().url) + + # 3. Send a transaction with dep.cell = tx2.output and replace child transaction tx11 + replace_hash = self.Tx.send_transfer_self_tx_with_input([tx1_hash], ["0x0"], account_private, + output_count=2, + fee=20900, + api_url=self.node1.getClient().url, + dep_cells=[{ + "tx_hash": tx2_hash, "index_hex": "0x0" + }] + ) + + # 4. query tx11 status + result = self.node1.getClient().get_transaction(tx_hash) + assert result['tx_status']['status'] == "rejected" + self.Miner.miner_until_tx_committed(self.node1, replace_hash, True) + + def test_conflicting_sub_transaction(self): + """ + Send another sub transaction where dep and input are the same, and there is a conflicting transaction with the pool, + + 1. Send a transaction: tx1 + send successful + 2. Send a sub transaction: tx11 + send successful + 3. Send another sub transaction, dep = tx1.output, input = tx1.output ,will replace tx11 + replace successful + 4. query tx11 status + tx11.status == rejected + """ + # 1. Send a transaction: tx1 + account = self.Ckb_cli.util_key_info_by_private_key(self.Config.ACCOUNT_PRIVATE_1) + account_private = self.Config.ACCOUNT_PRIVATE_1 + tx1_hash = self.Ckb_cli.wallet_transfer_by_private_key(account_private, + account["address"]["testnet"], 1000000, + self.node1.getClient().url, "1500000") + + # 2. Send a sub transaction: tx11 + tx_hash = self.Tx.send_transfer_self_tx_with_input([tx1_hash], ["0x0"], account_private, + output_count=2, + fee=1090, + api_url=self.node1.getClient().url) + + # 3. Send another sub transaction, dep = tx1.output, input = tx1.output + replace_hash = self.Tx.send_transfer_self_tx_with_input([tx1_hash], ["0x0"], account_private, + output_count=2, + fee=20900, + api_url=self.node1.getClient().url, + dep_cells=[{ + "tx_hash": tx1_hash, "index_hex": "0x0" + }] + ) + + result = self.node1.getClient().get_transaction(tx_hash) + assert result['tx_status']['status'] == "rejected" + self.Miner.miner_until_tx_committed(self.node1, replace_hash, True) + + def test_replacing_sub_tx_rejects_dependent_txs(self): + """ + When a sub transaction is replaced, other transactions in the pool referencing + the output of the sub transaction as dep will also be rejected. + + 1. Send 2 transactions: tx1, tx2 + + 2. Send a sub transaction: tx11 + + 3. Send a transaction: tx21 (dep = tx11.output) + + 4. Send a transaction: will replace the previous tx11 + + 5. Query pool: the previous tx11 and tx21 will be rejected + """ + + # Send 2 transactions: tx1, tx2 + account = self.Ckb_cli.util_key_info_by_private_key(self.Config.ACCOUNT_PRIVATE_1) + account_private = self.Config.ACCOUNT_PRIVATE_1 + tx1_hash = self.Ckb_cli.wallet_transfer_by_private_key(account_private, + account["address"]["testnet"], 1000000, + self.node1.getClient().url, "1500000") + + account2 = self.Ckb_cli.util_key_info_by_private_key(self.Config.ACCOUNT_PRIVATE_2) + account_private2 = self.Config.ACCOUNT_PRIVATE_2 + tx2_hash = self.Ckb_cli.wallet_transfer_by_private_key(account_private2, + account2["address"]["testnet"], 1000000, + self.node1.getClient().url, "1500000") + + # Send a sub transaction: tx11 + tx_hash = self.Tx.send_transfer_self_tx_with_input([tx1_hash], ["0x0"], account_private, + output_count=2, + fee=1090, + api_url=self.node1.getClient().url) + + # Send a transaction: tx21 (dep = tx11.output) + dep_is_tx_hash = self.Tx.send_transfer_self_tx_with_input([tx2_hash], ["0x0"], account_private2, + output_count=2, + fee=20900, + api_url=self.node1.getClient().url, + dep_cells=[{ + "tx_hash": tx_hash, "index_hex": "0x0" + }] + ) + + # Send a transaction: will replace the previous tx11 + replace_hash = self.Tx.send_transfer_self_tx_with_input([tx1_hash], ["0x0"], account_private, + output_count=2, + fee=20900, + api_url=self.node1.getClient().url, + dep_cells=[{ + "tx_hash": tx1_hash, "index_hex": "0x0" + }] + ) + + # Query pool: the previous tx11 and tx21 will be rejected + + result = self.node1.getClient().get_transaction(tx_hash) + assert result['tx_status']['status'] == "rejected" + result = self.node1.getClient().get_transaction(dep_is_tx_hash) + assert result['tx_status']['status'] == "rejected" + self.Miner.miner_until_tx_committed(self.node1, replace_hash, True) + result = self.node1.getClient().get_transaction(tx2_hash) + assert result['tx_status']['status'] == 'committed' + + def test_tx_with_pool_dep_cell_as_input_mines_successfully(self): + """ + Send a transaction where the input is a dep cell from another transaction in the pool -> send succeeds + + 1. Send 2 transactions: tx1, tx2 + + 2. Send a sub transaction: tx11 + + 3. Send tx111, dep = tx2.output + + 4. Send tx21, which will use up tx2.output + + 5. Query if tx111 was mined successfully + """ + # 1. Send 2 transactions: tx1, tx2 + account = self.Ckb_cli.util_key_info_by_private_key(self.Config.ACCOUNT_PRIVATE_1) + account_private = self.Config.ACCOUNT_PRIVATE_1 + tx1_hash = self.Ckb_cli.wallet_transfer_by_private_key(account_private, + account["address"]["testnet"], 1000000, + self.node1.getClient().url, "1500000") + + account2 = self.Ckb_cli.util_key_info_by_private_key(self.Config.ACCOUNT_PRIVATE_2) + account_private2 = self.Config.ACCOUNT_PRIVATE_2 + tx2_hash = self.Ckb_cli.wallet_transfer_by_private_key(account_private2, + account2["address"]["testnet"], 1000000, + self.node1.getClient().url, "1500000") + + # 2. Send a sub transaction: tx11 + tx_hash = self.Tx.send_transfer_self_tx_with_input([tx1_hash], ["0x0"], account_private, + output_count=2, + fee=1090, + api_url=self.node1.getClient().url) + + # 3. Send tx111, dep = tx2.output + + dep_cell_hash = self.Tx.send_transfer_self_tx_with_input([tx_hash], ["0x0"], account_private, + output_count=2, + fee=1090, + api_url=self.node1.getClient().url, + dep_cells=[{ + "tx_hash": tx2_hash, "index_hex": "0x0" + }]) + + # Send tx21, which will use up tx2.output + input_cell_hash = self.Tx.send_transfer_self_tx_with_input([tx2_hash], ["0x0"], account_private2, + output_count=2, + fee=1090, + api_url=self.node1.getClient().url) + + # Query if tx111 was mined successfully + self.Miner.miner_until_tx_committed(self.node1, input_cell_hash, True) + self.Miner.miner_until_tx_committed(self.node1, dep_cell_hash, True) + + def test_tx_with_spent_dep_cell_from_pool_fails(self): + """ + Send a transaction where the dep cell has been spent by another transaction in the pool -> send fails + + 1. Send 2 transactions: tx1, tx2 + + 2. Send a sub transaction: tx11 + + 3. Send tx21, which will use up tx2.output + + 4. Send tx111, dep = tx2.output + return error + """ + # 1. Send 2 transactions: tx1, tx2 + + account = self.Ckb_cli.util_key_info_by_private_key(self.Config.ACCOUNT_PRIVATE_1) + account_private = self.Config.ACCOUNT_PRIVATE_1 + tx1_hash = self.Ckb_cli.wallet_transfer_by_private_key(account_private, + account["address"]["testnet"], 1000000, + self.node1.getClient().url, "1500000") + + account2 = self.Ckb_cli.util_key_info_by_private_key(self.Config.ACCOUNT_PRIVATE_2) + account_private2 = self.Config.ACCOUNT_PRIVATE_2 + tx2_hash = self.Ckb_cli.wallet_transfer_by_private_key(account_private2, + account2["address"]["testnet"], 1000000, + self.node1.getClient().url, "1500000") + + # 2. Send a sub transaction: tx11 + tx_hash = self.Tx.send_transfer_self_tx_with_input([tx1_hash], ["0x0"], account_private, + output_count=2, + fee=1090, + api_url=self.node1.getClient().url) + + # 3. Send tx21, which will use up tx2.output + input_cell_hash = self.Tx.send_transfer_self_tx_with_input([tx2_hash], ["0x0"], account_private2, + output_count=2, + fee=1090, + api_url=self.node1.getClient().url) + + # 4. Send tx111, dep = tx2.output + + with pytest.raises(Exception) as exc_info: + self.Tx.send_transfer_self_tx_with_input([tx_hash], ["0x0"], account_private, + output_count=2, + fee=1090, + api_url=self.node1.getClient().url, + dep_cells=[{ + "tx_hash": tx2_hash, "index_hex": "0x0" + }]) + expected_error_message = "TransactionFailedToResolve" + assert expected_error_message in exc_info.value.args[0], \ + f"Expected substring '{expected_error_message}' " \ + f"not found in actual string '{exc_info.value.args[0]}'" + self.node1.getClient().clear_tx_pool() + + def test_sync_order_for_tx_with_pool_cell_dependency(self): + """ + The node receives the depCell transaction first, then receives the transaction consuming the depCell. + When relaying the transactions to another node, it relays the transaction consuming the depCell first, + then relays the depCell transaction. + + 1. Send 2 transactions: tx1, tx2 + 2. Send a sub transaction: tx11 + 3. Miner mines + 4. Send dep cell = tx2.output[0] + 5. Send tx21 + 6. Delete node2 data, resync node2 + 7. Ensure node2 pool is empty + 8. Resend tx21 to node1 first to sync to node2 + 9. Resend `dep cell transaction` that dep cell = tx2.output[0] + 10. Query for `dep cell transaction` on node2 and node1 + node1: pending + node2: rejected + """ + # 1. Send 2 transactions: tx1, tx2 + + account = self.Ckb_cli.util_key_info_by_private_key(self.Config.ACCOUNT_PRIVATE_1) + account_private = self.Config.ACCOUNT_PRIVATE_1 + tx1_hash = self.Ckb_cli.wallet_transfer_by_private_key(account_private, + account["address"]["testnet"], 1000000, + self.node1.getClient().url, "1500000") + + account2 = self.Ckb_cli.util_key_info_by_private_key(self.Config.ACCOUNT_PRIVATE_2) + account_private2 = self.Config.ACCOUNT_PRIVATE_2 + tx2_hash = self.Ckb_cli.wallet_transfer_by_private_key(account_private2, + account2["address"]["testnet"], 1000000, + self.node1.getClient().url, "1500000") + + # 2. Send a sub transaction: tx11 + tx_hash = self.Tx.send_transfer_self_tx_with_input([tx1_hash], ["0x0"], account_private, + output_count=2, + fee=1090, + api_url=self.node1.getClient().url) + # 3. Miner mines + self.Miner.miner_until_tx_committed(self.node1, tx_hash, True) + self.Miner.miner_until_tx_committed(self.node1, tx2_hash, True) + + # 4. Send dep cell = tx2.output[0] + + dep_cell_hash = self.Tx.send_transfer_self_tx_with_input([tx_hash], ["0x0"], account_private, + output_count=2, + fee=1090, + api_url=self.node1.getClient().url, + dep_cells=[{ + "tx_hash": tx2_hash, "index_hex": "0x0" + }]) + + # 5. Send tx21 + input_cell_hash = self.Tx.send_transfer_self_tx_with_input([tx2_hash], ["0x0"], account_private2, + output_count=2, + fee=1090, + api_url=self.node1.getClient().url) + + time.sleep(5) + + # 6. Delete node2 data, resync node2 + self.node2.restart(clean_data=True) + + self.node2.connected(self.node1) + tip_number = self.node1.getClient().get_tip_block_number() + self.Node.wait_node_height(self.node2, tip_number, 300) + time.sleep(10) + + # 7. Ensure node2 pool is empty + node2_pool = self.node2.getClient().tx_pool_info() + assert node2_pool['pending'] == '0x0' + + # 8. Resend tx21 to node1 first to sync to node2 + tx1 = self.node1.getClient().get_transaction(input_cell_hash) + del tx1['transaction']['hash'] + try: + self.node2.getClient().send_transaction(tx1['transaction']) + time.sleep(1) + result = self.node2.getClient().get_raw_tx_pool() + assert tx1 in result['pending'] + assert len(result['pending']) == 1 + except Exception: + pass + + # 9. Resend `dep cell transaction` that dep cell = tx2.output[0] + tx1 = self.node1.getClient().get_transaction(dep_cell_hash) + del tx1['transaction']['hash'] + try: + self.node2.getClient().send_transaction(tx1['transaction']) + time.sleep(1) + except Exception: + pass + + # 10. Query for `dep cell transaction` on node2 + self.node1.getClient().tx_pool_info() + self.node2.getClient().tx_pool_info() + self.node1.getClient().get_transaction(dep_cell_hash) + result = self.node2.getClient().get_transaction(dep_cell_hash) + assert result['tx_status']['status'] == 'rejected'