diff --git a/src/avalanche/peermanager.h b/src/avalanche/peermanager.h --- a/src/avalanche/peermanager.h +++ b/src/avalanche/peermanager.h @@ -199,7 +199,8 @@ std::shared_ptr getProof(const ProofId &proofid) const; Peer::Timestamp getProofTime(const ProofId &proofid) const; - bool isOrphan(const ProofId &id); + bool isOrphan(const ProofId &id) const; + std::shared_ptr getOrphan(const ProofId &id) const; private: PeerSet::iterator fetchOrCreatePeer(const std::shared_ptr &proof); diff --git a/src/avalanche/peermanager.cpp b/src/avalanche/peermanager.cpp --- a/src/avalanche/peermanager.cpp +++ b/src/avalanche/peermanager.cpp @@ -491,8 +491,12 @@ return nodeids; } -bool PeerManager::isOrphan(const ProofId &id) { +bool PeerManager::isOrphan(const ProofId &id) const { return orphanProofs.getProof(id) != nullptr; } +std::shared_ptr PeerManager::getOrphan(const ProofId &id) const { + return orphanProofs.getProof(id); +} + } // namespace avalanche diff --git a/src/avalanche/processor.h b/src/avalanche/processor.h --- a/src/avalanche/processor.h +++ b/src/avalanche/processor.h @@ -298,6 +298,7 @@ std::shared_ptr getProof(const ProofId &proofid) const; std::shared_ptr getLocalProof() const; Peer::Timestamp getProofTime(const ProofId &proofid) const; + std::shared_ptr getOrphan(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 @@ -524,6 +524,11 @@ return peerManager->getProofTime(proofid); } +std::shared_ptr Processor::getOrphan(const ProofId &proofid) const { + LOCK(cs_peerManager); + return peerManager->getOrphan(proofid); +} + bool Processor::startEventLoop(CScheduler &scheduler) { return eventLoop.startEventLoop( scheduler, [this]() { this->runEventLoop(); }, AVALANCHE_TIME_STEP); diff --git a/src/rpc/avalanche.cpp b/src/rpc/avalanche.cpp --- a/src/rpc/avalanche.cpp +++ b/src/rpc/avalanche.cpp @@ -444,6 +444,58 @@ return ret; } +static UniValue getrawavalancheproof(const Config &config, + const JSONRPCRequest &request) { + RPCHelpMan{ + "getrawavalancheproof", + "Lookup for a known avalanche proof by id.\n", + { + {"proofid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, + "The hex encoded avalanche proof identifier."}, + }, + RPCResult{ + RPCResult::Type::OBJ, + "", + "", + {{ + {RPCResult::Type::STR_HEX, "proof", + "The hex encoded proof matching the identifier."}, + {RPCResult::Type::BOOL, "orphan", + "Whether the proof is an orphan."}, + }}, + }, + RPCExamples{HelpExampleRpc("getrawavalancheproof", "")}, + } + .Check(request); + + if (!g_avalanche) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Avalanche is not initialized"); + } + + const avalanche::ProofId proofid = + avalanche::ProofId::fromHex(request.params[0].get_str()); + + bool isOrphan = false; + auto proof = g_avalanche->getProof(proofid); + if (!proof) { + proof = g_avalanche->getOrphan(proofid); + isOrphan = true; + } + + if (!proof) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Proof not found"); + } + + UniValue ret(UniValue::VOBJ); + + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << *proof; + ret.pushKV("proof", HexStr(ss)); + ret.pushKV("orphan", isOrphan); + + return ret; +} + static void verifyProofOrThrow(const NodeContext &node, avalanche::Proof &proof, const std::string &proofHex) { bilingual_str error; @@ -539,6 +591,7 @@ { "avalanche", "decodeavalancheproof", decodeavalancheproof, {"proof"}}, { "avalanche", "delegateavalancheproof", delegateavalancheproof, {"proof", "privatekey", "publickey", "delegation"}}, { "avalanche", "getavalanchepeerinfo", getavalanchepeerinfo, {}}, + { "avalanche", "getrawavalancheproof", getrawavalancheproof, {"proofid"}}, { "avalanche", "sendavalancheproof", sendavalancheproof, {"proof"}}, { "avalanche", "verifyavalancheproof", verifyavalancheproof, {"proof"}}, }; diff --git a/test/functional/abc_rpc_avalancheproof.py b/test/functional/abc_rpc_avalancheproof.py --- a/test/functional/abc_rpc_avalancheproof.py +++ b/test/functional/abc_rpc_avalancheproof.py @@ -6,6 +6,7 @@ import base64 from decimal import Decimal +from test_framework.address import ADDRESS_BCHREG_UNSPENDABLE from test_framework.avatools import ( create_coinbase_stakes, create_stakes, @@ -284,6 +285,30 @@ "inv") and peer.last_message["inv"].inv[-1].hash == proofid wait_until(inv_found) + self.log.info("Check the getrawproof RPC") + + raw_proof = node.getrawavalancheproof("{:064x}".format(proofid)) + assert_equal(raw_proof['proof'], proof) + assert_equal(raw_proof['orphan'], False) + + assert_raises_rpc_error(-8, "Proof not found", + node.getrawavalancheproof, '0' * 64) + + # Orphan the proof by sending the stake + raw_tx = node.createrawtransaction( + [{"txid": stakes[-1]["txid"], "vout": 0}], + {ADDRESS_BCHREG_UNSPENDABLE: stakes[-1] + ["amount"] - Decimal('0.01')} + ) + signed_tx = node.signrawtransactionwithkey(raw_tx, [addrkey0.key]) + node.sendrawtransaction(signed_tx["hex"]) + node.generate(1) + wait_until(lambda: proofid not in get_proof_ids(node)) + + raw_proof = node.getrawavalancheproof("{:064x}".format(proofid)) + assert_equal(raw_proof['proof'], proof) + assert_equal(raw_proof['orphan'], True) + self.log.info("Bad proof should be rejected at startup") self.stop_node(0)