Recover outputs from multiple wallets using same seed (#2348)

* adding optional mmr index to key ids

* rustfmt

* update index

* add mmr index to output display

* change restore to match on commit instead of ID, add extensive restore/check tests for multiple wallets using same seed

* rustfmt

* ensure check restores unknown accounts as well

* rustfmt

* remove storage of commit from wallet
This commit is contained in:
Yeastplume 2019-01-12 18:39:29 +00:00 committed by GitHub
parent f9a20aef0d
commit e93b380a06
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 649 additions and 140 deletions

View file

@ -94,6 +94,9 @@ impl TxHashSetHandler {
id
)))?;
let commit = Commitment::from_vec(c);
let output_pos = w(&self.chain)
.get_output_pos(&commit)
.context(ErrorKind::NotFound)?;
let merkle_proof = chain::Chain::get_merkle_proof_for_pos(&w(&self.chain), commit)
.map_err(|_| ErrorKind::NotFound)?;
Ok(OutputPrintable {
@ -104,6 +107,7 @@ impl TxHashSetHandler {
proof_hash: "".to_string(),
block_height: None,
merkle_proof: Some(merkle_proof),
mmr_index: output_pos,
})
}
}

View file

@ -51,7 +51,8 @@ pub fn get_output(
for x in outputs.iter() {
if let Ok(_) = w(chain).is_unspent(&x) {
let block_height = w(chain).get_header_for_output(&x).unwrap().height;
return Ok((Output::new(&commit, block_height), x.clone()));
let output_pos = w(chain).get_output_pos(&x.commit).unwrap_or(0);
return Ok((Output::new(&commit, block_height, output_pos), x.clone()));
}
}
Err(ErrorKind::NotFound)?

View file

@ -159,15 +159,18 @@ pub struct Output {
pub commit: PrintableCommitment,
/// Height of the block which contains the output
pub height: u64,
/// MMR Index of output
pub mmr_index: u64,
}
impl Output {
pub fn new(commit: &pedersen::Commitment, height: u64) -> Output {
pub fn new(commit: &pedersen::Commitment, height: u64, mmr_index: u64) -> Output {
Output {
commit: PrintableCommitment {
commit: commit.clone(),
},
height: height,
mmr_index: mmr_index,
}
}
}
@ -242,6 +245,8 @@ pub struct OutputPrintable {
pub block_height: Option<u64>,
/// Merkle Proof
pub merkle_proof: Option<MerkleProof>,
/// MMR Position
pub mmr_index: u64,
}
impl OutputPrintable {
@ -279,6 +284,8 @@ impl OutputPrintable {
merkle_proof = chain.get_merkle_proof(&out_id, &block_header.unwrap()).ok()
};
let output_pos = chain.get_output_pos(&output.commit).unwrap_or(0);
OutputPrintable {
output_type,
commit: output.commit,
@ -287,6 +294,7 @@ impl OutputPrintable {
proof_hash: util::to_hex(output.proof.hash().to_vec()),
block_height,
merkle_proof,
mmr_index: output_pos,
}
}
@ -327,6 +335,7 @@ impl serde::ser::Serialize for OutputPrintable {
let hex_merkle_proof = &self.merkle_proof.clone().map(|x| x.to_hex());
state.serialize_field("merkle_proof", &hex_merkle_proof)?;
state.serialize_field("mmr_index", &self.mmr_index)?;
state.end()
}
@ -347,6 +356,7 @@ impl<'de> serde::de::Deserialize<'de> for OutputPrintable {
ProofHash,
BlockHeight,
MerkleProof,
MmrIndex,
}
struct OutputPrintableVisitor;
@ -369,6 +379,7 @@ impl<'de> serde::de::Deserialize<'de> for OutputPrintable {
let mut proof_hash = None;
let mut block_height = None;
let mut merkle_proof = None;
let mut mmr_index = None;
while let Some(key) = map.next_key()? {
match key {
@ -410,6 +421,10 @@ impl<'de> serde::de::Deserialize<'de> for OutputPrintable {
}
}
}
Field::MmrIndex => {
no_dup!(mmr_index);
mmr_index = Some(map.next_value()?)
}
}
}
@ -421,12 +436,19 @@ impl<'de> serde::de::Deserialize<'de> for OutputPrintable {
proof_hash: proof_hash.unwrap(),
block_height: block_height,
merkle_proof: merkle_proof,
mmr_index: mmr_index.unwrap(),
})
}
}
const FIELDS: &'static [&'static str] =
&["output_type", "commit", "spent", "proof", "proof_hash"];
const FIELDS: &'static [&'static str] = &[
"output_type",
"commit",
"spent",
"proof",
"proof_hash",
"mmr_index",
];
deserializer.deserialize_struct("OutputPrintable", FIELDS, OutputPrintableVisitor)
}
}
@ -662,7 +684,8 @@ mod test {
\"proof\":null,\
\"proof_hash\":\"ed6ba96009b86173bade6a9227ed60422916593fa32dd6d78b25b7a4eeef4946\",\
\"block_height\":0,\
\"merkle_proof\":null\
\"merkle_proof\":null,\
\"mmr_index\":0\
}";
let deserialized: OutputPrintable = serde_json::from_str(&hex_output).unwrap();
let serialized = serde_json::to_string(&deserialized).unwrap();
@ -674,7 +697,8 @@ mod test {
let hex_commit =
"{\
\"commit\":\"083eafae5d61a85ab07b12e1a51b3918d8e6de11fc6cde641d54af53608aa77b9f\",\
\"height\":0\
\"height\":0,\
\"mmr_index\":0\
}";
let deserialized: Output = serde_json::from_str(&hex_commit).unwrap();
let serialized = serde_json::to_string(&deserialized).unwrap();

View file

@ -1041,6 +1041,11 @@ impl Chain {
self.txhashset.read().last_n_kernel(distance)
}
/// as above, for kernels
pub fn get_output_pos(&self, commit: &Commitment) -> Result<u64, Error> {
Ok(self.txhashset.read().get_output_pos(commit)?)
}
/// outputs by insertion index
pub fn unspent_outputs_by_insertion_index(
&self,

View file

@ -257,6 +257,11 @@ impl TxHashSet {
}
}
/// Return Commit's MMR position
pub fn get_output_pos(&self, commit: &Commitment) -> Result<u64, Error> {
Ok(self.commit_index.get_output_pos(&commit)?)
}
/// build a new merkle proof for the given position.
pub fn merkle_proof(&mut self, commit: Commitment) -> Result<MerkleProof, String> {
let pos = self.commit_index.get_output_pos(&commit).unwrap();

View file

@ -44,6 +44,7 @@ pub fn outputs(
table.set_titles(row![
bMG->"Output Commitment",
bMG->"MMR Index",
bMG->"Block Height",
bMG->"Locked Until",
bMG->"Status",
@ -55,6 +56,10 @@ pub fn outputs(
for (out, commit) in outputs {
let commit = format!("{}", util::to_hex(commit.as_ref().to_vec()));
let index = match out.mmr_index {
None => "None".to_owned(),
Some(t) => t.to_string(),
};
let height = format!("{}", out.height);
let lock_height = format!("{}", out.lock_height);
let is_coinbase = format!("{}", out.is_coinbase);
@ -75,6 +80,7 @@ pub fn outputs(
if dark_background_color_scheme {
table.add_row(row![
bFC->commit,
bFB->index,
bFB->height,
bFB->lock_height,
bFR->status,

View file

@ -32,13 +32,14 @@ where
pub fn retrieve_existing_key<T: ?Sized, C, K>(
wallet: &T,
key_id: Identifier,
mmr_index: Option<u64>,
) -> Result<(Identifier, u32), Error>
where
T: WalletBackend<C, K>,
C: NodeClient,
K: Keychain,
{
let existing = wallet.get(&key_id)?;
let existing = wallet.get(&key_id, &mmr_index)?;
let key_id = existing.key_id.clone();
let derivation = existing.n_child;
Ok((key_id, derivation))

View file

@ -32,6 +32,8 @@ struct OutputResult {
///
pub n_child: u32,
///
pub mmr_index: u64,
///
pub value: u64,
///
pub height: u64,
@ -45,7 +47,7 @@ struct OutputResult {
fn identify_utxo_outputs<T, C, K>(
wallet: &mut T,
outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64)>,
outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)>,
) -> Result<Vec<OutputResult>, Error>
where
T: WalletBackend<C, K>,
@ -60,7 +62,7 @@ where
);
for output in outputs.iter() {
let (commit, proof, is_coinbase, height) = output;
let (commit, proof, is_coinbase, height, mmr_index) = output;
// attempt to unwind message from the RP and get a value
// will fail if it's not ours
let info = proof::rewind(wallet.keychain(), *commit, None, *proof)?;
@ -80,8 +82,8 @@ where
let key_id = Identifier::from_serialized_path(3u8, &info.message.as_bytes());
info!(
"Output found: {:?}, amount: {:?}, key_id: {:?}",
commit, info.value, key_id
"Output found: {:?}, amount: {:?}, key_id: {:?}, mmr_index: {},",
commit, info.value, key_id, mmr_index,
);
wallet_outputs.push(OutputResult {
@ -93,6 +95,7 @@ where
lock_height: lock_height,
is_coinbase: *is_coinbase,
blinding: info.blinding,
mmr_index: *mmr_index,
});
}
Ok(wallet_outputs)
@ -163,6 +166,7 @@ where
root_key_id: parent_key_id.clone(),
key_id: output.key_id,
n_child: output.n_child,
mmr_index: Some(output.mmr_index),
value: output.value,
status: OutputStatus::Unspent,
height: output.height,
@ -246,7 +250,7 @@ where
// check all definitive outputs exist in the wallet outputs
for deffo in chain_outs.into_iter() {
let matched_out = wallet_outputs.iter().find(|wo| wo.0.key_id == deffo.key_id);
let matched_out = wallet_outputs.iter().find(|wo| wo.1 == deffo.commit);
match matched_out {
Some(s) => {
if s.0.status == OutputStatus::Spent {
@ -317,10 +321,30 @@ where
);
cancel_tx_log_entry(wallet, &o)?;
let mut batch = wallet.batch()?;
batch.delete(&o.key_id)?;
batch.delete(&o.key_id, &o.mmr_index)?;
batch.commit()?;
}
// restore labels, account paths and child derivation indices
let label_base = "account";
let mut index = 1;
for (path, max_child_index) in found_parents.iter() {
if *path == ExtKeychain::derive_key_id(2, 0, 0, 0, 0) {
//default path already exists
continue;
}
let res = wallet.acct_path_iter().find(|e| e.path == *path);
if let None = res {
let label = format!("{}_{}", label_base, index);
keys::set_acct_path(wallet, &label, path)?;
index = index + 1;
}
{
let mut batch = wallet.batch()?;
batch.save_child_index(path, max_child_index + 1)?;
}
}
Ok(())
}

View file

@ -82,12 +82,12 @@ where
// Store our private identifiers for each input
for input in inputs {
context.add_input(&input.key_id);
context.add_input(&input.key_id, &input.mmr_index);
}
// Store change output(s)
for (_, id) in &change_amounts_derivations {
context.add_output(&id);
for (_, id, mmr_index) in &change_amounts_derivations {
context.add_output(&id, &mmr_index);
}
let lock_inputs = context.get_inputs().clone();
@ -107,7 +107,7 @@ where
let mut amount_debited = 0;
t.num_inputs = lock_inputs.len();
for id in lock_inputs {
let mut coin = batch.get(&id).unwrap();
let mut coin = batch.get(&id.0, &id.1).unwrap();
coin.tx_log_entry = Some(log_id);
amount_debited = amount_debited + coin.value;
batch.lock_output(&mut coin)?;
@ -116,13 +116,14 @@ where
t.amount_debited = amount_debited;
// write the output representing our change
for (change_amount, id) in &change_amounts_derivations {
for (change_amount, id, _) in &change_amounts_derivations {
t.num_outputs += 1;
t.amount_credited += change_amount;
batch.save(OutputData {
root_key_id: parent_key_id.clone(),
key_id: id.clone(),
n_child: id.to_path().last_path_index(),
mmr_index: None,
value: change_amount.clone(),
status: OutputStatus::Unconfirmed,
height: current_height,
@ -183,7 +184,7 @@ where
.unwrap(),
);
context.add_output(&key_id);
context.add_output(&key_id, &None);
// Create closure that adds the output to recipient's wallet
// (up to the caller to decide when to do)
@ -197,6 +198,7 @@ where
batch.save(OutputData {
root_key_id: parent_key_id.clone(),
key_id: key_id_inner.clone(),
mmr_index: None,
n_child: key_id_inner.to_path().last_path_index(),
value: amount,
status: OutputStatus::Unconfirmed,
@ -229,9 +231,9 @@ pub fn select_send_tx<T: ?Sized, C, K>(
(
Vec<Box<build::Append<K>>>,
Vec<OutputData>,
Vec<(u64, Identifier)>, // change amounts and derivations
u64, // amount
u64, // fee
Vec<(u64, Identifier, Option<u64>)>, // change amounts and derivations
u64, // amount
u64, // fee
),
Error,
>
@ -337,7 +339,13 @@ pub fn inputs_and_change<T: ?Sized, C, K>(
amount: u64,
fee: u64,
num_change_outputs: usize,
) -> Result<(Vec<Box<build::Append<K>>>, Vec<(u64, Identifier)>), Error>
) -> Result<
(
Vec<Box<build::Append<K>>>,
Vec<(u64, Identifier, Option<u64>)>,
),
Error,
>
where
T: WalletBackend<C, K>,
C: NodeClient,
@ -387,7 +395,7 @@ where
let change_key = wallet.next_child().unwrap();
change_amounts_derivations.push((change_amount, change_key.clone()));
change_amounts_derivations.push((change_amount, change_key.clone(), None));
parts.push(build::output(change_amount, change_key));
}
}

View file

@ -69,11 +69,12 @@ where
}
outputs.sort_by_key(|out| out.n_child);
let keychain = wallet.keychain().clone();
let res = outputs
.into_iter()
.map(|out| {
let commit = wallet.get_commitment(&out.key_id).unwrap();
let commit = keychain.commit(out.value, &out.key_id).unwrap();
(out, commit)
})
.collect();
@ -138,13 +139,14 @@ pub fn map_wallet_outputs<T: ?Sized, C, K>(
wallet: &mut T,
parent_key_id: &Identifier,
update_all: bool,
) -> Result<HashMap<pedersen::Commitment, Identifier>, Error>
) -> Result<HashMap<pedersen::Commitment, (Identifier, Option<u64>)>, Error>
where
T: WalletBackend<C, K>,
C: NodeClient,
K: Keychain,
{
let mut wallet_outputs: HashMap<pedersen::Commitment, Identifier> = HashMap::new();
let mut wallet_outputs: HashMap<pedersen::Commitment, (Identifier, Option<u64>)> =
HashMap::new();
let keychain = wallet.keychain().clone();
let unspents: Vec<OutputData> = wallet
.iter()
@ -175,7 +177,7 @@ where
for out in unspents {
let commit = keychain.commit(out.value, &out.key_id)?;
wallet_outputs.insert(commit, out.key_id.clone());
wallet_outputs.insert(commit, (out.key_id.clone(), out.mmr_index));
}
Ok(wallet_outputs)
}
@ -197,7 +199,7 @@ where
for mut o in outputs {
// unlock locked outputs
if o.status == OutputStatus::Unconfirmed {
batch.delete(&o.key_id)?;
batch.delete(&o.key_id, &o.mmr_index)?;
}
if o.status == OutputStatus::Locked {
o.status = OutputStatus::Unconfirmed;
@ -219,8 +221,8 @@ where
/// Apply refreshed API output data to the wallet
pub fn apply_api_outputs<T: ?Sized, C, K>(
wallet: &mut T,
wallet_outputs: &HashMap<pedersen::Commitment, Identifier>,
api_outputs: &HashMap<pedersen::Commitment, (String, u64)>,
wallet_outputs: &HashMap<pedersen::Commitment, (Identifier, Option<u64>)>,
api_outputs: &HashMap<pedersen::Commitment, (String, u64, u64)>,
height: u64,
parent_key_id: &Identifier,
) -> Result<(), libwallet::Error>
@ -245,8 +247,8 @@ where
return Ok(());
}
let mut batch = wallet.batch()?;
for (commit, id) in wallet_outputs.iter() {
if let Ok(mut output) = batch.get(id) {
for (commit, (id, mmr_index)) in wallet_outputs.iter() {
if let Ok(mut output) = batch.get(id, mmr_index) {
match api_outputs.get(&commit) {
Some(o) => {
// if this is a coinbase tx being confirmed, it's recordable in tx log
@ -345,7 +347,7 @@ where
}
let mut batch = wallet.batch()?;
for id in ids_to_del {
batch.delete(&id)?;
batch.delete(&id, &None)?;
}
batch.commit()?;
Ok(())
@ -458,7 +460,7 @@ where
let parent_key_id = wallet.parent_key_id();
let key_id = match key_id {
Some(key_id) => keys::retrieve_existing_key(wallet, key_id)?.0,
Some(key_id) => keys::retrieve_existing_key(wallet, key_id, None)?.0,
None => keys::next_available_key(wallet)?,
};
@ -469,6 +471,7 @@ where
root_key_id: parent_key_id,
key_id: key_id.clone(),
n_child: key_id.to_path().last_path_index(),
mmr_index: None,
value: reward(block_fees.fees),
status: OutputStatus::Unconfirmed,
height: height,

View file

@ -81,10 +81,7 @@ where
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = OutputData> + 'a>;
/// Get output data by id
fn get(&self, id: &Identifier) -> Result<OutputData, Error>;
/// Get associated output commitment by id.
fn get_commitment(&mut self, id: &Identifier) -> Result<pedersen::Commitment, Error>;
fn get(&self, id: &Identifier, mmr_index: &Option<u64>) -> Result<OutputData, Error>;
/// Get an (Optional) tx log entry by uuid
fn get_tx_log_entry(&self, uuid: &Uuid) -> Result<Option<TxLogEntry>, Error>;
@ -139,13 +136,13 @@ where
fn save(&mut self, out: OutputData) -> Result<(), Error>;
/// Gets output data by id
fn get(&self, id: &Identifier) -> Result<OutputData, Error>;
fn get(&self, id: &Identifier, mmr_index: &Option<u64>) -> Result<OutputData, Error>;
/// Iterate over all output data stored by the backend
fn iter(&self) -> Box<dyn Iterator<Item = OutputData>>;
/// Delete data about an output from the backend
fn delete(&mut self, id: &Identifier) -> Result<(), Error>;
fn delete(&mut self, id: &Identifier, mmr_index: &Option<u64>) -> Result<(), Error>;
/// Save last stored child index of a given parent
fn save_child_index(&mut self, parent_key_id: &Identifier, child_n: u32) -> Result<(), Error>;
@ -211,13 +208,13 @@ pub trait NodeClient: Sync + Send + Clone {
fn get_outputs_from_node(
&self,
wallet_outputs: Vec<pedersen::Commitment>,
) -> Result<HashMap<pedersen::Commitment, (String, u64)>, Error>;
) -> Result<HashMap<pedersen::Commitment, (String, u64, u64)>, Error>;
/// Get a list of outputs from the node by traversing the UTXO
/// set in PMMR index order.
/// Returns
/// (last available output index, last insertion index retrieved,
/// outputs(commit, proof, is_coinbase, height))
/// outputs(commit, proof, is_coinbase, height, mmr_index))
fn get_outputs_by_pmmr_index(
&self,
start_height: u64,
@ -226,7 +223,7 @@ pub trait NodeClient: Sync + Send + Clone {
(
u64,
u64,
Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64)>,
Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)>,
),
Error,
>;
@ -244,6 +241,9 @@ pub struct OutputData {
pub key_id: Identifier,
/// How many derivations down from the root key
pub n_child: u32,
/// PMMR Index, used on restore in case of duplicate wallets using the same
/// key_id (2 wallets using same seed, for instance
pub mmr_index: Option<u64>,
/// Value of the output, necessary to rebuild the commitment
pub value: u64,
/// Current status of the output
@ -368,9 +368,9 @@ pub struct Context {
/// (basically a SecretKey)
pub sec_nonce: SecretKey,
/// store my outputs between invocations
pub output_ids: Vec<Identifier>,
pub output_ids: Vec<(Identifier, Option<u64>)>,
/// store my inputs
pub input_ids: Vec<Identifier>,
pub input_ids: Vec<(Identifier, Option<u64>)>,
/// store the calculated fee
pub fee: u64,
}
@ -391,23 +391,23 @@ impl Context {
impl Context {
/// Tracks an output contributing to my excess value (if it needs to
/// be kept between invocations
pub fn add_output(&mut self, output_id: &Identifier) {
self.output_ids.push(output_id.clone());
pub fn add_output(&mut self, output_id: &Identifier, mmr_index: &Option<u64>) {
self.output_ids.push((output_id.clone(), mmr_index.clone()));
}
/// Returns all stored outputs
pub fn get_outputs(&self) -> Vec<Identifier> {
pub fn get_outputs(&self) -> Vec<(Identifier, Option<u64>)> {
self.output_ids.clone()
}
/// Tracks IDs of my inputs into the transaction
/// be kept between invocations
pub fn add_input(&mut self, input_id: &Identifier) {
self.input_ids.push(input_id.clone());
pub fn add_input(&mut self, input_id: &Identifier, mmr_index: &Option<u64>) {
self.input_ids.push((input_id.clone(), mmr_index.clone()));
}
/// Returns all stored input identifiers
pub fn get_inputs(&self) -> Vec<Identifier> {
pub fn get_inputs(&self) -> Vec<(Identifier, Option<u64>)> {
self.input_ids.clone()
}

View file

@ -38,13 +38,11 @@ use crate::libwallet::{internal, Error, ErrorKind};
use crate::types::{WalletConfig, WalletSeed};
use crate::util;
use crate::util::secp::constants::SECRET_KEY_SIZE;
use crate::util::secp::pedersen;
use crate::util::ZeroingString;
pub const DB_DIR: &'static str = "db";
pub const TX_SAVE_DIR: &'static str = "saved_txs";
const COMMITMENT_PREFIX: u8 = 'C' as u8;
const OUTPUT_PREFIX: u8 = 'o' as u8;
const DERIV_PREFIX: u8 = 'd' as u8;
const CONFIRMED_HEIGHT_PREFIX: u8 = 'c' as u8;
@ -222,38 +220,14 @@ where
self.parent_key_id.clone()
}
fn get(&self, id: &Identifier) -> Result<OutputData, Error> {
let key = to_key(OUTPUT_PREFIX, &mut id.to_bytes().to_vec());
fn get(&self, id: &Identifier, mmr_index: &Option<u64>) -> Result<OutputData, Error> {
let key = match mmr_index {
Some(i) => to_key_u64(OUTPUT_PREFIX, &mut id.to_bytes().to_vec(), *i),
None => 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())
}
fn get_commitment(&mut self, id: &Identifier) -> Result<pedersen::Commitment, Error> {
let key = to_key(COMMITMENT_PREFIX, &mut id.to_bytes().to_vec());
let res: Result<pedersen::Commitment, Error> =
option_to_not_found(self.db.get_ser(&key), &format!("Key Id: {}", id))
.map_err(|e| e.into());
// "cache hit" and return the commitment
if let Ok(commit) = res {
Ok(commit)
} else {
let out = self.get(id)?;
// Save the output data back to the db
// which builds and saves the associated commitment.
{
let mut batch = self.batch()?;
batch.save(out)?;
batch.commit()?;
}
// Now retrieve the saved commitment and return it.
option_to_not_found(self.db.get_ser(&key), &format!("Key Id: {}", id))
.map_err(|e| e.into())
}
}
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = OutputData> + 'a> {
Box::new(self.db.iter(&[OUTPUT_PREFIX]).unwrap())
}
@ -402,22 +376,21 @@ where
fn save(&mut self, out: OutputData) -> Result<(), Error> {
// Save the output data to the db.
{
let key = to_key(OUTPUT_PREFIX, &mut out.key_id.to_bytes().to_vec());
let key = match out.mmr_index {
Some(i) => to_key_u64(OUTPUT_PREFIX, &mut out.key_id.to_bytes().to_vec(), i),
None => to_key(OUTPUT_PREFIX, &mut out.key_id.to_bytes().to_vec()),
};
self.db.borrow().as_ref().unwrap().put_ser(&key, &out)?;
}
// Save the associated output commitment.
{
let key = to_key(COMMITMENT_PREFIX, &mut out.key_id.to_bytes().to_vec());
let commit = self.keychain().commit(out.value, &out.key_id)?;
self.db.borrow().as_ref().unwrap().put_ser(&key, &commit)?;
}
Ok(())
}
fn get(&self, id: &Identifier) -> Result<OutputData, Error> {
let key = to_key(OUTPUT_PREFIX, &mut id.to_bytes().to_vec());
fn get(&self, id: &Identifier, mmr_index: &Option<u64>) -> Result<OutputData, Error> {
let key = match mmr_index {
Some(i) => to_key_u64(OUTPUT_PREFIX, &mut id.to_bytes().to_vec(), *i),
None => to_key(OUTPUT_PREFIX, &mut id.to_bytes().to_vec()),
};
option_to_not_found(
self.db.borrow().as_ref().unwrap().get_ser(&key),
&format!("Key ID: {}", id),
@ -436,16 +409,13 @@ where
)
}
fn delete(&mut self, id: &Identifier) -> Result<(), Error> {
fn delete(&mut self, id: &Identifier, mmr_index: &Option<u64>) -> Result<(), Error> {
// Delete the output data.
{
let key = to_key(OUTPUT_PREFIX, &mut id.to_bytes().to_vec());
let _ = self.db.borrow().as_ref().unwrap().delete(&key);
}
// Delete the associated output commitment.
{
let key = to_key(COMMITMENT_PREFIX, &mut id.to_bytes().to_vec());
let key = match mmr_index {
Some(i) => to_key_u64(OUTPUT_PREFIX, &mut id.to_bytes().to_vec(), *i),
None => to_key(OUTPUT_PREFIX, &mut id.to_bytes().to_vec()),
};
let _ = self.db.borrow().as_ref().unwrap().delete(&key);
}

View file

@ -89,7 +89,7 @@ impl NodeClient for HTTPNodeClient {
fn get_outputs_from_node(
&self,
wallet_outputs: Vec<pedersen::Commitment>,
) -> Result<HashMap<pedersen::Commitment, (String, u64)>, libwallet::Error> {
) -> Result<HashMap<pedersen::Commitment, (String, u64, u64)>, libwallet::Error> {
let addr = self.node_url();
// build the necessary query params -
// ?id=xxx&id=yyy&id=zzz
@ -99,7 +99,7 @@ impl NodeClient for HTTPNodeClient {
.collect();
// build a map of api outputs by commit so we can look them up efficiently
let mut api_outputs: HashMap<pedersen::Commitment, (String, u64)> = HashMap::new();
let mut api_outputs: HashMap<pedersen::Commitment, (String, u64, u64)> = HashMap::new();
let mut tasks = Vec::new();
for query_chunk in query_params.chunks(500) {
@ -125,7 +125,7 @@ impl NodeClient for HTTPNodeClient {
for out in res {
api_outputs.insert(
out.commit.commit(),
(util::to_hex(out.commit.to_vec()), out.height),
(util::to_hex(out.commit.to_vec()), out.height, out.mmr_index),
);
}
}
@ -140,7 +140,7 @@ impl NodeClient for HTTPNodeClient {
(
u64,
u64,
Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64)>,
Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)>,
),
libwallet::Error,
> {
@ -149,7 +149,7 @@ impl NodeClient for HTTPNodeClient {
let url = format!("{}/v1/txhashset/outputs?{}", addr, query_param,);
let mut api_outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64)> =
let mut api_outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)> =
Vec::new();
match api::client::get::<api::OutputListing>(url.as_str(), self.node_api_secret()) {
@ -164,6 +164,7 @@ impl NodeClient for HTTPNodeClient {
out.range_proof().unwrap(),
is_coinbase,
out.block_height.unwrap(),
out.mmr_index,
));
}

View file

@ -17,7 +17,8 @@ use self::core::core::{OutputFeatures, OutputIdentifier, Transaction};
use self::core::{consensus, global, pow, ser};
use self::util::secp::pedersen;
use self::util::Mutex;
use crate::libwallet::types::{BlockFees, CbData, NodeClient, WalletInst};
use crate::libwallet::api::APIOwner;
use crate::libwallet::types::{BlockFees, CbData, NodeClient, WalletInfo, WalletInst};
use crate::lmdb_wallet::LMDBBackend;
use crate::{controller, libwallet, WalletSeed};
use crate::{WalletBackend, WalletConfig};
@ -52,7 +53,8 @@ fn get_output_local(chain: &chain::Chain, commit: &pedersen::Commitment) -> Opti
for x in outputs.iter() {
if let Ok(_) = chain.is_unspent(&x) {
let block_height = chain.get_header_for_output(&x).unwrap().height;
return Some(api::Output::new(&commit, block_height));
let output_pos = chain.get_output_pos(&x.commit).unwrap_or(0);
return Some(api::Output::new(&commit, block_height, output_pos));
}
}
None
@ -153,14 +155,22 @@ where
}
/// dispatch a db wallet
pub fn create_wallet<C, K>(dir: &str, n_client: C) -> Arc<Mutex<dyn WalletInst<C, K>>>
pub fn create_wallet<C, K>(
dir: &str,
n_client: C,
rec_phrase: Option<&str>,
) -> Arc<Mutex<dyn WalletInst<C, K>>>
where
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
let z_string = match rec_phrase {
Some(s) => Some(util::ZeroingString::from(s)),
None => None,
};
let mut wallet_config = WalletConfig::default();
wallet_config.data_file_dir = String::from(dir);
let _ = WalletSeed::init_file(&wallet_config, 32, None, "");
let _ = WalletSeed::init_file(&wallet_config, 32, z_string, "");
let mut wallet = LMDBBackend::new(wallet_config.clone(), "", n_client)
.unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config));
wallet.open_with_credentials().unwrap_or_else(|e| {
@ -171,3 +181,45 @@ where
});
Arc::new(Mutex::new(wallet))
}
/// send an amount to a destination
pub fn send_to_dest<T: ?Sized, C, K>(
client: LocalWalletClient,
api: &mut APIOwner<T, C, K>,
dest: &str,
amount: u64,
) -> Result<(), libwallet::Error>
where
T: WalletBackend<C, K>,
C: NodeClient,
K: keychain::Keychain,
{
let (slate_i, lock_fn) = api.initiate_tx(
None, // account
amount, // amount
2, // minimum confirmations
500, // max outputs
1, // num change outputs
true, // select all outputs
None,
)?;
let mut slate = client.send_tx_slate_direct(dest, &slate_i)?;
api.tx_lock_outputs(&slate, lock_fn)?;
api.finalize_tx(&mut slate)?;
api.post_tx(&slate.tx, false)?; // mines a block
Ok(())
}
/// get wallet info totals
pub fn wallet_info<T: ?Sized, C, K>(
api: &mut APIOwner<T, C, K>,
) -> Result<WalletInfo, libwallet::Error>
where
T: WalletBackend<C, K>,
C: NodeClient,
K: keychain::Keychain,
{
let (wallet_refreshed, wallet_info) = api.retrieve_summary_info(true, 1)?;
assert!(wallet_refreshed);
Ok(wallet_info)
}

View file

@ -442,7 +442,7 @@ impl NodeClient for LocalWalletClient {
fn get_outputs_from_node(
&self,
wallet_outputs: Vec<pedersen::Commitment>,
) -> Result<HashMap<pedersen::Commitment, (String, u64)>, libwallet::Error> {
) -> Result<HashMap<pedersen::Commitment, (String, u64, u64)>, libwallet::Error> {
let query_params: Vec<String> = wallet_outputs
.iter()
.map(|commit| format!("{}", util::to_hex(commit.as_ref().to_vec())))
@ -463,11 +463,11 @@ impl NodeClient for LocalWalletClient {
let r = self.rx.lock();
let m = r.recv().unwrap();
let outputs: Vec<api::Output> = serde_json::from_str(&m.body).unwrap();
let mut api_outputs: HashMap<pedersen::Commitment, (String, u64)> = HashMap::new();
let mut api_outputs: HashMap<pedersen::Commitment, (String, u64, u64)> = HashMap::new();
for out in outputs {
api_outputs.insert(
out.commit.commit(),
(util::to_hex(out.commit.to_vec()), out.height),
(util::to_hex(out.commit.to_vec()), out.height, out.mmr_index),
);
}
Ok(api_outputs)
@ -481,7 +481,7 @@ impl NodeClient for LocalWalletClient {
(
u64,
u64,
Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64)>,
Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)>,
),
libwallet::Error,
> {
@ -504,7 +504,7 @@ impl NodeClient for LocalWalletClient {
let m = r.recv().unwrap();
let o: api::OutputListing = serde_json::from_str(&m.body).unwrap();
let mut api_outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64)> =
let mut api_outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)> =
Vec::new();
for out in o.outputs {
@ -517,6 +517,7 @@ impl NodeClient for LocalWalletClient {
out.range_proof().unwrap(),
is_coinbase,
out.block_height.unwrap(),
out.mmr_index,
));
}
Ok((o.highest_index, o.last_retrieved_index, api_outputs))

View file

@ -48,12 +48,14 @@ fn accounts_test_impl(test_dir: &str) -> Result<(), libwallet::Error> {
// Create a new wallet test client, and set its queues to communicate with the
// proxy
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
let wallet1 = test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone());
let wallet1 =
test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None);
wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone());
// define recipient wallet, add to proxy
let wallet2 = test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone());
let wallet2 =
test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone(), None);
wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone());
// Set the wallet proxy listener running

View file

@ -15,6 +15,7 @@
#[macro_use]
extern crate log;
use self::core::consensus;
use self::core::global;
use self::core::global::ChainTypes;
use self::keychain::ExtKeychain;
@ -38,7 +39,7 @@ fn setup(test_dir: &str) {
global::set_mining_mode(ChainTypes::AutomatedTesting);
}
/// Various tests on accounts within the same wallet
/// Various tests on checking functionality
fn check_repair_impl(test_dir: &str) -> Result<(), libwallet::Error> {
setup(test_dir);
// Create a new proxy to simulate server and wallet responses
@ -48,12 +49,14 @@ fn check_repair_impl(test_dir: &str) -> Result<(), libwallet::Error> {
// Create a new wallet test client, and set its queues to communicate with the
// proxy
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
let wallet1 = test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone());
let wallet1 =
test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None);
wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone());
// define recipient wallet, add to proxy
let wallet2 = test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone());
let wallet2 =
test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone(), None);
wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone());
// Set the wallet proxy listener running
@ -65,7 +68,7 @@ fn check_repair_impl(test_dir: &str) -> Result<(), libwallet::Error> {
// few values to keep things shorter
let reward = core::consensus::REWARD;
let cm = global::coinbase_maturity(); // assume all testing precedes soft fork height
let cm = global::coinbase_maturity() as u64; // assume all testing precedes soft fork height
// add some accounts
wallet::controller::owner_single_use(wallet1.clone(), |api| {
@ -115,8 +118,8 @@ fn check_repair_impl(test_dir: &str) -> Result<(), libwallet::Error> {
w.open_with_credentials()?;
{
let mut batch = w.batch()?;
batch.delete(&w1_outputs[4].key_id)?;
batch.delete(&w1_outputs[10].key_id)?;
batch.delete(&w1_outputs[4].key_id, &None)?;
batch.delete(&w1_outputs[10].key_id, &None)?;
let mut accidental_spent = w1_outputs[13].clone();
accidental_spent.status = libwallet::types::OutputStatus::Spent;
batch.save(accidental_spent)?;
@ -193,6 +196,385 @@ fn check_repair_impl(test_dir: &str) -> Result<(), libwallet::Error> {
Ok(())
}
fn two_wallets_one_seed_impl(test_dir: &str) -> Result<(), libwallet::Error> {
setup(test_dir);
let seed_phrase =
"affair pistol cancel crush garment candy ancient flag work \
market crush dry stand focus mutual weapon offer ceiling rival turn team spring \
where swift";
// Create a new proxy to simulate server and wallet responses
let mut wallet_proxy: WalletProxy<LocalWalletClient, ExtKeychain> = WalletProxy::new(test_dir);
let chain = wallet_proxy.chain.clone();
// Create a new wallet test client, and set its queues to communicate with the
// proxy
let m_client = LocalWalletClient::new("miner", wallet_proxy.tx.clone());
let miner =
test_framework::create_wallet(&format!("{}/miner", test_dir), m_client.clone(), None);
wallet_proxy.add_wallet("miner", m_client.get_send_instance(), miner.clone());
// non-mining recipient wallets
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
let wallet1 = test_framework::create_wallet(
&format!("{}/wallet1", test_dir),
client1.clone(),
Some(seed_phrase),
);
wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone());
let wallet2 = test_framework::create_wallet(
&format!("{}/wallet2", test_dir),
client2.clone(),
Some(seed_phrase),
);
wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone());
// we'll restore into here
let client3 = LocalWalletClient::new("wallet3", wallet_proxy.tx.clone());
let wallet3 = test_framework::create_wallet(
&format!("{}/wallet3", test_dir),
client3.clone(),
Some(seed_phrase),
);
wallet_proxy.add_wallet("wallet3", client3.get_send_instance(), wallet3.clone());
// also restore into here
let client4 = LocalWalletClient::new("wallet4", wallet_proxy.tx.clone());
let wallet4 = test_framework::create_wallet(
&format!("{}/wallet4", test_dir),
client4.clone(),
Some(seed_phrase),
);
wallet_proxy.add_wallet("wallet4", client4.get_send_instance(), wallet4.clone());
// Simulate a recover from seed without restore into here
let client5 = LocalWalletClient::new("wallet5", wallet_proxy.tx.clone());
let wallet5 = test_framework::create_wallet(
&format!("{}/wallet5", test_dir),
client5.clone(),
Some(seed_phrase),
);
wallet_proxy.add_wallet("wallet5", client5.get_send_instance(), wallet5.clone());
//simulate a recover from seed without restore into here
let client6 = LocalWalletClient::new("wallet6", wallet_proxy.tx.clone());
let wallet6 = test_framework::create_wallet(
&format!("{}/wallet6", test_dir),
client6.clone(),
Some(seed_phrase),
);
wallet_proxy.add_wallet("wallet6", client6.get_send_instance(), wallet6.clone());
let client7 = LocalWalletClient::new("wallet7", wallet_proxy.tx.clone());
let wallet7 = test_framework::create_wallet(
&format!("{}/wallet7", test_dir),
client7.clone(),
Some(seed_phrase),
);
wallet_proxy.add_wallet("wallet7", client7.get_send_instance(), wallet7.clone());
let client8 = LocalWalletClient::new("wallet8", wallet_proxy.tx.clone());
let wallet8 = test_framework::create_wallet(
&format!("{}/wallet8", test_dir),
client8.clone(),
Some(seed_phrase),
);
wallet_proxy.add_wallet("wallet8", client8.get_send_instance(), wallet8.clone());
let client9 = LocalWalletClient::new("wallet9", wallet_proxy.tx.clone());
let wallet9 = test_framework::create_wallet(
&format!("{}/wallet9", test_dir),
client9.clone(),
Some(seed_phrase),
);
wallet_proxy.add_wallet("wallet9", client9.get_send_instance(), wallet9.clone());
let client10 = LocalWalletClient::new("wallet10", wallet_proxy.tx.clone());
let wallet10 = test_framework::create_wallet(
&format!("{}/wallet10", test_dir),
client10.clone(),
Some(seed_phrase),
);
wallet_proxy.add_wallet("wallet10", client10.get_send_instance(), wallet10.clone());
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// few values to keep things shorter
let _reward = core::consensus::REWARD;
let cm = global::coinbase_maturity() as usize; // assume all testing precedes soft fork height
// Do some mining
let mut bh = 20u64;
let base_amount = consensus::GRIN_BASE;
let _ = test_framework::award_blocks_to_wallet(&chain, miner.clone(), bh as usize);
// send some funds to wallets 1
wallet::controller::owner_single_use(miner.clone(), |api| {
test_framework::send_to_dest(m_client.clone(), api, "wallet1", base_amount * 1)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet1", base_amount * 2)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet1", base_amount * 3)?;
bh += 3;
Ok(())
})?;
// 0) Check repair when all is okay should leave wallet contents alone
wallet::controller::owner_single_use(wallet1.clone(), |api| {
api.check_repair()?;
let info = test_framework::wallet_info(api)?;
assert_eq!(info.amount_currently_spendable, base_amount * 6);
assert_eq!(info.total, base_amount * 6);
Ok(())
})?;
// send some funds to wallet 2
wallet::controller::owner_single_use(miner.clone(), |api| {
test_framework::send_to_dest(m_client.clone(), api, "wallet2", base_amount * 4)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet2", base_amount * 5)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet2", base_amount * 6)?;
bh += 3;
Ok(())
})?;
let _ = test_framework::award_blocks_to_wallet(&chain, miner.clone(), cm);
bh += cm as u64;
// confirm balances
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let info = test_framework::wallet_info(api)?;
assert_eq!(info.amount_currently_spendable, base_amount * 6);
assert_eq!(info.total, base_amount * 6);
Ok(())
})?;
wallet::controller::owner_single_use(wallet2.clone(), |api| {
let info = test_framework::wallet_info(api)?;
assert_eq!(info.amount_currently_spendable, base_amount * 15);
assert_eq!(info.total, base_amount * 15);
Ok(())
})?;
// Now there should be outputs on the chain using the same
// seed + BIP32 path.
// 1) a full restore should recover all of them:
wallet::controller::owner_single_use(wallet3.clone(), |api| {
api.restore()?;
Ok(())
})?;
wallet::controller::owner_single_use(wallet3.clone(), |api| {
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 6);
assert_eq!(info.amount_currently_spendable, base_amount * 21);
assert_eq!(info.total, base_amount * 21);
Ok(())
})?;
// 2) check_repair should recover them into a single wallet
wallet::controller::owner_single_use(wallet1.clone(), |api| {
api.check_repair()?;
Ok(())
})?;
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 6);
assert_eq!(info.amount_currently_spendable, base_amount * 21);
Ok(())
})?;
// 3) If I recover from seed and start using the wallet without restoring,
// check_repair should restore the older outputs
wallet::controller::owner_single_use(miner.clone(), |api| {
test_framework::send_to_dest(m_client.clone(), api, "wallet4", base_amount * 7)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet4", base_amount * 8)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet4", base_amount * 9)?;
bh += 3;
Ok(())
})?;
let _ = test_framework::award_blocks_to_wallet(&chain, miner.clone(), cm);
bh += cm as u64;
wallet::controller::owner_single_use(wallet4.clone(), |api| {
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 3);
assert_eq!(info.amount_currently_spendable, base_amount * 24);
Ok(())
})?;
wallet::controller::owner_single_use(wallet5.clone(), |api| {
api.restore()?;
Ok(())
})?;
wallet::controller::owner_single_use(wallet5.clone(), |api| {
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 9);
assert_eq!(info.amount_currently_spendable, base_amount * (45));
Ok(())
})?;
// 4) If I recover from seed and start using the wallet without restoring,
// check_repair should restore the older outputs
wallet::controller::owner_single_use(miner.clone(), |api| {
test_framework::send_to_dest(m_client.clone(), api, "wallet6", base_amount * 10)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet6", base_amount * 11)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet6", base_amount * 12)?;
bh += 3;
Ok(())
})?;
let _ = test_framework::award_blocks_to_wallet(&chain, miner.clone(), cm as usize);
bh += cm as u64;
wallet::controller::owner_single_use(wallet6.clone(), |api| {
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 3);
assert_eq!(info.amount_currently_spendable, base_amount * 33);
Ok(())
})?;
wallet::controller::owner_single_use(wallet6.clone(), |api| {
api.check_repair()?;
Ok(())
})?;
wallet::controller::owner_single_use(wallet6.clone(), |api| {
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 12);
assert_eq!(info.amount_currently_spendable, base_amount * (78));
Ok(())
})?;
// 5) Start using same seed with a different account, amounts should
// be distinct and restore should return funds from other account
wallet::controller::owner_single_use(miner.clone(), |api| {
test_framework::send_to_dest(m_client.clone(), api, "wallet7", base_amount * 13)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet7", base_amount * 14)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet7", base_amount * 15)?;
bh += 3;
Ok(())
})?;
// mix it up a bit
wallet::controller::owner_single_use(wallet7.clone(), |api| {
api.create_account_path("account_1")?;
api.set_active_account("account_1")?;
Ok(())
})?;
wallet::controller::owner_single_use(miner.clone(), |api| {
test_framework::send_to_dest(m_client.clone(), api, "wallet7", base_amount * 1)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet7", base_amount * 2)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet7", base_amount * 3)?;
bh += 3;
Ok(())
})?;
// check balances
let _ = test_framework::award_blocks_to_wallet(&chain, miner.clone(), cm);
bh += cm as u64;
wallet::controller::owner_single_use(wallet7.clone(), |api| {
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 3);
assert_eq!(info.amount_currently_spendable, base_amount * 6);
api.set_active_account("default")?;
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 3);
assert_eq!(info.amount_currently_spendable, base_amount * 42);
Ok(())
})?;
wallet::controller::owner_single_use(wallet8.clone(), |api| {
api.restore()?;
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 15);
assert_eq!(info.amount_currently_spendable, base_amount * 120);
api.set_active_account("account_1")?;
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 3);
assert_eq!(info.amount_currently_spendable, base_amount * 6);
Ok(())
})?;
// 6) Start using same seed with a different account, now overwriting
// ids on account 2 as well, check_repair should get all outputs created
// to now into 2 accounts
wallet::controller::owner_single_use(wallet9.clone(), |api| {
api.create_account_path("account_1")?;
api.set_active_account("account_1")?;
Ok(())
})?;
wallet::controller::owner_single_use(miner.clone(), |api| {
test_framework::send_to_dest(m_client.clone(), api, "wallet9", base_amount * 4)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet9", base_amount * 5)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet9", base_amount * 6)?;
bh += 3;
Ok(())
})?;
wallet::controller::owner_single_use(wallet9.clone(), |api| {
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 3);
assert_eq!(info.amount_currently_spendable, base_amount * 15);
api.check_repair()?;
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 6);
assert_eq!(info.amount_currently_spendable, base_amount * 21);
api.set_active_account("default")?;
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 15);
assert_eq!(info.amount_currently_spendable, base_amount * 120);
Ok(())
})?;
let _ = test_framework::award_blocks_to_wallet(&chain, miner.clone(), cm);
bh += cm as u64;
// 7) Ensure check_repair creates missing accounts
wallet::controller::owner_single_use(wallet10.clone(), |api| {
api.check_repair()?;
api.set_active_account("account_1")?;
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 6);
assert_eq!(info.amount_currently_spendable, base_amount * 21);
api.set_active_account("default")?;
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 15);
assert_eq!(info.amount_currently_spendable, base_amount * 120);
Ok(())
})?;
// let logging finish
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn check_repair() {
let test_dir = "test_output/check_repair";
@ -200,3 +582,11 @@ fn check_repair() {
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap());
}
}
#[test]
fn two_wallets_one_seed() {
let test_dir = "test_output/two_wallets_one_seed";
if let Err(e) = two_wallets_one_seed_impl(test_dir) {
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap());
}
}

