Implement CSigningManager to process and propagage recovered signatures
This commit is contained in:
parent
56ee83a766
commit
43fd1b352f
@ -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 \
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
}
|
||||
|
532
src/llmq/quorums_signing.cpp
Normal file
532
src/llmq/quorums_signing.cpp
Normal 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
142
src/llmq/quorums_signing.h
Normal 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
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user