Addressed review for #1815 except those marked as 'TODO make issue'

This commit is contained in:
Jae Kwon 2018-08-02 03:10:50 -07:00
parent eb9b37e196
commit e719a93d1d
21 changed files with 154 additions and 157 deletions

View File

@ -556,7 +556,7 @@ BREAKING CHANGES:
- use scripts/wal2json to convert to json for debugging - use scripts/wal2json to convert to json for debugging
FEATURES: FEATURES:
- new `certifiers` pkg contains the tendermint light-client library (name subject to change)! - new `Verifiers` pkg contains the tendermint light-client library (name subject to change)!
- rpc: `/genesis` includes the `app_options` . - rpc: `/genesis` includes the `app_options` .
- rpc: `/abci_query` takes an additional `height` parameter to support historical queries. - rpc: `/abci_query` takes an additional `height` parameter to support historical queries.
- rpc/client: new ABCIQueryWithOptions supports options like `trusted` (set false to get a proof) and `height` to query a historical height. - rpc/client: new ABCIQueryWithOptions supports options like `trusted` (set false to get a proof) and `height` to query a historical height.

4
Gopkg.lock generated
View File

@ -11,7 +11,7 @@
branch = "master" branch = "master"
name = "github.com/btcsuite/btcd" name = "github.com/btcsuite/btcd"
packages = ["btcec"] packages = ["btcec"]
revision = "9a2f9524024889e129a5422aca2cff73cb3eabf6" revision = "f5e261fc9ec3437697fb31d8b38453c293204b29"
[[projects]] [[projects]]
name = "github.com/btcsuite/btcutil" name = "github.com/btcsuite/btcutil"
@ -342,7 +342,7 @@
"cpu", "cpu",
"unix" "unix"
] ]
revision = "bd9dbc187b6e1dacfdd2722a87e83093c2d7bd6e" revision = "3dc4335d56c789b04b0ba99b7a37249d9b614314"
[[projects]] [[projects]]
name = "golang.org/x/text" name = "golang.org/x/text"

View File

