Remove Tendermint33Client and related symbols
This commit is contained in:
parent
260a1fa09f
commit
fe07180431
@ -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
|
||||
|
||||
|
||||
@ -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 |
|
||||
|
||||
@ -20,9 +20,7 @@ export {
|
||||
ValidatorSecp256k1Pubkey,
|
||||
ValidatorPubkey,
|
||||
} from "./types";
|
||||
export * as tendermint33 from "./tendermint33";
|
||||
export {
|
||||
Tendermint33Client,
|
||||
AbciInfoResponse,
|
||||
AbciQueryResponse,
|
||||
Attribute,
|
||||
|
||||
@ -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,
|
||||
};
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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<number>(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<string, unknown> | 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<string, unknown>;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -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<T extends requests.Request> = (req: T) => JsonRpcRequest;
|
||||
|
||||
// Decoder is a generic that matches all methods of Responses
|
||||
export type Decoder<T extends responses.Response> = (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;
|
||||
}
|
||||
@ -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<T>(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<T>(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<T>(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<T>(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<T>(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<T, U>(transform: (val: T) => U, value: T | null | undefined): U | undefined {
|
||||
return value === undefined || value === null ? undefined : transform(value);
|
||||
}
|
||||
|
||||
export function dictionaryToStringMap(obj: Record<string, unknown>): Map<string, string> {
|
||||
const out = new Map<string, string>();
|
||||
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/
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<void> {
|
||||
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);
|
||||
});
|
||||
});
|
||||
@ -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<Tendermint33Client> {
|
||||
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<Tendermint33Client> {
|
||||
// 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<string> {
|
||||
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<responses.AbciInfoResponse> {
|
||||
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<responses.AbciQueryResponse> {
|
||||
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<responses.BlockResponse> {
|
||||
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<responses.BlockResultsResponse> {
|
||||
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<responses.BlockchainResponse> {
|
||||
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<responses.BroadcastTxSyncResponse> {
|
||||
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<responses.BroadcastTxAsyncResponse> {
|
||||
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<responses.BroadcastTxCommitResponse> {
|
||||
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<responses.CommitResponse> {
|
||||
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<responses.GenesisResponse> {
|
||||
const query: requests.GenesisRequest = { method: requests.Method.Genesis };
|
||||
return this.doCall(query, this.p.encodeGenesis, this.r.decodeGenesis);
|
||||
}
|
||||
|
||||
public async health(): Promise<responses.HealthResponse> {
|
||||
const query: requests.HealthRequest = { method: requests.Method.Health };
|
||||
return this.doCall(query, this.p.encodeHealth, this.r.decodeHealth);
|
||||
}
|
||||
|
||||
public async status(): Promise<responses.StatusResponse> {
|
||||
const query: requests.StatusRequest = { method: requests.Method.Status };
|
||||
return this.doCall(query, this.p.encodeStatus, this.r.decodeStatus);
|
||||
}
|
||||
|
||||
public subscribeNewBlock(): Stream<responses.NewBlockEvent> {
|
||||
const request: requests.SubscribeRequest = {
|
||||
method: requests.Method.Subscribe,
|
||||
query: { type: requests.SubscriptionEventType.NewBlock },
|
||||
};
|
||||
return this.subscribe(request, this.r.decodeNewBlockEvent);
|
||||
}
|
||||
|
||||
public subscribeNewBlockHeader(): Stream<responses.NewBlockHeaderEvent> {
|
||||
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<responses.TxEvent> {
|
||||
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<responses.TxResponse> {
|
||||
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<responses.TxSearchResponse> {
|
||||
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<responses.TxSearchResponse> {
|
||||
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<responses.ValidatorsResponse> {
|
||||
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<responses.ValidatorsResponse> {
|
||||
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<T extends requests.Request, U extends responses.Response>(
|
||||
request: T,
|
||||
encode: Encoder<T>,
|
||||
decode: Decoder<U>,
|
||||
): Promise<U> {
|
||||
const req = encode(request);
|
||||
const result = await this.client.execute(req);
|
||||
return decode(result);
|
||||
}
|
||||
|
||||
private subscribe<T>(request: requests.SubscribeRequest, decode: (e: SubscriptionEvent) => T): Stream<T> {
|
||||
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<T>((event) => {
|
||||
return decode(event);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
|
||||
@ -35,18 +35,6 @@ export interface TendermintInstance {
|
||||
* docker container kill <container id from 1st column>
|
||||
*/
|
||||
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",
|
||||
|
||||
@ -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)"
|
||||
|
||||
@ -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)"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user