Skip to content

Commit dd78738

Browse files
authored
Re-verify invalid IS sigs when the active quorum set rotated (#3052)
* Split ProcessPendingInstantSendLocks into two methods * Split SelectQuorumForSigning into SelectQuorumForSigning and GetActiveQuorumSet * Implement retrying of IS lock verification when the LLMQ active set rotates
1 parent 13e0235 commit dd78738

File tree

4 files changed

+55
-9
lines changed

4 files changed

+55
-9
lines changed

src/llmq/quorums_instantsend.cpp

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -708,8 +708,6 @@ bool CInstantSendManager::PreVerifyInstantSendLock(NodeId nodeId, const llmq::CI
708708

709709
bool CInstantSendManager::ProcessPendingInstantSendLocks()
710710
{
711-
auto llmqType = Params().GetConsensus().llmqForInstantSend;
712-
713711
decltype(pendingInstantSendLocks) pend;
714712

715713
{
@@ -731,6 +729,44 @@ bool CInstantSendManager::ProcessPendingInstantSendLocks()
731729
tipHeight = chainActive.Height();
732730
}
733731

732+
auto llmqType = Params().GetConsensus().llmqForInstantSend;
733+
734+
// Every time a new quorum enters the active set, an older one is removed. This means that between two blocks, the
735+
// active set can be different, leading to different selection of the signing quorum. When we detect such rotation
736+
// of the active set, we must re-check invalid sigs against the previous active set and only ban nodes when this also
737+
// fails.
738+
auto quorums1 = quorumSigningManager->GetActiveQuorumSet(llmqType, tipHeight);
739+
auto quorums2 = quorumSigningManager->GetActiveQuorumSet(llmqType, tipHeight - 1);
740+
bool quorumsRotated = quorums1 != quorums2;
741+
742+
if (quorumsRotated) {
743+
// first check against the current active set and don't ban
744+
auto badISLocks = ProcessPendingInstantSendLocks(tipHeight, pend, false);
745+
if (!badISLocks.empty()) {
746+
LogPrintf("CInstantSendManager::%s -- detected LLMQ active set rotation, redoing verification on old active set\n", __func__);
747+
748+
// filter out valid IS locks from "pend"
749+
for (auto it = pend.begin(); it != pend.end(); ) {
750+
if (!badISLocks.count(it->first)) {
751+
it = pend.erase(it);
752+
} else {
753+
++it;
754+
}
755+
}
756+
// now check against the previous active set and perform banning if this fails
757+
ProcessPendingInstantSendLocks(tipHeight - 1, pend, true);
758+
}
759+
} else {
760+
ProcessPendingInstantSendLocks(tipHeight, pend, true);
761+
}
762+
763+
return true;
764+
}
765+
766+
std::unordered_set<uint256> CInstantSendManager::ProcessPendingInstantSendLocks(int signHeight, const std::unordered_map<uint256, std::pair<NodeId, CInstantSendLock>>& pend, bool ban)
767+
{
768+
auto llmqType = Params().GetConsensus().llmqForInstantSend;
769+
734770
CBLSBatchVerifier<NodeId, uint256> batchVerifier(false, true, 8);
735771
std::unordered_map<uint256, std::pair<CQuorumCPtr, CRecoveredSig>> recSigs;
736772

@@ -755,10 +791,10 @@ bool CInstantSendManager::ProcessPendingInstantSendLocks()
755791
continue;
756792
}
757793

758-
auto quorum = quorumSigningManager->SelectQuorumForSigning(llmqType, tipHeight, id);
794+
auto quorum = quorumSigningManager->SelectQuorumForSigning(llmqType, signHeight, id);
759795
if (!quorum) {
760796
// should not happen, but if one fails to select, all others will also fail to select
761-
return false;
797+
return {};
762798
}
763799
uint256 signHash = CLLMQUtils::BuildSignHash(llmqType, quorum->qc.quorumHash, id, islock.txid);
764800
batchVerifier.PushMessage(nodeId, hash, signHash, islock.sig.Get(), quorum->qc.quorumPublicKey);
@@ -781,7 +817,9 @@ bool CInstantSendManager::ProcessPendingInstantSendLocks()
781817

782818
batchVerifier.Verify();
783819

784-
if (!batchVerifier.badSources.empty()) {
820+
std::unordered_set<uint256> badISLocks;
821+
822+
if (ban && !batchVerifier.badSources.empty()) {
785823
LOCK(cs_main);
786824
for (auto& nodeId : batchVerifier.badSources) {
787825
// Let's not be too harsh, as the peer might simply be unlucky and might have sent us an old lock which
@@ -797,6 +835,7 @@ bool CInstantSendManager::ProcessPendingInstantSendLocks()
797835
if (batchVerifier.badMessages.count(hash)) {
798836
LogPrintf("CInstantSendManager::%s -- txid=%s, islock=%s: invalid sig in islock, peer=%d\n", __func__,
799837
islock.txid.ToString(), hash.ToString(), nodeId);
838+
badISLocks.emplace(hash);
800839
continue;
801840
}
802841

@@ -817,7 +856,7 @@ bool CInstantSendManager::ProcessPendingInstantSendLocks()
817856
}
818857
}
819858

820-
return true;
859+
return badISLocks;
821860
}
822861

823862
void CInstantSendManager::ProcessInstantSendLock(NodeId from, const uint256& hash, const CInstantSendLock& islock)

src/llmq/quorums_instantsend.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ class CInstantSendManager : public CRecoveredSigsListener
137137
void ProcessMessageInstantSendLock(CNode* pfrom, const CInstantSendLock& islock, CConnman& connman);
138138
bool PreVerifyInstantSendLock(NodeId nodeId, const CInstantSendLock& islock, bool& retBan);
139139
bool ProcessPendingInstantSendLocks();
140+
std::unordered_set<uint256> ProcessPendingInstantSendLocks(int signHeight, const std::unordered_map<uint256, std::pair<NodeId, CInstantSendLock>>& pend, bool ban);
140141
void ProcessInstantSendLock(NodeId from, const uint256& hash, const CInstantSendLock& islock);
141142
void UpdateWalletTransaction(const CTransactionRef& tx, const CInstantSendLock& islock);
142143

src/llmq/quorums_signing.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -849,7 +849,7 @@ bool CSigningManager::GetVoteForId(Consensus::LLMQType llmqType, const uint256&
849849
return db.GetVoteForId(llmqType, id, msgHashRet);
850850
}
851851

852-
CQuorumCPtr CSigningManager::SelectQuorumForSigning(Consensus::LLMQType llmqType, int signHeight, const uint256& selectionHash)
852+
std::vector<CQuorumCPtr> CSigningManager::GetActiveQuorumSet(Consensus::LLMQType llmqType, int signHeight)
853853
{
854854
auto& llmqParams = Params().GetConsensus().llmqs.at(llmqType);
855855
size_t poolSize = (size_t)llmqParams.signingActiveQuorumCount;
@@ -859,12 +859,17 @@ CQuorumCPtr CSigningManager::SelectQuorumForSigning(Consensus::LLMQType llmqType
859859
LOCK(cs_main);
860860
int startBlockHeight = signHeight - SIGN_HEIGHT_OFFSET;
861861
if (startBlockHeight > chainActive.Height()) {
862-
return nullptr;
862+
return {};
863863
}
864864
pindexStart = chainActive[startBlockHeight];
865865
}
866866

867-
auto quorums = quorumManager->ScanQuorums(llmqType, pindexStart, poolSize);
867+
return quorumManager->ScanQuorums(llmqType, pindexStart, poolSize);
868+
}
869+
870+
CQuorumCPtr CSigningManager::SelectQuorumForSigning(Consensus::LLMQType llmqType, int signHeight, const uint256& selectionHash)
871+
{
872+
auto quorums = GetActiveQuorumSet(llmqType, signHeight);
868873
if (quorums.empty()) {
869874
return nullptr;
870875
}

src/llmq/quorums_signing.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ class CSigningManager
177177
bool HasVotedOnId(Consensus::LLMQType llmqType, const uint256& id);
178178
bool GetVoteForId(Consensus::LLMQType llmqType, const uint256& id, uint256& msgHashRet);
179179

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

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

0 commit comments

Comments
 (0)