diff --git a/README.md b/README.md index ea09fa7c..3a9b27cb 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,8 @@ [![CircleCI](https://circleci.com/gh/tendermint/abci.svg?style=svg)](https://circleci.com/gh/tendermint/abci) -Blockchains are a system for creating shared multi-master application state. +Blockchains are a system for creating shared multi-master application state. **ABCI** is a socket protocol enabling a blockchain consensus engine, running in one process, -to manage a blockchain application state, running in another. For more information on ABCI, motivations, and tutorials, please visit [our blog post](https://tendermint.com/blog/abci-the-tendermint-socket-protocol). @@ -18,15 +17,15 @@ Other implementations: This repository holds a number of important pieces: - `types/types.proto` - - the protobuf file defining ABCI message types, and the optional grpc interface. + - the protobuf file defining ABCI message types, and the optional grpc interface. - to build, run `make protoc` - see `protoc --help` and [the grpc docs](https://www.grpc.io/docs) for examples and details of other languages - golang implementation of ABCI client and server - two implementations: - - asynchronous, ordered message passing over unix or tcp; + - asynchronous, ordered message passing over unix or tcp; - messages are serialized using protobuf and length prefixed - - grpc + - grpc - TendermintCore runs a client, and the application runs a server - `cmd/abci-cli` @@ -77,7 +76,7 @@ ABCI requests/responses are simple Protobuf messages. Check out the [schema fil You can make CheckTx semi-stateful and clear the state upon `Commit` or `BeginBlock`, to allow for dependent sequences of transactions in the same block. -#### Commit +#### Commit * __Returns__: * `Data ([]byte)`: The Merkle root hash * `Log (string)`: Debug or error message @@ -92,6 +91,17 @@ ABCI requests/responses are simple Protobuf messages. Check out the [schema fil * `Data ([]byte)`: The query response bytes * `Log (string)`: Debug or error message +#### Proof + * __Arguments__: + * `Key ([]byte)`: The key whose data you want to verifiably query + * `Height (int64)`: The block height for which you want the proof (default=0 returns the proof for last committed block) + * __Returns__: + * `Code (uint32)`: Response code + * `Data ([]byte)`: The query response bytes + * `Log (string)`: Debug or error message + * __Usage__:
+ Return a Merkle proof from the key/value pair back to the application hash. + #### Flush * __Usage__:
Flush the response queue. Applications that implement `types.Application` need not implement this message -- it's handled by the project. diff --git a/example/dummy/dummy_test.go b/example/dummy/dummy_test.go index 8cd999c0..aa6c837d 100644 --- a/example/dummy/dummy_test.go +++ b/example/dummy/dummy_test.go @@ -6,56 +6,40 @@ import ( "sort" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + abcicli "github.com/tendermint/abci/client" + "github.com/tendermint/abci/server" + "github.com/tendermint/abci/types" . "github.com/tendermint/go-common" "github.com/tendermint/go-crypto" merkle "github.com/tendermint/go-merkle" "github.com/tendermint/go-wire" - "github.com/tendermint/abci/types" ) -func testDummy(t *testing.T, dummy types.Application, tx []byte, key, value string) { - if r := dummy.DeliverTx(tx); r.IsErr() { - t.Fatal(r) - } - if r := dummy.DeliverTx(tx); r.IsErr() { - t.Fatal(r) - } - - r := dummy.Query([]byte(key)) - if r.IsErr() { - t.Fatal(r) - } +func testDummy(t *testing.T, app types.Application, tx []byte, key, value string) { + ar := app.DeliverTx(tx) + require.False(t, ar.IsErr(), ar) + // repeating tx doesn't raise error + ar = app.DeliverTx(tx) + require.False(t, ar.IsErr(), ar) + // make sure query is fine + r := app.Query([]byte(key)) + require.False(t, r.IsErr(), r) q := new(QueryResult) - if err := wire.ReadJSONBytes(r.Data, q); err != nil { - t.Fatal(err) - } - - if q.Value != value { - t.Fatalf("Got %s, expected %s", q.Value, value) - } - - rp := dummy.Proof([]byte(key), 0) - if rp.IsErr() { - t.Fatal(rp) - } + err := wire.ReadJSONBytes(r.Data, q) + require.Nil(t, err) + require.Equal(t, value, q.Value) + // make sure proof is fine + rp := app.Proof([]byte(key), 0) + require.False(t, rp.IsErr(), rp) p, err := merkle.LoadProof(rp.Data) - if err != nil { - t.Fatal(err) - } - - if !p.Valid() { - t.Fatal("Invalid proof") - } - - if !bytes.Equal([]byte(key), p.Key()) { - t.Fatalf("Invalid key: %s", p.Key()) - } - - if !bytes.Equal([]byte(value), p.Value()) { - t.Fatalf("Invalid key: %s", p.Value()) - } + require.Nil(t, err) + require.True(t, p.Valid()) + assert.Equal(t, []byte(key), p.Key()) + assert.Equal(t, []byte(value), p.Value()) } func TestDummyKV(t *testing.T) { @@ -223,3 +207,97 @@ func valsEqual(t *testing.T, vals1, vals2 []*types.Validator) { } } } + +func makeSocketClientServer(app types.Application, name string) (abcicli.Client, Service, error) { + // Start the listener + socket := Fmt("unix://%s.sock", name) + server, err := server.NewSocketServer(socket, app) + if err != nil { + return nil, nil, err + } + + // Connect to the socket + client, err := abcicli.NewSocketClient(socket, false) + if err != nil { + server.Stop() + return nil, nil, err + } + client.Start() + + return client, server, err +} + +func makeGRPCClientServer(app types.Application, name string) (abcicli.Client, Service, error) { + // Start the listener + socket := Fmt("unix://%s.sock", name) + + gapp := types.NewGRPCApplication(app) + server, err := server.NewGRPCServer(socket, gapp) + if err != nil { + return nil, nil, err + } + + client, err := abcicli.NewGRPCClient(socket, true) + if err != nil { + server.Stop() + return nil, nil, err + } + return client, server, err +} + +func TestClientServer(t *testing.T) { + // set up socket app + dummy := NewDummyApplication() + client, server, err := makeSocketClientServer(dummy, "dummy-socket") + require.Nil(t, err) + defer server.Stop() + defer client.Stop() + + runClientTests(t, client) + + // set up grpc app + dummy = NewDummyApplication() + gclient, gserver, err := makeGRPCClientServer(dummy, "dummy-grpc") + require.Nil(t, err) + defer gserver.Stop() + defer gclient.Stop() + + runClientTests(t, gclient) +} + +func runClientTests(t *testing.T, client abcicli.Client) { + // run some tests.... + key := "abc" + value := key + tx := []byte(key) + testClient(t, client, tx, key, value) + + value = "def" + tx = []byte(key + "=" + value) + testClient(t, client, tx, key, value) +} + +func testClient(t *testing.T, app abcicli.Client, tx []byte, key, value string) { + ar := app.DeliverTxSync(tx) + require.False(t, ar.IsErr(), ar) + // repeating tx doesn't raise error + ar = app.DeliverTxSync(tx) + require.False(t, ar.IsErr(), ar) + + // make sure query is fine + r := app.QuerySync([]byte(key)) + require.False(t, r.IsErr(), r) + q := new(QueryResult) + err := wire.ReadJSONBytes(r.Data, q) + require.Nil(t, err) + require.Equal(t, value, q.Value) + + // make sure proof is fine + rp := app.ProofSync([]byte(key), 0) + require.False(t, rp.IsErr(), rp) + p, err := merkle.LoadProof(rp.Data) + require.Nil(t, err) + require.True(t, p.Valid()) + assert.Equal(t, []byte(key), p.Key()) + assert.Equal(t, []byte(value), p.Value()) +} diff --git a/example/dummy/dummy_test.go.orig b/example/dummy/dummy_test.go.orig new file mode 100644 index 00000000..5cf7eee7 --- /dev/null +++ b/example/dummy/dummy_test.go.orig @@ -0,0 +1,321 @@ +package dummy + +import ( + "bytes" + "io/ioutil" + "sort" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + . "github.com/tendermint/go-common" + "github.com/tendermint/go-crypto" + merkle "github.com/tendermint/go-merkle" + "github.com/tendermint/go-wire" +<<<<<<< HEAD + "github.com/tendermint/abci/types" +) + +func testDummy(t *testing.T, dummy types.Application, tx []byte, key, value string) { + if r := dummy.DeliverTx(tx); r.IsErr() { + t.Fatal(r) + } + if r := dummy.DeliverTx(tx); r.IsErr() { + t.Fatal(r) + } + + r := dummy.Query([]byte(key)) + if r.IsErr() { + t.Fatal(r) + } +======= + tmspcli "github.com/tendermint/tmsp/client" + "github.com/tendermint/tmsp/server" + "github.com/tendermint/tmsp/types" +) + +func testDummy(t *testing.T, app types.Application, tx []byte, key, value string) { + ar := app.AppendTx(tx) + require.False(t, ar.IsErr(), ar) + // repeating tx doesn't raise error + ar = app.AppendTx(tx) + require.False(t, ar.IsErr(), ar) +>>>>>>> Add tests for client-server proofs over socket and grpc + + // make sure query is fine + r := app.Query([]byte(key)) + require.False(t, r.IsErr(), r) + q := new(QueryResult) + err := wire.ReadJSONBytes(r.Data, q) + require.Nil(t, err) + require.Equal(t, value, q.Value) + + // make sure proof is fine + rp := app.Proof([]byte(key), 0) + require.False(t, rp.IsErr(), rp) + p, err := merkle.LoadProof(rp.Data) + require.Nil(t, err) + require.True(t, p.Valid()) + assert.Equal(t, []byte(key), p.Key()) + assert.Equal(t, []byte(value), p.Value()) +} + +func TestDummyKV(t *testing.T) { + dummy := NewDummyApplication() + key := "abc" + value := key + tx := []byte(key) + testDummy(t, dummy, tx, key, value) + + value = "def" + tx = []byte(key + "=" + value) + testDummy(t, dummy, tx, key, value) +} + +func TestPersistentDummyKV(t *testing.T) { + dir, err := ioutil.TempDir("/tmp", "abci-dummy-test") // TODO + if err != nil { + t.Fatal(err) + } + dummy := NewPersistentDummyApplication(dir) + key := "abc" + value := key + tx := []byte(key) + testDummy(t, dummy, tx, key, value) + + value = "def" + tx = []byte(key + "=" + value) + testDummy(t, dummy, tx, key, value) +} + +func TestPersistentDummyInfo(t *testing.T) { + dir, err := ioutil.TempDir("/tmp", "abci-dummy-test") // TODO + if err != nil { + t.Fatal(err) + } + dummy := NewPersistentDummyApplication(dir) + height := uint64(0) + + resInfo := dummy.Info() + if resInfo.LastBlockHeight != height { + t.Fatalf("expected height of %d, got %d", height, resInfo.LastBlockHeight) + } + + // make and apply block + height = uint64(1) + hash := []byte("foo") + header := &types.Header{ + Height: uint64(height), + } + dummy.BeginBlock(hash, header) + dummy.EndBlock(height) + dummy.Commit() + + resInfo = dummy.Info() + if resInfo.LastBlockHeight != height { + t.Fatalf("expected height of %d, got %d", height, resInfo.LastBlockHeight) + } + +} + +// add a validator, remove a validator, update a validator +func TestValSetChanges(t *testing.T) { + dir, err := ioutil.TempDir("/tmp", "abci-dummy-test") // TODO + if err != nil { + t.Fatal(err) + } + dummy := NewPersistentDummyApplication(dir) + + // init with some validators + total := 10 + nInit := 5 + vals := make([]*types.Validator, total) + for i := 0; i < total; i++ { + pubkey := crypto.GenPrivKeyEd25519FromSecret([]byte(Fmt("test%d", i))).PubKey().Bytes() + power := RandInt() + vals[i] = &types.Validator{pubkey, uint64(power)} + } + // iniitalize with the first nInit + dummy.InitChain(vals[:nInit]) + + vals1, vals2 := vals[:nInit], dummy.Validators() + valsEqual(t, vals1, vals2) + + var v1, v2, v3 *types.Validator + + // add some validators + v1, v2 = vals[nInit], vals[nInit+1] + diff := []*types.Validator{v1, v2} + tx1 := MakeValSetChangeTx(v1.PubKey, v1.Power) + tx2 := MakeValSetChangeTx(v2.PubKey, v2.Power) + + makeApplyBlock(t, dummy, 1, diff, tx1, tx2) + + vals1, vals2 = vals[:nInit+2], dummy.Validators() + valsEqual(t, vals1, vals2) + + // remove some validators + v1, v2, v3 = vals[nInit-2], vals[nInit-1], vals[nInit] + v1.Power = 0 + v2.Power = 0 + v3.Power = 0 + diff = []*types.Validator{v1, v2, v3} + tx1 = MakeValSetChangeTx(v1.PubKey, v1.Power) + tx2 = MakeValSetChangeTx(v2.PubKey, v2.Power) + tx3 := MakeValSetChangeTx(v3.PubKey, v3.Power) + + makeApplyBlock(t, dummy, 2, diff, tx1, tx2, tx3) + + vals1 = append(vals[:nInit-2], vals[nInit+1]) + vals2 = dummy.Validators() + valsEqual(t, vals1, vals2) + + // update some validators + v1 = vals[0] + if v1.Power == 5 { + v1.Power = 6 + } else { + v1.Power = 5 + } + diff = []*types.Validator{v1} + tx1 = MakeValSetChangeTx(v1.PubKey, v1.Power) + + makeApplyBlock(t, dummy, 3, diff, tx1) + + vals1 = append([]*types.Validator{v1}, vals1[1:len(vals1)]...) + vals2 = dummy.Validators() + valsEqual(t, vals1, vals2) + +} + +func makeApplyBlock(t *testing.T, dummy types.Application, heightInt int, diff []*types.Validator, txs ...[]byte) { + // make and apply block + height := uint64(heightInt) + hash := []byte("foo") + header := &types.Header{ + Height: height, + } + + dummyChain := dummy.(types.BlockchainAware) // hmm... + dummyChain.BeginBlock(hash, header) + for _, tx := range txs { + if r := dummy.DeliverTx(tx); r.IsErr() { + t.Fatal(r) + } + } + resEndBlock := dummyChain.EndBlock(height) + dummy.Commit() + + valsEqual(t, diff, resEndBlock.Diffs) + +} + +// order doesn't matter +func valsEqual(t *testing.T, vals1, vals2 []*types.Validator) { + if len(vals1) != len(vals2) { + t.Fatalf("vals dont match in len. got %d, expected %d", len(vals2), len(vals1)) + } + sort.Sort(types.Validators(vals1)) + sort.Sort(types.Validators(vals2)) + for i, v1 := range vals1 { + v2 := vals2[i] + if !bytes.Equal(v1.PubKey, v2.PubKey) || + v1.Power != v2.Power { + t.Fatalf("vals dont match at index %d. got %X/%d , expected %X/%d", i, v2.PubKey, v2.Power, v1.PubKey, v1.Power) + } + } +} + +func makeSocketClientServer(app types.Application, name string) (tmspcli.Client, Service, error) { + // Start the listener + socket := Fmt("unix://%s.sock", name) + server, err := server.NewSocketServer(socket, app) + if err != nil { + return nil, nil, err + } + + // Connect to the socket + client, err := tmspcli.NewSocketClient(socket, false) + if err != nil { + server.Stop() + return nil, nil, err + } + client.Start() + + return client, server, err +} + +func makeGRPCClientServer(app types.Application, name string) (tmspcli.Client, Service, error) { + // Start the listener + socket := Fmt("unix://%s.sock", name) + + gapp := types.NewGRPCApplication(app) + server, err := server.NewGRPCServer(socket, gapp) + if err != nil { + return nil, nil, err + } + + client, err := tmspcli.NewGRPCClient(socket, true) + if err != nil { + server.Stop() + return nil, nil, err + } + return client, server, err +} + +func TestClientServer(t *testing.T) { + // set up socket app + dummy := NewDummyApplication() + client, server, err := makeSocketClientServer(dummy, "dummy-socket") + require.Nil(t, err) + defer server.Stop() + defer client.Stop() + + runClientTests(t, client) + + // set up grpc app + dummy = NewDummyApplication() + gclient, gserver, err := makeGRPCClientServer(dummy, "dummy-grpc") + require.Nil(t, err) + defer gserver.Stop() + defer gclient.Stop() + + runClientTests(t, gclient) +} + +func runClientTests(t *testing.T, client tmspcli.Client) { + // run some tests.... + key := "abc" + value := key + tx := []byte(key) + testClient(t, client, tx, key, value) + + value = "def" + tx = []byte(key + "=" + value) + testClient(t, client, tx, key, value) +} + +func testClient(t *testing.T, app tmspcli.Client, tx []byte, key, value string) { + ar := app.AppendTxSync(tx) + require.False(t, ar.IsErr(), ar) + // repeating tx doesn't raise error + ar = app.AppendTxSync(tx) + require.False(t, ar.IsErr(), ar) + + // make sure query is fine + r := app.QuerySync([]byte(key)) + require.False(t, r.IsErr(), r) + q := new(QueryResult) + err := wire.ReadJSONBytes(r.Data, q) + require.Nil(t, err) + require.Equal(t, value, q.Value) + + // make sure proof is fine + rp := app.ProofSync([]byte(key), 0) + require.False(t, rp.IsErr(), rp) + p, err := merkle.LoadProof(rp.Data) + require.Nil(t, err) + require.True(t, p.Valid()) + assert.Equal(t, []byte(key), p.Key()) + assert.Equal(t, []byte(value), p.Value()) +} diff --git a/glide.lock b/glide.lock index 42776a3a..4829e672 100644 --- a/glide.lock +++ b/glide.lock @@ -19,6 +19,11 @@ imports: version: d228849504861217f796da67fae4f6e347643f15 - name: github.com/mattn/go-isatty version: 30a891c33c7cde7b02a981314b4228ec99380cca +- name: github.com/stretchr/testify + version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0 + subpackages: + - assert + - require - name: github.com/syndtr/goleveldb version: 23851d93a2292dcc56e71a18ec9e0624d84a0f65 subpackages: @@ -102,4 +107,12 @@ imports: - stats - tap - transport -testImports: [] +testImports: +- name: github.com/davecgh/go-spew + version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9 + subpackages: + - spew +- name: github.com/pmezard/go-difflib + version: d8ed2627bdf02c080bf22230dbb337003b7aba2d + subpackages: + - difflib diff --git a/glide.yaml b/glide.yaml index 295b3d3e..af174387 100644 --- a/glide.yaml +++ b/glide.yaml @@ -15,3 +15,7 @@ import: subpackages: - context - package: google.golang.org/grpc +- package: github.com/stretchr/testify + version: ^1.1.4 + subpackages: + - require