diff --git a/CHANGELOG.md b/CHANGELOG.md index 332afabc..5d853e7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,30 @@ 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. + +### 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. + +### Fixed + +- @cosmjs/socket: Upgrade dependency "ws" to version 7 to avoid potential + security problems. + +## [0.25.4] - 2021-05-31 + +### Fixed + +- @cosmjs/socket: Upgrade dependency "ws" to version 7 to avoid potential + security problems. + ## [0.25.3] - 2021-05-18 ### Fixed @@ -474,6 +498,7 @@ CHANGELOG entries missing. Please see [the diff][0.24.1]. - @cosmjs/sdk38: Rename package to @cosmjs/launchpad. [unreleased]: https://github.com/cosmos/cosmjs/compare/v0.25.3...HEAD +[0.25.4]: https://github.com/cosmos/cosmjs/compare/v0.25.3...v0.25.4 [0.25.3]: https://github.com/cosmos/cosmjs/compare/v0.25.2...v0.25.3 [0.25.2]: https://github.com/cosmos/cosmjs/compare/v0.25.1...v0.25.2 [0.25.1]: https://github.com/cosmos/cosmjs/compare/v0.25.0...v0.25.1 diff --git a/packages/amino/nonces/1622465640 b/packages/amino/nonces/1622465640 new file mode 100644 index 00000000..e69de29b diff --git a/packages/amino/package.json b/packages/amino/package.json index 2881a7a9..741837d8 100644 --- a/packages/amino/package.json +++ b/packages/amino/package.json @@ -1,6 +1,6 @@ { "name": "@cosmjs/amino", - "version": "0.25.3", + "version": "0.25.4", "description": "Helpers for Amino based signing which are shared between @cosmjs/launchpad and @cosmjs/stargate.", "contributors": [ "Simon Warta " diff --git a/packages/cli/nonces/1622465640 b/packages/cli/nonces/1622465640 new file mode 100644 index 00000000..e69de29b diff --git a/packages/cli/package.json b/packages/cli/package.json index 3b346f36..265091ab 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@cosmjs/cli", - "version": "0.25.3", + "version": "0.25.4", "description": "Command line interface", "contributors": [ "IOV SAS ", diff --git a/packages/cosmwasm-launchpad/nonces/1622465640 b/packages/cosmwasm-launchpad/nonces/1622465640 new file mode 100644 index 00000000..e69de29b diff --git a/packages/cosmwasm-launchpad/package.json b/packages/cosmwasm-launchpad/package.json index e35bf61c..7a091473 100644 --- a/packages/cosmwasm-launchpad/package.json +++ b/packages/cosmwasm-launchpad/package.json @@ -1,6 +1,6 @@ { "name": "@cosmjs/cosmwasm-launchpad", - "version": "0.25.3", + "version": "0.25.4", "description": "CosmWasm SDK for Launchpad", "contributors": [ "Ethan Frey ", diff --git a/packages/cosmwasm-stargate/nonces/1622465640 b/packages/cosmwasm-stargate/nonces/1622465640 new file mode 100644 index 00000000..e69de29b diff --git a/packages/cosmwasm-stargate/package.json b/packages/cosmwasm-stargate/package.json index 12faae00..10ae4090 100644 --- a/packages/cosmwasm-stargate/package.json +++ b/packages/cosmwasm-stargate/package.json @@ -1,6 +1,6 @@ { "name": "@cosmjs/cosmwasm-stargate", - "version": "0.25.3", + "version": "0.25.4", "description": "CosmWasm SDK", "contributors": [ "Will Clark " diff --git a/packages/cosmwasm/nonces/1622465640 b/packages/cosmwasm/nonces/1622465640 new file mode 100644 index 00000000..e69de29b diff --git a/packages/cosmwasm/package.json b/packages/cosmwasm/package.json index f9407ee0..f0bb4317 100644 --- a/packages/cosmwasm/package.json +++ b/packages/cosmwasm/package.json @@ -1,6 +1,6 @@ { "name": "@cosmjs/cosmwasm", - "version": "0.25.3", + "version": "0.25.4", "description": "CosmWasm SDK", "contributors": [ "Ethan Frey ", diff --git a/packages/crypto/nonces/1622465640 b/packages/crypto/nonces/1622465640 new file mode 100644 index 00000000..e69de29b diff --git a/packages/crypto/package.json b/packages/crypto/package.json index 29457ddc..225aeb38 100644 --- a/packages/crypto/package.json +++ b/packages/crypto/package.json @@ -1,6 +1,6 @@ { "name": "@cosmjs/crypto", - "version": "0.25.3", + "version": "0.25.4", "description": "Cryptography resources for blockchain projects", "contributors": [ "IOV SAS ", diff --git a/packages/encoding/nonces/1622465640 b/packages/encoding/nonces/1622465640 new file mode 100644 index 00000000..e69de29b diff --git a/packages/encoding/package.json b/packages/encoding/package.json index 2dc71731..0fe80ecc 100644 --- a/packages/encoding/package.json +++ b/packages/encoding/package.json @@ -1,6 +1,6 @@ { "name": "@cosmjs/encoding", - "version": "0.25.3", + "version": "0.25.4", "description": "Encoding helpers for blockchain projects", "contributors": [ "IOV SAS " diff --git a/packages/faucet-client/nonces/1622465640 b/packages/faucet-client/nonces/1622465640 new file mode 100644 index 00000000..e69de29b diff --git a/packages/faucet-client/package.json b/packages/faucet-client/package.json index 32672278..d684c403 100644 --- a/packages/faucet-client/package.json +++ b/packages/faucet-client/package.json @@ -1,6 +1,6 @@ { "name": "@cosmjs/faucet-client", - "version": "0.25.3", + "version": "0.25.4", "description": "The faucet client", "contributors": [ "Will Clark " diff --git a/packages/faucet/nonces/1622465640 b/packages/faucet/nonces/1622465640 new file mode 100644 index 00000000..e69de29b diff --git a/packages/faucet/package.json b/packages/faucet/package.json index ba595e67..56352c90 100644 --- a/packages/faucet/package.json +++ b/packages/faucet/package.json @@ -1,6 +1,6 @@ { "name": "@cosmjs/faucet", - "version": "0.25.3", + "version": "0.25.4", "description": "The faucet", "contributors": [ "Ethan Frey ", diff --git a/packages/json-rpc/nonces/1622465640 b/packages/json-rpc/nonces/1622465640 new file mode 100644 index 00000000..e69de29b diff --git a/packages/json-rpc/package.json b/packages/json-rpc/package.json index af5448a2..385f387e 100644 --- a/packages/json-rpc/package.json +++ b/packages/json-rpc/package.json @@ -1,6 +1,6 @@ { "name": "@cosmjs/json-rpc", - "version": "0.25.3", + "version": "0.25.4", "description": "Framework for implementing a JSON-RPC 2.0 API", "contributors": [ "IOV SAS ", diff --git a/packages/launchpad/nonces/1622465640 b/packages/launchpad/nonces/1622465640 new file mode 100644 index 00000000..e69de29b diff --git a/packages/launchpad/package.json b/packages/launchpad/package.json index be48f042..aa95ec42 100644 --- a/packages/launchpad/package.json +++ b/packages/launchpad/package.json @@ -1,6 +1,6 @@ { "name": "@cosmjs/launchpad", - "version": "0.25.3", + "version": "0.25.4", "description": "A client library for the Cosmos SDK 0.37 (cosmoshub-3), 0.38 and 0.39 (Launchpad)", "contributors": [ "Ethan Frey ", diff --git a/packages/ledger-amino/nonces/1622465640 b/packages/ledger-amino/nonces/1622465640 new file mode 100644 index 00000000..e69de29b diff --git a/packages/ledger-amino/package.json b/packages/ledger-amino/package.json index 852f1a0e..e098ce5e 100644 --- a/packages/ledger-amino/package.json +++ b/packages/ledger-amino/package.json @@ -1,6 +1,6 @@ { "name": "@cosmjs/ledger-amino", - "version": "0.25.3", + "version": "0.25.4", "description": "A library for signing Amino-encoded transactions using Ledger devices", "contributors": [ "Will Clark " diff --git a/packages/math/nonces/1622465640 b/packages/math/nonces/1622465640 new file mode 100644 index 00000000..e69de29b diff --git a/packages/math/package.json b/packages/math/package.json index d60ebb6b..4c80dcb3 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -1,6 +1,6 @@ { "name": "@cosmjs/math", - "version": "0.25.3", + "version": "0.25.4", "description": "Math helpers for blockchain projects", "contributors": [ "IOV SAS " diff --git a/packages/proto-signing/nonces/1622465640 b/packages/proto-signing/nonces/1622465640 new file mode 100644 index 00000000..e69de29b diff --git a/packages/proto-signing/package.json b/packages/proto-signing/package.json index bf9cdb6c..57b137d0 100644 --- a/packages/proto-signing/package.json +++ b/packages/proto-signing/package.json @@ -1,6 +1,6 @@ { "name": "@cosmjs/proto-signing", - "version": "0.25.3", + "version": "0.25.4", "description": "Utilities for protobuf based signing (Cosmos SDK 0.40+)", "contributors": [ "Will Clark ", diff --git a/packages/socket/nonces/1622465640 b/packages/socket/nonces/1622465640 new file mode 100644 index 00000000..e69de29b diff --git a/packages/socket/package.json b/packages/socket/package.json index 8f2018f1..d7358d67 100644 --- a/packages/socket/package.json +++ b/packages/socket/package.json @@ -1,6 +1,6 @@ { "name": "@cosmjs/socket", - "version": "0.25.3", + "version": "0.25.4", "description": "Utility functions for working with WebSockets", "contributors": [ "IOV SAS ", @@ -43,7 +43,7 @@ "dependencies": { "@cosmjs/stream": "workspace:packages/stream", "isomorphic-ws": "^4.0.1", - "ws": "^6.2.0", + "ws": "^7", "xstream": "^11.14.0" }, "devDependencies": { diff --git a/packages/stargate/nonces/1622465640 b/packages/stargate/nonces/1622465640 new file mode 100644 index 00000000..e69de29b diff --git a/packages/stargate/package.json b/packages/stargate/package.json index 4f69446e..ca2286f4 100644 --- a/packages/stargate/package.json +++ b/packages/stargate/package.json @@ -1,6 +1,6 @@ { "name": "@cosmjs/stargate", - "version": "0.25.3", + "version": "0.25.4", "description": "Utilities for Cosmos SDK 0.40", "contributors": [ "Simon Warta " diff --git a/packages/stargate/src/queries/queryclient.ts b/packages/stargate/src/queries/queryclient.ts index f22e1d9b..0e213f2a 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/stream/nonces/1622465640 b/packages/stream/nonces/1622465640 new file mode 100644 index 00000000..e69de29b diff --git a/packages/stream/package.json b/packages/stream/package.json index f971d4d8..50b5d66c 100644 --- a/packages/stream/package.json +++ b/packages/stream/package.json @@ -1,6 +1,6 @@ { "name": "@cosmjs/stream", - "version": "0.25.3", + "version": "0.25.4", "description": "Utility functions for producing and consuming streams", "contributors": [ "IOV SAS ", diff --git a/packages/tendermint-rpc/nonces/1622465640 b/packages/tendermint-rpc/nonces/1622465640 new file mode 100644 index 00000000..e69de29b diff --git a/packages/tendermint-rpc/package.json b/packages/tendermint-rpc/package.json index 6302b502..139c2041 100644 --- a/packages/tendermint-rpc/package.json +++ b/packages/tendermint-rpc/package.json @@ -1,6 +1,6 @@ { "name": "@cosmjs/tendermint-rpc", - "version": "0.25.3", + "version": "0.25.4", "description": "Tendermint RPC clients", "contributors": [ "IOV SAS ", diff --git a/packages/tendermint-rpc/src/legacy/adaptors/index.ts b/packages/tendermint-rpc/src/legacy/adaptors/index.ts index 51223aba..7097e36a 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 a3c290a5..eda05c4a 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 7248942d..44c06917 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 54436f7a..1dc11c4c 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 5386c7e1..90be82da 100644 --- a/packages/tendermint-rpc/src/tendermint34/adaptors/v0-34/responses.ts +++ b/packages/tendermint-rpc/src/tendermint34/adaptors/v0-34/responses.ts @@ -341,15 +341,17 @@ 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)), + 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(assertNotEmpty(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)), @@ -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), }; @@ -783,6 +787,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 +816,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/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/index.ts b/packages/tendermint-rpc/src/tendermint34/index.ts index f67990c2..324bae5a 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, diff --git a/packages/tendermint-rpc/src/tendermint34/requests.ts b/packages/tendermint-rpc/src/tendermint34/requests.ts index d985b8e6..75690766 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 ba7d3970..926178a6 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[]; @@ -212,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[]; } @@ -264,21 +272,39 @@ 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 + /** + * 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; } diff --git a/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts b/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts index 1f5b1666..006b33a3 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 42d20c4a..f528c2dd 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 c7315bf5..353dd26f 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/packages/utils/nonces/1622465640 b/packages/utils/nonces/1622465640 new file mode 100644 index 00000000..e69de29b diff --git a/packages/utils/package.json b/packages/utils/package.json index 810b9d59..07b815c6 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@cosmjs/utils", - "version": "0.25.3", + "version": "0.25.4", "description": "Utility tools, primarily for testing code", "contributors": [ "IOV SAS " diff --git a/scripts/tendermint/all_start.sh b/scripts/tendermint/all_start.sh index bbe27ca1..37a87fa8 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 a2ccd3e2..e77084df 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)"