From 2701acf37e722336e1145a85cdb60a0bb2682551 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 27 May 2021 17:00:46 +0200 Subject: [PATCH] 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 3088a7b2..bbf645f6 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 834bc65c..a7a7a87a 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 1260b805..7a9888a8 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 75d9f484..65ba6627 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 b71c4826..79067189 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;