diff --git a/api/src/handlers/transactions_api.rs b/api/src/handlers/transactions_api.rs index 6dc879855..3a279b477 100644 --- a/api/src/handlers/transactions_api.rs +++ b/api/src/handlers/transactions_api.rs @@ -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, }) } } diff --git a/api/src/handlers/utils.rs b/api/src/handlers/utils.rs index e0cfa57ec..6a4f98ad0 100644 --- a/api/src/handlers/utils.rs +++ b/api/src/handlers/utils.rs @@ -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)? diff --git a/api/src/types.rs b/api/src/types.rs index bb2469f35..c069ac014 100644 --- a/api/src/types.rs +++ b/api/src/types.rs @@ -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, /// Merkle Proof pub merkle_proof: Option, + /// 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(); diff --git a/chain/src/chain.rs b/chain/src/chain.rs index af10ed4e2..5eef3a778 100644 --- a/chain/src/chain.rs +++ b/chain/src/chain.rs @@ -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 { + Ok(self.txhashset.read().get_output_pos(commit)?) + } + /// outputs by insertion index pub fn unspent_outputs_by_insertion_index( &self, diff --git a/chain/src/txhashset/txhashset.rs b/chain/src/txhashset/txhashset.rs index 0ed33e087..6ee1dc219 100644 --- a/chain/src/txhashset/txhashset.rs +++ b/chain/src/txhashset/txhashset.rs @@ -257,6 +257,11 @@ impl TxHashSet { } } + /// Return Commit's MMR position + pub fn get_output_pos(&self, commit: &Commitment) -> Result { + 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 { let pos = self.commit_index.get_output_pos(&commit).unwrap(); diff --git a/wallet/src/display.rs b/wallet/src/display.rs index 118118b19..9f81770eb 100644 --- a/wallet/src/display.rs +++ b/wallet/src/display.rs @@ -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, diff --git a/wallet/src/libwallet/internal/keys.rs b/wallet/src/libwallet/internal/keys.rs index 2bce52674..40369cc37 100644 --- a/wallet/src/libwallet/internal/keys.rs +++ b/wallet/src/libwallet/internal/keys.rs @@ -32,13 +32,14 @@ where pub fn retrieve_existing_key( wallet: &T, key_id: Identifier, + mmr_index: Option, ) -> Result<(Identifier, u32), Error> where T: WalletBackend, 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)) diff --git a/wallet/src/libwallet/internal/restore.rs b/wallet/src/libwallet/internal/restore.rs index 56598e025..fe25a88ec 100644 --- a/wallet/src/libwallet/internal/restore.rs +++ b/wallet/src/libwallet/internal/restore.rs @@ -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( wallet: &mut T, - outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64)>, + outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)>, ) -> Result, Error> where T: WalletBackend, @@ -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(()) } diff --git a/wallet/src/libwallet/internal/selection.rs b/wallet/src/libwallet/internal/selection.rs index 30a5b29e6..a7740b26f 100644 --- a/wallet/src/libwallet/internal/selection.rs +++ b/wallet/src/libwallet/internal/selection.rs @@ -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( ( Vec>>, Vec, - Vec<(u64, Identifier)>, // change amounts and derivations - u64, // amount - u64, // fee + Vec<(u64, Identifier, Option)>, // change amounts and derivations + u64, // amount + u64, // fee ), Error, > @@ -337,7 +339,13 @@ pub fn inputs_and_change( amount: u64, fee: u64, num_change_outputs: usize, -) -> Result<(Vec>>, Vec<(u64, Identifier)>), Error> +) -> Result< + ( + Vec>>, + Vec<(u64, Identifier, Option)>, + ), + Error, +> where T: WalletBackend, 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)); } } diff --git a/wallet/src/libwallet/internal/updater.rs b/wallet/src/libwallet/internal/updater.rs index 74ff6293a..8ab9a16ad 100644 --- a/wallet/src/libwallet/internal/updater.rs +++ b/wallet/src/libwallet/internal/updater.rs @@ -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( wallet: &mut T, parent_key_id: &Identifier, update_all: bool, -) -> Result, Error> +) -> Result)>, Error> where T: WalletBackend, C: NodeClient, K: Keychain, { - let mut wallet_outputs: HashMap = HashMap::new(); + let mut wallet_outputs: HashMap)> = + HashMap::new(); let keychain = wallet.keychain().clone(); let unspents: Vec = 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( wallet: &mut T, - wallet_outputs: &HashMap, - api_outputs: &HashMap, + wallet_outputs: &HashMap)>, + api_outputs: &HashMap, 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, diff --git a/wallet/src/libwallet/types.rs b/wallet/src/libwallet/types.rs index 2ffcacbce..a1337e2c2 100644 --- a/wallet/src/libwallet/types.rs +++ b/wallet/src/libwallet/types.rs @@ -81,10 +81,7 @@ where fn iter<'a>(&'a self) -> Box + 'a>; /// Get output data by id - fn get(&self, id: &Identifier) -> Result; - - /// Get associated output commitment by id. - fn get_commitment(&mut self, id: &Identifier) -> Result; + fn get(&self, id: &Identifier, mmr_index: &Option) -> Result; /// Get an (Optional) tx log entry by uuid fn get_tx_log_entry(&self, uuid: &Uuid) -> Result, 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; + fn get(&self, id: &Identifier, mmr_index: &Option) -> Result; /// Iterate over all output data stored by the backend fn iter(&self) -> Box>; /// 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) -> 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, - ) -> Result, Error>; + ) -> Result, 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, /// 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, + pub output_ids: Vec<(Identifier, Option)>, /// store my inputs - pub input_ids: Vec, + pub input_ids: Vec<(Identifier, Option)>, /// 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) { + self.output_ids.push((output_id.clone(), mmr_index.clone())); } /// Returns all stored outputs - pub fn get_outputs(&self) -> Vec { + pub fn get_outputs(&self) -> Vec<(Identifier, Option)> { 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) { + self.input_ids.push((input_id.clone(), mmr_index.clone())); } /// Returns all stored input identifiers - pub fn get_inputs(&self) -> Vec { + pub fn get_inputs(&self) -> Vec<(Identifier, Option)> { self.input_ids.clone() } diff --git a/wallet/src/lmdb_wallet.rs b/wallet/src/lmdb_wallet.rs index 43fb87919..5f60cc909 100644 --- a/wallet/src/lmdb_wallet.rs +++ b/wallet/src/lmdb_wallet.rs @@ -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 { - let key = to_key(OUTPUT_PREFIX, &mut id.to_bytes().to_vec()); + fn get(&self, id: &Identifier, mmr_index: &Option) -> Result { + 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 { - let key = to_key(COMMITMENT_PREFIX, &mut id.to_bytes().to_vec()); - - let res: Result = - 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 + '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 { - let key = to_key(OUTPUT_PREFIX, &mut id.to_bytes().to_vec()); + fn get(&self, id: &Identifier, mmr_index: &Option) -> Result { + 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) -> 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); } diff --git a/wallet/src/node_clients/http.rs b/wallet/src/node_clients/http.rs index d21d29bc8..3581ceab9 100644 --- a/wallet/src/node_clients/http.rs +++ b/wallet/src/node_clients/http.rs @@ -89,7 +89,7 @@ impl NodeClient for HTTPNodeClient { fn get_outputs_from_node( &self, wallet_outputs: Vec, - ) -> Result, libwallet::Error> { + ) -> Result, 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 = HashMap::new(); + let mut api_outputs: HashMap = 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::(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, )); } diff --git a/wallet/src/test_framework/mod.rs b/wallet/src/test_framework/mod.rs index bbb8e8bfd..029dff57b 100644 --- a/wallet/src/test_framework/mod.rs +++ b/wallet/src/test_framework/mod.rs @@ -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(dir: &str, n_client: C) -> Arc>> +pub fn create_wallet( + dir: &str, + n_client: C, + rec_phrase: Option<&str>, +) -> Arc>> 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( + client: LocalWalletClient, + api: &mut APIOwner, + dest: &str, + amount: u64, +) -> Result<(), libwallet::Error> +where + T: WalletBackend, + 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( + api: &mut APIOwner, +) -> Result +where + T: WalletBackend, + C: NodeClient, + K: keychain::Keychain, +{ + let (wallet_refreshed, wallet_info) = api.retrieve_summary_info(true, 1)?; + assert!(wallet_refreshed); + Ok(wallet_info) +} diff --git a/wallet/src/test_framework/testclient.rs b/wallet/src/test_framework/testclient.rs index c64aefbbc..138953bcc 100644 --- a/wallet/src/test_framework/testclient.rs +++ b/wallet/src/test_framework/testclient.rs @@ -442,7 +442,7 @@ impl NodeClient for LocalWalletClient { fn get_outputs_from_node( &self, wallet_outputs: Vec, - ) -> Result, libwallet::Error> { + ) -> Result, libwallet::Error> { let query_params: Vec = 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 = serde_json::from_str(&m.body).unwrap(); - let mut api_outputs: HashMap = HashMap::new(); + let mut api_outputs: HashMap = 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)) diff --git a/wallet/tests/accounts.rs b/wallet/tests/accounts.rs index 94edcde15..98340c0d8 100644 --- a/wallet/tests/accounts.rs +++ b/wallet/tests/accounts.rs @@ -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 diff --git a/wallet/tests/check.rs b/wallet/tests/check.rs index a00b81dff..357ec09bf 100644 --- a/wallet/tests/check.rs +++ b/wallet/tests/check.rs @@ -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 = 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()); + } +} diff --git a/wallet/tests/file.rs b/wallet/tests/file.rs index 856e1d91d..c4202f730 100644 --- a/wallet/tests/file.rs +++ b/wallet/tests/file.rs @@ -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 diff --git a/wallet/tests/libwallet.rs b/wallet/tests/libwallet.rs index b5595f3f9..44447f50b 100644 --- a/wallet/tests/libwallet.rs +++ b/wallet/tests/libwallet.rs @@ -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(), diff --git a/wallet/tests/repost.rs b/wallet/tests/repost.rs index c2cc8ad81..1712245de 100644 --- a/wallet/tests/repost.rs +++ b/wallet/tests/repost.rs @@ -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 diff --git a/wallet/tests/restore.rs b/wallet/tests/restore.rs index 76d725887..1b39b535d 100644 --- a/wallet/tests/restore.rs +++ b/wallet/tests/restore.rs @@ -49,7 +49,7 @@ fn restore_wallet(base_dir: &str, wallet_dir: &str) -> Result<(), libwallet::Err let mut wallet_proxy: WalletProxy = 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 = 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 diff --git a/wallet/tests/self_send.rs b/wallet/tests/self_send.rs index 59379436b..54cab3078 100644 --- a/wallet/tests/self_send.rs +++ b/wallet/tests/self_send.rs @@ -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 diff --git a/wallet/tests/transaction.rs b/wallet/tests/transaction.rs index 9ad880ad5..c9840f616 100644 --- a/wallet/tests/transaction.rs +++ b/wallet/tests/transaction.rs @@ -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