diff --git a/src/avalanche/peermanager.h b/src/avalanche/peermanager.h --- a/src/avalanche/peermanager.h +++ b/src/avalanche/peermanager.h @@ -68,6 +68,7 @@ Peer(PeerId peerid_, Proof proof_) : peerid(peerid_), proof(std::move(proof_)) {} + const Proof &getProof() const { return proof; } const ProofId &getProofId() const { return proof.getId(); } uint32_t getScore() const { return proof.getScore(); } }; @@ -164,7 +165,7 @@ PeerId selectPeer() const; /** - * Trigger maintenance of internal datastructures. + * Trigger maintenance of internal data structures. * Returns how much slot space was saved after compaction. */ uint64_t compact(); @@ -174,13 +175,15 @@ */ bool verify() const; - // Accssors. + // Accessors. uint64_t getSlotCount() const { return slotCount; } uint64_t getFragmentation() const { return fragmentation; } std::vector getPeers() const; std::vector getNodeIdsForPeer(PeerId peerId) const; + const Proof *getProof(const ProofId &proofid) const; + private: PeerSet::iterator fetchOrCreatePeer(const Proof &proof); bool addNodeToPeer(const PeerSet::iterator &it); diff --git a/src/avalanche/peermanager.cpp b/src/avalanche/peermanager.cpp --- a/src/avalanche/peermanager.cpp +++ b/src/avalanche/peermanager.cpp @@ -185,6 +185,12 @@ return it == peers.end() ? NO_PEER : it->peerid; } +const Proof *PeerManager::getProof(const ProofId &proofid) const { + auto &pview = peers.get(); + auto it = pview.find(proofid); + return it == pview.end() ? nullptr : &it->getProof(); +} + PeerManager::PeerSet::iterator PeerManager::fetchOrCreatePeer(const Proof &proof) { { diff --git a/src/avalanche/processor.h b/src/avalanche/processor.h --- a/src/avalanche/processor.h +++ b/src/avalanche/processor.h @@ -302,13 +302,7 @@ */ uint256 buildRemoteSighash(CNode *pfrom) const; - /** - * Get the local proof used by this node. - * - * @returns Proof for this node. - * @throws a std::runtime_error if there is no proof set for this node - */ - const Proof getProof() const; + const Proof *getProof(const ProofId &proofid) const; /* * Return whether the avalanche service flag should be set. diff --git a/src/avalanche/processor.cpp b/src/avalanche/processor.cpp --- a/src/avalanche/processor.cpp +++ b/src/avalanche/processor.cpp @@ -502,11 +502,9 @@ return true; } -const Proof Processor::getProof() const { - if (!peerData) { - throw std::runtime_error("proof not set"); - } - return peerData->proof; +const Proof *Processor::getProof(const ProofId &proofid) const { + LOCK(cs_peerManager); + return peerManager->getProof(proofid); } bool Processor::startEventLoop(CScheduler &scheduler) { diff --git a/src/init.cpp b/src/init.cpp --- a/src/init.cpp +++ b/src/init.cpp @@ -1236,6 +1236,10 @@ ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE); argsman.AddArg("-avasessionkey", "Avalanche session key (default: random)", ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE); + argsman.AddArg( + "-enableavaproofdownload", + "Enable avalanche proof downloading from peers (default: false)", + ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE); // Add the hidden options argsman.AddHiddenArgs(hidden_args); diff --git a/src/net_processing.cpp b/src/net_processing.cpp --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -513,6 +513,12 @@ struct AvalancheState { std::chrono::time_point last_poll; + + /** + * Track when to attempt download of announced proofs. + */ + std::multimap + m_proofs_process_time; }; AvalancheState m_avalanche_state; @@ -1060,6 +1066,11 @@ peer_download_state.m_tx_process_time.emplace(process_time, txid); } +static void RequestProof(CNodeState *state, const avalanche::ProofId &proofid, + std::chrono::microseconds current_time) { + state->m_avalanche_state.m_proofs_process_time.emplace(current_time, + proofid); +} } // namespace // This function is used for testing the stale tip eviction logic, see @@ -1770,6 +1781,9 @@ } case MSG_BLOCK: return LookupBlockIndex(BlockHash(inv.hash)) != nullptr; + case MSG_AVA_PROOF: + const avalanche::ProofId proofid(inv.hash); + return g_avalanche && g_avalanche->getProof(proofid) != nullptr; } // Don't know what it is, just say we already got one return true; @@ -3146,7 +3160,25 @@ // then fetch the blocks we need to catch up. best_block = std::move(hash); } - } else { + continue; + } + + if (inv.type == MSG_AVA_PROOF) { + const avalanche::ProofId proofid(inv.hash); + // Don't request proofs during IBD since they probably cannot be + // verified yet. + if (!fAlreadyHave && + !m_chainman.ActiveChainstate().IsInitialBlockDownload() && + g_avalanche && + gArgs.GetBoolArg("-enableavalanche", + AVALANCHE_DEFAULT_ENABLED) && + gArgs.GetBoolArg("-enableavaproofdownload", false)) { + RequestProof(State(pfrom.GetId()), proofid, current_time); + } + continue; + } + + if (inv.type == MSG_TX) { const TxId txid(inv.hash); pfrom.AddKnownTx(txid); if (fBlocksOnly) { @@ -5479,6 +5511,33 @@ } } + auto addGetDataAndMaybeFlush = [&](CInv inv) { + LogPrint(BCLog::NET, "Requesting %s peer=%d\n", inv.ToString(), + pto->GetId()); + vGetData.push_back(std::move(inv)); + if (vGetData.size() >= MAX_GETDATA_SZ) { + m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, + std::move(vGetData))); + vGetData.clear(); + } + }; + + // + // Message: getdata (proof) + // + auto &proof_process_time = + state.m_avalanche_state.m_proofs_process_time; + while (!proof_process_time.empty() && + proof_process_time.begin()->first <= current_time) { + const avalanche::ProofId proofid = + proof_process_time.begin()->second; + proof_process_time.erase(proof_process_time.begin()); + CInv inv(MSG_AVA_PROOF, proofid); + if (!AlreadyHave(inv, m_mempool)) { + addGetDataAndMaybeFlush(std::move(inv)); + } + } + // // Message: getdata (transactions) // @@ -5522,14 +5581,7 @@ // ago, then request. const auto last_request_time = GetTxRequestTime(txid); if (last_request_time <= current_time - GETDATA_TX_INTERVAL) { - LogPrint(BCLog::NET, "Requesting %s peer=%d\n", - inv.ToString(), pto->GetId()); - vGetData.push_back(inv); - if (vGetData.size() >= MAX_GETDATA_SZ) { - m_connman.PushMessage( - pto, msgMaker.Make(NetMsgType::GETDATA, vGetData)); - vGetData.clear(); - } + addGetDataAndMaybeFlush(std::move(inv)); UpdateTxRequestTime(txid, current_time); state.m_tx_download.m_tx_in_flight.emplace(txid, current_time); diff --git a/test/functional/abc_p2p_proof_inventory.py b/test/functional/abc_p2p_proof_inventory.py new file mode 100644 --- /dev/null +++ b/test/functional/abc_p2p_proof_inventory.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# Copyright (c) 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 proof inventory relaying +""" + +from test_framework.messages import ( + CInv, + MSG_AVA_PROOF, + msg_inv, +) +from test_framework.mininode import ( + P2PInterface, + mininode_lock, +) +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + wait_until, +) + + +class ProofInventoryTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = False + self.num_nodes = 1 + self.extra_args = [['-enableavalanche=1', + '-avacooldown=0', + '-enableavaproofdownload=1']] * self.num_nodes + + def test_proof_requests(self): + self.log.info( + "Test that we request proofs from all our peers, eventually") + + node = self.nodes[0] + for i in range(10): + node.add_p2p_connection(P2PInterface()) + + proofid = 0xdeadbeef + msg = msg_inv([CInv(t=MSG_AVA_PROOF, h=proofid)]) + for p in node.p2ps: + p.send_and_ping(msg) + + def getdata_found(peer): + with mininode_lock: + return peer.last_message.get( + "getdata") and peer.last_message["getdata"].inv[-1].hash == proofid + + wait_until(lambda: all(getdata_found(p) for p in node.p2ps)) + + def run_test(self): + self.test_proof_requests() + + +if __name__ == '__main__': + ProofInventoryTest().main()