-
Notifications
You must be signed in to change notification settings - Fork 69
consensus: use signer pubkey to check for unique signatures and optimize performance, close XFN-03 #1625
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: dev-upgrade
Are you sure you want to change the base?
Conversation
|
ref #1643 |
94691c6 to
e87b9e3
Compare
43bea40 to
e87b9e3
Compare
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the ✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
004c3dc to
31d96a5
Compare
|
@wanwiset25 Why close this PR ? |
|
@gzliudan good suggestions, please check new optmize |
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.
Pull request overview
This PR addresses audit ticket XFN-03 by refactoring signature verification to use signer public keys instead of signature bytes for uniqueness checking, improving both correctness and performance.
- Introduces a new
countValidSignaturesfunction that performs concurrent signature verification and uniqueness checking based on signer pubkeys - Refactors
verifyQCandverifyTCfunctions to use the new unified verification approach - Removes the old
UniqueSignaturesfunction that checked uniqueness based on signature bytes
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| consensus/XDPoS/engines/engine_v2/utils.go | Adds new countValidSignatures function with concurrent signature verification and pubkey-based uniqueness checking; removes old UniqueSignatures function; enhances logging in verifyMsgSignature |
| consensus/XDPoS/engines/engine_v2/engine.go | Refactors verifyQC to use countValidSignatures and removes inline verification logic |
| consensus/XDPoS/engines/engine_v2/timeout.go | Refactors verifyTC to use countValidSignatures and removes inline verification logic |
| consensus/tests/engine_v2_tests/verify_header_test.go | Updates test assertion to check for new "duplicate signing found" error message |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| eg.Go(func() error { | ||
| select { | ||
| case <-ctx.Done(): | ||
| return ctx.Err() | ||
| default: | ||
| verified, signerAddress, err := x.verifyMsgSignature(messageHash, signature, candidates) | ||
| if err != nil { | ||
| log.Error("[verifySignatures] Error while verfying QC message signatures", "error", err) | ||
| return err | ||
| } | ||
| if !verified { | ||
| return fmt.Errorf("signature verification failed, signer is not part of masternode list. Signature: %v, SignedMessage: %v, SignerAddress: %v, Masternodes: %v", signature, messageHash.Hex(), signerAddress, candidates) | ||
| } | ||
| signatureList[i] = signature | ||
| pubkeys[i] = signerAddress | ||
|
|
Copilot
AI
Dec 18, 2025
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.
The loop variables i and signature are being captured by the goroutine closure, which creates a race condition. All goroutines will share the same variables, and by the time they execute, these variables will likely have been modified by subsequent loop iterations. This means multiple goroutines may access the same index or signature, leading to incorrect results or data races when writing to signatureList[i] and pubkeys[i].
The loop variables must be captured properly by passing them as parameters to the goroutine or by creating local copies within the loop body before starting the goroutine.
| eg.Go(func() error { | |
| select { | |
| case <-ctx.Done(): | |
| return ctx.Err() | |
| default: | |
| verified, signerAddress, err := x.verifyMsgSignature(messageHash, signature, candidates) | |
| if err != nil { | |
| log.Error("[verifySignatures] Error while verfying QC message signatures", "error", err) | |
| return err | |
| } | |
| if !verified { | |
| return fmt.Errorf("signature verification failed, signer is not part of masternode list. Signature: %v, SignedMessage: %v, SignerAddress: %v, Masternodes: %v", signature, messageHash.Hex(), signerAddress, candidates) | |
| } | |
| signatureList[i] = signature | |
| pubkeys[i] = signerAddress | |
| idx := i | |
| sig := signature | |
| eg.Go(func() error { | |
| select { | |
| case <-ctx.Done(): | |
| return ctx.Err() | |
| default: | |
| verified, signerAddress, err := x.verifyMsgSignature(messageHash, sig, candidates) | |
| if err != nil { | |
| log.Error("[verifySignatures] Error while verfying QC message signatures", "error", err) | |
| return err | |
| } | |
| if !verified { | |
| return fmt.Errorf("signature verification failed, signer is not part of masternode list. Signature: %v, SignedMessage: %v, SignerAddress: %v, Masternodes: %v", sig, messageHash.Hex(), signerAddress, candidates) | |
| } | |
| signatureList[idx] = sig | |
| pubkeys[idx] = signerAddress |
| default: | ||
| verified, signerAddress, err := x.verifyMsgSignature(messageHash, signature, candidates) | ||
| if err != nil { | ||
| log.Error("[verifySignatures] Error while verfying QC message signatures", "error", err) |
Copilot
AI
Dec 18, 2025
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.
Spelling error: "verfying" should be "verifying".
| log.Error("[verifySignatures] Error while verfying QC message signatures", "error", err) | |
| log.Error("[verifySignatures] Error while verifying QC message signatures", "error", err) |
| // TODO: snap.NextEpochCandidates should be replaced with epochInfo.Masternodes in another PR | ||
| numValidSignatures, err := x.countValidSignatures(signedTimeoutObj, timeoutCert.Signatures, snap.NextEpochCandidates) | ||
| if err != nil { | ||
| log.Error("[verifyTC] Error while verfying TC message signatures", "tcRound", timeoutCert.Round, "tcGapNumber", timeoutCert.GapNumber, "tcSignLen", len(timeoutCert.Signatures), "Error", err) |
Copilot
AI
Dec 18, 2025
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.
Spelling error: "verfying" should be "verifying".
| log.Error("[verifyTC] Error while verfying TC message signatures", "tcRound", timeoutCert.Round, "tcGapNumber", timeoutCert.GapNumber, "tcSignLen", len(timeoutCert.Signatures), "Error", err) | |
| log.Error("[verifyTC] Error while verifying TC message signatures", "tcRound", timeoutCert.Round, "tcGapNumber", timeoutCert.GapNumber, "tcSignLen", len(timeoutCert.Signatures), "Error", err) |
| elapsed := time.Since(start) | ||
| log.Debug("[verifyQC] time verify message signatures of qc", "elapsed", elapsed) | ||
| if err != nil { | ||
| log.Error("[verifyQC] Error while verfying QC message signatures", "Error", err) |
Copilot
AI
Dec 18, 2025
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.
Spelling error: "verfying" should be "verifying".
| log.Error("[verifyQC] Error while verfying QC message signatures", "Error", err) | |
| log.Error("[verifyQC] Error while verifying QC message signatures", "Error", err) |
| if float64(numValidSignatures) < float64(epochInfo.MasternodesLen)*certThreshold { | ||
| log.Warn("[verifyTC] Invalid TC Signature is less or empty", "tcRound", timeoutCert.Round, "tcGapNumber", timeoutCert.GapNumber, "tcSignLen", len(timeoutCert.Signatures), "certThreshold", float64(epochInfo.MasternodesLen)*certThreshold) |
Copilot
AI
Dec 18, 2025
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.
The signature validation at line 174 uses snap.NextEpochCandidates to verify signatures, but the threshold check at line 185 uses epochInfo.MasternodesLen. This inconsistency could lead to incorrect validation if these two lists have different lengths. The TODO comment on line 173 acknowledges this issue but it's concerning that the threshold check happens against a different set of masternodes than what was used for signature verification.
| if float64(numValidSignatures) < float64(epochInfo.MasternodesLen)*certThreshold { | |
| log.Warn("[verifyTC] Invalid TC Signature is less or empty", "tcRound", timeoutCert.Round, "tcGapNumber", timeoutCert.GapNumber, "tcSignLen", len(timeoutCert.Signatures), "certThreshold", float64(epochInfo.MasternodesLen)*certThreshold) | |
| if float64(numValidSignatures) < float64(len(snap.NextEpochCandidates))*certThreshold { | |
| log.Warn("[verifyTC] Invalid TC Signature is less or empty", "tcRound", timeoutCert.Round, "tcGapNumber", timeoutCert.GapNumber, "tcSignLen", len(timeoutCert.Signatures), "certThreshold", float64(len(snap.NextEpochCandidates))*certThreshold) |
Proposed changes
use signer pubkey to check for unique signatures
ref audit ticket: XFN-03
Types of changes
What types of changes does your code introduce to XDC network?
Put an
✅in the boxes that applyImpacted Components
Which part of the codebase this PR will touch base on,
Put an
✅in the boxes that applyChecklist
Put an
✅in the boxes once you have confirmed below actions (or provide reasons on not doing so) that