Browse Source

lite2: use bisection for some of backward verification (#4575)

Closes: #4537

Uses SignedHeaderBefore to find header before unverified header and then bisection to verify the header. Only when header is between first and last trusted header height else if before the first trusted header height then regular backwards verification is used.
pull/4628/head
Callum Waters 4 years ago
committed by GitHub
parent
commit
5c380cdacb
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 157 additions and 104 deletions
  1. +1
    -0
      CHANGELOG_PENDING.md
  2. +3
    -2
      docs/architecture/adr-046-light-client-implementation.md
  3. +90
    -51
      lite2/client.go
  4. +9
    -9
      lite2/client_benchmark_test.go
  5. +33
    -29
      lite2/client_test.go
  6. +8
    -0
      lite2/errors.go
  7. +6
    -6
      lite2/store/db/db.go
  8. +5
    -5
      lite2/store/db/db_test.go
  9. +2
    -2
      lite2/store/store.go

+ 1
- 0
CHANGELOG_PENDING.md View File

@ -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:


+ 3
- 2
docs/architecture/adr-046-light-client-implementation.md View File

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


+ 90
- 51
lite2/client.go View File

@ -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
}


+ 9
- 9
lite2/client_benchmark_test.go View File

@ -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()),
)


+ 33
- 29
lite2/client_test.go View File

@ -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{


+ 8
- 0
lite2/errors.go View File

@ -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")
}

+ 6
- 6
lite2/store/db/db.go View File

@ -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)


+ 5
- 5
lite2/store/db/db_test.go View File

@ -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)


+ 2
- 2
lite2/store/store.go View File

@ -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).


Loading…
Cancel
Save