diff --git a/servers/tests/framework/mod.rs b/servers/tests/framework/mod.rs index d85d393cb..2ffbfa434 100644 --- a/servers/tests/framework/mod.rs +++ b/servers/tests/framework/mod.rs @@ -27,7 +27,7 @@ use std::default::Default; use std::sync::{Arc, Mutex}; use std::{fs, thread, time}; -use wallet::{FileWallet, WalletConfig}; +use wallet::{HTTPWalletClient, FileWallet, WalletConfig}; /// Just removes all results from previous runs pub fn clean_all_output(test_name_dir: &str) { @@ -262,12 +262,14 @@ impl LocalServerContainer { let _ = fs::create_dir_all(self.wallet_config.clone().data_file_dir); let r = wallet::WalletSeed::init_file(&self.wallet_config); + let client = HTTPWalletClient::new(&self.wallet_config.check_node_api_http_addr); + if let Err(e) = r { //panic!("Error initting wallet seed: {}", e); } - let wallet: FileWallet = - FileWallet::new(self.wallet_config.clone(), "").unwrap_or_else(|e| { + let wallet: FileWallet = + FileWallet::new(self.wallet_config.clone(), "", client).unwrap_or_else(|e| { panic!( "Error creating wallet: {:?} Config: {:?}", e, self.wallet_config @@ -300,7 +302,8 @@ impl LocalServerContainer { let keychain: keychain::ExtKeychain = wallet_seed .derive_keychain("") .expect("Failed to derive keychain from seed file and passphrase."); - let mut wallet = FileWallet::new(config.clone(), "") + let client = HTTPWalletClient::new(&config.check_node_api_http_addr); + let mut wallet = FileWallet::new(config.clone(), "", client) .unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config)); wallet.keychain = Some(keychain); let _ = wallet::libwallet::internal::updater::refresh_outputs(&mut wallet); @@ -324,9 +327,11 @@ impl LocalServerContainer { let keychain: keychain::ExtKeychain = wallet_seed .derive_keychain("") .expect("Failed to derive keychain from seed file and passphrase."); + + let client = HTTPWalletClient::new(&config.check_node_api_http_addr); let max_outputs = 500; - let mut wallet = FileWallet::new(config.clone(), "") + let mut wallet = FileWallet::new(config.clone(), "", client) .unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config)); wallet.keychain = Some(keychain); let _ = diff --git a/src/bin/grin.rs b/src/bin/grin.rs index e69c9f90d..1fb295e26 100644 --- a/src/bin/grin.rs +++ b/src/bin/grin.rs @@ -53,7 +53,8 @@ use core::core::amount_to_hr_string; use core::global; use tui::ui; use util::{init_logger, LoggingConfig, LOGGER}; -use wallet::{libwallet, wallet_db_exists, FileWallet, LMDBBackend, WalletConfig, WalletInst}; +use wallet::{libwallet, wallet_db_exists, FileWallet, + HTTPWalletClient, LMDBBackend, WalletConfig, WalletInst}; // include build information pub mod built_info { @@ -553,9 +554,10 @@ fn instantiate_wallet( wallet_config: WalletConfig, passphrase: &str, use_db: bool, -) -> Box> { +) -> Box> { + let client = HTTPWalletClient::new(&wallet_config.check_node_api_http_addr); if use_db { - let db_wallet = LMDBBackend::new(wallet_config.clone(), "").unwrap_or_else(|e| { + let db_wallet = LMDBBackend::new(wallet_config.clone(), "", client).unwrap_or_else(|e| { panic!( "Error creating DB wallet: {} Config: {:?}", e, wallet_config @@ -564,7 +566,7 @@ fn instantiate_wallet( info!(LOGGER, "Using LMDB Backend for wallet"); Box::new(db_wallet) } else { - let file_wallet = FileWallet::new(wallet_config.clone(), passphrase) + let file_wallet = FileWallet::new(wallet_config.clone(), passphrase, client) .unwrap_or_else(|e| panic!("Error creating wallet: {} Config: {:?}", e, wallet_config)); info!(LOGGER, "Using File Backend for wallet"); Box::new(file_wallet) @@ -603,7 +605,8 @@ fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) { wallet::WalletSeed::init_file(&wallet_config).expect("Failed to init wallet seed file."); info!(LOGGER, "Wallet seed file created"); if use_db { - let _: LMDBBackend = LMDBBackend::new(wallet_config.clone(), "") + let client = HTTPWalletClient::new(&wallet_config.check_node_api_http_addr); + let _: LMDBBackend = LMDBBackend::new(wallet_config.clone(), "", client) .unwrap_or_else(|e| { panic!( "Error creating DB wallet: {} Config: {:?}", diff --git a/wallet/src/client.rs b/wallet/src/client.rs index 152b27fa1..5702cf386 100644 --- a/wallet/src/client.rs +++ b/wallet/src/client.rs @@ -28,10 +28,188 @@ use tokio_core::reactor; use api; use error::{Error, ErrorKind}; +use libwallet; use libtx::slate::Slate; use util::secp::pedersen; use util::{self, LOGGER}; +#[derive(Clone)] +pub struct HTTPWalletClient { + node_url: String, +} + +impl HTTPWalletClient { + /// Create a new client that will communicate with the given grin node + pub fn new(node_url:&str) -> HTTPWalletClient { + HTTPWalletClient { + node_url: node_url.to_owned(), + } + } +} + +impl WalletClient for HTTPWalletClient { + fn node_url(&self) -> &str { + &self.node_url + } + + /// Call the wallet API to create a coinbase output for the given block_fees. + /// Will retry based on default "retry forever with backoff" behavior. + fn create_coinbase(&self, dest: &str, block_fees: &BlockFees) -> Result { + let url = format!("{}/v1/wallet/foreign/build_coinbase", dest); + match single_create_coinbase(&url, &block_fees) { + Err(e) => { + error!( + LOGGER, + "Failed to get coinbase from {}. Run grin wallet listen?", url + ); + error!(LOGGER, "Underlying Error: {}", e.cause().unwrap()); + error!(LOGGER, "Backtrace: {}", e.backtrace().unwrap()); + Err(libwallet::ErrorKind::ClientCallback("Failed to get coinbase"))? + } + Ok(res) => Ok(res), + } + } + + /// Send the slate to a listening wallet instance + fn send_tx_slate(&self, dest: &str, slate: &Slate) -> Result { + if &dest[..4] != "http" { + let err_str = format!( + "dest formatted as {} but send -d expected stdout or http://IP:port", + dest + ); + error!(LOGGER, "{}", err_str,); + Err(libwallet::ErrorKind::Uri)? + } + let url = format!("{}/v1/wallet/foreign/receive_tx", dest); + debug!(LOGGER, "Posting transaction slate to {}", url); + + let mut core = reactor::Core::new() + .context(libwallet::ErrorKind::ClientCallback("Sending transaction: Initialise API"))?; + let client = hyper::Client::new(&core.handle()); + + let url_pool = url.to_owned(); + + let mut req = Request::new( + Method::Post, + url_pool.parse::() + .context(libwallet::ErrorKind::ClientCallback("Sending transaction: parsing URL"))? + ); + req.headers_mut().set(ContentType::json()); + let json = serde_json::to_string(&slate) + .context(libwallet::ErrorKind::ClientCallback("Sending transaction: parsing response"))?; + req.set_body(json); + + let work = client.request(req).and_then(|res| { + res.body().concat2().and_then(move |body| { + let slate: Slate = + serde_json::from_slice(&body).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + Ok(slate) + }) + }); + let res = core.run(work) + .context(libwallet::ErrorKind::ClientCallback("Sending transaction: posting request"))?; + Ok(res) + } + + + /// Posts a transaction to a grin node + fn post_tx(&self, tx: &TxWrapper, fluff: bool) -> Result<(), libwallet::Error> { + let url; + let dest = self.node_url(); + if fluff { + url = format!("{}/v1/pool/push?fluff", dest); + } else { + url = format!("{}/v1/pool/push", dest); + } + api::client::post(url.as_str(), tx) + .context(libwallet::ErrorKind::ClientCallback("Posting transaction to node"))?; + Ok(()) + } + + /// Return the chain tip from a given node + fn get_chain_height(&self) -> Result { + let addr = self.node_url(); + let url = format!("{}/v1/chain", addr); + let res = api::client::get::(url.as_str()) + .context(libwallet::ErrorKind::ClientCallback("Getting chain height from node"))?; + Ok(res.height) + } + + /// Retrieve outputs from node + fn get_outputs_from_node( + &self, + wallet_outputs: Vec, + ) -> Result, libwallet::Error> { + let addr = self.node_url(); + // build the necessary query params - + // ?id=xxx&id=yyy&id=zzz + let query_params: Vec = wallet_outputs + .iter() + .map(|commit| format!("id={}", util::to_hex(commit.as_ref().to_vec()))) + .collect(); + + // build a map of api outputs by commit so we can look them up efficiently + let mut api_outputs: HashMap = HashMap::new(); + + for query_chunk in query_params.chunks(1000) { + let url = format!("{}/v1/chain/outputs/byids?{}", addr, query_chunk.join("&"),); + + match api::client::get::>(url.as_str()) { + Ok(outputs) => for out in outputs { + api_outputs.insert(out.commit.commit(), util::to_hex(out.commit.to_vec())); + }, + Err(e) => { + // if we got anything other than 200 back from server, don't attempt to refresh + // the wallet data after + return Err(libwallet::ErrorKind::ClientCallback("Error from server"))?; + } + } + } + Ok(api_outputs) + } + + fn get_outputs_by_pmmr_index( + &self, + start_height: u64, + max_outputs: u64, + ) -> Result< + ( + u64, + u64, + Vec<(pedersen::Commitment, pedersen::RangeProof, bool)>, + ), + libwallet::Error, + > { + let addr = self.node_url(); + let query_param = format!("start_index={}&max={}", start_height, max_outputs); + + let url = format!("{}/v1/txhashset/outputs?{}", addr, query_param,); + + let mut api_outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool)> = Vec::new(); + + match api::client::get::(url.as_str()) { + Ok(o) => { + for out in o.outputs { + let is_coinbase = match out.output_type { + api::OutputType::Coinbase => true, + api::OutputType::Transaction => false, + }; + api_outputs.push((out.commit, out.range_proof().unwrap(), is_coinbase)); + } + + Ok((o.highest_index, o.last_retrieved_index, api_outputs)) + } + Err(e) => { + // if we got anything other than 200 back from server, bye + error!( + LOGGER, + "get_outputs_by_pmmr_index: unable to contact API {}. Error: {}", addr, e + ); + Err(libwallet::ErrorKind::ClientCallback("unable to contact api"))? + } + } + } +} /// Call the wallet API to create a coinbase output for the given block_fees. /// Will retry based on default "retry forever with backoff" behavior. pub fn create_coinbase(dest: &str, block_fees: &BlockFees) -> Result { @@ -44,49 +222,12 @@ pub fn create_coinbase(dest: &str, block_fees: &BlockFees) -> Result Ok(res), } } -/// Send the slate to a listening wallet instance -pub fn send_tx_slate(dest: &str, slate: &Slate) -> Result { - if &dest[..4] != "http" { - let err_str = format!( - "dest formatted as {} but send -d expected stdout or http://IP:port", - dest - ); - error!(LOGGER, "{}", err_str,); - Err(ErrorKind::Uri)? - } - let url = format!("{}/v1/wallet/foreign/receive_tx", dest); - debug!(LOGGER, "Posting transaction slate to {}", url); - - let mut core = reactor::Core::new().context(ErrorKind::Hyper)?; - let client = hyper::Client::new(&core.handle()); - - let url_pool = url.to_owned(); - - let mut req = Request::new( - Method::Post, - url_pool.parse::().context(ErrorKind::Hyper)?, - ); - req.headers_mut().set(ContentType::json()); - let json = serde_json::to_string(&slate).context(ErrorKind::Hyper)?; - req.set_body(json); - - let work = client.request(req).and_then(|res| { - res.body().concat2().and_then(move |body| { - let slate: Slate = - serde_json::from_slice(&body).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - Ok(slate) - }) - }); - let res = core.run(work).context(ErrorKind::Hyper)?; - Ok(res) -} - /// Makes a single request to the wallet API to create a new coinbase output. fn single_create_coinbase(url: &str, block_fees: &BlockFees) -> Result { let mut core = @@ -116,94 +257,3 @@ fn single_create_coinbase(url: &str, block_fees: &BlockFees) -> Result Result<(), Error> { - let url; - if fluff { - url = format!("{}/v1/pool/push?fluff", dest); - } else { - url = format!("{}/v1/pool/push", dest); - } - api::client::post(url.as_str(), tx)?; - Ok(()) -} - -/// Return the chain tip from a given node -pub fn get_chain_height(addr: &str) -> Result { - let url = format!("{}/v1/chain", addr); - let res = api::client::get::(url.as_str())?; - Ok(res.height) -} - -/// Retrieve outputs from node -pub fn get_outputs_from_node( - addr: &str, - wallet_outputs: Vec, -) -> Result, Error> { - // build the necessary query params - - // ?id=xxx&id=yyy&id=zzz - let query_params: Vec = wallet_outputs - .iter() - .map(|commit| format!("id={}", util::to_hex(commit.as_ref().to_vec()))) - .collect(); - - // build a map of api outputs by commit so we can look them up efficiently - let mut api_outputs: HashMap = HashMap::new(); - - for query_chunk in query_params.chunks(1000) { - let url = format!("{}/v1/chain/outputs/byids?{}", addr, query_chunk.join("&"),); - - match api::client::get::>(url.as_str()) { - Ok(outputs) => for out in outputs { - api_outputs.insert(out.commit.commit(), util::to_hex(out.commit.to_vec())); - }, - Err(e) => { - // if we got anything other than 200 back from server, don't attempt to refresh - // the wallet data after - return Err(e)?; - } - } - } - Ok(api_outputs) -} - -pub fn get_outputs_by_pmmr_index( - addr: &str, - start_height: u64, - max_outputs: u64, -) -> Result< - ( - u64, - u64, - Vec<(pedersen::Commitment, pedersen::RangeProof, bool)>, - ), - Error, -> { - let query_param = format!("start_index={}&max={}", start_height, max_outputs); - - let url = format!("{}/v1/txhashset/outputs?{}", addr, query_param,); - - let mut api_outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool)> = Vec::new(); - - match api::client::get::(url.as_str()) { - Ok(o) => { - for out in o.outputs { - let is_coinbase = match out.output_type { - api::OutputType::Coinbase => true, - api::OutputType::Transaction => false, - }; - api_outputs.push((out.commit, out.range_proof().unwrap(), is_coinbase)); - } - - Ok((o.highest_index, o.last_retrieved_index, api_outputs)) - } - Err(e) => { - // if we got anything other than 200 back from server, bye - error!( - LOGGER, - "get_outputs_by_pmmr_index: unable to contact API {}. Error: {}", addr, e - ); - Err(e)? - } - } -} diff --git a/wallet/src/file_wallet.rs b/wallet/src/file_wallet.rs index e87e3c426..253d2a685 100644 --- a/wallet/src/file_wallet.rs +++ b/wallet/src/file_wallet.rs @@ -139,9 +139,11 @@ impl<'a> Drop for FileBatch<'a> { /// 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 { +pub struct FileWallet { /// Keychain pub keychain: Option, + /// Client implementation + pub client: C, /// Configuration pub config: WalletConfig, /// passphrase: TODO better ways of dealing with this other than storing @@ -162,8 +164,9 @@ pub struct FileWallet { pub details_bak_path: String, } -impl WalletBackend for FileWallet +impl WalletBackend for FileWallet where + C: WalletClient, K: Keychain, { /// Initialize with whatever stored credentials we have @@ -190,6 +193,11 @@ where self.keychain.as_mut().unwrap() } + /// Return the client being used + fn client(&mut self) -> &mut C { + &mut self.client + } + fn iter<'a>(&'a self) -> Box + 'a> { Box::new(self.outputs.values().cloned()) } @@ -320,98 +328,16 @@ where } } -impl WalletClient for FileWallet { - /// Return URL for check node - fn node_url(&self) -> &str { - &self.config.check_node_api_http_addr - } - - /// Call the wallet API to create a coinbase transaction - fn create_coinbase(&self, block_fees: &BlockFees) -> Result { - let res = client::create_coinbase(self.node_url(), block_fees); - match res { - Ok(r) => Ok(r), - Err(e) => { - let message = format!("{}", e.cause().unwrap()); - error!( - LOGGER, - "Create Coinbase: Communication error: {},{}", - e.cause().unwrap(), - e.backtrace().unwrap() - ); - Err(libwallet::ErrorKind::WalletComms(message))? - } - } - } - - /// Send a transaction slate to another listening wallet and return result - fn send_tx_slate(&self, addr: &str, slate: &Slate) -> Result { - let res = client::send_tx_slate(addr, slate); - match res { - Ok(r) => Ok(r), - Err(e) => { - let message = format!("{}", e.cause().unwrap()); - error!( - LOGGER, - "Send TX Slate: Communication error: {},{}", - e.cause().unwrap(), - e.backtrace().unwrap() - ); - Err(libwallet::ErrorKind::WalletComms(message))? - } - } - } - - /// Posts a transaction to a grin node - fn post_tx(&self, tx: &TxWrapper, fluff: bool) -> Result<(), libwallet::Error> { - let res = client::post_tx(self.node_url(), tx, fluff).context(libwallet::ErrorKind::Node)?; - Ok(res) - } - - /// retrieves the current tip from the specified grin node - fn get_chain_height(&self) -> Result { - let res = client::get_chain_height(self.node_url()).context(libwallet::ErrorKind::Node)?; - Ok(res) - } - - /// retrieve a list of outputs from the specified grin node - /// need "by_height" and "by_id" variants - fn get_outputs_from_node( - &self, - wallet_outputs: Vec, - ) -> Result, libwallet::Error> { - let res = client::get_outputs_from_node(self.node_url(), wallet_outputs) - .context(libwallet::ErrorKind::Node)?; - Ok(res) - } - - /// Outputs by PMMR index - fn get_outputs_by_pmmr_index( - &self, - start_height: u64, - max_outputs: u64, - ) -> Result< - ( - u64, - u64, - Vec<(pedersen::Commitment, pedersen::RangeProof, bool)>, - ), - libwallet::Error, - > { - let res = client::get_outputs_by_pmmr_index(self.node_url(), start_height, max_outputs) - .context(libwallet::ErrorKind::Node)?; - Ok(res) - } -} - -impl FileWallet +impl FileWallet where + C: WalletClient, K: Keychain, { /// Create a new FileWallet instance - pub fn new(config: WalletConfig, passphrase: &str) -> Result { + pub fn new(config: WalletConfig, passphrase: &str, client: C) -> Result { let mut retval = FileWallet { keychain: None, + client: client, config: config.clone(), passphrase: String::from(passphrase), outputs: HashMap::new(), diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 50fd81778..b5a27aeb5 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -56,10 +56,10 @@ pub mod libwallet; pub mod lmdb_wallet; mod types; -pub use client::create_coinbase; pub use error::{Error, ErrorKind}; pub use file_wallet::FileWallet; +pub use client::{create_coinbase, HTTPWalletClient}; pub use libwallet::controller; -pub use libwallet::types::{BlockFees, CbData, WalletInfo, WalletInst}; +pub use libwallet::types::{BlockFees, CbData, WalletBackend, WalletClient, WalletInfo, WalletInst}; pub use lmdb_wallet::{wallet_db_exists, LMDBBackend}; pub use types::{WalletConfig, WalletSeed}; diff --git a/wallet/src/libwallet/api.rs b/wallet/src/libwallet/api.rs index fec15a850..e0823b716 100644 --- a/wallet/src/libwallet/api.rs +++ b/wallet/src/libwallet/api.rs @@ -31,27 +31,31 @@ use util::{self, LOGGER}; /// Wrapper around internal API functions, containing a reference to /// the wallet/keychain that they're acting upon -pub struct APIOwner<'a, W: ?Sized, K> +pub struct APIOwner<'a, W: ?Sized, C, K> where - W: 'a + WalletBackend + WalletClient, + W: 'a + WalletBackend, + C: WalletClient, K: Keychain, { /// Wallet, contains its keychain (TODO: Split these up into 2 traits /// perhaps) pub wallet: &'a mut Box, phantom: PhantomData, + phantom_c: PhantomData, } -impl<'a, W: ?Sized, K> APIOwner<'a, W, K> +impl<'a, W: ?Sized, C, K> APIOwner<'a, W, C, K> where - W: 'a + WalletBackend + WalletClient, + W: 'a + WalletBackend, + C: WalletClient, K: Keychain, { /// Create new API instance - pub fn new(wallet_in: &'a mut Box) -> APIOwner<'a, W, K> { + pub fn new(wallet_in: &'a mut Box) -> APIOwner<'a, W, C, K> { APIOwner { wallet: wallet_in, phantom: PhantomData, + phantom_c: PhantomData, } } @@ -103,7 +107,7 @@ where selection_strategy_is_use_all, )?; - let mut slate = match self.wallet.send_tx_slate(dest, &slate) { + let mut slate = match self.wallet.client().send_tx_slate(dest, &slate) { Ok(s) => s, Err(e) => { error!( @@ -119,7 +123,7 @@ where // All good here, so let's post it let tx_hex = util::to_hex(ser::ser_vec(&slate.tx).unwrap()); - self.wallet.post_tx(&TxWrapper { tx_hex: tx_hex }, fluff)?; + self.wallet.client().post_tx(&TxWrapper { tx_hex: tx_hex }, fluff)?; // All good here, lock our inputs lock_fn(self.wallet)?; @@ -140,7 +144,7 @@ where max_outputs, )?; let tx_hex = util::to_hex(ser::ser_vec(&tx_burn).unwrap()); - self.wallet.post_tx(&TxWrapper { tx_hex: tx_hex }, false)?; + self.wallet.client().post_tx(&TxWrapper { tx_hex: tx_hex }, false)?; Ok(()) } @@ -151,7 +155,7 @@ where /// Retrieve current height from node pub fn node_height(&mut self) -> Result<(u64, bool), Error> { - match self.wallet.get_chain_height() { + match self.wallet.client().get_chain_height() { Ok(height) => Ok((height, true)), Err(_) => { let outputs = self.retrieve_outputs(true, false)?; @@ -175,27 +179,31 @@ where /// Wrapper around external API functions, intended to communicate /// with other parties -pub struct APIForeign<'a, W: ?Sized, K> +pub struct APIForeign<'a, W: ?Sized, C, K> where - W: WalletBackend + WalletClient + 'a, + W: 'a + WalletBackend, + C: WalletClient, K: Keychain, { /// Wallet, contains its keychain (TODO: Split these up into 2 traits /// perhaps) pub wallet: &'a mut Box, phantom: PhantomData, + phantom_c: PhantomData, } -impl<'a, W: ?Sized, K> APIForeign<'a, W, K> +impl<'a, W: ?Sized, C, K> APIForeign<'a, W, C, K> where - W: WalletBackend + WalletClient, + W: WalletBackend, + C: WalletClient, K: Keychain, { /// Create new API instance - pub fn new(wallet_in: &'a mut Box) -> APIForeign { + pub fn new(wallet_in: &'a mut Box) -> APIForeign { APIForeign { wallet: wallet_in, phantom: PhantomData, + phantom_c: PhantomData, } } diff --git a/wallet/src/libwallet/controller.rs b/wallet/src/libwallet/controller.rs index f71020729..4bba1ad7b 100644 --- a/wallet/src/libwallet/controller.rs +++ b/wallet/src/libwallet/controller.rs @@ -32,7 +32,7 @@ use keychain::Keychain; use libtx::slate::Slate; use libwallet::api::{APIForeign, APIOwner}; use libwallet::types::{ - BlockFees, CbData, OutputData, SendTXArgs, WalletBackend, WalletClient, WalletInfo, WalletInst, + BlockFees, CbData, OutputData, SendTXArgs, WalletBackend, WalletClient, WalletInfo }; use libwallet::{Error, ErrorKind}; @@ -40,10 +40,11 @@ use util::LOGGER; /// Instantiate wallet Owner API for a single-use (command line) call /// Return a function containing a loaded API context to call -pub fn owner_single_use(wallet: Box, f: F) -> Result<(), Error> +pub fn owner_single_use(wallet: Box, f: F) -> Result<(), Error> where - T: WalletBackend + WalletClient, - F: FnOnce(&mut APIOwner) -> Result<(), Error>, + T: WalletBackend, + F: FnOnce(&mut APIOwner) -> Result<(), Error>, + C: WalletClient, K: Keychain, { let mut w = wallet; @@ -55,10 +56,11 @@ where /// Instantiate wallet Foreign API for a single-use (command line) call /// Return a function containing a loaded API context to call -pub fn foreign_single_use(wallet: &mut Box, f: F) -> Result<(), Error> +pub fn foreign_single_use(wallet: &mut Box, f: F) -> Result<(), Error> where - T: WalletBackend + WalletClient, - F: FnOnce(&mut APIForeign) -> Result<(), Error>, + T: WalletBackend, + F: FnOnce(&mut APIForeign) -> Result<(), Error>, + C: WalletClient, K: Keychain, { wallet.open_with_credentials()?; @@ -69,11 +71,12 @@ where /// Listener version, providing same API but listening for requests on a /// port and wrapping the calls -pub fn owner_listener(wallet: Box, addr: &str) -> Result<(), Error> +pub fn owner_listener(wallet: Box, addr: &str) -> Result<(), Error> where - T: WalletInst, - OwnerAPIGetHandler: Handler, - OwnerAPIPostHandler: Handler, + T: WalletBackend, + OwnerAPIGetHandler: Handler, + OwnerAPIPostHandler: Handler, + C: WalletClient, K: Keychain, { let wallet_arc = Arc::new(Mutex::new(wallet)); @@ -101,10 +104,11 @@ where /// Listener version, providing same API but listening for requests on a /// port and wrapping the calls -pub fn foreign_listener(wallet: Box, addr: &str) -> Result<(), Error> +pub fn foreign_listener(wallet: Box, addr: &str) -> Result<(), Error> where - T: WalletInst, - ForeignAPIHandler: Handler, + T: WalletBackend, + ForeignAPIHandler: Handler, + C: WalletClient, K: Keychain, { let api_handler = ForeignAPIHandler::new(Arc::new(Mutex::new(wallet))); @@ -126,33 +130,37 @@ where } /// API Handler/Wrapper for owner functions -pub struct OwnerAPIGetHandler +pub struct OwnerAPIGetHandler where - T: WalletBackend + WalletClient, + T: WalletBackend, + C: WalletClient, K: Keychain, { /// Wallet instance pub wallet: Arc>>, phantom: PhantomData, + phantom_c: PhantomData, } -impl OwnerAPIGetHandler +impl OwnerAPIGetHandler where - T: WalletBackend + WalletClient, + T: WalletBackend, + C: WalletClient, K: Keychain, { /// Create a new owner API handler for GET methods - pub fn new(wallet: Arc>>) -> OwnerAPIGetHandler { + pub fn new(wallet: Arc>>) -> OwnerAPIGetHandler { OwnerAPIGetHandler { wallet, phantom: PhantomData, + phantom_c: PhantomData, } } fn retrieve_outputs( &self, req: &mut Request, - api: &mut APIOwner, + api: &mut APIOwner, ) -> Result<(bool, Vec), Error> { let mut update_from_node = false; if let Ok(params) = req.get_ref::() { @@ -166,7 +174,7 @@ where fn retrieve_summary_info( &self, req: &mut Request, - api: &mut APIOwner, + api: &mut APIOwner, ) -> Result<(bool, WalletInfo), Error> { let mut update_from_node = false; if let Ok(params) = req.get_ref::() { @@ -180,12 +188,12 @@ where fn node_height( &self, _req: &mut Request, - api: &mut APIOwner, + api: &mut APIOwner, ) -> Result<(u64, bool), Error> { api.node_height() } - fn handle_request(&self, req: &mut Request, api: &mut APIOwner) -> IronResult { + fn handle_request(&self, req: &mut Request, api: &mut APIOwner) -> IronResult { let url = req.url.clone(); let path_elems = url.path(); match *path_elems.last().unwrap() { @@ -203,9 +211,10 @@ where } } -impl Handler for OwnerAPIGetHandler +impl Handler for OwnerAPIGetHandler where - T: WalletBackend + WalletClient + Send + Sync + 'static, + T: WalletBackend + Send + Sync + 'static, + C: WalletClient + 'static, K: Keychain + 'static, { fn handle(&self, req: &mut Request) -> IronResult { @@ -235,30 +244,34 @@ where } /// Handles all owner API POST requests -pub struct OwnerAPIPostHandler +pub struct OwnerAPIPostHandler where - T: WalletBackend, + T: WalletBackend, + C: WalletClient, K: Keychain, { /// Wallet instance pub wallet: Arc>>, phantom: PhantomData, + phantom_c: PhantomData, } -impl OwnerAPIPostHandler +impl OwnerAPIPostHandler where - T: WalletBackend + WalletClient, + T: WalletBackend, + C: WalletClient, K: Keychain, { /// New POST handler - pub fn new(wallet: Arc>>) -> OwnerAPIPostHandler { + pub fn new(wallet: Arc>>) -> OwnerAPIPostHandler { OwnerAPIPostHandler { wallet, phantom: PhantomData, + phantom_c: PhantomData, } } - fn issue_send_tx(&self, req: &mut Request, api: &mut APIOwner) -> Result<(), Error> { + fn issue_send_tx(&self, req: &mut Request, api: &mut APIOwner) -> Result<(), Error> { let struct_body = req.get::>(); match struct_body { Ok(Some(args)) => api.issue_send_tx( @@ -284,12 +297,12 @@ where } } - fn issue_burn_tx(&self, _req: &mut Request, api: &mut APIOwner) -> Result<(), Error> { + fn issue_burn_tx(&self, _req: &mut Request, api: &mut APIOwner) -> Result<(), Error> { // TODO: Args api.issue_burn_tx(60, 10, 1000) } - fn handle_request(&self, req: &mut Request, api: &mut APIOwner) -> Result { + fn handle_request(&self, req: &mut Request, api: &mut APIOwner) -> Result { let url = req.url.clone(); let path_elems = url.path(); match *path_elems.last().unwrap() { @@ -323,9 +336,10 @@ where } } -impl Handler for OwnerAPIPostHandler +impl Handler for OwnerAPIPostHandler where - T: WalletBackend + WalletClient + Send + Sync + 'static, + T: WalletBackend + Send + Sync + 'static, + C: WalletClient + 'static, K: Keychain + 'static, { fn handle(&self, req: &mut Request) -> IronResult { @@ -373,33 +387,37 @@ impl Handler for OwnerAPIOptionsHandler where { } /// API Handler/Wrapper for foreign functions -pub struct ForeignAPIHandler +pub struct ForeignAPIHandler where - T: WalletBackend + WalletClient, + T: WalletBackend, + C: WalletClient, K: Keychain, { /// Wallet instance pub wallet: Arc>>, phantom: PhantomData, + phantom_c: PhantomData, } -impl ForeignAPIHandler +impl ForeignAPIHandler where - T: WalletBackend + WalletClient, + T: WalletBackend, + C: WalletClient, K: Keychain, { /// create a new api handler - pub fn new(wallet: Arc>>) -> ForeignAPIHandler { + pub fn new(wallet: Arc>>) -> ForeignAPIHandler { ForeignAPIHandler { wallet, phantom: PhantomData, + phantom_c: PhantomData, } } fn build_coinbase( &self, req: &mut Request, - api: &mut APIForeign, + api: &mut APIForeign, ) -> Result { let struct_body = req.get::>(); match struct_body { @@ -419,7 +437,7 @@ where } } - fn receive_tx(&self, req: &mut Request, api: &mut APIForeign) -> Result { + fn receive_tx(&self, req: &mut Request, api: &mut APIForeign) -> Result { let struct_body = req.get::>(); if let Ok(Some(mut slate)) = struct_body { api.receive_tx(&mut slate)?; @@ -432,7 +450,7 @@ where fn handle_request( &self, req: &mut Request, - api: &mut APIForeign, + api: &mut APIForeign, ) -> IronResult { let url = req.url.clone(); let path_elems = url.path(); @@ -448,9 +466,10 @@ where } } } -impl Handler for ForeignAPIHandler +impl Handler for ForeignAPIHandler where - T: WalletBackend + WalletClient + Send + Sync + 'static, + T: WalletBackend + Send + Sync + 'static, + C: WalletClient + Send + Sync + 'static, K: Keychain + 'static, { fn handle(&self, req: &mut Request) -> IronResult { diff --git a/wallet/src/libwallet/error.rs b/wallet/src/libwallet/error.rs index 12c4a31b3..cbc26f3c2 100644 --- a/wallet/src/libwallet/error.rs +++ b/wallet/src/libwallet/error.rs @@ -72,6 +72,10 @@ pub enum ErrorKind { #[fail(display = "Transaction error")] Transaction(transaction::Error), + /// API Error + #[fail(display = "Client Callback Error: {}", _0)] + ClientCallback(&'static str), + /// Secp Error #[fail(display = "Secp error")] Secp, diff --git a/wallet/src/libwallet/internal/keys.rs b/wallet/src/libwallet/internal/keys.rs index e7529d320..5860c5ef9 100644 --- a/wallet/src/libwallet/internal/keys.rs +++ b/wallet/src/libwallet/internal/keys.rs @@ -15,12 +15,13 @@ //! Wallet key management functions use keychain::{Identifier, Keychain}; use libwallet::error::Error; -use libwallet::types::WalletBackend; +use libwallet::types::{WalletBackend, WalletClient}; /// Get next available key in the wallet -pub fn next_available_key(wallet: &mut T) -> Result<(Identifier, u32), Error> +pub fn next_available_key(wallet: &mut T) -> Result<(Identifier, u32), Error> where - T: WalletBackend, + T: WalletBackend, + C: WalletClient, K: Keychain, { let root_key_id = wallet.keychain().root_key_id(); @@ -30,12 +31,13 @@ where } /// Retrieve an existing key from a wallet -pub fn retrieve_existing_key( +pub fn retrieve_existing_key( wallet: &T, key_id: Identifier, ) -> Result<(Identifier, u32), Error> where - T: WalletBackend, + T: WalletBackend, + C: WalletClient, K: Keychain, { let existing = wallet.get(&key_id)?; diff --git a/wallet/src/libwallet/internal/restore.rs b/wallet/src/libwallet/internal/restore.rs index a3354ed61..69bc87420 100644 --- a/wallet/src/libwallet/internal/restore.rs +++ b/wallet/src/libwallet/internal/restore.rs @@ -41,12 +41,13 @@ struct OutputResult { pub blinding: SecretKey, } -fn identify_utxo_outputs( +fn identify_utxo_outputs( wallet: &mut T, outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool)>, ) -> Result, Error> where - T: WalletBackend + WalletClient, + T: WalletBackend, + C: WalletClient, K: Keychain, { let mut wallet_outputs: Vec = Vec::new(); @@ -56,7 +57,7 @@ where "Scanning {} outputs in the current Grin utxo set", outputs.len(), ); - let current_chain_height = wallet.get_chain_height()?; + let current_chain_height = wallet.client().get_chain_height()?; for output in outputs.iter() { let (commit, proof, is_coinbase) = output; @@ -96,13 +97,14 @@ where /// Attempts to populate a list of outputs with their /// correct child indices based on the root key -fn populate_child_indices( +fn populate_child_indices( wallet: &mut T, outputs: &mut Vec, max_derivations: u32, ) -> Result<(), Error> where - T: WalletBackend + WalletClient, + T: WalletBackend, + C: WalletClient, K: Keychain, { info!( @@ -146,9 +148,10 @@ where } /// Restore a wallet -pub fn restore(wallet: &mut T) -> Result<(), Error> +pub fn restore(wallet: &mut T) -> Result<(), Error> where - T: WalletBackend + WalletClient, + T: WalletBackend, + C: WalletClient, K: Keychain, { let max_derivations = 1_000_000; @@ -170,7 +173,7 @@ where let mut result_vec: Vec = vec![]; loop { let (highest_index, last_retrieved_index, outputs) = - wallet.get_outputs_by_pmmr_index(start_index, batch_size)?; + wallet.client().get_outputs_by_pmmr_index(start_index, batch_size)?; info!( LOGGER, "Retrieved {} outputs, up to index {}. (Highest index: {})", diff --git a/wallet/src/libwallet/internal/selection.rs b/wallet/src/libwallet/internal/selection.rs index f0bd4405e..20ef7ade1 100644 --- a/wallet/src/libwallet/internal/selection.rs +++ b/wallet/src/libwallet/internal/selection.rs @@ -25,7 +25,7 @@ use libwallet::types::*; /// and saves the private wallet identifiers of our selected outputs /// into our transaction context -pub fn build_send_tx_slate( +pub fn build_send_tx_slate( wallet: &mut T, num_participants: usize, amount: u64, @@ -43,7 +43,8 @@ pub fn build_send_tx_slate( Error, > where - T: WalletBackend, + T: WalletBackend, + C: WalletClient, K: Keychain, { let (elems, inputs, change, change_derivation, amount, fee) = select_send_tx( @@ -122,7 +123,7 @@ where /// 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( +pub fn build_recipient_output_with_slate( wallet: &mut T, slate: &mut Slate, ) -> Result< @@ -134,7 +135,8 @@ pub fn build_recipient_output_with_slate( Error, > where - T: WalletBackend, + T: WalletBackend, + C: WalletClient, K: Keychain, { // Create a potential output for this transaction @@ -182,7 +184,7 @@ where /// 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( +pub fn select_send_tx( wallet: &mut T, amount: u64, current_height: u64, @@ -202,7 +204,8 @@ pub fn select_send_tx( Error, > where - T: WalletBackend, + T: WalletBackend, + C: WalletClient, K: Keychain, { let key_id = wallet.keychain().root_key_id(); @@ -287,14 +290,15 @@ where } /// Selects inputs and change for a transaction -pub fn inputs_and_change( +pub fn inputs_and_change( coins: &Vec, wallet: &mut T, amount: u64, fee: u64, ) -> Result<(Vec>>, u64, Option), Error> where - T: WalletBackend, + T: WalletBackend, + C: WalletClient, K: Keychain, { let mut parts = vec![]; diff --git a/wallet/src/libwallet/internal/tx.rs b/wallet/src/libwallet/internal/tx.rs index 71635c0d9..865180840 100644 --- a/wallet/src/libwallet/internal/tx.rs +++ b/wallet/src/libwallet/internal/tx.rs @@ -25,9 +25,10 @@ use util::LOGGER; /// Receive a transaction, modifying the slate accordingly (which can then be /// sent back to sender for posting) -pub fn receive_tx(wallet: &mut T, slate: &mut Slate) -> Result<(), Error> +pub fn receive_tx(wallet: &mut T, slate: &mut Slate) -> Result<(), Error> where - T: WalletBackend, + T: WalletBackend, + C: WalletClient, K: Keychain, { // create an output using the amount in the slate @@ -53,7 +54,7 @@ where /// Issue a new transaction to the provided sender by spending some of our /// wallet -pub fn create_send_tx( +pub fn create_send_tx( wallet: &mut T, amount: u64, minimum_confirmations: u64, @@ -68,11 +69,12 @@ pub fn create_send_tx( Error, > where - T: WalletBackend + WalletClient, + T: WalletBackend, + C: WalletClient, K: Keychain, { // Get lock height - let current_height = wallet.get_chain_height()?; + let current_height = wallet.client().get_chain_height()?; // ensure outputs we're selecting are up to date updater::refresh_outputs(wallet)?; @@ -110,13 +112,14 @@ where } /// Complete a transaction as the sender -pub fn complete_tx( +pub fn complete_tx( wallet: &mut T, slate: &mut Slate, context: &sigcontext::Context, ) -> Result<(), Error> where - T: WalletBackend, + T: WalletBackend, + C: WalletClient, K: Keychain, { let _ = slate.fill_round_2(wallet.keychain(), &context.sec_key, &context.sec_nonce, 0)?; @@ -129,14 +132,15 @@ where } /// Issue a burn tx -pub fn issue_burn_tx( +pub fn issue_burn_tx( wallet: &mut T, amount: u64, minimum_confirmations: u64, max_outputs: usize, ) -> Result where - T: WalletBackend + WalletClient, + T: WalletBackend, + C: WalletClient, K: Keychain, { // TODO @@ -144,7 +148,7 @@ where // &Identifier::zero()); let keychain = wallet.keychain().clone(); - let current_height = wallet.get_chain_height()?; + let current_height = wallet.client().get_chain_height()?; let _ = updater::refresh_outputs(wallet); diff --git a/wallet/src/libwallet/internal/updater.rs b/wallet/src/libwallet/internal/updater.rs index a4878f347..1f1ff467c 100644 --- a/wallet/src/libwallet/internal/updater.rs +++ b/wallet/src/libwallet/internal/updater.rs @@ -33,12 +33,13 @@ use util::secp::pedersen; use util::{self, LOGGER}; /// Retrieve all of the outputs (doesn't attempt to update from node) -pub fn retrieve_outputs( +pub fn retrieve_outputs( wallet: &mut T, show_spent: bool, ) -> Result, Error> where - T: WalletBackend, + T: WalletBackend, + C: WalletClient, K: Keychain, { let root_key_id = wallet.keychain().clone().root_key_id(); @@ -61,23 +62,25 @@ where /// Refreshes the outputs in a wallet with the latest information /// from a node -pub fn refresh_outputs(wallet: &mut T) -> Result<(), Error> +pub fn refresh_outputs(wallet: &mut T) -> Result<(), Error> where - T: WalletBackend + WalletClient, + T: WalletBackend, + C: WalletClient, K: Keychain, { - let height = wallet.get_chain_height()?; + let height = wallet.client().get_chain_height()?; refresh_output_state(wallet, height)?; Ok(()) } /// 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( +pub fn map_wallet_outputs( wallet: &mut T, ) -> Result, Error> where - T: WalletBackend, + T: WalletBackend, + C: WalletClient, K: Keychain, { let mut wallet_outputs: HashMap = HashMap::new(); @@ -94,14 +97,15 @@ where } /// Apply refreshed API output data to the wallet -pub fn apply_api_outputs( +pub fn apply_api_outputs( wallet: &mut T, wallet_outputs: &HashMap, api_outputs: &HashMap, height: u64, ) -> Result<(), libwallet::Error> where - T: WalletBackend, + T: WalletBackend, + C: WalletClient, K: Keychain, { // now for each commit, find the output in the wallet and the corresponding @@ -129,9 +133,10 @@ where /// 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(wallet: &mut T, height: u64) -> Result<(), Error> +fn refresh_output_state(wallet: &mut T, height: u64) -> Result<(), Error> where - T: WalletBackend + WalletClient, + T: WalletBackend, + C: WalletClient, K: Keychain, { debug!(LOGGER, "Refreshing wallet outputs"); @@ -142,15 +147,16 @@ where let wallet_output_keys = wallet_outputs.keys().map(|commit| commit.clone()).collect(); - let api_outputs = wallet.get_outputs_from_node(wallet_output_keys)?; + let api_outputs = wallet.client().get_outputs_from_node(wallet_output_keys)?; apply_api_outputs(wallet, &wallet_outputs, &api_outputs, height)?; clean_old_unconfirmed(wallet, height)?; Ok(()) } -fn clean_old_unconfirmed(wallet: &mut T, height: u64) -> Result<(), Error> +fn clean_old_unconfirmed(wallet: &mut T, height: u64) -> Result<(), Error> where - T: WalletBackend, + T: WalletBackend, + C: WalletClient, K: Keychain, { if height < 500 { @@ -172,9 +178,10 @@ where /// Retrieve summary info about the wallet /// caller should refresh first if desired -pub fn retrieve_info(wallet: &mut T) -> Result +pub fn retrieve_info(wallet: &mut T) -> Result where - T: WalletBackend + WalletClient, + T: WalletBackend, + C: WalletClient, K: Keychain, { let current_height = wallet.details().last_confirmed_height; @@ -213,9 +220,10 @@ where } /// Build a coinbase output and insert into wallet -pub fn build_coinbase(wallet: &mut T, block_fees: &BlockFees) -> Result +pub fn build_coinbase(wallet: &mut T, block_fees: &BlockFees) -> Result where - T: WalletBackend, + T: WalletBackend, + C: WalletClient, K: Keychain, { let (out, kern, block_fees) = receive_coinbase(wallet, block_fees).context(ErrorKind::Node)?; @@ -238,12 +246,13 @@ where //TODO: Split up the output creation and the wallet insertion /// Build a coinbase output and the corresponding kernel -pub fn receive_coinbase( +pub fn receive_coinbase( wallet: &mut T, block_fees: &BlockFees, ) -> Result<(Output, TxKernel, BlockFees), Error> where - T: WalletBackend, + T: WalletBackend, + C: WalletClient, K: Keychain, { let root_key_id = wallet.keychain().root_key_id(); diff --git a/wallet/src/libwallet/types.rs b/wallet/src/libwallet/types.rs index a406decdf..69387ae5d 100644 --- a/wallet/src/libwallet/types.rs +++ b/wallet/src/libwallet/types.rs @@ -34,14 +34,16 @@ use libwallet::error::{Error, ErrorKind}; use util::secp::pedersen; /// Combined trait to allow dynamic wallet dispatch -pub trait WalletInst: WalletBackend + WalletClient + Send + Sync + 'static +pub trait WalletInst: WalletBackend + Send + Sync + 'static where + C: WalletClient, K: Keychain, { } -impl WalletInst for T +impl WalletInst for T where - T: WalletBackend + WalletClient + Send + Sync + 'static, + T: WalletBackend + Send + Sync + 'static, + C: WalletClient, K: Keychain, { } @@ -50,8 +52,9 @@ where /// 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 +pub trait WalletBackend where + C: WalletClient, K: Keychain, { /// Initialize with whatever stored credentials we have @@ -63,6 +66,9 @@ where /// Return the keychain being used fn keychain(&mut self) -> &mut K; + /// Return the client being used + fn client(&mut self) -> &mut C; + /// Iterate over all output data stored by the backend fn iter<'a>(&'a self) -> Box + 'a>; @@ -121,12 +127,12 @@ pub trait WalletOutputBatch { /// Encapsulate all communication functions. No functions within libwallet /// should care about communication details -pub trait WalletClient { +pub trait WalletClient: Sync + Send + Clone { /// Return the URL of the check node fn node_url(&self) -> &str; /// Call the wallet API to create a coinbase transaction - fn create_coinbase(&self, block_fees: &BlockFees) -> Result; + fn create_coinbase(&self, dest: &str, block_fees: &BlockFees) -> Result; /// Send a transaction slate to another listening wallet and return result /// TODO: Probably need a slate wrapper type diff --git a/wallet/src/lmdb_wallet.rs b/wallet/src/lmdb_wallet.rs index d051d785c..3cdd01036 100644 --- a/wallet/src/lmdb_wallet.rs +++ b/wallet/src/lmdb_wallet.rs @@ -13,7 +13,6 @@ // limitations under the License. use std::cell::RefCell; -use std::collections::HashMap; use std::sync::Arc; use std::{fs, path}; @@ -22,12 +21,9 @@ use failure::ResultExt; use keychain::{Identifier, Keychain}; use store::{self, option_to_not_found, to_key}; -use client; -use libtx::slate::Slate; use libwallet::types::*; use libwallet::{internal, Error, ErrorKind}; use types::{WalletConfig, WalletSeed}; -use util::secp::pedersen; pub const DB_DIR: &'static str = "wallet_data"; @@ -47,18 +43,19 @@ pub fn wallet_db_exists(config: WalletConfig) -> bool { db_path.exists() } -pub struct LMDBBackend { +pub struct LMDBBackend { db: store::Store, config: WalletConfig, /// passphrase: TODO better ways of dealing with this other than storing passphrase: String, - /// Keychain keychain: Option, + /// client + client: C, } -impl LMDBBackend { - pub fn new(config: WalletConfig, passphrase: &str) -> Result { +impl LMDBBackend { + pub fn new(config: WalletConfig, passphrase: &str, client: C) -> Result { let db_path = path::Path::new(&config.data_file_dir).join(DB_DIR); fs::create_dir_all(&db_path).expect("Couldn't create wallet backend directory!"); @@ -69,6 +66,7 @@ impl LMDBBackend { config: config.clone(), passphrase: String::from(passphrase), keychain: None, + client: client, }) } @@ -80,8 +78,9 @@ impl LMDBBackend { } } -impl WalletBackend for LMDBBackend +impl WalletBackend for LMDBBackend where + C: WalletClient, K: Keychain, { /// Initialise with whatever stored credentials we have @@ -106,6 +105,11 @@ where self.keychain.as_mut().unwrap() } + /// Return the client being used + fn client(&mut self) -> &mut C { + &mut self.client + } + fn get(&self, id: &Identifier) -> Result { let key = to_key(OUTPUT_PREFIX, &mut id.to_bytes().to_vec()); option_to_not_found(self.db.get_ser(&key), &format!("Key Id: {}", id)).map_err(|e| e.into()) @@ -159,13 +163,21 @@ where /// An atomic batch in which all changes can be committed all at once or /// discarded on error. -pub struct Batch<'a, K: 'a> { - store: &'a LMDBBackend, +pub struct Batch<'a, C: 'a, K: 'a> +where + C: WalletClient, + K: Keychain, +{ + store: &'a LMDBBackend, db: RefCell>>, } #[allow(missing_docs)] -impl<'a, K> WalletOutputBatch for Batch<'a, K> { +impl<'a, C, K> WalletOutputBatch for Batch<'a, C, K> +where + C: WalletClient, + K: Keychain, +{ fn save(&mut self, out: OutputData) -> Result<(), Error> { let key = to_key(OUTPUT_PREFIX, &mut out.key_id.to_bytes().to_vec()); self.db.borrow().as_ref().unwrap().put_ser(&key, &out)?; @@ -205,65 +217,3 @@ impl<'a, K> WalletOutputBatch for Batch<'a, K> { Ok(()) } } - -impl WalletClient for LMDBBackend { - /// Return URL for check node - fn node_url(&self) -> &str { - &self.config.check_node_api_http_addr - } - - /// Call the wallet API to create a coinbase transaction - fn create_coinbase(&self, block_fees: &BlockFees) -> Result { - let res = client::create_coinbase(self.node_url(), block_fees) - .context(ErrorKind::WalletComms(format!("Creating Coinbase")))?; - Ok(res) - } - - /// Send a transaction slate to another listening wallet and return result - fn send_tx_slate(&self, addr: &str, slate: &Slate) -> Result { - let res = client::send_tx_slate(addr, slate) - .context(ErrorKind::WalletComms(format!("Sending transaction")))?; - Ok(res) - } - - /// Posts a tranaction to a grin node - fn post_tx(&self, tx: &TxWrapper, fluff: bool) -> Result<(), Error> { - let res = client::post_tx(self.node_url(), tx, fluff).context(ErrorKind::Node)?; - Ok(res) - } - - /// retrieves the current tip from the specified grin node - fn get_chain_height(&self) -> Result { - let res = client::get_chain_height(self.node_url()).context(ErrorKind::Node)?; - Ok(res) - } - - /// retrieve a list of outputs from the specified grin node - /// need "by_height" and "by_id" variants - fn get_outputs_from_node( - &self, - wallet_outputs: Vec, - ) -> Result, Error> { - let res = client::get_outputs_from_node(self.node_url(), wallet_outputs) - .context(ErrorKind::Node)?; - Ok(res) - } - - /// Outputs by PMMR index - fn get_outputs_by_pmmr_index( - &self, - start_height: u64, - max_outputs: u64, - ) -> Result< - ( - u64, - u64, - Vec<(pedersen::Commitment, pedersen::RangeProof, bool)>, - ), - Error, - > { - let res = client::get_outputs_by_pmmr_index(self.node_url(), start_height, max_outputs) - .context(ErrorKind::Node)?; - Ok(res) - } -} diff --git a/wallet/tests/common/mod.rs b/wallet/tests/common/mod.rs index 61d1ace8e..de4786b92 100644 --- a/wallet/tests/common/mod.rs +++ b/wallet/tests/common/mod.rs @@ -28,11 +28,11 @@ use core::core::hash::Hashed; use core::core::{Output, OutputFeatures, OutputIdentifier, Transaction, TxKernel}; use core::{consensus, global, pow}; use keychain::ExtKeychain; -use wallet::WalletConfig; +use wallet::{HTTPWalletClient, WalletConfig}; use wallet::file_wallet::FileWallet; use wallet::libwallet::internal::updater; use wallet::libwallet::types::{BlockFees, BlockIdentifier, OutputStatus, - WalletBackend}; + WalletBackend, WalletClient}; use wallet::libwallet::{Error, ErrorKind}; use util; @@ -40,9 +40,10 @@ 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(wallet: &mut T, chain: &chain::Chain) -> Result<(), Error> +pub fn refresh_output_state_local(wallet: &mut T, chain: &chain::Chain) -> Result<(), Error> where - T: WalletBackend, + T: WalletBackend, + C: WalletClient, K: keychain::Keychain, { let wallet_outputs = updater::map_wallet_outputs(wallet)?; @@ -71,12 +72,13 @@ where /// (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( +pub fn get_wallet_balances( wallet: &mut T, height: u64, ) -> Result<(u64, u64, u64, u64, u64), Error> where - T: WalletBackend, + T: WalletBackend, + C: WalletClient, K: keychain::Keychain, { let mut unspent_total = 0; @@ -156,9 +158,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(chain: &Chain, txs: Vec<&Transaction>, wallet: &mut T) +pub fn award_block_to_wallet(chain: &Chain, txs: Vec<&Transaction>, wallet: &mut T) where - T: WalletBackend, + T: WalletBackend, + C: WalletClient, K: keychain::Keychain, { let prev = chain.head_header().unwrap(); @@ -183,9 +186,10 @@ where } /// adds many block rewards to a wallet, no transactions -pub fn award_blocks_to_wallet(chain: &Chain, wallet: &mut T, num_rewards: usize) +pub fn award_blocks_to_wallet(chain: &Chain, wallet: &mut T, num_rewards: usize) where - T: WalletBackend, + T: WalletBackend, + C: WalletClient, K: keychain::Keychain, { for _ in 0..num_rewards { @@ -194,11 +198,11 @@ where } /// Create a new wallet in a particular directory -pub fn create_wallet(dir: &str) -> FileWallet { +pub fn create_wallet(dir: &str, client: HTTPWalletClient) -> FileWallet { let mut wallet_config = WalletConfig::default(); wallet_config.data_file_dir = String::from(dir); wallet::WalletSeed::init_file(&wallet_config).expect("Failed to create wallet seed file."); - let mut wallet = FileWallet::new(wallet_config.clone(), "") + let mut wallet = FileWallet::new(wallet_config.clone(), "", client) .unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config)); wallet.open_with_credentials().unwrap_or_else(|e| { panic!( diff --git a/wallet/tests/transaction.rs b/wallet/tests/transaction.rs index b9fe61d30..5aa72be53 100644 --- a/wallet/tests/transaction.rs +++ b/wallet/tests/transaction.rs @@ -35,6 +35,7 @@ use chain::types::NoopAdapter; use core::global::ChainTypes; use core::{global, pow}; use util::LOGGER; +use wallet::HTTPWalletClient; use wallet::libwallet::internal::selection; fn clean_output_dir(test_dir: &str) { @@ -60,9 +61,10 @@ fn setup(test_dir: &str, chain_dir: &str) -> Chain { /// Build and test new version of sending API #[test] fn build_transaction() { + let client = HTTPWalletClient::new(""); let chain = setup("test_output", "build_transaction_2/.grin"); - let mut wallet1 = common::create_wallet("test_output/build_transaction_2/wallet1"); - let mut wallet2 = common::create_wallet("test_output/build_transaction_2/wallet2"); + let mut wallet1 = common::create_wallet("test_output/build_transaction_2/wallet1", client.clone()); + let mut wallet2 = common::create_wallet("test_output/build_transaction_2/wallet2", client); 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 libtx