Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for /block_search RPC endpoint #810

Closed
wants to merge 10 commits into from
18 changes: 5 additions & 13 deletions packages/tendermint-rpc/src/legacy/adaptors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,15 @@ export const adaptor33 = v0_33;
*/
export const adaptor34 = v0_33; // With this alias we can swap out the implementation without affecting caller code.

const hashes = {
v0_34: [
"ca2c9df", // v0.34.0-rc6
"182fa32", // v0.34.0
],
};

/**
* Returns an Adaptor implementation for a given tendermint version.
* Throws when version is not supported.
*
* @param version full Tendermint version string, e.g. "0.20.1"
*/
export function adaptorForVersion(version: string): Adaptor {
if (version.startsWith("0.33.") || version.startsWith("0.34.") || hashes.v0_34.includes(version)) {
return v0_33;
} else {
throw new Error(`Unsupported tendermint version: ${version}`);
}
export function adaptorForVersion(_version: string): Adaptor {
// Note: In some cases, Tendermint 0.34 returns an empty version value.
// This supports 0.33 and 0.34 now, no matter which version you provide.
// Very soon this function becomes obsolete (https://github.com/cosmos/cosmjs/issues/789).
return v0_33;
}
2 changes: 1 addition & 1 deletion packages/tendermint-rpc/src/legacy/client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, adaptor: Adaptor, expecte
const status = await client.status();

// node info
expect(status.nodeInfo.version).toEqual(expected.version);
expect(status.nodeInfo.version).toMatch(expected.version);
expect(status.nodeInfo.protocolVersion).toEqual({
p2p: expected.p2pVersion,
block: expected.blockVersion,
Expand Down
2 changes: 2 additions & 0 deletions packages/tendermint-rpc/src/tendermint34/adaptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,10 +284,10 @@ interface RpcBlockId {

function decodeBlockId(data: RpcBlockId): responses.BlockId {
return {
hash: fromHex(assertNotEmpty(data.hash)),
hash: fromHex(data.hash),
parts: {
total: assertNotEmpty(data.parts.total),
hash: fromHex(assertNotEmpty(data.parts.hash)),
total: data.parts.total,
hash: fromHex(data.parts.hash),
},
};
}
Expand Down Expand Up @@ -349,7 +349,7 @@ function decodeHeader(data: RpcHeader): responses.Header {
validatorsHash: fromHex(assertNotEmpty(data.validators_hash)),
nextValidatorsHash: fromHex(assertNotEmpty(data.next_validators_hash)),
consensusHash: fromHex(assertNotEmpty(data.consensus_hash)),
appHash: fromHex(assertNotEmpty(data.app_hash)),
appHash: fromHex(data.app_hash),
lastResultsHash: fromHex(assertSet(data.last_results_hash)),

evidenceHash: fromHex(assertSet(data.evidence_hash)),
Expand Down Expand Up @@ -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));
Expand All @@ -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);
}
Expand Down
17 changes: 17 additions & 0 deletions packages/tendermint-rpc/src/tendermint34/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -30,6 +31,7 @@ export type Request =
| AbciInfoRequest
| AbciQueryRequest
| BlockRequest
| BlockSearchRequest
| BlockchainRequest
| BlockResultsRequest
| BroadcastTxRequest
Expand Down Expand Up @@ -60,6 +62,7 @@ export interface AbciQueryRequest {
readonly method: Method.AbciQuery;
readonly params: AbciQueryParams;
}

export interface AbciQueryParams {
readonly path: string;
readonly data: Uint8Array;
Expand Down Expand Up @@ -97,10 +100,23 @@ 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;
}

export interface BroadcastTxParams {
readonly tx: Uint8Array;
}
Expand Down Expand Up @@ -141,6 +157,7 @@ export interface TxRequest {
readonly method: Method.Tx;
readonly params: TxParams;
}

export interface TxParams {
readonly hash: Uint8Array;
readonly prove?: boolean;
Expand Down
5 changes: 5 additions & 0 deletions packages/tendermint-rpc/src/tendermint34/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues)
const status = await client.status();

// node info
expect(status.nodeInfo.version).toEqual(expected.version);
expect(status.nodeInfo.version).toMatch(expected.version);
expect(status.nodeInfo.protocolVersion).toEqual({
p2p: expected.p2pVersion,
block: expected.blockVersion,
Expand Down Expand Up @@ -204,6 +204,72 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues)
});
});

