Feature/validator client upgrade (#675)

* Upgrading cosmos client to v0.25.5

* Formatting

* Renamed stakeDenom to prefix

* Added a denom field to the client

* New version needs a denom in the static methods

* ibid

* Formatting

* Renaming bech32prefix to prefix

* Adding a weird 'u' in the nymGasPrice

* Changing to use denom instead of prefix in nymGasPrice

* Alphabetizing fields

* Renaming method to something a bit more sane

* Using prefix, not denom, to build gas price

* Hardcoding an unavoidable and ugly manual conversion into the stargate helper

* denom is correct, for use in getBalance
This commit is contained in:
Dave Hrycyszyn
2021-07-06 12:50:26 +01:00
committed by GitHub
parent cb33871934
commit 427893efef
5 changed files with 157 additions and 156 deletions
+39 -37
View File
@@ -33,52 +33,52 @@ export { displayAmountToNative, nativeCoinToDisplay, printableCoin, printableBal
export { nymGasLimits, nymGasPrice }
export default class ValidatorClient {
private readonly stakeDenom: string;
urls: string[];
private readonly client: INetClient | IQueryClient
private mixNodesCache: MixnodesCache;
private gatewayCache: GatewaysCache
private readonly contractAddress: string;
// for some reason typescript thinks it's better to not be explicit about a trivial type...
// eslint-disable-next-line @typescript-eslint/no-inferrable-types
private readonly denom: string;
private failedRequests: number = 0;
private gatewayCache: GatewaysCache
private mixNodesCache: MixnodesCache;
private readonly prefix: string;
urls: string[];
private constructor(urls: string[], client: INetClient | IQueryClient, contractAddress: string, stakeDenom: string) {
private constructor(urls: string[], client: INetClient | IQueryClient, contractAddress: string, prefix: string) {
this.urls = urls;
this.client = client;
this.mixNodesCache = new MixnodesCache(client, 100);
this.gatewayCache = new GatewaysCache(client, 100);
this.contractAddress = contractAddress;
this.stakeDenom = stakeDenom;
this.prefix = prefix;
this.denom = "u" + prefix;
}
// allows also entering 'string' by itself for backwards compatibility
static async connect(contractAddress: string, mnemonic: string, urls: string | string[], stakeDenom: string): Promise<ValidatorClient> {
const validatorUrls = this.dealWithValidatorUrls(urls)
const wallet = await ValidatorClient.buildWallet(mnemonic);
static async connect(contractAddress: string, mnemonic: string, urls: string | string[], prefix: string): Promise<ValidatorClient> {
const validatorUrls = this.ensureArray(urls)
const wallet = await ValidatorClient.buildWallet(mnemonic, prefix);
// if we have more than a single validator, try to perform initial connection until we succeed or run out of options
if (validatorUrls.length > 1) {
for (let i = 0; i < validatorUrls.length; i++) {
console.log("Attempting initial connection to", validatorUrls[0])
const netClient = await NetClient.connect(wallet, validatorUrls[0], stakeDenom).catch((_) => ValidatorClient.moveArrayHeadToBack(validatorUrls))
const netClient = await NetClient.connect(wallet, validatorUrls[0], prefix).catch((_) => ValidatorClient.moveArrayHeadToBack(validatorUrls))
if (netClient !== undefined) {
return new ValidatorClient(validatorUrls, netClient, contractAddress, stakeDenom);
return new ValidatorClient(validatorUrls, netClient, contractAddress, prefix);
}
console.log("Initial connection to", validatorUrls[0], "failed")
}
} else {
const netClient = await NetClient.connect(wallet, validatorUrls[0], stakeDenom)
return new ValidatorClient(validatorUrls, netClient, contractAddress, stakeDenom);
const netClient = await NetClient.connect(wallet, validatorUrls[0], prefix)
return new ValidatorClient(validatorUrls, netClient, contractAddress, prefix);
}
throw new Error("None of the provided validators seem to be alive")
}
// allows also entering 'string' by itself for backwards compatibility
static async connectForQuery(contractAddress: string, urls: string | string[], stakeDenom: string): Promise<ValidatorClient> {
const validatorUrls = this.dealWithValidatorUrls(urls)
static async connectForQuery(contractAddress: string, urls: string | string[], prefix: string): Promise<ValidatorClient> {
const validatorUrls = this.ensureArray(urls)
// if we have more than a single validator, try to perform initial connection until we succeed or run out of options
if (validatorUrls.length > 1) {
@@ -86,19 +86,19 @@ export default class ValidatorClient {
console.log("Attempting initial connection to", validatorUrls[0])
const queryClient = await QueryClient.connect(validatorUrls[0]).catch((_) => ValidatorClient.moveArrayHeadToBack(validatorUrls))
if (queryClient !== undefined) {
return new ValidatorClient(validatorUrls, queryClient, contractAddress, stakeDenom)
return new ValidatorClient(validatorUrls, queryClient, contractAddress, prefix)
}
console.log("Initial connection to", validatorUrls[0], "failed")
}
} else {
const queryClient = await QueryClient.connect(validatorUrls[0])
return new ValidatorClient(validatorUrls, queryClient, contractAddress, stakeDenom)
return new ValidatorClient(validatorUrls, queryClient, contractAddress, prefix)
}
throw new Error("None of the provided validators seem to be alive")
}
private static dealWithValidatorUrls(urls: string | string[]): string[] {
private static ensureArray(urls: string | string[]): string[] {
let validatorsUrls: string[] = []
if (typeof urls === "string") {
validatorsUrls = [urls]
@@ -134,7 +134,7 @@ export default class ValidatorClient {
ValidatorClient.moveArrayHeadToBack(this.urls)
}
// and change validator to the front one and rethrow the error
return await this.changeValidator(this.urls[0]).then(() => {throw error})
return await this.changeValidator(this.urls[0]).then(() => { throw error })
} else {
// rethrow the error
throw error
@@ -157,7 +157,7 @@ export default class ValidatorClient {
// It is responsibility of the caller to ensure the input array is non-empty
private static moveArrayHeadToBack<T>(arr: T[]) {
const head = <T> arr.shift()
const head = <T>arr.shift()
arr.push(head)
}
@@ -200,18 +200,19 @@ export default class ValidatorClient {
* @param mnemonic A mnemonic from which to generate a public/private keypair.
* @returns the address for this client wallet
*/
static async mnemonicToAddress(mnemonic: string): Promise<string> {
const wallet = await ValidatorClient.buildWallet(mnemonic);
const [{address}] = await wallet.getAccounts()
static async mnemonicToAddress(mnemonic: string, prefix: string): Promise<string> {
const wallet = await ValidatorClient.buildWallet(mnemonic, prefix);
const [{ address }] = await wallet.getAccounts()
return address
}
static async buildWallet(mnemonic: string): Promise<DirectSecp256k1HdWallet> {
return DirectSecp256k1HdWallet.fromMnemonic(mnemonic, undefined, "hal");
static async buildWallet(mnemonic: string, prefix: string): Promise<DirectSecp256k1HdWallet> {
const signerOptions = { prefix: prefix };
return DirectSecp256k1HdWallet.fromMnemonic(mnemonic, signerOptions);
}
getBalance(address: string): Promise<Coin | null> {
return this.client.getBalance(address, this.stakeDenom).catch((err) => this.handleRequestFailure(err));
return this.client.getBalance(address, this.denom).catch((err) => this.handleRequestFailure(err));
}
async getStateParams(): Promise<StateParams> {
@@ -248,7 +249,7 @@ export default class ValidatorClient {
async minimumMixnodeBond(): Promise<Coin> {
const stateParams = await this.getStateParams()
// we trust the contract to return a valid number
return coin(Number(stateParams.minimum_mixnode_bond), this.stakeDenom)
return coin(Number(stateParams.minimum_mixnode_bond), this.prefix)
}
/**
@@ -394,7 +395,7 @@ export default class ValidatorClient {
async minimumGatewayBond(): Promise<Coin> {
const stateParams = await this.getStateParams()
// we trust the contract to return a valid number
return coin(Number(stateParams.minimum_gateway_bond), this.stakeDenom)
return coin(Number(stateParams.minimum_gateway_bond), this.prefix)
}
/**
@@ -402,7 +403,7 @@ export default class ValidatorClient {
*/
async bondGateway(gateway: Gateway, bond: Coin): Promise<ExecuteResult> {
if (this.client instanceof NetClient) {
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, {bond_gateway: {gateway: gateway}}, "adding gateway", [bond]).catch((err) => this.handleRequestFailure(err));
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, { bond_gateway: { gateway: gateway } }, "adding gateway", [bond]).catch((err) => this.handleRequestFailure(err));
console.log(`account ${this.client.clientAddress} added gateway with ${gateway.host}`);
return result;
} else {
@@ -415,7 +416,7 @@ export default class ValidatorClient {
*/
async unbondGateway(): Promise<ExecuteResult> {
if (this.client instanceof NetClient) {
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, {unbond_gateway: {}}).catch((err) => this.handleRequestFailure(err))
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, { unbond_gateway: {} }).catch((err) => this.handleRequestFailure(err))
console.log(`account ${this.client.clientAddress} unbonded gateway`);
return result;
} else {
@@ -425,7 +426,7 @@ export default class ValidatorClient {
async updateStateParams(newParams: StateParams): Promise<ExecuteResult> {
if (this.client instanceof NetClient) {
return await this.client.executeContract(this.client.clientAddress, this.contractAddress, {update_state_params: newParams}, "updating contract state").catch((err) => this.handleRequestFailure(err));
return await this.client.executeContract(this.client.clientAddress, this.contractAddress, { update_state_params: newParams }, "updating contract state").catch((err) => this.handleRequestFailure(err));
} else {
throw new Error("Tried to update state params with a query client")
}
@@ -443,7 +444,7 @@ export default class ValidatorClient {
let delegations: Delegation[] = [];
let response: PagedMixDelegationsResponse
let next: string | undefined = undefined;
for (;;) {
for (; ;) {
response = await this.client.getMixDelegations(this.contractAddress, mixIdentity, limit, next)
delegations = delegations.concat(response.delegations)
next = response.start_next_after
@@ -478,7 +479,7 @@ export default class ValidatorClient {
let delegations: Delegation[] = [];
let response: PagedGatewayDelegationsResponse
let next: string | undefined = undefined;
for (;;) {
for (; ;) {
response = await this.client.getGatewayDelegations(this.contractAddress, gatewayIdentity, limit, next)
delegations = delegations.concat(response.delegations)
next = response.start_next_after
@@ -531,7 +532,8 @@ export default class ValidatorClient {
const encoded = data.map(req => makeBankMsgSend(req.senderAddress, req.recipientAddress, req.transferAmount));
// the function to calculate fee for a single entry is not exposed...
const table = buildFeeTable(nymGasPrice(this.stakeDenom), {sendMultiple: nymGasLimits.send * data.length}, {sendMultiple: nymGasLimits.send * data.length})
console.log(`this.denom is ${this.denom}`);
const table = buildFeeTable(nymGasPrice(this.prefix), { sendMultiple: nymGasLimits.send * data.length }, { sendMultiple: nymGasLimits.send * data.length })
const fee = table.sendMultiple
const result = await this.client.signAndBroadcast(senderAddress, encoded, fee, memo)
if (isBroadcastTxFailure(result)) {
+8 -8
View File
@@ -16,7 +16,7 @@ import { ExecuteResult, InstantiateOptions, InstantiateResult, MigrateResult, Up
export interface INetClient {
clientAddress: string;
getBalance(address: string, stakeDenom: string): Promise<Coin | null>;
getBalance(address: string, denom: string): Promise<Coin | null>;
getMixNodes(contractAddress: string, limit: number, start_after?: string): Promise<PagedMixnodeResponse>;
getGateways(contractAddress: string, limit: number, start_after?: string): Promise<PagedGatewayResponse>;
getMixDelegations(contractAddress: string, mixIdentity: string, limit: number, start_after?: string): Promise<PagedMixDelegationsResponse>
@@ -57,10 +57,10 @@ export default class NetClient implements INetClient {
this.signerOptions = signerOptions;
}
public static async connect(wallet: DirectSecp256k1HdWallet, url: string, stakeDenom: string): Promise<INetClient> {
public static async connect(wallet: DirectSecp256k1HdWallet, url: string, prefix: string): Promise<INetClient> {
const [{ address }] = await wallet.getAccounts();
const signerOptions: SigningCosmWasmClientOptions = {
gasPrice: nymGasPrice(stakeDenom),
gasPrice: nymGasPrice(prefix),
gasLimits: nymGasLimits,
};
const client = await SigningCosmWasmClient.connectWithSigner(url, wallet, signerOptions);
@@ -91,7 +91,7 @@ export default class NetClient implements INetClient {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, { get_mix_delegations: { mix_identity: mixIdentity, limit } });
} else {
return this.cosmClient.queryContractSmart(contractAddress, { get_mix_delegations: { mix_identity: mixIdentity, limit, start_after } });
return this.cosmClient.queryContractSmart(contractAddress, { get_mix_delegations: { mix_identity: mixIdentity, limit, start_after } });
}
}
@@ -103,7 +103,7 @@ export default class NetClient implements INetClient {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, { get_gateway_delegations: { gateway_identity: gatewayIdentity, limit } });
} else {
return this.cosmClient.queryContractSmart(contractAddress, { get_gateway_delegations: { gateway_identity: gatewayIdentity, limit, start_after } });
return this.cosmClient.queryContractSmart(contractAddress, { get_gateway_delegations: { gateway_identity: gatewayIdentity, limit, start_after } });
}
}
@@ -119,12 +119,12 @@ export default class NetClient implements INetClient {
return this.cosmClient.queryContractSmart(contractAddress, { owns_gateway: { address } });
}
public getBalance(address: string, stakeDenom: string): Promise<Coin | null> {
return this.cosmClient.getBalance(address, stakeDenom);
public getBalance(address: string, denom: string): Promise<Coin | null> {
return this.cosmClient.getBalance(address, denom);
}
public getStateParams(contractAddress: string): Promise<StateParams> {
return this.cosmClient.queryContractSmart(contractAddress, { state_params: { } });
return this.cosmClient.queryContractSmart(contractAddress, { state_params: {} });
}
public executeContract(senderAddress: string, contractAddress: string, handleMsg: Record<string, unknown>, memo?: string, transferAmount?: readonly Coin[]): Promise<ExecuteResult> {
+5 -3
View File
@@ -7,7 +7,7 @@ export interface Options {
httpUrl: string;
networkId: string;
gasPrice: number;
bech32prefix: string;
prefix: string;
}
export const nymGasLimits: GasLimits<CosmWasmFeeTable> = {
@@ -19,13 +19,15 @@ export const nymGasLimits: GasLimits<CosmWasmFeeTable> = {
changeAdmin: 80_000,
};
export const nymGasPrice: (stakeDenom: string) => GasPrice = (stakeDenom: string) => GasPrice.fromString(`0.025${stakeDenom}`);
export function nymGasPrice(prefix: string): GasPrice {
return GasPrice.fromString(`0.025u${prefix}`); // TODO: ideally this ugly conversion shouldn't be hardcoded here.
};
export const defaultOptions: Options = {
httpUrl: "http://localhost:26657",
networkId: "nymnet",
gasPrice: 0.025,
bech32prefix: "hal",
prefix: "hal",
};
export const downloadWasm = async (url: string): Promise<Uint8Array> => {