2017-11-20 03:50:09 +03:00
|
|
|
// Copyright 2017 The Grin Developers
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
use keychain::{Keychain, Identifier};
|
|
|
|
use util::{LOGGER, from_hex};
|
|
|
|
use util::secp::pedersen;
|
|
|
|
use api;
|
2018-01-17 06:03:40 +03:00
|
|
|
use core::global;
|
2017-12-21 01:16:40 +03:00
|
|
|
use core::core::{Output, SwitchCommitHash};
|
2018-01-17 06:03:40 +03:00
|
|
|
use core::core::hash::Hash;
|
|
|
|
use core::core::transaction::{COINBASE_OUTPUT, DEFAULT_OUTPUT};
|
2017-11-20 03:50:09 +03:00
|
|
|
use types::{WalletConfig, WalletData, OutputData, OutputStatus, Error};
|
|
|
|
use byteorder::{BigEndian, ByteOrder};
|
|
|
|
|
2018-01-17 06:03:40 +03:00
|
|
|
|
2017-12-21 01:16:40 +03:00
|
|
|
pub fn get_chain_height(config: &WalletConfig) -> Result<u64, Error> {
|
|
|
|
let url = format!("{}/v1/chain", config.check_node_api_http_addr);
|
2017-11-20 03:50:09 +03:00
|
|
|
|
|
|
|
match api::client::get::<api::Tip>(url.as_str()) {
|
2017-12-21 01:16:40 +03:00
|
|
|
Ok(tip) => Ok(tip.height),
|
2017-11-20 03:50:09 +03:00
|
|
|
Err(e) => {
|
|
|
|
// if we got anything other than 200 back from server, bye
|
2018-01-13 21:27:40 +03:00
|
|
|
error!(
|
|
|
|
LOGGER,
|
|
|
|
"get_chain_height: Restore failed... unable to contact API {}. Error: {}",
|
|
|
|
config.check_node_api_http_addr,
|
|
|
|
e
|
|
|
|
);
|
2017-11-20 03:50:09 +03:00
|
|
|
Err(Error::Node(e))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-17 06:03:40 +03:00
|
|
|
fn output_with_range_proof(
|
|
|
|
config: &WalletConfig,
|
|
|
|
commit_id: &str,
|
|
|
|
height: u64,
|
|
|
|
) -> Result<api::OutputPrintable, Error> {
|
2017-12-21 01:16:40 +03:00
|
|
|
let url =
|
|
|
|
format!(
|
2018-01-17 06:03:40 +03:00
|
|
|
"{}/v1/chain/utxos/byheight?start_height={}&end_height={}&id={}&include_rp",
|
2017-11-20 03:50:09 +03:00
|
|
|
config.check_node_api_http_addr,
|
2018-01-17 06:03:40 +03:00
|
|
|
height,
|
|
|
|
height,
|
2017-11-20 03:50:09 +03:00
|
|
|
commit_id,
|
|
|
|
);
|
|
|
|
|
2018-01-17 06:03:40 +03:00
|
|
|
match api::client::get::<Vec<api::BlockOutputs>>(url.as_str()) {
|
|
|
|
Ok(block_outputs) => {
|
|
|
|
if let Some(block_output) = block_outputs.first() {
|
|
|
|
if let Some(output) = block_output.outputs.first() {
|
|
|
|
Ok(output.clone())
|
|
|
|
} else {
|
|
|
|
Err(Error::Node(api::Error::NotFound))
|
|
|
|
}
|
2017-12-21 01:16:40 +03:00
|
|
|
} else {
|
|
|
|
Err(Error::Node(api::Error::NotFound))
|
|
|
|
}
|
|
|
|
}
|
2017-11-20 03:50:09 +03:00
|
|
|
Err(e) => {
|
2017-12-21 01:16:40 +03:00
|
|
|
// if we got anything other than 200 back from server, don't attempt to refresh
|
|
|
|
// the wallet
|
2017-11-20 03:50:09 +03:00
|
|
|
// data after
|
|
|
|
Err(Error::Node(e))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-21 01:16:40 +03:00
|
|
|
fn retrieve_amount_and_coinbase_status(
|
|
|
|
config: &WalletConfig,
|
|
|
|
keychain: &Keychain,
|
|
|
|
key_id: Identifier,
|
|
|
|
commit_id: &str,
|
2018-01-17 06:03:40 +03:00
|
|
|
height: u64,
|
2017-12-21 01:16:40 +03:00
|
|
|
) -> Result<(u64, bool), Error> {
|
2018-01-17 06:03:40 +03:00
|
|
|
let output = output_with_range_proof(config, commit_id, height)?;
|
|
|
|
|
2017-11-20 03:50:09 +03:00
|
|
|
let core_output = Output {
|
2017-12-21 01:16:40 +03:00
|
|
|
features: match output.output_type {
|
2017-11-20 03:50:09 +03:00
|
|
|
api::OutputType::Coinbase => COINBASE_OUTPUT,
|
|
|
|
api::OutputType::Transaction => DEFAULT_OUTPUT,
|
|
|
|
},
|
2018-01-17 06:03:40 +03:00
|
|
|
proof: output.range_proof()?,
|
|
|
|
switch_commit_hash: output.switch_commit_hash()?,
|
|
|
|
commit: output.commit()?,
|
2017-11-20 03:50:09 +03:00
|
|
|
};
|
2018-01-17 06:03:40 +03:00
|
|
|
|
2017-12-21 01:16:40 +03:00
|
|
|
if let Some(amount) = core_output.recover_value(keychain, &key_id) {
|
|
|
|
let is_coinbase = match output.output_type {
|
|
|
|
api::OutputType::Coinbase => true,
|
|
|
|
api::OutputType::Transaction => false,
|
|
|
|
};
|
|
|
|
Ok((amount, is_coinbase))
|
|
|
|
} else {
|
|
|
|
Err(Error::GenericError(format!("cannot recover value")))
|
|
|
|
}
|
2017-11-20 03:50:09 +03:00
|
|
|
}
|
|
|
|
|
2017-12-21 01:16:40 +03:00
|
|
|
pub fn utxos_batch_block(
|
|
|
|
config: &WalletConfig,
|
|
|
|
start_height: u64,
|
|
|
|
end_height: u64,
|
|
|
|
) -> Result<Vec<api::BlockOutputs>, Error> {
|
|
|
|
let query_param = format!("start_height={}&end_height={}", start_height, end_height);
|
2017-11-20 03:50:09 +03:00
|
|
|
|
2017-12-21 01:16:40 +03:00
|
|
|
let url =
|
|
|
|
format!(
|
2018-01-04 18:39:30 +03:00
|
|
|
"{}/v1/chain/utxos/byheight?{}",
|
2017-11-20 03:50:09 +03:00
|
|
|
config.check_node_api_http_addr,
|
|
|
|
query_param,
|
|
|
|
);
|
|
|
|
|
|
|
|
match api::client::get::<Vec<api::BlockOutputs>>(url.as_str()) {
|
|
|
|
Ok(outputs) => Ok(outputs),
|
|
|
|
Err(e) => {
|
|
|
|
// if we got anything other than 200 back from server, bye
|
2018-01-13 21:27:40 +03:00
|
|
|
error!(
|
|
|
|
LOGGER,
|
|
|
|
"utxos_batch_block: Restore failed... unable to contact API {}. Error: {}",
|
|
|
|
config.check_node_api_http_addr,
|
|
|
|
e
|
|
|
|
);
|
2017-11-20 03:50:09 +03:00
|
|
|
Err(Error::Node(e))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-17 06:03:40 +03:00
|
|
|
// TODO - wrap the many return values in a struct
|
2017-12-21 01:16:40 +03:00
|
|
|
fn find_utxos_with_key(
|
|
|
|
config: &WalletConfig,
|
|
|
|
keychain: &Keychain,
|
2018-01-17 06:03:40 +03:00
|
|
|
switch_commit_cache: &Vec<pedersen::Commitment>,
|
2017-12-21 01:16:40 +03:00
|
|
|
block_outputs: api::BlockOutputs,
|
|
|
|
key_iterations: &mut usize,
|
|
|
|
padding: &mut usize,
|
2018-01-17 06:03:40 +03:00
|
|
|
) -> Vec<(pedersen::Commitment, Identifier, u32, u64, u64, u64, bool)> {
|
|
|
|
let mut wallet_outputs: Vec<(pedersen::Commitment, Identifier, u32, u64, u64, u64, bool)> =
|
2017-12-21 01:16:40 +03:00
|
|
|
Vec::new();
|
|
|
|
|
|
|
|
info!(
|
|
|
|
LOGGER,
|
|
|
|
"Scanning block {}, {} outputs, over {} key derivations",
|
|
|
|
block_outputs.header.height,
|
|
|
|
block_outputs.outputs.len(),
|
|
|
|
*key_iterations,
|
|
|
|
);
|
2017-11-20 03:50:09 +03:00
|
|
|
|
2018-01-17 06:03:40 +03:00
|
|
|
for output in block_outputs.outputs.iter().filter(|x| !x.spent) {
|
2017-11-20 03:50:09 +03:00
|
|
|
for i in 0..*key_iterations {
|
2018-01-17 06:03:40 +03:00
|
|
|
let expected_hash = SwitchCommitHash::from_switch_commit(switch_commit_cache[i as usize]);
|
2017-12-21 01:16:40 +03:00
|
|
|
|
2018-01-17 06:03:40 +03:00
|
|
|
if let Ok(x) = output.switch_commit_hash() {
|
|
|
|
if x == expected_hash {
|
|
|
|
info!(
|
|
|
|
LOGGER,
|
|
|
|
"Output found: {:?}, key_index: {:?}",
|
|
|
|
output,
|
|
|
|
i,
|
|
|
|
);
|
2017-12-21 01:16:40 +03:00
|
|
|
|
2018-01-17 06:03:40 +03:00
|
|
|
// add it to result set here
|
|
|
|
let commit_id = from_hex(output.commit.clone()).unwrap();
|
|
|
|
let key_id = keychain.derive_key_id(i as u32).unwrap();
|
2017-12-21 01:16:40 +03:00
|
|
|
|
2018-01-17 06:03:40 +03:00
|
|
|
let res = retrieve_amount_and_coinbase_status(
|
|
|
|
config,
|
|
|
|
keychain,
|
2017-12-21 01:16:40 +03:00
|
|
|
key_id.clone(),
|
2018-01-17 06:03:40 +03:00
|
|
|
&output.commit,
|
|
|
|
block_outputs.header.height,
|
2017-12-21 01:16:40 +03:00
|
|
|
);
|
|
|
|
|
2018-01-17 06:03:40 +03:00
|
|
|
if let Ok((amount, is_coinbase)) = res {
|
|
|
|
info!(LOGGER, "Amount: {}", amount);
|
|
|
|
|
|
|
|
let commit = keychain
|
|
|
|
.commit_with_key_index(BigEndian::read_u64(&commit_id), i as u32)
|
|
|
|
.expect("commit with key index");
|
|
|
|
|
|
|
|
let height = block_outputs.header.height;
|
|
|
|
let lock_height = if is_coinbase {
|
|
|
|
height + global::coinbase_maturity()
|
|
|
|
} else {
|
|
|
|
0
|
|
|
|
};
|
|
|
|
|
|
|
|
wallet_outputs.push((
|
|
|
|
commit,
|
|
|
|
key_id.clone(),
|
|
|
|
i as u32,
|
|
|
|
amount,
|
|
|
|
height,
|
|
|
|
lock_height,
|
|
|
|
is_coinbase,
|
|
|
|
));
|
|
|
|
|
|
|
|
// probably don't have to look for indexes greater than this now
|
|
|
|
*key_iterations = i + *padding;
|
|
|
|
if *key_iterations > switch_commit_cache.len() {
|
|
|
|
*key_iterations = switch_commit_cache.len();
|
|
|
|
}
|
|
|
|
info!(LOGGER, "Setting max key index to: {}", *key_iterations);
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
info!(
|
|
|
|
LOGGER,
|
|
|
|
"Unable to retrieve the amount (needs investigating) {:?}",
|
|
|
|
res,
|
|
|
|
);
|
|
|
|
}
|
2017-11-20 03:50:09 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-12-21 01:16:40 +03:00
|
|
|
debug!(
|
|
|
|
LOGGER,
|
|
|
|
"Found {} wallet_outputs for block {}",
|
|
|
|
wallet_outputs.len(),
|
|
|
|
block_outputs.header.height,
|
|
|
|
);
|
|
|
|
|
2017-11-20 03:50:09 +03:00
|
|
|
wallet_outputs
|
|
|
|
}
|
|
|
|
|
2017-12-21 01:16:40 +03:00
|
|
|
pub fn restore(
|
|
|
|
config: &WalletConfig,
|
|
|
|
keychain: &Keychain,
|
|
|
|
key_derivations: u32,
|
|
|
|
) -> Result<(), Error> {
|
2017-11-20 03:50:09 +03:00
|
|
|
// Don't proceed if wallet.dat has anything in it
|
|
|
|
let is_empty = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
|
2017-12-21 01:16:40 +03:00
|
|
|
wallet_data.outputs.len() == 0
|
2017-11-20 03:50:09 +03:00
|
|
|
})?;
|
|
|
|
if !is_empty {
|
2017-12-21 01:16:40 +03:00
|
|
|
error!(
|
|
|
|
LOGGER,
|
|
|
|
"Not restoring. Please back up and remove existing wallet.dat first."
|
|
|
|
);
|
|
|
|
return Ok(());
|
2017-11-20 03:50:09 +03:00
|
|
|
}
|
|
|
|
|
2017-12-21 01:16:40 +03:00
|
|
|
// Get height of chain from node (we'll check again when done)
|
2017-11-20 03:50:09 +03:00
|
|
|
let chain_height = get_chain_height(config)?;
|
2017-12-21 01:16:40 +03:00
|
|
|
info!(
|
|
|
|
LOGGER,
|
|
|
|
"Starting restore: Chain height is {}.",
|
|
|
|
chain_height
|
|
|
|
);
|
2017-11-20 03:50:09 +03:00
|
|
|
|
2018-01-17 06:03:40 +03:00
|
|
|
let mut switch_commit_cache: Vec<pedersen::Commitment> = vec![];
|
2017-12-21 01:16:40 +03:00
|
|
|
info!(
|
|
|
|
LOGGER,
|
|
|
|
"Building key derivation cache ({}) ...",
|
|
|
|
key_derivations,
|
|
|
|
);
|
2017-11-20 03:50:09 +03:00
|
|
|
for i in 0..key_derivations {
|
|
|
|
let switch_commit = keychain.switch_commit_from_index(i as u32).unwrap();
|
2018-01-17 06:03:40 +03:00
|
|
|
switch_commit_cache.push(switch_commit);
|
2017-11-20 03:50:09 +03:00
|
|
|
}
|
2017-12-21 01:16:40 +03:00
|
|
|
debug!(LOGGER, "... done");
|
2017-11-20 03:50:09 +03:00
|
|
|
|
2017-12-21 01:16:40 +03:00
|
|
|
let batch_size = 100;
|
|
|
|
// this will start here, then lower as outputs are found, moving backwards on
|
|
|
|
// the chain
|
|
|
|
let mut key_iterations = key_derivations as usize;
|
|
|
|
// set to a percentage of the key_derivation value
|
|
|
|
let mut padding = (key_iterations as f64 * 0.25) as usize;
|
2017-11-20 03:50:09 +03:00
|
|
|
let mut h = chain_height;
|
|
|
|
while {
|
2017-12-21 01:16:40 +03:00
|
|
|
let end_batch = h;
|
2017-11-20 03:50:09 +03:00
|
|
|
if h >= batch_size {
|
2017-12-21 01:16:40 +03:00
|
|
|
h -= batch_size;
|
2017-11-20 03:50:09 +03:00
|
|
|
} else {
|
2017-12-21 01:16:40 +03:00
|
|
|
h = 0;
|
2017-11-20 03:50:09 +03:00
|
|
|
}
|
2017-12-21 01:16:40 +03:00
|
|
|
let mut blocks = utxos_batch_block(config, h + 1, end_batch)?;
|
2017-11-20 03:50:09 +03:00
|
|
|
blocks.reverse();
|
2017-12-21 01:16:40 +03:00
|
|
|
|
|
|
|
let _ = WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
|
|
|
|
for block in blocks {
|
|
|
|
let result_vec = find_utxos_with_key(
|
|
|
|
config,
|
|
|
|
keychain,
|
|
|
|
&switch_commit_cache,
|
|
|
|
block,
|
|
|
|
&mut key_iterations,
|
|
|
|
&mut padding,
|
|
|
|
);
|
|
|
|
if result_vec.len() > 0 {
|
|
|
|
for output in result_vec.clone() {
|
2017-11-20 03:50:09 +03:00
|
|
|
let root_key_id = keychain.root_key_id();
|
2017-12-21 01:16:40 +03:00
|
|
|
// Just plonk it in for now, and refresh actual values via wallet info
|
|
|
|
// command later
|
2017-11-20 03:50:09 +03:00
|
|
|
wallet_data.add_output(OutputData {
|
|
|
|
root_key_id: root_key_id.clone(),
|
|
|
|
key_id: output.1.clone(),
|
|
|
|
n_child: output.2,
|
|
|
|
value: output.3,
|
|
|
|
status: OutputStatus::Unconfirmed,
|
|
|
|
height: output.4,
|
2018-01-17 06:03:40 +03:00
|
|
|
lock_height: output.5,
|
|
|
|
is_coinbase: output.6,
|
|
|
|
block_hash: Hash::zero(),
|
2017-11-20 03:50:09 +03:00
|
|
|
});
|
2017-12-21 01:16:40 +03:00
|
|
|
};
|
2017-11-20 03:50:09 +03:00
|
|
|
}
|
|
|
|
}
|
2017-12-21 01:16:40 +03:00
|
|
|
});
|
2017-11-20 03:50:09 +03:00
|
|
|
h > 0
|
2017-12-21 01:16:40 +03:00
|
|
|
}
|
|
|
|
{}
|
2017-11-20 03:50:09 +03:00
|
|
|
Ok(())
|
|
|
|
}
|