diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b99f844..20b59cf2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,11 +21,13 @@ and this project adheres to adding fields to interfaces. ([#928]) - @cosmjs/stargate and @cosmjs/cosmwasm-stargate: Add simulation support ([#931]). +- @cosmjs/tendermint-rpc: Remove `Tendermint33Client` and related symbols. [#865]: https://github.com/cosmos/cosmjs/issues/865 [#897]: https://github.com/cosmos/cosmjs/issues/897 [#928]: https://github.com/cosmos/cosmjs/issues/928 [#931]: https://github.com/cosmos/cosmjs/pull/931 +[#709]: https://github.com/cosmos/cosmjs/issues/709 ## [0.26.5] - 2021-11-20 diff --git a/HACKING.md b/HACKING.md index cb9752d6..a8f96965 100644 --- a/HACKING.md +++ b/HACKING.md @@ -101,7 +101,6 @@ order to avoid conflicts. Here is an overview of the ports used: | 4444 | socketserver | @cosmjs/sockets tests | | 4445 | socketserver slow | @cosmjs/sockets tests | | 9090 | simapp gRPC | Manual Stargate debugging | -| 11133 | Tendermint 0.33 RPC | @cosmjs/tendermint-rpc tests | | 11134 | Tendermint 0.34 RPC | @cosmjs/tendermint-rpc tests | | 26658 | simapp Tendermint RPC | Stargate client tests | | 26659 | wasmd Tendermint RPC | @cosmjs/cosmwasm-stargate tests | diff --git a/packages/tendermint-rpc/src/index.ts b/packages/tendermint-rpc/src/index.ts index 4c741d23..8ca195e3 100644 --- a/packages/tendermint-rpc/src/index.ts +++ b/packages/tendermint-rpc/src/index.ts @@ -20,9 +20,7 @@ export { ValidatorSecp256k1Pubkey, ValidatorPubkey, } from "./types"; -export * as tendermint33 from "./tendermint33"; export { - Tendermint33Client, AbciInfoResponse, AbciQueryResponse, Attribute, diff --git a/packages/tendermint-rpc/src/tendermint33/adaptor/index.ts b/packages/tendermint-rpc/src/tendermint33/adaptor/index.ts deleted file mode 100644 index a9309502..00000000 --- a/packages/tendermint-rpc/src/tendermint33/adaptor/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { hashBlock, hashTx } from "../hasher"; -import { Params } from "./requests"; -import { Responses } from "./responses"; -import { Adaptor } from "./types"; - -export { Decoder, Encoder, Params, Responses } from "./types"; - -export const adaptor33: Adaptor = { - params: Params, - responses: Responses, - hashTx: hashTx, - hashBlock: hashBlock, -}; diff --git a/packages/tendermint-rpc/src/tendermint33/adaptor/requests.ts b/packages/tendermint-rpc/src/tendermint33/adaptor/requests.ts deleted file mode 100644 index beb4b97c..00000000 --- a/packages/tendermint-rpc/src/tendermint33/adaptor/requests.ts +++ /dev/null @@ -1,161 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import { toBase64, toHex } from "@cosmjs/encoding"; -import { JsonRpcRequest } from "@cosmjs/json-rpc"; - -import { createJsonRpcRequest } from "../../jsonrpc"; -import { assertNotEmpty, Integer, may } from "../encodings"; -import * as requests from "../requests"; - -interface HeightParam { - readonly height?: number; -} -interface RpcHeightParam { - readonly height?: string; -} -function encodeHeightParam(param: HeightParam): RpcHeightParam { - return { - height: may(Integer.encode, param.height), - }; -} - -interface RpcBlockchainRequestParams { - readonly minHeight?: string; - readonly maxHeight?: string; -} - -function encodeBlockchainRequestParams(param: requests.BlockchainRequestParams): RpcBlockchainRequestParams { - return { - minHeight: may(Integer.encode, param.minHeight), - maxHeight: may(Integer.encode, param.maxHeight), - }; -} - -interface RpcAbciQueryParams { - readonly path: string; - /** hex encoded */ - readonly data: string; - readonly height?: string; - readonly prove?: boolean; -} - -function encodeAbciQueryParams(params: requests.AbciQueryParams): RpcAbciQueryParams { - return { - path: assertNotEmpty(params.path), - data: toHex(params.data), - height: may(Integer.encode, params.height), - prove: params.prove, - }; -} - -interface RpcBroadcastTxParams { - /** base64 encoded */ - readonly tx: string; -} -function encodeBroadcastTxParams(params: requests.BroadcastTxParams): RpcBroadcastTxParams { - return { - tx: toBase64(assertNotEmpty(params.tx)), - }; -} - -interface RpcTxParams { - /** base64 encoded */ - readonly hash: string; - readonly prove?: boolean; -} -function encodeTxParams(params: requests.TxParams): RpcTxParams { - return { - hash: toBase64(assertNotEmpty(params.hash)), - prove: params.prove, - }; -} - -interface RpcTxSearchParams { - readonly query: string; - readonly prove?: boolean; - readonly page?: string; - readonly per_page?: string; - readonly order_by?: string; -} -function encodeTxSearchParams(params: requests.TxSearchParams): RpcTxSearchParams { - return { - query: params.query, - prove: params.prove, - page: may(Integer.encode, params.page), - per_page: may(Integer.encode, params.per_page), - order_by: params.order_by, - }; -} - -interface RpcValidatorsParams { - readonly height?: string; - readonly page?: string; - readonly per_page?: string; -} -function encodeValidatorsParams(params: requests.ValidatorsParams): RpcValidatorsParams { - return { - height: may(Integer.encode, params.height), - page: may(Integer.encode, params.page), - per_page: may(Integer.encode, params.per_page), - }; -} - -export class Params { - public static encodeAbciInfo(req: requests.AbciInfoRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method); - } - - public static encodeAbciQuery(req: requests.AbciQueryRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeAbciQueryParams(req.params)); - } - - public static encodeBlock(req: requests.BlockRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeHeightParam(req.params)); - } - - public static encodeBlockchain(req: requests.BlockchainRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeBlockchainRequestParams(req.params)); - } - - public static encodeBlockResults(req: requests.BlockResultsRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeHeightParam(req.params)); - } - - public static encodeBroadcastTx(req: requests.BroadcastTxRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeBroadcastTxParams(req.params)); - } - - public static encodeCommit(req: requests.CommitRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeHeightParam(req.params)); - } - - public static encodeGenesis(req: requests.GenesisRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method); - } - - public static encodeHealth(req: requests.HealthRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method); - } - - public static encodeStatus(req: requests.StatusRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method); - } - - public static encodeSubscribe(req: requests.SubscribeRequest): JsonRpcRequest { - const eventTag = { key: "tm.event", value: req.query.type }; - const query = requests.buildQuery({ tags: [eventTag], raw: req.query.raw }); - return createJsonRpcRequest("subscribe", { query: query }); - } - - public static encodeTx(req: requests.TxRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeTxParams(req.params)); - } - - // TODO: encode params for query string??? - public static encodeTxSearch(req: requests.TxSearchRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeTxSearchParams(req.params)); - } - - public static encodeValidators(req: requests.ValidatorsRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeValidatorsParams(req.params)); - } -} diff --git a/packages/tendermint-rpc/src/tendermint33/adaptor/responses.ts b/packages/tendermint-rpc/src/tendermint33/adaptor/responses.ts deleted file mode 100644 index 8ed85c33..00000000 --- a/packages/tendermint-rpc/src/tendermint33/adaptor/responses.ts +++ /dev/null @@ -1,838 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import { fromBase64, fromHex } from "@cosmjs/encoding"; -import { JsonRpcSuccessResponse } from "@cosmjs/json-rpc"; -import { assert } from "@cosmjs/utils"; - -import { fromRfc3339WithNanoseconds } from "../../dates"; -import { SubscriptionEvent } from "../../rpcclients"; -import { BlockIdFlag, CommitSignature, ValidatorPubkey } from "../../types"; -import { - assertArray, - assertBoolean, - assertNotEmpty, - assertNumber, - assertObject, - assertSet, - assertString, - dictionaryToStringMap, - Integer, - may, - optional, -} from "../encodings"; -import { hashTx } from "../hasher"; -import * as responses from "../responses"; - -interface AbciInfoResult { - readonly response: RpcAbciInfoResponse; -} - -interface RpcAbciInfoResponse { - readonly data?: string; - readonly last_block_height?: string; - /** base64 encoded */ - readonly last_block_app_hash?: string; -} - -function decodeAbciInfo(data: RpcAbciInfoResponse): responses.AbciInfoResponse { - return { - data: data.data, - lastBlockHeight: may(Integer.parse, data.last_block_height), - lastBlockAppHash: may(fromBase64, data.last_block_app_hash), - }; -} - -interface AbciQueryResult { - readonly response: RpcAbciQueryResponse; -} - -export interface RpcProofOp { - readonly type: string; - /** base64 encoded */ - readonly key: string; - /** base64 encoded */ - readonly data: string; -} - -export interface RpcQueryProof { - readonly ops: readonly RpcProofOp[]; -} - -function decodeQueryProof(data: RpcQueryProof): responses.QueryProof { - return { - ops: data.ops.map((op) => ({ - type: op.type, - key: fromBase64(op.key), - data: fromBase64(op.data), - })), - }; -} - -interface RpcAbciQueryResponse { - /** base64 encoded */ - readonly key: string; - /** base64 encoded */ - readonly value?: string; - readonly proofOps?: RpcQueryProof; - readonly height?: string; - readonly index?: string; - readonly code?: string; // only for errors - readonly log?: string; -} - -function decodeAbciQuery(data: RpcAbciQueryResponse): responses.AbciQueryResponse { - return { - key: fromBase64(optional(data.key, "")), - value: fromBase64(optional(data.value, "")), - proof: may(decodeQueryProof, data.proofOps), - height: may(Integer.parse, data.height), - code: may(Integer.parse, data.code), - index: may(Integer.parse, data.index), - log: data.log, - }; -} - -interface RpcAttribute { - /** base64 encoded */ - readonly key: string; - /** base64 encoded */ - readonly value: string; -} - -function decodeAttribute(attribute: RpcAttribute): responses.Attribute { - return { - key: fromBase64(assertNotEmpty(attribute.key)), - value: fromBase64(optional(attribute.value, "")), - }; -} - -function decodeAttributes(attributes: readonly RpcAttribute[]): responses.Attribute[] { - return assertArray(attributes).map(decodeAttribute); -} - -interface RpcEvent { - readonly type: string; - readonly attributes: readonly RpcAttribute[]; -} - -function decodeEvent(event: RpcEvent): responses.Event { - return { - type: event.type, - attributes: decodeAttributes(event.attributes), - }; -} - -function decodeEvents(events: readonly RpcEvent[] | undefined): readonly responses.Event[] { - return assertArray(events ?? []).map(decodeEvent); -} - -interface RpcTxData { - readonly code?: number; - readonly log?: string; - /** base64 encoded */ - readonly data?: string; - readonly events?: readonly RpcEvent[]; -} - -function decodeTxData(data: RpcTxData): responses.TxData { - return { - data: may(fromBase64, data.data), - log: data.log, - code: Integer.parse(assertNumber(optional(data.code, 0))), - events: decodeEvents(data.events), - }; -} - -// yes, a different format for status and dump consensus state -interface RpcPubkey { - readonly type: string; - /** base64 encoded */ - readonly value: string; -} - -function decodePubkey(data: RpcPubkey): ValidatorPubkey { - switch (data.type) { - // go-amino special code - case "tendermint/PubKeyEd25519": - return { - algorithm: "ed25519", - data: fromBase64(assertNotEmpty(data.value)), - }; - case "tendermint/PubKeySecp256k1": - return { - algorithm: "secp256k1", - data: fromBase64(assertNotEmpty(data.value)), - }; - default: - throw new Error(`unknown pubkey type: ${data.type}`); - } -} - -// for evidence, block results, etc. -interface RpcValidatorUpdate { - /** hex encoded */ - readonly address: string; - readonly pub_key: RpcPubkey; - readonly voting_power: string; - readonly proposer_priority: string; -} - -function decodeValidatorUpdate(data: RpcValidatorUpdate): responses.Validator { - return { - pubkey: decodePubkey(assertObject(data.pub_key)), - votingPower: Integer.parse(assertNotEmpty(data.voting_power)), - address: fromHex(assertNotEmpty(data.address)), - proposerPriority: Integer.parse(data.proposer_priority), - }; -} - -interface RpcBlockParams { - readonly max_bytes: string; - readonly max_gas: string; -} - -/** - * Note: we do not parse block.time_iota_ms for now because of this CHANGELOG entry - * - * > Add time_iota_ms to block's consensus parameters (not exposed to the application) - * https://github.com/tendermint/tendermint/blob/master/CHANGELOG.md#v0310 - */ -function decodeBlockParams(data: RpcBlockParams): responses.BlockParams { - return { - maxBytes: Integer.parse(assertNotEmpty(data.max_bytes)), - maxGas: Integer.parse(assertNotEmpty(data.max_gas)), - }; -} - -interface RpcEvidenceParams { - readonly max_age_num_blocks: string; - readonly max_age_duration: string; -} - -function decodeEvidenceParams(data: RpcEvidenceParams): responses.EvidenceParams { - return { - maxAgeNumBlocks: Integer.parse(assertNotEmpty(data.max_age_num_blocks)), - maxAgeDuration: Integer.parse(assertNotEmpty(data.max_age_duration)), - }; -} - -/** - * Example data: - * { - * "block": { - * "max_bytes": "22020096", - * "max_gas": "-1", - * "time_iota_ms": "1000" - * }, - * "evidence": { - * "max_age_num_blocks": "100000", - * "max_age_duration": "172800000000000" - * }, - * "validator": { - * "pub_key_types": [ - * "ed25519" - * ] - * } - * } - */ -interface RpcConsensusParams { - readonly block: RpcBlockParams; - readonly evidence: RpcEvidenceParams; -} - -function decodeConsensusParams(data: RpcConsensusParams): responses.ConsensusParams { - return { - block: decodeBlockParams(assertObject(data.block)), - evidence: decodeEvidenceParams(assertObject(data.evidence)), - }; -} - -interface RpcBlockResultsResponse { - readonly height: string; - readonly txs_results: readonly RpcTxData[] | null; - readonly begin_block_events: readonly RpcEvent[] | null; - readonly end_block_events: readonly RpcEvent[] | null; - readonly validator_updates: readonly RpcValidatorUpdate[] | null; - readonly consensus_param_updates: RpcConsensusParams | null; -} - -function decodeBlockResults(data: RpcBlockResultsResponse): responses.BlockResultsResponse { - return { - height: Integer.parse(assertNotEmpty(data.height)), - results: (data.txs_results || []).map(decodeTxData), - validatorUpdates: (data.validator_updates || []).map(decodeValidatorUpdate), - consensusUpdates: may(decodeConsensusParams, data.consensus_param_updates), - beginBlockEvents: decodeEvents(data.begin_block_events || []), - endBlockEvents: decodeEvents(data.end_block_events || []), - }; -} - -interface RpcBlockId { - /** hex encoded */ - readonly hash: string; - readonly parts: { - readonly total: string; - /** hex encoded */ - readonly hash: string; - }; -} - -function decodeBlockId(data: RpcBlockId): responses.BlockId { - return { - hash: fromHex(assertNotEmpty(data.hash)), - parts: { - total: Integer.parse(assertNotEmpty(data.parts.total)), - hash: fromHex(assertNotEmpty(data.parts.hash)), - }, - }; -} - -interface RpcBlockVersion { - readonly block: string; - readonly app?: string; -} - -function decodeBlockVersion(data: RpcBlockVersion): responses.Version { - return { - block: Integer.parse(data.block), - app: Integer.parse(data.app ?? 0), - }; -} - -interface RpcHeader { - readonly version: RpcBlockVersion; - readonly chain_id: string; - readonly height: string; - readonly time: string; - readonly num_txs: string; - readonly total_txs: string; - - readonly last_block_id: RpcBlockId; - - /** hex encoded */ - readonly last_commit_hash: string; - /** hex encoded */ - readonly data_hash: string; - - /** hex encoded */ - readonly validators_hash: string; - /** hex encoded */ - readonly next_validators_hash: string; - /** hex encoded */ - readonly consensus_hash: string; - /** hex encoded */ - readonly app_hash: string; - /** hex encoded */ - readonly last_results_hash: string; - - /** hex encoded */ - readonly evidence_hash: string; - /** hex encoded */ - readonly proposer_address: string; -} - -function decodeHeader(data: RpcHeader): responses.Header { - return { - version: decodeBlockVersion(data.version), - chainId: assertNotEmpty(data.chain_id), - height: Integer.parse(assertNotEmpty(data.height)), - time: fromRfc3339WithNanoseconds(assertNotEmpty(data.time)), - - lastBlockId: decodeBlockId(data.last_block_id), - - lastCommitHash: fromHex(assertNotEmpty(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)), - lastResultsHash: fromHex(assertSet(data.last_results_hash)), - - evidenceHash: fromHex(assertSet(data.evidence_hash)), - proposerAddress: fromHex(assertNotEmpty(data.proposer_address)), - }; -} - -interface RpcBlockMeta { - readonly block_id: RpcBlockId; - readonly header: RpcHeader; -} - -function decodeBlockMeta(data: RpcBlockMeta): responses.BlockMeta { - return { - blockId: decodeBlockId(data.block_id), - header: decodeHeader(data.header), - }; -} - -interface RpcBlockchainResponse { - readonly last_height: string; - readonly block_metas: readonly RpcBlockMeta[]; -} - -function decodeBlockchain(data: RpcBlockchainResponse): responses.BlockchainResponse { - return { - lastHeight: Integer.parse(assertNotEmpty(data.last_height)), - blockMetas: assertArray(data.block_metas).map(decodeBlockMeta), - }; -} - -interface RpcBroadcastTxSyncResponse extends RpcTxData { - /** hex encoded */ - readonly hash: string; -} - -function decodeBroadcastTxSync(data: RpcBroadcastTxSyncResponse): responses.BroadcastTxSyncResponse { - return { - ...decodeTxData(data), - hash: fromHex(assertNotEmpty(data.hash)), - }; -} - -interface RpcBroadcastTxCommitResponse { - readonly height: string; - /** hex encoded */ - readonly hash: string; - readonly check_tx: RpcTxData; - readonly deliver_tx?: RpcTxData; -} - -function decodeBroadcastTxCommit(data: RpcBroadcastTxCommitResponse): responses.BroadcastTxCommitResponse { - return { - height: Integer.parse(data.height), - hash: fromHex(assertNotEmpty(data.hash)), - checkTx: decodeTxData(assertObject(data.check_tx)), - deliverTx: may(decodeTxData, data.deliver_tx), - }; -} - -function decodeBlockIdFlag(blockIdFlag: number): BlockIdFlag { - assert(blockIdFlag in BlockIdFlag); - return blockIdFlag; -} - -type RpcSignature = { - readonly block_id_flag: number; - /** hex encoded */ - readonly validator_address: string; - readonly timestamp: string; - /** bae64 encoded */ - readonly signature: string; -}; - -function decodeCommitSignature(data: RpcSignature): CommitSignature { - return { - blockIdFlag: decodeBlockIdFlag(data.block_id_flag), - validatorAddress: fromHex(data.validator_address), - timestamp: fromRfc3339WithNanoseconds(assertNotEmpty(data.timestamp)), - signature: fromBase64(assertNotEmpty(data.signature)), - }; -} - -interface RpcCommit { - readonly block_id: RpcBlockId; - readonly height: string; - readonly round: string; - readonly signatures: readonly RpcSignature[]; -} - -function decodeCommit(data: RpcCommit): responses.Commit { - return { - blockId: decodeBlockId(assertObject(data.block_id)), - height: Integer.parse(assertNotEmpty(data.height)), - round: Integer.parse(data.round), - signatures: assertArray(data.signatures).map(decodeCommitSignature), - }; -} - -interface RpcCommitResponse { - readonly signed_header: { - readonly header: RpcHeader; - readonly commit: RpcCommit; - }; - readonly canonical: boolean; -} - -function decodeCommitResponse(data: RpcCommitResponse): responses.CommitResponse { - return { - canonical: assertBoolean(data.canonical), - header: decodeHeader(data.signed_header.header), - commit: decodeCommit(data.signed_header.commit), - }; -} - -interface RpcValidatorGenesis { - /** hex-encoded */ - readonly address: string; - readonly pub_key: RpcPubkey; - readonly power: string; - readonly name?: string; -} - -function decodeValidatorGenesis(data: RpcValidatorGenesis): responses.Validator { - return { - address: fromHex(assertNotEmpty(data.address)), - pubkey: decodePubkey(assertObject(data.pub_key)), - votingPower: Integer.parse(assertNotEmpty(data.power)), - }; -} - -interface RpcGenesisResponse { - readonly genesis_time: string; - readonly chain_id: string; - readonly consensus_params: RpcConsensusParams; - // The validators key is used to specify a set of validators for testnets or PoA blockchains. - // PoS blockchains use the app_state.genutil.gentxs field to stake and bond a number of validators in the first block. - readonly validators?: readonly RpcValidatorGenesis[]; - /** hex encoded */ - readonly app_hash: string; - readonly app_state: Record | undefined; -} - -interface GenesisResult { - readonly genesis: RpcGenesisResponse; -} - -function decodeGenesis(data: RpcGenesisResponse): responses.GenesisResponse { - return { - genesisTime: fromRfc3339WithNanoseconds(assertNotEmpty(data.genesis_time)), - chainId: assertNotEmpty(data.chain_id), - consensusParams: decodeConsensusParams(data.consensus_params), - validators: data.validators ? assertArray(data.validators).map(decodeValidatorGenesis) : [], - appHash: fromHex(assertSet(data.app_hash)), // empty string in kvstore app - appState: data.app_state, - }; -} - -// this is in status -interface RpcValidatorInfo { - /** hex encoded */ - readonly address: string; - readonly pub_key: RpcPubkey; - readonly voting_power: string; -} - -function decodeValidatorInfo(data: RpcValidatorInfo): responses.Validator { - return { - pubkey: decodePubkey(assertObject(data.pub_key)), - votingPower: Integer.parse(assertNotEmpty(data.voting_power)), - address: fromHex(assertNotEmpty(data.address)), - }; -} - -interface RpcNodeInfo { - /** hex encoded */ - readonly id: string; - /** IP and port */ - readonly listen_addr: string; - readonly network: string; - readonly version: string; - readonly channels: string; // ??? - readonly moniker: string; - readonly protocol_version: { - readonly p2p: string; - readonly block: string; - readonly app: string; - }; - /** - * Additional information. E.g. - * { - * "tx_index": "on", - * "rpc_address":"tcp://0.0.0.0:26657" - * } - */ - readonly other: Record; -} - -function decodeNodeInfo(data: RpcNodeInfo): responses.NodeInfo { - return { - id: fromHex(assertNotEmpty(data.id)), - listenAddr: assertNotEmpty(data.listen_addr), - network: assertNotEmpty(data.network), - version: assertString(data.version), // Can be empty (https://github.com/cosmos/cosmos-sdk/issues/7963) - channels: assertNotEmpty(data.channels), - moniker: assertNotEmpty(data.moniker), - other: dictionaryToStringMap(data.other), - protocolVersion: { - app: Integer.parse(assertNotEmpty(data.protocol_version.app)), - block: Integer.parse(assertNotEmpty(data.protocol_version.block)), - p2p: Integer.parse(assertNotEmpty(data.protocol_version.p2p)), - }, - }; -} - -interface RpcSyncInfo { - /** hex encoded */ - readonly latest_block_hash: string; - /** hex encoded */ - readonly latest_app_hash: string; - readonly latest_block_height: string; - readonly latest_block_time: string; - readonly catching_up: boolean; -} - -function decodeSyncInfo(data: RpcSyncInfo): responses.SyncInfo { - return { - latestBlockHash: fromHex(assertNotEmpty(data.latest_block_hash)), - latestAppHash: fromHex(assertNotEmpty(data.latest_app_hash)), - latestBlockTime: fromRfc3339WithNanoseconds(assertNotEmpty(data.latest_block_time)), - latestBlockHeight: Integer.parse(assertNotEmpty(data.latest_block_height)), - catchingUp: assertBoolean(data.catching_up), - }; -} - -interface RpcStatusResponse { - readonly node_info: RpcNodeInfo; - readonly sync_info: RpcSyncInfo; - readonly validator_info: RpcValidatorInfo; -} - -function decodeStatus(data: RpcStatusResponse): responses.StatusResponse { - return { - nodeInfo: decodeNodeInfo(data.node_info), - syncInfo: decodeSyncInfo(data.sync_info), - validatorInfo: decodeValidatorInfo(data.validator_info), - }; -} - -/** - * Example data: - * { - * "root_hash": "10A1A17D5F818099B5CAB5B91733A3CC27C0DB6CE2D571AC27FB970C314308BB", - * "data": "ZVlERVhDV2lVNEUwPXhTUjc4Tmp2QkNVSg==", - * "proof": { - * "total": "1", - * "index": "0", - * "leaf_hash": "EKGhfV+BgJm1yrW5FzOjzCfA22zi1XGsJ/uXDDFDCLs=", - * "aunts": [] - * } - * } - */ -interface RpcTxProof { - /** base64 encoded */ - readonly data: string; - /** hex encoded */ - readonly root_hash: string; - readonly proof: { - readonly total: string; - readonly index: string; - /** base64 encoded */ - readonly leaf_hash: string; - /** base64 encoded */ - readonly aunts: readonly string[]; - }; -} - -function decodeTxProof(data: RpcTxProof): responses.TxProof { - return { - data: fromBase64(assertNotEmpty(data.data)), - rootHash: fromHex(assertNotEmpty(data.root_hash)), - proof: { - total: Integer.parse(assertNotEmpty(data.proof.total)), - index: Integer.parse(assertNotEmpty(data.proof.index)), - leafHash: fromBase64(assertNotEmpty(data.proof.leaf_hash)), - aunts: assertArray(data.proof.aunts).map(fromBase64), - }, - }; -} - -interface RpcTxResponse { - /** Raw tx bytes, base64 encoded */ - readonly tx: string; - readonly tx_result: RpcTxData; - readonly height: string; - readonly index: number; - /** hex encoded */ - readonly hash: string; - readonly proof?: RpcTxProof; -} - -function decodeTxResponse(data: RpcTxResponse): responses.TxResponse { - return { - tx: fromBase64(assertNotEmpty(data.tx)), - result: decodeTxData(assertObject(data.tx_result)), - height: Integer.parse(assertNotEmpty(data.height)), - index: Integer.parse(assertNumber(data.index)), - hash: fromHex(assertNotEmpty(data.hash)), - proof: may(decodeTxProof, data.proof), - }; -} - -interface RpcTxSearchResponse { - readonly txs: readonly RpcTxResponse[]; - readonly total_count: string; -} - -function decodeTxSearch(data: RpcTxSearchResponse): responses.TxSearchResponse { - return { - totalCount: Integer.parse(assertNotEmpty(data.total_count)), - txs: assertArray(data.txs).map(decodeTxResponse), - }; -} - -interface RpcTxEvent { - /** Raw tx bytes, base64 encoded */ - readonly tx: string; - readonly result: RpcTxData; - readonly height: string; - /** Not set since Tendermint 0.34 */ - readonly index?: number; -} - -function decodeTxEvent(data: RpcTxEvent): responses.TxEvent { - const tx = fromBase64(assertNotEmpty(data.tx)); - return { - tx: tx, - hash: hashTx(tx), - result: decodeTxData(data.result), - height: Integer.parse(assertNotEmpty(data.height)), - index: may(Integer.parse, data.index), - }; -} - -interface RpcValidatorsResponse { - readonly block_height: string; - readonly validators: readonly RpcValidatorUpdate[]; - readonly count: string; - readonly total: string; -} - -function decodeValidators(data: RpcValidatorsResponse): responses.ValidatorsResponse { - return { - blockHeight: Integer.parse(assertNotEmpty(data.block_height)), - validators: assertArray(data.validators).map(decodeValidatorUpdate), - count: Integer.parse(assertNotEmpty(data.count)), - total: Integer.parse(assertNotEmpty(data.total)), - }; -} - -interface RpcEvidence { - readonly type: string; - readonly validator: RpcValidatorUpdate; - readonly height: string; - readonly time: string; - readonly totalVotingPower: string; -} - -function decodeEvidence(data: RpcEvidence): responses.Evidence { - return { - type: assertNotEmpty(data.type), - height: Integer.parse(assertNotEmpty(data.height)), - time: Integer.parse(assertNotEmpty(data.time)), - totalVotingPower: Integer.parse(assertNotEmpty(data.totalVotingPower)), - validator: decodeValidatorUpdate(data.validator), - }; -} - -function decodeEvidences(ev: readonly RpcEvidence[]): readonly responses.Evidence[] { - return assertArray(ev).map(decodeEvidence); -} - -interface RpcBlock { - readonly header: RpcHeader; - readonly last_commit: RpcCommit; - readonly data: { - /** Raw tx bytes, base64 encoded */ - readonly txs?: readonly string[]; - }; - readonly evidence?: { - readonly evidence?: readonly RpcEvidence[]; - }; -} - -function decodeBlock(data: RpcBlock): responses.Block { - return { - header: decodeHeader(assertObject(data.header)), - lastCommit: decodeCommit(assertObject(data.last_commit)), - txs: data.data.txs ? assertArray(data.data.txs).map(fromBase64) : [], - evidence: data.evidence && may(decodeEvidences, data.evidence.evidence), - }; -} - -interface RpcBlockResponse { - readonly block_id: RpcBlockId; - readonly block: RpcBlock; -} - -function decodeBlockResponse(data: RpcBlockResponse): responses.BlockResponse { - return { - blockId: decodeBlockId(data.block_id), - block: decodeBlock(data.block), - }; -} - -export class Responses { - public static decodeAbciInfo(response: JsonRpcSuccessResponse): responses.AbciInfoResponse { - return decodeAbciInfo(assertObject((response.result as AbciInfoResult).response)); - } - - public static decodeAbciQuery(response: JsonRpcSuccessResponse): responses.AbciQueryResponse { - return decodeAbciQuery(assertObject((response.result as AbciQueryResult).response)); - } - - public static decodeBlock(response: JsonRpcSuccessResponse): responses.BlockResponse { - return decodeBlockResponse(response.result as RpcBlockResponse); - } - - public static decodeBlockResults(response: JsonRpcSuccessResponse): responses.BlockResultsResponse { - return decodeBlockResults(response.result as RpcBlockResultsResponse); - } - - public static decodeBlockchain(response: JsonRpcSuccessResponse): responses.BlockchainResponse { - return decodeBlockchain(response.result as RpcBlockchainResponse); - } - - public static decodeBroadcastTxSync(response: JsonRpcSuccessResponse): responses.BroadcastTxSyncResponse { - return decodeBroadcastTxSync(response.result as RpcBroadcastTxSyncResponse); - } - - public static decodeBroadcastTxAsync(response: JsonRpcSuccessResponse): responses.BroadcastTxAsyncResponse { - return Responses.decodeBroadcastTxSync(response); - } - - public static decodeBroadcastTxCommit( - response: JsonRpcSuccessResponse, - ): responses.BroadcastTxCommitResponse { - return decodeBroadcastTxCommit(response.result as RpcBroadcastTxCommitResponse); - } - - public static decodeCommit(response: JsonRpcSuccessResponse): responses.CommitResponse { - return decodeCommitResponse(response.result as RpcCommitResponse); - } - - public static decodeGenesis(response: JsonRpcSuccessResponse): responses.GenesisResponse { - return decodeGenesis(assertObject((response.result as GenesisResult).genesis)); - } - - public static decodeHealth(): responses.HealthResponse { - return null; - } - - public static decodeStatus(response: JsonRpcSuccessResponse): responses.StatusResponse { - return decodeStatus(response.result as RpcStatusResponse); - } - - public static decodeNewBlockEvent(event: SubscriptionEvent): responses.NewBlockEvent { - return decodeBlock(event.data.value.block as RpcBlock); - } - - public static decodeNewBlockHeaderEvent(event: SubscriptionEvent): responses.NewBlockHeaderEvent { - return decodeHeader(event.data.value.header as RpcHeader); - } - - public static decodeTxEvent(event: SubscriptionEvent): responses.TxEvent { - return decodeTxEvent(event.data.value.TxResult as RpcTxEvent); - } - - public static decodeTx(response: JsonRpcSuccessResponse): responses.TxResponse { - return decodeTxResponse(response.result as RpcTxResponse); - } - - public static decodeTxSearch(response: JsonRpcSuccessResponse): responses.TxSearchResponse { - return decodeTxSearch(response.result as RpcTxSearchResponse); - } - - public static decodeValidators(response: JsonRpcSuccessResponse): responses.ValidatorsResponse { - return decodeValidators(response.result as RpcValidatorsResponse); - } -} diff --git a/packages/tendermint-rpc/src/tendermint33/adaptor/types.ts b/packages/tendermint-rpc/src/tendermint33/adaptor/types.ts deleted file mode 100644 index 9ecd4ab4..00000000 --- a/packages/tendermint-rpc/src/tendermint33/adaptor/types.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { JsonRpcRequest, JsonRpcSuccessResponse } from "@cosmjs/json-rpc"; - -import { SubscriptionEvent } from "../../rpcclients"; -import * as requests from "../requests"; -import * as responses from "../responses"; - -export interface Adaptor { - readonly params: Params; - readonly responses: Responses; - readonly hashTx: (tx: Uint8Array) => Uint8Array; - readonly hashBlock: (header: responses.Header) => Uint8Array; -} - -// Encoder is a generic that matches all methods of Params -export type Encoder = (req: T) => JsonRpcRequest; - -// Decoder is a generic that matches all methods of Responses -export type Decoder = (res: JsonRpcSuccessResponse) => T; - -export interface Params { - readonly encodeAbciInfo: (req: requests.AbciInfoRequest) => JsonRpcRequest; - readonly encodeAbciQuery: (req: requests.AbciQueryRequest) => JsonRpcRequest; - readonly encodeBlock: (req: requests.BlockRequest) => JsonRpcRequest; - readonly encodeBlockchain: (req: requests.BlockchainRequest) => JsonRpcRequest; - readonly encodeBlockResults: (req: requests.BlockResultsRequest) => JsonRpcRequest; - readonly encodeBroadcastTx: (req: requests.BroadcastTxRequest) => JsonRpcRequest; - readonly encodeCommit: (req: requests.CommitRequest) => JsonRpcRequest; - readonly encodeGenesis: (req: requests.GenesisRequest) => JsonRpcRequest; - readonly encodeHealth: (req: requests.HealthRequest) => JsonRpcRequest; - readonly encodeStatus: (req: requests.StatusRequest) => JsonRpcRequest; - readonly encodeSubscribe: (req: requests.SubscribeRequest) => JsonRpcRequest; - readonly encodeTx: (req: requests.TxRequest) => JsonRpcRequest; - readonly encodeTxSearch: (req: requests.TxSearchRequest) => JsonRpcRequest; - readonly encodeValidators: (req: requests.ValidatorsRequest) => JsonRpcRequest; -} - -export interface Responses { - readonly decodeAbciInfo: (response: JsonRpcSuccessResponse) => responses.AbciInfoResponse; - readonly decodeAbciQuery: (response: JsonRpcSuccessResponse) => responses.AbciQueryResponse; - readonly decodeBlock: (response: JsonRpcSuccessResponse) => responses.BlockResponse; - readonly decodeBlockResults: (response: JsonRpcSuccessResponse) => responses.BlockResultsResponse; - readonly decodeBlockchain: (response: JsonRpcSuccessResponse) => responses.BlockchainResponse; - readonly decodeBroadcastTxSync: (response: JsonRpcSuccessResponse) => responses.BroadcastTxSyncResponse; - readonly decodeBroadcastTxAsync: (response: JsonRpcSuccessResponse) => responses.BroadcastTxAsyncResponse; - readonly decodeBroadcastTxCommit: (response: JsonRpcSuccessResponse) => responses.BroadcastTxCommitResponse; - readonly decodeCommit: (response: JsonRpcSuccessResponse) => responses.CommitResponse; - readonly decodeGenesis: (response: JsonRpcSuccessResponse) => responses.GenesisResponse; - readonly decodeHealth: (response: JsonRpcSuccessResponse) => responses.HealthResponse; - readonly decodeStatus: (response: JsonRpcSuccessResponse) => responses.StatusResponse; - readonly decodeTx: (response: JsonRpcSuccessResponse) => responses.TxResponse; - readonly decodeTxSearch: (response: JsonRpcSuccessResponse) => responses.TxSearchResponse; - readonly decodeValidators: (response: JsonRpcSuccessResponse) => responses.ValidatorsResponse; - - // events - readonly decodeNewBlockEvent: (response: SubscriptionEvent) => responses.NewBlockEvent; - readonly decodeNewBlockHeaderEvent: (response: SubscriptionEvent) => responses.NewBlockHeaderEvent; - readonly decodeTxEvent: (response: SubscriptionEvent) => responses.TxEvent; -} diff --git a/packages/tendermint-rpc/src/tendermint33/encodings.ts b/packages/tendermint-rpc/src/tendermint33/encodings.ts index bbdcfc2a..b3a9768f 100644 --- a/packages/tendermint-rpc/src/tendermint33/encodings.ts +++ b/packages/tendermint-rpc/src/tendermint33/encodings.ts @@ -1,161 +1,8 @@ import { toUtf8 } from "@cosmjs/encoding"; -import { Int53 } from "@cosmjs/math"; import { ReadonlyDateWithNanoseconds } from "../dates"; import { BlockId, Version } from "./responses"; -/** - * A runtime checker that ensures a given value is set (i.e. not undefined or null) - * - * This is used when you want to verify that data at runtime matches the expected type. - */ -export function assertSet(value: T): T { - if ((value as unknown) === undefined) { - throw new Error("Value must not be undefined"); - } - - if ((value as unknown) === null) { - throw new Error("Value must not be null"); - } - - return value; -} - -/** - * A runtime checker that ensures a given value is a boolean - * - * This is used when you want to verify that data at runtime matches the expected type. - * This implies assertSet. - */ -export function assertBoolean(value: boolean): boolean { - assertSet(value); - if (typeof (value as unknown) !== "boolean") { - throw new Error("Value must be a boolean"); - } - return value; -} - -/** - * A runtime checker that ensures a given value is a string. - * - * This is used when you want to verify that data at runtime matches the expected type. - * This implies assertSet. - */ -export function assertString(value: string): string { - assertSet(value); - if (typeof (value as unknown) !== "string") { - throw new Error("Value must be a string"); - } - return value; -} - -/** - * A runtime checker that ensures a given value is a number - * - * This is used when you want to verify that data at runtime matches the expected type. - * This implies assertSet. - */ -export function assertNumber(value: number): number { - assertSet(value); - if (typeof (value as unknown) !== "number") { - throw new Error("Value must be a number"); - } - return value; -} - -/** - * A runtime checker that ensures a given value is an array - * - * This is used when you want to verify that data at runtime matches the expected type. - * This implies assertSet. - */ -export function assertArray(value: readonly T[]): readonly T[] { - assertSet(value); - if (!Array.isArray(value as unknown)) { - throw new Error("Value must be a an array"); - } - return value; -} - -/** - * A runtime checker that ensures a given value is an object in the sense of JSON - * (an unordered collection of key–value pairs where the keys are strings) - * - * This is used when you want to verify that data at runtime matches the expected type. - * This implies assertSet. - */ -export function assertObject(value: T): T { - assertSet(value); - if (typeof (value as unknown) !== "object") { - throw new Error("Value must be an object"); - } - - // Exclude special kind of objects like Array, Date or Uint8Array - // Object.prototype.toString() returns a specified value: - // http://www.ecma-international.org/ecma-262/7.0/index.html#sec-object.prototype.tostring - if (Object.prototype.toString.call(value) !== "[object Object]") { - throw new Error("Value must be a simple object"); - } - - return value; -} - -interface Lengther { - readonly length: number; -} - -/** - * Throws an error if value matches the empty value for the - * given type (array/string of length 0, number of value 0, ...) - * - * Otherwise returns the value. - * - * This implies assertSet - */ -export function assertNotEmpty(value: T): T { - assertSet(value); - - if (typeof value === "number" && value === 0) { - throw new Error("must provide a non-zero value"); - } else if ((value as any as Lengther).length === 0) { - throw new Error("must provide a non-empty value"); - } - return value; -} - -// optional uses the value or provides a default -export function optional(value: T | null | undefined, fallback: T): T { - return value === undefined || value === null ? fallback : value; -} - -// may will run the transform if value is defined, otherwise returns undefined -export function may(transform: (val: T) => U, value: T | null | undefined): U | undefined { - return value === undefined || value === null ? undefined : transform(value); -} - -export function dictionaryToStringMap(obj: Record): Map { - const out = new Map(); - for (const key of Object.keys(obj)) { - const value = obj[key]; - if (typeof value !== "string") { - throw new Error("Found dictionary value of type other than string"); - } - out.set(key, value); - } - return out; -} - -export class Integer { - public static parse(input: string | number): number { - const asInt = typeof input === "number" ? new Int53(input) : Int53.fromString(input); - return asInt.toNumber(); - } - - public static encode(num: number): string { - return new Int53(num).toString(); - } -} - // Encodings needed for hashing block headers // Several of these functions are inspired by https://github.com/nomic-io/js-tendermint/blob/tendermint-0.30/src/ diff --git a/packages/tendermint-rpc/src/tendermint33/hasher.spec.ts b/packages/tendermint-rpc/src/tendermint33/hasher.spec.ts index c6644875..7ddc30e6 100644 --- a/packages/tendermint-rpc/src/tendermint33/hasher.spec.ts +++ b/packages/tendermint-rpc/src/tendermint33/hasher.spec.ts @@ -2,17 +2,9 @@ import { fromBase64, fromHex } from "@cosmjs/encoding"; import { ReadonlyDate } from "readonly-date"; import { ReadonlyDateWithNanoseconds } from "../dates"; -import { hashBlock, hashTx } from "./hasher"; +import { hashBlock } from "./hasher"; describe("Hasher", () => { - it("creates transaction hash equal to local test", () => { - // This was taken from a result from /tx_search of some random test transaction - // curl "http://localhost:11127/tx_search?query=\"tx.hash='5CB2CF94A1097A4BC19258BC2353C3E76102B6D528458BE45C855DC5563C1DB2'\"" - const txId = fromHex("5CB2CF94A1097A4BC19258BC2353C3E76102B6D528458BE45C855DC5563C1DB2"); - const txData = fromBase64("YUpxZDY2NURaUDMxPWd2TzBPdnNrVWFWYg=="); - expect(hashTx(txData)).toEqual(txId); - }); - it("creates block hash equal to local test for empty block", () => { // This was taken from a result from /block of some random empty block // curl "http://localhost:11133/block" diff --git a/packages/tendermint-rpc/src/tendermint33/hasher.ts b/packages/tendermint-rpc/src/tendermint33/hasher.ts index 75d9f484..e61ff04d 100644 --- a/packages/tendermint-rpc/src/tendermint33/hasher.ts +++ b/packages/tendermint-rpc/src/tendermint33/hasher.ts @@ -1,14 +1,8 @@ -import { Sha256, sha256 } from "@cosmjs/crypto"; +import { Sha256 } from "@cosmjs/crypto"; import { encodeBlockId, encodeBytes, encodeInt, encodeString, encodeTime, encodeVersion } from "./encodings"; import { Header } from "./responses"; -// hash is sha256 -// https://github.com/tendermint/tendermint/blob/master/UPGRADING.md#v0260 -export function hashTx(tx: Uint8Array): Uint8Array { - return sha256(tx); -} - function getSplitPoint(n: number): number { if (n < 1) throw new Error("Cannot split an empty tree"); const largestPowerOf2 = 2 ** Math.floor(Math.log2(n)); diff --git a/packages/tendermint-rpc/src/tendermint33/index.ts b/packages/tendermint-rpc/src/tendermint33/index.ts index 979eed37..718ca926 100644 --- a/packages/tendermint-rpc/src/tendermint33/index.ts +++ b/packages/tendermint-rpc/src/tendermint33/index.ts @@ -1,7 +1,6 @@ // Note: all exports in this module are public available via // `import { tendermint33 } from "@cosmjs/tendermint-rpc"` -export { Tendermint33Client } from "./tendermint33client"; export { AbciInfoRequest, AbciQueryParams, diff --git a/packages/tendermint-rpc/src/tendermint33/tendermint33client.spec.ts b/packages/tendermint-rpc/src/tendermint33/tendermint33client.spec.ts deleted file mode 100644 index fee2769a..00000000 --- a/packages/tendermint-rpc/src/tendermint33/tendermint33client.spec.ts +++ /dev/null @@ -1,775 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import { toAscii } from "@cosmjs/encoding"; -import { firstEvent, toListPromise } from "@cosmjs/stream"; -import { sleep } from "@cosmjs/utils"; -import { ReadonlyDate } from "readonly-date"; -import { Stream } from "xstream"; - -import { HttpClient, RpcClient, WebsocketClient } from "../rpcclients"; -import { - buildKvTx, - chainIdMatcher, - ExpectedValues, - pendingWithoutTendermint, - randomString, - tendermintEnabled, - tendermintInstances, - tendermintSearchIndexUpdated, -} from "../testutil.spec"; -import { adaptor33 } from "./adaptor"; -import { buildQuery } from "./requests"; -import * as responses from "./responses"; -import { Tendermint33Client } from "./tendermint33client"; - -function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues): void { - describe("create", () => { - it("can auto-discover Tendermint version and communicate", async () => { - pendingWithoutTendermint(); - const client = await Tendermint33Client.create(rpcFactory()); - const info = await client.abciInfo(); - expect(info).toBeTruthy(); - client.disconnect(); - }); - - it("can connect to Tendermint with known version", async () => { - pendingWithoutTendermint(); - const client = await Tendermint33Client.create(rpcFactory()); - expect(await client.abciInfo()).toBeTruthy(); - client.disconnect(); - }); - }); - - it("can get genesis", async () => { - pendingWithoutTendermint(); - const client = await Tendermint33Client.create(rpcFactory()); - const genesis = await client.genesis(); - expect(genesis).toBeTruthy(); - client.disconnect(); - }); - - describe("broadcastTxCommit", () => { - it("can broadcast a transaction", async () => { - pendingWithoutTendermint(); - const client = await Tendermint33Client.create(rpcFactory()); - const tx = buildKvTx(randomString(), randomString()); - - const response = await client.broadcastTxCommit({ tx: tx }); - expect(response.height).toBeGreaterThan(2); - expect(response.hash).toBeTruthy(); - // verify success - expect(response.checkTx.code).toBeFalsy(); - expect(response.deliverTx).toBeTruthy(); - if (response.deliverTx) { - expect(response.deliverTx.code).toBeFalsy(); - } - - client.disconnect(); - }); - }); - - describe("broadcastTxSync", () => { - it("can broadcast a transaction", async () => { - pendingWithoutTendermint(); - const client = await Tendermint33Client.create(rpcFactory()); - const tx = buildKvTx(randomString(), randomString()); - - const response = await client.broadcastTxSync({ tx: tx }); - expect(response.hash.length).toEqual(32); - // verify success - expect(response.code).toBeFalsy(); - - client.disconnect(); - }); - }); - - describe("broadcastTxAsync", () => { - it("can broadcast a transaction", async () => { - pendingWithoutTendermint(); - const client = await Tendermint33Client.create(rpcFactory()); - const tx = buildKvTx(randomString(), randomString()); - - const response = await client.broadcastTxAsync({ tx: tx }); - // TODO: Remove any cast after https://github.com/cosmos/cosmjs/issues/938 - expect((response as any).hash.length).toEqual(32); - - client.disconnect(); - }); - }); - - it("gets the same tx hash from backend as calculated locally", async () => { - pendingWithoutTendermint(); - const client = await Tendermint33Client.create(rpcFactory()); - const tx = buildKvTx(randomString(), randomString()); - const calculatedTxHash = adaptor33.hashTx(tx); - - const response = await client.broadcastTxCommit({ tx: tx }); - expect(response.hash).toEqual(calculatedTxHash); - - client.disconnect(); - }); - - it("can query the state", async () => { - pendingWithoutTendermint(); - const client = await Tendermint33Client.create(rpcFactory()); - - const key = randomString(); - const value = randomString(); - await client.broadcastTxCommit({ tx: buildKvTx(key, value) }); - - const binKey = toAscii(key); - const binValue = toAscii(value); - const queryParams = { path: "/key", data: binKey, prove: true }; - const response = await client.abciQuery(queryParams); - expect(response.key).toEqual(binKey); - expect(response.value).toEqual(binValue); - expect(response.code).toBeFalsy(); - - client.disconnect(); - }); - - it("can get a commit", async () => { - pendingWithoutTendermint(); - const client = await Tendermint33Client.create(rpcFactory()); - const response = await client.commit(4); - - expect(response).toBeTruthy(); - expect(response.commit.signatures.length).toBeGreaterThanOrEqual(1); - expect(response.commit.signatures[0].blockIdFlag).toEqual(2); - expect(response.commit.signatures[0].validatorAddress?.length).toEqual(20); - expect(response.commit.signatures[0].timestamp).toBeInstanceOf(Date); - expect(response.commit.signatures[0].signature?.length).toEqual(64); - - client.disconnect(); - }); - - it("can get validators", async () => { - pendingWithoutTendermint(); - const client = await Tendermint33Client.create(rpcFactory()); - const response = await client.validators({}); - - expect(response).toBeTruthy(); - expect(response.blockHeight).toBeGreaterThanOrEqual(1); - expect(response.count).toBeGreaterThanOrEqual(1); - expect(response.total).toBeGreaterThanOrEqual(1); - expect(response.validators.length).toBeGreaterThanOrEqual(1); - expect(response.validators[0].address.length).toEqual(20); - expect(response.validators[0].pubkey).toBeDefined(); - expect(response.validators[0].votingPower).toBeGreaterThanOrEqual(0); - expect(response.validators[0].proposerPriority).toBeGreaterThanOrEqual(0); - - client.disconnect(); - }); - - it("can get all validators", async () => { - pendingWithoutTendermint(); - const client = await Tendermint33Client.create(rpcFactory()); - const response = await client.validatorsAll(); - - expect(response).toBeTruthy(); - expect(response.blockHeight).toBeGreaterThanOrEqual(1); - expect(response.count).toBeGreaterThanOrEqual(1); - expect(response.total).toBeGreaterThanOrEqual(1); - expect(response.validators.length).toBeGreaterThanOrEqual(1); - expect(response.validators[0].address.length).toEqual(20); - expect(response.validators[0].pubkey).toBeDefined(); - expect(response.validators[0].votingPower).toBeGreaterThanOrEqual(0); - expect(response.validators[0].proposerPriority).toBeGreaterThanOrEqual(0); - - client.disconnect(); - }); - - it("can call a bunch of methods", async () => { - pendingWithoutTendermint(); - const client = await Tendermint33Client.create(rpcFactory()); - - expect(await client.block()).toBeTruthy(); - expect(await client.genesis()).toBeTruthy(); - expect(await client.health()).toBeNull(); - - client.disconnect(); - }); - - describe("status", () => { - it("works", async () => { - pendingWithoutTendermint(); - const client = await Tendermint33Client.create(rpcFactory()); - - const status = await client.status(); - - // node info - expect(status.nodeInfo.version).toMatch(expected.version); - expect(status.nodeInfo.protocolVersion).toEqual({ - p2p: expected.p2pVersion, - block: expected.blockVersion, - app: expected.appVersion, - }); - expect(status.nodeInfo.network).toMatch(chainIdMatcher); - expect(status.nodeInfo.other.size).toBeGreaterThanOrEqual(2); - expect(status.nodeInfo.other.get("tx_index")).toEqual("on"); - - // sync info - expect(status.syncInfo.catchingUp).toEqual(false); - expect(status.syncInfo.latestBlockHeight).toBeGreaterThanOrEqual(1); - - // validator info - expect(status.validatorInfo.pubkey).toBeTruthy(); - expect(status.validatorInfo.votingPower).toBeGreaterThan(0); - - client.disconnect(); - }); - }); - - describe("blockResults", () => { - it("works", async () => { - pendingWithoutTendermint(); - const client = await Tendermint33Client.create(rpcFactory()); - - const height = 3; - const results = await client.blockResults(height); - expect(results.height).toEqual(height); - expect(results.results).toEqual([]); - expect(results.beginBlockEvents).toEqual([]); - expect(results.endBlockEvents).toEqual([]); - - client.disconnect(); - }); - }); - - describe("blockchain", () => { - it("returns latest in descending order by default", async () => { - pendingWithoutTendermint(); - const client = await Tendermint33Client.create(rpcFactory()); - - // Run in parallel to increase chance there is no block between the calls - const [status, blockchain] = await Promise.all([client.status(), client.blockchain()]); - const height = status.syncInfo.latestBlockHeight; - - expect(blockchain.lastHeight).toEqual(height); - expect(blockchain.blockMetas.length).toBeGreaterThanOrEqual(3); - expect(blockchain.blockMetas[0].header.height).toEqual(height); - expect(blockchain.blockMetas[1].header.height).toEqual(height - 1); - expect(blockchain.blockMetas[2].header.height).toEqual(height - 2); - - client.disconnect(); - }); - - it("can limit by maxHeight", async () => { - pendingWithoutTendermint(); - const client = await Tendermint33Client.create(rpcFactory()); - - const height = (await client.status()).syncInfo.latestBlockHeight; - const blockchain = await client.blockchain(undefined, height - 1); - expect(blockchain.lastHeight).toEqual(height); - expect(blockchain.blockMetas.length).toBeGreaterThanOrEqual(2); - expect(blockchain.blockMetas[0].header.height).toEqual(height - 1); // upper limit included - expect(blockchain.blockMetas[1].header.height).toEqual(height - 2); - - client.disconnect(); - }); - - it("works with maxHeight in the future", async () => { - pendingWithoutTendermint(); - const client = await Tendermint33Client.create(rpcFactory()); - - const height = (await client.status()).syncInfo.latestBlockHeight; - const blockchain = await client.blockchain(undefined, height + 20); - expect(blockchain.lastHeight).toEqual(height); - expect(blockchain.blockMetas.length).toBeGreaterThanOrEqual(2); - expect(blockchain.blockMetas[0].header.height).toEqual(height); - expect(blockchain.blockMetas[1].header.height).toEqual(height - 1); - - client.disconnect(); - }); - - it("can limit by minHeight and maxHeight", async () => { - pendingWithoutTendermint(); - const client = await Tendermint33Client.create(rpcFactory()); - - const height = (await client.status()).syncInfo.latestBlockHeight; - const blockchain = await client.blockchain(height - 2, height - 1); - expect(blockchain.lastHeight).toEqual(height); - expect(blockchain.blockMetas.length).toEqual(2); - expect(blockchain.blockMetas[0].header.height).toEqual(height - 1); // upper limit included - expect(blockchain.blockMetas[1].header.height).toEqual(height - 2); // lower limit included - - client.disconnect(); - }); - - it("contains all the info", async () => { - pendingWithoutTendermint(); - const client = await Tendermint33Client.create(rpcFactory()); - - const height = (await client.status()).syncInfo.latestBlockHeight; - const blockchain = await client.blockchain(height - 1, height - 1); - - expect(blockchain.lastHeight).toEqual(height); - expect(blockchain.blockMetas.length).toBeGreaterThanOrEqual(1); - const meta = blockchain.blockMetas[0]; - - // TODO: check all the fields - expect(meta).toEqual({ - blockId: jasmine.objectContaining({}), - // block_size: jasmine.stringMatching(nonNegativeIntegerMatcher), - // num_txs: jasmine.stringMatching(nonNegativeIntegerMatcher), - header: jasmine.objectContaining({ - version: { - block: expected.blockVersion, - app: expected.appVersion, - }, - chainId: jasmine.stringMatching(chainIdMatcher), - }), - }); - - client.disconnect(); - }); - }); - - describe("tx", () => { - it("can query a tx properly", async () => { - pendingWithoutTendermint(); - const client = await Tendermint33Client.create(rpcFactory()); - - const find = randomString(); - const me = randomString(); - const tx = buildKvTx(find, me); - - const txRes = await client.broadcastTxCommit({ tx: tx }); - expect(responses.broadcastTxCommitSuccess(txRes)).toEqual(true); - expect(txRes.height).toBeTruthy(); - const height: number = txRes.height || 0; // || 0 for type system - expect(txRes.hash.length).not.toEqual(0); - const hash = txRes.hash; - - await tendermintSearchIndexUpdated(); - - // find by hash - does it match? - const r = await client.tx({ hash: hash, prove: true }); - // both values come from rpc, so same type (Buffer/Uint8Array) - expect(r.hash).toEqual(hash); - // force the type when comparing to locally generated value - expect(r.tx).toEqual(tx); - expect(r.height).toEqual(height); - expect(r.proof).toBeTruthy(); - - // txSearch - you must enable the indexer when running - // tendermint, else you get empty results - const query = buildQuery({ tags: [{ key: "app.key", value: find }] }); - - const s = await client.txSearch({ query: query, page: 1, per_page: 30 }); - // should find the tx - expect(s.totalCount).toEqual(1); - // should return same info as querying directly, - // except without the proof - expect(s.txs[0]).toEqual({ ...r, proof: undefined }); - - // ensure txSearchAll works as well - const sall = await client.txSearchAll({ query: query }); - // should find the tx - expect(sall.totalCount).toEqual(1); - // should return same info as querying directly, - // except without the proof - expect(sall.txs[0]).toEqual({ ...r, proof: undefined }); - - // and let's query the block itself to see this transaction - const block = await client.block(height); - expect(block.block.txs.length).toEqual(1); - expect(block.block.txs[0]).toEqual(tx); - - client.disconnect(); - }); - }); - - describe("txSearch", () => { - const key = randomString(); - - beforeAll(async () => { - if (tendermintEnabled()) { - const client = await Tendermint33Client.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("returns transactions in ascending order by default", async () => { - // NOTE: The Tendermint docs claim the default ordering is "desc" but it is actually "asc" - // Docs: https://docs.tendermint.com/master/rpc/#/Info/tx_search - // Code: https://github.com/tendermint/tendermint/blob/v0.33.9/rpc/core/tx.go#L84 - pendingWithoutTendermint(); - const client = await Tendermint33Client.create(rpcFactory()); - - const query = buildQuery({ tags: [{ key: "app.key", value: key }] }); - - const s = await client.txSearch({ query: query }); - - expect(s.totalCount).toEqual(3); - s.txs.slice(1).reduce((lastHeight, { height }) => { - expect(height).toBeGreaterThanOrEqual(lastHeight); - return height; - }, s.txs[0].height); - - client.disconnect(); - }); - - it("can set the order", async () => { - pendingWithoutTendermint(); - const client = await Tendermint33Client.create(rpcFactory()); - - const query = buildQuery({ tags: [{ key: "app.key", value: key }] }); - - const s1 = await client.txSearch({ query: query, order_by: "desc" }); - const s2 = await client.txSearch({ query: query, order_by: "asc" }); - - expect(s1.totalCount).toEqual(s2.totalCount); - expect([...s1.txs].reverse()).toEqual(s2.txs); - - client.disconnect(); - }); - - it("can paginate over txSearch results", async () => { - pendingWithoutTendermint(); - const client = await Tendermint33Client.create(rpcFactory()); - - const query = buildQuery({ tags: [{ key: "app.key", value: key }] }); - - // expect one page of results - const s1 = await client.txSearch({ query: query, page: 1, per_page: 2 }); - expect(s1.totalCount).toEqual(3); - expect(s1.txs.length).toEqual(2); - - // second page - const s2 = await client.txSearch({ query: query, page: 2, per_page: 2 }); - expect(s2.totalCount).toEqual(3); - expect(s2.txs.length).toEqual(1); - - client.disconnect(); - }); - - it("can get all search results in one call", async () => { - pendingWithoutTendermint(); - const client = await Tendermint33Client.create(rpcFactory()); - - const query = buildQuery({ tags: [{ key: "app.key", value: key }] }); - - const sall = await client.txSearchAll({ query: query, per_page: 2 }); - expect(sall.totalCount).toEqual(3); - expect(sall.txs.length).toEqual(3); - // make sure there are in order from lowest to highest height - const [tx1, tx2, tx3] = sall.txs; - expect(tx2.height).toEqual(tx1.height + 1); - expect(tx3.height).toEqual(tx2.height + 1); - - client.disconnect(); - }); - }); -} - -function websocketTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues): void { - it("can subscribe to block header events", (done) => { - pendingWithoutTendermint(); - - const testStart = ReadonlyDate.now(); - - (async () => { - const events: responses.NewBlockHeaderEvent[] = []; - const client = await Tendermint33Client.create(rpcFactory()); - const stream = client.subscribeNewBlockHeader(); - expect(stream).toBeTruthy(); - const subscription = stream.subscribe({ - next: (event) => { - expect(event.chainId).toMatch(chainIdMatcher); - expect(event.height).toBeGreaterThan(0); - // seems that tendermint just guarantees within the last second for timestamp - expect(event.time.getTime()).toBeGreaterThan(testStart - 1000); - // Tendermint clock is sometimes ahead of test clock. Add 10ms tolerance - expect(event.time.getTime()).toBeLessThanOrEqual(ReadonlyDate.now() + 10); - expect(event.lastBlockId).toBeTruthy(); - - // merkle roots for proofs - expect(event.appHash).toBeTruthy(); - expect(event.consensusHash).toBeTruthy(); - expect(event.dataHash).toBeTruthy(); - expect(event.evidenceHash).toBeTruthy(); - expect(event.lastCommitHash).toBeTruthy(); - expect(event.lastResultsHash).toBeTruthy(); - expect(event.validatorsHash).toBeTruthy(); - - events.push(event); - - if (events.length === 2) { - subscription.unsubscribe(); - expect(events.length).toEqual(2); - expect(events[1].chainId).toEqual(events[0].chainId); - expect(events[1].height).toEqual(events[0].height + 1); - expect(events[1].time.getTime()).toBeGreaterThan(events[0].time.getTime()); - - expect(events[1].appHash).toEqual(events[0].appHash); - expect(events[1].consensusHash).toEqual(events[0].consensusHash); - expect(events[1].dataHash).toEqual(events[0].dataHash); - expect(events[1].evidenceHash).toEqual(events[0].evidenceHash); - expect(events[1].lastCommitHash).not.toEqual(events[0].lastCommitHash); - expect(events[1].lastResultsHash).not.toEqual(events[0].lastResultsHash); - expect(events[1].validatorsHash).toEqual(events[0].validatorsHash); - - client.disconnect(); - done(); - } - }, - error: done.fail, - complete: () => done.fail("Stream completed before we are done"), - }); - })().catch(done.fail); - }); - - it("can subscribe to block events", async () => { - pendingWithoutTendermint(); - - const testStart = ReadonlyDate.now(); - - const transactionData1 = buildKvTx(randomString(), randomString()); - const transactionData2 = buildKvTx(randomString(), randomString()); - - const events: responses.NewBlockEvent[] = []; - const client = await Tendermint33Client.create(rpcFactory()); - const stream = client.subscribeNewBlock(); - const subscription = stream.subscribe({ - next: (event) => { - expect(event.header.chainId).toMatch(chainIdMatcher); - expect(event.header.height).toBeGreaterThan(0); - // seems that tendermint just guarantees within the last second for timestamp - expect(event.header.time.getTime()).toBeGreaterThan(testStart - 1000); - // Tendermint clock is sometimes ahead of test clock. Add 10ms tolerance - expect(event.header.time.getTime()).toBeLessThanOrEqual(ReadonlyDate.now() + 10); - expect(event.header.lastBlockId).toBeTruthy(); - - // merkle roots for proofs - expect(event.header.appHash).toBeTruthy(); - expect(event.header.consensusHash).toBeTruthy(); - expect(event.header.dataHash).toBeTruthy(); - expect(event.header.evidenceHash).toBeTruthy(); - expect(event.header.lastCommitHash).toBeTruthy(); - expect(event.header.lastResultsHash).toBeTruthy(); - expect(event.header.validatorsHash).toBeTruthy(); - - events.push(event); - - if (events.length === 2) { - subscription.unsubscribe(); - } - }, - error: fail, - }); - - await client.broadcastTxCommit({ tx: transactionData1 }); - await client.broadcastTxCommit({ tx: transactionData2 }); - - // wait for events to be processed - await sleep(100); - - expect(events.length).toEqual(2); - // Block header - expect(events[1].header.height).toEqual(events[0].header.height + 1); - expect(events[1].header.chainId).toEqual(events[0].header.chainId); - expect(events[1].header.time.getTime()).toBeGreaterThan(events[0].header.time.getTime()); - expect(events[1].header.appHash).not.toEqual(events[0].header.appHash); - expect(events[1].header.validatorsHash).toEqual(events[0].header.validatorsHash); - // Block body - expect(events[0].txs.length).toEqual(1); - expect(events[1].txs.length).toEqual(1); - expect(events[0].txs[0]).toEqual(transactionData1); - expect(events[1].txs[0]).toEqual(transactionData2); - - client.disconnect(); - }); - - it("can subscribe to transaction events", async () => { - pendingWithoutTendermint(); - - const events: responses.TxEvent[] = []; - const client = await Tendermint33Client.create(rpcFactory()); - const stream = client.subscribeTx(); - const subscription = stream.subscribe({ - next: (event) => { - expect(event.height).toBeGreaterThan(0); - expect(event.result).toBeTruthy(); - expect(event.result.events.length).toBeGreaterThanOrEqual(1); - - events.push(event); - - if (events.length === 2) { - subscription.unsubscribe(); - } - }, - error: fail, - }); - - const transactionData1 = buildKvTx(randomString(), randomString()); - const transactionData2 = buildKvTx(randomString(), randomString()); - - await client.broadcastTxCommit({ tx: transactionData1 }); - await client.broadcastTxCommit({ tx: transactionData2 }); - - // wait for events to be processed - await sleep(100); - - expect(events.length).toEqual(2); - // Meta - expect(events[1].height).toEqual(events[0].height + 1); - expect(events[1].result.events).not.toEqual(events[0].result.events); - // Content - expect(events[0].tx).toEqual(transactionData1); - expect(events[1].tx).toEqual(transactionData2); - - client.disconnect(); - }); - - it("can subscribe to transaction events filtered by creator", async () => { - pendingWithoutTendermint(); - - const transactionData1 = buildKvTx(randomString(), randomString()); - const transactionData2 = buildKvTx(randomString(), randomString()); - - const events: responses.TxEvent[] = []; - const client = await Tendermint33Client.create(rpcFactory()); - const query = buildQuery({ tags: [{ key: "app.creator", value: expected.appCreator }] }); - const stream = client.subscribeTx(query); - expect(stream).toBeTruthy(); - const subscription = stream.subscribe({ - next: (event) => { - expect(event.height).toBeGreaterThan(0); - expect(event.result).toBeTruthy(); - expect(event.result.events.length).toBeGreaterThanOrEqual(1); - events.push(event); - - if (events.length === 2) { - subscription.unsubscribe(); - } - }, - error: fail, - }); - - await client.broadcastTxCommit({ tx: transactionData1 }); - await client.broadcastTxCommit({ tx: transactionData2 }); - - // wait for events to be processed - await sleep(100); - - expect(events.length).toEqual(2); - // Meta - expect(events[1].height).toEqual(events[0].height + 1); - expect(events[1].result.events).not.toEqual(events[0].result.events); - // Content - expect(events[0].tx).toEqual(transactionData1); - expect(events[1].tx).toEqual(transactionData2); - - client.disconnect(); - }); - - it("can unsubscribe and re-subscribe to the same stream", async () => { - pendingWithoutTendermint(); - - const client = await Tendermint33Client.create(rpcFactory()); - const stream = client.subscribeNewBlockHeader(); - - const event1 = await firstEvent(stream); - expect(event1.height).toBeGreaterThanOrEqual(1); - expect(event1.time.getTime()).toBeGreaterThanOrEqual(1); - - // No sleep: producer will not be stopped in the meantime - - const event2 = await firstEvent(stream); - expect(event2.height).toBeGreaterThan(event1.height); - expect(event2.time.getTime()).toBeGreaterThan(event1.time.getTime()); - - // Very short sleep: just enough to schedule asynchronous producer stopping - await sleep(5); - - const event3 = await firstEvent(stream); - expect(event3.height).toBeGreaterThan(event2.height); - expect(event3.time.getTime()).toBeGreaterThan(event2.time.getTime()); - - // Proper sleep: enough to finish unsubscribing at over the network - await sleep(100); - - const event4 = await firstEvent(stream); - expect(event4.height).toBeGreaterThan(event3.height); - expect(event4.time.getTime()).toBeGreaterThan(event3.time.getTime()); - - client.disconnect(); - }); - - it("can subscribe twice", async () => { - pendingWithoutTendermint(); - - const client = await Tendermint33Client.create(rpcFactory()); - const stream1 = client.subscribeNewBlockHeader(); - const stream2 = client.subscribeNewBlockHeader(); - - const events = await toListPromise(Stream.merge(stream1, stream2), 4); - - expect(new Set(events.map((e) => e.height)).size).toEqual(2); - - client.disconnect(); - }); -} - -describe(`Tendermint33Client`, () => { - const { url, expected } = tendermintInstances[0]; - - it("can connect to a given url", async () => { - pendingWithoutTendermint(); - - // default connection - { - const client = await Tendermint33Client.connect(url); - const info = await client.abciInfo(); - expect(info).toBeTruthy(); - client.disconnect(); - } - - // http connection - { - const client = await Tendermint33Client.connect("http://" + url); - const info = await client.abciInfo(); - expect(info).toBeTruthy(); - client.disconnect(); - } - - // ws connection - { - const client = await Tendermint33Client.connect("ws://" + url); - const info = await client.abciInfo(); - expect(info).toBeTruthy(); - client.disconnect(); - } - }); - - describe("With HttpClient", () => { - defaultTestSuite(() => new HttpClient(url), expected); - }); - - describe("With WebsocketClient", () => { - // don't print out WebSocket errors if marked pending - const onError = process.env.TENDERMINT_ENABLED ? console.error : () => 0; - const factory = (): WebsocketClient => new WebsocketClient(url, onError); - defaultTestSuite(factory, expected); - websocketTestSuite(factory, expected); - }); -}); diff --git a/packages/tendermint-rpc/src/tendermint33/tendermint33client.ts b/packages/tendermint-rpc/src/tendermint33/tendermint33client.ts deleted file mode 100644 index 6ef2d594..00000000 --- a/packages/tendermint-rpc/src/tendermint33/tendermint33client.ts +++ /dev/null @@ -1,299 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import { Stream } from "xstream"; - -import { createJsonRpcRequest } from "../jsonrpc"; -import { - HttpClient, - instanceOfRpcStreamingClient, - RpcClient, - SubscriptionEvent, - WebsocketClient, -} from "../rpcclients"; -import { adaptor33, Decoder, Encoder, Params, Responses } from "./adaptor"; -import * as requests from "./requests"; -import * as responses from "./responses"; - -export class Tendermint33Client { - /** - * Creates a new Tendermint client for the given endpoint. - * - * Uses HTTP when the URL schema is http or https. Uses WebSockets otherwise. - */ - public static async connect(url: string): Promise { - const useHttp = url.startsWith("http://") || url.startsWith("https://"); - const rpcClient = useHttp ? new HttpClient(url) : new WebsocketClient(url); - return Tendermint33Client.create(rpcClient); - } - - /** - * Creates a new Tendermint client given an RPC client. - */ - public static async create(rpcClient: RpcClient): Promise { - // For some very strange reason I don't understand, tests start to fail on some systems - // (our CI) when skipping the status call before doing other queries. Sleeping a little - // while did not help. Thus we query the version as a way to say "hi" to the backend, - // even in cases where we don't use the result. - const _version = await this.detectVersion(rpcClient); - return new Tendermint33Client(rpcClient); - } - - private static async detectVersion(client: RpcClient): Promise { - const req = createJsonRpcRequest(requests.Method.Status); - const response = await client.execute(req); - const result = response.result; - - if (!result || !result.node_info) { - throw new Error("Unrecognized format for status response"); - } - - const version = result.node_info.version; - if (typeof version !== "string") { - throw new Error("Unrecognized version format: must be string"); - } - return version; - } - - private readonly client: RpcClient; - private readonly p: Params; - private readonly r: Responses; - - /** - * Use `Tendermint33Client.connect` or `Tendermint33Client.create` to create an instance. - */ - private constructor(client: RpcClient) { - this.client = client; - this.p = adaptor33.params; - this.r = adaptor33.responses; - } - - public disconnect(): void { - this.client.disconnect(); - } - - public async abciInfo(): Promise { - const query: requests.AbciInfoRequest = { method: requests.Method.AbciInfo }; - return this.doCall(query, this.p.encodeAbciInfo, this.r.decodeAbciInfo); - } - - public async abciQuery(params: requests.AbciQueryParams): Promise { - const query: requests.AbciQueryRequest = { params: params, method: requests.Method.AbciQuery }; - return this.doCall(query, this.p.encodeAbciQuery, this.r.decodeAbciQuery); - } - - public async block(height?: number): Promise { - const query: requests.BlockRequest = { method: requests.Method.Block, params: { height: height } }; - return this.doCall(query, this.p.encodeBlock, this.r.decodeBlock); - } - - public async blockResults(height?: number): Promise { - const query: requests.BlockResultsRequest = { - method: requests.Method.BlockResults, - params: { height: height }, - }; - return this.doCall(query, this.p.encodeBlockResults, this.r.decodeBlockResults); - } - - /** - * Queries block headers filtered by minHeight <= height <= maxHeight. - * - * @param minHeight The minimum height to be included in the result. Defaults to 0. - * @param maxHeight The maximum height to be included in the result. Defaults to infinity. - */ - public async blockchain(minHeight?: number, maxHeight?: number): Promise { - const query: requests.BlockchainRequest = { - method: requests.Method.Blockchain, - params: { - minHeight: minHeight, - maxHeight: maxHeight, - }, - }; - return this.doCall(query, this.p.encodeBlockchain, this.r.decodeBlockchain); - } - - /** - * Broadcast transaction to mempool and wait for response - * - * @see https://docs.tendermint.com/master/rpc/#/Tx/broadcast_tx_sync - */ - public async broadcastTxSync( - params: requests.BroadcastTxParams, - ): Promise { - const query: requests.BroadcastTxRequest = { params: params, method: requests.Method.BroadcastTxSync }; - return this.doCall(query, this.p.encodeBroadcastTx, this.r.decodeBroadcastTxSync); - } - - /** - * Broadcast transaction to mempool and do not wait for result - * - * @see https://docs.tendermint.com/master/rpc/#/Tx/broadcast_tx_async - */ - public async broadcastTxAsync( - params: requests.BroadcastTxParams, - ): Promise { - const query: requests.BroadcastTxRequest = { params: params, method: requests.Method.BroadcastTxAsync }; - return this.doCall(query, this.p.encodeBroadcastTx, this.r.decodeBroadcastTxAsync); - } - - /** - * Broadcast transaction to mempool and wait for block - * - * @see https://docs.tendermint.com/master/rpc/#/Tx/broadcast_tx_commit - */ - public async broadcastTxCommit( - params: requests.BroadcastTxParams, - ): Promise { - const query: requests.BroadcastTxRequest = { params: params, method: requests.Method.BroadcastTxCommit }; - return this.doCall(query, this.p.encodeBroadcastTx, this.r.decodeBroadcastTxCommit); - } - - public async commit(height?: number): Promise { - const query: requests.CommitRequest = { method: requests.Method.Commit, params: { height: height } }; - return this.doCall(query, this.p.encodeCommit, this.r.decodeCommit); - } - - public async genesis(): Promise { - const query: requests.GenesisRequest = { method: requests.Method.Genesis }; - return this.doCall(query, this.p.encodeGenesis, this.r.decodeGenesis); - } - - public async health(): Promise { - const query: requests.HealthRequest = { method: requests.Method.Health }; - return this.doCall(query, this.p.encodeHealth, this.r.decodeHealth); - } - - public async status(): Promise { - const query: requests.StatusRequest = { method: requests.Method.Status }; - return this.doCall(query, this.p.encodeStatus, this.r.decodeStatus); - } - - public subscribeNewBlock(): Stream { - const request: requests.SubscribeRequest = { - method: requests.Method.Subscribe, - query: { type: requests.SubscriptionEventType.NewBlock }, - }; - return this.subscribe(request, this.r.decodeNewBlockEvent); - } - - public subscribeNewBlockHeader(): Stream { - const request: requests.SubscribeRequest = { - method: requests.Method.Subscribe, - query: { type: requests.SubscriptionEventType.NewBlockHeader }, - }; - return this.subscribe(request, this.r.decodeNewBlockHeaderEvent); - } - - public subscribeTx(query?: string): Stream { - const request: requests.SubscribeRequest = { - method: requests.Method.Subscribe, - query: { - type: requests.SubscriptionEventType.Tx, - raw: query, - }, - }; - return this.subscribe(request, this.r.decodeTxEvent); - } - - /** - * Get a single transaction by hash - * - * @see https://docs.tendermint.com/master/rpc/#/Info/tx - */ - public async tx(params: requests.TxParams): Promise { - const query: requests.TxRequest = { params: params, method: requests.Method.Tx }; - return this.doCall(query, this.p.encodeTx, this.r.decodeTx); - } - - /** - * Search for transactions that are in a block - * - * @see https://docs.tendermint.com/master/rpc/#/Info/tx_search - */ - public async txSearch(params: requests.TxSearchParams): Promise { - const query: requests.TxSearchRequest = { params: params, method: requests.Method.TxSearch }; - return this.doCall(query, this.p.encodeTxSearch, this.r.decodeTxSearch); - } - - // this should paginate through all txSearch options to ensure it returns all results. - // starts with page 1 or whatever was provided (eg. to start on page 7) - public async txSearchAll(params: requests.TxSearchParams): Promise { - let page = params.page || 1; - const txs: responses.TxResponse[] = []; - let done = false; - - while (!done) { - const resp = await this.txSearch({ ...params, page: page }); - txs.push(...resp.txs); - if (txs.length < resp.totalCount) { - page++; - } else { - done = true; - } - } - - return { - totalCount: txs.length, - txs: txs, - }; - } - - public async validators(params: requests.ValidatorsParams): Promise { - const query: requests.ValidatorsRequest = { - method: requests.Method.Validators, - params: params, - }; - return this.doCall(query, this.p.encodeValidators, this.r.decodeValidators); - } - - public async validatorsAll(height?: number): Promise { - const validators: responses.Validator[] = []; - let page = 1; - let done = false; - let blockHeight = height; - - while (!done) { - const response = await this.validators({ - per_page: 50, - height: blockHeight, - page: page, - }); - validators.push(...response.validators); - blockHeight = blockHeight || response.blockHeight; - if (validators.length < response.total) { - page++; - } else { - done = true; - } - } - - return { - // NOTE: Default value is for type safety but this should always be set - blockHeight: blockHeight ?? 0, - count: validators.length, - total: validators.length, - validators: validators, - }; - } - - // doCall is a helper to handle the encode/call/decode logic - private async doCall( - request: T, - encode: Encoder, - decode: Decoder, - ): Promise { - const req = encode(request); - const result = await this.client.execute(req); - return decode(result); - } - - private subscribe(request: requests.SubscribeRequest, decode: (e: SubscriptionEvent) => T): Stream { - if (!instanceOfRpcStreamingClient(this.client)) { - throw new Error("This RPC client type cannot subscribe to events"); - } - - const req = this.p.encodeSubscribe(request); - const eventStream = this.client.listen(req); - return eventStream.map((event) => { - return decode(event); - }); - } -} diff --git a/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts b/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts index 58a099ac..584f7c00 100644 --- a/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts +++ b/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts @@ -794,7 +794,7 @@ function websocketTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValue } describe("Tendermint34Client", () => { - const { url, expected } = tendermintInstances[1]; + const { url, expected } = tendermintInstances[0]; it("can connect to a given url", async () => { pendingWithoutTendermint(); diff --git a/packages/tendermint-rpc/src/testutil.spec.ts b/packages/tendermint-rpc/src/testutil.spec.ts index 353dd26f..2b45d549 100644 --- a/packages/tendermint-rpc/src/testutil.spec.ts +++ b/packages/tendermint-rpc/src/testutil.spec.ts @@ -35,18 +35,6 @@ export interface TendermintInstance { * docker container kill */ export const tendermintInstances: readonly TendermintInstance[] = [ - { - url: "localhost:11133", - version: "0.33.x", - blockTime: 1000, - expected: { - version: "0.33.8", - appCreator: "Cosmoshi Netowoko", - p2pVersion: 7, - blockVersion: 10, - appVersion: 1, - }, - }, { url: "localhost:11134", version: "0.34.x", diff --git a/scripts/tendermint/all_start.sh b/scripts/tendermint/all_start.sh index 37a87fa8..00dff73d 100755 --- a/scripts/tendermint/all_start.sh +++ b/scripts/tendermint/all_start.sh @@ -4,7 +4,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[33]=v0.33.8 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 e77084df..e65e4416 100755 --- a/scripts/tendermint/all_stop.sh +++ b/scripts/tendermint/all_stop.sh @@ -3,7 +3,7 @@ set -o errexit -o nounset -o pipefail command -v shellcheck >/dev/null && shellcheck "$0" declare -a TM_VERSIONS -TM_VERSIONS[33]=v0.33.8 +# TM_VERSIONS[33]=v0.33.8 TM_VERSIONS[34]=v0.34.10 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"