diff --git a/src/net_processing.cpp b/src/net_processing.cpp --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -2024,6 +2024,38 @@ return {}; } +//! Determine whether or not a peer can request a proof, and return it (or +//! nullptr if not found or not allowed). +static std::shared_ptr +FindProofForGetData(const CNode &peer, const avalanche::ProofId &proofid, + const std::chrono::seconds now) { + auto proof = g_avalanche->getProof(proofid); + + // We don't have this proof + if (!proof) { + return nullptr; + } + + auto proofTime = std::chrono::duration_cast( + g_avalanche->getProofTime(proofid).time_since_epoch()); + + // If we know that proof for long enough, allow for requesting it + if (proofTime <= now - UNCONDITIONAL_RELAY_DELAY) { + return proof; + } + + { + LOCK(cs_main); + // Otherwise, the proofs must have been announced recently. + if (State(peer.GetId()) + ->m_recently_announced_proofs.contains(proofid)) { + return proof; + } + } + + return nullptr; +} + static void ProcessGetData(const Config &config, CNode &pfrom, CConnman &connman, CTxMemPool &mempool, const std::atomic &interruptMsgProc) @@ -2041,10 +2073,10 @@ ? pfrom.m_tx_relay->m_last_mempool_req.load() : std::chrono::seconds::min(); - // Process as many TX items from the front of the getdata queue as - // possible, since they're common and it's efficient to batch process - // them. - while (it != pfrom.vRecvGetData.end() && it->IsMsgTx()) { + // Process as many TX or AVA_PROOF items from the front of the getdata + // queue as possible, since they're common and it's efficient to batch + // process them. + while (it != pfrom.vRecvGetData.end()) { if (interruptMsgProc) { return; } @@ -2054,43 +2086,67 @@ break; } - const CInv &inv = *it++; + const CInv &inv = *it; - if (pfrom.m_tx_relay == nullptr) { - // Ignore GETDATA requests for transactions from blocks-only - // peers. + if (it->IsMsgProof()) { + auto proof = + FindProofForGetData(pfrom, avalanche::ProofId{inv.hash}, now); + if (proof) { + connman.PushMessage( + &pfrom, msgMaker.Make(NetMsgType::AVAPROOF, *proof)); + // TODO Remove from the set of unbroadcasted proof ids + } else { + vNotFound.push_back(inv); + } + + ++it; continue; } - CTransactionRef tx = - FindTxForGetData(pfrom, TxId{inv.hash}, mempool_req, now); - if (tx) { - int nSendFlags = 0; - connman.PushMessage(&pfrom, - msgMaker.Make(nSendFlags, NetMsgType::TX, *tx)); - mempool.RemoveUnbroadcastTx(TxId(inv.hash)); - // As we're going to send tx, make sure its unconfirmed parents are - // made requestable. - for (const auto &txin : tx->vin) { - auto txinfo = mempool.info(txin.prevout.GetTxId()); - if (txinfo.tx && - txinfo.m_time > now - UNCONDITIONAL_RELAY_DELAY) { - // Relaying a transaction with a recent but unconfirmed - // parent. - if (WITH_LOCK( - pfrom.m_tx_relay->cs_tx_inventory, - return !pfrom.m_tx_relay->filterInventoryKnown - .contains(txin.prevout.GetTxId()))) { - LOCK(cs_main); - State(pfrom.GetId()) - ->m_recently_announced_invs.insert( - txin.prevout.GetTxId()); + if (it->IsMsgTx()) { + if (pfrom.m_tx_relay == nullptr) { + // Ignore GETDATA requests for transactions from blocks-only + // peers. + continue; + } + + CTransactionRef tx = + FindTxForGetData(pfrom, TxId{inv.hash}, mempool_req, now); + if (tx) { + int nSendFlags = 0; + connman.PushMessage( + &pfrom, msgMaker.Make(nSendFlags, NetMsgType::TX, *tx)); + mempool.RemoveUnbroadcastTx(TxId(inv.hash)); + // As we're going to send tx, make sure its unconfirmed parents + // are made requestable. + for (const auto &txin : tx->vin) { + auto txinfo = mempool.info(txin.prevout.GetTxId()); + if (txinfo.tx && + txinfo.m_time > now - UNCONDITIONAL_RELAY_DELAY) { + // Relaying a transaction with a recent but unconfirmed + // parent. + if (WITH_LOCK( + pfrom.m_tx_relay->cs_tx_inventory, + return !pfrom.m_tx_relay->filterInventoryKnown + .contains( + txin.prevout.GetTxId()))) { + LOCK(cs_main); + State(pfrom.GetId()) + ->m_recently_announced_invs.insert( + txin.prevout.GetTxId()); + } } } + } else { + vNotFound.push_back(inv); } - } else { - vNotFound.push_back(inv); + + ++it; + continue; } + + // It's neither a proof nor a transaction + break; } // Only process one BLOCK item per call, since they're uncommon and can be diff --git a/test/functional/abc_p2p_avalanche.py b/test/functional/abc_p2p_avalanche.py --- a/test/functional/abc_p2p_avalanche.py +++ b/test/functional/abc_p2p_avalanche.py @@ -5,6 +5,7 @@ """Test the resolution of forks via avalanche.""" import random import struct +import time from test_framework.avatools import create_coinbase_stakes from test_framework.key import ( @@ -23,6 +24,8 @@ hash256, msg_avahello, msg_avapoll, + MSG_AVA_PROOF, + msg_getdata, msg_tcpavaresponse, NODE_AVALANCHE, NODE_NETWORK, @@ -44,6 +47,8 @@ QUORUM_NODE_COUNT = 16 +UNCONDITIONAL_RELAY_DELAY = 2 * 60 + class TestNode(P2PInterface): @@ -146,10 +151,10 @@ node = self.nodes[0] # Build a fake quorum of nodes. - def get_node(): + def get_node(services=NODE_NETWORK | NODE_AVALANCHE): n = TestNode() node.add_p2p_connection( - n, services=NODE_NETWORK | NODE_AVALANCHE) + n, services=services) n.wait_for_verack() # Get our own node id so we can use it later. @@ -421,6 +426,48 @@ "getdata") and good_interface.last_message["getdata"].inv[-1].hash == proofid wait_until(getdata_found) + self.log.info('Check that we can download the proof from our peer') + + # Connect some blocks to trigger the proof verification + node.generate(2) + + node_proofid = FromHex(AvalancheProof(), proof).proofid + getdata = msg_getdata([CInv(MSG_AVA_PROOF, node_proofid)]) + + self.log.info( + "Proof has been inv'ed recently, check it can be requested") + good_interface.send_message(getdata) + + def proof_received(peer): + with p2p_lock: + return peer.last_message.get( + "avaproof") and peer.last_message["avaproof"].proof.proofid == node_proofid + wait_until(lambda: proof_received(good_interface)) + + # Restart the node + self.restart_node(0, self.extra_args[0] + [ + "-avaproof={}".format(proof), + "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN", + ]) + node.generate(2) + + self.log.info( + "The proof has not been announced, it cannot be requested") + peer = get_node(services=NODE_NETWORK) + peer.send_message(getdata) + + # Give enough time for the node to answer. Since we cannot check for a + # non-event this is the best we can do + time.sleep(2) + assert not proof_received(peer) + + self.log.info("The proof is known for long enough to be requested") + current_time = int(time.time()) + node.setmocktime(current_time + UNCONDITIONAL_RELAY_DELAY) + + peer.send_message(getdata) + wait_until(lambda: proof_received(peer)) + if __name__ == '__main__': AvalancheTest().main()