From 62afab19364788fab2ca3d03e0b1ef9a284a4260 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 9 Feb 2021 14:59:56 +0100 Subject: [PATCH 1/5] Add queryRawProof to get proof info from QueryClient --- packages/stargate/src/queries/queryclient.ts | 47 ++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/packages/stargate/src/queries/queryclient.ts b/packages/stargate/src/queries/queryclient.ts index 31cf9ef1..3af4bdf8 100644 --- a/packages/stargate/src/queries/queryclient.ts +++ b/packages/stargate/src/queries/queryclient.ts @@ -6,6 +6,8 @@ import { Client as TendermintClient, Header, NewBlockHeaderEvent, ProofOp } from import { arrayContentEquals, assert, assertDefined, isNonNullObject, sleep } from "@cosmjs/utils"; import { Stream } from "xstream"; +import { ProofOps } from "../codec/tendermint/crypto/proof"; + type QueryExtensionSetup

= (base: QueryClient) => P; function checkAndParseOp(op: ProofOp, kind: string, key: Uint8Array): ics23.CommitmentProof { @@ -18,6 +20,13 @@ function checkAndParseOp(op: ProofOp, kind: string, key: Uint8Array): ics23.Comm return ics23.CommitmentProof.decode(op.data); } +export interface ProvenQuery { + readonly key: Uint8Array; + readonly value: Uint8Array; + readonly proof: ProofOps; + readonly height: number; +} + export class QueryClient { /** Constructs a QueryClient with 0 extensions */ public static withExtensions(tmClient: TendermintClient): QueryClient; @@ -216,6 +225,44 @@ export class QueryClient { return response.value; } + public async queryRawProof(store: string, queryKey: Uint8Array): Promise { + const { key, value, height, proof, code, log } = await this.tmClient.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, + }); + + 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?`); + } + + // const subProof = checkAndParseOp(response.proof.ops[0], "ics23:iavl", key); + // const storeProof = checkAndParseOp(response.proof.ops[1], "ics23:simple", toAscii(store)); + + return { + key, + value, + height, + // need to clone this: readonly input / writeable output + proof: { + ops: [...proof.ops], + }, + }; + } + public async queryUnverified(path: string, request: Uint8Array): Promise { const response = await this.tmClient.abciQuery({ path: path, From 5400c3ac8c29dbf0e00539559207e17a4ddd9496 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 9 Feb 2021 15:04:07 +0100 Subject: [PATCH 2/5] Query verified uses queryRawProof to remove duplication --- packages/stargate/src/queries/queryclient.ts | 79 +++++++------------- 1 file changed, 26 insertions(+), 53 deletions(-) diff --git a/packages/stargate/src/queries/queryclient.ts b/packages/stargate/src/queries/queryclient.ts index 3af4bdf8..780aeda2 100644 --- a/packages/stargate/src/queries/queryclient.ts +++ b/packages/stargate/src/queries/queryclient.ts @@ -167,60 +167,32 @@ export class QueryClient { } public async queryVerified(store: string, key: Uint8Array): Promise { - const response = await this.tmClient.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: key, - prove: true, - }); + const response = await this.queryRawProof(store, key); - if (response.code) { - throw new Error(`Query failed with (${response.code}): ${response.log}`); + const subProof = checkAndParseOp(response.proof.ops[0], "ics23:iavl", key); + const storeProof = checkAndParseOp(response.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 (!response.value || response.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, key); + } 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, response.value); } - if (!arrayContentEquals(response.key, key)) { - throw new Error(`Response key ${toHex(response.key)} doesn't match query key ${toHex(key)}`); - } - - if (response.proof) { - if (response.proof.ops.length !== 2) { - throw new Error( - `Expected 2 proof ops, got ${response.proof?.ops.length ?? 0}. Are you using stargate?`, - ); - } - - const subProof = checkAndParseOp(response.proof.ops[0], "ics23:iavl", key); - const storeProof = checkAndParseOp(response.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 (!response.value || response.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, key); - } 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, response.value); - } - - // the storeproof must map it's declared value (root of subProof) to the appHash of the next block - const header = await this.getNextHeader(response.height); - verifyExistence( - storeProof.exist, - tendermintSpec, - header.appHash, - toAscii(store), - storeProof.exist.value, - ); - } + // the storeproof must map it's declared value (root of subProof) to the appHash of the next block + const header = await this.getNextHeader(response.height); + verifyExistence(storeProof.exist, tendermintSpec, header.appHash, toAscii(store), storeProof.exist.value); return response.value; } @@ -249,8 +221,9 @@ export class QueryClient { throw new Error(`Expected 2 proof ops, got ${proof?.ops.length ?? 0}. Are you using stargate?`); } - // const subProof = checkAndParseOp(response.proof.ops[0], "ics23:iavl", key); - // const storeProof = checkAndParseOp(response.proof.ops[1], "ics23:simple", toAscii(store)); + // 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, From 1f9b03a254a49e293b3de0cc39b34232f1a89c18 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 9 Feb 2021 15:30:43 +0100 Subject: [PATCH 3/5] Use an unverified query for getting the sequence number for signing --- packages/stargate/src/signingstargateclient.ts | 2 +- packages/stargate/src/stargateclient.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/stargate/src/signingstargateclient.ts b/packages/stargate/src/signingstargateclient.ts index 0340b54e..dbfcccb1 100644 --- a/packages/stargate/src/signingstargateclient.ts +++ b/packages/stargate/src/signingstargateclient.ts @@ -139,7 +139,7 @@ export class SigningStargateClient extends StargateClient { throw new Error("Failed to retrieve account from signer"); } const pubkey = encodeSecp256k1Pubkey(accountFromSigner.pubkey); - const accountFromChain = await this.getAccount(signerAddress); + const accountFromChain = await this.getAccountUnverified(signerAddress); if (!accountFromChain) { throw new Error("Account not found"); } diff --git a/packages/stargate/src/stargateclient.ts b/packages/stargate/src/stargateclient.ts index 7eebf2c6..bc26a5a7 100644 --- a/packages/stargate/src/stargateclient.ts +++ b/packages/stargate/src/stargateclient.ts @@ -149,11 +149,19 @@ export class StargateClient { return status.syncInfo.latestBlockHeight; } + // this is nice to display data to the user, but is slower public async getAccount(searchAddress: string): Promise { const account = await this.queryClient.auth.account(searchAddress); return account ? accountFromProto(account) : null; } + // if we just need to get the sequence for signing a transaction, let's make this faster + // (no need to wait a block before submitting) + public async getAccountUnverified(searchAddress: string): Promise { + const account = await this.queryClient.auth.unverified.account(searchAddress); + return account ? accountFromProto(account) : null; + } + public async getSequence(address: string): Promise { const account = await this.getAccount(address); if (account) { From 9b5fd0aacfc222d81c86702d8f9020240a5f554e Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 9 Feb 2021 15:45:43 +0100 Subject: [PATCH 4/5] Maybe wrong for v0.33, but get proper proofs for v0.34 --- packages/tendermint-rpc/src/adaptors/v0-33/responses.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/tendermint-rpc/src/adaptors/v0-33/responses.ts b/packages/tendermint-rpc/src/adaptors/v0-33/responses.ts index 79982127..e62ed00c 100644 --- a/packages/tendermint-rpc/src/adaptors/v0-33/responses.ts +++ b/packages/tendermint-rpc/src/adaptors/v0-33/responses.ts @@ -71,7 +71,7 @@ interface RpcAbciQueryResponse { readonly key: string; /** base64 encoded */ readonly value?: string; - readonly proof?: RpcQueryProof; + readonly proofOps?: RpcQueryProof; readonly height?: string; readonly index?: string; readonly code?: string; // only for errors @@ -82,7 +82,7 @@ function decodeAbciQuery(data: RpcAbciQueryResponse): responses.AbciQueryRespons return { key: fromBase64(optional(data.key, "")), value: fromBase64(optional(data.value, "")), - proof: may(decodeQueryProof, data.proof), + proof: may(decodeQueryProof, data.proofOps), height: may(Integer.parse, data.height), code: may(Integer.parse, data.code), index: may(Integer.parse, data.index), From 7ec4c265dde9bcb50039048c362647e508c75c95 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 9 Feb 2021 15:47:03 +0100 Subject: [PATCH 5/5] Use verified account query for signing (to ensure it works) --- packages/stargate/src/signingstargateclient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stargate/src/signingstargateclient.ts b/packages/stargate/src/signingstargateclient.ts index dbfcccb1..0340b54e 100644 --- a/packages/stargate/src/signingstargateclient.ts +++ b/packages/stargate/src/signingstargateclient.ts @@ -139,7 +139,7 @@ export class SigningStargateClient extends StargateClient { throw new Error("Failed to retrieve account from signer"); } const pubkey = encodeSecp256k1Pubkey(accountFromSigner.pubkey); - const accountFromChain = await this.getAccountUnverified(signerAddress); + const accountFromChain = await this.getAccount(signerAddress); if (!accountFromChain) { throw new Error("Account not found"); }