Skip to content
This repository has been archived by the owner on Jul 6, 2018. It is now read-only.

Commit

Permalink
Merge pull request #21 from tendermint/proofsubcmd
Browse files Browse the repository at this point in the history
Reorganize how app-specific proof commands are customized
  • Loading branch information
ethanfrey authored Jun 14, 2017
2 parents 0cf9e01 + c7d5799 commit 96e571a
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 120 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*.swp
vendor
.keys
tmcli
80 changes: 16 additions & 64 deletions commands/proofs/get.go
Original file line number Diff line number Diff line change
@@ -1,97 +1,49 @@
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

// 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
}
72 changes: 48 additions & 24 deletions commands/proofs/root.go
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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

}
50 changes: 30 additions & 20 deletions commands/proofs/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
39 changes: 27 additions & 12 deletions commands/proofs/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,48 @@ 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.
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())
}
5 changes: 5 additions & 0 deletions proofs/presenters.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/hex"

"github.com/pkg/errors"

data "github.com/tendermint/go-wire/data"
)

Expand Down Expand Up @@ -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)
}

0 comments on commit 96e571a

Please sign in to comment.