Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make faucet path configurable #852

Merged
merged 3 commits into from
Jul 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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