Skip to content

Commit

Permalink
Re-verify invalid IS sigs when the active quorum set rotated (dashpay…
Browse files Browse the repository at this point in the history
…#3052)

* Split ProcessPendingInstantSendLocks into two methods

* Split SelectQuorumForSigning into SelectQuorumForSigning and GetActiveQuorumSet

* Implement retrying of IS lock verification when the LLMQ active set rotates
  • Loading branch information
codablock committed Aug 12, 2019
1 parent 8c49d9b commit 17ba238
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 9 deletions.
51 changes: 45 additions & 6 deletions src/llmq/quorums_instantsend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -727,8 +727,6 @@ bool CInstantSendManager::PreVerifyInstantSendLock(NodeId nodeId, const llmq::CI

bool CInstantSendManager::ProcessPendingInstantSendLocks()
{
auto llmqType = Params().GetConsensus().llmqForInstantSend;

decltype(pendingInstantSendLocks) pend;

{
Expand All @@ -750,6 +748,44 @@ bool CInstantSendManager::ProcessPendingInstantSendLocks()
tipHeight = chainActive.Height();
}

auto llmqType = Params().GetConsensus().llmqForInstantSend;

// Every time a new quorum enters the active set, an older one is removed. This means that between two blocks, the
// active set can be different, leading to different selection of the signing quorum. When we detect such rotation
// of the active set, we must re-check invalid sigs against the previous active set and only ban nodes when this also
// fails.
auto quorums1 = quorumSigningManager->GetActiveQuorumSet(llmqType, tipHeight);
auto quorums2 = quorumSigningManager->GetActiveQuorumSet(llmqType, tipHeight - 1);
bool quorumsRotated = quorums1 != quorums2;

if (quorumsRotated) {
// first check against the current active set and don't ban
auto badISLocks = ProcessPendingInstantSendLocks(tipHeight, pend, false);
if (!badISLocks.empty()) {
LogPrintf("CInstantSendManager::%s -- detected LLMQ active set rotation, redoing verification on old active set\n", __func__);

// filter out valid IS locks from "pend"
for (auto it = pend.begin(); it != pend.end(); ) {
if (!badISLocks.count(it->first)) {
it = pend.erase(it);
} else {
++it;
}
}
// now check against the previous active set and perform banning if this fails
ProcessPendingInstantSendLocks(tipHeight - 1, pend, true);
}
} else {
ProcessPendingInstantSendLocks(tipHeight, pend, true);
}

return true;
}

std::unordered_set<uint256> CInstantSendManager::ProcessPendingInstantSendLocks(int signHeight, const std::unordered_map<uint256, std::pair<NodeId, CInstantSendLock>>& pend, bool ban)
{
auto llmqType = Params().GetConsensus().llmqForInstantSend;

CBLSBatchVerifier<NodeId, uint256> batchVerifier(false, true, 8);
std::unordered_map<uint256, std::pair<CQuorumCPtr, CRecoveredSig>> recSigs;

Expand All @@ -774,10 +810,10 @@ bool CInstantSendManager::ProcessPendingInstantSendLocks()
continue;
}

auto quorum = quorumSigningManager->SelectQuorumForSigning(llmqType, tipHeight, id);
auto quorum = quorumSigningManager->SelectQuorumForSigning(llmqType, signHeight, id);
if (!quorum) {
// should not happen, but if one fails to select, all others will also fail to select
return false;
return {};
}
uint256 signHash = CLLMQUtils::BuildSignHash(llmqType, quorum->qc.quorumHash, id, islock.txid);
batchVerifier.PushMessage(nodeId, hash, signHash, islock.sig.Get(), quorum->qc.quorumPublicKey);
Expand All @@ -800,7 +836,9 @@ bool CInstantSendManager::ProcessPendingInstantSendLocks()

batchVerifier.Verify();

if (!batchVerifier.badSources.empty()) {
std::unordered_set<uint256> badISLocks;

if (ban && !batchVerifier.badSources.empty()) {
LOCK(cs_main);
for (auto& nodeId : batchVerifier.badSources) {
// Let's not be too harsh, as the peer might simply be unlucky and might have sent us an old lock which
Expand All @@ -816,6 +854,7 @@ bool CInstantSendManager::ProcessPendingInstantSendLocks()
if (batchVerifier.badMessages.count(hash)) {
LogPrintf("CInstantSendManager::%s -- txid=%s, islock=%s: invalid sig in islock, peer=%d\n", __func__,
islock.txid.ToString(), hash.ToString(), nodeId);
badISLocks.emplace(hash);
continue;
}

Expand All @@ -836,7 +875,7 @@ bool CInstantSendManager::ProcessPendingInstantSendLocks()
}
}

return true;
return badISLocks;
}

void CInstantSendManager::ProcessInstantSendLock(NodeId from, const uint256& hash, const CInstantSendLock& islock)
Expand Down
1 change: 1 addition & 0 deletions src/llmq/quorums_instantsend.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ class CInstantSendManager : public CRecoveredSigsListener
void ProcessMessageInstantSendLock(CNode* pfrom, const CInstantSendLock& islock, CConnman& connman);
bool PreVerifyInstantSendLock(NodeId nodeId, const CInstantSendLock& islock, bool& retBan);
bool ProcessPendingInstantSendLocks();
std::unordered_set<uint256> ProcessPendingInstantSendLocks(int signHeight, const std::unordered_map<uint256, std::pair<NodeId, CInstantSendLock>>& pend, bool ban);
void ProcessInstantSendLock(NodeId from, const uint256& hash, const CInstantSendLock& islock);
void UpdateWalletTransaction(const uint256& txid, const CTransactionRef& tx);

Expand Down
11 changes: 8 additions & 3 deletions src/llmq/quorums_signing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -849,7 +849,7 @@ bool CSigningManager::GetVoteForId(Consensus::LLMQType llmqType, const uint256&
return db.GetVoteForId(llmqType, id, msgHashRet);
}

CQuorumCPtr CSigningManager::SelectQuorumForSigning(Consensus::LLMQType llmqType, int signHeight, const uint256& selectionHash)
std::vector<CQuorumCPtr> CSigningManager::GetActiveQuorumSet(Consensus::LLMQType llmqType, int signHeight)
{
auto& llmqParams = Params().GetConsensus().llmqs.at(llmqType);
size_t poolSize = (size_t)llmqParams.signingActiveQuorumCount;
Expand All @@ -859,12 +859,17 @@ CQuorumCPtr CSigningManager::SelectQuorumForSigning(Consensus::LLMQType llmqType
LOCK(cs_main);
int startBlockHeight = signHeight - SIGN_HEIGHT_OFFSET;
if (startBlockHeight > chainActive.Height()) {
return nullptr;
return {};
}
pindexStart = chainActive[startBlockHeight];
}

auto quorums = quorumManager->ScanQuorums(llmqType, pindexStart, poolSize);
return quorumManager->ScanQuorums(llmqType, pindexStart, poolSize);
}

CQuorumCPtr CSigningManager::SelectQuorumForSigning(Consensus::LLMQType llmqType, int signHeight, const uint256& selectionHash)
{
auto quorums = GetActiveQuorumSet(llmqType, signHeight);
if (quorums.empty()) {
return nullptr;
}
Expand Down
1 change: 1 addition & 0 deletions src/llmq/quorums_signing.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ class CSigningManager
bool HasVotedOnId(Consensus::LLMQType llmqType, const uint256& id);
bool GetVoteForId(Consensus::LLMQType llmqType, const uint256& id, uint256& msgHashRet);

std::vector<CQuorumCPtr> GetActiveQuorumSet(Consensus::LLMQType llmqType, int signHeight);
CQuorumCPtr SelectQuorumForSigning(Consensus::LLMQType llmqType, int signHeight, const uint256& selectionHash);

// Verifies a recovered sig that was signed while the chain tip was at signedAtTip
Expand Down

0 comments on commit 17ba238

Please sign in to comment.