Page MenuHomePhabricator

D6914.diff
No OneTemporary

D6914.diff

diff --git a/src/avalanche/proof.h b/src/avalanche/proof.h
--- a/src/avalanche/proof.h
+++ b/src/avalanche/proof.h
@@ -117,6 +117,7 @@
uint64_t getSequence() const { return sequence; }
int64_t getExpirationTime() const { return expirationTime; }
const CPubKey &getMaster() const { return master; }
+ const std::vector<SignedStake> &getStakes() const { return stakes; }
const ProofId &getId() const { return proofid; }
uint32_t getScore() const;
diff --git a/src/rpc/avalanche.cpp b/src/rpc/avalanche.cpp
--- a/src/rpc/avalanche.cpp
+++ b/src/rpc/avalanche.cpp
@@ -4,6 +4,7 @@
#include <avalanche/processor.h>
#include <avalanche/proof.h>
+#include <avalanche/proofbuilder.h>
#include <config.h>
#include <key_io.h>
#include <rpc/server.h>
@@ -30,6 +31,18 @@
return HexStr(g_avalanche->getSessionPubKey());
}
+static CPubKey ParsePubKey(const UniValue &param) {
+ const std::string keyHex = param.get_str();
+ if ((keyHex.length() != 2 * CPubKey::COMPRESSED_PUBLIC_KEY_SIZE &&
+ keyHex.length() != 2 * CPubKey::PUBLIC_KEY_SIZE) ||
+ !IsHex(keyHex)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
+ strprintf("Invalid public key: %s\n", keyHex));
+ }
+
+ return HexToPubKey(keyHex);
+}
+
static UniValue addavalanchenode(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
@@ -58,22 +71,129 @@
}
const NodeId nodeid = request.params[0].get_int64();
-
- // Parse the pubkey
- const std::string keyHex = request.params[1].get_str();
- if ((keyHex.length() != 2 * CPubKey::COMPRESSED_PUBLIC_KEY_SIZE &&
- keyHex.length() != 2 * CPubKey::PUBLIC_KEY_SIZE) ||
- !IsHex(keyHex)) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
- strprintf("Invalid public key: %s\n", keyHex));
- }
+ const CPubKey key = ParsePubKey(request.params[1]);
CDataStream ss(ParseHexV(request.params[2], "proof"), SER_NETWORK,
PROTOCOL_VERSION);
avalanche::Proof proof;
ss >> proof;
- return g_avalanche->addNode(nodeid, proof, {HexToPubKey(keyHex)});
+ return g_avalanche->addNode(nodeid, proof, key);
+}
+
+static UniValue buildavalancheproof(const Config &config,
+ const JSONRPCRequest &request) {
+ RPCHelpMan{
+ "buildavalancheproof",
+ "\nBuild a proof for avalanche's sybil resistance.\n",
+ {
+ {"sequence", RPCArg::Type::NUM, RPCArg::Optional::NO,
+ "The proof's sequence"},
+ {"expiration", RPCArg::Type::NUM, RPCArg::Optional::NO,
+ "A timestamp indicating when the proof expire"},
+ {"master", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
+ "The master public key"},
+ {
+ "stakes",
+ RPCArg::Type::ARR,
+ RPCArg::Optional::NO,
+ "The stakes to be signed and associated private keys",
+ {
+ {
+ "stake",
+ RPCArg::Type::OBJ,
+ RPCArg::Optional::NO,
+ "A stake to be attached to this proof",
+ {
+ {"txid", RPCArg::Type::STR_HEX,
+ RPCArg::Optional::NO, "The transaction id"},
+ {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO,
+ "The output number"},
+ {"amount", RPCArg::Type::AMOUNT,
+ RPCArg::Optional::NO, "The amount in this UTXO"},
+ {"height", RPCArg::Type::NUM, RPCArg::Optional::NO,
+ "The height at which this UTXO was mined"},
+ {"iscoinbase", RPCArg::Type::BOOL,
+ /* default */ "false",
+ "Indicate wether the UTXO is a coinbase"},
+ {"privatekey", RPCArg::Type::STR,
+ RPCArg::Optional::NO,
+ "private key in base58-encoding"},
+ },
+ },
+ },
+ },
+ },
+ RPCResult{"\"poof\" (string) A string that is a serialized, "
+ "hex-encoded proof data.\n"},
+ RPCExamples{HelpExampleRpc("buildavalancheproof",
+ "0 1234567800 \"<master>\" []")},
+ }
+ .Check(request);
+
+ RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM,
+ UniValue::VSTR, UniValue::VARR});
+
+ if (!g_avalanche) {
+ throw JSONRPCError(RPC_INTERNAL_ERROR, "Avalanche is not initialized");
+ }
+
+ const uint64_t sequence = request.params[0].get_int64();
+ const int64_t expiration = request.params[1].get_int64();
+ avalanche::ProofBuilder pb(sequence, expiration,
+ ParsePubKey(request.params[2]));
+
+ const UniValue &stakes = request.params[3].get_array();
+ for (size_t i = 0; i < stakes.size(); i++) {
+ const UniValue &stake = stakes[i];
+ RPCTypeCheckObj(stake,
+ {
+ {"txid", UniValue::VSTR},
+ {"vout", UniValue::VNUM},
+ // "amount" is also required but check is done below
+ // due to UniValue::VNUM erroneously not accepting
+ // quoted numerics (which are valid JSON)
+ {"height", UniValue::VNUM},
+ {"privatekey", UniValue::VSTR},
+ });
+
+ int nOut = find_value(stake, "vout").get_int();
+ if (nOut < 0) {
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
+ "vout must be positive");
+ }
+
+ const int height = find_value(stake, "height").get_int();
+ if (height < 1) {
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
+ "height must be positive");
+ }
+
+ const TxId txid(ParseHashO(stake, "txid"));
+ const COutPoint utxo(txid, nOut);
+
+ if (!stake.exists("amount")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Missing amount");
+ }
+
+ const Amount amount = AmountFromValue(find_value(stake, "amount"));
+
+ const UniValue &iscbparam = find_value(stake, "iscoinbase");
+ const bool iscoinbase =
+ iscbparam.isNull() ? false : iscbparam.get_bool();
+ CKey key = DecodeSecret(find_value(stake, "privatekey").get_str());
+
+ if (!pb.addUTXO(utxo, amount, uint32_t(height) << 1 | iscoinbase,
+ std::move(key))) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid private key");
+ }
+ }
+
+ const avalanche::Proof proof = pb.build();
+
+ CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
+ ss << proof;
+ return HexStr(ss.begin(), ss.end());
}
// clang-format off
@@ -82,6 +202,7 @@
// ------------------- ------------------------ ---------------------- ----------
{ "avalanche", "getavalanchekey", getavalanchekey, {}},
{ "avalanche", "addavalanchenode", addavalanchenode, {"nodeid"}},
+ { "avalanche", "buildavalancheproof", buildavalancheproof, {"sequence", "expiration", "master", "stakes"}},
};
// clang-format on
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -157,6 +157,7 @@
{"stop", 0, "wait"},
// Avalanche
{"addavalanchenode", 0, "nodeid"},
+ {"buildavalancheproof", 3, "stakes"},
// ABC specific RPC
{"setexcessiveblock", 0, "blockSize"},
};
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
@@ -22,16 +22,6 @@
from test_framework import schnorr
-AVA_HEX_PROOF = (
- "d97587e6c882615796011ec8f9a7b1c64104e556ba887297fd6a655bc2579d26814e8"
- "54c902448ec3ce3c6fe965e3420aa3f0e9611d3c0b8a73b6329741e2e726fd17f5e8a"
- "bd546a614b0e05433528b195870169a79ff23e1d58c64afad42ad81cffe53967e16be"
- "b692fc5776bb442c79c5d91de00cf21804712806594010038e168a34104d9d84ebf65"
- "22cf24c6fd4addedd068632a4db06c3cd6e40031c72d416e9eefbd90037383d8da9e9"
- "213a4818a02f108ac1656141ecfbfdde0aeb8620eb210c08b2ce587d9fec0799f9725"
- "5d2bc5a077c7b4e8ca8a68d6a0377abf0aa2473f97b37779431062dad50ef5fd21cf1"
- "7c0276a293ee5b3f5a130fc5f9b217585cae23e")
-
BLOCK_ACCEPTED = 0
BLOCK_REJECTED = 1
BLOCK_UNKNOWN = -1
@@ -191,10 +181,18 @@
"12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747")
pubkey = schnorr.getpubkey(privkey, compressed=True)
+ privatekey = node.get_deterministic_priv_key().key
+ proof = node.buildavalancheproof(11, 12, pubkey.hex(), [{
+ 'txid': "12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747",
+ 'vout': 0,
+ 'amount': 10,
+ 'height': 100,
+ 'privatekey': privatekey,
+ }])
+
# Activate the quorum.
for n in quorum:
- success = node.addavalanchenode(
- n.nodeid, pubkey.hex(), AVA_HEX_PROOF)
+ success = node.addavalanchenode(n.nodeid, pubkey.hex(), proof)
assert success is True
def can_find_block_in_poll(hash, resp=BLOCK_ACCEPTED):

File Metadata

Mime Type
text/plain
Expires
Tue, May 20, 22:36 (21 h, 25 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5866074
Default Alt Text
D6914.diff (9 KB)

Event Timeline