diff --git a/.gitignore b/.gitignore index bd2a259..778fd92 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*.swp vendor .keys tmcli diff --git a/commands/proofs/get.go b/commands/proofs/get.go index 3a4c769..5c723e6 100644 --- a/commands/proofs/get.go +++ b/commands/proofs/get.go @@ -1,62 +1,23 @@ package proofs import ( - "fmt" - - "github.com/pkg/errors" + "github.com/tendermint/tendermint/rpc/client" - "github.com/spf13/cobra" - "github.com/spf13/viper" - data "github.com/tendermint/go-wire/data" lc "github.com/tendermint/light-client" "github.com/tendermint/light-client/commands" - "github.com/tendermint/tendermint/rpc/client" ) -func (p ProofCommander) GetCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "get", - Short: "Get a proof from the tendermint node", - RunE: p.doGet, - SilenceUsage: true, - } - cmd.Flags().Int(heightFlag, 0, "Height to query (skip to use latest block)") - cmd.Flags().String(appFlag, "raw", "App to use to interpret data") - cmd.Flags().String(keyFlag, "", "Key to query on") - return cmd -} - -func (p ProofCommander) doGet(cmd *cobra.Command, args []string) error { - app := viper.GetString(appFlag) - pres, err := p.Lookup(app) - if err != nil { - return err - } - - rawkey := viper.GetString(keyFlag) - if rawkey == "" { - return errors.New("missing required flag: --" + keyFlag) - } - - // prepare the query in an app-dependent manner - key, err := pres.MakeKey(rawkey) +// 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 err - } - - // instantiate the prover instance and get a proof from the server - p.Init() - h := viper.GetInt(heightFlag) - proof, err := p.Get(key, uint64(h)) - if err != nil { - return err + return } ph := int(proof.BlockHeight()) - // here is the certifier, root of all knowledge cert, err := commands.GetCertifier() if err != nil { - return err + return } // get and validate a signed header for this proof @@ -64,34 +25,25 @@ func (p ProofCommander) doGet(cmd *cobra.Command, args []string) error { // FIXME: cannot use cert.GetByHeight for now, as it also requires // Validators and will fail on querying tendermint for non-current height. // When this is supported, we should use it instead... - client.WaitForHeight(p.node, ph, nil) - commit, err := p.node.Commit(ph) + client.WaitForHeight(node, ph, nil) + commit, err := node.Commit(ph) if err != nil { - return err + return + } + check := lc.Checkpoint{ + Header: commit.Header, + Commit: commit.Commit, } - check := lc.Checkpoint{commit.Header, commit.Commit} err = cert.Certify(check) if err != nil { - return err + return } // validate the proof against the certified header to ensure data integrity err = proof.Validate(check) if err != nil { - return err + return } - // TODO: store the proof or do something more interesting than just printing - // fmt.Println("Your data is 100% certified:") - fmt.Printf("Height: %d\n", proof.BlockHeight()) - info, err := pres.ParseData(proof.Data()) - if err != nil { - return err - } - data, err := data.ToJSON(info) - if err != nil { - return err - } - fmt.Println(string(data)) - return nil + return proof, err } diff --git a/commands/proofs/root.go b/commands/proofs/root.go index 7d16c75..4312dc6 100644 --- a/commands/proofs/root.go +++ b/commands/proofs/root.go @@ -1,17 +1,24 @@ package proofs import ( + "fmt" + + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" - lc "github.com/tendermint/light-client" - "github.com/tendermint/light-client/commands" + + "github.com/tendermint/go-wire/data" + "github.com/tendermint/light-client/proofs" - "github.com/tendermint/tendermint/rpc/client" +) + +const ( + heightFlag = "height" ) // RootCmd represents the base command when called without any subcommands var RootCmd = &cobra.Command{ - Use: "proof", + Use: "query", Short: "Get and store merkle proofs for blockchain data", Long: `Proofs allows you to validate data and merkle proofs. @@ -21,29 +28,46 @@ data to other peers as needed. `, } -type ProofCommander struct { - node client.Client - lc.Prover - ProverFunc func(client.Client) lc.Prover - proofs.Presenters +func init() { + RootCmd.Flags().Int(heightFlag, 0, "Height to query (skip to use latest block)") + + RootCmd.AddCommand(txCmd) + RootCmd.AddCommand(keyCmd) } -// Init uses configuration info to create a network connection -// as well as initializing the prover -func (p *ProofCommander) Init() { - endpoint := viper.GetString(commands.NodeFlag) - p.node = client.NewHTTP(endpoint, "/websockets") - p.Prover = p.ProverFunc(p.node) +// 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 (p ProofCommander) Register(parent *cobra.Command) { - // we add each subcommand here, so we can register the - // ProofCommander in one swoop - parent.AddCommand(p.GetCmd()) +func GetHeight() int { + return viper.GetInt(heightFlag) } -const ( - heightFlag = "height" - appFlag = "app" - keyFlag = "key" -) +// 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(info interface{}, height uint64) error { + res, err := data.ToJSON(info) + if err != nil { + return err + } + + // TODO: store the proof or do something more interesting than just printing + fmt.Printf("Height: %d\n", height) + fmt.Println(string(res)) + return nil + +} diff --git a/commands/proofs/state.go b/commands/proofs/state.go index a769d88..a64d8b5 100644 --- a/commands/proofs/state.go +++ b/commands/proofs/state.go @@ -2,33 +2,43 @@ package proofs import ( "github.com/spf13/cobra" - lc "github.com/tendermint/light-client" + + "github.com/tendermint/go-wire/data" + + "github.com/tendermint/light-client/commands" "github.com/tendermint/light-client/proofs" - "github.com/tendermint/tendermint/rpc/client" ) -var StatePresenters = proofs.NewPresenters() - -var stateCmd = &cobra.Command{ - Use: "state", +var keyCmd = &cobra.Command{ + Use: "key [key]", Short: "Handle proofs for state of abci app", - Long: `Proofs allows you to validate abci state with merkle proofs. + Long: `This will look up a given key in the abci app, verify the proof, +and output it as hex. -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. -`, +If you want json output, use an app-specific command that knows key and value structure.`, + RunE: doKeyQuery, } -func init() { - stateProver := ProofCommander{ - ProverFunc: stateProver, - Presenters: StatePresenters, +func doKeyQuery(cmd *cobra.Command, args []string) error { + // parse cli + height := GetHeight() + bkey, err := ParseHexKey(args, "key") + if err != nil { + return err } - stateProver.Register(stateCmd) - RootCmd.AddCommand(stateCmd) -} -func stateProver(node client.Client) lc.Prover { - return proofs.NewAppProver(node) + // get the proof -> this will be used by all prover commands + node := commands.GetNode() + prover := proofs.NewAppProver(node) + proof, err := GetProof(node, prover, bkey, height) + 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()) } diff --git a/commands/proofs/tx.go b/commands/proofs/tx.go index 7f71dcf..5c199ab 100644 --- a/commands/proofs/tx.go +++ b/commands/proofs/tx.go @@ -2,15 +2,15 @@ package proofs import ( "github.com/spf13/cobra" - lc "github.com/tendermint/light-client" + + "github.com/tendermint/light-client/commands" "github.com/tendermint/light-client/proofs" - "github.com/tendermint/tendermint/rpc/client" ) var TxPresenters = proofs.NewPresenters() var txCmd = &cobra.Command{ - Use: "tx", + Use: "tx [txhash]", Short: "Handle proofs of commited txs", Long: `Proofs allows you to validate abci state with merkle proofs. @@ -18,17 +18,32 @@ 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: doTxQuery, } -func init() { - txProver := ProofCommander{ - ProverFunc: txProver, - Presenters: TxPresenters, +func doTxQuery(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 } - txProver.Register(txCmd) - RootCmd.AddCommand(txCmd) -} -func txProver(node client.Client) lc.Prover { - return proofs.NewTxProver(node) + // 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/proofs/presenters.go b/proofs/presenters.go index 0108f98..84e772a 100644 --- a/proofs/presenters.go +++ b/proofs/presenters.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "github.com/pkg/errors" + data "github.com/tendermint/go-wire/data" ) @@ -81,3 +82,7 @@ func (k KeyMaker) MakeKey(str string) ([]byte, error) { } return r, errors.WithStack(err) } + +func ParseHexKey(str string) ([]byte, error) { + return KeyMaker{}.MakeKey(str) +}