diff --git a/CHANGELOG.md b/CHANGELOG.md index e1eeb462..8efd05b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ and this project adheres to ## [Unreleased] +### Deprecated + +- @cosmjs/stargate: Deprecate `QueryClient.queryUnverified` in favour of newly + added `QueryClient.queryAbci`. + ## [0.29.4] - 2022-11-15 ### Added diff --git a/packages/stargate/src/queryclient/queryclient.spec.ts b/packages/stargate/src/queryclient/queryclient.spec.ts index 823fb13a..05cc40ec 100644 --- a/packages/stargate/src/queryclient/queryclient.spec.ts +++ b/packages/stargate/src/queryclient/queryclient.spec.ts @@ -166,4 +166,95 @@ describe("QueryClient", () => { tmClient.disconnect(); }); }); + + describe("queryAbci", () => { + it("works via WebSockets", async () => { + pendingWithoutSimapp(); + const [client, tmClient] = await makeClient(simapp.tendermintUrlWs); + + const requestData = Uint8Array.from( + QueryAllBalancesRequest.encode({ address: unused.address }).finish(), + ); + const { value } = await client.queryAbci(`/cosmos.bank.v1beta1.Query/AllBalances`, requestData); + const response = QueryAllBalancesResponse.decode(value); + expect(response.balances.length).toEqual(2); + + tmClient.disconnect(); + }); + + it("works via http", async () => { + pendingWithoutSimapp(); + const [client, tmClient] = await makeClient(simapp.tendermintUrlHttp); + + const requestData = Uint8Array.from( + QueryAllBalancesRequest.encode({ address: unused.address }).finish(), + ); + const { value } = await client.queryAbci(`/cosmos.bank.v1beta1.Query/AllBalances`, requestData); + const response = QueryAllBalancesResponse.decode(value); + expect(response.balances.length).toEqual(2); + + tmClient.disconnect(); + }); + + it("works for height", async () => { + pendingWithoutSimapp(); + const [queryClient, tmClient] = await makeClient(simapp.tendermintUrlHttp); + + const joe = makeRandomAddress(); + const h1 = (await tmClient.status()).syncInfo.latestBlockHeight; + + // Send tokens to `recipient` + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); + const client = await SigningStargateClient.connectWithSigner( + simapp.tendermintUrlHttp, + wallet, + defaultSigningClientOptions, + ); + const amount = coin(332211, simapp.denomFee); + await client.sendTokens(faucet.address0, joe, [amount], "auto"); + + const h2 = (await tmClient.status()).syncInfo.latestBlockHeight; + assert(h1 < h2); + + // Query with no height + { + const requestData = QueryBalanceRequest.encode({ address: joe, denom: simapp.denomFee }).finish(); + const { value, height } = await queryClient.queryAbci( + `/cosmos.bank.v1beta1.Query/Balance`, + requestData, + ); + const response = QueryBalanceResponse.decode(value); + expect(response.balance).toEqual(amount); + expect(height).toEqual(h2); + } + + // Query at h2 (after send) + { + const requestData = QueryBalanceRequest.encode({ address: joe, denom: simapp.denomFee }).finish(); + const { value, height } = await queryClient.queryAbci( + `/cosmos.bank.v1beta1.Query/Balance`, + requestData, + h2, + ); + const response = QueryBalanceResponse.decode(value); + expect(response.balance).toEqual(amount); + expect(height).toEqual(h2); + } + + // Query at h1 (before send) + { + const requestData = QueryBalanceRequest.encode({ address: joe, denom: simapp.denomFee }).finish(); + const { value, height } = await queryClient.queryAbci( + `/cosmos.bank.v1beta1.Query/Balance`, + requestData, + h1, + ); + const response = QueryBalanceResponse.decode(value); + expect(response.balance).toEqual({ amount: "0", denom: simapp.denomFee }); + expect(height).toEqual(h1); + } + + tmClient.disconnect(); + }); + }); }); diff --git a/packages/stargate/src/queryclient/queryclient.ts b/packages/stargate/src/queryclient/queryclient.ts index eceec992..2737d735 100644 --- a/packages/stargate/src/queryclient/queryclient.ts +++ b/packages/stargate/src/queryclient/queryclient.ts @@ -26,6 +26,16 @@ export interface ProvenQuery { readonly height: number; } +/** + * The response of an ABCI query to Tendermint. + * This is a subset of `tendermint34.AbciQueryResponse` in order + * to abstract away Tendermint versions. + */ +export interface QueryAbciResponse { + readonly value: Uint8Array; + readonly height: number; +} + export class QueryClient { /** Constructs a QueryClient with 0 extensions */ public static withExtensions(tmClient: Tendermint34Client): QueryClient; @@ -570,11 +580,32 @@ export class QueryClient { }; } + /** + * Performs an ABCI query to Tendermint without requesting a proof. + * + * @deprecated use queryAbci instead + */ public async queryUnverified( path: string, request: Uint8Array, desiredHeight?: number, ): Promise { + const response = await this.queryAbci(path, request, desiredHeight); + return response.value; + } + + /** + * Performs an ABCI query to Tendermint without requesting a proof. + * + * If the `desiredHeight` is set, a particular height is requested. Otherwise + * the latest height is requested. The response contains the actual height of + * the query. + */ + public async queryAbci( + path: string, + request: Uint8Array, + desiredHeight?: number, + ): Promise { const response = await this.tmClient.abciQuery({ path: path, data: request, @@ -586,7 +617,14 @@ export class QueryClient { throw new Error(`Query failed with (${response.code}): ${response.log}`); } - return response.value; + if (!response.height) { + throw new Error("No query height returned"); + } + + return { + value: response.value, + height: response.height, + }; } // this must return the header for height+1 diff --git a/packages/stargate/src/queryclient/utils.ts b/packages/stargate/src/queryclient/utils.ts index a0fb6220..4afa3739 100644 --- a/packages/stargate/src/queryclient/utils.ts +++ b/packages/stargate/src/queryclient/utils.ts @@ -31,9 +31,10 @@ export interface ProtobufRpcClient { export function createProtobufRpcClient(base: QueryClient): ProtobufRpcClient { return { - request: (service: string, method: string, data: Uint8Array): Promise => { + request: async (service: string, method: string, data: Uint8Array): Promise => { const path = `/${service}/${method}`; - return base.queryUnverified(path, data); + const response = await base.queryAbci(path, data, undefined); + return response.value; }, }; }