signpost - unfinished just parking changes

This commit is contained in:
Aid Thompson
2021-11-22 15:08:46 +00:00
parent cb549dfe25
commit b8494eb83a
3 changed files with 303 additions and 64 deletions
+48 -44
View File
@@ -1,7 +1,7 @@
import {MixNodeBond, PagedMixnodeResponse} from "../types";
import { INetClient } from "../net-client"
import {IQueryClient} from "../query-client";
import {VALIDATOR_API_MIXNODES, VALIDATOR_API_PORT} from "../index";
import { MixNodeBond, PagedMixnodeResponse } from "../types";
import { INetClient } from "../net-client";
import { IQueryClient } from "../query-client";
import { VALIDATOR_API_MIXNODES, VALIDATOR_API_PORT } from "../index";
import axios from "axios";
export { MixnodesCache };
@@ -13,48 +13,52 @@ export { MixnodesCache };
* available for querying.
* */
export default class MixnodesCache {
mixNodes: MixNodeBond[]
client: INetClient | IQueryClient
perPage: number
mixNodes: MixNodeBond[];
client: INetClient | IQueryClient;
perPage: number;
constructor(client: INetClient | IQueryClient, perPage: number) {
this.client = client;
this.mixNodes = [];
this.perPage = perPage;
constructor(client: INetClient | IQueryClient, perPage: number) {
this.client = client;
this.mixNodes = [];
this.perPage = perPage;
}
/// Makes repeated requests to assemble a full list of nodes.
/// Requests continue to be make as long as `shouldMakeAnotherRequest()`
// returns true.
async refreshMixNodes(contractAddress: string): Promise<MixNodeBond[]> {
let newMixnodes: MixNodeBond[] = [];
let response: PagedMixnodeResponse;
let next: string | undefined = undefined;
for (;;) {
response = await this.client.getMixNodes(
contractAddress,
this.perPage,
next
);
newMixnodes = newMixnodes.concat(response.nodes);
next = response.start_next_after;
// if `start_next_after` is not set, we're done
if (!next) {
break;
}
}
/// Makes repeated requests to assemble a full list of nodes.
/// Requests continue to be make as long as `shouldMakeAnotherRequest()`
// returns true.
async refreshMixNodes(contractAddress: string): Promise<MixNodeBond[]> {
let newMixnodes: MixNodeBond[] = [];
let response: PagedMixnodeResponse;
let next: string | undefined = undefined;
for (;;) {
response = await this.client.getMixNodes(contractAddress, this.perPage, next);
newMixnodes = newMixnodes.concat(response.nodes)
next = response.start_next_after;
// if `start_next_after` is not set, we're done
if (!next) {
break
}
}
this.mixNodes = newMixnodes;
return this.mixNodes;
}
this.mixNodes = newMixnodes
return this.mixNodes;
/// Makes requests to assemble a full list of mixnodes from validator-api
async refreshValidatorAPIMixNodes(urls: string[]): Promise<MixNodeBond[]> {
for (const url of urls) {
const validator_api_url = new URL(url);
validator_api_url.port = VALIDATOR_API_PORT;
validator_api_url.pathname += VALIDATOR_API_MIXNODES;
const response = await axios.get(validator_api_url.toString());
if (response.status == 200) {
return response.data;
}
}
/// Makes requests to assemble a full list of mixnodes from validator-api
async refreshValidatorAPIMixNodes(urls: string[]): Promise<MixNodeBond[]> {
for (const url of urls) {
const validator_api_url = new URL(url);
validator_api_url.port = VALIDATOR_API_PORT;
validator_api_url.pathname += VALIDATOR_API_MIXNODES;
const response = await axios.get(validator_api_url.toString());
if (response.status == 200) {
return response.data;
}
}
throw new Error("None of the provided validators seem to be alive")
}
}
throw new Error("None of the provided validators seem to be alive");
}
}
+189 -14
View File
@@ -4,15 +4,28 @@ import inquirer from "inquirer";
const VALIDATOR_URLS: string[] = [
"https://testnet-milhon-validator1.nymtech.net",
"https://testnet-milhon-validator2.nymtech.net",
// "https://testnet-milhon-validator2.nymtech.net", // <-- val 2 doesnt work apparently.
];
const DENOM = "punk";
const MOCK_MNEMONIC =
"vault risk throw flat garlic pretty clay senior birth correct panic floor around pen horror mail entry arrest zoo devote message evoke street total";
// ^^ addr: punk10dxwmqjy72s9nkm9x9pluyn6pyx0gkptjhs4k9
// curr balance: 899999747
// const MOCK_MNEMONIC =
// "oil once motion cute crawl patch happy wave donkey zoo retreat matrix emerge adult very universe aware error snap credit actress couple upset engine";
// ^^ addr: punk1yzr7gtmtlfd0s7s9wpexhteeu05y4xlcvh65eh
// curr balance: 5045 UPUNK
// const MOCK_MNEMONIC =
// "sample menu edit midnight guard review call record horn antenna stairs awkward fringe document during amazing twelve wise wide escape matter betray staff someone";
// ^^ addr: punk1wn8lwxe5hvdtx60c6p7ekskmu75agwfrslf0qs
// curr balance:
type AccountType = {
addr: string;
client: any;
mnemonic: string;
mnemonic?: string;
};
function validatorCli() {
// define funcs to be used in CLI switch-case
@@ -43,6 +56,75 @@ function validatorCli() {
restartApp();
}
function sendFundsMenu() {
inquirer
.prompt([
{
name: "recipient",
type: "input",
message: "please enter the receipient:",
},
{
name: "amount",
type: "input",
message: "please enter the amount (UPUNK):",
},
])
.then(async ({ recipient, amount }) => {
const { addr, client } = state;
console.log(
`🔥 Hold Tight - Sending ${amount}UPUNK to ${recipient} 🚀`
);
const res = await client.send(addr, recipient, [
{
denom: "upunk",
amount: amount,
},
]);
console.log("Funds Transfer Response:", res);
restartApp();
});
}
async function delegateGateway() {
console.log(
"unfortunately - gateway delegation is switched off at the moment."
);
startTransactionMenu();
// const id = "punk1yzr7gtmtlfd0s7s9wpexhteeu05y4xlcvh65eh";
// const gatewayID = "EQhjPpUuy4i1u87nfQMW21WiBT5mJk4dcq4ju7Vct7cB";
// const coin = {
// denom: "upunk",
// amount: "101",
// };
// const res = await state.client.delegateToMixnode(gatewayID, coin);
// console.log("delegateMixnode ==> ", res);
}
async function delegateMixnode() {
const mixNodeID = "2cFpCe7yP79CcuRpf6JBRdJaSp7JF5YcA5SHi8JVm1d2";
// const mixNodeID = "2Vrr7s2peGiWsPh6xY3ZFEMDRmMNv8xLBUtV5XMyQLSB";
const coin = {
denom: "upunk",
amount: "1001",
};
const res = await state.client.delegateToMixnode(mixNodeID, coin);
console.log("delegate to mixnode response: ", res);
}
async function findMinimumMixnodeBond() {
const res = await state.client.minimumMixnodeBond();
console.log("res is back ", res);
}
async function bondMixnode() {
state.client.bondMixnode();
}
async function checkOwnsMixnodes() {
const res = await state.client.ownsMixNode();
console.log("owns mixnode? ", res);
}
function startTransactionMenu() {
inquirer
.prompt([
@@ -50,19 +132,88 @@ function validatorCli() {
type: "list",
name: "task",
message: "What now?",
choices: ["send_funds"],
choices: [
"send_funds",
"get_mixnodes",
"refresh_mixnodes",
"refresh_val_api_mixnodes",
"min_mixn_bond",
"bond_mixnode",
"delegate_mixnode",
"delegate_gateway",
"check_owns_mixnode",
],
},
])
.then(({ task }) => {
switch (task) {
case "send_funds":
console.log("sending funds from ", state.addr);
sendFundsMenu();
break;
case "get_mixnodes":
getMixnodes();
break;
case "refresh_mixnodes":
refreshMixnodes();
break;
case "refresh_val_api_mixnodes":
refreshValApiMixnodes();
break;
case "min_mixn_bond":
findMinimumMixnodeBond();
break;
case "bond_mixnode":
bondMixnode();
break;
case "delegate_gateway":
delegateGateway();
break;
case "delegate_mixnode":
delegateMixnode();
break;
case "check_owns_mixnode":
checkOwnsMixnodes();
break;
default:
return null;
}
});
}
function queryUserAccount() {
inquirer
.prompt([
{
type: "input",
name: "query_user",
message: "Please enter the public address of user you wish to query",
},
])
.then(async ({ query_user }) => {
let response = "";
try {
const client = await ValidatorClient.connectForQuery(
query_user,
VALIDATOR_URLS,
DENOM
);
const balance = await client.getBalance(query_user);
response = `User ${query_user} has a balance of ${balance?.amount}${balance?.denom}`;
console.log(response);
return validatorCli();
} catch (error) {
console.log("error back ", error);
return validatorCli();
}
});
}
async function refreshMixnodes() {
const res = await state.client.refreshMixNodes(
"punk1yksauczytk60x5cejaras8w6nwf7r772n3kwkp"
);
console.log("done:", res);
}
function connectAccount() {
inquirer
.prompt([
@@ -80,7 +231,6 @@ function validatorCli() {
"punk"
);
console.log("Decryped address:", addr);
const client = await ValidatorClient.connect(
addr,
MOCK_MNEMONIC,
@@ -97,8 +247,9 @@ function validatorCli() {
const balance = await client.getBalance(addr);
console.log(`connected to validator, our address is ${client.address}`);
console.log("connected to validator", client.urls[0]);
console.log("📫 Your address:", addr);
console.log("💰 Your balance:", balance);
console.log(
`💰 Your balance is ${balance?.amount}${balance?.denom.toUpperCase()}`
);
startTransactionMenu();
})
@@ -106,19 +257,40 @@ function validatorCli() {
console.log("error: ", err);
});
}
function buildAWallet() {
inquirer
.prompt([
{
message: "enter your mnemonic to build wallet:",
type: "input",
name: "mnemonic",
},
])
.then(async ({ mnemonic }) => {
const res = await ValidatorClient.buildWallet(mnemonic, DENOM);
console.log("Build_Wallet Response: ", res);
});
}
async function refreshValApiMixnodes() {
const res = await state.client.refreshValidatorAPIMixNodes();
console.log("res is back: ", res);
}
function getMixnodes() {
const res = state.client.mixNodesCache;
console.log("Mixnodes", res);
}
// app provides a list of possible tasks
inquirer
.prompt([
{
type: "list",
name: "task",
message: "So...What would you like to do today?",
message: "Yo, What would you like to do today?",
choices: [
"create_account",
"connect_account",
"get_mixnodes",
"send_funds",
"build_wallet",
"query_user",
],
},
])
@@ -130,6 +302,12 @@ function validatorCli() {
case "connect_account":
connectAccount();
break;
case "build_wallet":
buildAWallet();
break;
case "query_user":
queryUserAccount();
break;
default:
return null;
}
@@ -137,6 +315,3 @@ function validatorCli() {
}
validatorCli();
// if it's get mixnodes, return all mixnodes
// if it's create an account
// if it's send funds from one address to another
+66 -6
View File
@@ -80,6 +80,13 @@ export default class ValidatorClient {
this.denom = "u" + prefix;
}
/**
* @param contractAddress the user's contract address eg. `punk23o85698370891702470413487`
* @param mnemonic A mnemonic string from which to generate a public/private keypair.
* @param urls the validator URLs in either array of string format.
* @param prefix the denom eg. `punk`
* @returns user's instance of the Validator Client.
*/
// allows also entering 'string' by itself for backwards compatibility
static async connect(
contractAddress: string,
@@ -126,6 +133,14 @@ export default class ValidatorClient {
throw new Error("None of the provided validators seem to be alive");
}
/**
* This method is the same as connect() but doesnt require a mnemonic
* as you cannot transfer/withdraw once connected. It is effectively ReadOnly.
* @param contractAddress the user's contract address eg. `punk23o85698370891702470413487`
* @param urls the validator URLs in either array of string format.
* @param prefix the denom eg. `punk`
* @returns user's instance of the Validator Client.
*/
// allows also entering 'string' by itself for backwards compatibility
static async connectForQuery(
contractAddress: string,
@@ -164,6 +179,10 @@ export default class ValidatorClient {
throw new Error("None of the provided validators seem to be alive");
}
/**
* @param urls the validator URLs in either array of string format.
* @returns a shuffled array of validator URLs.
*/
private static ensureArray(urls: string | string[]): string[] {
let validatorsUrls: string[] = [];
if (typeof urls === "string") {
@@ -184,8 +203,12 @@ export default class ValidatorClient {
return validatorsUrls;
}
// an error adapter function that upon an error attempts to switch currently used validator to the next one available
// note that it ALWAYS throws an error
/**
* Error adapter function that - upon an error - attempts to switch currently used validator to the next one available
* note that it ALWAYS throws an error
* @param error Error thrown by async/netw requests in other methods within this class.
* @returns a thrown error, as normal.
*/
async handleRequestFailure(error: Error): Promise<never> {
// don't bother doing any fancy validator switches if we only have 1 validator to choose from
if (this.urls.length > 1) {
@@ -208,12 +231,21 @@ export default class ValidatorClient {
throw error;
}
}
/**
* changes the client's validator to the new validator passed in.
* @param newUrl the URL of the new/alternative validator.
* @returns void
*/
private async changeValidator(newUrl: string): Promise<void> {
console.log("Changing validator to", newUrl);
return await this.client.changeValidator(newUrl);
}
/**
* shuffles the array
* @param arr the URL of the new/alternative validator.
* @returns array
*/
// adapted from https://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array/6274381#6274381
static shuffleArray<T>(arr: T[]): T[] {
for (let i = arr.length - 1; i > 0; i--) {
@@ -223,6 +255,12 @@ export default class ValidatorClient {
return arr;
}
/**
* utility function that moves the first element (Val url) to the back
* of the array again.
* @param arr
* @returns void
*/
// 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();
@@ -265,6 +303,7 @@ export default class ValidatorClient {
}
/**
* effectively decodes a mnemonic phrase into a public punk address.
* @param mnemonic A mnemonic from which to generate a public/private keypair.
* @returns the address for this client wallet
*/
@@ -277,6 +316,12 @@ export default class ValidatorClient {
return address;
}
/**
* async func to build/create a NYM wallet.
* @param mnemonic
* @param prefix
* @returns Promise<DirectSecp256k1HdWallet>
*/
static async buildWallet(
mnemonic: string,
prefix: string
@@ -285,12 +330,22 @@ export default class ValidatorClient {
return DirectSecp256k1HdWallet.fromMnemonic(mnemonic, signerOptions);
}
/**
* this method returns a promise which returns the amount/denom of a given user
* @param address the user's public contract address eg. `punk23o85698370891702470413487`
* @returns user's instance of the Validator Client.
*/
getBalance(address: string): Promise<Coin | null> {
return this.client
.getBalance(address, this.denom)
.catch((err) => this.handleRequestFailure(err));
}
/**
* Func calls the client (instance of INetClient / NetClient) method `getStateParams(addr)`
* Used in minimumMixnodeBond() and minimumGatewayBond()
* @returns Promisified State Params.
*/
async getStateParams(): Promise<StateParams> {
return this.client
.getStateParams(this.contractAddress)
@@ -305,9 +360,9 @@ export default class ValidatorClient {
* TODO: We will want to put this puppy on a timer, but for the moment we can
* just get things strung together and refresh it manually.
*/
refreshMixNodes(): Promise<MixNodeBond[]> {
async refreshMixNodes(arg: string): Promise<MixNodeBond[]> {
return this.mixNodesCache
.refreshMixNodes(this.contractAddress)
.refreshMixNodes(arg)
.catch((err) => this.handleRequestFailure(err));
}
@@ -319,7 +374,7 @@ export default class ValidatorClient {
* TODO: We will want to put this puppy on a timer, but for the moment we can
* just get things strung together and refresh it manually.
*/
refreshValidatorAPIMixNodes(): Promise<MixNodeBond[]> {
async refreshValidatorAPIMixNodes(): Promise<MixNodeBond[]> {
return this.mixNodesCache
.refreshValidatorAPIMixNodes(this.urls)
.catch((err) => this.handleRequestFailure(err));
@@ -397,6 +452,7 @@ export default class ValidatorClient {
amount: Coin
): Promise<ExecuteResult> {
if (this.client instanceof NetClient) {
console.log("args mixID ", mixIdentity, " amount ", amount);
const result = await this.client
.executeContract(
this.client.clientAddress,
@@ -747,6 +803,10 @@ export default class ValidatorClient {
/**
* Send funds multiple times from one address to another in a single block.
* @param senderAddress
* @param data
* @param memo
* @returns result
*/
async sendMultiple(
senderAddress: string,