Skip to content
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

feat: VerifyCommitLight and VerifyCommitLightTrusting _never_ check all signatures #11

Merged
merged 3 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions blocksync/reactor.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ FOR_LOOP:
// NOTE: we can probably make this more efficient, but note that calling
// first.Hash() doesn't verify the tx contents, so MakePartSet() is
// currently necessary.
// TODO(sergio): Should we also validate against the extended commit?
err = state.Validators.VerifyCommitLight(
chainID, firstID, first.Height, second.LastCommit)

Expand Down
10 changes: 5 additions & 5 deletions evidence/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,19 +132,19 @@ func (evpool *Pool) Update(state sm.State, ev types.EvidenceList) {

// AddEvidence checks the evidence is valid and adds it to the pool.
func (evpool *Pool) AddEvidence(ev types.Evidence) error {
evpool.logger.Debug("Attempting to add evidence", "ev", ev)
evpool.logger.Info("Attempting to add evidence", "ev", ev)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these really need to be info logs? If not im switching them back

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its fine to add them as Info, evidence is rare


// We have already verified this piece of evidence - no need to do it again
if evpool.isPending(ev) {
evpool.logger.Debug("Evidence already pending, ignoring this one", "ev", ev)
evpool.logger.Info("Evidence already pending, ignoring this one", "ev", ev)
return nil
}

// check that the evidence isn't already committed
if evpool.isCommitted(ev) {
// this can happen if the peer that sent us the evidence is behind so we shouldn't
// punish the peer.
evpool.logger.Debug("Evidence was already committed, ignoring this one", "ev", ev)
evpool.logger.Info("Evidence was already committed, ignoring this one", "ev", ev)
return nil
}

Expand Down Expand Up @@ -513,13 +513,13 @@ func (evpool *Pool) processConsensusBuffer(state sm.State) {

// check if we already have this evidence
if evpool.isPending(dve) {
evpool.logger.Debug("evidence already pending; ignoring", "evidence", dve)
evpool.logger.Info("evidence already pending; ignoring", "evidence", dve)
continue
}

// check that the evidence is not already committed on chain
if evpool.isCommitted(dve) {
evpool.logger.Debug("evidence already committed; ignoring", "evidence", dve)
evpool.logger.Info("evidence already committed; ignoring", "evidence", dve)
continue
}

Expand Down
5 changes: 3 additions & 2 deletions evidence/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ func (evpool *Pool) verify(evidence types.Evidence) error {
// the conflicting header's commit
// - 2/3+ of the conflicting validator set correctly signed the conflicting block
// - the nodes trusted header at the same height as the conflicting header has a different hash
// - all signatures must be checked as this will be used as evidence
//
// CONTRACT: must run ValidateBasic() on the evidence before verifying
//
Expand All @@ -115,7 +116,7 @@ func VerifyLightClientAttack(e *types.LightClientAttackEvidence, commonHeader, t
// In the case of lunatic attack there will be a different commonHeader height. Therefore the node perform a single
// verification jump between the common header and the conflicting one
if commonHeader.Height != e.ConflictingBlock.Height {
err := commonVals.VerifyCommitLightTrusting(trustedHeader.ChainID, e.ConflictingBlock.Commit, light.DefaultTrustLevel)
err := commonVals.VerifyCommitLightTrustingAllSignatures(trustedHeader.ChainID, e.ConflictingBlock.Commit, light.DefaultTrustLevel)
if err != nil {
return fmt.Errorf("skipping verification of conflicting block failed: %w", err)
}
Expand All @@ -127,7 +128,7 @@ func VerifyLightClientAttack(e *types.LightClientAttackEvidence, commonHeader, t
}

// Verify that the 2/3+ commits from the conflicting validator set were for the conflicting header
if err := e.ConflictingBlock.ValidatorSet.VerifyCommitLight(trustedHeader.ChainID, e.ConflictingBlock.Commit.BlockID,
if err := e.ConflictingBlock.ValidatorSet.VerifyCommitLightAllSignatures(trustedHeader.ChainID, e.ConflictingBlock.Commit.BlockID,
e.ConflictingBlock.Height, e.ConflictingBlock.Commit); err != nil {
return fmt.Errorf("invalid commit from conflicting block: %w", err)
}
Expand Down
42 changes: 33 additions & 9 deletions test/e2e/runner/evidence.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,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.
Expand Down Expand Up @@ -88,10 +88,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 {
ev, err = generateDuplicateVoteEvidence(
Expand All @@ -103,7 +105,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
}
}
Expand Down Expand Up @@ -148,6 +158,7 @@ func generateLightClientAttackEvidence(
vals *types.ValidatorSet,
chainID string,
evTime time.Time,
validEvidence bool,
) (*types.LightClientAttackEvidence, error) {
// forge a random header
forgedHeight := height + 2
Expand All @@ -157,7 +168,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)
if err != nil {
return nil, err
}
Expand All @@ -172,6 +183,11 @@ func generateLightClientAttackEvidence(
return nil, err
}

// malleate the last signature of the commit by adding one to its first byte
if !validEvidence {
commit.Signatures[len(commit.Signatures)-1].Signature[0]++
}

ev := &types.LightClientAttackEvidence{
ConflictingBlock: &types.LightBlock{
SignedHeader: &types.SignedHeader{
Expand Down Expand Up @@ -286,18 +302,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
Expand Down
80 changes: 71 additions & 9 deletions types/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,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.
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,
) error {
// run a basic validation of the arguments
if err := verifyBasicValsAndCommit(vals, commit, height, blockID); err != nil {
return err
Expand All @@ -75,12 +105,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
Expand All @@ -89,9 +119,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,
) error {
// sanity checks
if vals == nil {
return errors.New("nil validator set")
Expand Down Expand Up @@ -121,12 +183,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
Expand Down
Loading
Loading