Vulnerability in light client proxy (#1081)

* Vulnerability in light client proxy

When calling GetCertifiedCommit the light client proxy would call
Certify and even on error return the Commit as if it had been correctly
certified.

Now it returns the error correctly and returns an empty Commit on error.

* Improve names for clarity

The lite package now contains StaticCertifier, DynamicCertifier and
InqueringCertifier. This also changes the method receivers from one
letter to two letter names, which will make future refactoring easier
and follows the coding standards.

* Fix test failures

* Rename files

* remove dead code
This commit is contained in:
Adrian Brink
2018-01-09 17:36:11 +01:00
committed by Anton Kaliaev
parent b9cbaf8f10
commit 32311acd01
16 changed files with 246 additions and 232 deletions

View File

@ -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 := lite.NewStatic(chainID, seed.Validators) cert := lite.NewStaticCertifier(chainID, seed.Validators)
// historical queries now work :) // historical queries now work :)
lower := sh - 5 lower := sh - 5

View File

@ -6,9 +6,9 @@ import (
liteErr "github.com/tendermint/tendermint/lite/errors" liteErr "github.com/tendermint/tendermint/lite/errors"
) )
var _ Certifier = &Dynamic{} var _ Certifier = (*DynamicCertifier)(nil)
// Dynamic uses a Static for Certify, but adds an // DynamicCertifier uses a StaticCertifier for Certify, but adds an
// Update method to allow for a change of validators. // Update method to allow for a change of validators.
// //
// You can pass in a FullCommit with another validator set, // You can pass in a FullCommit with another validator set,
@ -17,46 +17,48 @@ var _ Certifier = &Dynamic{}
// validator set for the next Certify call. // validator set for the next Certify call.
// For security, it will only follow validator set changes // For security, it will only follow validator set changes
// going forward. // going forward.
type Dynamic struct { type DynamicCertifier struct {
cert *Static cert *StaticCertifier
lastHeight int64 lastHeight int64
} }
// NewDynamic returns a new dynamic certifier. // NewDynamic returns a new dynamic certifier.
func NewDynamic(chainID string, vals *types.ValidatorSet, height int64) *Dynamic { func NewDynamicCertifier(chainID string, vals *types.ValidatorSet, height int64) *DynamicCertifier {
return &Dynamic{ return &DynamicCertifier{
cert: NewStatic(chainID, vals), cert: NewStaticCertifier(chainID, vals),
lastHeight: height, lastHeight: height,
} }
} }
// ChainID returns the chain id of this certifier. // ChainID returns the chain id of this certifier.
func (c *Dynamic) ChainID() string { // Implements Certifier.
return c.cert.ChainID() func (dc *DynamicCertifier) ChainID() string {
return dc.cert.ChainID()
} }
// Validators returns the validators of this certifier. // Validators returns the validators of this certifier.
func (c *Dynamic) Validators() *types.ValidatorSet { func (dc *DynamicCertifier) Validators() *types.ValidatorSet {
return c.cert.vSet return dc.cert.vSet
} }
// Hash returns the hash of this certifier. // Hash returns the hash of this certifier.
func (c *Dynamic) Hash() []byte { func (dc *DynamicCertifier) Hash() []byte {
return c.cert.Hash() return dc.cert.Hash()
} }
// LastHeight returns the last height of this certifier. // LastHeight returns the last height of this certifier.
func (c *Dynamic) LastHeight() int64 { func (dc *DynamicCertifier) LastHeight() int64 {
return c.lastHeight return dc.lastHeight
} }
// Certify will verify whether the commit is valid and will update the height if it is or return an // Certify will verify whether the commit is valid and will update the height if it is or return an
// error if it is not. // error if it is not.
func (c *Dynamic) Certify(check Commit) error { // Implements Certifier.
err := c.cert.Certify(check) func (dc *DynamicCertifier) Certify(check Commit) error {
err := dc.cert.Certify(check)
if err == nil { if err == nil {
// update last seen height if input is valid // update last seen height if input is valid
c.lastHeight = check.Height() dc.lastHeight = check.Height()
} }
return err return err
} }
@ -65,15 +67,15 @@ func (c *Dynamic) Certify(check Commit) error {
// the certifying validator set if safe to do so. // the certifying validator set if safe to do so.
// //
// Returns an error if update is impossible (invalid proof or IsTooMuchChangeErr) // Returns an error if update is impossible (invalid proof or IsTooMuchChangeErr)
func (c *Dynamic) Update(fc FullCommit) error { func (dc *DynamicCertifier) 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 <= dc.lastHeight {
return liteErr.ErrPastTime() return liteErr.ErrPastTime()
} }
// first, verify if the input is self-consistent.... // first, verify if the input is self-consistent....
err := fc.ValidateBasic(c.ChainID()) err := fc.ValidateBasic(dc.ChainID())
if err != nil { if err != nil {
return err return err
} }
@ -82,14 +84,13 @@ func (c *Dynamic) Update(fc FullCommit) error {
// would be approved by the currently known validator set // would be approved by the currently known validator set
// as well as the new set // as well as the new set
commit := fc.Commit.Commit commit := fc.Commit.Commit
err = c.Validators().VerifyCommitAny(fc.Validators, c.ChainID(), err = dc.Validators().VerifyCommitAny(fc.Validators, dc.ChainID(), commit.BlockID, h, commit)
commit.BlockID, h, commit)
if err != nil { if err != nil {
return liteErr.ErrTooMuchChange() return liteErr.ErrTooMuchChange()
} }
// looks good, we can update // looks good, we can update
c.cert = NewStatic(c.ChainID(), fc.Validators) dc.cert = NewStaticCertifier(dc.ChainID(), fc.Validators)
c.lastHeight = h dc.lastHeight = h
return nil return nil
} }

