From 5433e5771e7744f8aafb401ad92c4503976066dc Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 11 Oct 2017 01:31:31 +0400 Subject: [PATCH] support historical abci queries (Refs #482) --- rpc/client/httpclient.go | 8 ++++++-- rpc/client/interface.go | 3 ++- rpc/client/localclient.go | 8 ++++++-- rpc/client/mock/abci.go | 33 +++++++++++++++++++++++---------- rpc/client/mock/abci_test.go | 7 ++++--- rpc/client/mock/client.go | 8 ++++++-- rpc/client/rpc_test.go | 21 +++++++++++++++++++-- rpc/client/types.go | 13 +++++++++++++ rpc/core/abci.go | 10 ++++++---- rpc/core/routes.go | 2 +- 10 files changed, 86 insertions(+), 27 deletions(-) create mode 100644 rpc/client/types.go diff --git a/rpc/client/httpclient.go b/rpc/client/httpclient.go index b00c97d23..e21c2b0fd 100644 --- a/rpc/client/httpclient.go +++ b/rpc/client/httpclient.go @@ -69,10 +69,14 @@ func (c *HTTP) ABCIInfo() (*ctypes.ResultABCIInfo, error) { return result, nil } -func (c *HTTP) ABCIQuery(path string, data data.Bytes, prove bool) (*ctypes.ResultABCIQuery, error) { +func (c *HTTP) ABCIQuery(path string, data data.Bytes) (*ctypes.ResultABCIQuery, error) { + return c.ABCIQueryWithOptions(path, data, DefaultABCIQueryOptions()) +} + +func (c *HTTP) ABCIQueryWithOptions(path string, data data.Bytes, opts ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { result := new(ctypes.ResultABCIQuery) _, err := c.rpc.Call("abci_query", - map[string]interface{}{"path": path, "data": data, "prove": prove}, + map[string]interface{}{"path": path, "data": data, "height": opts.Height, "prove": opts.Prove}, result) if err != nil { return nil, errors.Wrap(err, "ABCIQuery") diff --git a/rpc/client/interface.go b/rpc/client/interface.go index ed7ccabaf..10689a561 100644 --- a/rpc/client/interface.go +++ b/rpc/client/interface.go @@ -31,7 +31,8 @@ import ( type ABCIClient interface { // reading from abci app ABCIInfo() (*ctypes.ResultABCIInfo, error) - ABCIQuery(path string, data data.Bytes, prove bool) (*ctypes.ResultABCIQuery, error) + ABCIQuery(path string, data data.Bytes) (*ctypes.ResultABCIQuery, error) + ABCIQueryWithOptions(path string, data data.Bytes, opts ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) // writing to abci app BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) diff --git a/rpc/client/localclient.go b/rpc/client/localclient.go index 134f935ca..581ae7d16 100644 --- a/rpc/client/localclient.go +++ b/rpc/client/localclient.go @@ -57,8 +57,12 @@ func (c Local) ABCIInfo() (*ctypes.ResultABCIInfo, error) { return core.ABCIInfo() } -func (c Local) ABCIQuery(path string, data data.Bytes, prove bool) (*ctypes.ResultABCIQuery, error) { - return core.ABCIQuery(path, data, prove) +func (c Local) ABCIQuery(path string, data data.Bytes) (*ctypes.ResultABCIQuery, error) { + return c.ABCIQueryWithOptions(path, data, DefaultABCIQueryOptions()) +} + +func (c Local) ABCIQueryWithOptions(path string, data data.Bytes, opts ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { + return core.ABCIQuery(path, data, opts.Height, opts.Prove) } func (c Local) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { diff --git a/rpc/client/mock/abci.go b/rpc/client/mock/abci.go index db3fa4f1d..e08490418 100644 --- a/rpc/client/mock/abci.go +++ b/rpc/client/mock/abci.go @@ -24,8 +24,12 @@ func (a ABCIApp) ABCIInfo() (*ctypes.ResultABCIInfo, error) { return &ctypes.ResultABCIInfo{a.App.Info(abci.RequestInfo{version.Version})}, nil } -func (a ABCIApp) ABCIQuery(path string, data data.Bytes, prove bool) (*ctypes.ResultABCIQuery, error) { - q := a.App.Query(abci.RequestQuery{data, path, 0, prove}) +func (a ABCIApp) ABCIQuery(path string, data data.Bytes) (*ctypes.ResultABCIQuery, error) { + return a.ABCIQueryWithOptions(path, data, client.DefaultABCIQueryOptions()) +} + +func (a ABCIApp) ABCIQueryWithOptions(path string, data data.Bytes, opts client.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { + q := a.App.Query(abci.RequestQuery{data, path, opts.Height, opts.Prove}) return &ctypes.ResultABCIQuery{q.Result()}, nil } @@ -79,8 +83,12 @@ func (m ABCIMock) ABCIInfo() (*ctypes.ResultABCIInfo, error) { return &ctypes.ResultABCIInfo{res.(abci.ResponseInfo)}, nil } -func (m ABCIMock) ABCIQuery(path string, data data.Bytes, prove bool) (*ctypes.ResultABCIQuery, error) { - res, err := m.Query.GetResponse(QueryArgs{path, data, prove}) +func (m ABCIMock) ABCIQuery(path string, data data.Bytes) (*ctypes.ResultABCIQuery, error) { + return m.ABCIQueryWithOptions(path, data, client.DefaultABCIQueryOptions()) +} + +func (m ABCIMock) ABCIQueryWithOptions(path string, data data.Bytes, opts client.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { + res, err := m.Query.GetResponse(QueryArgs{path, data, opts.Height, opts.Prove}) if err != nil { return nil, err } @@ -131,9 +139,10 @@ func (r *ABCIRecorder) _assertABCIClient() client.ABCIClient { } type QueryArgs struct { - Path string - Data data.Bytes - Prove bool + Path string + Data data.Bytes + Height uint64 + Prove bool } func (r *ABCIRecorder) addCall(call Call) { @@ -150,11 +159,15 @@ func (r *ABCIRecorder) ABCIInfo() (*ctypes.ResultABCIInfo, error) { return res, err } -func (r *ABCIRecorder) ABCIQuery(path string, data data.Bytes, prove bool) (*ctypes.ResultABCIQuery, error) { - res, err := r.Client.ABCIQuery(path, data, prove) +func (r *ABCIRecorder) ABCIQuery(path string, data data.Bytes) (*ctypes.ResultABCIQuery, error) { + return r.ABCIQueryWithOptions(path, data, client.DefaultABCIQueryOptions()) +} + +func (r *ABCIRecorder) ABCIQueryWithOptions(path string, data data.Bytes, opts client.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { + res, err := r.Client.ABCIQueryWithOptions(path, data, opts) r.addCall(Call{ Name: "abci_query", - Args: QueryArgs{path, data, prove}, + Args: QueryArgs{path, data, opts.Height, opts.Prove}, Response: res, Error: err, }) diff --git a/rpc/client/mock/abci_test.go b/rpc/client/mock/abci_test.go index 245db6c68..6fe94c408 100644 --- a/rpc/client/mock/abci_test.go +++ b/rpc/client/mock/abci_test.go @@ -12,6 +12,7 @@ import ( "github.com/tendermint/abci/example/dummy" abci "github.com/tendermint/abci/types" data "github.com/tendermint/go-wire/data" + "github.com/tendermint/tendermint/rpc/client" "github.com/tendermint/tendermint/rpc/client/mock" ctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/tendermint/tendermint/types" @@ -50,7 +51,7 @@ func TestABCIMock(t *testing.T) { assert.Equal("foobar", err.Error()) // query always returns the response - query, err := m.ABCIQuery("/", nil, false) + query, err := m.ABCIQueryWithOptions("/", nil, client.ABCIQueryOptions{Prove: false}) require.Nil(err) require.NotNil(query) assert.EqualValues(key, query.Key) @@ -92,7 +93,7 @@ func TestABCIRecorder(t *testing.T) { require.Equal(0, len(r.Calls)) r.ABCIInfo() - r.ABCIQuery("path", data.Bytes("data"), true) + r.ABCIQueryWithOptions("path", data.Bytes("data"), client.ABCIQueryOptions{Prove: true}) require.Equal(2, len(r.Calls)) info := r.Calls[0] @@ -164,7 +165,7 @@ func TestABCIApp(t *testing.T) { assert.True(res.DeliverTx.Code.IsOK()) // check the key - qres, err := m.ABCIQuery("/key", data.Bytes(key), false) + qres, err := m.ABCIQueryWithOptions("/key", data.Bytes(key), client.ABCIQueryOptions{Prove: false}) require.Nil(err) assert.EqualValues(value, qres.Value) } diff --git a/rpc/client/mock/client.go b/rpc/client/mock/client.go index f32694edd..2cd16a7a5 100644 --- a/rpc/client/mock/client.go +++ b/rpc/client/mock/client.go @@ -84,8 +84,12 @@ func (c Client) ABCIInfo() (*ctypes.ResultABCIInfo, error) { return core.ABCIInfo() } -func (c Client) ABCIQuery(path string, data data.Bytes, prove bool) (*ctypes.ResultABCIQuery, error) { - return core.ABCIQuery(path, data, prove) +func (c Client) ABCIQuery(path string, data data.Bytes) (*ctypes.ResultABCIQuery, error) { + return c.ABCIQueryWithOptions(path, data, client.DefaultABCIQueryOptions()) +} + +func (c Client) ABCIQueryWithOptions(path string, data data.Bytes, opts client.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { + return core.ABCIQuery(path, data, opts.Height, opts.Prove) } func (c Client) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { diff --git a/rpc/client/rpc_test.go b/rpc/client/rpc_test.go index 9bcd3de45..74a6336ee 100644 --- a/rpc/client/rpc_test.go +++ b/rpc/client/rpc_test.go @@ -98,6 +98,23 @@ func TestGenesisAndValidators(t *testing.T) { } } +func TestABCIQuery(t *testing.T) { + for i, c := range GetClients() { + // write something + k, v, tx := MakeTxKV() + bres, err := c.BroadcastTxCommit(tx) + require.Nil(t, err, "%d: %+v", i, err) + apph := bres.Height + 1 // this is where the tx will be applied to the state + + // wait before querying + client.WaitForHeight(c, apph, nil) + qres, err := c.ABCIQuery("/key", k) + if assert.Nil(t, err) && assert.True(t, qres.Code.IsOK()) { + assert.EqualValues(t, v, qres.Value) + } + } +} + // Make some app checks func TestAppCalls(t *testing.T) { assert, require := assert.New(t), require.New(t) @@ -124,7 +141,7 @@ func TestAppCalls(t *testing.T) { // wait before querying client.WaitForHeight(c, apph, nil) - qres, err := c.ABCIQuery("/key", k, false) + qres, err := c.ABCIQueryWithOptions("/key", k, client.ABCIQueryOptions{Prove: false}) if assert.Nil(err) && assert.True(qres.Code.IsOK()) { // assert.Equal(k, data.GetKey()) // only returned for proofs assert.EqualValues(v, qres.Value) @@ -172,7 +189,7 @@ func TestAppCalls(t *testing.T) { assert.Equal(block.Block.LastCommit, commit2.Commit) // and we got a proof that works! - pres, err := c.ABCIQuery("/key", k, true) + pres, err := c.ABCIQueryWithOptions("/key", k, client.ABCIQueryOptions{Prove: true}) if assert.Nil(err) && assert.True(pres.Code.IsOK()) { proof, err := iavl.ReadProof(pres.Proof) if assert.Nil(err) { diff --git a/rpc/client/types.go b/rpc/client/types.go new file mode 100644 index 000000000..1e6431c5d --- /dev/null +++ b/rpc/client/types.go @@ -0,0 +1,13 @@ +package client + +// ABCIQueryOptions can be used to provide options for ABCIQuery call other +// than the DefaultABCIQueryOptions. +type ABCIQueryOptions struct { + Height uint64 + Prove bool +} + +// DefaultABCIQueryOptions are latest height (0) and prove equal to true. +func DefaultABCIQueryOptions() ABCIQueryOptions { + return ABCIQueryOptions{Height: 0, Prove: true} +} diff --git a/rpc/core/abci.go b/rpc/core/abci.go index 06275a9e3..d65a3efe6 100644 --- a/rpc/core/abci.go +++ b/rpc/core/abci.go @@ -45,12 +45,14 @@ import ( // |-----------+--------+---------+----------+---------------------------------------| // | path | string | false | false | Path to the data ("/a/b/c") | // | data | []byte | false | true | Data | +// | height | uint64 | 0 | false | Height (0 means latest) | // | prove | bool | false | false | Include a proof of the data inclusion | -func ABCIQuery(path string, data data.Bytes, prove bool) (*ctypes.ResultABCIQuery, error) { +func ABCIQuery(path string, data data.Bytes, height uint64, prove bool) (*ctypes.ResultABCIQuery, error) { resQuery, err := proxyAppQuery.QuerySync(abci.RequestQuery{ - Path: path, - Data: data, - Prove: prove, + Path: path, + Data: data, + Height: height, + Prove: prove, }) if err != nil { return nil, err diff --git a/rpc/core/routes.go b/rpc/core/routes.go index 485f7a00f..b1dbd3785 100644 --- a/rpc/core/routes.go +++ b/rpc/core/routes.go @@ -29,7 +29,7 @@ var Routes = map[string]*rpc.RPCFunc{ "broadcast_tx_async": rpc.NewRPCFunc(BroadcastTxAsync, "tx"), // abci API - "abci_query": rpc.NewRPCFunc(ABCIQuery, "path,data,prove"), + "abci_query": rpc.NewRPCFunc(ABCIQuery, "path,data,height,prove"), "abci_info": rpc.NewRPCFunc(ABCIInfo, ""), }