diff --git a/client/commands/query/get.go b/client/commands/query/get.go index 7798473a98..d1a9791d3b 100644 --- a/client/commands/query/get.go +++ b/client/commands/query/get.go @@ -10,12 +10,10 @@ import ( wire "github.com/tendermint/go-wire" "github.com/tendermint/go-wire/data" - lc "github.com/tendermint/light-client" - "github.com/tendermint/light-client/certifiers" "github.com/tendermint/light-client/proofs" "github.com/tendermint/merkleeyes/iavl" - "github.com/tendermint/tendermint/rpc/client" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/commands" ) @@ -67,98 +65,7 @@ func GetWithProof(key []byte) (data.Bytes, uint64, if err != nil { return nil, 0, nil, nil, err } - return getWithProof(key, node, cert) -} - -func getWithProof(key []byte, node client.Client, cert certifiers.Certifier) ( - val data.Bytes, height uint64, eproof *iavl.KeyExistsProof, neproof *iavl.KeyNotExistsProof, err error) { - - resp, err := node.ABCIQuery("/key", key, true) - if err != nil { - return - } - - // make sure the proof is the proper height - if !resp.Code.IsOK() { - err = errors.Errorf("Query error %d: %s", resp.Code, resp.Code.String()) - return - } - if len(resp.Key) == 0 || len(resp.Proof) == 0 { - err = lc.ErrNoData() - return - } - if resp.Height == 0 { - err = errors.New("Height returned is zero") - return - } - - check, err := getCertifiedCheckpoint(int(resp.Height), node, cert) - if err != nil { - return - } - - if len(resp.Value) > 0 { - // The key was found, construct a proof of existence. - eproof = new(iavl.KeyExistsProof) - err = wire.ReadBinaryBytes(resp.Proof, &eproof) - if err != nil { - err = errors.Wrap(err, "Error reading proof") - return - } - - // Validate the proof against the certified header to ensure data integrity. - err = eproof.Verify(resp.Key, resp.Value, check.Header.AppHash) - if err != nil { - err = errors.Wrap(err, "Couldn't verify proof") - return - } - val = data.Bytes(resp.Value) - } else { - // The key wasn't found, construct a proof of non-existence. - neproof = new(iavl.KeyNotExistsProof) - err = wire.ReadBinaryBytes(resp.Proof, &neproof) - if err != nil { - err = errors.Wrap(err, "Error reading proof") - return - } - - // Validate the proof against the certified header to ensure data integrity. - err = neproof.Verify(resp.Key, check.Header.AppHash) - if err != nil { - err = errors.Wrap(err, "Couldn't verify proof") - return - } - err = lc.ErrNoData() - } - - height = resp.Height - return -} - -// 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, node client.Client, - cert certifiers.Certifier) (empty lc.Checkpoint, err 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(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 + return client.GetWithProof(key, node, cert) } // ParseHexKey parses the key flag as hex and converts to bytes or returns error diff --git a/client/commands/query/tx.go b/client/commands/query/tx.go index 6d41d3a53f..3826fc932e 100644 --- a/client/commands/query/tx.go +++ b/client/commands/query/tx.go @@ -8,6 +8,7 @@ import ( wire "github.com/tendermint/go-wire" "github.com/tendermint/tendermint/types" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/commands" ) @@ -51,7 +52,7 @@ func txQueryCmd(cmd *cobra.Command, args []string) error { return err } - check, err := getCertifiedCheckpoint(res.Height, node, cert) + check, err := client.GetCertifiedCheckpoint(res.Height, node, cert) if err != nil { return err } diff --git a/client/commands/rpc/root.go b/client/commands/rpc/root.go index af9327470f..dcb40d6e43 100644 --- a/client/commands/rpc/root.go +++ b/client/commands/rpc/root.go @@ -6,9 +6,9 @@ import ( "github.com/spf13/cobra" "github.com/tendermint/go-wire/data" - certclient "github.com/tendermint/light-client/certifiers/client" - "github.com/tendermint/tendermint/rpc/client" + rpcclient "github.com/tendermint/tendermint/rpc/client" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/commands" ) @@ -39,15 +39,14 @@ func init() { ) } -func getSecureNode() (client.Client, error) { +func getSecureNode() (rpcclient.Client, error) { // First, connect a client c := commands.GetNode() cert, err := commands.GetCertifier() if err != nil { return nil, err } - sc := certclient.Wrap(c, cert) - return sc, nil + return client.GetSecureNode(c, cert), nil } // printResult just writes the struct to the console, returns an error if it can't diff --git a/client/commands/seeds/export.go b/client/commands/seeds/export.go index febdc7daea..1119800f2d 100644 --- a/client/commands/seeds/export.go +++ b/client/commands/seeds/export.go @@ -47,6 +47,7 @@ func exportSeed(cmd *cobra.Command, args []string) error { return writeSeed(seed, path) } +// TODO use certifiers function func writeSeed(seed certifiers.Seed, path string) (err error) { f, err := os.Create(path) if err != nil { diff --git a/client/common.go b/client/common.go index c1ce25cbd3..79f816f87d 100644 --- a/client/common.go +++ b/client/common.go @@ -46,3 +46,9 @@ func GetCertifier(chainID string, trust certifiers.Provider, cert := certifiers.NewInquiring(chainID, seed, trust, source) return cert, nil } + +// GetSecureNode uses a given certifier to wrap an connection to an untrusted +// host and return a cryptographically secure rpc client. +func GetSecureNode(c rpcclient.Client, cert *certifiers.InquiringCertifier) rpcclient.Client { + return certclient.Wrap(c, cert) +} diff --git a/client/query.go b/client/query.go new file mode 100644 index 0000000000..942f1f882c --- /dev/null +++ b/client/query.go @@ -0,0 +1,111 @@ +package client + +import ( + "github.com/pkg/errors" + + wire "github.com/tendermint/go-wire" + "github.com/tendermint/go-wire/data" + lc "github.com/tendermint/light-client" + "github.com/tendermint/light-client/certifiers" + "github.com/tendermint/merkleeyes/iavl" + + "github.com/tendermint/tendermint/rpc/client" +) + +// GetWithProof will query the key on the given node, and verify it has +// a valid proof, as defined by the certifier. +// +// If there is any error in checking, returns an error. +// If val is non-empty, eproof will be non-nil +// If val is empty, neproof will be non-nil +func GetWithProof(key []byte, node client.Client, cert certifiers.Certifier) ( + val data.Bytes, height uint64, eproof *iavl.KeyExistsProof, + neproof *iavl.KeyNotExistsProof, err error) { + + resp, err := node.ABCIQuery("/key", key, true) + if err != nil { + return + } + + // make sure the proof is the proper height + if !resp.Code.IsOK() { + err = errors.Errorf("Query error %d: %s", resp.Code, resp.Code.String()) + return + } + if len(resp.Key) == 0 || len(resp.Proof) == 0 { + err = lc.ErrNoData() + return + } + if resp.Height == 0 { + err = errors.New("Height returned is zero") + return + } + + check, err := GetCertifiedCheckpoint(int(resp.Height), node, cert) + if err != nil { + return + } + + if len(resp.Value) > 0 { + // The key was found, construct a proof of existence. + eproof = new(iavl.KeyExistsProof) + err = wire.ReadBinaryBytes(resp.Proof, &eproof) + if err != nil { + err = errors.Wrap(err, "Error reading proof") + return + } + + // Validate the proof against the certified header to ensure data integrity. + err = eproof.Verify(resp.Key, resp.Value, check.Header.AppHash) + if err != nil { + err = errors.Wrap(err, "Couldn't verify proof") + return + } + val = data.Bytes(resp.Value) + } else { + // The key wasn't found, construct a proof of non-existence. + neproof = new(iavl.KeyNotExistsProof) + err = wire.ReadBinaryBytes(resp.Proof, &neproof) + if err != nil { + err = errors.Wrap(err, "Error reading proof") + return + } + + // Validate the proof against the certified header to ensure data integrity. + err = neproof.Verify(resp.Key, check.Header.AppHash) + if err != nil { + err = errors.Wrap(err, "Couldn't verify proof") + return + } + err = lc.ErrNoData() + } + + height = resp.Height + return +} + +// 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, node client.Client, + cert certifiers.Certifier) (empty lc.Checkpoint, err 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(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 +} diff --git a/client/commands/query/query_test.go b/client/query_test.go similarity index 95% rename from client/commands/query/query_test.go rename to client/query_test.go index 0786851c2b..d7b0c3d996 100644 --- a/client/commands/query/query_test.go +++ b/client/query_test.go @@ -1,4 +1,4 @@ -package query +package client import ( "os" @@ -69,7 +69,7 @@ func TestAppProofs(t *testing.T) { // Test existing key. var data eyes.Data - bs, height, proofExists, _, err := getWithProof(k, cl, cert) + bs, height, proofExists, _, err := GetWithProof(k, cl, cert) require.NoError(err, "%+v", err) require.NotNil(proofExists) require.True(height >= uint64(latest.Header.Height)) @@ -87,7 +87,7 @@ func TestAppProofs(t *testing.T) { // Test non-existing key. missing := []byte("my-missing-key") - bs, _, proofExists, proofNotExists, err := getWithProof(missing, cl, cert) + bs, _, proofExists, proofNotExists, err := GetWithProof(missing, cl, cert) require.True(lc.IsNoDataErr(err)) require.Nil(bs) require.Nil(proofExists) @@ -119,7 +119,7 @@ func TestTxProofs(t *testing.T) { // First let's make sure a bogus transaction hash returns a valid non-existence proof. key := types.Tx([]byte("bogus")).Hash() - bs, _, proofExists, proofNotExists, err := getWithProof(key, cl, cert) + bs, _, proofExists, proofNotExists, err := GetWithProof(key, cl, cert) assert.Nil(bs, "value should be nil") require.True(lc.IsNoDataErr(err), "error should signal 'no data'") assert.Nil(proofExists, "existence proof should be nil")