diff --git a/src/net_processing.cpp b/src/net_processing.cpp --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1678,11 +1678,6 @@ }); } -static bool isAvalancheOutboundOrManual(const CNode *pnode) { - return pnode->IsAvalancheOutboundConnection() || - (pnode->IsManualConn() && (pnode->nServices & NODE_AVALANCHE)); -} - void PeerManagerImpl::AvalanchePeriodicNetworking(CScheduler &scheduler) const { const auto now = GetTime(); std::vector avanode_outbound_ids; @@ -1694,7 +1689,7 @@ m_connman.ForEachNode([&](CNode *pnode) { // Build a list of the avalanche manual or outbound peers nodeids - if (isAvalancheOutboundOrManual(pnode)) { + if (pnode->m_avalanche_state && !pnode->IsInboundConn()) { avanode_outbound_ids.push_back(pnode->GetId()); } @@ -3745,22 +3740,6 @@ localProof->getId()); } } - - // Send getavaaddr and getavaproofs to our avalanche outbound or - // manual connections - if (isAvalancheOutboundOrManual(&pfrom)) { - m_connman.PushMessage(&pfrom, - msgMaker.Make(NetMsgType::GETAVAADDR)); - WITH_LOCK(peer->m_addr_token_bucket_mutex, - peer->m_addr_token_bucket += GetMaxAddrToSend()); - - if (pfrom.m_proof_relay && - !m_chainman.ActiveChainstate().IsInitialBlockDownload()) { - m_connman.PushMessage( - &pfrom, msgMaker.Make(NetMsgType::GETAVAPROOFS)); - pfrom.m_proof_relay->compactproofs_requested = true; - } - } } pfrom.fSuccessfullyConnected = true; @@ -4936,49 +4915,63 @@ // A delegation with an all zero limited id indicates that the peer has // no proof, so we're done. - if (delegation.getLimitedProofId() == uint256::ZERO) { - return; - } + if (delegation.getLimitedProofId() != uint256::ZERO) { + avalanche::DelegationState state; + CPubKey &pubkey = pfrom.m_avalanche_state->pubkey; + if (!delegation.verify(state, pubkey)) { + Misbehaving(pfrom, 100, "invalid-delegation"); + return; + } - avalanche::DelegationState state; - CPubKey &pubkey = pfrom.m_avalanche_state->pubkey; - if (!delegation.verify(state, pubkey)) { - Misbehaving(pfrom, 100, "invalid-delegation"); - return; - } + CHashWriter sighasher(SER_GETHASH, 0); + sighasher << delegation.getId(); + sighasher << pfrom.nRemoteHostNonce; + sighasher << pfrom.GetLocalNonce(); + sighasher << pfrom.nRemoteExtraEntropy; + sighasher << pfrom.GetLocalExtraEntropy(); + + SchnorrSig sig; + vRecv >> sig; + if (!pubkey.VerifySchnorr(sighasher.GetHash(), sig)) { + Misbehaving(pfrom, 100, "invalid-avahello-signature"); + return; + } - CHashWriter sighasher(SER_GETHASH, 0); - sighasher << delegation.getId(); - sighasher << pfrom.nRemoteHostNonce; - sighasher << pfrom.GetLocalNonce(); - sighasher << pfrom.nRemoteExtraEntropy; - sighasher << pfrom.GetLocalExtraEntropy(); + // If we don't know this proof already, add it to the tracker so it + // can be requested. + const avalanche::ProofId proofid(delegation.getProofId()); + if (!AlreadyHaveProof(proofid)) { + const bool preferred = isPreferredDownloadPeer(pfrom); + LOCK(cs_proofrequest); + AddProofAnnouncement(pfrom, proofid, + GetTime(), + preferred); + } - SchnorrSig sig; - vRecv >> sig; - if (!pubkey.VerifySchnorr(sighasher.GetHash(), sig)) { - Misbehaving(pfrom, 100, "invalid-avahello-signature"); - return; + if (gArgs.GetBoolArg("-enableavalanchepeerdiscovery", + AVALANCHE_DEFAULT_PEER_DISCOVERY_ENABLED)) { + // Don't check the return value. If it fails we probably don't + // know about the proof yet. + g_avalanche->withPeerManager([&](avalanche::PeerManager &pm) { + return pm.addNode(pfrom.GetId(), proofid); + }); + } } - // If we don't know this proof already, add it to the tracker so it can - // be requested. - const avalanche::ProofId proofid(delegation.getProofId()); - if (!AlreadyHaveProof(proofid)) { - const bool preferred = isPreferredDownloadPeer(pfrom); - LOCK(cs_proofrequest); - AddProofAnnouncement(pfrom, proofid, - GetTime(), - preferred); - } + // Send getavaaddr and getavaproofs to our avalanche outbound or + // manual connections + if (!pfrom.IsInboundConn()) { + m_connman.PushMessage(&pfrom, + msgMaker.Make(NetMsgType::GETAVAADDR)); + WITH_LOCK(peer->m_addr_token_bucket_mutex, + peer->m_addr_token_bucket += GetMaxAddrToSend()); - if (gArgs.GetBoolArg("-enableavalanchepeerdiscovery", - AVALANCHE_DEFAULT_PEER_DISCOVERY_ENABLED)) { - // Don't check the return value. If it fails we probably don't know - // about the proof yet. - g_avalanche->withPeerManager([&](avalanche::PeerManager &pm) { - return pm.addNode(pfrom.GetId(), proofid); - }); + if (pfrom.m_proof_relay && + !m_chainman.ActiveChainstate().IsInitialBlockDownload()) { + m_connman.PushMessage(&pfrom, + msgMaker.Make(NetMsgType::GETAVAPROOFS)); + pfrom.m_proof_relay->compactproofs_requested = true; + } } return; diff --git a/test/functional/abc_p2p_avalanche_quorum.py b/test/functional/abc_p2p_avalanche_quorum.py --- a/test/functional/abc_p2p_avalanche_quorum.py +++ b/test/functional/abc_p2p_avalanche_quorum.py @@ -5,7 +5,7 @@ """Test the quorum detection of avalanche.""" from test_framework.avatools import ( - AvaP2PInterface, + HelloAvaP2PInterface, build_msg_avaproofs, gen_proof, get_ava_p2p_interface, @@ -29,6 +29,7 @@ self.min_avaproofs_node_count = 8 self.extra_args = [[ '-enableavalanche=1', + '-enableavalanchepeerdiscovery=1', '-avaproofstakeutxoconfirmations=1', '-avacooldown=0', '-avatimeout=0', @@ -87,20 +88,14 @@ expected = repr(AvalancheVote(expected, block)) assert_equal(actual, expected) - def addavalanchenode(node, peer): - pubkey = peer['key'].get_pubkey().get_bytes().hex() - assert node.addavalanchenode( - peer['node'].nodeid, - pubkey, - peer['proof'].serialize().hex(), - ) is True - p2p_idx = 0 def get_ava_outbound(node, peer, empty_avaproof): nonlocal p2p_idx - avapeer = AvaP2PInterface() + avapeer = HelloAvaP2PInterface() + avapeer.proof = peer['proof'] + avapeer.master_privkey = peer['key'] node.add_outbound_p2p_connection( avapeer, p2p_idx=p2p_idx, @@ -111,7 +106,6 @@ avapeer.nodeid = node.getpeerinfo()[-1]['id'] peer['node'] = avapeer - addavalanchenode(node, peer) # There is no compact proof request if the node is in IBD state if not node.getblockchaininfo()['initialblockdownload']: diff --git a/test/functional/abc_p2p_compactproofs.py b/test/functional/abc_p2p_compactproofs.py --- a/test/functional/abc_p2p_compactproofs.py +++ b/test/functional/abc_p2p_compactproofs.py @@ -11,6 +11,7 @@ from test_framework.avatools import ( AvaP2PInterface, + HelloAvaP2PInterface, build_msg_avaproofs, gen_proof, get_ava_p2p_interface, @@ -73,7 +74,7 @@ p2p_idx = 0 non_avapeers = [] - for i in range(4): + for _ in range(4): peer = P2PInterface() node.add_outbound_p2p_connection( peer, @@ -89,8 +90,9 @@ AvaP2PInterface()) for _ in range(4)] outbound_avapeers = [] - for i in range(4): - peer = P2PInterface() + # With a proof and the service bit set + for _ in range(4): + peer = HelloAvaP2PInterface(node) node.add_outbound_p2p_connection( peer, p2p_idx=p2p_idx, @@ -100,6 +102,18 @@ outbound_avapeers.append(peer) p2p_idx += 1 + # Without a proof and no service bit set + for _ in range(4): + peer = HelloAvaP2PInterface() + node.add_outbound_p2p_connection( + peer, + p2p_idx=p2p_idx, + connection_type="outbound-full-relay", + services=NODE_NETWORK, + ) + outbound_avapeers.append(peer) + p2p_idx += 1 + def all_peers_received_getavaproofs(): with p2p_lock: return all([p.last_message.get("getavaproofs") @@ -138,7 +152,7 @@ self.log.info( "After the first avaproofs has been received, all the peers are requested periodically") - responding_outbound_avapeer = P2PInterface() + responding_outbound_avapeer = HelloAvaP2PInterface(node) node.add_outbound_p2p_connection( responding_outbound_avapeer, p2p_idx=p2p_idx, @@ -197,7 +211,7 @@ def connect_callback(address, port): self.log.debug("Connecting to {}:{}".format(address, port)) - p = AvaP2PInterface() + p = HelloAvaP2PInterface(node) p2p_idx = 1 p.peer_accept_connection( connect_cb=connect_callback, @@ -298,7 +312,7 @@ def add_avalanche_p2p_outbound(): nonlocal p2p_idx - peer = P2PInterface() + peer = HelloAvaP2PInterface(node) node.add_outbound_p2p_connection( peer, p2p_idx=p2p_idx, diff --git a/test/functional/abc_p2p_getavaaddr.py b/test/functional/abc_p2p_getavaaddr.py --- a/test/functional/abc_p2p_getavaaddr.py +++ b/test/functional/abc_p2p_getavaaddr.py @@ -6,8 +6,7 @@ import time from decimal import Decimal -from test_framework.avatools import AvaP2PInterface, gen_proof -from test_framework.key import ECKey +from test_framework.avatools import HelloAvaP2PInterface, gen_proof from test_framework.messages import ( NODE_AVALANCHE, NODE_NETWORK, @@ -18,7 +17,6 @@ from test_framework.p2p import P2PInterface, p2p_lock from test_framework.test_framework import BitcoinTestFramework 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 # A node will ignore repeated getavaaddr during this interval @@ -53,9 +51,9 @@ return self.received_addrs is not None -class MutedAvaP2PInterface(AvaP2PInterface): - def __init__(self): - super().__init__() +class MutedAvaP2PInterface(HelloAvaP2PInterface): + def __init__(self, node=None): + super().__init__(node) self.is_responding = False self.privkey = None self.addr = None @@ -69,16 +67,16 @@ class AllYesAvaP2PInterface(MutedAvaP2PInterface): - def __init__(self, privkey): - super().__init__() - self.privkey = privkey + def __init__(self, node=None): + super().__init__(node) self.is_responding = True def on_avapoll(self, message): self.send_avaresponse( message.poll.round, [ 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) @@ -87,6 +85,7 @@ self.setup_clean_chain = False self.num_nodes = 1 self.extra_args = [['-enableavalanche=1', + '-enableavalanchepeerdiscovery=1', '-avaproofstakeutxoconfirmations=1', '-avacooldown=0', '-whitelist=noban@127.0.0.1']] @@ -108,15 +107,9 @@ mock_time = int(time.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 for _ in range(10): - node.add_p2p_connection(AllYesAvaP2PInterface(master_privkey)) - assert node.addavalanchenode( - node.getpeerinfo()[-1]['id'], master_pubkey, proof_hex) + node.add_p2p_connection(AllYesAvaP2PInterface(node)) # Build some statistics to ensure some addresses will be returned def all_peers_received_poll(): @@ -168,19 +161,15 @@ avanodes = [] for _ in range(num_proof): 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): - avanode = AllYesAvaP2PInterface( - master_privkey) if n % 2 else MutedAvaP2PInterface() + avanode = AllYesAvaP2PInterface() if n % 2 else MutedAvaP2PInterface() + avanode.master_privkey = master_privkey + avanode.proof = proof node.add_p2p_connection(avanode) peerinfo = node.getpeerinfo()[-1] avanode.set_addr(peerinfo["addr"]) - assert node.addavalanchenode( - peerinfo['id'], master_pubkey, proof_hex) avanodes.append(avanode) responding_addresses = [ @@ -188,10 +177,18 @@ assert_equal(len(responding_addresses), num_proof * num_avanode // 2) # Check we have what we expect - avapeers = node.getavalanchepeerinfo() - assert_equal(len(avapeers), num_proof) - for avapeer in avapeers: - assert_equal(len(avapeer['nodes']), num_avanode) + def all_nodes_connected(): + avapeers = node.getavalanchepeerinfo() + if len(avapeers) != num_proof: + return False + + for avapeer in avapeers: + 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 # muted nodes. @@ -242,12 +239,10 @@ avapeers = [] for i in range(16): - avapeer = P2PInterface() + avapeer = HelloAvaP2PInterface() node.add_outbound_p2p_connection( avapeer, p2p_idx=i, - connection_type="avalanche", - services=NODE_NETWORK | NODE_AVALANCHE, ) avapeers.append(avapeer) @@ -283,14 +278,13 @@ def connect_callback(address, port): self.log.debug("Connecting to {}:{}".format(address, port)) - p = AvaP2PInterface() + p = HelloAvaP2PInterface() p2p_idx = 1 p.peer_accept_connection( connect_cb=connect_callback, connect_id=p2p_idx, net=node.chain, timeout_factor=node.timeout_factor, - services=NODE_NETWORK | NODE_AVALANCHE, )() ip_port = f"127.0.01:{p2p_port(MAX_NODES - p2p_idx)}" @@ -316,15 +310,13 @@ node = self.nodes[0] self.restart_node(0, extra_args=self.extra_args[0] + [ - '-avaminquorumstake=100000000', + '-avaminquorumstake=1000000000', '-avaminquorumconnectedstakeratio=0.8', ]) - privkey, proof = gen_proof(node) - avapeers = [] for i in range(16): - avapeer = AllYesAvaP2PInterface(privkey) + avapeer = AllYesAvaP2PInterface(node) node.add_outbound_p2p_connection( avapeer, p2p_idx=i, @@ -351,18 +343,6 @@ self.wait_until(lambda: total_getavaaddr_msg() > total_getavaaddr) 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 # computed. But since we did not start polling yet it should remain all # zero. diff --git a/test/functional/test_framework/avatools.py b/test/functional/test_framework/avatools.py --- a/test/functional/test_framework/avatools.py +++ b/test/functional/test_framework/avatools.py @@ -11,6 +11,7 @@ from .authproxy import JSONRPCException from .key import ECKey from .messages import ( + MSG_AVA_PROOF, MSG_BLOCK, NODE_AVALANCHE, NODE_NETWORK, @@ -29,6 +30,7 @@ msg_avapoll, msg_avaproof, msg_avaproofs, + msg_notfound, msg_tcpavaresponse, ) from .p2p import P2PInterface, p2p_lock @@ -256,8 +258,8 @@ with p2p_lock: return self.avahello - def send_avahello(self, delegation_hex: str, delegated_privkey: ECKey): - delegation = FromHex(AvalancheDelegation(), delegation_hex) + def build_avahello(self, delegation: AvalancheDelegation, + delegated_privkey: ECKey) -> msg_avahello: local_sighash = hash256( delegation.getid() + struct.pack(" 0: + self.send_message(msg_notfound(not_found)) + + def get_ava_p2p_interface( node: TestNode, services=NODE_NETWORK | NODE_AVALANCHE) -> AvaP2PInterface: