Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14864572
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
10 KB
Subscribers
None
View Options
diff --git a/test/functional/abc_p2p_avalanche_contender_voting.py b/test/functional/abc_p2p_avalanche_contender_voting.py
index 7f1833199..4971221d3 100644
--- a/test/functional/abc_p2p_avalanche_contender_voting.py
+++ b/test/functional/abc_p2p_avalanche_contender_voting.py
@@ -1,239 +1,235 @@
# 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 resolution of stake contender preconsensus via avalanche."""
import time
from test_framework.avatools import can_find_inv_in_poll, get_ava_p2p_interface
from test_framework.key import ECPubKey
from test_framework.messages import (
MSG_AVA_STAKE_CONTENDER,
AvalancheContenderVoteError,
AvalancheVote,
hash256,
ser_uint256,
)
from test_framework.p2p import p2p_lock
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, uint256_hex
QUORUM_NODE_COUNT = 16
class AvalancheContenderVotingTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
self.extra_args = [
[
"-avalanchestakingpreconsensus=1",
"-avalanchestakingrewards=1",
"-avaproofstakeutxodustthreshold=1000000",
"-avaproofstakeutxoconfirmations=1",
"-avacooldown=0",
"-avaminquorumstake=0",
"-avaminavaproofsnodecount=0",
"-whitelist=noban@127.0.0.1",
"-persistavapeers=0",
],
]
self.supports_cli = False
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def run_test(self):
node = self.nodes[0]
# Set mock time so we can control when proofs will be considered for staking rewards
now = int(time.time())
node.setmocktime(now)
# Build a fake quorum of nodes.
def get_quorum():
def new_ava_interface(node):
# Generate a unique payout script for each proof so we can accurately test the stake winners
payoutAddress = node.getnewaddress()
peer = get_ava_p2p_interface(self, node, payoutAddress=payoutAddress)
# This test depends on each proof being added to the contender cache before
# the next block arrives, so we wait until that happens.
blockhash = node.getbestblockhash()
self.wait_until(
lambda: node.getstakecontendervote(
blockhash, uint256_hex(peer.proof.proofid)
)
== AvalancheContenderVoteError.PENDING
)
return peer
return [new_ava_interface(node) for _ in range(0, QUORUM_NODE_COUNT)]
# Pick one node from the quorum for polling.
quorum = get_quorum()
poll_node = quorum[0]
assert node.getavalancheinfo()["ready_to_poll"] is True
def has_finalized_proof(proofid):
can_find_inv_in_poll(quorum, proofid)
return node.getrawavalancheproof(uint256_hex(proofid))["finalized"]
for peer in quorum:
self.wait_until(lambda: has_finalized_proof(peer.proof.proofid))
# Get the key so we can verify signatures.
avakey = ECPubKey()
avakey.set(bytes.fromhex(node.getavalanchekey()))
def assert_response(expected):
response = poll_node.wait_for_avaresponse()
r = response.response
# Verify signature
assert avakey.verify_schnorr(response.sig, r.get_hash())
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 make_contender_id(prevblockhash, proofid):
return int.from_bytes(
hash256(ser_uint256(int(prevblockhash, 16)) + ser_uint256(proofid)),
"little",
)
# Before finalizing any blocks, no contender promotion occurs in the cache,
# so the only way to test if the node knows about a particular contender is
# to check it at the block height that the proof was first seen at.
for i in range(0, QUORUM_NODE_COUNT):
# We started with a clean chain and a new block is mined when creating
# each quorum proof to make it valid, so the first block the proof was
# seen at is the quorum proof's index + 1.
blockhash = node.getblockhash(i + 1)
poll_ids = []
expected = []
for p in range(0, QUORUM_NODE_COUNT):
contender_proof = quorum[p].proof
contender_id = make_contender_id(blockhash, contender_proof.proofid)
poll_ids.append(contender_id)
# If the node knows about the contender, it will respond as INVALID
expected_vote = (
AvalancheContenderVoteError.PENDING
if p == i
else AvalancheContenderVoteError.UNKNOWN
)
expected.append(AvalancheVote(expected_vote, contender_id))
poll_node.send_poll(poll_ids, inv_type=MSG_AVA_STAKE_CONTENDER)
assert_response(expected)
# Unknown contender
unknown_contender_id = 0x123
poll_node.send_poll([unknown_contender_id], inv_type=MSG_AVA_STAKE_CONTENDER)
assert_response(
[AvalancheVote(AvalancheContenderVoteError.UNKNOWN, unknown_contender_id)]
)
def has_finalized_tip(tip_expected):
hash_tip_final = int(tip_expected, 16)
can_find_inv_in_poll(quorum, hash_tip_final)
return node.isfinalblock(tip_expected)
# Finalize a block so we promote the contender cache with every block
tip = node.getbestblockhash()
self.wait_until(lambda: has_finalized_tip(tip))
assert_equal(node.getbestblockhash(), tip)
# Now trigger building the whole cache for a block
tip = self.generate(node, 1)[0]
# Unknown contender is still unknown
poll_node.send_poll([unknown_contender_id], inv_type=MSG_AVA_STAKE_CONTENDER)
assert_response(
[AvalancheVote(AvalancheContenderVoteError.UNKNOWN, unknown_contender_id)]
)
def get_all_contender_ids(tip):
return [make_contender_id(tip, peer.proof.proofid) for peer in quorum]
# All contenders are pending. They cannot be winners yet since mock time
# has not advanced past the staking rewards minimum registration delay.
for contender_id in get_all_contender_ids(tip):
poll_node.send_poll([contender_id], inv_type=MSG_AVA_STAKE_CONTENDER)
assert_response(
[AvalancheVote(AvalancheContenderVoteError.PENDING, contender_id)]
)
# Advance time past the staking rewards minimum registration delay and
# mine a block.
now += 90 * 60 + 1
node.setmocktime(now)
tip = self.generate(node, 1)[0]
# Staking rewards has been computed. Check vote for all contenders.
contenders = get_all_contender_ids(tip)
local_winner_proofid = int(node.getstakingreward(tip)[0]["proofid"], 16)
local_winner_cid = make_contender_id(tip, local_winner_proofid)
poll_node.send_poll(contenders, inv_type=MSG_AVA_STAKE_CONTENDER)
assert_response(
[
AvalancheVote(
(
AvalancheContenderVoteError.ACCEPTED
if cid == local_winner_cid
else AvalancheContenderVoteError.INVALID
),
cid,
)
for cid in contenders
]
)
# Answer polls until the chain tip (and contenders) start polling
self.wait_until(lambda: can_find_inv_in_poll(quorum, int(tip, 16)))
- # Pop a poll from any peer
- def wait_for_poll():
- self.wait_until(lambda: any(len(peer.avapolls) > 0 for peer in quorum))
- with p2p_lock:
- for peer in quorum:
- if len(peer.avapolls) > 0:
- return peer.avapolls.pop(0)
- return None
-
- poll = wait_for_poll()
- assert poll is not None
-
- # Count contenders being polled for
- count = 0
- found_local_winner = False
- for inv in poll.invs:
- if inv.hash in contenders:
- count += 1
- if local_winner_cid == inv.hash:
- found_local_winner = True
+ def get_polled_contenders():
+ # Pop a poll from any peer
+ def wait_for_poll():
+ self.wait_until(lambda: any(len(peer.avapolls) > 0 for peer in quorum))
+ with p2p_lock:
+ for peer in quorum:
+ if len(peer.avapolls) > 0:
+ return peer.avapolls.pop(0)
+ return None
+
+ poll = wait_for_poll()
+ assert poll is not None
+ return [
+ inv.hash for inv in poll.invs if inv.type == MSG_AVA_STAKE_CONTENDER
+ ]
# Check that the local winner was polled
- assert found_local_winner
+ polled_contenders = get_polled_contenders()
+ assert local_winner_cid in polled_contenders
# Check that the max number of contenders were polled
- assert_equal(count, 12)
+ assert_equal(len(polled_contenders), 12)
# Manually set a winner that isn't the local winner
manual_winner = (
quorum[0].proof
if local_winner_proofid != quorum[0].proof.proofid
else quorum[1].proof
)
manual_winner_cid = make_contender_id(tip, manual_winner.proofid)
node.setstakingreward(tip, manual_winner.payout_script.hex())
poll_node.send_poll([manual_winner_cid], inv_type=MSG_AVA_STAKE_CONTENDER)
assert_response(
[AvalancheVote(AvalancheContenderVoteError.ACCEPTED, manual_winner_cid)]
)
if __name__ == "__main__":
AvalancheContenderVotingTest().main()
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Wed, May 21, 20:36 (1 d, 14 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5865898
Default Alt Text
(10 KB)
Attached To
rABC Bitcoin ABC
Event Timeline
Log In to Comment