Merge pull request #210 from tendermint/feature/40-overhaul-proofs

Overhaul proofs and light-client / basecoin separation
This commit is contained in:
Ethan Frey 2017-08-04 21:19:41 +02:00 committed by GitHub
commit c87174875a
23 changed files with 359 additions and 314 deletions

View File

@ -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

View File

@ -1,134 +0,0 @@
package proofs
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/tendermint/rpc/client"
"github.com/tendermint/basecoin/client/commands"
)
// GetAndParseAppProof does most of the work of the query commands, but is quite
// opinionated, so if you want more control, set up the items and call GetProof
// directly. Notably, it always uses go-wire.ReadBinaryBytes to deserialize,
// and Height and Node from standard flags.
//
// It will try to get the proof for the given key. If it is successful,
// it will return the proof and also unserialize proof.Data into the data
// argument (so pass in a pointer to the appropriate struct)
func GetAndParseAppProof(key []byte, data interface{}) (lc.Proof, error) {
height := GetHeight()
node := commands.GetNode()
prover := proofs.NewAppProver(node)
proof, err := GetProof(node, prover, key, height)
if err != nil {
return proof, err
}
err = wire.ReadBinaryBytes(proof.Data(), data)
return proof, err
}
// GetProof performs the get command directly from the proof (not from the CLI)
func GetProof(node client.Client, prover lc.Prover, key []byte, height int) (proof lc.Proof, err error) {
proof, err = prover.Get(key, uint64(height))
if err != nil {
return
}
// short-circuit with no proofs
if viper.GetBool(commands.FlagTrustNode) {
return proof, err
}
// here is the certifier, root of all knowledge
cert, err := commands.GetCertifier()
if err != nil {
return
}
// get and validate a signed header for this proof
// 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...
ph := int(proof.BlockHeight())
client.WaitForHeight(node, ph, nil)
commit, err := node.Commit(ph)
if err != nil {
return
}
check := lc.Checkpoint{
Header: commit.Header,
Commit: commit.Commit,
}
err = cert.Certify(check)
if err != nil {
return
}
// validate the proof against the certified header to ensure data integrity
err = proof.Validate(check)
if err != nil {
return
}
return proof, err
}
// 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)
}
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)
}

View File

@ -1,52 +0,0 @@
package proofs
import (
"github.com/spf13/cobra"
"github.com/tendermint/light-client/proofs"
"github.com/tendermint/basecoin/client/commands"
)
//nolint TODO add description
var TxPresenters = proofs.NewPresenters()
// TxQueryCmd - CLI command to query a transaction with proof
var TxQueryCmd = &cobra.Command{
Use: "tx [txhash]",
Short: "Handle proofs of commited txs",
Long: `Proofs allows you to validate abci state with merkle proofs.
These proofs tie the data to a checkpoint, which is managed by "seeds".
Here we can validate these proofs and import/export them to prove specific
data to other peers as needed.
`,
RunE: commands.RequireInit(txQueryCmd),
}
func txQueryCmd(cmd *cobra.Command, args []string) error {
// parse cli
height := GetHeight()
bkey, err := ParseHexKey(args, "txhash")
if err != nil {
return err
}
// get the proof -> this will be used by all prover commands
node := commands.GetNode()
prover := proofs.NewTxProver(node)
proof, err := GetProof(node, prover, bkey, height)
if err != nil {
return err
}
// auto-determine which tx it was, over all registered tx types
info, err := TxPresenters.BruteForce(proof.Data())
if err != nil {
return err
}
// we can reuse this output for other commands for text/json
// unless they do something special like store a file to disk
return OutputProof(info, proof.BlockHeight())
}

View File

@ -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)
}

View File

