diff --git a/app/store.go b/app/store.go index 329b63516..efb5471ca 100644 --- a/app/store.go +++ b/app/store.go @@ -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 diff --git a/client/commands/proofs/get.go b/client/commands/proofs/get.go deleted file mode 100644 index 8ad394e3a..000000000 --- a/client/commands/proofs/get.go +++ /dev/null @@ -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": , "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) -} diff --git a/client/commands/proofs/tx.go b/client/commands/proofs/tx.go deleted file mode 100644 index cffc4dfc1..000000000 --- a/client/commands/proofs/tx.go +++ /dev/null @@ -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()) -} diff --git a/client/commands/query/get.go b/client/commands/query/get.go new file mode 100644 index 000000000..91831892d --- /dev/null +++ b/client/commands/query/get.go @@ -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": , "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) +} diff --git a/client/commands/proofs/root.go b/client/commands/query/root.go similarity index 98% rename from client/commands/proofs/root.go rename to client/commands/query/root.go index b833f3da4..24b25ff21 100644 --- a/client/commands/proofs/root.go +++ b/client/commands/query/root.go @@ -1,4 +1,4 @@ -package proofs +package query import ( "github.com/spf13/cobra" diff --git a/client/commands/proofs/state.go b/client/commands/query/state.go similarity index 59% rename from client/commands/proofs/state.go rename to client/commands/query/state.go index e55b5b581..513440b20 100644 --- a/client/commands/proofs/state.go +++ b/client/commands/query/state.go @@ -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) } diff --git a/client/commands/query/tx.go b/client/commands/query/tx.go new file mode 100644 index 000000000..873deb866 --- /dev/null +++ b/client/commands/query/tx.go @@ -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)) +} diff --git a/client/commands/txs/presenter.go b/client/commands/txs/presenter.go deleted file mode 100644 index 2886e7bd2..000000000 --- a/client/commands/txs/presenter.go +++ /dev/null @@ -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 -} diff --git a/cmd/basecli/main.go b/cmd/basecli/main.go index 6813dd916..0239c5ada 100644 --- a/cmd/basecli/main.go +++ b/cmd/basecli/main.go @@ -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, diff --git a/docs/guide/counter/cmd/countercli/commands/query.go b/docs/guide/counter/cmd/countercli/commands/query.go index 8debaf9c2..f497fec92 100644 --- a/docs/guide/counter/cmd/countercli/commands/query.go +++ b/docs/guide/counter/cmd/countercli/commands/query.go @@ -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) } diff --git a/docs/guide/counter/cmd/countercli/main.go b/docs/guide/counter/cmd/countercli/main.go index 6e930cd7d..37afba1d4 100644 --- a/docs/guide/counter/cmd/countercli/main.go +++ b/docs/guide/counter/cmd/countercli/main.go @@ -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, ) diff --git a/glide.lock b/glide.lock index 4a6213884..5d0c74a71 100644 --- a/glide.lock +++ b/glide.lock @@ -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 diff --git a/modules/coin/commands/query.go b/modules/coin/commands/query.go index 1faabb621..dea196529 100644 --- a/modules/coin/commands/query.go +++ b/modules/coin/commands/query.go @@ -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) } diff --git a/modules/coin/rest/handlers.go b/modules/coin/rest/handlers.go index 60f5d7b2d..c3393de50 100644 --- a/modules/coin/rest/handlers.go +++ b/modules/coin/rest/handlers.go @@ -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) } } diff --git a/modules/ibc/commands/query.go b/modules/ibc/commands/query.go index 38c3d01e1..b320da81c 100644 --- a/modules/ibc/commands/query.go +++ b/modules/ibc/commands/query.go @@ -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 diff --git a/modules/ibc/errors.go b/modules/ibc/errors.go index 8fafe7a73..228c85e88 100644 --- a/modules/ibc/errors.go +++ b/modules/ibc/errors.go @@ -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 { diff --git a/modules/ibc/middleware.go b/modules/ibc/middleware.go index c81bcec48..eafff78ac 100644 --- a/modules/ibc/middleware.go +++ b/modules/ibc/middleware.go @@ -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 diff --git a/modules/ibc/test_helpers.go b/modules/ibc/test_helpers.go index d2272a0e0..5eba0f00a 100644 --- a/modules/ibc/test_helpers.go +++ b/modules/ibc/test_helpers.go @@ -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?") } diff --git a/modules/ibc/tx.go b/modules/ibc/tx.go index 8ee4d934c..a7decdc24 100644 --- a/modules/ibc/tx.go +++ b/modules/ibc/tx.go @@ -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 diff --git a/modules/nonce/commands/query.go b/modules/nonce/commands/query.go index 405b0ea94..a369e9667 100644 --- a/modules/nonce/commands/query.go +++ b/modules/nonce/commands/query.go @@ -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 } diff --git a/modules/roles/commands/query.go b/modules/roles/commands/query.go index 6a333f211..fccd54938 100644 --- a/modules/roles/commands/query.go +++ b/modules/roles/commands/query.go @@ -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) } diff --git a/state/bonsai.go b/state/bonsai.go index 9800ba54f..7eb98568a 100644 --- a/state/bonsai.go +++ b/state/bonsai.go @@ -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 { diff --git a/state/merkle.go b/state/merkle.go index 666cac0bc..4d6ac45f7 100644 --- a/state/merkle.go +++ b/state/merkle.go @@ -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) } }