-
Notifications
You must be signed in to change notification settings - Fork 434
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
VerifyCommitLight
and VerifyCommitLightTrusting
_never_ check all signatures
#1750
Changes from all commits
d0b52c4
4180575
0ca684b
56f6371
0a6272b
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 |
---|---|---|
|
@@ -24,7 +24,7 @@ import ( | |
// 1 in 4 evidence is light client evidence, the rest is duplicate vote evidence | ||
const lightClientEvidenceRatio = 4 | ||
|
||
// InjectEvidence takes a running testnet and generates an amount of valid | ||
// InjectEvidence takes a running testnet and generates an amount of valid/invalid | ||
// evidence and broadcasts it to a random node through the rpc endpoint `/broadcast_evidence`. | ||
// Evidence is random and can be a mixture of LightClientAttackEvidence and | ||
// DuplicateVoteEvidence. | ||
|
@@ -87,10 +87,12 @@ func InjectEvidence(ctx context.Context, r *rand.Rand, testnet *e2e.Testnet, amo | |
} | ||
|
||
var ev types.Evidence | ||
for i := 1; i <= amount; i++ { | ||
for i := 0; i < amount; i++ { | ||
validEv := true | ||
if i%lightClientEvidenceRatio == 0 { | ||
validEv = i%(lightClientEvidenceRatio*2) != 0 // Alternate valid and invalid evidence | ||
ev, err = generateLightClientAttackEvidence( | ||
ctx, privVals, evidenceHeight, valSet, testnet.Name, blockRes.Block.Time, | ||
ctx, privVals, evidenceHeight, valSet, testnet.Name, blockRes.Block.Time, validEv, | ||
) | ||
} else { | ||
var dve *types.DuplicateVoteEvidence | ||
|
@@ -110,7 +112,15 @@ func InjectEvidence(ctx context.Context, r *rand.Rand, testnet *e2e.Testnet, amo | |
} | ||
|
||
_, err := client.BroadcastEvidence(ctx, ev) | ||
if err != nil { | ||
if !validEv { | ||
// The tests will count committed evidences later on, | ||
// and only valid evidences will make it | ||
amount++ | ||
} | ||
if validEv != (err == nil) { | ||
if err == nil { | ||
return errors.New("submitting invalid evidence didn't return an error") | ||
} | ||
return err | ||
} | ||
} | ||
|
@@ -155,6 +165,7 @@ func generateLightClientAttackEvidence( | |
vals *types.ValidatorSet, | ||
chainID string, | ||
evTime time.Time, | ||
validEvidence bool, | ||
) (*types.LightClientAttackEvidence, error) { | ||
// forge a random header | ||
forgedHeight := height + 2 | ||
|
@@ -164,7 +175,7 @@ func generateLightClientAttackEvidence( | |
|
||
// add a new bogus validator and remove an existing one to | ||
// vary the validator set slightly | ||
pv, conflictingVals, err := mutateValidatorSet(ctx, privVals, vals) | ||
pv, conflictingVals, err := mutateValidatorSet(ctx, privVals, vals, !validEvidence) | ||
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. If the
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. The thing is, what I'd need in the else is inside |
||
if err != nil { | ||
return nil, err | ||
} | ||
|
@@ -179,6 +190,11 @@ func generateLightClientAttackEvidence( | |
return nil, err | ||
} | ||
|
||
// malleate the last signature of the commit by adding one to its first byte | ||
if !validEvidence { | ||
sergio-mena marked this conversation as resolved.
Show resolved
Hide resolved
|
||
commit.Signatures[len(commit.Signatures)-1].Signature[0]++ | ||
} | ||
|
||
ev := &types.LightClientAttackEvidence{ | ||
ConflictingBlock: &types.LightBlock{ | ||
SignedHeader: &types.SignedHeader{ | ||
|
@@ -292,18 +308,26 @@ func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) types.Bloc | |
} | ||
} | ||
|
||
func mutateValidatorSet(ctx context.Context, privVals []types.MockPV, vals *types.ValidatorSet, | ||
func mutateValidatorSet( | ||
ctx context.Context, | ||
privVals []types.MockPV, | ||
vals *types.ValidatorSet, | ||
nop bool, | ||
) ([]types.PrivValidator, *types.ValidatorSet, error) { | ||
newVal, newPrivVal, err := test.Validator(ctx, 10) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
|
||
var newVals *types.ValidatorSet | ||
if vals.Size() > 2 { | ||
newVals = types.NewValidatorSet(append(vals.Copy().Validators[:vals.Size()-1], newVal)) | ||
if nop { | ||
newVals = types.NewValidatorSet(vals.Copy().Validators) | ||
} else { | ||
newVals = types.NewValidatorSet(append(vals.Copy().Validators, newVal)) | ||
if vals.Size() > 2 { | ||
newVals = types.NewValidatorSet(append(vals.Copy().Validators[:vals.Size()-1], newVal)) | ||
} else { | ||
newVals = types.NewValidatorSet(append(vals.Copy().Validators, newVal)) | ||
} | ||
} | ||
|
||
// we need to sort the priv validators with the same index as the validator set | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -55,10 +55,40 @@ func VerifyCommit(chainID string, vals *ValidatorSet, blockID BlockID, | |
|
||
// VerifyCommitLight verifies +2/3 of the set had signed the given commit. | ||
// | ||
// This method is primarily used by the light client and does not check all the | ||
// This method is primarily used by the light client and does NOT check all the | ||
// signatures. | ||
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. Remove "does not check all the signatures." because this now depends on |
||
func VerifyCommitLight(chainID string, vals *ValidatorSet, blockID BlockID, | ||
height int64, commit *Commit) error { | ||
func VerifyCommitLight( | ||
chainID string, | ||
vals *ValidatorSet, | ||
blockID BlockID, | ||
height int64, | ||
commit *Commit, | ||
) error { | ||
return verifyCommitLightInternal(chainID, vals, blockID, height, commit, false) | ||
} | ||
|
||
// VerifyCommitLightAllSignatures verifies +2/3 of the set had signed the given commit. | ||
// | ||
// This method is primarily used by the light client and DOES check all the | ||
// signatures. | ||
func VerifyCommitLightAllSignatures( | ||
chainID string, | ||
vals *ValidatorSet, | ||
blockID BlockID, | ||
height int64, | ||
commit *Commit, | ||
) error { | ||
return verifyCommitLightInternal(chainID, vals, blockID, height, commit, true) | ||
} | ||
|
||
func verifyCommitLightInternal( | ||
chainID string, | ||
vals *ValidatorSet, | ||
blockID BlockID, | ||
height int64, | ||
commit *Commit, | ||
countAllSignatures bool, | ||
sergio-mena marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) error { | ||
// run a basic validation of the arguments | ||
if err := verifyBasicValsAndCommit(vals, commit, height, blockID); err != nil { | ||
return err | ||
|
@@ -76,12 +106,12 @@ func VerifyCommitLight(chainID string, vals *ValidatorSet, blockID BlockID, | |
// attempt to batch verify | ||
if shouldBatchVerify(vals, commit) { | ||
return verifyCommitBatch(chainID, vals, commit, | ||
votingPowerNeeded, ignore, count, false, true) | ||
votingPowerNeeded, ignore, count, countAllSignatures, true) | ||
} | ||
|
||
// if verification failed or is not supported then fallback to single verification | ||
return verifyCommitSingle(chainID, vals, commit, votingPowerNeeded, | ||
ignore, count, false, true) | ||
ignore, count, countAllSignatures, true) | ||
} | ||
|
||
// VerifyCommitLightTrusting verifies that trustLevel of the validator set signed | ||
|
@@ -90,9 +120,41 @@ func VerifyCommitLight(chainID string, vals *ValidatorSet, blockID BlockID, | |
// NOTE the given validators do not necessarily correspond to the validator set | ||
// for this commit, but there may be some intersection. | ||
// | ||
// This method is primarily used by the light client and does not check all the | ||
// This method is primarily used by the light client and does NOT check all the | ||
// signatures. | ||
func VerifyCommitLightTrusting( | ||
chainID string, | ||
vals *ValidatorSet, | ||
commit *Commit, | ||
trustLevel cmtmath.Fraction, | ||
) error { | ||
return verifyCommitLightTrustingInternal(chainID, vals, commit, trustLevel, false) | ||
} | ||
|
||
// VerifyCommitLightTrustingAllSignatures verifies that trustLevel of the validator | ||
// set signed this commit. | ||
// | ||
// NOTE the given validators do not necessarily correspond to the validator set | ||
// for this commit, but there may be some intersection. | ||
// | ||
// This method is primarily used by the light client and DOES check all the | ||
// signatures. | ||
func VerifyCommitLightTrusting(chainID string, vals *ValidatorSet, commit *Commit, trustLevel cmtmath.Fraction) error { | ||
func VerifyCommitLightTrustingAllSignatures( | ||
chainID string, | ||
vals *ValidatorSet, | ||
commit *Commit, | ||
trustLevel cmtmath.Fraction, | ||
) error { | ||
return verifyCommitLightTrustingInternal(chainID, vals, commit, trustLevel, true) | ||
} | ||
|
||
func verifyCommitLightTrustingInternal( | ||
chainID string, | ||
vals *ValidatorSet, | ||
commit *Commit, | ||
trustLevel cmtmath.Fraction, | ||
countAllSignatures bool, | ||
sergio-mena marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) error { | ||
// sanity checks | ||
if vals == nil { | ||
return errors.New("nil validator set") | ||
|
@@ -122,12 +184,12 @@ func VerifyCommitLightTrusting(chainID string, vals *ValidatorSet, commit *Commi | |
// up by address rather than index. | ||
if shouldBatchVerify(vals, commit) { | ||
return verifyCommitBatch(chainID, vals, commit, | ||
votingPowerNeeded, ignore, count, false, false) | ||
votingPowerNeeded, ignore, count, countAllSignatures, false) | ||
} | ||
|
||
// attempt with single verification | ||
return verifyCommitSingle(chainID, vals, commit, votingPowerNeeded, | ||
ignore, count, false, false) | ||
ignore, count, countAllSignatures, false) | ||
} | ||
|
||
// ValidateHash returns an error if the hash is not empty, but its | ||
|
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.
(Being pedantic) I actually don't think this is right. This line is using the received second block's commit, whose voteset isn't finalized.
Agreed that every other usage is correct (and important to fix)!
So it should suffice for correctness here that 2/3rds of signatures are valid.
However for reasons noted in #1830 , I think the new change is better, and we refactor the logic to have the next two calls not verify these signatures.
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.
@ValarDragon Not pedantic at all. Spot on, I'd say.
The reason why we are using
VerifyCommitLightAllSignatures
here is that we decided to leave the previous behavior (i.e., skipping signatures) only in the light client logic, for the rest we took a conservative approach (bear in mind that the raison d'être of this PR was that skipping signatures was problematic when dealing with evidence verification).However, after careful consideration of your proposal, we realize that the usage of
VerifyCommitLight*
in blocksync can be seen as a light client-ish verification as well, in that it does not produce or verify any evidence of misbehavior. So we'll open a PR to change this shortly. Bear with us.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.
#1858