Implement CSigningManager to process and propagage recovered signatures

This commit is contained in:
Alexander Block 2019-01-15 15:35:26 +01:00
parent 56ee83a766
commit 43fd1b352f
14 changed files with 783 additions and 4 deletions

View File

@ -149,6 +149,7 @@ BITCOIN_CORE_H = \
llmq/quorums_dkgsessionmgr.h \
llmq/quorums_dkgsession.h \
llmq/quorums_init.h \
llmq/quorums_signing.h \
llmq/quorums_utils.h \
masternode-meta.h \
masternode-payments.h \
@ -262,6 +263,7 @@ libdash_server_a_SOURCES = \
llmq/quorums_dkgsessionmgr.cpp \
llmq/quorums_dkgsession.cpp \
llmq/quorums_init.cpp \
llmq/quorums_signing.cpp \
llmq/quorums_utils.cpp \
masternode-meta.cpp \
masternode-payments.cpp \

View File

@ -120,6 +120,8 @@ static Consensus::LLMQParams llmq10_60 = {
.dkgMiningWindowEnd = 18,
.dkgBadVotesThreshold = 8,
.signingActiveQuorumCount = 2, // just a few ones to allow easier testing
.neighborConnections = 2,
.diagonalConnections = 2,
.keepOldConnections = 24,
@ -138,6 +140,8 @@ static Consensus::LLMQParams llmq50_60 = {
.dkgMiningWindowEnd = 18,
.dkgBadVotesThreshold = 40,
.signingActiveQuorumCount = 24, // a full day worth of LLMQs
.neighborConnections = 2,
.diagonalConnections = 2,
.keepOldConnections = 24,
@ -156,6 +160,8 @@ static Consensus::LLMQParams llmq400_60 = {
.dkgMiningWindowEnd = 28,
.dkgBadVotesThreshold = 300,
.signingActiveQuorumCount = 4, // two days worth of LLMQs
.neighborConnections = 4,
.diagonalConnections = 4,
.keepOldConnections = 4,
@ -175,6 +181,8 @@ static Consensus::LLMQParams llmq400_85 = {
.dkgMiningWindowEnd = 48, // give it a larger mining window to make sure it is mined
.dkgBadVotesThreshold = 300,
.signingActiveQuorumCount = 4, // two days worth of LLMQs
.neighborConnections = 4,
.diagonalConnections = 4,
.keepOldConnections = 4,

View File

@ -104,6 +104,9 @@ struct LLMQParams {
// phase-transition, which would otherwise result in inconsistent views of the valid members set
int dkgBadVotesThreshold;
// Number of quorums to consider "active" for signing sessions
int signingActiveQuorumCount;
// Used for inter-quorum communication. This is the number of deterministic connections built to the clockwise
// neighbors on the circle shaped nodes topography
int neighborConnections;

View File

@ -1747,7 +1747,7 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler)
pcoinsdbview = new CCoinsViewDB(nCoinDBCache, false, fReindex || fReindexChainState);
pcoinscatcher = new CCoinsViewErrorCatcher(pcoinsdbview);
pcoinsTip = new CCoinsViewCache(pcoinscatcher);
llmq::InitLLMQSystem(*evoDb, &scheduler);
llmq::InitLLMQSystem(*evoDb, &scheduler, false);
if (fReindex) {
pblocktree->WriteReindexing(true);

View File

@ -9,6 +9,7 @@
#include "quorums_commitment.h"
#include "quorums_debug.h"
#include "quorums_dkgsessionmgr.h"
#include "quorums_signing.h"
#include "scheduler.h"
@ -17,16 +18,19 @@ namespace llmq
static CBLSWorker blsWorker;
void InitLLMQSystem(CEvoDB& evoDb, CScheduler* scheduler)
void InitLLMQSystem(CEvoDB& evoDb, CScheduler* scheduler, bool unitTests)
{
quorumDKGDebugManager = new CDKGDebugManager(scheduler);
quorumBlockProcessor = new CQuorumBlockProcessor(evoDb);
quorumDKGSessionManager = new CDKGSessionManager(evoDb, blsWorker);
quorumManager = new CQuorumManager(evoDb, blsWorker, *quorumDKGSessionManager);
quorumSigningManager = new CSigningManager(unitTests);
}
void DestroyLLMQSystem()
{
delete quorumSigningManager;
quorumSigningManager = nullptr;
delete quorumManager;
quorumManager = NULL;
delete quorumDKGSessionManager;

View File

@ -14,7 +14,7 @@ namespace llmq
// If true, we will connect to all new quorums and watch their communication
static const bool DEFAULT_WATCH_QUORUMS = false;
void InitLLMQSystem(CEvoDB& evoDb, CScheduler* scheduler);
void InitLLMQSystem(CEvoDB& evoDb, CScheduler* scheduler, bool unitTests);
void DestroyLLMQSystem();
}

View File

@ -0,0 +1,532 @@
// Copyright (c) 2018 The Dash Core developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "quorums_signing.h"
#include "quorums_utils.h"
#include "quorums_signing_shares.h"
#include "activemasternode.h"
#include "bls/bls_batchverifier.h"
#include "cxxtimer.hpp"
#include "init.h"
#include "net_processing.h"
#include "netmessagemaker.h"
#include "scheduler.h"
#include "validation.h"
#include <algorithm>
#include <limits>
namespace llmq
{
CSigningManager* quorumSigningManager;
CRecoveredSigsDb::CRecoveredSigsDb(bool fMemory) :
db(fMemory ? "" : (GetDataDir() / "llmq"), 1 << 20, fMemory)
{
}
bool CRecoveredSigsDb::HasRecoveredSig(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash)
{
auto k = std::make_tuple('r', (uint8_t)llmqType, id, msgHash);
return db.Exists(k);
}
bool CRecoveredSigsDb::HasRecoveredSigForId(Consensus::LLMQType llmqType, const uint256& id)
{
auto k = std::make_tuple('r', (uint8_t)llmqType, id);
return db.Exists(k);
}
bool CRecoveredSigsDb::HasRecoveredSigForSession(const uint256& signHash)
{
auto k = std::make_tuple('s', signHash);
return db.Exists(k);
}
bool CRecoveredSigsDb::HasRecoveredSigForHash(const uint256& hash)
{
auto k = std::make_tuple('h', hash);
return db.Exists(k);
}
bool CRecoveredSigsDb::ReadRecoveredSig(Consensus::LLMQType llmqType, const uint256& id, CRecoveredSig& ret)
{
auto k = std::make_tuple('r', (uint8_t)llmqType, id);
CDataStream ds(SER_DISK, CLIENT_VERSION);
if (!db.ReadDataStream(k, ds)) {
return false;
}
try {
ret.Unserialize(ds, false);
return true;
} catch (std::exception&) {
return false;
}
}
bool CRecoveredSigsDb::GetRecoveredSigByHash(const uint256& hash, CRecoveredSig& ret)
{
auto k1 = std::make_tuple('h', hash);
std::pair<uint8_t, uint256> k2;
if (!db.Read(k1, k2)) {
return false;
}
return ReadRecoveredSig((Consensus::LLMQType)k2.first, k2.second, ret);
}
bool CRecoveredSigsDb::GetRecoveredSigById(Consensus::LLMQType llmqType, const uint256& id, CRecoveredSig& ret)
{
return ReadRecoveredSig(llmqType, id, ret);
}
void CRecoveredSigsDb::WriteRecoveredSig(const llmq::CRecoveredSig& recSig)
{
CDBBatch batch(db);
// we put these close to each other to leverage leveldb's key compaction
// this way, the second key can be used for fast HasRecoveredSig checks while the first key stores the recSig
auto k1 = std::make_tuple('r', recSig.llmqType, recSig.id);
auto k2 = std::make_tuple('r', recSig.llmqType, recSig.id, recSig.msgHash);
batch.Write(k1, recSig);
batch.Write(k2, (uint8_t)1);
// store by object hash
auto k3 = std::make_tuple('h', recSig.GetHash());
batch.Write(k3, std::make_pair(recSig.llmqType, recSig.id));
// store by signHash
auto k4 = std::make_tuple('s', CLLMQUtils::BuildSignHash(recSig));
batch.Write(k4, (uint8_t)1);
// remove the votedForId entry as we won't need it anymore
auto k5 = std::make_tuple('v', recSig.llmqType, recSig.id);
batch.Erase(k5);
// store by current time. Allows fast cleanup of old recSigs
auto k6 = std::make_tuple('t', (uint32_t)GetAdjustedTime(), recSig.llmqType, recSig.id);
batch.Write(k6, (uint8_t)1);
db.WriteBatch(batch);
}
void CRecoveredSigsDb::CleanupOldRecoveredSigs(int64_t maxAge)
{
std::unique_ptr<CDBIterator> pcursor(db.NewIterator());
auto start = std::make_tuple('t', (uint32_t)0, (uint8_t)0, uint256());
auto end = std::make_tuple('t', (uint32_t)(GetAdjustedTime() - maxAge), (uint8_t)0, uint256());
pcursor->Seek(start);
std::vector<std::pair<Consensus::LLMQType, uint256>> toDelete;
std::vector<decltype(start)> toDelete2;
while (pcursor->Valid()) {
decltype(start) k;
if (!pcursor->GetKey(k)) {
break;
}
if (k >= end) {
break;
}
toDelete.emplace_back((Consensus::LLMQType)std::get<2>(k), std::get<3>(k));
toDelete2.emplace_back(k);
pcursor->Next();
}
pcursor.reset();
if (toDelete.empty()) {
return;
}
CDBBatch batch(db);
for (auto& e : toDelete) {
CRecoveredSig recSig;
if (!ReadRecoveredSig(e.first, e.second, recSig)) {
continue;
}
auto k1 = std::make_tuple('r', recSig.llmqType, recSig.id);
auto k2 = std::make_tuple('r', recSig.llmqType, recSig.id, recSig.msgHash);
auto k3 = std::make_tuple('h', recSig.GetHash());
auto k4 = std::make_tuple('s', CLLMQUtils::BuildSignHash(recSig));
auto k5 = std::make_tuple('v', recSig.llmqType, recSig.id);
batch.Erase(k1);
batch.Erase(k2);
batch.Erase(k3);
batch.Erase(k4);
batch.Erase(k5);
}
for (auto& e : toDelete2) {
batch.Erase(e);
}
db.WriteBatch(batch);
}
bool CRecoveredSigsDb::HasVotedOnId(Consensus::LLMQType llmqType, const uint256& id)
{
auto k = std::make_tuple('v', (uint8_t)llmqType, id);
return db.Exists(k);
}
bool CRecoveredSigsDb::GetVoteForId(Consensus::LLMQType llmqType, const uint256& id, uint256& msgHashRet)
{
auto k = std::make_tuple('v', (uint8_t)llmqType, id);
return db.Read(k, msgHashRet);
}
void CRecoveredSigsDb::WriteVoteForId(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash)
{
auto k = std::make_tuple('v', (uint8_t)llmqType, id);
db.Write(k, msgHash);
}
//////////////////
CSigningManager::CSigningManager(bool fMemory) :
db(fMemory)
{
}
bool CSigningManager::AlreadyHave(const CInv& inv)
{
LOCK(cs);
return inv.type == MSG_QUORUM_RECOVERED_SIG && db.HasRecoveredSigForHash(inv.hash);
}
bool CSigningManager::GetRecoveredSigForGetData(const uint256& hash, CRecoveredSig& ret)
{
if (!db.GetRecoveredSigByHash(hash, ret)) {
return false;
}
if (!CLLMQUtils::IsQuorumActive((Consensus::LLMQType)(ret.llmqType), ret.quorumHash)) {
// we don't want to propagate sigs from inactive quorums
return false;
}
return true;
}
void CSigningManager::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman)
{
if (strCommand == NetMsgType::QSIGREC) {
CRecoveredSig recoveredSig;
vRecv >> recoveredSig;
ProcessMessageRecoveredSig(pfrom, recoveredSig, connman);
}
}
void CSigningManager::ProcessMessageRecoveredSig(CNode* pfrom, const CRecoveredSig& recoveredSig, CConnman& connman)
{
bool ban = false;
if (!PreVerifyRecoveredSig(pfrom->id, recoveredSig, ban)) {
if (ban) {
LOCK(cs_main);
Misbehaving(pfrom->id, 100);
return;
}
return;
}
// It's important to only skip seen *valid* sig shares here. See comment for CBatchedSigShare
// We don't receive recovered sigs in batches, but we do batched verification per node on these
if (db.HasRecoveredSigForHash(recoveredSig.GetHash())) {
return;
}
LogPrint("llmq", "CSigningManager::%s -- signHash=%s, node=%d\n", __func__, CLLMQUtils::BuildSignHash(recoveredSig).ToString(), pfrom->id);
LOCK(cs);
pendingRecoveredSigs[pfrom->id].emplace_back(recoveredSig);
}
bool CSigningManager::PreVerifyRecoveredSig(NodeId nodeId, const CRecoveredSig& recoveredSig, bool& retBan)
{
retBan = false;
auto llmqType = (Consensus::LLMQType)recoveredSig.llmqType;
if (!Params().GetConsensus().llmqs.count(llmqType)) {
retBan = true;
return false;
}
CQuorumCPtr quorum;
{
LOCK(cs_main);
quorum = quorumManager->GetQuorum(llmqType, recoveredSig.quorumHash);
if (!quorum) {
LogPrintf("CSigningManager::%s -- quorum %s not found, node=%d\n", __func__,
recoveredSig.quorumHash.ToString(), nodeId);
return false;
}
if (!CLLMQUtils::IsQuorumActive(llmqType, quorum->quorumHash)) {
return false;
}
}
if (!recoveredSig.sig.IsValid()) {
retBan = true;
return false;
}
return true;
}
void CSigningManager::CollectPendingRecoveredSigsToVerify(
size_t maxUniqueSessions,
std::map<NodeId, std::list<CRecoveredSig>>& retSigShares,
std::map<std::pair<Consensus::LLMQType, uint256>, CQuorumCPtr>& retQuorums)
{
{
LOCK(cs);
if (pendingRecoveredSigs.empty()) {
return;
}
std::set<std::pair<NodeId, uint256>> uniqueSignHashes;
CLLMQUtils::IterateNodesRandom(pendingRecoveredSigs, [&]() {
return uniqueSignHashes.size() < maxUniqueSessions;
}, [&](NodeId nodeId, std::list<CRecoveredSig>& ns) {
if (ns.empty()) {
return false;
}
auto& recSig = *ns.begin();
bool alreadyHave = db.HasRecoveredSigForHash(recSig.GetHash());
if (!alreadyHave) {
uniqueSignHashes.emplace(nodeId, CLLMQUtils::BuildSignHash(recSig));
retSigShares[nodeId].emplace_back(recSig);
}
ns.erase(ns.begin());
return !ns.empty();
}, rnd);
if (retSigShares.empty()) {
return;
}
}
{
LOCK(cs_main);
for (auto& p : retSigShares) {
NodeId nodeId = p.first;
auto& v = p.second;
for (auto it = v.begin(); it != v.end();) {
auto& recSig = *it;
Consensus::LLMQType llmqType = (Consensus::LLMQType) recSig.llmqType;
auto quorumKey = std::make_pair((Consensus::LLMQType)recSig.llmqType, recSig.quorumHash);
if (!retQuorums.count(quorumKey)) {
CQuorumCPtr quorum = quorumManager->GetQuorum(llmqType, recSig.quorumHash);
if (!quorum) {
LogPrintf("CSigningManager::%s -- quorum %s not found, node=%d\n", __func__,
recSig.quorumHash.ToString(), nodeId);
it = v.erase(it);
continue;
}
if (!CLLMQUtils::IsQuorumActive(llmqType, quorum->quorumHash)) {
LogPrintf("CSigningManager::%s -- quorum %s not active anymore, node=%d\n", __func__,
recSig.quorumHash.ToString(), nodeId);
it = v.erase(it);
continue;
}
retQuorums.emplace(quorumKey, quorum);
}
++it;
}
}
}
}
void CSigningManager::ProcessPendingRecoveredSigs(CConnman& connman)
{
std::map<NodeId, std::list<CRecoveredSig>> recSigsByNode;
std::map<std::pair<Consensus::LLMQType, uint256>, CQuorumCPtr> quorums;
CollectPendingRecoveredSigsToVerify(32, recSigsByNode, quorums);
if (recSigsByNode.empty()) {
return;
}
CBLSInsecureBatchVerifier<NodeId, uint256> batchVerifier;
size_t verifyCount = 0;
for (auto& p : recSigsByNode) {
NodeId nodeId = p.first;
auto& v = p.second;
for (auto& recSig : v) {
const auto& quorum = quorums.at(std::make_pair((Consensus::LLMQType)recSig.llmqType, recSig.quorumHash));
batchVerifier.PushMessage(nodeId, recSig.GetHash(), CLLMQUtils::BuildSignHash(recSig), recSig.sig, quorum->quorumPublicKey);
verifyCount++;
}
}
cxxtimer::Timer verifyTimer;
batchVerifier.Verify(false);
verifyTimer.stop();
LogPrint("llmq", "CSigningManager::%s -- verified recovered sig(s). count=%d, vt=%d, nodes=%d\n", __func__, verifyCount, verifyTimer.count(), recSigsByNode.size());
std::set<uint256> processed;
for (auto& p : recSigsByNode) {
NodeId nodeId = p.first;
auto& v = p.second;
if (batchVerifier.badSources.count(nodeId)) {
LOCK(cs_main);
LogPrint("llmq", "CSigningManager::%s -- invalid recSig from other node, banning peer=%d\n", __func__, nodeId);
Misbehaving(nodeId, 100);
continue;
}
for (auto& recSig : v) {
if (!processed.emplace(recSig.GetHash()).second) {
continue;
}
const auto& quorum = quorums.at(std::make_pair((Consensus::LLMQType)recSig.llmqType, recSig.quorumHash));
ProcessRecoveredSig(nodeId, recSig, quorum, connman);
}
}
}
// signature must be verified already
void CSigningManager::ProcessRecoveredSig(NodeId nodeId, const CRecoveredSig& recoveredSig, const CQuorumCPtr& quorum, CConnman& connman)
{
auto llmqType = (Consensus::LLMQType)recoveredSig.llmqType;
{
LOCK(cs_main);
connman.RemoveAskFor(recoveredSig.GetHash());
}
{
LOCK(cs);
auto signHash = CLLMQUtils::BuildSignHash(recoveredSig);
LogPrintf("CSigningManager::%s -- valid recSig. signHash=%s, id=%s, msgHash=%s, node=%d\n", __func__,
signHash.ToString(), recoveredSig.id.ToString(), recoveredSig.msgHash.ToString(), nodeId);
if (db.HasRecoveredSigForId(llmqType, recoveredSig.id)) {
// this should really not happen, as each masternode is participating in only one vote,
// even if it's a member of multiple quorums. so a majority is only possible on one quorum and one msgHash per id
LogPrintf("CSigningManager::%s -- conflicting recoveredSig for id=%s, msgHash=%s\n", __func__,
recoveredSig.id.ToString(), recoveredSig.msgHash.ToString());
return;
}
db.WriteRecoveredSig(recoveredSig);
}
CInv inv(MSG_QUORUM_RECOVERED_SIG, recoveredSig.GetHash());
g_connman->RelayInv(inv);
}
void CSigningManager::Cleanup()
{
int64_t now = GetTimeMillis();
if (now - lastCleanupTime < 5000) {
return;
}
int64_t maxAge = GetArg("-recsigsmaxage", DEFAULT_MAX_RECOVERED_SIGS_AGE);
db.CleanupOldRecoveredSigs(maxAge);
lastCleanupTime = GetTimeMillis();
}
bool CSigningManager::AsyncSignIfMember(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash)
{
auto& params = Params().GetConsensus().llmqs.at(llmqType);
if (!fMasternodeMode || activeMasternodeInfo.proTxHash.IsNull()) {
return false;
}
{
LOCK(cs);
if (db.HasVotedOnId(llmqType, id)) {
uint256 prevMsgHash;
db.GetVoteForId(llmqType, id, prevMsgHash);
LogPrintf("CSigningManager::%s -- already voted for id=%s and msgHash=%s. Not voting on conflicting msgHash=%s\n", __func__,
id.ToString(), prevMsgHash.ToString(), msgHash.ToString());
return false;
}
if (db.HasRecoveredSigForId(llmqType, id)) {
// no need to sign it if we already have a recovered sig
return false;
}
db.WriteVoteForId(llmqType, id, msgHash);
}
// This might end up giving different results on different members
// This might happen when we are on the brink of confirming a new quorum
// This gives a slight risk of not getting enough shares to recover a signature
// But at least it shouldn't be possible to get conflicting recovered signatures
// TODO fix this by re-signing when the next block arrives, but only when that block results in a change of the quorum list and no recovered signature has been created in the mean time
CQuorumCPtr quorum = quorumManager->SelectQuorum(llmqType, id, params.signingActiveQuorumCount);
if (!quorum) {
LogPrintf("CSigningManager::%s -- failed to select quorum. id=%s, msgHash=%s\n", __func__, id.ToString(), msgHash.ToString());
return false;
}
if (!quorum->IsValidMember(activeMasternodeInfo.proTxHash)) {
//LogPrintf("CSigningManager::%s -- we're not a valid member of quorum %s\n", __func__, quorum->quorumHash.ToString());
return false;
}
quorumSigSharesManager->AsyncSign(quorum, id, msgHash);
return true;
}
bool CSigningManager::HasRecoveredSig(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash)
{
return db.HasRecoveredSig(llmqType, id, msgHash);
}
bool CSigningManager::HasRecoveredSigForId(Consensus::LLMQType llmqType, const uint256& id)
{
return db.HasRecoveredSigForId(llmqType, id);
}
bool CSigningManager::HasRecoveredSigForSession(const uint256& signHash)
{
return db.HasRecoveredSigForSession(signHash);
}
bool CSigningManager::IsConflicting(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash)
{
if (!db.HasRecoveredSigForId(llmqType, id)) {
// no recovered sig present, so no conflict
return false;
}
if (!db.HasRecoveredSig(llmqType, id, msgHash)) {
// recovered sig is present, but not for the given msgHash. That's a conflict!
return true;
}
// all good
return false;
}
}

142
src/llmq/quorums_signing.h Normal file
View File

@ -0,0 +1,142 @@
// Copyright (c) 2018 The Dash Core developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef DASH_QUORUMS_SIGNING_H
#define DASH_QUORUMS_SIGNING_H
#include "llmq/quorums.h"
#include "net.h"
#include "chainparams.h"
namespace llmq
{
class CRecoveredSig
{
public:
uint8_t llmqType;
uint256 quorumHash;
uint256 id;
uint256 msgHash;
CBLSSignature sig;
// only in-memory
uint256 hash;
public:
template<typename Stream>
inline void Serialize(Stream& s) const
{
s << llmqType;
s << quorumHash;
s << id;
s << msgHash;
s << sig;
}
template<typename Stream>
inline void Unserialize(Stream& s, bool checkMalleable = true, bool updateHash = true, bool skipSig = false)
{
s >> llmqType;
s >> quorumHash;
s >> id;
s >> msgHash;
if (!skipSig) {
sig.Unserialize(s, checkMalleable);
if (updateHash) {
UpdateHash();
}
}
}
void UpdateHash()
{
hash = ::SerializeHash(*this);
}
const uint256& GetHash() const
{
assert(!hash.IsNull());
return hash;
}
};
// TODO implement caching to speed things up
class CRecoveredSigsDb
{
private:
CDBWrapper db;
public:
CRecoveredSigsDb(bool fMemory);
bool HasRecoveredSig(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash);
bool HasRecoveredSigForId(Consensus::LLMQType llmqType, const uint256& id);
bool HasRecoveredSigForSession(const uint256& signHash);
bool HasRecoveredSigForHash(const uint256& hash);
bool GetRecoveredSigByHash(const uint256& hash, CRecoveredSig& ret);
bool GetRecoveredSigById(Consensus::LLMQType llmqType, const uint256& id, CRecoveredSig& ret);
void WriteRecoveredSig(const CRecoveredSig& recSig);
void CleanupOldRecoveredSigs(int64_t maxAge);
// votes are removed when the recovered sig is written to the db
bool HasVotedOnId(Consensus::LLMQType llmqType, const uint256& id);
bool GetVoteForId(Consensus::LLMQType llmqType, const uint256& id, uint256& msgHashRet);
void WriteVoteForId(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash);
private:
bool ReadRecoveredSig(Consensus::LLMQType llmqType, const uint256& id, CRecoveredSig& ret);
};
class CSigningManager
{
friend class CSigSharesManager;
static const int64_t DEFAULT_MAX_RECOVERED_SIGS_AGE = 60 * 60 * 24 * 7; // keep them for a week
private:
CCriticalSection cs;
CRecoveredSigsDb db;
// Incoming and not verified yet
std::map<NodeId, std::list<CRecoveredSig>> pendingRecoveredSigs;
// must be protected by cs
FastRandomContext rnd;
int64_t lastCleanupTime{0};
public:
CSigningManager(bool fMemory);
bool AlreadyHave(const CInv& inv);
bool GetRecoveredSigForGetData(const uint256& hash, CRecoveredSig& ret);
void ProcessMessage(CNode* pnode, const std::string& strCommand, CDataStream& vRecv, CConnman& connman);
private:
void ProcessMessageRecoveredSig(CNode* pfrom, const CRecoveredSig& recoveredSig, CConnman& connman);
bool PreVerifyRecoveredSig(NodeId nodeId, const CRecoveredSig& recoveredSig, bool& retBan);
void CollectPendingRecoveredSigsToVerify(size_t maxUniqueSessions, std::map<NodeId, std::list<CRecoveredSig>>& retSigShares, std::map<std::pair<Consensus::LLMQType, uint256>, CQuorumCPtr>& retQuorums);
void ProcessPendingRecoveredSigs(CConnman& connman); // called from the worker thread of CSigSharesManager
void ProcessRecoveredSig(NodeId nodeId, const CRecoveredSig& recoveredSig, const CQuorumCPtr& quorum, CConnman& connman);
void Cleanup(); // called from the worker thread of CSigSharesManager
public:
// public interface
bool AsyncSignIfMember(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash);
bool HasRecoveredSig(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash);
bool HasRecoveredSigForId(Consensus::LLMQType llmqType, const uint256& id);
bool HasRecoveredSigForSession(const uint256& signHash);
bool IsConflicting(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash);
};
extern CSigningManager* quorumSigningManager;
}
#endif //DASH_QUORUMS_SIGNING_H

View File

@ -2,10 +2,12 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "quorums.h"
#include "quorums_utils.h"
#include "chainparams.h"
#include "random.h"
#include "validation.h"
namespace llmq
{
@ -29,6 +31,16 @@ uint256 CLLMQUtils::BuildCommitmentHash(uint8_t llmqType, const uint256& blockHa
return hw.GetHash();
}
uint256 CLLMQUtils::BuildSignHash(Consensus::LLMQType llmqType, const uint256& quorumHash, const uint256& id, const uint256& msgHash)
{
CHashWriter h(SER_GETHASH, 0);
h << (uint8_t)llmqType;
h << quorumHash;
h << id;
h << msgHash;
return h.GetHash();
}
std::set<CService> CLLMQUtils::GetQuorumConnections(Consensus::LLMQType llmqType, const uint256& blockHash, const uint256& forMember)
{
auto& params = Params().GetConsensus().llmqs.at(llmqType);
@ -85,4 +97,23 @@ std::set<size_t> CLLMQUtils::CalcDeterministicWatchConnections(Consensus::LLMQTy
return result;
}
bool CLLMQUtils::IsQuorumActive(Consensus::LLMQType llmqType, const uint256& quorumHash)
{
AssertLockHeld(cs_main);
auto& params = Params().GetConsensus().llmqs.at(llmqType);
// sig shares and recovered sigs are only accepted from recent/active quorums
// we allow one more active quorum as specified in consensus, as otherwise there is a small window where things could
// fail while we are on the brink of a new quorum
auto quorums = quorumManager->ScanQuorums(llmqType, (int)params.signingActiveQuorumCount + 1);
for (auto& q : quorums) {
if (q->quorumHash == quorumHash) {
return true;
}
}
return false;
}
}

View File

@ -6,6 +6,7 @@
#define DASH_QUORUMS_UTILS_H
#include "consensus/params.h"
#include "net.h"
#include "evo/deterministicmns.h"
@ -21,9 +22,49 @@ public:
static std::vector<CDeterministicMNCPtr> GetAllQuorumMembers(Consensus::LLMQType llmqType, const uint256& blockHash);
static uint256 BuildCommitmentHash(uint8_t llmqType, const uint256& blockHash, const std::vector<bool>& validMembers, const CBLSPublicKey& pubKey, const uint256& vvecHash);
static uint256 BuildSignHash(Consensus::LLMQType llmqType, const uint256& quorumHash, const uint256& id, const uint256& msgHash);
// works for sig shares and recovered sigs
template<typename T>
static uint256 BuildSignHash(const T& s)
{
return BuildSignHash((Consensus::LLMQType)s.llmqType, s.quorumHash, s.id, s.msgHash);
}
static std::set<CService> GetQuorumConnections(Consensus::LLMQType llmqType, const uint256& blockHash, const uint256& forMember);
static std::set<size_t> CalcDeterministicWatchConnections(Consensus::LLMQType llmqType, const uint256& blockHash, size_t memberCount, size_t connectionCount);
static bool IsQuorumActive(Consensus::LLMQType llmqType, const uint256& quorumHash);
template<typename NodesContainer, typename Continue, typename Callback>
static void IterateNodesRandom(NodesContainer& nodeStates, Continue&& cont, Callback&& callback, FastRandomContext& rnd)
{
std::vector<typename NodesContainer::iterator> rndNodes;
rndNodes.reserve(nodeStates.size());
for (auto it = nodeStates.begin(); it != nodeStates.end(); ++it) {
rndNodes.emplace_back(it);
}
if (rndNodes.empty()) {
return;
}
std::random_shuffle(rndNodes.begin(), rndNodes.end(), rnd);
size_t idx = 0;
while (!rndNodes.empty() && cont()) {
auto nodeId = rndNodes[idx]->first;
auto& ns = rndNodes[idx]->second;
if (callback(nodeId, ns)) {
idx = (idx + 1) % rndNodes.size();
} else {
rndNodes.erase(rndNodes.begin() + idx);
if (rndNodes.empty()) {
break;
}
idx %= rndNodes.size();
}
}
}
};
}

View File

@ -49,6 +49,7 @@
#include "llmq/quorums_debug.h"
#include "llmq/quorums_dkgsessionmgr.h"
#include "llmq/quorums_init.h"
#include "llmq/quorums_signing.h"
#include <boost/thread.hpp>
@ -969,6 +970,8 @@ bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
return llmq::quorumDKGSessionManager->AlreadyHave(inv);
case MSG_QUORUM_DEBUG_STATUS:
return llmq::quorumDKGDebugManager->AlreadyHave(inv);
case MSG_QUORUM_RECOVERED_SIG:
return llmq::quorumSigningManager->AlreadyHave(inv);
}
// Don't know what it is, just say we already got one
@ -1273,6 +1276,13 @@ void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParam
push = true;
}
}
if (!push && (inv.type == MSG_QUORUM_RECOVERED_SIG)) {
llmq::CRecoveredSig o;
if (llmq::quorumSigningManager->GetRecoveredSigForGetData(inv.hash, o)) {
connman.PushMessage(pfrom, msgMaker.Make(NetMsgType::QSIGREC, o));
push = true;
}
}
if (!push)
vNotFound.push_back(inv);
@ -2932,6 +2942,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
llmq::quorumBlockProcessor->ProcessMessage(pfrom, strCommand, vRecv, connman);
llmq::quorumDKGSessionManager->ProcessMessage(pfrom, strCommand, vRecv, connman);
llmq::quorumDKGDebugManager->ProcessMessage(pfrom, strCommand, vRecv, connman);
llmq::quorumSigningManager->ProcessMessage(pfrom, strCommand, vRecv, connman);
}
else
{

View File

@ -65,6 +65,7 @@ const char *QJUSTIFICATION="qjustify";
const char *QPCOMMITMENT="qpcommit";
const char *QWATCH="qwatch";
const char *QDEBUGSTATUS="qdebugstatus";
const char *QSIGREC="qsigrec";
};
static const char* ppszTypeName[] =
@ -99,6 +100,7 @@ static const char* ppszTypeName[] =
NetMsgType::QJUSTIFICATION,
NetMsgType::QPCOMMITMENT,
NetMsgType::QDEBUGSTATUS,
NetMsgType::QSIGREC,
};
/** All known message types. Keep this in the same order as the list of
@ -158,6 +160,7 @@ const static std::string allNetMessageTypes[] = {
NetMsgType::QPCOMMITMENT,
NetMsgType::QWATCH,
NetMsgType::QDEBUGSTATUS,
NetMsgType::QSIGREC,
};
const static std::vector<std::string> allNetMessageTypesVec(allNetMessageTypes, allNetMessageTypes+ARRAYLEN(allNetMessageTypes));

View File

@ -271,6 +271,7 @@ extern const char *QJUSTIFICATION;
extern const char *QPCOMMITMENT;
extern const char *QWATCH;
extern const char *QDEBUGSTATUS;
extern const char *QSIGREC;
};
/* Get a vector of all valid message types (see above) */
@ -371,6 +372,7 @@ enum GetDataMsg {
MSG_QUORUM_JUSTIFICATION = 25,
MSG_QUORUM_PREMATURE_COMMITMENT = 26,
MSG_QUORUM_DEBUG_STATUS = 27,
MSG_QUORUM_RECOVERED_SIG = 28,
};
/** inv message data */

View File

@ -78,7 +78,7 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha
mempool.setSanityCheck(1.0);
pblocktree = new CBlockTreeDB(1 << 20, true);
pcoinsdbview = new CCoinsViewDB(1 << 23, true);
llmq::InitLLMQSystem(*evoDb, nullptr);
llmq::InitLLMQSystem(*evoDb, nullptr, true);
pcoinsTip = new CCoinsViewCache(pcoinsdbview);
InitBlockIndex(chainparams);
{