WalletClient trait refactor (#1238)

* refactor WalletClient

* revert chain changes

* missing files
This commit is contained in:
Yeastplume 2018-07-10 09:18:24 +01:00 committed by GitHub
parent c7d78446f3
commit 49cbab90f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 432 additions and 433 deletions

View file

@ -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<keychain::ExtKeychain> =
FileWallet::new(self.wallet_config.clone(), "").unwrap_or_else(|e| {
let wallet: FileWallet<HTTPWalletClient, keychain::ExtKeychain> =
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 _ =

View file

@ -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<WalletInst<keychain::ExtKeychain>> {
) -> Box<WalletInst<HTTPWalletClient, keychain::ExtKeychain>> {
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<keychain::ExtKeychain> = LMDBBackend::new(wallet_config.clone(), "")
let client = HTTPWalletClient::new(&wallet_config.check_node_api_http_addr);
let _: LMDBBackend<HTTPWalletClient, keychain::ExtKeychain> = LMDBBackend::new(wallet_config.clone(), "", client)
.unwrap_or_else(|e| {
panic!(
"Error creating DB wallet: {} Config: {:?}",

View file

@ -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<CbData, libwallet::Error> {
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<Slate, libwallet::Error> {
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::<hyper::Uri>()
.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<u64, libwallet::Error> {
let addr = self.node_url();
let url = format!("{}/v1/chain", addr);
let res = api::client::get::<api::Tip>(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<pedersen::Commitment>,
) -> Result<HashMap<pedersen::Commitment, String>, libwallet::Error> {
let addr = self.node_url();
// build the necessary query params -
// ?id=xxx&id=yyy&id=zzz
let query_params: Vec<String> = 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<pedersen::Commitment, String> = 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::<Vec<api::Output>>(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::<api::OutputListing>(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<CbData, Error> {
@ -44,49 +222,12 @@ pub fn create_coinbase(dest: &str, block_fees: &BlockFees) -> Result<CbData, Err
);
error!(LOGGER, "Underlying Error: {}", e.cause().unwrap());
error!(LOGGER, "Backtrace: {}", e.backtrace().unwrap());
Err(e)
Err(e)?
}
Ok(res) => Ok(res),
}
}
/// Send the slate to a listening wallet instance
pub fn send_tx_slate(dest: &str, slate: &Slate) -> Result<Slate, Error> {
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::<hyper::Uri>().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<CbData, Error> {
let mut core =
@ -116,94 +257,3 @@ fn single_create_coinbase(url: &str, block_fees: &BlockFees) -> Result<CbData, E
Ok(res)
}
/// Posts a transaction to a grin node
pub fn post_tx(dest: &str, tx: &TxWrapper, fluff: bool) -> 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<u64, Error> {
let url = format!("{}/v1/chain", addr);
let res = api::client::get::<api::Tip>(url.as_str())?;
Ok(res.height)
}
/// Retrieve outputs from node
pub fn get_outputs_from_node(
addr: &str,
wallet_outputs: Vec<pedersen::Commitment>,
) -> Result<HashMap<pedersen::Commitment, String>, Error> {
// build the necessary query params -
// ?id=xxx&id=yyy&id=zzz
let query_params: Vec<String> = 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<pedersen::Commitment, String> = 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::<Vec<api::Output>>(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::<api::OutputListing>(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)?
}
}
}

View file

@ -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<K> {
pub struct FileWallet<C, K> {
/// Keychain
pub keychain: Option<K>,
/// 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<K> {
pub details_bak_path: String,
}
impl<K> WalletBackend<K> for FileWallet<K>
impl<C, K> WalletBackend<C, K> for FileWallet<C, K>
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<Iterator<Item = OutputData> + 'a> {
Box::new(self.outputs.values().cloned())
}
@ -320,98 +328,16 @@ where
}
}
impl<K> WalletClient for FileWallet<K> {
/// 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<CbData, libwallet::Error> {
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<Slate, libwallet::Error> {
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<u64, libwallet::Error> {
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<pedersen::Commitment>,
) -> Result<HashMap<pedersen::Commitment, String>, 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<K> FileWallet<K>
impl<C, K> FileWallet<C, K>
where
C: WalletClient,
K: Keychain,
{
/// Create a new FileWallet instance
pub fn new(config: WalletConfig, passphrase: &str) -> Result<Self, Error> {
pub fn new(config: WalletConfig, passphrase: &str, client: C) -> Result<Self, Error> {
let mut retval = FileWallet {
keychain: None,
client: client,
config: config.clone(),
passphrase: String::from(passphrase),
outputs: HashMap::new(),

View file

@ -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};

View file

@ -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<K> + WalletClient,
W: 'a + WalletBackend<C, K>,
C: WalletClient,
K: Keychain,
{
/// Wallet, contains its keychain (TODO: Split these up into 2 traits
/// perhaps)
pub wallet: &'a mut Box<W>,
phantom: PhantomData<K>,
phantom_c: PhantomData<C>,
}
impl<'a, W: ?Sized, K> APIOwner<'a, W, K>
impl<'a, W: ?Sized, C, K> APIOwner<'a, W, C, K>
where
W: 'a + WalletBackend<K> + WalletClient,
W: 'a + WalletBackend<C, K>,
C: WalletClient,
K: Keychain,
{
/// Create new API instance
pub fn new(wallet_in: &'a mut Box<W>) -> APIOwner<'a, W, K> {
pub fn new(wallet_in: &'a mut Box<W>) -> 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<K> + WalletClient + 'a,
W: 'a + WalletBackend<C, K>,
C: WalletClient,
K: Keychain,
{
/// Wallet, contains its keychain (TODO: Split these up into 2 traits
/// perhaps)
pub wallet: &'a mut Box<W>,
phantom: PhantomData<K>,
phantom_c: PhantomData<C>,
}
impl<'a, W: ?Sized, K> APIForeign<'a, W, K>
impl<'a, W: ?Sized, C, K> APIForeign<'a, W, C, K>
where
W: WalletBackend<K> + WalletClient,
W: WalletBackend<C, K>,
C: WalletClient,
K: Keychain,
{
/// Create new API instance
pub fn new(wallet_in: &'a mut Box<W>) -> APIForeign<W, K> {
pub fn new(wallet_in: &'a mut Box<W>) -> APIForeign<W, C, K> {
APIForeign {
wallet: wallet_in,
phantom: PhantomData,
phantom_c: PhantomData,
}
}

View file

@ -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<F, T: ?Sized, K>(wallet: Box<T>, f: F) -> Result<(), Error>
pub fn owner_single_use<F, T: ?Sized, C, K>(wallet: Box<T>, f: F) -> Result<(), Error>
where
T: WalletBackend<K> + WalletClient,
F: FnOnce(&mut APIOwner<T, K>) -> Result<(), Error>,
T: WalletBackend<C, K>,
F: FnOnce(&mut APIOwner<T, C, K>) -> 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<F, T: ?Sized, K>(wallet: &mut Box<T>, f: F) -> Result<(), Error>
pub fn foreign_single_use<F, T: ?Sized, C, K>(wallet: &mut Box<T>, f: F) -> Result<(), Error>
where
T: WalletBackend<K> + WalletClient,
F: FnOnce(&mut APIForeign<T, K>) -> Result<(), Error>,
T: WalletBackend<C, K>,
F: FnOnce(&mut APIForeign<T, C, K>) -> 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<T: ?Sized, K>(wallet: Box<T>, addr: &str) -> Result<(), Error>
pub fn owner_listener<T: ?Sized, C, K>(wallet: Box<T>, addr: &str) -> Result<(), Error>
where
T: WalletInst<K>,
OwnerAPIGetHandler<T, K>: Handler,
OwnerAPIPostHandler<T, K>: Handler,
T: WalletBackend<C, K>,
OwnerAPIGetHandler<T, C, K>: Handler,
OwnerAPIPostHandler<T, C, K>: 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<T: ?Sized, K>(wallet: Box<T>, addr: &str) -> Result<(), Error>
pub fn foreign_listener<T: ?Sized, C, K>(wallet: Box<T>, addr: &str) -> Result<(), Error>
where
T: WalletInst<K>,
ForeignAPIHandler<T, K>: Handler,
T: WalletBackend<C, K>,
ForeignAPIHandler<T, C, K>: 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<T: ?Sized, K>
pub struct OwnerAPIGetHandler<T: ?Sized, C, K>
where
T: WalletBackend<K> + WalletClient,
T: WalletBackend<C, K>,
C: WalletClient,
K: Keychain,
{
/// Wallet instance
pub wallet: Arc<Mutex<Box<T>>>,
phantom: PhantomData<K>,
phantom_c: PhantomData<C>,
}
impl<T: ?Sized, K> OwnerAPIGetHandler<T, K>
impl<T: ?Sized, C, K> OwnerAPIGetHandler<T, C, K>
where
T: WalletBackend<K> + WalletClient,
T: WalletBackend<C, K>,
C: WalletClient,
K: Keychain,
{
/// Create a new owner API handler for GET methods
pub fn new(wallet: Arc<Mutex<Box<T>>>) -> OwnerAPIGetHandler<T, K> {
pub fn new(wallet: Arc<Mutex<Box<T>>>) -> OwnerAPIGetHandler<T, C, K> {
OwnerAPIGetHandler {
wallet,
phantom: PhantomData,
phantom_c: PhantomData,
}
}
fn retrieve_outputs(
&self,
req: &mut Request,
api: &mut APIOwner<T, K>,
api: &mut APIOwner<T, C, K>,
) -> Result<(bool, Vec<OutputData>), Error> {
let mut update_from_node = false;
if let Ok(params) = req.get_ref::<UrlEncodedQuery>() {
@ -166,7 +174,7 @@ where
fn retrieve_summary_info(
&self,
req: &mut Request,
api: &mut APIOwner<T, K>,
api: &mut APIOwner<T, C, K>,
) -> Result<(bool, WalletInfo), Error> {
let mut update_from_node = false;
if let Ok(params) = req.get_ref::<UrlEncodedQuery>() {
@ -180,12 +188,12 @@ where
fn node_height(
&self,
_req: &mut Request,
api: &mut APIOwner<T, K>,
api: &mut APIOwner<T, C, K>,
) -> Result<(u64, bool), Error> {
api.node_height()
}
fn handle_request(&self, req: &mut Request, api: &mut APIOwner<T, K>) -> IronResult<Response> {
fn handle_request(&self, req: &mut Request, api: &mut APIOwner<T, C, K>) -> IronResult<Response> {
let url = req.url.clone();
let path_elems = url.path();
match *path_elems.last().unwrap() {
@ -203,9 +211,10 @@ where
}
}
impl<T: ?Sized, K> Handler for OwnerAPIGetHandler<T, K>
impl<T: ?Sized, C, K> Handler for OwnerAPIGetHandler<T, C, K>
where
T: WalletBackend<K> + WalletClient + Send + Sync + 'static,
T: WalletBackend<C, K> + Send + Sync + 'static,
C: WalletClient + 'static,
K: Keychain + 'static,
{
fn handle(&self, req: &mut Request) -> IronResult<Response> {
@ -235,30 +244,34 @@ where
}
/// Handles all owner API POST requests
pub struct OwnerAPIPostHandler<T: ?Sized, K>
pub struct OwnerAPIPostHandler<T: ?Sized, C, K>
where
T: WalletBackend<K>,
T: WalletBackend<C, K>,
C: WalletClient,
K: Keychain,
{
/// Wallet instance
pub wallet: Arc<Mutex<Box<T>>>,
phantom: PhantomData<K>,
phantom_c: PhantomData<C>,
}
impl<T: ?Sized, K> OwnerAPIPostHandler<T, K>
impl<T: ?Sized, C, K> OwnerAPIPostHandler<T, C, K>
where
T: WalletBackend<K> + WalletClient,
T: WalletBackend<C, K>,
C: WalletClient,
K: Keychain,
{
/// New POST handler
pub fn new(wallet: Arc<Mutex<Box<T>>>) -> OwnerAPIPostHandler<T, K> {
pub fn new(wallet: Arc<Mutex<Box<T>>>) -> OwnerAPIPostHandler<T, C, K> {
OwnerAPIPostHandler {
wallet,
phantom: PhantomData,
phantom_c: PhantomData,
}
}
fn issue_send_tx(&self, req: &mut Request, api: &mut APIOwner<T, K>) -> Result<(), Error> {
fn issue_send_tx(&self, req: &mut Request, api: &mut APIOwner<T, C, K>) -> Result<(), Error> {
let struct_body = req.get::<bodyparser::Struct<SendTXArgs>>();
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<T, K>) -> Result<(), Error> {
fn issue_burn_tx(&self, _req: &mut Request, api: &mut APIOwner<T, C, K>) -> Result<(), Error> {
// TODO: Args
api.issue_burn_tx(60, 10, 1000)
}
fn handle_request(&self, req: &mut Request, api: &mut APIOwner<T, K>) -> Result<String, Error> {
fn handle_request(&self, req: &mut Request, api: &mut APIOwner<T, C, K>) -> Result<String, Error> {
let url = req.url.clone();
let path_elems = url.path();
match *path_elems.last().unwrap() {
@ -323,9 +336,10 @@ where
}
}
impl<T: ?Sized, K> Handler for OwnerAPIPostHandler<T, K>
impl<T: ?Sized, C, K> Handler for OwnerAPIPostHandler<T, C, K>
where
T: WalletBackend<K> + WalletClient + Send + Sync + 'static,
T: WalletBackend<C, K> + Send + Sync + 'static,
C: WalletClient + 'static,
K: Keychain + 'static,
{
fn handle(&self, req: &mut Request) -> IronResult<Response> {
@ -373,33 +387,37 @@ impl Handler for OwnerAPIOptionsHandler where {
}
/// API Handler/Wrapper for foreign functions
pub struct ForeignAPIHandler<T: ?Sized, K>
pub struct ForeignAPIHandler<T: ?Sized, C, K>
where
T: WalletBackend<K> + WalletClient,
T: WalletBackend<C, K>,
C: WalletClient,
K: Keychain,
{
/// Wallet instance
pub wallet: Arc<Mutex<Box<T>>>,
phantom: PhantomData<K>,
phantom_c: PhantomData<C>,
}
impl<T: ?Sized, K> ForeignAPIHandler<T, K>
impl<T: ?Sized, C, K> ForeignAPIHandler<T, C, K>
where
T: WalletBackend<K> + WalletClient,
T: WalletBackend<C, K>,
C: WalletClient,
K: Keychain,
{
/// create a new api handler
pub fn new(wallet: Arc<Mutex<Box<T>>>) -> ForeignAPIHandler<T, K> {
pub fn new(wallet: Arc<Mutex<Box<T>>>) -> ForeignAPIHandler<T, C, K> {
ForeignAPIHandler {
wallet,
phantom: PhantomData,
phantom_c: PhantomData,
}
}
fn build_coinbase(
&self,
req: &mut Request,
api: &mut APIForeign<T, K>,
api: &mut APIForeign<T, C, K>,
) -> Result<CbData, Error> {
let struct_body = req.get::<bodyparser::Struct<BlockFees>>();
match struct_body {
@ -419,7 +437,7 @@ where
}
}
fn receive_tx(&self, req: &mut Request, api: &mut APIForeign<T, K>) -> Result<Slate, Error> {
fn receive_tx(&self, req: &mut Request, api: &mut APIForeign<T, C, K>) -> Result<Slate, Error> {
let struct_body = req.get::<bodyparser::Struct<Slate>>();
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<T, K>,
api: &mut APIForeign<T, C, K>,
) -> IronResult<Response> {
let url = req.url.clone();
let path_elems = url.path();
@ -448,9 +466,10 @@ where
}
}
}
impl<T: ?Sized, K> Handler for ForeignAPIHandler<T, K>
impl<T: ?Sized, C, K> Handler for ForeignAPIHandler<T, C, K>
where
T: WalletBackend<K> + WalletClient + Send + Sync + 'static,
T: WalletBackend<C, K> + Send + Sync + 'static,
C: WalletClient + Send + Sync + 'static,
K: Keychain + 'static,
{
fn handle(&self, req: &mut Request) -> IronResult<Response> {

View file

@ -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,

View file

@ -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<T: ?Sized, K>(wallet: &mut T) -> Result<(Identifier, u32), Error>
pub fn next_available_key<T: ?Sized, C, K>(wallet: &mut T) -> Result<(Identifier, u32), Error>
where
T: WalletBackend<K>,
T: WalletBackend<C, K>,
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<T: ?Sized, K>(
pub fn retrieve_existing_key<T: ?Sized, C, K>(
wallet: &T,
key_id: Identifier,
) -> Result<(Identifier, u32), Error>
where
T: WalletBackend<K>,
T: WalletBackend<C, K>,
C: WalletClient,
K: Keychain,
{
let existing = wallet.get(&key_id)?;

View file

@ -41,12 +41,13 @@ struct OutputResult {
pub blinding: SecretKey,
}
fn identify_utxo_outputs<T, K>(
fn identify_utxo_outputs<T, C, K>(
wallet: &mut T,
outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool)>,
) -> Result<Vec<OutputResult>, Error>
where
T: WalletBackend<K> + WalletClient,
T: WalletBackend<C, K>,
C: WalletClient,
K: Keychain,
{
let mut wallet_outputs: Vec<OutputResult> = 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<T, K>(
fn populate_child_indices<T, C, K>(
wallet: &mut T,
outputs: &mut Vec<OutputResult>,
max_derivations: u32,
) -> Result<(), Error>
where
T: WalletBackend<K> + WalletClient,
T: WalletBackend<C, K>,
C: WalletClient,
K: Keychain,
{
info!(
@ -146,9 +148,10 @@ where
}
/// Restore a wallet
pub fn restore<T, K>(wallet: &mut T) -> Result<(), Error>
pub fn restore<T, C, K>(wallet: &mut T) -> Result<(), Error>
where
T: WalletBackend<K> + WalletClient,
T: WalletBackend<C, K>,
C: WalletClient,
K: Keychain,
{
let max_derivations = 1_000_000;
@ -170,7 +173,7 @@ where
let mut result_vec: Vec<OutputResult> = 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: {})",

View file

@ -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<T: ?Sized, K>(
pub fn build_send_tx_slate<T: ?Sized, C, K>(
wallet: &mut T,
num_participants: usize,
amount: u64,
@ -43,7 +43,8 @@ pub fn build_send_tx_slate<T: ?Sized, K>(
Error,
>
where
T: WalletBackend<K>,
T: WalletBackend<C, K>,
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<T: ?Sized, K>(
pub fn build_recipient_output_with_slate<T: ?Sized, C, K>(
wallet: &mut T,
slate: &mut Slate,
) -> Result<
@ -134,7 +135,8 @@ pub fn build_recipient_output_with_slate<T: ?Sized, K>(
Error,
>
where
T: WalletBackend<K>,
T: WalletBackend<C, K>,
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<T: ?Sized, K>(
pub fn select_send_tx<T: ?Sized, C, K>(
wallet: &mut T,
amount: u64,
current_height: u64,
@ -202,7 +204,8 @@ pub fn select_send_tx<T: ?Sized, K>(
Error,
>
where
T: WalletBackend<K>,
T: WalletBackend<C, K>,
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<T: ?Sized, K>(
pub fn inputs_and_change<T: ?Sized, C, K>(
coins: &Vec<OutputData>,
wallet: &mut T,
amount: u64,
fee: u64,
) -> Result<(Vec<Box<build::Append<K>>>, u64, Option<u32>), Error>
where
T: WalletBackend<K>,
T: WalletBackend<C, K>,
C: WalletClient,
K: Keychain,
{
let mut parts = vec![];

View file

@ -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<T: ?Sized, K>(wallet: &mut T, slate: &mut Slate) -> Result<(), Error>
pub fn receive_tx<T: ?Sized, C, K>(wallet: &mut T, slate: &mut Slate) -> Result<(), Error>
where
T: WalletBackend<K>,
T: WalletBackend<C, K>,
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<T: ?Sized, K>(
pub fn create_send_tx<T: ?Sized, C, K>(
wallet: &mut T,
amount: u64,
minimum_confirmations: u64,
@ -68,11 +69,12 @@ pub fn create_send_tx<T: ?Sized, K>(
Error,
>
where
T: WalletBackend<K> + WalletClient,
T: WalletBackend<C, K>,
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<T: ?Sized, K>(
pub fn complete_tx<T: ?Sized, C, K>(
wallet: &mut T,
slate: &mut Slate,
context: &sigcontext::Context,
) -> Result<(), Error>
where
T: WalletBackend<K>,
T: WalletBackend<C, K>,
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<T: ?Sized, K>(
pub fn issue_burn_tx<T: ?Sized, C, K>(
wallet: &mut T,
amount: u64,
minimum_confirmations: u64,
max_outputs: usize,
) -> Result<Transaction, Error>
where
T: WalletBackend<K> + WalletClient,
T: WalletBackend<C, K>,
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);

View file

@ -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<T: ?Sized, K>(
pub fn retrieve_outputs<T: ?Sized, C, K>(
wallet: &mut T,
show_spent: bool,
) -> Result<Vec<OutputData>, Error>
where
T: WalletBackend<K>,
T: WalletBackend<C, K>,
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<T: ?Sized, K>(wallet: &mut T) -> Result<(), Error>
pub fn refresh_outputs<T: ?Sized, C, K>(wallet: &mut T) -> Result<(), Error>
where
T: WalletBackend<K> + WalletClient,
T: WalletBackend<C, K>,
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<T: ?Sized, K>(
pub fn map_wallet_outputs<T: ?Sized, C, K>(
wallet: &mut T,
) -> Result<HashMap<pedersen::Commitment, Identifier>, Error>
where
T: WalletBackend<K>,
T: WalletBackend<C, K>,
C: WalletClient,
K: Keychain,
{
let mut wallet_outputs: HashMap<pedersen::Commitment, Identifier> = HashMap::new();
@ -94,14 +97,15 @@ where
}
/// Apply refreshed API output data to the wallet
pub fn apply_api_outputs<T: ?Sized, K>(
pub fn apply_api_outputs<T: ?Sized, C, K>(
wallet: &mut T,
wallet_outputs: &HashMap<pedersen::Commitment, Identifier>,
api_outputs: &HashMap<pedersen::Commitment, String>,
height: u64,
) -> Result<(), libwallet::Error>
where
T: WalletBackend<K>,
T: WalletBackend<C, K>,
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<T: ?Sized, K>(wallet: &mut T, height: u64) -> Result<(), Error>
fn refresh_output_state<T: ?Sized, C, K>(wallet: &mut T, height: u64) -> Result<(), Error>
where
T: WalletBackend<K> + WalletClient,
T: WalletBackend<C, K>,
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<T: ?Sized, K>(wallet: &mut T, height: u64) -> Result<(), Error>
fn clean_old_unconfirmed<T: ?Sized, C, K>(wallet: &mut T, height: u64) -> Result<(), Error>
where
T: WalletBackend<K>,
T: WalletBackend<C, K>,
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<T: ?Sized, K>(wallet: &mut T) -> Result<WalletInfo, Error>
pub fn retrieve_info<T: ?Sized, C, K>(wallet: &mut T) -> Result<WalletInfo, Error>
where
T: WalletBackend<K> + WalletClient,
T: WalletBackend<C, K>,
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<T: ?Sized, K>(wallet: &mut T, block_fees: &BlockFees) -> Result<CbData, Error>
pub fn build_coinbase<T: ?Sized, C, K>(wallet: &mut T, block_fees: &BlockFees) -> Result<CbData, Error>
where
T: WalletBackend<K>,
T: WalletBackend<C, K>,
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<T: ?Sized, K>(
pub fn receive_coinbase<T: ?Sized, C, K>(
wallet: &mut T,
block_fees: &BlockFees,
) -> Result<(Output, TxKernel, BlockFees), Error>
where
T: WalletBackend<K>,
T: WalletBackend<C, K>,
C: WalletClient,
K: Keychain,
{
let root_key_id = wallet.keychain().root_key_id();

View file

@ -34,14 +34,16 @@ use libwallet::error::{Error, ErrorKind};
use util::secp::pedersen;
/// Combined trait to allow dynamic wallet dispatch
pub trait WalletInst<K>: WalletBackend<K> + WalletClient + Send + Sync + 'static
pub trait WalletInst<C, K>: WalletBackend<C, K> + Send + Sync + 'static
where
C: WalletClient,
K: Keychain,
{
}
impl<T, K> WalletInst<K> for T
impl<T, C, K> WalletInst<C, K> for T
where
T: WalletBackend<K> + WalletClient + Send + Sync + 'static,
T: WalletBackend<C, K> + 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<K>
pub trait WalletBackend<C, K>
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<Iterator<Item = OutputData> + '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<CbData, Error>;
fn create_coinbase(&self, dest: &str, block_fees: &BlockFees) -> Result<CbData, Error>;
/// Send a transaction slate to another listening wallet and return result
/// TODO: Probably need a slate wrapper type

View file

@ -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<K> {
pub struct LMDBBackend<C, K> {
db: store::Store,
config: WalletConfig,
/// passphrase: TODO better ways of dealing with this other than storing
passphrase: String,
/// Keychain
keychain: Option<K>,
/// client
client: C,
}
impl<K> LMDBBackend<K> {
pub fn new(config: WalletConfig, passphrase: &str) -> Result<Self, Error> {
impl<C, K> LMDBBackend<C, K> {
pub fn new(config: WalletConfig, passphrase: &str, client: C) -> Result<Self, Error> {
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<K> LMDBBackend<K> {
config: config.clone(),
passphrase: String::from(passphrase),
keychain: None,
client: client,
})
}
@ -80,8 +78,9 @@ impl<K> LMDBBackend<K> {
}
}
impl<K> WalletBackend<K> for LMDBBackend<K>
impl<C, K> WalletBackend<C, K> for LMDBBackend<C, K>
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<OutputData, Error> {
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<K>,
pub struct Batch<'a, C: 'a, K: 'a>
where
C: WalletClient,
K: Keychain,
{
store: &'a LMDBBackend<C, K>,
db: RefCell<Option<store::Batch<'a>>>,
}
#[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<K> WalletClient for LMDBBackend<K> {
/// 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<CbData, Error> {
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<Slate, Error> {
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<u64, Error> {
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<pedersen::Commitment>,
) -> Result<HashMap<pedersen::Commitment, String>, 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)
}
}

View file

@ -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<T, K>(wallet: &mut T, chain: &chain::Chain) -> Result<(), Error>
pub fn refresh_output_state_local<T, C, K>(wallet: &mut T, chain: &chain::Chain) -> Result<(), Error>
where
T: WalletBackend<K>,
T: WalletBackend<C, K>,
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<T, K>(
pub fn get_wallet_balances<T, C, K>(
wallet: &mut T,
height: u64,
) -> Result<(u64, u64, u64, u64, u64), Error>
where
T: WalletBackend<K>,
T: WalletBackend<C, K>,
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<T, K>(chain: &Chain, txs: Vec<&Transaction>, wallet: &mut T)
pub fn award_block_to_wallet<T, C, K>(chain: &Chain, txs: Vec<&Transaction>, wallet: &mut T)
where
T: WalletBackend<K>,
T: WalletBackend<C, K>,
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<T, K>(chain: &Chain, wallet: &mut T, num_rewards: usize)
pub fn award_blocks_to_wallet<T, C, K>(chain: &Chain, wallet: &mut T, num_rewards: usize)
where
T: WalletBackend<K>,
T: WalletBackend<C, K>,
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<ExtKeychain> {
pub fn create_wallet(dir: &str, client: HTTPWalletClient) -> FileWallet<HTTPWalletClient, ExtKeychain> {
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!(

View file

@ -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