From 7a627cc831a906245dea63052467f3b5eef914fc Mon Sep 17 00:00:00 2001 From: Gustavo Chain Date: Tue, 20 Aug 2019 18:11:52 +0200 Subject: [PATCH 1/3] go-kosu: RPC numberPosters and totalOrders --- packages/go-kosu/abci/app.go | 2 +- packages/go-kosu/abci/client.go | 20 ++++++++++ packages/go-kosu/abci/order.go | 3 ++ packages/go-kosu/rpc/rpc_test.go | 24 ++++++++++++ packages/go-kosu/rpc/service.go | 21 ++++++++++ packages/go-kosu/store/{cosmos => }/codec.go | 5 ++- packages/go-kosu/store/cosmos/store.go | 41 +++++++++++++++++++- packages/go-kosu/store/cosmos/store_test.go | 6 +-- packages/go-kosu/store/store.go | 4 ++ packages/go-kosu/store/storetest/testing.go | 11 ++++++ packages/go-kosu/tests/order_test.go | 12 +++++- 11 files changed, 140 insertions(+), 9 deletions(-) rename packages/go-kosu/store/{cosmos => }/codec.go (95%) diff --git a/packages/go-kosu/abci/app.go b/packages/go-kosu/abci/app.go index bcda8d7b..4b8ad462 100644 --- a/packages/go-kosu/abci/app.go +++ b/packages/go-kosu/abci/app.go @@ -54,7 +54,7 @@ func NewAppWithConfig(db db.DB, cfg *config.Config) *App { } app := &App{ - store: cosmos.NewStore(db, new(cosmos.ProtoCodec)), + store: cosmos.NewStore(db, store.DefaultCodec), Config: cfg, log: logger.With("module", "app"), } diff --git a/packages/go-kosu/abci/client.go b/packages/go-kosu/abci/client.go index 573a79a2..da3dbb36 100644 --- a/packages/go-kosu/abci/client.go +++ b/packages/go-kosu/abci/client.go @@ -158,6 +158,26 @@ func (c *Client) QueryValidator(addr string) (*types.Validator, error) { return &pb, nil } +// QueryTotalOrders performs a ABCI Query to "/chain/totalorders" +func (c *Client) QueryTotalOrders() (uint64, error) { + out, err := c.ABCIQuery("/chain/key", []byte("totalorders")) + if err != nil { + return 0, err + } + + res := out.Response + if res.IsErr() { + return 0, errors.New(res.GetLog()) + } + + if len(res.Value) == 0 { + return 0, errors.New("empty") + } + + pb := proto.NewBuffer(res.Value) + return pb.DecodeFixed64() +} + func (c *Client) query(path string, data []byte, pb proto.Message) error { out, err := c.ABCIQuery(path, data) if err != nil { diff --git a/packages/go-kosu/abci/order.go b/packages/go-kosu/abci/order.go index b4f998c7..1f8e209b 100644 --- a/packages/go-kosu/abci/order.go +++ b/packages/go-kosu/abci/order.go @@ -62,6 +62,9 @@ func (app *App) deliverOrderTx(tx *types.TransactionOrder) abci.ResponseDeliverT // begin state modification poster.Limit-- app.store.SetPoster(posterAddress.String(), *poster) + + total := app.store.TotalOrders() + app.store.SetTotalOrders(total + 1) // end state modification return abci.ResponseDeliverTx{ diff --git a/packages/go-kosu/rpc/rpc_test.go b/packages/go-kosu/rpc/rpc_test.go index ec5ba0dc..ea4eb738 100644 --- a/packages/go-kosu/rpc/rpc_test.go +++ b/packages/go-kosu/rpc/rpc_test.go @@ -159,3 +159,27 @@ func TestNewRebalances(t *testing.T) { assert.Equal(t, tx.String(), e.String()) } } + +func TestNumberPosters(t *testing.T) { + app, rpc, closer := newServerClient(t) + defer closer() + + addresses := []string{ + "0x0000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000002", + "0x0000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000004", + } + + for _, addr := range addresses { + app.Store().SetPoster(addr, types.Poster{ + Balance: types.NewBigIntFromInt(100), + }) + } + + var num uint64 + err := rpc.Call(&num, "kosu_numberPosters") + require.NoError(t, err) + + assert.EqualValues(t, len(addresses), num) +} diff --git a/packages/go-kosu/rpc/service.go b/packages/go-kosu/rpc/service.go index 0a35c26c..3d6633c4 100644 --- a/packages/go-kosu/rpc/service.go +++ b/packages/go-kosu/rpc/service.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "go-kosu/abci" "go-kosu/abci/types" + "go-kosu/store" "log" "strings" @@ -688,3 +689,23 @@ curl -X POST localhost:14341 \ func (s *Service) QueryPoster(addr string) (*types.Poster, error) { return s.abci.QueryPoster(addr) } + +// TotalOrders ... +func (s *Service) TotalOrders() (uint64, error) { + return s.abci.QueryTotalOrders() +} + +// NumberPosters ... +func (s *Service) NumberPosters() (uint64, error) { + res, err := s.abci.ABCIQuery("/poster/number", nil) + if err != nil { + return 0, err + } + + var num uint64 + if err := store.DefaultCodec.Decode(res.Response.Value, &num); err != nil { + return 0, err + } + + return num, nil +} diff --git a/packages/go-kosu/store/cosmos/codec.go b/packages/go-kosu/store/codec.go similarity index 95% rename from packages/go-kosu/store/cosmos/codec.go rename to packages/go-kosu/store/codec.go index 385df3fa..64a4fb0b 100644 --- a/packages/go-kosu/store/cosmos/codec.go +++ b/packages/go-kosu/store/codec.go @@ -1,4 +1,4 @@ -package cosmos +package store import ( "bytes" @@ -98,3 +98,6 @@ func (c *ProtoCodec) Decode(bs []byte, s interface{}) error { msg := s.(proto.Message) return buf.Unmarshal(msg) } + +// DefaultCodec is the default codec to be used +var DefaultCodec = new(ProtoCodec) diff --git a/packages/go-kosu/store/cosmos/store.go b/packages/go-kosu/store/cosmos/store.go index 841bc17f..45b0af52 100644 --- a/packages/go-kosu/store/cosmos/store.go +++ b/packages/go-kosu/store/cosmos/store.go @@ -11,11 +11,12 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "go-kosu/abci/types" + "go-kosu/store" ) // Store stores the application state type Store struct { - codec Codec + codec store.Codec cms *rootmulti.Store @@ -26,7 +27,7 @@ type Store struct { } // NewStore returns a new store -func NewStore(db db.DB, cdc Codec) *Store { +func NewStore(db db.DB, cdc store.Codec) *Store { s := &Store{codec: cdc, cms: rootmulti.NewStore(db), @@ -105,9 +106,33 @@ func (s *Store) LastCommitID() cosmos.CommitID { return s.cms.LastCommitID() } // Query wrap the rootmulti.Query method func (s *Store) Query(req abci.RequestQuery) abci.ResponseQuery { + switch req.Path { + case "/poster/number": + return s.queryPosterNumber() + } return s.cms.Query(req) } +func (s *Store) queryPosterNumber() (resp abci.ResponseQuery) { + var num uint64 + s.All(s.posterKey, func(_ string, _ []byte) { + num++ + }) + + buf, err := s.codec.Encode(num) + if err != nil { + resp.Code = 1 + resp.Log = err.Error() + } else { + resp.Value = buf + } + + return resp +} + +// Codec returns the storage codec +func (s *Store) Codec() store.Codec { return s.codec } + // SetRoundInfo sets the RoundInfo func (s *Store) SetRoundInfo(v types.RoundInfo) { s.Set("roundinfo", s.chainKey, &v) @@ -144,6 +169,18 @@ func (s *Store) LastEvent() uint64 { return v } +// SetTotalOrders sets the TotalOrders +func (s *Store) SetTotalOrders(v uint64) { + s.Set("totalorders", s.chainKey, v) +} + +// TotalOrders gets the TotalOrders +func (s *Store) TotalOrders() uint64 { + var v uint64 + s.Get("totalorders", s.chainKey, &v) + return v +} + // WitnessTxExists checks if a given WitnessTx has been persisted func (s *Store) WitnessTxExists(id []byte) bool { return s.Has(string(id), s.witnessKey) diff --git a/packages/go-kosu/store/cosmos/store_test.go b/packages/go-kosu/store/cosmos/store_test.go index e899baa9..7cf734ad 100644 --- a/packages/go-kosu/store/cosmos/store_test.go +++ b/packages/go-kosu/store/cosmos/store_test.go @@ -10,9 +10,9 @@ import ( ) func TestCosmosStore(t *testing.T) { - for _, cdc := range []Codec{ - &ProtoCodec{}, - &GobCodec{}, + for _, cdc := range []store.Codec{ + &store.ProtoCodec{}, + &store.GobCodec{}, } { t.Run(cdc.String(), func(t *testing.T) { f := func() (store.Store, func()) { diff --git a/packages/go-kosu/store/store.go b/packages/go-kosu/store/store.go index 7939e8f0..5575bc85 100644 --- a/packages/go-kosu/store/store.go +++ b/packages/go-kosu/store/store.go @@ -13,6 +13,7 @@ type Store interface { Commit() store.CommitID LastCommitID() store.CommitID Query(abci.RequestQuery) abci.ResponseQuery + Codec() Codec RoundInfo() types.RoundInfo SetRoundInfo(types.RoundInfo) @@ -23,6 +24,9 @@ type Store interface { LastEvent() uint64 SetLastEvent(uint64) + TotalOrders() uint64 + SetTotalOrders(uint64) + WitnessTxExists([]byte) bool WitnessTx([]byte) *types.TransactionWitness IterateWitnessTxs(func(tx *types.TransactionWitness)) diff --git a/packages/go-kosu/store/storetest/testing.go b/packages/go-kosu/store/storetest/testing.go index 108fc45a..cdb8d573 100644 --- a/packages/go-kosu/store/storetest/testing.go +++ b/packages/go-kosu/store/storetest/testing.go @@ -23,6 +23,7 @@ func TestSuite(t *testing.T, f Factory) { {"RoundInfo", TestRoundInfo}, {"ConsensusParams", TestConsensusParams}, {"LastEvent", TestLastEvent}, + {"TotalOrders", TestTotalOrders}, {"Witness", TestWitness}, {"Poster", TestPoster}, {"Validator", TestValidator}, @@ -64,6 +65,16 @@ func TestLastEvent(t *testing.T, s store.Store) { assert.Equal(t, lastEvent, s.LastEvent()) } +// TestTotalOrders verifies the LastTotalOrders storage behavior +func TestTotalOrders(t *testing.T, s store.Store) { + s.SetTotalOrders(1) + s.SetTotalOrders(2) + s.SetTotalOrders(3) + s.SetTotalOrders(4) + + assert.Equal(t, uint64(4), s.TotalOrders()) +} + // TestWitness verifies the Witness storage behavior func TestWitness(t *testing.T, s store.Store) { witnessTx := &types.TransactionWitness{ diff --git a/packages/go-kosu/tests/order_test.go b/packages/go-kosu/tests/order_test.go index f369bf2d..a5151ef1 100644 --- a/packages/go-kosu/tests/order_test.go +++ b/packages/go-kosu/tests/order_test.go @@ -43,7 +43,7 @@ func (s *Suite) TestOrderTx() { }) }) - Convey("When a RPC subscription is active", func() { + Convey("Given a RPC client", func() { client := rpc.DialInProc( rpc.NewServer(s.client), ) @@ -58,13 +58,21 @@ func (s *Suite) TestOrderTx() { defer sub.Unsubscribe() Convey("And a OrderTx is sent", func() { - BroadcastTxSync(t, s.client, tx) + BroadcastTxCommit(t, s.client, tx) Convey("Event should be sent and matches the Broadcasted Tx", func() { event := <-ch So(event.String(), ShouldEqual, tx.String()) }) + + Convey("TotalOrders is updated", func() { + var total uint64 + err := client.Call(&total, "kosu_totalOrders") + require.NoError(t, err) + So(total, ShouldEqual, 1) + }) }) + }) Convey("And a non existing poster", func() { From aab87f1427b645a64231dd31d9c16d4f442e70bb Mon Sep 17 00:00:00 2001 From: Gustavo Chain Date: Wed, 21 Aug 2019 14:27:38 +0200 Subject: [PATCH 2/3] go-kosu: RPC test refactor --- packages/go-kosu/rpc/rpc_test.go | 89 +++++++++++++++----------------- 1 file changed, 42 insertions(+), 47 deletions(-) diff --git a/packages/go-kosu/rpc/rpc_test.go b/packages/go-kosu/rpc/rpc_test.go index ea4eb738..e8c550a5 100644 --- a/packages/go-kosu/rpc/rpc_test.go +++ b/packages/go-kosu/rpc/rpc_test.go @@ -16,35 +16,45 @@ import ( "github.com/tendermint/tendermint/libs/db" ) -func newServerClient(t *testing.T) (*abci.App, *rpc.Client, func()) { - app, closer := tests.StartServer(t, db.NewMemDB()) - appClient, err := app.NewClient() - require.NoError(t, err) - client := rpc.DialInProc( - NewServer(appClient), - ) +func TestRPC(t *testing.T) { + cases := []struct { + name string + run func(*testing.T, *abci.App, *abci.Client, *rpc.Client) + }{ + {"LatestHeight", LatestHeight}, + {"AddOrders", AddOrders}, + {"RebalancePeriod", RebalancePeriod}, + {"NewRebalances", NewRebalances}, + {"NumberPosters", NumberPosters}, + } - return app, client, closer -} + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + app, closer := tests.StartServer(t, db.NewMemDB()) + defer closer() -func TestRPCLatestHeight(t *testing.T) { - _, closer := tests.StartServer(t, db.NewMemDB()) - defer closer() - client := rpc.DialInProc( - NewServer( - abci.NewHTTPClient("http://localhost:26657", nil), - ), - ) + appClient, err := app.NewClient() + require.NoError(t, err) + defer appClient.Stop() + + rpcClient := rpc.DialInProc(NewServer(appClient)) + defer rpcClient.Close() + + test.run(t, app, appClient, rpcClient) + }) + } +} +func LatestHeight(t *testing.T, _ *abci.App, _ *abci.Client, rpcClient *rpc.Client) { var latest uint64 // Get the initial (prior the first block is mined) - require.NoError(t, client.Call(&latest, "kosu_latestHeight")) + require.NoError(t, rpcClient.Call(&latest, "kosu_latestHeight")) assert.EqualValues(t, 0, latest) ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) fn := func(_ interface{}) { // this is invoked when a block is mined - require.NoError(t, client.Call(&latest, "kosu_latestHeight")) + require.NoError(t, rpcClient.Call(&latest, "kosu_latestHeight")) assert.EqualValues(t, 1, latest) cancel() } @@ -52,9 +62,9 @@ func TestRPCLatestHeight(t *testing.T) { ch := make(chan interface{}) defer close(ch) - sub, err := client.Subscribe(ctx, "kosu", ch, "newBlocks") - defer sub.Unsubscribe() + sub, err := rpcClient.Subscribe(ctx, "kosu", ch, "newBlocks") require.NoError(t, err) + defer sub.Unsubscribe() for { select { @@ -68,10 +78,7 @@ func TestRPCLatestHeight(t *testing.T) { } } -func TestAddOrders(t *testing.T) { - app, client, closer := newServerClient(t) - defer closer() - +func AddOrders(t *testing.T, app *abci.App, appClient *abci.Client, rpcClient *rpc.Client) { // nolint:lll validTx := &types.TransactionOrder{ SubContract: "0xebe8fdf63db77e3b41b0aec8208c49fa46569606", @@ -94,7 +101,7 @@ func TestAddOrders(t *testing.T) { params := []interface{}{validTx, invalidTx} result := &AddOrdersResult{} - err := client.Call(result, "kosu_addOrders", params) + err := rpcClient.Call(result, "kosu_addOrders", params) require.NoError(t, err) assert.Len(t, result.Accepted, 1) @@ -111,42 +118,33 @@ func newTestRebalanceTx(number, starts uint64) *types.TransactionRebalance { } } -func TestRebalancePeriod(t *testing.T) { - app, rpc, closer := newServerClient(t) - defer closer() - - client, err := app.NewClient() - require.NoError(t, err) - +func RebalancePeriod(t *testing.T, _ *abci.App, appClient *abci.Client, rpcClient *rpc.Client) { tx := newTestRebalanceTx(1, 10) - res, err := client.BroadcastTxCommit(tx) + res, err := appClient.BroadcastTxCommit(tx) require.NoError(t, err) require.True(t, res.DeliverTx.IsOK()) var result types.RoundInfo require.NoError(t, - rpc.Call(&result, "kosu_roundInfo"), + rpcClient.Call(&result, "kosu_roundInfo"), ) assert.Equal(t, tx.RoundInfo.String(), result.String()) } -func TestNewRebalances(t *testing.T) { - app, rpc, closer := newServerClient(t) - defer closer() - +func NewRebalances(t *testing.T, app *abci.App, appClient *abci.Client, rpcClient *rpc.Client) { ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) defer cancel() ch := make(chan *types.TransactionRebalance) - sub, err := rpc.Subscribe(ctx, "kosu", ch, "newRebalances") + sub, err := rpcClient.Subscribe(ctx, "kosu", ch, "newRebalances") require.NoError(t, err) + defer sub.Unsubscribe() - client, err := app.NewClient() require.NoError(t, err) tx := newTestRebalanceTx(1, 10) - res, err := client.BroadcastTxSync(tx) + res, err := appClient.BroadcastTxSync(tx) require.NoError(t, err) require.Zero(t, res.Code, res.Log) @@ -160,10 +158,7 @@ func TestNewRebalances(t *testing.T) { } } -func TestNumberPosters(t *testing.T) { - app, rpc, closer := newServerClient(t) - defer closer() - +func NumberPosters(t *testing.T, app *abci.App, _ *abci.Client, rpcClient *rpc.Client) { addresses := []string{ "0x0000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000002", @@ -178,7 +173,7 @@ func TestNumberPosters(t *testing.T) { } var num uint64 - err := rpc.Call(&num, "kosu_numberPosters") + err := rpcClient.Call(&num, "kosu_numberPosters") require.NoError(t, err) assert.EqualValues(t, len(addresses), num) From 1649148306d92b3aa4565f27fc3dd5ccb1ae6e29 Mon Sep 17 00:00:00 2001 From: Gustavo Chain Date: Thu, 22 Aug 2019 13:27:16 +0200 Subject: [PATCH 3/3] go-kosu: implement network explorer methods --- packages/go-kosu/abci/client.go | 35 ++++++------- packages/go-kosu/docs/kosu_rpc.md | 31 ++++++++++++ packages/go-kosu/rpc/rpc_test.go | 68 +++++++++++++++----------- packages/go-kosu/rpc/service.go | 46 ++++++++++++++--- packages/go-kosu/store/cosmos/store.go | 21 ++++++++ 5 files changed, 145 insertions(+), 56 deletions(-) diff --git a/packages/go-kosu/abci/client.go b/packages/go-kosu/abci/client.go index da3dbb36..881a89a2 100644 --- a/packages/go-kosu/abci/client.go +++ b/packages/go-kosu/abci/client.go @@ -4,13 +4,13 @@ import ( "context" "errors" - "github.com/gogo/protobuf/proto" "github.com/tendermint/tendermint/libs/pubsub/query" "github.com/tendermint/tendermint/rpc/client" rpctypes "github.com/tendermint/tendermint/rpc/core/types" tmtypes "github.com/tendermint/tendermint/types" "go-kosu/abci/types" + "go-kosu/store" ) var ( @@ -23,12 +23,13 @@ var ( type Client struct { client.Client key []byte + cdc store.Codec } // NewClient returns a new Client type. // Key is the private key used to sign transactions. func NewClient(c client.Client, key []byte) *Client { - return &Client{Client: c, key: key} + return &Client{Client: c, key: key, cdc: store.DefaultCodec} } // NewHTTPClient calls NewClient using a HTTPClient as ABCClient @@ -121,7 +122,7 @@ func (c *Client) Unsubscribe(ctx context.Context, query string) error { // QueryRoundInfo performs a ABCIQuery to "/roundinfo" func (c *Client) QueryRoundInfo() (*types.RoundInfo, error) { var pb types.RoundInfo - if err := c.query("/chain/key", []byte("roundinfo"), &pb); err != nil { + if err := c.Query("/chain/key", []byte("roundinfo"), &pb); err != nil { return nil, err } @@ -131,7 +132,7 @@ func (c *Client) QueryRoundInfo() (*types.RoundInfo, error) { // QueryConsensusParams performs a ABCI Query to "/consensusparams" func (c *Client) QueryConsensusParams() (*types.ConsensusParams, error) { var pb types.ConsensusParams - if err := c.query("/chain/key", []byte("consensusparams"), &pb); err != nil { + if err := c.Query("/chain/key", []byte("consensusparams"), &pb); err != nil { return nil, err } @@ -141,7 +142,7 @@ func (c *Client) QueryConsensusParams() (*types.ConsensusParams, error) { // QueryPoster performs a ABCI Query to "/posters/" func (c *Client) QueryPoster(addr string) (*types.Poster, error) { var pb types.Poster - if err := c.query("/poster/key", []byte(addr), &pb); err != nil { + if err := c.Query("/poster/key", []byte(addr), &pb); err != nil { return nil, err } @@ -151,7 +152,7 @@ func (c *Client) QueryPoster(addr string) (*types.Poster, error) { // QueryValidator performs a ABCI Query to "/validator/" func (c *Client) QueryValidator(addr string) (*types.Validator, error) { var pb types.Validator - if err := c.query("/validator/key", []byte(addr), &pb); err != nil { + if err := c.Query("/validator/key", []byte(addr), &pb); err != nil { return nil, err } @@ -160,25 +161,17 @@ func (c *Client) QueryValidator(addr string) (*types.Validator, error) { // QueryTotalOrders performs a ABCI Query to "/chain/totalorders" func (c *Client) QueryTotalOrders() (uint64, error) { - out, err := c.ABCIQuery("/chain/key", []byte("totalorders")) - if err != nil { + var num uint64 + if err := c.Query("/chain/key", []byte("totalorders"), &num); err != nil { return 0, err } - res := out.Response - if res.IsErr() { - return 0, errors.New(res.GetLog()) - } - - if len(res.Value) == 0 { - return 0, errors.New("empty") - } - - pb := proto.NewBuffer(res.Value) - return pb.DecodeFixed64() + return num, nil } -func (c *Client) query(path string, data []byte, pb proto.Message) error { +// Query is a generic query interface. +// It will use the store.DefaultCodec codec to decode the `response.Value`. +func (c *Client) Query(path string, data []byte, v interface{}) error { out, err := c.ABCIQuery(path, data) if err != nil { return err @@ -193,5 +186,5 @@ func (c *Client) query(path string, data []byte, pb proto.Message) error { return ErrNotFound } - return proto.Unmarshal(res.Value, pb) + return c.cdc.Decode(res.Value, v) } diff --git a/packages/go-kosu/docs/kosu_rpc.md b/packages/go-kosu/docs/kosu_rpc.md index 37034f45..609701b4 100644 --- a/packages/go-kosu/docs/kosu_rpc.md +++ b/packages/go-kosu/docs/kosu_rpc.md @@ -129,6 +129,16 @@ curl -X POST --data '{"jsonrpc":"2.0","method":"kosu_latestHeight", "id": 1}' lo { "jsonrpc": "2.0", "id": 1, "result": 260 } ``` +### _NumberPosters_ + +NumberPosters returns the number of poster accounts + +_Parameters:_ + +_Returns:_ + +- `number` - _uint64_ + ### _QueryPoster_ QueryPoster returns a poster given its address. @@ -199,6 +209,16 @@ curl -X POST localhost:14341 \ } ``` +### _RemainingLimit_ + +RemainingLimit returns the sum of all the poster's limit. + +_Parameters:_ + +_Returns:_ + +- `number` - _uint64_ + ### _RoundInfo_ RoundInfo returns the current `RoundInfo`. @@ -224,6 +244,17 @@ curl -X POST localhost:14341 \ { "jsonrpc": "2.0", "id": 1, "result": { "number": 48, "starts_at": 2613, "ends_at": 2623, "limit": 10 } } ``` +### _TotalOrders_ + +TotalOrders returns the total number of orders in the system. +This number is incremented each time one submits a new valid order + +_Parameters:_ + +_Returns:_ + +- `number` - _uint64_ + ### _Validators_ Validators returns the full validator set diff --git a/packages/go-kosu/rpc/rpc_test.go b/packages/go-kosu/rpc/rpc_test.go index e8c550a5..eacf9fa6 100644 --- a/packages/go-kosu/rpc/rpc_test.go +++ b/packages/go-kosu/rpc/rpc_test.go @@ -16,6 +16,21 @@ import ( "github.com/tendermint/tendermint/libs/db" ) +func waitForNewBlock(t *testing.T, client *rpc.Client) { + ch := make(chan interface{}) + defer close(ch) + + sub, err := client.Subscribe(context.Background(), "kosu", ch, "newBlocks") + require.NoError(t, err) + defer sub.Unsubscribe() + + select { + case err := <-sub.Err(): + t.Error(err) + case <-ch: + } +} + func TestRPC(t *testing.T) { cases := []struct { name string @@ -35,7 +50,7 @@ func TestRPC(t *testing.T) { appClient, err := app.NewClient() require.NoError(t, err) - defer appClient.Stop() + defer appClient.Stop() // nolint:errcheck rpcClient := rpc.DialInProc(NewServer(appClient)) defer rpcClient.Close() @@ -51,31 +66,9 @@ func LatestHeight(t *testing.T, _ *abci.App, _ *abci.Client, rpcClient *rpc.Clie require.NoError(t, rpcClient.Call(&latest, "kosu_latestHeight")) assert.EqualValues(t, 0, latest) - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - fn := func(_ interface{}) { - // this is invoked when a block is mined - require.NoError(t, rpcClient.Call(&latest, "kosu_latestHeight")) - assert.EqualValues(t, 1, latest) - cancel() - } - - ch := make(chan interface{}) - defer close(ch) - - sub, err := rpcClient.Subscribe(ctx, "kosu", ch, "newBlocks") - require.NoError(t, err) - defer sub.Unsubscribe() - - for { - select { - case <-ctx.Done(): - return - case err := <-sub.Err(): - t.Error(err) - case e := <-ch: - fn(e) - } - } + waitForNewBlock(t, rpcClient) + require.NoError(t, rpcClient.Call(&latest, "kosu_latestHeight")) + assert.EqualValues(t, 1, latest) } func AddOrders(t *testing.T, app *abci.App, appClient *abci.Client, rpcClient *rpc.Client) { @@ -94,18 +87,35 @@ func AddOrders(t *testing.T, app *abci.App, appClient *abci.Client, rpcClient *r } // this poster address is generated out of the validTx - app.Store().SetPoster("0x02fbf1aa49bc3b9631e8e96572935a5894879724", types.Poster{ + poster := types.Poster{ Balance: types.NewBigIntFromInt(100), - }) + Limit: 10, + } + app.Store().SetPoster("0x02fbf1aa49bc3b9631e8e96572935a5894879724", poster) + + var remaining uint64 + err := rpcClient.Call(&remaining, "kosu_remainingLimit") + require.NoError(t, err) + assert.Equal(t, poster.Limit, remaining) params := []interface{}{validTx, invalidTx} result := &AddOrdersResult{} - err := rpcClient.Call(result, "kosu_addOrders", params) + err = rpcClient.Call(result, "kosu_addOrders", params) require.NoError(t, err) assert.Len(t, result.Accepted, 1) assert.Len(t, result.Rejected, 1) + + waitForNewBlock(t, rpcClient) + var total uint64 + err = rpcClient.Call(&total, "kosu_totalOrders") + require.NoError(t, err) + assert.EqualValues(t, 1, total) + + err = rpcClient.Call(&remaining, "kosu_remainingLimit") + require.NoError(t, err) + assert.Equal(t, poster.Limit-1, remaining) } func newTestRebalanceTx(number, starts uint64) *types.TransactionRebalance { diff --git a/packages/go-kosu/rpc/service.go b/packages/go-kosu/rpc/service.go index 3d6633c4..a9bc41d1 100644 --- a/packages/go-kosu/rpc/service.go +++ b/packages/go-kosu/rpc/service.go @@ -5,7 +5,6 @@ import ( "encoding/hex" "go-kosu/abci" "go-kosu/abci/types" - "go-kosu/store" "log" "strings" @@ -690,20 +689,55 @@ func (s *Service) QueryPoster(addr string) (*types.Poster, error) { return s.abci.QueryPoster(addr) } -// TotalOrders ... +/* +TotalOrders returns the total number of orders in the system. +This number is incremented each time one submits a new valid order + +_Parameters:_ + +_Returns:_ + +- `number` - _uint64_ + +*/ func (s *Service) TotalOrders() (uint64, error) { return s.abci.QueryTotalOrders() } -// NumberPosters ... +/* +NumberPosters returns the number of poster accounts + +_Parameters:_ + +_Returns:_ + +- `number` - _uint64_ + +*/ func (s *Service) NumberPosters() (uint64, error) { - res, err := s.abci.ABCIQuery("/poster/number", nil) - if err != nil { + var num uint64 + + if err := s.abci.Query("/poster/number", nil, &num); err != nil { return 0, err } + return num, nil +} + +/* +RemainingLimit returns the sum of all the poster's limit. + +_Parameters:_ + +_Returns:_ + +- `number` - _uint64_ + +*/ +func (s *Service) RemainingLimit() (uint64, error) { var num uint64 - if err := store.DefaultCodec.Decode(res.Response.Value, &num); err != nil { + + if err := s.abci.Query("/poster/remaininglimit", nil, &num); err != nil { return 0, err } diff --git a/packages/go-kosu/store/cosmos/store.go b/packages/go-kosu/store/cosmos/store.go index 45b0af52..bd11dce3 100644 --- a/packages/go-kosu/store/cosmos/store.go +++ b/packages/go-kosu/store/cosmos/store.go @@ -106,9 +106,13 @@ func (s *Store) LastCommitID() cosmos.CommitID { return s.cms.LastCommitID() } // Query wrap the rootmulti.Query method func (s *Store) Query(req abci.RequestQuery) abci.ResponseQuery { + // TODO: move the switch to a sensible place (perhaps ./store or ./abci) + // this should not be local to the store implementation switch req.Path { case "/poster/number": return s.queryPosterNumber() + case "/poster/remaininglimit": + return s.queryPosterRemainingLimit() } return s.cms.Query(req) } @@ -130,6 +134,23 @@ func (s *Store) queryPosterNumber() (resp abci.ResponseQuery) { return resp } +func (s *Store) queryPosterRemainingLimit() (resp abci.ResponseQuery) { + var num uint64 + s.IteratePosters(func(_ string, p *types.Poster) { + num += p.Limit + }) + + buf, err := s.codec.Encode(num) + if err != nil { + resp.Code = 1 + resp.Log = err.Error() + } else { + resp.Value = buf + } + + return resp +} + // Codec returns the storage codec func (s *Store) Codec() store.Codec { return s.codec }