From 9286b117999bf9ab1b8e13165efad7fc3fb67593 Mon Sep 17 00:00:00 2001 From: Jens Finkhaeuser Date: Tue, 9 Apr 2019 16:17:20 +0200 Subject: [PATCH 1/4] - Improve signup workflow a little with possibility for empty passphrases - Wait for the key file to identify a staked role, or abort. --- bin/cli.js | 47 +++++++++++++++++++++++---- lib/joystream/substrate/base.js | 34 +++++++++++++++++++ lib/joystream/substrate/identities.js | 24 +++++++++++--- lib/joystream/substrate/roles.js | 25 +++++++++----- 4 files changed, 111 insertions(+), 19 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index b43d0b60d3..22e0732a82 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -45,7 +45,7 @@ const cli = meow(` protocol. Defaults to 3030. --sync-period Number of milliseconds to wait between synchronization runs. Defaults to 30,000 (30s). - --key Private key to run the storage node under. FIXME should become file + --key-file JSON key export file to use as the storage provider. --storage=PATH, -s PATH Storage path to use. --storage-type=TYPE One of "fs", "hyperdrive". Defaults to "hyperdrive". `, { @@ -64,7 +64,7 @@ const cli = meow(` type: 'integer', default: undefined, }, - key: { + keyFile: { type: 'string', default: undefined, }, @@ -246,7 +246,9 @@ async function run_signup(account_file) // Create a role key const role_key = await api.createRoleKey(member_address); const role_address = role_key.address(); - console.log('Generated ', role_address); + console.log('Generated ', role_address, '- this is going to be exported to a', + 'JSON file. You can provide an empty passphrase to make starting the server', + 'easier, but you must keep the file very safe, then.'); const filename = await api.writeKeyPairExport(role_address); console.log('Identity stored in', filename); @@ -259,6 +261,19 @@ async function run_signup(account_file) console.log('Role application sent.'); } +async function wait_for_role(flags, config) +{ + // Load key information + const { RolesApi, ROLE_STORAGE } = require('joystream/substrate/roles'); + const account_file = flags['keyFile'] || config.get('keyFile'); + const api = await RolesApi.create(account_file); + + // Wait for the account role to be finalized + console.log('Waiitng for the account to be staked as a storage provider role...'); + const result = await api.waitForRole(api.key.address(), ROLE_STORAGE); + return [result, api]; +} + // Simple CLI commands var command = cli.input[0]; if (!command) { @@ -268,10 +283,28 @@ if (!command) { const commands = { 'server': () => { const cfg = create_config(pkg.name, cli.flags); - const store = get_storage(cfg, cli.flags); - banner(); - start_app(project_root, store, cfg, cli.flags); - start_sync_server(store, cfg, cli.flags); + + // Load key information + const errfunc = (err) => { + console.log(err); + process.exit(-1); + } + + const promise = wait_for_role(cli.flags, cfg); + promise.catch(errfunc).then((values) => { + const result = values[0] + const roles_api = values[1]; + if (!result) { + throw new Error(`Not staked as storage role.`); + } + console.log('Staked, proceeding.'); + + // Continue with server setup + const store = get_storage(cfg, cli.flags); + banner(); + start_app(project_root, store, cfg, cli.flags); + start_sync_server(store, cfg, cli.flags); + }).catch(errfunc); }, 'create': () => { const cfg = create_config(pkg.name, cli.flags); diff --git a/lib/joystream/substrate/base.js b/lib/joystream/substrate/base.js index df6db85bf4..804e575bf3 100644 --- a/lib/joystream/substrate/base.js +++ b/lib/joystream/substrate/base.js @@ -24,6 +24,40 @@ class SubstrateApi // Create the API instrance this.api = await ApiPromise.create(); } + + async waitForEvent(module, name) + { + return new Promise((resolve, reject) => { + this.api.query.system.events((events) => { + debug(`Number of events: ${events.length}`); + events.forEach((record) => { + // extract the phase, event and the event types + const { event, phase } = record; + const types = event.typeDef; + + // show what we are busy with + debug(`\t${event.section}:${event.method}:: (phase=${phase.toString()})`); + debug(`\t\t${event.meta.documentation.toString()}`); + + // Skip events we're not interested in. + if (event.section != module || event.method != name) { + // Nothing to do here. + return; + } + + // loop through each of the parameters, displaying the type and data + const payload = {}; + event.data.forEach((data, index) => { + debug(`\t\t\t${types[index].type}: ${data.toString()}`); + payload[types[index].type] = data; + }); + + const full_name = `${module}.${name}`; + resolve([full_name, payload]); + }); + }); + }); + } } module.exports = { diff --git a/lib/joystream/substrate/identities.js b/lib/joystream/substrate/identities.js index 6e60e1624c..2edd040d63 100644 --- a/lib/joystream/substrate/identities.js +++ b/lib/joystream/substrate/identities.js @@ -37,13 +37,29 @@ class IdentitiesApi extends SubstrateApi const fullname = path.resolve(account_file); debug('Initializing key from', fullname); this.key = this.keyring.addFromJson(require(fullname), true); - if (this.key.isLocked()) { - const passphrase = await this.askForPassphrase(this.key.address()); - this.key.decodePkcs8(passphrase); - } + await this.tryUnlock(this.key); debug('Successfully initialized with address', this.key.address()); } + async tryUnlock(key) + { + if (!key.isLocked()) { + return; + } + + // First try with an empty passphrase - for convenience + try { + key.decodePkcs8(''); + return; + } catch (err) { + // pass + } + + // If that didn't work, ask for a passphrase. + const passphrase = await this.askForPassphrase(this.key.address()); + key.decodePkcs8(passphrase); + } + async askForPassphrase(address) { // Query for passphrase diff --git a/lib/joystream/substrate/roles.js b/lib/joystream/substrate/roles.js index dae7cffd12..6cd1d2c734 100644 --- a/lib/joystream/substrate/roles.js +++ b/lib/joystream/substrate/roles.js @@ -68,14 +68,23 @@ class RolesApi extends BalancesApi return !_.isEqual(actor.raw, new Null()); } -// async waitForRole(roleAccountId, role) -// { -// if (await this.checkForRole(roleAccountId, role)) { -// return true; -// } -// -// -// } + async waitForRole(roleAccountId, role) + { + if (await this.checkForRole(roleAccountId, role)) { + return true; + } + + return new Promise((resolve, reject) => { + this.waitForEvent('actors', 'Staked').then((values) => { + const name = values[0]; + const payload = values[1]; + + if (payload.AccountId == roleAccountId) { + resolve(true); + } + }); + }); + } } module.exports = { From b75632bb9b47042dcf99092d6160e6bf69ebdc93 Mon Sep 17 00:00:00 2001 From: Jens Finkhaeuser Date: Tue, 9 Apr 2019 16:24:14 +0200 Subject: [PATCH 2/4] Require key file for running a storage node now. --- bin/cli.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bin/cli.js b/bin/cli.js index 22e0732a82..2712d9d64f 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -266,6 +266,9 @@ async function wait_for_role(flags, config) // Load key information const { RolesApi, ROLE_STORAGE } = require('joystream/substrate/roles'); const account_file = flags['keyFile'] || config.get('keyFile'); + if (!account_file) { + throw new Error("Must specify a key file for running a storage node! Sign up for the role; see `js_storage --help' for details."); + } const api = await RolesApi.create(account_file); // Wait for the account role to be finalized From 9b82a1c9e63e4cd8e27e46d3d7b67aa6bbe8d84c Mon Sep 17 00:00:00 2001 From: Jens Finkhaeuser Date: Tue, 9 Apr 2019 16:33:26 +0200 Subject: [PATCH 3/4] Staking instructions --- README.md | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7488c694f0..b7e10bdc61 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ files in that folder are generated. Command-Line ------------ -Running a storage server is as easy as running the bundled `js_storage` +Running a storage server is (almost) as easy as running the bundled `js_storage` executable: ```bash @@ -64,6 +64,48 @@ $ js_storage --storage=/path/to/storage/directory Run with `--help` to see a list of available CLI options. +You need to stake as a storage provider to run a storage node. + +Storage Provider Staking +------------------------ + +Staking for the storage provider role happens in a few simple steps: + +1. Using [the app](https://github.com/Joystream/apps), create an account and make + it a member. Make sure to save the JSON file. Not only is this account your + identity, the file is also needed for the signup process. Make sure thea ccount + has some currency. + - You need some currency to become a member. + - You need to stake some currency to become a storage provider. + - There's a transaction fee for applying as a storage provider. +1. Using the `js_storage` cli, run the signup process: + ```bash + $ js_storage signup MEMBER_ADDRESS.json + Enter passphrase for MEMBER_ADDRESS: asdf + Account is working for staking, proceeding. + Generated ROLE_ADDRESS - this is going to be exported to a JSON file. You can provide an empty passphrase to make starting the server easier, but you must keep the file very safe, then. + Enter passphrase for ROLE_ADDRESS: + Identity stored in ROLE_ADDRESS.json + Funds transferred. + Role application sent. + ``` +1. The newly created account is also saved to a JSON file. This is the account + you use for running the storage node. Funds will be transferred from the member + account to the role account, and an application to stake for the role will be + created. +1. Navigate to the `Roles` menu entry of the app. If you're currently the member + account, you should see the role application under the `MyRequests` tab. +1. Stake for the role. +1. Back with the CLI, run the server: + ```bash + $ js_storage --key-file ROLE_ADDRESS.json + ``` + +Note that the JSON files contain the full key pair of either account. It's best +to protect them with a passphrase. If you want to run the `js_storage` server +more easily, you can also provide an empty passphrase when the JSON file is +created - but be aware that you must then take extra care to secure this file! + Configuration ------------- From 9a8f90b2d359ca6e417422c25ccfbb26627bebf3 Mon Sep 17 00:00:00 2001 From: Jens Finkhaeuser Date: Tue, 9 Apr 2019 16:36:45 +0200 Subject: [PATCH 4/4] Fix indentation --- README.md | 48 +++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index b7e10bdc61..c50c402f64 100644 --- a/README.md +++ b/README.md @@ -72,34 +72,36 @@ Storage Provider Staking Staking for the storage provider role happens in a few simple steps: 1. Using [the app](https://github.com/Joystream/apps), create an account and make - it a member. Make sure to save the JSON file. Not only is this account your - identity, the file is also needed for the signup process. Make sure thea ccount - has some currency. - - You need some currency to become a member. - - You need to stake some currency to become a storage provider. - - There's a transaction fee for applying as a storage provider. + it a member. Make sure to save the JSON file. Not only is this account your + identity, the file is also needed for the signup process. Make sure thea ccount + has some currency. + - You need some currency to become a member. + - You need to stake some currency to become a storage provider. + - There's a transaction fee for applying as a storage provider. 1. Using the `js_storage` cli, run the signup process: - ```bash - $ js_storage signup MEMBER_ADDRESS.json - Enter passphrase for MEMBER_ADDRESS: asdf - Account is working for staking, proceeding. - Generated ROLE_ADDRESS - this is going to be exported to a JSON file. You can provide an empty passphrase to make starting the server easier, but you must keep the file very safe, then. - Enter passphrase for ROLE_ADDRESS: - Identity stored in ROLE_ADDRESS.json - Funds transferred. - Role application sent. - ``` + ```bash + $ js_storage signup MEMBER_ADDRESS.json + Enter passphrase for MEMBER_ADDRESS: asdf + Account is working for staking, proceeding. + Generated ROLE_ADDRESS - this is going to be exported to a JSON file. + You can provide an empty passphrase to make starting the server easier, + but you must keep the file very safe, then. + Enter passphrase for ROLE_ADDRESS: + Identity stored in ROLE_ADDRESS.json + Funds transferred. + Role application sent. + ``` 1. The newly created account is also saved to a JSON file. This is the account - you use for running the storage node. Funds will be transferred from the member - account to the role account, and an application to stake for the role will be - created. + you use for running the storage node. Funds will be transferred from the member + account to the role account, and an application to stake for the role will be + created. 1. Navigate to the `Roles` menu entry of the app. If you're currently the member - account, you should see the role application under the `MyRequests` tab. + account, you should see the role application under the `MyRequests` tab. 1. Stake for the role. 1. Back with the CLI, run the server: - ```bash - $ js_storage --key-file ROLE_ADDRESS.json - ``` + ```bash + $ js_storage --key-file ROLE_ADDRESS.json + ``` Note that the JSON files contain the full key pair of either account. It's best to protect them with a passphrase. If you want to run the `js_storage` server