-
Notifications
You must be signed in to change notification settings - Fork 1.2k
perf: improve signing latency by collecting lazy signatures under lock #6911
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -592,8 +592,11 @@ std::shared_ptr<CRecoveredSig> CSigSharesManager::TryRecoverSig(const CQuorum& q | |||||||||||||||||||||||||||||||||||||||||||
| return nullptr; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| std::vector<CBLSSignature> sigSharesForRecovery; | ||||||||||||||||||||||||||||||||||||||||||||
| // Collect lazy signatures (cheap copy) under lock, then materialize outside lock | ||||||||||||||||||||||||||||||||||||||||||||
| std::vector<CBLSLazySignature> lazySignatures; | ||||||||||||||||||||||||||||||||||||||||||||
| std::vector<CBLSId> idsForRecovery; | ||||||||||||||||||||||||||||||||||||||||||||
| bool isSingleNode = false; | ||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💬 Nitpick: isSingleNode should be isSingleMember for consistency The codebase consistently uses 'member' terminology: 💡 Suggested change
Suggested change
source: ['claude'] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||
| LOCK(cs); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -603,7 +606,6 @@ std::shared_ptr<CRecoveredSig> CSigSharesManager::TryRecoverSig(const CQuorum& q | |||||||||||||||||||||||||||||||||||||||||||
| return nullptr; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| std::shared_ptr<CRecoveredSig> singleMemberRecoveredSig; | ||||||||||||||||||||||||||||||||||||||||||||
| if (quorum.params.is_single_member()) { | ||||||||||||||||||||||||||||||||||||||||||||
| if (sigSharesForSignHash->empty()) { | ||||||||||||||||||||||||||||||||||||||||||||
| LogPrint(BCLog::LLMQ_SIGS, /* Continued */ | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -613,29 +615,42 @@ std::shared_ptr<CRecoveredSig> 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<CRecoveredSig>(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<CRecoveredSig>(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<CBLSSignature> sigSharesForRecovery; | ||||||||||||||||||||||||||||||||||||||||||||
| sigSharesForRecovery.reserve(lazySignatures.size()); | ||||||||||||||||||||||||||||||||||||||||||||
| for (const auto& lazySig : lazySignatures) { | ||||||||||||||||||||||||||||||||||||||||||||
| sigSharesForRecovery.emplace_back(lazySig.Get()); // Expensive, but outside lock | ||||||||||||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is copy of bls signature indeed that expensive? Maybe there's a bug in our bls's wrapper which calls "is-valid" or "serialize&deserialize" for each constructor copy? Just copy of 500bytes are not that expensive; I think we should improve performance of our bls-wrapper rather than do this refactoring... Do I miss anything?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not a copy; look into the implementation of .Get()
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See: Lines 488 to 508 in a488c8d
I'd love ways to increase performance here, but it is quite expensive as is. |
||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| // now recover it | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💬 Nitpick: "cheap copy" is imprecise — state what it avoids
CBLSLazyWrapper's copy constructor acquires a mutex on the source and deep-copies a 96-byte array plus potentially a full bls::G2Element (if already materialized). It IS much cheaper than Get() on the hot path (unmaterialized objects skip BLS point decompression), but "cheap" without qualification could mislead a reader into thinking it's trivial. The same comment pattern appears at lines 620 and 633.
💡 Suggested change
source: ['claude']