mirror of
https://github.com/fluencelabs/tendermint
synced 2025-06-28 12:11:44 +00:00
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:
committed by
Anton Kaliaev
parent
b9cbaf8f10
commit
32311acd01
@ -35,7 +35,7 @@ func TestProvider(t *testing.T) {
|
||||
|
||||
// let's check this is valid somehow
|
||||
assert.Nil(seed.ValidateBasic(chainID))
|
||||
cert := lite.NewStatic(chainID, seed.Validators)
|
||||
cert := lite.NewStaticCertifier(chainID, seed.Validators)
|
||||
|
||||
// historical queries now work :)
|
||||
lower := sh - 5
|
||||
|
@ -6,9 +6,9 @@ import (
|
||||
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.
|
||||
//
|
||||
// You can pass in a FullCommit with another validator set,
|
||||
@ -17,46 +17,48 @@ var _ Certifier = &Dynamic{}
|
||||
// validator set for the next Certify call.
|
||||
// For security, it will only follow validator set changes
|
||||
// going forward.
|
||||
type Dynamic struct {
|
||||
cert *Static
|
||||
type DynamicCertifier struct {
|
||||
cert *StaticCertifier
|
||||
lastHeight int64
|
||||
}
|
||||
|
||||
// NewDynamic returns a new dynamic certifier.
|
||||
func NewDynamic(chainID string, vals *types.ValidatorSet, height int64) *Dynamic {
|
||||
return &Dynamic{
|
||||
cert: NewStatic(chainID, vals),
|
||||
func NewDynamicCertifier(chainID string, vals *types.ValidatorSet, height int64) *DynamicCertifier {
|
||||
return &DynamicCertifier{
|
||||
cert: NewStaticCertifier(chainID, vals),
|
||||
lastHeight: height,
|
||||
}
|
||||
}
|
||||
|
||||
// ChainID returns the chain id of this certifier.
|
||||
func (c *Dynamic) ChainID() string {
|
||||
return c.cert.ChainID()
|
||||
// Implements Certifier.
|
||||
func (dc *DynamicCertifier) ChainID() string {
|
||||
return dc.cert.ChainID()
|
||||
}
|
||||
|
||||
// Validators returns the validators of this certifier.
|
||||
func (c *Dynamic) Validators() *types.ValidatorSet {
|
||||
return c.cert.vSet
|
||||
func (dc *DynamicCertifier) Validators() *types.ValidatorSet {
|
||||
return dc.cert.vSet
|
||||
}
|
||||
|
||||
// Hash returns the hash of this certifier.
|
||||
func (c *Dynamic) Hash() []byte {
|
||||
return c.cert.Hash()
|
||||
func (dc *DynamicCertifier) Hash() []byte {
|
||||
return dc.cert.Hash()
|
||||
}
|
||||
|
||||
// LastHeight returns the last height of this certifier.
|
||||
func (c *Dynamic) LastHeight() int64 {
|
||||
return c.lastHeight
|
||||
func (dc *DynamicCertifier) LastHeight() int64 {
|
||||
return dc.lastHeight
|
||||
}
|
||||
|
||||
// Certify will verify whether the commit is valid and will update the height if it is or return an
|
||||
// error if it is not.
|
||||
func (c *Dynamic) Certify(check Commit) error {
|
||||
err := c.cert.Certify(check)
|
||||
// Implements Certifier.
|
||||
func (dc *DynamicCertifier) Certify(check Commit) error {
|
||||
err := dc.cert.Certify(check)
|
||||
if err == nil {
|
||||
// update last seen height if input is valid
|
||||
c.lastHeight = check.Height()
|
||||
dc.lastHeight = check.Height()
|
||||
}
|
||||
return err
|
||||
}
|
||||
@ -65,15 +67,15 @@ func (c *Dynamic) Certify(check Commit) error {
|
||||
// the certifying validator set if safe to do so.
|
||||
//
|
||||
// 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
|
||||
h := fc.Height()
|
||||
if h <= c.lastHeight {
|
||||
if h <= dc.lastHeight {
|
||||
return liteErr.ErrPastTime()
|
||||
}
|
||||
|
||||
// first, verify if the input is self-consistent....
|
||||
err := fc.ValidateBasic(c.ChainID())
|
||||
err := fc.ValidateBasic(dc.ChainID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -82,14 +84,13 @@ func (c *Dynamic) Update(fc FullCommit) error {
|
||||
// would be approved by the currently known validator set
|
||||
// as well as the new set
|
||||
commit := fc.Commit.Commit
|
||||
err = c.Validators().VerifyCommitAny(fc.Validators, c.ChainID(),
|
||||
commit.BlockID, h, commit)
|
||||
err = dc.Validators().VerifyCommitAny(fc.Validators, dc.ChainID(), commit.BlockID, h, commit)
|
||||
if err != nil {
|
||||
return liteErr.ErrTooMuchChange()
|
||||
}
|
||||
|
||||
// looks good, we can update
|
||||
c.cert = NewStatic(c.ChainID(), fc.Validators)
|
||||
c.lastHeight = h
|
||||
dc.cert = NewStaticCertifier(dc.ChainID(), fc.Validators)
|
||||
dc.lastHeight = h
|
||||
return nil
|
||||
}
|
@ -23,7 +23,7 @@ func TestDynamicCert(t *testing.T) {
|
||||
vals := keys.ToValidators(20, 10)
|
||||
// and a certifier based on our known set
|
||||
chainID := "test-dyno"
|
||||
cert := lite.NewDynamic(chainID, vals, 0)
|
||||
cert := lite.NewDynamicCertifier(chainID, vals, 0)
|
||||
|
||||
cases := []struct {
|
||||
keys lite.ValKeys
|
||||
@ -67,7 +67,7 @@ func TestDynamicUpdate(t *testing.T) {
|
||||
chainID := "test-dyno-up"
|
||||
keys := lite.GenValKeys(5)
|
||||
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
|
||||
h := int64(100)
|
159
lite/inquirer.go
159
lite/inquirer.go
@ -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
163
lite/inquiring_certifier.go
Normal 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)
|
||||
}
|
@ -37,7 +37,7 @@ func TestInquirerValidPath(t *testing.T) {
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
// this should fail validation....
|
||||
@ -88,7 +88,7 @@ func TestInquirerMinimalPath(t *testing.T) {
|
||||
}
|
||||
|
||||
// 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....
|
||||
commit := commits[count-1].Commit
|
||||
@ -138,7 +138,7 @@ func TestInquirerVerifyHistorical(t *testing.T) {
|
||||
}
|
||||
|
||||
// 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
|
||||
for _, i := range []int{2, 5} {
|
@ -105,7 +105,7 @@ func BenchmarkCertifyCommitSec100(b *testing.B) {
|
||||
func benchmarkCertifyCommit(b *testing.B, keys lite.ValKeys) {
|
||||
chainID := "bench-certify"
|
||||
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))
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := cert.Certify(check)
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"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(
|
||||
lite.NewMemStoreProvider(),
|
||||
files.NewProvider(rootDir),
|
||||
@ -26,7 +26,7 @@ func GetCertifier(chainID, rootDir, nodeAddr string) (*lite.Inquiring, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert, err := lite.NewInquiring(chainID, fc, trust, source)
|
||||
cert, err := lite.NewInquiringCertifier(chainID, fc, trust, source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -18,7 +18,11 @@ const (
|
||||
// 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)
|
||||
func StartProxy(c rpcclient.Client, listenAddr string, logger log.Logger) error {
|
||||
c.Start()
|
||||
err := c.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r := RPCRoutes(c)
|
||||
|
||||
// build the handler...
|
||||
@ -30,7 +34,7 @@ func StartProxy(c rpcclient.Client, listenAddr string, logger log.Logger) error
|
||||
core.SetLogger(logger)
|
||||
mux.HandleFunc(wsEndpoint, wm.WebsocketHandler)
|
||||
|
||||
_, err := rpc.StartHTTPServer(listenAddr, mux, logger)
|
||||
_, err = rpc.StartHTTPServer(listenAddr, mux, logger)
|
||||
|
||||
return err
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ func GetWithProofOptions(path string, key []byte, opts rpcclient.ABCIQueryOption
|
||||
|
||||
// make sure the proof is the proper height
|
||||
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
|
||||
}
|
||||
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 {
|
||||
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.
|
||||
@ -93,13 +93,12 @@ func GetWithProofOptions(path string, key []byte, opts rpcclient.ABCIQueryOption
|
||||
if err != nil {
|
||||
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
|
||||
// and certifies it. Returns error if unable to get a proven header.
|
||||
func GetCertifiedCommit(h int64, node rpcclient.Client,
|
||||
cert lite.Certifier) (empty lite.Commit, err error) {
|
||||
func GetCertifiedCommit(h int64, node rpcclient.Client, cert lite.Certifier) (lite.Commit, error) {
|
||||
|
||||
// FIXME: cannot use cert.GetByHeight for now, as it also requires
|
||||
// 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)
|
||||
cresp, err := node.Commit(&h)
|
||||
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.
|
||||
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
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ func _TestAppProofs(t *testing.T) {
|
||||
source := certclient.NewProvider(cl)
|
||||
seed, err := source.GetByHeight(brh - 2)
|
||||
require.NoError(err, "%+v", err)
|
||||
cert := lite.NewStatic("my-chain", seed.Validators)
|
||||
cert := lite.NewStaticCertifier("my-chain", seed.Validators)
|
||||
|
||||
client.WaitForHeight(cl, 3, nil)
|
||||
latest, err := source.LatestCommit()
|
||||
@ -117,7 +117,7 @@ func _TestTxProofs(t *testing.T) {
|
||||
source := certclient.NewProvider(cl)
|
||||
seed, err := source.GetByHeight(brh - 2)
|
||||
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.
|
||||
key := types.Tx([]byte("bogus")).Hash()
|
||||
@ -136,5 +136,4 @@ func _TestTxProofs(t *testing.T) {
|
||||
commit, err := GetCertifiedCommit(br.Height, cl, cert)
|
||||
require.Nil(err, "%+v", err)
|
||||
require.Equal(res.Proof.RootHash, commit.Header.DataHash)
|
||||
|
||||
}
|
||||
|
@ -15,14 +15,14 @@ var _ rpcclient.Client = Wrapper{}
|
||||
// provable before passing it along. Allows you to make any rpcclient fully secure.
|
||||
type Wrapper struct {
|
||||
rpcclient.Client
|
||||
cert *lite.Inquiring
|
||||
cert *lite.InquiringCertifier
|
||||
}
|
||||
|
||||
// SecureClient uses a given certifier 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.Inquiring) Wrapper {
|
||||
func SecureClient(c rpcclient.Client, cert *lite.InquiringCertifier) 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
|
||||
@ -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
|
||||
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)
|
||||
return res, err
|
||||
}
|
||||
|
@ -10,62 +10,64 @@ import (
|
||||
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.
|
||||
// The signatures on every header is checked for > 2/3 votes
|
||||
// against the known validator set upon Certify
|
||||
//
|
||||
// Good for testing or really simple chains. Building block
|
||||
// to support real-world functionality.
|
||||
type Static struct {
|
||||
type StaticCertifier struct {
|
||||
chainID string
|
||||
vSet *types.ValidatorSet
|
||||
vhash []byte
|
||||
}
|
||||
|
||||
// NewStatic returns a new certifier with a static validator set.
|
||||
func NewStatic(chainID string, vals *types.ValidatorSet) *Static {
|
||||
return &Static{
|
||||
// NewStaticCertifier returns a new certifier with a static validator set.
|
||||
func NewStaticCertifier(chainID string, vals *types.ValidatorSet) *StaticCertifier {
|
||||
return &StaticCertifier{
|
||||
chainID: chainID,
|
||||
vSet: vals,
|
||||
}
|
||||
}
|
||||
|
||||
// ChainID returns the chain id.
|
||||
func (c *Static) ChainID() string {
|
||||
return c.chainID
|
||||
// Implements Certifier.
|
||||
func (sc *StaticCertifier) ChainID() string {
|
||||
return sc.chainID
|
||||
}
|
||||
|
||||
// Validators returns the validator set.
|
||||
func (c *Static) Validators() *types.ValidatorSet {
|
||||
return c.vSet
|
||||
func (sc *StaticCertifier) Validators() *types.ValidatorSet {
|
||||
return sc.vSet
|
||||
}
|
||||
|
||||
// Hash returns the hash of the validator set.
|
||||
func (c *Static) Hash() []byte {
|
||||
if len(c.vhash) == 0 {
|
||||
c.vhash = c.vSet.Hash()
|
||||
func (sc *StaticCertifier) Hash() []byte {
|
||||
if len(sc.vhash) == 0 {
|
||||
sc.vhash = sc.vSet.Hash()
|
||||
}
|
||||
return c.vhash
|
||||
return sc.vhash
|
||||
}
|
||||
|
||||
// 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
|
||||
err := commit.ValidateBasic(c.chainID)
|
||||
err := commit.ValidateBasic(sc.chainID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
// 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)
|
||||
return errors.WithStack(err)
|
||||
}
|
@ -21,7 +21,7 @@ func TestStaticCert(t *testing.T) {
|
||||
vals := keys.ToValidators(20, 10)
|
||||
// and a certifier based on our known set
|
||||
chainID := "test-static"
|
||||
cert := lite.NewStatic(chainID, vals)
|
||||
cert := lite.NewStaticCertifier(chainID, vals)
|
||||
|
||||
cases := []struct {
|
||||
keys lite.ValKeys
|
@ -33,7 +33,8 @@ type ABCIClient interface {
|
||||
// reading from abci app
|
||||
ABCIInfo() (*ctypes.ResultABCIInfo, 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
|
||||
BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error)
|
||||
|
@ -369,8 +369,6 @@ func (pvs PrivValidatorsByAddress) Swap(i, j int) {
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
type checkOnlyDifferByTimestamp func([]byte, []byte) bool
|
||||
|
||||
// returns the timestamp from the lastSignBytes.
|
||||
// returns true if the only difference in the votes is their timestamp.
|
||||
func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) {
|
||||
|
Reference in New Issue
Block a user