From 615a257316268cecb410a59cc3e72a0281b370e9 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 26 Oct 2017 16:08:41 +0200 Subject: [PATCH 1/9] Import certifiers from tendermint not light-client --- client/commands/commits/export.go | 2 +- client/commands/commits/import.go | 2 +- client/commands/commits/show.go | 4 ++-- client/commands/commits/update.go | 2 +- client/commands/common.go | 2 +- client/commands/init.go | 4 ++-- client/common.go | 8 ++++---- client/query.go | 4 ++-- client/query_test.go | 4 ++-- glide.lock | 24 ++++++++++++------------ modules/ibc/commands/tx.go | 2 +- modules/ibc/ibc_test.go | 2 +- modules/ibc/provider.go | 4 ++-- modules/ibc/provider_test.go | 4 ++-- modules/ibc/test_helpers.go | 2 +- modules/ibc/tx.go | 2 +- 16 files changed, 36 insertions(+), 36 deletions(-) diff --git a/client/commands/commits/export.go b/client/commands/commits/export.go index c27826fa7664..b0b1c2a64cc2 100644 --- a/client/commands/commits/export.go +++ b/client/commands/commits/export.go @@ -5,7 +5,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/tendermint/light-client/certifiers/files" + "github.com/tendermint/tendermint/certifiers/files" "github.com/cosmos/cosmos-sdk/client/commands" ) diff --git a/client/commands/commits/import.go b/client/commands/commits/import.go index 936d25b06390..3af3d79e6a4d 100644 --- a/client/commands/commits/import.go +++ b/client/commands/commits/import.go @@ -7,7 +7,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/tendermint/light-client/certifiers/files" + "github.com/tendermint/tendermint/certifiers/files" "github.com/cosmos/cosmos-sdk/client/commands" ) diff --git a/client/commands/commits/show.go b/client/commands/commits/show.go index e9cc6ed28f0c..67152044c900 100644 --- a/client/commands/commits/show.go +++ b/client/commands/commits/show.go @@ -8,8 +8,8 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/tendermint/light-client/certifiers" - "github.com/tendermint/light-client/certifiers/files" + "github.com/tendermint/tendermint/certifiers" + "github.com/tendermint/tendermint/certifiers/files" "github.com/cosmos/cosmos-sdk/client/commands" ) diff --git a/client/commands/commits/update.go b/client/commands/commits/update.go index c42144b61b85..f522a914a345 100644 --- a/client/commands/commits/update.go +++ b/client/commands/commits/update.go @@ -6,7 +6,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/tendermint/light-client/certifiers" + "github.com/tendermint/tendermint/certifiers" "github.com/cosmos/cosmos-sdk/client/commands" ) diff --git a/client/commands/common.go b/client/commands/common.go index c51dbb469bd1..ff93a05a69c2 100644 --- a/client/commands/common.go +++ b/client/commands/common.go @@ -12,7 +12,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/tendermint/light-client/certifiers" + "github.com/tendermint/tendermint/certifiers" "github.com/tendermint/tmlibs/cli" cmn "github.com/tendermint/tmlibs/common" diff --git a/client/commands/init.go b/client/commands/init.go index 807dd1c0b27c..c290d1e14a6e 100644 --- a/client/commands/init.go +++ b/client/commands/init.go @@ -15,8 +15,8 @@ import ( "github.com/spf13/pflag" "github.com/spf13/viper" - "github.com/tendermint/light-client/certifiers" - "github.com/tendermint/light-client/certifiers/files" + "github.com/tendermint/tendermint/certifiers" + "github.com/tendermint/tendermint/certifiers/files" "github.com/tendermint/tmlibs/cli" cmn "github.com/tendermint/tmlibs/common" diff --git a/client/common.go b/client/common.go index 5e79b8afbdb3..901c74483463 100644 --- a/client/common.go +++ b/client/common.go @@ -3,10 +3,10 @@ package client import ( "errors" - "github.com/tendermint/light-client/certifiers" - certclient "github.com/tendermint/light-client/certifiers/client" - certerr "github.com/tendermint/light-client/certifiers/errors" - "github.com/tendermint/light-client/certifiers/files" + "github.com/tendermint/tendermint/certifiers" + certclient "github.com/tendermint/tendermint/certifiers/client" + certerr "github.com/tendermint/tendermint/certifiers/errors" + "github.com/tendermint/tendermint/certifiers/files" "github.com/tendermint/light-client/proofs" diff --git a/client/query.go b/client/query.go index 25b3823d6f53..27606289bce2 100644 --- a/client/query.go +++ b/client/query.go @@ -5,8 +5,8 @@ import ( "github.com/tendermint/go-wire/data" "github.com/tendermint/iavl" - "github.com/tendermint/light-client/certifiers" - certerr "github.com/tendermint/light-client/certifiers/errors" + "github.com/tendermint/tendermint/certifiers" + certerr "github.com/tendermint/tendermint/certifiers/errors" "github.com/tendermint/tendermint/rpc/client" ) diff --git a/client/query_test.go b/client/query_test.go index 0320176eb357..3d4a5a387517 100644 --- a/client/query_test.go +++ b/client/query_test.go @@ -9,8 +9,8 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/go-wire" - "github.com/tendermint/light-client/certifiers" - certclient "github.com/tendermint/light-client/certifiers/client" + "github.com/tendermint/tendermint/certifiers" + certclient "github.com/tendermint/tendermint/certifiers/client" "github.com/tendermint/tmlibs/log" nm "github.com/tendermint/tendermint/node" diff --git a/glide.lock b/glide.lock index e0ea8a99dd39..0dab715f7776 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ hash: fbfdd03c0367bb0785ceb81ed34059df219e55d5a9c71c12597e505fbce14165 -updated: 2017-10-25T19:24:51.90002008+02:00 +updated: 2017-10-26T15:57:25.483144016+02:00 imports: - name: github.com/bgentry/speakeasy version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd @@ -11,10 +11,10 @@ imports: version: a368813c5e648fee92e5f6c30e3944ff9d5e8895 - name: github.com/ebuchman/fail-test version: 95f809107225be108efcf10a3509e4ea6ceef3c4 +- name: github.com/ethanfrey/hid + version: f379bda1dbc8e79333b04563f71a12e86206efe5 - name: github.com/ethanfrey/ledger - version: 5e432577be582bd18a3b4a9cd75dae7a317ade36 -- name: github.com/flynn/hid - version: ed06a31c6245d4552e8dbba7e32e5b010b875d65 + version: 3689ce9be93e1a5bef836b1cc2abb18381c79176 - name: github.com/fsnotify/fsnotify version: 4da3e2cfbabc9f751898f250b49f2439785783a1 - name: github.com/go-kit/kit @@ -48,7 +48,7 @@ imports: - name: github.com/gorilla/mux version: 24fca303ac6da784b9e8269f724ddeb0b2eea5e7 - name: github.com/gorilla/websocket - version: 71fa72d4842364bc5f74185f4161e0099ea3624a + version: ea4d1f681babbce9545c9c5f3d5194a789c89f5b - name: github.com/hashicorp/hcl version: 23c074d0eceb2b8a5bfdbb271ab780cde70f05a8 subpackages: @@ -95,7 +95,7 @@ imports: - name: github.com/spf13/pflag version: 97afa5e7ca8a08a383cb259e06636b5e2cc7897f - name: github.com/spf13/viper - version: 8ef37cbca71638bf32f3d5e194117d4cb46da163 + version: 25b30aa063fc18e48662b86996252eabdcf2f0c7 - name: github.com/syndtr/goleveldb version: b89cc31ef7977104127d34c1bd31ebd1a9db2199 subpackages: @@ -112,7 +112,7 @@ imports: - leveldb/table - leveldb/util - name: github.com/tendermint/abci - version: bb9bb4aa465a31fd6a272765be381888e6898c74 + version: a0e38dc58374f485481ea07b23659d85f670a694 subpackages: - client - example/dummy @@ -124,7 +124,7 @@ imports: - edwards25519 - extra25519 - name: github.com/tendermint/go-crypto - version: 0a5b1d979a1bc86200c9ff829fbbcd575799a1b6 + version: db5603e37435933c13665a708055fadd18222f5f subpackages: - bcrypt - keys @@ -134,7 +134,7 @@ imports: - keys/wordlist - nano - name: github.com/tendermint/go-wire - version: 99d2169a1e39c65983eacaa1da867d6f3218e1c9 + version: 3180c867ca52bcd9ba6c905ce63613f8d8e9837c subpackages: - data - data/base58 @@ -149,7 +149,7 @@ imports: - certifiers/files - proofs - name: github.com/tendermint/tendermint - version: b2d5546cf8f71e0e168072e118d9836862384e6c + version: bb6c15b00a07e2aafc7fe245b3acfb33b9c25abe subpackages: - blockchain - cmd/tendermint/commands @@ -177,7 +177,7 @@ imports: - types - version - name: github.com/tendermint/tmlibs - version: 0a652499ead7cd20a57a6a592f0491a2b493bb85 + version: b30e3ba26d4077edeed83c50a4e0c38b0ec9ddb3 subpackages: - autofile - cli @@ -228,7 +228,7 @@ imports: subpackages: - googleapis/rpc/status - name: google.golang.org/grpc - version: a5986a5c88227370a9c0a82e5277167229c034cd + version: f7bf885db0b7479a537ec317c6e48ce53145f3db subpackages: - balancer - balancer/roundrobin diff --git a/modules/ibc/commands/tx.go b/modules/ibc/commands/tx.go index 292658721cff..f5ed752973ac 100644 --- a/modules/ibc/commands/tx.go +++ b/modules/ibc/commands/tx.go @@ -11,7 +11,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/commands" txcmd "github.com/cosmos/cosmos-sdk/client/commands/txs" "github.com/cosmos/cosmos-sdk/modules/ibc" - "github.com/tendermint/light-client/certifiers" + "github.com/tendermint/tendermint/certifiers" ) // RegisterChainTxCmd is CLI command to register a new chain for ibc diff --git a/modules/ibc/ibc_test.go b/modules/ibc/ibc_test.go index ceb92237fdbf..e491f94a2a95 100644 --- a/modules/ibc/ibc_test.go +++ b/modules/ibc/ibc_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" wire "github.com/tendermint/go-wire" - "github.com/tendermint/light-client/certifiers" + "github.com/tendermint/tendermint/certifiers" "github.com/tendermint/tmlibs/log" sdk "github.com/cosmos/cosmos-sdk" diff --git a/modules/ibc/provider.go b/modules/ibc/provider.go index 712b6ee81daa..a01b7504fd28 100644 --- a/modules/ibc/provider.go +++ b/modules/ibc/provider.go @@ -2,8 +2,8 @@ package ibc import ( wire "github.com/tendermint/go-wire" - "github.com/tendermint/light-client/certifiers" - certerr "github.com/tendermint/light-client/certifiers/errors" + "github.com/tendermint/tendermint/certifiers" + certerr "github.com/tendermint/tendermint/certifiers/errors" "github.com/cosmos/cosmos-sdk/stack" "github.com/cosmos/cosmos-sdk/state" diff --git a/modules/ibc/provider_test.go b/modules/ibc/provider_test.go index 2318f979652e..abdb93faacd7 100644 --- a/modules/ibc/provider_test.go +++ b/modules/ibc/provider_test.go @@ -7,8 +7,8 @@ import ( "github.com/stretchr/testify/require" "github.com/cosmos/cosmos-sdk/state" - "github.com/tendermint/light-client/certifiers" - certerr "github.com/tendermint/light-client/certifiers/errors" + "github.com/tendermint/tendermint/certifiers" + certerr "github.com/tendermint/tendermint/certifiers/errors" ) func assertCommitsEqual(t *testing.T, fc, fc2 certifiers.FullCommit) { diff --git a/modules/ibc/test_helpers.go b/modules/ibc/test_helpers.go index 79960dadfbd1..456e3076f0ee 100644 --- a/modules/ibc/test_helpers.go +++ b/modules/ibc/test_helpers.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/tendermint/iavl" - "github.com/tendermint/light-client/certifiers" + "github.com/tendermint/tendermint/certifiers" "github.com/tendermint/tmlibs/log" sdk "github.com/cosmos/cosmos-sdk" diff --git a/modules/ibc/tx.go b/modules/ibc/tx.go index eae5a38f9425..2686080ce80b 100644 --- a/modules/ibc/tx.go +++ b/modules/ibc/tx.go @@ -3,7 +3,7 @@ package ibc import ( "github.com/tendermint/go-wire/data" "github.com/tendermint/iavl" - "github.com/tendermint/light-client/certifiers" + "github.com/tendermint/tendermint/certifiers" sdk "github.com/cosmos/cosmos-sdk" ) From 4c1b4add4aa2e5653042ae9733ffafd726327889 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 26 Oct 2017 16:16:30 +0200 Subject: [PATCH 2/9] Crash-merge light-client/proofs into client/proofs --- client/commands/query/get.go | 2 +- client/common.go | 2 +- client/proofs/app.go | 109 +++++++++++++++++++ client/proofs/app_test.go | 116 ++++++++++++++++++++ client/proofs/block.go | 40 +++++++ client/proofs/errors.go | 22 ++++ client/proofs/errors_test.go | 18 ++++ client/proofs/interface.go | 28 +++++ client/proofs/main_test.go | 38 +++++++ client/proofs/presenters.go | 89 +++++++++++++++ client/proofs/tx.go | 81 ++++++++++++++ client/proofs/tx_test.go | 66 ++++++++++++ client/proofs/wrapper.go | 204 +++++++++++++++++++++++++++++++++++ client/query.go | 17 +-- 14 files changed, 822 insertions(+), 10 deletions(-) create mode 100644 client/proofs/app.go create mode 100644 client/proofs/app_test.go create mode 100644 client/proofs/block.go create mode 100644 client/proofs/errors.go create mode 100644 client/proofs/errors_test.go create mode 100644 client/proofs/interface.go create mode 100644 client/proofs/main_test.go create mode 100644 client/proofs/presenters.go create mode 100644 client/proofs/tx.go create mode 100644 client/proofs/tx_test.go create mode 100644 client/proofs/wrapper.go diff --git a/client/commands/query/get.go b/client/commands/query/get.go index 7ffc1c1f5d36..048241a3bea3 100644 --- a/client/commands/query/get.go +++ b/client/commands/query/get.go @@ -11,7 +11,7 @@ import ( wire "github.com/tendermint/go-wire" "github.com/tendermint/go-wire/data" "github.com/tendermint/iavl" - "github.com/tendermint/light-client/proofs" + "github.com/cosmos/cosmos-sdk/client/proofs" rpcclient "github.com/tendermint/tendermint/rpc/client" diff --git a/client/common.go b/client/common.go index 901c74483463..ea80cc11b0e1 100644 --- a/client/common.go +++ b/client/common.go @@ -8,7 +8,7 @@ import ( certerr "github.com/tendermint/tendermint/certifiers/errors" "github.com/tendermint/tendermint/certifiers/files" - "github.com/tendermint/light-client/proofs" + "github.com/cosmos/cosmos-sdk/client/proofs" rpcclient "github.com/tendermint/tendermint/rpc/client" ) diff --git a/client/proofs/app.go b/client/proofs/app.go new file mode 100644 index 000000000000..58d762962674 --- /dev/null +++ b/client/proofs/app.go @@ -0,0 +1,109 @@ +package proofs + +import ( + "github.com/pkg/errors" + + wire "github.com/tendermint/go-wire" + data "github.com/tendermint/go-wire/data" + "github.com/tendermint/iavl" + + "github.com/tendermint/tendermint/rpc/client" + + "github.com/tendermint/tendermint/certifiers" + certerr "github.com/tendermint/tendermint/certifiers/errors" +) + +var _ Prover = AppProver{} +var _ Proof = AppProof{} + +// we limit proofs to 1MB to stop overflow attacks +const appLimit = 1000 * 1000 + +// AppProver provides positive proofs of key-value pairs in the abciapp. +// +// TODO: also support negative proofs (this key is not set) +type AppProver struct { + node client.Client +} + +func NewAppProver(node client.Client) AppProver { + return AppProver{node: node} +} + +// Get tries to download a merkle hash for app state on this key from +// the tendermint node. +func (a AppProver) Get(key []byte, h uint64) (Proof, error) { + resp, err := a.node.ABCIQuery("/key", key) + if err != nil { + return nil, err + } + + // make sure the proof is the proper height + if !resp.Code.IsOK() { + return nil, errors.Errorf("Query error %d: %s", resp.Code, resp.Code.String()) + } + if len(resp.Key) == 0 || len(resp.Value) == 0 || len(resp.Proof) == 0 { + return nil, ErrNoData() + } + if resp.Height == 0 { + resp.Height = h + } + if h != 0 && h != resp.Height { + return nil, certerr.ErrHeightMismatch(int(h), int(resp.Height)) + } + proof := AppProof{ + Height: resp.Height, + Key: resp.Key, + Value: resp.Value, + Proof: resp.Proof, + } + return proof, nil +} + +func (a AppProver) Unmarshal(data []byte) (Proof, error) { + var proof AppProof + err := errors.WithStack(wire.ReadBinaryBytes(data, &proof)) + return proof, err +} + +// AppProof containts a key-value pair at a given height. +// It also contains the merkle proof from that key-value pair to the root hash, +// which can be verified against a signed header. +type AppProof struct { + Height uint64 + Key data.Bytes + Value data.Bytes + Proof data.Bytes +} + +func (p AppProof) Data() []byte { + return p.Value +} + +func (p AppProof) BlockHeight() uint64 { + return p.Height +} + +func (p AppProof) Validate(check certifiers.Commit) error { + if uint64(check.Height()) != p.Height { + return certerr.ErrHeightMismatch(int(p.Height), check.Height()) + } + + proof, err := iavl.ReadKeyExistsProof(p.Proof) + if err != nil { + return errors.WithStack(err) + } + + err = proof.Verify(p.Key, p.Value, check.Header.AppHash) + if err != nil { + return err + } + + // LGTM! + return nil +} + +func (p AppProof) Marshal() ([]byte, error) { + data := wire.BinaryBytes(p) + return data, nil +} diff --git a/client/proofs/app_test.go b/client/proofs/app_test.go new file mode 100644 index 000000000000..a32e8b2a1347 --- /dev/null +++ b/client/proofs/app_test.go @@ -0,0 +1,116 @@ +package proofs_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + ctest "github.com/tendermint/tmlibs/test" + + "github.com/tendermint/tendermint/certifiers" + certcli "github.com/tendermint/tendermint/certifiers/client" + "github.com/tendermint/tendermint/rpc/client" + + "github.com/cosmos/cosmos-sdk/client/proofs" +) + +func getCurrentCheck(t *testing.T, cl client.Client) certifiers.Commit { + stat, err := cl.Status() + require.Nil(t, err, "%+v", err) + return getCheckForHeight(t, cl, stat.LatestBlockHeight) +} + +func getCheckForHeight(t *testing.T, cl client.Client, h int) certifiers.Commit { + client.WaitForHeight(cl, h, nil) + commit, err := cl.Commit(&h) + require.Nil(t, err, "%+v", err) + return certcli.CommitFromResult(commit) +} + +func TestAppProofs(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + cl := getLocalClient() + prover := proofs.NewAppProver(cl) + time.Sleep(200 * time.Millisecond) + + precheck := getCurrentCheck(t, cl) + + // great, let's store some data here, and make more checks.... + k, v, tx := MakeTxKV() + br, err := cl.BroadcastTxCommit(tx) + require.Nil(err, "%+v", err) + require.EqualValues(0, br.CheckTx.Code) + require.EqualValues(0, br.DeliverTx.Code) + h := br.Height + 1 + + // unfortunately we cannot tell the server to give us any height + // other than the most recent, so 0 is the only choice :( + pr, err := prover.Get(k, uint64(h)) + require.Nil(err, "%+v", err) + check := getCheckForHeight(t, cl, h) + + // matches and validates with post-tx header + err = pr.Validate(check) + assert.Nil(err, "%+v", err) + + // doesn't matches with pre-tx header + err = pr.Validate(precheck) + assert.NotNil(err) + + // make sure it has the values we want + apk, ok := pr.(proofs.AppProof) + if assert.True(ok) { + assert.EqualValues(k, apk.Key) + assert.EqualValues(v, apk.Value) + } + + // make sure we read/write properly, and any changes to the serialized + // object are invalid proof (2000 random attempts) + + // TODO: iavl panics, fix this + // testSerialization(t, prover, pr, check, 2000) +} + +// testSerialization makes sure the proof will un/marshal properly +// and validate with the checkpoint. It also does lots of modifications +// to the binary data and makes sure no mods validates properly +func testSerialization(t *testing.T, prover proofs.Prover, pr proofs.Proof, + check certifiers.Commit, mods int) { + + require := require.New(t) + + // first, make sure that we can serialize and deserialize + err := pr.Validate(check) + require.Nil(err, "%+v", err) + + // store the data + data, err := pr.Marshal() + require.Nil(err, "%+v", err) + + // recover the data and make sure it still checks out + npr, err := prover.Unmarshal(data) + require.Nil(err, "%+v", err) + err = npr.Validate(check) + require.Nil(err, "%#v\n%+v", npr, err) + + // now let's go mod... + for i := 0; i < mods; i++ { + bdata := ctest.MutateByteSlice(data) + bpr, err := prover.Unmarshal(bdata) + if err == nil { + assert.NotNil(t, bpr.Validate(check)) + } + } +} + +// // validate all tx in the block +// block, err := cl.Block(check.Height()) +// require.Nil(err, "%+v", err) +// err = check.CheckTxs(block.Block.Data.Txs) +// assert.Nil(err, "%+v", err) + +// oh, i would like the know the hieght of the broadcast_commit..... +// so i could verify that tx :( diff --git a/client/proofs/block.go b/client/proofs/block.go new file mode 100644 index 000000000000..8200f8b779b8 --- /dev/null +++ b/client/proofs/block.go @@ -0,0 +1,40 @@ +package proofs + +import ( + "bytes" + "errors" + + "github.com/tendermint/tendermint/types" + + "github.com/tendermint/tendermint/certifiers" + certerr "github.com/tendermint/tendermint/certifiers/errors" +) + +func ValidateBlockMeta(meta *types.BlockMeta, check certifiers.Commit) error { + // TODO: check the BlockID?? + return ValidateHeader(meta.Header, check) +} + +func ValidateBlock(meta *types.Block, check certifiers.Commit) error { + err := ValidateHeader(meta.Header, check) + if err != nil { + return err + } + if !bytes.Equal(meta.Data.Hash(), meta.Header.DataHash) { + return errors.New("Data hash doesn't match header") + } + return nil +} + +func ValidateHeader(head *types.Header, check certifiers.Commit) error { + // make sure they are for the same height (obvious fail) + if head.Height != check.Height() { + return certerr.ErrHeightMismatch(head.Height, check.Height()) + } + // check if they are equal by using hashes + chead := check.Header + if !bytes.Equal(head.Hash(), chead.Hash()) { + return errors.New("Headers don't match") + } + return nil +} diff --git a/client/proofs/errors.go b/client/proofs/errors.go new file mode 100644 index 000000000000..8a728ca1828d --- /dev/null +++ b/client/proofs/errors.go @@ -0,0 +1,22 @@ +package proofs + +import ( + "fmt" + + "github.com/pkg/errors" +) + +//-------------------------------------------- + +var errNoData = fmt.Errorf("No data returned for query") + +// IsNoDataErr checks whether an error is due to a query returning empty data +func IsNoDataErr(err error) bool { + return errors.Cause(err) == errNoData +} + +func ErrNoData() error { + return errors.WithStack(errNoData) +} + +//-------------------------------------------- diff --git a/client/proofs/errors_test.go b/client/proofs/errors_test.go new file mode 100644 index 000000000000..addb128715a1 --- /dev/null +++ b/client/proofs/errors_test.go @@ -0,0 +1,18 @@ +package proofs + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestErrorNoData(t *testing.T) { + e1 := ErrNoData() + e1.Error() + assert.True(t, IsNoDataErr(e1)) + + e2 := errors.New("foobar") + assert.False(t, IsNoDataErr(e2)) + assert.False(t, IsNoDataErr(nil)) +} diff --git a/client/proofs/interface.go b/client/proofs/interface.go new file mode 100644 index 000000000000..7585fd39b58e --- /dev/null +++ b/client/proofs/interface.go @@ -0,0 +1,28 @@ +package proofs + +import "github.com/tendermint/tendermint/certifiers" + +// Prover is anything that can provide proofs. +// Such as a AppProver (for merkle proofs of app state) +// or TxProver (for merkle proofs that a tx is in a block) +type Prover interface { + // Get returns the key for the given block height + // The prover should accept h=0 for latest height + Get(key []byte, h uint64) (Proof, error) + Unmarshal([]byte) (Proof, error) +} + +// Proof is a generic interface for data along with the cryptographic proof +// of it's validity, tied to a checkpoint. +// +// Every implementation should offer some method to recover the data itself +// that was proven (like k-v pair, tx bytes, etc....) +type Proof interface { + BlockHeight() uint64 + // Validates this Proof matches the checkpoint + Validate(certifiers.Commit) error + // Marshal prepares for storage + Marshal() ([]byte, error) + // Data extracts the query result we want to see + Data() []byte +} diff --git a/client/proofs/main_test.go b/client/proofs/main_test.go new file mode 100644 index 000000000000..24b37b1edc27 --- /dev/null +++ b/client/proofs/main_test.go @@ -0,0 +1,38 @@ +package proofs_test + +import ( + "os" + "testing" + + "github.com/tendermint/abci/example/dummy" + cmn "github.com/tendermint/tmlibs/common" + + nm "github.com/tendermint/tendermint/node" + "github.com/tendermint/tendermint/rpc/client" + rpctest "github.com/tendermint/tendermint/rpc/test" +) + +var node *nm.Node + +func getLocalClient() client.Local { + return client.NewLocal(node) +} + +func TestMain(m *testing.M) { + // start a tendermint node (and merkleeyes) in the background to test against + app := dummy.NewDummyApplication() + node = rpctest.StartTendermint(app) + code := m.Run() + + // and shut down proper at the end + node.Stop() + node.Wait() + os.Exit(code) +} + +func MakeTxKV() ([]byte, []byte, []byte) { + k := cmn.RandStr(8) + v := cmn.RandStr(8) + tx := k + "=" + v + return []byte(k), []byte(v), []byte(tx) +} diff --git a/client/proofs/presenters.go b/client/proofs/presenters.go new file mode 100644 index 000000000000..a7408811a50c --- /dev/null +++ b/client/proofs/presenters.go @@ -0,0 +1,89 @@ +package proofs + +import ( + "encoding/hex" + + "github.com/pkg/errors" + + data "github.com/tendermint/go-wire/data" + cmn "github.com/tendermint/tmlibs/common" +) + +const Raw = "raw" + +// Presenter allows us to encode queries and parse results in an app-specific way +type Presenter interface { + MakeKey(string) ([]byte, error) + ParseData([]byte) (interface{}, error) +} + +type Presenters map[string]Presenter + +// NewPresenters gives you a default raw presenter +func NewPresenters() Presenters { + return Presenters{} +} + +// Lookup tries to find a registered presenter, or the raw presenter +func (p Presenters) Lookup(app string) (Presenter, error) { + if app == Raw { + return RawPresenter{}, nil + } + res, ok := p[app] + if !ok { + return nil, errors.Errorf("No presenter registered for %s", app) + } + return res, nil +} + +// Register adds this app to the lookup table to parse it +func (p Presenters) Register(app string, pres Presenter) { + p[app] = pres +} + +// BruteForce will try all regitered parsers in random order, +// before calling RawPresenter. Use if we have no idea how to +// interpret the data (eg. decoding all tx in a block) +func (p Presenters) BruteForce(raw []byte) (interface{}, error) { + for _, pr := range p { + res, err := pr.ParseData(raw) + if err == nil { + return res, err + } + } + // no luck with any of them...just go raw + return RawPresenter{}.ParseData(raw) +} + +var _ Presenter = RawPresenter{} + +// RawPresenter just hex-encodes/decodes text. Useful as default, +// or to embed in other structs for the MakeKey implementation +// +// If you set a prefix, it will be prepended to all your data +// after hex-decoding them +type RawPresenter struct { + KeyMaker +} + +// ParseData on the raw-presenter, just provides a hex-encoding of the bytes +func (p RawPresenter) ParseData(raw []byte) (interface{}, error) { + return data.Bytes(raw), nil +} + +// KeyMaker can be embedded for a basic and flexible key encoder +type KeyMaker struct { + Prefix []byte +} + +func (k KeyMaker) MakeKey(str string) ([]byte, error) { + r, err := hex.DecodeString(cmn.StripHex(str)) + if err == nil && len(k.Prefix) > 0 { + r = append(k.Prefix, r...) + } + return r, errors.WithStack(err) +} + +func ParseHexKey(str string) ([]byte, error) { + return KeyMaker{}.MakeKey(str) +} diff --git a/client/proofs/tx.go b/client/proofs/tx.go new file mode 100644 index 000000000000..200c7ac4c5ce --- /dev/null +++ b/client/proofs/tx.go @@ -0,0 +1,81 @@ +package proofs + +import ( + "github.com/pkg/errors" + + wire "github.com/tendermint/go-wire" + + "github.com/tendermint/tendermint/rpc/client" + "github.com/tendermint/tendermint/types" + + "github.com/tendermint/tendermint/certifiers" + certerr "github.com/tendermint/tendermint/certifiers/errors" +) + +var _ Prover = TxProver{} +var _ Proof = TxProof{} + +// we store up to 10MB as a proof, as we need an entire block! right now +const txLimit = 10 * 1000 * 1000 + +// TxProver provides positive proofs of key-value pairs in the abciapp. +// +// TODO: also support negative proofs (this key is not set) +type TxProver struct { + node client.Client +} + +func NewTxProver(node client.Client) TxProver { + return TxProver{node: node} +} + +// Get tries to download a merkle hash for app state on this key from +// the tendermint node. +// +// Important: key must be Tx.Hash() +// Height is completely ignored for now :( +func (t TxProver) Get(key []byte, h uint64) (Proof, error) { + res, err := t.node.Tx(key, true) + if err != nil { + return nil, err + } + + // and build a proof for lighter storage + proof := TxProof{ + Height: uint64(res.Height), + Proof: res.Proof, + } + return proof, err +} + +func (t TxProver) Unmarshal(data []byte) (pr Proof, err error) { + var proof TxProof + err = errors.WithStack(wire.ReadBinaryBytes(data, &proof)) + return proof, err +} + +// TxProof checks ALL txs for one block... we need a better way! +type TxProof struct { + Height uint64 + Proof types.TxProof +} + +func (p TxProof) Data() []byte { + return p.Proof.Data +} + +func (p TxProof) BlockHeight() uint64 { + return p.Height +} + +func (p TxProof) Validate(check certifiers.Commit) error { + if uint64(check.Height()) != p.Height { + return certerr.ErrHeightMismatch(int(p.Height), check.Height()) + } + return p.Proof.Validate(check.Header.DataHash) +} + +func (p TxProof) Marshal() ([]byte, error) { + data := wire.BinaryBytes(p) + return data, nil +} diff --git a/client/proofs/tx_test.go b/client/proofs/tx_test.go new file mode 100644 index 000000000000..bb28c5207595 --- /dev/null +++ b/client/proofs/tx_test.go @@ -0,0 +1,66 @@ +package proofs_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/cosmos/cosmos-sdk/client/proofs" + "github.com/tendermint/tendermint/types" +) + +func TestTxProofs(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + cl := getLocalClient() + prover := proofs.NewTxProver(cl) + time.Sleep(200 * time.Millisecond) + + precheck := getCurrentCheck(t, cl) + + // great, let's store some data here, and make more checks.... + _, _, btx := MakeTxKV() + tx := types.Tx(btx) + br, err := cl.BroadcastTxCommit(tx) + require.Nil(err, "%+v", err) + require.EqualValues(0, br.CheckTx.Code) + require.EqualValues(0, br.DeliverTx.Code) + h := br.Height + + // let's get a proof for our tx + pr, err := prover.Get(tx.Hash(), uint64(h)) + require.Nil(err, "%+v", err) + + // it should also work for 0 height (using indexer) + pr2, err := prover.Get(tx.Hash(), 0) + require.Nil(err, "%+v", err) + require.Equal(pr, pr2) + + // make sure bad queries return errors + _, err = prover.Get([]byte("no-such-tx"), uint64(h)) + require.NotNil(err) + _, err = prover.Get(tx, uint64(h+1)) + require.NotNil(err) + + // matches and validates with post-tx header + check := getCheckForHeight(t, cl, h) + err = pr.Validate(check) + assert.Nil(err, "%+v", err) + + // doesn't matches with pre-tx header + err = pr.Validate(precheck) + assert.NotNil(err) + + // make sure it has the values we want + txpr, ok := pr.(proofs.TxProof) + if assert.True(ok) { + assert.EqualValues(tx, txpr.Data()) + } + + // make sure we read/write properly, and any changes to the serialized + // object are invalid proof (2000 random attempts) + + // TODO: iavl panics, fix that + // testSerialization(t, prover, pr, check, 2000) +} diff --git a/client/proofs/wrapper.go b/client/proofs/wrapper.go new file mode 100644 index 000000000000..ea5a6653f5a3 --- /dev/null +++ b/client/proofs/wrapper.go @@ -0,0 +1,204 @@ +package proofs + +import ( + "fmt" + + "github.com/tendermint/go-wire/data" + "github.com/tendermint/tmlibs/events" + + "github.com/tendermint/tendermint/certifiers" + "github.com/tendermint/tendermint/certifiers/client" + rpcclient "github.com/tendermint/tendermint/rpc/client" + ctypes "github.com/tendermint/tendermint/rpc/core/types" + "github.com/tendermint/tendermint/types" +) + +var _ rpcclient.Client = Wrapper{} + +type Wrapper struct { + rpcclient.Client + cert *certifiers.Inquiring +} + +func Wrap(c rpcclient.Client, cert *certifiers.Inquiring) Wrapper { + wrap := Wrapper{c, cert} + // if we wrap http client, then we can swap out the event switch to filter + if hc, ok := c.(*rpcclient.HTTP); ok { + evt := hc.WSEvents.EventSwitch + hc.WSEvents.EventSwitch = WrappedSwitch{evt, wrap} + } + return wrap +} + +func (w Wrapper) ABCIQueryWithOptions(path string, data data.Bytes, opts rpcclient.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { + r, err := w.Client.ABCIQuery(path, data) + if opts.Trusted || err != nil { + return r, err + } + + return w.proveQuery(r) +} + +func (w Wrapper) ABCIQuery(path string, data data.Bytes) (*ctypes.ResultABCIQuery, error) { + // default always with proof + r, err := w.Client.ABCIQuery(path, data) + if err != nil { + return nil, err + } + + return w.proveQuery(r) +} + +func (w Wrapper) proveQuery(r *ctypes.ResultABCIQuery) (*ctypes.ResultABCIQuery, error) { + // get a verified commit to validate from + h := int(r.Height) + c, err := w.Commit(&h) + if err != nil { + return nil, err + } + // make sure the checkpoint and proof match up + check := client.CommitFromResult(c) + // verify query + proof := AppProof{ + Height: r.Height, + Key: r.Key, + Value: r.Value, + Proof: r.Proof, + } + err = proof.Validate(check) + return r, err +} + +func (w Wrapper) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) { + r, err := w.Client.Tx(hash, prove) + if !prove || err != nil { + return r, err + } + // get a verified commit to validate from + c, err := w.Commit(&r.Height) + if err != nil { + return nil, err + } + // make sure the checkpoint and proof match up + check := client.CommitFromResult(c) + // verify tx + proof := TxProof{ + Height: uint64(r.Height), + Proof: r.Proof, + } + err = proof.Validate(check) + return r, err +} + +func (w Wrapper) BlockchainInfo(minHeight, maxHeight int) (*ctypes.ResultBlockchainInfo, error) { + r, err := w.Client.BlockchainInfo(minHeight, maxHeight) + if err != nil { + return nil, err + } + + // go and verify every blockmeta in the result.... + for _, meta := range r.BlockMetas { + // get a checkpoint to verify from + c, err := w.Commit(&meta.Header.Height) + if err != nil { + return nil, err + } + check := client.CommitFromResult(c) + err = ValidateBlockMeta(meta, check) + if err != nil { + return nil, err + } + } + + return r, nil +} + +func (w Wrapper) Block(height *int) (*ctypes.ResultBlock, error) { + r, err := w.Client.Block(height) + if err != nil { + return nil, err + } + // get a checkpoint to verify from + c, err := w.Commit(height) + if err != nil { + return nil, err + } + check := client.CommitFromResult(c) + + // now verify + err = ValidateBlockMeta(r.BlockMeta, check) + if err != nil { + return nil, err + } + err = ValidateBlock(r.Block, check) + if err != nil { + return nil, err + } + return r, nil +} + +// Commit downloads the Commit and certifies it with the certifiers. +// +// This is the foundation for all other verification in this module +func (w Wrapper) Commit(height *int) (*ctypes.ResultCommit, error) { + rpcclient.WaitForHeight(w.Client, *height, nil) + r, err := w.Client.Commit(height) + // if we got it, then certify it + if err == nil { + check := client.CommitFromResult(r) + err = w.cert.Certify(check) + } + return r, err +} + +type WrappedSwitch struct { + types.EventSwitch + client rpcclient.Client +} + +func (s WrappedSwitch) FireEvent(event string, data events.EventData) { + tm, ok := data.(types.TMEventData) + if !ok { + fmt.Printf("bad type %#v\n", data) + return + } + + // check to validate it if possible, and drop if not valid + switch t := tm.Unwrap().(type) { + case types.EventDataNewBlockHeader: + err := verifyHeader(s.client, t.Header) + if err != nil { + fmt.Printf("Invalid header: %#v\n", err) + return + } + case types.EventDataNewBlock: + err := verifyBlock(s.client, t.Block) + if err != nil { + fmt.Printf("Invalid block: %#v\n", err) + return + } + } + + // looks good, we fire it + s.EventSwitch.FireEvent(event, data) +} + +func verifyHeader(c rpcclient.Client, head *types.Header) error { + // get a checkpoint to verify from + commit, err := c.Commit(&head.Height) + if err != nil { + return err + } + check := client.CommitFromResult(commit) + return ValidateHeader(head, check) +} + +func verifyBlock(c rpcclient.Client, block *types.Block) error { + // get a checkpoint to verify from + commit, err := c.Commit(&block.Height) + if err != nil { + return err + } + check := client.CommitFromResult(commit) + return ValidateBlock(block, check) +} diff --git a/client/query.go b/client/query.go index 27606289bce2..20ce9af31656 100644 --- a/client/query.go +++ b/client/query.go @@ -6,9 +6,10 @@ import ( "github.com/tendermint/go-wire/data" "github.com/tendermint/iavl" "github.com/tendermint/tendermint/certifiers" + "github.com/tendermint/tendermint/certifiers/client" certerr "github.com/tendermint/tendermint/certifiers/errors" - "github.com/tendermint/tendermint/rpc/client" + rpcclient "github.com/tendermint/tendermint/rpc/client" ) // GetWithProof will query the key on the given node, and verify it has @@ -17,7 +18,7 @@ import ( // If there is any error in checking, returns an error. // If val is non-empty, proof should be KeyExistsProof // If val is empty, proof should be KeyMissingProof -func GetWithProof(key []byte, reqHeight int, node client.Client, +func GetWithProof(key []byte, reqHeight int, node rpcclient.Client, cert certifiers.Certifier) ( val data.Bytes, height uint64, proof iavl.KeyProof, err error) { @@ -27,7 +28,7 @@ func GetWithProof(key []byte, reqHeight int, node client.Client, } resp, err := node.ABCIQueryWithOptions("/key", key, - client.ABCIQueryOptions{Height: uint64(reqHeight)}) + rpcclient.ABCIQueryOptions{Height: uint64(reqHeight)}) if err != nil { return } @@ -47,7 +48,7 @@ func GetWithProof(key []byte, reqHeight int, node client.Client, } // AppHash for height H is in header H+1 - var commit *certifiers.Commit + var commit certifiers.Commit commit, err = GetCertifiedCommit(int(resp.Height+1), node, cert) if err != nil { return @@ -94,18 +95,18 @@ func GetWithProof(key []byte, reqHeight int, node client.Client, // GetCertifiedCommit gets the signed header for a given height // and certifies it. Returns error if unable to get a proven header. -func GetCertifiedCommit(h int, node client.Client, - cert certifiers.Certifier) (empty *certifiers.Commit, err error) { +func GetCertifiedCommit(h int, node rpcclient.Client, + cert certifiers.Certifier) (empty certifiers.Commit, 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) + rpcclient.WaitForHeight(node, h, nil) cresp, err := node.Commit(&h) if err != nil { return } - commit := certifiers.CommitFromResult(cresp) + commit := client.CommitFromResult(cresp) // validate downloaded checkpoint with our request and trust store. if commit.Height() != h { From a9de936d2950ee8bd4da10a728268bd3928d0872 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 26 Oct 2017 16:26:00 +0200 Subject: [PATCH 3/9] Update deps, no more light-client --- glide.lock | 18 ++++++------------ glide.yaml | 10 +++------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/glide.lock b/glide.lock index 0dab715f7776..f25122a5b7a2 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: fbfdd03c0367bb0785ceb81ed34059df219e55d5a9c71c12597e505fbce14165 -updated: 2017-10-26T15:57:25.483144016+02:00 +hash: 809845bf4c18a4abc85cf5d7b5ee7a17e0f437cd377b22e158787c718272261a +updated: 2017-10-26T16:18:37.833408343+02:00 imports: - name: github.com/bgentry/speakeasy version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd @@ -140,18 +140,14 @@ imports: - data/base58 - name: github.com/tendermint/iavl version: 595f3dcd5b6cd4a292e90757ae6d367fd7a6e653 -- name: github.com/tendermint/light-client - version: 76313d625e662ed7b284d066d68ff71edd7a9fac +- name: github.com/tendermint/tendermint + version: bb6c15b00a07e2aafc7fe245b3acfb33b9c25abe subpackages: + - blockchain - certifiers - certifiers/client - certifiers/errors - certifiers/files - - proofs -- name: github.com/tendermint/tendermint - version: bb6c15b00a07e2aafc7fe245b3acfb33b9c25abe - subpackages: - - blockchain - cmd/tendermint/commands - config - consensus @@ -190,6 +186,7 @@ imports: - log - logger - merkle + - test - name: golang.org/x/crypto version: edd5e9b0879d13ee6970a50153d85b8fec9f7686 subpackages: @@ -231,7 +228,6 @@ imports: version: f7bf885db0b7479a537ec317c6e48ce53145f3db subpackages: - balancer - - balancer/roundrobin - codes - connectivity - credentials @@ -243,8 +239,6 @@ imports: - naming - peer - resolver - - resolver/dns - - resolver/passthrough - stats - status - tap diff --git a/glide.yaml b/glide.yaml index 258c028e44f7..97630213545a 100644 --- a/glide.yaml +++ b/glide.yaml @@ -19,18 +19,14 @@ import: version: develop subpackages: - data -- package: github.com/tendermint/light-client - version: develop - subpackages: - - proofs - - certifiers - - certifiers/client - - certifiers/files - package: github.com/tendermint/iavl version: develop - package: github.com/tendermint/tendermint version: develop subpackages: + - certifiers + - certifiers/client + - certifiers/files - config - node - proxy From f41bf63c9c0697ca53deb9a85dc0407bb5c5052c Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 26 Oct 2017 18:54:50 +0200 Subject: [PATCH 4/9] noop cleanup --- client/errors_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/client/errors_test.go b/client/errors_test.go index c561c35b7af8..11a846a6de08 100644 --- a/client/errors_test.go +++ b/client/errors_test.go @@ -9,7 +9,6 @@ import ( func TestErrorNoData(t *testing.T) { e1 := ErrNoData() - e1.Error() assert.True(t, IsNoDataErr(e1)) e2 := errors.New("foobar") From 68c7ed8798a03d7874659c87dfb93f867f546d22 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 26 Oct 2017 19:39:32 +0200 Subject: [PATCH 5/9] Refactored query, use it in proxy --- client/common.go | 8 ---- client/proofs/errors_test.go | 1 - client/proofs/presenters.go | 89 ------------------------------------ client/proofs/terminal.glue | 1 + client/proofs/wrapper.go | 59 ++++++++---------------- client/query.go | 77 ++++++++++++++++--------------- 6 files changed, 58 insertions(+), 177 deletions(-) delete mode 100644 client/proofs/presenters.go create mode 100644 client/proofs/terminal.glue diff --git a/client/common.go b/client/common.go index ea80cc11b0e1..17ff9a1ba1d3 100644 --- a/client/common.go +++ b/client/common.go @@ -8,8 +8,6 @@ import ( certerr "github.com/tendermint/tendermint/certifiers/errors" "github.com/tendermint/tendermint/certifiers/files" - "github.com/cosmos/cosmos-sdk/client/proofs" - rpcclient "github.com/tendermint/tendermint/rpc/client" ) @@ -49,9 +47,3 @@ func GetCertifier(chainID string, trust certifiers.Provider, cert := certifiers.NewInquiring(chainID, fc, trust, source) return cert, nil } - -// SecureClient uses a given certifier to wrap an connection to an untrusted -// host and return a cryptographically secure rpc client. -func SecureClient(c rpcclient.Client, cert *certifiers.Inquiring) rpcclient.Client { - return proofs.Wrap(c, cert) -} diff --git a/client/proofs/errors_test.go b/client/proofs/errors_test.go index addb128715a1..88d6a39e5832 100644 --- a/client/proofs/errors_test.go +++ b/client/proofs/errors_test.go @@ -9,7 +9,6 @@ import ( func TestErrorNoData(t *testing.T) { e1 := ErrNoData() - e1.Error() assert.True(t, IsNoDataErr(e1)) e2 := errors.New("foobar") diff --git a/client/proofs/presenters.go b/client/proofs/presenters.go deleted file mode 100644 index a7408811a50c..000000000000 --- a/client/proofs/presenters.go +++ /dev/null @@ -1,89 +0,0 @@ -package proofs - -import ( - "encoding/hex" - - "github.com/pkg/errors" - - data "github.com/tendermint/go-wire/data" - cmn "github.com/tendermint/tmlibs/common" -) - -const Raw = "raw" - -// Presenter allows us to encode queries and parse results in an app-specific way -type Presenter interface { - MakeKey(string) ([]byte, error) - ParseData([]byte) (interface{}, error) -} - -type Presenters map[string]Presenter - -// NewPresenters gives you a default raw presenter -func NewPresenters() Presenters { - return Presenters{} -} - -// Lookup tries to find a registered presenter, or the raw presenter -func (p Presenters) Lookup(app string) (Presenter, error) { - if app == Raw { - return RawPresenter{}, nil - } - res, ok := p[app] - if !ok { - return nil, errors.Errorf("No presenter registered for %s", app) - } - return res, nil -} - -// Register adds this app to the lookup table to parse it -func (p Presenters) Register(app string, pres Presenter) { - p[app] = pres -} - -// BruteForce will try all regitered parsers in random order, -// before calling RawPresenter. Use if we have no idea how to -// interpret the data (eg. decoding all tx in a block) -func (p Presenters) BruteForce(raw []byte) (interface{}, error) { - for _, pr := range p { - res, err := pr.ParseData(raw) - if err == nil { - return res, err - } - } - // no luck with any of them...just go raw - return RawPresenter{}.ParseData(raw) -} - -var _ Presenter = RawPresenter{} - -// RawPresenter just hex-encodes/decodes text. Useful as default, -// or to embed in other structs for the MakeKey implementation -// -// If you set a prefix, it will be prepended to all your data -// after hex-decoding them -type RawPresenter struct { - KeyMaker -} - -// ParseData on the raw-presenter, just provides a hex-encoding of the bytes -func (p RawPresenter) ParseData(raw []byte) (interface{}, error) { - return data.Bytes(raw), nil -} - -// KeyMaker can be embedded for a basic and flexible key encoder -type KeyMaker struct { - Prefix []byte -} - -func (k KeyMaker) MakeKey(str string) ([]byte, error) { - r, err := hex.DecodeString(cmn.StripHex(str)) - if err == nil && len(k.Prefix) > 0 { - r = append(k.Prefix, r...) - } - return r, errors.WithStack(err) -} - -func ParseHexKey(str string) ([]byte, error) { - return KeyMaker{}.MakeKey(str) -} diff --git a/client/proofs/terminal.glue b/client/proofs/terminal.glue new file mode 100644 index 000000000000..0519ecba6ea9 --- /dev/null +++ b/client/proofs/terminal.glue @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/proofs/wrapper.go b/client/proofs/wrapper.go index ea5a6653f5a3..51c1a02ad0d7 100644 --- a/client/proofs/wrapper.go +++ b/client/proofs/wrapper.go @@ -7,10 +7,12 @@ import ( "github.com/tendermint/tmlibs/events" "github.com/tendermint/tendermint/certifiers" - "github.com/tendermint/tendermint/certifiers/client" + certclient "github.com/tendermint/tendermint/certifiers/client" rpcclient "github.com/tendermint/tendermint/rpc/client" ctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/tendermint/tendermint/types" + + "github.com/cosmos/cosmos-sdk/client" ) var _ rpcclient.Client = Wrapper{} @@ -20,7 +22,9 @@ type Wrapper struct { cert *certifiers.Inquiring } -func Wrap(c rpcclient.Client, cert *certifiers.Inquiring) Wrapper { +// SecureClient uses a given certifier to wrap an connection to an untrusted +// host and return a cryptographically secure rpc client. +func SecureClient(c rpcclient.Client, cert *certifiers.Inquiring) Wrapper { wrap := Wrapper{c, cert} // if we wrap http client, then we can swap out the event switch to filter if hc, ok := c.(*rpcclient.HTTP); ok { @@ -31,42 +35,12 @@ func Wrap(c rpcclient.Client, cert *certifiers.Inquiring) Wrapper { } func (w Wrapper) ABCIQueryWithOptions(path string, data data.Bytes, opts rpcclient.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { - r, err := w.Client.ABCIQuery(path, data) - if opts.Trusted || err != nil { - return r, err - } - - return w.proveQuery(r) + res, _, err := client.GetWithProofOptions(path, data, opts, w.Client, w.cert) + return res, err } func (w Wrapper) ABCIQuery(path string, data data.Bytes) (*ctypes.ResultABCIQuery, error) { - // default always with proof - r, err := w.Client.ABCIQuery(path, data) - if err != nil { - return nil, err - } - - return w.proveQuery(r) -} - -func (w Wrapper) proveQuery(r *ctypes.ResultABCIQuery) (*ctypes.ResultABCIQuery, error) { - // get a verified commit to validate from - h := int(r.Height) - c, err := w.Commit(&h) - if err != nil { - return nil, err - } - // make sure the checkpoint and proof match up - check := client.CommitFromResult(c) - // verify query - proof := AppProof{ - Height: r.Height, - Key: r.Key, - Value: r.Value, - Proof: r.Proof, - } - err = proof.Validate(check) - return r, err + return w.ABCIQueryWithOptions(path, data, rpcclient.DefaultABCIQueryOptions) } func (w Wrapper) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) { @@ -80,7 +54,9 @@ func (w Wrapper) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) { return nil, err } // make sure the checkpoint and proof match up - check := client.CommitFromResult(c) + check := certclient.CommitFromResult(c) + + // TODO: 2 // verify tx proof := TxProof{ Height: uint64(r.Height), @@ -103,7 +79,8 @@ func (w Wrapper) BlockchainInfo(minHeight, maxHeight int) (*ctypes.ResultBlockch if err != nil { return nil, err } - check := client.CommitFromResult(c) + check := certclient.CommitFromResult(c) + // TODO: 3 err = ValidateBlockMeta(meta, check) if err != nil { return nil, err @@ -123,7 +100,7 @@ func (w Wrapper) Block(height *int) (*ctypes.ResultBlock, error) { if err != nil { return nil, err } - check := client.CommitFromResult(c) + check := certclient.CommitFromResult(c) // now verify err = ValidateBlockMeta(r.BlockMeta, check) @@ -145,7 +122,7 @@ func (w Wrapper) Commit(height *int) (*ctypes.ResultCommit, error) { r, err := w.Client.Commit(height) // if we got it, then certify it if err == nil { - check := client.CommitFromResult(r) + check := certclient.CommitFromResult(r) err = w.cert.Certify(check) } return r, err @@ -189,7 +166,7 @@ func verifyHeader(c rpcclient.Client, head *types.Header) error { if err != nil { return err } - check := client.CommitFromResult(commit) + check := certclient.CommitFromResult(commit) return ValidateHeader(head, check) } @@ -199,6 +176,6 @@ func verifyBlock(c rpcclient.Client, block *types.Block) error { if err != nil { return err } - check := client.CommitFromResult(commit) + check := certclient.CommitFromResult(commit) return ValidateBlock(block, check) } diff --git a/client/query.go b/client/query.go index 20ce9af31656..6a0c3469a90a 100644 --- a/client/query.go +++ b/client/query.go @@ -5,11 +5,12 @@ import ( "github.com/tendermint/go-wire/data" "github.com/tendermint/iavl" + "github.com/tendermint/tendermint/certifiers" "github.com/tendermint/tendermint/certifiers/client" certerr "github.com/tendermint/tendermint/certifiers/errors" - rpcclient "github.com/tendermint/tendermint/rpc/client" + ctypes "github.com/tendermint/tendermint/rpc/core/types" ) // GetWithProof will query the key on the given node, and verify it has @@ -27,70 +28,70 @@ func GetWithProof(key []byte, reqHeight int, node rpcclient.Client, return } - resp, err := node.ABCIQueryWithOptions("/key", key, - rpcclient.ABCIQueryOptions{Height: uint64(reqHeight)}) + resp, proof, err := GetWithProofOptions("/key", key, + rpcclient.ABCIQueryOptions{Height: uint64(reqHeight)}, + node, cert) + if resp != nil { + val, height = resp.Value, resp.Height + } + return val, height, proof, err +} + +// GetWithProofOptions is useful if you want full access to the ABCIQueryOptions +func GetWithProofOptions(path string, key []byte, opts rpcclient.ABCIQueryOptions, + node rpcclient.Client, cert certifiers.Certifier) ( + *ctypes.ResultABCIQuery, iavl.KeyProof, error) { + + resp, err := node.ABCIQueryWithOptions(path, key, opts) if err != nil { - return + return nil, nil, err } // 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 + return nil, nil, err } if len(resp.Key) == 0 || len(resp.Proof) == 0 { - err = ErrNoData() - return + return nil, nil, ErrNoData() } if resp.Height == 0 { - err = errors.New("Height returned is zero") - return + return nil, nil, errors.New("Height returned is zero") } // AppHash for height H is in header H+1 - var commit certifiers.Commit - commit, err = GetCertifiedCommit(int(resp.Height+1), node, cert) + commit, err := GetCertifiedCommit(int(resp.Height+1), node, cert) if err != nil { - return + return nil, nil, err } if len(resp.Value) > 0 { // The key was found, construct a proof of existence. - var eproof *iavl.KeyExistsProof - eproof, err = iavl.ReadKeyExistsProof(resp.Proof) + eproof, err := iavl.ReadKeyExistsProof(resp.Proof) if err != nil { - err = errors.Wrap(err, "Error reading proof") - return + return nil, nil, errors.Wrap(err, "Error reading proof") } // Validate the proof against the certified header to ensure data integrity. err = eproof.Verify(resp.Key, resp.Value, commit.Header.AppHash) if err != nil { - err = errors.Wrap(err, "Couldn't verify proof") - return - } - val = data.Bytes(resp.Value) - proof = eproof - } else { - // The key wasn't found, construct a proof of non-existence. - var aproof *iavl.KeyAbsentProof - aproof, err = iavl.ReadKeyAbsentProof(resp.Proof) - if err != nil { - err = errors.Wrap(err, "Error reading proof") - return - } - // Validate the proof against the certified header to ensure data integrity. - err = aproof.Verify(resp.Key, nil, commit.Header.AppHash) - if err != nil { - err = errors.Wrap(err, "Couldn't verify proof") - return + return nil, nil, errors.Wrap(err, "Couldn't verify proof") } - err = ErrNoData() - proof = aproof + return resp, eproof, nil } - height = resp.Height - return + // The key wasn't found, construct a proof of non-existence. + var aproof *iavl.KeyAbsentProof + aproof, err = iavl.ReadKeyAbsentProof(resp.Proof) + if err != nil { + return nil, nil, errors.Wrap(err, "Error reading proof") + } + // Validate the proof against the certified header to ensure data integrity. + err = aproof.Verify(resp.Key, nil, commit.Header.AppHash) + if err != nil { + return nil, nil, errors.Wrap(err, "Couldn't verify proof") + } + return resp, aproof, ErrNoData() } // GetCertifiedCommit gets the signed header for a given height From dcb1f468d9d8af9210552544c5673d9207a71612 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 26 Oct 2017 19:43:17 +0200 Subject: [PATCH 6/9] Replace custom tx verification in proxy --- client/proofs/app.go | 109 ----------------------------------- client/proofs/app_test.go | 116 -------------------------------------- client/proofs/tx.go | 81 -------------------------- client/proofs/tx_test.go | 66 ---------------------- client/proofs/wrapper.go | 22 ++------ 5 files changed, 6 insertions(+), 388 deletions(-) delete mode 100644 client/proofs/app.go delete mode 100644 client/proofs/app_test.go delete mode 100644 client/proofs/tx.go delete mode 100644 client/proofs/tx_test.go diff --git a/client/proofs/app.go b/client/proofs/app.go deleted file mode 100644 index 58d762962674..000000000000 --- a/client/proofs/app.go +++ /dev/null @@ -1,109 +0,0 @@ -package proofs - -import ( - "github.com/pkg/errors" - - wire "github.com/tendermint/go-wire" - data "github.com/tendermint/go-wire/data" - "github.com/tendermint/iavl" - - "github.com/tendermint/tendermint/rpc/client" - - "github.com/tendermint/tendermint/certifiers" - certerr "github.com/tendermint/tendermint/certifiers/errors" -) - -var _ Prover = AppProver{} -var _ Proof = AppProof{} - -// we limit proofs to 1MB to stop overflow attacks -const appLimit = 1000 * 1000 - -// AppProver provides positive proofs of key-value pairs in the abciapp. -// -// TODO: also support negative proofs (this key is not set) -type AppProver struct { - node client.Client -} - -func NewAppProver(node client.Client) AppProver { - return AppProver{node: node} -} - -// Get tries to download a merkle hash for app state on this key from -// the tendermint node. -func (a AppProver) Get(key []byte, h uint64) (Proof, error) { - resp, err := a.node.ABCIQuery("/key", key) - if err != nil { - return nil, err - } - - // make sure the proof is the proper height - if !resp.Code.IsOK() { - return nil, errors.Errorf("Query error %d: %s", resp.Code, resp.Code.String()) - } - if len(resp.Key) == 0 || len(resp.Value) == 0 || len(resp.Proof) == 0 { - return nil, ErrNoData() - } - if resp.Height == 0 { - resp.Height = h - } - if h != 0 && h != resp.Height { - return nil, certerr.ErrHeightMismatch(int(h), int(resp.Height)) - } - proof := AppProof{ - Height: resp.Height, - Key: resp.Key, - Value: resp.Value, - Proof: resp.Proof, - } - return proof, nil -} - -func (a AppProver) Unmarshal(data []byte) (Proof, error) { - var proof AppProof - err := errors.WithStack(wire.ReadBinaryBytes(data, &proof)) - return proof, err -} - -// AppProof containts a key-value pair at a given height. -// It also contains the merkle proof from that key-value pair to the root hash, -// which can be verified against a signed header. -type AppProof struct { - Height uint64 - Key data.Bytes - Value data.Bytes - Proof data.Bytes -} - -func (p AppProof) Data() []byte { - return p.Value -} - -func (p AppProof) BlockHeight() uint64 { - return p.Height -} - -func (p AppProof) Validate(check certifiers.Commit) error { - if uint64(check.Height()) != p.Height { - return certerr.ErrHeightMismatch(int(p.Height), check.Height()) - } - - proof, err := iavl.ReadKeyExistsProof(p.Proof) - if err != nil { - return errors.WithStack(err) - } - - err = proof.Verify(p.Key, p.Value, check.Header.AppHash) - if err != nil { - return err - } - - // LGTM! - return nil -} - -func (p AppProof) Marshal() ([]byte, error) { - data := wire.BinaryBytes(p) - return data, nil -} diff --git a/client/proofs/app_test.go b/client/proofs/app_test.go deleted file mode 100644 index a32e8b2a1347..000000000000 --- a/client/proofs/app_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package proofs_test - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - ctest "github.com/tendermint/tmlibs/test" - - "github.com/tendermint/tendermint/certifiers" - certcli "github.com/tendermint/tendermint/certifiers/client" - "github.com/tendermint/tendermint/rpc/client" - - "github.com/cosmos/cosmos-sdk/client/proofs" -) - -func getCurrentCheck(t *testing.T, cl client.Client) certifiers.Commit { - stat, err := cl.Status() - require.Nil(t, err, "%+v", err) - return getCheckForHeight(t, cl, stat.LatestBlockHeight) -} - -func getCheckForHeight(t *testing.T, cl client.Client, h int) certifiers.Commit { - client.WaitForHeight(cl, h, nil) - commit, err := cl.Commit(&h) - require.Nil(t, err, "%+v", err) - return certcli.CommitFromResult(commit) -} - -func TestAppProofs(t *testing.T) { - assert, require := assert.New(t), require.New(t) - - cl := getLocalClient() - prover := proofs.NewAppProver(cl) - time.Sleep(200 * time.Millisecond) - - precheck := getCurrentCheck(t, cl) - - // great, let's store some data here, and make more checks.... - k, v, tx := MakeTxKV() - br, err := cl.BroadcastTxCommit(tx) - require.Nil(err, "%+v", err) - require.EqualValues(0, br.CheckTx.Code) - require.EqualValues(0, br.DeliverTx.Code) - h := br.Height + 1 - - // unfortunately we cannot tell the server to give us any height - // other than the most recent, so 0 is the only choice :( - pr, err := prover.Get(k, uint64(h)) - require.Nil(err, "%+v", err) - check := getCheckForHeight(t, cl, h) - - // matches and validates with post-tx header - err = pr.Validate(check) - assert.Nil(err, "%+v", err) - - // doesn't matches with pre-tx header - err = pr.Validate(precheck) - assert.NotNil(err) - - // make sure it has the values we want - apk, ok := pr.(proofs.AppProof) - if assert.True(ok) { - assert.EqualValues(k, apk.Key) - assert.EqualValues(v, apk.Value) - } - - // make sure we read/write properly, and any changes to the serialized - // object are invalid proof (2000 random attempts) - - // TODO: iavl panics, fix this - // testSerialization(t, prover, pr, check, 2000) -} - -// testSerialization makes sure the proof will un/marshal properly -// and validate with the checkpoint. It also does lots of modifications -// to the binary data and makes sure no mods validates properly -func testSerialization(t *testing.T, prover proofs.Prover, pr proofs.Proof, - check certifiers.Commit, mods int) { - - require := require.New(t) - - // first, make sure that we can serialize and deserialize - err := pr.Validate(check) - require.Nil(err, "%+v", err) - - // store the data - data, err := pr.Marshal() - require.Nil(err, "%+v", err) - - // recover the data and make sure it still checks out - npr, err := prover.Unmarshal(data) - require.Nil(err, "%+v", err) - err = npr.Validate(check) - require.Nil(err, "%#v\n%+v", npr, err) - - // now let's go mod... - for i := 0; i < mods; i++ { - bdata := ctest.MutateByteSlice(data) - bpr, err := prover.Unmarshal(bdata) - if err == nil { - assert.NotNil(t, bpr.Validate(check)) - } - } -} - -// // validate all tx in the block -// block, err := cl.Block(check.Height()) -// require.Nil(err, "%+v", err) -// err = check.CheckTxs(block.Block.Data.Txs) -// assert.Nil(err, "%+v", err) - -// oh, i would like the know the hieght of the broadcast_commit..... -// so i could verify that tx :( diff --git a/client/proofs/tx.go b/client/proofs/tx.go deleted file mode 100644 index 200c7ac4c5ce..000000000000 --- a/client/proofs/tx.go +++ /dev/null @@ -1,81 +0,0 @@ -package proofs - -import ( - "github.com/pkg/errors" - - wire "github.com/tendermint/go-wire" - - "github.com/tendermint/tendermint/rpc/client" - "github.com/tendermint/tendermint/types" - - "github.com/tendermint/tendermint/certifiers" - certerr "github.com/tendermint/tendermint/certifiers/errors" -) - -var _ Prover = TxProver{} -var _ Proof = TxProof{} - -// we store up to 10MB as a proof, as we need an entire block! right now -const txLimit = 10 * 1000 * 1000 - -// TxProver provides positive proofs of key-value pairs in the abciapp. -// -// TODO: also support negative proofs (this key is not set) -type TxProver struct { - node client.Client -} - -func NewTxProver(node client.Client) TxProver { - return TxProver{node: node} -} - -// Get tries to download a merkle hash for app state on this key from -// the tendermint node. -// -// Important: key must be Tx.Hash() -// Height is completely ignored for now :( -func (t TxProver) Get(key []byte, h uint64) (Proof, error) { - res, err := t.node.Tx(key, true) - if err != nil { - return nil, err - } - - // and build a proof for lighter storage - proof := TxProof{ - Height: uint64(res.Height), - Proof: res.Proof, - } - return proof, err -} - -func (t TxProver) Unmarshal(data []byte) (pr Proof, err error) { - var proof TxProof - err = errors.WithStack(wire.ReadBinaryBytes(data, &proof)) - return proof, err -} - -// TxProof checks ALL txs for one block... we need a better way! -type TxProof struct { - Height uint64 - Proof types.TxProof -} - -func (p TxProof) Data() []byte { - return p.Proof.Data -} - -func (p TxProof) BlockHeight() uint64 { - return p.Height -} - -func (p TxProof) Validate(check certifiers.Commit) error { - if uint64(check.Height()) != p.Height { - return certerr.ErrHeightMismatch(int(p.Height), check.Height()) - } - return p.Proof.Validate(check.Header.DataHash) -} - -func (p TxProof) Marshal() ([]byte, error) { - data := wire.BinaryBytes(p) - return data, nil -} diff --git a/client/proofs/tx_test.go b/client/proofs/tx_test.go deleted file mode 100644 index bb28c5207595..000000000000 --- a/client/proofs/tx_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package proofs_test - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/cosmos/cosmos-sdk/client/proofs" - "github.com/tendermint/tendermint/types" -) - -func TestTxProofs(t *testing.T) { - assert, require := assert.New(t), require.New(t) - - cl := getLocalClient() - prover := proofs.NewTxProver(cl) - time.Sleep(200 * time.Millisecond) - - precheck := getCurrentCheck(t, cl) - - // great, let's store some data here, and make more checks.... - _, _, btx := MakeTxKV() - tx := types.Tx(btx) - br, err := cl.BroadcastTxCommit(tx) - require.Nil(err, "%+v", err) - require.EqualValues(0, br.CheckTx.Code) - require.EqualValues(0, br.DeliverTx.Code) - h := br.Height - - // let's get a proof for our tx - pr, err := prover.Get(tx.Hash(), uint64(h)) - require.Nil(err, "%+v", err) - - // it should also work for 0 height (using indexer) - pr2, err := prover.Get(tx.Hash(), 0) - require.Nil(err, "%+v", err) - require.Equal(pr, pr2) - - // make sure bad queries return errors - _, err = prover.Get([]byte("no-such-tx"), uint64(h)) - require.NotNil(err) - _, err = prover.Get(tx, uint64(h+1)) - require.NotNil(err) - - // matches and validates with post-tx header - check := getCheckForHeight(t, cl, h) - err = pr.Validate(check) - assert.Nil(err, "%+v", err) - - // doesn't matches with pre-tx header - err = pr.Validate(precheck) - assert.NotNil(err) - - // make sure it has the values we want - txpr, ok := pr.(proofs.TxProof) - if assert.True(ok) { - assert.EqualValues(tx, txpr.Data()) - } - - // make sure we read/write properly, and any changes to the serialized - // object are invalid proof (2000 random attempts) - - // TODO: iavl panics, fix that - // testSerialization(t, prover, pr, check, 2000) -} diff --git a/client/proofs/wrapper.go b/client/proofs/wrapper.go index 51c1a02ad0d7..edc26dd525e0 100644 --- a/client/proofs/wrapper.go +++ b/client/proofs/wrapper.go @@ -44,26 +44,16 @@ func (w Wrapper) ABCIQuery(path string, data data.Bytes) (*ctypes.ResultABCIQuer } func (w Wrapper) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) { - r, err := w.Client.Tx(hash, prove) + res, err := w.Client.Tx(hash, prove) if !prove || err != nil { - return r, err + return res, err } - // get a verified commit to validate from - c, err := w.Commit(&r.Height) + check, err := client.GetCertifiedCommit(res.Height, w.Client, w.cert) if err != nil { - return nil, err - } - // make sure the checkpoint and proof match up - check := certclient.CommitFromResult(c) - - // TODO: 2 - // verify tx - proof := TxProof{ - Height: uint64(r.Height), - Proof: r.Proof, + return res, err } - err = proof.Validate(check) - return r, err + err = res.Proof.Validate(check.Header.DataHash) + return res, err } func (w Wrapper) BlockchainInfo(minHeight, maxHeight int) (*ctypes.ResultBlockchainInfo, error) { From 78b87dd5388c272618d2ed6e70b67bcba27d514f Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 26 Oct 2017 19:45:18 +0200 Subject: [PATCH 7/9] more cleanup --- client/proofs/interface.go | 28 ---------------------------- client/proofs/main_test.go | 38 -------------------------------------- 2 files changed, 66 deletions(-) delete mode 100644 client/proofs/interface.go delete mode 100644 client/proofs/main_test.go diff --git a/client/proofs/interface.go b/client/proofs/interface.go deleted file mode 100644 index 7585fd39b58e..000000000000 --- a/client/proofs/interface.go +++ /dev/null @@ -1,28 +0,0 @@ -package proofs - -import "github.com/tendermint/tendermint/certifiers" - -// Prover is anything that can provide proofs. -// Such as a AppProver (for merkle proofs of app state) -// or TxProver (for merkle proofs that a tx is in a block) -type Prover interface { - // Get returns the key for the given block height - // The prover should accept h=0 for latest height - Get(key []byte, h uint64) (Proof, error) - Unmarshal([]byte) (Proof, error) -} - -// Proof is a generic interface for data along with the cryptographic proof -// of it's validity, tied to a checkpoint. -// -// Every implementation should offer some method to recover the data itself -// that was proven (like k-v pair, tx bytes, etc....) -type Proof interface { - BlockHeight() uint64 - // Validates this Proof matches the checkpoint - Validate(certifiers.Commit) error - // Marshal prepares for storage - Marshal() ([]byte, error) - // Data extracts the query result we want to see - Data() []byte -} diff --git a/client/proofs/main_test.go b/client/proofs/main_test.go deleted file mode 100644 index 24b37b1edc27..000000000000 --- a/client/proofs/main_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package proofs_test - -import ( - "os" - "testing" - - "github.com/tendermint/abci/example/dummy" - cmn "github.com/tendermint/tmlibs/common" - - nm "github.com/tendermint/tendermint/node" - "github.com/tendermint/tendermint/rpc/client" - rpctest "github.com/tendermint/tendermint/rpc/test" -) - -var node *nm.Node - -func getLocalClient() client.Local { - return client.NewLocal(node) -} - -func TestMain(m *testing.M) { - // start a tendermint node (and merkleeyes) in the background to test against - app := dummy.NewDummyApplication() - node = rpctest.StartTendermint(app) - code := m.Run() - - // and shut down proper at the end - node.Stop() - node.Wait() - os.Exit(code) -} - -func MakeTxKV() ([]byte, []byte, []byte) { - k := cmn.RandStr(8) - v := cmn.RandStr(8) - tx := k + "=" + v - return []byte(k), []byte(v), []byte(tx) -} From d5bce4b9ec47a0711ab8124081cee89b26f32122 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 26 Oct 2017 19:53:47 +0200 Subject: [PATCH 8/9] Collapse remnants of proofs into client --- client/{proofs => }/block.go | 6 +++--- client/commands/query/get.go | 6 ++++-- client/proofs/errors.go | 22 ---------------------- client/proofs/errors_test.go | 17 ----------------- client/proofs/terminal.glue | 1 - client/{proofs => }/wrapper.go | 8 +++----- 6 files changed, 10 insertions(+), 50 deletions(-) rename client/{proofs => }/block.go (96%) delete mode 100644 client/proofs/errors.go delete mode 100644 client/proofs/errors_test.go delete mode 100644 client/proofs/terminal.glue rename client/{proofs => }/wrapper.go (95%) diff --git a/client/proofs/block.go b/client/block.go similarity index 96% rename from client/proofs/block.go rename to client/block.go index 8200f8b779b8..2bf3f39a8208 100644 --- a/client/proofs/block.go +++ b/client/block.go @@ -1,13 +1,13 @@ -package proofs +package client import ( "bytes" - "errors" - "github.com/tendermint/tendermint/types" + "github.com/pkg/errors" "github.com/tendermint/tendermint/certifiers" certerr "github.com/tendermint/tendermint/certifiers/errors" + "github.com/tendermint/tendermint/types" ) func ValidateBlockMeta(meta *types.BlockMeta, check certifiers.Commit) error { diff --git a/client/commands/query/get.go b/client/commands/query/get.go index 048241a3bea3..59c44c05f8c5 100644 --- a/client/commands/query/get.go +++ b/client/commands/query/get.go @@ -1,6 +1,7 @@ package query import ( + "encoding/hex" "fmt" "io" "os" @@ -11,7 +12,7 @@ import ( wire "github.com/tendermint/go-wire" "github.com/tendermint/go-wire/data" "github.com/tendermint/iavl" - "github.com/cosmos/cosmos-sdk/client/proofs" + cmn "github.com/tendermint/tmlibs/common" rpcclient "github.com/tendermint/tendermint/rpc/client" @@ -87,7 +88,8 @@ func ParseHexKey(args []string, argname string) ([]byte, error) { 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) + key, err := hex.DecodeString(cmn.StripHex(rawkey)) + return key, errors.WithStack(err) } // GetHeight reads the viper config for the query height diff --git a/client/proofs/errors.go b/client/proofs/errors.go deleted file mode 100644 index 8a728ca1828d..000000000000 --- a/client/proofs/errors.go +++ /dev/null @@ -1,22 +0,0 @@ -package proofs - -import ( - "fmt" - - "github.com/pkg/errors" -) - -//-------------------------------------------- - -var errNoData = fmt.Errorf("No data returned for query") - -// IsNoDataErr checks whether an error is due to a query returning empty data -func IsNoDataErr(err error) bool { - return errors.Cause(err) == errNoData -} - -func ErrNoData() error { - return errors.WithStack(errNoData) -} - -//-------------------------------------------- diff --git a/client/proofs/errors_test.go b/client/proofs/errors_test.go deleted file mode 100644 index 88d6a39e5832..000000000000 --- a/client/proofs/errors_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package proofs - -import ( - "errors" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestErrorNoData(t *testing.T) { - e1 := ErrNoData() - assert.True(t, IsNoDataErr(e1)) - - e2 := errors.New("foobar") - assert.False(t, IsNoDataErr(e2)) - assert.False(t, IsNoDataErr(nil)) -} diff --git a/client/proofs/terminal.glue b/client/proofs/terminal.glue deleted file mode 100644 index 0519ecba6ea9..000000000000 --- a/client/proofs/terminal.glue +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/client/proofs/wrapper.go b/client/wrapper.go similarity index 95% rename from client/proofs/wrapper.go rename to client/wrapper.go index edc26dd525e0..508069c77047 100644 --- a/client/proofs/wrapper.go +++ b/client/wrapper.go @@ -1,4 +1,4 @@ -package proofs +package client import ( "fmt" @@ -11,8 +11,6 @@ import ( rpcclient "github.com/tendermint/tendermint/rpc/client" ctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/tendermint/tendermint/types" - - "github.com/cosmos/cosmos-sdk/client" ) var _ rpcclient.Client = Wrapper{} @@ -35,7 +33,7 @@ func SecureClient(c rpcclient.Client, cert *certifiers.Inquiring) Wrapper { } func (w Wrapper) ABCIQueryWithOptions(path string, data data.Bytes, opts rpcclient.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { - res, _, err := client.GetWithProofOptions(path, data, opts, w.Client, w.cert) + res, _, err := GetWithProofOptions(path, data, opts, w.Client, w.cert) return res, err } @@ -48,7 +46,7 @@ func (w Wrapper) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) { if !prove || err != nil { return res, err } - check, err := client.GetCertifiedCommit(res.Height, w.Client, w.cert) + check, err := GetCertifiedCommit(res.Height, w.Client, w.cert) if err != nil { return res, err } From c514176abc084c73892362b7d1e9beea735f6899 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 26 Oct 2017 20:01:53 +0200 Subject: [PATCH 9/9] Add godoc --- client/wrapper.go | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/client/wrapper.go b/client/wrapper.go index 508069c77047..3bebd822192a 100644 --- a/client/wrapper.go +++ b/client/wrapper.go @@ -15,6 +15,8 @@ import ( var _ rpcclient.Client = Wrapper{} +// Wrapper wraps a rpcclient with a Certifier and double-checks any input that is +// provable before passing it along. Allows you to make any rpcclient fully secure. type Wrapper struct { rpcclient.Client cert *certifiers.Inquiring @@ -22,6 +24,8 @@ type Wrapper struct { // SecureClient uses a given certifier to wrap an connection to an untrusted // host and return a cryptographically secure rpc client. +// +// If it is wrapping an HTTP rpcclient, it will also wrap the websocket interface func SecureClient(c rpcclient.Client, cert *certifiers.Inquiring) Wrapper { wrap := Wrapper{c, cert} // if we wrap http client, then we can swap out the event switch to filter @@ -32,15 +36,18 @@ func SecureClient(c rpcclient.Client, cert *certifiers.Inquiring) Wrapper { return wrap } +// ABCIQueryWithOptions exposes all options for the ABCI query and verifies the returned proof func (w Wrapper) ABCIQueryWithOptions(path string, data data.Bytes, opts rpcclient.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { res, _, err := GetWithProofOptions(path, data, opts, w.Client, w.cert) return res, err } +// ABCIQuery uses default options for the ABCI query and verifies the returned proof func (w Wrapper) ABCIQuery(path string, data data.Bytes) (*ctypes.ResultABCIQuery, error) { return w.ABCIQueryWithOptions(path, data, rpcclient.DefaultABCIQueryOptions) } +// Tx queries for a given tx and verifies the proof if it was requested func (w Wrapper) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) { res, err := w.Client.Tx(hash, prove) if !prove || err != nil { @@ -54,6 +61,10 @@ func (w Wrapper) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) { return res, err } +// BlockchainInfo requests a list of headers and verifies them all... +// Rather expensive. +// +// TODO: optimize this if used for anything needing performance func (w Wrapper) BlockchainInfo(minHeight, maxHeight int) (*ctypes.ResultBlockchainInfo, error) { r, err := w.Client.BlockchainInfo(minHeight, maxHeight) if err != nil { @@ -68,7 +79,6 @@ func (w Wrapper) BlockchainInfo(minHeight, maxHeight int) (*ctypes.ResultBlockch return nil, err } check := certclient.CommitFromResult(c) - // TODO: 3 err = ValidateBlockMeta(meta, check) if err != nil { return nil, err @@ -78,6 +88,7 @@ func (w Wrapper) BlockchainInfo(minHeight, maxHeight int) (*ctypes.ResultBlockch return r, nil } +// Block returns an entire block and verifies all signatures func (w Wrapper) Block(height *int) (*ctypes.ResultBlock, error) { r, err := w.Client.Block(height) if err != nil { @@ -116,11 +127,17 @@ func (w Wrapper) Commit(height *int) (*ctypes.ResultCommit, error) { return r, err } +// WrappedSwitch creates a websocket connection that auto-verifies any info +// coming through before passing it along. +// +// Since the verification takes 1-2 rpc calls, this is obviously only for +// relatively low-throughput situations that can tolerate a bit extra latency type WrappedSwitch struct { types.EventSwitch client rpcclient.Client } +// FireEvent verifies any block or header returned from the eventswitch func (s WrappedSwitch) FireEvent(event string, data events.EventData) { tm, ok := data.(types.TMEventData) if !ok { @@ -142,6 +159,7 @@ func (s WrappedSwitch) FireEvent(event string, data events.EventData) { fmt.Printf("Invalid block: %#v\n", err) return } + // TODO: can we verify tx as well? anything else } // looks good, we fire it