diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 6aeb1007a..fe8f4a48e 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -25,6 +25,7 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - [Docker] \#4569 Default configuration added to docker image (you can still mount your own config the same way) (@greg-szabo) - [lite2] [\#4562](https://github.com/tendermint/tendermint/pull/4562) Cache headers when using bisection (@cmwaters) - [all] [\#4608](https://github.com/tendermint/tendermint/pull/4608) Give reactors descriptive names when they're initialized +- [lite2] [\#4575](https://github.com/tendermint/tendermint/pull/4575) Use bisection for within-range verification (@cmwaters) - [tools] \#4615 Allow developers to use Docker to generate proto stubs, via `make proto-gen-docker`. ### BUG FIXES: diff --git a/docs/architecture/adr-046-light-client-implementation.md b/docs/architecture/adr-046-light-client-implementation.md index 37a7c83c5..a1e322a9c 100644 --- a/docs/architecture/adr-046-light-client-implementation.md +++ b/docs/architecture/adr-046-light-client-implementation.md @@ -60,8 +60,9 @@ also cross-checked with witnesses for additional security. Due to bisection algorithm nature, some headers might be skipped. If the light client does not have a header for height `X` and `VerifyHeaderAtHeight(X)` or -`VerifyHeader(H#X)` methods are called, it will perform a backwards -verification from the latest header back to the header at height `X`. +`VerifyHeader(H#X)` methods are called, these will perform either a) backwards +verification from the latest header back to the header at height `X` or b) +bisection verification from the first stored header to the header at height `X`. `TrustedHeader`, `TrustedValidatorSet` only communicate with the trusted store. If some header is not there, an error will be returned indicating that diff --git a/lite2/client.go b/lite2/client.go index 14c07e511..b93c54b8b 100644 --- a/lite2/client.go +++ b/lite2/client.go @@ -149,7 +149,7 @@ func NewClient( options ...Option) (*Client, error) { if err := trustOptions.ValidateBasic(); err != nil { - return nil, errors.Wrap(err, "invalid TrustOptions") + return nil, fmt.Errorf("invalid TrustOptions: %w", err) } c, err := NewClientFromTrustedStore(chainID, trustOptions.Period, primary, witnesses, trustedStore, options...) @@ -206,13 +206,13 @@ func NewClientFromTrustedStore( // Validate the number of witnesses. if len(c.witnesses) < 1 { - return nil, errors.New("expected at least one witness") + return nil, errNoWitnesses{} } // Verify witnesses are all on the same chain. for i, w := range witnesses { if w.ChainID() != chainID { - return nil, errors.Errorf("witness #%d: %v is on another chain %s, expected %s", + return nil, fmt.Errorf("witness #%d: %v is on another chain %s, expected %s", i, w, w.ChainID(), chainID) } } @@ -234,18 +234,18 @@ func NewClientFromTrustedStore( func (c *Client) restoreTrustedHeaderAndVals() error { lastHeight, err := c.trustedStore.LastSignedHeaderHeight() if err != nil { - return errors.Wrap(err, "can't get last trusted header height") + return fmt.Errorf("can't get last trusted header height: %w", err) } if lastHeight > 0 { trustedHeader, err := c.trustedStore.SignedHeader(lastHeight) if err != nil { - return errors.Wrap(err, "can't get last trusted header") + return fmt.Errorf("can't get last trusted header: %w", err) } trustedVals, err := c.trustedStore.ValidatorSet(lastHeight) if err != nil { - return errors.Wrap(err, "can't get last trusted validators") + return fmt.Errorf("can't get last trusted validators: %w", err) } c.latestTrustedHeader = trustedHeader @@ -300,7 +300,7 @@ func (c *Client) checkTrustedHeaderUsingOptions(options TrustOptions) error { // remove all the headers (options.Height, trustedHeader.Height] err := c.cleanupAfter(options.Height) if err != nil { - return errors.Wrapf(err, "cleanupAfter(%d)", options.Height) + return fmt.Errorf("cleanupAfter(%d): %w", options.Height, err) } c.logger.Info("Rolled back to older header (newer headers were removed)", @@ -322,7 +322,7 @@ func (c *Client) checkTrustedHeaderUsingOptions(options TrustOptions) error { if c.confirmationFn(action) { err := c.Cleanup() if err != nil { - return errors.Wrap(err, "failed to cleanup") + return fmt.Errorf("failed to cleanup: %w", err) } } else { return errors.New("refused to remove the stored headers despite hashes mismatch") @@ -350,7 +350,7 @@ func (c *Client) initializeWithTrustOptions(options TrustOptions) error { } if !bytes.Equal(h.Hash(), options.Hash) { - return errors.Errorf("expected header's hash %X, but got %X", options.Hash, h.Hash()) + return fmt.Errorf("expected header's hash %X, but got %X", options.Hash, h.Hash()) } err = c.compareNewHeaderWithWitnesses(h) @@ -365,7 +365,7 @@ func (c *Client) initializeWithTrustOptions(options TrustOptions) error { } if !bytes.Equal(h.ValidatorsHash, vals.Hash()) { - return errors.Errorf("expected header's validators (%X) to match those that were supplied (%X)", + return fmt.Errorf("expected header's validators (%X) to match those that were supplied (%X)", h.ValidatorsHash, vals.Hash(), ) @@ -374,7 +374,7 @@ func (c *Client) initializeWithTrustOptions(options TrustOptions) error { // Ensure that +2/3 of validators signed correctly. err = vals.VerifyCommit(c.chainID, h.Commit.BlockID, h.Height, h.Commit) if err != nil { - return errors.Wrap(err, "invalid commit") + return fmt.Errorf("invalid commit: %w", err) } // 3) Persist both of them and continue. @@ -436,7 +436,7 @@ func (c *Client) TrustedValidatorSet(height int64) (valSet *types.ValidatorSet, func (c *Client) compareWithLatestHeight(height int64) (int64, error) { latestHeight, err := c.LastTrustedHeight() if err != nil { - return 0, errors.Wrap(err, "can't get last trusted height") + return 0, fmt.Errorf("can't get last trusted height: %w", err) } if latestHeight == -1 { return 0, errors.New("no headers exist") @@ -444,7 +444,7 @@ func (c *Client) compareWithLatestHeight(height int64) (int64, error) { switch { case height > latestHeight: - return 0, errors.Errorf("unverified header/valset requested (latest: %d)", latestHeight) + return 0, fmt.Errorf("unverified header/valset requested (latest: %d)", latestHeight) case height == 0: return latestHeight, nil case height < 0: @@ -499,6 +499,11 @@ func (c *Client) VerifyHeaderAtHeight(height int64, now time.Time) (*types.Signe // Intermediate headers are not saved to database. // https://github.com/tendermint/spec/blob/master/spec/consensus/light-client.md // +// If the header, which is older than the currently trusted header, is +// requested and the light client does not have it, VerifyHeader will perform: +// a) bisection verification if nearest trusted header is found & not expired +// b) backwards verification in all other cases +// // It returns ErrOldHeaderExpired if the latest trusted header expired. // // If the primary provides an invalid header (ErrInvalidHeader), it is rejected @@ -517,7 +522,7 @@ func (c *Client) VerifyHeader(newHeader *types.SignedHeader, newVals *types.Vali if err == nil { // Make sure it's the same header. if !bytes.Equal(h.Hash(), newHeader.Hash()) { - return errors.Errorf("existing trusted header %X does not match newHeader %X", h.Hash(), newHeader.Hash()) + return fmt.Errorf("existing trusted header %X does not match newHeader %X", h.Hash(), newHeader.Hash()) } c.logger.Info("Header has already been verified", "height", newHeader.Height, "hash", hash2str(newHeader.Hash())) @@ -533,7 +538,7 @@ func (c *Client) verifyHeader(newHeader *types.SignedHeader, newVals *types.Vali var err error - // 1) If going forward, perform either bisection or sequential verification + // 1) If going forward, perform either bisection or sequential verification. if newHeader.Height >= c.latestTrustedHeader.Height { switch c.verificationMode { case sequential: @@ -544,26 +549,55 @@ func (c *Client) verifyHeader(newHeader *types.SignedHeader, newVals *types.Vali panic(fmt.Sprintf("Unknown verification mode: %b", c.verificationMode)) } } else { - // 2) Otherwise, perform backwards verification - // Find the closest trusted header after newHeader.Height - var closestHeader *types.SignedHeader - closestHeader, err = c.trustedStore.SignedHeaderAfter(newHeader.Height) + // 2) If verifying before the first trusted header, perform backwards + // verification. + var ( + closestHeader *types.SignedHeader + firstHeaderHeight int64 + ) + firstHeaderHeight, err = c.FirstTrustedHeight() if err != nil { - return errors.Wrapf(err, "can't get signed header after height %d", newHeader.Height) + return fmt.Errorf("can't get first header height: %w", err) + } + if newHeader.Height < firstHeaderHeight { + closestHeader, err = c.TrustedHeader(firstHeaderHeight) + if err != nil { + return fmt.Errorf("can't get first signed header: %w", err) + } + if HeaderExpired(closestHeader, c.trustingPeriod, now) { + closestHeader = c.latestTrustedHeader + } + err = c.backwards(closestHeader, newHeader, now) + } else { + // 3) OR if between trusted headers where the nearest has not expired, + // perform bisection verification, else backwards. + closestHeader, err = c.trustedStore.SignedHeaderBefore(newHeader.Height) + if err != nil { + return fmt.Errorf("can't get signed header before height %d: %w", newHeader.Height, err) + } + var closestValidatorSet *types.ValidatorSet + if c.verificationMode == sequential || HeaderExpired(closestHeader, c.trustingPeriod, now) { + err = c.backwards(c.latestTrustedHeader, newHeader, now) + } else { + closestValidatorSet, _, err = c.TrustedValidatorSet(closestHeader.Height) + if err != nil { + return fmt.Errorf("can't get validator set at height %d: %w", closestHeader.Height, err) + } + err = c.bisection(closestHeader, closestValidatorSet, newHeader, newVals, now) + } } - - err = c.backwards(closestHeader, newHeader, now) } if err != nil { c.logger.Error("Can't verify", "err", err) return err } - + // 4) Compare header with other witnesses if err := c.compareNewHeaderWithWitnesses(newHeader); err != nil { c.logger.Error("Error when comparing new header with witnesses", "err", err) return err } + // 5) Once verified, save and return return c.updateTrustedHeaderAndVals(newHeader, newVals) } @@ -590,7 +624,7 @@ func (c *Client) sequence( } else { // intermediate headers interimHeader, interimVals, err = c.fetchHeaderAndValsAtHeight(height) if err != nil { - return errors.Wrapf(err, "failed to obtain the header #%d", height) + return err } } @@ -604,10 +638,10 @@ func (c *Client) sequence( err = VerifyAdjacent(c.chainID, trustedHeader, interimHeader, interimVals, c.trustingPeriod, now) if err != nil { - err = errors.Wrapf(err, "verify adjacent from #%d to #%d failed", - trustedHeader.Height, interimHeader.Height) + err = fmt.Errorf("verify adjacent from #%d to #%d failed: %w", + trustedHeader.Height, interimHeader.Height, err) - switch errors.Cause(err).(type) { + switch errors.Unwrap(err).(type) { case ErrInvalidHeader: c.logger.Error("primary sent invalid header -> replacing", "err", err) replaceErr := c.replacePrimaryProvider() @@ -696,15 +730,15 @@ func (c *Client) bisection( if replaceErr != nil { c.logger.Error("Can't replace primary", "err", replaceErr) // return original error - return errors.Wrapf(err, "verify from #%d to #%d failed", - trustedHeader.Height, headerCache[depth].sh.Height) + return fmt.Errorf("verify non adjacent from #%d to #%d failed: %w", + trustedHeader.Height, headerCache[depth].sh.Height, err) } // attempt to verify the header again continue default: - return errors.Wrapf(err, "verify from #%d to #%d failed", - trustedHeader.Height, headerCache[depth].sh.Height) + return fmt.Errorf("verify non adjacent from #%d to #%d failed: %w", + trustedHeader.Height, headerCache[depth].sh.Height, err) } } } @@ -762,14 +796,14 @@ func (c *Client) Cleanup() error { // cleanupAfter deletes all headers & validator sets after +height+. It also // resets latestTrustedHeader to the latest header. func (c *Client) cleanupAfter(height int64) error { - nextHeight := height + prevHeight := c.latestTrustedHeader.Height for { - h, err := c.trustedStore.SignedHeaderAfter(nextHeight) - if err == store.ErrSignedHeaderNotFound { + h, err := c.trustedStore.SignedHeaderBefore(prevHeight) + if err == store.ErrSignedHeaderNotFound || (h != nil && h.Height <= height) { break } else if err != nil { - return errors.Wrapf(err, "failed to get header after %d", nextHeight) + return fmt.Errorf("failed to get header before %d: %w", prevHeight, err) } err = c.trustedStore.DeleteSignedHeaderAndValidatorSet(h.Height) @@ -778,7 +812,7 @@ func (c *Client) cleanupAfter(height int64) error { "height", h.Height) } - nextHeight = h.Height + prevHeight = h.Height } c.latestTrustedHeader = nil @@ -793,16 +827,16 @@ func (c *Client) cleanupAfter(height int64) error { func (c *Client) updateTrustedHeaderAndVals(h *types.SignedHeader, vals *types.ValidatorSet) error { if !bytes.Equal(h.ValidatorsHash, vals.Hash()) { - return errors.Errorf("expected validator's hash %X, but got %X", h.ValidatorsHash, vals.Hash()) + return fmt.Errorf("expected validator's hash %X, but got %X", h.ValidatorsHash, vals.Hash()) } if err := c.trustedStore.SaveSignedHeaderAndValidatorSet(h, vals); err != nil { - return errors.Wrap(err, "failed to save trusted header") + return fmt.Errorf("failed to save trusted header: %w", err) } if c.pruningSize > 0 { if err := c.trustedStore.Prune(c.pruningSize); err != nil { - return errors.Wrap(err, "prune") + return fmt.Errorf("prune: %w", err) } } @@ -819,11 +853,11 @@ func (c *Client) updateTrustedHeaderAndVals(h *types.SignedHeader, vals *types.V func (c *Client) fetchHeaderAndValsAtHeight(height int64) (*types.SignedHeader, *types.ValidatorSet, error) { h, err := c.signedHeaderFromPrimary(height) if err != nil { - return nil, nil, errors.Wrapf(err, "failed to obtain the header #%d", height) + return nil, nil, fmt.Errorf("failed to obtain the header #%d: %w", height, err) } vals, err := c.validatorSetFromPrimary(height) if err != nil { - return nil, nil, errors.Wrapf(err, "failed to obtain the vals #%d", height) + return nil, nil, fmt.Errorf("failed to obtain the vals #%d: %w", height, err) } return h, vals, nil } @@ -837,6 +871,7 @@ func (c *Client) backwards( now time.Time) error { if HeaderExpired(initiallyTrustedHeader, c.trustingPeriod, now) { + c.logger.Error("Header Expired") return ErrOldHeaderExpired{initiallyTrustedHeader.Time.Add(c.trustingPeriod), now} } @@ -849,16 +884,20 @@ func (c *Client) backwards( for trustedHeader.Height > newHeader.Height { interimHeader, err = c.signedHeaderFromPrimary(trustedHeader.Height - 1) if err != nil { - return errors.Wrapf(err, "failed to obtain the header at height #%d", trustedHeader.Height-1) + return fmt.Errorf("failed to obtain the header at height #%d: %w", trustedHeader.Height-1, err) } - + c.logger.Debug("Verify newHeader against trustedHeader", + "trustedHeight", trustedHeader.Height, + "trustedHash", hash2str(trustedHeader.Hash()), + "newHeight", interimHeader.Height, + "newHash", hash2str(interimHeader.Hash())) if err := VerifyBackwards(c.chainID, interimHeader, trustedHeader); err != nil { c.logger.Error("primary sent invalid header -> replacing", "err", err) if replaceErr := c.replacePrimaryProvider(); replaceErr != nil { c.logger.Error("Can't replace primary", "err", replaceErr) // return original error - return errors.Wrapf(err, "verify backwards from %d to %d failed", - trustedHeader.Height, interimHeader.Height) + return fmt.Errorf("verify backwards from %d to %d failed: %w", + trustedHeader.Height, interimHeader.Height, err) } } @@ -883,7 +922,7 @@ func (c *Client) compareNewHeaderWithWitnesses(h *types.SignedHeader) error { witnessesToRemove := make([]int, 0) for attempt := uint16(1); attempt <= c.maxRetryAttempts; attempt++ { if len(c.witnesses) == 0 { - return errors.New("could not find any witnesses. please reset the light client") + return errNoWitnesses{} } for i, witness := range c.witnesses { @@ -909,7 +948,7 @@ func (c *Client) compareNewHeaderWithWitnesses(h *types.SignedHeader) error { // TODO: send the diverged headers to primary && all witnesses - return errors.Errorf( + return fmt.Errorf( "header hash %X does not match one %X from the witness %v", h.Hash(), altH.Hash(), witness) } @@ -952,7 +991,7 @@ func (c *Client) removeWitness(idx int) { func (c *Client) Update(now time.Time) (*types.SignedHeader, error) { lastTrustedHeight, err := c.LastTrustedHeight() if err != nil { - return nil, errors.Wrap(err, "can't get last trusted height") + return nil, fmt.Errorf("can't get last trusted height: %w", err) } if lastTrustedHeight == -1 { @@ -962,7 +1001,7 @@ func (c *Client) Update(now time.Time) (*types.SignedHeader, error) { latestHeader, latestVals, err := c.fetchHeaderAndValsAtHeight(0) if err != nil { - return nil, errors.Wrapf(err, "can't get latest header and vals") + return nil, err } if latestHeader.Height > lastTrustedHeight { @@ -984,7 +1023,7 @@ func (c *Client) replacePrimaryProvider() error { defer c.providerMutex.Unlock() if len(c.witnesses) <= 1 { - return errors.Errorf("only one witness left. please reset the light client") + return errNoWitnesses{} } c.primary = c.witnesses[0] c.witnesses = c.witnesses[1:] @@ -1004,7 +1043,7 @@ func (c *Client) signedHeaderFromPrimary(height int64) (*types.SignedHeader, err if err == nil { // sanity check if height > 0 && h.Height != height { - return nil, errors.Errorf("expected %d height, got %d", height, h.Height) + return nil, fmt.Errorf("expected %d height, got %d", height, h.Height) } return h, nil } diff --git a/lite2/client_benchmark_test.go b/lite2/client_benchmark_test.go index 276c962a1..97b140ea5 100644 --- a/lite2/client_benchmark_test.go +++ b/lite2/client_benchmark_test.go @@ -20,8 +20,8 @@ import ( // // Remember that none of these benchmarks account for network latency. var ( - largeFullNode = mockp.New(GenMockNode(chainID, 1000, 100, 1, bTime)) - genesisHeader, _ = largeFullNode.SignedHeader(1) + benchmarkFullNode = mockp.New(GenMockNode(chainID, 1000, 100, 1, bTime)) + genesisHeader, _ = benchmarkFullNode.SignedHeader(1) ) func BenchmarkSequence(b *testing.B) { @@ -32,8 +32,8 @@ func BenchmarkSequence(b *testing.B) { Height: 1, Hash: genesisHeader.Hash(), }, - largeFullNode, - []provider.Provider{largeFullNode}, + benchmarkFullNode, + []provider.Provider{benchmarkFullNode}, dbs.New(dbm.NewMemDB(), chainID), Logger(log.TestingLogger()), SequentialVerification(), @@ -59,8 +59,8 @@ func BenchmarkBisection(b *testing.B) { Height: 1, Hash: genesisHeader.Hash(), }, - largeFullNode, - []provider.Provider{largeFullNode}, + benchmarkFullNode, + []provider.Provider{benchmarkFullNode}, dbs.New(dbm.NewMemDB(), chainID), Logger(log.TestingLogger()), ) @@ -78,7 +78,7 @@ func BenchmarkBisection(b *testing.B) { } func BenchmarkBackwards(b *testing.B) { - trustedHeader, _ := largeFullNode.SignedHeader(0) + trustedHeader, _ := benchmarkFullNode.SignedHeader(0) c, err := NewClient( chainID, TrustOptions{ @@ -86,8 +86,8 @@ func BenchmarkBackwards(b *testing.B) { Height: trustedHeader.Height, Hash: trustedHeader.Hash(), }, - largeFullNode, - []provider.Provider{largeFullNode}, + benchmarkFullNode, + []provider.Provider{benchmarkFullNode}, dbs.New(dbm.NewMemDB(), chainID), Logger(log.TestingLogger()), ) diff --git a/lite2/client_test.go b/lite2/client_test.go index 65ea55122..9481fb0b5 100644 --- a/lite2/client_test.go +++ b/lite2/client_test.go @@ -57,7 +57,8 @@ var ( headerSet, valSet, ) - deadNode = mockp.NewDeadMock(chainID) + deadNode = mockp.NewDeadMock(chainID) + largeFullNode = mockp.New(GenMockNode(chainID, 10, 3, 0, bTime)) ) func TestClient_SequentialVerification(t *testing.T) { @@ -682,62 +683,65 @@ func TestClientReplacesPrimaryWithWitnessIfPrimaryIsUnavailable(t *testing.T) { func TestClient_BackwardsVerification(t *testing.T) { { + trustHeader, _ := largeFullNode.SignedHeader(6) c, err := NewClient( chainID, TrustOptions{ - Period: 1 * time.Hour, - Height: 3, - Hash: h3.Hash(), + Period: 4 * time.Minute, + Height: trustHeader.Height, + Hash: trustHeader.Hash(), }, - fullNode, - []provider.Provider{fullNode}, + largeFullNode, + []provider.Provider{largeFullNode}, dbs.New(dbm.NewMemDB(), chainID), Logger(log.TestingLogger()), ) require.NoError(t, err) - // 1) header is missing => expect no error - h, err := c.VerifyHeaderAtHeight(2, bTime.Add(1*time.Hour).Add(1*time.Second)) + // 1) verify before the trusted header using backwards => expect no error + h, err := c.VerifyHeaderAtHeight(5, bTime.Add(6*time.Minute)) require.NoError(t, err) if assert.NotNil(t, h) { - assert.EqualValues(t, 2, h.Height) + assert.EqualValues(t, 5, h.Height) } // 2) untrusted header is expired but trusted header is not => expect no error - h, err = c.VerifyHeaderAtHeight(1, bTime.Add(1*time.Hour).Add(1*time.Second)) + h, err = c.VerifyHeaderAtHeight(3, bTime.Add(8*time.Minute)) assert.NoError(t, err) assert.NotNil(t, h) // 3) already stored headers should return the header without error - h, err = c.VerifyHeaderAtHeight(2, bTime.Add(1*time.Hour).Add(1*time.Second)) + h, err = c.VerifyHeaderAtHeight(5, bTime.Add(6*time.Minute)) assert.NoError(t, err) assert.NotNil(t, h) - } - { - c, err := NewClient( - chainID, - TrustOptions{ - Period: 1 * time.Hour, - Height: 3, - Hash: h3.Hash(), - }, - fullNode, - []provider.Provider{fullNode}, - dbs.New(dbm.NewMemDB(), chainID), - Logger(log.TestingLogger()), - ) + + // 4a) First verify latest header + _, err = c.VerifyHeaderAtHeight(9, bTime.Add(9*time.Minute)) require.NoError(t, err) - // 3) trusted header has expired => expect error - _, err = c.VerifyHeaderAtHeight(1, bTime.Add(4*time.Hour).Add(1*time.Second)) + // 4b) Verify backwards using bisection => expect no error + _, err = c.VerifyHeaderAtHeight(7, bTime.Add(10*time.Minute)) + assert.NoError(t, err) + // shouldn't have verified this header in the process + _, err = c.TrustedHeader(8) assert.Error(t, err) + + // 5) trusted header has expired => expect error + _, err = c.VerifyHeaderAtHeight(1, bTime.Add(20*time.Minute)) + assert.Error(t, err) + + // 6) Try bisection method, but closest header (at 7) has expired + // so change to backwards => expect no error + _, err = c.VerifyHeaderAtHeight(8, bTime.Add(12*time.Minute)) + assert.NoError(t, err) + } { testCases := []struct { provider provider.Provider }{ { - // provides incorrect height + // 7) provides incorrect height mockp.New( chainID, map[int64]*types.SignedHeader{ @@ -750,7 +754,7 @@ func TestClient_BackwardsVerification(t *testing.T) { ), }, { - // provides incorrect hash + // 8) provides incorrect hash mockp.New( chainID, map[int64]*types.SignedHeader{ diff --git a/lite2/errors.go b/lite2/errors.go index 13a6cf29d..7bc70f698 100644 --- a/lite2/errors.go +++ b/lite2/errors.go @@ -38,3 +38,11 @@ type ErrInvalidHeader struct { func (e ErrInvalidHeader) Error() string { return fmt.Sprintf("invalid header: %v", e.Reason) } + +// errNoWitnesses means that there are not enough witnesses connected to +// continue running the light client. +type errNoWitnesses struct{} + +func (e errNoWitnesses) Error() string { + return fmt.Sprint("no witnesses connected. please reset light client") +} diff --git a/lite2/store/db/db.go b/lite2/store/db/db.go index d405b9865..8d37ace9f 100644 --- a/lite2/store/db/db.go +++ b/lite2/store/db/db.go @@ -203,18 +203,18 @@ func (s *dbs) FirstSignedHeaderHeight() (int64, error) { return -1, nil } -// SignedHeaderAfter iterates over headers until it finds a header after one at -// height. It returns ErrSignedHeaderNotFound if no such header exists. +// SignedHeaderBefore iterates over headers until it finds a header before +// the given height. It returns ErrSignedHeaderNotFound if no such header exists. // // Safe for concurrent use by multiple goroutines. -func (s *dbs) SignedHeaderAfter(height int64) (*types.SignedHeader, error) { +func (s *dbs) SignedHeaderBefore(height int64) (*types.SignedHeader, error) { if height <= 0 { panic("negative or zero height") } - itr, err := s.db.Iterator( - s.shKey(height+1), - append(s.shKey(1<<63-1), byte(0x00)), + itr, err := s.db.ReverseIterator( + s.shKey(1), + s.shKey(height), ) if err != nil { panic(err) diff --git a/lite2/store/db/db_test.go b/lite2/store/db/db_test.go index 2b82de8f3..ce45f3bcf 100644 --- a/lite2/store/db/db_test.go +++ b/lite2/store/db/db_test.go @@ -76,19 +76,19 @@ func Test_SaveSignedHeaderAndValidatorSet(t *testing.T) { assert.Nil(t, valSet) } -func Test_SignedHeaderAfter(t *testing.T) { - dbStore := New(dbm.NewMemDB(), "Test_SignedHeaderAfter") +func Test_SignedHeaderBefore(t *testing.T) { + dbStore := New(dbm.NewMemDB(), "Test_SignedHeaderBefore") assert.Panics(t, func() { - dbStore.SignedHeaderAfter(0) - dbStore.SignedHeaderAfter(100) + _, _ = dbStore.SignedHeaderBefore(0) + _, _ = dbStore.SignedHeaderBefore(100) }) err := dbStore.SaveSignedHeaderAndValidatorSet( &types.SignedHeader{Header: &types.Header{Height: 2}}, &types.ValidatorSet{}) require.NoError(t, err) - h, err := dbStore.SignedHeaderAfter(1) + h, err := dbStore.SignedHeaderBefore(3) require.NoError(t, err) if assert.NotNil(t, h) { assert.EqualValues(t, 2, h.Height) diff --git a/lite2/store/store.go b/lite2/store/store.go index 7ea6b9c6b..0d36c48b6 100644 --- a/lite2/store/store.go +++ b/lite2/store/store.go @@ -41,10 +41,10 @@ type Store interface { // If the store is empty, -1 and nil error are returned. FirstSignedHeaderHeight() (int64, error) - // SignedHeaderAfter returns the SignedHeader after the certain height. + // SignedHeaderBefore returns the SignedHeader before a certain height. // // height must be > 0 && <= LastSignedHeaderHeight. - SignedHeaderAfter(height int64) (*types.SignedHeader, error) + SignedHeaderBefore(height int64) (*types.SignedHeader, error) // Prune removes headers & the associated validator sets when Store reaches a // defined size (number of header & validator set pairs).