diff --git a/packages/stargate/src/queries/queryclient.ts b/packages/stargate/src/queries/queryclient.ts
index 31cf9ef1..780aeda2 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;
@@ -158,62 +167,73 @@ export class QueryClient {
}
public async queryVerified(store: string, key: Uint8Array): Promise {
- const response = await this.tmClient.abciQuery({
+ const response = await this.queryRawProof(store, key);
+
+ 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);
+
+ 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: key,
+ data: queryKey,
prove: true,
});
- if (response.code) {
- throw new Error(`Query failed with (${response.code}): ${response.log}`);
+ if (code) {
+ throw new Error(`Query failed with (${code}): ${log}`);
}
- if (!arrayContentEquals(response.key, key)) {
- throw new Error(`Response key ${toHex(response.key)} doesn't match query key ${toHex(key)}`);
+ if (!arrayContentEquals(queryKey, key)) {
+ throw new Error(`Response key ${toHex(key)} doesn't match query key ${toHex(queryKey)}`);
}
- 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,
- );
+ 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?`);
}
- return response.value;
+ // 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,
+ value,
+ height,
+ // need to clone this: readonly input / writeable output
+ proof: {
+ ops: [...proof.ops],
+ },
+ };
}
public async queryUnverified(path: string, request: Uint8Array): Promise {
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) {
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),