Backport block search

This commit is contained in:
Simon Warta 2021-06-23 13:24:16 +02:00
parent 5f694f2ed6
commit 65db62d3ee
9 changed files with 177 additions and 0 deletions

View File

@ -6,6 +6,14 @@ and this project adheres to
## [Unreleased]
### Added
- @cosmjs/tendermint-rpc: `Tendermint34Client.blockSearch` and
`Tendermint34Client.blockSearchAll` were added to allow searching blocks in
Tendermint 0.34.9+ backends. This is a backport of [#815].
[#815]: https://github.com/cosmos/cosmjs/pull/815
## [0.25.4] - 2021-05-31
### Fixed

View File

@ -23,6 +23,7 @@ export interface Params {
readonly encodeBlock: (req: requests.BlockRequest) => JsonRpcRequest;
readonly encodeBlockchain: (req: requests.BlockchainRequest) => JsonRpcRequest;
readonly encodeBlockResults: (req: requests.BlockResultsRequest) => JsonRpcRequest;
readonly encodeBlockSearch: (req: requests.BlockSearchRequest) => JsonRpcRequest;
readonly encodeBroadcastTx: (req: requests.BroadcastTxRequest) => JsonRpcRequest;
readonly encodeCommit: (req: requests.CommitRequest) => JsonRpcRequest;
readonly encodeGenesis: (req: requests.GenesisRequest) => JsonRpcRequest;
@ -39,6 +40,7 @@ export interface Responses {
readonly decodeAbciQuery: (response: JsonRpcSuccessResponse) => responses.AbciQueryResponse;
readonly decodeBlock: (response: JsonRpcSuccessResponse) => responses.BlockResponse;
readonly decodeBlockResults: (response: JsonRpcSuccessResponse) => responses.BlockResultsResponse;
readonly decodeBlockSearch: (response: JsonRpcSuccessResponse) => responses.BlockSearchResponse;
readonly decodeBlockchain: (response: JsonRpcSuccessResponse) => responses.BlockchainResponse;
readonly decodeBroadcastTxSync: (response: JsonRpcSuccessResponse) => responses.BroadcastTxSyncResponse;
readonly decodeBroadcastTxAsync: (response: JsonRpcSuccessResponse) => responses.BroadcastTxAsyncResponse;

View File

@ -30,6 +30,21 @@ function encodeBlockchainRequestParams(param: requests.BlockchainRequestParams):
};
}
interface RpcBlockSearchParams {
readonly query: string;
readonly page?: string;
readonly per_page?: string;
readonly order_by?: string;
}
function encodeBlockSearchParams(params: requests.BlockSearchParams): RpcBlockSearchParams {
return {
query: params.query,
page: may(Integer.encode, params.page),
per_page: may(Integer.encode, params.per_page),
order_by: params.order_by,
};
}
interface RpcAbciQueryParams {
readonly path: string;
/** hex encoded */
@ -118,6 +133,10 @@ export class Params {
return createJsonRpcRequest(req.method, encodeHeightParam(req.params));
}
public static encodeBlockSearch(req: requests.BlockSearchRequest): JsonRpcRequest {
return createJsonRpcRequest(req.method, encodeBlockSearchParams(req.params));
}
public static encodeBroadcastTx(req: requests.BroadcastTxRequest): JsonRpcRequest {
return createJsonRpcRequest(req.method, encodeBroadcastTxParams(req.params));
}

View File

@ -783,6 +783,18 @@ function decodeBlockResponse(data: RpcBlockResponse): responses.BlockResponse {
};
}
interface RpcBlockSearchResponse {
readonly blocks: readonly RpcBlockResponse[];
readonly total_count: string;
}
function decodeBlockSearch(data: RpcBlockSearchResponse): responses.BlockSearchResponse {
return {
totalCount: Integer.parse(assertNotEmpty(data.total_count)),
blocks: assertArray(data.blocks).map(decodeBlockResponse),
};
}
export class Responses {
public static decodeAbciInfo(response: JsonRpcSuccessResponse): responses.AbciInfoResponse {
return decodeAbciInfo(assertObject((response.result as AbciInfoResult).response));
@ -800,6 +812,10 @@ export class Responses {
return decodeBlockResults(response.result as RpcBlockResultsResponse);
}
public static decodeBlockSearch(response: JsonRpcSuccessResponse): responses.BlockSearchResponse {
return decodeBlockSearch(response.result as RpcBlockSearchResponse);
}
public static decodeBlockchain(response: JsonRpcSuccessResponse): responses.BlockchainResponse {
return decodeBlockchain(response.result as RpcBlockchainResponse);
}

View File

@ -8,6 +8,8 @@ export {
AbciQueryRequest,
BlockRequest,
BlockchainRequest,
BlockSearchParams,
BlockSearchRequest,
BlockResultsRequest,
BroadcastTxRequest,
BroadcastTxParams,
@ -38,6 +40,7 @@ export {
BlockParams,
BlockResponse,
BlockResultsResponse,
BlockSearchResponse,
BroadcastTxAsyncResponse,
BroadcastTxCommitResponse,
broadcastTxCommitSuccess,

View File

@ -12,6 +12,7 @@ export enum Method {
/** Get block headers for minHeight <= height <= maxHeight. */
Blockchain = "blockchain",
BlockResults = "block_results",
BlockSearch = "block_search",
BroadcastTxAsync = "broadcast_tx_async",
BroadcastTxSync = "broadcast_tx_sync",
BroadcastTxCommit = "broadcast_tx_commit",
@ -30,6 +31,7 @@ export type Request =
| AbciInfoRequest
| AbciQueryRequest
| BlockRequest
| BlockSearchRequest
| BlockchainRequest
| BlockResultsRequest
| BroadcastTxRequest
@ -97,6 +99,18 @@ export interface BlockResultsRequest {
};
}
export interface BlockSearchRequest {
readonly method: Method.BlockSearch;
readonly params: BlockSearchParams;
}
export interface BlockSearchParams {
readonly query: string;
readonly page?: number;
readonly per_page?: number;
readonly order_by?: string;
}
export interface BroadcastTxRequest {
readonly method: Method.BroadcastTxAsync | Method.BroadcastTxSync | Method.BroadcastTxCommit;
readonly params: BroadcastTxParams;

View File

@ -60,6 +60,11 @@ export interface BlockResultsResponse {
readonly endBlockEvents: readonly Event[];
}
export interface BlockSearchResponse {
readonly blocks: readonly BlockResponse[];
readonly totalCount: number;
}
export interface BlockchainResponse {
readonly lastHeight: number;
readonly blockMetas: readonly BlockMeta[];

View File

@ -204,6 +204,69 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues)
});
});
describe("blockSearch", () => {
beforeAll(async () => {
if (tendermintEnabled()) {
const client = await Tendermint34Client.create(rpcFactory());
// eslint-disable-next-line no-inner-declarations
async function sendTx(): Promise<void> {
const tx = buildKvTx(randomString(), randomString());
const txRes = await client.broadcastTxCommit({ tx: tx });
expect(responses.broadcastTxCommitSuccess(txRes)).toEqual(true);
expect(txRes.height).toBeTruthy();
expect(txRes.hash.length).not.toEqual(0);
}
// send 3 txs
await sendTx();
await sendTx();
await sendTx();
client.disconnect();
await tendermintSearchIndexUpdated();
}
});
it("can paginate over blockSearch results", async () => {
pendingWithoutTendermint();
const client = await Tendermint34Client.create(rpcFactory());
const query = buildQuery({ raw: "block.height >= 1 AND block.height <= 3" });
// expect one page of results
const s1 = await client.blockSearch({ query: query, page: 1, per_page: 2 });
expect(s1.totalCount).toEqual(3);
expect(s1.blocks.length).toEqual(2);
// second page
const s2 = await client.blockSearch({ query: query, page: 2, per_page: 2 });
expect(s2.totalCount).toEqual(3);
expect(s2.blocks.length).toEqual(1);
client.disconnect();
});
it("can get all search results in one call", async () => {
pendingWithoutTendermint();
const client = await Tendermint34Client.create(rpcFactory());
const query = buildQuery({ raw: "block.height >= 1 AND block.height <= 3" });
const sall = await client.blockSearchAll({ query: query, per_page: 2 });
expect(sall.totalCount).toEqual(3);
expect(sall.blocks.length).toEqual(3);
// make sure there are in order from lowest to highest height
const [b1, b2, b3] = sall.blocks;
expect(b2.block.header.height).toEqual(b1.block.header.height + 1);
expect(b3.block.header.height).toEqual(b2.block.header.height + 1);
client.disconnect();
});
});
describe("blockchain", () => {
it("returns latest in descending order by default", async () => {
pendingWithoutTendermint();

View File

@ -98,6 +98,53 @@ export class Tendermint34Client {
return this.doCall(query, this.p.encodeBlockResults, this.r.decodeBlockResults);
}
/**
* Search for events that are in a block.
*
* NOTE
* This method will error on any node that is running a Tendermint version lower than 0.34.9.
*
* @see https://docs.tendermint.com/master/rpc/#/Info/block_search
*/
public async blockSearch(params: requests.BlockSearchParams): Promise<responses.BlockSearchResponse> {
const query: requests.BlockSearchRequest = { params: params, method: requests.Method.BlockSearch };
const resp = await this.doCall(query, this.p.encodeBlockSearch, this.r.decodeBlockSearch);
return {
...resp,
// make sure we sort by height, as tendermint may be sorting by string value of the height
blocks: [...resp.blocks].sort((a, b) => a.block.header.height - b.block.header.height),
};
}
// this should paginate through all blockSearch options to ensure it returns all results.
// starts with page 1 or whatever was provided (eg. to start on page 7)
//
// NOTE
// This method will error on any node that is running a Tendermint version lower than 0.34.9.
public async blockSearchAll(params: requests.BlockSearchParams): Promise<responses.BlockSearchResponse> {
let page = params.page || 1;
const blocks: responses.BlockResponse[] = [];
let done = false;
while (!done) {
const resp = await this.blockSearch({ ...params, page: page });
blocks.push(...resp.blocks);
if (blocks.length < resp.totalCount) {
page++;
} else {
done = true;
}
}
// make sure we sort by height, as tendermint may be sorting by string value of the height
// and the earlier items may be in a higher page than the later items
blocks.sort((a, b) => a.block.header.height - b.block.header.height);
return {
totalCount: blocks.length,
blocks: blocks,
};
}
/**
* Queries block headers filtered by minHeight <= height <= maxHeight.
*