diff --git a/src/avalanche/delegation.h b/src/avalanche/delegation.h --- a/src/avalanche/delegation.h +++ b/src/avalanche/delegation.h @@ -53,6 +53,7 @@ const LimitedProofId &getLimitedProofId() const { return limitedProofid; } const CPubKey &getProofMaster() const { return proofMaster; } const CPubKey &getDelegatedPubkey() const; + const std::vector &getLevels() const { return levels; } ProofId getProofId() const; diff --git a/src/rpc/avalanche.cpp b/src/rpc/avalanche.cpp --- a/src/rpc/avalanche.cpp +++ b/src/rpc/avalanche.cpp @@ -545,6 +545,91 @@ }; } +static RPCHelpMan decodeavalanchedelegation() { + return RPCHelpMan{ + "decodeavalanchedelegation", + "Convert a serialized, hex-encoded avalanche proof delegation, into " + "JSON object. \n" + "The validity of the delegation is not verified.\n", + { + {"delegation", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, + "The delegation hex string"}, + }, + RPCResult{ + RPCResult::Type::OBJ, + "", + "", + { + {RPCResult::Type::STR_HEX, "pubkey", + "The public key the proof is delegated to."}, + {RPCResult::Type::STR_HEX, "proofmaster", + "The delegated proof master public key."}, + {RPCResult::Type::STR_HEX, "delegationid", + "The identifier of this delegation."}, + {RPCResult::Type::STR_HEX, "limitedid", + "A delegated proof data hash excluding the master key."}, + {RPCResult::Type::STR_HEX, "proofid", + "A hash of the delegated proof limitedid and master key."}, + {RPCResult::Type::NUM, "depth", + "The number of delegation levels."}, + {RPCResult::Type::ARR, + "levels", + "", + { + {RPCResult::Type::OBJ, + "", + "", + { + {RPCResult::Type::NUM, "index", + "The index of this delegation level."}, + {RPCResult::Type::STR_HEX, "pubkey", + "This delegated public key for this level"}, + {RPCResult::Type::STR, "signature", + "Signature of this delegation level (base64 " + "encoded)"}, + }}, + }}, + }}, + RPCExamples{HelpExampleCli("decodeavalanchedelegation", + "\"\"") + + HelpExampleRpc("decodeavalanchedelegation", + "\"\"")}, + [&](const RPCHelpMan &self, const Config &config, + const JSONRPCRequest &request) -> UniValue { + RPCTypeCheck(request.params, {UniValue::VSTR}); + + avalanche::Delegation delegation; + bilingual_str error; + if (!avalanche::Delegation::FromHex( + delegation, request.params[0].get_str(), error)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, error.original); + } + + UniValue result(UniValue::VOBJ); + result.pushKV("pubkey", HexStr(delegation.getDelegatedPubkey())); + result.pushKV("proofmaster", HexStr(delegation.getProofMaster())); + result.pushKV("delegationid", delegation.getId().ToString()); + result.pushKV("limitedid", + delegation.getLimitedProofId().ToString()); + result.pushKV("proofid", delegation.getProofId().ToString()); + + auto levels = delegation.getLevels(); + result.pushKV("depth", levels.size()); + + UniValue levelsArray(UniValue::VARR); + for (auto &level : levels) { + UniValue obj(UniValue::VOBJ); + obj.pushKV("pubkey", HexStr(level.pubkey)); + obj.pushKV("signature", EncodeBase64(level.sig)); + levelsArray.push_back(std::move(obj)); + } + result.pushKV("levels", levelsArray); + + return result; + }, + }; +} + static RPCHelpMan getavalancheinfo() { return RPCHelpMan{ "getavalancheinfo", @@ -911,16 +996,17 @@ static const CRPCCommand commands[] = { // category actor (function) // ----------------- -------------------- - { "avalanche", getavalanchekey, }, - { "avalanche", addavalanchenode, }, - { "avalanche", buildavalancheproof, }, - { "avalanche", decodeavalancheproof, }, - { "avalanche", delegateavalancheproof, }, - { "avalanche", getavalancheinfo, }, - { "avalanche", getavalanchepeerinfo, }, - { "avalanche", getrawavalancheproof, }, - { "avalanche", sendavalancheproof, }, - { "avalanche", verifyavalancheproof, }, + { "avalanche", getavalanchekey, }, + { "avalanche", addavalanchenode, }, + { "avalanche", buildavalancheproof, }, + { "avalanche", decodeavalancheproof, }, + { "avalanche", delegateavalancheproof, }, + { "avalanche", decodeavalanchedelegation, }, + { "avalanche", getavalancheinfo, }, + { "avalanche", getavalanchepeerinfo, }, + { "avalanche", getrawavalancheproof, }, + { "avalanche", sendavalancheproof, }, + { "avalanche", verifyavalancheproof, }, }; // clang-format on 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 @@ -96,12 +96,13 @@ proofobj = FromHex(LegacyAvalancheProof(), proof) decodedproof = node.decodeavalancheproof(proof) limited_id_hex = f"{proofobj.limited_proofid:0{64}x}" + proofid_hex = f"{proofobj.proofid:0{64}x}" assert_equal(decodedproof["sequence"], proof_sequence) assert_equal(decodedproof["expiration"], proof_expiration) assert_equal(decodedproof["master"], proof_master) assert_equal(decodedproof["payoutscript"]["hex"], "") assert "signature" not in decodedproof.keys() - assert_equal(decodedproof["proofid"], f"{proofobj.proofid:0{64}x}") + assert_equal(decodedproof["proofid"], proofid_hex) assert_equal(decodedproof["limitedid"], limited_id_hex) assert_equal(decodedproof["staked_amount"], Decimal('50000000.00')) assert_equal(decodedproof["score"], 5000) @@ -206,16 +207,14 @@ ]) # Mine a block to trigger an attempt at registering the proof self.nodes[1].generate(1) - wait_for_proof(self.nodes[1], f"{proofobj.proofid:0{64}x}", - expect_orphan=True) + wait_for_proof(self.nodes[1], proofid_hex, expect_orphan=True) self.log.info("Connect to an up-to-date node to unorphan the proof") self.connect_nodes(1, node.index) self.sync_all() - wait_for_proof(self.nodes[1], f"{proofobj.proofid:0{64}x}", - expect_orphan=False) + wait_for_proof(self.nodes[1], proofid_hex, expect_orphan=False) - self.log.info("Generate delegations for the proof") + self.log.info("Generate delegations for the proof and decode them") # Stack up a few delegation levels def gen_privkey(): @@ -225,14 +224,27 @@ delegator_privkey = privkey delegation = None - for _ in range(10): + for i in range(10): delegated_privkey = gen_privkey() + delegated_pubkey = get_hex_pubkey(delegated_privkey) delegation = node.delegateavalancheproof( limited_id_hex, bytes_to_wif(delegator_privkey.get_bytes()), - get_hex_pubkey(delegated_privkey), + delegated_pubkey, delegation, ) + + dg_info = node.decodeavalanchedelegation(delegation) + assert_equal(dg_info['pubkey'], delegated_pubkey) + assert_equal(dg_info['proofmaster'], proof_master) + assert 'delegationid' in dg_info.keys() + assert_equal(dg_info['limitedid'], limited_id_hex) + assert_equal(dg_info['proofid'], proofid_hex) + assert_equal(dg_info['depth'], i + 1) + assert_equal(len(dg_info['levels']), dg_info['depth']) + assert_equal(dg_info['levels'][-1]['pubkey'], delegated_pubkey) + assert 'signature' in dg_info['levels'][-1] + delegator_privkey = delegated_privkey random_privkey = gen_privkey()