From e6e4c24cbbf4a421f5f6b4dbd6302c2c6b70f002 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 15 Jan 2025 13:32:40 +0100 Subject: [PATCH] Remove support for verified queries The ics23 library for TypeScript is unmaintained sucht that we cannot provide this feature anymore --- packages/stargate/src/modules/ibc/queries.ts | 53 ------- .../src/queryclient/queryclient.spec.ts | 65 -------- .../stargate/src/queryclient/queryclient.ts | 143 +----------------- 3 files changed, 2 insertions(+), 259 deletions(-) diff --git a/packages/stargate/src/modules/ibc/queries.ts b/packages/stargate/src/modules/ibc/queries.ts index cbf2faacdc..33c27017ba 100644 --- a/packages/stargate/src/modules/ibc/queries.ts +++ b/packages/stargate/src/modules/ibc/queries.ts @@ -1,6 +1,4 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import { toAscii } from "@cosmjs/encoding"; -import { Uint64 } from "@cosmjs/math"; import { Any } from "cosmjs-types/google/protobuf/any"; import { QueryClientImpl as TransferQuery, @@ -8,7 +6,6 @@ import { QueryDenomTracesResponse, QueryParamsResponse as QueryTransferParamsResponse, } from "cosmjs-types/ibc/applications/transfer/v1/query"; -import { Channel } from "cosmjs-types/ibc/core/channel/v1/channel"; import { QueryChannelClientStateResponse, QueryChannelConsensusStateResponse, @@ -166,22 +163,6 @@ export interface IbcExtension { readonly allDenomTraces: () => Promise; readonly params: () => Promise; }; - readonly verified: { - readonly channel: { - readonly channel: (portId: string, channelId: string) => Promise; - readonly packetCommitment: ( - portId: string, - channelId: string, - sequence: number, - ) => Promise; - readonly packetAcknowledgement: ( - portId: string, - channelId: string, - sequence: number, - ) => Promise; - readonly nextSequenceReceive: (portId: string, channelId: string) => Promise; - }; - }; }; } @@ -502,40 +483,6 @@ export function setupIbcExtension(base: QueryClient): IbcExtension { }, params: async () => transferQueryService.Params({}), }, - verified: { - channel: { - channel: async (portId: string, channelId: string) => { - // 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 { 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 { value } = await base.queryStoreVerified("ibc", key); - // keeper code doesn't parse, but returns raw - 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 { value } = await base.queryStoreVerified("ibc", key); - // keeper code doesn't parse, but returns raw - 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 { value } = await base.queryStoreVerified("ibc", key); - return value.length ? Uint64.fromBytes(value).toNumber() : null; - }, - }, - }, }, }; } diff --git a/packages/stargate/src/queryclient/queryclient.spec.ts b/packages/stargate/src/queryclient/queryclient.spec.ts index cfbf318521..891f0c50b9 100644 --- a/packages/stargate/src/queryclient/queryclient.spec.ts +++ b/packages/stargate/src/queryclient/queryclient.spec.ts @@ -1,10 +1,8 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { coin } from "@cosmjs/amino"; -import { toAscii } from "@cosmjs/encoding"; import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; import { CometClient, Tendermint34Client } from "@cosmjs/tendermint-rpc"; import { assert } from "@cosmjs/utils"; -import { Metadata } from "cosmjs-types/cosmos/bank/v1beta1/bank"; import { QueryAllBalancesRequest, QueryAllBalancesResponse, @@ -19,7 +17,6 @@ import { makeRandomAddress, pendingWithoutSimapp, simapp, - simapp44Enabled, unused, } from "../testutils.spec"; import { QueryClient } from "./queryclient"; @@ -29,69 +26,7 @@ async function makeClient(rpcUrl: string): Promise<[QueryClient, CometClient]> { return [QueryClient.withExtensions(cometClient), cometClient]; } -/** - * See - * - https://github.com/cosmos/cosmos-sdk/blob/v0.42.10/x/bank/types/key.go#L27 - * - https://github.com/cosmos/cosmos-sdk/blob/v0.44.2/x/bank/types/key.go#L28 - */ -const denomMetadataPrefix = new Uint8Array([0x01]); - describe("QueryClient", () => { - describe("queryStoreVerified", () => { - it("works via WebSockets", async () => { - pendingWithoutSimapp(); - const [client, cometClient] = 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 queryKey: Uint8Array; - if (simapp44Enabled()) { - queryKey = Uint8Array.from([ - ...denomMetadataPrefix, - ...toAscii(simapp.denomFee), - ...toAscii(simapp.denomFee), - ]); - } else { - queryKey = Uint8Array.from([...denomMetadataPrefix, ...toAscii(simapp.denomFee)]); - } - 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"); - - cometClient.disconnect(); - }); - - it("works via http", async () => { - pendingWithoutSimapp(); - const [client, cometClient] = await makeClient(simapp.tendermintUrlHttp); - - // "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 queryKey: Uint8Array; - if (simapp44Enabled()) { - queryKey = Uint8Array.from([ - ...denomMetadataPrefix, - ...toAscii(simapp.denomFee), - ...toAscii(simapp.denomFee), - ]); - } else { - queryKey = Uint8Array.from([...denomMetadataPrefix, ...toAscii(simapp.denomFee)]); - } - - 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"); - - cometClient.disconnect(); - }); - }); - describe("queryAbci", () => { it("works via WebSockets", async () => { pendingWithoutSimapp(); diff --git a/packages/stargate/src/queryclient/queryclient.ts b/packages/stargate/src/queryclient/queryclient.ts index e839666cb0..3157825e25 100644 --- a/packages/stargate/src/queryclient/queryclient.ts +++ b/packages/stargate/src/queryclient/queryclient.ts @@ -1,24 +1,10 @@ /* eslint-disable no-dupe-class-members, @typescript-eslint/ban-types, @typescript-eslint/naming-convention */ -import { iavlSpec, ics23, tendermintSpec, verifyExistence, verifyNonExistence } from "@confio/ics23"; -import { toAscii, toHex } from "@cosmjs/encoding"; -import { firstEvent } from "@cosmjs/stream"; -import { CometClient, tendermint34 } from "@cosmjs/tendermint-rpc"; -import { arrayContentEquals, assert, assertDefined, isNonNullObject, sleep } from "@cosmjs/utils"; +import { CometClient } from "@cosmjs/tendermint-rpc"; +import { assert, isNonNullObject } from "@cosmjs/utils"; import { ProofOps } from "cosmjs-types/tendermint/crypto/proof"; -import { Stream } from "xstream"; type QueryExtensionSetup

= (base: QueryClient) => P; -function checkAndParseOp(op: tendermint34.ProofOp, kind: string, key: Uint8Array): ics23.CommitmentProof { - if (op.type !== kind) { - throw new Error(`Op expected to be ${kind}, got "${op.type}`); - } - if (!arrayContentEquals(key, op.key)) { - throw new Error(`Proven key different than queried key.\nQuery: ${toHex(key)}\nProven: ${toHex(op.key)}`); - } - return ics23.CommitmentProof.decode(op.data); -} - export interface ProvenQuery { readonly key: Uint8Array; readonly value: Uint8Array; @@ -512,90 +498,6 @@ export class QueryClient { this.cometClient = cometClient; } - /** - * 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 - assert(storeProof.exist); - assert(storeProof.exist.value); - - // this may be exist or non-exist, depends on response - if (!value || value.length === 0) { - // 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, 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, 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 { key, value, height }; - } - - public async queryRawProof( - store: string, - queryKey: Uint8Array, - desiredHeight?: number, - ): Promise { - const { key, value, height, proof, code, log } = await this.cometClient.abciQuery({ - // we need the StoreKey for the module, not the module name - // https://github.com/cosmos/cosmos-sdk/blob/8cab43c8120fec5200c3459cbf4a92017bb6f287/x/auth/types/keys.go#L12 - path: `/store/${store}/key`, - data: queryKey, - prove: true, - height: desiredHeight, - }); - - if (code) { - throw new Error(`Query failed with (${code}): ${log}`); - } - - if (!arrayContentEquals(queryKey, key)) { - throw new Error(`Response key ${toHex(key)} doesn't match query key ${toHex(queryKey)}`); - } - - if (!height) { - throw new Error("No query height returned"); - } - if (!proof || proof.ops.length !== 2) { - throw new Error(`Expected 2 proof ops, got ${proof?.ops.length ?? 0}. Are you using stargate?`); - } - - // we don't need the results, but we can ensure the data is the proper format - checkAndParseOp(proof.ops[0], "ics23:iavl", key); - checkAndParseOp(proof.ops[1], "ics23:simple", toAscii(store)); - - return { - key: key, - value: value, - height: height, - // need to clone this: readonly input / writeable output - proof: { - ops: [...proof.ops], - }, - }; - } - /** * Performs an ABCI query to Tendermint without requesting a proof. * @@ -628,45 +530,4 @@ export class QueryClient { height: response.height, }; } - - // this must return the header for height+1 - // throws an error if height is 0 or undefined - private async getNextHeader(height?: number): Promise { - assertDefined(height); - if (height === 0) { - throw new Error("Query returned height 0, cannot prove it"); - } - - const searchHeight = height + 1; - let nextHeader: tendermint34.Header | undefined; - let headersSubscription: Stream | undefined; - try { - headersSubscription = this.cometClient.subscribeNewBlockHeader(); - } catch { - // Ignore exception caused by non-WebSocket Tendermint clients - } - - if (headersSubscription) { - const firstHeader = await firstEvent(headersSubscription); - // The first header we get might not be n+1 but n+2 or even higher. In such cases we fall back on a query. - if (firstHeader.height === searchHeight) { - nextHeader = firstHeader; - } - } - - while (!nextHeader) { - // start from current height to avoid backend error for minHeight in the future - const correctHeader = (await this.cometClient.blockchain(height, searchHeight)).blockMetas - .map((meta) => meta.header) - .find((h) => h.height === searchHeight); - if (correctHeader) { - nextHeader = correctHeader; - } else { - await sleep(1000); - } - } - - assert(nextHeader.height === searchHeight, "Got wrong header. This is a bug in the logic above."); - return nextHeader; - } }