mirror of
https://github.com/fluencelabs/tendermint
synced 2025-04-24 22:32:15 +00:00
Addressed review for #1815 except those marked as 'TODO make issue'
This commit is contained in:
parent
eb9b37e196
commit
e719a93d1d
@ -556,7 +556,7 @@ BREAKING CHANGES:
|
||||
- use scripts/wal2json to convert to json for debugging
|
||||
|
||||
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: `/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.
|
||||
|
4
Gopkg.lock
generated
4
Gopkg.lock
generated
@ -11,7 +11,7 @@
|
||||
branch = "master"
|
||||
name = "github.com/btcsuite/btcd"
|
||||
packages = ["btcec"]
|
||||
revision = "9a2f9524024889e129a5422aca2cff73cb3eabf6"
|
||||
revision = "f5e261fc9ec3437697fb31d8b38453c293204b29"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/btcsuite/btcutil"
|
||||
@ -342,7 +342,7 @@
|
||||
"cpu",
|
||||
"unix"
|
||||
]
|
||||
revision = "bd9dbc187b6e1dacfdd2722a87e83093c2d7bd6e"
|
||||
revision = "3dc4335d56c789b04b0ba99b7a37249d9b614314"
|
||||
|
||||
[[projects]]
|
||||
name = "golang.org/x/text"
|
||||
|
@ -68,10 +68,10 @@ func runProxy(cmd *cobra.Command, args []string) error {
|
||||
logger.Info("Connecting to source HTTP client...")
|
||||
node := rpcclient.NewHTTP(nodeAddr, "/websocket")
|
||||
|
||||
logger.Info("Constructing certifier...")
|
||||
cert, err := proxy.NewCertifier(chainID, home, node, logger)
|
||||
logger.Info("Constructing Verifier...")
|
||||
cert, err := proxy.NewVerifier(chainID, home, node, logger)
|
||||
if err != nil {
|
||||
return cmn.ErrorWrap(err, "constructing certifier")
|
||||
return cmn.ErrorWrap(err, "constructing Verifier")
|
||||
}
|
||||
cert.SetLogger(logger)
|
||||
sc := proxy.SecureClient(node, cert)
|
||||
|
@ -3,48 +3,48 @@ package lite
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
lerr "github.com/tendermint/tendermint/lite/errors"
|
||||
"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.
|
||||
// To certify blocks produced by a blockchain with mutable validator sets,
|
||||
// use the InquiringCertifier.
|
||||
// use the DynamicVerifier.
|
||||
// TODO: Handle unbonding time.
|
||||
type BaseCertifier struct {
|
||||
type BaseVerifier struct {
|
||||
chainID string
|
||||
height int64
|
||||
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.
|
||||
func NewBaseCertifier(chainID string, height int64, valset *types.ValidatorSet) *BaseCertifier {
|
||||
if valset == nil || len(valset.Hash()) == 0 {
|
||||
panic("NewBaseCertifier requires a valid valset")
|
||||
func NewBaseVerifier(chainID string, height int64, valset *types.ValidatorSet) *BaseVerifier {
|
||||
if valset.IsNilOrEmpty() {
|
||||
panic("NewBaseVerifier requires a valid valset")
|
||||
}
|
||||
return &BaseCertifier{
|
||||
return &BaseVerifier{
|
||||
chainID: chainID,
|
||||
height: height,
|
||||
valset: valset,
|
||||
}
|
||||
}
|
||||
|
||||
// Implements Certifier.
|
||||
func (bc *BaseCertifier) ChainID() string {
|
||||
// Implements Verifier.
|
||||
func (bc *BaseVerifier) ChainID() string {
|
||||
return bc.chainID
|
||||
}
|
||||
|
||||
// Implements Certifier.
|
||||
func (bc *BaseCertifier) Certify(signedHeader types.SignedHeader) error {
|
||||
// Implements Verifier.
|
||||
func (bc *BaseVerifier) Certify(signedHeader types.SignedHeader) error {
|
||||
|
||||
// We can't certify commits older than 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)
|
||||
}
|
||||
|
@ -10,16 +10,14 @@ import (
|
||||
)
|
||||
|
||||
func TestBaseCert(t *testing.T) {
|
||||
// assert, require := assert.New(t), require.New(t)
|
||||
assert := assert.New(t)
|
||||
// require := require.New(t)
|
||||
|
||||
keys := genPrivKeys(4)
|
||||
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
|
||||
vals := keys.ToValidators(20, 10)
|
||||
// and a certifier based on our known set
|
||||
// and a Verifier based on our known set
|
||||
chainID := "test-static"
|
||||
cert := NewBaseCertifier(chainID, 2, vals)
|
||||
cert := NewBaseVerifier(chainID, 2, vals)
|
||||
|
||||
cases := []struct {
|
||||
keys privKeys
|
@ -8,12 +8,12 @@ package client
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
log "github.com/tendermint/tendermint/libs/log"
|
||||
"github.com/tendermint/tendermint/lite"
|
||||
lerr "github.com/tendermint/tendermint/lite/errors"
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
log "github.com/tendermint/tendermint/libs/log"
|
||||
)
|
||||
|
||||
// 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)
|
||||
return
|
||||
}
|
||||
heightPtr := new(int64)
|
||||
*heightPtr = height
|
||||
res, err := p.client.Validators(heightPtr)
|
||||
res, err := p.client.Validators(&height)
|
||||
if err != nil {
|
||||
// TODO pass through other types of errors.
|
||||
return nil, lerr.ErrMissingValidators(chainID, height)
|
||||
return nil, lerr.ErrUnknownValidators(chainID, height)
|
||||
}
|
||||
valset = types.NewValidatorSet(res.Validators)
|
||||
return
|
||||
|
@ -13,7 +13,6 @@ import (
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// TODO fix tests!!
|
||||
func TestMain(m *testing.M) {
|
||||
app := kvstore.NewKVStoreApplication()
|
||||
node := rpctest.StartTendermint(app)
|
||||
@ -59,15 +58,4 @@ func TestProvider(t *testing.T) {
|
||||
assert.Nil(err, "%+v", err)
|
||||
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))
|
||||
*/
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
// 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
|
||||
// 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.
|
||||
type FullCommit struct {
|
||||
SignedHeader types.SignedHeader `json:"signed_header"`
|
||||
|
@ -22,7 +22,10 @@ type DBProvider struct {
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
cdc := amino.NewCodec()
|
||||
cryptoAmino.RegisterAmino(cdc)
|
||||
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())
|
||||
return lfc, nil
|
||||
} else {
|
||||
dbp.logger.Info("DBProvider.LatestFullCommit() got error", "lfc", lfc)
|
||||
dbp.logger.Info(fmt.Sprintf("%+v", err))
|
||||
dbp.logger.Error("DBProvider.LatestFullCommit() got error", "lfc", lfc)
|
||||
dbp.logger.Error(fmt.Sprintf("%+v", 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) {
|
||||
vsBz := dbp.db.Get(validatorSetKey(chainID, height))
|
||||
if vsBz == nil {
|
||||
err = lerr.ErrMissingValidators(chainID, height)
|
||||
err = lerr.ErrUnknownValidators(chainID, height)
|
||||
return
|
||||
}
|
||||
err = dbp.cdc.UnmarshalBinary(vsBz, &valset)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
@ -209,52 +217,52 @@ func (dbp *DBProvider) deleteAfterN(chainID string, after int) error {
|
||||
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
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
// key encoding
|
||||
|
||||
func signedHeaderKey(chainID string, height int64) []byte {
|
||||
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 {
|
||||
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) {
|
||||
submatch := chainKeyPrefixPattern.FindSubmatch(key)
|
||||
var keyPattern = regexp.MustCompile(`^([^/]+)/([0-9]*)/(.*)$`)
|
||||
|
||||
func parseKey(key []byte) (chainID string, height int64, part string, ok bool) {
|
||||
submatch := keyPattern.FindSubmatch(key)
|
||||
if submatch == nil {
|
||||
return "", 0, false
|
||||
return "", 0, "", false
|
||||
}
|
||||
chainID = string(submatch[1])
|
||||
heightStr := string(submatch[2])
|
||||
heightInt, err := strconv.Atoi(heightStr)
|
||||
if err != nil {
|
||||
return "", 0, false
|
||||
return "", 0, "", false
|
||||
}
|
||||
height = int64(heightInt)
|
||||
part = string(submatch[3])
|
||||
ok = true // good!
|
||||
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
|
||||
}
|
||||
|
18
lite/doc.go
18
lite/doc.go
@ -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
|
||||
lite client syncing is optimized to use intelligent bisection and
|
||||
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).
|
||||
|
||||
The FullCommit is also declared in this package as a convenience structure,
|
||||
which includes the SignedHeader along with the full current and next
|
||||
ValidatorSets.
|
||||
|
||||
## Certifier
|
||||
## Verifier
|
||||
|
||||
A Certifier validates a new SignedHeader given the currently known state. There
|
||||
are two different types of Certifiers provided.
|
||||
A Verifier validates a new SignedHeader given the currently known state. There
|
||||
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
|
||||
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
|
||||
greater).
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## Provider and PersistentProvider
|
||||
@ -77,7 +77,7 @@ type Provider interface {
|
||||
* client.NewHTTPProvider - query Tendermint rpc.
|
||||
|
||||
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
|
||||
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 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.
|
||||
|
||||
TODO: Update the software to handle cases around the unbonding period.
|
||||
|
@ -3,18 +3,18 @@ package lite
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
log "github.com/tendermint/tendermint/libs/log"
|
||||
lerr "github.com/tendermint/tendermint/lite/errors"
|
||||
"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
|
||||
// validator set changes. It stores properly validated data on the
|
||||
// "trusted" local system.
|
||||
type InquiringCertifier struct {
|
||||
type DynamicVerifier struct {
|
||||
logger log.Logger
|
||||
chainID string
|
||||
// These are only properly validated data, from local system.
|
||||
@ -23,14 +23,14 @@ type InquiringCertifier struct {
|
||||
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
|
||||
// obtain missing data (e.g. FullCommits).
|
||||
//
|
||||
// The trusted provider should a CacheProvider, MemProvider or
|
||||
// files.Provider. The source provider should be a client.HTTPProvider.
|
||||
func NewInquiringCertifier(chainID string, trusted PersistentProvider, source Provider) *InquiringCertifier {
|
||||
return &InquiringCertifier{
|
||||
func NewDynamicVerifier(chainID string, trusted PersistentProvider, source Provider) *DynamicVerifier {
|
||||
return &DynamicVerifier{
|
||||
logger: log.NewNopLogger(),
|
||||
chainID: chainID,
|
||||
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")
|
||||
ic.logger = logger
|
||||
ic.trusted.SetLogger(logger)
|
||||
ic.source.SetLogger(logger)
|
||||
}
|
||||
|
||||
// Implements Certifier.
|
||||
func (ic *InquiringCertifier) ChainID() string {
|
||||
// Implements Verifier.
|
||||
func (ic *DynamicVerifier) ChainID() string {
|
||||
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
|
||||
// try to store the SignedHeader in ic.trusted if the next
|
||||
// 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.
|
||||
// The full commit at h-1 contains the valset to sign for h.
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
if tfc.Height() == h {
|
||||
if trustedFC.Height() == h {
|
||||
// Return error if valset doesn't match.
|
||||
if !bytes.Equal(
|
||||
tfc.NextValidators.Hash(),
|
||||
trustedFC.NextValidators.Hash(),
|
||||
shdr.Header.ValidatorsHash) {
|
||||
return lerr.ErrUnexpectedValidators(
|
||||
tfc.NextValidators.Hash(),
|
||||
trustedFC.NextValidators.Hash(),
|
||||
shdr.Header.ValidatorsHash)
|
||||
}
|
||||
} else {
|
||||
// If valset doesn't match...
|
||||
if !bytes.Equal(tfc.NextValidators.Hash(),
|
||||
if !bytes.Equal(trustedFC.NextValidators.Hash(),
|
||||
shdr.Header.ValidatorsHash) {
|
||||
// ... update.
|
||||
tfc, err = ic.updateToHeight(h)
|
||||
trustedFC, err = ic.updateToHeight(h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Return error if valset _still_ doesn't match.
|
||||
if !bytes.Equal(tfc.NextValidators.Hash(),
|
||||
if !bytes.Equal(trustedFC.NextValidators.Hash(),
|
||||
shdr.Header.ValidatorsHash) {
|
||||
return lerr.ErrUnexpectedValidators(
|
||||
tfc.NextValidators.Hash(),
|
||||
trustedFC.NextValidators.Hash(),
|
||||
shdr.Header.ValidatorsHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -103,7 +103,7 @@ func (ic *InquiringCertifier) Certify(shdr types.SignedHeader) error {
|
||||
|
||||
// Get the next validator set.
|
||||
nextValset, err := ic.source.ValidatorSet(ic.chainID, shdr.Height+1)
|
||||
if lerr.IsErrMissingValidators(err) {
|
||||
if lerr.IsErrUnknownValidators(err) {
|
||||
// Ignore this error.
|
||||
return nil
|
||||
} else if err != nil {
|
||||
@ -113,7 +113,7 @@ func (ic *InquiringCertifier) Certify(shdr types.SignedHeader) error {
|
||||
// Create filled FullCommit.
|
||||
nfc := FullCommit{
|
||||
SignedHeader: shdr,
|
||||
Validators: tfc.NextValidators,
|
||||
Validators: trustedFC.NextValidators,
|
||||
NextValidators: nextValset,
|
||||
}
|
||||
// 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
|
||||
// best match trusted full commit, and if good, persist to ic.trusted.
|
||||
// Returns ErrTooMuchChange when >2/3 of tfc did not sign sfc.
|
||||
// Panics if tfc.Height() >= sfc.Height().
|
||||
func (ic *InquiringCertifier) verifyAndSave(tfc, sfc FullCommit) error {
|
||||
if tfc.Height() >= sfc.Height() {
|
||||
// Returns ErrTooMuchChange when >2/3 of trustedFC did not sign sourceFC.
|
||||
// Panics if trustedFC.Height() >= sourceFC.Height().
|
||||
func (ic *DynamicVerifier) verifyAndSave(trustedFC, sourceFC FullCommit) error {
|
||||
if trustedFC.Height() >= sourceFC.Height() {
|
||||
panic("should not happen")
|
||||
}
|
||||
err := tfc.NextValidators.VerifyFutureCommit(
|
||||
sfc.Validators,
|
||||
ic.chainID, sfc.SignedHeader.Commit.BlockID,
|
||||
sfc.SignedHeader.Height, sfc.SignedHeader.Commit,
|
||||
err := trustedFC.NextValidators.VerifyFutureCommit(
|
||||
sourceFC.Validators,
|
||||
ic.chainID, sourceFC.SignedHeader.Commit.BlockID,
|
||||
sourceFC.SignedHeader.Height, sourceFC.SignedHeader.Commit,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ic.trusted.SaveFullCommit(sfc)
|
||||
return ic.trusted.SaveFullCommit(sourceFC)
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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.
|
||||
sfc, err := ic.source.LatestFullCommit(ic.chainID, h, h)
|
||||
sourceFC, err := ic.source.LatestFullCommit(ic.chainID, h, h)
|
||||
if err != nil {
|
||||
return FullCommit{}, err
|
||||
}
|
||||
|
||||
// Validate the full commit. This checks the cryptographic
|
||||
// signatures of Commit against Validators.
|
||||
if err := sfc.ValidateFull(ic.chainID); err != nil {
|
||||
if err := sourceFC.ValidateFull(ic.chainID); err != nil {
|
||||
return FullCommit{}, err
|
||||
}
|
||||
|
||||
// If sfc.Height() != h, we can't do it.
|
||||
if sfc.Height() != h {
|
||||
// If sourceFC.Height() != h, we can't do it.
|
||||
if sourceFC.Height() != h {
|
||||
return FullCommit{}, lerr.ErrCommitNotFound()
|
||||
}
|
||||
|
||||
FOR_LOOP:
|
||||
for {
|
||||
// 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 {
|
||||
return FullCommit{}, err
|
||||
}
|
||||
// We have nothing to do.
|
||||
if tfc.Height() == h {
|
||||
return tfc, nil
|
||||
if trustedFC.Height() == h {
|
||||
return trustedFC, nil
|
||||
}
|
||||
|
||||
// Try to update to full commit with checks.
|
||||
err = ic.verifyAndSave(tfc, sfc)
|
||||
err = ic.verifyAndSave(trustedFC, sourceFC)
|
||||
if err == nil {
|
||||
// All good!
|
||||
return sfc, nil
|
||||
return sourceFC, nil
|
||||
}
|
||||
|
||||
// Handle special case when err is ErrTooMuchChange.
|
||||
if lerr.IsErrTooMuchChange(err) {
|
||||
// Divide and conquer.
|
||||
start, end := tfc.Height(), sfc.Height()
|
||||
start, end := trustedFC.Height(), sourceFC.Height()
|
||||
if !(start < end) {
|
||||
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)
|
||||
if err != nil {
|
||||
panic("should not happen")
|
@ -41,10 +41,10 @@ func TestInquirerValidPath(t *testing.T) {
|
||||
nkeys = nkeys.Extend(1)
|
||||
}
|
||||
|
||||
// Initialize a certifier with the initial state.
|
||||
// Initialize a Verifier with the initial state.
|
||||
err := trust.SaveFullCommit(fcz[0])
|
||||
require.Nil(err)
|
||||
cert := NewInquiringCertifier(chainID, trust, source)
|
||||
cert := NewDynamicVerifier(chainID, trust, source)
|
||||
cert.SetLogger(log.TestingLogger())
|
||||
|
||||
// This should fail validation:
|
||||
@ -99,10 +99,10 @@ func TestInquirerVerifyHistorical(t *testing.T) {
|
||||
nkeys = nkeys.Extend(1)
|
||||
}
|
||||
|
||||
// Initialize a certifier with the initial state.
|
||||
// Initialize a Verifier with the initial state.
|
||||
err := trust.SaveFullCommit(fcz[0])
|
||||
require.Nil(err)
|
||||
cert := NewInquiringCertifier(chainID, trust, source)
|
||||
cert := NewDynamicVerifier(chainID, trust, source)
|
||||
cert.SetLogger(log.TestingLogger())
|
||||
|
||||
// Store a few full commits as trust.
|
@ -31,12 +31,12 @@ func (e errTooMuchChange) Error() string {
|
||||
return "Insufficient signatures to validate due to valset changes"
|
||||
}
|
||||
|
||||
type errMissingValidators struct {
|
||||
type errUnknownValidators struct {
|
||||
chainID string
|
||||
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",
|
||||
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.
|
||||
func ErrMissingValidators(chainID string, height int64) error {
|
||||
return cmn.ErrorWrap(errMissingValidators{chainID, height}, "")
|
||||
// ErrUnknownValidators indicates that some validator set was missing or unknown.
|
||||
func ErrUnknownValidators(chainID string, height int64) error {
|
||||
return cmn.ErrorWrap(errUnknownValidators{chainID, height}, "")
|
||||
}
|
||||
|
||||
func IsErrMissingValidators(err error) bool {
|
||||
func IsErrUnknownValidators(err error) bool {
|
||||
if err_, ok := err.(cmn.Error); ok {
|
||||
_, ok := err_.Data().(errMissingValidators)
|
||||
_, ok := err_.Data().(errUnknownValidators)
|
||||
return ok
|
||||
}
|
||||
return false
|
||||
|
@ -1,9 +1,9 @@
|
||||
package lite
|
||||
|
||||
import (
|
||||
log "github.com/tendermint/tendermint/libs/log"
|
||||
lerr "github.com/tendermint/tendermint/lite/errors"
|
||||
"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
|
||||
@ -79,5 +79,5 @@ func (mc *multiProvider) ValidatorSet(chainID string, height int64) (valset *typ
|
||||
return valset, nil
|
||||
}
|
||||
}
|
||||
return nil, lerr.ErrMissingValidators(chainID, height)
|
||||
return nil, lerr.ErrUnknownValidators(chainID, height)
|
||||
}
|
||||
|
@ -28,13 +28,13 @@ type KeyProof interface {
|
||||
}
|
||||
|
||||
// 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 val is non-empty, proof should be KeyExistsProof
|
||||
// If val is empty, proof should be KeyMissingProof
|
||||
func GetWithProof(key []byte, reqHeight int64, node rpcclient.Client,
|
||||
cert lite.Certifier) (
|
||||
cert lite.Verifier) (
|
||||
val cmn.HexBytes, height int64, proof KeyProof, err error) {
|
||||
|
||||
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
|
||||
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) {
|
||||
|
||||
_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
|
||||
// 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
|
||||
// Validators and will fail on querying tendermint for non-current height.
|
||||
|
@ -58,7 +58,7 @@ func _TestAppProofs(t *testing.T) {
|
||||
source := certclient.NewProvider(chainID, cl)
|
||||
seed, err := source.LatestFullCommit(chainID, brh-2, brh-2)
|
||||
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)
|
||||
latest, err := source.LatestFullCommit(chainID, 1, 1<<63-1)
|
||||
@ -117,7 +117,7 @@ func _TestTxProofs(t *testing.T) {
|
||||
source := certclient.NewProvider(chainID, cl)
|
||||
seed, err := source.LatestFullCommit(chainID, brh-2, brh-2)
|
||||
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.
|
||||
key := types.Tx([]byte("bogus")).Hash()
|
||||
|
@ -8,10 +8,10 @@ import (
|
||||
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.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)
|
||||
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,
|
||||
)
|
||||
source := lclient.NewProvider(chainID, client)
|
||||
cert := lite.NewInquiringCertifier(chainID, trust, source)
|
||||
cert := lite.NewDynamicVerifier(chainID, trust, source)
|
||||
cert.SetLogger(logger) // Sets logger recursively.
|
||||
|
||||
// TODO: Make this more secure, e.g. make it interactive in the console?
|
||||
_, err := trust.LatestFullCommit(chainID, 1, 1<<63-1)
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, cmn.ErrorWrap(err, "fetching source full commit @ height 1")
|
@ -10,18 +10,18 @@ import (
|
||||
|
||||
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.
|
||||
type Wrapper struct {
|
||||
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.
|
||||
//
|
||||
// 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}
|
||||
// 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
|
||||
|
@ -4,10 +4,10 @@ import (
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// Certifier 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 checks the votes to make sure the block really is signed properly.
|
||||
// Verifier must know the current or recent set of validitors by some other
|
||||
// means.
|
||||
type Certifier interface {
|
||||
type Verifier interface {
|
||||
Certify(sheader types.SignedHeader) error
|
||||
ChainID() string
|
||||
}
|
||||
|
@ -446,7 +446,7 @@ type SignedHeader struct {
|
||||
// and commit are consistent.
|
||||
//
|
||||
// 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.
|
||||
func (sh SignedHeader) ValidateBasic(chainID string) error {
|
||||
|
||||
|
@ -48,6 +48,11 @@ func NewValidatorSet(valz []*Validator) *ValidatorSet {
|
||||
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.
|
||||
func (vals *ValidatorSet) CopyIncrementAccum(times int) *ValidatorSet {
|
||||
copy := vals.Copy()
|
||||
|
Loading…
x
Reference in New Issue
Block a user