Create query client

This commit is contained in:
Simon Warta 2020-08-13 15:18:43 +02:00
parent 0dc22c2525
commit 01506951e7
4 changed files with 300 additions and 43 deletions

View File

@ -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<P> = (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<A extends object>(
tmClient: TendermintClient,
setupExtensionA: QueryExtensionSetup<A>,
): QueryClient & A;
/** Constructs a QueryClient with 2 extensions */
public static withExtensions<A extends object, B extends object>(
tmClient: TendermintClient,
setupExtensionA: QueryExtensionSetup<A>,
setupExtensionB: QueryExtensionSetup<B>,
): QueryClient & A & B;
/** Constructs a QueryClient with 3 extensions */
public static withExtensions<A extends object, B extends object, C extends object>(
tmClient: TendermintClient,
setupExtensionA: QueryExtensionSetup<A>,
setupExtensionB: QueryExtensionSetup<B>,
setupExtensionC: QueryExtensionSetup<C>,
): QueryClient & A & B & C;
/** Constructs a QueryClient with 4 extensions */
public static withExtensions<A extends object, B extends object, C extends object, D extends object>(
tmClient: TendermintClient,
setupExtensionA: QueryExtensionSetup<A>,
setupExtensionB: QueryExtensionSetup<B>,
setupExtensionC: QueryExtensionSetup<C>,
setupExtensionD: QueryExtensionSetup<D>,
): 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<A>,
setupExtensionB: QueryExtensionSetup<B>,
setupExtensionC: QueryExtensionSetup<C>,
setupExtensionD: QueryExtensionSetup<D>,
setupExtensionE: QueryExtensionSetup<E>,
): 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<A>,
setupExtensionB: QueryExtensionSetup<B>,
setupExtensionC: QueryExtensionSetup<C>,
setupExtensionD: QueryExtensionSetup<D>,
setupExtensionE: QueryExtensionSetup<E>,
setupExtensionF: QueryExtensionSetup<F>,
): 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<A>,
setupExtensionB: QueryExtensionSetup<B>,
setupExtensionC: QueryExtensionSetup<C>,
setupExtensionD: QueryExtensionSetup<D>,
setupExtensionE: QueryExtensionSetup<E>,
setupExtensionF: QueryExtensionSetup<F>,
setupExtensionG: QueryExtensionSetup<G>,
): 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<A>,
setupExtensionB: QueryExtensionSetup<B>,
setupExtensionC: QueryExtensionSetup<C>,
setupExtensionD: QueryExtensionSetup<D>,
setupExtensionE: QueryExtensionSetup<E>,
setupExtensionF: QueryExtensionSetup<F>,
setupExtensionG: QueryExtensionSetup<G>,
setupExtensionH: QueryExtensionSetup<H>,
): QueryClient & A & B & C & D & E & F & G & H;
public static withExtensions(
tmClient: TendermintClient,
...extensionSetups: Array<QueryExtensionSetup<object>>
): 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<Uint8Array> {
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<Uint8Array> {
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;
}
}

View File

@ -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<StargateClient> {
@ -123,6 +125,7 @@ export class StargateClient {
private constructor(tmClient: TendermintClient) {
this.tmClient = tmClient;
this.queryClient = QueryClient.withExtensions(tmClient);
}
public async getChainId(): Promise<string> {
@ -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<Uint8Array> {
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<Uint8Array> {
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<readonly IndexedTx[]> {
const params = {
query: query as QueryString,

109
packages/stargate/types/queryclient.d.ts vendored Normal file
View File

@ -0,0 +1,109 @@
import { Client as TendermintClient } from "@cosmjs/tendermint-rpc";
declare type QueryExtensionSetup<P> = (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<A extends object>(
tmClient: TendermintClient,
setupExtensionA: QueryExtensionSetup<A>,
): QueryClient & A;
/** Constructs a QueryClient with 2 extensions */
static withExtensions<A extends object, B extends object>(
tmClient: TendermintClient,
setupExtensionA: QueryExtensionSetup<A>,
setupExtensionB: QueryExtensionSetup<B>,
): QueryClient & A & B;
/** Constructs a QueryClient with 3 extensions */
static withExtensions<A extends object, B extends object, C extends object>(
tmClient: TendermintClient,
setupExtensionA: QueryExtensionSetup<A>,
setupExtensionB: QueryExtensionSetup<B>,
setupExtensionC: QueryExtensionSetup<C>,
): QueryClient & A & B & C;
/** Constructs a QueryClient with 4 extensions */
static withExtensions<A extends object, B extends object, C extends object, D extends object>(
tmClient: TendermintClient,
setupExtensionA: QueryExtensionSetup<A>,
setupExtensionB: QueryExtensionSetup<B>,
setupExtensionC: QueryExtensionSetup<C>,
setupExtensionD: QueryExtensionSetup<D>,
): 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<A>,
setupExtensionB: QueryExtensionSetup<B>,
setupExtensionC: QueryExtensionSetup<C>,
setupExtensionD: QueryExtensionSetup<D>,
setupExtensionE: QueryExtensionSetup<E>,
): 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<A>,
setupExtensionB: QueryExtensionSetup<B>,
setupExtensionC: QueryExtensionSetup<C>,
setupExtensionD: QueryExtensionSetup<D>,
setupExtensionE: QueryExtensionSetup<E>,
setupExtensionF: QueryExtensionSetup<F>,
): 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<A>,
setupExtensionB: QueryExtensionSetup<B>,
setupExtensionC: QueryExtensionSetup<C>,
setupExtensionD: QueryExtensionSetup<D>,
setupExtensionE: QueryExtensionSetup<E>,
setupExtensionF: QueryExtensionSetup<F>,
setupExtensionG: QueryExtensionSetup<G>,
): 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<A>,
setupExtensionB: QueryExtensionSetup<B>,
setupExtensionC: QueryExtensionSetup<C>,
setupExtensionD: QueryExtensionSetup<D>,
setupExtensionE: QueryExtensionSetup<E>,
setupExtensionF: QueryExtensionSetup<F>,
setupExtensionG: QueryExtensionSetup<G>,
setupExtensionH: QueryExtensionSetup<H>,
): QueryClient & A & B & C & D & E & F & G & H;
private readonly tmClient;
constructor(tmClient: TendermintClient);
queryVerified(store: string, key: Uint8Array): Promise<Uint8Array>;
queryUnverified(path: string, request: Uint8Array): Promise<Uint8Array>;
}
export {};

View File

@ -49,6 +49,7 @@ export interface PrivateStargateClient {
}
export declare class StargateClient {
private readonly tmClient;
private readonly queryClient;
private chainId;
static connect(endpoint: string): Promise<StargateClient>;
private constructor();
@ -68,7 +69,5 @@ export declare class StargateClient {
searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise<readonly IndexedTx[]>;
disconnect(): void;
broadcastTx(tx: Uint8Array): Promise<BroadcastTxResponse>;
private queryVerified;
private queryUnverified;
private txsQuery;
}