diff --git a/Gopkg.lock b/Gopkg.lock index a3aab3a86678..05019f84a186 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -665,6 +665,7 @@ "github.com/tendermint/tendermint/libs/db", "github.com/tendermint/tendermint/libs/log", "github.com/tendermint/tendermint/lite", + "github.com/tendermint/tendermint/lite/errors", "github.com/tendermint/tendermint/lite/proxy", "github.com/tendermint/tendermint/node", "github.com/tendermint/tendermint/p2p", diff --git a/client/context/context.go b/client/context/context.go index 0983c970ff0f..db6fbef7d3dc 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -15,6 +15,7 @@ import ( tmlite "github.com/tendermint/tendermint/lite" tmliteProxy "github.com/tendermint/tendermint/lite/proxy" rpcclient "github.com/tendermint/tendermint/rpc/client" + "os" ) const ctxAccStoreName = "acc" @@ -68,32 +69,40 @@ func NewCLIContext() CLIContext { } func createCertifier() tmlite.Certifier { + trustNodeDefined := viper.IsSet(client.FlagTrustNode) + if !trustNodeDefined { + return nil + } + trustNode := viper.GetBool(client.FlagTrustNode) if trustNode { return nil } + chainID := viper.GetString(client.FlagChainID) home := viper.GetString(cli.HomeFlag) nodeURI := viper.GetString(client.FlagNode) var errMsg bytes.Buffer if chainID == "" { - errMsg.WriteString("chain-id ") + errMsg.WriteString("--chain-id ") } if home == "" { - errMsg.WriteString("home ") + errMsg.WriteString("--home ") } if nodeURI == "" { - errMsg.WriteString("node ") + errMsg.WriteString("--node ") } - // errMsg is not empty if errMsg.Len() != 0 { - panic(fmt.Errorf("can't create certifier for distrust mode, empty values from these options: %s", errMsg.String())) + fmt.Printf("must specify these options: %s when --trust-node is false\n", errMsg.String()) + os.Exit(1) } + certifier, err := tmliteProxy.GetCertifier(chainID, home, nodeURI) if err != nil { panic(err) } + return certifier } diff --git a/client/context/errors.go b/client/context/errors.go index 9c611494a5b3..de96aaa18ce0 100644 --- a/client/context/errors.go +++ b/client/context/errors.go @@ -11,3 +11,11 @@ func ErrInvalidAccount(addr sdk.AccAddress) error { return errors.Errorf(`No account with address %s was found in the state. Are you sure there has been a transaction involving it?`, addr) } + +// ErrVerifyCommit returns a common error reflecting that the blockchain commit at a given +// height can't be verified. The reason is that the base checkpoint of the certifier is +// newer than the given height +func ErrVerifyCommit(height int64) error { + return errors.Errorf(`The height of base truststore in gaia-lite is higher than height %d. +Can't verify blockchain proof at this height. Please set --trust-node to true and try again`, height) +} diff --git a/client/context/query.go b/client/context/query.go index 30cd2db3003d..9eb70fd8cc14 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -16,6 +16,8 @@ import ( "github.com/cosmos/cosmos-sdk/store" abci "github.com/tendermint/tendermint/abci/types" cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/lite" + tmliteErr "github.com/tendermint/tendermint/lite/errors" tmliteProxy "github.com/tendermint/tendermint/lite/proxy" rpcclient "github.com/tendermint/tendermint/rpc/client" ctypes "github.com/tendermint/tendermint/rpc/core/types" @@ -310,7 +312,7 @@ func (ctx CLIContext) query(path string, key cmn.HexBytes) (res []byte, err erro return res, errors.Errorf("query failed: (%d) %s", resp.Code, resp.Log) } - // Data from trusted node or subspace query doesn't need verification + // Data from trusted node or subspace query doesn't need verification. if ctx.TrustNode || !isQueryStoreWithProof(path) { return resp.Value, nil } @@ -323,6 +325,17 @@ func (ctx CLIContext) query(path string, key cmn.HexBytes) (res []byte, err erro return resp.Value, nil } +// Certify verifies the consensus proof at given height +func (ctx CLIContext) Certify(height int64) (lite.Commit, error) { + check, err := tmliteProxy.GetCertifiedCommit(height, ctx.Client, ctx.Certifier) + if tmliteErr.IsCommitNotFoundErr(err) { + return lite.Commit{}, ErrVerifyCommit(height) + } else if err != nil { + return lite.Commit{}, err + } + return check, nil +} + // verifyProof perform response proof verification // nolint: unparam func (ctx CLIContext) verifyProof(path string, resp abci.ResponseQuery) error { @@ -331,13 +344,8 @@ func (ctx CLIContext) verifyProof(path string, resp abci.ResponseQuery) error { return fmt.Errorf("missing valid certifier to verify data from untrusted node") } - node, err := ctx.GetNode() - if err != nil { - return err - } - // AppHash for height H is in header H+1 - commit, err := tmliteProxy.GetCertifiedCommit(resp.Height+1, node, ctx.Certifier) + commit, err := ctx.Certify(resp.Height + 1) if err != nil { return err } @@ -350,15 +358,17 @@ func (ctx CLIContext) verifyProof(path string, resp abci.ResponseQuery) error { } // Verify the substore commit hash against trusted appHash - substoreCommitHash, err := store.VerifyMultiStoreCommitInfo(multiStoreProof.StoreName, - multiStoreProof.StoreInfos, commit.Header.AppHash) + substoreCommitHash, err := store.VerifyMultiStoreCommitInfo( + multiStoreProof.StoreName, multiStoreProof.StoreInfos, commit.Header.AppHash) if err != nil { return errors.Wrap(err, "failed in verifying the proof against appHash") } + err = store.VerifyRangeProof(resp.Key, resp.Value, substoreCommitHash, &multiStoreProof.RangeProof) if err != nil { return errors.Wrap(err, "failed in the range proof verification") } + return nil } diff --git a/client/flags.go b/client/flags.go index c98898029114..55b29b53ca1d 100644 --- a/client/flags.go +++ b/client/flags.go @@ -46,8 +46,7 @@ var ( // GetCommands adds common flags to query commands func GetCommands(cmds ...*cobra.Command) []*cobra.Command { for _, c := range cmds { - // TODO: make this default false when we support proofs - c.Flags().Bool(FlagTrustNode, true, "Don't verify proofs for responses") + c.Flags().Bool(FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") c.Flags().Bool(FlagUseLedger, false, "Use a connected Ledger device") c.Flags().String(FlagChainID, "", "Chain ID of tendermint node") c.Flags().String(FlagNode, "tcp://localhost:26657", ": to tendermint rpc interface for this chain") @@ -71,7 +70,7 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command { c.Flags().Bool(FlagAsync, false, "broadcast transactions asynchronously") c.Flags().Bool(FlagJson, false, "return output in json format") c.Flags().Bool(FlagPrintResponse, true, "return tx response (only works with async = false)") - c.Flags().Bool(FlagTrustNode, true, "Don't verify proofs for query responses") + c.Flags().Bool(FlagTrustNode, true, "Trust connected full node (don't verify proofs for responses)") c.Flags().Bool(FlagDryRun, false, "ignore the --gas flag and perform a simulation of a transaction, but don't broadcast it") c.Flags().Bool(FlagGenerateOnly, false, "build an unsigned transaction and write it to STDOUT") // --gas can accept integers and "simulate" diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 3d51ca6db2de..7bd629cb1845 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -177,7 +177,7 @@ func TestBlock(t *testing.T) { // -- - res, body = Request(t, port, "GET", "/blocks/1", nil) + res, body = Request(t, port, "GET", "/blocks/2", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) err = codec.Cdc.UnmarshalJSON([]byte(body), &resultBlock) @@ -210,7 +210,7 @@ func TestValidators(t *testing.T) { // -- - res, body = Request(t, port, "GET", "/validatorsets/1", nil) + res, body = Request(t, port, "GET", "/validatorsets/2", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) err = cdc.UnmarshalJSON([]byte(body), &resultVals) diff --git a/client/lcd/root.go b/client/lcd/root.go index 088344b846b6..ed5baa688efd 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -63,10 +63,10 @@ func ServeCommand(cdc *codec.Codec) *cobra.Command { cmd.Flags().String(flagListenAddr, "tcp://localhost:1317", "The address for the server to listen on") cmd.Flags().String(flagCORS, "", "Set the domains that can make CORS requests (* for all)") - cmd.Flags().String(client.FlagChainID, "", "The chain ID to connect to") + cmd.Flags().String(client.FlagChainID, "", "Chain ID of Tendermint node") cmd.Flags().String(client.FlagNode, "tcp://localhost:26657", "Address of the node to connect to") cmd.Flags().Int(flagMaxOpenConnections, 1000, "The number of maximum open connections") - cmd.Flags().Bool(client.FlagTrustNode, false, "Whether trust connected full node") + cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") return cmd } diff --git a/client/lcd/test_helpers.go b/client/lcd/test_helpers.go index 1075c078b9ec..3f601382246a 100644 --- a/client/lcd/test_helpers.go +++ b/client/lcd/test_helpers.go @@ -186,6 +186,10 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress // XXX: Need to set this so LCD knows the tendermint node address! viper.Set(client.FlagNode, config.RPC.ListenAddress) viper.Set(client.FlagChainID, genDoc.ChainID) + viper.Set(client.FlagTrustNode, false) + dir, err := ioutil.TempDir("", "lcd_test") + require.NoError(t, err) + viper.Set(cli.HomeFlag, dir) node, err := startTM(config, logger, genDoc, privVal, app) require.NoError(t, err) diff --git a/client/rpc/block.go b/client/rpc/block.go index 4e6ed0c5fe5b..3b1545fc7a64 100644 --- a/client/rpc/block.go +++ b/client/rpc/block.go @@ -10,6 +10,7 @@ import ( "github.com/gorilla/mux" "github.com/spf13/cobra" + tmliteProxy "github.com/tendermint/tendermint/lite/proxy" ) //BlockCommand returns the verified block data for a given heights @@ -21,8 +22,8 @@ func BlockCommand() *cobra.Command { RunE: printBlock, } cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") - // TODO: change this to false when we can - cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses") + cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") + cmd.Flags().String(client.FlagChainID, "", "Chain ID of Tendermint node") return cmd } @@ -41,6 +42,23 @@ func getBlock(cliCtx context.CLIContext, height *int64) ([]byte, error) { return nil, err } + if !cliCtx.TrustNode { + check, err := cliCtx.Certify(*height) + if err != nil { + return nil, err + } + + err = tmliteProxy.ValidateBlockMeta(res.BlockMeta, check) + if err != nil { + return nil, err + } + + err = tmliteProxy.ValidateBlock(res.Block, check) + if err != nil { + return nil, err + } + } + // TODO move maarshalling into cmd/rest functions // output, err := tmcodec.MarshalJSON(res) output, err := cdc.MarshalJSON(res) diff --git a/client/rpc/validators.go b/client/rpc/validators.go index cb002d3ea046..b034704d484e 100644 --- a/client/rpc/validators.go +++ b/client/rpc/validators.go @@ -8,9 +8,11 @@ import ( "github.com/gorilla/mux" "github.com/spf13/cobra" + "bytes" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" + tmTypes "github.com/tendermint/tendermint/types" tmtypes "github.com/tendermint/tendermint/types" ) @@ -25,8 +27,8 @@ func ValidatorCommand() *cobra.Command { RunE: printValidators, } cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") - // TODO: change this to false when we can - cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses") + cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") + cmd.Flags().String(client.FlagChainID, "", "Chain ID of Tendermint node") return cmd } @@ -70,6 +72,17 @@ func getValidators(cliCtx context.CLIContext, height *int64) ([]byte, error) { return nil, err } + if !cliCtx.TrustNode { + check, err := cliCtx.Certify(*height) + if err != nil { + return nil, err + } + + if !bytes.Equal(check.ValidatorsHash(), tmTypes.NewValidatorSet(validatorsRes.Validators).Hash()) { + return nil, fmt.Errorf("got invalid validatorset") + } + } + outputValidatorsRes := ResultValidatorsOutput{ BlockHeight: validatorsRes.BlockHeight, Validators: make([]ValidatorOutput, len(validatorsRes.Validators)), diff --git a/client/tx/query.go b/client/tx/query.go index 20a455d79b56..732c11b6599f 100644 --- a/client/tx/query.go +++ b/client/tx/query.go @@ -3,14 +3,11 @@ package tx import ( "encoding/hex" "fmt" - "net/http" - "strconv" - "github.com/tendermint/tendermint/libs/common" + "net/http" "github.com/gorilla/mux" "github.com/spf13/cobra" - "github.com/spf13/viper" abci "github.com/tendermint/tendermint/abci/types" ctypes "github.com/tendermint/tendermint/rpc/core/types" @@ -30,11 +27,10 @@ func QueryTxCmd(cdc *codec.Codec) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { // find the key to look up the account hashHexStr := args[0] - trustNode := viper.GetBool(client.FlagTrustNode) cliCtx := context.NewCLIContext().WithCodec(cdc) - output, err := queryTx(cdc, cliCtx, hashHexStr, trustNode) + output, err := queryTx(cdc, cliCtx, hashHexStr) if err != nil { return err } @@ -45,13 +41,12 @@ func QueryTxCmd(cdc *codec.Codec) *cobra.Command { } cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") - - // TODO: change this to false when we can - cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses") + cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") + cmd.Flags().String(client.FlagChainID, "", "Chain ID of Tendermint node") return cmd } -func queryTx(cdc *codec.Codec, cliCtx context.CLIContext, hashHexStr string, trustNode bool) ([]byte, error) { +func queryTx(cdc *codec.Codec, cliCtx context.CLIContext, hashHexStr string) ([]byte, error) { hash, err := hex.DecodeString(hashHexStr) if err != nil { return nil, err @@ -62,11 +57,18 @@ func queryTx(cdc *codec.Codec, cliCtx context.CLIContext, hashHexStr string, tru return nil, err } - res, err := node.Tx(hash, !trustNode) + res, err := node.Tx(hash, !cliCtx.TrustNode) if err != nil { return nil, err } + if !cliCtx.TrustNode { + err := ValidateTxResult(cliCtx, res) + if err != nil { + return nil, err + } + } + info, err := formatTxResult(cdc, res) if err != nil { return nil, err @@ -75,8 +77,21 @@ func queryTx(cdc *codec.Codec, cliCtx context.CLIContext, hashHexStr string, tru return codec.MarshalJSONIndent(cdc, info) } +// ValidateTxResult performs transaction verification +func ValidateTxResult(cliCtx context.CLIContext, res *ctypes.ResultTx) error { + check, err := cliCtx.Certify(res.Height) + if err != nil { + return err + } + + err = res.Proof.Validate(check.Header.DataHash) + if err != nil { + return err + } + return nil +} + func formatTxResult(cdc *codec.Codec, res *ctypes.ResultTx) (Info, error) { - // TODO: verify the proof if requested tx, err := parseTx(cdc, res.Tx) if err != nil { return Info{}, err @@ -116,13 +131,8 @@ func QueryTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.H return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) hashHexStr := vars["hash"] - trustNode, err := strconv.ParseBool(r.FormValue("trust_node")) - // trustNode defaults to true - if err != nil { - trustNode = true - } - output, err := queryTx(cdc, cliCtx, hashHexStr, trustNode) + output, err := queryTx(cdc, cliCtx, hashHexStr) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) diff --git a/client/tx/search.go b/client/tx/search.go index 000dc57ebb50..90c0ca168a36 100644 --- a/client/tx/search.go +++ b/client/tx/search.go @@ -62,9 +62,8 @@ $ gaiacli tendermint txs --tag test1,test2 --any } cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") - - // TODO: change this to false once proofs built in - cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses") + cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") + cmd.Flags().String(client.FlagChainID, "", "Chain ID of Tendermint node") cmd.Flags().StringSlice(flagTags, nil, "Comma-separated list of tags that must match") cmd.Flags().Bool(flagAny, false, "Return transactions that match ANY tag, rather than ALL") return cmd @@ -84,7 +83,7 @@ func searchTxs(cliCtx context.CLIContext, cdc *codec.Codec, tags []string) ([]In return nil, err } - prove := !viper.GetBool(client.FlagTrustNode) + prove := !cliCtx.TrustNode // TODO: take these as args page := 0 @@ -94,7 +93,16 @@ func searchTxs(cliCtx context.CLIContext, cdc *codec.Codec, tags []string) ([]In return nil, err } - info, err := FormatTxResults(cdc, res.Txs) + if prove { + for _, tx := range res.Txs { + err := ValidateTxResult(cliCtx, tx) + if err != nil { + return nil, err + } + } + } + + info, err := FormatTxResults(cdc, cliCtx, res.Txs) if err != nil { return nil, err } @@ -103,7 +111,7 @@ func searchTxs(cliCtx context.CLIContext, cdc *codec.Codec, tags []string) ([]In } // parse the indexed txs into an array of Info -func FormatTxResults(cdc *codec.Codec, res []*ctypes.ResultTx) ([]Info, error) { +func FormatTxResults(cdc *codec.Codec, cliCtx context.CLIContext, res []*ctypes.ResultTx) ([]Info, error) { var err error out := make([]Info, len(res)) for i := range res { diff --git a/x/stake/client/rest/query.go b/x/stake/client/rest/query.go index c6e39ebd278d..8d55bed8cfcd 100644 --- a/x/stake/client/rest/query.go +++ b/x/stake/client/rest/query.go @@ -181,7 +181,7 @@ func delegatorTxsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Han } for _, action := range actions { - foundTxs, errQuery := queryTxs(node, cdc, action, delegatorAddr) + foundTxs, errQuery := queryTxs(node, cliCtx, cdc, action, delegatorAddr) if errQuery != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(errQuery.Error())) diff --git a/x/stake/client/rest/utils.go b/x/stake/client/rest/utils.go index afe672840c18..a2266c36c521 100644 --- a/x/stake/client/rest/utils.go +++ b/x/stake/client/rest/utils.go @@ -7,6 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/x/stake/tags" rpcclient "github.com/tendermint/tendermint/rpc/client" + "github.com/cosmos/cosmos-sdk/client/context" ) // contains checks if the a given query contains one of the tx types @@ -20,15 +21,24 @@ func contains(stringSlice []string, txType string) bool { } // queries staking txs -func queryTxs(node rpcclient.Client, cdc *codec.Codec, tag string, delegatorAddr string) ([]tx.Info, error) { +func queryTxs(node rpcclient.Client, cliCtx context.CLIContext, cdc *codec.Codec, tag string, delegatorAddr string) ([]tx.Info, error) { page := 0 perPage := 100 - prove := false + prove := !cliCtx.TrustNode query := fmt.Sprintf("%s='%s' AND %s='%s'", tags.Action, tag, tags.Delegator, delegatorAddr) res, err := node.TxSearch(query, prove, page, perPage) if err != nil { return nil, err } - return tx.FormatTxResults(cdc, res.Txs) + if prove { + for _, txData := range res.Txs { + err := tx.ValidateTxResult(cliCtx, txData) + if err != nil { + return nil, err + } + } + } + + return tx.FormatTxResults(cdc, cliCtx, res.Txs) }