From 961b46d36ef81eb5590b20fce5dd0de20a2a8a66 Mon Sep 17 00:00:00 2001 From: Riccardo Montagnin Date: Tue, 25 May 2021 12:20:51 +0200 Subject: [PATCH 1/9] Added the support for block_search RPC endpoint --- .../src/tendermint34/adaptor.ts | 2 + .../tendermint34/adaptors/v0-34/requests.ts | 19 +++++++++ .../tendermint34/adaptors/v0-34/responses.ts | 16 ++++++++ .../src/tendermint34/requests.ts | 17 ++++++++ .../src/tendermint34/responses.ts | 5 +++ .../src/tendermint34/tendermint34client.ts | 41 +++++++++++++++++++ 6 files changed, 100 insertions(+) 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..7f5ffa0eac 100644 --- a/packages/tendermint-rpc/src/tendermint34/adaptors/v0-34/responses.ts +++ b/packages/tendermint-rpc/src/tendermint34/adaptors/v0-34/responses.ts @@ -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.ts b/packages/tendermint-rpc/src/tendermint34/tendermint34client.ts index 42d20c4a7e..3b5335e7ae 100644 --- a/packages/tendermint-rpc/src/tendermint34/tendermint34client.ts +++ b/packages/tendermint-rpc/src/tendermint34/tendermint34client.ts @@ -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 { + 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 { + 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. * From 859b4ec19f45b93b09710d350e80bdf00f412dc0 Mon Sep 17 00:00:00 2001 From: Riccardo Montagnin Date: Wed, 26 May 2021 11:45:41 +0200 Subject: [PATCH 2/9] Tests - Added block search tests - Incremented v0.34 Tendermint version to 0.34.9 --- .../tendermint34/tendermint34client.spec.ts | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts b/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts index 2c33d21651..abb58468a3 100644 --- a/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts +++ b/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts @@ -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 { + const me = randomString(); + const tx = buildKvTx(key, me); + + 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 () => { + pendingWithoutTendermint(); + const client = await Tendermint34Client.create(rpcFactory()); + + const query = buildQuery({ raw: "block.height >= 1 AND block.height <= 5" }); + + // 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(5); + expect(sall.blocks.length).toEqual(5); + // 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(); From 36c7a01e523386b799f491881fb7a409b3e0580e Mon Sep 17 00:00:00 2001 From: Riccardo Montagnin Date: Thu, 27 May 2021 07:23:59 +0200 Subject: [PATCH 3/9] Fixed tests and response parser --- .../src/tendermint34/adaptors/v0-34/responses.ts | 8 ++++---- .../src/tendermint34/tendermint34client.spec.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) 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 7f5ffa0eac..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)), diff --git a/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts b/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts index abb58468a3..e03d5cd23f 100644 --- a/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts +++ b/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts @@ -259,8 +259,8 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) 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(5); - expect(sall.blocks.length).toEqual(5); + 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); From be6b56b0785459d0ca8ddca65fda0ee951a7bd2b Mon Sep 17 00:00:00 2001 From: Riccardo Montagnin Date: Thu, 27 May 2021 09:18:09 +0200 Subject: [PATCH 4/9] Applied fixes --- .../src/tendermint34/tendermint34client.spec.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts b/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts index e03d5cd23f..3b94bbb2ca 100644 --- a/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts +++ b/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts @@ -205,16 +205,13 @@ 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 { - const me = randomString(); - const tx = buildKvTx(key, me); + const tx = buildKvTx(randomString(), randomString()); const txRes = await client.broadcastTxCommit({ tx: tx }); expect(responses.broadcastTxCommitSuccess(txRes)).toEqual(true); @@ -233,20 +230,20 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) } }); - it("can paginate over txSearch results", async () => { + it("can paginate over blockSearch results", async () => { pendingWithoutTendermint(); const client = await Tendermint34Client.create(rpcFactory()); - const query = buildQuery({ raw: "block.height >= 1 AND block.height <= 5" }); + 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(5); + 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(5); + expect(s2.totalCount).toEqual(3); expect(s2.blocks.length).toEqual(2); client.disconnect(); From cd74f2523ba5ff0891cf4f73da32e2c2c414961f Mon Sep 17 00:00:00 2001 From: Riccardo Montagnin Date: Thu, 27 May 2021 13:40:23 +0200 Subject: [PATCH 5/9] Added Tendermint version notes and fixed tests --- .../src/tendermint34/tendermint34client.spec.ts | 2 +- .../tendermint-rpc/src/tendermint34/tendermint34client.ts | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts b/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts index 3b94bbb2ca..006b33a308 100644 --- a/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts +++ b/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts @@ -244,7 +244,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) // second page const s2 = await client.blockSearch({ query: query, page: 2, per_page: 2 }); expect(s2.totalCount).toEqual(3); - expect(s2.blocks.length).toEqual(2); + expect(s2.blocks.length).toEqual(1); client.disconnect(); }); diff --git a/packages/tendermint-rpc/src/tendermint34/tendermint34client.ts b/packages/tendermint-rpc/src/tendermint34/tendermint34client.ts index 3b5335e7ae..f528c2dd1f 100644 --- a/packages/tendermint-rpc/src/tendermint34/tendermint34client.ts +++ b/packages/tendermint-rpc/src/tendermint34/tendermint34client.ts @@ -99,7 +99,10 @@ export class Tendermint34Client { } /** - * Search for events that are in a block + * 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 */ @@ -115,6 +118,9 @@ export class Tendermint34Client { // 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[] = []; From 2389601783dba1fe906cf4b9ebf15dc6a980f9d3 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 27 May 2021 16:09:33 +0200 Subject: [PATCH 6/9] Export missing types --- packages/tendermint-rpc/src/tendermint34/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/tendermint-rpc/src/tendermint34/index.ts b/packages/tendermint-rpc/src/tendermint34/index.ts index f67990c295..324bae5adc 100644 --- a/packages/tendermint-rpc/src/tendermint34/index.ts +++ b/packages/tendermint-rpc/src/tendermint34/index.ts @@ -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, From 69a47c0065c0520f9207c0417f66eb37cb09d32e Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 27 May 2021 16:31:26 +0200 Subject: [PATCH 7/9] Add CHANGELOG entry for blockSearch/blockSearchAll --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 332afabce4..3088a7b2c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ 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. + ## [0.25.3] - 2021-05-18 ### Fixed From 2701acf37e722336e1145a85cdb60a0bb2682551 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 27 May 2021 17:00:46 +0200 Subject: [PATCH 8/9] Make lastBlockId and lastCommit optional --- CHANGELOG.md | 6 ++++++ packages/stargate/src/queries/queryclient.ts | 10 +++++----- .../src/tendermint34/adaptors/v0-34/responses.ts | 14 +++++++++----- packages/tendermint-rpc/src/tendermint34/hasher.ts | 6 ++++++ .../tendermint-rpc/src/tendermint34/responses.ts | 11 ++++++++--- 5 files changed, 34 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3088a7b2c5..bbf645f6f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,12 @@ and this project adheres to `Tendermint34Client.blockSearchAll` were added to allow searching blocks in Tendermint 0.34.9+ backends. +### Changes + +- @cosmjs/tendermint-rpc: Make `tendermint34.Header.lastBlockId` and + `tendermint34.Block.lastCommit` optional to better handle the case of height 1 + where there is no previous block. + ## [0.25.3] - 2021-05-18 ### Fixed diff --git a/packages/stargate/src/queries/queryclient.ts b/packages/stargate/src/queries/queryclient.ts index 834bc65c18..a7a7a87ad3 100644 --- a/packages/stargate/src/queries/queryclient.ts +++ b/packages/stargate/src/queries/queryclient.ts @@ -2,7 +2,7 @@ import { iavlSpec, ics23, tendermintSpec, verifyExistence, verifyNonExistence } from "@confio/ics23"; import { toAscii, toHex } from "@cosmjs/encoding"; import { firstEvent } from "@cosmjs/stream"; -import { Header, NewBlockHeaderEvent, ProofOp, Tendermint34Client } from "@cosmjs/tendermint-rpc"; +import { tendermint34, Tendermint34Client } from "@cosmjs/tendermint-rpc"; import { arrayContentEquals, assert, assertDefined, isNonNullObject, sleep } from "@cosmjs/utils"; import { Stream } from "xstream"; @@ -10,7 +10,7 @@ import { ProofOps } from "../codec/tendermint/crypto/proof"; type QueryExtensionSetup

= (base: QueryClient) => P; -function checkAndParseOp(op: ProofOp, kind: string, key: Uint8Array): ics23.CommitmentProof { +function checkAndParseOp(op: tendermint34.ProofOp, kind: string, key: Uint8Array): ics23.CommitmentProof { if (op.type !== kind) { throw new Error(`Op expected to be ${kind}, got "${op.type}`); } @@ -587,15 +587,15 @@ export class QueryClient { // this must return the header for height+1 // throws an error if height is 0 or undefined - private async getNextHeader(height?: number): Promise

{ + private async getNextHeader(height?: number): Promise { assertDefined(height); if (height === 0) { throw new Error("Query returned height 0, cannot prove it"); } const searchHeight = height + 1; - let nextHeader: Header | undefined; - let headersSubscription: Stream | undefined; + let nextHeader: tendermint34.Header | undefined; + let headersSubscription: Stream | undefined; try { headersSubscription = this.tmClient.subscribeNewBlockHeader(); } catch { 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 1260b8058d..7a9888a8bb 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(data.hash), + hash: fromHex(assertNotEmpty(data.hash)), parts: { - total: data.parts.total, - hash: fromHex(data.parts.hash), + total: assertNotEmpty(data.parts.total), + hash: fromHex(assertNotEmpty(data.parts.hash)), }, }; } @@ -341,7 +341,9 @@ function decodeHeader(data: RpcHeader): responses.Header { height: Integer.parse(assertNotEmpty(data.height)), time: fromRfc3339WithNanoseconds(assertNotEmpty(data.time)), - lastBlockId: decodeBlockId(data.last_block_id), + // When there is no last block ID (i.e. this block's height is 1), we get an empty structure like this: + // { hash: '', parts: { total: 0, hash: '' } } + lastBlockId: data.last_block_id.hash ? decodeBlockId(data.last_block_id) : null, lastCommitHash: fromHex(assertNotEmpty(data.last_commit_hash)), dataHash: fromHex(assertSet(data.data_hash)), @@ -765,7 +767,9 @@ interface RpcBlock { function decodeBlock(data: RpcBlock): responses.Block { return { header: decodeHeader(assertObject(data.header)), - lastCommit: decodeCommit(assertObject(data.last_commit)), + // For the block at height 1, last commit is not set. This is represented in an empty object like this: + // { height: '0', round: 0, block_id: { hash: '', parts: [Object] }, signatures: [] } + lastCommit: data.last_commit.block_id.hash ? decodeCommit(assertObject(data.last_commit)) : null, txs: data.data.txs ? assertArray(data.data.txs).map(fromBase64) : [], evidence: data.evidence && may(decodeEvidences, data.evidence.evidence), }; diff --git a/packages/tendermint-rpc/src/tendermint34/hasher.ts b/packages/tendermint-rpc/src/tendermint34/hasher.ts index 75d9f48487..65ba6627be 100644 --- a/packages/tendermint-rpc/src/tendermint34/hasher.ts +++ b/packages/tendermint-rpc/src/tendermint34/hasher.ts @@ -46,6 +46,12 @@ function hashTree(hashes: readonly Uint8Array[]): Uint8Array { } export function hashBlock(header: Header): Uint8Array { + if (!header.lastBlockId) { + throw new Error( + "Hashing a block header with no last block ID (i.e. header at height 1) is not supported. If you need this, contributions are welcome. Please add documentation and test vectors for this case.", + ); + } + const encodedFields: readonly Uint8Array[] = [ encodeVersion(header.version), encodeString(header.chainId), diff --git a/packages/tendermint-rpc/src/tendermint34/responses.ts b/packages/tendermint-rpc/src/tendermint34/responses.ts index b71c482612..7906718969 100644 --- a/packages/tendermint-rpc/src/tendermint34/responses.ts +++ b/packages/tendermint-rpc/src/tendermint34/responses.ts @@ -217,7 +217,10 @@ export interface BlockId { export interface Block { readonly header: Header; - readonly lastCommit: Commit; + /** + * For the block at height 1, last commit is not set. + */ + readonly lastCommit: Commit | null; readonly txs: readonly Uint8Array[]; readonly evidence?: readonly Evidence[]; } @@ -269,8 +272,10 @@ export interface Header { readonly height: number; readonly time: ReadonlyDateWithNanoseconds; - // prev block info - readonly lastBlockId: BlockId; + /** + * Block ID of the previous block. This can be `null` when the currect block is height 1. + */ + readonly lastBlockId: BlockId | null; // hashes of block data readonly lastCommitHash: Uint8Array; From 8d3c93a44c443ec22347fef5765a29b42634d453 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 27 May 2021 17:17:16 +0200 Subject: [PATCH 9/9] Use assertSet consistently to check the hashes --- .../tendermint34/adaptors/v0-34/responses.ts | 10 +++++----- .../src/tendermint34/responses.ts | 20 +++++++++++++++++-- 2 files changed, 23 insertions(+), 7 deletions(-) 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 7a9888a8bb..90be82da73 100644 --- a/packages/tendermint-rpc/src/tendermint34/adaptors/v0-34/responses.ts +++ b/packages/tendermint-rpc/src/tendermint34/adaptors/v0-34/responses.ts @@ -345,13 +345,13 @@ function decodeHeader(data: RpcHeader): responses.Header { // { hash: '', parts: { total: 0, hash: '' } } lastBlockId: data.last_block_id.hash ? decodeBlockId(data.last_block_id) : null, - lastCommitHash: fromHex(assertNotEmpty(data.last_commit_hash)), + lastCommitHash: fromHex(assertSet(data.last_commit_hash)), dataHash: fromHex(assertSet(data.data_hash)), - validatorsHash: fromHex(assertNotEmpty(data.validators_hash)), - nextValidatorsHash: fromHex(assertNotEmpty(data.next_validators_hash)), - consensusHash: fromHex(assertNotEmpty(data.consensus_hash)), - appHash: fromHex(data.app_hash), + validatorsHash: fromHex(assertSet(data.validators_hash)), + nextValidatorsHash: fromHex(assertSet(data.next_validators_hash)), + consensusHash: fromHex(assertSet(data.consensus_hash)), + appHash: fromHex(assertSet(data.app_hash)), lastResultsHash: fromHex(assertSet(data.last_results_hash)), evidenceHash: fromHex(assertSet(data.evidence_hash)), diff --git a/packages/tendermint-rpc/src/tendermint34/responses.ts b/packages/tendermint-rpc/src/tendermint34/responses.ts index 7906718969..926178a683 100644 --- a/packages/tendermint-rpc/src/tendermint34/responses.ts +++ b/packages/tendermint-rpc/src/tendermint34/responses.ts @@ -277,18 +277,34 @@ export interface Header { */ readonly lastBlockId: BlockId | null; - // hashes of block data + /** + * Hashes of block data. + * + * This is `sha256("")` for height 1 🤷‍ + */ readonly lastCommitHash: Uint8Array; - readonly dataHash: Uint8Array; // empty when number of transaction is 0 + /** + * This is `sha256("")` as long as there is no data 🤷‍ + */ + readonly dataHash: Uint8Array; // hashes from the app output from the prev block readonly validatorsHash: Uint8Array; readonly nextValidatorsHash: Uint8Array; readonly consensusHash: Uint8Array; + /** + * This can be an empty string for height 1 and turn into "0000000000000000" later on 🤷‍ + */ readonly appHash: Uint8Array; + /** + * This is `sha256("")` as long as there is no data 🤷‍ + */ readonly lastResultsHash: Uint8Array; // consensus info + /** + * This is `sha256("")` as long as there is no data 🤷‍ + */ readonly evidenceHash: Uint8Array; readonly proposerAddress: Uint8Array; }