@ -1,4 +1,4 @@
package proofs
package query
import (
"github.com/spf13/cobra"

View File

@ -1,10 +1,8 @@
package proofs
package query
import (
"github.com/spf13/cobra"
"github.com/tendermint/go-wire/data"
"github.com/tendermint/light-client/proofs"
"github.com/spf13/viper"
"github.com/tendermint/basecoin/client/commands"
)
@ -24,24 +22,15 @@ If you want json output, use an app-specific command that knows key and value st
// parse the object, but rather return the raw bytes
func keyQueryCmd(cmd *cobra.Command, args []string) error {
// parse cli
height := GetHeight()
key, err := ParseHexKey(args, "key")
if err != nil {
return err
}
prove := !viper.GetBool(commands.FlagTrustNode)
// get the proof -> this will be used by all prover commands
node := commands.GetNode()
prover := proofs.NewAppProver(node)
proof, err := GetProof(node, prover, key, height)
val, h, err := Get(key, prove)
if err != nil {
return err
}
// state just returns raw hex....
info := data.Bytes(proof.Data())
// we can reuse this output for other commands for text/json
// unless they do something special like store a file to disk
return OutputProof(info, proof.BlockHeight())
return OutputProof(val, h)
}

View File

@ -0,0 +1,71 @@
package query
import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/basecoin"
wire "github.com/tendermint/go-wire"
"github.com/tendermint/tendermint/types"
"github.com/tendermint/basecoin/client/commands"
)
// TxQueryCmd - CLI command to query a transaction with proof
var TxQueryCmd = &cobra.Command{
Use: "tx [txhash]",
Short: "Handle proofs of commited txs",
Long: `Proofs allows you to validate abci state with merkle proofs.
These proofs tie the data to a checkpoint, which is managed by "seeds".
Here we can validate these proofs and import/export them to prove specific
data to other peers as needed.
`,
RunE: commands.RequireInit(txQueryCmd),
}
func txQueryCmd(cmd *cobra.Command, args []string) error {
// parse cli
// TODO: when querying historical heights is allowed... pass it
// height := GetHeight()
bkey, err := ParseHexKey(args, "txhash")
if err != nil {
return err
}
// get the proof -> this will be used by all prover commands
node := commands.GetNode()
prove := !viper.GetBool(commands.FlagTrustNode)
res, err := node.Tx(bkey, prove)
if err != nil {
return err
}
// no checks if we don't get a proof
if !prove {
return showTx(res.Height, res.Tx)
}
check, err := GetCertifiedCheckpoint(res.Height)
if err != nil {
return err
}
err = res.Proof.Validate(check.Header.DataHash)
if err != nil {
return err
}
// note that we return res.Proof.Data, not res.Tx,
// as res.Proof.Validate only verifies res.Proof.Data
return showTx(res.Height, res.Proof.Data)
}
// showTx parses anything that was previously registered as basecoin.Tx
func showTx(h int, tx types.Tx) error {
var info basecoin.Tx
err := wire.ReadBinaryBytes(tx, &info)
if err != nil {
return err
}
return OutputProof(info, uint64(h))
}

View File

@ -1,20 +0,0 @@
package txs
import (
wire "github.com/tendermint/go-wire"
"github.com/tendermint/light-client/proofs"
"github.com/tendermint/basecoin"
)
// BaseTxPresenter this decodes all basecoin tx
type BaseTxPresenter struct {
proofs.RawPresenter // this handles MakeKey as hex bytes
}
// ParseData - unmarshal raw bytes to a basecoin tx
func (BaseTxPresenter) ParseData(raw []byte) (interface{}, error) {
var tx basecoin.Tx
err := wire.ReadBinaryBytes(raw, &tx)
return tx, err
}

View File

