diff --git a/api/grpcserver/v2alpha1/account.go b/api/grpcserver/v2alpha1/account.go index ae4887e27a..1c090f5d02 100644 --- a/api/grpcserver/v2alpha1/account.go +++ b/api/grpcserver/v2alpha1/account.go @@ -52,11 +52,6 @@ func (s *AccountService) List( ctx context.Context, request *spacemeshv2alpha1.AccountRequest, ) (*spacemeshv2alpha1.AccountList, error) { - ops, err := toAccountOperations(request) - if err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) - } - switch { case request.Limit > 100: return nil, status.Error(codes.InvalidArgument, "limit is capped at 100") @@ -64,6 +59,11 @@ func (s *AccountService) List( return nil, status.Error(codes.InvalidArgument, "limit must be set to <= 100") } + ops, err := toAccountOperations(request) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + rst := make([]*spacemeshv2alpha1.Account, 0, request.Limit) if err := accounts.IterateAccountsOps(s.db, ops, func(account *types.Account) bool { counterProjected, balanceProjected := s.conState.GetProjection(account.Address) @@ -83,7 +83,14 @@ func (s *AccountService) List( }); err != nil { return nil, status.Error(codes.Internal, err.Error()) } - return &spacemeshv2alpha1.AccountList{Accounts: rst}, nil + + ops.Modifiers = nil + count, err := accounts.CountAccountsByOps(s.db, ops) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &spacemeshv2alpha1.AccountList{Accounts: rst, Total: count}, nil } func toAccountOperations(filter *spacemeshv2alpha1.AccountRequest) (builder.Operations, error) { diff --git a/api/grpcserver/v2alpha1/account_test.go b/api/grpcserver/v2alpha1/account_test.go index 3d8684d5bc..3739de03b7 100644 --- a/api/grpcserver/v2alpha1/account_test.go +++ b/api/grpcserver/v2alpha1/account_test.go @@ -97,12 +97,14 @@ func TestAccountService_List(t *testing.T) { require.NoError(t, err) fmt.Println(list) require.Len(t, list.Accounts, 25) + require.Equal(t, len(accs), int(list.Total)) }) t.Run("all", func(t *testing.T) { list, err := client.List(ctx, &spacemeshv2alpha1.AccountRequest{Limit: 100}) require.NoError(t, err) require.Len(t, list.Accounts, len(accs)) + require.Equal(t, len(accs), int(list.Total)) }) t.Run("address", func(t *testing.T) { diff --git a/api/grpcserver/v2alpha1/activation.go b/api/grpcserver/v2alpha1/activation.go index 2025596ae2..fc8fd2f424 100644 --- a/api/grpcserver/v2alpha1/activation.go +++ b/api/grpcserver/v2alpha1/activation.go @@ -179,17 +179,19 @@ func (s *ActivationService) List( ctx context.Context, request *spacemeshv2alpha1.ActivationRequest, ) (*spacemeshv2alpha1.ActivationList, error) { - ops, err := toAtxOperations(request) - if err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) - } - // every full atx is ~1KB. 100 atxs is ~100KB. switch { case request.Limit > 100: return nil, status.Error(codes.InvalidArgument, "limit is capped at 100") case request.Limit == 0: return nil, status.Error(codes.InvalidArgument, "limit must be set to <= 100") } + + ops, err := toAtxOperations(request) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + // every full atx is ~1KB. 100 atxs is ~100KB. rst := make([]*spacemeshv2alpha1.Activation, 0, request.Limit) if err := atxs.IterateAtxsOps(s.db, ops, func(atx *types.ActivationTx) bool { rst = append(rst, toAtx(atx)) @@ -197,7 +199,14 @@ func (s *ActivationService) List( }); err != nil { return nil, status.Error(codes.Internal, err.Error()) } - return &spacemeshv2alpha1.ActivationList{Activations: rst}, nil + + ops.Modifiers = nil + count, err := atxs.CountAtxsByOps(s.db, ops) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &spacemeshv2alpha1.ActivationList{Activations: rst, Total: count}, nil } func (s *ActivationService) ActivationsCount( diff --git a/api/grpcserver/v2alpha1/activation_test.go b/api/grpcserver/v2alpha1/activation_test.go index 851c0bb432..02d70648eb 100644 --- a/api/grpcserver/v2alpha1/activation_test.go +++ b/api/grpcserver/v2alpha1/activation_test.go @@ -67,12 +67,14 @@ func TestActivationService_List(t *testing.T) { }) require.NoError(t, err) require.Len(t, list.Activations, 25) + require.Equal(t, len(activations), int(list.Total)) }) t.Run("all", func(t *testing.T) { list, err := client.List(ctx, &spacemeshv2alpha1.ActivationRequest{Limit: 100}) require.NoError(t, err) require.Equal(t, len(activations), len(list.Activations)) + require.Equal(t, len(activations), int(list.Total)) }) t.Run("coinbase", func(t *testing.T) { diff --git a/api/grpcserver/v2alpha1/layer.go b/api/grpcserver/v2alpha1/layer.go index 3650ba2e4f..a9d7c5c4ca 100644 --- a/api/grpcserver/v2alpha1/layer.go +++ b/api/grpcserver/v2alpha1/layer.go @@ -194,11 +194,6 @@ func (s *LayerService) List( ctx context.Context, request *spacemeshv2alpha1.LayerRequest, ) (*spacemeshv2alpha1.LayerList, error) { - ops, err := toLayerOperations(request) - if err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) - } - switch { case request.Limit > 100: return nil, status.Error(codes.InvalidArgument, "limit is capped at 100") @@ -206,6 +201,11 @@ func (s *LayerService) List( return nil, status.Error(codes.InvalidArgument, "limit must be set to <= 100") } + ops, err := toLayerOperations(request) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + rst := make([]*spacemeshv2alpha1.Layer, 0, request.Limit) var derr error if err := layers.IterateLayersWithBlockOps(s.db, ops, func(layer *layers.Layer) bool { @@ -218,7 +218,13 @@ func (s *LayerService) List( return nil, derr } - return &spacemeshv2alpha1.LayerList{Layers: rst}, nil + ops.Modifiers = nil + count, err := layers.CountLayersByOps(s.db, ops) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &spacemeshv2alpha1.LayerList{Layers: rst, Total: count}, nil } func toLayerOperations(filter *spacemeshv2alpha1.LayerRequest) (builder.Operations, error) { diff --git a/api/grpcserver/v2alpha1/layer_test.go b/api/grpcserver/v2alpha1/layer_test.go index bfc082b068..0f6588ab3e 100644 --- a/api/grpcserver/v2alpha1/layer_test.go +++ b/api/grpcserver/v2alpha1/layer_test.go @@ -70,6 +70,7 @@ func TestLayerService_List(t *testing.T) { }) require.NoError(t, err) require.Len(t, list.Layers, 25) + require.Equal(t, len(lrs), int(list.Total)) }) t.Run("all", func(t *testing.T) { @@ -80,6 +81,7 @@ func TestLayerService_List(t *testing.T) { }) require.NoError(t, err) require.Len(t, lrs, len(ls.Layers)) + require.Equal(t, len(lrs), int(ls.Total)) }) } diff --git a/api/grpcserver/v2alpha1/reward.go b/api/grpcserver/v2alpha1/reward.go index da3459b64b..4e71a72cae 100644 --- a/api/grpcserver/v2alpha1/reward.go +++ b/api/grpcserver/v2alpha1/reward.go @@ -187,7 +187,14 @@ func (s *RewardService) List( }); err != nil { return nil, status.Error(codes.Internal, err.Error()) } - return &spacemeshv2alpha1.RewardList{Rewards: rst}, nil + + ops.Modifiers = nil + count, err := rewards.CountRewardsByOps(s.db, ops) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &spacemeshv2alpha1.RewardList{Rewards: rst, Total: count}, nil } func toRewardRequest(filter *spacemeshv2alpha1.RewardStreamRequest) *spacemeshv2alpha1.RewardRequest { diff --git a/api/grpcserver/v2alpha1/reward_test.go b/api/grpcserver/v2alpha1/reward_test.go index 50952097d1..d5ba38c710 100644 --- a/api/grpcserver/v2alpha1/reward_test.go +++ b/api/grpcserver/v2alpha1/reward_test.go @@ -65,12 +65,14 @@ func TestRewardService_List(t *testing.T) { }) require.NoError(t, err) require.Len(t, list.Rewards, 25) + require.Equal(t, len(rwds), int(list.Total)) }) t.Run("all", func(t *testing.T) { list, err := client.List(ctx, &spacemeshv2alpha1.RewardRequest{Limit: 100}) require.NoError(t, err) require.Len(t, rwds, len(list.Rewards)) + require.Equal(t, len(rwds), int(list.Total)) }) t.Run("coinbase", func(t *testing.T) { diff --git a/api/grpcserver/v2alpha1/transaction.go b/api/grpcserver/v2alpha1/transaction.go index de09133217..3682b4e813 100644 --- a/api/grpcserver/v2alpha1/transaction.go +++ b/api/grpcserver/v2alpha1/transaction.go @@ -136,7 +136,13 @@ func (s *TransactionService) List( return nil, status.Error(codes.Internal, err.Error()) } - return &spacemeshv2alpha1.TransactionList{Transactions: rst}, nil + ops.Modifiers = nil + count, err := transactions.CountTransactionsByOps(s.db, ops) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &spacemeshv2alpha1.TransactionList{Transactions: rst, Total: count}, nil } func (s *TransactionService) ParseTransaction( diff --git a/api/grpcserver/v2alpha1/transaction_test.go b/api/grpcserver/v2alpha1/transaction_test.go index bb30d32361..4ef14d72b5 100644 --- a/api/grpcserver/v2alpha1/transaction_test.go +++ b/api/grpcserver/v2alpha1/transaction_test.go @@ -79,12 +79,14 @@ func TestTransactionService_List(t *testing.T) { list, err := client.List(ctx, &spacemeshv2alpha1.TransactionRequest{Limit: 25, Offset: 50}) require.NoError(t, err) require.Len(t, list.Transactions, 25) + require.Equal(t, len(txsList), int(list.Total)) }) t.Run("all", func(t *testing.T) { list, err := client.List(ctx, &spacemeshv2alpha1.TransactionRequest{Limit: 100}) require.NoError(t, err) require.Len(t, list.Transactions, len(txsList)) + require.Equal(t, len(txsList), int(list.Total)) }) t.Run("address", func(t *testing.T) { diff --git a/go.mod b/go.mod index dd0db27708..1e79e65fca 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/rs/cors v1.11.0 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 github.com/seehuhn/mt19937 v1.0.0 - github.com/spacemeshos/api/release/go v1.45.0 + github.com/spacemeshos/api/release/go v1.46.0 github.com/spacemeshos/economics v0.1.3 github.com/spacemeshos/fixed v0.1.1 github.com/spacemeshos/go-scale v1.2.0 diff --git a/go.sum b/go.sum index b0506d7de2..60efd90966 100644 --- a/go.sum +++ b/go.sum @@ -556,8 +556,8 @@ github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:Udh github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= -github.com/spacemeshos/api/release/go v1.45.0 h1:w8Ra0mjphMxiHMeFQoVn4AudsV+JO3NmpeKL25H74HA= -github.com/spacemeshos/api/release/go v1.45.0/go.mod h1:8pxGN6/di8iBpQReiOgY+Cppi7bhJ+qJ3QiRQtJfoag= +github.com/spacemeshos/api/release/go v1.46.0 h1:wm+VpTwP1mvCligEwgPo1ykDKY/omPCJtGYwatKLaE8= +github.com/spacemeshos/api/release/go v1.46.0/go.mod h1:8pxGN6/di8iBpQReiOgY+Cppi7bhJ+qJ3QiRQtJfoag= github.com/spacemeshos/economics v0.1.3 h1:ACkq3mTebIky4Zwbs9SeSSRZrUCjU/Zk0wq9Z0BTh2A= github.com/spacemeshos/economics v0.1.3/go.mod h1:FH7u0FzTIm6Kpk+X5HOZDvpkgNYBKclmH86rVwYaDAo= github.com/spacemeshos/fixed v0.1.1 h1:N1y4SUpq1EV+IdJrWJwUCt1oBFzeru/VKVcBsvPc2Fk= diff --git a/sql/accounts/accounts.go b/sql/accounts/accounts.go index 6e4a425135..e5a1e7731f 100644 --- a/sql/accounts/accounts.go +++ b/sql/accounts/accounts.go @@ -171,6 +171,18 @@ func Revert(db sql.Executor, after types.LayerID) error { return nil } +func CountAccountsByOps(db sql.Executor, operations builder.Operations) (count uint32, err error) { + _, err = db.Exec( + "SELECT COUNT(DISTINCT address) FROM accounts"+builder.FilterFrom(operations), + builder.BindingsFrom(operations), + func(stmt *sql.Statement) bool { + count = uint32(stmt.ColumnInt32(0)) + return true + }, + ) + return +} + func IterateAccountsOps( db sql.Executor, operations builder.Operations, diff --git a/sql/layers/layers.go b/sql/layers/layers.go index 5ba9116808..e8dac5aea4 100644 --- a/sql/layers/layers.go +++ b/sql/layers/layers.go @@ -319,6 +319,19 @@ type Layer struct { Block *types.Block } +func CountLayersByOps( + db sql.Executor, + operations builder.Operations, +) (count uint32, err error) { + _, err = db.Exec(`select count(*) from layers l`+builder.FilterFrom(operations), + builder.BindingsFrom(operations), + func(stmt *sql.Statement) bool { + count = uint32(stmt.ColumnInt32(0)) + return true + }) + return +} + func IterateLayersWithBlockOps( db sql.Executor, operations builder.Operations, diff --git a/sql/rewards/rewards.go b/sql/rewards/rewards.go index 279d628b48..2398b72536 100644 --- a/sql/rewards/rewards.go +++ b/sql/rewards/rewards.go @@ -111,6 +111,19 @@ func ListBySmesherId(db sql.Executor, smesherID types.NodeID) (rst []*types.Rewa return ListByKey(db, nil, &smesherID) } +func CountRewardsByOps( + db sql.Executor, + operations builder.Operations, +) (count uint32, err error) { + _, err = db.Exec(`select count(*) from rewards`+builder.FilterFrom(operations), + builder.BindingsFrom(operations), + func(stmt *sql.Statement) bool { + count = uint32(stmt.ColumnInt32(0)) + return true + }) + return +} + func IterateRewardsOps( db sql.Executor, operations builder.Operations, diff --git a/sql/transactions/transactions.go b/sql/transactions/transactions.go index f0830e8e3a..79b08d153b 100644 --- a/sql/transactions/transactions.go +++ b/sql/transactions/transactions.go @@ -412,6 +412,21 @@ func TransactionInBlock( return bid, rst, nil } +func CountTransactionsByOps( + db sql.Executor, + operations builder.Operations, +) (count uint32, err error) { + _, err = db.Exec(`select COUNT(DISTINCT tx) + from transactions + left join transactions_results_addresses on id=tid`+builder.FilterFrom(operations), + builder.BindingsFrom(operations), + func(stmt *sql.Statement) bool { + count = uint32(stmt.ColumnInt32(0)) + return true + }) + return +} + func IterateTransactionsOps( db sql.Executor, operations builder.Operations,