diff --git a/src/net_processing.h b/src/net_processing.h --- a/src/net_processing.h +++ b/src/net_processing.h @@ -234,6 +234,7 @@ //! Next time to check for stale tip int64_t m_stale_tip_check_time; uint32_t getAvalancheVoteForBlock(const BlockHash &hash); + uint32_t getAvalancheVoteForAvalancheProof(const avalanche::ProofId &id); }; struct CNodeStateStats { diff --git a/src/net_processing.cpp b/src/net_processing.cpp --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -4354,6 +4354,10 @@ case MSG_BLOCK: { vote = getAvalancheVoteForBlock(BlockHash(inv.hash)); } break; + case MSG_AVA_PROOF: { + vote = getAvalancheVoteForAvalancheProof( + avalanche::ProofId(inv.hash)); + } break; } votes.emplace_back(vote, inv.hash); @@ -5184,6 +5188,26 @@ return -3; }; +uint32_t +PeerManager::getAvalancheVoteForAvalancheProof(const avalanche::ProofId &id) { + assert(g_avalanche); + + // Rejected proof + if (WITH_LOCK(cs_rejectedProofs, return rejectedProofs->contains(id))) { + return 1; + } + + // Unknown proof + if (g_avalanche->withPeerManager( + [&id](avalanche::PeerManager &pm) { return !pm.exists(id); })) { + return -1; + } + + // The proof is known, valid, not orphaned, and our currently active proof + // for the peer it belongs to + return 0; +}; + namespace { class CompareInvMempoolOrder { CTxMemPool *mp; diff --git a/test/functional/abc_p2p_avalanche_voting_proofs.py b/test/functional/abc_p2p_avalanche_voting_proofs.py new file mode 100755 --- /dev/null +++ b/test/functional/abc_p2p_avalanche_voting_proofs.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020-2021 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 polling of Avalanche stake proofs.""" + +from test_framework.avatools import ( + create_coinbase_stakes, + get_ava_p2p_interface, +) +from test_framework.key import ECKey, ECPubKey +from test_framework.messages import ( + MSG_AVA_PROOF, + AvalancheVote, + AvalancheVoteError, + FromHex, + LegacyAvalancheProof, +) +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal +from test_framework.wallet_util import bytes_to_wif + +QUORUM_NODE_COUNT = 16 + +PROOF_0 = { + "sequence": 11, + "expiration": 12, + "priv_key": "12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747", +} + + +class AvalancheTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 2 + self.extra_args = [ + ['-enableavalanche=1', '-avacooldown=0'], + ['-enableavalanche=1', '-avacooldown=0', '-noparkdeepreorg', '-maxreorgdepth=-1']] + self.supports_cli = False + + def run_test(self): + node = self.nodes[0] + poll_node = get_ava_p2p_interface(node) + + # Generate a coinbases to use for stakes + addrkey_0 = node.get_deterministic_priv_key() + blockhashes = node.generatetoaddress(1, addrkey_0.address) + stakes_0 = create_coinbase_stakes( + node, [blockhashes[0]], addrkey_0.key) + + # Get the key so we can verify signatures. + avakey = ECPubKey() + avakey.set(bytes.fromhex(node.getavalanchekey())) + + privkey = ECKey() + privkey.set(bytes.fromhex(PROOF_0["priv_key"]), True) + masterkey = bytes_to_wif(privkey.get_bytes()) + + # Build a proof + proof_0 = node.buildavalancheproof( + PROOF_0["sequence"], + PROOF_0["expiration"], + masterkey, + stakes_0) + + proof_0_obj = FromHex(LegacyAvalancheProof(), proof_0) + proof_0_id = proof_0_obj.proofid + unknown_proof_id = 100 + + # Create a helper to issue a poll and validate the responses + def poll_assert_response(expected): + self.log.info("Trigger polling from the node...") + poll_node.send_poll([proof_0_id, unknown_proof_id], MSG_AVA_PROOF) + 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])) + + # Check that all proofs are still unknown + poll_assert_response([AvalancheVote(AvalancheVoteError.UNKNOWN, proof_0_id), + AvalancheVote(AvalancheVoteError.UNKNOWN, unknown_proof_id)]) + + # Send the proof to the poll node + assert node.sendavalancheproof(proof_0) + + # Nodes should now respond that the proof is accepted + poll_assert_response([AvalancheVote(AvalancheVoteError.ACCEPTED, proof_0_id), + AvalancheVote(AvalancheVoteError.UNKNOWN, unknown_proof_id)]) + + +if __name__ == '__main__': + AvalancheTest().main()