Rename certifier to light (#784) and add godocs

The certifier package is renamed to light. This is more descriptive
especially in the wider blockchain context. Moreover we are building
light-clients using the light package.

This also adds godocs to all exported functions.

Furthermore it introduces some extra error handling. I've added one TODO
where I would like someone else's opinion on how to handle the error.
This commit is contained in:
Adrian Brink 2017-10-26 11:10:14 +02:00
parent 38c4de3fc7
commit 1871a7c3d0
No known key found for this signature in database
GPG Key ID: 9168AC350E33CEF2
22 changed files with 258 additions and 211 deletions

View File

@ -12,10 +12,11 @@ import (
ctypes "github.com/tendermint/tendermint/rpc/core/types" ctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
"github.com/tendermint/tendermint/certifiers" "github.com/tendermint/tendermint/light"
certerr "github.com/tendermint/tendermint/certifiers/errors" lightErr "github.com/tendermint/tendermint/light/errors"
) )
// SignStatusClient combines a SignClient and StatusClient.
type SignStatusClient interface { type SignStatusClient interface {
rpcclient.SignClient rpcclient.SignClient
rpcclient.StatusClient rpcclient.StatusClient
@ -28,13 +29,13 @@ type provider struct {
// NewProvider can wrap any rpcclient to expose it as // NewProvider can wrap any rpcclient to expose it as
// a read-only provider. // a read-only provider.
func NewProvider(node SignStatusClient) certifiers.Provider { func NewProvider(node SignStatusClient) light.Provider {
return &provider{node: node} return &provider{node: node}
} }
// NewProvider can connects to a tendermint json-rpc endpoint // NewHTTPProvider can connects to a tendermint json-rpc endpoint
// at the given url, and uses that as a read-only provider. // at the given url, and uses that as a read-only provider.
func NewHTTPProvider(remote string) certifiers.Provider { func NewHTTPProvider(remote string) light.Provider {
return &provider{ return &provider{
node: rpcclient.NewHTTP(remote, "/websocket"), node: rpcclient.NewHTTP(remote, "/websocket"),
} }
@ -46,13 +47,13 @@ func (p *provider) StatusClient() rpcclient.StatusClient {
} }
// StoreCommit is a noop, as clients can only read from the chain... // StoreCommit is a noop, as clients can only read from the chain...
func (p *provider) StoreCommit(_ certifiers.FullCommit) error { return nil } func (p *provider) StoreCommit(_ light.FullCommit) error { return nil }
// GetHash gets the most recent validator and sees if it matches // GetHash gets the most recent validator and sees if it matches
// //
// TODO: improve when the rpc interface supports more functionality // TODO: improve when the rpc interface supports more functionality
func (p *provider) GetByHash(hash []byte) (certifiers.FullCommit, error) { func (p *provider) GetByHash(hash []byte) (light.FullCommit, error) {
var fc certifiers.FullCommit var fc light.FullCommit
vals, err := p.node.Validators(nil) vals, err := p.node.Validators(nil)
// if we get no validators, or a different height, return an error // if we get no validators, or a different height, return an error
if err != nil { if err != nil {
@ -61,13 +62,13 @@ func (p *provider) GetByHash(hash []byte) (certifiers.FullCommit, error) {
p.updateHeight(vals.BlockHeight) p.updateHeight(vals.BlockHeight)
vhash := types.NewValidatorSet(vals.Validators).Hash() vhash := types.NewValidatorSet(vals.Validators).Hash()
if !bytes.Equal(hash, vhash) { if !bytes.Equal(hash, vhash) {
return fc, certerr.ErrCommitNotFound() return fc, lightErr.ErrCommitNotFound()
} }
return p.seedFromVals(vals) return p.seedFromVals(vals)
} }
// GetByHeight gets the validator set by height // GetByHeight gets the validator set by height
func (p *provider) GetByHeight(h int) (fc certifiers.FullCommit, err error) { func (p *provider) GetByHeight(h int) (fc light.FullCommit, err error) {
commit, err := p.node.Commit(&h) commit, err := p.node.Commit(&h)
if err != nil { if err != nil {
return fc, err return fc, err
@ -75,7 +76,8 @@ func (p *provider) GetByHeight(h int) (fc certifiers.FullCommit, err error) {
return p.seedFromCommit(commit) return p.seedFromCommit(commit)
} }
func (p *provider) LatestCommit() (fc certifiers.FullCommit, err error) { // LatestCommit returns the newest commit stored.
func (p *provider) LatestCommit() (fc light.FullCommit, err error) {
commit, err := p.GetLatestCommit() commit, err := p.GetLatestCommit()
if err != nil { if err != nil {
return fc, err return fc, err
@ -94,24 +96,25 @@ func (p *provider) GetLatestCommit() (*ctypes.ResultCommit, error) {
return p.node.Commit(&status.LatestBlockHeight) return p.node.Commit(&status.LatestBlockHeight)
} }
func CommitFromResult(result *ctypes.ResultCommit) certifiers.Commit { // CommitFromResult ...
return (certifiers.Commit)(result.SignedHeader) func CommitFromResult(result *ctypes.ResultCommit) light.Commit {
return (light.Commit)(result.SignedHeader)
} }
func (p *provider) seedFromVals(vals *ctypes.ResultValidators) (certifiers.FullCommit, error) { func (p *provider) seedFromVals(vals *ctypes.ResultValidators) (light.FullCommit, error) {
// now get the commits and build a full commit // now get the commits and build a full commit
commit, err := p.node.Commit(&vals.BlockHeight) commit, err := p.node.Commit(&vals.BlockHeight)
if err != nil { if err != nil {
return certifiers.FullCommit{}, err return light.FullCommit{}, err
} }
fc := certifiers.NewFullCommit( fc := light.NewFullCommit(
CommitFromResult(commit), CommitFromResult(commit),
types.NewValidatorSet(vals.Validators), types.NewValidatorSet(vals.Validators),
) )
return fc, nil return fc, nil
} }
func (p *provider) seedFromCommit(commit *ctypes.ResultCommit) (fc certifiers.FullCommit, err error) { func (p *provider) seedFromCommit(commit *ctypes.ResultCommit) (fc light.FullCommit, err error) {
fc.Commit = CommitFromResult(commit) fc.Commit = CommitFromResult(commit)
// now get the proper validators // now get the proper validators
@ -123,7 +126,7 @@ func (p *provider) seedFromCommit(commit *ctypes.ResultCommit) (fc certifiers.Fu
// make sure they match the commit (as we cannot enforce height) // make sure they match the commit (as we cannot enforce height)
vset := types.NewValidatorSet(vals.Validators) vset := types.NewValidatorSet(vals.Validators)
if !bytes.Equal(vset.Hash(), commit.Header.ValidatorsHash) { if !bytes.Equal(vset.Hash(), commit.Header.ValidatorsHash) {
return fc, certerr.ErrValidatorsChanged() return fc, lightErr.ErrValidatorsChanged()
} }
p.updateHeight(commit.Header.Height) p.updateHeight(commit.Header.Height)

View File

@ -6,8 +6,8 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/certifiers" "github.com/tendermint/tendermint/light"
certerr "github.com/tendermint/tendermint/certifiers/errors" lightErr "github.com/tendermint/tendermint/light/errors"
rpcclient "github.com/tendermint/tendermint/rpc/client" rpcclient "github.com/tendermint/tendermint/rpc/client"
rpctest "github.com/tendermint/tendermint/rpc/test" rpctest "github.com/tendermint/tendermint/rpc/test"
) )
@ -35,7 +35,7 @@ func TestProvider(t *testing.T) {
// let's check this is valid somehow // let's check this is valid somehow
assert.Nil(seed.ValidateBasic(chainID)) assert.Nil(seed.ValidateBasic(chainID))
cert := certifiers.NewStatic(chainID, seed.Validators) cert := light.NewStatic(chainID, seed.Validators)
// historical queries now work :) // historical queries now work :)
lower := sh - 5 lower := sh - 5
@ -53,7 +53,7 @@ func TestProvider(t *testing.T) {
// get by hash fails without match // get by hash fails without match
seed, err = p.GetByHash([]byte("foobar")) seed, err = p.GetByHash([]byte("foobar"))
assert.NotNil(err) assert.NotNil(err)
assert.True(certerr.IsCommitNotFoundErr(err)) assert.True(lightErr.IsCommitNotFoundErr(err))
// storing the seed silently ignored // storing the seed silently ignored
err = p.StoreCommit(seed) err = p.StoreCommit(seed)

View File

@ -1,4 +1,4 @@
package certifiers package light
import ( import (
"bytes" "bytes"
@ -7,7 +7,7 @@ import (
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
certerr "github.com/tendermint/tendermint/certifiers/errors" lightErr "github.com/tendermint/tendermint/light/errors"
) )
// Certifier checks the votes to make sure the block really is signed properly. // Certifier checks the votes to make sure the block really is signed properly.
@ -33,6 +33,7 @@ type FullCommit struct {
Validators *types.ValidatorSet `json:"validator_set"` Validators *types.ValidatorSet `json:"validator_set"`
} }
// NewFullCommit returns a new FullCommit.
func NewFullCommit(commit Commit, vals *types.ValidatorSet) FullCommit { func NewFullCommit(commit Commit, vals *types.ValidatorSet) FullCommit {
return FullCommit{ return FullCommit{
Commit: commit, Commit: commit,
@ -40,6 +41,7 @@ func NewFullCommit(commit Commit, vals *types.ValidatorSet) FullCommit {
} }
} }
// Height returns the of the header.
func (c Commit) Height() int { func (c Commit) Height() int {
if c.Header == nil { if c.Header == nil {
return 0 return 0
@ -47,6 +49,7 @@ func (c Commit) Height() int {
return c.Header.Height return c.Header.Height
} }
// ValidatorsHash returns the hash of the validator set.
func (c Commit) ValidatorsHash() []byte { func (c Commit) ValidatorsHash() []byte {
if c.Header == nil { if c.Header == nil {
return nil return nil
@ -75,7 +78,7 @@ func (c Commit) ValidateBasic(chainID string) error {
// make sure the header and commit match (height and hash) // make sure the header and commit match (height and hash)
if c.Commit.Height() != c.Header.Height { if c.Commit.Height() != c.Header.Height {
return certerr.ErrHeightMismatch(c.Commit.Height(), c.Header.Height) return lightErr.ErrHeightMismatch(c.Commit.Height(), c.Header.Height)
} }
hhash := c.Header.Hash() hhash := c.Header.Hash()
chash := c.Commit.BlockID.Hash chash := c.Commit.BlockID.Hash

View File

@ -1,5 +1,5 @@
/* /*
Package certifiers allows you to securely validate headers Package light allows you to securely validate headers
without a full node. without a full node.
This library pulls together all the crypto and algorithms, This library pulls together all the crypto and algorithms,
@ -130,4 +130,4 @@ to manually verify the new validator set hash using off-chain
means (the same as getting the initial hash). means (the same as getting the initial hash).
*/ */
package certifiers package light

View File

@ -1,9 +1,9 @@
package certifiers package light
import ( import (
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
certerr "github.com/tendermint/tendermint/certifiers/errors" lightErr "github.com/tendermint/tendermint/light/errors"
) )
var _ Certifier = &Dynamic{} var _ Certifier = &Dynamic{}
@ -22,6 +22,7 @@ type Dynamic struct {
lastHeight int lastHeight int
} }
// NewDynamic returns a new dynamic certifier.
func NewDynamic(chainID string, vals *types.ValidatorSet, height int) *Dynamic { func NewDynamic(chainID string, vals *types.ValidatorSet, height int) *Dynamic {
return &Dynamic{ return &Dynamic{
cert: NewStatic(chainID, vals), cert: NewStatic(chainID, vals),
@ -29,23 +30,28 @@ func NewDynamic(chainID string, vals *types.ValidatorSet, height int) *Dynamic {
} }
} }
// ChainID returns the chain id of this certifier.
func (c *Dynamic) ChainID() string { func (c *Dynamic) ChainID() string {
return c.cert.ChainID() return c.cert.ChainID()
} }
// Validators returns the validators of this certifier.
func (c *Dynamic) Validators() *types.ValidatorSet { func (c *Dynamic) Validators() *types.ValidatorSet {
return c.cert.vSet return c.cert.vSet
} }
// Hash returns the hash of this certifier.
func (c *Dynamic) Hash() []byte { func (c *Dynamic) Hash() []byte {
return c.cert.Hash() return c.cert.Hash()
} }
// LastHeight returns the last height of this certifier.
func (c *Dynamic) LastHeight() int { func (c *Dynamic) LastHeight() int {
return c.lastHeight return c.lastHeight
} }
// Certify handles this with // Certify will verify whether the commit is valid and will update the height if it is or return an
// error if it is not.
func (c *Dynamic) Certify(check Commit) error { func (c *Dynamic) Certify(check Commit) error {
err := c.cert.Certify(check) err := c.cert.Certify(check)
if err == nil { if err == nil {
@ -63,7 +69,7 @@ func (c *Dynamic) Update(fc FullCommit) error {
// ignore all checkpoints in the past -> only to the future // ignore all checkpoints in the past -> only to the future
h := fc.Height() h := fc.Height()
if h <= c.lastHeight { if h <= c.lastHeight {
return certerr.ErrPastTime() return lightErr.ErrPastTime()
} }
// first, verify if the input is self-consistent.... // first, verify if the input is self-consistent....
@ -79,7 +85,7 @@ func (c *Dynamic) Update(fc FullCommit) error {
err = c.Validators().VerifyCommitAny(fc.Validators, c.ChainID(), err = c.Validators().VerifyCommitAny(fc.Validators, c.ChainID(),
commit.BlockID, h, commit) commit.BlockID, h, commit)
if err != nil { if err != nil {
return certerr.ErrTooMuchChange() return lightErr.ErrTooMuchChange()
} }
// looks good, we can update // looks good, we can update

View File

@ -1,4 +1,4 @@
package certifiers_test package light_test
import ( import (
"testing" "testing"
@ -8,8 +8,8 @@ import (
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
"github.com/tendermint/tendermint/certifiers" "github.com/tendermint/tendermint/light"
"github.com/tendermint/tendermint/certifiers/errors" "github.com/tendermint/tendermint/light/errors"
) )
// TestDynamicCert just makes sure it still works like StaticCert // TestDynamicCert just makes sure it still works like StaticCert
@ -18,15 +18,15 @@ func TestDynamicCert(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
// require := require.New(t) // require := require.New(t)
keys := certifiers.GenValKeys(4) keys := light.GenValKeys(4)
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do! // 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
vals := keys.ToValidators(20, 10) vals := keys.ToValidators(20, 10)
// and a certifier based on our known set // and a certifier based on our known set
chainID := "test-dyno" chainID := "test-dyno"
cert := certifiers.NewDynamic(chainID, vals, 0) cert := light.NewDynamic(chainID, vals, 0)
cases := []struct { cases := []struct {
keys certifiers.ValKeys keys light.ValKeys
vals *types.ValidatorSet vals *types.ValidatorSet
height int height int
first, last int // who actually signs first, last int // who actually signs
@ -65,9 +65,9 @@ func TestDynamicUpdate(t *testing.T) {
assert, require := assert.New(t), require.New(t) assert, require := assert.New(t), require.New(t)
chainID := "test-dyno-up" chainID := "test-dyno-up"
keys := certifiers.GenValKeys(5) keys := light.GenValKeys(5)
vals := keys.ToValidators(20, 0) vals := keys.ToValidators(20, 0)
cert := certifiers.NewDynamic(chainID, vals, 40) cert := light.NewDynamic(chainID, vals, 40)
// one valid block to give us a sense of time // one valid block to give us a sense of time
h := 100 h := 100
@ -81,7 +81,7 @@ func TestDynamicUpdate(t *testing.T) {
// we try to update with some blocks // we try to update with some blocks
cases := []struct { cases := []struct {
keys certifiers.ValKeys keys light.ValKeys
vals *types.ValidatorSet vals *types.ValidatorSet
height int height int
first, last int // who actually signs first, last int // who actually signs

View File

@ -19,34 +19,39 @@ func IsCommitNotFoundErr(err error) bool {
return err != nil && (errors.Cause(err) == errCommitNotFound) return err != nil && (errors.Cause(err) == errCommitNotFound)
} }
// ErrCommitNotFound indicates that a the requested commit was not found.
func ErrCommitNotFound() error { func ErrCommitNotFound() error {
return errors.WithStack(errCommitNotFound) return errors.WithStack(errCommitNotFound)
} }
// IsValidatorsChangedErr checks whether an error is due // IsValidatorsChangedErr checks whether an error is due
// to a differing validator set // to a differing validator set.
func IsValidatorsChangedErr(err error) bool { func IsValidatorsChangedErr(err error) bool {
return err != nil && (errors.Cause(err) == errValidatorsChanged) return err != nil && (errors.Cause(err) == errValidatorsChanged)
} }
// ErrValidatorsChanged indicates that the validator set was changed between two commits.
func ErrValidatorsChanged() error { func ErrValidatorsChanged() error {
return errors.WithStack(errValidatorsChanged) return errors.WithStack(errValidatorsChanged)
} }
// IsTooMuchChangeErr checks whether an error is due to too much change // IsTooMuchChangeErr checks whether an error is due to too much change
// between these validators sets // between these validators sets.
func IsTooMuchChangeErr(err error) bool { func IsTooMuchChangeErr(err error) bool {
return err != nil && (errors.Cause(err) == errTooMuchChange) return err != nil && (errors.Cause(err) == errTooMuchChange)
} }
// ErrTooMuchChange indicates that the underlying validator set was changed by >1/3.
func ErrTooMuchChange() error { func ErrTooMuchChange() error {
return errors.WithStack(errTooMuchChange) return errors.WithStack(errTooMuchChange)
} }
// IsPastTimeErr ...
func IsPastTimeErr(err error) bool { func IsPastTimeErr(err error) bool {
return err != nil && (errors.Cause(err) == errPastTime) return err != nil && (errors.Cause(err) == errPastTime)
} }
// ErrPastTime ...
func ErrPastTime() error { func ErrPastTime() error {
return errors.WithStack(errPastTime) return errors.WithStack(errPastTime)
} }
@ -57,6 +62,7 @@ func IsNoPathFoundErr(err error) bool {
return err != nil && (errors.Cause(err) == errNoPathFound) return err != nil && (errors.Cause(err) == errNoPathFound)
} }
// ErrNoPathFound ...
func ErrNoPathFound() error { func ErrNoPathFound() error {
return errors.WithStack(errNoPathFound) return errors.WithStack(errNoPathFound)
} }

View File

@ -8,8 +8,8 @@ import (
wire "github.com/tendermint/go-wire" wire "github.com/tendermint/go-wire"
"github.com/tendermint/tendermint/certifiers" "github.com/tendermint/tendermint/light"
certerr "github.com/tendermint/tendermint/certifiers/errors" lightErr "github.com/tendermint/tendermint/light/errors"
) )
const ( const (
@ -20,7 +20,7 @@ const (
) )
// SaveFullCommit exports the seed in binary / go-wire style // SaveFullCommit exports the seed in binary / go-wire style
func SaveFullCommit(fc certifiers.FullCommit, path string) error { func SaveFullCommit(fc light.FullCommit, path string) error {
f, err := os.Create(path) f, err := os.Create(path)
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)
@ -33,7 +33,7 @@ func SaveFullCommit(fc certifiers.FullCommit, path string) error {
} }
// SaveFullCommitJSON exports the seed in a json format // SaveFullCommitJSON exports the seed in a json format
func SaveFullCommitJSON(fc certifiers.FullCommit, path string) error { func SaveFullCommitJSON(fc light.FullCommit, path string) error {
f, err := os.Create(path) f, err := os.Create(path)
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)
@ -44,12 +44,13 @@ func SaveFullCommitJSON(fc certifiers.FullCommit, path string) error {
return errors.WithStack(err) return errors.WithStack(err)
} }
func LoadFullCommit(path string) (certifiers.FullCommit, error) { // LoadFullCommit loads the full commit from the file system.
var fc certifiers.FullCommit func LoadFullCommit(path string) (light.FullCommit, error) {
var fc light.FullCommit
f, err := os.Open(path) f, err := os.Open(path)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
return fc, certerr.ErrCommitNotFound() return fc, lightErr.ErrCommitNotFound()
} }
return fc, errors.WithStack(err) return fc, errors.WithStack(err)
} }
@ -60,12 +61,13 @@ func LoadFullCommit(path string) (certifiers.FullCommit, error) {
return fc, errors.WithStack(err) return fc, errors.WithStack(err)
} }
func LoadFullCommitJSON(path string) (certifiers.FullCommit, error) { // LoadFullCommitJSON loads the commit from the file system in JSON format.
var fc certifiers.FullCommit func LoadFullCommitJSON(path string) (light.FullCommit, error) {
var fc light.FullCommit
f, err := os.Open(path) f, err := os.Open(path)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
return fc, certerr.ErrCommitNotFound() return fc, lightErr.ErrCommitNotFound()
} }
return fc, errors.WithStack(err) return fc, errors.WithStack(err)
} }

View File

@ -10,7 +10,7 @@ import (
cmn "github.com/tendermint/tmlibs/common" cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tendermint/certifiers" "github.com/tendermint/tendermint/light"
) )
func tmpFile() string { func tmpFile() string {
@ -27,7 +27,7 @@ func TestSerializeFullCommits(t *testing.T) {
h := 25 h := 25
// build a fc // build a fc
keys := certifiers.GenValKeys(5) keys := light.GenValKeys(5)
vals := keys.ToValidators(10, 0) vals := keys.ToValidators(10, 0)
fc := keys.GenFullCommit(chainID, h, nil, vals, appHash, 0, 5) fc := keys.GenFullCommit(chainID, h, nil, vals, appHash, 0, 5)

View File

@ -24,10 +24,11 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/tendermint/tendermint/certifiers" "github.com/tendermint/tendermint/light"
certerr "github.com/tendermint/tendermint/certifiers/errors" lightErr "github.com/tendermint/tendermint/light/errors"
) )
// nolint
const ( const (
Ext = ".tsd" Ext = ".tsd"
ValDir = "validators" ValDir = "validators"
@ -43,7 +44,7 @@ type provider struct {
// NewProvider creates the parent dir and subdirs // NewProvider creates the parent dir and subdirs
// for validators and checkpoints as needed // for validators and checkpoints as needed
func NewProvider(dir string) certifiers.Provider { func NewProvider(dir string) light.Provider {
valDir := filepath.Join(dir, ValDir) valDir := filepath.Join(dir, ValDir)
checkDir := filepath.Join(dir, CheckDir) checkDir := filepath.Join(dir, CheckDir)
for _, d := range []string{valDir, checkDir} { for _, d := range []string{valDir, checkDir} {
@ -64,7 +65,8 @@ func (p *provider) encodeHeight(h int) string {
return fmt.Sprintf("%012d%s", h, Ext) return fmt.Sprintf("%012d%s", h, Ext)
} }
func (p *provider) StoreCommit(fc certifiers.FullCommit) error { // StoreCommit saves a full commit after it has been verified.
func (p *provider) StoreCommit(fc light.FullCommit) error {
// make sure the fc is self-consistent before saving // make sure the fc is self-consistent before saving
err := fc.ValidateBasic(fc.Commit.Header.ChainID) err := fc.ValidateBasic(fc.Commit.Header.ChainID)
if err != nil { if err != nil {
@ -85,11 +87,12 @@ func (p *provider) StoreCommit(fc certifiers.FullCommit) error {
return nil return nil
} }
func (p *provider) GetByHeight(h int) (certifiers.FullCommit, error) { // GetByHeight returns the closest commit with height <= h.
func (p *provider) GetByHeight(h int) (light.FullCommit, error) {
// first we look for exact match, then search... // first we look for exact match, then search...
path := filepath.Join(p.checkDir, p.encodeHeight(h)) path := filepath.Join(p.checkDir, p.encodeHeight(h))
fc, err := LoadFullCommit(path) fc, err := LoadFullCommit(path)
if certerr.IsCommitNotFoundErr(err) { if lightErr.IsCommitNotFoundErr(err) {
path, err = p.searchForHeight(h) path, err = p.searchForHeight(h)
if err == nil { if err == nil {
fc, err = LoadFullCommit(path) fc, err = LoadFullCommit(path)
@ -98,7 +101,8 @@ func (p *provider) GetByHeight(h int) (certifiers.FullCommit, error) {
return fc, err return fc, err
} }
func (p *provider) LatestCommit() (fc certifiers.FullCommit, err error) { // LatestCommit returns the newest commit stored.
func (p *provider) LatestCommit() (fc light.FullCommit, err error) {
// Note to future: please update by 2077 to avoid rollover // Note to future: please update by 2077 to avoid rollover
return p.GetByHeight(math.MaxInt32 - 1) return p.GetByHeight(math.MaxInt32 - 1)
} }
@ -121,14 +125,15 @@ func (p *provider) searchForHeight(h int) (string, error) {
sort.Strings(files) sort.Strings(files)
i := sort.SearchStrings(files, desired) i := sort.SearchStrings(files, desired)
if i == 0 { if i == 0 {
return "", certerr.ErrCommitNotFound() return "", lightErr.ErrCommitNotFound()
} }
found := files[i-1] found := files[i-1]
path := filepath.Join(p.checkDir, found) path := filepath.Join(p.checkDir, found)
return path, errors.WithStack(err) return path, errors.WithStack(err)
} }
func (p *provider) GetByHash(hash []byte) (certifiers.FullCommit, error) { // GetByHash returns a commit exactly matching this validator hash.
func (p *provider) GetByHash(hash []byte) (light.FullCommit, error) {
path := filepath.Join(p.valDir, p.encodeHash(hash)) path := filepath.Join(p.valDir, p.encodeHash(hash))
return LoadFullCommit(path) return LoadFullCommit(path)
} }

View File

@ -10,12 +10,12 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/certifiers" "github.com/tendermint/tendermint/light"
certerr "github.com/tendermint/tendermint/certifiers/errors" lightErr "github.com/tendermint/tendermint/light/errors"
"github.com/tendermint/tendermint/certifiers/files" "github.com/tendermint/tendermint/light/files"
) )
func checkEqual(stored, loaded certifiers.FullCommit, chainID string) error { func checkEqual(stored, loaded light.FullCommit, chainID string) error {
err := loaded.ValidateBasic(chainID) err := loaded.ValidateBasic(chainID)
if err != nil { if err != nil {
return err return err
@ -36,28 +36,28 @@ func TestFileProvider(t *testing.T) {
chainID := "test-files" chainID := "test-files"
appHash := []byte("some-data") appHash := []byte("some-data")
keys := certifiers.GenValKeys(5) keys := light.GenValKeys(5)
count := 10 count := 10
// make a bunch of seeds... // make a bunch of seeds...
seeds := make([]certifiers.FullCommit, count) seeds := make([]light.FullCommit, count)
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
// two seeds for each validator, to check how we handle dups // two seeds for each validator, to check how we handle dups
// (10, 0), (10, 1), (10, 1), (10, 2), (10, 2), ... // (10, 0), (10, 1), (10, 1), (10, 2), (10, 2), ...
vals := keys.ToValidators(10, int64(count/2)) vals := keys.ToValidators(10, int64(count/2))
h := 20 + 10*i h := 20 + 10*i
check := keys.GenCommit(chainID, h, nil, vals, appHash, 0, 5) check := keys.GenCommit(chainID, h, nil, vals, appHash, 0, 5)
seeds[i] = certifiers.NewFullCommit(check, vals) seeds[i] = light.NewFullCommit(check, vals)
} }
// check provider is empty // check provider is empty
seed, err := p.GetByHeight(20) seed, err := p.GetByHeight(20)
require.NotNil(err) require.NotNil(err)
assert.True(certerr.IsCommitNotFoundErr(err)) assert.True(lightErr.IsCommitNotFoundErr(err))
seed, err = p.GetByHash(seeds[3].ValidatorsHash()) seed, err = p.GetByHash(seeds[3].ValidatorsHash())
require.NotNil(err) require.NotNil(err)
assert.True(certerr.IsCommitNotFoundErr(err)) assert.True(lightErr.IsCommitNotFoundErr(err))
// now add them all to the provider // now add them all to the provider
for _, s := range seeds { for _, s := range seeds {
@ -92,5 +92,5 @@ func TestFileProvider(t *testing.T) {
// and proper error for too low // and proper error for too low
_, err = p.GetByHeight(5) _, err = p.GetByHeight(5)
assert.NotNil(err) assert.NotNil(err)
assert.True(certerr.IsCommitNotFoundErr(err)) assert.True(lightErr.IsCommitNotFoundErr(err))
} }

View File

@ -1,4 +1,4 @@
package certifiers package light
import ( import (
"time" "time"
@ -12,14 +12,14 @@ import (
// //
// It lets us simulate signing with many keys, either ed25519 or secp256k1. // It lets us simulate signing with many keys, either ed25519 or secp256k1.
// The main use case is to create a set, and call GenCommit // The main use case is to create a set, and call GenCommit
// to get propely signed header for testing. // to get properly signed header for testing.
// //
// You can set different weights of validators each time you call // You can set different weights of validators each time you call
// ToValidators, and can optionally extend the validator set later // ToValidators, and can optionally extend the validator set later
// with Extend or ExtendSecp // with Extend or ExtendSecp
type ValKeys []crypto.PrivKey type ValKeys []crypto.PrivKey
// GenValKeys produces an array of private keys to generate commits // GenValKeys produces an array of private keys to generate commits.
func GenValKeys(n int) ValKeys { func GenValKeys(n int) ValKeys {
res := make(ValKeys, n) res := make(ValKeys, n)
for i := range res { for i := range res {
@ -28,7 +28,7 @@ func GenValKeys(n int) ValKeys {
return res return res
} }
// Change replaces the key at index i // Change replaces the key at index i.
func (v ValKeys) Change(i int) ValKeys { func (v ValKeys) Change(i int) ValKeys {
res := make(ValKeys, len(v)) res := make(ValKeys, len(v))
copy(res, v) copy(res, v)
@ -36,13 +36,13 @@ func (v ValKeys) Change(i int) ValKeys {
return res return res
} }
// Extend adds n more keys (to remove, just take a slice) // Extend adds n more keys (to remove, just take a slice).
func (v ValKeys) Extend(n int) ValKeys { func (v ValKeys) Extend(n int) ValKeys {
extra := GenValKeys(n) extra := GenValKeys(n)
return append(v, extra...) return append(v, extra...)
} }
// GenSecpValKeys produces an array of secp256k1 private keys to generate commits // GenSecpValKeys produces an array of secp256k1 private keys to generate commits.
func GenSecpValKeys(n int) ValKeys { func GenSecpValKeys(n int) ValKeys {
res := make(ValKeys, n) res := make(ValKeys, n)
for i := range res { for i := range res {
@ -51,7 +51,7 @@ func GenSecpValKeys(n int) ValKeys {
return res return res
} }
// ExtendSecp adds n more secp256k1 keys (to remove, just take a slice) // ExtendSecp adds n more secp256k1 keys (to remove, just take a slice).
func (v ValKeys) ExtendSecp(n int) ValKeys { func (v ValKeys) ExtendSecp(n int) ValKeys {
extra := GenSecpValKeys(n) extra := GenSecpValKeys(n)
return append(v, extra...) return append(v, extra...)
@ -60,7 +60,7 @@ func (v ValKeys) ExtendSecp(n int) ValKeys {
// ToValidators produces a list of validators from the set of keys // ToValidators produces a list of validators from the set of keys
// The first key has weight `init` and it increases by `inc` every step // The first key has weight `init` and it increases by `inc` every step
// so we can have all the same weight, or a simple linear distribution // so we can have all the same weight, or a simple linear distribution
// (should be enough for testing) // (should be enough for testing).
func (v ValKeys) ToValidators(init, inc int64) *types.ValidatorSet { func (v ValKeys) ToValidators(init, inc int64) *types.ValidatorSet {
res := make([]*types.Validator, len(v)) res := make([]*types.Validator, len(v))
for i, k := range v { for i, k := range v {
@ -69,7 +69,7 @@ func (v ValKeys) ToValidators(init, inc int64) *types.ValidatorSet {
return types.NewValidatorSet(res) return types.NewValidatorSet(res)
} }
// signHeader properly signs the header with all keys from first to last exclusive // signHeader properly signs the header with all keys from first to last exclusive.
func (v ValKeys) signHeader(header *types.Header, first, last int) *types.Commit { func (v ValKeys) signHeader(header *types.Header, first, last int) *types.Commit {
votes := make([]*types.Vote, len(v)) votes := make([]*types.Vote, len(v))
@ -106,6 +106,8 @@ func makeVote(header *types.Header, vals *types.ValidatorSet, key crypto.PrivKey
return vote return vote
} }
// Silences warning that vals can also be merkle.Hashable
// nolint: interfacer
func genHeader(chainID string, height int, txs types.Txs, func genHeader(chainID string, height int, txs types.Txs,
vals *types.ValidatorSet, appHash []byte) *types.Header { vals *types.ValidatorSet, appHash []byte) *types.Header {
@ -122,7 +124,7 @@ func genHeader(chainID string, height int, txs types.Txs,
} }
} }
// GenCommit calls genHeader and signHeader and combines them into a Commit // GenCommit calls genHeader and signHeader and combines them into a Commit.
func (v ValKeys) GenCommit(chainID string, height int, txs types.Txs, func (v ValKeys) GenCommit(chainID string, height int, txs types.Txs,
vals *types.ValidatorSet, appHash []byte, first, last int) Commit { vals *types.ValidatorSet, appHash []byte, first, last int) Commit {
@ -134,7 +136,7 @@ func (v ValKeys) GenCommit(chainID string, height int, txs types.Txs,
return check return check
} }
// GenFullCommit calls genHeader and signHeader and combines them into a Commit // GenFullCommit calls genHeader and signHeader and combines them into a Commit.
func (v ValKeys) GenFullCommit(chainID string, height int, txs types.Txs, func (v ValKeys) GenFullCommit(chainID string, height int, txs types.Txs,
vals *types.ValidatorSet, appHash []byte, first, last int) FullCommit { vals *types.ValidatorSet, appHash []byte, first, last int) FullCommit {

View File

@ -1,11 +1,15 @@
package certifiers package light
import ( import (
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
certerr "github.com/tendermint/tendermint/certifiers/errors" lightErr "github.com/tendermint/tendermint/light/errors"
) )
// Inquiring wraps a dynamic certifier and implements an auto-update strategy. If a call to Certify
// fails due to a change it validator set, Inquiring will try and find a previous FullCommit which
// it can use to safely update the validator set. It uses a source provider to obtain the needed
// FullCommits. It stores properly validated data on the local system.
type Inquiring struct { type Inquiring struct {
cert *Dynamic cert *Dynamic
// These are only properly validated data, from local system // These are only properly validated data, from local system
@ -14,8 +18,14 @@ type Inquiring struct {
Source Provider Source Provider
} }
// NewInquiring returns a new Inquiring object. It uses the trusted provider to store validated
// data and the source provider to obtain missing FullCommits.
//
// Example: The trusted provider should a CacheProvider, MemProvider or files.Provider. The source
// provider should be a client.HTTPProvider.
func NewInquiring(chainID string, fc FullCommit, trusted Provider, source Provider) *Inquiring { func NewInquiring(chainID string, fc FullCommit, trusted Provider, source Provider) *Inquiring {
// store the data in trusted // store the data in trusted
// TODO: StoredCommit() can return an error and we need to handle this.
trusted.StoreCommit(fc) trusted.StoreCommit(fc)
return &Inquiring{ return &Inquiring{
@ -25,14 +35,17 @@ func NewInquiring(chainID string, fc FullCommit, trusted Provider, source Provid
} }
} }
// ChainID returns the chain id.
func (c *Inquiring) ChainID() string { func (c *Inquiring) ChainID() string {
return c.cert.ChainID() return c.cert.ChainID()
} }
// Validators returns the validator set.
func (c *Inquiring) Validators() *types.ValidatorSet { func (c *Inquiring) Validators() *types.ValidatorSet {
return c.cert.cert.vSet return c.cert.cert.vSet
} }
// LastHeight returns the last height.
func (c *Inquiring) LastHeight() int { func (c *Inquiring) LastHeight() int {
return c.cert.lastHeight return c.cert.lastHeight
} }
@ -50,7 +63,7 @@ func (c *Inquiring) Certify(commit Commit) error {
} }
err = c.cert.Certify(commit) err = c.cert.Certify(commit)
if !certerr.IsValidatorsChangedErr(err) { if !lightErr.IsValidatorsChangedErr(err) {
return err return err
} }
err = c.updateToHash(commit.Header.ValidatorsHash) err = c.updateToHash(commit.Header.ValidatorsHash)
@ -64,11 +77,11 @@ func (c *Inquiring) Certify(commit Commit) error {
} }
// store the new checkpoint // store the new checkpoint
c.trusted.StoreCommit( return c.trusted.StoreCommit(NewFullCommit(commit, c.Validators()))
NewFullCommit(commit, c.Validators()))
return nil
} }
// Update will verify if this is a valid change and update
// the certifying validator set if safe to do so.
func (c *Inquiring) Update(fc FullCommit) error { func (c *Inquiring) Update(fc FullCommit) error {
err := c.useClosestTrust(fc.Height()) err := c.useClosestTrust(fc.Height())
if err != nil { if err != nil {
@ -77,7 +90,7 @@ func (c *Inquiring) Update(fc FullCommit) error {
err = c.cert.Update(fc) err = c.cert.Update(fc)
if err == nil { if err == nil {
c.trusted.StoreCommit(fc) err = c.trusted.StoreCommit(fc)
} }
return err return err
} }
@ -106,7 +119,7 @@ func (c *Inquiring) updateToHash(vhash []byte) error {
} }
err = c.cert.Update(fc) err = c.cert.Update(fc)
// handle IsTooMuchChangeErr by using divide and conquer // handle IsTooMuchChangeErr by using divide and conquer
if certerr.IsTooMuchChangeErr(err) { if lightErr.IsTooMuchChangeErr(err) {
err = c.updateToHeight(fc.Height()) err = c.updateToHeight(fc.Height())
} }
return err return err
@ -121,12 +134,12 @@ func (c *Inquiring) updateToHeight(h int) error {
} }
start, end := c.LastHeight(), fc.Height() start, end := c.LastHeight(), fc.Height()
if end <= start { if end <= start {
return certerr.ErrNoPathFound() return lightErr.ErrNoPathFound()
} }
err = c.Update(fc) err = c.Update(fc)
// we can handle IsTooMuchChangeErr specially // we can handle IsTooMuchChangeErr specially
if !certerr.IsTooMuchChangeErr(err) { if !lightErr.IsTooMuchChangeErr(err) {
return err return err
} }

View File

@ -1,4 +1,5 @@
package certifiers_test // nolint: vetshadow
package light_test
import ( import (
"fmt" "fmt"
@ -7,34 +8,33 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/certifiers" "github.com/tendermint/tendermint/light"
) )
func TestInquirerValidPath(t *testing.T) { func TestInquirerValidPath(t *testing.T) {
assert, require := assert.New(t), require.New(t) assert, require := assert.New(t), require.New(t)
trust := certifiers.NewMemStoreProvider() trust := light.NewMemStoreProvider()
source := certifiers.NewMemStoreProvider() source := light.NewMemStoreProvider()
// set up the validators to generate test blocks // set up the validators to generate test blocks
var vote int64 = 10 var vote int64 = 10
keys := certifiers.GenValKeys(5) keys := light.GenValKeys(5)
vals := keys.ToValidators(vote, 0)
// construct a bunch of commits, each with one more height than the last // construct a bunch of commits, each with one more height than the last
chainID := "inquiry-test" chainID := "inquiry-test"
count := 50 count := 50
commits := make([]certifiers.FullCommit, count) commits := make([]light.FullCommit, count)
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
// extend the keys by 1 each time // extend the keys by 1 each time
keys = keys.Extend(1) keys = keys.Extend(1)
vals = keys.ToValidators(vote, 0) vals := keys.ToValidators(vote, 0)
h := 20 + 10*i h := 20 + 10*i
appHash := []byte(fmt.Sprintf("h=%d", h)) appHash := []byte(fmt.Sprintf("h=%d", h))
commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, 0, len(keys)) commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, 0, len(keys))
} }
// initialize a certifier with the initial state // initialize a certifier with the initial state
cert := certifiers.NewInquiring(chainID, commits[0], trust, source) cert := light.NewInquiring(chainID, commits[0], trust, source)
// this should fail validation.... // this should fail validation....
commit := commits[count-1].Commit commit := commits[count-1].Commit
@ -60,29 +60,28 @@ func TestInquirerValidPath(t *testing.T) {
func TestInquirerMinimalPath(t *testing.T) { func TestInquirerMinimalPath(t *testing.T) {
assert, require := assert.New(t), require.New(t) assert, require := assert.New(t), require.New(t)
trust := certifiers.NewMemStoreProvider() trust := light.NewMemStoreProvider()
source := certifiers.NewMemStoreProvider() source := light.NewMemStoreProvider()
// set up the validators to generate test blocks // set up the validators to generate test blocks
var vote int64 = 10 var vote int64 = 10
keys := certifiers.GenValKeys(5) keys := light.GenValKeys(5)
vals := keys.ToValidators(vote, 0)
// construct a bunch of commits, each with one more height than the last // construct a bunch of commits, each with one more height than the last
chainID := "minimal-path" chainID := "minimal-path"
count := 12 count := 12
commits := make([]certifiers.FullCommit, count) commits := make([]light.FullCommit, count)
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
// extend the validators, so we are just below 2/3 // extend the validators, so we are just below 2/3
keys = keys.Extend(len(keys)/2 - 1) keys = keys.Extend(len(keys)/2 - 1)
vals = keys.ToValidators(vote, 0) vals := keys.ToValidators(vote, 0)
h := 5 + 10*i h := 5 + 10*i
appHash := []byte(fmt.Sprintf("h=%d", h)) appHash := []byte(fmt.Sprintf("h=%d", h))
commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, 0, len(keys)) commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, 0, len(keys))
} }
// initialize a certifier with the initial state // initialize a certifier with the initial state
cert := certifiers.NewInquiring(chainID, commits[0], trust, source) cert := light.NewInquiring(chainID, commits[0], trust, source)
// this should fail validation.... // this should fail validation....
commit := commits[count-1].Commit commit := commits[count-1].Commit
@ -108,29 +107,28 @@ func TestInquirerMinimalPath(t *testing.T) {
func TestInquirerVerifyHistorical(t *testing.T) { func TestInquirerVerifyHistorical(t *testing.T) {
assert, require := assert.New(t), require.New(t) assert, require := assert.New(t), require.New(t)
trust := certifiers.NewMemStoreProvider() trust := light.NewMemStoreProvider()
source := certifiers.NewMemStoreProvider() source := light.NewMemStoreProvider()
// set up the validators to generate test blocks // set up the validators to generate test blocks
var vote int64 = 10 var vote int64 = 10
keys := certifiers.GenValKeys(5) keys := light.GenValKeys(5)
vals := keys.ToValidators(vote, 0)
// construct a bunch of commits, each with one more height than the last // construct a bunch of commits, each with one more height than the last
chainID := "inquiry-test" chainID := "inquiry-test"
count := 10 count := 10
commits := make([]certifiers.FullCommit, count) commits := make([]light.FullCommit, count)
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
// extend the keys by 1 each time // extend the keys by 1 each time
keys = keys.Extend(1) keys = keys.Extend(1)
vals = keys.ToValidators(vote, 0) vals := keys.ToValidators(vote, 0)
h := 20 + 10*i h := 20 + 10*i
appHash := []byte(fmt.Sprintf("h=%d", h)) appHash := []byte(fmt.Sprintf("h=%d", h))
commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, 0, len(keys)) commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, 0, len(keys))
} }
// initialize a certifier with the initial state // initialize a certifier with the initial state
cert := certifiers.NewInquiring(chainID, commits[0], trust, source) cert := light.NewInquiring(chainID, commits[0], trust, source)
// store a few commits as trust // store a few commits as trust
for _, i := range []int{2, 5} { for _, i := range []int{2, 5} {

View File

@ -1,10 +1,10 @@
package certifiers package light
import ( import (
"encoding/hex" "encoding/hex"
"sort" "sort"
certerr "github.com/tendermint/tendermint/certifiers/errors" lightErr "github.com/tendermint/tendermint/light/errors"
) )
type memStoreProvider struct { type memStoreProvider struct {
@ -23,6 +23,7 @@ func (s fullCommits) Less(i, j int) bool {
return s[i].Height() < s[j].Height() return s[i].Height() < s[j].Height()
} }
// NewMemStoreProvider returns a new in-memory provider.
func NewMemStoreProvider() Provider { func NewMemStoreProvider() Provider {
return &memStoreProvider{ return &memStoreProvider{
byHeight: fullCommits{}, byHeight: fullCommits{},
@ -34,6 +35,7 @@ func (m *memStoreProvider) encodeHash(hash []byte) string {
return hex.EncodeToString(hash) return hex.EncodeToString(hash)
} }
// StoreCommit stores a FullCommit after verifying it.
func (m *memStoreProvider) StoreCommit(fc FullCommit) error { func (m *memStoreProvider) StoreCommit(fc FullCommit) error {
// make sure the fc is self-consistent before saving // make sure the fc is self-consistent before saving
err := fc.ValidateBasic(fc.Commit.Header.ChainID) err := fc.ValidateBasic(fc.Commit.Header.ChainID)
@ -49,6 +51,7 @@ func (m *memStoreProvider) StoreCommit(fc FullCommit) error {
return nil return nil
} }
// GetByHeight returns the FullCommit for height h or an error if the commit is not found.
func (m *memStoreProvider) GetByHeight(h int) (FullCommit, error) { func (m *memStoreProvider) GetByHeight(h int) (FullCommit, error) {
// search from highest to lowest // search from highest to lowest
for i := len(m.byHeight) - 1; i >= 0; i-- { for i := len(m.byHeight) - 1; i >= 0; i-- {
@ -57,22 +60,24 @@ func (m *memStoreProvider) GetByHeight(h int) (FullCommit, error) {
return fc, nil return fc, nil
} }
} }
return FullCommit{}, certerr.ErrCommitNotFound() return FullCommit{}, lightErr.ErrCommitNotFound()
} }
// GetByHash returns the FullCommit for the hash or an error if the commit is not found.
func (m *memStoreProvider) GetByHash(hash []byte) (FullCommit, error) { func (m *memStoreProvider) GetByHash(hash []byte) (FullCommit, error) {
var err error var err error
fc, ok := m.byHash[m.encodeHash(hash)] fc, ok := m.byHash[m.encodeHash(hash)]
if !ok { if !ok {
err = certerr.ErrCommitNotFound() err = lightErr.ErrCommitNotFound()
} }
return fc, err return fc, err
} }
// LatestCommit returns the latest FullCommit or an error if no commits exist.
func (m *memStoreProvider) LatestCommit() (FullCommit, error) { func (m *memStoreProvider) LatestCommit() (FullCommit, error) {
l := len(m.byHeight) l := len(m.byHeight)
if l == 0 { if l == 0 {
return FullCommit{}, certerr.ErrCommitNotFound() return FullCommit{}, lightErr.ErrCommitNotFound()
} }
return m.byHeight[l-1], nil return m.byHeight[l-1], nil
} }

View File

@ -1,33 +1,33 @@
package certifiers_test package light_test
import ( import (
"fmt" "fmt"
"testing" "testing"
"github.com/tendermint/tendermint/certifiers" "github.com/tendermint/tendermint/light"
) )
func BenchmarkGenCommit20(b *testing.B) { func BenchmarkGenCommit20(b *testing.B) {
keys := certifiers.GenValKeys(20) keys := light.GenValKeys(20)
benchmarkGenCommit(b, keys) benchmarkGenCommit(b, keys)
} }
func BenchmarkGenCommit100(b *testing.B) { func BenchmarkGenCommit100(b *testing.B) {
keys := certifiers.GenValKeys(100) keys := light.GenValKeys(100)
benchmarkGenCommit(b, keys) benchmarkGenCommit(b, keys)
} }
func BenchmarkGenCommitSec20(b *testing.B) { func BenchmarkGenCommitSec20(b *testing.B) {
keys := certifiers.GenSecpValKeys(20) keys := light.GenSecpValKeys(20)
benchmarkGenCommit(b, keys) benchmarkGenCommit(b, keys)
} }
func BenchmarkGenCommitSec100(b *testing.B) { func BenchmarkGenCommitSec100(b *testing.B) {
keys := certifiers.GenSecpValKeys(100) keys := light.GenSecpValKeys(100)
benchmarkGenCommit(b, keys) benchmarkGenCommit(b, keys)
} }
func benchmarkGenCommit(b *testing.B, keys certifiers.ValKeys) { func benchmarkGenCommit(b *testing.B, keys light.ValKeys) {
chainID := fmt.Sprintf("bench-%d", len(keys)) chainID := fmt.Sprintf("bench-%d", len(keys))
vals := keys.ToValidators(20, 10) vals := keys.ToValidators(20, 10)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
@ -39,7 +39,7 @@ func benchmarkGenCommit(b *testing.B, keys certifiers.ValKeys) {
// this benchmarks generating one key // this benchmarks generating one key
func BenchmarkGenValKeys(b *testing.B) { func BenchmarkGenValKeys(b *testing.B) {
keys := certifiers.GenValKeys(20) keys := light.GenValKeys(20)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
keys = keys.Extend(1) keys = keys.Extend(1)
} }
@ -47,7 +47,7 @@ func BenchmarkGenValKeys(b *testing.B) {
// this benchmarks generating one key // this benchmarks generating one key
func BenchmarkGenSecpValKeys(b *testing.B) { func BenchmarkGenSecpValKeys(b *testing.B) {
keys := certifiers.GenSecpValKeys(20) keys := light.GenSecpValKeys(20)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
keys = keys.Extend(1) keys = keys.Extend(1)
} }
@ -63,7 +63,7 @@ func BenchmarkToValidators100(b *testing.B) {
// this benchmarks constructing the validator set (.PubKey() * nodes) // this benchmarks constructing the validator set (.PubKey() * nodes)
func benchmarkToValidators(b *testing.B, nodes int) { func benchmarkToValidators(b *testing.B, nodes int) {
keys := certifiers.GenValKeys(nodes) keys := light.GenValKeys(nodes)
for i := 1; i <= b.N; i++ { for i := 1; i <= b.N; i++ {
keys.ToValidators(int64(2*i), int64(i)) keys.ToValidators(int64(2*i), int64(i))
} }
@ -75,36 +75,36 @@ func BenchmarkToValidatorsSec100(b *testing.B) {
// this benchmarks constructing the validator set (.PubKey() * nodes) // this benchmarks constructing the validator set (.PubKey() * nodes)
func benchmarkToValidatorsSec(b *testing.B, nodes int) { func benchmarkToValidatorsSec(b *testing.B, nodes int) {
keys := certifiers.GenSecpValKeys(nodes) keys := light.GenSecpValKeys(nodes)
for i := 1; i <= b.N; i++ { for i := 1; i <= b.N; i++ {
keys.ToValidators(int64(2*i), int64(i)) keys.ToValidators(int64(2*i), int64(i))
} }
} }
func BenchmarkCertifyCommit20(b *testing.B) { func BenchmarkCertifyCommit20(b *testing.B) {
keys := certifiers.GenValKeys(20) keys := light.GenValKeys(20)
benchmarkCertifyCommit(b, keys) benchmarkCertifyCommit(b, keys)
} }
func BenchmarkCertifyCommit100(b *testing.B) { func BenchmarkCertifyCommit100(b *testing.B) {
keys := certifiers.GenValKeys(100) keys := light.GenValKeys(100)
benchmarkCertifyCommit(b, keys) benchmarkCertifyCommit(b, keys)
} }
func BenchmarkCertifyCommitSec20(b *testing.B) { func BenchmarkCertifyCommitSec20(b *testing.B) {
keys := certifiers.GenSecpValKeys(20) keys := light.GenSecpValKeys(20)
benchmarkCertifyCommit(b, keys) benchmarkCertifyCommit(b, keys)
} }
func BenchmarkCertifyCommitSec100(b *testing.B) { func BenchmarkCertifyCommitSec100(b *testing.B) {
keys := certifiers.GenSecpValKeys(100) keys := light.GenSecpValKeys(100)
benchmarkCertifyCommit(b, keys) benchmarkCertifyCommit(b, keys)
} }
func benchmarkCertifyCommit(b *testing.B, keys certifiers.ValKeys) { func benchmarkCertifyCommit(b *testing.B, keys light.ValKeys) {
chainID := "bench-certify" chainID := "bench-certify"
vals := keys.ToValidators(20, 10) vals := keys.ToValidators(20, 10)
cert := certifiers.NewStatic(chainID, vals) cert := light.NewStatic(chainID, vals)
check := keys.GenCommit(chainID, 123, nil, vals, []byte("foo"), 0, len(keys)) check := keys.GenCommit(chainID, 123, nil, vals, []byte("foo"), 0, len(keys))
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
err := cert.Certify(check) err := cert.Certify(check)

View File

@ -1,22 +1,18 @@
package certifiers package light
import ( // Provider is used to get more validators by other means.
certerr "github.com/tendermint/tendermint/certifiers/errors"
)
// Provider is used to get more validators by other means
// //
// Examples: MemProvider, files.Provider, client.Provider.... // Examples: MemProvider, files.Provider, client.Provider, CacheProvider....
type Provider interface { type Provider interface {
// StoreCommit saves a FullCommit after we have verified it, // StoreCommit saves a FullCommit after we have verified it,
// so we can query for it later. Important for updating our // so we can query for it later. Important for updating our
// store of trusted commits // store of trusted commits.
StoreCommit(fc FullCommit) error StoreCommit(fc FullCommit) error
// GetByHeight returns the closest commit with height <= h // GetByHeight returns the closest commit with height <= h.
GetByHeight(h int) (FullCommit, error) GetByHeight(h int) (FullCommit, error)
// GetByHash returns a commit exactly matching this validator hash // GetByHash returns a commit exactly matching this validator hash.
GetByHash(hash []byte) (FullCommit, error) GetByHash(hash []byte) (FullCommit, error)
// LatestCommit returns the newest commit stored // LatestCommit returns the newest commit stored.
LatestCommit() (FullCommit, error) LatestCommit() (FullCommit, error)
} }
@ -28,6 +24,7 @@ type cacheProvider struct {
Providers []Provider Providers []Provider
} }
// NewCacheProvider returns a new provider which wraps multiple other providers.
func NewCacheProvider(providers ...Provider) Provider { func NewCacheProvider(providers ...Provider) Provider {
return cacheProvider{ return cacheProvider{
Providers: providers, Providers: providers,
@ -47,19 +44,17 @@ func (c cacheProvider) StoreCommit(fc FullCommit) (err error) {
return err return err
} }
/* // GetByHeight should return the closest possible match from all providers.
GetByHeight should return the closest possible match from all providers. //
// The Cache is usually organized in order from cheapest call (memory)
The Cache is usually organized in order from cheapest call (memory) // to most expensive calls (disk/network). However, since GetByHeight returns
to most expensive calls (disk/network). However, since GetByHeight returns // a FullCommit at h' <= h, if the memory has a seed at h-10, but the network would
a FullCommit at h' <= h, if the memory has a seed at h-10, but the network would // give us the exact match, a naive "stop at first non-error" would hide
give us the exact match, a naive "stop at first non-error" would hide // the actual desired results.
the actual desired results. //
// Thus, we query each provider in order until we find an exact match
Thus, we query each provider in order until we find an exact match // or we finished querying them all. If at least one returned a non-error,
or we finished querying them all. If at least one returned a non-error, // then this returns the best match (minimum h-h').
then this returns the best match (minimum h-h').
*/
func (c cacheProvider) GetByHeight(h int) (fc FullCommit, err error) { func (c cacheProvider) GetByHeight(h int) (fc FullCommit, err error) {
for _, p := range c.Providers { for _, p := range c.Providers {
var tfc FullCommit var tfc FullCommit
@ -80,6 +75,7 @@ func (c cacheProvider) GetByHeight(h int) (fc FullCommit, err error) {
return fc, err return fc, err
} }
// GetByHash returns the FullCommit for the hash or an error if the commit is not found.
func (c cacheProvider) GetByHash(hash []byte) (fc FullCommit, err error) { func (c cacheProvider) GetByHash(hash []byte) (fc FullCommit, err error) {
for _, p := range c.Providers { for _, p := range c.Providers {
fc, err = p.GetByHash(hash) fc, err = p.GetByHash(hash)
@ -90,6 +86,7 @@ func (c cacheProvider) GetByHash(hash []byte) (fc FullCommit, err error) {
return fc, err return fc, err
} }
// LatestCommit returns the latest FullCommit or an error if no commit exists.
func (c cacheProvider) LatestCommit() (fc FullCommit, err error) { func (c cacheProvider) LatestCommit() (fc FullCommit, err error) {
for _, p := range c.Providers { for _, p := range c.Providers {
var tfc FullCommit var tfc FullCommit
@ -104,22 +101,3 @@ func (c cacheProvider) LatestCommit() (fc FullCommit, err error) {
} }
return fc, err return fc, err
} }
// missingProvider doens't store anything, always a miss
// Designed as a mock for testing
type missingProvider struct{}
func NewMissingProvider() Provider {
return missingProvider{}
}
func (missingProvider) StoreCommit(_ FullCommit) error { return nil }
func (missingProvider) GetByHeight(_ int) (FullCommit, error) {
return FullCommit{}, certerr.ErrCommitNotFound()
}
func (missingProvider) GetByHash(_ []byte) (FullCommit, error) {
return FullCommit{}, certerr.ErrCommitNotFound()
}
func (missingProvider) LatestCommit() (FullCommit, error) {
return FullCommit{}, certerr.ErrCommitNotFound()
}

View File

@ -1,4 +1,5 @@
package certifiers_test // nolint: vetshadow
package light_test
import ( import (
"testing" "testing"
@ -6,32 +7,52 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/certifiers" light "github.com/tendermint/tendermint/light"
"github.com/tendermint/tendermint/certifiers/errors" lightErr "github.com/tendermint/tendermint/light/errors"
) )
// missingProvider doens't store anything, always a miss
// Designed as a mock for testing
type missingProvider struct{}
// NewMissingProvider returns a provider which does not store anything and always misses.
func NewMissingProvider() light.Provider {
return missingProvider{}
}
func (missingProvider) StoreCommit(_ light.FullCommit) error { return nil }
func (missingProvider) GetByHeight(_ int) (light.FullCommit, error) {
return light.FullCommit{}, lightErr.ErrCommitNotFound()
}
func (missingProvider) GetByHash(_ []byte) (light.FullCommit, error) {
return light.FullCommit{}, lightErr.ErrCommitNotFound()
}
func (missingProvider) LatestCommit() (light.FullCommit, error) {
return light.FullCommit{}, lightErr.ErrCommitNotFound()
}
func TestMemProvider(t *testing.T) { func TestMemProvider(t *testing.T) {
p := certifiers.NewMemStoreProvider() p := light.NewMemStoreProvider()
checkProvider(t, p, "test-mem", "empty") checkProvider(t, p, "test-mem", "empty")
} }
func TestCacheProvider(t *testing.T) { func TestCacheProvider(t *testing.T) {
p := certifiers.NewCacheProvider( p := light.NewCacheProvider(
certifiers.NewMissingProvider(), NewMissingProvider(),
certifiers.NewMemStoreProvider(), light.NewMemStoreProvider(),
certifiers.NewMissingProvider(), NewMissingProvider(),
) )
checkProvider(t, p, "test-cache", "kjfhekfhkewhgit") checkProvider(t, p, "test-cache", "kjfhekfhkewhgit")
} }
func checkProvider(t *testing.T, p certifiers.Provider, chainID, app string) { func checkProvider(t *testing.T, p light.Provider, chainID, app string) {
assert, require := assert.New(t), require.New(t) assert, require := assert.New(t), require.New(t)
appHash := []byte(app) appHash := []byte(app)
keys := certifiers.GenValKeys(5) keys := light.GenValKeys(5)
count := 10 count := 10
// make a bunch of commits... // make a bunch of commits...
commits := make([]certifiers.FullCommit, count) commits := make([]light.FullCommit, count)
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
// two commits for each validator, to check how we handle dups // two commits for each validator, to check how we handle dups
// (10, 0), (10, 1), (10, 1), (10, 2), (10, 2), ... // (10, 0), (10, 1), (10, 1), (10, 2), (10, 2), ...
@ -43,11 +64,11 @@ func checkProvider(t *testing.T, p certifiers.Provider, chainID, app string) {
// check provider is empty // check provider is empty
fc, err := p.GetByHeight(20) fc, err := p.GetByHeight(20)
require.NotNil(err) require.NotNil(err)
assert.True(errors.IsCommitNotFoundErr(err)) assert.True(lightErr.IsCommitNotFoundErr(err))
fc, err = p.GetByHash(commits[3].ValidatorsHash()) fc, err = p.GetByHash(commits[3].ValidatorsHash())
require.NotNil(err) require.NotNil(err)
assert.True(errors.IsCommitNotFoundErr(err)) assert.True(lightErr.IsCommitNotFoundErr(err))
// now add them all to the provider // now add them all to the provider
for _, s := range commits { for _, s := range commits {
@ -80,7 +101,7 @@ func checkProvider(t *testing.T, p certifiers.Provider, chainID, app string) {
} }
// this will make a get height, and if it is good, set the data as well // this will make a get height, and if it is good, set the data as well
func checkGetHeight(t *testing.T, p certifiers.Provider, ask, expect int) { func checkGetHeight(t *testing.T, p light.Provider, ask, expect int) {
fc, err := p.GetByHeight(ask) fc, err := p.GetByHeight(ask)
require.Nil(t, err, "%+v", err) require.Nil(t, err, "%+v", err)
if assert.Equal(t, expect, fc.Height()) { if assert.Equal(t, expect, fc.Height()) {
@ -95,13 +116,13 @@ func TestCacheGetsBestHeight(t *testing.T) {
// we will write data to the second level of the cache (p2), // we will write data to the second level of the cache (p2),
// and see what gets cached, stored in // and see what gets cached, stored in
p := certifiers.NewMemStoreProvider() p := light.NewMemStoreProvider()
p2 := certifiers.NewMemStoreProvider() p2 := light.NewMemStoreProvider()
cp := certifiers.NewCacheProvider(p, p2) cp := light.NewCacheProvider(p, p2)
chainID := "cache-best-height" chainID := "cache-best-height"
appHash := []byte("01234567") appHash := []byte("01234567")
keys := certifiers.GenValKeys(5) keys := light.GenValKeys(5)
count := 10 count := 10
// set a bunch of commits // set a bunch of commits

View File

@ -1,4 +1,4 @@
package certifiers package light
import ( import (
"bytes" "bytes"
@ -7,7 +7,7 @@ import (
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
certerr "github.com/tendermint/tendermint/certifiers/errors" lightErr "github.com/tendermint/tendermint/light/errors"
) )
var _ Certifier = &Static{} var _ Certifier = &Static{}
@ -25,6 +25,7 @@ type Static struct {
vhash []byte vhash []byte
} }
// NewStatic returns a new certifier with a static validator set.
func NewStatic(chainID string, vals *types.ValidatorSet) *Static { func NewStatic(chainID string, vals *types.ValidatorSet) *Static {
return &Static{ return &Static{
chainID: chainID, chainID: chainID,
@ -32,14 +33,17 @@ func NewStatic(chainID string, vals *types.ValidatorSet) *Static {
} }
} }
// ChainID returns the chain id.
func (c *Static) ChainID() string { func (c *Static) ChainID() string {
return c.chainID return c.chainID
} }
// Validators returns the validator set.
func (c *Static) Validators() *types.ValidatorSet { func (c *Static) Validators() *types.ValidatorSet {
return c.vSet return c.vSet
} }
// Hash returns the hash of the validator set.
func (c *Static) Hash() []byte { func (c *Static) Hash() []byte {
if len(c.vhash) == 0 { if len(c.vhash) == 0 {
c.vhash = c.vSet.Hash() c.vhash = c.vSet.Hash()
@ -47,6 +51,7 @@ func (c *Static) Hash() []byte {
return c.vhash return c.vhash
} }
// Certify makes sure that the commit is valid.
func (c *Static) Certify(commit Commit) error { func (c *Static) Certify(commit Commit) error {
// do basic sanity checks // do basic sanity checks
err := commit.ValidateBasic(c.chainID) err := commit.ValidateBasic(c.chainID)
@ -56,7 +61,7 @@ func (c *Static) Certify(commit Commit) error {
// make sure it has the same validator set we have (static means static) // make sure it has the same validator set we have (static means static)
if !bytes.Equal(c.Hash(), commit.Header.ValidatorsHash) { if !bytes.Equal(c.Hash(), commit.Header.ValidatorsHash) {
return certerr.ErrValidatorsChanged() return lightErr.ErrValidatorsChanged()
} }
// then make sure we have the proper signatures for this // then make sure we have the proper signatures for this

View File

@ -1,4 +1,4 @@
package certifiers_test package light_test
import ( import (
"testing" "testing"
@ -7,8 +7,8 @@ import (
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
"github.com/tendermint/tendermint/certifiers" "github.com/tendermint/tendermint/light"
errors "github.com/tendermint/tendermint/certifiers/errors" lightErr "github.com/tendermint/tendermint/light/errors"
) )
func TestStaticCert(t *testing.T) { func TestStaticCert(t *testing.T) {
@ -16,15 +16,15 @@ func TestStaticCert(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
// require := require.New(t) // require := require.New(t)
keys := certifiers.GenValKeys(4) keys := light.GenValKeys(4)
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do! // 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
vals := keys.ToValidators(20, 10) vals := keys.ToValidators(20, 10)
// and a certifier based on our known set // and a certifier based on our known set
chainID := "test-static" chainID := "test-static"
cert := certifiers.NewStatic(chainID, vals) cert := light.NewStatic(chainID, vals)
cases := []struct { cases := []struct {
keys certifiers.ValKeys keys light.ValKeys
vals *types.ValidatorSet vals *types.ValidatorSet
height int height int
first, last int // who actually signs first, last int // who actually signs
@ -51,7 +51,7 @@ func TestStaticCert(t *testing.T) {
} else { } else {
assert.NotNil(err) assert.NotNil(err)
if tc.changed { if tc.changed {
assert.True(errors.IsValidatorsChangedErr(err), "%+v", err) assert.True(lightErr.IsValidatorsChangedErr(err), "%+v", err)
} }
} }
} }