Skip to content

Commit

Permalink
Merge pull request #210 from tendermint/feature/40-overhaul-proofs
Browse files Browse the repository at this point in the history
Overhaul proofs and light-client / basecoin separation
  • Loading branch information
ethanfrey authored Aug 4, 2017
2 parents f2adf36 + d6d6b75 commit c871748
Show file tree
Hide file tree
Showing 23 changed files with 359 additions and 314 deletions.
16 changes: 11 additions & 5 deletions app/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,12 +180,18 @@ func (s *Store) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery)
key := reqQuery.Data // Data holds the key bytes
resQuery.Key = key
if reqQuery.Prove {
value, proof, exists := tree.Proof(key)
if !exists {
resQuery.Log = "Key not found"
value, proofExists, proofNotExists, err := tree.GetWithProof(key)
if err != nil {
resQuery.Log = err.Error()
break
}

if value != nil {
resQuery.Value = value
resQuery.Proof = wire.BinaryBytes(proofExists)
} else {
resQuery.Proof = wire.BinaryBytes(proofNotExists)
}
resQuery.Value = value
resQuery.Proof = proof
} else {
value := tree.Get(key)
resQuery.Value = value
Expand Down
134 changes: 0 additions & 134 deletions client/commands/proofs/get.go

This file was deleted.

52 changes: 0 additions & 52 deletions client/commands/proofs/tx.go

This file was deleted.

180 changes: 180 additions & 0 deletions client/commands/query/get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package query

import (
"fmt"
"io"
"os"

"github.com/pkg/errors"
"github.com/spf13/viper"

wire "github.com/tendermint/go-wire"
"github.com/tendermint/go-wire/data"
lc "github.com/tendermint/light-client"
"github.com/tendermint/light-client/proofs"
"github.com/tendermint/merkleeyes/iavl"
"github.com/tendermint/tendermint/rpc/client"

"github.com/tendermint/basecoin/client/commands"
)

// GetParsed does most of the work of the query commands, but is quite
// opinionated, so if you want more control about parsing, call Get
// directly.
//
// It will try to get the proof for the given key. If it is successful,
// it will return the height and also unserialize proof.Data into the data
// argument (so pass in a pointer to the appropriate struct)
func GetParsed(key []byte, data interface{}, prove bool) (uint64, error) {
bs, h, err := Get(key, prove)
if err != nil {
return 0, err
}
err = wire.ReadBinaryBytes(bs, data)
if err != nil {
return 0, err
}
return h, nil
}

// Get queries the given key and returns the value stored there and the
// height we checked at.
//
// If prove is true (and why shouldn't it be?),
// the data is fully verified before returning. If prove is false,
// we just repeat whatever any (potentially malicious) node gives us.
// Only use that if you are running the full node yourself,
// and it is localhost or you have a secure connection (not HTTP)
func Get(key []byte, prove bool) (data.Bytes, uint64, error) {
if !prove {
node := commands.GetNode()
resp, err := node.ABCIQuery("/key", key, false)
return data.Bytes(resp.Value), resp.Height, err
}
val, h, _, err := GetWithProof(key)
return val, h, err
}

// GetWithProof returns the values stored under a given key at the named
// height as in Get. Additionally, it will return a validated merkle
// proof for the key-value pair if it exists, and all checks pass.
func GetWithProof(key []byte) (data.Bytes, uint64, *iavl.KeyExistsProof, error) {
node := commands.GetNode()

resp, err := node.ABCIQuery("/key", key, true)
if err != nil {
return nil, 0, nil, err
}
ph := int(resp.Height)

// make sure the proof is the proper height
if !resp.Code.IsOK() {
return nil, 0, nil, errors.Errorf("Query error %d: %s", resp.Code, resp.Code.String())
}
// TODO: Handle null proofs
if len(resp.Key) == 0 || len(resp.Value) == 0 || len(resp.Proof) == 0 {
return nil, 0, nil, lc.ErrNoData()
}
if ph != 0 && ph != int(resp.Height) {
return nil, 0, nil, lc.ErrHeightMismatch(ph, int(resp.Height))
}

check, err := GetCertifiedCheckpoint(ph)
if err != nil {
return nil, 0, nil, err
}

proof := new(iavl.KeyExistsProof)
err = wire.ReadBinaryBytes(resp.Proof, &proof)
if err != nil {
return nil, 0, nil, err
}

// validate the proof against the certified header to ensure data integrity
err = proof.Verify(resp.Key, resp.Value, check.Header.AppHash)
if err != nil {
return nil, 0, nil, err
}

return data.Bytes(resp.Value), resp.Height, proof, nil
}

// GetCertifiedCheckpoint gets the signed header for a given height
// and certifies it. Returns error if unable to get a proven header.
func GetCertifiedCheckpoint(h int) (empty lc.Checkpoint, err error) {
// here is the certifier, root of all trust
node := commands.GetNode()
cert, err := commands.GetCertifier()
if err != nil {
return
}

// get the checkpoint for this height

// 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...
client.WaitForHeight(node, h, nil)
commit, err := node.Commit(h)
if err != nil {
return
}
check := lc.Checkpoint{
Header: commit.Header,
Commit: commit.Commit,
}

// validate downloaded checkpoint with our request and trust store.
if check.Height() != h {
return empty, lc.ErrHeightMismatch(h, check.Height())
}
err = cert.Certify(check)
return check, nil
}

// ParseHexKey parses the key flag as hex and converts to bytes or returns error
// argname is used to customize the error message
func ParseHexKey(args []string, argname string) ([]byte, error) {
if len(args) == 0 {
return nil, errors.Errorf("Missing required argument [%s]", argname)
}
if len(args) > 1 {
return nil, errors.Errorf("Only accepts one argument [%s]", argname)
}
rawkey := args[0]
if rawkey == "" {
return nil, errors.Errorf("[%s] argument must be non-empty ", argname)
}
// with tx, we always just parse key as hex and use to lookup
return proofs.ParseHexKey(rawkey)
}

// GetHeight reads the viper config for the query height
func GetHeight() int {
return viper.GetInt(FlagHeight)
}

type proof struct {
Height uint64 `json:"height"`
Data interface{} `json:"data"`
}

// FoutputProof writes the output of wrapping height and info
// in the form {"data": <the_data>, "height": <the_height>}
// to the provider io.Writer
func FoutputProof(w io.Writer, v interface{}, height uint64) error {
wrap := &proof{height, v}
blob, err := data.ToJSON(wrap)
if err != nil {
return err
}
_, err = fmt.Fprintf(w, "%s\n", blob)
return err
}

// OutputProof prints the proof to stdout
// reuse this for printing proofs and we should enhance this for text/json,
// better presentation of height
func OutputProof(data interface{}, height uint64) error {
return FoutputProof(os.Stdout, data, height)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package proofs
package query

import (
"github.com/spf13/cobra"
Expand Down
Loading

0 comments on commit c871748

Please sign in to comment.