diff --git a/packages/tendermint-rpc/src/legacy/adaptors/index.ts b/packages/tendermint-rpc/src/legacy/adaptors/index.ts index 51223aba6f..7097e36a8a 100644 --- a/packages/tendermint-rpc/src/legacy/adaptors/index.ts +++ b/packages/tendermint-rpc/src/legacy/adaptors/index.ts @@ -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; } diff --git a/packages/tendermint-rpc/src/legacy/client.spec.ts b/packages/tendermint-rpc/src/legacy/client.spec.ts index a3c290a57a..eda05c4a22 100644 --- a/packages/tendermint-rpc/src/legacy/client.spec.ts +++ b/packages/tendermint-rpc/src/legacy/client.spec.ts @@ -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, diff --git a/packages/tendermint-rpc/src/tendermint34/adaptor.ts b/packages/tendermint-rpc/src/tendermint34/adaptor.ts index 7248942d63..44c0691756 100644 --- a/packages/tendermint-rpc/src/tendermint34/adaptor.ts +++ b/packages/tendermint-rpc/src/tendermint34/adaptor.ts @@ -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; diff --git a/packages/tendermint-rpc/src/tendermint34/adaptors/v0-34/requests.ts b/packages/tendermint-rpc/src/tendermint34/adaptors/v0-34/requests.ts index 54436f7a07..1dc11c4c7b 100644 --- a/packages/tendermint-rpc/src/tendermint34/adaptors/v0-34/requests.ts +++ b/packages/tendermint-rpc/src/tendermint34/adaptors/v0-34/requests.ts @@ -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)); } diff --git a/packages/tendermint-rpc/src/tendermint34/adaptors/v0-34/responses.ts b/packages/tendermint-rpc/src/tendermint34/adaptors/v0-34/responses.ts index 5386c7e19c..1260b8058d 100644 --- a/packages/tendermint-rpc/src/tendermint34/adaptors/v0-34/responses.ts +++ b/packages/tendermint-rpc/src/tendermint34/adaptors/v0-34/responses.ts @@ -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), }, }; } @@ -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)), @@ -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); } diff --git a/packages/tendermint-rpc/src/tendermint34/requests.ts b/packages/tendermint-rpc/src/tendermint34/requests.ts index d985b8e64e..75690766f0 100644 --- a/packages/tendermint-rpc/src/tendermint34/requests.ts +++ b/packages/tendermint-rpc/src/tendermint34/requests.ts @@ -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 @@ -60,6 +62,7 @@ export interface AbciQueryRequest { readonly method: Method.AbciQuery; readonly params: AbciQueryParams; } + export interface AbciQueryParams { readonly path: string; readonly data: Uint8Array; @@ -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; } @@ -141,6 +157,7 @@ export interface TxRequest { readonly method: Method.Tx; readonly params: TxParams; } + export interface TxParams { readonly hash: Uint8Array; readonly prove?: boolean; diff --git a/packages/tendermint-rpc/src/tendermint34/responses.ts b/packages/tendermint-rpc/src/tendermint34/responses.ts index ba7d39706c..b71c482612 100644 --- a/packages/tendermint-rpc/src/tendermint34/responses.ts +++ b/packages/tendermint-rpc/src/tendermint34/responses.ts @@ -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[]; diff --git a/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts b/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts index 1f5b166612..006b33a308 100644 --- a/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts +++ b/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts @@ -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, @@ -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 { + 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(); diff --git a/packages/tendermint-rpc/src/tendermint34/tendermint34client.ts b/packages/tendermint-rpc/src/tendermint34/tendermint34client.ts index 42d20c4a7e..f528c2dd1f 100644 --- a/packages/tendermint-rpc/src/tendermint34/tendermint34client.ts +++ b/packages/tendermint-rpc/src/tendermint34/tendermint34client.ts @@ -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 { + 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 { + 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. * diff --git a/packages/tendermint-rpc/src/testutil.spec.ts b/packages/tendermint-rpc/src/testutil.spec.ts index c7315bf5cc..353dd26fe3 100644 --- a/packages/tendermint-rpc/src/testutil.spec.ts +++ b/packages/tendermint-rpc/src/testutil.spec.ts @@ -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; @@ -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, @@ -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; } diff --git a/scripts/tendermint/all_start.sh b/scripts/tendermint/all_start.sh index bbe27ca1d0..37a87fa868 100755 --- a/scripts/tendermint/all_start.sh +++ b/scripts/tendermint/all_start.sh @@ -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)" diff --git a/scripts/tendermint/all_stop.sh b/scripts/tendermint/all_stop.sh index a2ccd3e28b..e77084df96 100755 --- a/scripts/tendermint/all_stop.sh +++ b/scripts/tendermint/all_stop.sh @@ -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)"