From 01506951e712dd6a81b96ff44cbd4151fab7b703 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 13 Aug 2020 15:18:43 +0200 Subject: [PATCH] Create query client --- packages/stargate/src/queryclient.ts | 183 ++++++++++++++++++++ packages/stargate/src/stargateclient.ts | 48 +---- packages/stargate/types/queryclient.d.ts | 109 ++++++++++++ packages/stargate/types/stargateclient.d.ts | 3 +- 4 files changed, 300 insertions(+), 43 deletions(-) create mode 100644 packages/stargate/src/queryclient.ts create mode 100644 packages/stargate/types/queryclient.d.ts diff --git a/packages/stargate/src/queryclient.ts b/packages/stargate/src/queryclient.ts new file mode 100644 index 00000000..fedc9a0f --- /dev/null +++ b/packages/stargate/src/queryclient.ts @@ -0,0 +1,183 @@ +/* eslint-disable no-dupe-class-members, @typescript-eslint/ban-types, @typescript-eslint/naming-convention */ +import { toHex } from "@cosmjs/encoding"; +import { Client as TendermintClient } from "@cosmjs/tendermint-rpc"; +import { arrayContentEquals, assert, isNonNullObject } from "@cosmjs/utils"; + +type QueryExtensionSetup

= (base: QueryClient) => P; + +export class QueryClient { + /** Constructs a QueryClient with 0 extensions */ + public static withExtensions(tmClient: TendermintClient): QueryClient; + + /** Constructs a QueryClient with 1 extension */ + public static withExtensions( + tmClient: TendermintClient, + setupExtensionA: QueryExtensionSetup, + ): QueryClient & A; + + /** Constructs a QueryClient with 2 extensions */ + public static withExtensions( + tmClient: TendermintClient, + setupExtensionA: QueryExtensionSetup, + setupExtensionB: QueryExtensionSetup, + ): QueryClient & A & B; + + /** Constructs a QueryClient with 3 extensions */ + public static withExtensions( + tmClient: TendermintClient, + setupExtensionA: QueryExtensionSetup, + setupExtensionB: QueryExtensionSetup, + setupExtensionC: QueryExtensionSetup, + ): QueryClient & A & B & C; + + /** Constructs a QueryClient with 4 extensions */ + public static withExtensions( + tmClient: TendermintClient, + setupExtensionA: QueryExtensionSetup, + setupExtensionB: QueryExtensionSetup, + setupExtensionC: QueryExtensionSetup, + setupExtensionD: QueryExtensionSetup, + ): QueryClient & A & B & C & D; + + /** Constructs a QueryClient with 5 extensions */ + public static withExtensions< + A extends object, + B extends object, + C extends object, + D extends object, + E extends object + >( + tmClient: TendermintClient, + setupExtensionA: QueryExtensionSetup, + setupExtensionB: QueryExtensionSetup, + setupExtensionC: QueryExtensionSetup, + setupExtensionD: QueryExtensionSetup, + setupExtensionE: QueryExtensionSetup, + ): QueryClient & A & B & C & D & E; + + /** Constructs a QueryClient with 6 extensions */ + public static withExtensions< + A extends object, + B extends object, + C extends object, + D extends object, + E extends object, + F extends object + >( + tmClient: TendermintClient, + setupExtensionA: QueryExtensionSetup, + setupExtensionB: QueryExtensionSetup, + setupExtensionC: QueryExtensionSetup, + setupExtensionD: QueryExtensionSetup, + setupExtensionE: QueryExtensionSetup, + setupExtensionF: QueryExtensionSetup, + ): QueryClient & A & B & C & D & E & F; + + /** Constructs a QueryClient with 7 extensions */ + public static withExtensions< + A extends object, + B extends object, + C extends object, + D extends object, + E extends object, + F extends object, + G extends object + >( + tmClient: TendermintClient, + setupExtensionA: QueryExtensionSetup, + setupExtensionB: QueryExtensionSetup, + setupExtensionC: QueryExtensionSetup, + setupExtensionD: QueryExtensionSetup, + setupExtensionE: QueryExtensionSetup, + setupExtensionF: QueryExtensionSetup, + setupExtensionG: QueryExtensionSetup, + ): QueryClient & A & B & C & D & E & F & G; + + /** Constructs a QueryClient with 8 extensions */ + public static withExtensions< + A extends object, + B extends object, + C extends object, + D extends object, + E extends object, + F extends object, + G extends object, + H extends object + >( + tmClient: TendermintClient, + setupExtensionA: QueryExtensionSetup, + setupExtensionB: QueryExtensionSetup, + setupExtensionC: QueryExtensionSetup, + setupExtensionD: QueryExtensionSetup, + setupExtensionE: QueryExtensionSetup, + setupExtensionF: QueryExtensionSetup, + setupExtensionG: QueryExtensionSetup, + setupExtensionH: QueryExtensionSetup, + ): QueryClient & A & B & C & D & E & F & G & H; + + public static withExtensions( + tmClient: TendermintClient, + ...extensionSetups: Array> + ): any { + const client = new QueryClient(tmClient); + const extensions = extensionSetups.map((setupExtension) => setupExtension(client)); + for (const extension of extensions) { + assert(isNonNullObject(extension), `Extension must be a non-null object`); + for (const [moduleKey, moduleValue] of Object.entries(extension)) { + assert( + isNonNullObject(moduleValue), + `Module must be a non-null object. Found type ${typeof moduleValue} for module "${moduleKey}".`, + ); + const current = (client as any)[moduleKey] || {}; + (client as any)[moduleKey] = { + ...current, + ...moduleValue, + }; + } + } + return client; + } + + private readonly tmClient: TendermintClient; + + public constructor(tmClient: TendermintClient) { + this.tmClient = tmClient; + } + + 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, + }); + + if (response.code) { + throw new Error(`Query failed with (${response.code}): ${response.log}`); + } + + if (!arrayContentEquals(response.key, key)) { + throw new Error(`Response key ${toHex(response.key)} doesn't match query key ${toHex(key)}`); + } + + // TODO: implement proof verification + // https://github.com/CosmWasm/cosmjs/issues/347 + + return response.value; + } + + public async queryUnverified(path: string, request: Uint8Array): Promise { + const response = await this.tmClient.abciQuery({ + path: path, + data: request, + prove: false, + }); + + if (response.code) { + throw new Error(`Query failed with (${response.code}): ${response.log}`); + } + + return response.value; + } +} diff --git a/packages/stargate/src/stargateclient.ts b/packages/stargate/src/stargateclient.ts index 75b18ec9..566c0602 100644 --- a/packages/stargate/src/stargateclient.ts +++ b/packages/stargate/src/stargateclient.ts @@ -13,10 +13,11 @@ import { import { Uint53, Uint64 } from "@cosmjs/math"; import { decodeAny } from "@cosmjs/proto-signing"; import { broadcastTxCommitSuccess, Client as TendermintClient, QueryString } from "@cosmjs/tendermint-rpc"; -import { arrayContentEquals, assert, assertDefined } from "@cosmjs/utils"; +import { assert, assertDefined } from "@cosmjs/utils"; import Long from "long"; import { cosmos } from "./generated/codecimpl"; +import { QueryClient } from "./queryclient"; /** A transaction that is indexed as part of the transaction history */ export interface IndexedTx { @@ -114,6 +115,7 @@ export interface PrivateStargateClient { export class StargateClient { private readonly tmClient: TendermintClient; + private readonly queryClient: QueryClient; private chainId: string | undefined; public static async connect(endpoint: string): Promise { @@ -123,6 +125,7 @@ export class StargateClient { private constructor(tmClient: TendermintClient) { this.tmClient = tmClient; + this.queryClient = QueryClient.withExtensions(tmClient); } public async getChainId(): Promise { @@ -145,7 +148,7 @@ export class StargateClient { const { prefix, data: binAddress } = Bech32.decode(searchAddress); // https://github.com/cosmos/cosmos-sdk/blob/8cab43c8120fec5200c3459cbf4a92017bb6f287/x/auth/types/keys.go#L29-L32 const accountKey = Uint8Array.from([0x01, ...binAddress]); - const responseData = await this.queryVerified("acc", accountKey); + const responseData = await this.queryClient.queryVerified("acc", accountKey); if (responseData.length === 0) return null; @@ -198,7 +201,7 @@ export class StargateClient { const binAddress = Bech32.decode(address).data; const bankKey = Uint8Array.from([...toAscii("balances"), ...binAddress, ...toAscii(searchDenom)]); - const responseData = await this.queryVerified("bank", bankKey); + const responseData = await this.queryClient.queryVerified("bank", bankKey); const { amount, denom } = cosmos.Coin.decode(responseData); if (denom === "") { return null; @@ -221,7 +224,7 @@ export class StargateClient { const request = cosmos.bank.QueryAllBalancesRequest.encode({ address: Bech32.decode(address).data, }).finish(); - const responseData = await this.queryUnverified(path, request); + const responseData = await this.queryClient.queryUnverified(path, request); const response = cosmos.bank.QueryAllBalancesResponse.decode(responseData); return response.balances.map(coinFromProto); } @@ -280,43 +283,6 @@ export class StargateClient { }; } - private 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, - }); - - if (response.code) { - throw new Error(`Query failed with (${response.code}): ${response.log}`); - } - - if (!arrayContentEquals(response.key, key)) { - throw new Error(`Response key ${toHex(response.key)} doesn't match query key ${toHex(key)}`); - } - - // TODO: implement proof verification - // https://github.com/CosmWasm/cosmjs/issues/347 - - return response.value; - } - - private async queryUnverified(path: string, request: Uint8Array): Promise { - const response = await this.tmClient.abciQuery({ - path: path, - data: request, - prove: false, - }); - - if (response.code) { - throw new Error(`Query failed with (${response.code}): ${response.log}`); - } - - return response.value; - } - private async txsQuery(query: string): Promise { const params = { query: query as QueryString, diff --git a/packages/stargate/types/queryclient.d.ts b/packages/stargate/types/queryclient.d.ts new file mode 100644 index 00000000..c00db57b --- /dev/null +++ b/packages/stargate/types/queryclient.d.ts @@ -0,0 +1,109 @@ +import { Client as TendermintClient } from "@cosmjs/tendermint-rpc"; +declare type QueryExtensionSetup

