Browse Source

lite2: move AutoClient into Client (#4326)

* lite2: move AutoClient into Client

Most of the users will want auto update feature, so it makes sense to
move it into the Client itself, rather than having a separate
abstraction (it makes the code cleaner, but introduces an extra thing
the user will need to learn).

Also, add `FirstTrustedHeight` func to Client to get first trusted height.

* fix db store tests

* separate examples for auto and manual clients

* AutoUpdate tries to update to latest state

NOT 1 header at a time

* fix errors

* lite2: make Logger an option

remove SetLogger func

* fix lite cmd

* lite2: make concurrency assumptions explicit

* fixes after my own review

* no need for nextHeightFn

sequence func will download intermediate headers

* correct comment
pull/4334/head
Anton Kaliaev 5 years ago
committed by GitHub
parent
commit
f95409e070
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 303 additions and 222 deletions
  1. +1
    -1
      cmd/tendermint/commands/lite.go
  2. +0
    -76
      lite2/auto_client.go
  3. +0
    -76
      lite2/auto_client_test.go
  4. +100
    -14
      lite2/client.go
  5. +146
    -13
      lite2/client_test.go
  6. +22
    -26
      lite2/example_test.go
  7. +12
    -0
      lite2/provider/errors.go
  8. +9
    -0
      lite2/provider/http/http.go
  9. +2
    -4
      lite2/provider/mock/mock.go
  10. +2
    -2
      lite2/provider/provider.go
  11. +3
    -2
      lite2/store/db/db.go
  12. +4
    -4
      lite2/store/db/db_test.go
  13. +2
    -4
      lite2/store/store.go

+ 1
- 1
cmd/tendermint/commands/lite.go View File

@ -86,11 +86,11 @@ func runProxy(cmd *cobra.Command, args []string) error {
},
httpp.NewWithClient(chainID, node),
dbs.New(db, chainID),
lite.Logger(liteLogger),
)
if err != nil {
return err
}
c.SetLogger(liteLogger)
p := lproxy.Proxy{
Addr: listenAddr,


+ 0
- 76
lite2/auto_client.go View File

@ -1,76 +0,0 @@
package lite
import (
"time"
"github.com/tendermint/tendermint/types"
)
// AutoClient can auto update itself by fetching headers every N seconds.
type AutoClient struct {
base *Client
updatePeriod time.Duration
quit chan struct{}
trustedHeaders chan *types.SignedHeader
errs chan error
}
// NewAutoClient creates a new client and starts a polling goroutine.
func NewAutoClient(base *Client, updatePeriod time.Duration) *AutoClient {
c := &AutoClient{
base: base,
updatePeriod: updatePeriod,
quit: make(chan struct{}),
trustedHeaders: make(chan *types.SignedHeader),
errs: make(chan error),
}
go c.autoUpdate()
return c
}
// TrustedHeaders returns a channel onto which new trusted headers are posted.
func (c *AutoClient) TrustedHeaders() <-chan *types.SignedHeader {
return c.trustedHeaders
}
// Err returns a channel onto which errors are posted.
func (c *AutoClient) Errs() <-chan error {
return c.errs
}
// Stop stops the client.
func (c *AutoClient) Stop() {
close(c.quit)
}
func (c *AutoClient) autoUpdate() {
ticker := time.NewTicker(c.updatePeriod)
defer ticker.Stop()
for {
select {
case <-ticker.C:
lastTrustedHeight, err := c.base.LastTrustedHeight()
if err != nil {
c.errs <- err
continue
}
if lastTrustedHeight == -1 {
// no headers yet => wait
continue
}
h, err := c.base.VerifyHeaderAtHeight(lastTrustedHeight+1, time.Now())
if err != nil {
// no header yet or verification error => try again after updatePeriod
c.errs <- err
continue
}
c.trustedHeaders <- h
case <-c.quit:
return
}
}
}

+ 0
- 76
lite2/auto_client_test.go View File

@ -1,76 +0,0 @@
package lite
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
dbm "github.com/tendermint/tm-db"
"github.com/tendermint/tendermint/libs/log"
mockp "github.com/tendermint/tendermint/lite2/provider/mock"
dbs "github.com/tendermint/tendermint/lite2/store/db"
"github.com/tendermint/tendermint/types"
)
func TestAutoClient(t *testing.T) {
const (
chainID = "TestAutoClient"
)
var (
keys = genPrivKeys(4)
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
vals = keys.ToValidators(20, 10)
bTime = time.Now().Add(-1 * time.Hour)
header = keys.GenSignedHeader(chainID, 1, bTime, nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
)
base, err := NewClient(
chainID,
TrustOptions{
Period: 4 * time.Hour,
Height: 1,
Hash: header.Hash(),
},
mockp.New(
chainID,
map[int64]*types.SignedHeader{
// trusted header
1: header,
// interim header (3/3 signed)
2: keys.GenSignedHeader(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
// last header (3/3 signed)
3: keys.GenSignedHeader(chainID, 3, bTime.Add(1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
3: vals,
4: vals,
},
),
dbs.New(dbm.NewMemDB(), chainID),
)
require.NoError(t, err)
base.SetLogger(log.TestingLogger())
c := NewAutoClient(base, 1*time.Second)
defer c.Stop()
for i := 2; i <= 3; i++ {
select {
case h := <-c.TrustedHeaders():
assert.EqualValues(t, i, h.Height)
case err := <-c.Errs():
require.NoError(t, err)
case <-time.After(2 * time.Second):
t.Fatal("no headers/errors received in 2 sec")
}
}
}

+ 100
- 14
lite2/client.go View File

@ -48,6 +48,7 @@ const (
sequential mode = iota + 1
skipping
defaultUpdatePeriod = 5 * time.Second
defaultRemoveNoLongerTrustedHeadersPeriod = 24 * time.Hour
)
@ -90,6 +91,13 @@ func AlternativeSources(providers []provider.Provider) Option {
}
}
// UpdatePeriod option can be used to change default polling period (5s).
func UpdatePeriod(d time.Duration) Option {
return func(c *Client) {
c.updatePeriod = d
}
}
// RemoveNoLongerTrustedHeadersPeriod option can be used to define how often
// the routine, which cleans up no longer trusted headers (outside of trusting
// period), is run. Default: once a day. When set to zero, the routine won't be
@ -109,10 +117,20 @@ func ConfirmationFunction(fn func(action string) bool) Option {
}
}
// Logger option can be used to set a logger for the client.
func Logger(l log.Logger) Option {
return func(c *Client) {
c.logger = l
}
}
// Client represents a light client, connected to a single chain, which gets
// headers from a primary provider, verifies them either sequentially or by
// skipping some and stores them in a trusted store (usually, a local FS).
//
// By default, the client will poll the primary provider for new headers every
// 5s (UpdatePeriod). If there are any, it will try to advance the state.
//
// Default verification: SkippingVerification(DefaultTrustLevel)
type Client struct {
chainID string
@ -134,6 +152,7 @@ type Client struct {
// Highest next validator set from the store (height=H+1).
trustedNextVals *types.ValidatorSet
updatePeriod time.Duration
removeNoLongerTrustedHeadersPeriod time.Duration
confirmationFn func(action string) bool
@ -162,6 +181,7 @@ func NewClient(
trustLevel: DefaultTrustLevel,
primary: primary,
trustedStore: trustedStore,
updatePeriod: defaultUpdatePeriod,
removeNoLongerTrustedHeadersPeriod: defaultRemoveNoLongerTrustedHeadersPeriod,
confirmationFn: func(action string) bool { return true },
quit: make(chan struct{}),
@ -191,6 +211,10 @@ func NewClient(
go c.removeNoLongerTrustedHeadersRoutine()
}
if c.updatePeriod > 0 {
go c.autoUpdate()
}
return c, nil
}
@ -339,16 +363,12 @@ func (c *Client) initializeWithTrustOptions(options TrustOptions) error {
return c.updateTrustedHeaderAndVals(h, nextVals)
}
// Stop stops the light client.
// Stop stops all the goroutines. If you wish to remove all the data, call
// Cleanup.
func (c *Client) Stop() {
close(c.quit)
}
// SetLogger sets a logger.
func (c *Client) SetLogger(l log.Logger) {
c.logger = l
}
// TrustedHeader returns a trusted header at the given height (0 - the latest)
// or nil if no such header exist.
//
@ -361,7 +381,10 @@ func (c *Client) SetLogger(l log.Logger) {
// - header expired, therefore can't be trusted (ErrOldHeaderExpired);
// - there are some issues with the trusted store, although that should not
// happen normally;
// - negative height is passed.
// - negative height is passed;
// - header is not found.
//
// Safe for concurrent use by multiple goroutines.
func (c *Client) TrustedHeader(height int64, now time.Time) (*types.SignedHeader, error) {
if height < 0 {
return nil, errors.New("negative height")
@ -379,9 +402,6 @@ func (c *Client) TrustedHeader(height int64, now time.Time) (*types.SignedHeader
if err != nil {
return nil, err
}
if h == nil {
return nil, nil
}
// Ensure header can still be trusted.
if HeaderExpired(h, c.trustingPeriod, now) {
@ -393,11 +413,23 @@ func (c *Client) TrustedHeader(height int64, now time.Time) (*types.SignedHeader
// LastTrustedHeight returns a last trusted height. -1 and nil are returned if
// there are no trusted headers.
//
// Safe for concurrent use by multiple goroutines.
func (c *Client) LastTrustedHeight() (int64, error) {
return c.trustedStore.LastSignedHeaderHeight()
}
// FirstTrustedHeight returns a first trusted height. -1 and nil are returned if
// there are no trusted headers.
//
// Safe for concurrent use by multiple goroutines.
func (c *Client) FirstTrustedHeight() (int64, error) {
return c.trustedStore.FirstSignedHeaderHeight()
}
// ChainID returns the chain ID the light client was configured with.
//
// Safe for concurrent use by multiple goroutines.
func (c *Client) ChainID() string {
return c.chainID
}
@ -406,6 +438,8 @@ func (c *Client) ChainID() string {
// and calls VerifyHeader.
//
// If the trusted header is more recent than one here, an error is returned.
// If the header is not found by the primary provider,
// provider.ErrSignedHeaderNotFound error is returned.
func (c *Client) VerifyHeaderAtHeight(height int64, now time.Time) (*types.SignedHeader, error) {
c.logger.Info("VerifyHeaderAtHeight", "height", height)
@ -435,6 +469,10 @@ func (c *Client) VerifyHeaderAtHeight(height int64, now time.Time) (*types.Signe
// https://github.com/tendermint/spec/blob/master/spec/consensus/light-client.md
//
// If the trusted header is more recent than one here, an error is returned.
//
// If, at any moment, SignedHeader or ValidatorSet are not found by the primary
// provider, provider.ErrSignedHeaderNotFound /
// provider.ErrValidatorSetNotFound error is returned.
func (c *Client) VerifyHeader(newHeader *types.SignedHeader, newVals *types.ValidatorSet, now time.Time) error {
c.logger.Info("VerifyHeader", "height", newHeader.Hash(), "newVals", newVals.Hash())
@ -708,10 +746,6 @@ func (c *Client) RemoveNoLongerTrustedHeaders(now time.Time) {
c.logger.Error("can't get a trusted header", "err", err, "height", height)
continue
}
if h == nil {
c.logger.Debug("attempted to remove non-existing header", "height", height)
continue
}
// Stop if the header is within the trusting period.
if !HeaderExpired(h, c.trustingPeriod, now) {
@ -725,3 +759,55 @@ func (c *Client) RemoveNoLongerTrustedHeaders(now time.Time) {
}
}
}
func (c *Client) autoUpdate() {
ticker := time.NewTicker(c.updatePeriod)
defer ticker.Stop()
for {
select {
case <-ticker.C:
err := c.AutoUpdate(time.Now())
if err != nil {
c.logger.Error("Error during auto update", "err", err)
}
case <-c.quit:
return
}
}
}
// AutoUpdate attempts to advance the state making exponential steps (note:
// when SequentialVerification is being used, the client will still be
// downloading all intermediate headers).
//
// Exposed for testing.
func (c *Client) AutoUpdate(now time.Time) error {
lastTrustedHeight, err := c.LastTrustedHeight()
if err != nil {
return errors.Wrap(err, "can't get last trusted height")
}
if lastTrustedHeight == -1 {
// no headers yet => wait
return nil
}
var i int64
for err == nil {
// exponential increment: 1, 2, 4, 8, 16, ...
height := lastTrustedHeight + int64(1<<uint(i))
h, err := c.VerifyHeaderAtHeight(height, now)
if err != nil {
if errors.Is(err, provider.ErrSignedHeaderNotFound) {
c.logger.Debug("No header yet", "at", height)
return nil
}
return errors.Wrapf(err, "failed to verify the header #%d", height)
}
c.logger.Info("Advanced to new state", "height", h.Height, "hash", h.Hash())
i++
}
return nil
}

+ 146
- 13
lite2/client_test.go View File

@ -1,6 +1,7 @@
package lite
import (
"sync"
"testing"
"time"
@ -286,10 +287,10 @@ func TestClientRemovesNoLongerTrustedHeaders(t *testing.T) {
},
),
dbs.New(dbm.NewMemDB(), chainID),
Logger(log.TestingLogger()),
)
require.NoError(t, err)
defer c.Stop()
c.SetLogger(log.TestingLogger())
// Verify new headers.
_, err = c.VerifyHeaderAtHeight(2, bTime.Add(2*time.Hour).Add(1*time.Second))
@ -303,7 +304,7 @@ func TestClientRemovesNoLongerTrustedHeaders(t *testing.T) {
// Check expired headers are no longer available.
h, err := c.TrustedHeader(1, now)
assert.NoError(t, err)
assert.Error(t, err)
assert.Nil(t, h)
// Check not expired headers are available.
@ -345,15 +346,15 @@ func TestClient_Cleanup(t *testing.T) {
},
),
dbs.New(dbm.NewMemDB(), chainID),
Logger(log.TestingLogger()),
)
require.NoError(t, err)
c.SetLogger(log.TestingLogger())
c.Cleanup()
// Check no headers exist after Cleanup.
h, err := c.TrustedHeader(1, bTime.Add(1*time.Second))
assert.NoError(t, err)
assert.Error(t, err)
assert.Nil(t, h)
}
@ -397,9 +398,9 @@ func TestClientRestoreTrustedHeaderAfterStartup1(t *testing.T) {
},
),
trustedStore,
Logger(log.TestingLogger()),
)
require.NoError(t, err)
c.SetLogger(log.TestingLogger())
h, err := c.TrustedHeader(1, bTime.Add(1*time.Second))
assert.NoError(t, err)
@ -436,9 +437,9 @@ func TestClientRestoreTrustedHeaderAfterStartup1(t *testing.T) {
},
),
trustedStore,
Logger(log.TestingLogger()),
)
require.NoError(t, err)
c.SetLogger(log.TestingLogger())
h, err := c.TrustedHeader(1, bTime.Add(1*time.Second))
assert.NoError(t, err)
@ -491,9 +492,9 @@ func TestClientRestoreTrustedHeaderAfterStartup2(t *testing.T) {
},
),
trustedStore,
Logger(log.TestingLogger()),
)
require.NoError(t, err)
c.SetLogger(log.TestingLogger())
// Check we still have the 1st header (+header+).
h, err := c.TrustedHeader(1, bTime.Add(2*time.Hour).Add(1*time.Second))
@ -536,13 +537,13 @@ func TestClientRestoreTrustedHeaderAfterStartup2(t *testing.T) {
},
),
trustedStore,
Logger(log.TestingLogger()),
)
require.NoError(t, err)
c.SetLogger(log.TestingLogger())
// Check we no longer have the invalid 1st header (+header+).
h, err := c.TrustedHeader(1, bTime.Add(2*time.Hour).Add(1*time.Second))
assert.NoError(t, err)
assert.Error(t, err)
assert.Nil(t, h)
}
}
@ -593,9 +594,9 @@ func TestClientRestoreTrustedHeaderAfterStartup3(t *testing.T) {
},
),
trustedStore,
Logger(log.TestingLogger()),
)
require.NoError(t, err)
c.SetLogger(log.TestingLogger())
// Check we still have the 1st header (+header+).
h, err := c.TrustedHeader(1, bTime.Add(2*time.Hour).Add(1*time.Second))
@ -605,7 +606,7 @@ func TestClientRestoreTrustedHeaderAfterStartup3(t *testing.T) {
// Check we no longer have 2nd header (+header2+).
h, err = c.TrustedHeader(2, bTime.Add(2*time.Hour).Add(1*time.Second))
assert.NoError(t, err)
assert.Error(t, err)
assert.Nil(t, h)
}
@ -643,9 +644,9 @@ func TestClientRestoreTrustedHeaderAfterStartup3(t *testing.T) {
},
),
trustedStore,
Logger(log.TestingLogger()),
)
require.NoError(t, err)
c.SetLogger(log.TestingLogger())
// Check we have swapped invalid 1st header (+header+) with correct one (+header1+).
h, err := c.TrustedHeader(1, bTime.Add(2*time.Hour).Add(1*time.Second))
@ -655,7 +656,139 @@ func TestClientRestoreTrustedHeaderAfterStartup3(t *testing.T) {
// Check we no longer have invalid 2nd header (+header2+).
h, err = c.TrustedHeader(2, bTime.Add(2*time.Hour).Add(1*time.Second))
assert.NoError(t, err)
assert.Error(t, err)
assert.Nil(t, h)
}
}
func TestClient_AutoUpdate(t *testing.T) {
const (
chainID = "TestClient_AutoUpdate"
)
var (
keys = genPrivKeys(4)
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
vals = keys.ToValidators(20, 10)
bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
header = keys.GenSignedHeader(chainID, 1, bTime, nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
)
c, err := NewClient(
chainID,
TrustOptions{
Period: 4 * time.Hour,
Height: 1,
Hash: header.Hash(),
},
mockp.New(
chainID,
map[int64]*types.SignedHeader{
// trusted header
1: header,
// interim header (3/3 signed)
2: keys.GenSignedHeader(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
// last header (3/3 signed)
3: keys.GenSignedHeader(chainID, 3, bTime.Add(1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
3: vals,
4: vals,
},
),
dbs.New(dbm.NewMemDB(), chainID),
Logger(log.TestingLogger()),
)
require.NoError(t, err)
defer c.Stop()
// should result in downloading & verifying headers #2 and #3
err = c.AutoUpdate(bTime.Add(2 * time.Hour))
require.NoError(t, err)
h, err := c.TrustedHeader(2, bTime.Add(2*time.Hour))
assert.NoError(t, err)
require.NotNil(t, h)
assert.EqualValues(t, 2, h.Height)
h, err = c.TrustedHeader(3, bTime.Add(2*time.Hour))
assert.NoError(t, err)
require.NotNil(t, h)
assert.EqualValues(t, 3, h.Height)
}
func TestClient_Concurrency(t *testing.T) {
const (
chainID = "TestClient_Concurrency"
)
var (
keys = genPrivKeys(4)
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
vals = keys.ToValidators(20, 10)
bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
header = keys.GenSignedHeader(chainID, 1, bTime, nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
)
c, err := NewClient(
chainID,
TrustOptions{
Period: 4 * time.Hour,
Height: 1,
Hash: header.Hash(),
},
mockp.New(
chainID,
map[int64]*types.SignedHeader{
// trusted header
1: header,
// interim header (3/3 signed)
2: keys.GenSignedHeader(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
// last header (3/3 signed)
3: keys.GenSignedHeader(chainID, 3, bTime.Add(1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
3: vals,
4: vals,
},
),
dbs.New(dbm.NewMemDB(), chainID),
UpdatePeriod(0),
Logger(log.TestingLogger()),
)
require.NoError(t, err)
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// NOTE: Cleanup, Stop, VerifyHeaderAtHeight and Verify are not supposed
// to be concurrenly safe.
assert.Equal(t, chainID, c.ChainID())
_, err := c.LastTrustedHeight()
assert.NoError(t, err)
_, err = c.FirstTrustedHeight()
assert.NoError(t, err)
h, err := c.TrustedHeader(1, bTime.Add(2*time.Hour))
assert.NoError(t, err)
assert.NotNil(t, h)
}()
}
wg.Wait()
}

+ 22
- 26
lite2/example_test.go View File

@ -8,8 +8,6 @@ import (
"testing"
"time"
"github.com/pkg/errors"
dbm "github.com/tendermint/tm-db"
"github.com/tendermint/tendermint/abci/example/kvstore"
@ -19,7 +17,8 @@ import (
rpctest "github.com/tendermint/tendermint/rpc/test"
)
func TestExample_Client(t *testing.T) {
// Automatically getting new headers and verifying them.
func TestExample_Client_AutoUpdate(t *testing.T) {
// give Tendermint time to generate some blocks
time.Sleep(5 * time.Second)
@ -58,16 +57,15 @@ func TestExample_Client(t *testing.T) {
},
provider,
dbs.New(db, chainID),
UpdatePeriod(1*time.Second),
Logger(log.TestingLogger()),
)
if err != nil {
stdlog.Fatal(err)
}
c.SetLogger(log.TestingLogger())
defer c.Stop()
_, err = c.VerifyHeaderAtHeight(3, time.Now())
if err != nil {
stdlog.Fatal(err)
}
time.Sleep(2 * time.Second)
h, err := c.TrustedHeader(3, time.Now())
if err != nil {
@ -78,7 +76,8 @@ func TestExample_Client(t *testing.T) {
// Output: got header 3
}
func TestExample_AutoClient(t *testing.T) {
// Manually getting headers and verifying them.
func TestExample_Client_ManualUpdate(t *testing.T) {
// give Tendermint time to generate some blocks
time.Sleep(5 * time.Second)
@ -108,7 +107,7 @@ func TestExample_AutoClient(t *testing.T) {
stdlog.Fatal(err)
}
base, err := NewClient(
c, err := NewClient(
chainID,
TrustOptions{
Period: 504 * time.Hour, // 21 days
@ -117,29 +116,26 @@ func TestExample_AutoClient(t *testing.T) {
},
provider,
dbs.New(db, chainID),
UpdatePeriod(0),
Logger(log.TestingLogger()),
)
if err != nil {
stdlog.Fatal(err)
}
base.SetLogger(log.TestingLogger())
c := NewAutoClient(base, 1*time.Second)
defer c.Stop()
select {
case h := <-c.TrustedHeaders():
fmt.Println("got header", h.Height)
// Output: got header 3
case err := <-c.Errs():
switch errors.Cause(err).(type) {
case ErrOldHeaderExpired:
// reobtain trust height and hash
stdlog.Fatal(err)
default:
// try with another full node
stdlog.Fatal(err)
}
_, err = c.VerifyHeaderAtHeight(3, time.Now())
if err != nil {
stdlog.Fatal(err)
}
h, err := c.TrustedHeader(3, time.Now())
if err != nil {
stdlog.Fatal(err)
}
fmt.Println("got header", h.Height)
// Output: got header 3
}
func TestMain(m *testing.M) {


+ 12
- 0
lite2/provider/errors.go View File

@ -0,0 +1,12 @@
package provider
import "errors"
var (
// ErrSignedHeaderNotFound is returned when a provider can't find the
// requested header.
ErrSignedHeaderNotFound = errors.New("signed header not found")
// ErrValidatorSetNotFound is returned when a provider can't find the
// requested validator set.
ErrValidatorSetNotFound = errors.New("validator set not found")
)

+ 9
- 0
lite2/provider/http/http.go View File

@ -2,6 +2,7 @@ package http
import (
"fmt"
"strings"
"github.com/tendermint/tendermint/lite2/provider"
rpcclient "github.com/tendermint/tendermint/rpc/client"
@ -54,6 +55,10 @@ func (p *http) SignedHeader(height int64) (*types.SignedHeader, error) {
commit, err := p.client.Commit(h)
if err != nil {
// TODO: standartise errors on the RPC side
if strings.Contains(err.Error(), "height must be less than or equal") {
return nil, provider.ErrSignedHeaderNotFound
}
return nil, err
}
@ -76,6 +81,10 @@ func (p *http) ValidatorSet(height int64) (*types.ValidatorSet, error) {
const maxPerPage = 100
res, err := p.client.Validators(h, 0, maxPerPage)
if err != nil {
// TODO: standartise errors on the RPC side
if strings.Contains(err.Error(), "height must be less than or equal") {
return nil, provider.ErrValidatorSetNotFound
}
return nil, err
}


+ 2
- 4
lite2/provider/mock/mock.go View File

@ -1,8 +1,6 @@
package mock
import (
"github.com/pkg/errors"
"github.com/tendermint/tendermint/lite2/provider"
"github.com/tendermint/tendermint/types"
)
@ -32,12 +30,12 @@ func (p *mock) SignedHeader(height int64) (*types.SignedHeader, error) {
if _, ok := p.headers[height]; ok {
return p.headers[height], nil
}
return nil, errors.Errorf("no header at height %d", height)
return nil, provider.ErrSignedHeaderNotFound
}
func (p *mock) ValidatorSet(height int64) (*types.ValidatorSet, error) {
if _, ok := p.vals[height]; ok {
return p.vals[height], nil
}
return nil, errors.Errorf("no vals for height %d", height)
return nil, provider.ErrValidatorSetNotFound
}

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

@ -17,7 +17,7 @@ type Provider interface {
// If the provider fails to fetch the SignedHeader due to the IO or other
// issues, an error will be returned.
// If there's no SignedHeader for the given height, ErrSignedHeaderNotFound
// will be returned.
// error is returned.
SignedHeader(height int64) (*types.SignedHeader, error)
// ValidatorSet returns the ValidatorSet that corresponds to height.
@ -28,6 +28,6 @@ type Provider interface {
// If the provider fails to fetch the ValidatorSet due to the IO or other
// issues, an error will be returned.
// If there's no ValidatorSet for the given height, ErrValidatorSetNotFound
// will be returned.
// error is returned.
ValidatorSet(height int64) (*types.ValidatorSet, error)
}

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

@ -1,6 +1,7 @@
package db
import (
"errors"
"fmt"
"regexp"
"strconv"
@ -78,7 +79,7 @@ func (s *dbs) SignedHeader(height int64) (*types.SignedHeader, error) {
panic(err)
}
if len(bz) == 0 {
return nil, nil
return nil, errors.New("signed header not found")
}
var signedHeader *types.SignedHeader
@ -97,7 +98,7 @@ func (s *dbs) ValidatorSet(height int64) (*types.ValidatorSet, error) {
panic(err)
}
if len(bz) == 0 {
return nil, nil
return nil, errors.New("validator set not found")
}
var valSet *types.ValidatorSet


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

@ -42,11 +42,11 @@ func Test_SaveSignedHeaderAndNextValidatorSet(t *testing.T) {
// Empty store
h, err := dbStore.SignedHeader(1)
require.NoError(t, err)
require.Error(t, err)
assert.Nil(t, h)
valSet, err := dbStore.ValidatorSet(2)
require.NoError(t, err)
require.Error(t, err)
assert.Nil(t, valSet)
// 1 key
@ -67,10 +67,10 @@ func Test_SaveSignedHeaderAndNextValidatorSet(t *testing.T) {
require.NoError(t, err)
h, err = dbStore.SignedHeader(1)
require.NoError(t, err)
require.Error(t, err)
assert.Nil(t, h)
valSet, err = dbStore.ValidatorSet(2)
require.NoError(t, err)
require.Error(t, err)
assert.Nil(t, valSet)
}

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

@ -21,16 +21,14 @@ type Store interface {
//
// height must be > 0.
//
// If the store is empty and the latest SignedHeader is requested, an error
// is returned.
// If SignedHeader is not found, an error is returned.
SignedHeader(height int64) (*types.SignedHeader, error)
// ValidatorSet returns the ValidatorSet that corresponds to height.
//
// height must be > 0.
//
// If the store is empty and the latest ValidatorSet is requested, an error
// is returned.
// If ValidatorSet is not found, an error is returned.
ValidatorSet(height int64) (*types.ValidatorSet, error)
// LastSignedHeaderHeight returns the last (newest) SignedHeader height.


Loading…
Cancel
Save