[WIP] Factoring out wallet traits and continued wallet library work (#1096)

* rename wallet libs

* rename transaction.rs to slate.rs

* rename transaction.rs to slate.rs

* move some types into libwallet/types

* rustfmt

* rename libtransaction libtx

* rename libtransaction libtx

* change types.rs to file_wallet

* rustfmt

* rename WalletData to FileWallet

* refactoring WalletBackend types out

* rustfmt

* All compiling, at least

* rustfmt

* fix FileWallet paths to persist

* rustfmt

* re-ignore wallet integration test
This commit is contained in:
Yeastplume 2018-05-30 17:48:32 +01:00 committed by GitHub
parent 601c9e769e
commit 82ed280625
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 1512 additions and 1249 deletions

View file

@ -26,14 +26,14 @@ use std::sync::Arc;
use chain::Chain;
use chain::types::*;
use core::core::{Block, BlockHeader, Transaction};
use core::core::target::Difficulty;
use core::{consensus, genesis};
use core::core::{Block, BlockHeader, Transaction};
use core::global;
use core::global::ChainTypes;
use core::{consensus, genesis};
use keychain::Keychain;
use wallet::libwallet;
use wallet::libtx;
use core::pow;
@ -75,7 +75,7 @@ fn data_files() {
let prev = chain.head_header().unwrap();
let difficulty = consensus::next_difficulty(chain.difficulty_iter()).unwrap();
let pk = keychain.derive_key_id(n as u32).unwrap();
let reward = libwallet::reward::output(&keychain, &pk, 0, prev.height).unwrap();
let reward = libtx::reward::output(&keychain, &pk, 0, prev.height).unwrap();
let mut b = core::core::Block::new(&prev, vec![], difficulty.clone(), reward).unwrap();
b.header.timestamp = prev.timestamp + time::Duration::seconds(60);
@ -159,7 +159,7 @@ fn _prepare_block_nosum(
let key_id = kc.derive_key_id(diff as u32).unwrap();
let fees = txs.iter().map(|tx| tx.fee()).sum();
let reward = libwallet::reward::output(&kc, &key_id, fees, prev.height).unwrap();
let reward = libtx::reward::output(&kc, &key_id, fees, prev.height).unwrap();
let mut b = match core::core::Block::new(prev, txs, Difficulty::from_num(diff), reward) {
Err(e) => panic!("{:?}", e),
Ok(b) => b,

View file

@ -26,13 +26,13 @@ use std::sync::Arc;
use chain::Chain;
use chain::types::*;
use core::core::{Block, BlockHeader, OutputFeatures, OutputIdentifier, Transaction};
use core::consensus;
use core::core::hash::Hashed;
use core::core::target::Difficulty;
use core::consensus;
use core::core::{Block, BlockHeader, OutputFeatures, OutputIdentifier, Transaction};
use core::global;
use core::global::ChainTypes;
use wallet::libwallet::{self, build};
use wallet::libtx::{self, build};
use keychain::Keychain;
@ -64,7 +64,7 @@ fn mine_empty_chain() {
let prev = chain.head_header().unwrap();
let difficulty = consensus::next_difficulty(chain.difficulty_iter()).unwrap();
let pk = keychain.derive_key_id(n as u32).unwrap();
let reward = libwallet::reward::output(&keychain, &pk, 0, prev.height).unwrap();
let reward = libtx::reward::output(&keychain, &pk, 0, prev.height).unwrap();
let mut b = core::core::Block::new(&prev, vec![], difficulty.clone(), reward).unwrap();
b.header.timestamp = prev.timestamp + time::Duration::seconds(60);
@ -413,7 +413,7 @@ fn prepare_block_nosum(
let key_id = kc.derive_key_id(diff as u32).unwrap();
let fees = txs.iter().map(|tx| tx.fee()).sum();
let reward = libwallet::reward::output(&kc, &key_id, fees, prev.height).unwrap();
let reward = libtx::reward::output(&kc, &key_id, fees, prev.height).unwrap();
let mut b = match core::core::Block::new(prev, txs, Difficulty::from_num(diff), reward) {
Err(e) => panic!("{:?}", e),
Ok(b) => b,

View file

@ -22,16 +22,16 @@ extern crate rand;
use std::fs;
use chain::{ChainStore, Tip};
use core::core::hash::Hashed;
use core::core::Block;
use core::core::BlockHeader;
use core::core::hash::Hashed;
use core::core::target::Difficulty;
use keychain::Keychain;
use core::global;
use core::global::ChainTypes;
use core::pow;
use keychain::Keychain;
use wallet::libwallet;
use wallet::libtx;
fn clean_output_dir(dir_name: &str) {
let _ = fs::remove_dir_all(dir_name);
@ -56,7 +56,7 @@ fn test_various_store_indices() {
.setup_height(&genesis.header, &Tip::new(genesis.hash()))
.unwrap();
let reward = libwallet::reward::output(&keychain, &key_id, 0, 1).unwrap();
let reward = libtx::reward::output(&keychain, &key_id, 0, 1).unwrap();
let block = Block::new(&genesis.header, vec![], Difficulty::one(), reward).unwrap();
let block_hash = block.hash();

View file

@ -24,16 +24,16 @@ use std::fs;
use std::sync::Arc;
use chain::types::*;
use wallet::libwallet::build;
use core::consensus;
use core::core::OutputIdentifier;
use core::core::target::Difficulty;
use core::core::transaction;
use core::core::OutputIdentifier;
use core::consensus;
use core::global;
use core::global::ChainTypes;
use wallet::libtx::build;
use keychain::Keychain;
use wallet::libwallet;
use wallet::libtx;
use core::pow;
@ -64,7 +64,7 @@ fn test_coinbase_maturity() {
let key_id3 = keychain.derive_key_id(3).unwrap();
let key_id4 = keychain.derive_key_id(4).unwrap();
let reward = libwallet::reward::output(&keychain, &key_id1, 0, prev.height).unwrap();
let reward = libtx::reward::output(&keychain, &key_id1, 0, prev.height).unwrap();
let mut block = core::core::Block::new(&prev, vec![], Difficulty::one(), reward).unwrap();
block.header.timestamp = prev.timestamp + time::Duration::seconds(60);
@ -118,7 +118,7 @@ fn test_coinbase_maturity() {
let txs = vec![&coinbase_txn];
let fees = txs.iter().map(|tx| tx.fee()).sum();
let reward = libwallet::reward::output(&keychain, &key_id3, fees, prev.height).unwrap();
let reward = libtx::reward::output(&keychain, &key_id3, fees, prev.height).unwrap();
let mut block = core::core::Block::new(&prev, txs, Difficulty::one(), reward).unwrap();
block.header.timestamp = prev.timestamp + time::Duration::seconds(60);
@ -144,7 +144,7 @@ fn test_coinbase_maturity() {
let keychain = Keychain::from_random_seed().unwrap();
let pk = keychain.derive_key_id(1).unwrap();
let reward = libwallet::reward::output(&keychain, &pk, 0, prev.height).unwrap();
let reward = libtx::reward::output(&keychain, &pk, 0, prev.height).unwrap();
let mut block = core::core::Block::new(&prev, vec![], Difficulty::one(), reward).unwrap();
block.header.timestamp = prev.timestamp + time::Duration::seconds(60);
@ -175,7 +175,7 @@ fn test_coinbase_maturity() {
let txs = vec![&coinbase_txn];
let fees = txs.iter().map(|tx| tx.fee()).sum();
let reward = libwallet::reward::output(&keychain, &key_id4, fees, prev.height).unwrap();
let reward = libtx::reward::output(&keychain, &key_id4, fees, prev.height).unwrap();
let mut block =
core::core::Block::new(&prev, vec![&coinbase_txn], Difficulty::one(), reward).unwrap();

View file

@ -19,17 +19,17 @@ extern crate grin_wallet as wallet;
pub mod common;
use grin_core::core::{Block, BlockHeader, CompactBlock, KernelFeatures, OutputFeatures};
use grin_core::core::hash::Hashed;
use grin_core::core::block::Error;
use grin_core::core::id::{ShortId, ShortIdentifiable};
use wallet::libwallet::build::{self, input, output, with_fee};
use common::{new_block, tx1i2o, tx2i1o, txspend1i1o};
use keychain::Keychain;
use grin_core::consensus::{BLOCK_OUTPUT_WEIGHT, MAX_BLOCK_WEIGHT};
use grin_core::ser;
use grin_core::core::block::Error;
use grin_core::core::hash::Hashed;
use grin_core::core::id::{ShortId, ShortIdentifiable};
use grin_core::core::{Block, BlockHeader, CompactBlock, KernelFeatures, OutputFeatures};
use grin_core::global;
use grin_core::ser;
use keychain::Keychain;
use std::time::Instant;
use wallet::libtx::build::{self, input, output, with_fee};
use util::{secp, secp_static};

View file

@ -19,12 +19,12 @@ extern crate grin_keychain as keychain;
extern crate grin_util as util;
extern crate grin_wallet as wallet;
use grin_core::core::Transaction;
use grin_core::core::block::{Block, BlockHeader};
use grin_core::core::target::Difficulty;
use grin_core::core::Transaction;
use keychain::{Identifier, Keychain};
use wallet::libwallet::build::{self, input, output, with_fee};
use wallet::libwallet::reward;
use wallet::libtx::build::{self, input, output, with_fee};
use wallet::libtx::reward;
// utility producing a transaction with 2 inputs and a single outputs
pub fn tx2i1o() -> Transaction {

View file

@ -20,17 +20,17 @@ extern crate grin_wallet as wallet;
pub mod common;
use grin_core::core::hash::{Hashed, ZERO_HASH};
use common::{new_block, tx1i1o, tx1i2o, tx2i1o};
use grin_core::core::block::BlockHeader;
use grin_core::core::block::Error::KernelLockHeight;
use grin_core::core::hash::{Hashed, ZERO_HASH};
use grin_core::core::{aggregate, aggregate_with_cut_through, deaggregate, KernelFeatures, Output,
Transaction};
use wallet::libwallet::build::{self, initial_tx, input, output, with_excess, with_fee,
with_lock_height};
use grin_core::core::block::Error::KernelLockHeight;
use grin_core::ser;
use keychain::Keychain;
use util::{secp_static, static_secp_instance};
use common::{new_block, tx1i1o, tx1i2o, tx2i1o};
use wallet::libtx::build::{self, initial_tx, input, output, with_excess, with_fee,
with_lock_height};
#[test]
fn simple_tx_ser() {

View file

@ -24,7 +24,7 @@ use grin_core::core::{Output, OutputFeatures};
use grin_core::ser;
use keychain::Keychain;
use util::secp;
use wallet::libwallet::proof;
use wallet::libtx::proof;
#[test]
fn test_output_ser_deser() {

View file

@ -21,10 +21,10 @@ extern crate grin_wallet as wallet;
extern crate rand;
use keychain::Keychain;
use core::core::OutputFeatures;
use core::core::transaction::ProofMessageElements;
use wallet::libwallet::proof;
use keychain::Keychain;
use wallet::libtx::proof;
#[test]
fn test_add_entry() {

View file

@ -42,7 +42,7 @@ use std::sync::{Arc, RwLock};
use types::PoolError::InvalidTx;
use keychain::Keychain;
use wallet::libwallet::{build, proof, reward};
use wallet::libtx::{build, proof, reward};
use pool::types::*;

View file

@ -226,7 +226,7 @@ fn burn_reward(block_fees: BlockFees) -> Result<(core::Output, core::TxKernel, B
let keychain = Keychain::from_random_seed().unwrap();
let key_id = keychain.derive_key_id(1).unwrap();
let (out, kernel) =
wallet::libwallet::reward::output(&keychain, &key_id, block_fees.fees, block_fees.height)
wallet::libtx::reward::output(&keychain, &key_id, block_fees.fees, block_fees.height)
.unwrap();
Ok((out, kernel, block_fees))
}

View file

@ -29,7 +29,7 @@ use std::sync::{Arc, Mutex};
use std::thread;
use std::time;
use wallet::WalletConfig;
use wallet::{FileWallet, WalletConfig};
/// Just removes all results from previous runs
pub fn clean_all_output(test_name_dir: &str) {
@ -275,7 +275,14 @@ impl LocalServerContainer {
.derive_keychain("grin_test")
.expect("Failed to derive keychain from seed file and passphrase.");
wallet::server::start_rest_apis(self.wallet_config.clone(), keychain);
let wallet = FileWallet::new(self.wallet_config.clone(), keychain).unwrap_or_else(|e| {
panic!(
"Error creating wallet: {:?} Config: {:?}",
e, self.wallet_config
)
});
wallet::server::start_rest_apis(wallet, &self.wallet_config.api_listen_addr());
self.wallet_is_running = true;
}
@ -294,8 +301,9 @@ impl LocalServerContainer {
let keychain = wallet_seed
.derive_keychain("grin_test")
.expect("Failed to derive keychain from seed file and passphrase.");
wallet::retrieve_info(config, &keychain)
let mut wallet = FileWallet::new(config.clone(), keychain)
.unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config));
wallet::retrieve_info(&mut wallet)
}
pub fn send_amount_to(
@ -312,13 +320,14 @@ impl LocalServerContainer {
let wallet_seed =
wallet::WalletSeed::from_file(config).expect("Failed to read wallet seed file.");
let mut keychain = wallet_seed
let keychain = wallet_seed
.derive_keychain("grin_test")
.expect("Failed to derive keychain from seed file and passphrase.");
let max_outputs = 500;
let mut wallet = FileWallet::new(config.clone(), keychain)
.unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config));
let result = wallet::issue_send_tx(
config,
&mut keychain,
&mut wallet,
amount,
minimum_confirmations,
dest.to_string(),

View file

@ -37,20 +37,21 @@ extern crate grin_wallet as wallet;
mod client;
pub mod tui;
use std::thread;
use std::sync::Arc;
use std::time::Duration;
use std::env::current_dir;
use std::process::exit;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use clap::{App, Arg, ArgMatches, SubCommand};
use daemonize::Daemonize;
use config::GlobalConfig;
use core::global;
use core::core::amount_to_hr_string;
use util::{init_logger, LoggingConfig, LOGGER};
use core::global;
use tui::ui;
use util::{init_logger, LoggingConfig, LOGGER};
use wallet::FileWallet;
// include build information
pub mod built_info {
@ -422,7 +423,10 @@ fn server_command(server_args: Option<&ArgMatches>, mut global_config: GlobalCon
let _ = thread::Builder::new()
.name("wallet_listener".to_string())
.spawn(move || {
wallet::server::start_rest_apis(wallet_config, keychain);
let wallet = FileWallet::new(wallet_config.clone(), keychain).unwrap_or_else(|e| {
panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config)
});
wallet::server::start_rest_apis(wallet, &wallet_config.api_listen_addr());
});
}
@ -530,16 +534,18 @@ fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) {
let passphrase = wallet_args
.value_of("pass")
.expect("Failed to read passphrase.");
let mut keychain = wallet_seed
let keychain = wallet_seed
.derive_keychain(&passphrase)
.expect("Failed to derive keychain from seed file and passphrase.");
let mut wallet = FileWallet::new(wallet_config.clone(), keychain)
.unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config));
match wallet_args.subcommand() {
("listen", Some(listen_args)) => {
if let Some(port) = listen_args.value_of("port") {
wallet_config.api_listen_port = port.parse().unwrap();
}
wallet::server::start_rest_apis(wallet_config, keychain);
wallet::server::start_rest_apis(wallet, &wallet_config.api_listen_addr());
}
("send", Some(send_args)) => {
let amount = send_args
@ -564,8 +570,7 @@ fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) {
}
let max_outputs = 500;
let result = wallet::issue_send_tx(
&wallet_config,
&mut keychain,
&mut wallet,
amount,
minimum_confirmations,
dest.to_string(),
@ -618,22 +623,16 @@ fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) {
.parse()
.expect("Could not parse minimum_confirmations as a whole number.");
let max_outputs = 500;
wallet::issue_burn_tx(
&wallet_config,
&keychain,
amount,
minimum_confirmations,
max_outputs,
).unwrap();
wallet::issue_burn_tx(&mut wallet, amount, minimum_confirmations, max_outputs).unwrap();
}
("info", Some(_)) => {
wallet::show_info(&wallet_config, &keychain);
wallet::show_info(&mut wallet);
}
("outputs", Some(_)) => {
wallet::show_outputs(&wallet_config, &keychain, show_spent);
wallet::show_outputs(&mut wallet, show_spent);
}
("restore", Some(_)) => {
let _ = wallet::restore(&wallet_config, &keychain);
let _ = wallet::restore(&mut wallet);
}
_ => panic!("Unknown wallet command, use 'grin help wallet' for details"),
}

View file

@ -20,29 +20,31 @@ use std::collections::HashMap;
use std::collections::hash_map::Entry;
use api;
use keychain::{Identifier, Keychain};
use types::*;
use keychain::Identifier;
use libwallet::types::*;
use util;
use util::LOGGER;
use util::secp::pedersen;
pub fn refresh_outputs(config: &WalletConfig, keychain: &Keychain) -> Result<(), Error> {
let tip = get_tip_from_node(config)?;
refresh_output_state(config, keychain, &tip)?;
refresh_missing_block_hashes(config, keychain, &tip)?;
pub fn refresh_outputs<T>(wallet: &mut T) -> Result<(), Error>
where
T: WalletBackend,
{
let tip = get_tip_from_node(&wallet.node_url())?;
refresh_output_state(wallet, &tip)?;
refresh_missing_block_hashes(wallet, &tip)?;
Ok(())
}
// TODO - this might be slow if we have really old outputs that have never been
// refreshed
fn refresh_missing_block_hashes(
config: &WalletConfig,
keychain: &Keychain,
tip: &api::Tip,
) -> Result<(), Error> {
fn refresh_missing_block_hashes<T>(wallet: &mut T, tip: &api::Tip) -> Result<(), Error>
where
T: WalletBackend,
{
// build a local map of wallet outputs keyed by commit
// and a list of outputs we want to query the node for
let wallet_outputs = map_wallet_outputs_missing_block(config, keychain)?;
let wallet_outputs = map_wallet_outputs_missing_block(wallet)?;
// nothing to do so return (otherwise we hit the api with a monster query...)
if wallet_outputs.is_empty() {
@ -69,7 +71,7 @@ fn refresh_missing_block_hashes(
for mut query_chunk in id_params.chunks(1000) {
let url = format!(
"{}/v1/chain/outputs/byheight?{}",
config.check_node_api_http_addr,
wallet.node_url(),
[&height_params, query_chunk].concat().join("&"),
);
@ -94,10 +96,10 @@ fn refresh_missing_block_hashes(
// the corresponding api output (if it exists)
// and refresh it in-place in the wallet.
// Note: minimizing the time we spend holding the wallet lock.
WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
wallet.with_wallet(|wallet_data| {
for commit in wallet_outputs.keys() {
let id = wallet_outputs.get(&commit).unwrap();
if let Entry::Occupied(mut output) = wallet_data.outputs.entry(id.to_hex()) {
if let Entry::Occupied(mut output) = wallet_data.outputs().entry(id.to_hex()) {
if let Some(b) = api_blocks.get(&commit) {
let output = output.get_mut();
output.block = Some(BlockIdentifier::from_hex(&b.hash).unwrap());
@ -113,16 +115,20 @@ fn refresh_missing_block_hashes(
/// build a local map of wallet outputs keyed by commit
/// and a list of outputs we want to query the node for
pub fn map_wallet_outputs(
config: &WalletConfig,
keychain: &Keychain,
) -> Result<HashMap<pedersen::Commitment, Identifier>, Error> {
pub fn map_wallet_outputs<T>(
wallet: &mut T,
) -> Result<HashMap<pedersen::Commitment, Identifier>, Error>
where
T: WalletBackend,
{
let mut wallet_outputs: HashMap<pedersen::Commitment, Identifier> = HashMap::new();
let _ = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
let _ = wallet.read_wallet(|wallet_data| {
let keychain = wallet_data.keychain().clone();
let root_key_id = keychain.root_key_id().clone();
let unspents = wallet_data
.outputs
.outputs()
.values()
.filter(|x| x.root_key_id == keychain.root_key_id() && x.status != OutputStatus::Spent);
.filter(|x| x.root_key_id == root_key_id && x.status != OutputStatus::Spent);
for out in unspents {
let commit = keychain
.commit_with_key_index(out.value, out.n_child)
@ -136,14 +142,17 @@ pub fn map_wallet_outputs(
/// As above, but only return unspent outputs with missing block hashes
/// and a list of outputs we want to query the node for
pub fn map_wallet_outputs_missing_block(
config: &WalletConfig,
keychain: &Keychain,
) -> Result<HashMap<pedersen::Commitment, Identifier>, Error> {
pub fn map_wallet_outputs_missing_block<T>(
wallet: &mut T,
) -> Result<HashMap<pedersen::Commitment, Identifier>, Error>
where
T: WalletBackend,
{
let mut wallet_outputs: HashMap<pedersen::Commitment, Identifier> = HashMap::new();
let _ = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
for out in wallet_data.outputs.values().filter(|x| {
x.root_key_id == keychain.root_key_id() && x.block.is_none()
let _ = wallet.read_wallet(|wallet_data| {
let keychain = wallet_data.keychain().clone();
for out in wallet_data.outputs().clone().values().filter(|x| {
x.root_key_id == wallet_data.keychain().root_key_id() && x.block.is_none()
&& x.status == OutputStatus::Unspent
}) {
let commit = keychain
@ -157,18 +166,21 @@ pub fn map_wallet_outputs_missing_block(
}
/// Apply refreshed API output data to the wallet
pub fn apply_api_outputs(
config: &WalletConfig,
pub fn apply_api_outputs<T>(
wallet: &mut T,
wallet_outputs: &HashMap<pedersen::Commitment, Identifier>,
api_outputs: &HashMap<pedersen::Commitment, api::Output>,
) -> Result<(), Error> {
) -> Result<(), Error>
where
T: WalletBackend,
{
// now for each commit, find the output in the wallet and the corresponding
// api output (if it exists) and refresh it in-place in the wallet.
// Note: minimizing the time we spend holding the wallet lock.
WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
wallet.with_wallet(|wallet_data| {
for commit in wallet_outputs.keys() {
let id = wallet_outputs.get(&commit).unwrap();
if let Entry::Occupied(mut output) = wallet_data.outputs.entry(id.to_hex()) {
if let Entry::Occupied(mut output) = wallet_data.outputs().entry(id.to_hex()) {
match api_outputs.get(&commit) {
Some(_) => output.get_mut().mark_unspent(),
None => output.get_mut().mark_spent(),
@ -180,16 +192,15 @@ pub fn apply_api_outputs(
/// Builds a single api query to retrieve the latest output data from the node.
/// So we can refresh the local wallet outputs.
fn refresh_output_state(
config: &WalletConfig,
keychain: &Keychain,
tip: &api::Tip,
) -> Result<(), Error> {
fn refresh_output_state<T>(wallet: &mut T, tip: &api::Tip) -> Result<(), Error>
where
T: WalletBackend,
{
debug!(LOGGER, "Refreshing wallet outputs");
// build a local map of wallet outputs keyed by commit
// and a list of outputs we want to query the node for
let wallet_outputs = map_wallet_outputs(config, keychain)?;
let wallet_outputs = map_wallet_outputs(wallet)?;
// build the necessary query params -
// ?id=xxx&id=yyy&id=zzz
@ -204,7 +215,7 @@ fn refresh_output_state(
for query_chunk in query_params.chunks(1000) {
let url = format!(
"{}/v1/chain/outputs/byids?{}",
config.check_node_api_http_addr,
wallet.node_url(),
query_chunk.join("&"),
);
@ -220,25 +231,28 @@ fn refresh_output_state(
}
}
apply_api_outputs(config, &wallet_outputs, &api_outputs)?;
clean_old_unconfirmed(config, tip)?;
apply_api_outputs(wallet, &wallet_outputs, &api_outputs)?;
clean_old_unconfirmed(wallet, tip)?;
Ok(())
}
fn clean_old_unconfirmed(config: &WalletConfig, tip: &api::Tip) -> Result<(), Error> {
fn clean_old_unconfirmed<T>(wallet: &mut T, tip: &api::Tip) -> Result<(), Error>
where
T: WalletBackend,
{
if tip.height < 500 {
return Ok(());
}
WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
wallet_data.outputs.retain(|_, ref mut out| {
wallet.with_wallet(|wallet_data| {
wallet_data.outputs().retain(|_, ref mut out| {
!(out.status == OutputStatus::Unconfirmed && out.height > 0
&& out.height < tip.height - 500)
});
})
}
pub fn get_tip_from_node(config: &WalletConfig) -> Result<api::Tip, Error> {
let url = format!("{}/v1/chain", config.check_node_api_http_addr);
pub fn get_tip_from_node(addr: &str) -> Result<api::Tip, Error> {
let url = format!("{}/v1/chain", addr);
api::client::get::<api::Tip>(url.as_str())
.context(ErrorKind::Node)
.map_err(|e| e.into())

View file

@ -17,12 +17,12 @@ use futures::{Future, Stream};
use hyper;
use hyper::header::ContentType;
use hyper::{Method, Request};
use libwallet::transaction::Slate;
use libtx::slate::Slate;
use serde_json;
use tokio_core::reactor;
use libwallet::types::*;
use std::io;
use types::*;
use util::LOGGER;
/// Call the wallet API to create a coinbase output for the given block_fees.

465
wallet/src/file_wallet.rs Normal file
View file

@ -0,0 +1,465 @@
// Copyright 2018 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use blake2;
use rand::{thread_rng, Rng};
use std::cmp::min;
use std::collections::HashMap;
use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::MAIN_SEPARATOR;
use std::path::Path;
use serde_json;
use tokio_core::reactor;
use tokio_retry::Retry;
use tokio_retry::strategy::FibonacciBackoff;
use failure::{Fail, ResultExt};
use keychain::{self, Keychain};
use util;
use util::LOGGER;
use libwallet::types::*;
const DAT_FILE: &'static str = "wallet.dat";
const BCK_FILE: &'static str = "wallet.bck";
const LOCK_FILE: &'static str = "wallet.lock";
const SEED_FILE: &'static str = "wallet.seed";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WalletConfig {
// Right now the decision to run or not a wallet is based on the command.
// This may change in the near-future.
// pub enable_wallet: bool,
// The api interface/ip_address that this api server (i.e. this wallet) will run
// by default this is 127.0.0.1 (and will not accept connections from external clients)
pub api_listen_interface: String,
// The port this wallet will run on
pub api_listen_port: u16,
// The api address of a running server node against which transaction inputs
// will be checked during send
pub check_node_api_http_addr: String,
// The directory in which wallet files are stored
pub data_file_dir: String,
}
impl Default for WalletConfig {
fn default() -> WalletConfig {
WalletConfig {
// enable_wallet: false,
api_listen_interface: "127.0.0.1".to_string(),
api_listen_port: 13415,
check_node_api_http_addr: "http://127.0.0.1:13413".to_string(),
data_file_dir: ".".to_string(),
}
}
}
impl WalletConfig {
pub fn api_listen_addr(&self) -> String {
format!("{}:{}", self.api_listen_interface, self.api_listen_port)
}
}
#[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()).context(ErrorKind::GenericError("Invalid hex"))?;
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()).context(ErrorKind::Keychain)?;
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).context(ErrorKind::IO)?;
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() {
Err(ErrorKind::WalletSeedExists)?
} else {
let seed = WalletSeed::init_new();
let mut file = File::create(seed_file_path).context(ErrorKind::IO)?;
file.write_all(&seed.to_hex().as_bytes())
.context(ErrorKind::IO)?;
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).context(ErrorKind::IO)?;
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).context(ErrorKind::IO)?;
let mut buffer = String::new();
file.read_to_string(&mut buffer).context(ErrorKind::IO)?;
let wallet_seed = WalletSeed::from_hex(&buffer)?;
Ok(wallet_seed)
} else {
error!(
LOGGER,
"wallet seed file {} could not be opened (grin wallet init). \
Run \"grin wallet init\" to initialize a new wallet.",
seed_file_path
);
Err(ErrorKind::WalletSeedDoesntExist)?
}
}
}
/// Wallet information tracking all our outputs. Based on HD derivation and
/// avoids storing any key data, only storing output amounts and child index.
#[derive(Debug, Clone)]
pub struct FileWallet {
/// Keychain
pub keychain: Keychain,
/// Configuration
pub config: WalletConfig,
/// List of outputs
pub outputs: HashMap<String, OutputData>,
/// Data file path
pub data_file_path: String,
/// Backup file path
pub backup_file_path: String,
/// lock file path
pub lock_file_path: String,
}
impl WalletBackend for FileWallet {
/// Return the keychain being used
fn keychain(&mut self) -> &mut Keychain {
&mut self.keychain
}
/// Return URL for check node
fn node_url(&self) -> &str {
&self.config.check_node_api_http_addr
}
/// Return the outputs directly
fn outputs(&mut self) -> &mut HashMap<String, OutputData> {
&mut self.outputs
}
/// Allows for reading wallet data (without needing to acquire the write
/// lock).
fn read_wallet<T, F>(&mut self, f: F) -> Result<T, Error>
where
F: FnOnce(&mut Self) -> Result<T, Error>,
{
self.read_or_create_paths()?;
f(self)
}
/// Allows the reading and writing of the wallet data within a file lock.
/// Just provide a closure taking a mutable FileWallet. The lock should
/// be held for as short a period as possible to avoid contention.
/// Note that due to the impossibility to do an actual file lock easily
/// across operating systems, this just creates a lock file with a "should
/// not exist" option.
fn with_wallet<T, F>(&mut self, f: F) -> Result<T, Error>
where
F: FnOnce(&mut Self) -> T,
{
// create directory if it doesn't exist
fs::create_dir_all(self.config.data_file_dir.clone()).unwrap_or_else(|why| {
info!(LOGGER, "! {:?}", why.kind());
});
info!(LOGGER, "Acquiring wallet lock ...");
let lock_file_path = self.lock_file_path.clone();
let action = || {
trace!(LOGGER, "making lock file for wallet lock");
fs::create_dir(&lock_file_path)
};
// use tokio_retry to cleanly define some retry logic
let mut core = reactor::Core::new().unwrap();
let retry_strategy = FibonacciBackoff::from_millis(1000).take(10);
let retry_future = Retry::spawn(core.handle(), retry_strategy, action);
let retry_result = core.run(retry_future);
match retry_result {
Ok(_) => {}
Err(e) => {
error!(
LOGGER,
"Failed to acquire wallet lock file (multiple retries)",
);
return Err(
e.context(ErrorKind::FileWallet("Failed to acquire lock file"))
.into(),
);
}
}
// We successfully acquired the lock - so do what needs to be done.
self.read_or_create_paths()?;
self.write(&self.backup_file_path)?;
let res = f(self);
self.write(&self.data_file_path)?;
// delete the lock file
fs::remove_dir(&self.lock_file_path).context(ErrorKind::FileWallet(
"Could not remove wallet lock file. Maybe insufficient rights?",
))?;
info!(LOGGER, "... released wallet lock");
Ok(res)
}
/// Append a new output data to the wallet data.
/// TODO - we should check for overwriting here - only really valid for
/// unconfirmed coinbase
fn add_output(&mut self, out: OutputData) {
self.outputs.insert(out.key_id.to_hex(), out.clone());
}
// TODO - careful with this, only for Unconfirmed (maybe Locked)?
fn delete_output(&mut self, id: &keychain::Identifier) {
self.outputs.remove(&id.to_hex());
}
/// Lock an output data.
/// TODO - we should track identifier on these outputs (not just n_child)
fn lock_output(&mut self, out: &OutputData) {
if let Some(out_to_lock) = self.outputs.get_mut(&out.key_id.to_hex()) {
if out_to_lock.value == out.value {
out_to_lock.lock()
}
}
}
/// get a single output
fn get_output(&self, key_id: &keychain::Identifier) -> Option<&OutputData> {
self.outputs.get(&key_id.to_hex())
}
/// Next child index when we want to create a new output.
fn next_child(&self, root_key_id: keychain::Identifier) -> u32 {
let mut max_n = 0;
for out in self.outputs.values() {
if max_n < out.n_child && out.root_key_id == root_key_id {
max_n = out.n_child;
}
}
max_n + 1
}
/// Select spendable coins from the wallet.
/// Default strategy is to spend the maximum number of outputs (up to
/// max_outputs). Alternative strategy is to spend smallest outputs first
/// but only as many as necessary. When we introduce additional strategies
/// we should pass something other than a bool in.
fn select_coins(
&self,
root_key_id: keychain::Identifier,
amount: u64,
current_height: u64,
minimum_confirmations: u64,
max_outputs: usize,
select_all: bool,
) -> Vec<OutputData> {
// first find all eligible outputs based on number of confirmations
let mut eligible = self.outputs
.values()
.filter(|out| {
out.root_key_id == root_key_id
&& out.eligible_to_spend(current_height, minimum_confirmations)
})
.cloned()
.collect::<Vec<OutputData>>();
// sort eligible outputs by increasing value
eligible.sort_by_key(|out| out.value);
// use a sliding window to identify potential sets of possible outputs to spend
// Case of amount > total amount of max_outputs(500):
// The limit exists because by default, we always select as many inputs as
// possible in a transaction, to reduce both the Output set and the fees.
// But that only makes sense up to a point, hence the limit to avoid being too
// greedy. But if max_outputs(500) is actually not enought to cover the whole
// amount, the wallet should allow going over it to satisfy what the user
// wants to send. So the wallet considers max_outputs more of a soft limit.
if eligible.len() > max_outputs {
for window in eligible.windows(max_outputs) {
let windowed_eligibles = window.iter().cloned().collect::<Vec<_>>();
if let Some(outputs) = self.select_from(amount, select_all, windowed_eligibles) {
return outputs;
}
}
// Not exist in any window of which total amount >= amount.
// Then take coins from the smallest one up to the total amount of selected
// coins = the amount.
if let Some(outputs) = self.select_from(amount, false, eligible.clone()) {
debug!(
LOGGER,
"Extending maximum number of outputs. {} outputs selected.",
outputs.len()
);
return outputs;
}
} else {
if let Some(outputs) = self.select_from(amount, select_all, eligible.clone()) {
return outputs;
}
}
// we failed to find a suitable set of outputs to spend,
// so return the largest amount we can so we can provide guidance on what is
// possible
eligible.reverse();
eligible.iter().take(max_outputs).cloned().collect()
}
}
impl FileWallet {
/// Create a new FileWallet instance
pub fn new(config: WalletConfig, keychain: Keychain) -> Result<Self, Error> {
let mut retval = FileWallet {
keychain: keychain,
config: config.clone(),
outputs: HashMap::new(),
data_file_path: format!("{}{}{}", config.data_file_dir, MAIN_SEPARATOR, DAT_FILE),
backup_file_path: format!("{}{}{}", config.data_file_dir, MAIN_SEPARATOR, BCK_FILE),
lock_file_path: format!("{}{}{}", config.data_file_dir, MAIN_SEPARATOR, LOCK_FILE),
};
match retval.read_or_create_paths() {
Ok(_) => Ok(retval),
Err(e) => Err(e),
}
}
/// Read the wallet data or create brand files if the data
/// files don't yet exist
fn read_or_create_paths(&mut self) -> Result<(), Error> {
if !Path::new(&self.config.data_file_dir.clone()).exists() {
fs::create_dir_all(&self.config.data_file_dir.clone()).unwrap_or_else(|why| {
info!(LOGGER, "! {:?}", why.kind());
});
}
if Path::new(&self.data_file_path.clone()).exists() {
self.read()?;
}
Ok(())
}
/// Read output_data vec from disk.
fn read_outputs(&self) -> Result<Vec<OutputData>, Error> {
let data_file = File::open(self.data_file_path.clone())
.context(ErrorKind::FileWallet(&"Could not open wallet file"))?;
serde_json::from_reader(data_file).map_err(|e| {
e.context(ErrorKind::FileWallet(&"Error reading wallet file "))
.into()
})
}
/// Populate wallet_data with output_data from disk.
fn read(&mut self) -> Result<(), Error> {
let outputs = self.read_outputs()?;
self.outputs = HashMap::new();
for out in outputs {
self.add_output(out);
}
Ok(())
}
/// Write the wallet data to disk.
fn write(&self, data_file_path: &str) -> Result<(), Error> {
let mut data_file = File::create(data_file_path)
.map_err(|e| e.context(ErrorKind::FileWallet(&"Could not create ")))?;
let mut outputs = self.outputs.values().collect::<Vec<_>>();
outputs.sort();
let res_json = serde_json::to_vec_pretty(&outputs)
.map_err(|e| e.context(ErrorKind::FileWallet("Error serializing wallet data")))?;
data_file
.write_all(res_json.as_slice())
.context(ErrorKind::FileWallet(&"Error writing wallet file"))
.map_err(|e| e.into())
}
// Select the full list of outputs if we are using the select_all strategy.
// Otherwise select just enough outputs to cover the desired amount.
fn select_from(
&self,
amount: u64,
select_all: bool,
outputs: Vec<OutputData>,
) -> Option<Vec<OutputData>> {
let total = outputs.iter().fold(0, |acc, x| acc + x.value);
if total >= amount {
if select_all {
return Some(outputs.iter().cloned().collect());
} else {
let mut selected_amount = 0;
return Some(
outputs
.iter()
.take_while(|out| {
let res = selected_amount < amount;
selected_amount += out.value;
res
})
.cloned()
.collect(),
);
}
} else {
None
}
}
}

View file

@ -11,6 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::sync::{Arc, RwLock};
use bodyparser;
use iron::Handler;
@ -20,20 +21,24 @@ use serde_json;
use core::ser;
use failure::{Fail, ResultExt};
use keychain::Keychain;
use libwallet::types::*;
use receiver::receive_coinbase;
use types::*;
use util;
pub struct CoinbaseHandler {
pub config: WalletConfig,
pub keychain: Keychain,
pub struct CoinbaseHandler<T>
where
T: WalletBackend,
{
pub wallet: Arc<RwLock<T>>,
}
impl CoinbaseHandler {
fn build_coinbase(&self, block_fees: &BlockFees) -> Result<CbData, Error> {
impl<T> CoinbaseHandler<T>
where
T: WalletBackend,
{
fn build_coinbase(&self, wallet: &mut T, block_fees: &BlockFees) -> Result<CbData, Error> {
let (out, kern, block_fees) =
receive_coinbase(&self.config, &self.keychain, block_fees).context(ErrorKind::Node)?;
receive_coinbase(wallet, block_fees).context(ErrorKind::Node)?;
let out_bin = ser::ser_vec(&out).context(ErrorKind::Node)?;
@ -54,12 +59,15 @@ impl CoinbaseHandler {
// TODO - error handling - what to return if we fail to get the wallet lock for
// some reason...
impl Handler for CoinbaseHandler {
impl<T> Handler for CoinbaseHandler<T>
where
T: WalletBackend + Send + Sync + 'static,
{
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let struct_body = req.get::<bodyparser::Struct<BlockFees>>();
let mut wallet = self.wallet.write().unwrap();
if let Ok(Some(block_fees)) = struct_body {
let coinbase = self.build_coinbase(&block_fees)
let coinbase = self.build_coinbase(&mut wallet, &block_fees)
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?;
if let Ok(json) = serde_json::to_string(&coinbase) {
Ok(Response::with((status::Ok, json)))

View file

@ -14,12 +14,14 @@
use checker;
use core::core::amount_to_hr_string;
use keychain::Keychain;
use libwallet::types::*;
use prettytable;
use types::{OutputStatus, WalletConfig, WalletData, WalletInfo};
pub fn show_info(config: &WalletConfig, keychain: &Keychain) {
let wallet_info = retrieve_info(config, keychain);
pub fn show_info<T>(wallet: &mut T)
where
T: WalletBackend,
{
let wallet_info = retrieve_info(wallet);
println!(
"\n____ Wallet Summary Info at {} ({}) ____\n",
wallet_info.current_height, wallet_info.data_confirmed_from
@ -45,13 +47,16 @@ pub fn show_info(config: &WalletConfig, keychain: &Keychain) {
}
}
pub fn retrieve_info(config: &WalletConfig, keychain: &Keychain) -> WalletInfo {
let result = checker::refresh_outputs(&config, &keychain);
pub fn retrieve_info<T>(wallet: &mut T) -> WalletInfo
where
T: WalletBackend,
{
let result = checker::refresh_outputs(wallet);
let ret_val = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
let (current_height, from) = match checker::get_tip_from_node(config) {
let ret_val = wallet.read_wallet(|wallet_data| {
let (current_height, from) = match checker::get_tip_from_node(&wallet_data.node_url()) {
Ok(tip) => (tip.height, "from server node"),
Err(_) => match wallet_data.outputs.values().map(|out| out.height).max() {
Err(_) => match wallet_data.outputs().values().map(|out| out.height).max() {
Some(height) => (height, "from wallet"),
None => (0, "node/wallet unavailable"),
},
@ -61,9 +66,10 @@ pub fn retrieve_info(config: &WalletConfig, keychain: &Keychain) -> WalletInfo {
let mut unconfirmed_total = 0;
let mut locked_total = 0;
for out in wallet_data
.outputs
.outputs()
.clone()
.values()
.filter(|out| out.root_key_id == keychain.root_key_id())
.filter(|out| out.root_key_id == wallet_data.keychain().root_key_id())
{
if out.status == OutputStatus::Unspent {
unspent_total += out.value;

View file

@ -48,21 +48,21 @@ extern crate grin_util as util;
pub mod checker;
pub mod client;
pub mod grinwallet;
pub mod file_wallet;
mod handlers;
mod info;
pub mod libtx;
pub mod libwallet;
mod outputs;
pub mod receiver;
mod restore;
mod sender;
pub mod server;
pub mod types;
pub use file_wallet::{FileWallet, WalletConfig, WalletSeed};
pub use info::{retrieve_info, show_info};
pub use libwallet::types::{BlockFees, CbData, Error, ErrorKind, WalletInfo, WalletReceiveRequest};
pub use outputs::show_outputs;
pub use receiver::WalletReceiver;
pub use restore::restore;
pub use sender::{issue_burn_tx, issue_send_tx};
pub use types::{BlockFees, CbData, Error, ErrorKind, WalletConfig, WalletInfo,
WalletReceiveRequest, WalletSeed};

View file

@ -16,7 +16,7 @@
use keychain::Keychain;
use keychain::blind::BlindingFactor;
use keychain::extkey::Identifier;
use libwallet::error::Error;
use libtx::error::Error;
use util::kernel_sig_msg;
use util::secp::key::{PublicKey, SecretKey};
use util::secp::pedersen::Commitment;

View file

@ -32,7 +32,7 @@ use core::core::pmmr::MerkleProof;
use core::core::{Input, Output, OutputFeatures, ProofMessageElements, Transaction, TxKernel};
use keychain;
use keychain::{BlindSum, BlindingFactor, Identifier, Keychain};
use libwallet::{aggsig, proof};
use libtx::{aggsig, proof};
use util::LOGGER;
/// Context information available to transaction combinators.

View file

@ -12,12 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//! Library specific to the Grin wallet implementation, as distinct from
//! libwallet, which should build transactions without any knowledge of the
//! wallet implementation.
// TODO: Once this is working, extract a set of traits that wallet
// implementations would need to provide
//! Wallet lib... should be used by clients to build wallets and
//! encapsulate all functions needed to build transactions and operate a wallet
#![deny(non_upper_case_globals)]
#![deny(non_camel_case_types)]
@ -25,6 +21,24 @@
#![deny(unused_mut)]
#![warn(missing_docs)]
pub mod keys;
pub mod selection;
pub mod sigcontext;
pub mod aggsig;
pub mod build;
pub mod error;
pub mod proof;
pub mod reward;
pub mod slate;
use core::consensus;
use core::core::Transaction;
const DEFAULT_BASE_FEE: u64 = consensus::MILLI_GRIN;
/// Transaction fee calculation
pub fn tx_fee(input_len: usize, output_len: usize, proof_len: usize, base_fee: Option<u64>) -> u64 {
let use_base_fee = match base_fee {
Some(bf) => bf,
None => DEFAULT_BASE_FEE,
};
(Transaction::weight(input_len, output_len, proof_len) as u64) * use_base_fee
}

View file

@ -17,7 +17,7 @@
use blake2;
use keychain::Keychain;
use keychain::extkey::Identifier;
use libwallet::error::Error;
use libtx::error::Error;
use util::logger::LOGGER;
use util::secp::key::SecretKey;
use util::secp::pedersen::{Commitment, ProofInfo, ProofMessage, RangeProof};

View file

@ -0,0 +1,79 @@
// Copyright 2018 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Builds the blinded output and related signature proof for the block
//! reward.
use keychain;
use core::consensus::reward;
use core::core::KernelFeatures;
use core::core::{Output, OutputFeatures, ProofMessageElements, TxKernel};
use libtx::error::Error;
use libtx::{aggsig, proof};
use util::{kernel_sig_msg, secp, static_secp_instance, LOGGER};
/// output a reward output
pub fn output(
keychain: &keychain::Keychain,
key_id: &keychain::Identifier,
fees: u64,
height: u64,
) -> Result<(Output, TxKernel), Error> {
let value = reward(fees);
let commit = keychain.commit(value, key_id)?;
let msg = ProofMessageElements::new(value, key_id);
trace!(LOGGER, "Block reward - Pedersen Commit is: {:?}", commit,);
let rproof = proof::create(
keychain,
value,
key_id,
commit,
None,
msg.to_proof_message(),
)?;
let output = Output {
features: OutputFeatures::COINBASE_OUTPUT,
commit: commit,
proof: rproof,
};
let secp = static_secp_instance();
let secp = secp.lock().unwrap();
let over_commit = secp.commit_value(reward(fees))?;
let out_commit = output.commitment();
let excess = secp.commit_sum(vec![out_commit], vec![over_commit])?;
// NOTE: Remember we sign the fee *and* the lock_height.
// For a coinbase output the fee is 0 and the lock_height is
// the lock_height of the coinbase output itself,
// not the lock_height of the tx (there is no tx for a coinbase output).
// This output will not be spendable earlier than lock_height (and we sign this
// here).
let msg = secp::Message::from_slice(&kernel_sig_msg(0, height))?;
let sig = aggsig::sign_from_key_id(&secp, keychain, &msg, &key_id)?;
let proof = TxKernel {
features: KernelFeatures::COINBASE_KERNEL,
excess: excess,
excess_sig: sig,
fee: 0,
// lock_height here is the height of the block (tx should be valid immediately)
// *not* the lock_height of the coinbase output (only spendable 1,000 blocks later)
lock_height: height,
};
Ok((output, proof))
}

View file

@ -17,18 +17,15 @@
use rand::thread_rng;
use uuid::Uuid;
use core::consensus;
use core::core::{amount_to_hr_string, Committed, Transaction};
use keychain::{BlindSum, BlindingFactor, Keychain};
use libwallet::error::Error;
use libwallet::{aggsig, build};
use libtx::error::Error;
use libtx::{aggsig, build, tx_fee};
use util::secp::Signature;
use util::secp::key::{PublicKey, SecretKey};
use util::{secp, LOGGER};
const DEFAULT_BASE_FEE: u64 = consensus::MILLI_GRIN;
/// Public data for each participant in the slate
#[derive(Serialize, Deserialize, Debug, Clone)]
@ -380,13 +377,3 @@ impl Slate {
Ok(())
}
}
/// Transaction fee calculation
pub fn tx_fee(input_len: usize, output_len: usize, proof_len: usize, base_fee: Option<u64>) -> u64 {
let use_base_fee = match base_fee {
Some(bf) => bf,
None => DEFAULT_BASE_FEE,
};
(Transaction::weight(input_len, output_len, proof_len) as u64) * use_base_fee
}

View file

@ -12,31 +12,35 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//! Grin Wallet specific key management functions
use keychain::{Identifier, Keychain};
use types::*;
//! Wallet key management functions
use keychain::Identifier;
use libwallet::types::{Error, WalletBackend};
/// Get our next available key
pub fn new_output_key(
config: &WalletConfig,
keychain: &Keychain,
) -> Result<(Identifier, u32), Error> {
WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
next_available_key(&wallet_data, keychain)
})
pub fn new_output_key<T>(wallet: &mut T) -> Result<(Identifier, u32), Error>
where
T: WalletBackend,
{
wallet.with_wallet(|wallet_data| next_available_key(wallet_data))
}
/// Get next available key in the wallet
pub fn next_available_key(wallet_data: &WalletData, keychain: &Keychain) -> (Identifier, u32) {
let root_key_id = keychain.root_key_id();
let derivation = wallet_data.next_child(root_key_id.clone());
let key_id = keychain.derive_key_id(derivation).unwrap();
pub fn next_available_key<T>(wallet: &mut T) -> (Identifier, u32)
where
T: WalletBackend,
{
let root_key_id = wallet.keychain().root_key_id();
let derivation = wallet.next_child(root_key_id.clone());
let key_id = wallet.keychain().derive_key_id(derivation).unwrap();
(key_id, derivation)
}
/// Retrieve an existing key from a wallet
pub fn retrieve_existing_key(wallet_data: &WalletData, key_id: Identifier) -> (Identifier, u32) {
if let Some(existing) = wallet_data.get_output(&key_id) {
pub fn retrieve_existing_key<T>(wallet: &T, key_id: Identifier) -> (Identifier, u32)
where
T: WalletBackend,
{
if let Some(existing) = wallet.get_output(&key_id) {
let key_id = existing.key_id.clone();
let derivation = existing.n_child;
(key_id, derivation)

View file

@ -12,8 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//! Wallet lib... should be used by clients to build wallets and
//! encapsulate all functions needed to build transactions and operate a wallet
//! Library specific to the Grin wallet implementation, as distinct from
//! libwallet, which should build transactions without any knowledge of the
//! wallet implementation.
// TODO: Once this is working, extract a set of traits that wallet
// implementations would need to provide
#![deny(non_upper_case_globals)]
#![deny(non_camel_case_types)]
@ -21,9 +25,7 @@
#![deny(unused_mut)]
#![warn(missing_docs)]
pub mod aggsig;
pub mod build;
pub mod error;
pub mod proof;
pub mod reward;
pub mod transaction;
pub mod keys;
pub mod selection;
pub mod sigcontext;
pub mod types;

View file

@ -19,8 +19,8 @@ use keychain;
use core::consensus::reward;
use core::core::KernelFeatures;
use core::core::{Output, OutputFeatures, ProofMessageElements, TxKernel};
use libwallet::error::Error;
use libwallet::{aggsig, proof};
use libtx::error::Error;
use libtx::{aggsig, proof};
use util::{kernel_sig_msg, secp, static_secp_instance, LOGGER};
/// output a reward output

View file

@ -15,19 +15,18 @@
//! Selection of inputs for building transactions
use failure::ResultExt;
use grinwallet::{keys, sigcontext};
use keychain::{Identifier, Keychain};
use libwallet::{build, transaction};
use types::*;
use keychain::Identifier;
use libtx::{build, tx_fee, slate::Slate};
use libwallet::types::*;
use libwallet::{keys, sigcontext};
/// Initialise a transaction on the sender side, returns a corresponding
/// libwallet transaction slate with the appropriate inputs selected,
/// and saves the private wallet identifiers of our selected outputs
/// into our transaction context
pub fn build_send_tx_slate(
config: &WalletConfig,
keychain: &Keychain,
pub fn build_send_tx_slate<T>(
wallet: &mut T,
num_participants: usize,
amount: u64,
current_height: u64,
@ -37,15 +36,17 @@ pub fn build_send_tx_slate(
selection_strategy_is_use_all: bool,
) -> Result<
(
transaction::Slate,
Slate,
sigcontext::Context,
impl FnOnce() -> Result<(), Error>,
impl FnOnce(&mut T) -> Result<(), Error>,
),
Error,
> {
>
where
T: WalletBackend,
{
let (elems, inputs, change_id, amount, fee) = select_send_tx(
config,
keychain,
wallet,
amount,
current_height,
minimum_confirmations,
@ -55,19 +56,21 @@ pub fn build_send_tx_slate(
)?;
// Create public slate
let mut slate = transaction::Slate::blank(num_participants);
let mut slate = Slate::blank(num_participants);
slate.amount = amount;
slate.height = current_height;
slate.lock_height = lock_height;
slate.fee = fee;
let keychain = wallet.keychain().clone();
let blinding = slate
.add_transaction_elements(keychain, elems)
.add_transaction_elements(&keychain, elems)
.context(ErrorKind::LibWalletError)?;
// Create our own private context
let mut context = sigcontext::Context::new(
keychain.secp(),
blinding.secret_key(keychain.secp()).unwrap(),
wallet.keychain().secp(),
blinding.secret_key(&keychain.secp()).unwrap(),
);
// Store our private identifiers for each input
@ -82,12 +85,11 @@ pub fn build_send_tx_slate(
let lock_inputs = context.get_inputs().clone();
let _lock_outputs = context.get_outputs().clone();
let data_file_dir = config.data_file_dir.clone();
// Return a closure to acquire wallet lock and lock the coins being spent
// so we avoid accidental double spend attempt.
let update_sender_wallet_fn = move || {
WalletData::with_wallet(&data_file_dir, |wallet_data| {
let update_sender_wallet_fn = move |wallet: &mut T| {
wallet.with_wallet(|wallet_data| {
for id in lock_inputs {
let coin = wallet_data.get_output(&id).unwrap().clone();
wallet_data.lock_output(&coin);
@ -107,43 +109,46 @@ pub fn build_send_tx_slate(
/// returning the key of the fresh output and a closure
/// that actually performs the addition of the output to the
/// wallet
pub fn build_recipient_output_with_slate(
config: &WalletConfig,
keychain: &Keychain,
slate: &mut transaction::Slate,
pub fn build_recipient_output_with_slate<T>(
wallet: &mut T,
slate: &mut Slate,
) -> Result<
(
Identifier,
sigcontext::Context,
impl FnOnce() -> Result<(), Error>,
impl FnOnce(&mut T) -> Result<(), Error>,
),
Error,
> {
>
where
T: WalletBackend,
{
// Create a potential output for this transaction
let (key_id, derivation) = keys::new_output_key(config, keychain)?;
let (key_id, derivation) = keys::new_output_key(wallet)?;
let data_file_dir = config.data_file_dir.clone();
let root_key_id = keychain.root_key_id();
let root_key_id = wallet.keychain().root_key_id();
let key_id_inner = key_id.clone();
let amount = slate.amount;
let height = slate.height;
let keychain = wallet.keychain().clone();
let blinding = slate
.add_transaction_elements(keychain, vec![build::output(amount, key_id.clone())])
.add_transaction_elements(&keychain, vec![build::output(amount, key_id.clone())])
.context(ErrorKind::LibWalletError)?;
// Add blinding sum to our context
let mut context = sigcontext::Context::new(
keychain.secp(),
blinding.secret_key(keychain.secp()).unwrap(),
blinding.secret_key(wallet.keychain().secp()).unwrap(),
);
context.add_output(&key_id);
// Create closure that adds the output to recipient's wallet
// (up to the caller to decide when to do)
let wallet_add_fn = move || {
WalletData::with_wallet(&data_file_dir, |wallet_data| {
let wallet_add_fn = move |wallet: &mut T| {
wallet.with_wallet(|wallet_data| {
wallet_data.add_output(OutputData {
root_key_id: root_key_id,
key_id: key_id_inner,
@ -164,9 +169,8 @@ pub fn build_recipient_output_with_slate(
/// Builds a transaction to send to someone from the HD seed associated with the
/// wallet and the amount to send. Handles reading through the wallet data file,
/// selecting outputs to spend and building the change.
pub fn select_send_tx(
config: &WalletConfig,
keychain: &Keychain,
pub fn select_send_tx<T>(
wallet: &mut T,
amount: u64,
current_height: u64,
minimum_confirmations: u64,
@ -182,11 +186,14 @@ pub fn select_send_tx(
u64, // fee
),
Error,
> {
let key_id = keychain.clone().root_key_id();
>
where
T: WalletBackend,
{
let key_id = wallet.keychain().clone().root_key_id();
// select some spendable coins from the wallet
let mut coins = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
let mut coins = wallet.read_wallet(|wallet_data| {
Ok(wallet_data.select_coins(
key_id.clone(),
amount,
@ -198,7 +205,8 @@ pub fn select_send_tx(
})?;
// Get the maximum number of outputs in the wallet
let max_outputs = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
let max_outputs = wallet
.read_wallet(|wallet_data| {
Ok(wallet_data.select_coins(
key_id.clone(),
amount,
@ -207,14 +215,15 @@ pub fn select_send_tx(
max_outputs,
true,
))
})?.len();
})?
.len();
// sender is responsible for setting the fee on the partial tx
// recipient should double check the fee calculation and not blindly trust the
// sender
let mut fee;
// First attempt to spend without change
fee = transaction::tx_fee(coins.len(), 1, coins_proof_count(&coins), None);
fee = tx_fee(coins.len(), 1, coins_proof_count(&coins), None);
let mut total: u64 = coins.iter().map(|c| c.value).sum();
let mut amount_with_fee = amount + fee;
@ -224,7 +233,7 @@ pub fn select_send_tx(
// Check if we need to use a change address
if total > amount_with_fee {
fee = transaction::tx_fee(coins.len(), 2, coins_proof_count(&coins), None);
fee = tx_fee(coins.len(), 2, coins_proof_count(&coins), None);
amount_with_fee = amount + fee;
// Here check if we have enough outputs for the amount including fee otherwise
@ -236,7 +245,7 @@ pub fn select_send_tx(
}
// select some spendable coins from the wallet
coins = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
coins = wallet.read_wallet(|wallet_data| {
Ok(wallet_data.select_coins(
key_id.clone(),
amount_with_fee,
@ -246,15 +255,14 @@ pub fn select_send_tx(
selection_strategy_is_use_all,
))
})?;
fee = transaction::tx_fee(coins.len(), 2, coins_proof_count(&coins), None);
fee = tx_fee(coins.len(), 2, coins_proof_count(&coins), None);
total = coins.iter().map(|c| c.value).sum();
amount_with_fee = amount + fee;
}
}
// build transaction skeleton with inputs and change
let (mut parts, change_key) =
inputs_and_change(&coins, config, keychain, current_height, amount, fee)?;
let (mut parts, change_key) = inputs_and_change(&coins, wallet, current_height, amount, fee)?;
// This is more proof of concept than anything but here we set lock_height
// on tx being sent (based on current chain height via api).
@ -269,14 +277,16 @@ pub fn coins_proof_count(coins: &Vec<OutputData>) -> usize {
}
/// Selects inputs and change for a transaction
pub fn inputs_and_change(
pub fn inputs_and_change<T>(
coins: &Vec<OutputData>,
config: &WalletConfig,
keychain: &Keychain,
wallet: &mut T,
height: u64,
amount: u64,
fee: u64,
) -> Result<(Vec<Box<build::Append>>, Option<Identifier>), Error> {
) -> Result<(Vec<Box<build::Append>>, Option<Identifier>), Error>
where
T: WalletBackend,
{
let mut parts = vec![];
// calculate the total across all inputs, and how much is left
@ -291,7 +301,8 @@ pub fn inputs_and_change(
// build inputs using the appropriate derived key_ids
for coin in coins {
let key_id = keychain
let key_id = wallet
.keychain()
.derive_key_id(coin.n_child)
.context(ErrorKind::Keychain)?;
if coin.is_coinbase {
@ -312,7 +323,8 @@ pub fn inputs_and_change(
let change_key;
if change != 0 {
// track the output representing our change
change_key = WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
change_key = wallet.with_wallet(|wallet_data| {
let keychain = wallet_data.keychain().clone();
let root_key_id = keychain.root_key_id();
let change_derivation = wallet_data.next_child(root_key_id.clone());
let change_key = keychain.derive_key_id(change_derivation).unwrap();

View file

@ -13,7 +13,7 @@
// limitations under the License.
//! Signature context holder helper (may be removed or replaced eventually)
use keychain::extkey::Identifier;
use libwallet::aggsig;
use libtx::aggsig;
use util::secp::key::{PublicKey, SecretKey};
use util::secp::{self, Secp256k1};

View file

@ -0,0 +1,454 @@
// Copyright 2018 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Types and traits that should be provided by a wallet
//! implementation
use std::collections::HashMap;
use std::fmt::{self, Display};
use serde;
use failure::{Backtrace, Context, Fail, ResultExt};
use core::core::hash::Hash;
use core::core::pmmr::MerkleProof;
use keychain::{Identifier, Keychain};
/// TODO:
/// Wallets should implement this backend for their storage. All functions
/// here expect that the wallet instance has instantiated itself or stored
/// whatever credentials it needs
pub trait WalletBackend {
/// Return the keychain being used
fn keychain(&mut self) -> &mut Keychain;
/// Return the URL of the check node
fn node_url(&self) -> &str;
/// Return the outputs directly
fn outputs(&mut self) -> &mut HashMap<String, OutputData>;
/// Allows for reading wallet data (read-only)
fn read_wallet<T, F>(&mut self, f: F) -> Result<T, Error>
where
F: FnOnce(&mut Self) -> Result<T, Error>;
/// Get all outputs from a wallet impl (probably with some sort
/// of query param), read+write. Implementor should save
/// any changes to its data and perform any locking needed
fn with_wallet<T, F>(&mut self, f: F) -> Result<T, Error>
where
F: FnOnce(&mut Self) -> T;
/// Add an output
fn add_output(&mut self, out: OutputData);
/// Delete an output
fn delete_output(&mut self, id: &Identifier);
/// Lock an output
fn lock_output(&mut self, out: &OutputData);
/// get a single output
fn get_output(&self, key_id: &Identifier) -> Option<&OutputData>;
/// Next child ID when we want to create a new output
fn next_child(&self, root_key_id: Identifier) -> u32;
/// Select spendable coins from the wallet
fn select_coins(
&self,
root_key_id: Identifier,
amount: u64,
current_height: u64,
minimum_confirmations: u64,
max_outputs: usize,
select_all: bool,
) -> Vec<OutputData>;
}
/// Information about an output that's being tracked by the wallet. Must be
/// enough to reconstruct the commitment associated with the ouput when the
/// root private key is known.*/
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub struct OutputData {
/// Root key_id that the key for this output is derived from
pub root_key_id: Identifier,
/// Derived key for this output
pub key_id: Identifier,
/// How many derivations down from the root key
pub n_child: u32,
/// Value of the output, necessary to rebuild the commitment
pub value: u64,
/// Current status of the output
pub status: OutputStatus,
/// Height of the output
pub height: u64,
/// Height we are locked until
pub lock_height: u64,
/// Is this a coinbase output? Is it subject to coinbase locktime?
pub is_coinbase: bool,
/// Hash of the block this output originated from.
pub block: Option<BlockIdentifier>,
pub merkle_proof: Option<MerkleProofWrapper>,
}
impl OutputData {
/// Lock a given output to avoid conflicting use
pub fn lock(&mut self) {
self.status = OutputStatus::Locked;
}
/// How many confirmations has this output received?
/// If height == 0 then we are either Unconfirmed or the output was
/// cut-through
/// so we do not actually know how many confirmations this output had (and
/// never will).
pub fn num_confirmations(&self, current_height: u64) -> u64 {
if self.status == OutputStatus::Unconfirmed {
0
} else if self.height == 0 {
0
} else {
// if an output has height n and we are at block n
// then we have a single confirmation (the block it originated in)
1 + (current_height - self.height)
}
}
/// Check if output is eligible to spend based on state and height and
/// confirmations
pub fn eligible_to_spend(&self, current_height: u64, minimum_confirmations: u64) -> bool {
if [OutputStatus::Spent, OutputStatus::Locked].contains(&self.status) {
return false;
} else if self.status == OutputStatus::Unconfirmed && self.is_coinbase {
return false;
} else if self.is_coinbase && self.block.is_none() {
// if we do not have a block hash for coinbase output we cannot spent it
// block index got compacted before we refreshed our wallet?
return false;
} else if self.is_coinbase && self.merkle_proof.is_none() {
// if we do not have a Merkle proof for coinbase output we cannot spent it
// block index got compacted before we refreshed our wallet?
return false;
} else if self.lock_height > current_height {
return false;
} else if self.status == OutputStatus::Unspent
&& self.num_confirmations(current_height) >= minimum_confirmations
{
return true;
} else if self.status == OutputStatus::Unconfirmed && minimum_confirmations == 0 {
return true;
} else {
return false;
}
}
/// Marks this output as unspent if it was previously unconfirmed
pub fn mark_unspent(&mut self) {
match self.status {
OutputStatus::Unconfirmed => self.status = OutputStatus::Unspent,
_ => (),
}
}
pub fn mark_spent(&mut self) {
match self.status {
OutputStatus::Unspent => self.status = OutputStatus::Spent,
OutputStatus::Locked => self.status = OutputStatus::Spent,
_ => (),
}
}
}
/// Status of an output that's being tracked by the wallet. Can either be
/// unconfirmed, spent, unspent, or locked (when it's been used to generate
/// a transaction but we don't have confirmation that the transaction was
/// broadcasted or mined).
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Ord, PartialOrd)]
pub enum OutputStatus {
Unconfirmed,
Unspent,
Locked,
Spent,
}
impl fmt::Display for OutputStatus {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
OutputStatus::Unconfirmed => write!(f, "Unconfirmed"),
OutputStatus::Unspent => write!(f, "Unspent"),
OutputStatus::Locked => write!(f, "Locked"),
OutputStatus::Spent => write!(f, "Spent"),
}
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub struct MerkleProofWrapper(pub MerkleProof);
impl MerkleProofWrapper {
pub fn merkle_proof(&self) -> MerkleProof {
self.0.clone()
}
}
impl serde::ser::Serialize for MerkleProofWrapper {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
serializer.serialize_str(&self.0.to_hex())
}
}
impl<'de> serde::de::Deserialize<'de> for MerkleProofWrapper {
fn deserialize<D>(deserializer: D) -> Result<MerkleProofWrapper, D::Error>
where
D: serde::de::Deserializer<'de>,
{
deserializer.deserialize_str(MerkleProofWrapperVisitor)
}
}
struct MerkleProofWrapperVisitor;
impl<'de> serde::de::Visitor<'de> for MerkleProofWrapperVisitor {
type Value = MerkleProofWrapper;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a merkle proof")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let merkle_proof = MerkleProof::from_hex(s).unwrap();
Ok(MerkleProofWrapper(merkle_proof))
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub struct BlockIdentifier(pub Hash);
impl BlockIdentifier {
pub fn hash(&self) -> Hash {
self.0
}
pub fn from_hex(hex: &str) -> Result<BlockIdentifier, Error> {
let hash = Hash::from_hex(hex).context(ErrorKind::GenericError("Invalid hex"))?;
Ok(BlockIdentifier(hash))
}
}
impl serde::ser::Serialize for BlockIdentifier {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
serializer.serialize_str(&self.0.to_hex())
}
}
impl<'de> serde::de::Deserialize<'de> for BlockIdentifier {
fn deserialize<D>(deserializer: D) -> Result<BlockIdentifier, D::Error>
where
D: serde::de::Deserializer<'de>,
{
deserializer.deserialize_str(BlockIdentifierVisitor)
}
}
struct BlockIdentifierVisitor;
impl<'de> serde::de::Visitor<'de> for BlockIdentifierVisitor {
type Value = BlockIdentifier;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a block hash")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let block_hash = Hash::from_hex(s).unwrap();
Ok(BlockIdentifier(block_hash))
}
}
/// Amount in request to build a coinbase output.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum WalletReceiveRequest {
Coinbase(BlockFees),
PartialTransaction(String),
Finalize(String),
}
/// Fees in block to use for coinbase amount calculation
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BlockFees {
pub fees: u64,
pub height: u64,
pub key_id: Option<Identifier>,
}
impl BlockFees {
pub fn key_id(&self) -> Option<Identifier> {
self.key_id.clone()
}
}
/// Response to build a coinbase output.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CbData {
pub output: String,
pub kernel: String,
pub key_id: String,
}
/// a contained wallet info struct, so automated tests can parse wallet info
/// can add more fields here over time as needed
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct WalletInfo {
// height from which info was taken
pub current_height: u64,
// total amount in the wallet
pub total: u64,
// amount awaiting confirmation
pub amount_awaiting_confirmation: u64,
// confirmed but locked
pub amount_confirmed_but_locked: u64,
// amount currently spendable
pub amount_currently_spendable: u64,
// amount locked by previous transactions
pub amount_locked: u64,
// whether the data was confirmed against a live node
pub data_confirmed: bool,
// node confirming the data
pub data_confirmed_from: String,
}
#[derive(Debug)]
pub struct Error {
inner: Context<ErrorKind>,
}
/// Wallet errors, mostly wrappers around underlying crypto or I/O errors.
#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)]
pub enum ErrorKind {
#[fail(display = "Not enough funds")]
NotEnoughFunds(u64),
#[fail(display = "Fee dispute: sender fee {}, recipient fee {}", sender_fee, recipient_fee)]
FeeDispute { sender_fee: u64, recipient_fee: u64 },
#[fail(display = "Fee exceeds amount: sender amount {}, recipient fee {}", sender_amount,
recipient_fee)]
FeeExceedsAmount {
sender_amount: u64,
recipient_fee: u64,
},
#[fail(display = "Keychain error")]
Keychain,
#[fail(display = "Transaction error")]
Transaction,
#[fail(display = "Secp error")]
Secp,
#[fail(display = "LibWallet error")]
LibWalletError,
#[fail(display = "Wallet data error: {}", _0)]
FileWallet(&'static str),
/// An error in the format of the JSON structures exchanged by the wallet
#[fail(display = "JSON format error")]
Format,
#[fail(display = "I/O error")]
IO,
/// Error when contacting a node through its API
#[fail(display = "Node API error")]
Node,
/// Error originating from hyper.
#[fail(display = "Hyper error")]
Hyper,
/// Error originating from hyper uri parsing.
#[fail(display = "Uri parsing error")]
Uri,
#[fail(display = "Signature error")]
Signature(&'static str),
/// Attempt to use duplicate transaction id in separate transactions
#[fail(display = "Duplicate transaction ID error")]
DuplicateTransactionId,
/// Wallet seed already exists
#[fail(display = "Wallet seed exists error")]
WalletSeedExists,
/// Wallet seed doesn't exist
#[fail(display = "Wallet seed doesn't exist error")]
WalletSeedDoesntExist,
#[fail(display = "Generic error: {}", _0)]
GenericError(&'static str),
}
impl Fail for Error {
fn cause(&self) -> Option<&Fail> {
self.inner.cause()
}
fn backtrace(&self) -> Option<&Backtrace> {
self.inner.backtrace()
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.inner, f)
}
}
impl Error {
pub fn kind(&self) -> ErrorKind {
*self.inner.get_context()
}
}
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Error {
Error {
inner: Context::new(kind),
}
}
}
impl From<Context<ErrorKind>> for Error {
fn from(inner: Context<ErrorKind>) -> Error {
Error { inner: inner }
}
}

View file

@ -14,30 +14,29 @@
use checker;
use core::core;
use keychain::Keychain;
use libwallet::types::*;
use prettytable;
use std::io::prelude::*;
use term;
use types::{OutputStatus, WalletConfig, WalletData};
pub fn show_outputs(config: &WalletConfig, keychain: &Keychain, show_spent: bool) {
let root_key_id = keychain.root_key_id();
let result = checker::refresh_outputs(&config, &keychain);
pub fn show_outputs<T: WalletBackend>(wallet: &mut T, show_spent: bool) {
let root_key_id = wallet.keychain().clone().root_key_id();
let result = checker::refresh_outputs(wallet);
// just read the wallet here, no need for a write lock
let _ = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
let _ = wallet.read_wallet(|wallet_data| {
// get the current height via the api
// if we cannot get the current height use the max height known to the wallet
let current_height = match checker::get_tip_from_node(config) {
let current_height = match checker::get_tip_from_node(wallet_data.node_url()) {
Ok(tip) => tip.height,
Err(_) => match wallet_data.outputs.values().map(|out| out.height).max() {
Err(_) => match wallet_data.outputs().values().map(|out| out.height).max() {
Some(height) => height,
None => 0,
},
};
let mut outputs = wallet_data
.outputs
.outputs()
.values()
.filter(|out| out.root_key_id == root_key_id)
.filter(|out| {

View file

@ -16,6 +16,8 @@
//! receiving money in MimbleWimble requires an interactive exchange, a
//! wallet server that's running at all time is required in many cases.
use std::sync::{Arc, RwLock};
use bodyparser;
use iron::Handler;
use iron::prelude::*;
@ -26,12 +28,10 @@ use api;
use core::consensus::reward;
use core::core::{Output, TxKernel};
use core::global;
use failure::Fail;
use failure::ResultExt;
use grinwallet::{keys, selection};
use keychain::Keychain;
use libwallet::{reward, transaction};
use types::*;
use failure::{Fail, ResultExt};
use libtx::{reward, slate::Slate};
use libwallet::types::*;
use libwallet::{keys, selection};
use util::LOGGER;
/// Dummy wrapper for the hex-encoded serialized transaction.
@ -40,45 +40,57 @@ pub struct TxWrapper {
pub tx_hex: String,
}
fn handle_send(
config: &WalletConfig,
keychain: &Keychain,
slate: &mut transaction::Slate,
) -> Result<(), Error> {
/// Component used to receive coins, implements all the receiving end of the
/// wallet REST API as well as some of the command-line operations.
#[derive(Clone)]
pub struct WalletReceiver<T>
where
T: WalletBackend,
{
pub wallet: Arc<RwLock<T>>,
}
impl<T> WalletReceiver<T>
where
T: WalletBackend,
{
fn handle_send(&self, wallet: &mut T, slate: &mut Slate) -> Result<(), Error> {
// create an output using the amount in the slate
let (_, mut context, receiver_create_fn) =
selection::build_recipient_output_with_slate(config, keychain, slate).unwrap();
selection::build_recipient_output_with_slate(wallet, slate).unwrap();
// fill public keys
let _ = slate
.fill_round_1(&keychain, &mut context.sec_key, &context.sec_nonce, 1)
.fill_round_1(
wallet.keychain(),
&mut context.sec_key,
&context.sec_nonce,
1,
)
.context(ErrorKind::LibWalletError)?;
// perform partial sig
let _ = slate
.fill_round_2(&keychain, &context.sec_key, &context.sec_nonce, 1)
.fill_round_2(wallet.keychain(), &context.sec_key, &context.sec_nonce, 1)
.context(ErrorKind::LibWalletError)?;
// Save output in wallet
let _ = receiver_create_fn();
let _ = receiver_create_fn(wallet);
Ok(())
}
/// Component used to receive coins, implements all the receiving end of the
/// wallet REST API as well as some of the command-line operations.
#[derive(Clone)]
pub struct WalletReceiver {
pub keychain: Keychain,
pub config: WalletConfig,
}
impl Handler for WalletReceiver {
impl<T> Handler for WalletReceiver<T>
where
T: WalletBackend + Send + Sync + 'static,
{
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let struct_body = req.get::<bodyparser::Struct<transaction::Slate>>();
let struct_body = req.get::<bodyparser::Struct<Slate>>();
let mut wallet = self.wallet.write().unwrap();
if let Ok(Some(mut slate)) = struct_body {
let _ = handle_send(&self.config, &self.keychain, &mut slate)
let _ = self.handle_send(&mut wallet, &mut slate)
.map_err(|e| {
error!(
LOGGER,
@ -99,22 +111,24 @@ impl Handler for WalletReceiver {
//TODO: Split up the output creation and the wallet insertion
/// Build a coinbase output and the corresponding kernel
pub fn receive_coinbase(
config: &WalletConfig,
keychain: &Keychain,
pub fn receive_coinbase<T>(
wallet: &mut T,
block_fees: &BlockFees,
) -> Result<(Output, TxKernel, BlockFees), Error> {
let root_key_id = keychain.root_key_id();
) -> Result<(Output, TxKernel, BlockFees), Error>
where
T: WalletBackend,
{
let root_key_id = wallet.keychain().root_key_id();
let height = block_fees.height;
let lock_height = height + global::coinbase_maturity();
// Now acquire the wallet lock and write the new output.
let (key_id, derivation) = WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
let (key_id, derivation) = wallet.with_wallet(|wallet_data| {
let key_id = block_fees.key_id();
let (key_id, derivation) = match key_id {
Some(key_id) => keys::retrieve_existing_key(&wallet_data, key_id),
None => keys::next_available_key(&wallet_data, keychain),
Some(key_id) => keys::retrieve_existing_key(wallet_data, key_id),
None => keys::next_available_key(wallet_data),
};
// track the new output and return the stuff needed for reward
@ -146,8 +160,12 @@ pub fn receive_coinbase(
debug!(LOGGER, "receive_coinbase: {:?}", block_fees);
let (out, kern) =
reward::output(&keychain, &key_id, block_fees.fees, block_fees.height).unwrap();
let (out, kern) = reward::output(
&wallet.keychain(),
&key_id,
block_fees.fees,
block_fees.height,
).unwrap();
/* .context(ErrorKind::Keychain)?; */
Ok((out, kern, block_fees))
}

View file

@ -16,16 +16,15 @@ use byteorder::{BigEndian, ByteOrder};
use core::core::transaction::ProofMessageElements;
use core::global;
use failure::{Fail, ResultExt};
use keychain::{Identifier, Keychain};
use libwallet::proof;
use types::{Error, ErrorKind, MerkleProofWrapper, OutputData, OutputStatus, WalletConfig,
WalletData};
use keychain::Identifier;
use libtx::proof;
use libwallet::types::*;
use util;
use util::LOGGER;
use util::secp::pedersen;
pub fn get_chain_height(config: &WalletConfig) -> Result<u64, Error> {
let url = format!("{}/v1/chain", config.check_node_api_http_addr);
pub fn get_chain_height(node_addr: &str) -> Result<u64, Error> {
let url = format!("{}/v1/chain", node_addr);
match api::client::get::<api::Tip>(url.as_str()) {
Ok(tip) => Ok(tip.height),
@ -34,7 +33,7 @@ pub fn get_chain_height(config: &WalletConfig) -> Result<u64, Error> {
error!(
LOGGER,
"get_chain_height: Restore failed... unable to contact API {}. Error: {}",
config.check_node_api_http_addr,
node_addr,
e
);
Err(e.context(ErrorKind::Node).into())
@ -43,13 +42,10 @@ pub fn get_chain_height(config: &WalletConfig) -> Result<u64, Error> {
}
pub fn get_merkle_proof_for_commit(
config: &WalletConfig,
node_addr: &str,
commit: &str,
) -> Result<MerkleProofWrapper, Error> {
let url = format!(
"{}/v1/txhashset/merkleproof?id={}",
config.check_node_api_http_addr, commit
);
let url = format!("{}/v1/txhashset/merkleproof?id={}", node_addr, commit);
match api::client::get::<api::OutputPrintable>(url.as_str()) {
Ok(output) => Ok(MerkleProofWrapper(output.merkle_proof.unwrap())),
@ -72,17 +68,17 @@ fn coinbase_status(output: &api::OutputPrintable) -> bool {
}
}
pub fn outputs_batch(
config: &WalletConfig,
pub fn outputs_batch<T>(
wallet: &T,
start_height: u64,
max: u64,
) -> Result<api::OutputListing, Error> {
) -> Result<api::OutputListing, Error>
where
T: WalletBackend,
{
let query_param = format!("start_index={}&max={}", start_height, max);
let url = format!(
"{}/v1/txhashset/outputs?{}",
config.check_node_api_http_addr, query_param,
);
let url = format!("{}/v1/txhashset/outputs?{}", wallet.node_url(), query_param,);
match api::client::get::<api::OutputListing>(url.as_str()) {
Ok(o) => Ok(o),
@ -91,7 +87,7 @@ pub fn outputs_batch(
error!(
LOGGER,
"outputs_batch: Restore failed... unable to contact API {}. Error: {}",
config.check_node_api_http_addr,
wallet.node_url(),
e
);
Err(e.context(ErrorKind::Node))?
@ -100,9 +96,8 @@ pub fn outputs_batch(
}
// TODO - wrap the many return values in a struct
fn find_outputs_with_key(
config: &WalletConfig,
keychain: &Keychain,
fn find_outputs_with_key<T: WalletBackend>(
wallet: &mut T,
outputs: Vec<api::OutputPrintable>,
found_key_index: &mut Vec<u32>,
) -> Vec<(
@ -129,10 +124,10 @@ fn find_outputs_with_key(
let max_derivations = 1_000_000;
info!(LOGGER, "Scanning {} outputs", outputs.len(),);
let current_chain_height = get_chain_height(config).unwrap();
let current_chain_height = get_chain_height(wallet.node_url()).unwrap();
// skey doesn't matter in this case
let skey = keychain.derive_key_id(1).unwrap();
let skey = wallet.keychain().derive_key_id(1).unwrap();
for output in outputs.iter().filter(|x| !x.spent) {
// attempt to unwind message from the RP and get a value.. note
// this will only return okay if the value is included in the
@ -140,7 +135,7 @@ fn find_outputs_with_key(
// to unwind in this case will be meaningless. With only the nonce known
// only the first 32 bytes of the recovered message will be accurate
let info = proof::rewind(
keychain,
wallet.keychain(),
&skey,
output.commit,
None,
@ -171,14 +166,14 @@ fn find_outputs_with_key(
/*if found_key_index.contains(&(i as u32)) {
continue;
}*/
let key_id = &keychain.derive_key_id(i as u32).unwrap();
let key_id = &wallet.keychain().derive_key_id(i as u32).unwrap();
if !message.compare_bf_first_8(key_id) {
continue;
}
found = true;
// we have a partial match, let's just confirm
let info = proof::rewind(
keychain,
wallet.keychain(),
key_id,
output.commit,
None,
@ -202,7 +197,8 @@ fn find_outputs_with_key(
info!(LOGGER, "Amount: {}", value);
let commit = keychain
let commit = wallet
.keychain()
.commit_with_key_index(BigEndian::read_u64(&commit_id), i as u32)
.expect("commit with key index");
@ -210,7 +206,8 @@ fn find_outputs_with_key(
let commit_str = util::to_hex(output.commit.as_ref().to_vec());
if is_coinbase {
merkle_proof = Some(get_merkle_proof_for_commit(config, &commit_str).unwrap());
merkle_proof =
Some(get_merkle_proof_for_commit(wallet.node_url(), &commit_str).unwrap());
}
let height = current_chain_height;
@ -248,11 +245,11 @@ fn find_outputs_with_key(
wallet_outputs
}
pub fn restore(config: &WalletConfig, keychain: &Keychain) -> Result<(), Error> {
pub fn restore<T: WalletBackend>(wallet: &mut T) -> Result<(), Error> {
// Don't proceed if wallet.dat has anything in it
let is_empty = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
Ok(wallet_data.outputs.len() == 0)
}).context(ErrorKind::WalletData("could not read wallet"))?;
let is_empty = wallet
.read_wallet(|wallet_data| Ok(wallet_data.outputs().len() == 0))
.context(ErrorKind::FileWallet("could not read wallet"))?;
if !is_empty {
error!(
LOGGER,
@ -271,7 +268,7 @@ pub fn restore(config: &WalletConfig, keychain: &Keychain) -> Result<(), Error>
// this will start here, then lower as outputs are found, moving backwards on
// the chain
loop {
let output_listing = outputs_batch(config, start_index, batch_size)?;
let output_listing = outputs_batch(wallet, start_index, batch_size)?;
info!(
LOGGER,
"Retrieved {} outputs, up to index {}. (Highest index: {})",
@ -280,10 +277,10 @@ pub fn restore(config: &WalletConfig, keychain: &Keychain) -> Result<(), Error>
output_listing.highest_index
);
let _ = WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
let _ = wallet.with_wallet(|wallet_data| {
let keychain = wallet_data.keychain().clone();
let result_vec = find_outputs_with_key(
config,
keychain,
wallet_data,
output_listing.outputs.clone(),
&mut found_key_index,
);

View file

@ -18,11 +18,11 @@ use client;
use core::core::amount_to_hr_string;
use core::ser;
use failure::ResultExt;
use grinwallet::selection;
use keychain::{Identifier, Keychain};
use libwallet::{build, transaction};
use libtx::{build, tx_fee};
use libwallet::selection;
use libwallet::types::*;
use receiver::TxWrapper;
use types::*;
use util;
use util::LOGGER;
@ -30,9 +30,8 @@ use util::LOGGER;
/// wallet
/// Outputs. The destination can be "stdout" (for command line) (currently
/// disabled) or a URL to the recipients wallet receiver (to be implemented).
pub fn issue_send_tx(
config: &WalletConfig,
keychain: &Keychain,
pub fn issue_send_tx<T: WalletBackend>(
wallet: &mut T,
amount: u64,
minimum_confirmations: u64,
dest: String,
@ -48,13 +47,13 @@ pub fn issue_send_tx(
);
}
checker::refresh_outputs(config, keychain)?;
checker::refresh_outputs(wallet)?;
// Get lock height
let chain_tip = checker::get_tip_from_node(config)?;
let chain_tip = checker::get_tip_from_node(wallet.node_url())?;
let current_height = chain_tip.height;
// ensure outputs we're selecting are up to date
checker::refresh_outputs(config, keychain)?;
checker::refresh_outputs(wallet)?;
let lock_height = current_height;
@ -66,8 +65,7 @@ pub fn issue_send_tx(
// This function is just a big helper to do all of that, in theory
// this process can be split up in any way
let (mut slate, mut context, sender_lock_fn) = selection::build_send_tx_slate(
config,
keychain,
wallet,
2,
amount,
current_height,
@ -81,7 +79,12 @@ pub fn issue_send_tx(
// the offset in the slate's transaction kernel, and adds our public key
// information to the slate
let _ = slate
.fill_round_1(keychain, &mut context.sec_key, &context.sec_nonce, 0)
.fill_round_1(
wallet.keychain(),
&mut context.sec_key,
&context.sec_nonce,
0,
)
.unwrap();
let url = format!("{}/v1/receive/transaction", &dest);
@ -109,48 +112,46 @@ pub fn issue_send_tx(
};
let _ = slate
.fill_round_2(keychain, &context.sec_key, &context.sec_nonce, 0)
.fill_round_2(wallet.keychain(), &context.sec_key, &context.sec_nonce, 0)
.context(ErrorKind::LibWalletError)?;
// Final transaction can be built by anyone at this stage
slate.finalize(keychain).context(ErrorKind::LibWalletError)?;
slate
.finalize(wallet.keychain())
.context(ErrorKind::LibWalletError)?;
// So let's post it
let tx_hex = util::to_hex(ser::ser_vec(&slate.tx).unwrap());
let url;
if fluff {
url = format!(
"{}/v1/pool/push?fluff",
config.check_node_api_http_addr.as_str()
);
url = format!("{}/v1/pool/push?fluff", wallet.node_url(),);
} else {
url = format!("{}/v1/pool/push", config.check_node_api_http_addr.as_str());
url = format!("{}/v1/pool/push", wallet.node_url());
}
api::client::post(url.as_str(), &TxWrapper { tx_hex: tx_hex }).context(ErrorKind::Node)?;
// All good so, lock our inputs
sender_lock_fn()?;
sender_lock_fn(wallet)?;
Ok(())
}
pub fn issue_burn_tx(
config: &WalletConfig,
keychain: &Keychain,
pub fn issue_burn_tx<T: WalletBackend>(
wallet: &mut T,
amount: u64,
minimum_confirmations: u64,
max_outputs: usize,
) -> Result<(), Error> {
let keychain = &Keychain::burn_enabled(keychain, &Identifier::zero());
let keychain = &Keychain::burn_enabled(wallet.keychain(), &Identifier::zero());
let chain_tip = checker::get_tip_from_node(config)?;
let chain_tip = checker::get_tip_from_node(wallet.node_url())?;
let current_height = chain_tip.height;
let _ = checker::refresh_outputs(config, keychain);
let _ = checker::refresh_outputs(wallet);
let key_id = keychain.root_key_id();
// select some spendable coins from the wallet
let coins = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
let coins = wallet.read_wallet(|wallet_data| {
Ok(wallet_data.select_coins(
key_id.clone(),
amount,
@ -163,9 +164,8 @@ pub fn issue_burn_tx(
debug!(LOGGER, "selected some coins - {}", coins.len());
let fee = transaction::tx_fee(coins.len(), 2, selection::coins_proof_count(&coins), None);
let (mut parts, _) =
selection::inputs_and_change(&coins, config, keychain, current_height, amount, fee)?;
let fee = tx_fee(coins.len(), 2, selection::coins_proof_count(&coins), None);
let (mut parts, _) = selection::inputs_and_change(&coins, wallet, current_height, amount, fee)?;
// add burn output and fees
parts.push(build::output(amount - fee, Identifier::zero()));
@ -175,7 +175,7 @@ pub fn issue_burn_tx(
tx_burn.validate().context(ErrorKind::Transaction)?;
let tx_hex = util::to_hex(ser::ser_vec(&tx_burn).unwrap());
let url = format!("{}/v1/pool/push", config.check_node_api_http_addr.as_str());
let url = format!("{}/v1/pool/push", wallet.node_url());
let _: () =
api::client::post(url.as_str(), &TxWrapper { tx_hex: tx_hex }).context(ErrorKind::Node)?;
Ok(())
@ -184,7 +184,7 @@ pub fn issue_burn_tx(
#[cfg(test)]
mod test {
use keychain::Keychain;
use libwallet::build;
use libtx::build;
#[test]
// demonstrate that input.commitment == referenced output.commitment

View file

@ -14,25 +14,30 @@
use api::ApiServer;
use handlers::CoinbaseHandler;
use keychain::Keychain;
use iron::Handler;
use libwallet::types::WalletBackend;
use receiver::WalletReceiver;
use types::WalletConfig;
use std::sync::{Arc, RwLock};
use util::LOGGER;
pub fn start_rest_apis(wallet_config: WalletConfig, keychain: Keychain) {
pub fn start_rest_apis<T>(in_wallet: T, api_listen_addr: &str)
where
T: WalletBackend,
CoinbaseHandler<T>: Handler,
WalletReceiver<T>: Handler,
{
info!(
LOGGER,
"Starting the Grin wallet receiving daemon at {}...",
wallet_config.api_listen_addr()
"Starting the Grin wallet receiving daemon at {}...", api_listen_addr
);
let wallet = Arc::new(RwLock::new(in_wallet));
let receive_tx_handler = WalletReceiver {
config: wallet_config.clone(),
keychain: keychain.clone(),
wallet: wallet.clone(),
};
let coinbase_handler = CoinbaseHandler {
config: wallet_config.clone(),
keychain: keychain.clone(),
wallet: wallet.clone(),
};
let router = router!(
@ -42,7 +47,7 @@ pub fn start_rest_apis(wallet_config: WalletConfig, keychain: Keychain) {
let mut apis = ApiServer::new("/v1".to_string());
apis.register_handler(router);
match apis.start(wallet_config.api_listen_addr()) {
match apis.start(api_listen_addr) {
Err(e) => error!(LOGGER, "Failed to start Grin wallet listener: {}.", e),
Ok(_) => info!(LOGGER, "Wallet listener started"),
};

View file

@ -1,803 +0,0 @@
// Copyright 2018 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use blake2;
use rand::{thread_rng, Rng};
use std::cmp::min;
use std::collections::HashMap;
use std::convert::From;
use std::fmt;
use std::fmt::Display;
use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::MAIN_SEPARATOR;
use std::path::Path;
use serde;
use serde_json;
use tokio_core::reactor;
use tokio_retry::Retry;
use tokio_retry::strategy::FibonacciBackoff;
use failure::{Backtrace, Context, Fail, ResultExt};
use core::core::hash::Hash;
use core::core::pmmr::MerkleProof;
use keychain;
use util;
use util::LOGGER;
const DAT_FILE: &'static str = "wallet.dat";
const BCK_FILE: &'static str = "wallet.bck";
const LOCK_FILE: &'static str = "wallet.lock";
const SEED_FILE: &'static str = "wallet.seed";
#[derive(Debug)]
pub struct Error {
inner: Context<ErrorKind>,
}
/// Wallet errors, mostly wrappers around underlying crypto or I/O errors.
#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)]
pub enum ErrorKind {
#[fail(display = "Not enough funds")]
NotEnoughFunds(u64),
#[fail(display = "Fee dispute: sender fee {}, recipient fee {}", sender_fee, recipient_fee)]
FeeDispute { sender_fee: u64, recipient_fee: u64 },
#[fail(display = "Fee exceeds amount: sender amount {}, recipient fee {}", sender_amount,
recipient_fee)]
FeeExceedsAmount {
sender_amount: u64,
recipient_fee: u64,
},
#[fail(display = "Keychain error")]
Keychain,
#[fail(display = "Transaction error")]
Transaction,
#[fail(display = "Secp error")]
Secp,
#[fail(display = "LibWallet error")]
LibWalletError,
#[fail(display = "Wallet data error: {}", _0)]
WalletData(&'static str),
/// An error in the format of the JSON structures exchanged by the wallet
#[fail(display = "JSON format error")]
Format,
#[fail(display = "I/O error")]
IO,
/// Error when contacting a node through its API
#[fail(display = "Node API error")]
Node,
/// Error originating from hyper.
#[fail(display = "Hyper error")]
Hyper,
/// Error originating from hyper uri parsing.
#[fail(display = "Uri parsing error")]
Uri,
#[fail(display = "Signature error")]
Signature(&'static str),
/// Attempt to use duplicate transaction id in separate transactions
#[fail(display = "Duplicate transaction ID error")]
DuplicateTransactionId,
/// Wallet seed already exists
#[fail(display = "Wallet seed exists error")]
WalletSeedExists,
/// Wallet seed doesn't exist
#[fail(display = "Wallet seed doesn't exist error")]
WalletSeedDoesntExist,
#[fail(display = "Generic error: {}", _0)]
GenericError(&'static str),
}
impl Fail for Error {
fn cause(&self) -> Option<&Fail> {
self.inner.cause()
}
fn backtrace(&self) -> Option<&Backtrace> {
self.inner.backtrace()
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.inner, f)
}
}
impl Error {
pub fn kind(&self) -> ErrorKind {
*self.inner.get_context()
}
}
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Error {
Error {
inner: Context::new(kind),
}
}
}
impl From<Context<ErrorKind>> for Error {
fn from(inner: Context<ErrorKind>) -> Error {
Error { inner: inner }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WalletConfig {
// Right now the decision to run or not a wallet is based on the command.
// This may change in the near-future.
// pub enable_wallet: bool,
// The api interface/ip_address that this api server (i.e. this wallet) will run
// by default this is 127.0.0.1 (and will not accept connections from external clients)
pub api_listen_interface: String,
// The port this wallet will run on
pub api_listen_port: u16,
// The api address of a running server node against which transaction inputs
// will be checked during send
pub check_node_api_http_addr: String,
// The directory in which wallet files are stored
pub data_file_dir: String,
}
impl Default for WalletConfig {
fn default() -> WalletConfig {
WalletConfig {
// enable_wallet: false,
api_listen_interface: "127.0.0.1".to_string(),
api_listen_port: 13415,
check_node_api_http_addr: "http://127.0.0.1:13413".to_string(),
data_file_dir: ".".to_string(),
}
}
}
impl WalletConfig {
pub fn api_listen_addr(&self) -> String {
format!("{}:{}", self.api_listen_interface, self.api_listen_port)
}
}
/// Status of an output that's being tracked by the wallet. Can either be
/// unconfirmed, spent, unspent, or locked (when it's been used to generate
/// a transaction but we don't have confirmation that the transaction was
/// broadcasted or mined).
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Ord, PartialOrd)]
pub enum OutputStatus {
Unconfirmed,
Unspent,
Locked,
Spent,
}
impl fmt::Display for OutputStatus {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
OutputStatus::Unconfirmed => write!(f, "Unconfirmed"),
OutputStatus::Unspent => write!(f, "Unspent"),
OutputStatus::Locked => write!(f, "Locked"),
OutputStatus::Spent => write!(f, "Spent"),
}
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub struct MerkleProofWrapper(pub MerkleProof);
impl MerkleProofWrapper {
pub fn merkle_proof(&self) -> MerkleProof {
self.0.clone()
}
}
impl serde::ser::Serialize for MerkleProofWrapper {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
serializer.serialize_str(&self.0.to_hex())
}
}
impl<'de> serde::de::Deserialize<'de> for MerkleProofWrapper {
fn deserialize<D>(deserializer: D) -> Result<MerkleProofWrapper, D::Error>
where
D: serde::de::Deserializer<'de>,
{
deserializer.deserialize_str(MerkleProofWrapperVisitor)
}
}
struct MerkleProofWrapperVisitor;
impl<'de> serde::de::Visitor<'de> for MerkleProofWrapperVisitor {
type Value = MerkleProofWrapper;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a merkle proof")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let merkle_proof = MerkleProof::from_hex(s).unwrap();
Ok(MerkleProofWrapper(merkle_proof))
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub struct BlockIdentifier(pub Hash);
impl BlockIdentifier {
pub fn hash(&self) -> Hash {
self.0
}
pub fn from_hex(hex: &str) -> Result<BlockIdentifier, Error> {
let hash = Hash::from_hex(hex).context(ErrorKind::GenericError("Invalid hex"))?;
Ok(BlockIdentifier(hash))
}
}
impl serde::ser::Serialize for BlockIdentifier {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
serializer.serialize_str(&self.0.to_hex())
}
}
impl<'de> serde::de::Deserialize<'de> for BlockIdentifier {
fn deserialize<D>(deserializer: D) -> Result<BlockIdentifier, D::Error>
where
D: serde::de::Deserializer<'de>,
{
deserializer.deserialize_str(BlockIdentifierVisitor)
}
}
struct BlockIdentifierVisitor;
impl<'de> serde::de::Visitor<'de> for BlockIdentifierVisitor {
type Value = BlockIdentifier;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a block hash")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let block_hash = Hash::from_hex(s).unwrap();
Ok(BlockIdentifier(block_hash))
}
}
/// Information about an output that's being tracked by the wallet. Must be
/// enough to reconstruct the commitment associated with the ouput when the
/// root private key is known.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub struct OutputData {
/// Root key_id that the key for this output is derived from
pub root_key_id: keychain::Identifier,
/// Derived key for this output
pub key_id: keychain::Identifier,
/// How many derivations down from the root key
pub n_child: u32,
/// Value of the output, necessary to rebuild the commitment
pub value: u64,
/// Current status of the output
pub status: OutputStatus,
/// Height of the output
pub height: u64,
/// Height we are locked until
pub lock_height: u64,
/// Is this a coinbase output? Is it subject to coinbase locktime?
pub is_coinbase: bool,
/// Hash of the block this output originated from.
pub block: Option<BlockIdentifier>,
pub merkle_proof: Option<MerkleProofWrapper>,
}
impl OutputData {
/// Lock a given output to avoid conflicting use
fn lock(&mut self) {
self.status = OutputStatus::Locked;
}
/// How many confirmations has this output received?
/// If height == 0 then we are either Unconfirmed or the output was
/// cut-through
/// so we do not actually know how many confirmations this output had (and
/// never will).
pub fn num_confirmations(&self, current_height: u64) -> u64 {
if self.status == OutputStatus::Unconfirmed {
0
} else if self.height == 0 {
0
} else {
// if an output has height n and we are at block n
// then we have a single confirmation (the block it originated in)
1 + (current_height - self.height)
}
}
/// Check if output is eligible to spend based on state and height and
/// confirmations
pub fn eligible_to_spend(&self, current_height: u64, minimum_confirmations: u64) -> bool {
if [OutputStatus::Spent, OutputStatus::Locked].contains(&self.status) {
return false;
} else if self.status == OutputStatus::Unconfirmed && self.is_coinbase {
return false;
} else if self.is_coinbase && self.block.is_none() {
// if we do not have a block hash for coinbase output we cannot spent it
// block index got compacted before we refreshed our wallet?
return false;
} else if self.is_coinbase && self.merkle_proof.is_none() {
// if we do not have a Merkle proof for coinbase output we cannot spent it
// block index got compacted before we refreshed our wallet?
return false;
} else if self.lock_height > current_height {
return false;
} else if self.status == OutputStatus::Unspent
&& self.num_confirmations(current_height) >= minimum_confirmations
{
return true;
} else if self.status == OutputStatus::Unconfirmed && minimum_confirmations == 0 {
return true;
} else {
return false;
}
}
/// Marks this output as unspent if it was previously unconfirmed
pub fn mark_unspent(&mut self) {
match self.status {
OutputStatus::Unconfirmed => self.status = OutputStatus::Unspent,
_ => (),
}
}
pub fn mark_spent(&mut self) {
match self.status {
OutputStatus::Unspent => self.status = OutputStatus::Spent,
OutputStatus::Locked => self.status = OutputStatus::Spent,
_ => (),
}
}
}
#[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()).context(ErrorKind::GenericError("Invalid hex"))?;
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()).context(ErrorKind::Keychain)?;
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).context(ErrorKind::IO)?;
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() {
Err(ErrorKind::WalletSeedExists)?
} else {
let seed = WalletSeed::init_new();
let mut file = File::create(seed_file_path).context(ErrorKind::IO)?;
file.write_all(&seed.to_hex().as_bytes())
.context(ErrorKind::IO)?;
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).context(ErrorKind::IO)?;
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).context(ErrorKind::IO)?;
let mut buffer = String::new();
file.read_to_string(&mut buffer).context(ErrorKind::IO)?;
let wallet_seed = WalletSeed::from_hex(&buffer)?;
Ok(wallet_seed)
} else {
error!(
LOGGER,
"wallet seed file {} could not be opened (grin wallet init). \
Run \"grin wallet init\" to initialize a new wallet.",
seed_file_path
);
Err(ErrorKind::WalletSeedDoesntExist)?
}
}
}
/// Wallet information tracking all our outputs. Based on HD derivation and
/// avoids storing any key data, only storing output amounts and child index.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct WalletData {
pub outputs: HashMap<String, OutputData>,
}
impl WalletData {
/// Allows for reading wallet data (without needing to acquire the write
/// lock).
pub fn read_wallet<T, F>(data_file_dir: &str, f: F) -> Result<T, Error>
where
F: FnOnce(&WalletData) -> Result<T, Error>,
{
// open the wallet readonly and do what needs to be done with it
let data_file_path = &format!("{}{}{}", data_file_dir, MAIN_SEPARATOR, DAT_FILE);
let wdat = WalletData::read_or_create(data_file_path)?;
f(&wdat)
}
/// Allows the reading and writing of the wallet data within a file lock.
/// Just provide a closure taking a mutable WalletData. The lock should
/// be held for as short a period as possible to avoid contention.
/// Note that due to the impossibility to do an actual file lock easily
/// across operating systems, this just creates a lock file with a "should
/// not exist" option.
pub fn with_wallet<T, F>(data_file_dir: &str, f: F) -> Result<T, Error>
where
F: FnOnce(&mut WalletData) -> T,
{
// create directory if it doesn't exist
fs::create_dir_all(data_file_dir).unwrap_or_else(|why| {
info!(LOGGER, "! {:?}", why.kind());
});
let data_file_path = &format!("{}{}{}", data_file_dir, MAIN_SEPARATOR, DAT_FILE);
let backup_file_path = &format!("{}{}{}", data_file_dir, MAIN_SEPARATOR, BCK_FILE);
let lock_file_path = &format!("{}{}{}", data_file_dir, MAIN_SEPARATOR, LOCK_FILE);
info!(LOGGER, "Acquiring wallet lock ...");
let action = || {
trace!(LOGGER, "making lock file for wallet lock");
fs::create_dir(lock_file_path)
};
// use tokio_retry to cleanly define some retry logic
let mut core = reactor::Core::new().unwrap();
let retry_strategy = FibonacciBackoff::from_millis(1000).take(10);
let retry_future = Retry::spawn(core.handle(), retry_strategy, action);
let retry_result = core.run(retry_future);
match retry_result {
Ok(_) => {}
Err(e) => {
error!(
LOGGER,
"Failed to acquire wallet lock file (multiple retries)",
);
return Err(
e.context(ErrorKind::WalletData("Failed to acquire lock file"))
.into(),
);
}
}
// We successfully acquired the lock - so do what needs to be done.
let mut wdat = WalletData::read_or_create(data_file_path)?;
wdat.write(backup_file_path)?;
let res = f(&mut wdat);
wdat.write(data_file_path)?;
// delete the lock file
fs::remove_dir(lock_file_path).context(ErrorKind::WalletData(
"Could not remove wallet lock file. Maybe insufficient rights?",
))?;
info!(LOGGER, "... released wallet lock");
Ok(res)
}
/// Read the wallet data or created a brand new one if it doesn't exist yet
fn read_or_create(data_file_path: &str) -> Result<WalletData, Error> {
if Path::new(data_file_path).exists() {
WalletData::read(data_file_path)
} else {
// just create a new instance, it will get written afterward
Ok(WalletData {
outputs: HashMap::new(),
})
}
}
/// Read output_data vec from disk.
fn read_outputs(data_file_path: &str) -> Result<Vec<OutputData>, Error> {
let data_file = File::open(data_file_path)
.context(ErrorKind::WalletData(&"Could not open wallet file"))?;
serde_json::from_reader(data_file).map_err(|e| {
e.context(ErrorKind::WalletData(&"Error reading wallet file "))
.into()
})
}
/// Populate wallet_data with output_data from disk.
fn read(data_file_path: &str) -> Result<WalletData, Error> {
let outputs = WalletData::read_outputs(data_file_path)?;
let mut wallet_data = WalletData {
outputs: HashMap::new(),
};
for out in outputs {
wallet_data.add_output(out);
}
Ok(wallet_data)
}
/// Write the wallet data to disk.
fn write(&self, data_file_path: &str) -> Result<(), Error> {
let mut data_file = File::create(data_file_path)
.map_err(|e| e.context(ErrorKind::WalletData(&"Could not create ")))?;
let mut outputs = self.outputs.values().collect::<Vec<_>>();
outputs.sort();
let res_json = serde_json::to_vec_pretty(&outputs)
.map_err(|e| e.context(ErrorKind::WalletData("Error serializing wallet data")))?;
data_file
.write_all(res_json.as_slice())
.context(ErrorKind::WalletData(&"Error writing wallet file"))
.map_err(|e| e.into())
}
/// Append a new output data to the wallet data.
/// TODO - we should check for overwriting here - only really valid for
/// unconfirmed coinbase
pub fn add_output(&mut self, out: OutputData) {
self.outputs.insert(out.key_id.to_hex(), out.clone());
}
// TODO - careful with this, only for Unconfirmed (maybe Locked)?
pub fn delete_output(&mut self, id: &keychain::Identifier) {
self.outputs.remove(&id.to_hex());
}
/// Lock an output data.
/// TODO - we should track identifier on these outputs (not just n_child)
pub fn lock_output(&mut self, out: &OutputData) {
if let Some(out_to_lock) = self.outputs.get_mut(&out.key_id.to_hex()) {
if out_to_lock.value == out.value {
out_to_lock.lock()
}
}
}
pub fn get_output(&self, key_id: &keychain::Identifier) -> Option<&OutputData> {
self.outputs.get(&key_id.to_hex())
}
/// Select spendable coins from the wallet.
/// Default strategy is to spend the maximum number of outputs (up to
/// max_outputs). Alternative strategy is to spend smallest outputs first
/// but only as many as necessary. When we introduce additional strategies
/// we should pass something other than a bool in.
pub fn select_coins(
&self,
root_key_id: keychain::Identifier,
amount: u64,
current_height: u64,
minimum_confirmations: u64,
max_outputs: usize,
select_all: bool,
) -> Vec<OutputData> {
// first find all eligible outputs based on number of confirmations
let mut eligible = self.outputs
.values()
.filter(|out| {
out.root_key_id == root_key_id
&& out.eligible_to_spend(current_height, minimum_confirmations)
})
.cloned()
.collect::<Vec<OutputData>>();
// sort eligible outputs by increasing value
eligible.sort_by_key(|out| out.value);
// use a sliding window to identify potential sets of possible outputs to spend
// Case of amount > total amount of max_outputs(500):
// The limit exists because by default, we always select as many inputs as
// possible in a transaction, to reduce both the Output set and the fees.
// But that only makes sense up to a point, hence the limit to avoid being too
// greedy. But if max_outputs(500) is actually not enought to cover the whole
// amount, the wallet should allow going over it to satisfy what the user
// wants to send. So the wallet considers max_outputs more of a soft limit.
if eligible.len() > max_outputs {
for window in eligible.windows(max_outputs) {
let windowed_eligibles = window.iter().cloned().collect::<Vec<_>>();
if let Some(outputs) = self.select_from(amount, select_all, windowed_eligibles) {
return outputs;
}
}
// Not exist in any window of which total amount >= amount.
// Then take coins from the smallest one up to the total amount of selected
// coins = the amount.
if let Some(outputs) = self.select_from(amount, false, eligible.clone()) {
debug!(
LOGGER,
"Extending maximum number of outputs. {} outputs selected.",
outputs.len()
);
return outputs;
}
} else {
if let Some(outputs) = self.select_from(amount, select_all, eligible.clone()) {
return outputs;
}
}
// we failed to find a suitable set of outputs to spend,
// so return the largest amount we can so we can provide guidance on what is
// possible
eligible.reverse();
eligible.iter().take(max_outputs).cloned().collect()
}
// Select the full list of outputs if we are using the select_all strategy.
// Otherwise select just enough outputs to cover the desired amount.
fn select_from(
&self,
amount: u64,
select_all: bool,
outputs: Vec<OutputData>,
) -> Option<Vec<OutputData>> {
let total = outputs.iter().fold(0, |acc, x| acc + x.value);
if total >= amount {
if select_all {
return Some(outputs.iter().cloned().collect());
} else {
let mut selected_amount = 0;
return Some(
outputs
.iter()
.take_while(|out| {
let res = selected_amount < amount;
selected_amount += out.value;
res
})
.cloned()
.collect(),
);
}
} else {
None
}
}
/// Next child index when we want to create a new output.
pub fn next_child(&self, root_key_id: keychain::Identifier) -> u32 {
let mut max_n = 0;
for out in self.outputs.values() {
if max_n < out.n_child && out.root_key_id == root_key_id {
max_n = out.n_child;
}
}
max_n + 1
}
}
/// Amount in request to build a coinbase output.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum WalletReceiveRequest {
Coinbase(BlockFees),
PartialTransaction(String),
Finalize(String),
}
/// Fees in block to use for coinbase amount calculation
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BlockFees {
pub fees: u64,
pub height: u64,
pub key_id: Option<keychain::Identifier>,
}
impl BlockFees {
pub fn key_id(&self) -> Option<keychain::Identifier> {
self.key_id.clone()
}
}
/// Response to build a coinbase output.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CbData {
pub output: String,
pub kernel: String,
pub key_id: String,
}
/// a contained wallet info struct, so automated tests can parse wallet info
/// can add more fields here over time as needed
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct WalletInfo {
// height from which info was taken
pub current_height: u64,
// total amount in the wallet
pub total: u64,
// amount awaiting confirmation
pub amount_awaiting_confirmation: u64,
// confirmed but locked
pub amount_confirmed_but_locked: u64,
// amount currently spendable
pub amount_currently_spendable: u64,
// amount locked by previous transactions
pub amount_locked: u64,
// whether the data was confirmed against a live node
pub data_confirmed: bool,
// node confirming the data
pub data_confirmed_from: String,
}

View file

@ -27,21 +27,19 @@ use chain::Chain;
use core::core::hash::Hashed;
use core::core::{Output, OutputFeatures, OutputIdentifier, Transaction, TxKernel};
use core::{consensus, global, pow};
use keychain::Keychain;
use wallet::types::{BlockIdentifier, Error, ErrorKind, MerkleProofWrapper, OutputStatus,
WalletConfig, WalletData};
use wallet::file_wallet::*;
use wallet::libwallet::types::*;
use wallet::{checker, BlockFees};
use util::secp::pedersen;
/// Mostly for testing, refreshes output state against a local chain instance
/// instead of via an http API call
pub fn refresh_output_state_local(
config: &WalletConfig,
keychain: &Keychain,
pub fn refresh_output_state_local<T: WalletBackend>(
wallet: &mut T,
chain: &chain::Chain,
) -> Result<(), Error> {
let wallet_outputs = checker::map_wallet_outputs(config, keychain)?;
let wallet_outputs = checker::map_wallet_outputs(wallet)?;
let chain_outputs: Vec<Option<api::Output>> = wallet_outputs
.keys()
.map(|k| match get_output_local(chain, &k) {
@ -58,7 +56,7 @@ pub fn refresh_output_state_local(
None => {}
}
}
checker::apply_api_outputs(config, &wallet_outputs, &api_outputs)?;
checker::apply_api_outputs(wallet, &wallet_outputs, &api_outputs)?;
Ok(())
}
@ -66,18 +64,18 @@ pub fn refresh_output_state_local(
/// (0:total, 1:amount_awaiting_confirmation, 2:confirmed but locked,
/// 3:currently_spendable, 4:locked total) TODO: Should be a wallet lib
/// function with nicer return values
pub fn get_wallet_balances(
config: &WalletConfig,
keychain: &Keychain,
pub fn get_wallet_balances<T: WalletBackend>(
wallet: &mut T,
height: u64,
) -> Result<(u64, u64, u64, u64, u64), Error> {
let ret_val = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
let ret_val = wallet.read_wallet(|wallet_data| {
let mut unspent_total = 0;
let mut unspent_but_locked_total = 0;
let mut unconfirmed_total = 0;
let mut locked_total = 0;
let keychain = wallet_data.keychain().clone();
for out in wallet_data
.outputs
.outputs()
.values()
.filter(|out| out.root_key_id == keychain.root_key_id())
{
@ -144,10 +142,10 @@ pub fn add_block_with_reward(chain: &Chain, txs: Vec<&Transaction>, reward: (Out
/// adds a reward output to a wallet, includes that reward in a block, mines
/// the block and adds it to the chain, with option transactions included.
/// Helpful for building up precise wallet balances for testing.
pub fn award_block_to_wallet(
pub fn award_block_to_wallet<T: WalletBackend>(
chain: &Chain,
txs: Vec<&Transaction>,
wallet: &(WalletConfig, Keychain),
wallet: &mut T,
) {
let prev = chain.head_header().unwrap();
let fee_amt = txs.iter().map(|tx| tx.fee()).sum();
@ -156,7 +154,7 @@ pub fn award_block_to_wallet(
key_id: None,
height: prev.height + 1,
};
let coinbase_tx = wallet::receiver::receive_coinbase(&wallet.0, &wallet.1, &fees);
let coinbase_tx = wallet::receiver::receive_coinbase(wallet, &fees);
let (coinbase_tx, fees) = match coinbase_tx {
Ok(t) => ((t.0, t.1), t.2),
Err(e) => {
@ -168,9 +166,9 @@ pub fn award_block_to_wallet(
let output_id = OutputIdentifier::from_output(&coinbase_tx.0.clone());
let m_proof = chain.get_merkle_proof(&output_id, &chain.head_header().unwrap());
let block_id = Some(BlockIdentifier(chain.head_header().unwrap().hash()));
let _ = WalletData::with_wallet(&wallet.0.data_file_dir, |wallet_data| {
let _ = wallet.with_wallet(|wallet_data| {
if let Entry::Occupied(mut output) = wallet_data
.outputs
.outputs()
.entry(fees.key_id.as_ref().unwrap().to_hex())
{
let output = output.get_mut();
@ -181,23 +179,20 @@ pub fn award_block_to_wallet(
}
/// adds many block rewards to a wallet, no transactions
pub fn award_blocks_to_wallet(
chain: &Chain,
wallet: &(WalletConfig, Keychain),
num_rewards: usize,
) {
pub fn award_blocks_to_wallet<T: WalletBackend>(chain: &Chain, wallet: &mut T, num_rewards: usize) {
for _ in 0..num_rewards {
award_block_to_wallet(chain, vec![], wallet);
}
}
/// Create a new wallet in a particular directory
pub fn create_wallet(dir: &str) -> (WalletConfig, Keychain) {
pub fn create_wallet(dir: &str) -> FileWallet {
let mut wallet_config = WalletConfig::default();
wallet_config.data_file_dir = String::from(dir);
let wallet_seed = wallet::WalletSeed::init_file(&wallet_config).unwrap();
let keychain = wallet_seed
.derive_keychain("")
.expect("Failed to derive keychain from seed file and passphrase.");
(wallet_config, keychain)
FileWallet::new(wallet_config.clone(), keychain)
.unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config))
}

View file

@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//! libwallet specific tests
//! libtx specific tests
extern crate grin_core as core;
extern crate grin_keychain as keychain;
extern crate grin_util as util;
@ -24,8 +24,8 @@ use keychain::{BlindSum, BlindingFactor, Keychain};
use util::secp::key::{PublicKey, SecretKey};
use util::secp::pedersen::ProofMessage;
use util::{kernel_sig_msg, secp};
use wallet::grinwallet::sigcontext;
use wallet::libwallet::{aggsig, proof};
use wallet::libtx::{aggsig, proof};
use wallet::libwallet::sigcontext;
use rand::thread_rng;

View file

@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//! tests for transactions building within libwallet
//! tests for transactions building within libtx
extern crate grin_chain as chain;
extern crate grin_core as core;
extern crate grin_keychain as keychain;
@ -34,7 +34,7 @@ use chain::types::*;
use core::global::ChainTypes;
use core::{global, pow};
use util::LOGGER;
use wallet::grinwallet::selection;
use wallet::libwallet::selection;
fn clean_output_dir(test_dir: &str) {
let _ = fs::remove_dir_all(test_dir);
@ -58,11 +58,11 @@ fn setup(test_dir: &str, chain_dir: &str) -> Chain {
#[test]
fn build_transaction() {
let chain = setup("test_output", "build_transaction_2/.grin");
let wallet1 = common::create_wallet("test_output/build_transaction_2/wallet1");
let wallet2 = common::create_wallet("test_output/build_transaction_2/wallet2");
common::award_blocks_to_wallet(&chain, &wallet1, 10);
let mut wallet1 = common::create_wallet("test_output/build_transaction_2/wallet1");
let mut wallet2 = common::create_wallet("test_output/build_transaction_2/wallet2");
common::award_blocks_to_wallet(&chain, &mut wallet1, 10);
// Wallet 1 has 600 Grins, wallet 2 has 0. Create a transaction that sends
// 300 Grins from wallet 1 to wallet 2, using libwallet
// 300 Grins from wallet 1 to wallet 2, using libtx
// Get lock height
let chain_tip = chain.head().unwrap();
@ -70,7 +70,7 @@ fn build_transaction() {
let min_confirmations = 3;
// ensure outputs we're selecting are up to date
let res = common::refresh_output_state_local(&wallet1.0, &wallet1.1, &chain);
let res = common::refresh_output_state_local(&mut wallet1, &chain);
if let Err(e) = res {
panic!("Unable to refresh sender wallet outputs: {}", e);
@ -85,8 +85,7 @@ fn build_transaction() {
// This function is just a big helper to do all of that, in theory
// this process can be split up in any way
let (mut slate, mut sender_context, sender_lock_fn) = selection::build_send_tx_slate(
&wallet1.0,
&wallet1.1,
&mut wallet1,
2,
amount,
chain_tip.height,
@ -101,7 +100,7 @@ fn build_transaction() {
// information to the slate
let _ = slate
.fill_round_1(
&wallet1.1,
&wallet1.keychain,
&mut sender_context.sec_key,
&sender_context.sec_nonce,
0,
@ -117,11 +116,11 @@ fn build_transaction() {
// Identifier Again, this is a helper to do that, which returns a closure that
// creates the output when we're satisified the process was successful
let (_, mut recp_context, receiver_create_fn) =
selection::build_recipient_output_with_slate(&wallet2.0, &wallet2.1, &mut slate).unwrap();
selection::build_recipient_output_with_slate(&mut wallet2, &mut slate).unwrap();
let _ = slate
.fill_round_1(
&wallet2.1,
&wallet2.keychain,
&mut recp_context.sec_key,
&recp_context.sec_nonce,
1,
@ -129,11 +128,11 @@ fn build_transaction() {
.unwrap();
// recipient can proceed to round 2 now
let _ = receiver_create_fn();
let _ = receiver_create_fn(&mut wallet2);
let _ = slate
.fill_round_2(
&wallet2.1,
&wallet1.keychain,
&recp_context.sec_key,
&recp_context.sec_nonce,
1,
@ -150,7 +149,7 @@ fn build_transaction() {
// SENDER Part 3: Sender confirmation
let _ = slate
.fill_round_2(
&wallet1.1,
&wallet1.keychain,
&sender_context.sec_key,
&sender_context.sec_nonce,
0,
@ -162,7 +161,7 @@ fn build_transaction() {
debug!(LOGGER, "{:?}", slate);
// Final transaction can be built by anyone at this stage
let res = slate.finalize(&wallet1.1);
let res = slate.finalize(&wallet1.keychain);
if let Err(e) = res {
panic!("Error creating final tx: {:?}", e);
@ -173,30 +172,30 @@ fn build_transaction() {
debug!(LOGGER, "{:?}", slate.tx);
// All okay, lock sender's outputs
let _ = sender_lock_fn();
let _ = sender_lock_fn(&mut wallet1);
// Insert this transaction into a new block, then mine till confirmation
common::award_block_to_wallet(&chain, vec![&slate.tx], &wallet1);
common::award_blocks_to_wallet(&chain, &wallet1, 5);
common::award_block_to_wallet(&chain, vec![&slate.tx], &mut wallet1);
common::award_blocks_to_wallet(&chain, &mut wallet1, 5);
// Refresh wallets
let res = common::refresh_output_state_local(&wallet2.0, &wallet2.1, &chain);
let res = common::refresh_output_state_local(&mut wallet2, &chain);
if let Err(e) = res {
panic!("Error refreshing output state for wallet: {:?}", e);
}
// check recipient wallet
let chain_tip = chain.head().unwrap();
let balances = common::get_wallet_balances(&wallet2.0, &wallet2.1, chain_tip.height).unwrap();
let balances = common::get_wallet_balances(&mut wallet2, chain_tip.height).unwrap();
assert_eq!(balances.3, 300_000_000_000);
// check sender wallet
let res = common::refresh_output_state_local(&wallet1.0, &wallet1.1, &chain);
let res = common::refresh_output_state_local(&mut wallet1, &chain);
if let Err(e) = res {
panic!("Error refreshing output state for wallet: {:?}", e);
}
let balances = common::get_wallet_balances(&wallet1.0, &wallet1.1, chain_tip.height).unwrap();
let balances = common::get_wallet_balances(&mut wallet1, chain_tip.height).unwrap();
println!("tip height: {:?}", chain_tip.height);
println!("Sender balances: {:?}", balances);
// num blocks * grins per block, and wallet1 mined the fee