View File

@ -23,7 +23,7 @@ func TestDynamicCert(t *testing.T) {
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 := lite.NewDynamic(chainID, vals, 0) cert := lite.NewDynamicCertifier(chainID, vals, 0)
cases := []struct { cases := []struct {
keys lite.ValKeys keys lite.ValKeys
@ -67,7 +67,7 @@ func TestDynamicUpdate(t *testing.T) {
chainID := "test-dyno-up" chainID := "test-dyno-up"
keys := lite.GenValKeys(5) keys := lite.GenValKeys(5)
vals := keys.ToValidators(20, 0) vals := keys.ToValidators(20, 0)
cert := lite.NewDynamic(chainID, vals, 40) cert := lite.NewDynamicCertifier(chainID, vals, 40)
// one valid block to give us a sense of time // one valid block to give us a sense of time
h := int64(100) h := int64(100)

View File

@ -1,159 +0,0 @@
package lite
import (
"github.com/tendermint/tendermint/types"
liteErr "github.com/tendermint/tendermint/lite/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 {
cert *Dynamic
// These are only properly validated data, from local system
trusted Provider
// This is a source of new info, like a node rpc, or other import method
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, error) {
// store the data in trusted
err := trusted.StoreCommit(fc)
if err != nil {
return nil, err
}
return &Inquiring{
cert: NewDynamic(chainID, fc.Validators, fc.Height()),
trusted: trusted,
Source: source,
}, nil
}
// ChainID returns the chain id.
func (c *Inquiring) ChainID() string {
return c.cert.ChainID()
}
// Validators returns the validator set.
func (c *Inquiring) Validators() *types.ValidatorSet {
return c.cert.cert.vSet
}
// LastHeight returns the last height.
func (c *Inquiring) LastHeight() int64 {
return c.cert.lastHeight
}
// Certify makes sure this is checkpoint is valid.
//
// If the validators have changed since the last know time, it looks
// for a path to prove the new validators.
//
// On success, it will store the checkpoint in the store for later viewing
func (c *Inquiring) Certify(commit Commit) error {
err := c.useClosestTrust(commit.Height())
if err != nil {
return err
}
err = c.cert.Certify(commit)
if !liteErr.IsValidatorsChangedErr(err) {
return err
}
err = c.updateToHash(commit.Header.ValidatorsHash)
if err != nil {
return err
}
err = c.cert.Certify(commit)
if err != nil {
return err
}
// store the new checkpoint
return c.trusted.StoreCommit(NewFullCommit(commit, c.Validators()))
}
// 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 {
err := c.useClosestTrust(fc.Height())
if err != nil {
return err
}
err = c.cert.Update(fc)
if err == nil {
err = c.trusted.StoreCommit(fc)
}
return err
}
func (c *Inquiring) useClosestTrust(h int64) error {
closest, err := c.trusted.GetByHeight(h)
if err != nil {
return err
}
// if the best seed is not the one we currently use,
// let's just reset the dynamic validator
if closest.Height() != c.LastHeight() {
c.cert = NewDynamic(c.ChainID(), closest.Validators, closest.Height())
}
return nil
}
// updateToHash gets the validator hash we want to update to
// if IsTooMuchChangeErr, we try to find a path by binary search over height
func (c *Inquiring) updateToHash(vhash []byte) error {
// try to get the match, and update
fc, err := c.Source.GetByHash(vhash)
if err != nil {
return err
}
err = c.cert.Update(fc)
// handle IsTooMuchChangeErr by using divide and conquer
if liteErr.IsTooMuchChangeErr(err) {
err = c.updateToHeight(fc.Height())
}
return err
}
// updateToHeight will use divide-and-conquer to find a path to h
func (c *Inquiring) updateToHeight(h int64) error {
// try to update to this height (with checks)
fc, err := c.Source.GetByHeight(h)
if err != nil {
return err
}
start, end := c.LastHeight(), fc.Height()
if end <= start {
return liteErr.ErrNoPathFound()
}
err = c.Update(fc)
// we can handle IsTooMuchChangeErr specially
if !liteErr.IsTooMuchChangeErr(err) {
return err
}
// try to update to mid
mid := (start + end) / 2
err = c.updateToHeight(mid)
if err != nil {
return err
}
// if we made it to mid, we recurse
return c.updateToHeight(h)
}

163
lite/inquiring_certifier.go Normal file
View File

@ -0,0 +1,163 @@
package lite
import (
"github.com/tendermint/tendermint/types"
liteErr "github.com/tendermint/tendermint/lite/errors"
)
var _ Certifier = (*InquiringCertifier)(nil)
// InquiringCertifier wraps a dynamic certifier and implements an auto-update strategy. If a call
// to Certify fails due to a change it validator set, InquiringCertifier 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 InquiringCertifier struct {
cert *DynamicCertifier
// These are only properly validated data, from local system
trusted Provider
// This is a source of new info, like a node rpc, or other import method
Source Provider
}
// NewInquiringCertifier 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 NewInquiringCertifier(chainID string, fc FullCommit, trusted Provider,
source Provider) (*InquiringCertifier, error) {
// store the data in trusted
err := trusted.StoreCommit(fc)
if err != nil {
return nil, err
}
return &InquiringCertifier{
cert: NewDynamicCertifier(chainID, fc.Validators, fc.Height()),
trusted: trusted,
Source: source,
}, nil
}
// ChainID returns the chain id.
// Implements Certifier.
func (ic *InquiringCertifier) ChainID() string {
return ic.cert.ChainID()
}
// Validators returns the validator set.
func (ic *InquiringCertifier) Validators() *types.ValidatorSet {
return ic.cert.cert.vSet
}
// LastHeight returns the last height.
func (ic *InquiringCertifier) LastHeight() int64 {
return ic.cert.lastHeight
}
// Certify makes sure this is checkpoint is valid.
//
// If the validators have changed since the last know time, it looks
// for a path to prove the new validators.
//
// On success, it will store the checkpoint in the store for later viewing
// Implements Certifier.
func (ic *InquiringCertifier) Certify(commit Commit) error {
err := ic.useClosestTrust(commit.Height())
if err != nil {
return err
}
err = ic.cert.Certify(commit)
if !liteErr.IsValidatorsChangedErr(err) {
return err
}
err = ic.updateToHash(commit.Header.ValidatorsHash)
if err != nil {
return err
}
err = ic.cert.Certify(commit)
if err != nil {
return err
}
// store the new checkpoint
return ic.trusted.StoreCommit(NewFullCommit(commit, ic.Validators()))
}
// Update will verify if this is a valid change and update
// the certifying validator set if safe to do so.
func (ic *InquiringCertifier) Update(fc FullCommit) error {
err := ic.useClosestTrust(fc.Height())
if err != nil {
return err
}
err = ic.cert.Update(fc)
if err == nil {
err = ic.trusted.StoreCommit(fc)
}
return err
}
func (ic *InquiringCertifier) useClosestTrust(h int64) error {
closest, err := ic.trusted.GetByHeight(h)
if err != nil {
return err
}
// if the best seed is not the one we currently use,
// let's just reset the dynamic validator
if closest.Height() != ic.LastHeight() {
ic.cert = NewDynamicCertifier(ic.ChainID(), closest.Validators, closest.Height())
}
return nil
}
// updateToHash gets the validator hash we want to update to
// if IsTooMuchChangeErr, we try to find a path by binary search over height
func (ic *InquiringCertifier) updateToHash(vhash []byte) error {
// try to get the match, and update
fc, err := ic.Source.GetByHash(vhash)
if err != nil {
return err
}
err = ic.cert.Update(fc)
// handle IsTooMuchChangeErr by using divide and conquer
if liteErr.IsTooMuchChangeErr(err) {
err = ic.updateToHeight(fc.Height())
}
return err
}
// updateToHeight will use divide-and-conquer to find a path to h
func (ic *InquiringCertifier) updateToHeight(h int64) error {
// try to update to this height (with checks)
fc, err := ic.Source.GetByHeight(h)
if err != nil {
return err
}
start, end := ic.LastHeight(), fc.Height()
if end <= start {
return liteErr.ErrNoPathFound()
}
err = ic.Update(fc)
// we can handle IsTooMuchChangeErr specially
if !liteErr.IsTooMuchChangeErr(err) {
return err
}
// try to update to mid
mid := (start + end) / 2
err = ic.updateToHeight(mid)
if err != nil {
return err
}
// if we made it to mid, we recurse
return ic.updateToHeight(h)
}

View File

@ -37,7 +37,7 @@ func TestInquirerValidPath(t *testing.T) {
} }
// initialize a certifier with the initial state // initialize a certifier with the initial state
cert, err := lite.NewInquiring(chainID, commits[0], trust, source) cert, err := lite.NewInquiringCertifier(chainID, commits[0], trust, source)
require.Nil(err) require.Nil(err)
// this should fail validation.... // this should fail validation....
@ -88,7 +88,7 @@ func TestInquirerMinimalPath(t *testing.T) {
} }
// initialize a certifier with the initial state // initialize a certifier with the initial state
cert, _ := lite.NewInquiring(chainID, commits[0], trust, source) cert, _ := lite.NewInquiringCertifier(chainID, commits[0], trust, source)
// this should fail validation.... // this should fail validation....
commit := commits[count-1].Commit commit := commits[count-1].Commit
@ -138,7 +138,7 @@ func TestInquirerVerifyHistorical(t *testing.T) {
} }
// initialize a certifier with the initial state // initialize a certifier with the initial state
cert, _ := lite.NewInquiring(chainID, commits[0], trust, source) cert, _ := lite.NewInquiringCertifier(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

@ -105,7 +105,7 @@ func BenchmarkCertifyCommitSec100(b *testing.B) {
func benchmarkCertifyCommit(b *testing.B, keys lite.ValKeys) { func benchmarkCertifyCommit(b *testing.B, keys lite.ValKeys) {
chainID := "bench-certify" chainID := "bench-certify"
vals := keys.ToValidators(20, 10) vals := keys.ToValidators(20, 10)
cert := lite.NewStatic(chainID, vals) cert := lite.NewStaticCertifier(chainID, vals)
check := keys.GenCommit(chainID, 123, nil, vals, []byte("foo"), []byte("params"), []byte("res"), 0, len(keys)) check := keys.GenCommit(chainID, 123, nil, vals, []byte("foo"), []byte("params"), []byte("res"), 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

@ -6,7 +6,7 @@ import (
"github.com/tendermint/tendermint/lite/files" "github.com/tendermint/tendermint/lite/files"
) )
func GetCertifier(chainID, rootDir, nodeAddr string) (*lite.Inquiring, error) { func GetCertifier(chainID, rootDir, nodeAddr string) (*lite.InquiringCertifier, error) {
trust := lite.NewCacheProvider( trust := lite.NewCacheProvider(
lite.NewMemStoreProvider(), lite.NewMemStoreProvider(),
files.NewProvider(rootDir), files.NewProvider(rootDir),
@ -26,7 +26,7 @@ func GetCertifier(chainID, rootDir, nodeAddr string) (*lite.Inquiring, error) {
return nil, err return nil, err
} }
cert, err := lite.NewInquiring(chainID, fc, trust, source) cert, err := lite.NewInquiringCertifier(chainID, fc, trust, source)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -18,7 +18,11 @@ const (
// set up the rpc routes to proxy via the given client, // set up the rpc routes to proxy via the given client,
// and start up an http/rpc server on the location given by bind (eg. :1234) // and start up an http/rpc server on the location given by bind (eg. :1234)
func StartProxy(c rpcclient.Client, listenAddr string, logger log.Logger) error { func StartProxy(c rpcclient.Client, listenAddr string, logger log.Logger) error {
c.Start() err := c.Start()
if err != nil {
return err
}
r := RPCRoutes(c) r := RPCRoutes(c)
// build the handler... // build the handler...
@ -30,7 +34,7 @@ func StartProxy(c rpcclient.Client, listenAddr string, logger log.Logger) error
core.SetLogger(logger) core.SetLogger(logger)
mux.HandleFunc(wsEndpoint, wm.WebsocketHandler) mux.HandleFunc(wsEndpoint, wm.WebsocketHandler)
_, err := rpc.StartHTTPServer(listenAddr, mux, logger) _, err = rpc.StartHTTPServer(listenAddr, mux, logger)
return err return err
} }

View File

@ -51,7 +51,7 @@ func GetWithProofOptions(path string, key []byte, opts rpcclient.ABCIQueryOption
// make sure the proof is the proper height // make sure the proof is the proper height
if resp.IsErr() { if resp.IsErr() {
err = errors.Errorf("Query error %d: %d", resp.Code) err = errors.Errorf("Query error for key %d: %d", key, resp.Code)
return nil, nil, err return nil, nil, err
} }
if len(resp.Key) == 0 || len(resp.Proof) == 0 { if len(resp.Key) == 0 || len(resp.Proof) == 0 {
@ -79,7 +79,7 @@ func GetWithProofOptions(path string, key []byte, opts rpcclient.ABCIQueryOption
if err != nil { if err != nil {
return nil, nil, errors.Wrap(err, "Couldn't verify proof") return nil, nil, errors.Wrap(err, "Couldn't verify proof")
} }
return &ctypes.ResultABCIQuery{resp}, eproof, nil return &ctypes.ResultABCIQuery{Response: resp}, eproof, nil
} }
// The key wasn't found, construct a proof of non-existence. // The key wasn't found, construct a proof of non-existence.
@ -93,13 +93,12 @@ func GetWithProofOptions(path string, key []byte, opts rpcclient.ABCIQueryOption
if err != nil { if err != nil {
return nil, nil, errors.Wrap(err, "Couldn't verify proof") return nil, nil, errors.Wrap(err, "Couldn't verify proof")
} }
return &ctypes.ResultABCIQuery{resp}, aproof, ErrNoData() return &ctypes.ResultABCIQuery{Response: resp}, aproof, ErrNoData()
} }
// GetCertifiedCommit gets the signed header for a given height // GetCertifiedCommit gets the signed header for a given height
// and certifies it. Returns error if unable to get a proven header. // and certifies it. Returns error if unable to get a proven header.
func GetCertifiedCommit(h int64, node rpcclient.Client, func GetCertifiedCommit(h int64, node rpcclient.Client, cert lite.Certifier) (lite.Commit, error) {
cert lite.Certifier) (empty lite.Commit, err 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.
@ -107,14 +106,18 @@ func GetCertifiedCommit(h int64, node rpcclient.Client,
rpcclient.WaitForHeight(node, h, nil) rpcclient.WaitForHeight(node, h, nil)
cresp, err := node.Commit(&h) cresp, err := node.Commit(&h)
if err != nil { if err != nil {
return return lite.Commit{}, err
} }
commit := client.CommitFromResult(cresp)
commit := client.CommitFromResult(cresp)
// validate downloaded checkpoint with our request and trust store. // validate downloaded checkpoint with our request and trust store.
if commit.Height() != h { if commit.Height() != h {
return empty, certerr.ErrHeightMismatch(h, commit.Height()) return lite.Commit{}, certerr.ErrHeightMismatch(h, commit.Height())
} }
err = cert.Certify(commit)
if err = cert.Certify(commit); err != nil {
return lite.Commit{}, err
}
return commit, nil return commit, nil
} }

View File

@ -58,7 +58,7 @@ func _TestAppProofs(t *testing.T) {
source := certclient.NewProvider(cl) source := certclient.NewProvider(cl)
seed, err := source.GetByHeight(brh - 2) seed, err := source.GetByHeight(brh - 2)
require.NoError(err, "%+v", err) require.NoError(err, "%+v", err)
cert := lite.NewStatic("my-chain", seed.Validators) cert := lite.NewStaticCertifier("my-chain", seed.Validators)
client.WaitForHeight(cl, 3, nil) client.WaitForHeight(cl, 3, nil)
latest, err := source.LatestCommit() latest, err := source.LatestCommit()
@ -117,7 +117,7 @@ func _TestTxProofs(t *testing.T) {
source := certclient.NewProvider(cl) source := certclient.NewProvider(cl)
seed, err := source.GetByHeight(brh - 2) seed, err := source.GetByHeight(brh - 2)
require.NoError(err, "%+v", err) require.NoError(err, "%+v", err)
cert := lite.NewStatic("my-chain", seed.Validators) cert := lite.NewStaticCertifier("my-chain", 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()
@ -136,5 +136,4 @@ func _TestTxProofs(t *testing.T) {
commit, err := GetCertifiedCommit(br.Height, cl, cert) commit, err := GetCertifiedCommit(br.Height, cl, cert)
require.Nil(err, "%+v", err) require.Nil(err, "%+v", err)
require.Equal(res.Proof.RootHash, commit.Header.DataHash) require.Equal(res.Proof.RootHash, commit.Header.DataHash)
} }

View File

@ -15,14 +15,14 @@ var _ rpcclient.Client = Wrapper{}
// 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.Inquiring cert *lite.InquiringCertifier
} }
// SecureClient uses a given certifier to wrap an connection to an untrusted // SecureClient uses a given certifier 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.Inquiring) Wrapper { func SecureClient(c rpcclient.Client, cert *lite.InquiringCertifier) 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
@ -34,7 +34,9 @@ func SecureClient(c rpcclient.Client, cert *lite.Inquiring) Wrapper {
} }
// ABCIQueryWithOptions exposes all options for the ABCI query and verifies the returned proof // ABCIQueryWithOptions exposes all options for the ABCI query and verifies the returned proof
func (w Wrapper) ABCIQueryWithOptions(path string, data data.Bytes, opts rpcclient.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { func (w Wrapper) ABCIQueryWithOptions(path string, data data.Bytes,
opts rpcclient.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) {
res, _, err := GetWithProofOptions(path, data, opts, w.Client, w.cert) res, _, err := GetWithProofOptions(path, data, opts, w.Client, w.cert)
return res, err return res, err
} }

View File

@ -10,62 +10,64 @@ import (
liteErr "github.com/tendermint/tendermint/lite/errors" liteErr "github.com/tendermint/tendermint/lite/errors"
) )
var _ Certifier = &Static{} var _ Certifier = (*StaticCertifier)(nil)
// Static assumes a static set of validators, set on // StaticCertifier assumes a static set of validators, set on
// initilization and checks against them. // initilization and checks against them.
// The signatures on every header is checked for > 2/3 votes // The signatures on every header is checked for > 2/3 votes
// against the known validator set upon Certify // against the known validator set upon Certify
// //
// Good for testing or really simple chains. Building block // Good for testing or really simple chains. Building block
// to support real-world functionality. // to support real-world functionality.
type Static struct { type StaticCertifier struct {
chainID string chainID string
vSet *types.ValidatorSet vSet *types.ValidatorSet
vhash []byte vhash []byte
} }
// NewStatic returns a new certifier with a static validator set. // NewStaticCertifier returns a new certifier with a static validator set.
func NewStatic(chainID string, vals *types.ValidatorSet) *Static { func NewStaticCertifier(chainID string, vals *types.ValidatorSet) *StaticCertifier {
return &Static{ return &StaticCertifier{
chainID: chainID, chainID: chainID,
vSet: vals, vSet: vals,
} }
} }
// ChainID returns the chain id. // ChainID returns the chain id.
func (c *Static) ChainID() string { // Implements Certifier.
return c.chainID func (sc *StaticCertifier) ChainID() string {
return sc.chainID
} }
// Validators returns the validator set. // Validators returns the validator set.
func (c *Static) Validators() *types.ValidatorSet { func (sc *StaticCertifier) Validators() *types.ValidatorSet {
return c.vSet return sc.vSet
} }
// Hash returns the hash of the validator set. // Hash returns the hash of the validator set.
func (c *Static) Hash() []byte { func (sc *StaticCertifier) Hash() []byte {
if len(c.vhash) == 0 { if len(sc.vhash) == 0 {
c.vhash = c.vSet.Hash() sc.vhash = sc.vSet.Hash()
} }
return c.vhash return sc.vhash
} }
// Certify makes sure that the commit is valid. // Certify makes sure that the commit is valid.
func (c *Static) Certify(commit Commit) error { // Implements Certifier.
func (sc *StaticCertifier) Certify(commit Commit) error {
// do basic sanity checks // do basic sanity checks
err := commit.ValidateBasic(c.chainID) err := commit.ValidateBasic(sc.chainID)
if err != nil { if err != nil {
return err return err
} }
// 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(sc.Hash(), commit.Header.ValidatorsHash) {
return liteErr.ErrValidatorsChanged() return liteErr.ErrValidatorsChanged()
} }
// then make sure we have the proper signatures for this // then make sure we have the proper signatures for this
err = c.vSet.VerifyCommit(c.chainID, commit.Commit.BlockID, err = sc.vSet.VerifyCommit(sc.chainID, commit.Commit.BlockID,
commit.Header.Height, commit.Commit) commit.Header.Height, commit.Commit)
return errors.WithStack(err) return errors.WithStack(err)
} }

View File

@ -21,7 +21,7 @@ func TestStaticCert(t *testing.T) {
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 := lite.NewStatic(chainID, vals) cert := lite.NewStaticCertifier(chainID, vals)
cases := []struct { cases := []struct {
keys lite.ValKeys keys lite.ValKeys

View File

@ -33,7 +33,8 @@ type ABCIClient interface {
// reading from abci app // reading from abci app
ABCIInfo() (*ctypes.ResultABCIInfo, error) ABCIInfo() (*ctypes.ResultABCIInfo, error)
ABCIQuery(path string, data data.Bytes) (*ctypes.ResultABCIQuery, error) ABCIQuery(path string, data data.Bytes) (*ctypes.ResultABCIQuery, error)
ABCIQueryWithOptions(path string, data data.Bytes, opts ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) ABCIQueryWithOptions(path string, data data.Bytes,
opts ABCIQueryOptions) (*ctypes.ResultABCIQuery, error)
// writing to abci app // writing to abci app
BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error)

View File

@ -369,8 +369,6 @@ func (pvs PrivValidatorsByAddress) Swap(i, j int) {
//------------------------------------- //-------------------------------------
type checkOnlyDifferByTimestamp func([]byte, []byte) bool
// returns the timestamp from the lastSignBytes. // returns the timestamp from the lastSignBytes.
// returns true if the only difference in the votes is their timestamp. // returns true if the only difference in the votes is their timestamp.
func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) { func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) {