diff --git a/api/src/handlers.rs b/api/src/handlers.rs index 05ababfb8..846e72618 100644 --- a/api/src/handlers.rs +++ b/api/src/handlers.rs @@ -237,6 +237,10 @@ impl Handler for OutputHandler { // UTXO traversal:: // GET /v1/txhashset/outputs?start_index=1&max=100 +// +// Build a merkle proof for a given pos +// GET /v1/txhashset/merkleproof?n=1 + struct TxHashSetHandler { chain: Weak, } @@ -281,6 +285,25 @@ impl TxHashSetHandler { .collect(), } } + + // return a dummy output with merkle proof for position filled out + // (to avoid having to create a new type to pass around) + fn get_merkle_proof_for_output(&self, id: &str) -> Result { + let c = util::from_hex(String::from(id)).context(ErrorKind::Argument(format!( + "Not a valid commitment: {}", + id + )))?; + let commit = Commitment::from_vec(c); + let merkle_proof = chain::Chain::get_merkle_proof_for_pos(&w(&self.chain), commit).unwrap(); + Ok(OutputPrintable { + output_type: OutputType::Coinbase, + commit: Commitment::from_vec(vec![]), + spent: false, + proof: None, + proof_hash: "".to_string(), + merkle_proof: Some(merkle_proof), + }) + } } impl Handler for TxHashSetHandler { @@ -289,6 +312,7 @@ impl Handler for TxHashSetHandler { let mut path_elems = url.path(); let mut start_index = 1; let mut max = 100; + let mut id = ""; if *path_elems.last().unwrap() == "" { path_elems.pop(); } @@ -316,6 +340,11 @@ impl Handler for TxHashSetHandler { } } } + if let Some(ids) = params.get("id") { + for i in ids { + id = i; + } + } } match *path_elems.last().unwrap() { "roots" => json_response_pretty(&self.get_roots()), @@ -323,6 +352,7 @@ impl Handler for TxHashSetHandler { "lastrangeproofs" => json_response_pretty(&self.get_last_n_rangeproof(last_n)), "lastkernels" => json_response_pretty(&self.get_last_n_kernel(last_n)), "outputs" => json_response_pretty(&self.outputs(start_index, max)), + "merkleproof" => json_response_pretty(&self.get_merkle_proof_for_output(id).unwrap()), _ => Ok(Response::with((status::BadRequest, ""))), } } diff --git a/chain/src/chain.rs b/chain/src/chain.rs index 21e980e56..897930de2 100644 --- a/chain/src/chain.rs +++ b/chain/src/chain.rs @@ -30,7 +30,7 @@ use pipe; use store; use txhashset; use types::*; -use util::secp::pedersen::RangeProof; +use util::secp::pedersen::{Commitment, RangeProof}; use util::LOGGER; /// Orphan pool size is limited by MAX_ORPHAN_SIZE @@ -507,6 +507,13 @@ impl Chain { Ok(merkle_proof) } + /// Return a merkle proof valid for the current output pmmr state at the + /// given pos + pub fn get_merkle_proof_for_pos(&self, commit: Commitment) -> Result { + let mut txhashset = self.txhashset.write().unwrap(); + txhashset.merkle_proof(commit) + } + /// Returns current txhashset roots pub fn get_txhashset_roots(&self) -> (Hash, Hash, Hash) { let mut txhashset = self.txhashset.write().unwrap(); diff --git a/chain/src/txhashset.rs b/chain/src/txhashset.rs index ae84d5524..2f7849fde 100644 --- a/chain/src/txhashset.rs +++ b/chain/src/txhashset.rs @@ -205,6 +205,14 @@ impl TxHashSet { (output_pmmr.root(), rproof_pmmr.root(), kernel_pmmr.root()) } + /// 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(); + let output_pmmr: PMMR = + PMMR::at(&mut self.output_pmmr_h.backend, self.output_pmmr_h.last_pos); + output_pmmr.merkle_proof(pos) + } + /// Compact the MMR data files and flush the rm logs pub fn compact(&mut self) -> Result<(), Error> { let commit_index = self.commit_index.clone(); diff --git a/wallet/src/restore.rs b/wallet/src/restore.rs index 652d72153..bec4471e0 100644 --- a/wallet/src/restore.rs +++ b/wallet/src/restore.rs @@ -13,15 +13,17 @@ // limitations under the License. use failure::{Fail, ResultExt}; use keychain::{Identifier, Keychain}; +use util; use util::LOGGER; use util::secp::pedersen; use api; use core::global; use core::core::transaction::ProofMessageElements; -use types::{Error, ErrorKind, OutputData, OutputStatus, WalletConfig, WalletData}; +use types::{Error, ErrorKind, MerkleProofWrapper, OutputData, OutputStatus, WalletConfig, + WalletData}; use byteorder::{BigEndian, ByteOrder}; -pub fn _get_chain_height(config: &WalletConfig) -> Result { +pub fn get_chain_height(config: &WalletConfig) -> Result { let url = format!("{}/v1/chain", config.check_node_api_http_addr); match api::client::get::(url.as_str()) { @@ -39,6 +41,29 @@ pub fn _get_chain_height(config: &WalletConfig) -> Result { } } +pub fn get_merkle_proof_for_commit( + config: &WalletConfig, + commit: &str, +) -> Result { + let url = format!( + "{}/v1/txhashset/merkleproof?id={}", + config.check_node_api_http_addr, commit + ); + + match api::client::get::(url.as_str()) { + Ok(output) => Ok(MerkleProofWrapper(output.merkle_proof.unwrap())), + Err(e) => { + // if we got anything other than 200 back from server, bye + error!( + LOGGER, + "get_merkle_proof_for_pos: Restore failed... unable to create merkle proof for commit {}. Error: {}", + commit, + e + ); + Err(e.context(ErrorKind::Node).into()) + } + } +} fn coinbase_status(output: &api::OutputPrintable) -> bool { match output.output_type { api::OutputType::Coinbase => true, @@ -75,15 +100,38 @@ pub fn outputs_batch( // TODO - wrap the many return values in a struct fn find_outputs_with_key( + config: &WalletConfig, keychain: &Keychain, outputs: Vec, -) -> Vec<(pedersen::Commitment, Identifier, u32, u64, u64, u64, bool)> { - let mut wallet_outputs: Vec<(pedersen::Commitment, Identifier, u32, u64, u64, u64, bool)> = - Vec::new(); +) -> Vec< + ( + pedersen::Commitment, + Identifier, + u32, + u64, + u64, + u64, + bool, + Option, + ), +> { + let mut wallet_outputs: Vec< + ( + pedersen::Commitment, + Identifier, + u32, + u64, + u64, + u64, + bool, + Option, + ), + > = Vec::new(); let max_derivations = 1_000_000; info!(LOGGER, "Scanning {} outputs", outputs.len(),); + let current_chain_height = get_chain_height(config).unwrap(); // skey doesn't matter in this case let skey = keychain.derive_key_id(1).unwrap(); @@ -135,12 +183,18 @@ fn find_outputs_with_key( .commit_with_key_index(BigEndian::read_u64(&commit_id), i as u32) .expect("commit with key index"); - //let height = outputs.header.height; - let height = 0; + let mut merkle_proof = None; + let commit_str = util::to_hex(output.commit.as_ref().to_vec()); + + if is_coinbase { + merkle_proof = Some(get_merkle_proof_for_commit(config, &commit_str).unwrap()); + } + + let height = current_chain_height; let lock_height = if is_coinbase { height + global::coinbase_maturity() } else { - 0 + height }; wallet_outputs.push(( @@ -151,6 +205,7 @@ fn find_outputs_with_key( height, lock_height, is_coinbase, + merkle_proof, )); break; @@ -200,12 +255,11 @@ pub fn restore(config: &WalletConfig, keychain: &Keychain) -> Result<(), Error> ); let _ = WalletData::with_wallet(&config.data_file_dir, |wallet_data| { - let result_vec = find_outputs_with_key(keychain, output_listing.outputs.clone()); + let result_vec = + find_outputs_with_key(config, keychain, output_listing.outputs.clone()); if result_vec.len() > 0 { for output in result_vec.clone() { let root_key_id = keychain.root_key_id(); - // Just plonk it in for now, and refresh actual values via wallet info - // command later wallet_data.add_output(OutputData { root_key_id: root_key_id.clone(), key_id: output.1.clone(), @@ -216,7 +270,7 @@ pub fn restore(config: &WalletConfig, keychain: &Keychain) -> Result<(), Error> lock_height: output.5, is_coinbase: output.6, block: None, - merkle_proof: None, + merkle_proof: output.7, }); } }