@@ -736,10 +736,41 @@ bool CInstantSendManager::ProcessPendingInstantSendLocks()
736736 tipHeight = chainActive.Height ();
737737 }
738738
739- return ProcessPendingInstantSendLocks (tipHeight, pend);
739+ auto llmqType = Params ().GetConsensus ().llmqForInstantSend ;
740+
741+ // Every time a new quorum enters the active set, an older one is removed. This means that between two blocks, the
742+ // active set can be different, leading to different selection of the signing quorum. When we detect such rotation
743+ // of the active set, we must re-check invalid sigs against the previous active set and only ban nodes when this also
744+ // fails.
745+ auto quorums1 = quorumSigningManager->GetActiveQuorumSet (llmqType, tipHeight);
746+ auto quorums2 = quorumSigningManager->GetActiveQuorumSet (llmqType, tipHeight - 1 );
747+ bool quorumsRotated = quorums1 != quorums2;
748+
749+ if (quorumsRotated) {
750+ // first check against the current active set and don't ban
751+ auto badISLocks = ProcessPendingInstantSendLocks (tipHeight, pend, false );
752+ if (!badISLocks.empty ()) {
753+ LogPrintf (" CInstantSendManager::%s -- detected LLMQ active set rotation, redoing verification on old active set\n " , __func__);
754+
755+ // filter out valid IS locks from "pend"
756+ for (auto it = pend.begin (); it != pend.end (); ) {
757+ if (!badISLocks.count (it->first )) {
758+ it = pend.erase (it);
759+ } else {
760+ ++it;
761+ }
762+ }
763+ // now check against the previous active set and perform banning if this fails
764+ ProcessPendingInstantSendLocks (tipHeight - 1 , pend, true );
765+ }
766+ } else {
767+ ProcessPendingInstantSendLocks (tipHeight, pend, true );
768+ }
769+
770+ return true ;
740771}
741772
742- bool CInstantSendManager::ProcessPendingInstantSendLocks (int signHeight, const std::unordered_map<uint256, std::pair<NodeId, CInstantSendLock>>& pend)
773+ std::unordered_set<uint256> CInstantSendManager::ProcessPendingInstantSendLocks (int signHeight, const std::unordered_map<uint256, std::pair<NodeId, CInstantSendLock>>& pend, bool ban )
743774{
744775 auto llmqType = Params ().GetConsensus ().llmqForInstantSend ;
745776
@@ -770,7 +801,7 @@ bool CInstantSendManager::ProcessPendingInstantSendLocks(int signHeight, const s
770801 auto quorum = quorumSigningManager->SelectQuorumForSigning (llmqType, signHeight, id);
771802 if (!quorum) {
772803 // should not happen, but if one fails to select, all others will also fail to select
773- return false ;
804+ return {} ;
774805 }
775806 uint256 signHash = CLLMQUtils::BuildSignHash (llmqType, quorum->qc .quorumHash , id, islock.txid );
776807 batchVerifier.PushMessage (nodeId, hash, signHash, islock.sig .Get (), quorum->qc .quorumPublicKey );
@@ -793,7 +824,9 @@ bool CInstantSendManager::ProcessPendingInstantSendLocks(int signHeight, const s
793824
794825 batchVerifier.Verify ();
795826
796- if (!batchVerifier.badSources .empty ()) {
827+ std::unordered_set<uint256> badISLocks;
828+
829+ if (ban && !batchVerifier.badSources .empty ()) {
797830 LOCK (cs_main);
798831 for (auto & nodeId : batchVerifier.badSources ) {
799832 // Let's not be too harsh, as the peer might simply be unlucky and might have sent us an old lock which
@@ -809,6 +842,7 @@ bool CInstantSendManager::ProcessPendingInstantSendLocks(int signHeight, const s
809842 if (batchVerifier.badMessages .count (hash)) {
810843 LogPrintf (" CInstantSendManager::%s -- txid=%s, islock=%s: invalid sig in islock, peer=%d\n " , __func__,
811844 islock.txid .ToString (), hash.ToString (), nodeId);
845+ badISLocks.emplace (hash);
812846 continue ;
813847 }
814848
@@ -829,7 +863,7 @@ bool CInstantSendManager::ProcessPendingInstantSendLocks(int signHeight, const s
829863 }
830864 }
831865
832- return true ;
866+ return badISLocks ;
833867}
834868
835869void CInstantSendManager::ProcessInstantSendLock (NodeId from, const uint256& hash, const CInstantSendLock& islock)
0 commit comments