mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 12:02:48 +01:00
Merge pull request #3389 from codablock/pr_concentrated_recovery
Implement "concentrated recovery" of LLMQ signatures
This commit is contained in:
commit
c7b6eb851d
@ -175,6 +175,7 @@ static Consensus::LLMQParams llmq_test = {
|
||||
.signingActiveQuorumCount = 2, // just a few ones to allow easier testing
|
||||
|
||||
.keepOldConnections = 3,
|
||||
.recoveryMembers = 3,
|
||||
};
|
||||
|
||||
// this one is for devnets only
|
||||
@ -194,6 +195,7 @@ static Consensus::LLMQParams llmq_devnet = {
|
||||
.signingActiveQuorumCount = 3, // just a few ones to allow easier testing
|
||||
|
||||
.keepOldConnections = 4,
|
||||
.recoveryMembers = 6,
|
||||
};
|
||||
|
||||
static Consensus::LLMQParams llmq50_60 = {
|
||||
@ -212,6 +214,7 @@ static Consensus::LLMQParams llmq50_60 = {
|
||||
.signingActiveQuorumCount = 24, // a full day worth of LLMQs
|
||||
|
||||
.keepOldConnections = 25,
|
||||
.recoveryMembers = 25,
|
||||
};
|
||||
|
||||
static Consensus::LLMQParams llmq400_60 = {
|
||||
@ -230,6 +233,7 @@ static Consensus::LLMQParams llmq400_60 = {
|
||||
.signingActiveQuorumCount = 4, // two days worth of LLMQs
|
||||
|
||||
.keepOldConnections = 5,
|
||||
.recoveryMembers = 100,
|
||||
};
|
||||
|
||||
// Used for deployment and min-proto-version signalling, so it needs a higher threshold
|
||||
@ -249,6 +253,7 @@ static Consensus::LLMQParams llmq400_85 = {
|
||||
.signingActiveQuorumCount = 4, // four days worth of LLMQs
|
||||
|
||||
.keepOldConnections = 5,
|
||||
.recoveryMembers = 100,
|
||||
};
|
||||
|
||||
|
||||
|
@ -114,6 +114,9 @@ struct LLMQParams {
|
||||
// Used for inter-quorum communication. This is the number of quorums for which we should keep old connections. This
|
||||
// should be at least one more then the active quorums set.
|
||||
int keepOldConnections;
|
||||
|
||||
// How many members should we try to send all sigShares to before we give up.
|
||||
int recoveryMembers;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <init.h>
|
||||
#include <net_processing.h>
|
||||
#include <netmessagemaker.h>
|
||||
#include <spork.h>
|
||||
#include <validation.h>
|
||||
|
||||
#include <cxxtimer.hpp>
|
||||
@ -236,6 +237,23 @@ void CSigSharesManager::ProcessMessage(CNode* pfrom, const std::string& strComma
|
||||
return;
|
||||
}
|
||||
|
||||
if (sporkManager.IsSporkActive(SPORK_21_QUORUM_ALL_CONNECTED)) {
|
||||
if (strCommand == NetMsgType::QSIGSHARE) {
|
||||
std::vector<CSigShare> sigShares;
|
||||
vRecv >> sigShares;
|
||||
|
||||
if (sigShares.size() > MAX_MSGS_SIG_SHARES) {
|
||||
LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- too many sigs in QSIGSHARE message. cnt=%d, max=%d, node=%d\n", __func__, sigShares.size(), MAX_MSGS_SIG_SHARES, pfrom->GetId());
|
||||
BanNode(pfrom->GetId());
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& sigShare : sigShares) {
|
||||
ProcessMessageSigShare(pfrom->GetId(), sigShare, connman);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (strCommand == NetMsgType::QSIGSESANN) {
|
||||
std::vector<CSigSesAnn> msgs;
|
||||
vRecv >> msgs;
|
||||
@ -465,6 +483,57 @@ bool CSigSharesManager::ProcessMessageBatchedSigShares(CNode* pfrom, const CBatc
|
||||
return true;
|
||||
}
|
||||
|
||||
void CSigSharesManager::ProcessMessageSigShare(NodeId fromId, const CSigShare& sigShare, CConnman& connman)
|
||||
{
|
||||
auto quorum = quorumManager->GetQuorum(sigShare.llmqType, sigShare.quorumHash);
|
||||
if (!quorum) {
|
||||
return;
|
||||
}
|
||||
if (!CLLMQUtils::IsQuorumActive(sigShare.llmqType, quorum->qc.quorumHash)) {
|
||||
// quorum is too old
|
||||
return;
|
||||
}
|
||||
if (!quorum->IsMember(activeMasternodeInfo.proTxHash)) {
|
||||
// we're not a member so we can't verify it (we actually shouldn't have received it)
|
||||
return;
|
||||
}
|
||||
if (quorum->quorumVvec == nullptr) {
|
||||
// TODO we should allow to ask other nodes for the quorum vvec if we missed it in the DKG
|
||||
LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- we don't have the quorum vvec for %s, no verification possible. node=%d\n", __func__,
|
||||
quorum->qc.quorumHash.ToString(), fromId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sigShare.quorumMember >= quorum->members.size()) {
|
||||
LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- quorumMember out of bounds\n", __func__);
|
||||
BanNode(fromId);
|
||||
return;
|
||||
}
|
||||
if (!quorum->qc.validMembers[sigShare.quorumMember]) {
|
||||
LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- quorumMember not valid\n", __func__);
|
||||
BanNode(fromId);
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
LOCK(cs);
|
||||
|
||||
if (sigShares.Has(sigShare.GetKey())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (quorumSigningManager->HasRecoveredSigForId((Consensus::LLMQType)sigShare.llmqType, sigShare.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& nodeState = nodeStates[fromId];
|
||||
nodeState.pendingIncomingSigShares.Add(sigShare.GetKey(), sigShare);
|
||||
}
|
||||
|
||||
LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- signHash=%s, id=%s, msgHash=%s, member=%d, node=%d\n", __func__,
|
||||
sigShare.GetSignHash().ToString(), sigShare.id.ToString(), sigShare.msgHash.ToString(), sigShare.quorumMember, fromId);
|
||||
}
|
||||
|
||||
bool CSigSharesManager::PreVerifyBatchedSigShares(NodeId nodeId, const CSigSharesNodeState::SessionInfo& session, const CBatchedSigShares& batchedSigShares, bool& retBan)
|
||||
{
|
||||
retBan = false;
|
||||
@ -668,8 +737,10 @@ void CSigSharesManager::ProcessSigShare(NodeId nodeId, const CSigShare& sigShare
|
||||
|
||||
// prepare node set for direct-push in case this is our sig share
|
||||
std::set<NodeId> quorumNodes;
|
||||
if (sigShare.quorumMember == quorum->GetMemberIndex(activeMasternodeInfo.proTxHash)) {
|
||||
quorumNodes = connman.GetMasternodeQuorumNodes((Consensus::LLMQType) sigShare.llmqType, sigShare.quorumHash);
|
||||
if (!sporkManager.IsSporkActive(SPORK_21_QUORUM_ALL_CONNECTED)) {
|
||||
if (sigShare.quorumMember == quorum->GetMemberIndex(activeMasternodeInfo.proTxHash)) {
|
||||
quorumNodes = connman.GetMasternodeQuorumNodes((Consensus::LLMQType) sigShare.llmqType, sigShare.quorumHash);
|
||||
}
|
||||
}
|
||||
|
||||
if (quorumSigningManager->HasRecoveredSigForId(llmqType, sigShare.id)) {
|
||||
@ -780,6 +851,21 @@ void CSigSharesManager::TryRecoverSig(const CQuorumCPtr& quorum, const uint256&
|
||||
quorumSigningManager->ProcessRecoveredSig(-1, rs, quorum, connman);
|
||||
}
|
||||
|
||||
CDeterministicMNCPtr CSigSharesManager::SelectMemberForRecovery(const CQuorumCPtr& quorum, const uint256 &id, int attempt)
|
||||
{
|
||||
assert(attempt < quorum->members.size());
|
||||
|
||||
std::vector<std::pair<uint256, CDeterministicMNCPtr>> v;
|
||||
v.reserve(quorum->members.size());
|
||||
for (const auto& dmn : quorum->members) {
|
||||
auto h = ::SerializeHash(std::make_pair(dmn->proTxHash, id));
|
||||
v.emplace_back(h, dmn);
|
||||
}
|
||||
std::sort(v.begin(), v.end());
|
||||
|
||||
return v[attempt].second;
|
||||
}
|
||||
|
||||
void CSigSharesManager::CollectSigSharesToRequest(std::unordered_map<NodeId, std::unordered_map<uint256, CSigSharesInv, StaticSaltedHasher>>& sigSharesToRequest)
|
||||
{
|
||||
AssertLockHeld(cs);
|
||||
@ -928,6 +1014,43 @@ void CSigSharesManager::CollectSigSharesToSend(std::unordered_map<NodeId, std::u
|
||||
}
|
||||
}
|
||||
|
||||
void CSigSharesManager::CollectSigSharesToSend(std::unordered_map<NodeId, std::vector<CSigShare>>& sigSharesToSend, const std::vector<CNode*>& vNodes)
|
||||
{
|
||||
AssertLockHeld(cs);
|
||||
|
||||
std::unordered_map<uint256, CNode*> proTxToNode;
|
||||
for (const auto& pnode : vNodes) {
|
||||
if (pnode->verifiedProRegTxHash.IsNull()) {
|
||||
continue;
|
||||
}
|
||||
proTxToNode.emplace(pnode->verifiedProRegTxHash, pnode);
|
||||
}
|
||||
|
||||
auto curTime = GetTime();
|
||||
|
||||
for (auto& p : signedSessions) {
|
||||
if (p.second.attempt > p.second.quorum->params.recoveryMembers) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (curTime >= p.second.nextAttemptTime) {
|
||||
p.second.nextAttemptTime = curTime + SEND_FOR_RECOVERY_TIMEOUT;
|
||||
auto dmn = SelectMemberForRecovery(p.second.quorum, p.second.sigShare.id, p.second.attempt);
|
||||
LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- sending to %s, signHash=%s\n", __func__,
|
||||
dmn->proTxHash.ToString(), p.second.sigShare.GetSignHash().ToString());
|
||||
p.second.attempt++;
|
||||
|
||||
auto it = proTxToNode.find(dmn->proTxHash);
|
||||
if (it == proTxToNode.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& m = sigSharesToSend[it->second->GetId()];
|
||||
m.emplace_back(p.second.sigShare);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CSigSharesManager::CollectSigSharesToAnnounce(std::unordered_map<NodeId, std::unordered_map<uint256, CSigSharesInv, StaticSaltedHasher>>& sigSharesToAnnounce)
|
||||
{
|
||||
AssertLockHeld(cs);
|
||||
@ -983,7 +1106,8 @@ void CSigSharesManager::CollectSigSharesToAnnounce(std::unordered_map<NodeId, st
|
||||
bool CSigSharesManager::SendMessages()
|
||||
{
|
||||
std::unordered_map<NodeId, std::unordered_map<uint256, CSigSharesInv, StaticSaltedHasher>> sigSharesToRequest;
|
||||
std::unordered_map<NodeId, std::unordered_map<uint256, CBatchedSigShares, StaticSaltedHasher>> sigSharesToSend;
|
||||
std::unordered_map<NodeId, std::unordered_map<uint256, CBatchedSigShares, StaticSaltedHasher>> sigShareBatchesToSend;
|
||||
std::unordered_map<NodeId, std::vector<CSigShare>> sigSharesToSend;
|
||||
std::unordered_map<NodeId, std::unordered_map<uint256, CSigSharesInv, StaticSaltedHasher>> sigSharesToAnnounce;
|
||||
std::unordered_map<NodeId, std::vector<CSigSesAnn>> sigSessionAnnouncements;
|
||||
|
||||
@ -1006,18 +1130,24 @@ bool CSigSharesManager::SendMessages()
|
||||
return session->sendSessionId;
|
||||
};
|
||||
|
||||
std::vector<CNode*> vNodesCopy = g_connman->CopyNodeVector(CConnman::FullyConnectedOnly);
|
||||
|
||||
{
|
||||
LOCK(cs);
|
||||
CollectSigSharesToRequest(sigSharesToRequest);
|
||||
CollectSigSharesToSend(sigSharesToSend);
|
||||
CollectSigSharesToAnnounce(sigSharesToAnnounce);
|
||||
if (!sporkManager.IsSporkActive(SPORK_21_QUORUM_ALL_CONNECTED)) {
|
||||
CollectSigSharesToRequest(sigSharesToRequest);
|
||||
CollectSigSharesToSend(sigShareBatchesToSend);
|
||||
CollectSigSharesToAnnounce(sigSharesToAnnounce);
|
||||
} else {
|
||||
CollectSigSharesToSend(sigSharesToSend, vNodesCopy);
|
||||
}
|
||||
|
||||
for (auto& p : sigSharesToRequest) {
|
||||
for (auto& p2 : p.second) {
|
||||
p2.second.sessionId = addSigSesAnnIfNeeded(p.first, p2.first);
|
||||
}
|
||||
}
|
||||
for (auto& p : sigSharesToSend) {
|
||||
for (auto& p : sigShareBatchesToSend) {
|
||||
for (auto& p2 : p.second) {
|
||||
p2.second.sessionId = addSigSesAnnIfNeeded(p.first, p2.first);
|
||||
}
|
||||
@ -1031,8 +1161,6 @@ bool CSigSharesManager::SendMessages()
|
||||
|
||||
bool didSend = false;
|
||||
|
||||
std::vector<CNode*> vNodesCopy = g_connman->CopyNodeVector(CConnman::FullyConnectedOnly);
|
||||
|
||||
for (auto& pnode : vNodesCopy) {
|
||||
CNetMsgMaker msgMaker(pnode->GetSendVersion());
|
||||
|
||||
@ -1076,8 +1204,8 @@ bool CSigSharesManager::SendMessages()
|
||||
}
|
||||
}
|
||||
|
||||
auto jt = sigSharesToSend.find(pnode->GetId());
|
||||
if (jt != sigSharesToSend.end()) {
|
||||
auto jt = sigShareBatchesToSend.find(pnode->GetId());
|
||||
if (jt != sigShareBatchesToSend.end()) {
|
||||
size_t totalSigsCount = 0;
|
||||
std::vector<CBatchedSigShares> msgs;
|
||||
for (auto& p : jt->second) {
|
||||
@ -1119,6 +1247,25 @@ bool CSigSharesManager::SendMessages()
|
||||
didSend = true;
|
||||
}
|
||||
}
|
||||
|
||||
auto lt = sigSharesToSend.find(pnode->GetId());
|
||||
if (lt != sigSharesToSend.end()) {
|
||||
std::vector<CSigShare> msgs;
|
||||
for (auto& sigShare : lt->second) {
|
||||
LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::SendMessages -- QSIGSHARE signHash=%s, node=%d\n",
|
||||
sigShare.GetSignHash().ToString(), pnode->GetId());
|
||||
msgs.emplace_back(std::move(sigShare));
|
||||
if (msgs.size() == MAX_MSGS_SIG_SHARES) {
|
||||
g_connman->PushMessage(pnode, msgMaker.Make(NetMsgType::QSIGSHARE, msgs), false);
|
||||
msgs.clear();
|
||||
didSend = true;
|
||||
}
|
||||
}
|
||||
if (!msgs.empty()) {
|
||||
g_connman->PushMessage(pnode, msgMaker.Make(NetMsgType::QSIGSHARE, msgs), false);
|
||||
didSend = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// looped through all nodes, release them
|
||||
@ -1285,6 +1432,7 @@ void CSigSharesManager::RemoveSigSharesForSession(const uint256& signHash)
|
||||
sigSharesRequested.EraseAllForSignHash(signHash);
|
||||
sigSharesToAnnounce.EraseAllForSignHash(signHash);
|
||||
sigShares.EraseAllForSignHash(signHash);
|
||||
signedSessions.erase(signHash);
|
||||
timeSeenForSessions.erase(signHash);
|
||||
}
|
||||
|
||||
@ -1431,6 +1579,15 @@ void CSigSharesManager::Sign(const CQuorumCPtr& quorum, const uint256& id, const
|
||||
LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- signed sigShare. signHash=%s, id=%s, msgHash=%s, llmqType=%d, quorum=%s, time=%s\n", __func__,
|
||||
signHash.ToString(), sigShare.id.ToString(), sigShare.msgHash.ToString(), quorum->params.type, quorum->qc.quorumHash.ToString(), t.count());
|
||||
ProcessSigShare(-1, sigShare, *g_connman, quorum);
|
||||
|
||||
if (sporkManager.IsSporkActive(SPORK_21_QUORUM_ALL_CONNECTED)) {
|
||||
LOCK(cs);
|
||||
auto& session = signedSessions[sigShare.GetSignHash()];
|
||||
session.sigShare = sigShare;
|
||||
session.quorum = quorum;
|
||||
session.nextAttemptTime = 0;
|
||||
session.attempt = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// causes all known sigShares to be re-announced
|
||||
|
@ -30,7 +30,6 @@ namespace llmq
|
||||
// <signHash, quorumMember>
|
||||
typedef std::pair<uint256, uint16_t> SigShareKey;
|
||||
|
||||
// this one does not get transmitted over the wire as it is batched inside CBatchedSigShares
|
||||
class CSigShare
|
||||
{
|
||||
public:
|
||||
@ -54,6 +53,22 @@ public:
|
||||
assert(!key.first.IsNull());
|
||||
return key.first;
|
||||
}
|
||||
|
||||
ADD_SERIALIZE_METHODS
|
||||
|
||||
template<typename Stream, typename Operation>
|
||||
inline void SerializationOp(Stream& s, Operation ser_action) {
|
||||
READWRITE(llmqType);
|
||||
READWRITE(quorumHash);
|
||||
READWRITE(quorumMember);
|
||||
READWRITE(id);
|
||||
READWRITE(msgHash);
|
||||
READWRITE(sigShare);
|
||||
|
||||
if (ser_action.ForRead()) {
|
||||
UpdateKey();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Nodes will first announce a signing session with a sessionId to be used in all future P2P messages related to that
|
||||
@ -327,6 +342,16 @@ public:
|
||||
void RemoveSession(const uint256& signHash);
|
||||
};
|
||||
|
||||
class CSignedSession
|
||||
{
|
||||
public:
|
||||
CSigShare sigShare;
|
||||
CQuorumCPtr quorum;
|
||||
|
||||
int64_t nextAttemptTime{0};
|
||||
int attempt{0};
|
||||
};
|
||||
|
||||
class CSigSharesManager : public CRecoveredSigsListener
|
||||
{
|
||||
static const int64_t SESSION_NEW_SHARES_TIMEOUT = 60;
|
||||
@ -339,6 +364,9 @@ class CSigSharesManager : public CRecoveredSigsListener
|
||||
// 400 is the maximum quorum size, so this is also the maximum number of sigs we need to support
|
||||
const size_t MAX_MSGS_TOTAL_BATCHED_SIGS = 400;
|
||||
|
||||
const int64_t SEND_FOR_RECOVERY_TIMEOUT = 1;
|
||||
const size_t MAX_MSGS_SIG_SHARES = 32;
|
||||
|
||||
private:
|
||||
CCriticalSection cs;
|
||||
|
||||
@ -346,6 +374,7 @@ private:
|
||||
CThreadInterrupt workInterrupt;
|
||||
|
||||
SigShareMap<CSigShare> sigShares;
|
||||
std::unordered_map<uint256, CSignedSession, StaticSaltedHasher> signedSessions;
|
||||
|
||||
// stores time of last receivedSigShare. Used to detect timeouts
|
||||
std::unordered_map<uint256, int64_t, StaticSaltedHasher> timeSeenForSessions;
|
||||
@ -381,12 +410,15 @@ public:
|
||||
|
||||
void HandleNewRecoveredSig(const CRecoveredSig& recoveredSig);
|
||||
|
||||
static CDeterministicMNCPtr SelectMemberForRecovery(const CQuorumCPtr& quorum, const uint256& id, int attempt);
|
||||
|
||||
private:
|
||||
// all of these return false when the currently processed message should be aborted (as each message actually contains multiple messages)
|
||||
bool ProcessMessageSigSesAnn(CNode* pfrom, const CSigSesAnn& ann, CConnman& connman);
|
||||
bool ProcessMessageSigSharesInv(CNode* pfrom, const CSigSharesInv& inv, CConnman& connman);
|
||||
bool ProcessMessageGetSigShares(CNode* pfrom, const CSigSharesInv& inv, CConnman& connman);
|
||||
bool ProcessMessageBatchedSigShares(CNode* pfrom, const CBatchedSigShares& batchedSigShares, CConnman& connman);
|
||||
void ProcessMessageSigShare(NodeId fromId, const CSigShare& sigShare, CConnman& connman);
|
||||
|
||||
bool VerifySigSharesInv(NodeId from, Consensus::LLMQType llmqType, const CSigSharesInv& inv);
|
||||
bool PreVerifyBatchedSigShares(NodeId nodeId, const CSigSharesNodeState::SessionInfo& session, const CBatchedSigShares& batchedSigShares, bool& retBan);
|
||||
@ -417,6 +449,7 @@ private:
|
||||
bool SendMessages();
|
||||
void CollectSigSharesToRequest(std::unordered_map<NodeId, std::unordered_map<uint256, CSigSharesInv, StaticSaltedHasher>>& sigSharesToRequest);
|
||||
void CollectSigSharesToSend(std::unordered_map<NodeId, std::unordered_map<uint256, CBatchedSigShares, StaticSaltedHasher>>& sigSharesToSend);
|
||||
void CollectSigSharesToSend(std::unordered_map<NodeId, std::vector<CSigShare>>& sigSharesToSend, const std::vector<CNode*>& vNodes);
|
||||
void CollectSigSharesToAnnounce(std::unordered_map<NodeId, std::unordered_map<uint256, CSigSharesInv, StaticSaltedHasher>>& sigSharesToAnnounce);
|
||||
bool SignPendingSigShares();
|
||||
void WorkThreadMain();
|
||||
|
@ -69,6 +69,7 @@ const char *QSIGSHARESINV="qsigsinv";
|
||||
const char *QGETSIGSHARES="qgetsigs";
|
||||
const char *QBSIGSHARES="qbsigs";
|
||||
const char *QSIGREC="qsigrec";
|
||||
const char *QSIGSHARE="qsigshare";
|
||||
const char *CLSIG="clsig";
|
||||
const char *ISLOCK="islock";
|
||||
const char *MNAUTH="mnauth";
|
||||
@ -135,6 +136,7 @@ const static std::string allNetMessageTypes[] = {
|
||||
NetMsgType::QGETSIGSHARES,
|
||||
NetMsgType::QBSIGSHARES,
|
||||
NetMsgType::QSIGREC,
|
||||
NetMsgType::QSIGSHARE,
|
||||
NetMsgType::CLSIG,
|
||||
NetMsgType::ISLOCK,
|
||||
NetMsgType::MNAUTH,
|
||||
|
@ -266,6 +266,7 @@ extern const char *QSIGSHARESINV;
|
||||
extern const char *QGETSIGSHARES;
|
||||
extern const char *QBSIGSHARES;
|
||||
extern const char *QSIGREC;
|
||||
extern const char *QSIGSHARE;
|
||||
extern const char *CLSIG;
|
||||
extern const char *ISLOCK;
|
||||
extern const char *MNAUTH;
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <llmq/quorums_debug.h>
|
||||
#include <llmq/quorums_dkgsession.h>
|
||||
#include <llmq/quorums_signing.h>
|
||||
#include <llmq/quorums_signing_shares.h>
|
||||
|
||||
void quorum_list_help()
|
||||
{
|
||||
@ -377,6 +378,54 @@ UniValue quorum_sigs_cmd(const JSONRPCRequest& request)
|
||||
}
|
||||
}
|
||||
|
||||
void quorum_selectquorum_help()
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"quorum selectquorum llmqType \"id\"\n"
|
||||
"Returns the quorum that would/should sign a request\n"
|
||||
"\nArguments:\n"
|
||||
"1. llmqType (int, required) LLMQ type.\n"
|
||||
"2. \"id\" (string, required) Request id.\n"
|
||||
);
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
int tipHeight;
|
||||
{
|
||||
LOCK(cs_main);
|
||||
tipHeight = chainActive.Height();
|
||||
}
|
||||
|
||||
UniValue ret(UniValue::VOBJ);
|
||||
|
||||
auto quorum = llmq::quorumSigningManager->SelectQuorumForSigning(llmqType, tipHeight, id);
|
||||
if (!quorum) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "no quorums active");
|
||||
}
|
||||
ret.push_back(Pair("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.push_back(Pair("recoveryMembers", recoveryMembers));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void quorum_dkgsimerror_help()
|
||||
{
|
||||
throw std::runtime_error(
|
||||
@ -427,6 +476,7 @@ UniValue quorum_dkgsimerror(const JSONRPCRequest& request)
|
||||
" 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"
|
||||
);
|
||||
}
|
||||
|
||||
@ -451,6 +501,8 @@ UniValue quorum(const JSONRPCRequest& request)
|
||||
return quorum_memberof(request);
|
||||
} else if (command == "sign" || 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 {
|
||||
|
@ -21,9 +21,15 @@ class LLMQSigningTest(DashTestFramework):
|
||||
self.set_dash_test_params(6, 5, fast_dip3_enforcement=True)
|
||||
self.set_dash_llmq_test_params(5, 3)
|
||||
|
||||
def add_options(self, parser):
|
||||
parser.add_option("--spork21", dest="spork21", default=False, action="store_true",
|
||||
help="Test with spork21 enabled")
|
||||
|
||||
def run_test(self):
|
||||
|
||||
self.nodes[0].spork("SPORK_17_QUORUM_DKG_ENABLED", 0)
|
||||
if self.options.spork21:
|
||||
self.nodes[0].spork("SPORK_21_QUORUM_ALL_CONNECTED", 0)
|
||||
self.wait_for_sporks_same()
|
||||
|
||||
self.mine_quorum()
|
||||
@ -94,5 +100,24 @@ class LLMQSigningTest(DashTestFramework):
|
||||
self.mninfo[i].node.quorum("sign", 100, id, msgHash)
|
||||
wait_for_sigs(True, False, True, 15)
|
||||
|
||||
if self.options.spork21:
|
||||
id = "0000000000000000000000000000000000000000000000000000000000000002"
|
||||
|
||||
# Isolate the node that is responsible for the recovery of a signature and assert that recovery fails
|
||||
q = self.nodes[0].quorum('selectquorum', 100, id)
|
||||
mn = self.get_mninfo(q['recoveryMembers'][0])
|
||||
mn.node.setnetworkactive(False)
|
||||
wait_until(lambda: mn.node.getconnectioncount() == 0)
|
||||
for i in range(4):
|
||||
self.mninfo[i].node.quorum("sign", 100, id, msgHash)
|
||||
assert_sigs_nochange(False, False, False, 3)
|
||||
# Need to re-connect so that it later gets the recovered sig
|
||||
mn.node.setnetworkactive(True)
|
||||
connect_nodes(mn.node, 0)
|
||||
# Let 1 second pass so that the next node is used for recovery, which should succeed
|
||||
self.bump_mocktime(1)
|
||||
set_node_times(self.nodes, self.mocktime)
|
||||
wait_for_sigs(True, False, True, 5)
|
||||
|
||||
if __name__ == '__main__':
|
||||
LLMQSigningTest().main()
|
||||
|
@ -962,12 +962,15 @@ class DashTestFramework(BitcoinTestFramework):
|
||||
qi = self.nodes[0].quorum('info', 100, q)
|
||||
result = []
|
||||
for m in qi['members']:
|
||||
for mn in self.mninfo:
|
||||
if mn.proTxHash == m['proTxHash']:
|
||||
result.append(mn)
|
||||
break
|
||||
result.append(self.get_mninfo(m['proTxHash']))
|
||||
return result
|
||||
|
||||
def get_mninfo(self, proTxHash):
|
||||
for mn in self.mninfo:
|
||||
if mn.proTxHash == proTxHash:
|
||||
return mn
|
||||
return None
|
||||
|
||||
def wait_for_mnauth(self, node, count, timeout=10):
|
||||
def test():
|
||||
pi = node.getpeerinfo()
|
||||
|
@ -69,6 +69,7 @@ BASE_SCRIPTS= [
|
||||
'listtransactions.py',
|
||||
'multikeysporks.py',
|
||||
'llmq-signing.py', # NOTE: needs dash_hash to pass
|
||||
'llmq-signing.py --spork21', # NOTE: needs dash_hash to pass
|
||||
'llmq-chainlocks.py', # NOTE: needs dash_hash to pass
|
||||
'llmq-connections.py', # NOTE: needs dash_hash to pass
|
||||
'llmq-simplepose.py', # NOTE: needs dash_hash to pass
|
||||
|
Loading…
Reference in New Issue
Block a user