mirror of
https://github.com/mimblewimble/grin.git
synced 2025-02-01 17:01:09 +03:00
[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:
parent
601c9e769e
commit
82ed280625
41 changed files with 1512 additions and 1249 deletions
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
465
wallet/src/file_wallet.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)))
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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;
|
|
@ -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.
|
|
@ -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
|
||||
}
|
|
@ -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};
|
79
wallet/src/libtx/reward.rs
Normal file
79
wallet/src/libtx/reward.rs
Normal 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))
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
|
@ -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};
|
||||
|
454
wallet/src/libwallet/types.rs
Normal file
454
wallet/src/libwallet/types.rs
Normal 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 }
|
||||
}
|
||||
}
|
|
@ -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| {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"),
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue