Skip to content

Commit

Permalink
Blockheaders: Basic BlockHeaders API support. (#926)
Browse files Browse the repository at this point in the history
* Basic BlockHeaders API support.

* Implement cucumber tests for block-headers API call.
  • Loading branch information
gmalouf authored Feb 3, 2025
1 parent f913221 commit f9e2c9d
Show file tree
Hide file tree
Showing 5 changed files with 309 additions and 1 deletion.
16 changes: 16 additions & 0 deletions src/client/v2/indexer/indexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import LookupApplications from './lookupApplications.js';
import LookupApplicationLogs from './lookupApplicationLogs.js';
import LookupApplicationBoxByIDandName from './lookupApplicationBoxByIDandName.js';
import SearchAccounts from './searchAccounts.js';
import SearchForBlockHeaders from './searchForBlockHeaders.js';
import SearchForTransactions from './searchForTransactions.js';
import SearchForAssets from './searchForAssets.js';
import SearchForApplications from './searchForApplications.js';
Expand Down Expand Up @@ -327,6 +328,21 @@ export class IndexerClient extends ServiceClient {
return new SearchAccounts(this.c);
}

/**
* Returns information about indexed block headers.
*
* #### Example
* ```typescript
* const bhs = await indexerClient.searchForBlockHeaders().do();
* ```
*
* [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2block-headers)
* @category GET
*/
searchForBlockHeaders() {
return new SearchForBlockHeaders(this.c);
}

/**
* Returns information about indexed transactions.
*
Expand Down
223 changes: 223 additions & 0 deletions src/client/v2/indexer/searchForBlockHeaders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import JSONRequest from '../jsonrequest.js';
import { HTTPClientResponse } from '../../client.js';
import { decodeJSON } from '../../../encoding/encoding.js';
import { Address } from '../../../encoding/address.js';
import { BlockHeadersResponse } from './models/types.js';

/**
* Returns information about indexed block headers.
*
* #### Example
* ```typescript
* const bhs = await indexerClient.searchForBlockHeaders().do();
* ```
*
* [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2block-headers)
* @category GET
*/
export default class SearchForBlockHeaders extends JSONRequest<BlockHeadersResponse> {
/**
* @returns `/v2/block-headers`
*/
// eslint-disable-next-line class-methods-use-this
path() {
return '/v2/block-headers';
}

/**
* Accounts marked as absent in the block header's participation updates.
*
* #### Example
* ```typescript
* const address1 = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA";
* const address2 = "4H5UNRBJ2Q6JENAXQ6HNTGKLKINP4J4VTQBEPK5F3I6RDICMZBPGNH6KD4";
* const bhs = await indexerClient
* .searchForBlockHeaders()
* .absent([address1,address2])
* .do();
* ```
*
* @param absent - a comma separated list of addresses
* @category query
*/
absent(absent: (string | Address)[]) {
this.query.absent = absent;
return this;
}

/**
* Include results after the given time.
*
* #### Example
* ```typescript
* const afterTime = "2022-10-21T00:00:11.55Z";
* const bhs = await indexerClient
* .searchForBlockHeaders()
* .afterTime(afterTime)
* .do();
* ```
*
* @param after - rfc3339 string or Date object
* @category query
*/
afterTime(after: string | Date) {
this.query['after-time'] =
after instanceof Date ? after.toISOString() : after;
return this;
}

/**
* Include results before the given time.
*
* #### Example
* ```typescript
* const beforeTime = "2022-02-02T20:20:22.02Z";
* const bhs = await indexerClient
* .searchForBlockHeaders()
* .beforeTime(beforeTime)
* .do();
* ```
*
* @param before - rfc3339 string or Date object
* @category query
*/
beforeTime(before: string | Date) {
this.query['before-time'] =
before instanceof Date ? before.toISOString() : before;
return this;
}

/**
* Accounts marked as expired in the block header's participation updates.
*
* #### Example
* ```typescript
* const address1 = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA";
* const address2 = "4H5UNRBJ2Q6JENAXQ6HNTGKLKINP4J4VTQBEPK5F3I6RDICMZBPGNH6KD4";
* const bhs = await indexerClient
* .searchForBlockHeaders()
* .expired([address1,address2])
* .do();
* ```
*
* @param expired - - a comma separated list of addresses
* @category query
*/
expired(expired: (string | Address)[]) {
this.query.expired = expired;
return this;
}

/**
* Maximum number of results to return.
*
* #### Example
* ```typescript
* const maxResults = 25;
* const bhs = await indexerClient
* .searchForBlockHeaders()
* .limit(maxResults)
* .do();
* ```
*
* @param limit
* @category query
*/
limit(limit: number) {
this.query.limit = limit;
return this;
}

/**
* Include results at or before the specified max-round.
*
* #### Example
* ```typescript
* const maxRound = 18309917;
* const bhs = await indexerClient
* .searchForBlockHeaders()
* .maxRound(maxRound)
* .do();
* ```
*
* @param round
* @category query
*/
maxRound(round: number | bigint) {
this.query['max-round'] = round;
return this;
}

/**
* Include results at or after the specified min-round.
*
* #### Example
* ```typescript
* const minRound = 18309917;
* const bhs = await indexerClient
* .searchForBlockHeaders()
* .minRound(minRound)
* .do();
* ```
*
* @param round
* @category query
*/
minRound(round: number | bigint) {
this.query['min-round'] = round;
return this;
}

/**
* The next page of results.
*
* #### Example
* ```typescript
* const maxResults = 25;
*
* const bh1 = await indexerClient
* .searchForBlockHeaders()
* .limit(maxResults)
* .do();
*
* const bh2 = await indexerClient
* .searchForBlockHeaders()
* .limit(maxResults)
* .nextToken(bh1["next-token"])
* .do();
* ```
*
* @param nextToken - provided by the previous results
* @category query
*/
nextToken(nextToken: string) {
this.query.next = nextToken;
return this;
}

/**
* Accounts marked as proposer in the block header's participation updates.
*
* #### Example
* ```typescript
* const address1 = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA";
* const address2 = "4H5UNRBJ2Q6JENAXQ6HNTGKLKINP4J4VTQBEPK5F3I6RDICMZBPGNH6KD4";
* const bhs = await indexerClient
* .searchForBlockHeaders()
* .proposers([address1,address2])
* .do();
* ```
*
* @param proposers - a comma separated list of addresses
* @category query
*/
proposers(proposers: (string | Address)[]) {
this.query.proposers = proposers;
return this;
}

// eslint-disable-next-line class-methods-use-this
prepare(response: HTTPClientResponse): BlockHeadersResponse {
return decodeJSON(response.getJSONText(), BlockHeadersResponse);
}
}
7 changes: 6 additions & 1 deletion tests/9.Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,13 @@ describe('client', () => {
format: 'json',
abc: 'xyz',
l: '2',
adds: [
'XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA',
'4H5UNRBJ2Q6JENAXQ6HNTGKLKINP4J4VTQBEPK5F3I6RDICMZBPGNH6KD4',
],
});
const expected = 'http://localhost:3000/relative?format=json&abc=xyz&l=2';
const expected =
'http://localhost:3000/relative?format=json&abc=xyz&l=2&adds=XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA%2C4H5UNRBJ2Q6JENAXQ6HNTGKLKINP4J4VTQBEPK5F3I6RDICMZBPGNH6KD4';

assert.strictEqual(actual, expected);
});
Expand Down
63 changes: 63 additions & 0 deletions tests/cucumber/steps/steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -2729,6 +2729,47 @@ module.exports = function getSteps(options) {
}
);

When(
'we make a Search For BlockHeaders call with minRound {int} maxRound {int} limit {int} nextToken {string} beforeTime {string} afterTime {string} proposers {string} expired {string} absent {string}',
async function (
minRound,
maxRound,
limit,
nextToken,
beforeTime,
afterTime,
proposers,
expired,
absent
) {
const builder = this.indexerClient
.searchForBlockHeaders()
.afterTime(afterTime)
.beforeTime(beforeTime)
.limit(limit)
.maxRound(maxRound)
.minRound(minRound)
.nextToken(nextToken);

if (proposers !== null && proposers.trim().length > 0) {
const proposersArray = proposers.split(',');
builder.proposers(proposersArray);
}

if (expired !== null && expired.trim().length > 0) {
const expiredArray = expired.split(',');
builder.expired(expiredArray);
}

if (absent !== null && absent.trim().length > 0) {
const absentArray = absent.split(',');
builder.absent(absentArray);
}

await doOrDoRaw(builder);
}
);

When(
'we make a Search For Transactions call with account {string} NotePrefix {string} TxType {string} SigType {string} txid {string} round {int} minRound {int} maxRound {int} limit {int} beforeTime {string} afterTime {string} currencyGreaterThan {int} currencyLessThan {int} assetIndex {int} addressRole {string} ExcluseCloseTo {string} rekeyTo {string}',
async function (
Expand Down Expand Up @@ -3113,6 +3154,28 @@ module.exports = function getSteps(options) {
}
);

let anySearchForBlockHeadersResponse;

When('we make any SearchForBlockHeaders call', async function () {
anySearchForBlockHeadersResponse = await this.indexerClient
.searchForBlockHeaders()
.do();
});

Then(
'the parsed SearchForBlockHeaders response should have a block array of len {int} and the element at index {int} should have round {string}',
(length, idx, roundStr) => {
assert.strictEqual(
anySearchForBlockHeadersResponse.blocks.length,
length
);
assert.strictEqual(
anySearchForBlockHeadersResponse.blocks[idx].round,
BigInt(roundStr)
);
}
);

let anySearchForAssetsResponse;

When('we make any SearchForAssets call', async function () {
Expand Down
1 change: 1 addition & 0 deletions tests/cucumber/unit.tags
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
@unit.dryrun.trace.application
@unit.feetest
@unit.indexer
@unit.indexer.blockheaders
@unit.indexer.ledger_refactoring
@unit.indexer.logs
@unit.indexer.heartbeat
Expand Down

0 comments on commit f9e2c9d

Please sign in to comment.