@ -68,10 +68,10 @@ func runProxy(cmd *cobra.Command, args []string) error {
logger.Info("Connecting to source HTTP client...") logger.Info("Connecting to source HTTP client...")
node := rpcclient.NewHTTP(nodeAddr, "/websocket") node := rpcclient.NewHTTP(nodeAddr, "/websocket")
logger.Info("Constructing certifier...") logger.Info("Constructing Verifier...")
cert, err := proxy.NewCertifier(chainID, home, node, logger) cert, err := proxy.NewVerifier(chainID, home, node, logger)
if err != nil { if err != nil {
return cmn.ErrorWrap(err, "constructing certifier") return cmn.ErrorWrap(err, "constructing Verifier")
} }
cert.SetLogger(logger) cert.SetLogger(logger)
sc := proxy.SecureClient(node, cert) sc := proxy.SecureClient(node, cert)

View File

@ -3,48 +3,48 @@ package lite
import ( import (
"bytes" "bytes"
cmn "github.com/tendermint/tendermint/libs/common"
lerr "github.com/tendermint/tendermint/lite/errors" lerr "github.com/tendermint/tendermint/lite/errors"
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
cmn "github.com/tendermint/tendermint/libs/common"
) )
var _ Certifier = (*BaseCertifier)(nil) var _ Verifier = (*BaseVerifier)(nil)
// BaseCertifier lets us check the validity of SignedHeaders at height or // BaseVerifier lets us check the validity of SignedHeaders at height or
// later, requiring sufficient votes (> 2/3) from the given valset. // later, requiring sufficient votes (> 2/3) from the given valset.
// To certify blocks produced by a blockchain with mutable validator sets, // To certify blocks produced by a blockchain with mutable validator sets,
// use the InquiringCertifier. // use the DynamicVerifier.
// TODO: Handle unbonding time. // TODO: Handle unbonding time.
type BaseCertifier struct { type BaseVerifier struct {
chainID string chainID string
height int64 height int64
valset *types.ValidatorSet valset *types.ValidatorSet
} }
// NewBaseCertifier returns a new certifier initialized with a validator set at // NewBaseVerifier returns a new Verifier initialized with a validator set at
// some height. // some height.
func NewBaseCertifier(chainID string, height int64, valset *types.ValidatorSet) *BaseCertifier { func NewBaseVerifier(chainID string, height int64, valset *types.ValidatorSet) *BaseVerifier {
if valset == nil || len(valset.Hash()) == 0 { if valset.IsNilOrEmpty() {
panic("NewBaseCertifier requires a valid valset") panic("NewBaseVerifier requires a valid valset")
} }
return &BaseCertifier{ return &BaseVerifier{
chainID: chainID, chainID: chainID,
height: height, height: height,
valset: valset, valset: valset,
} }
} }
// Implements Certifier. // Implements Verifier.
func (bc *BaseCertifier) ChainID() string { func (bc *BaseVerifier) ChainID() string {
return bc.chainID return bc.chainID
} }
// Implements Certifier. // Implements Verifier.
func (bc *BaseCertifier) Certify(signedHeader types.SignedHeader) error { func (bc *BaseVerifier) Certify(signedHeader types.SignedHeader) error {
// We can't certify commits older than bc.height. // We can't certify commits older than bc.height.
if signedHeader.Height < bc.height { if signedHeader.Height < bc.height {
return cmn.NewError("BaseCertifier height is %v, cannot certify height %v", return cmn.NewError("BaseVerifier height is %v, cannot certify height %v",
bc.height, signedHeader.Height) bc.height, signedHeader.Height)
} }

View File

@ -10,16 +10,14 @@ import (
) )
func TestBaseCert(t *testing.T) { func TestBaseCert(t *testing.T) {
// assert, require := assert.New(t), require.New(t)
assert := assert.New(t) assert := assert.New(t)
// require := require.New(t)
keys := genPrivKeys(4) keys := genPrivKeys(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 Verifier based on our known set
chainID := "test-static" chainID := "test-static"
cert := NewBaseCertifier(chainID, 2, vals) cert := NewBaseVerifier(chainID, 2, vals)
cases := []struct { cases := []struct {
keys privKeys keys privKeys

View File

@ -8,12 +8,12 @@ package client
import ( import (
"fmt" "fmt"
log "github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/lite" "github.com/tendermint/tendermint/lite"
lerr "github.com/tendermint/tendermint/lite/errors" lerr "github.com/tendermint/tendermint/lite/errors"
rpcclient "github.com/tendermint/tendermint/rpc/client" rpcclient "github.com/tendermint/tendermint/rpc/client"
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"
log "github.com/tendermint/tendermint/libs/log"
) )
// SignStatusClient combines a SignClient and StatusClient. // SignStatusClient combines a SignClient and StatusClient.
@ -106,12 +106,10 @@ func (p *provider) getValidatorSet(chainID string, height int64) (valset *types.
err = fmt.Errorf("expected height >= 1, got height %v", height) err = fmt.Errorf("expected height >= 1, got height %v", height)
return return
} }
heightPtr := new(int64) res, err := p.client.Validators(&height)
*heightPtr = height
res, err := p.client.Validators(heightPtr)
if err != nil { if err != nil {
// TODO pass through other types of errors. // TODO pass through other types of errors.
return nil, lerr.ErrMissingValidators(chainID, height) return nil, lerr.ErrUnknownValidators(chainID, height)
} }
valset = types.NewValidatorSet(res.Validators) valset = types.NewValidatorSet(res.Validators)
return return

View File

@ -13,7 +13,6 @@ import (
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
) )
// TODO fix tests!!
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
app := kvstore.NewKVStoreApplication() app := kvstore.NewKVStoreApplication()
node := rpctest.StartTendermint(app) node := rpctest.StartTendermint(app)
@ -59,15 +58,4 @@ func TestProvider(t *testing.T) {
assert.Nil(err, "%+v", err) assert.Nil(err, "%+v", err)
assert.Equal(lower, fc.Height()) assert.Equal(lower, fc.Height())
/*
// also get by hash (given the match)
fc, err = p.GetByHash(vhash)
require.Nil(err, "%+v", err)
require.Equal(vhash, fc.Header.ValidatorsHash)
// get by hash fails without match
fc, err = p.GetByHash([]byte("foobar"))
assert.NotNil(err)
assert.True(liteErr.IsCommitNotFoundErr(err))
*/
} }

View File

@ -11,7 +11,7 @@ import (
// FullCommit is a signed header (the block header and a commit that signs it), // FullCommit is a signed header (the block header and a commit that signs it),
// the validator set which signed the commit, and the next validator set. The // the validator set which signed the commit, and the next validator set. The
// next validator set (which is proven from the block header) allows us to // next validator set (which is proven from the block header) allows us to
// revert to block-by-block updating of lite certifier's latest validator set, // revert to block-by-block updating of lite Verifier's latest validator set,
// even in the face of arbitrarily large power changes. // even in the face of arbitrarily large power changes.
type FullCommit struct { type FullCommit struct {
SignedHeader types.SignedHeader `json:"signed_header"` SignedHeader types.SignedHeader `json:"signed_header"`

View File

@ -22,7 +22,10 @@ type DBProvider struct {
} }
func NewDBProvider(label string, db dbm.DB) *DBProvider { func NewDBProvider(label string, db dbm.DB) *DBProvider {
// NOTE: when debugging, this type of construction might be useful.
//db = dbm.NewDebugDB("db provider "+cmn.RandStr(4), db) //db = dbm.NewDebugDB("db provider "+cmn.RandStr(4), db)
cdc := amino.NewCodec() cdc := amino.NewCodec()
cryptoAmino.RegisterAmino(cdc) cryptoAmino.RegisterAmino(cdc)
dbp := &DBProvider{ dbp := &DBProvider{
@ -127,8 +130,8 @@ func (dbp *DBProvider) LatestFullCommit(chainID string, minHeight, maxHeight int
dbp.logger.Info("DBProvider.LatestFullCommit() found latest.", "height", lfc.Height()) dbp.logger.Info("DBProvider.LatestFullCommit() found latest.", "height", lfc.Height())
return lfc, nil return lfc, nil
} else { } else {
dbp.logger.Info("DBProvider.LatestFullCommit() got error", "lfc", lfc) dbp.logger.Error("DBProvider.LatestFullCommit() got error", "lfc", lfc)
dbp.logger.Info(fmt.Sprintf("%+v", err)) dbp.logger.Error(fmt.Sprintf("%+v", err))
return lfc, err return lfc, err
} }
} }
@ -144,14 +147,19 @@ func (dbp *DBProvider) ValidatorSet(chainID string, height int64) (valset *types
func (dbp *DBProvider) getValidatorSet(chainID string, height int64) (valset *types.ValidatorSet, err error) { func (dbp *DBProvider) getValidatorSet(chainID string, height int64) (valset *types.ValidatorSet, err error) {
vsBz := dbp.db.Get(validatorSetKey(chainID, height)) vsBz := dbp.db.Get(validatorSetKey(chainID, height))
if vsBz == nil { if vsBz == nil {
err = lerr.ErrMissingValidators(chainID, height) err = lerr.ErrUnknownValidators(chainID, height)
return return
} }
err = dbp.cdc.UnmarshalBinary(vsBz, &valset) err = dbp.cdc.UnmarshalBinary(vsBz, &valset)
if err != nil { if err != nil {
return return
} }
valset.TotalVotingPower() // to test deep equality.
// To test deep equality. This makes it easier to test for e.g. valset
// equivalence using assert.Equal (tests for deep equality) in our tests,
// which also tests for unexported/private field equivalence.
valset.TotalVotingPower()
return return
} }
@ -209,52 +217,52 @@ func (dbp *DBProvider) deleteAfterN(chainID string, after int) error {
itr.Next() itr.Next()
} }
dbp.logger.Info(fmt.Sprintf("DBProvider.deleteAfterN() deleted %v items\n", numDeleted)) dbp.logger.Info(fmt.Sprintf("DBProvider.deleteAfterN() deleted %v items", numDeleted))
return nil return nil
} }
//---------------------------------------- //----------------------------------------
// key encoding
func signedHeaderKey(chainID string, height int64) []byte { func signedHeaderKey(chainID string, height int64) []byte {
return []byte(fmt.Sprintf("%s/%010d/sh", chainID, height)) return []byte(fmt.Sprintf("%s/%010d/sh", chainID, height))
} }
var signedHeaderKeyPattern = regexp.MustCompile(`([^/]+)/([0-9]*)/sh`)
func parseSignedHeaderKey(key []byte) (chainID string, height int64, ok bool) {
submatch := signedHeaderKeyPattern.FindSubmatch(key)
if submatch == nil {
return "", 0, false
}
chainID = string(submatch[1])
heightStr := string(submatch[2])
heightInt, err := strconv.Atoi(heightStr)
if err != nil {
return "", 0, false
}
height = int64(heightInt)
ok = true // good!
return
}
func validatorSetKey(chainID string, height int64) []byte { func validatorSetKey(chainID string, height int64) []byte {
return []byte(fmt.Sprintf("%s/%010d/vs", chainID, height)) return []byte(fmt.Sprintf("%s/%010d/vs", chainID, height))
} }
var chainKeyPrefixPattern = regexp.MustCompile(`([^/]+)/([0-9]*)/`) //----------------------------------------
// key parsing
func parseChainKeyPrefix(key []byte) (chainID string, height int64, ok bool) { var keyPattern = regexp.MustCompile(`^([^/]+)/([0-9]*)/(.*)$`)
submatch := chainKeyPrefixPattern.FindSubmatch(key)
func parseKey(key []byte) (chainID string, height int64, part string, ok bool) {
submatch := keyPattern.FindSubmatch(key)
if submatch == nil { if submatch == nil {
return "", 0, false return "", 0, "", false
} }
chainID = string(submatch[1]) chainID = string(submatch[1])
heightStr := string(submatch[2]) heightStr := string(submatch[2])
heightInt, err := strconv.Atoi(heightStr) heightInt, err := strconv.Atoi(heightStr)
if err != nil { if err != nil {
return "", 0, false return "", 0, "", false
} }
height = int64(heightInt) height = int64(heightInt)
part = string(submatch[3])
ok = true // good! ok = true // good!
return return
} }
func parseSignedHeaderKey(key []byte) (chainID string, height int64, ok bool) {
chainID, height, part, ok := parseKey(key)
if part != "sh" {
return "", 0, false
}
return chainID, height, true
}
func parseChainKeyPrefix(key []byte) (chainID string, height int64, ok bool) {
chainID, height, _, ok = parseKey(key)
return chainID, height, true
}

View File

@ -35,29 +35,29 @@ change on the chain. In practice, most applications will not have frequent
drastic updates to the validator set, so the logic defined in this package for drastic updates to the validator set, so the logic defined in this package for
lite client syncing is optimized to use intelligent bisection and lite client syncing is optimized to use intelligent bisection and
block-skipping for efficient sourcing and verification of these data structures block-skipping for efficient sourcing and verification of these data structures
and updates to the validator set (see the InquiringCertifier for more and updates to the validator set (see the DynamicVerifier for more
information). information).
The FullCommit is also declared in this package as a convenience structure, The FullCommit is also declared in this package as a convenience structure,
which includes the SignedHeader along with the full current and next which includes the SignedHeader along with the full current and next
ValidatorSets. ValidatorSets.
## Certifier ## Verifier
A Certifier validates a new SignedHeader given the currently known state. There A Verifier validates a new SignedHeader given the currently known state. There
are two different types of Certifiers provided. are two different types of Verifiers provided.
BaseCertifier - given a validator set and a height, this Certifier verifies BaseVerifier - given a validator set and a height, this Verifier verifies
that > 2/3 of the voting power of the given validator set had signed the that > 2/3 of the voting power of the given validator set had signed the
SignedHeader, and that the SignedHeader was to be signed by the exact given SignedHeader, and that the SignedHeader was to be signed by the exact given
validator set, and that the height of the commit is at least height (or validator set, and that the height of the commit is at least height (or
greater). greater).
SignedHeader.Commit may be signed by a different validator set, it can get SignedHeader.Commit may be signed by a different validator set, it can get
certified with a BaseCertifier as long as sufficient signatures from the certified with a BaseVerifier as long as sufficient signatures from the
previous validator set are present in the commit. previous validator set are present in the commit.
InquiringCertifier - this certifier implements an auto-update and persistence DynamicVerifier - this Verifier implements an auto-update and persistence
strategy to certify any SignedHeader of the blockchain. strategy to certify any SignedHeader of the blockchain.
## Provider and PersistentProvider ## Provider and PersistentProvider
@ -77,7 +77,7 @@ type Provider interface {
* client.NewHTTPProvider - query Tendermint rpc. * client.NewHTTPProvider - query Tendermint rpc.
A PersistentProvider is a Provider that also allows for saving state. This is A PersistentProvider is a Provider that also allows for saving state. This is
used by the InquiringCertifier for persistence. used by the DynamicVerifier for persistence.
```go ```go
type PersistentProvider interface { type PersistentProvider interface {
@ -131,7 +131,7 @@ important to verify that you have the proper validator set when initializing
the client, as that is the root of all trust. the client, as that is the root of all trust.
The software currently assumes that the unbonding period is infinite in The software currently assumes that the unbonding period is infinite in
duration. If the InquiringCertifier hasn't been updated in a while, you should duration. If the DynamicVerifier hasn't been updated in a while, you should
manually verify the block headers using other sources. manually verify the block headers using other sources.
TODO: Update the software to handle cases around the unbonding period. TODO: Update the software to handle cases around the unbonding period.

View File

@ -3,18 +3,18 @@ package lite
import ( import (
"bytes" "bytes"
log "github.com/tendermint/tendermint/libs/log"
lerr "github.com/tendermint/tendermint/lite/errors" lerr "github.com/tendermint/tendermint/lite/errors"
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
log "github.com/tendermint/tendermint/libs/log"
) )
var _ Certifier = (*InquiringCertifier)(nil) var _ Verifier = (*DynamicVerifier)(nil)
// InquiringCertifier implements an auto-updating certifier. It uses a // DynamicVerifier implements an auto-updating Verifier. It uses a
// "source" provider to obtain the needed FullCommits to securely sync with // "source" provider to obtain the needed FullCommits to securely sync with
// validator set changes. It stores properly validated data on the // validator set changes. It stores properly validated data on the
// "trusted" local system. // "trusted" local system.
type InquiringCertifier struct { type DynamicVerifier struct {
logger log.Logger logger log.Logger
chainID string chainID string
// These are only properly validated data, from local system. // These are only properly validated data, from local system.
@ -23,14 +23,14 @@ type InquiringCertifier struct {
source Provider source Provider
} }
// NewInquiringCertifier returns a new InquiringCertifier. It uses the // NewDynamicVerifier returns a new DynamicVerifier. It uses the
// trusted provider to store validated data and the source provider to // trusted provider to store validated data and the source provider to
// obtain missing data (e.g. FullCommits). // obtain missing data (e.g. FullCommits).
// //
// The trusted provider should a CacheProvider, MemProvider or // The trusted provider should a CacheProvider, MemProvider or
// files.Provider. The source provider should be a client.HTTPProvider. // files.Provider. The source provider should be a client.HTTPProvider.
func NewInquiringCertifier(chainID string, trusted PersistentProvider, source Provider) *InquiringCertifier { func NewDynamicVerifier(chainID string, trusted PersistentProvider, source Provider) *DynamicVerifier {
return &InquiringCertifier{ return &DynamicVerifier{
logger: log.NewNopLogger(), logger: log.NewNopLogger(),
chainID: chainID, chainID: chainID,
trusted: trusted, trusted: trusted,
@ -38,64 +38,64 @@ func NewInquiringCertifier(chainID string, trusted PersistentProvider, source Pr
} }
} }
func (ic *InquiringCertifier) SetLogger(logger log.Logger) { func (ic *DynamicVerifier) SetLogger(logger log.Logger) {
logger = logger.With("module", "lite") logger = logger.With("module", "lite")
ic.logger = logger ic.logger = logger
ic.trusted.SetLogger(logger) ic.trusted.SetLogger(logger)
ic.source.SetLogger(logger) ic.source.SetLogger(logger)
} }
// Implements Certifier. // Implements Verifier.
func (ic *InquiringCertifier) ChainID() string { func (ic *DynamicVerifier) ChainID() string {
return ic.chainID return ic.chainID
} }
// Implements Certifier. // Implements Verifier.
// //
// If the validators have changed since the last know time, it looks to // If the validators have changed since the last known time, it looks to
// ic.trusted and ic.source to prove the new validators. On success, it will // ic.trusted and ic.source to prove the new validators. On success, it will
// try to store the SignedHeader in ic.trusted if the next // try to store the SignedHeader in ic.trusted if the next
// validator can be sourced. // validator can be sourced.
func (ic *InquiringCertifier) Certify(shdr types.SignedHeader) error { func (ic *DynamicVerifier) Certify(shdr types.SignedHeader) error {
// Get the latest known full commit <= h-1 from our trusted providers. // Get the latest known full commit <= h-1 from our trusted providers.
// The full commit at h-1 contains the valset to sign for h. // The full commit at h-1 contains the valset to sign for h.
h := shdr.Height - 1 h := shdr.Height - 1
tfc, err := ic.trusted.LatestFullCommit(ic.chainID, 1, h) trustedFC, err := ic.trusted.LatestFullCommit(ic.chainID, 1, h)
if err != nil { if err != nil {
return err return err
} }
if tfc.Height() == h { if trustedFC.Height() == h {
// Return error if valset doesn't match. // Return error if valset doesn't match.
if !bytes.Equal( if !bytes.Equal(
tfc.NextValidators.Hash(), trustedFC.NextValidators.Hash(),
shdr.Header.ValidatorsHash) { shdr.Header.ValidatorsHash) {
return lerr.ErrUnexpectedValidators( return lerr.ErrUnexpectedValidators(
tfc.NextValidators.Hash(), trustedFC.NextValidators.Hash(),
shdr.Header.ValidatorsHash) shdr.Header.ValidatorsHash)
} }
} else { } else {
// If valset doesn't match... // If valset doesn't match...
if !bytes.Equal(tfc.NextValidators.Hash(), if !bytes.Equal(trustedFC.NextValidators.Hash(),
shdr.Header.ValidatorsHash) { shdr.Header.ValidatorsHash) {
// ... update. // ... update.
tfc, err = ic.updateToHeight(h) trustedFC, err = ic.updateToHeight(h)
if err != nil { if err != nil {
return err return err
} }
// Return error if valset _still_ doesn't match. // Return error if valset _still_ doesn't match.
if !bytes.Equal(tfc.NextValidators.Hash(), if !bytes.Equal(trustedFC.NextValidators.Hash(),
shdr.Header.ValidatorsHash) { shdr.Header.ValidatorsHash) {
return lerr.ErrUnexpectedValidators( return lerr.ErrUnexpectedValidators(
tfc.NextValidators.Hash(), trustedFC.NextValidators.Hash(),
shdr.Header.ValidatorsHash) shdr.Header.ValidatorsHash)
} }
} }
} }
// Certify the signed header using the matching valset. // Certify the signed header using the matching valset.
cert := NewBaseCertifier(ic.chainID, tfc.Height()+1, tfc.NextValidators) cert := NewBaseVerifier(ic.chainID, trustedFC.Height()+1, trustedFC.NextValidators)
err = cert.Certify(shdr) err = cert.Certify(shdr)
if err != nil { if err != nil {
return err return err
@ -103,7 +103,7 @@ func (ic *InquiringCertifier) Certify(shdr types.SignedHeader) error {
// Get the next validator set. // Get the next validator set.
nextValset, err := ic.source.ValidatorSet(ic.chainID, shdr.Height+1) nextValset, err := ic.source.ValidatorSet(ic.chainID, shdr.Height+1)
if lerr.IsErrMissingValidators(err) { if lerr.IsErrUnknownValidators(err) {
// Ignore this error. // Ignore this error.
return nil return nil
} else if err != nil { } else if err != nil {
@ -113,7 +113,7 @@ func (ic *InquiringCertifier) Certify(shdr types.SignedHeader) error {
// Create filled FullCommit. // Create filled FullCommit.
nfc := FullCommit{ nfc := FullCommit{
SignedHeader: shdr, SignedHeader: shdr,
Validators: tfc.NextValidators, Validators: trustedFC.NextValidators,
NextValidators: nextValset, NextValidators: nextValset,
} }
// Validate the full commit. This checks the cryptographic // Validate the full commit. This checks the cryptographic
@ -127,22 +127,22 @@ func (ic *InquiringCertifier) Certify(shdr types.SignedHeader) error {
// verifyAndSave will verify if this is a valid source full commit given the // verifyAndSave will verify if this is a valid source full commit given the
// best match trusted full commit, and if good, persist to ic.trusted. // best match trusted full commit, and if good, persist to ic.trusted.
// Returns ErrTooMuchChange when >2/3 of tfc did not sign sfc. // Returns ErrTooMuchChange when >2/3 of trustedFC did not sign sourceFC.
// Panics if tfc.Height() >= sfc.Height(). // Panics if trustedFC.Height() >= sourceFC.Height().
func (ic *InquiringCertifier) verifyAndSave(tfc, sfc FullCommit) error { func (ic *DynamicVerifier) verifyAndSave(trustedFC, sourceFC FullCommit) error {
if tfc.Height() >= sfc.Height() { if trustedFC.Height() >= sourceFC.Height() {
panic("should not happen") panic("should not happen")
} }
err := tfc.NextValidators.VerifyFutureCommit( err := trustedFC.NextValidators.VerifyFutureCommit(
sfc.Validators, sourceFC.Validators,
ic.chainID, sfc.SignedHeader.Commit.BlockID, ic.chainID, sourceFC.SignedHeader.Commit.BlockID,
sfc.SignedHeader.Height, sfc.SignedHeader.Commit, sourceFC.SignedHeader.Height, sourceFC.SignedHeader.Commit,
) )
if err != nil { if err != nil {
return err return err
} }
return ic.trusted.SaveFullCommit(sfc) return ic.trusted.SaveFullCommit(sourceFC)
} }
// updateToHeight will use divide-and-conquer to find a path to h. // updateToHeight will use divide-and-conquer to find a path to h.
@ -150,48 +150,48 @@ func (ic *InquiringCertifier) verifyAndSave(tfc, sfc FullCommit) error {
// for height h, using repeated applications of bisection if necessary. // for height h, using repeated applications of bisection if necessary.
// //
// Returns ErrCommitNotFound if source provider doesn't have the commit for h. // Returns ErrCommitNotFound if source provider doesn't have the commit for h.
func (ic *InquiringCertifier) updateToHeight(h int64) (FullCommit, error) { func (ic *DynamicVerifier) updateToHeight(h int64) (FullCommit, error) {
// Fetch latest full commit from source. // Fetch latest full commit from source.
sfc, err := ic.source.LatestFullCommit(ic.chainID, h, h) sourceFC, err := ic.source.LatestFullCommit(ic.chainID, h, h)
if err != nil { if err != nil {
return FullCommit{}, err return FullCommit{}, err
} }
// Validate the full commit. This checks the cryptographic // Validate the full commit. This checks the cryptographic
// signatures of Commit against Validators. // signatures of Commit against Validators.
if err := sfc.ValidateFull(ic.chainID); err != nil { if err := sourceFC.ValidateFull(ic.chainID); err != nil {
return FullCommit{}, err return FullCommit{}, err
} }
// If sfc.Height() != h, we can't do it. // If sourceFC.Height() != h, we can't do it.
if sfc.Height() != h { if sourceFC.Height() != h {
return FullCommit{}, lerr.ErrCommitNotFound() return FullCommit{}, lerr.ErrCommitNotFound()
} }
FOR_LOOP: FOR_LOOP:
for { for {
// Fetch latest full commit from trusted. // Fetch latest full commit from trusted.
tfc, err := ic.trusted.LatestFullCommit(ic.chainID, 1, h) trustedFC, err := ic.trusted.LatestFullCommit(ic.chainID, 1, h)
if err != nil { if err != nil {
return FullCommit{}, err return FullCommit{}, err
} }
// We have nothing to do. // We have nothing to do.
if tfc.Height() == h { if trustedFC.Height() == h {
return tfc, nil return trustedFC, nil
} }
// Try to update to full commit with checks. // Try to update to full commit with checks.
err = ic.verifyAndSave(tfc, sfc) err = ic.verifyAndSave(trustedFC, sourceFC)
if err == nil { if err == nil {
// All good! // All good!
return sfc, nil return sourceFC, nil
} }
// Handle special case when err is ErrTooMuchChange. // Handle special case when err is ErrTooMuchChange.
if lerr.IsErrTooMuchChange(err) { if lerr.IsErrTooMuchChange(err) {
// Divide and conquer. // Divide and conquer.
start, end := tfc.Height(), sfc.Height() start, end := trustedFC.Height(), sourceFC.Height()
if !(start < end) { if !(start < end) {
panic("should not happen") panic("should not happen")
} }
@ -207,7 +207,7 @@ FOR_LOOP:
} }
} }
func (ic *InquiringCertifier) LastTrustedHeight() int64 { func (ic *DynamicVerifier) LastTrustedHeight() int64 {
fc, err := ic.trusted.LatestFullCommit(ic.chainID, 1, 1<<63-1) fc, err := ic.trusted.LatestFullCommit(ic.chainID, 1, 1<<63-1)
if err != nil { if err != nil {
panic("should not happen") panic("should not happen")

View File

@ -41,10 +41,10 @@ func TestInquirerValidPath(t *testing.T) {
nkeys = nkeys.Extend(1) nkeys = nkeys.Extend(1)
} }
// Initialize a certifier with the initial state. // Initialize a Verifier with the initial state.
err := trust.SaveFullCommit(fcz[0]) err := trust.SaveFullCommit(fcz[0])
require.Nil(err) require.Nil(err)
cert := NewInquiringCertifier(chainID, trust, source) cert := NewDynamicVerifier(chainID, trust, source)
cert.SetLogger(log.TestingLogger()) cert.SetLogger(log.TestingLogger())
// This should fail validation: // This should fail validation:
@ -99,10 +99,10 @@ func TestInquirerVerifyHistorical(t *testing.T) {
nkeys = nkeys.Extend(1) nkeys = nkeys.Extend(1)
} }
// Initialize a certifier with the initial state. // Initialize a Verifier with the initial state.
err := trust.SaveFullCommit(fcz[0]) err := trust.SaveFullCommit(fcz[0])
require.Nil(err) require.Nil(err)
cert := NewInquiringCertifier(chainID, trust, source) cert := NewDynamicVerifier(chainID, trust, source)
cert.SetLogger(log.TestingLogger()) cert.SetLogger(log.TestingLogger())
// Store a few full commits as trust. // Store a few full commits as trust.

View File

@ -31,12 +31,12 @@ func (e errTooMuchChange) Error() string {
return "Insufficient signatures to validate due to valset changes" return "Insufficient signatures to validate due to valset changes"
} }
type errMissingValidators struct { type errUnknownValidators struct {
chainID string chainID string
height int64 height int64
} }
func (e errMissingValidators) Error() string { func (e errUnknownValidators) Error() string {
return fmt.Sprintf("Validators are unknown or missing for chain %s and height %d", return fmt.Sprintf("Validators are unknown or missing for chain %s and height %d",
e.chainID, e.height) e.chainID, e.height)
} }
@ -96,16 +96,16 @@ func IsErrTooMuchChange(err error) bool {
} }
//----------------- //-----------------
// ErrMissingValidators // ErrUnknownValidators
// ErrMissingValidators indicates that some validator set was missing or unknown. // ErrUnknownValidators indicates that some validator set was missing or unknown.
func ErrMissingValidators(chainID string, height int64) error { func ErrUnknownValidators(chainID string, height int64) error {
return cmn.ErrorWrap(errMissingValidators{chainID, height}, "") return cmn.ErrorWrap(errUnknownValidators{chainID, height}, "")
} }
func IsErrMissingValidators(err error) bool { func IsErrUnknownValidators(err error) bool {
if err_, ok := err.(cmn.Error); ok { if err_, ok := err.(cmn.Error); ok {
_, ok := err_.Data().(errMissingValidators) _, ok := err_.Data().(errUnknownValidators)
return ok return ok
} }
return false return false

View File

@ -1,9 +1,9 @@
package lite package lite
import ( import (
log "github.com/tendermint/tendermint/libs/log"
lerr "github.com/tendermint/tendermint/lite/errors" lerr "github.com/tendermint/tendermint/lite/errors"
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
log "github.com/tendermint/tendermint/libs/log"
) )
// multiProvider allows you to place one or more caches in front of a source // multiProvider allows you to place one or more caches in front of a source
@ -79,5 +79,5 @@ func (mc *multiProvider) ValidatorSet(chainID string, height int64) (valset *typ
return valset, nil return valset, nil
} }
} }
return nil, lerr.ErrMissingValidators(chainID, height) return nil, lerr.ErrUnknownValidators(chainID, height)
} }

View File

@ -28,13 +28,13 @@ type KeyProof interface {
} }
// GetWithProof will query the key on the given node, and verify it has // GetWithProof will query the key on the given node, and verify it has
// a valid proof, as defined by the certifier. // a valid proof, as defined by the Verifier.
// //
// If there is any error in checking, returns an error. // If there is any error in checking, returns an error.
// If val is non-empty, proof should be KeyExistsProof // If val is non-empty, proof should be KeyExistsProof
// If val is empty, proof should be KeyMissingProof // If val is empty, proof should be KeyMissingProof
func GetWithProof(key []byte, reqHeight int64, node rpcclient.Client, func GetWithProof(key []byte, reqHeight int64, node rpcclient.Client,
cert lite.Certifier) ( cert lite.Verifier) (
val cmn.HexBytes, height int64, proof KeyProof, err error) { val cmn.HexBytes, height int64, proof KeyProof, err error) {
if reqHeight < 0 { if reqHeight < 0 {
@ -54,7 +54,7 @@ func GetWithProof(key []byte, reqHeight int64, node rpcclient.Client,
// GetWithProofOptions is useful if you want full access to the ABCIQueryOptions // GetWithProofOptions is useful if you want full access to the ABCIQueryOptions
func GetWithProofOptions(path string, key []byte, opts rpcclient.ABCIQueryOptions, func GetWithProofOptions(path string, key []byte, opts rpcclient.ABCIQueryOptions,
node rpcclient.Client, cert lite.Certifier) ( node rpcclient.Client, cert lite.Verifier) (
*ctypes.ResultABCIQuery, KeyProof, error) { *ctypes.ResultABCIQuery, KeyProof, error) {
_resp, err := node.ABCIQueryWithOptions(path, key, opts) _resp, err := node.ABCIQueryWithOptions(path, key, opts)
@ -128,7 +128,7 @@ func GetWithProofOptions(path string, key []byte, opts rpcclient.ABCIQueryOption
// GetCertifiedCommit gets the signed header for a given height and certifies // GetCertifiedCommit gets the signed header for a given height and certifies
// it. Returns error if unable to get a proven header. // it. Returns error if unable to get a proven header.
func GetCertifiedCommit(h int64, client rpcclient.Client, cert lite.Certifier) (types.SignedHeader, error) { func GetCertifiedCommit(h int64, client rpcclient.Client, cert lite.Verifier) (types.SignedHeader, error) {
// FIXME: cannot use cert.GetByHeight for now, as it also requires // FIXME: cannot use cert.GetByHeight for now, as it also requires
// Validators and will fail on querying tendermint for non-current height. // Validators and will fail on querying tendermint for non-current height.

View File

@ -58,7 +58,7 @@ func _TestAppProofs(t *testing.T) {
source := certclient.NewProvider(chainID, cl) source := certclient.NewProvider(chainID, cl)
seed, err := source.LatestFullCommit(chainID, brh-2, brh-2) seed, err := source.LatestFullCommit(chainID, brh-2, brh-2)
require.NoError(err, "%+v", err) require.NoError(err, "%+v", err)
cert := lite.NewBaseCertifier("my-chain", seed.Height(), seed.Validators) cert := lite.NewBaseVerifier("my-chain", seed.Height(), seed.Validators)
client.WaitForHeight(cl, 3, nil) client.WaitForHeight(cl, 3, nil)
latest, err := source.LatestFullCommit(chainID, 1, 1<<63-1) latest, err := source.LatestFullCommit(chainID, 1, 1<<63-1)
@ -117,7 +117,7 @@ func _TestTxProofs(t *testing.T) {
source := certclient.NewProvider(chainID, cl) source := certclient.NewProvider(chainID, cl)
seed, err := source.LatestFullCommit(chainID, brh-2, brh-2) seed, err := source.LatestFullCommit(chainID, brh-2, brh-2)
require.NoError(err, "%+v", err) require.NoError(err, "%+v", err)
cert := lite.NewBaseCertifier("my-chain", seed.Height(), seed.Validators) cert := lite.NewBaseVerifier("my-chain", seed.Height(), seed.Validators)
// First let's make sure a bogus transaction hash returns a valid non-existence proof. // First let's make sure a bogus transaction hash returns a valid non-existence proof.
key := types.Tx([]byte("bogus")).Hash() key := types.Tx([]byte("bogus")).Hash()

View File

@ -8,10 +8,10 @@ import (
log "github.com/tendermint/tendermint/libs/log" log "github.com/tendermint/tendermint/libs/log"
) )
func NewCertifier(chainID, rootDir string, client lclient.SignStatusClient, logger log.Logger) (*lite.InquiringCertifier, error) { func NewVerifier(chainID, rootDir string, client lclient.SignStatusClient, logger log.Logger) (*lite.DynamicVerifier, error) {
logger = logger.With("module", "lite/proxy") logger = logger.With("module", "lite/proxy")
logger.Info("lite/proxy/NewCertifier()...", "chainID", chainID, "rootDir", rootDir, "client", client) logger.Info("lite/proxy/NewVerifier()...", "chainID", chainID, "rootDir", rootDir, "client", client)
memProvider := lite.NewDBProvider("trusted.mem", dbm.NewMemDB()).SetLimit(10) memProvider := lite.NewDBProvider("trusted.mem", dbm.NewMemDB()).SetLimit(10)
lvlProvider := lite.NewDBProvider("trusted.lvl", dbm.NewDB("trust-base", dbm.LevelDBBackend, rootDir)) lvlProvider := lite.NewDBProvider("trusted.lvl", dbm.NewDB("trust-base", dbm.LevelDBBackend, rootDir))
@ -20,13 +20,13 @@ func NewCertifier(chainID, rootDir string, client lclient.SignStatusClient, logg
lvlProvider, lvlProvider,
) )
source := lclient.NewProvider(chainID, client) source := lclient.NewProvider(chainID, client)
cert := lite.NewInquiringCertifier(chainID, trust, source) cert := lite.NewDynamicVerifier(chainID, trust, source)
cert.SetLogger(logger) // Sets logger recursively. cert.SetLogger(logger) // Sets logger recursively.
// TODO: Make this more secure, e.g. make it interactive in the console? // TODO: Make this more secure, e.g. make it interactive in the console?
_, err := trust.LatestFullCommit(chainID, 1, 1<<63-1) _, err := trust.LatestFullCommit(chainID, 1, 1<<63-1)
if err != nil { if err != nil {
logger.Info("lite/proxy/NewCertifier found no trusted full commit, initializing from source from height 1...") logger.Info("lite/proxy/NewVerifier found no trusted full commit, initializing from source from height 1...")
fc, err := source.LatestFullCommit(chainID, 1, 1) fc, err := source.LatestFullCommit(chainID, 1, 1)
if err != nil { if err != nil {
return nil, cmn.ErrorWrap(err, "fetching source full commit @ height 1") return nil, cmn.ErrorWrap(err, "fetching source full commit @ height 1")

View File

@ -10,18 +10,18 @@ import (
var _ rpcclient.Client = Wrapper{} var _ rpcclient.Client = Wrapper{}
// Wrapper wraps a rpcclient with a Certifier and double-checks any input that is // Wrapper wraps a rpcclient with a Verifier and double-checks any input that is
// provable before passing it along. Allows you to make any rpcclient fully secure. // provable before passing it along. Allows you to make any rpcclient fully secure.
type Wrapper struct { type Wrapper struct {
rpcclient.Client rpcclient.Client
cert *lite.InquiringCertifier cert *lite.DynamicVerifier
} }
// SecureClient uses a given certifier to wrap an connection to an untrusted // SecureClient uses a given Verifier to wrap an connection to an untrusted
// host and return a cryptographically secure rpc client. // host and return a cryptographically secure rpc client.
// //
// If it is wrapping an HTTP rpcclient, it will also wrap the websocket interface // If it is wrapping an HTTP rpcclient, it will also wrap the websocket interface
func SecureClient(c rpcclient.Client, cert *lite.InquiringCertifier) Wrapper { func SecureClient(c rpcclient.Client, cert *lite.DynamicVerifier) Wrapper {
wrap := Wrapper{c, cert} wrap := Wrapper{c, cert}
// TODO: no longer possible as no more such interface exposed.... // TODO: no longer possible as no more such interface exposed....
// if we wrap http client, then we can swap out the event switch to filter // if we wrap http client, then we can swap out the event switch to filter

View File

@ -4,10 +4,10 @@ import (
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
) )
// Certifier checks the votes to make sure the block really is signed properly. // Verifier checks the votes to make sure the block really is signed properly.
// Certifier must know the current or recent set of validitors by some other // Verifier must know the current or recent set of validitors by some other
// means. // means.
type Certifier interface { type Verifier interface {
Certify(sheader types.SignedHeader) error Certify(sheader types.SignedHeader) error
ChainID() string ChainID() string
} }

View File

@ -446,7 +446,7 @@ type SignedHeader struct {
// and commit are consistent. // and commit are consistent.
// //
// NOTE: This does not actually check the cryptographic signatures. Make // NOTE: This does not actually check the cryptographic signatures. Make
// sure to use a Certifier to validate the signatures actually provide a // sure to use a Verifier to validate the signatures actually provide a
// significantly strong proof for this header's validity. // significantly strong proof for this header's validity.
func (sh SignedHeader) ValidateBasic(chainID string) error { func (sh SignedHeader) ValidateBasic(chainID string) error {

View File

@ -48,6 +48,11 @@ func NewValidatorSet(valz []*Validator) *ValidatorSet {
return vals return vals
} }
// Nil or empty validator sets are invalid.
func (vals *ValidatorSet) IsNilOrEmpty() bool {
return vals == nil || len(vals.Validators) == 0
}
// Increment Accum and update the proposer on a copy, and return it. // Increment Accum and update the proposer on a copy, and return it.
func (vals *ValidatorSet) CopyIncrementAccum(times int) *ValidatorSet { func (vals *ValidatorSet) CopyIncrementAccum(times int) *ValidatorSet {
copy := vals.Copy() copy := vals.Copy()