describe("blockSearch", () => {
const key = randomString();

beforeAll(async () => {
if (tendermintEnabled()) {
const client = await Tendermint34Client.create(rpcFactory());

// eslint-disable-next-line no-inner-declarations
async function sendTx(): Promise<void> {
const me = randomString();
const tx = buildKvTx(key, me);
RiccardoM marked this conversation as resolved.
Show resolved Hide resolved

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 txSearch results", async () => {
RiccardoM marked this conversation as resolved.
Show resolved Hide resolved
pendingWithoutTendermint();
const client = await Tendermint34Client.create(rpcFactory());

const query = buildQuery({ raw: "block.height >= 1 AND block.height <= 5" });
RiccardoM marked this conversation as resolved.
Show resolved Hide resolved

// expect one page of results
const s1 = await client.blockSearch({ query: query, page: 1, per_page: 2 });
expect(s1.totalCount).toEqual(5);
expect(s1.blocks.length).toEqual(2);

// second page
const s2 = await client.blockSearch({ query: query, page: 2, per_page: 2 });
expect(s2.totalCount).toEqual(5);
expect(s2.blocks.length).toEqual(2);

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();
Expand Down
41 changes: 41 additions & 0 deletions packages/tendermint-rpc/src/tendermint34/tendermint34client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,47 @@ export class Tendermint34Client {
return this.doCall(query, this.p.encodeBlockResults, this.r.decodeBlockResults);
}

/**
* Search for events that are in a block
*
* @see https://docs.tendermint.com/master/rpc/#/Info/block_search
*/
public async blockSearch(params: requests.BlockSearchParams): Promise<responses.BlockSearchResponse> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me (in terms of functionality).

My one question is that this is only available in tendermint v0.34.9+, correct? Minimally that should be documented in the comments along with a link to the PR. However, what happens when someone tries to run this on tendermint v0.34.8 node? There should be a clearly defined error case that distinguishes "network error" from "tendermint version too old" or it will be very hard for people to debug this.

Maybe @webmaster128 has some idea how to do this properly - it seems this error would be thrown in this.doCall

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a note regarding compatibility here would be nice.

I would not get into the version checking business again, which led to a lot of trouble sttarting with Tendermint 0.34. If the backend does not support an RPC method, it will return some kind of error that explains the problem somehow.

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)
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.
*
Expand Down
9 changes: 5 additions & 4 deletions packages/tendermint-rpc/src/testutil.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { toAscii } from "@cosmjs/encoding";
import { sleep } from "@cosmjs/utils";

export const chainIdMatcher = /^[-a-zA-Z0-9]{3,30}$/;
export const anyMatcher = /^.*$/; // Any string, including empty. Does not do more than a type check.

export interface ExpectedValues {
/** The Tendermint version as reported by Tendermint itself */
readonly version: string;
readonly version: string | RegExp;
readonly appCreator: string;
readonly p2pVersion: number;
readonly blockVersion: number;
Expand Down Expand Up @@ -49,7 +52,7 @@ export const tendermintInstances: readonly TendermintInstance[] = [
version: "0.34.x",
blockTime: 500,
expected: {
version: "182fa32", // srsly?
version: anyMatcher,
appCreator: "Cosmoshi Netowoko",
p2pVersion: 8,
blockVersion: 11,
Expand All @@ -60,8 +63,6 @@ export const tendermintInstances: readonly TendermintInstance[] = [

export const defaultInstance: TendermintInstance = tendermintInstances[0];

export const chainIdMatcher = /^[-a-zA-Z0-9]{3,30}$/;

export function tendermintEnabled(): boolean {
return !!process.env.TENDERMINT_ENABLED;
}
Expand Down
2 changes: 1 addition & 1 deletion scripts/tendermint/all_start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ command -v shellcheck >/dev/null && shellcheck "$0"
# Find latest patch releases at https://hub.docker.com/r/tendermint/tendermint/tags/
declare -a TM_VERSIONS
TM_VERSIONS[33]=v0.33.8
TM_VERSIONS[34]=v0.34.0
TM_VERSIONS[34]=v0.34.10

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

Expand Down
2 changes: 1 addition & 1 deletion scripts/tendermint/all_stop.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ command -v shellcheck >/dev/null && shellcheck "$0"

declare -a TM_VERSIONS
TM_VERSIONS[33]=v0.33.8
TM_VERSIONS[34]=v0.34.0
TM_VERSIONS[34]=v0.34.10

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

Expand Down