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

View file

@ -51,7 +51,8 @@ pub fn get_output(
for x in outputs.iter() { for x in outputs.iter() {
if let Ok(_) = w(chain).is_unspent(&x) { if let Ok(_) = w(chain).is_unspent(&x) {
let block_height = w(chain).get_header_for_output(&x).unwrap().height; 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)? Err(ErrorKind::NotFound)?

View file

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

View file

@ -1041,6 +1041,11 @@ impl Chain {
self.txhashset.read().last_n_kernel(distance) 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 /// outputs by insertion index
pub fn unspent_outputs_by_insertion_index( pub fn unspent_outputs_by_insertion_index(
&self, &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. /// build a new merkle proof for the given position.
pub fn merkle_proof(&mut self, commit: Commitment) -> Result<MerkleProof, String> { pub fn merkle_proof(&mut self, commit: Commitment) -> Result<MerkleProof, String> {
let pos = self.commit_index.get_output_pos(&commit).unwrap(); let pos = self.commit_index.get_output_pos(&commit).unwrap();

View file

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

View file

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

View file

@ -32,6 +32,8 @@ struct OutputResult {
/// ///
pub n_child: u32, pub n_child: u32,
/// ///
pub mmr_index: u64,
///
pub value: u64, pub value: u64,
/// ///
pub height: u64, pub height: u64,
@ -45,7 +47,7 @@ struct OutputResult {
fn identify_utxo_outputs<T, C, K>( fn identify_utxo_outputs<T, C, K>(
wallet: &mut T, wallet: &mut T,
outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64)>, outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)>,
) -> Result<Vec<OutputResult>, Error> ) -> Result<Vec<OutputResult>, Error>
where where
T: WalletBackend<C, K>, T: WalletBackend<C, K>,
@ -60,7 +62,7 @@ where
); );
for output in outputs.iter() { 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 // attempt to unwind message from the RP and get a value
// will fail if it's not ours // will fail if it's not ours
let info = proof::rewind(wallet.keychain(), *commit, None, *proof)?; 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()); let key_id = Identifier::from_serialized_path(3u8, &info.message.as_bytes());
info!( info!(
"Output found: {:?}, amount: {:?}, key_id: {:?}", "Output found: {:?}, amount: {:?}, key_id: {:?}, mmr_index: {},",
commit, info.value, key_id commit, info.value, key_id, mmr_index,
); );
wallet_outputs.push(OutputResult { wallet_outputs.push(OutputResult {
@ -93,6 +95,7 @@ where
lock_height: lock_height, lock_height: lock_height,
is_coinbase: *is_coinbase, is_coinbase: *is_coinbase,
blinding: info.blinding, blinding: info.blinding,
mmr_index: *mmr_index,
}); });
} }
Ok(wallet_outputs) Ok(wallet_outputs)
@ -163,6 +166,7 @@ where
root_key_id: parent_key_id.clone(), root_key_id: parent_key_id.clone(),
key_id: output.key_id, key_id: output.key_id,
n_child: output.n_child, n_child: output.n_child,
mmr_index: Some(output.mmr_index),
value: output.value, value: output.value,
status: OutputStatus::Unspent, status: OutputStatus::Unspent,
height: output.height, height: output.height,
@ -246,7 +250,7 @@ where
// check all definitive outputs exist in the wallet outputs // check all definitive outputs exist in the wallet outputs
for deffo in chain_outs.into_iter() { 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 { match matched_out {
Some(s) => { Some(s) => {
if s.0.status == OutputStatus::Spent { if s.0.status == OutputStatus::Spent {
@ -317,10 +321,30 @@ where
); );
cancel_tx_log_entry(wallet, &o)?; cancel_tx_log_entry(wallet, &o)?;
let mut batch = wallet.batch()?; let mut batch = wallet.batch()?;
batch.delete(&o.key_id)?; batch.delete(&o.key_id, &o.mmr_index)?;
batch.commit()?; 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(()) Ok(())
} }

View file

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

View file

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

View file

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

View file

@ -38,13 +38,11 @@ use crate::libwallet::{internal, Error, ErrorKind};
use crate::types::{WalletConfig, WalletSeed}; use crate::types::{WalletConfig, WalletSeed};
use crate::util; use crate::util;
use crate::util::secp::constants::SECRET_KEY_SIZE; use crate::util::secp::constants::SECRET_KEY_SIZE;
use crate::util::secp::pedersen;
use crate::util::ZeroingString; use crate::util::ZeroingString;
pub const DB_DIR: &'static str = "db"; pub const DB_DIR: &'static str = "db";
pub const TX_SAVE_DIR: &'static str = "saved_txs"; pub const TX_SAVE_DIR: &'static str = "saved_txs";
const COMMITMENT_PREFIX: u8 = 'C' as u8;
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; const CONFIRMED_HEIGHT_PREFIX: u8 = 'c' as u8;
@ -222,38 +220,14 @@ where
self.parent_key_id.clone() self.parent_key_id.clone()
} }
fn get(&self, id: &Identifier) -> Result<OutputData, Error> { fn get(&self, id: &Identifier, mmr_index: &Option<u64>) -> Result<OutputData, Error> {
let key = to_key(OUTPUT_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()),
};
option_to_not_found(self.db.get_ser(&key), &format!("Key Id: {}", id)).map_err(|e| e.into()) 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> { fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = OutputData> + 'a> {
Box::new(self.db.iter(&[OUTPUT_PREFIX]).unwrap()) Box::new(self.db.iter(&[OUTPUT_PREFIX]).unwrap())
} }
@ -402,22 +376,21 @@ where
fn save(&mut self, out: OutputData) -> Result<(), Error> { fn save(&mut self, out: OutputData) -> Result<(), Error> {
// Save the output data to the db. // 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)?; 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(()) Ok(())
} }
fn get(&self, id: &Identifier) -> Result<OutputData, Error> { fn get(&self, id: &Identifier, mmr_index: &Option<u64>) -> Result<OutputData, Error> {
let key = to_key(OUTPUT_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()),
};
option_to_not_found( option_to_not_found(
self.db.borrow().as_ref().unwrap().get_ser(&key), self.db.borrow().as_ref().unwrap().get_ser(&key),
&format!("Key ID: {}", id), &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. // Delete the output data.
{ {
let key = to_key(OUTPUT_PREFIX, &mut id.to_bytes().to_vec()); let key = match mmr_index {
let _ = self.db.borrow().as_ref().unwrap().delete(&key); 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()),
};
// Delete the associated output commitment.
{
let key = to_key(COMMITMENT_PREFIX, &mut id.to_bytes().to_vec());
let _ = self.db.borrow().as_ref().unwrap().delete(&key); let _ = self.db.borrow().as_ref().unwrap().delete(&key);
} }

View file

@ -89,7 +89,7 @@ impl NodeClient for HTTPNodeClient {
fn get_outputs_from_node( fn get_outputs_from_node(
&self, &self,
wallet_outputs: Vec<pedersen::Commitment>, 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(); let addr = self.node_url();
// build the necessary query params - // build the necessary query params -
// ?id=xxx&id=yyy&id=zzz // ?id=xxx&id=yyy&id=zzz
@ -99,7 +99,7 @@ impl NodeClient for HTTPNodeClient {
.collect(); .collect();
// build a map of api outputs by commit so we can look them up efficiently // 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(); let mut tasks = Vec::new();
for query_chunk in query_params.chunks(500) { for query_chunk in query_params.chunks(500) {
@ -125,7 +125,7 @@ impl NodeClient for HTTPNodeClient {
for out in res { for out in res {
api_outputs.insert( api_outputs.insert(
out.commit.commit(), 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,
u64, u64,
Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64)>, Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)>,
), ),
libwallet::Error, libwallet::Error,
> { > {
@ -149,7 +149,7 @@ impl NodeClient for HTTPNodeClient {
let url = format!("{}/v1/txhashset/outputs?{}", addr, query_param,); 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(); Vec::new();
match api::client::get::<api::OutputListing>(url.as_str(), self.node_api_secret()) { 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(), out.range_proof().unwrap(),
is_coinbase, is_coinbase,
out.block_height.unwrap(), 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::core::{consensus, global, pow, ser};
use self::util::secp::pedersen; use self::util::secp::pedersen;
use self::util::Mutex; 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::lmdb_wallet::LMDBBackend;
use crate::{controller, libwallet, WalletSeed}; use crate::{controller, libwallet, WalletSeed};
use crate::{WalletBackend, WalletConfig}; use crate::{WalletBackend, WalletConfig};
@ -52,7 +53,8 @@ fn get_output_local(chain: &chain::Chain, commit: &pedersen::Commitment) -> Opti
for x in outputs.iter() { for x in outputs.iter() {
if let Ok(_) = chain.is_unspent(&x) { if let Ok(_) = chain.is_unspent(&x) {
let block_height = chain.get_header_for_output(&x).unwrap().height; 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 None
@ -153,14 +155,22 @@ where
} }
/// dispatch a db wallet /// 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 where
C: NodeClient + 'static, C: NodeClient + 'static,
K: keychain::Keychain + '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(); let mut wallet_config = WalletConfig::default();
wallet_config.data_file_dir = String::from(dir); 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) let mut wallet = LMDBBackend::new(wallet_config.clone(), "", n_client)
.unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config)); .unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config));
wallet.open_with_credentials().unwrap_or_else(|e| { wallet.open_with_credentials().unwrap_or_else(|e| {
@ -171,3 +181,45 @@ where
}); });
Arc::new(Mutex::new(wallet)) 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( fn get_outputs_from_node(
&self, &self,
wallet_outputs: Vec<pedersen::Commitment>, 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 let query_params: Vec<String> = wallet_outputs
.iter() .iter()
.map(|commit| format!("{}", util::to_hex(commit.as_ref().to_vec()))) .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 r = self.rx.lock();
let m = r.recv().unwrap(); let m = r.recv().unwrap();
let outputs: Vec<api::Output> = serde_json::from_str(&m.body).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 { for out in outputs {
api_outputs.insert( api_outputs.insert(
out.commit.commit(), 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) Ok(api_outputs)
@ -481,7 +481,7 @@ impl NodeClient for LocalWalletClient {
( (
u64, u64,
u64, u64,
Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64)>, Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)>,
), ),
libwallet::Error, libwallet::Error,
> { > {
@ -504,7 +504,7 @@ impl NodeClient for LocalWalletClient {
let m = r.recv().unwrap(); let m = r.recv().unwrap();
let o: api::OutputListing = serde_json::from_str(&m.body).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(); Vec::new();
for out in o.outputs { for out in o.outputs {
@ -517,6 +517,7 @@ impl NodeClient for LocalWalletClient {
out.range_proof().unwrap(), out.range_proof().unwrap(),
is_coinbase, is_coinbase,
out.block_height.unwrap(), out.block_height.unwrap(),
out.mmr_index,
)); ));
} }
Ok((o.highest_index, o.last_retrieved_index, api_outputs)) 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 // Create a new wallet test client, and set its queues to communicate with the
// proxy // proxy
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.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()); wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone()); let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone());
// define recipient wallet, add to proxy // 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()); wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone());
// Set the wallet proxy listener running // Set the wallet proxy listener running

View file

@ -15,6 +15,7 @@
#[macro_use] #[macro_use]
extern crate log; extern crate log;
use self::core::consensus;
use self::core::global; use self::core::global;
use self::core::global::ChainTypes; use self::core::global::ChainTypes;
use self::keychain::ExtKeychain; use self::keychain::ExtKeychain;
@ -38,7 +39,7 @@ fn setup(test_dir: &str) {
global::set_mining_mode(ChainTypes::AutomatedTesting); 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> { fn check_repair_impl(test_dir: &str) -> Result<(), libwallet::Error> {
setup(test_dir); setup(test_dir);
// Create a new proxy to simulate server and wallet responses // 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 // Create a new wallet test client, and set its queues to communicate with the
// proxy // proxy
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.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()); wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone()); let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone());
// define recipient wallet, add to proxy // 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()); wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone());
// Set the wallet proxy listener running // 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 // few values to keep things shorter
let reward = core::consensus::REWARD; 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 // add some accounts
wallet::controller::owner_single_use(wallet1.clone(), |api| { 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()?; w.open_with_credentials()?;
{ {
let mut batch = w.batch()?; let mut batch = w.batch()?;
batch.delete(&w1_outputs[4].key_id)?; batch.delete(&w1_outputs[4].key_id, &None)?;
batch.delete(&w1_outputs[10].key_id)?; batch.delete(&w1_outputs[10].key_id, &None)?;
let mut accidental_spent = w1_outputs[13].clone(); let mut accidental_spent = w1_outputs[13].clone();
accidental_spent.status = libwallet::types::OutputStatus::Spent; accidental_spent.status = libwallet::types::OutputStatus::Spent;
batch.save(accidental_spent)?; batch.save(accidental_spent)?;
@ -193,6 +196,385 @@ fn check_repair_impl(test_dir: &str) -> Result<(), libwallet::Error> {
Ok(()) 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] #[test]
fn check_repair() { fn check_repair() {
let test_dir = "test_output/check_repair"; let test_dir = "test_output/check_repair";
@ -200,3 +582,11 @@ fn check_repair() {
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap()); 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 chain = wallet_proxy.chain.clone();
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.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()); wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.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()); wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone());
// Set the wallet proxy listener running // Set the wallet proxy listener running

View file

@ -89,7 +89,7 @@ fn aggsig_sender_receiver_interaction() {
rx_cx = Context::new(&keychain.secp(), blind); rx_cx = Context::new(&keychain.secp(), blind);
let (pub_excess, pub_nonce) = rx_cx.get_public_keys(&keychain.secp()); 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( pub_nonce_sum = PublicKey::from_combination(
keychain.secp(), keychain.secp(),
@ -305,7 +305,7 @@ fn aggsig_sender_receiver_interaction_offset() {
rx_cx = Context::new(&keychain.secp(), blind); rx_cx = Context::new(&keychain.secp(), blind);
let (pub_excess, pub_nonce) = rx_cx.get_public_keys(&keychain.secp()); 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( pub_nonce_sum = PublicKey::from_combination(
keychain.secp(), 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 chain = wallet_proxy.chain.clone();
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.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()); wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.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()); wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone());
// Set the wallet proxy listener running // 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 mut wallet_proxy: WalletProxy<LocalWalletClient, ExtKeychain> = WalletProxy::new(base_dir);
let client = LocalWalletClient::new(wallet_dir, wallet_proxy.tx.clone()); 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()); 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 mut wallet_proxy: WalletProxy<LocalWalletClient, ExtKeychain> = WalletProxy::new(base_dir);
let client = LocalWalletClient::new(wallet_dir, wallet_proxy.tx.clone()); 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_proxy.add_wallet(
&wallet_dir, &wallet_dir,
client.get_send_instance(), client.get_send_instance(),
@ -90,7 +90,7 @@ fn compare_wallet_restore(
); );
let client = LocalWalletClient::new(&restore_name, wallet_proxy.tx.clone()); 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( wallet_proxy.add_wallet(
&restore_name, &restore_name,
client.get_send_instance(), 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 // Create a new wallet test client, and set its queues to communicate with the
// proxy // proxy
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.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()); wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
// define recipient wallet, add to proxy // define recipient wallet, add to proxy
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.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()); wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone());
// wallet 2 will use another account // wallet 2 will use another account
@ -201,7 +203,8 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> {
// Another wallet // Another wallet
let client3 = LocalWalletClient::new("wallet3", wallet_proxy.tx.clone()); 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()); wallet_proxy.add_wallet("wallet3", client3.get_send_instance(), wallet3.clone());
// Set the wallet proxy listener running // 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 // Create a new wallet test client, and set its queues to communicate with the
// proxy // proxy
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.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()); wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
// Set the wallet proxy listener running // 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 // Create a new wallet test client, and set its queues to communicate with the
// proxy // proxy
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.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()); wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone()); let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone());
// define recipient wallet, add to proxy // 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()); wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone());
// Set the wallet proxy listener running // 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 // Create a new wallet test client, and set its queues to communicate with the
// proxy // proxy
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.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()); wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
// define recipient wallet, add to proxy // define recipient wallet, add to proxy
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.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()); wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone());
// Set the wallet proxy listener running // Set the wallet proxy listener running