Changeset View
Changeset View
Standalone View
Standalone View
test/functional/abc_p2p_avalanche_policy_rtt.py
- This file was added.
| # Copyright (c) 2024 The Bitcoin developers | |||||
| # Distributed under the MIT software license, see the accompanying | |||||
| # file COPYING or http://www.opensource.org/licenses/mit-license.php. | |||||
| """Test the real time targeting policy.""" | |||||
| import time | |||||
| from test_framework.avatools import can_find_inv_in_poll, get_ava_p2p_interface | |||||
| from test_framework.blocktools import create_block, create_coinbase | |||||
| from test_framework.messages import AvalancheVote, AvalancheVoteError, ToHex | |||||
| from test_framework.p2p import P2PInterface | |||||
| from test_framework.test_framework import BitcoinTestFramework | |||||
| from test_framework.util import assert_equal | |||||
| QUORUM_NODE_COUNT = 16 | |||||
| class AvalancheRTTTest(BitcoinTestFramework): | |||||
| def set_test_params(self): | |||||
| self.setup_clean_chain = True | |||||
| self.num_nodes = 2 | |||||
| self.extra_args = [ | |||||
| [ | |||||
| "-enablertt", | |||||
| "-avaproofstakeutxodustthreshold=1000000", | |||||
| "-avaproofstakeutxoconfirmations=1", | |||||
| "-avacooldown=0", | |||||
| "-avaminquorumstake=0", | |||||
| "-avaminavaproofsnodecount=0", | |||||
| "-whitelist=noban@127.0.0.1", | |||||
| ], | |||||
| [], | |||||
| ] | |||||
| def run_test(self): | |||||
| node = self.nodes[0] | |||||
| now = int(time.time()) - 10000 | |||||
| node.setmocktime(now) | |||||
| node.add_p2p_connection(P2PInterface()) | |||||
| self.nodes[1].add_p2p_connection(P2PInterface()) | |||||
| tip = self.generate(node, 1, sync_fun=self.no_op)[0] | |||||
| assert_equal(node.getbestblockhash(), tip) | |||||
| self.log.info( | |||||
| "The getblocktempate call returns an increasing target over time (decreasing difficulty)" | |||||
| ) | |||||
| def get_quorum(): | |||||
| nonlocal now | |||||
| quorum = [] | |||||
| for _ in range(QUORUM_NODE_COUNT): | |||||
| now += 600 | |||||
| node.setmocktime(now) | |||||
| quorum.append(get_ava_p2p_interface(self, node)) | |||||
| return quorum | |||||
| quorum = get_quorum() | |||||
| poll_node = quorum[0] | |||||
| assert node.getavalancheinfo()["ready_to_poll"] is True | |||||
| def has_accepted_tip(tip_expected): | |||||
| hash_tip_final = int(tip_expected, 16) | |||||
| can_find_inv_in_poll(quorum, hash_tip_final) | |||||
| return node.getbestblockhash() == tip_expected | |||||
| def has_finalized_tip(tip_expected): | |||||
| return has_accepted_tip(tip_expected) and node.isfinalblock(tip_expected) | |||||
| tip = node.getbestblockhash() | |||||
| self.wait_until(lambda: has_finalized_tip(tip)) | |||||
| def assert_response(expected): | |||||
| response = poll_node.wait_for_avaresponse() | |||||
| r = response.response | |||||
| assert_equal(r.cooldown, 0) | |||||
| votes = r.votes | |||||
| assert_equal(len(votes), len(expected)) | |||||
| for i in range(0, len(votes)): | |||||
| assert_equal(repr(votes[i]), repr(expected[i])) | |||||
| def check_new_block(tip, expect_accepted): | |||||
| cb = create_coinbase(node.getblockcount()) | |||||
| block = create_block(int(tip, 16), cb, now) | |||||
| block.solve() | |||||
| assert_equal(node.submitblock(ToHex(block)), None) | |||||
| expected_tip = block.hash if expect_accepted else tip | |||||
| assert_equal(node.getbestblockhash(), expected_tip) | |||||
| # Poll and check the node votes what we expect | |||||
| poll_node.send_poll([block.sha256]) | |||||
| expected_vote = ( | |||||
| AvalancheVoteError.ACCEPTED | |||||
| if expect_accepted | |||||
| else AvalancheVoteError.PARKED | |||||
| ) | |||||
| assert_response([AvalancheVote(expected_vote, block.sha256)]) | |||||
| # Vote yes on this block until the node accepts it | |||||
| self.wait_until(lambda: has_accepted_tip(block.hash)) | |||||
| assert_equal(node.getbestblockhash(), block.hash) | |||||
| poll_node.send_poll([block.sha256]) | |||||
| assert_response([AvalancheVote(AvalancheVoteError.ACCEPTED, block.sha256)]) | |||||
| self.log.info("Check the node rejects blocks that doesn't match RTT") | |||||
| height = node.getblockcount() | |||||
| # Create a block with the regtest target, not accounting for RTT | |||||
| check_new_block(node.getbestblockhash(), False) | |||||
| # The check_new_block call will avalanche accept the initially rejected | |||||
| # block | |||||
| assert_equal(node.getblockcount(), height + 1) | |||||
roqqit: Reading this without digging into the internals of check_new_block is confusing. The API… | |||||
| self.log.info("Check the node accept blocks that match RTT") | |||||
| # Elapse enough time for the the difficulty to settle to regtest target | |||||
| now += 600 | |||||
roqqitUnsubmitted Not Done Inline ActionsThis test could be improved to check late blocks as well (now += 1200, 1800, etc). I know this overlaps with the unit tests a bit, but it costs very little to have that extra confidence. roqqit: This test could be improved to check late blocks as well (now += 1200, 1800, etc). I know this… | |||||
| node.setmocktime(now) | |||||
| assert_equal(int(node.getblocktemplate()["bits"], 16), 0x207FFFFF) | |||||
| check_new_block(node.getbestblockhash(), True) | |||||
| self.log.info( | |||||
| "Check the node catches up after a restart without parking due to RTT" | |||||
| ) | |||||
| self.sync_blocks() | |||||
| self.stop_node(0) | |||||
| self.generate(self.nodes[1], 10, sync_fun=self.no_op) | |||||
| self.start_node(0, extra_args=self.extra_args[0]) | |||||
| self.connect_nodes(0, 1) | |||||
| self.sync_blocks() | |||||
| if __name__ == "__main__": | |||||
| AvalancheRTTTest().main() | |||||
Reading this without digging into the internals of check_new_block is confusing. The API suggests the new block is rejected but then what immediately follows is a check that it was actually accepted. The easiest fix is the rename check_new_block to something like check_and_accept_new_block with a rename of the expect_accepted parameter to expect_initially_accepted.