Skip to content

Commit f64303b

Browse files
authored
Search for online accounts (#1646)
* Search for online accounts in `GET /v2/accounts` * Fix typo * Fix an issue in the `online-only` filter Fix an issue in the underlying SQL query for the `online-only` parameter. The previous SQL query was returning online accounts with null key material. This commit filters out those accounts - only accounts with non-null key material are returned. * Style fixes * Add e2e test coverage for `online-only` parameter * Disable `online-only` parameter by default. In the endpoint `GET /v2/accounts`, disable the `online-only` parameter by default. * Remove unnecesary code * Increase test coverage for `online-only` param * Style fixes * Update disabled parameter list in README.md * Update param list in `DisablingParametersGuide.md` * Remove duplicated code * Add extra parenthesis in SQL expression * Add missing table alias in SQL query * Improve readability in e2e test * Remove unnecessary comments * Add comments to e2e test * Update misleading comment
1 parent 0845773 commit f64303b

14 files changed

+545
-408
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ Below is a snippet of the output from `algorand-indexer api-config`:
110110
optional:
111111
- currency-greater-than: disabled
112112
- currency-less-than: disabled
113+
- online-only: disabled
113114
/v2/assets/{asset-id}/transactions:
114115
optional:
115116
- note-prefix: disabled

api/disabled_parameters.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ func GetDefaultDisabledMapConfigForPostgres() *DisabledMapConfig {
317317
rval.addEntry(restPath, http.MethodGet, parameterNames)
318318
}
319319

320-
get("/v2/accounts", []string{"currency-greater-than", "currency-less-than"})
320+
get("/v2/accounts", []string{"currency-greater-than", "currency-less-than", "online-only"})
321321
get("/v2/accounts/{account-id}/transactions", []string{"note-prefix", "tx-type", "sig-type", "asset-id", "before-time", "after-time", "rekey-to"})
322322
get("/v2/assets", []string{"name", "unit"})
323323
get("/v2/assets/{asset-id}/balances", []string{"currency-greater-than", "currency-less-than"})

api/error_messages.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ const (
3838
ErrMultipleBoxes = "multiple application boxes found for this app id and box name, please contact us, this shouldn't happen"
3939
ErrFailedLookingUpBoxes = "failed while looking up application boxes"
4040
errMultiAcctRewind = "multiple accounts rewind is not supported by this server"
41+
errOnlineOnlyRewind = "simultaneously rewinding and searching for online accounts is not supported"
42+
errOnlineOnlyDeleted = "simultaneously searching for online and deleted accounts is not supported"
4143
errRewindingAccount = "error while rewinding account"
4244
errLookingUpBlockForRound = "error while looking up block for round"
4345
errBlockHeaderSearch = "error while searching for block headers"

api/generated/common/routes.go

Lines changed: 170 additions & 169 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/generated/common/types.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/generated/v2/routes.go

Lines changed: 244 additions & 236 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/generated/v2/types.go

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/handlers.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,14 @@ func (si *ServerImplementation) SearchForAccounts(ctx echo.Context, params gener
415415
return badRequest(ctx, errMultiAcctRewind)
416416
}
417417

418+
// Input validations related to the "online-only" parameter
419+
if params.Round != nil && boolOrDefault(params.OnlineOnly) {
420+
return badRequest(ctx, errOnlineOnlyRewind)
421+
}
422+
if boolOrDefault(params.OnlineOnly) && boolOrDefault(params.IncludeAll) {
423+
return badRequest(ctx, errOnlineOnlyDeleted)
424+
}
425+
418426
var spendingAddrBytes []byte
419427
if params.AuthAddr != nil {
420428
spendingAddr, err := sdk.DecodeAddress(*params.AuthAddr)
@@ -435,6 +443,7 @@ func (si *ServerImplementation) SearchForAccounts(ctx echo.Context, params gener
435443
EqualToAuthAddr: spendingAddrBytes,
436444
IncludeDeleted: boolOrDefault(params.IncludeAll),
437445
MaxResources: si.opts.MaxAPIResourcesPerAccount,
446+
OnlineOnly: boolOrDefault(params.OnlineOnly),
438447
}
439448

440449
if params.Exclude != nil {

api/handlers_e2e_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,6 +1147,80 @@ func TestVersion(t *testing.T) {
11471147
require.Equal(t, response.Version, "(unknown version)")
11481148
}
11491149

1150+
// TestAccountsOnlineOnlyParam exercises the `online-only` parameter in `GET /v2/accounts`.
1151+
func TestAccountsOnlineOnlyParam(t *testing.T) {
1152+
1153+
// Spin a fresh database
1154+
db, shutdownFunc := setupIdb(t, test.MakeGenesis())
1155+
defer shutdownFunc()
1156+
1157+
// Load a block (with a keyreg txn) into the database
1158+
vb, err := test.ReadValidatedBlockFromFile("test_resources/validated_blocks/KeyregTransactionWithStateProofKeys.vb")
1159+
require.NoError(t, err)
1160+
err = db.AddBlock(&vb)
1161+
require.NoError(t, err)
1162+
api := &ServerImplementation{db: db}
1163+
1164+
e := echo.New()
1165+
{
1166+
//////////
1167+
// When // We query for only online accounts
1168+
//////////
1169+
req := httptest.NewRequest(http.MethodGet, "/", nil)
1170+
rec := httptest.NewRecorder()
1171+
c := e.NewContext(req, rec)
1172+
c.SetPath("/v2/accounts")
1173+
err = api.SearchForAccounts(c, generated.SearchForAccountsParams{OnlineOnly: boolPtr(true)})
1174+
//////////
1175+
// Then // Only AccountA should be returned
1176+
//////////
1177+
require.NoError(t, err)
1178+
require.Equal(t, http.StatusOK, rec.Code)
1179+
var response generated.AccountsResponse
1180+
data := rec.Body.Bytes()
1181+
err = json.Decode(data, &response)
1182+
require.NoError(t, err)
1183+
require.Equal(t, 1, len(response.Accounts))
1184+
require.Equal(t, test.AccountA.String(), response.Accounts[0].Address)
1185+
}
1186+
{
1187+
//////////
1188+
// When // We query for accounts using online-only=false
1189+
//////////
1190+
req := httptest.NewRequest(http.MethodGet, "/", nil)
1191+
rec := httptest.NewRecorder()
1192+
c := e.NewContext(req, rec)
1193+
c.SetPath("/v2/accounts")
1194+
err = api.SearchForAccounts(c, generated.SearchForAccountsParams{OnlineOnly: boolPtr(false)})
1195+
//////////
1196+
// Then // All accounts should be returned, regardless of whether their status is online or not
1197+
//////////
1198+
require.NoError(t, err)
1199+
require.Equal(t, http.StatusOK, rec.Code)
1200+
var response generated.AccountsResponse
1201+
data := rec.Body.Bytes()
1202+
err = json.Decode(data, &response)
1203+
require.NoError(t, err)
1204+
require.Equal(t, len(test.MakeGenesis().Allocation), len(response.Accounts))
1205+
}
1206+
{
1207+
//////////
1208+
// When // We query for accounts using both `online-only=true` and `include-all=true` parameters
1209+
//////////
1210+
req := httptest.NewRequest(http.MethodGet, "/", nil)
1211+
rec := httptest.NewRecorder()
1212+
c := e.NewContext(req, rec)
1213+
c.SetPath("/v2/accounts")
1214+
err = api.SearchForAccounts(c, generated.SearchForAccountsParams{OnlineOnly: boolPtr(true), IncludeAll: boolPtr(true)})
1215+
//////////
1216+
// Then // The response should be a 404 "bad request"
1217+
//////////
1218+
require.NoError(t, err)
1219+
require.Equal(t, http.StatusBadRequest, rec.Code)
1220+
require.Contains(t, rec.Body.String(), errOnlineOnlyDeleted)
1221+
}
1222+
}
1223+
11501224
func TestAccountClearsNonUTF8(t *testing.T) {
11511225
db, shutdownFunc := setupIdb(t, test.MakeGenesis())
11521226
defer shutdownFunc()

api/indexer.oas2.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,14 @@
8181
{
8282
"type": "integer",
8383
"description": "Include results for the specified round. For performance reasons, this parameter may be disabled on some configurations. Using application-id or asset-id filters will return both creator and opt-in accounts. Filtering by include-all will return creator and opt-in accounts for deleted assets and accounts. Non-opt-in managers are not included in the results when asset-id is used.",
84-
8584
"name": "round",
8685
"in": "query"
8786
},
8887
{
8988
"$ref": "#/parameters/application-id"
89+
},
90+
{
91+
"$ref": "#/parameters/online-only"
9092
}
9193
],
9294
"responses": {
@@ -2972,6 +2974,12 @@
29722974
"in": "query",
29732975
"x-algorand-format": "base64"
29742976
},
2977+
"online-only": {
2978+
"type": "boolean",
2979+
"description": "When this is set to true, return only accounts whose participation status is currently online.",
2980+
"name": "online-only",
2981+
"in": "query"
2982+
},
29752983
"rekey-to": {
29762984
"type": "boolean",
29772985
"description": "Include results which include the rekey-to field.",

api/indexer.oas3.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,14 @@
221221
},
222222
"x-algorand-format": "base64"
223223
},
224+
"online-only": {
225+
"description": "When this is set to true, return only accounts whose participation status is currently online.",
226+
"in": "query",
227+
"name": "online-only",
228+
"schema": {
229+
"type": "boolean"
230+
}
231+
},
224232
"proposers": {
225233
"description": "Accounts marked as proposer in the block header's participation updates. This parameter accepts a comma separated list of addresses.",
226234
"explode": false,
@@ -2644,6 +2652,14 @@
26442652
"schema": {
26452653
"type": "integer"
26462654
}
2655+
},
2656+
{
2657+
"description": "When this is set to true, return only accounts whose participation status is currently online.",
2658+
"in": "query",
2659+
"name": "online-only",
2660+
"schema": {
2661+
"type": "boolean"
2662+
}
26472663
}
26482664
],
26492665
"responses": {

docs/DisablingParametersGuide.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ The configuration file that is used to enable/disable parameters is a YAML file
2525
optional:
2626
- currency-greater-than: disabled
2727
- currency-less-than: disabled
28+
- online-only: disabled
2829
/v2/assets/{asset-id}/transactions:
2930
optional:
3031
- note-prefix: disabled

idb/idb.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,11 @@ type AccountQueryOptions struct {
304304
// return any accounts with this auth addr
305305
EqualToAuthAddr []byte
306306

307+
// OnlineOnly, when set to true, indicates that only accounts that are online should be returned.
308+
//
309+
// When set to false, this parameter is ignored (i.e. it becomes a no-op).
310+
OnlineOnly bool
311+
307312
// Filter on accounts with current balance greater than x
308313
AlgosGreaterThan *uint64
309314
// Filter on accounts with current balance less than x.
@@ -326,7 +331,7 @@ type AccountQueryOptions struct {
326331
// MaxResources is the maximum combined number of AppParam, AppLocalState, AssetParam, and AssetHolding objects allowed.
327332
MaxResources uint64
328333

329-
// IncludeDeleted indicated whether to include deleted Assets, Applications, etc within the account.
334+
// IncludeDeleted indicates whether to include deleted Assets, Applications, etc within the account.
330335
IncludeDeleted bool
331336

332337
Limit uint64

idb/postgres/postgres.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2031,6 +2031,9 @@ func (db *IndexerDb) buildAccountQuery(opts idb.AccountQueryOptions, countOnly b
20312031
whereArgs = append(whereArgs, encoding.Base64(opts.EqualToAuthAddr))
20322032
partNumber++
20332033
}
2034+
if opts.OnlineOnly {
2035+
whereParts = append(whereParts, "((a.account_data->>'onl' IS NOT NULL) AND (a.account_data->>'voteLst' IS NOT NULL) AND ((a.account_data->>'onl') = '1'))")
2036+
}
20342037
query = `SELECT a.addr, a.microalgos, a.rewards_total, a.created_at, a.closed_at, a.deleted, a.rewardsbase, a.keytype, a.account_data FROM account a`
20352038
if opts.HasAssetID != 0 {
20362039
// inner join requires match, filtering on presence of asset

0 commit comments

Comments
 (0)