mirror of
https://github.com/fluencelabs/tendermint
synced 2025-04-29 00:32:14 +00:00
* 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
124 lines
3.8 KiB
Go
124 lines
3.8 KiB
Go
package proxy
|
|
|
|
import (
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/tendermint/go-wire/data"
|
|
"github.com/tendermint/iavl"
|
|
|
|
"github.com/tendermint/tendermint/lite"
|
|
"github.com/tendermint/tendermint/lite/client"
|
|
certerr "github.com/tendermint/tendermint/lite/errors"
|
|
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
|
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
|
)
|
|
|
|
// GetWithProof will query the key on the given node, and verify it has
|
|
// a valid proof, as defined by the certifier.
|
|
//
|
|
// 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) (
|
|
val data.Bytes, height int64, proof iavl.KeyProof, err error) {
|
|
|
|
if reqHeight < 0 {
|
|
err = errors.Errorf("Height cannot be negative")
|
|
return
|
|
}
|
|
|
|
_resp, proof, err := GetWithProofOptions("/key", key,
|
|
rpcclient.ABCIQueryOptions{Height: int64(reqHeight)},
|
|
node, cert)
|
|
if _resp != nil {
|
|
resp := _resp.Response
|
|
val, height = resp.Value, resp.Height
|
|
}
|
|
return val, height, proof, err
|
|
}
|
|
|
|
// 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) (
|
|
*ctypes.ResultABCIQuery, iavl.KeyProof, error) {
|
|
|
|
_resp, err := node.ABCIQueryWithOptions(path, key, opts)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
resp := _resp.Response
|
|
|
|
// make sure the proof is the proper height
|
|
if resp.IsErr() {
|
|
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 {
|
|
return nil, nil, ErrNoData()
|
|
}
|
|
if resp.Height == 0 {
|
|
return nil, nil, errors.New("Height returned is zero")
|
|
}
|
|
|
|
// AppHash for height H is in header H+1
|
|
commit, err := GetCertifiedCommit(resp.Height+1, node, cert)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
if len(resp.Value) > 0 {
|
|
// The key was found, construct a proof of existence.
|
|
eproof, err := iavl.ReadKeyExistsProof(resp.Proof)
|
|
if err != nil {
|
|
return nil, nil, errors.Wrap(err, "Error reading proof")
|
|
}
|
|
|
|
// Validate the proof against the certified header to ensure data integrity.
|
|
err = eproof.Verify(resp.Key, resp.Value, commit.Header.AppHash)
|
|
if err != nil {
|
|
return nil, nil, errors.Wrap(err, "Couldn't verify proof")
|
|
}
|
|
return &ctypes.ResultABCIQuery{Response: resp}, eproof, nil
|
|
}
|
|
|
|
// The key wasn't found, construct a proof of non-existence.
|
|
var aproof *iavl.KeyAbsentProof
|
|
aproof, err = iavl.ReadKeyAbsentProof(resp.Proof)
|
|
if err != nil {
|
|
return nil, nil, errors.Wrap(err, "Error reading proof")
|
|
}
|
|
// Validate the proof against the certified header to ensure data integrity.
|
|
err = aproof.Verify(resp.Key, nil, commit.Header.AppHash)
|
|
if err != nil {
|
|
return nil, nil, errors.Wrap(err, "Couldn't verify proof")
|
|
}
|
|
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) (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.
|
|
// When this is supported, we should use it instead...
|
|
rpcclient.WaitForHeight(node, h, nil)
|
|
cresp, err := node.Commit(&h)
|
|
if err != nil {
|
|
return lite.Commit{}, err
|
|
}
|
|
|
|
commit := client.CommitFromResult(cresp)
|
|
// validate downloaded checkpoint with our request and trust store.
|
|
if commit.Height() != h {
|
|
return lite.Commit{}, certerr.ErrHeightMismatch(h, commit.Height())
|
|
}
|
|
|
|
if err = cert.Certify(commit); err != nil {
|
|
return lite.Commit{}, err
|
|
}
|
|
|
|
return commit, nil
|
|
}
|