From 037dc3d69f3b72a8235aa3224e85972aad09af01 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 7 Dec 2022 14:15:16 +0100 Subject: [PATCH] Create queryStoreVerified and deprecate queryVerified --- CHANGELOG.md | 2 ++ packages/stargate/src/index.ts | 1 + packages/stargate/src/modules/ibc/queries.ts | 16 ++++----- packages/stargate/src/queryclient/index.ts | 2 +- .../src/queryclient/queryclient.spec.ts | 28 ++++++++------- .../stargate/src/queryclient/queryclient.ts | 34 ++++++++++++++++--- 6 files changed, 56 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08279b5b..7709d107 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ and this project adheres to - @cosmjs/stargate: Deprecate `QueryClient.queryUnverified` in favour of newly added `QueryClient.queryAbci`. +- @cosmjs/stargate: Deprecate `QueryClient.queryVerified` in favour of newly + added `QueryClient.queryStoreVerified`. ## [0.29.4] - 2022-11-15 diff --git a/packages/stargate/src/index.ts b/packages/stargate/src/index.ts index 55d01699..fab188f7 100644 --- a/packages/stargate/src/index.ts +++ b/packages/stargate/src/index.ts @@ -110,6 +110,7 @@ export { ProtobufRpcClient, QueryAbciResponse, QueryClient, + QueryStoreResponse, } from "./queryclient"; export { isSearchByHeightQuery, diff --git a/packages/stargate/src/modules/ibc/queries.ts b/packages/stargate/src/modules/ibc/queries.ts index c35dc62e..c4c2f43e 100644 --- a/packages/stargate/src/modules/ibc/queries.ts +++ b/packages/stargate/src/modules/ibc/queries.ts @@ -510,31 +510,31 @@ export function setupIbcExtension(base: QueryClient): IbcExtension { // keeper: https://github.com/cosmos/cosmos-sdk/blob/3bafd8255a502e5a9cee07391cf8261538245dfd/x/ibc/04-channel/keeper/keeper.go#L55-L65 // key: https://github.com/cosmos/cosmos-sdk/blob/ef0a7344af345882729598bc2958a21143930a6b/x/ibc/24-host/keys.go#L117-L120 const key = toAscii(`channelEnds/ports/${portId}/channels/${channelId}`); - const responseData = await base.queryVerified("ibc", key); - return responseData.length ? Channel.decode(responseData) : null; + const { value } = await base.queryStoreVerified("ibc", key); + return value.length ? Channel.decode(value) : null; }, packetCommitment: async (portId: string, channelId: string, sequence: number) => { // keeper: https://github.com/cosmos/cosmos-sdk/blob/3bafd8255a502e5a9cee07391cf8261538245dfd/x/ibc/04-channel/keeper/keeper.go#L128-L133 // key: https://github.com/cosmos/cosmos-sdk/blob/ef0a7344af345882729598bc2958a21143930a6b/x/ibc/24-host/keys.go#L183-L185 const key = toAscii(`commitments/ports/${portId}/channels/${channelId}/packets/${sequence}`); - const responseData = await base.queryVerified("ibc", key); + const { value } = await base.queryStoreVerified("ibc", key); // keeper code doesn't parse, but returns raw - return responseData; + return value; }, packetAcknowledgement: async (portId: string, channelId: string, sequence: number) => { // keeper: https://github.com/cosmos/cosmos-sdk/blob/3bafd8255a502e5a9cee07391cf8261538245dfd/x/ibc/04-channel/keeper/keeper.go#L159-L166 // key: https://github.com/cosmos/cosmos-sdk/blob/ef0a7344af345882729598bc2958a21143930a6b/x/ibc/24-host/keys.go#L153-L156 const key = toAscii(`acks/ports/${portId}/channels/${channelId}/acknowledgements/${sequence}`); - const responseData = await base.queryVerified("ibc", key); + const { value } = await base.queryStoreVerified("ibc", key); // keeper code doesn't parse, but returns raw - return responseData; + return value; }, nextSequenceReceive: async (portId: string, channelId: string) => { // keeper: https://github.com/cosmos/cosmos-sdk/blob/3bafd8255a502e5a9cee07391cf8261538245dfd/x/ibc/04-channel/keeper/keeper.go#L92-L101 // key: https://github.com/cosmos/cosmos-sdk/blob/ef0a7344af345882729598bc2958a21143930a6b/x/ibc/24-host/keys.go#L133-L136 const key = toAscii(`seqAcks/ports/${portId}/channels/${channelId}/nextSequenceAck`); - const responseData = await base.queryVerified("ibc", key); - return responseData.length ? Uint64.fromBytes(responseData).toNumber() : null; + const { value } = await base.queryStoreVerified("ibc", key); + return value.length ? Uint64.fromBytes(value).toNumber() : null; }, }, }, diff --git a/packages/stargate/src/queryclient/index.ts b/packages/stargate/src/queryclient/index.ts index 4dd0463b..0e910592 100644 --- a/packages/stargate/src/queryclient/index.ts +++ b/packages/stargate/src/queryclient/index.ts @@ -1,4 +1,4 @@ -export { QueryAbciResponse, QueryClient } from "./queryclient"; +export { QueryAbciResponse, QueryClient, QueryStoreResponse } from "./queryclient"; export { createPagination, createProtobufRpcClient, diff --git a/packages/stargate/src/queryclient/queryclient.spec.ts b/packages/stargate/src/queryclient/queryclient.spec.ts index 05cc40ec..5a825b36 100644 --- a/packages/stargate/src/queryclient/queryclient.spec.ts +++ b/packages/stargate/src/queryclient/queryclient.spec.ts @@ -37,26 +37,27 @@ async function makeClient(rpcUrl: string): Promise<[QueryClient, Tendermint34Cli const denomMetadataPrefix = new Uint8Array([0x01]); describe("QueryClient", () => { - describe("queryVerified", () => { + describe("queryStoreVerified", () => { it("works via WebSockets", async () => { pendingWithoutSimapp(); const [client, tmClient] = await makeClient(simapp.tendermintUrlWs); // "keys before 0.45 had denom two times in the key" // https://github.com/cosmos/cosmos-sdk/blob/10ad61a4dd/x/bank/migrations/v045/store_test.go#L91 - let key: Uint8Array; + let queryKey: Uint8Array; if (simapp44Enabled()) { - key = Uint8Array.from([ + queryKey = Uint8Array.from([ ...denomMetadataPrefix, ...toAscii(simapp.denomFee), ...toAscii(simapp.denomFee), ]); } else { - key = Uint8Array.from([...denomMetadataPrefix, ...toAscii(simapp.denomFee)]); + queryKey = Uint8Array.from([...denomMetadataPrefix, ...toAscii(simapp.denomFee)]); } - const data = await client.queryVerified("bank", key); - - const response = Metadata.decode(data); + const { key, value, height } = await client.queryStoreVerified("bank", queryKey); + expect(height).toBeGreaterThanOrEqual(1); + expect(key).toEqual(queryKey); + const response = Metadata.decode(value); expect(response.base).toEqual(simapp.denomFee); expect(response.description).toEqual("The fee token of this test chain"); @@ -69,20 +70,21 @@ describe("QueryClient", () => { // "keys before 0.45 had denom two times in the key" // https://github.com/cosmos/cosmos-sdk/blob/10ad61a4dd/x/bank/migrations/v045/store_test.go#L91 - let key: Uint8Array; + let queryKey: Uint8Array; if (simapp44Enabled()) { - key = Uint8Array.from([ + queryKey = Uint8Array.from([ ...denomMetadataPrefix, ...toAscii(simapp.denomFee), ...toAscii(simapp.denomFee), ]); } else { - key = Uint8Array.from([...denomMetadataPrefix, ...toAscii(simapp.denomFee)]); + queryKey = Uint8Array.from([...denomMetadataPrefix, ...toAscii(simapp.denomFee)]); } - const data = await client.queryVerified("bank", key); - - const response = Metadata.decode(data); + const { key, value, height } = await client.queryStoreVerified("bank", queryKey); + expect(height).toBeGreaterThanOrEqual(1); + expect(key).toEqual(queryKey); + const response = Metadata.decode(value); expect(response.base).toEqual(simapp.denomFee); expect(response.description).toEqual("The fee token of this test chain"); diff --git a/packages/stargate/src/queryclient/queryclient.ts b/packages/stargate/src/queryclient/queryclient.ts index 2737d735..48a218c8 100644 --- a/packages/stargate/src/queryclient/queryclient.ts +++ b/packages/stargate/src/queryclient/queryclient.ts @@ -26,6 +26,13 @@ export interface ProvenQuery { readonly height: number; } +export interface QueryStoreResponse { + /** The response key from Tendermint. This is the same as the query key in the request. */ + readonly key: Uint8Array; + readonly value: Uint8Array; + readonly height: number; +} + /** * The response of an ABCI query to Tendermint. * This is a subset of `tendermint34.AbciQueryResponse` in order @@ -505,10 +512,27 @@ export class QueryClient { this.tmClient = tmClient; } + /** + * @deprecated use queryStoreVerified instead + */ public async queryVerified(store: string, key: Uint8Array, desiredHeight?: number): Promise { - const { height, proof, value } = await this.queryRawProof(store, key, desiredHeight); + const { value } = await this.queryStoreVerified(store, key, desiredHeight); + return value; + } - const subProof = checkAndParseOp(proof.ops[0], "ics23:iavl", key); + /** + * Queries the database store with a proof, which is then verified. + * + * Please note: the current implementation trusts block headers it gets from the PRC endpoint. + */ + public async queryStoreVerified( + store: string, + queryKey: Uint8Array, + desiredHeight?: number, + ): Promise { + const { height, proof, key, value } = await this.queryRawProof(store, queryKey, desiredHeight); + + const subProof = checkAndParseOp(proof.ops[0], "ics23:iavl", queryKey); const storeProof = checkAndParseOp(proof.ops[1], "ics23:simple", toAscii(store)); // this must always be existence, if the store is not a typo @@ -520,20 +544,20 @@ export class QueryClient { // non-existence check assert(subProof.nonexist); // the subproof must map the desired key to the "value" of the storeProof - verifyNonExistence(subProof.nonexist, iavlSpec, storeProof.exist.value, key); + verifyNonExistence(subProof.nonexist, iavlSpec, storeProof.exist.value, queryKey); } else { // existence check assert(subProof.exist); assert(subProof.exist.value); // the subproof must map the desired key to the "value" of the storeProof - verifyExistence(subProof.exist, iavlSpec, storeProof.exist.value, key, value); + verifyExistence(subProof.exist, iavlSpec, storeProof.exist.value, queryKey, value); } // the store proof must map its declared value (root of subProof) to the appHash of the next block const header = await this.getNextHeader(height); verifyExistence(storeProof.exist, tendermintSpec, header.appHash, toAscii(store), storeProof.exist.value); - return value; + return { key, value, height }; } public async queryRawProof(