Changeset View
Changeset View
Standalone View
Standalone View
test/functional/abc_p2p_avalanche_policy_early_blocks.py
- This file was added.
Property | Old Value | New Value |
---|---|---|
File Mode | null | 100755 |
#!/usr/bin/env python3 | |||||
# Copyright (c) 2022 The Bitcoin developers | |||||
# Distributed under the MIT software license, see the accompanying | |||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php. | |||||
"""Test avalanche post-consensus policy: difficulty penalty for early blocks""" | |||||
import math | |||||
import time | |||||
from test_framework.avatools import get_ava_p2p_interface | |||||
from test_framework.blocktools import create_block, create_coinbase | |||||
from test_framework.messages import ( | |||||
AvalancheVote, | |||||
AvalancheVoteError, | |||||
msg_block, | |||||
uint256_from_compact, | |||||
) | |||||
from test_framework.test_framework import BitcoinTestFramework | |||||
from test_framework.util import assert_equal | |||||
QUORUM_NODE_COUNT = 16 | |||||
NUM_BLOCKS_TO_CHECK = 20 | |||||
class AvalanchePolicyEarlyBlocksTest(BitcoinTestFramework): | |||||
def set_test_params(self): | |||||
self.setup_clean_chain = True | |||||
self.num_nodes = 1 | |||||
self.penaltyfactor = 10 | |||||
self.penaltywindow = 100 | |||||
self.extra_args = [ | |||||
[ | |||||
'-avalanche=1', | |||||
'-avapolicyearlyblockpenalty=1', | |||||
f'-avapolicyearlyblockpenaltyfactor={self.penaltyfactor}', | |||||
f'-avapolicyearlyblockpenaltywindow={self.penaltywindow}', | |||||
'-avaproofstakeutxodustthreshold=1000000', | |||||
'-avaproofstakeutxoconfirmations=1', | |||||
'-avacooldown=0', | |||||
'-avaminquorumstake=0', | |||||
'-avaminavaproofsnodecount=0', | |||||
'-whitelist=noban@127.0.0.1', | |||||
], | |||||
] | |||||
self.supports_cli = False | |||||
self.rpc_timeout = 120 | |||||
def run_test(self): | |||||
node = self.nodes[0] | |||||
mocktime = int(time.time()) | |||||
node.setmocktime(mocktime) | |||||
# Build a fake quorum of nodes. | |||||
def get_quorum(): | |||||
nonlocal mocktime | |||||
quorum = [] | |||||
for _ in range(0, QUORUM_NODE_COUNT): | |||||
# Set mocktime far beyond the penalty window so that the block | |||||
# generated as part of proof creation is always accepted. | |||||
mocktime += self.penaltywindow * 10 | |||||
node.setmocktime(mocktime) | |||||
quorum.append(get_ava_p2p_interface(node)) | |||||
return quorum | |||||
# Pick one node from the quorum for polling. | |||||
quorum = get_quorum() | |||||
poll_node = quorum[0] | |||||
assert node.getavalancheinfo()['ready_to_poll'] is True | |||||
def assert_response(expected): | |||||
r = poll_node.wait_for_avaresponse().response | |||||
votes = r.votes | |||||
assert_equal(len(votes), len(expected)) | |||||
for i in range(0, len(votes)): | |||||
assert_equal(repr(votes[i]), repr(expected[i])) | |||||
# Generate some blocks outside of the penalty window and check them | |||||
# against the policy. | |||||
tip = int(node.getbestblockhash(), 16) | |||||
height = node.getblockcount() + 1 | |||||
blocktime = node.getblock(node.getbestblockhash())['time'] + 1 | |||||
for _ in range(0, NUM_BLOCKS_TO_CHECK): | |||||
block = create_block( | |||||
tip, | |||||
create_coinbase(height), | |||||
blocktime, | |||||
version=4) | |||||
block.solve() | |||||
tip = block.sha256 | |||||
height += 1 | |||||
blocktime += 1 | |||||
# Make block received time just outside of the penalty window. | |||||
mocktime += 100 | |||||
node.setmocktime(mocktime) | |||||
poll_node.send_and_ping(msg_block(block)) | |||||
# Blocks outside of the penalty window are accepted. | |||||
poll_node.send_poll([tip]) | |||||
assert_response([AvalancheVote(AvalancheVoteError.ACCEPTED, tip)]) | |||||
def calculate_time_delta(target, blockhash): | |||||
# Calculate the received time delta closest to the boundary where | |||||
# the block would be accepted | |||||
return math.floor((target * | |||||
self.penaltywindow / | |||||
block.sha256 - | |||||
self.penaltyfactor * | |||||
self.penaltywindow) / | |||||
(1 - | |||||
self.penaltyfactor)) | |||||
# Generate some blocks that are rejected inside of the penalty window. | |||||
for _ in range(0, NUM_BLOCKS_TO_CHECK): | |||||
block = create_block( | |||||
tip, | |||||
create_coinbase(height), | |||||
blocktime, | |||||
version=4) | |||||
block.solve() | |||||
expect_parked = block.sha256 | |||||
target = uint256_from_compact(block.nBits) | |||||
if block.sha256 <= target / self.penaltyfactor: | |||||
# Block difficulty exceeds highest possible difficulty penalty, | |||||
# so just ignore it | |||||
continue | |||||
assert block.sha256 <= target | |||||
time_delta = calculate_time_delta(target, block.sha256) | |||||
assert time_delta >= 0 | |||||
assert time_delta < self.penaltywindow | |||||
# Set block received time such that the block is rejected. | |||||
node.setmocktime(mocktime + time_delta) | |||||
poll_node.send_and_ping(msg_block(block)) | |||||
poll_node.send_poll([expect_parked]) | |||||
assert_response( | |||||
[AvalancheVote(AvalancheVoteError.PARKED, expect_parked)]) | |||||
# Generate some blocks that are accepted inside of the penalty window. | |||||
for _ in range(0, NUM_BLOCKS_TO_CHECK): | |||||
block = create_block( | |||||
tip, | |||||
create_coinbase(height), | |||||
blocktime, | |||||
version=4) | |||||
block.solve() | |||||
if block.sha256 <= target / self.penaltyfactor: | |||||
# Block difficulty exceeds highest possible difficulty penalty, | |||||
# so just ignore it | |||||
continue | |||||
tip = block.sha256 | |||||
height += 1 | |||||
blocktime += 1 | |||||
time_delta = calculate_time_delta(target, block.sha256) | |||||
assert time_delta >= 0 | |||||
assert time_delta < self.penaltywindow | |||||
# Set block received time such that the block is accepted. | |||||
mocktime += time_delta + 1 | |||||
node.setmocktime(mocktime) | |||||
poll_node.send_and_ping(msg_block(block)) | |||||
poll_node.send_poll([tip]) | |||||
assert_response([AvalancheVote(AvalancheVoteError.ACCEPTED, tip)]) | |||||
if __name__ == '__main__': | |||||
AvalanchePolicyEarlyBlocksTest().main() |