dash/src/rpc/rpcquorums.cpp
Dzutte 85537e4532 Dash-specific changes to support RPCHelpMan improvements
Signed-off-by: Dzutte <dzutte.tomsk@gmail.com>
2021-11-09 15:39:22 -08:00

820 lines
32 KiB
C++

// Copyright (c) 2017-2021 The Dash Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <chainparams.h>
#include <index/txindex.h>
#include <rpc/server.h>
#include <rpc/util.h>
#include <validation.h>
#include <masternode/node.h>
#include <evo/deterministicmns.h>
#include <llmq/quorums.h>
#include <llmq/commitment.h>
#include <llmq/blockprocessor.h>
#include <llmq/debug.h>
#include <llmq/dkgsession.h>
#include <llmq/signing.h>
#include <llmq/signing_shares.h>
namespace llmq {
extern const std::string CLSIG_REQUESTID_PREFIX;
}
static void quorum_list_help()
{
throw std::runtime_error(
RPCHelpMan{"quorum list",
"List of on-chain quorums\n",
{
{"count", RPCArg::Type::NUM, /* default */ "", "Number of quorums to list. Will list active quorums if \"count\" is not specified."},
},
RPCResult{
"{\n"
" \"quorumName\" : [ (array of strings) List of quorum hashes per some quorum type.\n"
" \"quorumHash\" (string) Quorum hash. Note: most recent quorums come first.\n"
" ,...\n"
" ],\n"
"}\n"
},
RPCExamples{
HelpExampleCli("quorum", "list")
+ HelpExampleCli("quorum", "list 10")
+ HelpExampleRpc("quorum", "list, 10")
},
}.ToString());
}
static UniValue quorum_list(const JSONRPCRequest& request)
{
if (request.fHelp || (request.params.size() != 1 && request.params.size() != 2))
quorum_list_help();
int count = -1;
if (!request.params[1].isNull()) {
count = ParseInt32V(request.params[1], "count");
if (count < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "count can't be negative");
}
}
UniValue ret(UniValue::VOBJ);
CBlockIndex* pindexTip = WITH_LOCK(cs_main, return ::ChainActive().Tip());
for (auto& type : llmq::CLLMQUtils::GetEnabledQuorumTypes(pindexTip)) {
const auto& llmq_params = llmq::GetLLMQParams(type);
UniValue v(UniValue::VARR);
auto quorums = llmq::quorumManager->ScanQuorums(type, pindexTip, count > -1 ? count : llmq_params.signingActiveQuorumCount);
for (auto& q : quorums) {
v.push_back(q->qc->quorumHash.ToString());
}
ret.pushKV(std::string(llmq_params.name), v);
}
return ret;
}
static void quorum_info_help()
{
throw std::runtime_error(
RPCHelpMan{"quorum info",
"Return information about a quorum\n",
{
{"llmqType", RPCArg::Type::NUM, RPCArg::Optional::NO, "LLMQ type."},
{"quorumHash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Block hash of quorum."},
{"includeSkShare", RPCArg::Type::BOOL, /* default */ "", "Include secret key share in output."},
},
RPCResults{},
RPCExamples{""},
}.ToString());
}
static UniValue BuildQuorumInfo(const llmq::CQuorumCPtr& quorum, bool includeMembers, bool includeSkShare)
{
UniValue ret(UniValue::VOBJ);
ret.pushKV("height", quorum->m_quorum_base_block_index->nHeight);
ret.pushKV("type", std::string(quorum->params.name));
ret.pushKV("quorumHash", quorum->qc->quorumHash.ToString());
ret.pushKV("minedBlock", quorum->minedBlockHash.ToString());
if (includeMembers) {
UniValue membersArr(UniValue::VARR);
for (size_t i = 0; i < quorum->members.size(); i++) {
auto& dmn = quorum->members[i];
UniValue mo(UniValue::VOBJ);
mo.pushKV("proTxHash", dmn->proTxHash.ToString());
mo.pushKV("pubKeyOperator", dmn->pdmnState->pubKeyOperator.Get().ToString());
mo.pushKV("valid", quorum->qc->validMembers[i]);
if (quorum->qc->validMembers[i]) {
CBLSPublicKey pubKey = quorum->GetPubKeyShare(i);
if (pubKey.IsValid()) {
mo.pushKV("pubKeyShare", pubKey.ToString());
}
}
membersArr.push_back(mo);
}
ret.pushKV("members", membersArr);
}
ret.pushKV("quorumPublicKey", quorum->qc->quorumPublicKey.ToString());
const CBLSSecretKey& skShare = quorum->GetSkShare();
if (includeSkShare && skShare.IsValid()) {
ret.pushKV("secretKeyShare", skShare.ToString());
}
return ret;
}
static UniValue quorum_info(const JSONRPCRequest& request)
{
if (request.fHelp || (request.params.size() != 3 && request.params.size() != 4))
quorum_info_help();
Consensus::LLMQType llmqType = (Consensus::LLMQType)ParseInt32V(request.params[1], "llmqType");
if (!Params().GetConsensus().llmqs.count(llmqType)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid LLMQ type");
}
uint256 quorumHash = ParseHashV(request.params[2], "quorumHash");
bool includeSkShare = false;
if (!request.params[3].isNull()) {
includeSkShare = ParseBoolV(request.params[3], "includeSkShare");
}
auto quorum = llmq::quorumManager->GetQuorum(llmqType, quorumHash);
if (!quorum) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "quorum not found");
}
return BuildQuorumInfo(quorum, true, includeSkShare);
}
static void quorum_dkgstatus_help()
{
throw std::runtime_error(
RPCHelpMan{"quorum dkgstatus",
"Return the status of the current DKG process.\n"
"Works only when SPORK_17_QUORUM_DKG_ENABLED spork is ON.\n",
{
{"detail_level", RPCArg::Type::NUM, /* default */ "0",
"Detail level of output.\n"
"0=Only show counts. 1=Show member indexes. 2=Show member's ProTxHashes."},
},
RPCResults{},
RPCExamples{""},
}.ToString());
}
static UniValue quorum_dkgstatus(const JSONRPCRequest& request)
{
if (request.fHelp || (request.params.size() < 1 || request.params.size() > 2)) {
quorum_dkgstatus_help();
}
int detailLevel = 0;
if (!request.params[1].isNull()) {
detailLevel = ParseInt32V(request.params[1], "detail_level");
if (detailLevel < 0 || detailLevel > 2) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid detail_level");
}
}
llmq::CDKGDebugStatus status;
llmq::quorumDKGDebugManager->GetLocalDebugStatus(status);
auto ret = status.ToJson(detailLevel);
CBlockIndex* pindexTip = WITH_LOCK(cs_main, return ::ChainActive().Tip());
int tipHeight = pindexTip->nHeight;
auto proTxHash = WITH_LOCK(activeMasternodeInfoCs, return activeMasternodeInfo.proTxHash);
UniValue minableCommitments(UniValue::VOBJ);
UniValue quorumConnections(UniValue::VOBJ);
for (const auto& type : llmq::CLLMQUtils::GetEnabledQuorumTypes(pindexTip)) {
const auto& llmq_params = llmq::GetLLMQParams(type);
if (fMasternodeMode) {
const CBlockIndex* pQuorumBaseBlockIndex = WITH_LOCK(cs_main, return ::ChainActive()[tipHeight - (tipHeight % llmq_params.dkgInterval)]);
auto allConnections = llmq::CLLMQUtils::GetQuorumConnections(llmq_params, pQuorumBaseBlockIndex, proTxHash, false);
auto outboundConnections = llmq::CLLMQUtils::GetQuorumConnections(llmq_params, pQuorumBaseBlockIndex, proTxHash, true);
std::map<uint256, CAddress> foundConnections;
g_connman->ForEachNode([&](const CNode* pnode) {
auto verifiedProRegTxHash = pnode->GetVerifiedProRegTxHash();
if (!verifiedProRegTxHash.IsNull() && allConnections.count(verifiedProRegTxHash)) {
foundConnections.emplace(verifiedProRegTxHash, pnode->addr);
}
});
UniValue arr(UniValue::VARR);
for (auto& ec : allConnections) {
UniValue obj(UniValue::VOBJ);
obj.pushKV("proTxHash", ec.ToString());
if (foundConnections.count(ec)) {
obj.pushKV("connected", true);
obj.pushKV("address", foundConnections[ec].ToString(false));
} else {
obj.pushKV("connected", false);
}
obj.pushKV("outbound", outboundConnections.count(ec) != 0);
arr.push_back(obj);
}
quorumConnections.pushKV(std::string(llmq_params.name), arr);
}
LOCK(cs_main);
llmq::CFinalCommitment fqc;
if (llmq::quorumBlockProcessor->GetMineableCommitment(llmq_params, tipHeight, fqc)) {
UniValue obj(UniValue::VOBJ);
fqc.ToJson(obj);
minableCommitments.pushKV(std::string(llmq_params.name), obj);
}
}
ret.pushKV("minableCommitments", minableCommitments);
ret.pushKV("quorumConnections", quorumConnections);
return ret;
}
static void quorum_memberof_help()
{
throw std::runtime_error(
RPCHelpMan{"quorum memberof",
"Checks which quorums the given masternode is a member of.\n",
{
{"proTxHash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "ProTxHash of the masternode."},
{"scanQuorumsCount", RPCArg::Type::NUM, /* default */ "",
"Number of quorums to scan for. If not specified,\n"
"the active quorum count for each specific quorum type is used."},
},
RPCResults{},
RPCExamples{""},
}.ToString());
}
static UniValue quorum_memberof(const JSONRPCRequest& request)
{
if (request.fHelp || (request.params.size() < 2 || request.params.size() > 3)) {
quorum_memberof_help();
}
uint256 protxHash = ParseHashV(request.params[1], "proTxHash");
int scanQuorumsCount = -1;
if (!request.params[2].isNull()) {
scanQuorumsCount = ParseInt32V(request.params[2], "scanQuorumsCount");
if (scanQuorumsCount <= 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid scanQuorumsCount parameter");
}
}
const CBlockIndex* pindexTip = WITH_LOCK(cs_main, return ::ChainActive().Tip());
auto mnList = deterministicMNManager->GetListForBlock(pindexTip);
auto dmn = mnList.GetMN(protxHash);
if (!dmn) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "masternode not found");
}
UniValue result(UniValue::VARR);
for (const auto& type : llmq::CLLMQUtils::GetEnabledQuorumTypes(pindexTip)) {
const auto& llmq_params = llmq::GetLLMQParams(type);
size_t count = llmq_params.signingActiveQuorumCount;
if (scanQuorumsCount != -1) {
count = (size_t)scanQuorumsCount;
}
auto quorums = llmq::quorumManager->ScanQuorums(llmq_params.type, count);
for (auto& quorum : quorums) {
if (quorum->IsMember(dmn->proTxHash)) {
auto json = BuildQuorumInfo(quorum, false, false);
json.pushKV("isValidMember", quorum->IsValidMember(dmn->proTxHash));
json.pushKV("memberIndex", quorum->GetMemberIndex(dmn->proTxHash));
result.push_back(json);
}
}
}
return result;
}
static void quorum_sign_help()
{
throw std::runtime_error(
RPCHelpMan{"quorum sign",
"Threshold-sign a message\n",
{
{"llmqType", RPCArg::Type::NUM, RPCArg::Optional::NO, "LLMQ type."},
{"id", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Request id."},
{"msgHash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Message hash."},
{"quorumHash", RPCArg::Type::STR_HEX, /* default */ "", "The quorum identifier."},
{"submit", RPCArg::Type::BOOL, /* default */ "true", "Submits the signature share to the network if this is true."},
},
RPCResult{
"\nReturns an object containing the signature share if this is false.\n"
},
RPCExamples{""},
}.ToString());
}
static void quorum_verify_help()
{
throw std::runtime_error(
RPCHelpMan{"quorum verify",
"Test if a quorum signature is valid for a request id and a message hash\n",
{
{"llmqType", RPCArg::Type::NUM, RPCArg::Optional::NO, "LLMQ type."},
{"id", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Request id."},
{"msgHash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Message hash."},
{"signature", RPCArg::Type::STR, RPCArg::Optional::NO, "Quorum signature to verify."},
{"quorumHash", RPCArg::Type::STR_HEX, /* default */ "",
"The quorum identifier.\n"
"Set to \"\" if you want to specify signHeight instead."},
{"signHeight", RPCArg::Type::NUM, /* default */ "",
"The height at which the message was signed.\n"
"Only works when quorumHash is \"\"."},
},
RPCResults{},
RPCExamples{""},
}.ToString());
}
static void quorum_hasrecsig_help()
{
throw std::runtime_error(
RPCHelpMan{"quorum hasrecsig",
"Test if a valid recovered signature is present\n",
{
{"llmqType", RPCArg::Type::NUM, RPCArg::Optional::NO, "LLMQ type."},
{"id", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Request id."},
{"msgHash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Message hash."},
},
RPCResults{},
RPCExamples{""},
}.ToString());
}
static void quorum_getrecsig_help()
{
throw std::runtime_error(
RPCHelpMan{"quorum getrecsig",
"Get a recovered signature\n",
{
{"llmqType", RPCArg::Type::NUM, RPCArg::Optional::NO, "LLMQ type."},
{"id", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Request id."},
{"msgHash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Message hash."},
},
RPCResults{},
RPCExamples{""},
}.ToString());
}
static void quorum_isconflicting_help()
{
throw std::runtime_error(
RPCHelpMan{"quorum isconflicting",
"Test if a conflict exists\n",
{
{"llmqType", RPCArg::Type::NUM, RPCArg::Optional::NO, "LLMQ type."},
{"id", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Request id."},
{"msgHash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Message hash."},
},
RPCResults{},
RPCExamples{""},
}.ToString());
}
static UniValue quorum_sigs_cmd(const JSONRPCRequest& request)
{
auto cmd = request.params[0].get_str();
if (request.fHelp || (request.params.size() != 4)) {
if (cmd == "sign") {
if ((request.params.size() < 4) || (request.params.size() > 6)) {
quorum_sign_help();
}
} else if (cmd == "verify") {
if (request.params.size() < 5 || request.params.size() > 7) {
quorum_verify_help();
}
} else if (cmd == "hasrecsig") {
quorum_hasrecsig_help();
} else if (cmd == "getrecsig") {
quorum_getrecsig_help();
} else if (cmd == "isconflicting") {
quorum_isconflicting_help();
} else {
// shouldn't happen as it's already handled by the caller
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid cmd");
}
}
Consensus::LLMQType llmqType = (Consensus::LLMQType)ParseInt32V(request.params[1], "llmqType");
if (!Params().GetConsensus().llmqs.count(llmqType)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid LLMQ type");
}
uint256 id = ParseHashV(request.params[2], "id");
uint256 msgHash = ParseHashV(request.params[3], "msgHash");
if (cmd == "sign") {
uint256 quorumHash;
if (!request.params[4].isNull() && !request.params[4].get_str().empty()) {
quorumHash = ParseHashV(request.params[4], "quorumHash");
}
bool fSubmit{true};
if (!request.params[5].isNull()) {
fSubmit = ParseBoolV(request.params[5], "submit");
}
if (fSubmit) {
return llmq::quorumSigningManager->AsyncSignIfMember(llmqType, id, msgHash, quorumHash);
} else {
llmq::CQuorumCPtr pQuorum;
if (quorumHash.IsNull()) {
pQuorum = llmq::quorumSigningManager->SelectQuorumForSigning(llmqType, id);
} else {
pQuorum = llmq::quorumManager->GetQuorum(llmqType, quorumHash);
}
if (pQuorum == nullptr) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "quorum not found");
}
llmq::CSigShare sigShare = llmq::quorumSigSharesManager->CreateSigShare(pQuorum, id, msgHash);
if (!sigShare.sigShare.Get().IsValid()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "failed to create sigShare");
}
UniValue obj(UniValue::VOBJ);
obj.pushKV("llmqType", static_cast<uint8_t>(llmqType));
obj.pushKV("quorumHash", sigShare.quorumHash.ToString());
obj.pushKV("quorumMember", sigShare.quorumMember);
obj.pushKV("id", id.ToString());
obj.pushKV("msgHash", msgHash.ToString());
obj.pushKV("signHash", sigShare.GetSignHash().ToString());
obj.pushKV("signature", sigShare.sigShare.Get().ToString());
return obj;
}
} else if (cmd == "verify") {
CBLSSignature sig;
if (!sig.SetHexStr(request.params[4].get_str())) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid signature format");
}
if (request.params[5].isNull() || (request.params[5].get_str().empty() && !request.params[6].isNull())) {
int signHeight{-1};
if (!request.params[6].isNull()) {
signHeight = ParseInt32V(request.params[6], "signHeight");
}
// First check against the current active set, if it fails check against the last active set
int signOffset{llmq::GetLLMQParams(llmqType).dkgInterval};
return llmq::quorumSigningManager->VerifyRecoveredSig(llmqType, signHeight, id, msgHash, sig, 0) ||
llmq::quorumSigningManager->VerifyRecoveredSig(llmqType, signHeight, id, msgHash, sig, signOffset);
} else {
uint256 quorumHash = ParseHashV(request.params[5], "quorumHash");
llmq::CQuorumCPtr quorum = llmq::quorumManager->GetQuorum(llmqType, quorumHash);
if (!quorum) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "quorum not found");
}
uint256 signHash = llmq::CLLMQUtils::BuildSignHash(llmqType, quorum->qc->quorumHash, id, msgHash);
return sig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash);
}
} else if (cmd == "hasrecsig") {
return llmq::quorumSigningManager->HasRecoveredSig(llmqType, id, msgHash);
} else if (cmd == "getrecsig") {
llmq::CRecoveredSig recSig;
if (!llmq::quorumSigningManager->GetRecoveredSigForId(llmqType, id, recSig)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "recovered signature not found");
}
if (recSig.msgHash != msgHash) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "recovered signature not found");
}
return recSig.ToJson();
} else if (cmd == "isconflicting") {
return llmq::quorumSigningManager->IsConflicting(llmqType, id, msgHash);
} else {
// shouldn't happen as it's already handled by the caller
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid cmd");
}
}
static void quorum_selectquorum_help()
{
throw std::runtime_error(
RPCHelpMan{"quorum selectquorum",
"Returns the quorum that would/should sign a request\n",
{
{"llmqType", RPCArg::Type::NUM, RPCArg::Optional::NO, "LLMQ type."},
{"id", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Request id."},
},
RPCResults{},
RPCExamples{""},
}.ToString());
}
static UniValue quorum_selectquorum(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 3) {
quorum_selectquorum_help();
}
Consensus::LLMQType llmqType = (Consensus::LLMQType)ParseInt32V(request.params[1], "llmqType");
if (!Params().GetConsensus().llmqs.count(llmqType)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid LLMQ type");
}
uint256 id = ParseHashV(request.params[2], "id");
UniValue ret(UniValue::VOBJ);
auto quorum = llmq::quorumSigningManager->SelectQuorumForSigning(llmqType, id);
if (!quorum) {
throw JSONRPCError(RPC_MISC_ERROR, "no quorums active");
}
ret.pushKV("quorumHash", quorum->qc->quorumHash.ToString());
UniValue recoveryMembers(UniValue::VARR);
for (int i = 0; i < quorum->params.recoveryMembers; i++) {
auto dmn = llmq::quorumSigSharesManager->SelectMemberForRecovery(quorum, id, i);
recoveryMembers.push_back(dmn->proTxHash.ToString());
}
ret.pushKV("recoveryMembers", recoveryMembers);
return ret;
}
static void quorum_dkgsimerror_help()
{
throw std::runtime_error(
RPCHelpMan{"quorum dkgsimerror",
"This enables simulation of errors and malicious behaviour in the DKG. Do NOT use this on mainnet\n"
"as you will get yourself very likely PoSe banned for this.\n",
{
{"type", RPCArg::Type::STR, RPCArg::Optional::NO, "Error type."},
{"rate", RPCArg::Type::NUM, RPCArg::Optional::NO, "Rate at which to simulate this error type."},
},
RPCResults{},
RPCExamples{""},
}.ToString());
}
static UniValue quorum_dkgsimerror(const JSONRPCRequest& request)
{
if (request.fHelp || (request.params.size() != 3)) {
quorum_dkgsimerror_help();
}
std::string type = request.params[1].get_str();
double rate = ParseDoubleV(request.params[2], "rate");
if (rate < 0 || rate > 1) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid rate. Must be between 0 and 1");
}
llmq::SetSimulatedDKGErrorRate(type, rate);
return UniValue();
}
static void quorum_getdata_help()
{
throw std::runtime_error(
RPCHelpMan{"quorum getdata",
"Send a QGETDATA message to the specified peer.\n",
{
{"nodeId", RPCArg::Type::NUM, RPCArg::Optional::NO, "The internal nodeId of the peer to request quorum data from."},
{"llmqType", RPCArg::Type::NUM, RPCArg::Optional::NO, "The quorum type related to the quorum data being requested."},
{"quorumHash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The quorum hash related to the quorum data being requested."},
{"dataMask", RPCArg::Type::NUM, RPCArg::Optional::NO,
"Specify what data to request.\n"
"Possible values: 1 - Request quorum verification vector\n"
"2 - Request encrypted contributions for member defined by \"proTxHash\". \"proTxHash\" must be specified if this option is used.\n"
"3 - Request both, 1 and 2"},
{"proTxHash", RPCArg::Type::STR_HEX, /* default */ "", "The proTxHash the contributions will be requested for. Must be member of the specified LLMQ."},
},
RPCResults{},
RPCExamples{""},
}.ToString());
}
static UniValue quorum_getdata(const JSONRPCRequest& request)
{
if (request.fHelp || (request.params.size() < 5 || request.params.size() > 6)) {
quorum_getdata_help();
}
NodeId nodeId = ParseInt64V(request.params[1], "nodeId");
Consensus::LLMQType llmqType = static_cast<Consensus::LLMQType>(ParseInt32V(request.params[2], "llmqType"));
uint256 quorumHash = ParseHashV(request.params[3], "quorumHash");
uint16_t nDataMask = static_cast<uint16_t>(ParseInt32V(request.params[4], "dataMask"));
uint256 proTxHash;
// Check if request wants ENCRYPTED_CONTRIBUTIONS data
if (nDataMask & llmq::CQuorumDataRequest::ENCRYPTED_CONTRIBUTIONS) {
if (!request.params[5].isNull()) {
proTxHash = ParseHashV(request.params[5], "proTxHash");
if (proTxHash.IsNull()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "proTxHash invalid");
}
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, "proTxHash missing");
}
}
const CBlockIndex* pQuorumBaseBlockIndex = WITH_LOCK(cs_main, return LookupBlockIndex(quorumHash));
return g_connman->ForNode(nodeId, [&](CNode* pNode) {
return llmq::quorumManager->RequestQuorumData(pNode, llmqType, pQuorumBaseBlockIndex, nDataMask, proTxHash);
});
}
[[ noreturn ]] static void quorum_help()
{
throw std::runtime_error(
RPCHelpMan{"quorum",
"Set of commands for quorums/LLMQs.\n"
"To get help on individual commands, use \"help quorum command\".\n"
"\nAvailable commands:\n"
" list - List of on-chain quorums\n"
" info - Return information about a quorum\n"
" dkgsimerror - Simulates DKG errors and malicious behavior\n"
" dkgstatus - Return the status of the current DKG process\n"
" memberof - Checks which quorums the given masternode is a member of\n"
" sign - Threshold-sign a message\n"
" verify - Test if a quorum signature is valid for a request id and a message hash\n"
" hasrecsig - Test if a valid recovered signature is present\n"
" getrecsig - Get a recovered signature\n"
" isconflicting - Test if a conflict exists\n"
" selectquorum - Return the quorum that would/should sign a request\n"
" getdata - Request quorum data from other masternodes in the quorum\n",
{
{"command", RPCArg::Type::STR, RPCArg::Optional::NO, "The command to execute"},
},
RPCResults{},
RPCExamples{""},
}.ToString());
}
static UniValue _quorum(const JSONRPCRequest& request)
{
if (request.fHelp && request.params.empty()) {
quorum_help();
}
std::string command;
if (!request.params[0].isNull()) {
command = request.params[0].get_str();
}
if (command == "list") {
return quorum_list(request);
} else if (command == "info") {
return quorum_info(request);
} else if (command == "dkgstatus") {
return quorum_dkgstatus(request);
} else if (command == "memberof") {
return quorum_memberof(request);
} else if (command == "sign" || command == "verify" || command == "hasrecsig" || command == "getrecsig" || command == "isconflicting") {
return quorum_sigs_cmd(request);
} else if (command == "selectquorum") {
return quorum_selectquorum(request);
} else if (command == "dkgsimerror") {
return quorum_dkgsimerror(request);
} else if (command == "getdata") {
return quorum_getdata(request);
} else {
quorum_help();
}
}
static void verifychainlock_help()
{
throw std::runtime_error(
RPCHelpMan{"verifychainlock",
"Test if a quorum signature is valid for a ChainLock.\n",
{
{"blockHash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash of the ChainLock."},
{"signature", RPCArg::Type::STR, RPCArg::Optional::NO, "The signature of the ChainLock."},
{"blockHeight", RPCArg::Type::NUM, /* default */ "", "The height of the ChainLock. There will be an internal lookup of \"blockHash\" if this is not provided."},
},
RPCResults{},
RPCExamples{""},
}.ToString());
}
static UniValue verifychainlock(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) {
verifychainlock_help();
}
const uint256 nBlockHash = ParseHashV(request.params[0], "blockHash");
CBLSSignature chainLockSig;
if (!chainLockSig.SetHexStr(request.params[1].get_str())) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid signature format");
}
int nBlockHeight;
if (request.params[2].isNull()) {
const CBlockIndex* pIndex = WITH_LOCK(cs_main, return LookupBlockIndex(nBlockHash));
if (pIndex == nullptr) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "blockHash not found");
}
nBlockHeight = pIndex->nHeight;
} else {
nBlockHeight = ParseInt32V(request.params[2], "blockHeight");
}
const auto llmqType = Params().GetConsensus().llmqTypeChainLocks;
const uint256 nRequestId = ::SerializeHash(std::make_pair(llmq::CLSIG_REQUESTID_PREFIX, nBlockHeight));
return llmq::CSigningManager::VerifyRecoveredSig(llmqType, nBlockHeight, nRequestId, nBlockHash, chainLockSig);
}
static void verifyislock_help()
{
throw std::runtime_error(
RPCHelpMan{"verifyislock",
"Test if a quorum signature is valid for an InstantSend Lock\n",
{
{"id", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Request id."},
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id."},
{"signature", RPCArg::Type::STR, RPCArg::Optional::NO, "The InstantSend Lock signature to verify."},
{"maxHeight", RPCArg::Type::NUM, /* default */ "", "The maximum height to search quorums from."},
},
RPCResults{},
RPCExamples{""},
}.ToString());
}
static UniValue verifyislock(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() < 3 || request.params.size() > 4) {
verifyislock_help();
}
uint256 id = ParseHashV(request.params[0], "id");
uint256 txid = ParseHashV(request.params[1], "txid");
CBLSSignature sig;
if (!sig.SetHexStr(request.params[2].get_str())) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid signature format");
}
if (g_txindex) {
g_txindex->BlockUntilSyncedToCurrentChain();
}
CBlockIndex* pindexMined{nullptr};
{
LOCK(cs_main);
CTransactionRef tx;
uint256 hash_block;
if (GetTransaction(txid, tx, Params().GetConsensus(), hash_block) && !hash_block.IsNull()) {
pindexMined = LookupBlockIndex(hash_block);
}
}
int maxHeight{-1};
if (!request.params[3].isNull()) {
maxHeight = ParseInt32V(request.params[3], "maxHeight");
}
int signHeight;
if (pindexMined == nullptr || pindexMined->nHeight > maxHeight) {
signHeight = maxHeight;
} else { // pindexMined->nHeight <= maxHeight
signHeight = pindexMined->nHeight;
}
auto llmqType = Params().GetConsensus().llmqTypeInstantSend;
// First check against the current active set, if it fails check against the last active set
int signOffset{llmq::GetLLMQParams(llmqType).dkgInterval};
return llmq::quorumSigningManager->VerifyRecoveredSig(llmqType, signHeight, id, txid, sig, 0) ||
llmq::quorumSigningManager->VerifyRecoveredSig(llmqType, signHeight, id, txid, sig, signOffset);
}
// clang-format off
static const CRPCCommand commands[] =
{ // category name actor (function)
// --------------------- ------------------------ -----------------------
{ "evo", "quorum", &_quorum, {} },
{ "evo", "verifychainlock", &verifychainlock, {"blockHash", "signature", "blockHeight"} },
{ "evo", "verifyislock", &verifyislock, {"id", "txid", "signature", "maxHeight"} },
};
// clang-format on
void RegisterQuorumsRPCCommands(CRPCTable &tableRPC)
{
for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++)
tableRPC.appendCommand(commands[vcidx].name, &commands[vcidx]);
}