Move proof logic away from viper

This commit is contained in:
Ethan Frey 2017-09-05 18:50:57 +02:00
parent 521503026e
commit e0ddecc229
7 changed files with 130 additions and 105 deletions

View File

@ -10,12 +10,10 @@ import (
wire "github.com/tendermint/go-wire" wire "github.com/tendermint/go-wire"
"github.com/tendermint/go-wire/data" "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/light-client/proofs"
"github.com/tendermint/merkleeyes/iavl" "github.com/tendermint/merkleeyes/iavl"
"github.com/tendermint/tendermint/rpc/client"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/commands" "github.com/cosmos/cosmos-sdk/client/commands"
) )
@ -67,98 +65,7 @@ func GetWithProof(key []byte) (data.Bytes, uint64,
if err != nil { if err != nil {
return nil, 0, nil, nil, err return nil, 0, nil, nil, err
} }
return getWithProof(key, node, cert) return client.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
} }
// ParseHexKey parses the key flag as hex and converts to bytes or returns error // ParseHexKey parses the key flag as hex and converts to bytes or returns error

View File

@ -8,6 +8,7 @@ import (
wire "github.com/tendermint/go-wire" wire "github.com/tendermint/go-wire"
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/commands" "github.com/cosmos/cosmos-sdk/client/commands"
) )
@ -51,7 +52,7 @@ func txQueryCmd(cmd *cobra.Command, args []string) error {
return err return err
} }
check, err := getCertifiedCheckpoint(res.Height, node, cert) check, err := client.GetCertifiedCheckpoint(res.Height, node, cert)
if err != nil { if err != nil {
return err return err
} }

View File

@ -6,9 +6,9 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/tendermint/go-wire/data" "github.com/tendermint/go-wire/data"
certclient "github.com/tendermint/light-client/certifiers/client" rpcclient "github.com/tendermint/tendermint/rpc/client"
"github.com/tendermint/tendermint/rpc/client"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/commands" "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 // First, connect a client
c := commands.GetNode() c := commands.GetNode()
cert, err := commands.GetCertifier() cert, err := commands.GetCertifier()
if err != nil { if err != nil {
return nil, err return nil, err
} }
sc := certclient.Wrap(c, cert) return client.GetSecureNode(c, cert), nil
return sc, nil
} }
// printResult just writes the struct to the console, returns an error if it can't // printResult just writes the struct to the console, returns an error if it can't

View File

@ -47,6 +47,7 @@ func exportSeed(cmd *cobra.Command, args []string) error {
return writeSeed(seed, path) return writeSeed(seed, path)
} }
// TODO use certifiers function
func writeSeed(seed certifiers.Seed, path string) (err error) { func writeSeed(seed certifiers.Seed, path string) (err error) {
f, err := os.Create(path) f, err := os.Create(path)
if err != nil { if err != nil {

View File

@ -46,3 +46,9 @@ func GetCertifier(chainID string, trust certifiers.Provider,
cert := certifiers.NewInquiring(chainID, seed, trust, source) cert := certifiers.NewInquiring(chainID, seed, trust, source)
return cert, nil 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)
}

111
client/query.go Normal file
View File

@ -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
}

View File

@ -1,4 +1,4 @@
package query package client
import ( import (
"os" "os"
@ -69,7 +69,7 @@ func TestAppProofs(t *testing.T) {
// Test existing key. // Test existing key.
var data eyes.Data 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.NoError(err, "%+v", err)
require.NotNil(proofExists) require.NotNil(proofExists)
require.True(height >= uint64(latest.Header.Height)) require.True(height >= uint64(latest.Header.Height))
@ -87,7 +87,7 @@ func TestAppProofs(t *testing.T) {
// Test non-existing key. // Test non-existing key.
missing := []byte("my-missing-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.True(lc.IsNoDataErr(err))
require.Nil(bs) require.Nil(bs)
require.Nil(proofExists) 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. // First let's make sure a bogus transaction hash returns a valid non-existence proof.
key := types.Tx([]byte("bogus")).Hash() 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") assert.Nil(bs, "value should be nil")
require.True(lc.IsNoDataErr(err), "error should signal 'no data'") require.True(lc.IsNoDataErr(err), "error should signal 'no data'")
assert.Nil(proofExists, "existence proof should be nil") assert.Nil(proofExists, "existence proof should be nil")