Skip to content

Commit

Permalink
Merge pull request Joystream#23 from jfinkhaeuser/finish-storage-prov…
Browse files Browse the repository at this point in the history
…ider-role-integration

Finish role signup process
  • Loading branch information
jfinkhaeuser committed Apr 9, 2019
2 parents 7941179 + 9a8f90b commit 8b956cb
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 20 deletions.
46 changes: 45 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -64,6 +64,50 @@ $ 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
-------------

Expand Down
50 changes: 43 additions & 7 deletions bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -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".
`, {
Expand All @@ -64,7 +64,7 @@ const cli = meow(`
type: 'integer',
default: undefined,
},
key: {
keyFile: {
type: 'string',
default: undefined,
},
Expand Down Expand Up @@ -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);

Expand All @@ -259,6 +261,22 @@ 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');
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
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) {
Expand All @@ -268,10 +286,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);
Expand Down
34 changes: 34 additions & 0 deletions lib/joystream/substrate/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
24 changes: 20 additions & 4 deletions lib/joystream/substrate/identities.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 17 additions & 8 deletions lib/joystream/substrate/roles.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down

0 comments on commit 8b956cb

Please sign in to comment.