"grin wallet init" to create the initial wallet.seed file (#198)

* "wallet init" to create the initial wallet.seed file
* cleanup and better error msgs
* add some basic wallet docs
* cleanup and make passphrase optional
This commit is contained in:
AntiochP 2017-10-24 13:34:34 -04:00 committed by Ignotus Peverell
parent 1582ec4e73
commit 68cfbbecad
5 changed files with 205 additions and 29 deletions

View file

@ -17,7 +17,7 @@ The instructions below will assume a Linux system.
In order to compile and run Grin on your machine, you should have installed:
* <b>Git</b> - to clone the repository
* <b>cmake</b> - 3.2 or greater should be installed and on your $PATH. Used by the build to compile the mining plugins found in the included [Cuckoo Miner](https://github.com/mimblewimble/cuckoo-miner)
* <b>cmake</b> - 3.2 or greater should be installed and on your $PATH. Used by the build to compile the mining plugins found in the included [Cuckoo Miner](https://github.com/mimblewimble/cuckoo-miner)
* <b>Rust</b> - via [Rustup](https://www.rustup.rs/) - Can be installed via your package manager or manually via the following commands:
```
curl https://sh.rustup.rs -sSf | sh
@ -61,7 +61,7 @@ Provided all of the prerequisites were installed and there were no issues, there
* A set of mining plugins, which should be in the 'plugins' directory located next to the grin executable
* A configuration file in the root project directory named grin.toml
* A configuration file in the root project directory named grin.toml
By default, executing:
@ -98,22 +98,25 @@ At present, the relevant modes of operation are 'server' and 'wallet'. When runn
For a basic example simulating a single node network, create a directory called 'node1' and change your working directory to it. You'll use this directory to run a wallet and create a new blockchain via a server running in mining mode.
Before running your mining server, a wallet server needs to be set up and listening so that the mining server knows where to send mining rewards. Do this from the first node directory with the following command:
Before running your mining server, a wallet server needs to be set up and listening so that the mining server knows where to send mining rewards. Do this from the first node directory with the following commands:
node1$ grin wallet -p "password" receive
node1$ grin wallet init
node1$ grin wallet -p "password" receive
See [wallet](wallet.md) for more info on the various Grin wallet commands and options.
This will create a wallet server listening on the default port 13416 with the password "password". Next, in another terminal window in the 'node1' directory, run a full mining node with the following command:
node1$ grin server -m run
node1$ grin server -m run
This creates a new .grin database directory in the current directory, and begins mining new blocks (with no transactions, for now). Note this starts two services listening on two default ports,
port 13414 for the peer-to-peer (P2P) service which keeps all nodes synchronised, and 13413 for the Rest API service used to verify transactions and post new transactions to the pool (for example). These ports can be configured via command line switches, or via a grin.toml file in the working directory.
This creates a new .grin database directory in the current directory, and begins mining new blocks (with no transactions, for now). Note this starts two services listening on two default ports,
port 13414 for the peer-to-peer (P2P) service which keeps all nodes synchronized, and 13413 for the Rest API service used to verify transactions and post new transactions to the pool (for example). These ports can be configured via command line switches, or via a grin.toml file in the working directory.
Let the mining server find a few blocks, then stop (just ctrl-c) the mining server and the wallet server. You'll notice grin has created a database directory (.grin) in which the blockchain and peer data is stored. There should also be a wallet.dat file in the current directory, which contains a few coinbase mining rewards created each time the server mines a new block.
## Advanced Example
The following outlines a more advanced example simulating a multi-server network with transactions being posted.
The following outlines a more advanced example simulating a multi-server network with transactions being posted.
For the sake of example, we're going to run three nodes with varying setups. Create two more directories beside your node1 directory, called node2 and node3. If you want to clear data from your previous run (or anytime you want to reset the blockchain and all peer data) just delete the wallet.dat file in the server1 directory and run rm -rf .grin to remove grin's database.
@ -161,7 +164,7 @@ use node 2's API listener to validate our transaction inputs before sending:
node1$ grin wallet -p "password" -a "http://127.0.0.1:20001" send 20000 -d "http://127.0.0.1:35000"
Your terminal windows should all light up now. Node 1 will check its inputs against node 2, and then send a partial transaction to node 3's wallet listener. Node 3 has been configured to
Your terminal windows should all light up now. Node 1 will check its inputs against node 2, and then send a partial transaction to node 3's wallet listener. Node 3 has been configured to
send signed and finalised transactions to the api listener on node 1, which should then add the transaction to the next block and validate it via mining.
You can feel free to try any number of permutations or combinations of the above, just note that grin is very new and under active development, so your mileage may vary. You can also use a separate 'grin.toml' file in each server directory to simplify command line switches.

52
doc/wallet.md Normal file
View file

@ -0,0 +1,52 @@
# Grin - Basic Wallet
## Wallet Files
A Grin wallet maintains its state in the following files -
```
wallet.seed # *** passphrase protected seed file (keep this private) ***
wallet.dat # wallet outputs (both spent and unspent)
wallet.lock # lock file, prevents multiple processes writing to wallet.dat
```
By default Grin will look for these in the current working directory.
## Basic Wallet Commands
`grin wallet --help` will display usage info about the following.
### grin wallet init
Before using a wallet a new seed file `wallet.seed` needs to be generated via `grin wallet init` -
```
grin wallet init
Generating wallet seed file at: ./wallet.seed
```
### grin wallet info
Some (very) basic information about current wallet outputs can be displayed with `grin wallet info` -
```
grin wallet -p "password" info
Using wallet seed file at: ./wallet.seed
Outputs -
key_id, height, lock_height, status, spendable?, coinbase?, value
----------------------------------
96805837571719c692b6, 21, 24, Spent, false, true, 50000000000
...
```
### grin wallet receive
(tbd)
### grin wallet send
(tbd)
### grin wallet burn
[tbd]

View file

@ -41,7 +41,6 @@ use daemonize::Daemonize;
use config::GlobalConfig;
use wallet::WalletConfig;
use core::global;
use keychain::Keychain;
use util::{LoggingConfig, LOGGER, init_logger};
fn start_from_config_file(mut global_config: GlobalConfig) {
@ -164,7 +163,8 @@ fn main() {
.short("p")
.long("pass")
.help("Wallet passphrase used to generate the private key seed")
.takes_value(true))
.takes_value(true)
.default_value("mimblewimble"))
.arg(Arg::with_name("data_dir")
.short("dd")
.long("data_dir")
@ -227,7 +227,10 @@ fn main() {
.takes_value(true)))
.subcommand(SubCommand::with_name("info")
.about("basic wallet info (outputs)")))
.about("basic wallet info (outputs)"))
.subcommand(SubCommand::with_name("init")
.about("Initialize a new wallet seed file.")))
.get_matches();
@ -333,17 +336,8 @@ fn server_command(server_args: &ArgMatches, global_config: GlobalConfig) {
}
fn wallet_command(wallet_args: &ArgMatches) {
let hd_seed = wallet_args.value_of("pass").expect(
"Wallet passphrase required.",
);
// TODO do something closer to BIP39, eazy solution right now
let seed = blake2::blake2b::blake2b(32, &[], hd_seed.as_bytes());
let keychain = Keychain::from_seed(seed.as_bytes()).expect(
"Failed to initialize keychain from the provided seed.",
);
let mut wallet_config = WalletConfig::default();
if let Some(port) = wallet_args.value_of("port") {
let default_ip = "0.0.0.0";
wallet_config.api_http_addr = format!("{}:{}", default_ip, port);
@ -357,8 +351,25 @@ fn wallet_command(wallet_args: &ArgMatches) {
wallet_config.check_node_api_http_addr = sa.to_string().clone();
}
match wallet_args.subcommand() {
// Derive the keychain based on seed from seed file and specified passphrase.
// Generate the initial wallet seed if we are running "wallet init".
if let ("init", Some(_)) = wallet_args.subcommand() {
wallet::WalletSeed::init_file(&wallet_config)
.expect("Failed to init wallet seed file.");
// we are done here with creating the wallet, so just return
return;
}
let wallet_seed = wallet::WalletSeed::from_file(&wallet_config)
.expect("Failed to read wallet seed file.");
let passphrase = wallet_args
.value_of("pass")
.expect("Failed to read passphrase.");
let keychain = wallet_seed.derive_keychain(&passphrase)
.expect("Failed to derive keychain from seed file and passphrase.");
match wallet_args.subcommand() {
("receive", Some(receive_args)) => {
if let Some(f) = receive_args.value_of("input") {
let mut file = File::open(f).expect("Unable to open transaction file.");

View file

@ -39,4 +39,4 @@ mod types;
pub use info::show_info;
pub use receiver::{WalletReceiver, receive_json_tx};
pub use sender::{issue_send_tx, issue_burn_tx};
pub use types::{WalletConfig, WalletReceiveRequest, BlockFees, CbData};
pub use types::{BlockFees, CbData, WalletConfig, WalletReceiveRequest, WalletSeed};

View file

@ -12,13 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use blake2;
use rand::{thread_rng, Rng};
use std::{fmt, num, thread, time};
use std::convert::From;
use std::fs::{self, File, OpenOptions};
use std::io::Write;
use std::io::{self, Read, Write};
use std::path::Path;
use std::path::MAIN_SEPARATOR;
use std::collections::HashMap;
use std::cmp::min;
use serde_json;
use secp;
@ -32,6 +35,7 @@ use util::LOGGER;
const DAT_FILE: &'static str = "wallet.dat";
const LOCK_FILE: &'static str = "wallet.lock";
const SEED_FILE: &'static str = "wallet.seed";
const DEFAULT_BASE_FEE: u64 = 10;
@ -60,6 +64,8 @@ pub enum Error {
WalletData(String),
/// An error in the format of the JSON structures exchanged by the wallet
Format(String),
/// An IO Error
IOError(io::Error),
/// Error when contacting a node through its API
Node(api::Error),
}
@ -100,6 +106,12 @@ impl From<api::Error> for Error {
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Error {
Error::IOError(e)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WalletConfig {
// Whether to run a wallet
@ -200,6 +212,98 @@ impl OutputData {
}
}
#[derive(Clone, PartialEq)]
pub struct WalletSeed([u8; 32]);
impl WalletSeed {
pub fn from_bytes(bytes: &[u8]) -> WalletSeed {
let mut seed = [0; 32];
for i in 0..min(32, bytes.len()) {
seed[i] = bytes[i];
}
WalletSeed(seed)
}
fn from_hex(hex: &str) -> Result<WalletSeed, Error> {
let bytes = util::from_hex(hex.to_string())?;
Ok(WalletSeed::from_bytes(&bytes))
}
pub fn to_hex(&self) -> String {
util::to_hex(self.0.to_vec())
}
pub fn derive_keychain(&self, password: &str) -> Result<keychain::Keychain, Error> {
let seed = blake2::blake2b::blake2b(64, &password.as_bytes(), &self.0);
let result = keychain::Keychain::from_seed(seed.as_bytes())?;
Ok(result)
}
pub fn init_new() -> WalletSeed {
let seed: [u8; 32] = thread_rng().gen();
WalletSeed(seed)
}
pub fn init_file(wallet_config: &WalletConfig) -> Result<WalletSeed, Error> {
// create directory if it doesn't exist
fs::create_dir_all(&wallet_config.data_file_dir)?;
let seed_file_path = &format!(
"{}{}{}",
wallet_config.data_file_dir,
MAIN_SEPARATOR,
SEED_FILE,
);
debug!(
LOGGER,
"Generating wallet seed file at: {}",
seed_file_path,
);
if Path::new(seed_file_path).exists() {
panic!("wallet seed file already exists");
} else {
let seed = WalletSeed::init_new();
let mut file = File::create(seed_file_path)?;
file.write_all(&seed.to_hex().as_bytes())?;
Ok(seed)
}
}
pub fn from_file(wallet_config: &WalletConfig) -> Result<WalletSeed, Error> {
// create directory if it doesn't exist
fs::create_dir_all(&wallet_config.data_file_dir)?;
let seed_file_path = &format!(
"{}{}{}",
wallet_config.data_file_dir,
MAIN_SEPARATOR,
SEED_FILE,
);
debug!(
LOGGER,
"Using wallet seed file at: {}",
seed_file_path,
);
if Path::new(seed_file_path).exists() {
let mut file = File::open(seed_file_path)?;
let mut buffer = String::new();
file.read_to_string(&mut buffer)?;
let wallet_seed = WalletSeed::from_hex(&buffer)?;
Ok(wallet_seed)
} else {
error!(
LOGGER,
"Run: \"grin wallet init\" to initialize a new wallet.",
);
panic!("wallet seed file does not yet exist (grin wallet init)");
}
}
}
/// Wallet information tracking all our outputs. Based on HD derivation and
/// avoids storing any key data, only storing output amounts and child index.
/// This data structure is directly based on the JSON representation stored
@ -242,7 +346,7 @@ impl WalletData {
.map_err(|_| {
Error::WalletData(format!(
"Could not create wallet lock file. Either \
some other process is using the wallet or there's a write access issue."
some other process is using the wallet or there is a write access issue."
))
});
match result {
@ -250,16 +354,22 @@ impl WalletData {
break;
}
Err(e) => {
if retries >= 3 {
if retries >= 6 {
info!(
LOGGER,
"failed to obtain wallet.lock after {} retries, \
unable to successfully open the wallet",
retries
);
return Err(e);
}
debug!(
LOGGER,
"failed to obtain wallet.lock, retries - {}, sleeping",
"failed to obtain wallet.lock, retries - {}, sleeping and retrying",
retries
);
retries += 1;
thread::sleep(time::Duration::from_millis(500));
thread::sleep(time::Duration::from_millis(1000));
}
}
}