mirror of
https://github.com/mimblewimble/grin.git
synced 2025-02-01 17:01:09 +03:00
"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:
parent
1582ec4e73
commit
68cfbbecad
5 changed files with 205 additions and 29 deletions
11
doc/build.md
11
doc/build.md
|
@ -98,16 +98,19 @@ 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.
|
||||
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.
|
||||
|
||||
|
|
52
doc/wallet.md
Normal file
52
doc/wallet.md
Normal 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]
|
|
@ -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.");
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue