Skip to content

Commit 17ba238

Browse files
committed
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 8c49d9b commit 17ba238

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
@@ -727,8 +727,6 @@ bool CInstantSendManager::PreVerifyInstantSendLock(NodeId nodeId, const llmq::CI
727727

728728
bool CInstantSendManager::ProcessPendingInstantSendLocks()
729729
{
730-
auto llmqType = Params().GetConsensus().llmqForInstantSend;
731-
732730
decltype(pendingInstantSendLocks) pend;
733731

734732
{
@@ -750,6 +748,44 @@ bool CInstantSendManager::ProcessPendingInstantSendLocks()
750748
tipHeight = chainActive.Height();
751749
}
752750

751+
auto llmqType = Params().GetConsensus().llmqForInstantSend;
752+
753+
// Every time a new quorum enters the active set, an older one is removed. This means that between two blocks, the
754+
// active set can be different, leading to different selection of the signing quorum. When we detect such rotation
755+
// of the active set, we must re-check invalid sigs against the previous active set and only ban nodes when this also
756+
// fails.
757+
auto quorums1 = quorumSigningManager->GetActiveQuorumSet(llmqType, tipHeight);
758+
auto quorums2 = quorumSigningManager->GetActiveQuorumSet(llmqType, tipHeight - 1);
759+
bool quorumsRotated = quorums1 != quorums2;
760+
761+
if (quorumsRotated) {
762+
// first check against the current active set and don't ban
763+
auto badISLocks = ProcessPendingInstantSendLocks(tipHeight, pend, false);
764+
if (!badISLocks.empty()) {
765+
LogPrintf("CInstantSendManager::%s -- detected LLMQ active set rotation, redoing verification on old active set\n", __func__);
766+
767+
// filter out valid IS locks from "pend"
768+
for (auto it = pend.begin(); it != pend.end(); ) {
769+
if (!badISLocks.count(it->first)) {
770+
it = pend.erase(it);
771+
} else {
772+
++it;
773+
}
774+
}
775+
// now check against the previous active set and perform banning if this fails
776+
ProcessPendingInstantSendLocks(tipHeight - 1, pend, true);
777+
}
778+
} else {
779+
ProcessPendingInstantSendLocks(tipHeight, pend, true);
780+
}
781+
782+
return true;
783+
}
784+
785+
std::unordered_set<uint256> CInstantSendManager::ProcessPendingInstantSendLocks(int signHeight, const std::unordered_map<uint256, std::pair<NodeId, CInstantSendLock>>& pend, bool ban)
786+
{
787+
auto llmqType = Params().GetConsensus().llmqForInstantSend;
788+
753789
CBLSBatchVerifier<NodeId, uint256> batchVerifier(false, true, 8);
754790
std::unordered_map<uint256, std::pair<CQuorumCPtr, CRecoveredSig>> recSigs;
755791

@@ -774,10 +810,10 @@ bool CInstantSendManager::ProcessPendingInstantSendLocks()
774810
continue;
775811
}
776812

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

801837
batchVerifier.Verify();
802838

803-
if (!batchVerifier.badSources.empty()) {
839+
std::unordered_set<uint256> badISLocks;
840+
841+
if (ban && !batchVerifier.badSources.empty()) {
804842
LOCK(cs_main);
805843
for (auto& nodeId : batchVerifier.badSources) {
806844
// Let's not be too harsh, as the peer might simply be unlucky and might have sent us an old lock which
@@ -816,6 +854,7 @@ bool CInstantSendManager::ProcessPendingInstantSendLocks()
816854
if (batchVerifier.badMessages.count(hash)) {
817855
LogPrintf("CInstantSendManager::%s -- txid=%s, islock=%s: invalid sig in islock, peer=%d\n", __func__,
818856
islock.txid.ToString(), hash.ToString(), nodeId);
857+
badISLocks.emplace(hash);
819858
continue;
820859
}
821860

@@ -836,7 +875,7 @@ bool CInstantSendManager::ProcessPendingInstantSendLocks()
836875
}
837876
}
838877

839-
return true;
878+
return badISLocks;
840879
}
841880

842881
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 uint256& txid, const CTransactionRef& tx);
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)