mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 03:52:49 +01:00
Merge #6115: backport: v21.0.x rc.2
db828177bf
Merge #6106: feat: create new composite quorum-command platformsign (pasta)a45e6df58b
Merge #6104: fix: adjust incorrect parameter description that says there is a default that doesn't exist (pasta)7330982631
Merge #6100: feat: make whitelist works with composite commands for platform needs (pasta)9998ffd92b
Merge #6096: feat: split type of error in submitchainlock - return enum in CL verifying code (pasta)cdf7a25012
Merge #6095: fix: createwallet to require 'load_on_startup' for descriptor wallets (pasta)c1c2c55690
Merge #6092: fix: mixing for partially unlocked descriptor wallets (pasta)117548660d
Merge #6073: feat: add logging for RPC HTTP requests: command, user, http-code, time of running (pasta) Pull request description: ## Issue being fixed or feature implemented Backports a set of 6 PRs needed in rc.2 ## What was done? Backported PRs with labels ## How Has This Been Tested? ## Breaking Changes ## Checklist: _Go over all the following points, and put an `x` in all the boxes that apply._ - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_ ACKs for top commit: kwvg: LGTM, utACKdb828177bf
UdjinM6: utACKdb828177bf
Tree-SHA512: 1b242c5db04bd5873ef622543bc2a25e29567f15962c677ea51ff05cb784291968d18f419bf611c206b912e8f15d687208ae75af33aab89038b6f0167d99c4bf
This commit is contained in:
commit
b0b6ba11c1
@ -35,6 +35,7 @@ Descriptor Wallet should be created.
|
|||||||
|
|
||||||
Without those options being set, a Legacy Wallet will be created instead.
|
Without those options being set, a Legacy Wallet will be created instead.
|
||||||
|
|
||||||
|
|
||||||
#### `IsMine` Semantics
|
#### `IsMine` Semantics
|
||||||
|
|
||||||
`IsMine` refers to the function used to determine whether a script belongs to the wallet.
|
`IsMine` refers to the function used to determine whether a script belongs to the wallet.
|
||||||
@ -117,3 +118,4 @@ descriptors with private keys for now as explained earlier.
|
|||||||
## RPC changes
|
## RPC changes
|
||||||
- `createwallet` has changed list of arguments: `createwallet "wallet_name" ( disable_private_keys blank "passphrase" avoid_reuse descriptors load_on_startup )`
|
- `createwallet` has changed list of arguments: `createwallet "wallet_name" ( disable_private_keys blank "passphrase" avoid_reuse descriptors load_on_startup )`
|
||||||
`load_on_startup` used to be an argument 5 but now has a number 6.
|
`load_on_startup` used to be an argument 5 but now has a number 6.
|
||||||
|
- `createwallet` requires specifying the `load_on_startup` flag when creating descriptor wallets due to breaking changes in v21.
|
||||||
|
9
doc/release-notes-6100.md
Normal file
9
doc/release-notes-6100.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
## Remote Procedure Call (RPC) Changes
|
||||||
|
|
||||||
|
### Improved support of composite commands
|
||||||
|
|
||||||
|
Dash Core's composite commands such as `quorum list` or `bls generate` now are compatible with a whitelist feature.
|
||||||
|
|
||||||
|
For example, whitelist `getblockcount,quorumlist` will let to call commands `getblockcount`, `quorum list`, but not `quorum sign`
|
||||||
|
|
||||||
|
Note, that adding simple `quorum` in whitelist will allow to use all kind of `quorum...` commands, such as `quorum`, `quorum list`, `quorum sign`, etc
|
6
doc/release-notes-6106.md
Normal file
6
doc/release-notes-6106.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
## Remote Procedure Call (RPC) Changes
|
||||||
|
|
||||||
|
### The new RPCs are:
|
||||||
|
|
||||||
|
- `quorum signplatform` This RPC is added for Platform needs. This composite command let to limit quorum type for signing by platform. It is equivalent of `quorum sign <platform type>`.
|
||||||
|
|
@ -25,7 +25,7 @@ void SetupChainParamsBaseOptions(ArgsManager& argsman)
|
|||||||
argsman.AddArg("-highsubsidyblocks=<n>", "The number of blocks with a higher than normal subsidy to mine at the start of a chain. Block after that height will have fixed subsidy base. (default: 0, devnet-only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
|
argsman.AddArg("-highsubsidyblocks=<n>", "The number of blocks with a higher than normal subsidy to mine at the start of a chain. Block after that height will have fixed subsidy base. (default: 0, devnet-only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
|
||||||
argsman.AddArg("-highsubsidyfactor=<n>", "The factor to multiply the normal block subsidy by while in the highsubsidyblocks window of a chain (default: 1, devnet-only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
|
argsman.AddArg("-highsubsidyfactor=<n>", "The factor to multiply the normal block subsidy by while in the highsubsidyblocks window of a chain (default: 1, devnet-only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
|
||||||
argsman.AddArg("-llmqchainlocks=<quorum name>", "Override the default LLMQ type used for ChainLocks. Allows using ChainLocks with smaller LLMQs. (default: llmq_devnet, devnet-only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
|
argsman.AddArg("-llmqchainlocks=<quorum name>", "Override the default LLMQ type used for ChainLocks. Allows using ChainLocks with smaller LLMQs. (default: llmq_devnet, devnet-only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
|
||||||
argsman.AddArg("-llmqdevnetparams=<size>:<threshold>", "Override the default LLMQ size for the LLMQ_DEVNET quorum (default: 3:2, devnet-only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
|
argsman.AddArg("-llmqdevnetparams=<size>:<threshold>", "Override the default LLMQ size for the LLMQ_DEVNET quorum (devnet-only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
|
||||||
argsman.AddArg("-llmqinstantsenddip0024=<quorum name>", "Override the default LLMQ type used for InstantSendDIP0024. (default: llmq_devnet_dip0024, devnet-only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
|
argsman.AddArg("-llmqinstantsenddip0024=<quorum name>", "Override the default LLMQ type used for InstantSendDIP0024. (default: llmq_devnet_dip0024, devnet-only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
|
||||||
argsman.AddArg("-llmqplatform=<quorum name>", "Override the default LLMQ type used for Platform. (default: llmq_devnet_platform, devnet-only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
|
argsman.AddArg("-llmqplatform=<quorum name>", "Override the default LLMQ type used for Platform. (default: llmq_devnet_platform, devnet-only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
|
||||||
argsman.AddArg("-llmqmnhf=<quorum name>", "Override the default LLMQ type used for EHF. (default: llmq_devnet, devnet-only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
|
argsman.AddArg("-llmqmnhf=<quorum name>", "Override the default LLMQ type used for EHF. (default: llmq_devnet, devnet-only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
|
||||||
|
@ -374,7 +374,7 @@ bool CheckCbTxBestChainlock(const CBlock& block, const CBlockIndex* pindex,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
uint256 curBlockCoinbaseCLBlockHash = pindex->GetAncestor(curBlockCoinbaseCLHeight)->GetBlockHash();
|
uint256 curBlockCoinbaseCLBlockHash = pindex->GetAncestor(curBlockCoinbaseCLHeight)->GetBlockHash();
|
||||||
if (!chainlock_handler.VerifyChainLock(llmq::CChainLockSig(curBlockCoinbaseCLHeight, curBlockCoinbaseCLBlockHash, opt_cbTx->bestCLSignature))) {
|
if (chainlock_handler.VerifyChainLock(llmq::CChainLockSig(curBlockCoinbaseCLHeight, curBlockCoinbaseCLBlockHash, opt_cbTx->bestCLSignature)) != llmq::VerifyRecSigStatus::Valid) {
|
||||||
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-invalid-clsig");
|
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-invalid-clsig");
|
||||||
}
|
}
|
||||||
} else if (opt_cbTx->bestCLHeightDiff != 0) {
|
} else if (opt_cbTx->bestCLHeightDiff != 0) {
|
||||||
|
@ -72,7 +72,47 @@ static std::vector<std::vector<std::string>> g_rpcauth;
|
|||||||
static std::map<std::string, std::set<std::string>> g_rpc_whitelist;
|
static std::map<std::string, std::set<std::string>> g_rpc_whitelist;
|
||||||
static bool g_rpc_whitelist_default = false;
|
static bool g_rpc_whitelist_default = false;
|
||||||
|
|
||||||
static void JSONErrorReply(HTTPRequest* req, const UniValue& objError, const UniValue& id)
|
extern std::vector<std::string> g_external_usernames;
|
||||||
|
class RpcHttpRequest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
HTTPRequest* m_req;
|
||||||
|
int64_t m_startTime;
|
||||||
|
int m_status{0};
|
||||||
|
std::string user;
|
||||||
|
std::string command;
|
||||||
|
|
||||||
|
RpcHttpRequest(HTTPRequest* req) :
|
||||||
|
m_req{req},
|
||||||
|
m_startTime{GetTimeMicros()}
|
||||||
|
{}
|
||||||
|
|
||||||
|
~RpcHttpRequest()
|
||||||
|
{
|
||||||
|
const bool is_external = find(g_external_usernames.begin(), g_external_usernames.end(), user) != g_external_usernames.end();
|
||||||
|
LogPrint(BCLog::BENCHMARK, "HTTP RPC request handled: user=%s command=%s external=%s status=%d elapsed_time_ms=%d\n", user, command, is_external, m_status, (GetTimeMicros() - m_startTime) / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool send_reply(int status, const std::string& response = "")
|
||||||
|
{
|
||||||
|
m_status = status;
|
||||||
|
m_req->WriteReply(status, response);
|
||||||
|
return m_status == HTTP_OK;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool whitelisted(JSONRPCRequest jreq)
|
||||||
|
{
|
||||||
|
if (g_rpc_whitelist[jreq.authUser].count(jreq.strMethod)) return true;
|
||||||
|
|
||||||
|
// check for composite command after
|
||||||
|
if (!jreq.params.isArray() || jreq.params.empty()) return false;
|
||||||
|
if (!jreq.params[0].isStr()) return false;
|
||||||
|
|
||||||
|
return g_rpc_whitelist[jreq.authUser].count(jreq.strMethod + jreq.params[0].get_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool JSONErrorReply(RpcHttpRequest& rpcRequest, const UniValue& objError, const UniValue& id)
|
||||||
{
|
{
|
||||||
// Send error reply from json-rpc error object
|
// Send error reply from json-rpc error object
|
||||||
int nStatus = HTTP_INTERNAL_SERVER_ERROR;
|
int nStatus = HTTP_INTERNAL_SERVER_ERROR;
|
||||||
@ -88,8 +128,8 @@ static void JSONErrorReply(HTTPRequest* req, const UniValue& objError, const Uni
|
|||||||
|
|
||||||
std::string strReply = JSONRPCReply(NullUniValue, objError, id);
|
std::string strReply = JSONRPCReply(NullUniValue, objError, id);
|
||||||
|
|
||||||
req->WriteHeader("Content-Type", "application/json");
|
rpcRequest.m_req->WriteHeader("Content-Type", "application/json");
|
||||||
req->WriteReply(nStatus, strReply);
|
return rpcRequest.send_reply(nStatus, strReply);
|
||||||
}
|
}
|
||||||
|
|
||||||
//This function checks username and password against -rpcauth
|
//This function checks username and password against -rpcauth
|
||||||
@ -146,24 +186,25 @@ static bool RPCAuthorized(const std::string& strAuth, std::string& strAuthUserna
|
|||||||
return multiUserAuthorized(strUserPass);
|
return multiUserAuthorized(strUserPass);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool HTTPReq_JSONRPC(const CoreContext& context, HTTPRequest* req, bool external = false)
|
static bool HTTPReq_JSONRPC(const CoreContext& context, HTTPRequest* req)
|
||||||
{
|
{
|
||||||
|
RpcHttpRequest rpcRequest(req);
|
||||||
|
|
||||||
// JSONRPC handles only POST
|
// JSONRPC handles only POST
|
||||||
if (req->GetRequestMethod() != HTTPRequest::POST) {
|
if (req->GetRequestMethod() != HTTPRequest::POST) {
|
||||||
req->WriteReply(HTTP_BAD_METHOD, "JSONRPC server handles only POST requests");
|
return rpcRequest.send_reply(HTTP_BAD_METHOD, "JSONRPC server handles only POST requests");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
// Check authorization
|
// Check authorization
|
||||||
std::pair<bool, std::string> authHeader = req->GetHeader("authorization");
|
std::pair<bool, std::string> authHeader = req->GetHeader("authorization");
|
||||||
if (!authHeader.first) {
|
if (!authHeader.first) {
|
||||||
req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA);
|
req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA);
|
||||||
req->WriteReply(HTTP_UNAUTHORIZED);
|
return rpcRequest.send_reply(HTTP_UNAUTHORIZED);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JSONRPCRequest jreq(context);
|
JSONRPCRequest jreq(context);
|
||||||
|
|
||||||
jreq.peerAddr = req->GetPeer().ToString();
|
jreq.peerAddr = req->GetPeer().ToString();
|
||||||
if (!RPCAuthorized(authHeader.second, jreq.authUser)) {
|
if (!RPCAuthorized(authHeader.second, rpcRequest.user)) {
|
||||||
LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", jreq.peerAddr);
|
LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", jreq.peerAddr);
|
||||||
|
|
||||||
/* Deter brute-forcing
|
/* Deter brute-forcing
|
||||||
@ -172,9 +213,9 @@ static bool HTTPReq_JSONRPC(const CoreContext& context, HTTPRequest* req, bool e
|
|||||||
UninterruptibleSleep(std::chrono::milliseconds{250});
|
UninterruptibleSleep(std::chrono::milliseconds{250});
|
||||||
|
|
||||||
req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA);
|
req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA);
|
||||||
req->WriteReply(HTTP_UNAUTHORIZED);
|
return rpcRequest.send_reply(HTTP_UNAUTHORIZED);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
jreq.authUser = rpcRequest.user;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Parse request
|
// Parse request
|
||||||
@ -189,16 +230,16 @@ static bool HTTPReq_JSONRPC(const CoreContext& context, HTTPRequest* req, bool e
|
|||||||
bool user_has_whitelist = g_rpc_whitelist.count(jreq.authUser);
|
bool user_has_whitelist = g_rpc_whitelist.count(jreq.authUser);
|
||||||
if (!user_has_whitelist && g_rpc_whitelist_default) {
|
if (!user_has_whitelist && g_rpc_whitelist_default) {
|
||||||
LogPrintf("RPC User %s not allowed to call any methods\n", jreq.authUser);
|
LogPrintf("RPC User %s not allowed to call any methods\n", jreq.authUser);
|
||||||
req->WriteReply(HTTP_FORBIDDEN);
|
return rpcRequest.send_reply(HTTP_FORBIDDEN);
|
||||||
return false;
|
|
||||||
|
|
||||||
// singleton request
|
// singleton request
|
||||||
} else if (valRequest.isObject()) {
|
} else if (valRequest.isObject()) {
|
||||||
jreq.parse(valRequest);
|
jreq.parse(valRequest);
|
||||||
if (user_has_whitelist && !g_rpc_whitelist[jreq.authUser].count(jreq.strMethod)) {
|
rpcRequest.command = jreq.strMethod;
|
||||||
|
|
||||||
|
if (user_has_whitelist && !whitelisted(jreq)) {
|
||||||
LogPrintf("RPC User %s not allowed to call method %s\n", jreq.authUser, jreq.strMethod);
|
LogPrintf("RPC User %s not allowed to call method %s\n", jreq.authUser, jreq.strMethod);
|
||||||
req->WriteReply(HTTP_FORBIDDEN);
|
return rpcRequest.send_reply(HTTP_FORBIDDEN);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
UniValue result = tableRPC.execute(jreq);
|
UniValue result = tableRPC.execute(jreq);
|
||||||
|
|
||||||
@ -215,10 +256,9 @@ static bool HTTPReq_JSONRPC(const CoreContext& context, HTTPRequest* req, bool e
|
|||||||
const UniValue& request = valRequest[reqIdx].get_obj();
|
const UniValue& request = valRequest[reqIdx].get_obj();
|
||||||
// Parse method
|
// Parse method
|
||||||
std::string strMethod = find_value(request, "method").get_str();
|
std::string strMethod = find_value(request, "method").get_str();
|
||||||
if (!g_rpc_whitelist[jreq.authUser].count(strMethod)) {
|
if (!whitelisted(jreq)) {
|
||||||
LogPrintf("RPC User %s not allowed to call method %s\n", jreq.authUser, strMethod);
|
LogPrintf("RPC User %s not allowed to call method %s\n", jreq.authUser, strMethod);
|
||||||
req->WriteReply(HTTP_FORBIDDEN);
|
return rpcRequest.send_reply(HTTP_FORBIDDEN);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -229,15 +269,13 @@ static bool HTTPReq_JSONRPC(const CoreContext& context, HTTPRequest* req, bool e
|
|||||||
throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error");
|
throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error");
|
||||||
|
|
||||||
req->WriteHeader("Content-Type", "application/json");
|
req->WriteHeader("Content-Type", "application/json");
|
||||||
req->WriteReply(HTTP_OK, strReply);
|
return rpcRequest.send_reply(HTTP_OK, strReply);
|
||||||
} catch (const UniValue& objError) {
|
} catch (const UniValue& objError) {
|
||||||
JSONErrorReply(req, objError, jreq.id);
|
return JSONErrorReply(rpcRequest, objError, jreq.id);
|
||||||
return false;
|
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
JSONErrorReply(req, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id);
|
return JSONErrorReply(rpcRequest, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
assert(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool InitRPCAuthentication()
|
static bool InitRPCAuthentication()
|
||||||
|
@ -154,8 +154,8 @@ static struct evhttp* eventHTTP = nullptr;
|
|||||||
static std::vector<CSubNet> rpc_allow_subnets;
|
static std::vector<CSubNet> rpc_allow_subnets;
|
||||||
//! Work queue for handling longer requests off the event loop thread
|
//! Work queue for handling longer requests off the event loop thread
|
||||||
static std::unique_ptr<WorkQueue<HTTPClosure>> g_work_queue{nullptr};
|
static std::unique_ptr<WorkQueue<HTTPClosure>> g_work_queue{nullptr};
|
||||||
//! List of 'external' RPC users
|
//! List of 'external' RPC users (global variable, used by httprpc)
|
||||||
static std::vector<std::string> g_external_usernames;
|
std::vector<std::string> g_external_usernames;
|
||||||
//! Handlers for (sub)paths
|
//! Handlers for (sub)paths
|
||||||
static std::vector<HTTPPathHandler> pathHandlers;
|
static std::vector<HTTPPathHandler> pathHandlers;
|
||||||
//! Bound listening sockets
|
//! Bound listening sockets
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include <txmempool.h>
|
#include <txmempool.h>
|
||||||
#include <util/thread.h>
|
#include <util/thread.h>
|
||||||
#include <util/time.h>
|
#include <util/time.h>
|
||||||
|
#include <util/underlying.h>
|
||||||
#include <validation.h>
|
#include <validation.h>
|
||||||
#include <validationinterface.h>
|
#include <validationinterface.h>
|
||||||
|
|
||||||
@ -130,8 +131,8 @@ PeerMsgRet CChainLocksHandler::ProcessNewChainLock(const NodeId from, const llmq
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!VerifyChainLock(clsig)) {
|
if (const auto ret = VerifyChainLock(clsig); ret != VerifyRecSigStatus::Valid) {
|
||||||
LogPrint(BCLog::CHAINLOCKS, "CChainLocksHandler::%s -- invalid CLSIG (%s), peer=%d\n", __func__, clsig.ToString(), from);
|
LogPrint(BCLog::CHAINLOCKS, "CChainLocksHandler::%s -- invalid CLSIG (%s), status=%d peer=%d\n", __func__, clsig.ToString(), ToUnderlying(ret), from);
|
||||||
if (from != -1) {
|
if (from != -1) {
|
||||||
return tl::unexpected{10};
|
return tl::unexpected{10};
|
||||||
}
|
}
|
||||||
@ -551,10 +552,12 @@ bool CChainLocksHandler::HasChainLock(int nHeight, const uint256& blockHash) con
|
|||||||
return InternalHasChainLock(nHeight, blockHash);
|
return InternalHasChainLock(nHeight, blockHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CChainLocksHandler::VerifyChainLock(const CChainLockSig& clsig) const
|
|
||||||
|
VerifyRecSigStatus CChainLocksHandler::VerifyChainLock(const CChainLockSig& clsig) const
|
||||||
{
|
{
|
||||||
const auto llmqType = Params().GetConsensus().llmqTypeChainLocks;
|
const auto llmqType = Params().GetConsensus().llmqTypeChainLocks;
|
||||||
const uint256 nRequestId = ::SerializeHash(std::make_pair(llmq::CLSIG_REQUESTID_PREFIX, clsig.getHeight()));
|
const uint256 nRequestId = ::SerializeHash(std::make_pair(llmq::CLSIG_REQUESTID_PREFIX, clsig.getHeight()));
|
||||||
|
|
||||||
return llmq::VerifyRecoveredSig(llmqType, m_chainstate.m_chain, qman, clsig.getHeight(), nRequestId, clsig.getBlockHash(), clsig.getSig());
|
return llmq::VerifyRecoveredSig(llmqType, m_chainstate.m_chain, qman, clsig.getHeight(), nRequestId, clsig.getBlockHash(), clsig.getSig());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include <crypto/common.h>
|
#include <crypto/common.h>
|
||||||
#include <llmq/signing.h>
|
#include <llmq/signing.h>
|
||||||
|
#include <llmq/quorums.h>
|
||||||
#include <net.h>
|
#include <net.h>
|
||||||
#include <net_types.h>
|
#include <net_types.h>
|
||||||
#include <primitives/block.h>
|
#include <primitives/block.h>
|
||||||
@ -114,7 +115,7 @@ public:
|
|||||||
|
|
||||||
bool HasChainLock(int nHeight, const uint256& blockHash) const EXCLUSIVE_LOCKS_REQUIRED(!cs);
|
bool HasChainLock(int nHeight, const uint256& blockHash) const EXCLUSIVE_LOCKS_REQUIRED(!cs);
|
||||||
bool HasConflictingChainLock(int nHeight, const uint256& blockHash) const EXCLUSIVE_LOCKS_REQUIRED(!cs);
|
bool HasConflictingChainLock(int nHeight, const uint256& blockHash) const EXCLUSIVE_LOCKS_REQUIRED(!cs);
|
||||||
bool VerifyChainLock(const CChainLockSig& clsig) const;
|
VerifyRecSigStatus VerifyChainLock(const CChainLockSig& clsig) const;
|
||||||
|
|
||||||
bool IsTxSafeForMining(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(!cs);
|
bool IsTxSafeForMining(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(!cs);
|
||||||
|
|
||||||
|
@ -1169,7 +1169,7 @@ CQuorumCPtr SelectQuorumForSigning(const Consensus::LLMQParams& llmq_params, con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VerifyRecoveredSig(Consensus::LLMQType llmqType, const CChain& active_chain, const CQuorumManager& qman,
|
VerifyRecSigStatus VerifyRecoveredSig(Consensus::LLMQType llmqType, const CChain& active_chain, const CQuorumManager& qman,
|
||||||
int signedAtHeight, const uint256& id, const uint256& msgHash, const CBLSSignature& sig,
|
int signedAtHeight, const uint256& id, const uint256& msgHash, const CBLSSignature& sig,
|
||||||
const int signOffset)
|
const int signOffset)
|
||||||
{
|
{
|
||||||
@ -1177,10 +1177,11 @@ bool VerifyRecoveredSig(Consensus::LLMQType llmqType, const CChain& active_chain
|
|||||||
assert(llmq_params_opt.has_value());
|
assert(llmq_params_opt.has_value());
|
||||||
auto quorum = SelectQuorumForSigning(llmq_params_opt.value(), active_chain, qman, id, signedAtHeight, signOffset);
|
auto quorum = SelectQuorumForSigning(llmq_params_opt.value(), active_chain, qman, id, signedAtHeight, signOffset);
|
||||||
if (!quorum) {
|
if (!quorum) {
|
||||||
return false;
|
return VerifyRecSigStatus::NoQuorum;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint256 signHash = BuildSignHash(llmqType, quorum->qc->quorumHash, id, msgHash);
|
uint256 signHash = BuildSignHash(llmqType, quorum->qc->quorumHash, id, msgHash);
|
||||||
return sig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash);
|
const bool ret = sig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash);
|
||||||
|
return ret ? VerifyRecSigStatus::Valid : VerifyRecSigStatus::Invalid;
|
||||||
}
|
}
|
||||||
} // namespace llmq
|
} // namespace llmq
|
||||||
|
@ -36,6 +36,13 @@ using CDeterministicMNCPtr = std::shared_ptr<const CDeterministicMN>;
|
|||||||
|
|
||||||
namespace llmq
|
namespace llmq
|
||||||
{
|
{
|
||||||
|
enum class VerifyRecSigStatus
|
||||||
|
{
|
||||||
|
NoQuorum,
|
||||||
|
Invalid,
|
||||||
|
Valid,
|
||||||
|
};
|
||||||
|
|
||||||
class CDKGSessionManager;
|
class CDKGSessionManager;
|
||||||
class CQuorumBlockProcessor;
|
class CQuorumBlockProcessor;
|
||||||
|
|
||||||
@ -298,9 +305,9 @@ CQuorumCPtr SelectQuorumForSigning(const Consensus::LLMQParams& llmq_params, con
|
|||||||
const uint256& selectionHash, int signHeight = -1 /*chain tip*/, int signOffset = SIGN_HEIGHT_OFFSET);
|
const uint256& selectionHash, int signHeight = -1 /*chain tip*/, int signOffset = SIGN_HEIGHT_OFFSET);
|
||||||
|
|
||||||
// Verifies a recovered sig that was signed while the chain tip was at signedAtTip
|
// Verifies a recovered sig that was signed while the chain tip was at signedAtTip
|
||||||
bool VerifyRecoveredSig(Consensus::LLMQType llmqType, const CChain& active_chain, const CQuorumManager& qman,
|
VerifyRecSigStatus VerifyRecoveredSig(Consensus::LLMQType llmqType, const CChain& active_chain, const CQuorumManager& qman,
|
||||||
int signedAtHeight, const uint256& id, const uint256& msgHash, const CBLSSignature& sig,
|
int signedAtHeight, const uint256& id, const uint256& msgHash, const CBLSSignature& sig,
|
||||||
int signOffset = SIGN_HEIGHT_OFFSET);
|
int signOffset = SIGN_HEIGHT_OFFSET);
|
||||||
} // namespace llmq
|
} // namespace llmq
|
||||||
|
|
||||||
template<typename T> struct SaltedHasherImpl;
|
template<typename T> struct SaltedHasherImpl;
|
||||||
|
@ -578,6 +578,30 @@ PeerMsgRet CSigningManager::ProcessMessage(const CNode& pfrom, const std::string
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool PreVerifyRecoveredSig(const CQuorumManager& quorum_manager, const CRecoveredSig& recoveredSig, bool& retBan)
|
||||||
|
{
|
||||||
|
retBan = false;
|
||||||
|
|
||||||
|
auto llmqType = recoveredSig.getLlmqType();
|
||||||
|
if (!Params().GetLLMQ(llmqType).has_value()) {
|
||||||
|
retBan = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CQuorumCPtr quorum = quorum_manager.GetQuorum(llmqType, recoveredSig.getQuorumHash());
|
||||||
|
|
||||||
|
if (!quorum) {
|
||||||
|
LogPrint(BCLog::LLMQ, "CSigningManager::%s -- quorum %s not found\n", __func__,
|
||||||
|
recoveredSig.getQuorumHash().ToString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!IsQuorumActive(llmqType, quorum_manager, quorum->qc->quorumHash)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
PeerMsgRet CSigningManager::ProcessMessageRecoveredSig(const CNode& pfrom, const std::shared_ptr<const CRecoveredSig>& recoveredSig)
|
PeerMsgRet CSigningManager::ProcessMessageRecoveredSig(const CNode& pfrom, const std::shared_ptr<const CRecoveredSig>& recoveredSig)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
@ -614,30 +638,6 @@ PeerMsgRet CSigningManager::ProcessMessageRecoveredSig(const CNode& pfrom, const
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CSigningManager::PreVerifyRecoveredSig(const CQuorumManager& quorum_manager, const CRecoveredSig& recoveredSig, bool& retBan)
|
|
||||||
{
|
|
||||||
retBan = false;
|
|
||||||
|
|
||||||
auto llmqType = recoveredSig.getLlmqType();
|
|
||||||
if (!Params().GetLLMQ(llmqType).has_value()) {
|
|
||||||
retBan = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
CQuorumCPtr quorum = quorum_manager.GetQuorum(llmqType, recoveredSig.getQuorumHash());
|
|
||||||
|
|
||||||
if (!quorum) {
|
|
||||||
LogPrint(BCLog::LLMQ, "CSigningManager::%s -- quorum %s not found\n", __func__,
|
|
||||||
recoveredSig.getQuorumHash().ToString());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!IsQuorumActive(llmqType, quorum_manager, quorum->qc->quorumHash)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CSigningManager::CollectPendingRecoveredSigsToVerify(
|
void CSigningManager::CollectPendingRecoveredSigsToVerify(
|
||||||
size_t maxUniqueSessions,
|
size_t maxUniqueSessions,
|
||||||
std::unordered_map<NodeId, std::list<std::shared_ptr<const CRecoveredSig>>>& retSigShares,
|
std::unordered_map<NodeId, std::list<std::shared_ptr<const CRecoveredSig>>>& retSigShares,
|
||||||
|
@ -201,7 +201,6 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
PeerMsgRet ProcessMessageRecoveredSig(const CNode& pfrom, const std::shared_ptr<const CRecoveredSig>& recoveredSig);
|
PeerMsgRet ProcessMessageRecoveredSig(const CNode& pfrom, const std::shared_ptr<const CRecoveredSig>& recoveredSig);
|
||||||
static bool PreVerifyRecoveredSig(const CQuorumManager& quorum_manager, const CRecoveredSig& recoveredSig, bool& retBan);
|
|
||||||
|
|
||||||
void CollectPendingRecoveredSigsToVerify(size_t maxUniqueSessions,
|
void CollectPendingRecoveredSigsToVerify(size_t maxUniqueSessions,
|
||||||
std::unordered_map<NodeId, std::list<std::shared_ptr<const CRecoveredSig>>>& retSigShares,
|
std::unordered_map<NodeId, std::list<std::shared_ptr<const CRecoveredSig>>>& retSigShares,
|
||||||
|
@ -427,42 +427,27 @@ static RPCHelpMan quorum_memberof()
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static RPCHelpMan quorum_sign()
|
static UniValue quorum_sign_helper(const JSONRPCRequest& request, Consensus::LLMQType llmqType)
|
||||||
{
|
|
||||||
return 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. "
|
|
||||||
"Returns an object containing the signature share if this is false."},
|
|
||||||
},
|
|
||||||
RPCResults{},
|
|
||||||
RPCExamples{""},
|
|
||||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
|
||||||
{
|
{
|
||||||
const NodeContext& node = EnsureAnyNodeContext(request.context);
|
const NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||||
const ChainstateManager& chainman = EnsureChainman(node);
|
const ChainstateManager& chainman = EnsureChainman(node);
|
||||||
const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
|
const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
|
||||||
|
|
||||||
const Consensus::LLMQType llmqType{static_cast<Consensus::LLMQType>(ParseInt32V(request.params[0], "llmqType"))};
|
|
||||||
const auto llmq_params_opt = Params().GetLLMQ(llmqType);
|
const auto llmq_params_opt = Params().GetLLMQ(llmqType);
|
||||||
if (!llmq_params_opt.has_value()) {
|
if (!llmq_params_opt.has_value()) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid LLMQ type");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid LLMQ type");
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint256 id(ParseHashV(request.params[1], "id"));
|
const uint256 id(ParseHashV(request.params[0], "id"));
|
||||||
const uint256 msgHash(ParseHashV(request.params[2], "msgHash"));
|
const uint256 msgHash(ParseHashV(request.params[1], "msgHash"));
|
||||||
|
|
||||||
uint256 quorumHash;
|
uint256 quorumHash;
|
||||||
if (!request.params[3].isNull() && !request.params[3].get_str().empty()) {
|
if (!request.params[2].isNull() && !request.params[2].get_str().empty()) {
|
||||||
quorumHash = ParseHashV(request.params[3], "quorumHash");
|
quorumHash = ParseHashV(request.params[2], "quorumHash");
|
||||||
}
|
}
|
||||||
bool fSubmit{true};
|
bool fSubmit{true};
|
||||||
if (!request.params[4].isNull()) {
|
if (!request.params[3].isNull()) {
|
||||||
fSubmit = ParseBoolV(request.params[4], "submit");
|
fSubmit = ParseBoolV(request.params[3], "submit");
|
||||||
}
|
}
|
||||||
if (fSubmit) {
|
if (fSubmit) {
|
||||||
return llmq_ctx.sigman->AsyncSignIfMember(llmqType, *llmq_ctx.shareman, id, msgHash, quorumHash);
|
return llmq_ctx.sigman->AsyncSignIfMember(llmqType, *llmq_ctx.shareman, id, msgHash, quorumHash);
|
||||||
@ -496,10 +481,69 @@ static RPCHelpMan quorum_sign()
|
|||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static RPCHelpMan quorum_sign()
|
||||||
|
{
|
||||||
|
return 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. "
|
||||||
|
"Returns an object containing the signature share if this is false."},
|
||||||
|
},
|
||||||
|
RPCResults{},
|
||||||
|
RPCExamples{""},
|
||||||
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||||
|
{
|
||||||
|
const Consensus::LLMQType llmqType{static_cast<Consensus::LLMQType>(ParseInt32V(request.params[0], "llmqType"))};
|
||||||
|
|
||||||
|
JSONRPCRequest new_request{request};
|
||||||
|
new_request.params.setArray();
|
||||||
|
for (unsigned int i = 1; i < request.params.size(); ++i) {
|
||||||
|
new_request.params.push_back(request.params[i]);
|
||||||
|
}
|
||||||
|
return quorum_sign_helper(new_request, llmqType);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static RPCHelpMan quorum_platformsign()
|
||||||
|
{
|
||||||
|
return RPCHelpMan{"quorum platformsign",
|
||||||
|
"Threshold-sign a message. It signs messages only for platform quorums\n",
|
||||||
|
{
|
||||||
|
{"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. "
|
||||||
|
"Returns an object containing the signature share if this is false."},
|
||||||
|
},
|
||||||
|
RPCResults{},
|
||||||
|
RPCExamples{""},
|
||||||
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||||
|
{
|
||||||
|
const Consensus::LLMQType llmqType{Params().GetConsensus().llmqTypePlatform};
|
||||||
|
return quorum_sign_helper(request, llmqType);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool VerifyRecoveredSigLatestQuorums(const Consensus::LLMQParams& llmq_params, const CChain& active_chain, const llmq::CQuorumManager& qman,
|
||||||
|
int signHeight, const uint256& id, const uint256& msgHash, const CBLSSignature& sig)
|
||||||
|
{
|
||||||
|
// First check against the current active set, if it fails check against the last active set
|
||||||
|
for (int signOffset : {0, llmq_params.dkgInterval}) {
|
||||||
|
if (llmq::VerifyRecoveredSig(llmq_params.type, active_chain, qman, signHeight, id, msgHash, sig, signOffset) == llmq::VerifyRecSigStatus::Valid) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static RPCHelpMan quorum_verify()
|
static RPCHelpMan quorum_verify()
|
||||||
{
|
{
|
||||||
return RPCHelpMan{"quorum verify",
|
return RPCHelpMan{"quorum verify",
|
||||||
@ -545,10 +589,7 @@ static RPCHelpMan quorum_verify()
|
|||||||
if (!request.params[5].isNull()) {
|
if (!request.params[5].isNull()) {
|
||||||
signHeight = ParseInt32V(request.params[5], "signHeight");
|
signHeight = ParseInt32V(request.params[5], "signHeight");
|
||||||
}
|
}
|
||||||
// First check against the current active set, if it fails check against the last active set
|
return VerifyRecoveredSigLatestQuorums(*llmq_params_opt, chainman.ActiveChain(), *llmq_ctx.qman, signHeight, id, msgHash, sig);
|
||||||
int signOffset{llmq_params_opt->dkgInterval};
|
|
||||||
return llmq::VerifyRecoveredSig(llmqType, chainman.ActiveChain(), *llmq_ctx.qman, signHeight, id, msgHash, sig, 0) ||
|
|
||||||
llmq::VerifyRecoveredSig(llmqType, chainman.ActiveChain(), *llmq_ctx.qman, signHeight, id, msgHash, sig, signOffset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint256 quorumHash(ParseHashV(request.params[4], "quorumHash"));
|
uint256 quorumHash(ParseHashV(request.params[4], "quorumHash"));
|
||||||
@ -958,7 +999,7 @@ static RPCHelpMan verifychainlock()
|
|||||||
}
|
}
|
||||||
|
|
||||||
const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
|
const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
|
||||||
return llmq_ctx.clhandler->VerifyChainLock(llmq::CChainLockSig(nBlockHeight, nBlockHash, sig));
|
return llmq_ctx.clhandler->VerifyChainLock(llmq::CChainLockSig(nBlockHeight, nBlockHash, sig)) == llmq::VerifyRecSigStatus::Valid;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -1030,12 +1071,9 @@ static RPCHelpMan verifyislock()
|
|||||||
const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
|
const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
|
||||||
|
|
||||||
auto llmqType = Params().GetConsensus().llmqTypeDIP0024InstantSend;
|
auto llmqType = Params().GetConsensus().llmqTypeDIP0024InstantSend;
|
||||||
// First check against the current active set, if it fails check against the last active set
|
|
||||||
const auto llmq_params_opt = Params().GetLLMQ(llmqType);
|
const auto llmq_params_opt = Params().GetLLMQ(llmqType);
|
||||||
CHECK_NONFATAL(llmq_params_opt.has_value());
|
CHECK_NONFATAL(llmq_params_opt.has_value());
|
||||||
const int signOffset{llmq_params_opt->dkgInterval};
|
return VerifyRecoveredSigLatestQuorums(*llmq_params_opt, chainman.ActiveChain(), *llmq_ctx.qman, signHeight, id, txid, sig);
|
||||||
return llmq::VerifyRecoveredSig(llmqType, chainman.ActiveChain(), *llmq_ctx.qman, signHeight, id, txid, sig, 0) ||
|
|
||||||
llmq::VerifyRecoveredSig(llmqType, chainman.ActiveChain(), *llmq_ctx.qman, signHeight, id, txid, sig, signOffset);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -1060,7 +1098,8 @@ static RPCHelpMan submitchainlock()
|
|||||||
if (nBlockHeight <= 0) {
|
if (nBlockHeight <= 0) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid block height");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid block height");
|
||||||
}
|
}
|
||||||
const LLMQContext& llmq_ctx = EnsureLLMQContext(EnsureAnyNodeContext(request.context));
|
const NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||||
|
const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
|
||||||
const int32_t bestCLHeight = llmq_ctx.clhandler->GetBestChainLock().getHeight();
|
const int32_t bestCLHeight = llmq_ctx.clhandler->GetBestChainLock().getHeight();
|
||||||
if (nBlockHeight <= bestCLHeight) return bestCLHeight;
|
if (nBlockHeight <= bestCLHeight) return bestCLHeight;
|
||||||
|
|
||||||
@ -1070,8 +1109,15 @@ static RPCHelpMan submitchainlock()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
auto clsig = llmq::CChainLockSig(nBlockHeight, nBlockHash, sig);
|
const auto clsig{llmq::CChainLockSig(nBlockHeight, nBlockHash, sig)};
|
||||||
if (!llmq_ctx.clhandler->VerifyChainLock(clsig)) {
|
const llmq::VerifyRecSigStatus ret{llmq_ctx.clhandler->VerifyChainLock(clsig)};
|
||||||
|
if (ret == llmq::VerifyRecSigStatus::NoQuorum) {
|
||||||
|
LOCK(cs_main);
|
||||||
|
const ChainstateManager& chainman = EnsureChainman(node);
|
||||||
|
const CBlockIndex* pIndex{chainman.ActiveChain().Tip()};
|
||||||
|
throw JSONRPCError(RPC_MISC_ERROR, strprintf("No quorum found. Current tip height: %d hash: %s\n", pIndex->nHeight, pIndex->GetBlockHash().ToString()));
|
||||||
|
}
|
||||||
|
if (ret != llmq::VerifyRecSigStatus::Valid) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid signature");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid signature");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1096,6 +1142,7 @@ static const CRPCCommand commands[] =
|
|||||||
{ "evo", "quorum", "dkgstatus", &quorum_dkgstatus, {"detail_level"} },
|
{ "evo", "quorum", "dkgstatus", &quorum_dkgstatus, {"detail_level"} },
|
||||||
{ "evo", "quorum", "memberof", &quorum_memberof, {"proTxHash", "scanQuorumsCount"} },
|
{ "evo", "quorum", "memberof", &quorum_memberof, {"proTxHash", "scanQuorumsCount"} },
|
||||||
{ "evo", "quorum", "sign", &quorum_sign, {"llmqType", "id", "msgHash", "quorumHash", "submit"} },
|
{ "evo", "quorum", "sign", &quorum_sign, {"llmqType", "id", "msgHash", "quorumHash", "submit"} },
|
||||||
|
{ "evo", "quorum", "platformsign", &quorum_platformsign, {"id", "msgHash", "quorumHash", "submit"} },
|
||||||
{ "evo", "quorum", "verify", &quorum_verify, {"llmqType", "id", "msgHash", "signature", "quorumHash", "signHeight"} },
|
{ "evo", "quorum", "verify", &quorum_verify, {"llmqType", "id", "msgHash", "signature", "quorumHash", "signHeight"} },
|
||||||
{ "evo", "quorum", "hasrecsig", &quorum_hasrecsig, {"llmqType", "id", "msgHash"} },
|
{ "evo", "quorum", "hasrecsig", &quorum_hasrecsig, {"llmqType", "id", "msgHash"} },
|
||||||
{ "evo", "quorum", "getrecsig", &quorum_getrecsig, {"llmqType", "id", "msgHash"} },
|
{ "evo", "quorum", "getrecsig", &quorum_getrecsig, {"llmqType", "id", "msgHash"} },
|
||||||
|
@ -3032,6 +3032,9 @@ static RPCHelpMan createwallet()
|
|||||||
#ifndef USE_SQLITE
|
#ifndef USE_SQLITE
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without sqlite support (required for descriptor wallets)");
|
throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without sqlite support (required for descriptor wallets)");
|
||||||
#endif
|
#endif
|
||||||
|
if (request.params[6].isNull()) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "The createwallet RPC requires specifying the 'load_on_startup' flag when creating descriptor wallets. Dash Core v21 introduced this requirement due to breaking changes in the createwallet RPC.");
|
||||||
|
}
|
||||||
flags |= WALLET_FLAG_DESCRIPTORS;
|
flags |= WALLET_FLAG_DESCRIPTORS;
|
||||||
warnings.emplace_back(Untranslated("Wallet is an experimental descriptor wallet"));
|
warnings.emplace_back(Untranslated("Wallet is an experimental descriptor wallet"));
|
||||||
}
|
}
|
||||||
|
@ -331,7 +331,7 @@ void LegacyScriptPubKeyMan::MarkUnusedAddresses(WalletBatch &batch, const CScrip
|
|||||||
void LegacyScriptPubKeyMan::UpgradeKeyMetadata()
|
void LegacyScriptPubKeyMan::UpgradeKeyMetadata()
|
||||||
{
|
{
|
||||||
LOCK(cs_KeyStore); // mapKeyMetadata
|
LOCK(cs_KeyStore); // mapKeyMetadata
|
||||||
if (m_storage.IsLocked() || m_storage.IsWalletFlagSet(WALLET_FLAG_KEY_ORIGIN_METADATA) || !IsHDEnabled()) {
|
if (m_storage.IsLocked(false) || m_storage.IsWalletFlagSet(WALLET_FLAG_KEY_ORIGIN_METADATA) || !IsHDEnabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1901,7 +1901,7 @@ void DescriptorScriptPubKeyMan::ReturnDestination(int64_t index, bool internal,
|
|||||||
std::map<CKeyID, CKey> DescriptorScriptPubKeyMan::GetKeys() const
|
std::map<CKeyID, CKey> DescriptorScriptPubKeyMan::GetKeys() const
|
||||||
{
|
{
|
||||||
AssertLockHeld(cs_desc_man);
|
AssertLockHeld(cs_desc_man);
|
||||||
if (m_storage.HasEncryptionKeys() && !m_storage.IsLocked()) {
|
if (m_storage.HasEncryptionKeys() && !m_storage.IsLocked(true)) {
|
||||||
KeyMap keys;
|
KeyMap keys;
|
||||||
for (auto key_pair : m_map_crypted_keys) {
|
for (auto key_pair : m_map_crypted_keys) {
|
||||||
const CPubKey& pubkey = key_pair.second.first;
|
const CPubKey& pubkey = key_pair.second.first;
|
||||||
@ -2014,7 +2014,7 @@ bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (m_storage.HasEncryptionKeys()) {
|
if (m_storage.HasEncryptionKeys()) {
|
||||||
if (m_storage.IsLocked()) {
|
if (m_storage.IsLocked(true)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2423,7 +2423,7 @@ bool DescriptorScriptPubKeyMan::GetDescriptorString(std::string& out) const
|
|||||||
void DescriptorScriptPubKeyMan::UpgradeDescriptorCache()
|
void DescriptorScriptPubKeyMan::UpgradeDescriptorCache()
|
||||||
{
|
{
|
||||||
LOCK(cs_desc_man);
|
LOCK(cs_desc_man);
|
||||||
if (m_storage.IsLocked() || m_storage.IsWalletFlagSet(WALLET_FLAG_LAST_HARDENED_XPUB_CACHED)) {
|
if (m_storage.IsLocked(false) || m_storage.IsWalletFlagSet(WALLET_FLAG_LAST_HARDENED_XPUB_CACHED)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ public:
|
|||||||
virtual void SetMinVersion(enum WalletFeature, WalletBatch* = nullptr) = 0;
|
virtual void SetMinVersion(enum WalletFeature, WalletBatch* = nullptr) = 0;
|
||||||
virtual const CKeyingMaterial& GetEncryptionKey() const = 0;
|
virtual const CKeyingMaterial& GetEncryptionKey() const = 0;
|
||||||
virtual bool HasEncryptionKeys() const = 0;
|
virtual bool HasEncryptionKeys() const = 0;
|
||||||
virtual bool IsLocked(bool fForMixing = false) const = 0;
|
virtual bool IsLocked(bool fForMixing) const = 0;
|
||||||
|
|
||||||
// for LegacyScriptPubKeyMan::TopUpInner needs:
|
// for LegacyScriptPubKeyMan::TopUpInner needs:
|
||||||
virtual void UpdateProgress(const std::string&, int) = 0;
|
virtual void UpdateProgress(const std::string&, int) = 0;
|
||||||
|
@ -5459,21 +5459,11 @@ bool CWallet::IsLocked(bool fForMixing) const
|
|||||||
{
|
{
|
||||||
if (!IsCrypted())
|
if (!IsCrypted())
|
||||||
return false;
|
return false;
|
||||||
bool result;
|
|
||||||
{
|
|
||||||
LOCK(cs_wallet);
|
|
||||||
result = vMasterKey.empty();
|
|
||||||
}
|
|
||||||
// fForMixing fOnlyMixingAllowed return
|
|
||||||
// ---------------------------------------
|
|
||||||
// true true result
|
|
||||||
// true false result
|
|
||||||
// false true true
|
|
||||||
// false false result
|
|
||||||
|
|
||||||
if(!fForMixing && fOnlyMixingAllowed) return true;
|
if(!fForMixing && fOnlyMixingAllowed) return true;
|
||||||
|
|
||||||
return result;
|
LOCK(cs_wallet);
|
||||||
|
return vMasterKey.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CWallet::Lock(bool fAllowMixing)
|
bool CWallet::Lock(bool fAllowMixing)
|
||||||
|
@ -119,7 +119,7 @@ class AssetLocksTest(DashTestFramework):
|
|||||||
unlock_tx.calc_sha256()
|
unlock_tx.calc_sha256()
|
||||||
msgHash = format(unlock_tx.sha256, '064x')
|
msgHash = format(unlock_tx.sha256, '064x')
|
||||||
|
|
||||||
recsig = self.get_recovered_sig(request_id, msgHash, llmq_type=llmq_type_test)
|
recsig = self.get_recovered_sig(request_id, msgHash, llmq_type=llmq_type_test, use_platformsign=True)
|
||||||
|
|
||||||
unlockTx_payload.quorumSig = bytearray.fromhex(recsig["sig"])
|
unlockTx_payload.quorumSig = bytearray.fromhex(recsig["sig"])
|
||||||
unlock_tx.vExtraPayload = unlockTx_payload.serialize()
|
unlock_tx.vExtraPayload = unlockTx_payload.serialize()
|
||||||
|
@ -84,11 +84,14 @@ class LLMQChainLocksTest(DashTestFramework):
|
|||||||
block = self.nodes[0].getblock(self.nodes[0].getblockhash(h))
|
block = self.nodes[0].getblock(self.nodes[0].getblockhash(h))
|
||||||
assert block['chainlock']
|
assert block['chainlock']
|
||||||
|
|
||||||
# Update spork to SPORK_19_CHAINLOCKS_ENABLED and test its behaviour
|
self.log.info(f"Test submitchainlock for too high block")
|
||||||
|
assert_raises_rpc_error(-1, f"No quorum found. Current tip height: {self.nodes[1].getblockcount()}", self.nodes[1].submitchainlock, '0000000000000000000000000000000000000000000000000000000000000000', 'a5c69505b5744524c9ed6551d8a57dc520728ea013496f46baa8a73df96bfd3c86e474396d747a4af11aaef10b17dbe80498b6a2fe81938fe917a3fedf651361bfe5367c800d23d3125820e6ee5b42189f0043be94ce27e73ea13620c9ef6064', self.nodes[1].getblockcount() + 300)
|
||||||
|
|
||||||
|
self.log.info("Update spork to SPORK_19_CHAINLOCKS_ENABLED and test its behaviour")
|
||||||
self.nodes[0].sporkupdate("SPORK_19_CHAINLOCKS_ENABLED", 1)
|
self.nodes[0].sporkupdate("SPORK_19_CHAINLOCKS_ENABLED", 1)
|
||||||
self.wait_for_sporks_same()
|
self.wait_for_sporks_same()
|
||||||
|
|
||||||
# Generate new blocks and verify that they are not chainlocked
|
self.log.info("Generate new blocks and verify that they are not chainlocked")
|
||||||
previous_block_hash = self.nodes[0].getbestblockhash()
|
previous_block_hash = self.nodes[0].getbestblockhash()
|
||||||
for _ in range(2):
|
for _ in range(2):
|
||||||
block_hash = self.nodes[0].generate(1)[0]
|
block_hash = self.nodes[0].generate(1)[0]
|
||||||
|
@ -12,7 +12,9 @@ from test_framework.util import (
|
|||||||
assert_equal,
|
assert_equal,
|
||||||
str_to_b64str
|
str_to_b64str
|
||||||
)
|
)
|
||||||
|
import json
|
||||||
import http.client
|
import http.client
|
||||||
|
import re
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
def rpccall(node, user, method):
|
def rpccall(node, user, method):
|
||||||
@ -20,7 +22,17 @@ def rpccall(node, user, method):
|
|||||||
headers = {"Authorization": "Basic " + str_to_b64str('{}:{}'.format(user[0], user[3]))}
|
headers = {"Authorization": "Basic " + str_to_b64str('{}:{}'.format(user[0], user[3]))}
|
||||||
conn = http.client.HTTPConnection(url.hostname, url.port)
|
conn = http.client.HTTPConnection(url.hostname, url.port)
|
||||||
conn.connect()
|
conn.connect()
|
||||||
conn.request('POST', '/', '{"method": "' + method + '"}', headers)
|
|
||||||
|
# composite commands are presented without space in whitelist
|
||||||
|
# but space can't be ommitted when using CLI/http rpc
|
||||||
|
# for sack of test, substitute missing space for quorum composite command
|
||||||
|
params = []
|
||||||
|
if re.match(r"^quorum[^ ]", method):
|
||||||
|
params = [method[6:]]
|
||||||
|
method = "quorum"
|
||||||
|
query = {"method" : method, "params" : params}
|
||||||
|
|
||||||
|
conn.request('POST', '/', json.dumps(query), headers)
|
||||||
resp = conn.getresponse()
|
resp = conn.getresponse()
|
||||||
conn.close()
|
conn.close()
|
||||||
return resp
|
return resp
|
||||||
@ -39,7 +51,8 @@ class RPCWhitelistTest(BitcoinTestFramework):
|
|||||||
# 3 => Password Plaintext
|
# 3 => Password Plaintext
|
||||||
self.users = [
|
self.users = [
|
||||||
["user1", "50358aa884c841648e0700b073c32b2e$b73e95fff0748cc0b517859d2ca47d9bac1aa78231f3e48fa9222b612bd2083e", "getbestblockhash,getblockcount,", "12345"],
|
["user1", "50358aa884c841648e0700b073c32b2e$b73e95fff0748cc0b517859d2ca47d9bac1aa78231f3e48fa9222b612bd2083e", "getbestblockhash,getblockcount,", "12345"],
|
||||||
["user2", "8650ba41296f62092377a38547f361de$4620db7ba063ef4e2f7249853e9f3c5c3592a9619a759e3e6f1c63f2e22f1d21", "getblockcount", "54321"]
|
["user2", "8650ba41296f62092377a38547f361de$4620db7ba063ef4e2f7249853e9f3c5c3592a9619a759e3e6f1c63f2e22f1d21", "getblockcount", "54321"],
|
||||||
|
["platform-user", "8650ba41296f62092377a38547f361de$4620db7ba063ef4e2f7249853e9f3c5c3592a9619a759e3e6f1c63f2e22f1d21", "getblockcount,quorumlist", "54321"],
|
||||||
]
|
]
|
||||||
# For exceptions
|
# For exceptions
|
||||||
self.strange_users = [
|
self.strange_users = [
|
||||||
@ -55,7 +68,7 @@ class RPCWhitelistTest(BitcoinTestFramework):
|
|||||||
["strangedude5", "d12c6e962d47a454f962eb41225e6ec8$2dd39635b155536d3c1a2e95d05feff87d5ba55f2d5ff975e6e997a836b717c9", ":getblockcount,getblockcount", "s7R4nG3R7H1nGZ"]
|
["strangedude5", "d12c6e962d47a454f962eb41225e6ec8$2dd39635b155536d3c1a2e95d05feff87d5ba55f2d5ff975e6e997a836b717c9", ":getblockcount,getblockcount", "s7R4nG3R7H1nGZ"]
|
||||||
]
|
]
|
||||||
# These commands shouldn't be allowed for any user to test failures
|
# These commands shouldn't be allowed for any user to test failures
|
||||||
self.never_allowed = ["getnetworkinfo"]
|
self.never_allowed = ["getnetworkinfo", "quorum sign"]
|
||||||
with open(os.path.join(get_datadir_path(self.options.tmpdir, 0), "dash.conf"), 'a', encoding='utf8') as f:
|
with open(os.path.join(get_datadir_path(self.options.tmpdir, 0), "dash.conf"), 'a', encoding='utf8') as f:
|
||||||
f.write("\nrpcwhitelistdefault=0\n")
|
f.write("\nrpcwhitelistdefault=0\n")
|
||||||
for user in self.users:
|
for user in self.users:
|
||||||
|
@ -2033,13 +2033,16 @@ class DashTestFramework(BitcoinTestFramework):
|
|||||||
return True
|
return True
|
||||||
wait_until_helper(check_recovered_sig, timeout=timeout, sleep=1)
|
wait_until_helper(check_recovered_sig, timeout=timeout, sleep=1)
|
||||||
|
|
||||||
def get_recovered_sig(self, rec_sig_id, rec_sig_msg_hash, llmq_type=100):
|
def get_recovered_sig(self, rec_sig_id, rec_sig_msg_hash, llmq_type=100, use_platformsign=False):
|
||||||
# Note: recsigs aren't relayed to regular nodes by default,
|
# Note: recsigs aren't relayed to regular nodes by default,
|
||||||
# make sure to pick a mn as a node to query for recsigs.
|
# make sure to pick a mn as a node to query for recsigs.
|
||||||
try:
|
try:
|
||||||
quorumHash = self.mninfo[0].node.quorum("selectquorum", llmq_type, rec_sig_id)["quorumHash"]
|
quorumHash = self.mninfo[0].node.quorum("selectquorum", llmq_type, rec_sig_id)["quorumHash"]
|
||||||
for mn in self.mninfo:
|
for mn in self.mninfo:
|
||||||
mn.node.quorum("sign", llmq_type, rec_sig_id, rec_sig_msg_hash, quorumHash)
|
if use_platformsign:
|
||||||
|
mn.node.quorum("platformsign", rec_sig_id, rec_sig_msg_hash, quorumHash)
|
||||||
|
else:
|
||||||
|
mn.node.quorum("sign", llmq_type, rec_sig_id, rec_sig_msg_hash, quorumHash)
|
||||||
self.wait_for_recovered_sig(rec_sig_id, rec_sig_msg_hash, llmq_type, 10)
|
self.wait_for_recovered_sig(rec_sig_id, rec_sig_msg_hash, llmq_type, 10)
|
||||||
return self.mninfo[0].node.quorum("getrecsig", llmq_type, rec_sig_id, rec_sig_msg_hash)
|
return self.mninfo[0].node.quorum("getrecsig", llmq_type, rec_sig_id, rec_sig_msg_hash)
|
||||||
except JSONRPCException as e:
|
except JSONRPCException as e:
|
||||||
|
@ -704,6 +704,8 @@ class RPCOverloadWrapper():
|
|||||||
def createwallet(self, wallet_name, disable_private_keys=None, blank=None, passphrase='', avoid_reuse=None, descriptors=None, load_on_startup=None):
|
def createwallet(self, wallet_name, disable_private_keys=None, blank=None, passphrase='', avoid_reuse=None, descriptors=None, load_on_startup=None):
|
||||||
if descriptors is None:
|
if descriptors is None:
|
||||||
descriptors = self.descriptors
|
descriptors = self.descriptors
|
||||||
|
if descriptors is not None and load_on_startup is None:
|
||||||
|
load_on_startup = False
|
||||||
return self.__getattr__('createwallet')(wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors, load_on_startup)
|
return self.__getattr__('createwallet')(wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors, load_on_startup)
|
||||||
|
|
||||||
def importprivkey(self, privkey, label=None, rescan=None):
|
def importprivkey(self, privkey, label=None, rescan=None):
|
||||||
|
Loading…
Reference in New Issue
Block a user