View file

@ -45,11 +45,13 @@ fn file_exchange_test_impl(test_dir: &str) -> Result<(), libwallet::Error> {
let chain = wallet_proxy.chain.clone();
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
let wallet1 = test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone());
let wallet1 =
test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None);
wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone());
let wallet2 = test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone());
let wallet2 =
test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone(), None);
wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone());
// Set the wallet proxy listener running

View file

@ -89,7 +89,7 @@ fn aggsig_sender_receiver_interaction() {
rx_cx = Context::new(&keychain.secp(), blind);
let (pub_excess, pub_nonce) = rx_cx.get_public_keys(&keychain.secp());
rx_cx.add_output(&key_id);
rx_cx.add_output(&key_id, &None);
pub_nonce_sum = PublicKey::from_combination(
keychain.secp(),
@ -305,7 +305,7 @@ fn aggsig_sender_receiver_interaction_offset() {
rx_cx = Context::new(&keychain.secp(), blind);
let (pub_excess, pub_nonce) = rx_cx.get_public_keys(&keychain.secp());
rx_cx.add_output(&key_id);
rx_cx.add_output(&key_id, &None);
pub_nonce_sum = PublicKey::from_combination(
keychain.secp(),

View file

@ -47,11 +47,13 @@ fn file_repost_test_impl(test_dir: &str) -> Result<(), libwallet::Error> {
let chain = wallet_proxy.chain.clone();
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
let wallet1 = test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone());
let wallet1 =
test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None);
wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone());
let wallet2 = test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone());
let wallet2 =
test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone(), None);
wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone());
// Set the wallet proxy listener running

