mirror of
https://github.com/cosmos/cosmjs.git
synced 2025-03-10 13:47:12 +00:00
Remove support for verified queries
The ics23 library for TypeScript is unmaintained sucht that we cannot provide this feature anymore
This commit is contained in:
parent
9f92a188cb
commit
e6e4c24cbb
@ -1,6 +1,4 @@
|
|||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* 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 { Any } from "cosmjs-types/google/protobuf/any";
|
||||||
import {
|
import {
|
||||||
QueryClientImpl as TransferQuery,
|
QueryClientImpl as TransferQuery,
|
||||||
@ -8,7 +6,6 @@ import {
|
|||||||
QueryDenomTracesResponse,
|
QueryDenomTracesResponse,
|
||||||
QueryParamsResponse as QueryTransferParamsResponse,
|
QueryParamsResponse as QueryTransferParamsResponse,
|
||||||
} from "cosmjs-types/ibc/applications/transfer/v1/query";
|
} from "cosmjs-types/ibc/applications/transfer/v1/query";
|
||||||
import { Channel } from "cosmjs-types/ibc/core/channel/v1/channel";
|
|
||||||
import {
|
import {
|
||||||
QueryChannelClientStateResponse,
|
QueryChannelClientStateResponse,
|
||||||
QueryChannelConsensusStateResponse,
|
QueryChannelConsensusStateResponse,
|
||||||
@ -166,22 +163,6 @@ export interface IbcExtension {
|
|||||||
readonly allDenomTraces: () => Promise<QueryDenomTracesResponse>;
|
readonly allDenomTraces: () => Promise<QueryDenomTracesResponse>;
|
||||||
readonly params: () => Promise<QueryTransferParamsResponse>;
|
readonly params: () => Promise<QueryTransferParamsResponse>;
|
||||||
};
|
};
|
||||||
readonly verified: {
|
|
||||||
readonly channel: {
|
|
||||||
readonly channel: (portId: string, channelId: string) => Promise<Channel | null>;
|
|
||||||
readonly packetCommitment: (
|
|
||||||
portId: string,
|
|
||||||
channelId: string,
|
|
||||||
sequence: number,
|
|
||||||
) => Promise<Uint8Array>;
|
|
||||||
readonly packetAcknowledgement: (
|
|
||||||
portId: string,
|
|
||||||
channelId: string,
|
|
||||||
sequence: number,
|
|
||||||
) => Promise<Uint8Array>;
|
|
||||||
readonly nextSequenceReceive: (portId: string, channelId: string) => Promise<number | null>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -502,40 +483,6 @@ export function setupIbcExtension(base: QueryClient): IbcExtension {
|
|||||||
},
|
},
|
||||||
params: async () => transferQueryService.Params({}),
|
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;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
import { coin } from "@cosmjs/amino";
|
import { coin } from "@cosmjs/amino";
|
||||||
import { toAscii } from "@cosmjs/encoding";
|
|
||||||
import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing";
|
import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing";
|
||||||
import { CometClient, Tendermint34Client } from "@cosmjs/tendermint-rpc";
|
import { CometClient, Tendermint34Client } from "@cosmjs/tendermint-rpc";
|
||||||
import { assert } from "@cosmjs/utils";
|
import { assert } from "@cosmjs/utils";
|
||||||
import { Metadata } from "cosmjs-types/cosmos/bank/v1beta1/bank";
|
|
||||||
import {
|
import {
|
||||||
QueryAllBalancesRequest,
|
QueryAllBalancesRequest,
|
||||||
QueryAllBalancesResponse,
|
QueryAllBalancesResponse,
|
||||||
@ -19,7 +17,6 @@ import {
|
|||||||
makeRandomAddress,
|
makeRandomAddress,
|
||||||
pendingWithoutSimapp,
|
pendingWithoutSimapp,
|
||||||
simapp,
|
simapp,
|
||||||
simapp44Enabled,
|
|
||||||
unused,
|
unused,
|
||||||
} from "../testutils.spec";
|
} from "../testutils.spec";
|
||||||
import { QueryClient } from "./queryclient";
|
import { QueryClient } from "./queryclient";
|
||||||
@ -29,69 +26,7 @@ async function makeClient(rpcUrl: string): Promise<[QueryClient, CometClient]> {
|
|||||||
return [QueryClient.withExtensions(cometClient), 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("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", () => {
|
describe("queryAbci", () => {
|
||||||
it("works via WebSockets", async () => {
|
it("works via WebSockets", async () => {
|
||||||
pendingWithoutSimapp();
|
pendingWithoutSimapp();
|
||||||
|
@ -1,24 +1,10 @@
|
|||||||
/* eslint-disable no-dupe-class-members, @typescript-eslint/ban-types, @typescript-eslint/naming-convention */
|
/* eslint-disable no-dupe-class-members, @typescript-eslint/ban-types, @typescript-eslint/naming-convention */
|
||||||
import { iavlSpec, ics23, tendermintSpec, verifyExistence, verifyNonExistence } from "@confio/ics23";
|
import { CometClient } from "@cosmjs/tendermint-rpc";
|
||||||
import { toAscii, toHex } from "@cosmjs/encoding";
|
import { assert, isNonNullObject } from "@cosmjs/utils";
|
||||||
import { firstEvent } from "@cosmjs/stream";
|
|
||||||
import { CometClient, tendermint34 } from "@cosmjs/tendermint-rpc";
|
|
||||||
import { arrayContentEquals, assert, assertDefined, isNonNullObject, sleep } from "@cosmjs/utils";
|
|
||||||
import { ProofOps } from "cosmjs-types/tendermint/crypto/proof";
|
import { ProofOps } from "cosmjs-types/tendermint/crypto/proof";
|
||||||
import { Stream } from "xstream";
|
|
||||||
|
|
||||||
type QueryExtensionSetup<P> = (base: QueryClient) => P;
|
type QueryExtensionSetup<P> = (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 {
|
export interface ProvenQuery {
|
||||||
readonly key: Uint8Array;
|
readonly key: Uint8Array;
|
||||||
readonly value: Uint8Array;
|
readonly value: Uint8Array;
|
||||||
@ -512,90 +498,6 @@ export class QueryClient {
|
|||||||
this.cometClient = cometClient;
|
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<QueryStoreResponse> {
|
|
||||||
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<ProvenQuery> {
|
|
||||||
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.
|
* Performs an ABCI query to Tendermint without requesting a proof.
|
||||||
*
|
*
|
||||||
@ -628,45 +530,4 @@ export class QueryClient {
|
|||||||
height: response.height,
|
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<tendermint34.Header> {
|
|
||||||
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<tendermint34.NewBlockHeaderEvent> | 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user