db wallet now working identically to file wallet (#1259)

This commit is contained in:
Yeastplume 2018-07-13 15:27:16 +01:00 committed by GitHub
parent 42bc03f5f3
commit 41e20056d4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 213 additions and 222 deletions

View file

@ -65,6 +65,11 @@ run_tui = true
#Whether to run the wallet listener with the server by default #Whether to run the wallet listener with the server by default
run_wallet_listener = true run_wallet_listener = true
#whether to use the database backend storage for the default listener wallet
#true = lmdb storage
#false = files storage
use_db_wallet = false
# Whether to run the web-wallet API (will only run on localhost) # Whether to run the web-wallet API (will only run on localhost)
run_wallet_owner_api = true run_wallet_owner_api = true

View file

@ -25,19 +25,13 @@ use tokio_retry::Retry;
use failure::ResultExt; use failure::ResultExt;
use keychain::{self, Identifier, Keychain}; use keychain::{self, Identifier, Keychain};
use util::secp::pedersen;
use util::LOGGER; use util::LOGGER;
use error::{Error, ErrorKind}; use error::{Error, ErrorKind};
use client;
use libtx::slate::Slate;
use libwallet; use libwallet;
use libwallet::types::{ use libwallet::types::{OutputData, WalletBackend, WalletClient, WalletDetails, WalletOutputBatch};
BlockFees, CbData, OutputData, TxWrapper, WalletBackend, WalletClient, WalletDetails,
WalletOutputBatch,
};
use types::{WalletConfig, WalletSeed}; use types::{WalletConfig, WalletSeed};
@ -52,7 +46,7 @@ struct FileBatch<'a> {
/// List of outputs /// List of outputs
outputs: &'a mut HashMap<String, OutputData>, outputs: &'a mut HashMap<String, OutputData>,
/// Wallet Details /// Wallet Details
details: &'a mut WalletDetails, details: WalletDetails,
/// Data file path /// Data file path
data_file_path: String, data_file_path: String,
/// Details file path /// Details file path
@ -67,10 +61,6 @@ impl<'a> WalletOutputBatch for FileBatch<'a> {
Ok(()) Ok(())
} }
fn details(&mut self) -> &mut WalletDetails {
&mut self.details
}
fn get(&self, id: &Identifier) -> Result<OutputData, libwallet::Error> { fn get(&self, id: &Identifier) -> Result<OutputData, libwallet::Error> {
self.outputs self.outputs
.get(&id.to_hex()) .get(&id.to_hex())
@ -78,15 +68,16 @@ impl<'a> WalletOutputBatch for FileBatch<'a> {
.ok_or(libwallet::ErrorKind::Backend("not found".to_string()).into()) .ok_or(libwallet::ErrorKind::Backend("not found".to_string()).into())
} }
fn iter<'b>(&'b self) -> Box<Iterator<Item = OutputData> + 'b> {
Box::new(self.outputs.values().cloned())
}
fn delete(&mut self, id: &Identifier) -> Result<(), libwallet::Error> { fn delete(&mut self, id: &Identifier) -> Result<(), libwallet::Error> {
let _ = self.outputs.remove(&id.to_hex()); let _ = self.outputs.remove(&id.to_hex());
Ok(()) Ok(())
} }
fn save_details(&mut self, _r: Identifier, w: WalletDetails) -> Result<(), libwallet::Error> {
self.details = w;
Ok(())
}
fn lock_output(&mut self, out: &mut OutputData) -> Result<(), libwallet::Error> { fn lock_output(&mut self, out: &mut OutputData) -> Result<(), libwallet::Error> {
if let Some(out_to_lock) = self.outputs.get_mut(&out.key_id.to_hex()) { if let Some(out_to_lock) = self.outputs.get_mut(&out.key_id.to_hex()) {
if out_to_lock.value == out.value { if out_to_lock.value == out.value {
@ -219,7 +210,7 @@ where
Ok(Box::new(FileBatch { Ok(Box::new(FileBatch {
outputs: &mut self.outputs, outputs: &mut self.outputs,
details: &mut self.details, details: self.details.clone(),
data_file_path: self.data_file_path.clone(), data_file_path: self.data_file_path.clone(),
details_file_path: self.details_file_path.clone(), details_file_path: self.details_file_path.clone(),
lock_file_path: self.lock_file_path.clone(), lock_file_path: self.lock_file_path.clone(),
@ -231,94 +222,28 @@ where
&'a mut self, &'a mut self,
root_key_id: keychain::Identifier, root_key_id: keychain::Identifier,
) -> Result<u32, libwallet::Error> { ) -> Result<u32, libwallet::Error> {
let mut details = self.details(root_key_id.clone())?;
let mut max_n = 0;
for out in self.iter() {
if max_n < out.n_child && out.root_key_id == root_key_id {
max_n = out.n_child;
}
}
let mut batch = self.batch()?; let mut batch = self.batch()?;
{ if details.last_child_index <= max_n {
let mut max_n = 0; details.last_child_index = max_n + 1;
for out in batch.iter() {
if max_n < out.n_child && out.root_key_id == root_key_id {
max_n = out.n_child;
}
}
let details = batch.details();
if details.last_child_index <= max_n {
details.last_child_index = max_n + 1;
} else {
details.last_child_index += 1;
}
}
batch.commit()?;
Ok(batch.details().last_child_index)
}
/// Select spendable coins from the wallet.
/// Default strategy is to spend the maximum number of outputs (up to
/// max_outputs). Alternative strategy is to spend smallest outputs first
/// but only as many as necessary. When we introduce additional strategies
/// we should pass something other than a bool in.
fn select_coins(
&self,
root_key_id: keychain::Identifier,
amount: u64,
current_height: u64,
minimum_confirmations: u64,
max_outputs: usize,
select_all: bool,
) -> Vec<OutputData> {
// first find all eligible outputs based on number of confirmations
let mut eligible = self.outputs
.values()
.filter(|out| {
out.root_key_id == root_key_id
&& out.eligible_to_spend(current_height, minimum_confirmations)
})
.cloned()
.collect::<Vec<OutputData>>();
// sort eligible outputs by increasing value
eligible.sort_by_key(|out| out.value);
// use a sliding window to identify potential sets of possible outputs to spend
// Case of amount > total amount of max_outputs(500):
// The limit exists because by default, we always select as many inputs as
// possible in a transaction, to reduce both the Output set and the fees.
// But that only makes sense up to a point, hence the limit to avoid being too
// greedy. But if max_outputs(500) is actually not enough to cover the whole
// amount, the wallet should allow going over it to satisfy what the user
// wants to send. So the wallet considers max_outputs more of a soft limit.
if eligible.len() > max_outputs {
for window in eligible.windows(max_outputs) {
let windowed_eligibles = window.iter().cloned().collect::<Vec<_>>();
if let Some(outputs) = self.select_from(amount, select_all, windowed_eligibles) {
return outputs;
}
}
// Not exist in any window of which total amount >= amount.
// Then take coins from the smallest one up to the total amount of selected
// coins = the amount.
if let Some(outputs) = self.select_from(amount, false, eligible.clone()) {
debug!(
LOGGER,
"Extending maximum number of outputs. {} outputs selected.",
outputs.len()
);
return outputs;
}
} else { } else {
if let Some(outputs) = self.select_from(amount, select_all, eligible.clone()) { details.last_child_index += 1;
return outputs;
}
} }
batch.save_details(root_key_id.clone(), details.clone())?;
// we failed to find a suitable set of outputs to spend, batch.commit()?;
// so return the largest amount we can so we can provide guidance on what is Ok(details.last_child_index)
// possible
eligible.reverse();
eligible.iter().take(max_outputs).cloned().collect()
} }
/// Return current metadata /// Return current metadata
fn details(&mut self) -> &mut WalletDetails { fn details(&mut self, _root_key_id: Identifier) -> Result<WalletDetails, libwallet::Error> {
&mut self.details self.batch()?;
Ok(self.details.clone())
} }
/// Restore wallet contents /// Restore wallet contents
@ -455,35 +380,4 @@ where
.context(ErrorKind::FileWallet(&"Error writing wallet details file")) .context(ErrorKind::FileWallet(&"Error writing wallet details file"))
.map_err(|e| e.into()) .map_err(|e| e.into())
} }
// Select the full list of outputs if we are using the select_all strategy.
// Otherwise select just enough outputs to cover the desired amount.
fn select_from(
&self,
amount: u64,
select_all: bool,
outputs: Vec<OutputData>,
) -> Option<Vec<OutputData>> {
let total = outputs.iter().fold(0, |acc, x| acc + x.value);
if total >= amount {
if select_all {
return Some(outputs.iter().cloned().collect());
} else {
let mut selected_amount = 0;
return Some(
outputs
.iter()
.take_while(|out| {
let res = selected_amount < amount;
selected_amount += out.value;
res
})
.cloned()
.collect(),
);
}
} else {
None
}
}
} }

View file

@ -56,10 +56,12 @@ pub mod libwallet;
pub mod lmdb_wallet; pub mod lmdb_wallet;
mod types; mod types;
pub use client::{create_coinbase, HTTPWalletClient};
pub use error::{Error, ErrorKind}; pub use error::{Error, ErrorKind};
pub use file_wallet::FileWallet; pub use file_wallet::FileWallet;
pub use client::{create_coinbase, HTTPWalletClient};
pub use libwallet::controller; pub use libwallet::controller;
pub use libwallet::types::{BlockFees, CbData, WalletBackend, WalletClient, WalletInfo, WalletInst}; pub use libwallet::types::{
BlockFees, CbData, WalletBackend, WalletClient, WalletInfo, WalletInst,
};
pub use lmdb_wallet::{wallet_db_exists, LMDBBackend}; pub use lmdb_wallet::{wallet_db_exists, LMDBBackend};
pub use types::{WalletConfig, WalletSeed}; pub use types::{WalletConfig, WalletSeed};

View file

@ -67,7 +67,6 @@ where
include_spent: bool, include_spent: bool,
refresh_from_node: bool, refresh_from_node: bool,
) -> Result<(bool, Vec<OutputData>), Error> { ) -> Result<(bool, Vec<OutputData>), Error> {
let mut w = self.wallet.lock().unwrap(); let mut w = self.wallet.lock().unwrap();
w.open_with_credentials()?; w.open_with_credentials()?;
@ -120,7 +119,7 @@ where
let client; let client;
let mut slate_out: Slate; let mut slate_out: Slate;
let lock_fn_out; let lock_fn_out;
client = w.client().clone(); client = w.client().clone();
let (slate, context, lock_fn) = tx::create_send_tx( let (slate, context, lock_fn) = tx::create_send_tx(
&mut **w, &mut **w,
@ -196,7 +195,7 @@ where
Ok(height) => { Ok(height) => {
w.close()?; w.close()?;
Ok((height, true)) Ok((height, true))
}, }
Err(_) => { Err(_) => {
let outputs = self.retrieve_outputs(true, false)?; let outputs = self.retrieve_outputs(true, false)?;
let height = match outputs.1.iter().map(|out| out.height).max() { let height = match outputs.1.iter().map(|out| out.height).max() {
@ -210,7 +209,7 @@ where
} }
/// Attempt to update outputs in wallet, return whether it was successful /// Attempt to update outputs in wallet, return whether it was successful
fn update_outputs(&self, w: &mut W ) -> bool { fn update_outputs(&self, w: &mut W) -> bool {
match updater::refresh_outputs(&mut *w) { match updater::refresh_outputs(&mut *w) {
Ok(_) => true, Ok(_) => true,
Err(_) => false, Err(_) => false,
@ -255,7 +254,6 @@ where
let res = updater::build_coinbase(&mut **w, block_fees); let res = updater::build_coinbase(&mut **w, block_fees);
w.close()?; w.close()?;
res res
} }
/// Receive a transaction from a sender /// Receive a transaction from a sender

View file

@ -40,10 +40,7 @@ use util::LOGGER;
/// Instantiate wallet Owner API for a single-use (command line) call /// Instantiate wallet Owner API for a single-use (command line) call
/// Return a function containing a loaded API context to call /// Return a function containing a loaded API context to call
pub fn owner_single_use<F, T: ?Sized, C, K>( pub fn owner_single_use<F, T: ?Sized, C, K>(wallet: Arc<Mutex<Box<T>>>, f: F) -> Result<(), Error>
wallet: Arc<Mutex<Box<T>>>,
f: F,
) -> Result<(), Error>
where where
T: WalletBackend<C, K>, T: WalletBackend<C, K>,
F: FnOnce(&mut APIOwner<T, C, K>) -> Result<(), Error>, F: FnOnce(&mut APIOwner<T, C, K>) -> Result<(), Error>,
@ -56,10 +53,7 @@ where
/// Instantiate wallet Foreign API for a single-use (command line) call /// Instantiate wallet Foreign API for a single-use (command line) call
/// Return a function containing a loaded API context to call /// Return a function containing a loaded API context to call
pub fn foreign_single_use<F, T: ?Sized, C, K>( pub fn foreign_single_use<F, T: ?Sized, C, K>(wallet: Arc<Mutex<Box<T>>>, f: F) -> Result<(), Error>
wallet: Arc<Mutex<Box<T>>>,
f: F,
) -> Result<(), Error>
where where
T: WalletBackend<C, K>, T: WalletBackend<C, K>,
F: FnOnce(&mut APIForeign<T, C, K>) -> Result<(), Error>, F: FnOnce(&mut APIForeign<T, C, K>) -> Result<(), Error>,
@ -264,7 +258,11 @@ where
} }
} }
fn issue_send_tx(&self, req: &mut Request, api: &mut APIOwner<T, C, K>) -> Result<Slate, Error> { fn issue_send_tx(
&self,
req: &mut Request,
api: &mut APIOwner<T, C, K>,
) -> Result<Slate, Error> {
let struct_body = req.get::<bodyparser::Struct<SendTXArgs>>(); let struct_body = req.get::<bodyparser::Struct<SendTXArgs>>();
match struct_body { match struct_body {
Ok(Some(args)) => api.issue_send_tx( Ok(Some(args)) => api.issue_send_tx(

View file

@ -172,8 +172,9 @@ where
let mut start_index = 1; let mut start_index = 1;
let mut result_vec: Vec<OutputResult> = vec![]; let mut result_vec: Vec<OutputResult> = vec![];
loop { loop {
let (highest_index, last_retrieved_index, outputs) = let (highest_index, last_retrieved_index, outputs) = wallet
wallet.client().get_outputs_by_pmmr_index(start_index, batch_size)?; .client()
.get_outputs_by_pmmr_index(start_index, batch_size)?;
info!( info!(
LOGGER, LOGGER,
"Retrieved {} outputs, up to index {}. (Highest index: {})", "Retrieved {} outputs, up to index {}. (Highest index: {})",

View file

@ -20,6 +20,8 @@ use libwallet::error::{Error, ErrorKind};
use libwallet::internal::{keys, sigcontext}; use libwallet::internal::{keys, sigcontext};
use libwallet::types::*; use libwallet::types::*;
use util::LOGGER;
/// Initialize a transaction on the sender side, returns a corresponding /// Initialize a transaction on the sender side, returns a corresponding
/// libwallet transaction slate with the appropriate inputs selected, /// libwallet transaction slate with the appropriate inputs selected,
/// and saves the private wallet identifiers of our selected outputs /// and saves the private wallet identifiers of our selected outputs
@ -211,8 +213,8 @@ where
let key_id = wallet.keychain().root_key_id(); let key_id = wallet.keychain().root_key_id();
// select some spendable coins from the wallet // select some spendable coins from the wallet
let mut coins = wallet.select_coins( let (max_outputs, mut coins) = select_coins(
key_id.clone(), wallet,
amount, amount,
current_height, current_height,
minimum_confirmations, minimum_confirmations,
@ -220,18 +222,6 @@ where
selection_strategy_is_use_all, selection_strategy_is_use_all,
); );
// Get the maximum number of outputs in the wallet
let max_outputs = wallet
.select_coins(
key_id.clone(),
amount,
current_height,
minimum_confirmations,
max_outputs,
true,
)
.len();
// sender is responsible for setting the fee on the partial tx // sender is responsible for setting the fee on the partial tx
// recipient should double check the fee calculation and not blindly trust the // recipient should double check the fee calculation and not blindly trust the
// sender // sender
@ -265,14 +255,14 @@ where
} }
// select some spendable coins from the wallet // select some spendable coins from the wallet
coins = wallet.select_coins( coins = select_coins(
key_id.clone(), wallet,
amount_with_fee, amount_with_fee,
current_height, current_height,
minimum_confirmations, minimum_confirmations,
max_outputs, max_outputs,
selection_strategy_is_use_all, selection_strategy_is_use_all,
); ).1;
fee = tx_fee(coins.len(), 2, None); fee = tx_fee(coins.len(), 2, None);
total = coins.iter().map(|c| c.value).sum(); total = coins.iter().map(|c| c.value).sum();
amount_with_fee = amount + fee; amount_with_fee = amount + fee;
@ -334,3 +324,105 @@ where
Ok((parts, change, change_derivation)) Ok((parts, change, change_derivation))
} }
/// Select spendable coins from a wallet.
/// Default strategy is to spend the maximum number of outputs (up to
/// max_outputs). Alternative strategy is to spend smallest outputs first
/// but only as many as necessary. When we introduce additional strategies
/// we should pass something other than a bool in.
/// TODO: Possibly move this into another trait to be owned by a wallet?
pub fn select_coins<T: ?Sized, C, K>(
wallet: &mut T,
amount: u64,
current_height: u64,
minimum_confirmations: u64,
max_outputs: usize,
select_all: bool,
) -> (usize, Vec<OutputData>)
// max_outputs_available, Outputs
where
T: WalletBackend<C, K>,
C: WalletClient,
K: Keychain,
{
// first find all eligible outputs based on number of confirmations
let root_key_id = wallet.keychain().root_key_id();
let mut eligible = wallet
.iter()
.filter(|out| {
out.root_key_id == root_key_id
&& out.eligible_to_spend(current_height, minimum_confirmations)
})
.collect::<Vec<OutputData>>();
let max_available = eligible.len();
// sort eligible outputs by increasing value
eligible.sort_by_key(|out| out.value);
// use a sliding window to identify potential sets of possible outputs to spend
// Case of amount > total amount of max_outputs(500):
// The limit exists because by default, we always select as many inputs as
// possible in a transaction, to reduce both the Output set and the fees.
// But that only makes sense up to a point, hence the limit to avoid being too
// greedy. But if max_outputs(500) is actually not enough to cover the whole
// amount, the wallet should allow going over it to satisfy what the user
// wants to send. So the wallet considers max_outputs more of a soft limit.
if eligible.len() > max_outputs {
for window in eligible.windows(max_outputs) {
let windowed_eligibles = window.iter().cloned().collect::<Vec<_>>();
if let Some(outputs) = select_from(amount, select_all, windowed_eligibles) {
return (max_available, outputs);
}
}
// Not exist in any window of which total amount >= amount.
// Then take coins from the smallest one up to the total amount of selected
// coins = the amount.
if let Some(outputs) = select_from(amount, false, eligible.clone()) {
debug!(
LOGGER,
"Extending maximum number of outputs. {} outputs selected.",
outputs.len()
);
return (max_available, outputs);
}
} else {
if let Some(outputs) = select_from(amount, select_all, eligible.clone()) {
return (max_available, outputs);
}
}
// we failed to find a suitable set of outputs to spend,
// so return the largest amount we can so we can provide guidance on what is
// possible
eligible.reverse();
(
max_available,
eligible.iter().take(max_outputs).cloned().collect(),
)
}
fn select_from(amount: u64, select_all: bool, outputs: Vec<OutputData>) -> Option<Vec<OutputData>> {
let total = outputs.iter().fold(0, |acc, x| acc + x.value);
if total >= amount {
if select_all {
return Some(outputs.iter().cloned().collect());
} else {
let mut selected_amount = 0;
return Some(
outputs
.iter()
.take_while(|out| {
let res = selected_amount < amount;
selected_amount += out.value;
res
})
.cloned()
.collect(),
);
}
} else {
None
}
}

View file

@ -152,11 +152,9 @@ where
let _ = updater::refresh_outputs(wallet); let _ = updater::refresh_outputs(wallet);
let key_id = keychain.root_key_id();
// select some spendable coins from the wallet // select some spendable coins from the wallet
let coins = wallet.select_coins( let (_, coins) = selection::select_coins(
key_id.clone(), wallet,
amount, amount,
current_height, current_height,
minimum_confirmations, minimum_confirmations,

View file

@ -112,6 +112,8 @@ where
// api output (if it exists) and refresh it in-place in the wallet. // api output (if it exists) and refresh it in-place in the wallet.
// Note: minimizing the time we spend holding the wallet lock. // Note: minimizing the time we spend holding the wallet lock.
{ {
let root_key_id = wallet.keychain().root_key_id();
let mut details = wallet.details(root_key_id.clone())?;
let mut batch = wallet.batch()?; let mut batch = wallet.batch()?;
for (commit, id) in wallet_outputs.iter() { for (commit, id) in wallet_outputs.iter() {
if let Ok(mut output) = batch.get(id) { if let Ok(mut output) = batch.get(id) {
@ -123,8 +125,8 @@ where
} }
} }
{ {
let details = batch.details();
details.last_confirmed_height = height; details.last_confirmed_height = height;
batch.save_details(root_key_id, details)?;
} }
batch.commit()?; batch.commit()?;
} }
@ -184,7 +186,8 @@ where
C: WalletClient, C: WalletClient,
K: Keychain, K: Keychain,
{ {
let current_height = wallet.details().last_confirmed_height; let root_key_id = wallet.keychain().root_key_id();
let current_height = wallet.details(root_key_id.clone())?.last_confirmed_height;
let keychain = wallet.keychain().clone(); let keychain = wallet.keychain().clone();
let outputs = wallet let outputs = wallet
.iter() .iter()
@ -220,7 +223,10 @@ where
} }
/// Build a coinbase output and insert into wallet /// Build a coinbase output and insert into wallet
pub fn build_coinbase<T: ?Sized, C, 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 where
T: WalletBackend<C, K>, T: WalletBackend<C, K>,
C: WalletClient, C: WalletClient,

View file

@ -82,18 +82,7 @@ where
fn next_child<'a>(&mut self, root_key_id: Identifier) -> Result<u32, Error>; fn next_child<'a>(&mut self, root_key_id: Identifier) -> Result<u32, Error>;
/// Return current details /// Return current details
fn details(&mut self) -> &mut WalletDetails; fn details(&mut self, root_key_id: Identifier) -> Result<WalletDetails, Error>;
/// Select spendable coins from the wallet
fn select_coins(
&self,
root_key_id: Identifier,
amount: u64,
current_height: u64,
minimum_confirmations: u64,
max_outputs: usize,
select_all: bool,
) -> Vec<OutputData>;
/// Attempt to restore the contents of a wallet from seed /// Attempt to restore the contents of a wallet from seed
fn restore(&mut self) -> Result<(), Error>; fn restore(&mut self) -> Result<(), Error>;
@ -106,18 +95,15 @@ pub trait WalletOutputBatch {
/// Add or update data about an output to the backend /// Add or update data about an output to the backend
fn save(&mut self, out: OutputData) -> Result<(), Error>; fn save(&mut self, out: OutputData) -> Result<(), Error>;
/// Get wallet details
fn details(&mut self) -> &mut WalletDetails;
/// Gets output data by id /// Gets output data by id
fn get(&self, id: &Identifier) -> Result<OutputData, Error>; fn get(&self, id: &Identifier) -> Result<OutputData, Error>;
/// Iterate over all output data in batch
fn iter<'b>(&'b self) -> Box<Iterator<Item = OutputData> + 'b>;
/// Delete data about an output to the backend /// Delete data about an output to the backend
fn delete(&mut self, id: &Identifier) -> Result<(), Error>; fn delete(&mut self, id: &Identifier) -> Result<(), Error>;
/// save wallet details
fn save_details(&mut self, r: Identifier, w: WalletDetails) -> Result<(), Error>;
/// Save an output as locked in the backend /// Save an output as locked in the backend
fn lock_output(&mut self, out: &mut OutputData) -> Result<(), Error>; fn lock_output(&mut self, out: &mut OutputData) -> Result<(), Error>;

View file

@ -29,6 +29,7 @@ pub const DB_DIR: &'static str = "wallet_data";
const OUTPUT_PREFIX: u8 = 'o' as u8; const OUTPUT_PREFIX: u8 = 'o' as u8;
const DERIV_PREFIX: u8 = 'd' as u8; const DERIV_PREFIX: u8 = 'd' as u8;
const CONFIRMED_HEIGHT_PREFIX: u8 = 'c' as u8;
impl From<store::Error> for Error { impl From<store::Error> for Error {
fn from(error: store::Error) -> Error { fn from(error: store::Error) -> Error {
@ -121,38 +122,39 @@ where
fn batch<'a>(&'a mut self) -> Result<Box<WalletOutputBatch + 'a>, Error> { fn batch<'a>(&'a mut self) -> Result<Box<WalletOutputBatch + 'a>, Error> {
Ok(Box::new(Batch { Ok(Box::new(Batch {
store: self, _store: self,
db: RefCell::new(Some(self.db.batch()?)), db: RefCell::new(Some(self.db.batch()?)),
})) }))
} }
fn next_child<'a>(&mut self, root_key_id: Identifier) -> Result<u32, Error> { fn next_child<'a>(&mut self, root_key_id: Identifier) -> Result<u32, Error> {
let mut details = self.details(root_key_id.clone())?;
let mut batch = self.batch()?;
details.last_child_index = details.last_child_index + 1;
batch.save_details(root_key_id, details.clone())?;
batch.commit()?;
Ok(details.last_child_index + 1)
}
fn details(&mut self, root_key_id: Identifier) -> Result<WalletDetails, Error> {
let batch = self.db.batch()?; let batch = self.db.batch()?;
// a simple counter, only one batch per db guarantees atomicity
let deriv_key = to_key(DERIV_PREFIX, &mut root_key_id.to_bytes().to_vec()); let deriv_key = to_key(DERIV_PREFIX, &mut root_key_id.to_bytes().to_vec());
let deriv_idx = match batch.get_ser(&deriv_key)? { let deriv_idx = match batch.get_ser(&deriv_key)? {
Some(idx) => idx, Some(idx) => idx,
None => 0, None => 0,
}; };
batch.put_ser(&deriv_key, &(deriv_idx + 1))?; let height_key = to_key(
batch.commit()?; CONFIRMED_HEIGHT_PREFIX,
Ok(deriv_idx + 1) &mut root_key_id.to_bytes().to_vec(),
} );
let last_confirmed_height = match batch.get_ser(&height_key)? {
fn select_coins( Some(h) => h,
&self, None => 0,
root_key_id: Identifier, };
amount: u64, Ok(WalletDetails {
current_height: u64, last_child_index: deriv_idx,
minimum_confirmations: u64, last_confirmed_height: last_confirmed_height,
max_outputs: usize, })
select_all: bool,
) -> Vec<OutputData> {
unimplemented!()
}
fn details(&mut self) -> &mut WalletDetails {
unimplemented!()
} }
fn restore(&mut self) -> Result<(), Error> { fn restore(&mut self) -> Result<(), Error> {
@ -168,7 +170,7 @@ where
C: WalletClient, C: WalletClient,
K: Keychain, K: Keychain,
{ {
store: &'a LMDBBackend<C, K>, _store: &'a LMDBBackend<C, K>,
db: RefCell<Option<store::Batch<'a>>>, db: RefCell<Option<store::Batch<'a>>>,
} }
@ -184,10 +186,6 @@ where
Ok(()) Ok(())
} }
fn details(&mut self) -> &mut WalletDetails {
unimplemented!()
}
fn get(&self, id: &Identifier) -> Result<OutputData, Error> { fn get(&self, id: &Identifier) -> Result<OutputData, Error> {
let key = to_key(OUTPUT_PREFIX, &mut id.to_bytes().to_vec()); let key = to_key(OUTPUT_PREFIX, &mut id.to_bytes().to_vec());
option_to_not_found( option_to_not_found(
@ -196,16 +194,31 @@ where
).map_err(|e| e.into()) ).map_err(|e| e.into())
} }
fn iter<'b>(&'b self) -> Box<Iterator<Item = OutputData> + 'b> {
unimplemented!();
}
fn delete(&mut self, id: &Identifier) -> Result<(), Error> { fn delete(&mut self, id: &Identifier) -> Result<(), Error> {
let key = to_key(OUTPUT_PREFIX, &mut id.to_bytes().to_vec()); let key = to_key(OUTPUT_PREFIX, &mut id.to_bytes().to_vec());
self.db.borrow().as_ref().unwrap().delete(&key)?; self.db.borrow().as_ref().unwrap().delete(&key)?;
Ok(()) Ok(())
} }
fn save_details(&mut self, root_key_id: Identifier, d: WalletDetails) -> Result<(), Error> {
let deriv_key = to_key(DERIV_PREFIX, &mut root_key_id.to_bytes().to_vec());
let height_key = to_key(
CONFIRMED_HEIGHT_PREFIX,
&mut root_key_id.to_bytes().to_vec(),
);
self.db
.borrow()
.as_ref()
.unwrap()
.put_ser(&deriv_key, &d.last_child_index)?;
self.db
.borrow()
.as_ref()
.unwrap()
.put_ser(&height_key, &d.last_confirmed_height)?;
Ok(())
}
fn lock_output(&mut self, out: &mut OutputData) -> Result<(), Error> { fn lock_output(&mut self, out: &mut OutputData) -> Result<(), Error> {
out.lock(); out.lock();
self.save(out.clone()) self.save(out.clone())

View file

@ -208,8 +208,6 @@ fn file_wallet_basic_transaction_api() {
basic_transaction_api(test_dir, common::BackendType::FileBackend); basic_transaction_api(test_dir, common::BackendType::FileBackend);
} }
// not yet ready
#[ignore]
#[test] #[test]
fn db_wallet_basic_transaction_api() { fn db_wallet_basic_transaction_api() {
let test_dir = "test_output/basic_transaction_api_db"; let test_dir = "test_output/basic_transaction_api_db";