= (base: QueryClient) => P; +export declare class QueryClient { + /** Constructs a QueryClient with 0 extensions */ + static withExtensions(tmClient: TendermintClient): QueryClient; + /** Constructs a QueryClient with 1 extension */ + static withExtensions( + tmClient: TendermintClient, + setupExtensionA: QueryExtensionSetup, + ): QueryClient & A; + /** Constructs a QueryClient with 2 extensions */ + static withExtensions( + tmClient: TendermintClient, + setupExtensionA: QueryExtensionSetup, + setupExtensionB: QueryExtensionSetup, + ): QueryClient & A & B; + /** Constructs a QueryClient with 3 extensions */ + static withExtensions( + tmClient: TendermintClient, + setupExtensionA: QueryExtensionSetup, + setupExtensionB: QueryExtensionSetup, + setupExtensionC: QueryExtensionSetup, + ): QueryClient & A & B & C; + /** Constructs a QueryClient with 4 extensions */ + static withExtensions( + tmClient: TendermintClient, + setupExtensionA: QueryExtensionSetup, + setupExtensionB: QueryExtensionSetup, + setupExtensionC: QueryExtensionSetup, + setupExtensionD: QueryExtensionSetup, + ): QueryClient & A & B & C & D; + /** Constructs a QueryClient with 5 extensions */ + static withExtensions< + A extends object, + B extends object, + C extends object, + D extends object, + E extends object + >( + tmClient: TendermintClient, + setupExtensionA: QueryExtensionSetup, + setupExtensionB: QueryExtensionSetup, + setupExtensionC: QueryExtensionSetup, + setupExtensionD: QueryExtensionSetup, + setupExtensionE: QueryExtensionSetup, + ): QueryClient & A & B & C & D & E; + /** Constructs a QueryClient with 6 extensions */ + static withExtensions< + A extends object, + B extends object, + C extends object, + D extends object, + E extends object, + F extends object + >( + tmClient: TendermintClient, + setupExtensionA: QueryExtensionSetup, + setupExtensionB: QueryExtensionSetup, + setupExtensionC: QueryExtensionSetup, + setupExtensionD: QueryExtensionSetup, + setupExtensionE: QueryExtensionSetup, + setupExtensionF: QueryExtensionSetup, + ): QueryClient & A & B & C & D & E & F; + /** Constructs a QueryClient with 7 extensions */ + static withExtensions< + A extends object, + B extends object, + C extends object, + D extends object, + E extends object, + F extends object, + G extends object + >( + tmClient: TendermintClient, + setupExtensionA: QueryExtensionSetup, + setupExtensionB: QueryExtensionSetup, + setupExtensionC: QueryExtensionSetup, + setupExtensionD: QueryExtensionSetup, + setupExtensionE: QueryExtensionSetup, + setupExtensionF: QueryExtensionSetup, + setupExtensionG: QueryExtensionSetup, + ): QueryClient & A & B & C & D & E & F & G; + /** Constructs a QueryClient with 8 extensions */ + static withExtensions< + A extends object, + B extends object, + C extends object, + D extends object, + E extends object, + F extends object, + G extends object, + H extends object + >( + tmClient: TendermintClient, + setupExtensionA: QueryExtensionSetup, + setupExtensionB: QueryExtensionSetup, + setupExtensionC: QueryExtensionSetup, + setupExtensionD: QueryExtensionSetup, + setupExtensionE: QueryExtensionSetup, + setupExtensionF: QueryExtensionSetup, + setupExtensionG: QueryExtensionSetup, + setupExtensionH: QueryExtensionSetup, + ): QueryClient & A & B & C & D & E & F & G & H; + private readonly tmClient; + constructor(tmClient: TendermintClient); + queryVerified(store: string, key: Uint8Array): Promise; + queryUnverified(path: string, request: Uint8Array): Promise; +} +export {}; diff --git a/packages/stargate/types/stargateclient.d.ts b/packages/stargate/types/stargateclient.d.ts index d641352f..b8c8e460 100644 --- a/packages/stargate/types/stargateclient.d.ts +++ b/packages/stargate/types/stargateclient.d.ts @@ -49,6 +49,7 @@ export interface PrivateStargateClient { } export declare class StargateClient { private readonly tmClient; + private readonly queryClient; private chainId; static connect(endpoint: string): Promise; private constructor(); @@ -68,7 +69,5 @@ export declare class StargateClient { searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise; disconnect(): void; broadcastTx(tx: Uint8Array): Promise; - private queryVerified; - private queryUnverified; private txsQuery; }