From 9b779995499fd6b522cdf312b7b3101ba9d385c0 Mon Sep 17 00:00:00 2001 From: pasta Date: Sat, 18 Oct 2025 11:24:09 -0500 Subject: [PATCH] perf: improve signing latency by collecting lazy signatures under lock --- src/llmq/signing_shares.cpp | 53 ++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/src/llmq/signing_shares.cpp b/src/llmq/signing_shares.cpp index f337d63ff19e..3e00c2a826fe 100644 --- a/src/llmq/signing_shares.cpp +++ b/src/llmq/signing_shares.cpp @@ -592,8 +592,11 @@ std::shared_ptr CSigSharesManager::TryRecoverSig(const CQuorum& q return nullptr; } - std::vector sigSharesForRecovery; + // Collect lazy signatures (cheap copy) under lock, then materialize outside lock + std::vector lazySignatures; std::vector idsForRecovery; + bool isSingleNode = false; + { LOCK(cs); @@ -603,7 +606,6 @@ std::shared_ptr CSigSharesManager::TryRecoverSig(const CQuorum& q return nullptr; } - std::shared_ptr singleMemberRecoveredSig; if (quorum.params.is_single_member()) { if (sigSharesForSignHash->empty()) { LogPrint(BCLog::LLMQ_SIGS, /* Continued */ @@ -613,29 +615,42 @@ std::shared_ptr CSigSharesManager::TryRecoverSig(const CQuorum& q return nullptr; } const auto& sigShare = sigSharesForSignHash->begin()->second; - CBLSSignature recoveredSig = sigShare.sigShare.Get(); + // Copy the lazy signature (cheap), materialize later outside lock + lazySignatures.emplace_back(sigShare.sigShare); + isSingleNode = true; LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- recover single-node signature. id=%s, msgHash=%s\n", __func__, id.ToString(), msgHash.ToString()); + } else { + // Collect lazy signatures and IDs under lock (cheap operations) + lazySignatures.reserve((size_t) quorum.params.threshold); + idsForRecovery.reserve((size_t) quorum.params.threshold); + for (auto it = sigSharesForSignHash->begin(); + it != sigSharesForSignHash->end() && lazySignatures.size() < size_t(quorum.params.threshold); + ++it) { + const auto& sigShare = it->second; + lazySignatures.emplace_back(sigShare.sigShare); // Cheap copy of lazy wrapper + idsForRecovery.emplace_back(quorum.members[sigShare.getQuorumMember()]->proTxHash); + } - singleMemberRecoveredSig = std::make_shared(quorum.params.type, quorum.qc->quorumHash, id, msgHash, - recoveredSig); + // check if we can recover the final signature + if (lazySignatures.size() < size_t(quorum.params.threshold)) { + return nullptr; + } } + } // Release lock before expensive materialization - sigSharesForRecovery.reserve((size_t) quorum.params.threshold); - idsForRecovery.reserve((size_t) quorum.params.threshold); - for (auto it = sigSharesForSignHash->begin(); it != sigSharesForSignHash->end() && sigSharesForRecovery.size() < size_t(quorum.params.threshold); ++it) { - const auto& sigShare = it->second; - sigSharesForRecovery.emplace_back(sigShare.sigShare.Get()); - idsForRecovery.emplace_back(quorum.members[sigShare.getQuorumMember()]->proTxHash); - } + // Materialize signatures outside the critical section (expensive BLS operations) + if (quorum.params.is_single_member()) { + CBLSSignature recoveredSig = lazySignatures[0].Get(); + return std::make_shared(quorum.params.type, quorum.qc->quorumHash, id, msgHash, + recoveredSig); + } - // check if we can recover the final signature - if (sigSharesForRecovery.size() < size_t(quorum.params.threshold)) { - return nullptr; - } - if (quorum.params.is_single_member()) { - return singleMemberRecoveredSig; // end of single-quorum processing - } + // Multi-node case: materialize all signatures + std::vector sigSharesForRecovery; + sigSharesForRecovery.reserve(lazySignatures.size()); + for (const auto& lazySig : lazySignatures) { + sigSharesForRecovery.emplace_back(lazySig.Get()); // Expensive, but outside lock } // now recover it