-
Notifications
You must be signed in to change notification settings - Fork 22
perf(blocksync): verify a commit once, not again as the next block's LastCommit #1427
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
Changes from 3 commits
f30ba09
f735774
409dbbc
438cad5
a158d22
1deb0da
bcae735
e7d505f
81ea61e
3bdfcba
c3c078f
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 |
|---|---|---|
|
|
@@ -8,8 +8,10 @@ import ( | |
| "encoding/hex" | ||
| "errors" | ||
| "fmt" | ||
| "sync/atomic" | ||
| "time" | ||
|
|
||
| "github.com/dashpay/dashd-go/btcjson" | ||
| abciclient "github.com/dashpay/tenderdash/abci/client" | ||
| abci "github.com/dashpay/tenderdash/abci/types" | ||
| "github.com/dashpay/tenderdash/crypto" | ||
|
|
@@ -51,6 +53,11 @@ type Executor interface { | |
|
|
||
| ValidateBlock(ctx context.Context, state State, block *types.Block) error | ||
|
|
||
| // NoteVerifiedCommit records a commit the caller has just verified against | ||
| // state.Validators, so the executor can skip verifying it a second time when | ||
| // it reappears as the next block's LastCommit. | ||
| NoteVerifiedCommit(state State, blockID types.BlockID, commit *types.Commit) | ||
|
|
||
| ValidateBlockWithRoundState( | ||
| ctx context.Context, | ||
| state State, | ||
|
|
@@ -108,6 +115,22 @@ type BlockExecutor struct { | |
| // detect non-deterministic prepare proposal responses | ||
| lastRequestPrepareProposalHash []byte | ||
| lastResponsePrepareProposalHash []byte | ||
|
|
||
| // the commit most recently verified by a caller, so validateBlock can skip | ||
| // re-verifying it when it comes back as the next block's LastCommit | ||
| verifiedCommit atomic.Pointer[verifiedCommit] | ||
|
PastaPastaPasta marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| // verifiedCommit pins every input ValidatorSet.verifyCommit reads, so a match | ||
| // means a repeat verification would be handed identical arguments. | ||
| type verifiedCommit struct { | ||
| chainID string | ||
| height int64 | ||
| blockID types.BlockID | ||
| quorumType btcjson.LLMQType | ||
| quorumHash crypto.QuorumHash | ||
|
PastaPastaPasta marked this conversation as resolved.
Outdated
|
||
| thresholdKey crypto.PubKey | ||
| commit []byte | ||
| } | ||
|
|
||
| // BlockExecWithLogger is an option function to set a logger to BlockExecutor | ||
|
|
@@ -408,7 +431,7 @@ func (blockExec *BlockExecutor) ValidateBlock(ctx context.Context, state State, | |
| return nil | ||
| } | ||
|
|
||
| err := validateBlock(state, block) | ||
| err := validateBlock(state, block, blockExec.lastCommitAlreadyVerified(state, block)) | ||
|
Contributor
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. PR's "consensus is unaffected" claim is false — the memo is read by the consensus path at every blocksync-to-consensus handover. The PR states "Consensus path never calls No signature-verification bypass exists today — every input Recommendation: narrow it structurally rather than just re-documenting it — clear the memo in 🤖 Co-authored by Claudius the Magnificent AI Agent
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. You are right that the read side is reachable from consensus for one height after the handover, and the PR description overstated it. I have rewritten that section rather than adding the clear-before-switch. I pushed back on the structural fix because I do not think it buys anything. The skip fires only on a full match: same chain ID, height, block ID, quorum type, quorum hash, threshold key and a byte-identical marshalled commit, all captured as owned copies at the moment Clearing it would also need a new interface method or a type assertion in 🤖 Posted autonomously by Claude on behalf of pasta. |
||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
@@ -422,6 +445,60 @@ func (blockExec *BlockExecutor) ValidateBlock(ctx context.Context, state State, | |
| return nil | ||
| } | ||
|
|
||
| // NoteVerifiedCommit records a successful VerifyCommit so the identical | ||
| // verification can be skipped when that commit reappears as the next block's | ||
| // LastCommit. Only the most recent one is kept: block sync applies blocks in | ||
| // order, so it is the only one that can match. | ||
| func (blockExec *BlockExecutor) NoteVerifiedCommit(state State, blockID types.BlockID, commit *types.Commit) { | ||
| if commit == nil || state.Validators == nil { | ||
| return | ||
| } | ||
| encoded, err := commit.ToProto().Marshal() | ||
| if err != nil { | ||
| blockExec.verifiedCommit.Store(nil) | ||
|
PastaPastaPasta marked this conversation as resolved.
Outdated
|
||
| return | ||
| } | ||
| blockExec.verifiedCommit.Store(&verifiedCommit{ | ||
| chainID: state.ChainID, | ||
| height: commit.Height, | ||
| blockID: blockID, | ||
| quorumType: state.Validators.QuorumType, | ||
| quorumHash: state.Validators.QuorumHash, | ||
| thresholdKey: state.Validators.ThresholdPublicKey, | ||
| commit: encoded, | ||
| }) | ||
| } | ||
|
|
||
| // lastCommitAlreadyVerified reports whether block.LastCommit was already | ||
| // verified against exactly the inputs validateBlock would use: same chain, | ||
| // height, block ID, quorum and threshold key, and a byte-identical commit. | ||
| // Anything short of a full match falls through to a real verification. | ||
| func (blockExec *BlockExecutor) lastCommitAlreadyVerified(state State, block *types.Block) bool { | ||
| vc := blockExec.verifiedCommit.Load() | ||
| if vc == nil || block.LastCommit == nil || state.LastValidators == nil { | ||
| return false | ||
| } | ||
| if vc.chainID != state.ChainID || vc.height != block.Height-1 { | ||
| return false | ||
| } | ||
| if !vc.blockID.Equals(state.LastBlockID) { | ||
| return false | ||
| } | ||
| if vc.quorumType != state.LastValidators.QuorumType || | ||
| !vc.quorumHash.Equal(state.LastValidators.QuorumHash) { | ||
| return false | ||
| } | ||
| if vc.thresholdKey == nil || state.LastValidators.ThresholdPublicKey == nil || | ||
| !vc.thresholdKey.Equals(state.LastValidators.ThresholdPublicKey) { | ||
| return false | ||
| } | ||
| got, err := block.LastCommit.ToProto().Marshal() | ||
| if err != nil { | ||
| return false | ||
| } | ||
| return bytes.Equal(vc.commit, got) | ||
|
PastaPastaPasta marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| func (blockExec *BlockExecutor) ValidateBlockWithRoundState( | ||
| ctx context.Context, | ||
| state State, | ||
|
|
@@ -450,7 +527,14 @@ func (blockExec *BlockExecutor) ValidateBlockWithRoundState( | |
| ) | ||
| } | ||
|
|
||
| if block.Height > state.InitialHeight { | ||
| // ValidateBlock above normally verifies block.LastCommit, but it short-circuits | ||
| // on its per-height cache, which is keyed on the block hash alone and so says | ||
| // nothing about the state the earlier validation ran against. That is why this | ||
| // second verification exists. Skip it only when the memo proves this exact | ||
| // commit was already verified against these exact inputs — same chain, height, | ||
| // block ID, quorum, threshold key and byte-identical commit — which is a | ||
| // property of the data rather than of the cache. | ||
| if block.Height > state.InitialHeight && !blockExec.lastCommitAlreadyVerified(state, block) { | ||
| if err := state.LastValidators.VerifyCommit( | ||
| state.ChainID, state.LastBlockID, block.Height-1, block.LastCommit); err != nil { | ||
| return fmt.Errorf("error validating block: %w", err) | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.