Skip to content

Commit

Permalink
Merge pull request #11 from osmosis-labs/adam/batch-verify-backport-2
Browse files Browse the repository at this point in the history
feat: `VerifyCommitLight` and `VerifyCommitLightTrusting` _never_ check all signatures
  • Loading branch information
czarcas7ic authored Feb 27, 2024
2 parents 216c951 + e61ef5f commit fd03f91
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 52 deletions.
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)

// 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

0 comments on commit fd03f91

Please sign in to comment.