Changeset View
Changeset View
Standalone View
Standalone View
test/functional/abc_p2p_getavaaddr.py
#!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||
# Copyright (c) 2022 The Bitcoin developers | # Copyright (c) 2022 The Bitcoin developers | ||||
# Distributed under the MIT software license, see the accompanying | # Distributed under the MIT software license, see the accompanying | ||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php. | # file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
"""Test getavaaddr p2p message""" | """Test getavaaddr p2p message""" | ||||
import time | import time | ||||
from decimal import Decimal | from decimal import Decimal | ||||
from test_framework.avatools import AvaP2PInterface, gen_proof | from test_framework.avatools import HelloAvaP2PInterface, gen_proof | ||||
from test_framework.key import ECKey | |||||
from test_framework.messages import ( | from test_framework.messages import ( | ||||
NODE_AVALANCHE, | NODE_AVALANCHE, | ||||
NODE_NETWORK, | NODE_NETWORK, | ||||
AvalancheVote, | AvalancheVote, | ||||
AvalancheVoteError, | AvalancheVoteError, | ||||
msg_getavaaddr, | msg_getavaaddr, | ||||
) | ) | ||||
from test_framework.p2p import P2PInterface, p2p_lock | from test_framework.p2p import P2PInterface, p2p_lock | ||||
from test_framework.test_framework import BitcoinTestFramework | from test_framework.test_framework import BitcoinTestFramework | ||||
from test_framework.util import MAX_NODES, assert_equal, p2p_port | from test_framework.util import MAX_NODES, assert_equal, p2p_port | ||||
from test_framework.wallet_util import bytes_to_wif | |||||
# getavaaddr time interval in seconds, as defined in net_processing.cpp | # getavaaddr time interval in seconds, as defined in net_processing.cpp | ||||
# A node will ignore repeated getavaaddr during this interval | # A node will ignore repeated getavaaddr during this interval | ||||
GETAVAADDR_INTERVAL = 2 * 60 | GETAVAADDR_INTERVAL = 2 * 60 | ||||
# Address are sent every 30s on average, with a Poisson filter. Use a large | # Address are sent every 30s on average, with a Poisson filter. Use a large | ||||
# enough delay so it's very unlikely we don't get the message within this time. | # enough delay so it's very unlikely we don't get the message within this time. | ||||
MAX_ADDR_SEND_DELAY = 5 * 60 | MAX_ADDR_SEND_DELAY = 5 * 60 | ||||
Show All 18 Lines | def on_addr(self, message): | ||||
self.received_addrs = [] | self.received_addrs = [] | ||||
for addr in message.addrs: | for addr in message.addrs: | ||||
self.received_addrs.append(f"{addr.ip}:{addr.port}") | self.received_addrs.append(f"{addr.ip}:{addr.port}") | ||||
def addr_received(self): | def addr_received(self): | ||||
return self.received_addrs is not None | return self.received_addrs is not None | ||||
class MutedAvaP2PInterface(AvaP2PInterface): | class MutedAvaP2PInterface(HelloAvaP2PInterface): | ||||
def __init__(self): | def __init__(self, node=None): | ||||
super().__init__() | super().__init__(node) | ||||
self.is_responding = False | self.is_responding = False | ||||
self.privkey = None | self.privkey = None | ||||
self.addr = None | self.addr = None | ||||
self.poll_received = 0 | self.poll_received = 0 | ||||
def set_addr(self, addr): | def set_addr(self, addr): | ||||
self.addr = addr | self.addr = addr | ||||
def on_avapoll(self, message): | def on_avapoll(self, message): | ||||
self.poll_received += 1 | self.poll_received += 1 | ||||
class AllYesAvaP2PInterface(MutedAvaP2PInterface): | class AllYesAvaP2PInterface(MutedAvaP2PInterface): | ||||
def __init__(self, privkey): | def __init__(self, node=None): | ||||
super().__init__() | super().__init__(node) | ||||
self.privkey = privkey | |||||
self.is_responding = True | self.is_responding = True | ||||
def on_avapoll(self, message): | def on_avapoll(self, message): | ||||
self.send_avaresponse( | self.send_avaresponse( | ||||
message.poll.round, [ | message.poll.round, [ | ||||
AvalancheVote( | AvalancheVote( | ||||
AvalancheVoteError.ACCEPTED, inv.hash) for inv in message.poll.invs], self.privkey) | AvalancheVoteError.ACCEPTED, inv.hash) for inv in message.poll.invs], | ||||
self.master_privkey if self.delegation is None else self.delegated_privkey) | |||||
super().on_avapoll(message) | super().on_avapoll(message) | ||||
class AvaAddrTest(BitcoinTestFramework): | class AvaAddrTest(BitcoinTestFramework): | ||||
def set_test_params(self): | def set_test_params(self): | ||||
self.setup_clean_chain = False | self.setup_clean_chain = False | ||||
self.num_nodes = 1 | self.num_nodes = 1 | ||||
self.extra_args = [['-enableavalanche=1', | self.extra_args = [['-enableavalanche=1', | ||||
'-enableavalanchepeerdiscovery=1', | |||||
'-avaproofstakeutxoconfirmations=1', | '-avaproofstakeutxoconfirmations=1', | ||||
'-avacooldown=0', '-whitelist=noban@127.0.0.1']] | '-avacooldown=0', '-whitelist=noban@127.0.0.1']] | ||||
def check_all_peers_received_getavaaddr_once(self, avapeers): | def check_all_peers_received_getavaaddr_once(self, avapeers): | ||||
def received_all_getavaaddr(avapeers): | def received_all_getavaaddr(avapeers): | ||||
with p2p_lock: | with p2p_lock: | ||||
return all([p.last_message.get("getavaaddr") | return all([p.last_message.get("getavaaddr") | ||||
for p in avapeers]) | for p in avapeers]) | ||||
self.wait_until(lambda: received_all_getavaaddr(avapeers)) | self.wait_until(lambda: received_all_getavaaddr(avapeers)) | ||||
with p2p_lock: | with p2p_lock: | ||||
assert all([p.message_count.get( | assert all([p.message_count.get( | ||||
"getavaaddr", 0) == 1 for p in avapeers]) | "getavaaddr", 0) == 1 for p in avapeers]) | ||||
def getavaaddr_interval_test(self): | def getavaaddr_interval_test(self): | ||||
node = self.nodes[0] | node = self.nodes[0] | ||||
# Init mock time | # Init mock time | ||||
mock_time = int(time.time()) | mock_time = int(time.time()) | ||||
node.setmocktime(mock_time) | node.setmocktime(mock_time) | ||||
master_privkey, proof = gen_proof(node) | |||||
master_pubkey = master_privkey.get_pubkey().get_bytes().hex() | |||||
proof_hex = proof.serialize().hex() | |||||
# Add some avalanche peers to the node | # Add some avalanche peers to the node | ||||
for _ in range(10): | for _ in range(10): | ||||
node.add_p2p_connection(AllYesAvaP2PInterface(master_privkey)) | node.add_p2p_connection(AllYesAvaP2PInterface(node)) | ||||
assert node.addavalanchenode( | |||||
node.getpeerinfo()[-1]['id'], master_pubkey, proof_hex) | |||||
# Build some statistics to ensure some addresses will be returned | # Build some statistics to ensure some addresses will be returned | ||||
def all_peers_received_poll(): | def all_peers_received_poll(): | ||||
with p2p_lock: | with p2p_lock: | ||||
return all([avanode.poll_received > | return all([avanode.poll_received > | ||||
0 for avanode in node.p2ps]) | 0 for avanode in node.p2ps]) | ||||
self.wait_until(all_peers_received_poll) | self.wait_until(all_peers_received_poll) | ||||
node.mockscheduler(AVALANCHE_STATISTICS_INTERVAL) | node.mockscheduler(AVALANCHE_STATISTICS_INTERVAL) | ||||
Show All 35 Lines | def address_test(self, maxaddrtosend, num_proof, num_avanode): | ||||
# Init mock time | # Init mock time | ||||
mock_time = int(time.time()) | mock_time = int(time.time()) | ||||
node.setmocktime(mock_time) | node.setmocktime(mock_time) | ||||
# Create a bunch of proofs and associate each a bunch of nodes. | # Create a bunch of proofs and associate each a bunch of nodes. | ||||
avanodes = [] | avanodes = [] | ||||
for _ in range(num_proof): | for _ in range(num_proof): | ||||
master_privkey, proof = gen_proof(node) | master_privkey, proof = gen_proof(node) | ||||
master_pubkey = master_privkey.get_pubkey().get_bytes().hex() | |||||
proof_hex = proof.serialize().hex() | |||||
for n in range(num_avanode): | for n in range(num_avanode): | ||||
avanode = AllYesAvaP2PInterface( | avanode = AllYesAvaP2PInterface() if n % 2 else MutedAvaP2PInterface() | ||||
master_privkey) if n % 2 else MutedAvaP2PInterface() | avanode.master_privkey = master_privkey | ||||
avanode.proof = proof | |||||
node.add_p2p_connection(avanode) | node.add_p2p_connection(avanode) | ||||
peerinfo = node.getpeerinfo()[-1] | peerinfo = node.getpeerinfo()[-1] | ||||
avanode.set_addr(peerinfo["addr"]) | avanode.set_addr(peerinfo["addr"]) | ||||
assert node.addavalanchenode( | |||||
peerinfo['id'], master_pubkey, proof_hex) | |||||
avanodes.append(avanode) | avanodes.append(avanode) | ||||
responding_addresses = [ | responding_addresses = [ | ||||
avanode.addr for avanode in avanodes if avanode.is_responding] | avanode.addr for avanode in avanodes if avanode.is_responding] | ||||
assert_equal(len(responding_addresses), num_proof * num_avanode // 2) | assert_equal(len(responding_addresses), num_proof * num_avanode // 2) | ||||
# Check we have what we expect | # Check we have what we expect | ||||
def all_nodes_connected(): | |||||
avapeers = node.getavalanchepeerinfo() | avapeers = node.getavalanchepeerinfo() | ||||
assert_equal(len(avapeers), num_proof) | if len(avapeers) != num_proof: | ||||
return False | |||||
for avapeer in avapeers: | for avapeer in avapeers: | ||||
assert_equal(len(avapeer['nodes']), num_avanode) | if len(avapeer['nodes']) != num_avanode: | ||||
return False | |||||
return True | |||||
self.wait_until(all_nodes_connected) | |||||
# Force the availability score to diverge between the responding and the | # Force the availability score to diverge between the responding and the | ||||
# muted nodes. | # muted nodes. | ||||
def poll_all_for_block(): | def poll_all_for_block(): | ||||
node.generate(1) | node.generate(1) | ||||
with p2p_lock: | with p2p_lock: | ||||
return all([avanode.poll_received > ( | return all([avanode.poll_received > ( | ||||
10 if avanode.is_responding else 0) for avanode in avanodes]) | 10 if avanode.is_responding else 0) for avanode in avanodes]) | ||||
Show All 34 Lines | def getavaaddr_outbound_test(self): | ||||
"Check we send a getavaaddr message to our avalanche outbound peers") | "Check we send a getavaaddr message to our avalanche outbound peers") | ||||
node = self.nodes[0] | node = self.nodes[0] | ||||
# Get rid of previously connected nodes | # Get rid of previously connected nodes | ||||
node.disconnect_p2ps() | node.disconnect_p2ps() | ||||
avapeers = [] | avapeers = [] | ||||
for i in range(16): | for i in range(16): | ||||
avapeer = P2PInterface() | avapeer = HelloAvaP2PInterface() | ||||
node.add_outbound_p2p_connection( | node.add_outbound_p2p_connection( | ||||
avapeer, | avapeer, | ||||
p2p_idx=i, | p2p_idx=i, | ||||
connection_type="avalanche", | |||||
services=NODE_NETWORK | NODE_AVALANCHE, | |||||
) | ) | ||||
avapeers.append(avapeer) | avapeers.append(avapeer) | ||||
self.check_all_peers_received_getavaaddr_once(avapeers) | self.check_all_peers_received_getavaaddr_once(avapeers) | ||||
# Generate some block to poll for | # Generate some block to poll for | ||||
node.generate(1) | node.generate(1) | ||||
Show All 19 Lines | def getavaaddr_manual_test(self): | ||||
def added_node_connected(ip_port): | def added_node_connected(ip_port): | ||||
added_node_info = node.getaddednodeinfo(ip_port) | added_node_info = node.getaddednodeinfo(ip_port) | ||||
return len( | return len( | ||||
added_node_info) == 1 and added_node_info[0]['connected'] | added_node_info) == 1 and added_node_info[0]['connected'] | ||||
def connect_callback(address, port): | def connect_callback(address, port): | ||||
self.log.debug("Connecting to {}:{}".format(address, port)) | self.log.debug("Connecting to {}:{}".format(address, port)) | ||||
p = AvaP2PInterface() | p = HelloAvaP2PInterface() | ||||
p2p_idx = 1 | p2p_idx = 1 | ||||
p.peer_accept_connection( | p.peer_accept_connection( | ||||
connect_cb=connect_callback, | connect_cb=connect_callback, | ||||
connect_id=p2p_idx, | connect_id=p2p_idx, | ||||
net=node.chain, | net=node.chain, | ||||
timeout_factor=node.timeout_factor, | timeout_factor=node.timeout_factor, | ||||
services=NODE_NETWORK | NODE_AVALANCHE, | |||||
)() | )() | ||||
ip_port = f"127.0.01:{p2p_port(MAX_NODES - p2p_idx)}" | ip_port = f"127.0.01:{p2p_port(MAX_NODES - p2p_idx)}" | ||||
node.addnode(node=ip_port, command="add") | node.addnode(node=ip_port, command="add") | ||||
self.wait_until(lambda: added_node_connected(ip_port)) | self.wait_until(lambda: added_node_connected(ip_port)) | ||||
assert_equal(node.getpeerinfo()[-1]['addr'], ip_port) | assert_equal(node.getpeerinfo()[-1]['addr'], ip_port) | ||||
assert_equal(node.getpeerinfo()[-1]['connection_type'], 'manual') | assert_equal(node.getpeerinfo()[-1]['connection_type'], 'manual') | ||||
Show All 9 Lines | def getavaaddr_manual_test(self): | ||||
p.wait_until(lambda: p.message_count.get("getavaaddr", 0) > 1) | p.wait_until(lambda: p.message_count.get("getavaaddr", 0) > 1) | ||||
def getavaaddr_noquorum(self): | def getavaaddr_noquorum(self): | ||||
self.log.info( | self.log.info( | ||||
"Check we send a getavaaddr message while our quorum is not established") | "Check we send a getavaaddr message while our quorum is not established") | ||||
node = self.nodes[0] | node = self.nodes[0] | ||||
self.restart_node(0, extra_args=self.extra_args[0] + [ | self.restart_node(0, extra_args=self.extra_args[0] + [ | ||||
'-avaminquorumstake=100000000', | '-avaminquorumstake=1000000000', | ||||
sdulfari: Is this necessary? | |||||
FabienAuthorUnsubmitted Done Inline ActionsYes, because you're now connecting more peers (all the below avapeers) Fabien: Yes, because you're now connecting more peers (all the below avapeers) | |||||
'-avaminquorumconnectedstakeratio=0.8', | '-avaminquorumconnectedstakeratio=0.8', | ||||
]) | ]) | ||||
privkey, proof = gen_proof(node) | |||||
avapeers = [] | avapeers = [] | ||||
for i in range(16): | for i in range(16): | ||||
avapeer = AllYesAvaP2PInterface(privkey) | avapeer = AllYesAvaP2PInterface(node) | ||||
node.add_outbound_p2p_connection( | node.add_outbound_p2p_connection( | ||||
avapeer, | avapeer, | ||||
p2p_idx=i, | p2p_idx=i, | ||||
connection_type="avalanche", | connection_type="avalanche", | ||||
services=NODE_NETWORK | NODE_AVALANCHE, | services=NODE_NETWORK | NODE_AVALANCHE, | ||||
) | ) | ||||
avapeers.append(avapeer) | avapeers.append(avapeer) | ||||
Show All 10 Lines | def getavaaddr_noquorum(self): | ||||
# Because we have not enough stake to start polling, we keep requesting | # Because we have not enough stake to start polling, we keep requesting | ||||
# more addresses | # more addresses | ||||
total_getavaaddr = total_getavaaddr_msg() | total_getavaaddr = total_getavaaddr_msg() | ||||
for i in range(5): | for i in range(5): | ||||
node.mockscheduler(MAX_GETAVAADDR_DELAY) | node.mockscheduler(MAX_GETAVAADDR_DELAY) | ||||
self.wait_until(lambda: total_getavaaddr_msg() > total_getavaaddr) | self.wait_until(lambda: total_getavaaddr_msg() > total_getavaaddr) | ||||
total_getavaaddr = total_getavaaddr_msg() | total_getavaaddr = total_getavaaddr_msg() | ||||
# Connect the nodes via an avahello message | |||||
limitedproofid_hex = f"{proof.limited_proofid:0{64}x}" | |||||
for avapeer in avapeers: | |||||
avakey = ECKey() | |||||
avakey.generate() | |||||
delegation = node.delegateavalancheproof( | |||||
limitedproofid_hex, | |||||
bytes_to_wif(privkey.get_bytes()), | |||||
avakey.get_pubkey().get_bytes().hex(), | |||||
) | |||||
avapeer.send_avahello(delegation, avakey) | |||||
# Move the schedulter time forward to make seure we get statistics | # Move the schedulter time forward to make seure we get statistics | ||||
# computed. But since we did not start polling yet it should remain all | # computed. But since we did not start polling yet it should remain all | ||||
# zero. | # zero. | ||||
node.mockscheduler(AVALANCHE_STATISTICS_INTERVAL) | node.mockscheduler(AVALANCHE_STATISTICS_INTERVAL) | ||||
def wait_for_availability_score(): | def wait_for_availability_score(): | ||||
peerinfo = node.getpeerinfo() | peerinfo = node.getpeerinfo() | ||||
return all([p.get('availability_score', None) == Decimal(0) | return all([p.get('availability_score', None) == Decimal(0) | ||||
Show All 30 Lines |
Is this necessary?