View file

@ -49,7 +49,7 @@ fn restore_wallet(base_dir: &str, wallet_dir: &str) -> Result<(), libwallet::Err
let mut wallet_proxy: WalletProxy<LocalWalletClient, ExtKeychain> = WalletProxy::new(base_dir);
let client = LocalWalletClient::new(wallet_dir, wallet_proxy.tx.clone());
let wallet = test_framework::create_wallet(&dest_dir, client.clone());
let wallet = test_framework::create_wallet(&dest_dir, client.clone(), None);
wallet_proxy.add_wallet(wallet_dir, client.get_send_instance(), wallet.clone());
@ -82,7 +82,7 @@ fn compare_wallet_restore(
let mut wallet_proxy: WalletProxy<LocalWalletClient, ExtKeychain> = WalletProxy::new(base_dir);
let client = LocalWalletClient::new(wallet_dir, wallet_proxy.tx.clone());
let wallet_source = test_framework::create_wallet(&source_dir, client.clone());
let wallet_source = test_framework::create_wallet(&source_dir, client.clone(), None);
wallet_proxy.add_wallet(
&wallet_dir,
client.get_send_instance(),
@ -90,7 +90,7 @@ fn compare_wallet_restore(
);
let client = LocalWalletClient::new(&restore_name, wallet_proxy.tx.clone());
let wallet_dest = test_framework::create_wallet(&dest_dir, client.clone());
let wallet_dest = test_framework::create_wallet(&dest_dir, client.clone(), None);
wallet_proxy.add_wallet(
&restore_name,
client.get_send_instance(),
@ -178,12 +178,14 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> {
// Create a new wallet test client, and set its queues to communicate with the
// proxy
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
let wallet1 = test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone());
let wallet1 =
test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None);
wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
// define recipient wallet, add to proxy
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone());
let wallet2 = test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone());
let wallet2 =
test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone(), None);
wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone());
// wallet 2 will use another account
@ -201,7 +203,8 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> {
// Another wallet
let client3 = LocalWalletClient::new("wallet3", wallet_proxy.tx.clone());
let wallet3 = test_framework::create_wallet(&format!("{}/wallet3", test_dir), client3.clone());
let wallet3 =
test_framework::create_wallet(&format!("{}/wallet3", test_dir), client3.clone(), None);
wallet_proxy.add_wallet("wallet3", client3.get_send_instance(), wallet3.clone());
// Set the wallet proxy listener running

View file

@ -48,7 +48,8 @@ fn self_send_test_impl(test_dir: &str) -> Result<(), libwallet::Error> {
// Create a new wallet test client, and set its queues to communicate with the
// proxy
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
let wallet1 = test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone());
let wallet1 =
test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None);
wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
// Set the wallet proxy listener running

View file

@ -51,12 +51,14 @@ fn basic_transaction_api(test_dir: &str) -> Result<(), libwallet::Error> {
// Create a new wallet test client, and set its queues to communicate with the
// proxy
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
let wallet1 = test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone());
let wallet1 =
test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None);
wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone());
// define recipient wallet, add to proxy
let wallet2 = test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone());
let wallet2 =
test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone(), None);
wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone());
// Set the wallet proxy listener running
@ -299,12 +301,14 @@ fn tx_rollback(test_dir: &str) -> Result<(), libwallet::Error> {
// Create a new wallet test client, and set its queues to communicate with the
// proxy
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
let wallet1 = test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone());
let wallet1 =
test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None);
wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
// define recipient wallet, add to proxy
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone());
let wallet2 = test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone());
let wallet2 =
test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone(), None);
wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone());
// Set the wallet proxy listener running