diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bdb018257..752ffcfc76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ and this project adheres to - @cosmjs/proto-signing: Remove `fromJSON`/`toJSON` from `TsProtoGeneratedType` such that generated types are not required to generate those anymore. The methods were provided by ts-proto but we never needed them. ([#1329]) +- @cosmjs/stargate: Rename `fromTendermint34Event` to `fromTendermintEvent` and + let it support both Tendermint 0.34 and 0.37 events as input. [#1002]: https://github.com/cosmos/cosmjs/issues/1002 [#1240]: https://github.com/cosmos/cosmjs/pull/1240 @@ -29,10 +31,23 @@ and this project adheres to [#1329]: https://github.com/cosmos/cosmjs/pull/1329 ### Added + - @cosmjs/stargate: Add `granteeGrants` and `granterGrants` queries to `AuthzExtension` ([#1308]). +- @cosmjs/tendermint-rpc: Add new `Tendermint37Client` and remove unused + `Tendermint35Client`; Add `TendermintClient` as a union type for + `Tendermint34Client` or `Tendermint37Client` and + `isTendermint34Client`/`isTendermint37Client` to get the specific type + ([#1376]). +- @cosmjs/stargate: Add constructors `StargateClient.create` and + `SigningStargateClient.createWithSigner` to construct with a given Tendermint + client ([#1376]). +- @cosmjs/cosmwasm-stargate: Add constructors `CosmWasmClient.create` and + `SigningCosmWasmClient.createWithSigner` to construct with a given Tendermint + client ([#1376]). [#1308]: https://github.com/cosmos/cosmjs/pull/1308 +[#1376]: https://github.com/cosmos/cosmjs/pull/1376 ## [0.29.5] - 2022-12-07 diff --git a/packages/cli/examples/tendermint0.37.ts b/packages/cli/examples/tendermint0.37.ts new file mode 100644 index 0000000000..65200a2598 --- /dev/null +++ b/packages/cli/examples/tendermint0.37.ts @@ -0,0 +1,40 @@ +import { coins, makeCosmoshubPath } from "@cosmjs/amino"; +import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; +import { assertIsDeliverTxSuccess, calculateFee, GasPrice, SigningStargateClient } from "@cosmjs/stargate"; +import { Tendermint37Client } from "@cosmjs/tendermint-rpc"; + +// Network config +const prefix = "wasm"; +const rpcEndpoint = "http://146.190.50.102:26657"; // or 137.184.83.82:26657 +const gasPrice = GasPrice.fromString("0.001stake"); + +// Wallet wasm16jd84xm6yerfaafvtp7s6tpetdqkpu6wxumszp +const mnemonic = "royal next favorite duck plastic august rent knee strong weather father opinion"; +const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { prefix: prefix }); +const [account] = await wallet.getAccounts(); +console.log("Signer address:", account.address); + +// Setup client. In contrast to most other examples out there, we create the Tendermint client +// explicitly. Otherwise the 0.34 client will be used. +const tmClient = await Tendermint37Client.connect(rpcEndpoint); +const version = (await tmClient.status()).nodeInfo.version; +console.log("Tendermint version:", version); +const client = await SigningStargateClient.createWithSigner(tmClient, wallet, { gasPrice: gasPrice }); + +// Get my balance +const balance = await client.getAllBalances(account.address); +console.log("Balance:", balance); + +// Send a transaction +const recipient = "wasm142u9fgcjdlycfcez3lw8x6x5h7rfjlnfaallkd"; +const result = await client.sendTokens( + account.address, + recipient, + coins(1, "stake"), + 1.5, // In the current testnet the default multiplier of 1.3 is not sufficient 🤷‍♂️ + "Have fun with this gift", +); +assertIsDeliverTxSuccess(result); +console.log("Successfully broadcasted:", result); + +client.disconnect(); diff --git a/packages/cosmwasm-stargate/src/cosmwasmclient.ts b/packages/cosmwasm-stargate/src/cosmwasmclient.ts index ab357d9aa4..97c362008b 100644 --- a/packages/cosmwasm-stargate/src/cosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/cosmwasmclient.ts @@ -10,7 +10,7 @@ import { BroadcastTxError, Coin, DeliverTxResponse, - fromTendermint34Event, + fromTendermintEvent, IndexedTx, isSearchByHeightQuery, isSearchBySentFromOrToQuery, @@ -25,7 +25,12 @@ import { TimeoutError, TxExtension, } from "@cosmjs/stargate"; -import { HttpEndpoint, Tendermint34Client, toRfc3339WithNanoseconds } from "@cosmjs/tendermint-rpc"; +import { + HttpEndpoint, + Tendermint34Client, + TendermintClient, + toRfc3339WithNanoseconds, +} from "@cosmjs/tendermint-rpc"; import { assert, sleep } from "@cosmjs/utils"; import { CodeInfoResponse, @@ -77,26 +82,40 @@ export interface ContractCodeHistoryEntry { /** Use for testing only */ export interface PrivateCosmWasmClient { - readonly tmClient: Tendermint34Client | undefined; + readonly tmClient: TendermintClient | undefined; readonly queryClient: | (QueryClient & AuthExtension & BankExtension & TxExtension & WasmExtension) | undefined; } export class CosmWasmClient { - private readonly tmClient: Tendermint34Client | undefined; + private readonly tmClient: TendermintClient | undefined; private readonly queryClient: | (QueryClient & AuthExtension & BankExtension & TxExtension & WasmExtension) | undefined; private readonly codesCache = new Map(); private chainId: string | undefined; + /** + * Creates an instance by connecting to the given Tendermint RPC endpoint. + * + * For now this uses the Tendermint 0.34 client. If you need Tendermint 0.37 + * support, see `create`. + */ public static async connect(endpoint: string | HttpEndpoint): Promise { const tmClient = await Tendermint34Client.connect(endpoint); + return CosmWasmClient.create(tmClient); + } + + /** + * Creates an instance from a manually created Tendermint client. + * Use this to use `Tendermint37Client` instead of `Tendermint34Client`. + */ + public static async create(tmClient: TendermintClient): Promise { return new CosmWasmClient(tmClient); } - protected constructor(tmClient: Tendermint34Client | undefined) { + protected constructor(tmClient: TendermintClient | undefined) { if (tmClient) { this.tmClient = tmClient; this.queryClient = QueryClient.withExtensions( @@ -109,11 +128,11 @@ export class CosmWasmClient { } } - protected getTmClient(): Tendermint34Client | undefined { + protected getTmClient(): TendermintClient | undefined { return this.tmClient; } - protected forceGetTmClient(): Tendermint34Client { + protected forceGetTmClient(): TendermintClient { if (!this.tmClient) { throw new Error( "Tendermint client not available. You cannot use online functionality in offline mode.", @@ -464,7 +483,7 @@ export class CosmWasmClient { height: tx.height, hash: toHex(tx.hash).toUpperCase(), code: tx.result.code, - events: tx.result.events.map(fromTendermint34Event), + events: tx.result.events.map(fromTendermintEvent), rawLog: tx.result.log || "", tx: tx.tx, gasUsed: tx.result.gasUsed, diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts index 5d03812a71..6cdad85257 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts @@ -31,7 +31,7 @@ import { SignerData, StdFee, } from "@cosmjs/stargate"; -import { HttpEndpoint, Tendermint34Client } from "@cosmjs/tendermint-rpc"; +import { HttpEndpoint, Tendermint34Client, TendermintClient } from "@cosmjs/tendermint-rpc"; import { assert, assertDefined } from "@cosmjs/utils"; import { MsgWithdrawDelegatorReward } from "cosmjs-types/cosmos/distribution/v1beta1/tx"; import { MsgDelegate, MsgUndelegate } from "cosmjs-types/cosmos/staking/v1beta1/tx"; @@ -183,12 +183,30 @@ export class SigningCosmWasmClient extends CosmWasmClient { private readonly aminoTypes: AminoTypes; private readonly gasPrice: GasPrice | undefined; + /** + * Creates an instance by connecting to the given Tendermint RPC endpoint. + * + * For now this uses the Tendermint 0.34 client. If you need Tendermint 0.37 + * support, see `createWithSigner`. + */ public static async connectWithSigner( endpoint: string | HttpEndpoint, signer: OfflineSigner, options: SigningCosmWasmClientOptions = {}, ): Promise { const tmClient = await Tendermint34Client.connect(endpoint); + return SigningCosmWasmClient.createWithSigner(tmClient, signer, options); + } + + /** + * Creates an instance from a manually created Tendermint client. + * Use this to use `Tendermint37Client` instead of `Tendermint34Client`. + */ + public static async createWithSigner( + tmClient: TendermintClient, + signer: OfflineSigner, + options: SigningCosmWasmClientOptions = {}, + ): Promise { return new SigningCosmWasmClient(tmClient, signer, options); } @@ -209,7 +227,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { } protected constructor( - tmClient: Tendermint34Client | undefined, + tmClient: TendermintClient | undefined, signer: OfflineSigner, options: SigningCosmWasmClientOptions, ) { diff --git a/packages/stargate/src/events.ts b/packages/stargate/src/events.ts index b4b70cce29..57f12aaa47 100644 --- a/packages/stargate/src/events.ts +++ b/packages/stargate/src/events.ts @@ -1,5 +1,5 @@ import { fromUtf8 } from "@cosmjs/encoding"; -import { tendermint34 } from "@cosmjs/tendermint-rpc"; +import { tendermint34, tendermint37 } from "@cosmjs/tendermint-rpc"; /** * An event attribute. @@ -30,16 +30,16 @@ export interface Event { } /** - * Takes a Tendemrint 0.34 event with binary encoded key and value + * Takes a Tendermint 0.34 or 0.37 event with binary encoded key and value * and converts it into an `Event` with string attributes. */ -export function fromTendermint34Event(event: tendermint34.Event): Event { +export function fromTendermintEvent(event: tendermint34.Event | tendermint37.Event): Event { return { type: event.type, attributes: event.attributes.map( (attr): Attribute => ({ - key: fromUtf8(attr.key, true), - value: fromUtf8(attr.value, true), + key: typeof attr.key == "string" ? attr.key : fromUtf8(attr.key, true), + value: typeof attr.value == "string" ? attr.value : fromUtf8(attr.value, true), }), ), }; diff --git a/packages/stargate/src/index.ts b/packages/stargate/src/index.ts index fab188f774..62560972de 100644 --- a/packages/stargate/src/index.ts +++ b/packages/stargate/src/index.ts @@ -1,6 +1,6 @@ export { Account, accountFromAny, AccountParser } from "./accounts"; export { AminoConverter, AminoConverters, AminoTypes } from "./aminotypes"; -export { Attribute, Event, fromTendermint34Event } from "./events"; +export { Attribute, Event, fromTendermintEvent } from "./events"; export { calculateFee, GasPrice } from "./fee"; export * as logs from "./logs"; export { diff --git a/packages/stargate/src/queryclient/queryclient.ts b/packages/stargate/src/queryclient/queryclient.ts index 6664443316..aaa000a904 100644 --- a/packages/stargate/src/queryclient/queryclient.ts +++ b/packages/stargate/src/queryclient/queryclient.ts @@ -2,7 +2,7 @@ import { iavlSpec, ics23, tendermintSpec, verifyExistence, verifyNonExistence } from "@confio/ics23"; import { toAscii, toHex } from "@cosmjs/encoding"; import { firstEvent } from "@cosmjs/stream"; -import { tendermint34, Tendermint34Client } from "@cosmjs/tendermint-rpc"; +import { tendermint34, TendermintClient } from "@cosmjs/tendermint-rpc"; import { arrayContentEquals, assert, assertDefined, isNonNullObject, sleep } from "@cosmjs/utils"; import { ProofOps } from "cosmjs-types/tendermint/crypto/proof"; import { Stream } from "xstream"; @@ -45,24 +45,24 @@ export interface QueryAbciResponse { export class QueryClient { /** Constructs a QueryClient with 0 extensions */ - public static withExtensions(tmClient: Tendermint34Client): QueryClient; + public static withExtensions(tmClient: TendermintClient): QueryClient; /** Constructs a QueryClient with 1 extension */ public static withExtensions( - tmClient: Tendermint34Client, + tmClient: TendermintClient, setupExtensionA: QueryExtensionSetup, ): QueryClient & A; /** Constructs a QueryClient with 2 extensions */ public static withExtensions( - tmClient: Tendermint34Client, + tmClient: TendermintClient, setupExtensionA: QueryExtensionSetup, setupExtensionB: QueryExtensionSetup, ): QueryClient & A & B; /** Constructs a QueryClient with 3 extensions */ public static withExtensions( - tmClient: Tendermint34Client, + tmClient: TendermintClient, setupExtensionA: QueryExtensionSetup, setupExtensionB: QueryExtensionSetup, setupExtensionC: QueryExtensionSetup, @@ -70,7 +70,7 @@ export class QueryClient { /** Constructs a QueryClient with 4 extensions */ public static withExtensions( - tmClient: Tendermint34Client, + tmClient: TendermintClient, setupExtensionA: QueryExtensionSetup, setupExtensionB: QueryExtensionSetup, setupExtensionC: QueryExtensionSetup, @@ -85,7 +85,7 @@ export class QueryClient { D extends object, E extends object, >( - tmClient: Tendermint34Client, + tmClient: TendermintClient, setupExtensionA: QueryExtensionSetup, setupExtensionB: QueryExtensionSetup, setupExtensionC: QueryExtensionSetup, @@ -102,7 +102,7 @@ export class QueryClient { E extends object, F extends object, >( - tmClient: Tendermint34Client, + tmClient: TendermintClient, setupExtensionA: QueryExtensionSetup, setupExtensionB: QueryExtensionSetup, setupExtensionC: QueryExtensionSetup, @@ -121,7 +121,7 @@ export class QueryClient { F extends object, G extends object, >( - tmClient: Tendermint34Client, + tmClient: TendermintClient, setupExtensionA: QueryExtensionSetup, setupExtensionB: QueryExtensionSetup, setupExtensionC: QueryExtensionSetup, @@ -142,7 +142,7 @@ export class QueryClient { G extends object, H extends object, >( - tmClient: Tendermint34Client, + tmClient: TendermintClient, setupExtensionA: QueryExtensionSetup, setupExtensionB: QueryExtensionSetup, setupExtensionC: QueryExtensionSetup, @@ -165,7 +165,7 @@ export class QueryClient { H extends object, I extends object, >( - tmClient: Tendermint34Client, + tmClient: TendermintClient, setupExtensionA: QueryExtensionSetup, setupExtensionB: QueryExtensionSetup, setupExtensionC: QueryExtensionSetup, @@ -190,7 +190,7 @@ export class QueryClient { I extends object, J extends object, >( - tmClient: Tendermint34Client, + tmClient: TendermintClient, setupExtensionA: QueryExtensionSetup, setupExtensionB: QueryExtensionSetup, setupExtensionC: QueryExtensionSetup, @@ -217,7 +217,7 @@ export class QueryClient { J extends object, K extends object, >( - tmClient: Tendermint34Client, + tmClient: TendermintClient, setupExtensionA: QueryExtensionSetup, setupExtensionB: QueryExtensionSetup, setupExtensionC: QueryExtensionSetup, @@ -246,7 +246,7 @@ export class QueryClient { K extends object, L extends object, >( - tmClient: Tendermint34Client, + tmClient: TendermintClient, setupExtensionA: QueryExtensionSetup, setupExtensionB: QueryExtensionSetup, setupExtensionC: QueryExtensionSetup, @@ -277,7 +277,7 @@ export class QueryClient { L extends object, M extends object, >( - tmClient: Tendermint34Client, + tmClient: TendermintClient, setupExtensionA: QueryExtensionSetup, setupExtensionB: QueryExtensionSetup, setupExtensionC: QueryExtensionSetup, @@ -310,7 +310,7 @@ export class QueryClient { M extends object, N extends object, >( - tmClient: Tendermint34Client, + tmClient: TendermintClient, setupExtensionA: QueryExtensionSetup, setupExtensionB: QueryExtensionSetup, setupExtensionC: QueryExtensionSetup, @@ -345,7 +345,7 @@ export class QueryClient { N extends object, O extends object, >( - tmClient: Tendermint34Client, + tmClient: TendermintClient, setupExtensionA: QueryExtensionSetup, setupExtensionB: QueryExtensionSetup, setupExtensionC: QueryExtensionSetup, @@ -382,7 +382,7 @@ export class QueryClient { O extends object, P extends object, >( - tmClient: Tendermint34Client, + tmClient: TendermintClient, setupExtensionA: QueryExtensionSetup, setupExtensionB: QueryExtensionSetup, setupExtensionC: QueryExtensionSetup, @@ -421,7 +421,7 @@ export class QueryClient { P extends object, Q extends object, >( - tmClient: Tendermint34Client, + tmClient: TendermintClient, setupExtensionA: QueryExtensionSetup, setupExtensionB: QueryExtensionSetup, setupExtensionC: QueryExtensionSetup, @@ -462,7 +462,7 @@ export class QueryClient { Q extends object, R extends object, >( - tmClient: Tendermint34Client, + tmClient: TendermintClient, setupExtensionA: QueryExtensionSetup, setupExtensionB: QueryExtensionSetup, setupExtensionC: QueryExtensionSetup, @@ -484,7 +484,7 @@ export class QueryClient { ): QueryClient & A & B & C & D & E & F & G & H & I & J & K & L & M & N & O & P & Q & R; public static withExtensions( - tmClient: Tendermint34Client, + tmClient: TendermintClient, ...extensionSetups: Array> ): any { const client = new QueryClient(tmClient); @@ -506,9 +506,9 @@ export class QueryClient { return client; } - private readonly tmClient: Tendermint34Client; + private readonly tmClient: TendermintClient; - public constructor(tmClient: Tendermint34Client) { + public constructor(tmClient: TendermintClient) { this.tmClient = tmClient; } diff --git a/packages/stargate/src/signingstargateclient.ts b/packages/stargate/src/signingstargateclient.ts index ed89c7e93c..a1efb39bef 100644 --- a/packages/stargate/src/signingstargateclient.ts +++ b/packages/stargate/src/signingstargateclient.ts @@ -12,7 +12,7 @@ import { Registry, TxBodyEncodeObject, } from "@cosmjs/proto-signing"; -import { HttpEndpoint, Tendermint34Client } from "@cosmjs/tendermint-rpc"; +import { HttpEndpoint, Tendermint34Client, TendermintClient } from "@cosmjs/tendermint-rpc"; import { assert, assertDefined } from "@cosmjs/utils"; import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin"; import { MsgWithdrawDelegatorReward } from "cosmjs-types/cosmos/distribution/v1beta1/tx"; @@ -114,12 +114,30 @@ export class SigningStargateClient extends StargateClient { private readonly aminoTypes: AminoTypes; private readonly gasPrice: GasPrice | undefined; + /** + * Creates an instance by connecting to the given Tendermint RPC endpoint. + * + * For now this uses the Tendermint 0.34 client. If you need Tendermint 0.37 + * support, see `createWithSigner`. + */ public static async connectWithSigner( endpoint: string | HttpEndpoint, signer: OfflineSigner, options: SigningStargateClientOptions = {}, ): Promise { const tmClient = await Tendermint34Client.connect(endpoint); + return SigningStargateClient.createWithSigner(tmClient, signer, options); + } + + /** + * Creates an instance from a manually created Tendermint client. + * Use this to use `Tendermint37Client` instead of `Tendermint34Client`. + */ + public static async createWithSigner( + tmClient: TendermintClient, + signer: OfflineSigner, + options: SigningStargateClientOptions = {}, + ): Promise { return new SigningStargateClient(tmClient, signer, options); } @@ -140,7 +158,7 @@ export class SigningStargateClient extends StargateClient { } protected constructor( - tmClient: Tendermint34Client | undefined, + tmClient: TendermintClient | undefined, signer: OfflineSigner, options: SigningStargateClientOptions, ) { diff --git a/packages/stargate/src/stargateclient.ts b/packages/stargate/src/stargateclient.ts index 410af11198..c81ca8e256 100644 --- a/packages/stargate/src/stargateclient.ts +++ b/packages/stargate/src/stargateclient.ts @@ -2,7 +2,12 @@ import { addCoins } from "@cosmjs/amino"; import { toHex } from "@cosmjs/encoding"; import { Uint53 } from "@cosmjs/math"; -import { HttpEndpoint, Tendermint34Client, toRfc3339WithNanoseconds } from "@cosmjs/tendermint-rpc"; +import { + HttpEndpoint, + Tendermint34Client, + TendermintClient, + toRfc3339WithNanoseconds, +} from "@cosmjs/tendermint-rpc"; import { assert, sleep } from "@cosmjs/utils"; import { MsgData } from "cosmjs-types/cosmos/base/abci/v1beta1/abci"; import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin"; @@ -10,7 +15,7 @@ import { QueryDelegatorDelegationsResponse } from "cosmjs-types/cosmos/staking/v import { DelegationResponse } from "cosmjs-types/cosmos/staking/v1beta1/staking"; import { Account, accountFromAny, AccountParser } from "./accounts"; -import { Event, fromTendermint34Event } from "./events"; +import { Event, fromTendermintEvent } from "./events"; import { AuthExtension, BankExtension, @@ -171,7 +176,7 @@ export class BroadcastTxError extends Error { /** Use for testing only */ export interface PrivateStargateClient { - readonly tmClient: Tendermint34Client | undefined; + readonly tmClient: TendermintClient | undefined; } export interface StargateClientOptions { @@ -179,22 +184,39 @@ export interface StargateClientOptions { } export class StargateClient { - private readonly tmClient: Tendermint34Client | undefined; + private readonly tmClient: TendermintClient | undefined; private readonly queryClient: | (QueryClient & AuthExtension & BankExtension & StakingExtension & TxExtension) | undefined; private chainId: string | undefined; private readonly accountParser: AccountParser; + /** + * Creates an instance by connecting to the given Tendermint RPC endpoint. + * + * For now this uses the Tendermint 0.34 client. If you need Tendermint 0.37 + * support, see `create`. + */ public static async connect( endpoint: string | HttpEndpoint, options: StargateClientOptions = {}, ): Promise { const tmClient = await Tendermint34Client.connect(endpoint); + return StargateClient.create(tmClient, options); + } + + /** + * Creates an instance from a manually created Tendermint client. + * Use this to use `Tendermint37Client` instead of `Tendermint34Client`. + */ + public static async create( + tmClient: TendermintClient, + options: StargateClientOptions = {}, + ): Promise { return new StargateClient(tmClient, options); } - protected constructor(tmClient: Tendermint34Client | undefined, options: StargateClientOptions) { + protected constructor(tmClient: TendermintClient | undefined, options: StargateClientOptions) { if (tmClient) { this.tmClient = tmClient; this.queryClient = QueryClient.withExtensions( @@ -209,11 +231,11 @@ export class StargateClient { this.accountParser = accountParser; } - protected getTmClient(): Tendermint34Client | undefined { + protected getTmClient(): TendermintClient | undefined { return this.tmClient; } - protected forceGetTmClient(): Tendermint34Client { + protected forceGetTmClient(): TendermintClient { if (!this.tmClient) { throw new Error( "Tendermint client not available. You cannot use online functionality in offline mode.", @@ -471,7 +493,7 @@ export class StargateClient { height: tx.height, hash: toHex(tx.hash).toUpperCase(), code: tx.result.code, - events: tx.result.events.map(fromTendermint34Event), + events: tx.result.events.map(fromTendermintEvent), rawLog: tx.result.log || "", tx: tx.tx, gasUsed: tx.result.gasUsed, diff --git a/packages/tendermint-rpc/src/index.ts b/packages/tendermint-rpc/src/index.ts index a1556d15df..5b645eff44 100644 --- a/packages/tendermint-rpc/src/index.ts +++ b/packages/tendermint-rpc/src/index.ts @@ -95,10 +95,9 @@ export { } from "./tendermint34"; export * as tendermint34 from "./tendermint34"; export { Tendermint34Client } from "./tendermint34"; -// Tendermint 0.35 support is not public. The implementation may break or be removed at any point in time. -// See https://github.com/cosmos/cosmjs/issues/1225 for more context. -// export * as tendermint35 from "./tendermint35"; -// export { Tendermint35Client } from "./tendermint35"; +export * as tendermint37 from "./tendermint37"; +export { Tendermint37Client } from "./tendermint37"; +export { isTendermint34Client, isTendermint37Client, TendermintClient } from "./tendermintclient"; export { BlockIdFlag, CommitSignature, diff --git a/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts b/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts index 1daa834f8d..11c5d1428d 100644 --- a/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts +++ b/packages/tendermint-rpc/src/tendermint34/tendermint34client.spec.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import { toAscii } from "@cosmjs/encoding"; +import { toAscii, toHex } from "@cosmjs/encoding"; import { firstEvent, toListPromise } from "@cosmjs/stream"; -import { sleep } from "@cosmjs/utils"; +import { assert, sleep } from "@cosmjs/utils"; import { ReadonlyDate } from "readonly-date"; import { Stream } from "xstream"; @@ -344,7 +344,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) const height = (await client.status()).syncInfo.latestBlockHeight; const blockchain = await client.blockchain(undefined, height - 1); - expect(blockchain.lastHeight).toEqual(height); + expect(blockchain.lastHeight).toBeGreaterThanOrEqual(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); @@ -435,25 +435,6 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) 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); @@ -464,25 +445,28 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) }); describe("txSearch", () => { - const key = randomString(); + const txKey = randomString(); // a key used for multiple transactions + let tx1: Uint8Array | undefined; + let broadcast1: responses.BroadcastTxCommitResponse | undefined; beforeAll(async () => { if (tendermintEnabled()) { const client = await Tendermint34Client.create(rpcFactory()); // eslint-disable-next-line no-inner-declarations - async function sendTx(): Promise { + async function sendTx(): Promise<[Uint8Array, responses.BroadcastTxCommitResponse]> { const me = randomString(); - const tx = buildKvTx(key, me); + const tx = buildKvTx(txKey, 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); + expect(txRes.hash.length).toEqual(32); + return [tx, txRes]; } // send 3 txs - await sendTx(); + [tx1, broadcast1] = await sendTx(); await sendTx(); await sendTx(); @@ -492,6 +476,68 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) } }); + it("finds a single tx by hash", async () => { + pendingWithoutTendermint(); + assert(tx1 && broadcast1); + const client = await Tendermint34Client.create(rpcFactory()); + + const result = await client.txSearch({ query: `tx.hash='${toHex(broadcast1.hash)}'` }); + expect(result.totalCount).toEqual(1); + expect(result.txs[0]).toEqual({ + hash: broadcast1.hash, + height: broadcast1.height, + index: 0, + tx: tx1, + result: broadcast1.deliverTx!, + proof: undefined, + }); + + client.disconnect(); + }); + + it("finds a single tx by tags", async () => { + pendingWithoutTendermint(); + const client = await Tendermint34Client.create(rpcFactory()); + + const txKey2 = randomString(); + const txValue2 = randomString(); + const tx = buildKvTx(txKey2, txValue2); + + const txRes = await client.broadcastTxCommit({ tx: tx }); + expect(responses.broadcastTxCommitSuccess(txRes)).toEqual(true); + await tendermintSearchIndexUpdated(); + + // txSearch - you must enable the indexer when running + // tendermint, else you get empty results + const query = buildQuery({ tags: [{ key: "app.key", value: txKey2 }] }); + + const search = await client.txSearch({ query: query, page: 1, per_page: 30 }); + // should find the tx + expect(search.totalCount).toEqual(1); + // should return same info as querying directly, + // except without the proof + expect(search.txs[0]).toEqual({ + hash: txRes.hash, + height: txRes.height, + index: 0, + tx: tx, + result: txRes.deliverTx!, + proof: undefined, + }); + + // Ensure txSearchAll works as well. This should be moved in a dedicated "txSearchAll" test block. + const searchAll = await client.txSearchAll({ query: query }); + expect(searchAll.totalCount).toEqual(1); + expect(searchAll.txs[0]).toEqual({ + hash: txRes.hash, + height: txRes.height, + index: 0, + tx: tx, + result: txRes.deliverTx!, + proof: undefined, + }); + }); + 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 @@ -499,15 +545,15 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) pendingWithoutTendermint(); const client = await Tendermint34Client.create(rpcFactory()); - const query = buildQuery({ tags: [{ key: "app.key", value: key }] }); + const query = buildQuery({ tags: [{ key: "app.key", value: txKey }] }); - const s = await client.txSearch({ query: query }); + const result = await client.txSearch({ query: query }); - expect(s.totalCount).toEqual(3); - s.txs.slice(1).reduce((lastHeight, { height }) => { + expect(result.totalCount).toEqual(3); + result.txs.slice(1).reduce((lastHeight, { height }) => { expect(height).toBeGreaterThanOrEqual(lastHeight); return height; - }, s.txs[0].height); + }, result.txs[0].height); client.disconnect(); }); @@ -516,7 +562,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) pendingWithoutTendermint(); const client = await Tendermint34Client.create(rpcFactory()); - const query = buildQuery({ tags: [{ key: "app.key", value: key }] }); + const query = buildQuery({ tags: [{ key: "app.key", value: txKey }] }); const s1 = await client.txSearch({ query: query, order_by: "desc" }); const s2 = await client.txSearch({ query: query, order_by: "asc" }); @@ -531,7 +577,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) pendingWithoutTendermint(); const client = await Tendermint34Client.create(rpcFactory()); - const query = buildQuery({ tags: [{ key: "app.key", value: key }] }); + const query = buildQuery({ tags: [{ key: "app.key", value: txKey }] }); // expect one page of results const s1 = await client.txSearch({ query: query, page: 1, per_page: 2 }); @@ -550,15 +596,14 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) pendingWithoutTendermint(); const client = await Tendermint34Client.create(rpcFactory()); - const query = buildQuery({ tags: [{ key: "app.key", value: key }] }); + const query = buildQuery({ tags: [{ key: "app.key", value: txKey }] }); 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); + expect(sall.txs[1].height).toEqual(sall.txs[0].height + 1); + expect(sall.txs[2].height).toEqual(sall.txs[1].height + 1); client.disconnect(); }); diff --git a/packages/tendermint-rpc/src/tendermint35/adaptor/index.ts b/packages/tendermint-rpc/src/tendermint37/adaptor/index.ts similarity index 89% rename from packages/tendermint-rpc/src/tendermint35/adaptor/index.ts rename to packages/tendermint-rpc/src/tendermint37/adaptor/index.ts index 9f5061ff84..0fa5031aaa 100644 --- a/packages/tendermint-rpc/src/tendermint35/adaptor/index.ts +++ b/packages/tendermint-rpc/src/tendermint37/adaptor/index.ts @@ -5,7 +5,7 @@ import { Adaptor } from "./types"; export { Decoder, Encoder, Params, Responses } from "./types"; -export const adaptor35: Adaptor = { +export const adaptor37: Adaptor = { params: Params, responses: Responses, hashTx: hashTx, diff --git a/packages/tendermint-rpc/src/tendermint35/adaptor/requests.ts b/packages/tendermint-rpc/src/tendermint37/adaptor/requests.ts similarity index 98% rename from packages/tendermint-rpc/src/tendermint35/adaptor/requests.ts rename to packages/tendermint-rpc/src/tendermint37/adaptor/requests.ts index 52a06a2932..ae2bd709d8 100644 --- a/packages/tendermint-rpc/src/tendermint35/adaptor/requests.ts +++ b/packages/tendermint-rpc/src/tendermint37/adaptor/requests.ts @@ -74,13 +74,13 @@ function encodeBroadcastTxParams(params: requests.BroadcastTxParams): RpcBroadca } interface RpcTxParams { - /** hex encoded */ + /** base64 encoded */ readonly hash: string; readonly prove?: boolean; } function encodeTxParams(params: requests.TxParams): RpcTxParams { return { - hash: toHex(assertNotEmpty(params.hash)), + hash: toBase64(assertNotEmpty(params.hash)), prove: params.prove, }; } diff --git a/packages/tendermint-rpc/src/tendermint35/adaptor/responses.spec.ts b/packages/tendermint-rpc/src/tendermint37/adaptor/responses.spec.ts similarity index 100% rename from packages/tendermint-rpc/src/tendermint35/adaptor/responses.spec.ts rename to packages/tendermint-rpc/src/tendermint37/adaptor/responses.spec.ts diff --git a/packages/tendermint-rpc/src/tendermint35/adaptor/responses.ts b/packages/tendermint-rpc/src/tendermint37/adaptor/responses.ts similarity index 100% rename from packages/tendermint-rpc/src/tendermint35/adaptor/responses.ts rename to packages/tendermint-rpc/src/tendermint37/adaptor/responses.ts diff --git a/packages/tendermint-rpc/src/tendermint35/adaptor/types.ts b/packages/tendermint-rpc/src/tendermint37/adaptor/types.ts similarity index 100% rename from packages/tendermint-rpc/src/tendermint35/adaptor/types.ts rename to packages/tendermint-rpc/src/tendermint37/adaptor/types.ts diff --git a/packages/tendermint-rpc/src/tendermint35/encodings.spec.ts b/packages/tendermint-rpc/src/tendermint37/encodings.spec.ts similarity index 100% rename from packages/tendermint-rpc/src/tendermint35/encodings.spec.ts rename to packages/tendermint-rpc/src/tendermint37/encodings.spec.ts diff --git a/packages/tendermint-rpc/src/tendermint35/encodings.ts b/packages/tendermint-rpc/src/tendermint37/encodings.ts similarity index 100% rename from packages/tendermint-rpc/src/tendermint35/encodings.ts rename to packages/tendermint-rpc/src/tendermint37/encodings.ts diff --git a/packages/tendermint-rpc/src/tendermint35/hasher.spec.ts b/packages/tendermint-rpc/src/tendermint37/hasher.spec.ts similarity index 100% rename from packages/tendermint-rpc/src/tendermint35/hasher.spec.ts rename to packages/tendermint-rpc/src/tendermint37/hasher.spec.ts diff --git a/packages/tendermint-rpc/src/tendermint35/hasher.ts b/packages/tendermint-rpc/src/tendermint37/hasher.ts similarity index 100% rename from packages/tendermint-rpc/src/tendermint35/hasher.ts rename to packages/tendermint-rpc/src/tendermint37/hasher.ts diff --git a/packages/tendermint-rpc/src/tendermint35/index.ts b/packages/tendermint-rpc/src/tendermint37/index.ts similarity index 92% rename from packages/tendermint-rpc/src/tendermint35/index.ts rename to packages/tendermint-rpc/src/tendermint37/index.ts index 468b2e98ba..87931eb179 100644 --- a/packages/tendermint-rpc/src/tendermint35/index.ts +++ b/packages/tendermint-rpc/src/tendermint37/index.ts @@ -1,5 +1,5 @@ // Note: all exports in this module are publicly available via -// `import { tendermint35 } from "@cosmjs/tendermint-rpc"` +// `import { tendermint37 } from "@cosmjs/tendermint-rpc"` export { AbciInfoRequest, @@ -76,4 +76,4 @@ export { Vote, VoteType, } from "./responses"; -export { Tendermint35Client } from "./tendermint35client"; +export { Tendermint37Client } from "./tendermint37client"; diff --git a/packages/tendermint-rpc/src/tendermint35/requests.spec.ts b/packages/tendermint-rpc/src/tendermint37/requests.spec.ts similarity index 100% rename from packages/tendermint-rpc/src/tendermint35/requests.spec.ts rename to packages/tendermint-rpc/src/tendermint37/requests.spec.ts diff --git a/packages/tendermint-rpc/src/tendermint35/requests.ts b/packages/tendermint-rpc/src/tendermint37/requests.ts similarity index 100% rename from packages/tendermint-rpc/src/tendermint35/requests.ts rename to packages/tendermint-rpc/src/tendermint37/requests.ts diff --git a/packages/tendermint-rpc/src/tendermint35/responses.ts b/packages/tendermint-rpc/src/tendermint37/responses.ts similarity index 100% rename from packages/tendermint-rpc/src/tendermint35/responses.ts rename to packages/tendermint-rpc/src/tendermint37/responses.ts diff --git a/packages/tendermint-rpc/src/tendermint35/tendermint35client.spec.ts b/packages/tendermint-rpc/src/tendermint37/tendermint37client.spec.ts similarity index 83% rename from packages/tendermint-rpc/src/tendermint35/tendermint35client.spec.ts rename to packages/tendermint-rpc/src/tendermint37/tendermint37client.spec.ts index d46b95cc88..73458aa645 100644 --- a/packages/tendermint-rpc/src/tendermint35/tendermint35client.spec.ts +++ b/packages/tendermint-rpc/src/tendermint37/tendermint37client.spec.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import { toAscii } from "@cosmjs/encoding"; +import { toAscii, toHex } from "@cosmjs/encoding"; import { firstEvent, toListPromise } from "@cosmjs/stream"; -import { sleep } from "@cosmjs/utils"; +import { assert, sleep } from "@cosmjs/utils"; import { ReadonlyDate } from "readonly-date"; import { Stream } from "xstream"; @@ -16,16 +16,16 @@ import { tendermintInstances, tendermintSearchIndexUpdated, } from "../testutil.spec"; -import { adaptor35 } from "./adaptor"; +import { adaptor37 } from "./adaptor"; import { buildQuery } from "./requests"; import * as responses from "./responses"; -import { Tendermint35Client } from "./tendermint35client"; +import { Tendermint37Client } from "./tendermint37client"; function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues): void { describe("create", () => { it("can auto-discover Tendermint version and communicate", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const info = await client.abciInfo(); expect(info).toBeTruthy(); client.disconnect(); @@ -33,7 +33,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) it("can connect to Tendermint with known version", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); expect(await client.abciInfo()).toBeTruthy(); client.disconnect(); }); @@ -41,7 +41,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) it("can get genesis", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const genesis = await client.genesis(); expect(genesis).toBeTruthy(); client.disconnect(); @@ -50,7 +50,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) describe("broadcastTxCommit", () => { it("can broadcast a transaction", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const tx = buildKvTx(randomString(), randomString()); const response = await client.broadcastTxCommit({ tx: tx }); @@ -70,7 +70,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) describe("broadcastTxSync", () => { it("can broadcast a transaction", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const tx = buildKvTx(randomString(), randomString()); const response = await client.broadcastTxSync({ tx: tx }); @@ -86,7 +86,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) describe("broadcastTxAsync", () => { it("can broadcast a transaction", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const tx = buildKvTx(randomString(), randomString()); const response = await client.broadcastTxAsync({ tx: tx }); @@ -98,9 +98,9 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) it("gets the same tx hash from backend as calculated locally", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const tx = buildKvTx(randomString(), randomString()); - const calculatedTxHash = adaptor35.hashTx(tx); + const calculatedTxHash = adaptor37.hashTx(tx); const response = await client.broadcastTxCommit({ tx: tx }); expect(response.hash).toEqual(calculatedTxHash); @@ -111,7 +111,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) describe("abciQuery", () => { it("can query the state", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const key = randomString(); const value = randomString(); @@ -137,7 +137,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) it("can get a commit", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const response = await client.commit(4); expect(response).toBeTruthy(); @@ -152,7 +152,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) it("can get validators", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const response = await client.validators({}); expect(response).toBeTruthy(); @@ -170,7 +170,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) it("can get all validators", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const response = await client.validatorsAll(); expect(response).toBeTruthy(); @@ -188,7 +188,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) it("can call a bunch of methods", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); expect(await client.block()).toBeTruthy(); expect(await client.genesis()).toBeTruthy(); @@ -200,7 +200,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) describe("status", () => { it("works", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const status = await client.status(); @@ -230,7 +230,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) describe("numUnconfirmedTxs", () => { it("works", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const response = await client.numUnconfirmedTxs(); @@ -244,7 +244,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) describe("blockResults", () => { it("works", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const height = 3; const results = await client.blockResults(height); @@ -260,7 +260,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) describe("blockSearch", () => { beforeAll(async () => { if (tendermintEnabled()) { - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); // eslint-disable-next-line no-inner-declarations async function sendTx(): Promise { @@ -285,7 +285,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) it("can paginate over blockSearch results", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const query = buildQuery({ raw: "block.height >= 1 AND block.height <= 3" }); @@ -304,7 +304,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) it("can get all search results in one call", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const query = buildQuery({ raw: "block.height >= 1 AND block.height <= 3" }); @@ -323,7 +323,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) describe("blockchain", () => { it("returns latest in descending order by default", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.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()]); @@ -340,7 +340,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) it("can limit by maxHeight", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const height = (await client.status()).syncInfo.latestBlockHeight; const blockchain = await client.blockchain(undefined, height - 1); @@ -354,7 +354,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) it("works with maxHeight in the future", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const height = (await client.status()).syncInfo.latestBlockHeight; const blockchain = await client.blockchain(undefined, height + 20); @@ -369,7 +369,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) it("can limit by minHeight and maxHeight", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const height = (await client.status()).syncInfo.latestBlockHeight; const blockchain = await client.blockchain(height - 2, height - 1); @@ -383,7 +383,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) it("contains all the info", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const height = (await client.status()).syncInfo.latestBlockHeight; const blockchain = await client.blockchain(height - 1, height - 1); @@ -412,7 +412,7 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) describe("tx", () => { it("can query a tx properly", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const find = randomString(); const me = randomString(); @@ -436,25 +436,6 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) 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); @@ -465,25 +446,28 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) }); describe("txSearch", () => { - const key = randomString(); + const txKey = randomString(); // a key used for multiple transactions + let tx1: Uint8Array | undefined; + let broadcast1: responses.BroadcastTxCommitResponse | undefined; beforeAll(async () => { if (tendermintEnabled()) { - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); // eslint-disable-next-line no-inner-declarations - async function sendTx(): Promise { + async function sendTx(): Promise<[Uint8Array, responses.BroadcastTxCommitResponse]> { const me = randomString(); - const tx = buildKvTx(key, me); + const tx = buildKvTx(txKey, 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); + expect(txRes.hash.length).toEqual(32); + return [tx, txRes]; } // send 3 txs - await sendTx(); + [tx1, broadcast1] = await sendTx(); await sendTx(); await sendTx(); @@ -493,22 +477,86 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) } }); - it("returns transactions in descending order by default", async () => { + it("finds a single tx by hash", async () => { + pendingWithoutTendermint(); + assert(tx1 && broadcast1); + const client = await Tendermint37Client.create(rpcFactory()); + + const result = await client.txSearch({ query: `tx.hash='${toHex(broadcast1.hash)}'` }); + expect(result.totalCount).toEqual(1); + expect(result.txs[0]).toEqual({ + hash: broadcast1.hash, + height: broadcast1.height, + index: 0, + tx: tx1, + result: broadcast1.deliverTx!, + proof: undefined, + }); + + client.disconnect(); + }); + + it("finds a single tx by tags", async () => { + pendingWithoutTendermint(); + const client = await Tendermint37Client.create(rpcFactory()); + + const txKey2 = randomString(); + const txValue2 = randomString(); + const tx = buildKvTx(txKey2, txValue2); + + const txRes = await client.broadcastTxCommit({ tx: tx }); + expect(responses.broadcastTxCommitSuccess(txRes)).toEqual(true); + await tendermintSearchIndexUpdated(); + + // txSearch - you must enable the indexer when running + // tendermint, else you get empty results + const query = buildQuery({ tags: [{ key: "app.key", value: txKey2 }] }); + + const search = await client.txSearch({ query: query, page: 1, per_page: 30 }); + // should find the tx + expect(search.totalCount).toEqual(1); + // should return same info as querying directly, + // except without the proof + expect(search.txs[0]).toEqual({ + hash: txRes.hash, + height: txRes.height, + index: 0, + tx: tx, + result: txRes.deliverTx!, + proof: undefined, + }); + + // Ensure txSearchAll works as well. This should be moved in a dedicated "txSearchAll" test block. + const searchAll = await client.txSearchAll({ query: query }); + expect(searchAll.totalCount).toEqual(1); + expect(searchAll.txs[0]).toEqual({ + hash: txRes.hash, + height: txRes.height, + index: 0, + tx: tx, + result: txRes.deliverTx!, + proof: undefined, + }); + }); + + it("returns transactions in ascending order by default", async () => { // NOTE: The Tendermint docs states the default ordering is "desc". Until // 0.35 it was actually "asc" but from 0.35 on it is "desc". + // Then it was changed back to "asc" in 0.37. // Docs: https://docs.tendermint.com/master/rpc/#/Info/tx_search // Code 0.34: https://github.com/tendermint/tendermint/blob/v0.34.10/rpc/core/tx.go#L89 // Code 0.35: https://github.com/tendermint/tendermint/blob/v0.35.6/internal/rpc/core/tx.go#L93 + // Code 0.37: https://github.com/cometbft/cometbft/blob/v0.37.0-rc3/rpc/core/tx.go#L87 pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); - const query = buildQuery({ tags: [{ key: "app.key", value: key }] }); + const query = buildQuery({ tags: [{ key: "app.key", value: txKey }] }); const result = await client.txSearch({ query: query }); expect(result.totalCount).toEqual(3); result.txs.slice(1).reduce((lastHeight, { height }) => { - expect(height).toBeLessThanOrEqual(lastHeight); + expect(height).toBeGreaterThanOrEqual(lastHeight); return height; }, result.txs[0].height); @@ -517,9 +565,9 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) it("can set the order", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); - const query = buildQuery({ tags: [{ key: "app.key", value: key }] }); + const query = buildQuery({ tags: [{ key: "app.key", value: txKey }] }); const result1 = await client.txSearch({ query: query, order_by: "desc" }); const result2 = await client.txSearch({ query: query, order_by: "asc" }); @@ -532,9 +580,9 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) it("can paginate over txSearch results", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); - const query = buildQuery({ tags: [{ key: "app.key", value: key }] }); + const query = buildQuery({ tags: [{ key: "app.key", value: txKey }] }); // expect one page of results const s1 = await client.txSearch({ query: query, page: 1, per_page: 2 }); @@ -551,17 +599,16 @@ function defaultTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValues) it("can get all search results in one call", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); - const query = buildQuery({ tags: [{ key: "app.key", value: key }] }); + const query = buildQuery({ tags: [{ key: "app.key", value: txKey }] }); 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 highest to lowest height - const [tx1, tx2, tx3] = sall.txs; - expect(tx2.height).toBeLessThan(tx1.height); - expect(tx3.height).toBeLessThan(tx2.height); + expect(sall.txs[1].height).toEqual(sall.txs[0].height + 1); + expect(sall.txs[2].height).toEqual(sall.txs[1].height + 1); client.disconnect(); }); @@ -576,7 +623,7 @@ function websocketTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValue (async () => { const events: responses.NewBlockHeaderEvent[] = []; - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const stream = client.subscribeNewBlockHeader(); expect(stream).toBeTruthy(); const subscription = stream.subscribe({ @@ -635,7 +682,7 @@ function websocketTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValue const transactionData2 = buildKvTx(randomString(), randomString()); const events: responses.NewBlockEvent[] = []; - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const stream = client.subscribeNewBlock(); const subscription = stream.subscribe({ next: (event) => { @@ -694,7 +741,7 @@ function websocketTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValue pendingWithoutTendermint(); const events: responses.TxEvent[] = []; - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const stream = client.subscribeTx(); const subscription = stream.subscribe({ next: (event) => { @@ -738,7 +785,7 @@ function websocketTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValue const transactionData2 = buildKvTx(randomString(), randomString()); const events: responses.TxEvent[] = []; - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const query = buildQuery({ tags: [{ key: "app.creator", value: expected.appCreator }] }); const stream = client.subscribeTx(query); expect(stream).toBeTruthy(); @@ -776,7 +823,7 @@ function websocketTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValue it("can unsubscribe and re-subscribe to the same stream", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const stream = client.subscribeNewBlockHeader(); const event1 = await firstEvent(stream); @@ -809,7 +856,7 @@ function websocketTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValue it("can subscribe twice", async () => { pendingWithoutTendermint(); - const client = await Tendermint35Client.create(rpcFactory()); + const client = await Tendermint37Client.create(rpcFactory()); const stream1 = client.subscribeNewBlockHeader(); const stream2 = client.subscribeNewBlockHeader(); @@ -821,15 +868,15 @@ function websocketTestSuite(rpcFactory: () => RpcClient, expected: ExpectedValue }); } -describe("Tendermint35Client", () => { - const { url, expected } = tendermintInstances[35]; +describe("Tendermint37Client", () => { + const { url, expected } = tendermintInstances[37]; it("can connect to a given url", async () => { pendingWithoutTendermint(); // default connection { - const client = await Tendermint35Client.connect(url); + const client = await Tendermint37Client.connect(url); const info = await client.abciInfo(); expect(info).toBeTruthy(); client.disconnect(); @@ -837,7 +884,7 @@ describe("Tendermint35Client", () => { // http connection { - const client = await Tendermint35Client.connect("http://" + url); + const client = await Tendermint37Client.connect("http://" + url); const info = await client.abciInfo(); expect(info).toBeTruthy(); client.disconnect(); @@ -845,7 +892,7 @@ describe("Tendermint35Client", () => { // ws connection { - const client = await Tendermint35Client.connect("ws://" + url); + const client = await Tendermint37Client.connect("ws://" + url); const info = await client.abciInfo(); expect(info).toBeTruthy(); client.disconnect(); diff --git a/packages/tendermint-rpc/src/tendermint35/tendermint35client.ts b/packages/tendermint-rpc/src/tendermint37/tendermint37client.ts similarity index 95% rename from packages/tendermint-rpc/src/tendermint35/tendermint35client.ts rename to packages/tendermint-rpc/src/tendermint37/tendermint37client.ts index 108966203e..cdbbf164a9 100644 --- a/packages/tendermint-rpc/src/tendermint35/tendermint35client.ts +++ b/packages/tendermint-rpc/src/tendermint37/tendermint37client.ts @@ -10,41 +10,36 @@ import { SubscriptionEvent, WebsocketClient, } from "../rpcclients"; -import { adaptor35, Decoder, Encoder, Params, Responses } from "./adaptor"; +import { adaptor37, Decoder, Encoder, Params, Responses } from "./adaptor"; import * as requests from "./requests"; import * as responses from "./responses"; -/** - * Please note the Tendermint 0.35 client is currently not exported and may break or be removed at any point in time. - * - * @see https://github.com/cosmos/cosmjs/issues/1225 - */ -export class Tendermint35Client { +export class Tendermint37Client { /** * 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(endpoint: string | HttpEndpoint): Promise { + public static async connect(endpoint: string | HttpEndpoint): Promise { if (typeof endpoint === "object") { - return Tendermint35Client.create(new HttpClient(endpoint)); + return Tendermint37Client.create(new HttpClient(endpoint)); } else { const useHttp = endpoint.startsWith("http://") || endpoint.startsWith("https://"); const rpcClient = useHttp ? new HttpClient(endpoint) : new WebsocketClient(endpoint); - return Tendermint35Client.create(rpcClient); + return Tendermint37Client.create(rpcClient); } } /** * Creates a new Tendermint client given an RPC client. */ - public static async create(rpcClient: RpcClient): Promise { + public static async create(rpcClient: RpcClient): Promise { // For some very strange reason I don't understand, tests start to fail on some systems // (our CI) when skipping the status call before doing other queries. Sleeping a little // while did not help. Thus we query the version as a way to say "hi" to the backend, // even in cases where we don't use the result. const _version = await this.detectVersion(rpcClient); - return new Tendermint35Client(rpcClient); + return new Tendermint37Client(rpcClient); } private static async detectVersion(client: RpcClient): Promise { @@ -68,12 +63,12 @@ export class Tendermint35Client { private readonly r: Responses; /** - * Use `Tendermint34Client.connect` or `Tendermint34Client.create` to create an instance. + * Use `Tendermint37Client.connect` or `Tendermint37Client.create` to create an instance. */ private constructor(client: RpcClient) { this.client = client; - this.p = adaptor35.params; - this.r = adaptor35.responses; + this.p = adaptor37.params; + this.r = adaptor37.responses; } public disconnect(): void { diff --git a/packages/tendermint-rpc/src/tendermintclient.ts b/packages/tendermint-rpc/src/tendermintclient.ts new file mode 100644 index 0000000000..3d07558bd4 --- /dev/null +++ b/packages/tendermint-rpc/src/tendermintclient.ts @@ -0,0 +1,13 @@ +import { Tendermint34Client } from "./tendermint34"; +import { Tendermint37Client } from "./tendermint37"; + +/** A TendermintClient is either a Tendermint34Client or a Tendermint37Client */ +export type TendermintClient = Tendermint34Client | Tendermint37Client; + +export function isTendermint34Client(client: TendermintClient): client is Tendermint34Client { + return client instanceof Tendermint34Client; +} + +export function isTendermint37Client(client: TendermintClient): client is Tendermint37Client { + return client instanceof Tendermint37Client; +} diff --git a/packages/tendermint-rpc/src/testutil.spec.ts b/packages/tendermint-rpc/src/testutil.spec.ts index c724a631a5..3b699c67b5 100644 --- a/packages/tendermint-rpc/src/testutil.spec.ts +++ b/packages/tendermint-rpc/src/testutil.spec.ts @@ -49,13 +49,13 @@ export const tendermintInstances = { appVersion: 1, }, }, - 35: { - url: "localhost:11135", - version: "0.35.x", + 37: { + url: "localhost:11137", + version: "0.37.x", blockTime: 500, expected: { chainId: /^dockerchain$/, - version: /^$/, // Unfortunately we don't get info here + version: /^0\.37\.0-alpha\.3$/, appCreator: "Cosmoshi Netowoko", p2pVersion: 8, blockVersion: 11, diff --git a/scripts/tendermint/all_start.sh b/scripts/tendermint/all_start.sh index f68135cb6c..5c691cd829 100755 --- a/scripts/tendermint/all_start.sh +++ b/scripts/tendermint/all_start.sh @@ -2,18 +2,25 @@ set -o errexit -o nounset -o pipefail 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[34]=v0.34.19 -TM_VERSIONS[35]=v0.35.6 +# Find latest patch releases at +# - https://hub.docker.com/r/tendermint/tendermint/tags/ +# - https://hub.docker.com/r/cometbft/cometbft/tags/ +declare -a TM_IMAGES +TM_IMAGES[34]="tendermint/tendermint:v0.34.19" +TM_IMAGES[37]="cometbft/cometbft:v0.37.0-rc3" + +declare -a TM_ROOTS +TM_ROOTS[34]="/tendermint" +TM_ROOTS[37]="/cometbft" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -for KEY in "${!TM_VERSIONS[@]}"; do - export TENDERMINT_VERSION="${TM_VERSIONS[$KEY]}" +for KEY in "${!TM_IMAGES[@]}"; do + export TENDERMINT_IMAGE="${TM_IMAGES[$KEY]}" + export TENDERMINT_ROOT="${TM_ROOTS[$KEY]}" export TENDERMINT_PORT="111$KEY" export TENDERMINT_NAME="tendermint-$KEY" - echo "Starting $TENDERMINT_NAME ($TENDERMINT_VERSION) on port $TENDERMINT_PORT ..." + echo "Starting $TENDERMINT_NAME ($TENDERMINT_IMAGE) on port $TENDERMINT_PORT ..." "$SCRIPT_DIR/start.sh" done diff --git a/scripts/tendermint/all_stop.sh b/scripts/tendermint/all_stop.sh index 136dfd054f..a2988c4fb5 100755 --- a/scripts/tendermint/all_stop.sh +++ b/scripts/tendermint/all_stop.sh @@ -2,13 +2,9 @@ set -o errexit -o nounset -o pipefail command -v shellcheck >/dev/null && shellcheck "$0" -declare -a TM_VERSIONS -TM_VERSIONS[34]=v0.34.19 -TM_VERSIONS[35]=v0.35.6 - SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -for KEY in "${!TM_VERSIONS[@]}"; do +for KEY in 34 37; do export TENDERMINT_NAME="tendermint-$KEY" echo "Stopping $TENDERMINT_NAME ..." diff --git a/scripts/tendermint/start.sh b/scripts/tendermint/start.sh index 00ae26e2e7..e871df8ddd 100755 --- a/scripts/tendermint/start.sh +++ b/scripts/tendermint/start.sh @@ -6,7 +6,7 @@ gnused="$(command -v gsed || echo sed)" # Tendermint settings must be specified # Choose version from https://hub.docker.com/r/tendermint/tendermint/tags/ -for SETTING in "TENDERMINT_VERSION" "TENDERMINT_PORT" "TENDERMINT_NAME"; do +for SETTING in "TENDERMINT_IMAGE" "TENDERMINT_PORT" "TENDERMINT_NAME"; do if test -z "$(eval echo "\$$SETTING")"; then echo "\$$SETTING must be set when running this script" exit 1 @@ -20,8 +20,8 @@ LOGFILE="$TMP_DIR/tendermint.log" docker run --rm \ --user="$UID" \ - -v "${TMP_DIR}:/tendermint" \ - "tendermint/tendermint:${TENDERMINT_VERSION}" \ + -v "${TMP_DIR}:${TENDERMINT_ROOT}" \ + "${TENDERMINT_IMAGE}" \ init validator # make sure we allow cors origins, only possible by modifying the config file @@ -36,11 +36,11 @@ docker run --rm \ docker run --rm \ --user="$UID" \ --name "$TENDERMINT_NAME" \ - -p "${TENDERMINT_PORT}:26657" -v "${TMP_DIR}:/tendermint" \ + -p "${TENDERMINT_PORT}:26657" -v "${TMP_DIR}:${TENDERMINT_ROOT}" \ -e "TM_TX_INDEX_INDEX_ALL_KEYS=true" \ -e "PROXY_APP=kvstore" \ -e "LOG_LEVEL=state:info,rpc:info,*:error" \ - "tendermint/tendermint:${TENDERMINT_VERSION}" node \ + "${TENDERMINT_IMAGE}" node \ --rpc.laddr=tcp://0.0.0.0:26657 \ >"$LOGFILE" 2>&1 &