Make lastBlockId and lastCommit optional

This commit is contained in:
Simon Warta 2021-05-27 17:00:46 +02:00
parent 69a47c0065
commit 2701acf37e
5 changed files with 34 additions and 13 deletions

View File

@ -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

View File

@ -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<P> = (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<Header> {
private async getNextHeader(height?: number): Promise<tendermint34.Header> {
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<NewBlockHeaderEvent> | undefined;
let nextHeader: tendermint34.Header | undefined;
let headersSubscription: Stream<tendermint34.NewBlockHeaderEvent> | undefined;
try {
headersSubscription = this.tmClient.subscribeNewBlockHeader();
} catch {

View File

@ -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),
};

View File

@ -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),

View File

@ -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;