Skip to content
This repository was archived by the owner on Sep 2, 2022. It is now read-only.

Improvements in key-manager for workshops #14

Merged
merged 7 commits into from
Jun 23, 2021
Merged
Show file tree
Hide file tree
Changes from 6 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
50 changes: 35 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,52 @@ The steps below are a quick start if you have already set up your [develoment en
### Set up the Rust toolchain
You need the Rust toolchain to develop smart contracts.
```bash
$ cd contract
$ rustup install $(cat rust-toolchain)
$ rustup target add --toolchain $(cat rust-toolchain) wasm32-unknown-unknown
```

### Compile Account Code (Smart Contracts)
Create a WASM file that will be used by the JS client.
```bash
$ cd contract
$ cargo build --release
```

### Install the JS packages
```bash
$ cd client
$ npm install
```

## Prepare the local `nctl` network
1. Set up [nctl](https://github.com/CasperLabs/casper-node/tree/master/utils/nctl)
2. Update `client/src/utils.js`:
- Set `baseKeyPath` to your nctl faucet key.

## Run
Run the client code.
```bash
$ cd client
$ node src/keys-manager.js
$ node src/keys-manager-set-all.js
## Running prepared client-scenarios

### Installation

Just run `npm i` in `./client`.

### Env configuration

Environment variables needs to be set in `.env` file in `./client`.

```
BASE_KEY_PATH=... # absolute path to keys directory
NODE_URL=... # optional, defaults to standard NCTL address http://localhost:40101/rpc
WASM_PATH=... # optional, defaults to ../contract/target/wasm32-unknown-unknown/release/keys-manager.wasm
NETWORK_NAME=... # optional, defaults to casper-net-1
FUND_AMOUNT=10000000000000 # defaults to 10000000000000 = 10000CSPR
PAYMENT_AMOUNT=100000000000 # defaults to 100000000000 = 100CSPR
TRANSFER_AMOUNT=2500000000 # defaults to 2500000000 = 2.5CSPR
```

You can also run run both scripts providing custom `.env` path by running

`npm run start:atomic dotenv_config_path=./example-env-file`

### Set-all

`npm run start:all`

### Step-by-step

`npm run start:atomic`

### Interactive mode

To run a script in interactive mode just add `interactive` to the above commands.
27 changes: 0 additions & 27 deletions client/README.md

This file was deleted.

190 changes: 109 additions & 81 deletions client/src/key-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,31 @@ const {
CLValueBuilder,
} = require('casper-js-sdk');

const { getAccountFromKeyPair, randomSeed, toAccountHashString, sleep } = require('./utils');
const { getAccountFromKeyPair, randomSeed, toAccountHashString, sleep, pauseAndWaitForKeyPress } = require('./utils');

const FUND_AMOUNT = 10000000000000;
const PAYMENT_AMOUNT = 100000000000;
const FUND_AMOUNT = process.env.FUND_AMOUNT || 10000000000000;
const PAYMENT_AMOUNT = process.env.PAYMENT_AMOUNT || 100000000000;

const NODE_URL = process.env.NODE_URL || 'http://localhost:40101/rpc';
const WASM_PATH = process.env.WASM_PATH || '../contract/target/wasm32-unknown-unknown/release/keys-manager.wasm';
const NETWORK_NAME = process.env.NETWORK_NAME || 'casper-net-1';
const BASE_KEY_PATH = process.env.BASE_KEY_PATH;

// Get a faucet account from provided path
const faucetAccount = getAccountFromKeyPair(BASE_KEY_PATH);

// Create a client connect to Casper Node
let client = new CasperClient(NODE_URL);
// Create a client connected to Casper Node
const client = new CasperClient(NODE_URL);

async function sendDeploy(deploy, signingKeys) {
for(let key of signingKeys){
console.log(`Signed by: ${toAccountHashString(key.publicKey)}`);
deploy = client.signDeploy(deploy, key);
}
let deployHash = await client.putDeploy(deploy);
await printDeploy(deployHash);
}
//
// Helper methods
//

// Helper method for geting a deploy in a defined time period (30s)
async function getDeploy(deployHash) {
let i = 300;
while (i != 0) {
let [deploy, raw] = await client.getDeploy(deployHash);
const [deploy, raw] = await client.getDeploy(deployHash);
if (raw.execution_results.length !== 0){
if (raw.execution_results[0].result.Success) {
return deploy;
Expand All @@ -56,122 +53,153 @@ async function getDeploy(deployHash) {
throw Error('Timeout after ' + i + 's. Something\'s wrong');
}

// Helper method for getting the current state of the account
async function getAccount(publicKey) {
let c = new CasperServiceByJsonRPC(NODE_URL);
let stateRootHash = (await c.getLatestBlockInfo()).block.header.state_root_hash;
let account = await c.getBlockState(
const c = new CasperServiceByJsonRPC(NODE_URL);
const stateRootHash = (await c.getLatestBlockInfo()).block.header.state_root_hash;
const account = await c.getBlockState(
stateRootHash,
'account-hash-' + toAccountHashString(publicKey),
publicKey.toAccountHashStr(),
[]
).then(res => res.Account);
return account;
}

// Helper method for sending deploy and displaying signing keys
async function sendDeploy(deploy, signingKeys) {
for(let key of signingKeys){
console.log(`Signed by: ${key.publicKey.toAccountHashStr()}`);
deploy = client.signDeploy(deploy, key);
}
const deployHash = await client.putDeploy(deploy);
await printDeploy(deployHash);
}

// Helper method to create a new hierarchical deterministic wallet
function randomMasterKey() {
const seed = new Uint8Array(randomSeed());
return client.newHdWallet(seed);
}

// Helper method for printing deploy result
async function printDeploy(deployHash) {
console.log("Deploy hash: " + deployHash);
console.log("Deploy result:");
console.log(DeployUtil.deployToJson(await getDeploy(deployHash)));
}

// Helper method for printing account info
async function printAccount(account) {
console.log("\n[x] Current state of the account:");
console.log(JSON.stringify(await getAccount(account.publicKey), null, 2));
console.log(JSON.parse(JSON.stringify(await getAccount(account.publicKey), null, 2)));
await pauseAndWaitForKeyPress();
}

function randomMasterKey() {
const seed = new Uint8Array(randomSeed());
return client.newHdWallet(seed);
//
// Transfers
//

// Builds native transfer deploy
function transferDeploy(fromAccount, toAccount, amount) {
const deployParams = new DeployUtil.DeployParams(
fromAccount.publicKey,
NETWORK_NAME
);
const transferParams = DeployUtil.ExecutableDeployItem.newTransfer(
amount,
toAccount.publicKey,
null,
1
);
const payment = DeployUtil.standardPayment(PAYMENT_AMOUNT);
return DeployUtil.makeDeploy(deployParams, transferParams, payment);
}

// Helper method for funding the specified account from a faucetAccount
async function fundAccount(account) {
const deploy = transferDeploy(faucetAccount, account, FUND_AMOUNT);
await sendDeploy(deploy, [faucetAccount]);
}

// Key manager
//
// Contract deploy related methods
//

// Builds a deploy that will install key-manager contract on specified account
function buildContractInstallDeploy(baseAccount) {
const deployParams = new DeployUtil.DeployParams(
baseAccount.publicKey,
NETWORK_NAME
);
const session = new Uint8Array(fs.readFileSync(WASM_PATH, null).buffer);
const runtimeArgs = RuntimeArgs.fromMap({});
const sessionModule = DeployUtil.ExecutableDeployItem.newModuleBytes(
session,
runtimeArgs
);
const payment = DeployUtil.standardPayment(PAYMENT_AMOUNT);

function setAll(fromAccount, deployThereshold, keyManagementThreshold, accountWeights) {
let accounts = accountWeights.map(x => x.publicKey);
let weights = accountWeights.map(x => CLValueBuilder.u8(x.weight));
return DeployUtil.makeDeploy(deployParams, sessionModule, payment);
}

// Builds key-manager deploy that takes entrypoint and args
function buildKeyManagerDeploy(baseAccount, entrypoint, args) {
const deployParams = new DeployUtil.DeployParams(
baseAccount.publicKey,
NETWORK_NAME
);
const runtimeArgs = RuntimeArgs.fromMap(args);
const sessionModule = DeployUtil.ExecutableDeployItem.newStoredContractByName(
"keys_manager",
entrypoint,
runtimeArgs
);
const payment = DeployUtil.standardPayment(PAYMENT_AMOUNT);
return DeployUtil.makeDeploy(deployParams, sessionModule, payment);
}

//
// Key-manager contract specific methods
//

// Sets deploy threshold, key management threshold, and weights for the specified accounts
function setAll(fromAccount, deployThreshold, keyManagementThreshold, accountWeights) {
const accounts = accountWeights.map(x => x.publicKey);
const weights = accountWeights.map(x => CLValueBuilder.u8(x.weight));

return buildKeyManagerDeploy(fromAccount, "set_all", {
deployment_thereshold: CLValueBuilder.u8(deployThereshold),
deployment_thereshold: CLValueBuilder.u8(deployThreshold),
key_management_threshold: CLValueBuilder.u8(keyManagementThreshold),
accounts: CLValueBuilder.list(accounts),
weights: CLValueBuilder.list(weights),
});
}

// Sets key with a specified weight
function setKeyWeightDeploy(fromAccount, account, weight) {
return buildKeyManagerDeploy(fromAccount, "set_key_weight", {
account: account.publicKey,
weight: CLValueBuilder.u8(weight)
});
}

// Sets deploys threshold
function setDeploymentThresholdDeploy(fromAccount, weight) {
return buildKeyManagerDeploy(fromAccount, "set_deployment_threshold", {
weight: CLValueBuilder.u8(weight)
});
}

// Sets key-management threshold
function setKeyManagementThresholdDeploy(fromAccount, weight) {
return buildKeyManagerDeploy(fromAccount, "set_key_management_threshold", {
weight: CLValueBuilder.u8(weight)
});
}

function buildKeyManagerDeploy(baseAccount, entrypoint, args) {
let deployParams = new DeployUtil.DeployParams(
baseAccount.publicKey,
NETWORK_NAME
);
let runtimeArgs = RuntimeArgs.fromMap(args);
let sessionModule = DeployUtil.ExecutableDeployItem.newStoredContractByName(
"keys_manager",
entrypoint,
runtimeArgs
);
let payment = DeployUtil.standardPayment(PAYMENT_AMOUNT);
return DeployUtil.makeDeploy(deployParams, sessionModule, payment);
}

function buildContractInstallDeploy(baseAccount) {
let deployParams = new DeployUtil.DeployParams(
baseAccount.publicKey,
NETWORK_NAME
);
const session = new Uint8Array(fs.readFileSync(WASM_PATH, null).buffer);
let runtimeArgs = RuntimeArgs.fromMap({});

let sessionModule = DeployUtil.ExecutableDeployItem.newModuleBytes(
session,
runtimeArgs
);
let payment = DeployUtil.standardPayment(PAYMENT_AMOUNT);
return DeployUtil.makeDeploy(deployParams, sessionModule, payment);
}

// Funding
function transferDeploy(fromAccount, toAccount, amount) {
let deployParams = new DeployUtil.DeployParams(
fromAccount.publicKey,
NETWORK_NAME
);
let transferParams = DeployUtil.ExecutableDeployItem.newTransfer(
amount,
toAccount.publicKey,
null,
1
);
let payment = DeployUtil.standardPayment(PAYMENT_AMOUNT);
return DeployUtil.makeDeploy(deployParams, transferParams, payment);
}

async function fund(account) {
let deploy = transferDeploy(faucetAccount, account, FUND_AMOUNT);
await sendDeploy(deploy, [faucetAccount]);
}

module.exports = {
'randomMasterKey': randomMasterKey,
'toAccountHashString': toAccountHashString,
'fund': fund,
'fundAccount': fundAccount,
'printAccount': printAccount,
'keys': {
'setAll': setAll,
Expand Down
Loading