mirror of
https://github.com/fluencelabs/tendermint
synced 2025-04-25 14:52:17 +00:00
1."abci_query": rpcserver.NewRPCFunc(c.ABCIQuery, "path,data,prove") "validators": rpcserver.NewRPCFunc(c.Validators, "height"), the parameters and function do not match, cause index out of range error. 2. the prove of query is forced to be true, while default option is false. 3. fix the wrong key of merkle
145 lines
4.4 KiB
Go
145 lines
4.4 KiB
Go
package proxy
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
cmn "github.com/tendermint/tendermint/libs/common"
|
|
|
|
"github.com/tendermint/tendermint/crypto/merkle"
|
|
"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"
|
|
)
|
|
|
|
// GetWithProof will query the key on the given node, and verify it has
|
|
// a valid proof, as defined by the Verifier.
|
|
//
|
|
// If there is any error in checking, returns an error.
|
|
func GetWithProof(prt *merkle.ProofRuntime, key []byte, reqHeight int64, node rpcclient.Client,
|
|
cert lite.Verifier) (
|
|
val cmn.HexBytes, height int64, proof *merkle.Proof, err error) {
|
|
|
|
if reqHeight < 0 {
|
|
err = cmn.NewError("Height cannot be negative")
|
|
return
|
|
}
|
|
|
|
res, err := GetWithProofOptions(prt, "/key", key,
|
|
rpcclient.ABCIQueryOptions{Height: int64(reqHeight), Prove: true},
|
|
node, cert)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
resp := res.Response
|
|
val, height = resp.Value, resp.Height
|
|
return val, height, proof, err
|
|
}
|
|
|
|
// GetWithProofOptions is useful if you want full access to the ABCIQueryOptions.
|
|
// XXX Usage of path? It's not used, and sometimes it's /, sometimes /key, sometimes /store.
|
|
func GetWithProofOptions(prt *merkle.ProofRuntime, path string, key []byte, opts rpcclient.ABCIQueryOptions,
|
|
node rpcclient.Client, cert lite.Verifier) (
|
|
*ctypes.ResultABCIQuery, error) {
|
|
opts.Prove = true
|
|
res, err := node.ABCIQueryWithOptions(path, key, opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp := res.Response
|
|
|
|
// Validate the response, e.g. height.
|
|
if resp.IsErr() {
|
|
err = cmn.NewError("Query error for key %d: %d", key, resp.Code)
|
|
return nil, err
|
|
}
|
|
|
|
if len(resp.Key) == 0 || resp.Proof == nil {
|
|
return nil, lerr.ErrEmptyTree()
|
|
}
|
|
if resp.Height == 0 {
|
|
return nil, cmn.NewError("Height returned is zero")
|
|
}
|
|
|
|
// AppHash for height H is in header H+1
|
|
signedHeader, err := GetCertifiedCommit(resp.Height+1, node, cert)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Validate the proof against the certified header to ensure data integrity.
|
|
if resp.Value != nil {
|
|
// Value exists
|
|
// XXX How do we encode the key into a string...
|
|
storeName, err := parseQueryStorePath(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
kp := merkle.KeyPath{}
|
|
kp = kp.AppendKey([]byte(storeName), merkle.KeyEncodingURL)
|
|
kp = kp.AppendKey(resp.Key, merkle.KeyEncodingURL)
|
|
err = prt.VerifyValue(resp.Proof, signedHeader.AppHash, kp.String(), resp.Value)
|
|
if err != nil {
|
|
return nil, cmn.ErrorWrap(err, "Couldn't verify value proof")
|
|
}
|
|
return &ctypes.ResultABCIQuery{Response: resp}, nil
|
|
} else {
|
|
// Value absent
|
|
// Validate the proof against the certified header to ensure data integrity.
|
|
// XXX How do we encode the key into a string...
|
|
err = prt.VerifyAbsence(resp.Proof, signedHeader.AppHash, string(resp.Key))
|
|
if err != nil {
|
|
return nil, cmn.ErrorWrap(err, "Couldn't verify absence proof")
|
|
}
|
|
return &ctypes.ResultABCIQuery{Response: resp}, nil
|
|
}
|
|
}
|
|
|
|
func parseQueryStorePath(path string) (storeName string, err error) {
|
|
if !strings.HasPrefix(path, "/") {
|
|
return "", fmt.Errorf("expected path to start with /")
|
|
}
|
|
|
|
paths := strings.SplitN(path[1:], "/", 3)
|
|
switch {
|
|
case len(paths) != 3:
|
|
return "", fmt.Errorf("expected format like /store/<storeName>/key")
|
|
case paths[0] != "store":
|
|
return "", fmt.Errorf("expected format like /store/<storeName>/key")
|
|
case paths[2] != "key":
|
|
return "", fmt.Errorf("expected format like /store/<storeName>/key")
|
|
}
|
|
|
|
return paths[1], nil
|
|
}
|
|
|
|
// 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.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.
|
|
// When this is supported, we should use it instead...
|
|
rpcclient.WaitForHeight(client, h, nil)
|
|
cresp, err := client.Commit(&h)
|
|
if err != nil {
|
|
return types.SignedHeader{}, err
|
|
}
|
|
|
|
// Validate downloaded checkpoint with our request and trust store.
|
|
sh := cresp.SignedHeader
|
|
if sh.Height != h {
|
|
return types.SignedHeader{}, fmt.Errorf("height mismatch: want %v got %v",
|
|
h, sh.Height)
|
|
}
|
|
|
|
if err = cert.Verify(sh); err != nil {
|
|
return types.SignedHeader{}, err
|
|
}
|
|
|
|
return sh, nil
|
|
}
|