@ -10,8 +10,8 @@ import (
"github.com/tendermint/basecoin/client/commands"
"github.com/tendermint/basecoin/client/commands/auto"
"github.com/tendermint/basecoin/client/commands/proofs"
"github.com/tendermint/basecoin/client/commands/proxy"
"github.com/tendermint/basecoin/client/commands/query"
rpccmd "github.com/tendermint/basecoin/client/commands/rpc"
"github.com/tendermint/basecoin/client/commands/seeds"
txcmd "github.com/tendermint/basecoin/client/commands/txs"
@ -40,16 +40,15 @@ func main() {
commands.AddBasicFlags(BaseCli)
// Prepare queries
proofs.RootCmd.AddCommand(
query.RootCmd.AddCommand(
// These are default parsers, but optional in your app (you can remove key)
proofs.TxQueryCmd,
proofs.KeyQueryCmd,
query.TxQueryCmd,
query.KeyQueryCmd,
coincmd.AccountQueryCmd,
noncecmd.NonceQueryCmd,
rolecmd.RoleQueryCmd,
ibccmd.IBCQueryCmd,
)
proofs.TxPresenters.Register("base", txcmd.BaseTxPresenter{})
// set up the middleware
txcmd.Middleware = txcmd.Wrappers{
@ -81,7 +80,7 @@ func main() {
keycmd.RootCmd,
seeds.RootCmd,
rpccmd.RootCmd,
proofs.RootCmd,
query.RootCmd,
txcmd.RootCmd,
proxy.RootCmd,
commands.VersionCmd,

View File

@ -2,8 +2,10 @@ package commands
import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
proofcmd "github.com/tendermint/basecoin/client/commands/proofs"
"github.com/tendermint/basecoin/client/commands"
"github.com/tendermint/basecoin/client/commands/query"
"github.com/tendermint/basecoin/docs/guide/counter/plugins/counter"
"github.com/tendermint/basecoin/stack"
@ -17,13 +19,14 @@ var CounterQueryCmd = &cobra.Command{
}
func counterQueryCmd(cmd *cobra.Command, args []string) error {
key := stack.PrefixedKey(counter.NameCounter, counter.StateKey())
var cp counter.State
proof, err := proofcmd.GetAndParseAppProof(key, &cp)
prove := !viper.GetBool(commands.FlagTrustNode)
key := stack.PrefixedKey(counter.NameCounter, counter.StateKey())
h, err := query.GetParsed(key, &cp, prove)
if err != nil {
return err
}
return proofcmd.OutputProof(cp, proof.BlockHeight())
return query.OutputProof(cp, h)
}

View File

@ -9,8 +9,8 @@ import (
"github.com/tendermint/tmlibs/cli"
"github.com/tendermint/basecoin/client/commands"
"github.com/tendermint/basecoin/client/commands/proofs"
"github.com/tendermint/basecoin/client/commands/proxy"
"github.com/tendermint/basecoin/client/commands/query"
"github.com/tendermint/basecoin/client/commands/seeds"
txcmd "github.com/tendermint/basecoin/client/commands/txs"
bcount "github.com/tendermint/basecoin/docs/guide/counter/cmd/countercli/commands"
@ -37,10 +37,10 @@ func main() {
commands.AddBasicFlags(BaseCli)
// Prepare queries
proofs.RootCmd.AddCommand(
query.RootCmd.AddCommand(
// These are default parsers, optional in your app
proofs.TxQueryCmd,
proofs.KeyQueryCmd,
query.TxQueryCmd,
query.KeyQueryCmd,
coincmd.AccountQueryCmd,
noncecmd.NonceQueryCmd,
@ -58,7 +58,6 @@ func main() {
txcmd.Middleware.Register(txcmd.RootCmd.PersistentFlags())
// Prepare transactions
proofs.TxPresenters.Register("base", txcmd.BaseTxPresenter{})
txcmd.RootCmd.AddCommand(
// This is the default transaction, optional in your app
coincmd.SendTxCmd,
@ -73,7 +72,7 @@ func main() {
commands.ResetCmd,
keycmd.RootCmd,
seeds.RootCmd,
proofs.RootCmd,
query.RootCmd,
txcmd.RootCmd,
proxy.RootCmd,
)

6
glide.lock generated
View File

@ -1,5 +1,5 @@
hash: 2848c30b31fb205f846dd7dfca14ebed8a3249cbc5aaa759066b2bab3e4bbf42
updated: 2017-07-27T16:46:31.962147949-04:00
updated: 2017-08-04T15:38:45.048261895+02:00
imports:
- name: github.com/bgentry/speakeasy
version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd
@ -119,7 +119,7 @@ imports:
- edwards25519
- extra25519
- name: github.com/tendermint/go-crypto
version: bf355d1b58b27d4e98d8fb237eb14887b93a88f7
version: 03d2b2446e8587f159e52070b1f1e0def971a2c7
subpackages:
- cmd
- keys
@ -142,7 +142,7 @@ imports:
- certifiers/files
- proofs
- name: github.com/tendermint/merkleeyes
version: 0310013053953eef80def3619aeb1e3a3254f452
version: 44c4c64c731db5be4261ff3971b01b7e19729419
subpackages:
- client
- iavl

View File

@ -3,11 +3,12 @@ package commands
import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
lc "github.com/tendermint/light-client"
"github.com/tendermint/basecoin/client/commands"
proofcmd "github.com/tendermint/basecoin/client/commands/proofs"
"github.com/tendermint/basecoin/client/commands/query"
"github.com/tendermint/basecoin/modules/coin"
"github.com/tendermint/basecoin/stack"
)
@ -32,12 +33,13 @@ func accountQueryCmd(cmd *cobra.Command, args []string) error {
key := stack.PrefixedKey(coin.NameCoin, act.Bytes())
acc := coin.Account{}
proof, err := proofcmd.GetAndParseAppProof(key, &acc)
prove := !viper.GetBool(commands.FlagTrustNode)
height, err := query.GetParsed(key, &acc, prove)
if lc.IsNoDataErr(err) {
return errors.Errorf("Account bytes are empty for address %s ", addr)
} else if err != nil {
return err
}
return proofcmd.OutputProof(acc, proof.BlockHeight())
return query.OutputProof(acc, height)
}

View File

@ -6,10 +6,11 @@ import (
"strings"
"github.com/gorilla/mux"
"github.com/spf13/viper"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/client/commands"
"github.com/tendermint/basecoin/client/commands/proofs"
"github.com/tendermint/basecoin/client/commands/query"
"github.com/tendermint/basecoin/modules/auth"
"github.com/tendermint/basecoin/modules/base"
"github.com/tendermint/basecoin/modules/coin"
@ -37,8 +38,8 @@ type SendInput struct {
// doQueryAccount is the HTTP handlerfunc to query an account
// It expects a query string with
func doQueryAccount(w http.ResponseWriter, r *http.Request) {
query := mux.Vars(r)
signature := query["signature"]
args := mux.Vars(r)
signature := args["signature"]
actor, err := commands.ParseActor(signature)
if err != nil {
common.WriteError(w, err)
@ -47,7 +48,8 @@ func doQueryAccount(w http.ResponseWriter, r *http.Request) {
actor = coin.ChainAddr(actor)
key := stack.PrefixedKey(coin.NameCoin, actor.Bytes())
account := new(coin.Account)
proof, err := proofs.GetAndParseAppProof(key, account)
prove := !viper.GetBool(commands.FlagTrustNode)
height, err := query.GetParsed(key, account, prove)
if lightclient.IsNoDataErr(err) {
err := fmt.Errorf("account bytes are empty for address: %q", signature)
common.WriteError(w, err)
@ -57,7 +59,7 @@ func doQueryAccount(w http.ResponseWriter, r *http.Request) {
return
}
if err := proofs.FoutputProof(w, account, proof.BlockHeight()); err != nil {
if err := query.FoutputProof(w, account, height); err != nil {
common.WriteError(w, err)
}
}

View File

@ -8,12 +8,11 @@ import (
"github.com/spf13/viper"
"github.com/tendermint/basecoin/client/commands"
proofcmd "github.com/tendermint/basecoin/client/commands/proofs"
"github.com/tendermint/basecoin/client/commands/query"
"github.com/tendermint/basecoin/modules/ibc"
"github.com/tendermint/basecoin/stack"
wire "github.com/tendermint/go-wire"
"github.com/tendermint/go-wire/data"
"github.com/tendermint/light-client/proofs"
"github.com/tendermint/merkleeyes/iavl"
)
// TODO: query seeds (register/update)
@ -86,17 +85,19 @@ func init() {
func ibcQueryCmd(cmd *cobra.Command, args []string) error {
var res ibc.HandlerInfo
key := stack.PrefixedKey(ibc.NameIBC, ibc.HandlerKey())
proof, err := proofcmd.GetAndParseAppProof(key, &res)
prove := !viper.GetBool(commands.FlagTrustNode)
h, err := query.GetParsed(key, &res, prove)
if err != nil {
return err
}
return proofcmd.OutputProof(res, proof.BlockHeight())
return query.OutputProof(res, h)
}
func chainsQueryCmd(cmd *cobra.Command, args []string) error {
list := [][]byte{}
key := stack.PrefixedKey(ibc.NameIBC, ibc.ChainsKey())
proof, err := proofcmd.GetAndParseAppProof(key, &list)
prove := !viper.GetBool(commands.FlagTrustNode)
h, err := query.GetParsed(key, &list, prove)
if err != nil {
return err
}
@ -107,7 +108,7 @@ func chainsQueryCmd(cmd *cobra.Command, args []string) error {
res[i] = string(list[i])
}
return proofcmd.OutputProof(res, proof.BlockHeight())
return query.OutputProof(res, h)
}
func chainQueryCmd(cmd *cobra.Command, args []string) error {
@ -118,12 +119,13 @@ func chainQueryCmd(cmd *cobra.Command, args []string) error {
var res ibc.ChainInfo
key := stack.PrefixedKey(ibc.NameIBC, ibc.ChainKey(arg))
proof, err := proofcmd.GetAndParseAppProof(key, &res)
prove := !viper.GetBool(commands.FlagTrustNode)
h, err := query.GetParsed(key, &res, prove)
if err != nil {
return err
}
return proofcmd.OutputProof(res, proof.BlockHeight())
return query.OutputProof(res, h)
}
func assertOne(from, to string) error {
@ -154,12 +156,13 @@ func packetsQueryCmd(cmd *cobra.Command, args []string) error {
}
var res uint64
proof, err := proofcmd.GetAndParseAppProof(key, &res)
prove := !viper.GetBool(commands.FlagTrustNode)
h, err := query.GetParsed(key, &res, prove)
if err != nil {
return err
}
return proofcmd.OutputProof(res, proof.BlockHeight())
return query.OutputProof(res, h)
}
func packetQueryCmd(cmd *cobra.Command, args []string) error {
@ -174,6 +177,7 @@ func packetQueryCmd(cmd *cobra.Command, args []string) error {
if seq < 0 {
return errors.Errorf("--%s must be a non-negative number", FlagSequence)
}
prove := !viper.GetBool(commands.FlagTrustNode)
var key []byte
if from != "" {
@ -183,26 +187,21 @@ func packetQueryCmd(cmd *cobra.Command, args []string) error {
}
// Input queue just display the results
var packet ibc.Packet
if from != "" {
var packet ibc.Packet
proof, err := proofcmd.GetAndParseAppProof(key, &packet)
h, err := query.GetParsed(key, &packet, prove)
if err != nil {
return err
}
return proofcmd.OutputProof(packet, proof.BlockHeight())
return query.OutputProof(packet, h)
}
// output queue, create a post packet
var packet ibc.Packet
proof, err := proofcmd.GetAndParseAppProof(key, &packet)
bs, height, proof, err := query.GetWithProof(key)
if err != nil {
return err
}
// TODO: oh so ugly. fix before merge!
// wait, i want to change go-merkle too....
appProof := proof.(proofs.AppProof)
extractedProof, err := iavl.ReadProof(appProof.Proof)
err = wire.ReadBinaryBytes(bs, &packet)
if err != nil {
return err
}
@ -210,10 +209,10 @@ func packetQueryCmd(cmd *cobra.Command, args []string) error {
// create the post packet here.
post := ibc.PostPacketTx{
FromChainID: commands.GetChainID(),
FromChainHeight: proof.BlockHeight(),
FromChainHeight: height,
Key: key,
Packet: packet,
Proof: extractedProof,
Proof: proof,
}
// print json direct, as we don't need to wrap with the height

View File

@ -91,8 +91,11 @@ func IsPacketOutOfOrderErr(err error) bool {
func ErrInvalidProof() error {
return errors.WithCode(errInvalidProof, IBCCodeInvalidProof)
}
func ErrInvalidProofWithReason(err error) error {
return errors.WithCode(err, IBCCodeInvalidProof)
}
func IsInvalidProofErr(err error) bool {
return errors.IsSameError(errInvalidProof, err)
return errors.HasErrorCode(err, IBCCodeInvalidProof)
}
func ErrInvalidCommit(err error) error {

View File

@ -102,9 +102,9 @@ func (m Middleware) verifyPost(ctx basecoin.Context, store state.SimpleDB,
// verify the merkle hash....
root := seed.Header.AppHash
pBytes := packet.Bytes()
valid := tx.Proof.Verify(tx.Key, pBytes, root)
if !valid {
return ictx, itx, ErrInvalidProof()
err = tx.Proof.Verify(tx.Key, pBytes, root)
if err != nil {
return ictx, itx, ErrInvalidProofWithReason(err)
}
// add to input queue

View File

@ -59,7 +59,10 @@ func genEmptySeed(keys certifiers.ValKeys, chain string, h int,
func makePostPacket(tree *iavl.IAVLTree, packet Packet, fromID string, fromHeight int) PostPacketTx {
key := []byte(fmt.Sprintf("some-long-prefix-%06d", packet.Sequence))
tree.Set(key, packet.Bytes())
_, proof := tree.ConstructProof(key)
_, proof, _, err := tree.GetWithProof(key)
if err != nil {
panic(err)
}
if proof == nil {
panic("wtf?")
}

View File

@ -3,7 +3,7 @@ package ibc
import (
"github.com/tendermint/go-wire/data"
"github.com/tendermint/light-client/certifiers"
merkle "github.com/tendermint/merkleeyes/iavl"
"github.com/tendermint/merkleeyes/iavl"
"github.com/tendermint/basecoin"
)
@ -115,9 +115,9 @@ type PostPacketTx struct {
// The block height in which Packet was committed, to check Proof
FromChainHeight uint64 `json:"src_height"`
// this proof must match the header and the packet.Bytes()
Proof *merkle.IAVLProof `json:"proof"`
Key data.Bytes `json:"key"`
Packet Packet `json:"packet"`
Proof *iavl.KeyExistsProof `json:"proof"`
Key data.Bytes `json:"key"`
Packet Packet `json:"packet"`
}
// ValidateBasic makes sure this is consistent - used to satisfy TxInner

View File

@ -5,12 +5,13 @@ import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
lc "github.com/tendermint/light-client"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/client/commands"
proofcmd "github.com/tendermint/basecoin/client/commands/proofs"
"github.com/tendermint/basecoin/client/commands/query"
"github.com/tendermint/basecoin/modules/nonce"
"github.com/tendermint/basecoin/stack"
)
@ -33,23 +34,21 @@ func nonceQueryCmd(cmd *cobra.Command, args []string) error {
return err
}
seq, proof, err := doNonceQuery(signers)
seq, height, err := doNonceQuery(signers)
if err != nil {
return err
}
return proofcmd.OutputProof(seq, proof.BlockHeight())
return query.OutputProof(seq, height)
}
func doNonceQuery(signers []basecoin.Actor) (sequence uint32, proof lc.Proof, err error) {
func doNonceQuery(signers []basecoin.Actor) (sequence uint32, height uint64, err error) {
key := stack.PrefixedKey(nonce.NameNonce, nonce.GetSeqKey(signers))
proof, err = proofcmd.GetAndParseAppProof(key, &sequence)
prove := !viper.GetBool(commands.FlagTrustNode)
height, err = query.GetParsed(key, &sequence, prove)
if lc.IsNoDataErr(err) {
// no data, return sequence 0
return 0, proof, nil
return 0, 0, nil
}
return
}

View File

@ -2,9 +2,10 @@ package commands
import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/basecoin/client/commands"
proofcmd "github.com/tendermint/basecoin/client/commands/proofs"
"github.com/tendermint/basecoin/client/commands/query"
"github.com/tendermint/basecoin/modules/roles"
"github.com/tendermint/basecoin/stack"
)
@ -28,10 +29,11 @@ func roleQueryCmd(cmd *cobra.Command, args []string) error {
var res roles.Role
key := stack.PrefixedKey(roles.NameRole, role)
proof, err := proofcmd.GetAndParseAppProof(key, &res)
prove := !viper.GetBool(commands.FlagTrustNode)
height, err := query.GetParsed(key, &res, prove)
if err != nil {
return err
}
return proofcmd.OutputProof(res, proof.BlockHeight())
return query.OutputProof(res, height)
}

View File

@ -3,7 +3,7 @@ package state
import (
"math/rand"
"github.com/tendermint/tmlibs/merkle"
"github.com/tendermint/merkleeyes/iavl"
)
// store nonce as it's own type so no one can even try to fake it
@ -12,13 +12,13 @@ type nonce int64
// Bonsai is a deformed tree forced to fit in a small pot
type Bonsai struct {
id nonce
Tree merkle.Tree
Tree *iavl.IAVLTree
}
var _ SimpleDB = &Bonsai{}
// NewBonsai wraps a merkle tree and tags it to track children
func NewBonsai(tree merkle.Tree) *Bonsai {
func NewBonsai(tree *iavl.IAVLTree) *Bonsai {
return &Bonsai{
id: nonce(rand.Int63()),
Tree: tree,
@ -46,8 +46,8 @@ func (b *Bonsai) Remove(key []byte) (value []byte) {
return
}
func (b *Bonsai) Proof(key []byte) (value []byte, proof []byte, exists bool) {
return b.Tree.Proof(key)
func (b *Bonsai) GetWithProof(key []byte) ([]byte, *iavl.KeyExistsProof, *iavl.KeyNotExistsProof, error) {
return b.Tree.GetWithProof(key)
}
func (b *Bonsai) List(start, end []byte, limit int) []Model {

View File

@ -1,9 +1,6 @@
package state
import (
"github.com/tendermint/merkleeyes/iavl"
"github.com/tendermint/tmlibs/merkle"
)
import "github.com/tendermint/merkleeyes/iavl"
// State represents the app states, separating the commited state (for queries)
// from the working state (for CheckTx and AppendTx)
@ -14,7 +11,7 @@ type State struct {
persistent bool
}
func NewState(tree merkle.Tree, persistent bool) State {
func NewState(tree *iavl.IAVLTree, persistent bool) State {
base := NewBonsai(tree)
return State{
committed: base,
@ -59,10 +56,7 @@ func (s *State) Hash() ([]byte, error) {
func (s *State) BatchSet(key, value []byte) {
if s.persistent {
// This is in the batch with the Save, but not in the tree
tree, ok := s.committed.Tree.(*iavl.IAVLTree)
if ok {
tree.BatchSet(key, value)
}
s.committed.Tree.BatchSet(key, value)
}
}