Skip to content

Commit

Permalink
Merge pull request #852 from cosmos/make-faucet-path-configurable
Browse files Browse the repository at this point in the history
Make faucet path configurable
  • Loading branch information
webmaster128 committed Jul 24, 2021
2 parents 52fade5 + 9106f12 commit ae1a81d
Show file tree
Hide file tree
Showing 11 changed files with 148 additions and 7 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ and this project adheres to
- @cosmjs/cosmwasm-stargate: Copy symbols `Code`, `CodeDetails`, `Contract`,
`ContractCodeHistoryEntry` and `JsonObject` from @cosmjs/cosmwasm-launchpad
and remove dependency on @cosmjs/cosmwasm-launchpad.
- @cosmjs/faucet: Add new configuration variable `FAUCET_PATH_PATTERN` to
configure the HD path of the faucet accounts ([#832]).

[#832]: https://github.com/cosmos/cosmjs/issues/832

### Changed

Expand Down
4 changes: 4 additions & 0 deletions packages/faucet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ FAUCET_GAS_PRICE Gas price for transactions as a comma separated list.
FAUCET_GAS_LIMIT Gas limit for send transactions. Defaults to 80000.
FAUCET_MNEMONIC Secret mnemonic that serves as the base secret for the
faucet HD accounts
FAUCET_PATH_PATTERN The pattern of BIP32 paths for the faucet accounts.
Must contain one "a" placeholder that is replaced with
the account index.
Defaults to the Cosmos Hub path "m/44'/118'/0'/0/a".
FAUCET_ADDRESS_PREFIX The bech32 address prefix. Defaults to "cosmos".
FAUCET_TOKENS A comma separated list of token denoms, e.g.
"uatom" or "ucosm, mstake".
Expand Down
6 changes: 5 additions & 1 deletion packages/faucet/src/actions/generate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Bip39, Random } from "@cosmjs/crypto";

import * as constants from "../constants";
import { makePathBuilder } from "../pathbuilder";
import { createWallets } from "../profile";

export async function generate(args: readonly string[]): Promise<void> {
Expand All @@ -13,6 +14,9 @@ export async function generate(args: readonly string[]): Promise<void> {
const mnemonic = Bip39.encode(Random.getBytes(16)).toString();
console.info(`FAUCET_MNEMONIC="${mnemonic}"`);

const pathBuilder = makePathBuilder(constants.pathPattern);
console.info(`FAUCET_PATH_PATTERN="${constants.pathPattern}"`);

// Log the addresses
await createWallets(mnemonic, constants.addressPrefix, constants.concurrency, true);
await createWallets(mnemonic, pathBuilder, constants.addressPrefix, constants.concurrency, true, true);
}
4 changes: 4 additions & 0 deletions packages/faucet/src/actions/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ FAUCET_GAS_PRICE Gas price for transactions as a comma separated list.
FAUCET_GAS_LIMIT Gas limit for send transactions. Defaults to 80000.
FAUCET_MNEMONIC Secret mnemonic that serves as the base secret for the
faucet HD accounts
FAUCET_PATH_PATTERN The pattern of BIP32 paths for the faucet accounts.
Must contain one "a" placeholder that is replaced with
the account index.
Defaults to the Cosmos Hub path "m/44'/118'/0'/0/a".
FAUCET_ADDRESS_PREFIX The bech32 address prefix. Defaults to "cosmos".
FAUCET_TOKENS A comma separated list of token denoms, e.g.
"uatom" or "ucosm, mstake".
Expand Down
3 changes: 3 additions & 0 deletions packages/faucet/src/actions/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Webserver } from "../api/webserver";
import * as constants from "../constants";
import { logAccountsState } from "../debugging";
import { Faucet } from "../faucet";
import { makePathBuilder } from "../pathbuilder";

export async function start(args: readonly string[]): Promise<void> {
if (args.length < 1) {
Expand All @@ -29,11 +30,13 @@ export async function start(args: readonly string[]): Promise<void> {
// Faucet
if (!constants.mnemonic) throw new Error("The FAUCET_MNEMONIC environment variable is not set");
const logging = true;
const pathBuilder = makePathBuilder(constants.pathPattern);
const faucet = await Faucet.make(
blockchainBaseUrl,
constants.addressPrefix,
constants.tokenConfig,
constants.mnemonic,
pathBuilder,
constants.concurrency,
stargate,
logging,
Expand Down
1 change: 1 addition & 0 deletions packages/faucet/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const concurrency: number = Number.parseInt(process.env.FAUCET_CONCURRENC
export const port: number = Number.parseInt(process.env.FAUCET_PORT || "", 10) || 8000;
export const mnemonic: string | undefined = process.env.FAUCET_MNEMONIC;
export const addressPrefix = process.env.FAUCET_ADDRESS_PREFIX || "cosmos";
export const pathPattern = process.env.FAUCET_PATH_PATTERN || "m/44'/118'/0'/0/a";
export const tokenConfig: TokenConfiguration = {
bankTokens: parseBankTokens(process.env.FAUCET_TOKENS || "ucosm, ustake"),
};
22 changes: 21 additions & 1 deletion packages/faucet/src/faucet.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Random } from "@cosmjs/crypto";
import { Bech32 } from "@cosmjs/encoding";
import { CosmosClient } from "@cosmjs/launchpad";
import { StargateClient } from "@cosmjs/stargate";
import { makeCosmoshubPath, StargateClient } from "@cosmjs/stargate";
import { assert } from "@cosmjs/utils";

import { Faucet } from "./faucet";
Expand Down Expand Up @@ -32,6 +32,8 @@ const faucetMnemonic =
"economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone";

describe("Faucet", () => {
const pathBuilder = makeCosmoshubPath;

describe("launchpad", () => {
const apiUrl = "http://localhost:1317";
const stargate = false;
Expand All @@ -44,6 +46,7 @@ describe("Faucet", () => {
defaultAddressPrefix,
defaultTokenConfig,
faucetMnemonic,
pathBuilder,
3,
stargate,
);
Expand All @@ -59,6 +62,7 @@ describe("Faucet", () => {
defaultAddressPrefix,
{ bankTokens: [] },
faucetMnemonic,
pathBuilder,
3,
stargate,
);
Expand All @@ -73,6 +77,7 @@ describe("Faucet", () => {
defaultAddressPrefix,
defaultTokenConfig,
faucetMnemonic,
pathBuilder,
3,
stargate,
);
Expand All @@ -89,6 +94,7 @@ describe("Faucet", () => {
defaultAddressPrefix,
defaultTokenConfig,
faucetMnemonic,
pathBuilder,
3,
stargate,
);
Expand Down Expand Up @@ -122,6 +128,7 @@ describe("Faucet", () => {
defaultAddressPrefix,
defaultTokenConfig,
faucetMnemonic,
pathBuilder,
3,
stargate,
);
Expand Down Expand Up @@ -150,6 +157,7 @@ describe("Faucet", () => {
defaultAddressPrefix,
defaultTokenConfig,
faucetMnemonic,
pathBuilder,
3,
stargate,
);
Expand All @@ -174,6 +182,7 @@ describe("Faucet", () => {
defaultAddressPrefix,
defaultTokenConfig,
faucetMnemonic,
pathBuilder,
3,
stargate,
);
Expand All @@ -200,6 +209,7 @@ describe("Faucet", () => {
defaultAddressPrefix,
defaultTokenConfig,
faucetMnemonic,
pathBuilder,
3,
stargate,
);
Expand All @@ -216,6 +226,7 @@ describe("Faucet", () => {
defaultAddressPrefix,
defaultTokenConfig,
faucetMnemonic,
pathBuilder,
1,
stargate,
);
Expand Down Expand Up @@ -262,6 +273,7 @@ describe("Faucet", () => {
defaultAddressPrefix,
defaultTokenConfig,
faucetMnemonic,
pathBuilder,
3,
stargate,
);
Expand All @@ -277,6 +289,7 @@ describe("Faucet", () => {
defaultAddressPrefix,
{ bankTokens: [] },
faucetMnemonic,
pathBuilder,
3,
stargate,
);
Expand All @@ -291,6 +304,7 @@ describe("Faucet", () => {
defaultAddressPrefix,
defaultTokenConfig,
faucetMnemonic,
pathBuilder,
3,
stargate,
);
Expand All @@ -307,6 +321,7 @@ describe("Faucet", () => {
defaultAddressPrefix,
defaultTokenConfig,
faucetMnemonic,
pathBuilder,
3,
stargate,
);
Expand Down Expand Up @@ -340,6 +355,7 @@ describe("Faucet", () => {
defaultAddressPrefix,
defaultTokenConfig,
faucetMnemonic,
pathBuilder,
3,
stargate,
);
Expand Down Expand Up @@ -368,6 +384,7 @@ describe("Faucet", () => {
defaultAddressPrefix,
defaultTokenConfig,
faucetMnemonic,
pathBuilder,
3,
stargate,
);
Expand All @@ -392,6 +409,7 @@ describe("Faucet", () => {
defaultAddressPrefix,
defaultTokenConfig,
faucetMnemonic,
pathBuilder,
3,
stargate,
);
Expand All @@ -418,6 +436,7 @@ describe("Faucet", () => {
defaultAddressPrefix,
defaultTokenConfig,
faucetMnemonic,
pathBuilder,
3,
stargate,
);
Expand All @@ -434,6 +453,7 @@ describe("Faucet", () => {
defaultAddressPrefix,
defaultTokenConfig,
faucetMnemonic,
pathBuilder,
1,
stargate,
);
Expand Down
11 changes: 10 additions & 1 deletion packages/faucet/src/faucet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { sleep } from "@cosmjs/utils";

import * as constants from "./constants";
import { debugAccount, logAccountsState, logSendJob } from "./debugging";
import { PathBuilder } from "./pathbuilder";
import { createClients, createWallets } from "./profile";
import { TokenConfiguration, TokenManager } from "./tokenmanager";
import { MinimalAccount, SendJob } from "./types";
Expand All @@ -27,11 +28,19 @@ export class Faucet {
addressPrefix: string,
config: TokenConfiguration,
mnemonic: string,
pathBuilder: PathBuilder,
numberOfDistributors: number,
stargate = true,
logging = false,
): Promise<Faucet> {
const wallets = await createWallets(mnemonic, addressPrefix, numberOfDistributors, stargate, logging);
const wallets = await createWallets(
mnemonic,
pathBuilder,
addressPrefix,
numberOfDistributors,
stargate,
logging,
);
const clients = await createClients(apiUrl, wallets);
const readonlyClient = stargate ? await StargateClient.connect(apiUrl) : new CosmosClient(apiUrl);
return new Faucet(addressPrefix, config, clients, readonlyClient, logging);
Expand Down
64 changes: 64 additions & 0 deletions packages/faucet/src/pathbuilder.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Slip10RawIndex } from "@cosmjs/crypto";
import { makeCosmoshubPath } from "@cosmjs/proto-signing";

import { makePathBuilder, PathBuilder } from "./pathbuilder";

describe("pathbuilder", () => {
describe("PathBuilder", () => {
it("is compatible to makeCosmoshubPath", () => {
const _builder: PathBuilder = makeCosmoshubPath;
});
});

describe("makePathBuilder", () => {
it("works", () => {
const hub = makePathBuilder("m/44'/118'/0'/0/a");
expect(hub(0)).toEqual([
Slip10RawIndex.hardened(44),
Slip10RawIndex.hardened(118),
Slip10RawIndex.hardened(0),
Slip10RawIndex.normal(0),
Slip10RawIndex.normal(0),
]);
expect(hub(25)).toEqual([
Slip10RawIndex.hardened(44),
Slip10RawIndex.hardened(118),
Slip10RawIndex.hardened(0),
Slip10RawIndex.normal(0),
Slip10RawIndex.normal(25),
]);

const stellar = makePathBuilder("m/44'/148'/a'");
expect(stellar(0)).toEqual([
Slip10RawIndex.hardened(44),
Slip10RawIndex.hardened(148),
Slip10RawIndex.hardened(0),
]);
expect(stellar(42)).toEqual([
Slip10RawIndex.hardened(44),
Slip10RawIndex.hardened(148),
Slip10RawIndex.hardened(42),
]);
});

it("throws for invalid patterns", () => {
// No `a`
expect(() => makePathBuilder("m/44'/118'/0'/0/30")).toThrowError(
/Missing account index variable `a` in pattern./i,
);

// Two `a`
expect(() => makePathBuilder("m/44'/118'/a'/0/a")).toThrowError(
/More than one account index variable `a` in pattern/i,
);

// Stray character
expect(() => makePathBuilder("m/44'?/118'/0'/0/a")).toThrowError(
/Syntax error while reading path component/i,
);

// Missing m/
expect(() => makePathBuilder("44'/118'/0'/0/a")).toThrowError(/Path string must start with 'm'/i);
});
});
});
26 changes: 26 additions & 0 deletions packages/faucet/src/pathbuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { HdPath, stringToPath } from "@cosmjs/crypto";

export type PathBuilder = (account_index: number) => HdPath;

/**
* Insert a BIP32 path that contains a valiable `a` for the numeric account index.
* This variable will be replaces when the path builder is used.
*
* @param pattern, e.g. m/44'/148'/a' for Stellar paths
*/
export function makePathBuilder(pattern: string): PathBuilder {
if (pattern.indexOf("a") === -1) throw new Error("Missing account index variable `a` in pattern.");
if (pattern.indexOf("a") !== pattern.lastIndexOf("a")) {
throw new Error("More than one account index variable `a` in pattern.");
}

const builder: PathBuilder = function (a: number): HdPath {
const path = pattern.replace("a", a.toString());
return stringToPath(path);
};

// test builder
const _path = builder(0);

return builder;
}
10 changes: 6 additions & 4 deletions packages/faucet/src/profile.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import { pathToString } from "@cosmjs/crypto";
import { makeCosmoshubPath, Secp256k1HdWallet, SigningCosmosClient } from "@cosmjs/launchpad";
import { Secp256k1HdWallet, SigningCosmosClient } from "@cosmjs/launchpad";
import { DirectSecp256k1HdWallet, isOfflineDirectSigner, OfflineSigner } from "@cosmjs/proto-signing";
import { SigningStargateClient } from "@cosmjs/stargate";

import * as constants from "./constants";
import { PathBuilder } from "./pathbuilder";

export async function createWallets(
mnemonic: string,
pathBuilder: PathBuilder,
addressPrefix: string,
numberOfDistributors: number,
stargate = true,
logging = false,
stargate: boolean,
logging: boolean,
): Promise<ReadonlyArray<readonly [string, OfflineSigner]>> {
const createWallet = stargate ? DirectSecp256k1HdWallet.fromMnemonic : Secp256k1HdWallet.fromMnemonic;
const wallets = new Array<readonly [string, OfflineSigner]>();

// first account is the token holder
const numberOfIdentities = 1 + numberOfDistributors;
for (let i = 0; i < numberOfIdentities; i++) {
const path = makeCosmoshubPath(i);
const path = pathBuilder(i);
const wallet = await createWallet(mnemonic, { hdPaths: [path], prefix: addressPrefix });
const [{ address }] = await wallet.getAccounts();
if (logging) {
Expand Down

0 comments on commit ae1a81d

Please sign in to comment.