mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-21 03:21:08 +03:00
[WIP] Core PMMR and API updates to support wallet restore (#950)
* update pmmr to get batch of elements by insertion position * update pmmr to get batch of elements by insertion position * add api + chain calls to get traversed outputs back out * add api + chain calls to get traversed outputs back out * first pass getting wallet restore to work again with updated utxo-walking api
This commit is contained in:
parent
cf2ffbc11a
commit
dcdf654bc9
7 changed files with 283 additions and 112 deletions
|
@ -127,7 +127,12 @@ impl OutputHandler {
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|output| commitments.is_empty() || commitments.contains(&output.commit))
|
.filter(|output| commitments.is_empty() || commitments.contains(&output.commit))
|
||||||
.map(|output| {
|
.map(|output| {
|
||||||
OutputPrintable::from_output(output, w(&self.chain), &header, include_proof)
|
OutputPrintable::from_output(
|
||||||
|
output,
|
||||||
|
w(&self.chain),
|
||||||
|
Some(&header),
|
||||||
|
include_proof,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -226,6 +231,9 @@ impl Handler for OutputHandler {
|
||||||
// GET /v1/txhashset/lastoutputs?n=5
|
// GET /v1/txhashset/lastoutputs?n=5
|
||||||
// GET /v1/txhashset/lastrangeproofs
|
// GET /v1/txhashset/lastrangeproofs
|
||||||
// GET /v1/txhashset/lastkernels
|
// GET /v1/txhashset/lastkernels
|
||||||
|
|
||||||
|
// UTXO traversal::
|
||||||
|
// GET /v1/txhashset/outputs?start_index=1&max=100
|
||||||
struct TxHashSetHandler {
|
struct TxHashSetHandler {
|
||||||
chain: Weak<chain::Chain>,
|
chain: Weak<chain::Chain>,
|
||||||
}
|
}
|
||||||
|
@ -250,12 +258,34 @@ impl TxHashSetHandler {
|
||||||
fn get_last_n_kernel(&self, distance: u64) -> Vec<TxHashSetNode> {
|
fn get_last_n_kernel(&self, distance: u64) -> Vec<TxHashSetNode> {
|
||||||
TxHashSetNode::get_last_n_kernel(w(&self.chain), distance)
|
TxHashSetNode::get_last_n_kernel(w(&self.chain), distance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// allows traversal of utxo set
|
||||||
|
fn outputs(&self, start_index: u64, mut max: u64) -> OutputListing {
|
||||||
|
//set a limit here
|
||||||
|
if max > 1000 {
|
||||||
|
max = 1000;
|
||||||
|
}
|
||||||
|
let outputs = w(&self.chain)
|
||||||
|
.unspent_outputs_by_insertion_index(start_index, max)
|
||||||
|
.unwrap();
|
||||||
|
OutputListing {
|
||||||
|
last_retrieved_index: outputs.0,
|
||||||
|
highest_index: outputs.1,
|
||||||
|
outputs: outputs
|
||||||
|
.2
|
||||||
|
.iter()
|
||||||
|
.map(|x| OutputPrintable::from_output(x, w(&self.chain), None, true))
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler for TxHashSetHandler {
|
impl Handler for TxHashSetHandler {
|
||||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
||||||
let url = req.url.clone();
|
let url = req.url.clone();
|
||||||
let mut path_elems = url.path();
|
let mut path_elems = url.path();
|
||||||
|
let mut start_index = 1;
|
||||||
|
let mut max = 100;
|
||||||
if *path_elems.last().unwrap() == "" {
|
if *path_elems.last().unwrap() == "" {
|
||||||
path_elems.pop();
|
path_elems.pop();
|
||||||
}
|
}
|
||||||
|
@ -269,12 +299,27 @@ impl Handler for TxHashSetHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(start_indexes) = params.get("start_index") {
|
||||||
|
for si in start_indexes {
|
||||||
|
if let Ok(s) = str::parse(si) {
|
||||||
|
start_index = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(maxes) = params.get("max") {
|
||||||
|
for ma in maxes {
|
||||||
|
if let Ok(m) = str::parse(ma) {
|
||||||
|
max = m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
match *path_elems.last().unwrap() {
|
match *path_elems.last().unwrap() {
|
||||||
"roots" => json_response_pretty(&self.get_roots()),
|
"roots" => json_response_pretty(&self.get_roots()),
|
||||||
"lastoutputs" => json_response_pretty(&self.get_last_n_output(last_n)),
|
"lastoutputs" => json_response_pretty(&self.get_last_n_output(last_n)),
|
||||||
"lastrangeproofs" => json_response_pretty(&self.get_last_n_rangeproof(last_n)),
|
"lastrangeproofs" => json_response_pretty(&self.get_last_n_rangeproof(last_n)),
|
||||||
"lastkernels" => json_response_pretty(&self.get_last_n_kernel(last_n)),
|
"lastkernels" => json_response_pretty(&self.get_last_n_kernel(last_n)),
|
||||||
|
"outputs" => json_response_pretty(&self.outputs(start_index, max)),
|
||||||
_ => Ok(Response::with((status::BadRequest, ""))),
|
_ => Ok(Response::with((status::BadRequest, ""))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -710,6 +755,7 @@ pub fn start_rest_apis<T>(
|
||||||
"get txhashset/lastoutputs?n=10".to_string(),
|
"get txhashset/lastoutputs?n=10".to_string(),
|
||||||
"get txhashset/lastrangeproofs".to_string(),
|
"get txhashset/lastrangeproofs".to_string(),
|
||||||
"get txhashset/lastkernels".to_string(),
|
"get txhashset/lastkernels".to_string(),
|
||||||
|
"get txhashset/outputs?start_index=1&max=100".to_string(),
|
||||||
"get pool".to_string(),
|
"get pool".to_string(),
|
||||||
"post pool/push".to_string(),
|
"post pool/push".to_string(),
|
||||||
"post peers/a.b.c.d:p/ban".to_string(),
|
"post peers/a.b.c.d:p/ban".to_string(),
|
||||||
|
|
|
@ -240,7 +240,7 @@ impl OutputPrintable {
|
||||||
pub fn from_output(
|
pub fn from_output(
|
||||||
output: &core::Output,
|
output: &core::Output,
|
||||||
chain: Arc<chain::Chain>,
|
chain: Arc<chain::Chain>,
|
||||||
block_header: &core::BlockHeader,
|
block_header: Option<&core::BlockHeader>,
|
||||||
include_proof: bool,
|
include_proof: bool,
|
||||||
) -> OutputPrintable {
|
) -> OutputPrintable {
|
||||||
let output_type = if output
|
let output_type = if output
|
||||||
|
@ -269,8 +269,9 @@ impl OutputPrintable {
|
||||||
if output
|
if output
|
||||||
.features
|
.features
|
||||||
.contains(core::transaction::OutputFeatures::COINBASE_OUTPUT) && !spent
|
.contains(core::transaction::OutputFeatures::COINBASE_OUTPUT) && !spent
|
||||||
|
&& block_header.is_some()
|
||||||
{
|
{
|
||||||
merkle_proof = chain.get_merkle_proof(&out_id, &block_header).ok()
|
merkle_proof = chain.get_merkle_proof(&out_id, &block_header.unwrap()).ok()
|
||||||
};
|
};
|
||||||
|
|
||||||
OutputPrintable {
|
OutputPrintable {
|
||||||
|
@ -527,7 +528,12 @@ impl BlockPrintable {
|
||||||
.outputs
|
.outputs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|output| {
|
.map(|output| {
|
||||||
OutputPrintable::from_output(output, chain.clone(), &block.header, include_proof)
|
OutputPrintable::from_output(
|
||||||
|
output,
|
||||||
|
chain.clone(),
|
||||||
|
Some(&block.header),
|
||||||
|
include_proof,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let kernels = block
|
let kernels = block
|
||||||
|
@ -566,7 +572,7 @@ impl CompactBlockPrintable {
|
||||||
let block = chain.get_block(&cb.hash()).unwrap();
|
let block = chain.get_block(&cb.hash()).unwrap();
|
||||||
let out_full = cb.out_full
|
let out_full = cb.out_full
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| OutputPrintable::from_output(x, chain.clone(), &block.header, false))
|
.map(|x| OutputPrintable::from_output(x, chain.clone(), Some(&block.header), false))
|
||||||
.collect();
|
.collect();
|
||||||
let kern_full = cb.kern_full
|
let kern_full = cb.kern_full
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -591,6 +597,18 @@ pub struct BlockOutputs {
|
||||||
pub outputs: Vec<OutputPrintable>,
|
pub outputs: Vec<OutputPrintable>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For traversing all outputs in the UTXO set
|
||||||
|
// transactions in the block
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct OutputListing {
|
||||||
|
/// The last available output index
|
||||||
|
pub highest_index: u64,
|
||||||
|
/// The last insertion index retrieved
|
||||||
|
pub last_retrieved_index: u64,
|
||||||
|
/// A printable version of the outputs
|
||||||
|
pub outputs: Vec<OutputPrintable>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct PoolInfo {
|
pub struct PoolInfo {
|
||||||
/// Size of the pool
|
/// Size of the pool
|
||||||
|
|
|
@ -20,7 +20,7 @@ use std::fs::File;
|
||||||
use std::sync::{Arc, Mutex, RwLock};
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use core::core::{Block, BlockHeader, Input, OutputFeatures, OutputIdentifier, TxKernel};
|
use core::core::{Block, BlockHeader, Input, Output, OutputFeatures, OutputIdentifier, TxKernel};
|
||||||
use core::core::hash::{Hash, Hashed};
|
use core::core::hash::{Hash, Hashed};
|
||||||
use core::core::pmmr::MerkleProof;
|
use core::core::pmmr::MerkleProof;
|
||||||
use core::core::target::Difficulty;
|
use core::core::target::Difficulty;
|
||||||
|
@ -657,6 +657,32 @@ impl Chain {
|
||||||
txhashset.last_n_kernel(distance)
|
txhashset.last_n_kernel(distance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// outputs by insertion index
|
||||||
|
pub fn unspent_outputs_by_insertion_index(
|
||||||
|
&self,
|
||||||
|
start_index: u64,
|
||||||
|
max: u64,
|
||||||
|
) -> Result<(u64, u64, Vec<Output>), Error> {
|
||||||
|
let mut txhashset = self.txhashset.write().unwrap();
|
||||||
|
let max_index = txhashset.highest_output_insertion_index();
|
||||||
|
let outputs = txhashset.outputs_by_insertion_index(start_index, max);
|
||||||
|
let rangeproofs = txhashset.rangeproofs_by_insertion_index(start_index, max);
|
||||||
|
if outputs.0 != rangeproofs.0 || outputs.1.len() != rangeproofs.1.len() {
|
||||||
|
return Err(Error::TxHashSetErr(String::from(
|
||||||
|
"Output and rangeproof sets don't match",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let mut output_vec: Vec<Output> = vec![];
|
||||||
|
for (ref x, &y) in outputs.1.iter().zip(rangeproofs.1.iter()) {
|
||||||
|
output_vec.push(Output {
|
||||||
|
commit: x.commit,
|
||||||
|
features: x.features,
|
||||||
|
proof: y,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok((outputs.0, max_index, output_vec))
|
||||||
|
}
|
||||||
|
|
||||||
/// Total difficulty at the head of the chain
|
/// Total difficulty at the head of the chain
|
||||||
pub fn total_difficulty(&self) -> Difficulty {
|
pub fn total_difficulty(&self) -> Difficulty {
|
||||||
self.head.lock().unwrap().clone().total_difficulty
|
self.head.lock().unwrap().clone().total_difficulty
|
||||||
|
|
|
@ -183,6 +183,34 @@ impl TxHashSet {
|
||||||
kernel_pmmr.get_last_n_insertions(distance)
|
kernel_pmmr.get_last_n_insertions(distance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// returns outputs from the given insertion (leaf) index up to the specified
|
||||||
|
/// limit. Also returns the last index actually populated
|
||||||
|
pub fn outputs_by_insertion_index(
|
||||||
|
&mut self,
|
||||||
|
start_index: u64,
|
||||||
|
max_count: u64,
|
||||||
|
) -> (u64, Vec<OutputIdentifier>) {
|
||||||
|
let output_pmmr: PMMR<OutputIdentifier, _> =
|
||||||
|
PMMR::at(&mut self.output_pmmr_h.backend, self.output_pmmr_h.last_pos);
|
||||||
|
output_pmmr.elements_from_insertion_index(start_index, max_count)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// highest output insertion index availalbe
|
||||||
|
pub fn highest_output_insertion_index(&mut self) -> u64 {
|
||||||
|
pmmr::n_leaves(self.output_pmmr_h.last_pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// As above, for rangeproofs
|
||||||
|
pub fn rangeproofs_by_insertion_index(
|
||||||
|
&mut self,
|
||||||
|
start_index: u64,
|
||||||
|
max_count: u64,
|
||||||
|
) -> (u64, Vec<RangeProof>) {
|
||||||
|
let rproof_pmmr: PMMR<RangeProof, _> =
|
||||||
|
PMMR::at(&mut self.rproof_pmmr_h.backend, self.rproof_pmmr_h.last_pos);
|
||||||
|
rproof_pmmr.elements_from_insertion_index(start_index, max_count)
|
||||||
|
}
|
||||||
|
|
||||||
/// Output and kernel MMR indexes at the end of the provided block
|
/// Output and kernel MMR indexes at the end of the provided block
|
||||||
pub fn indexes_at(&self, bh: &Hash) -> Result<(u64, u64), Error> {
|
pub fn indexes_at(&self, bh: &Hash) -> Result<(u64, u64), Error> {
|
||||||
self.commit_index.get_block_marker(bh).map_err(&From::from)
|
self.commit_index.get_block_marker(bh).map_err(&From::from)
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
|
|
||||||
use std::clone::Clone;
|
use std::clone::Clone;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use core::hash::{Hash};
|
use core::hash::Hash;
|
||||||
use ser;
|
use ser;
|
||||||
use ser::{Readable, Reader, Writeable, Writer};
|
use ser::{Readable, Reader, Writeable, Writer};
|
||||||
use ser::{PMMRIndexHashable, PMMRable};
|
use ser::{PMMRIndexHashable, PMMRable};
|
||||||
|
@ -223,7 +223,7 @@ impl MerkleProof {
|
||||||
for peak in self.peaks.iter().rev() {
|
for peak in self.peaks.iter().rev() {
|
||||||
bagged = match bagged {
|
bagged = match bagged {
|
||||||
None => Some(*peak),
|
None => Some(*peak),
|
||||||
Some(rhs) => Some((*peak,rhs).hash_with_index(self.mmr_size)),
|
Some(rhs) => Some((*peak, rhs).hash_with_index(self.mmr_size)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bagged == Some(self.root);
|
return bagged == Some(self.root);
|
||||||
|
@ -236,9 +236,9 @@ impl MerkleProof {
|
||||||
// hash our node and sibling together (noting left/right position of the
|
// hash our node and sibling together (noting left/right position of the
|
||||||
// sibling)
|
// sibling)
|
||||||
let parent = if is_left_sibling(sibling_pos) {
|
let parent = if is_left_sibling(sibling_pos) {
|
||||||
(sibling, self.node).hash_with_index(parent_pos-1)
|
(sibling, self.node).hash_with_index(parent_pos - 1)
|
||||||
} else {
|
} else {
|
||||||
(self.node, sibling).hash_with_index(parent_pos-1)
|
(self.node, sibling).hash_with_index(parent_pos - 1)
|
||||||
};
|
};
|
||||||
|
|
||||||
let proof = MerkleProof {
|
let proof = MerkleProof {
|
||||||
|
@ -316,7 +316,7 @@ where
|
||||||
for peak in self.peaks().iter().rev() {
|
for peak in self.peaks().iter().rev() {
|
||||||
res = match res {
|
res = match res {
|
||||||
None => Some(*peak),
|
None => Some(*peak),
|
||||||
Some(rhash) => Some((*peak,rhash).hash_with_index(self.unpruned_size())),
|
Some(rhash) => Some((*peak, rhash).hash_with_index(self.unpruned_size())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res.expect("no root, invalid tree")
|
res.expect("no root, invalid tree")
|
||||||
|
@ -370,7 +370,7 @@ where
|
||||||
/// the same time if applicable.
|
/// the same time if applicable.
|
||||||
pub fn push(&mut self, elmt: T) -> Result<u64, String> {
|
pub fn push(&mut self, elmt: T) -> Result<u64, String> {
|
||||||
let elmt_pos = self.last_pos + 1;
|
let elmt_pos = self.last_pos + 1;
|
||||||
let mut current_hash = elmt.hash_with_index(elmt_pos-1);
|
let mut current_hash = elmt.hash_with_index(elmt_pos - 1);
|
||||||
|
|
||||||
let mut to_append = vec![(current_hash, Some(elmt))];
|
let mut to_append = vec![(current_hash, Some(elmt))];
|
||||||
let mut height = 0;
|
let mut height = 0;
|
||||||
|
@ -390,7 +390,7 @@ where
|
||||||
height += 1;
|
height += 1;
|
||||||
pos += 1;
|
pos += 1;
|
||||||
|
|
||||||
current_hash = (left_hash, current_hash).hash_with_index(pos-1);
|
current_hash = (left_hash, current_hash).hash_with_index(pos - 1);
|
||||||
|
|
||||||
to_append.push((current_hash.clone(), None));
|
to_append.push((current_hash.clone(), None));
|
||||||
}
|
}
|
||||||
|
@ -525,6 +525,27 @@ where
|
||||||
return_vec
|
return_vec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function which returns un-pruned nodes from the insertion index
|
||||||
|
/// forward
|
||||||
|
/// returns last insertion index returned along with data
|
||||||
|
pub fn elements_from_insertion_index(&self, mut index: u64, max_count: u64) -> (u64, Vec<T>) {
|
||||||
|
let mut return_vec = vec![];
|
||||||
|
if index == 0 {
|
||||||
|
index = 1;
|
||||||
|
}
|
||||||
|
let mut return_index = index;
|
||||||
|
let mut pmmr_index = insertion_to_pmmr_index(index);
|
||||||
|
while return_vec.len() < max_count as usize && pmmr_index <= self.last_pos {
|
||||||
|
if let Some(t) = self.get_data(pmmr_index) {
|
||||||
|
return_vec.push(t);
|
||||||
|
return_index = index;
|
||||||
|
}
|
||||||
|
index += 1;
|
||||||
|
pmmr_index = insertion_to_pmmr_index(index);
|
||||||
|
}
|
||||||
|
(return_index, return_vec)
|
||||||
|
}
|
||||||
|
|
||||||
/// Walks all unpruned nodes in the MMR and revalidate all parent hashes
|
/// Walks all unpruned nodes in the MMR and revalidate all parent hashes
|
||||||
pub fn validate(&self) -> Result<(), String> {
|
pub fn validate(&self) -> Result<(), String> {
|
||||||
// iterate on all parent nodes
|
// iterate on all parent nodes
|
||||||
|
@ -540,7 +561,9 @@ where
|
||||||
if let Some(right_child_hs) = self.get_from_file(right_pos) {
|
if let Some(right_child_hs) = self.get_from_file(right_pos) {
|
||||||
// hash the two child nodes together with parent_pos and compare
|
// hash the two child nodes together with parent_pos and compare
|
||||||
let (parent_pos, _) = family(left_pos);
|
let (parent_pos, _) = family(left_pos);
|
||||||
if (left_child_hs, right_child_hs).hash_with_index(parent_pos-1) != hash {
|
if (left_child_hs, right_child_hs).hash_with_index(parent_pos - 1)
|
||||||
|
!= hash
|
||||||
|
{
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"Invalid MMR, hash of parent at {} does \
|
"Invalid MMR, hash of parent at {} does \
|
||||||
not match children.",
|
not match children.",
|
||||||
|
@ -824,6 +847,13 @@ pub fn n_leaves(mut sz: u64) -> u64 {
|
||||||
.sum()
|
.sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the pmmr index of the nth inserted element
|
||||||
|
pub fn insertion_to_pmmr_index(mut sz: u64) -> u64 {
|
||||||
|
//1 based pmmrs
|
||||||
|
sz = sz - 1;
|
||||||
|
2 * sz - sz.count_ones() as u64 + 1
|
||||||
|
}
|
||||||
|
|
||||||
/// The height of a node in a full binary tree from its postorder traversal
|
/// The height of a node in a full binary tree from its postorder traversal
|
||||||
/// index. This function is the base on which all others, as well as the MMR,
|
/// index. This function is the base on which all others, as well as the MMR,
|
||||||
/// are built.
|
/// are built.
|
||||||
|
@ -1472,7 +1502,10 @@ mod test {
|
||||||
pmmr.push(elems[6]).unwrap();
|
pmmr.push(elems[6]).unwrap();
|
||||||
let pos_10 = elems[6].hash_with_index(10);
|
let pos_10 = elems[6].hash_with_index(10);
|
||||||
assert_eq!(pmmr.peaks(), vec![pos_6, pos_9, pos_10]);
|
assert_eq!(pmmr.peaks(), vec![pos_6, pos_9, pos_10]);
|
||||||
assert_eq!(pmmr.root(), (pos_6, (pos_9, pos_10).hash_with_index(11)).hash_with_index(11));
|
assert_eq!(
|
||||||
|
pmmr.root(),
|
||||||
|
(pos_6, (pos_9, pos_10).hash_with_index(11)).hash_with_index(11)
|
||||||
|
);
|
||||||
assert_eq!(pmmr.unpruned_size(), 11);
|
assert_eq!(pmmr.unpruned_size(), 11);
|
||||||
|
|
||||||
// 001001200100123
|
// 001001200100123
|
||||||
|
@ -1901,11 +1934,10 @@ mod test {
|
||||||
assert_eq!(n_leaves(10), 6);
|
assert_eq!(n_leaves(10), 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_all_ones() {
|
fn check_all_ones() {
|
||||||
for i in 0..1000000 {
|
for i in 0..1000000 {
|
||||||
assert_eq!(old_all_ones(i),all_ones(i));
|
assert_eq!(old_all_ones(i), all_ones(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1927,7 +1959,7 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn check_most_significant_pos() {
|
fn check_most_significant_pos() {
|
||||||
for i in 0u64..1000000 {
|
for i in 0u64..1000000 {
|
||||||
assert_eq!(old_most_significant_pos(i),most_significant_pos(i));
|
assert_eq!(old_most_significant_pos(i), most_significant_pos(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1941,4 +1973,57 @@ mod test {
|
||||||
}
|
}
|
||||||
pos
|
pos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_insertion_to_pmmr_index() {
|
||||||
|
assert_eq!(insertion_to_pmmr_index(1), 1);
|
||||||
|
assert_eq!(insertion_to_pmmr_index(2), 2);
|
||||||
|
assert_eq!(insertion_to_pmmr_index(3), 4);
|
||||||
|
assert_eq!(insertion_to_pmmr_index(4), 5);
|
||||||
|
assert_eq!(insertion_to_pmmr_index(5), 8);
|
||||||
|
assert_eq!(insertion_to_pmmr_index(6), 9);
|
||||||
|
assert_eq!(insertion_to_pmmr_index(7), 11);
|
||||||
|
assert_eq!(insertion_to_pmmr_index(8), 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_elements_from_insertion_index() {
|
||||||
|
let mut ba = VecBackend::new();
|
||||||
|
let mut pmmr = PMMR::new(&mut ba);
|
||||||
|
for x in 1..1000 {
|
||||||
|
pmmr.push(TestElem([0, 0, 0, x])).unwrap();
|
||||||
|
}
|
||||||
|
// Normal case
|
||||||
|
let res = pmmr.elements_from_insertion_index(1, 100);
|
||||||
|
assert_eq!(res.0, 100);
|
||||||
|
assert_eq!(res.1.len(), 100);
|
||||||
|
assert_eq!(res.1[0].0[3], 1);
|
||||||
|
assert_eq!(res.1[99].0[3], 100);
|
||||||
|
|
||||||
|
// middle of pack
|
||||||
|
let res = pmmr.elements_from_insertion_index(351, 70);
|
||||||
|
assert_eq!(res.0, 420);
|
||||||
|
assert_eq!(res.1.len(), 70);
|
||||||
|
assert_eq!(res.1[0].0[3], 351);
|
||||||
|
assert_eq!(res.1[69].0[3], 420);
|
||||||
|
|
||||||
|
// past the end
|
||||||
|
let res = pmmr.elements_from_insertion_index(650, 1000);
|
||||||
|
assert_eq!(res.0, 999);
|
||||||
|
assert_eq!(res.1.len(), 350);
|
||||||
|
assert_eq!(res.1[0].0[3], 650);
|
||||||
|
assert_eq!(res.1[349].0[3], 999);
|
||||||
|
|
||||||
|
// pruning a few nodes should get consistent results
|
||||||
|
pmmr.prune(insertion_to_pmmr_index(650), 0).unwrap();
|
||||||
|
pmmr.prune(insertion_to_pmmr_index(651), 0).unwrap();
|
||||||
|
pmmr.prune(insertion_to_pmmr_index(800), 0).unwrap();
|
||||||
|
pmmr.prune(insertion_to_pmmr_index(900), 0).unwrap();
|
||||||
|
pmmr.prune(insertion_to_pmmr_index(998), 0).unwrap();
|
||||||
|
let res = pmmr.elements_from_insertion_index(650, 1000);
|
||||||
|
assert_eq!(res.0, 999);
|
||||||
|
assert_eq!(res.1.len(), 345);
|
||||||
|
assert_eq!(res.1[0].0[3], 652);
|
||||||
|
assert_eq!(res.1[344].0[3], 999);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -208,14 +208,6 @@ fn main() {
|
||||||
.long("api_server_address")
|
.long("api_server_address")
|
||||||
.help("Api address of running node on which to check inputs and post transactions")
|
.help("Api address of running node on which to check inputs and post transactions")
|
||||||
.takes_value(true))
|
.takes_value(true))
|
||||||
.arg(Arg::with_name("key_derivations")
|
|
||||||
.help("The number of keys possiblities to search for each output. \
|
|
||||||
Ideally, set this to a number greater than the number of outputs \
|
|
||||||
you believe should belong to this seed/password.")
|
|
||||||
.short("k")
|
|
||||||
.long("key_derivations")
|
|
||||||
.default_value("1000")
|
|
||||||
.takes_value(true))
|
|
||||||
|
|
||||||
.subcommand(SubCommand::with_name("listen")
|
.subcommand(SubCommand::with_name("listen")
|
||||||
.about("Runs the wallet in listening mode waiting for transactions.")
|
.about("Runs the wallet in listening mode waiting for transactions.")
|
||||||
|
@ -528,12 +520,6 @@ fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) {
|
||||||
wallet_config.check_node_api_http_addr = sa.to_string().clone();
|
wallet_config.check_node_api_http_addr = sa.to_string().clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
let key_derivations: u32 = wallet_args
|
|
||||||
.value_of("key_derivations")
|
|
||||||
.unwrap()
|
|
||||||
.parse()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut show_spent = false;
|
let mut show_spent = false;
|
||||||
if wallet_args.is_present("show_spent") {
|
if wallet_args.is_present("show_spent") {
|
||||||
show_spent = true;
|
show_spent = true;
|
||||||
|
@ -656,7 +642,7 @@ fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) {
|
||||||
wallet::show_outputs(&wallet_config, &keychain, show_spent);
|
wallet::show_outputs(&wallet_config, &keychain, show_spent);
|
||||||
}
|
}
|
||||||
("restore", Some(_)) => {
|
("restore", Some(_)) => {
|
||||||
let _ = wallet::restore(&wallet_config, &keychain, key_derivations);
|
let _ = wallet::restore(&wallet_config, &keychain);
|
||||||
}
|
}
|
||||||
_ => panic!("Unknown wallet command, use 'grin help wallet' for details"),
|
_ => panic!("Unknown wallet command, use 'grin help wallet' for details"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ use core::core::transaction::ProofMessageElements;
|
||||||
use types::{Error, ErrorKind, OutputData, OutputStatus, WalletConfig, WalletData};
|
use types::{Error, ErrorKind, OutputData, OutputStatus, WalletConfig, WalletData};
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
|
|
||||||
pub fn get_chain_height(config: &WalletConfig) -> Result<u64, Error> {
|
pub fn _get_chain_height(config: &WalletConfig) -> Result<u64, Error> {
|
||||||
let url = format!("{}/v1/chain", config.check_node_api_http_addr);
|
let url = format!("{}/v1/chain", config.check_node_api_http_addr);
|
||||||
|
|
||||||
match api::client::get::<api::Tip>(url.as_str()) {
|
match api::client::get::<api::Tip>(url.as_str()) {
|
||||||
|
@ -46,28 +46,25 @@ fn coinbase_status(output: &api::OutputPrintable) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn outputs_batch_block(
|
pub fn outputs_batch(
|
||||||
config: &WalletConfig,
|
config: &WalletConfig,
|
||||||
start_height: u64,
|
start_height: u64,
|
||||||
end_height: u64,
|
max: u64,
|
||||||
) -> Result<Vec<api::BlockOutputs>, Error> {
|
) -> Result<api::OutputListing, Error> {
|
||||||
let query_param = format!(
|
let query_param = format!("start_index={}&max={}", start_height, max);
|
||||||
"start_height={}&end_height={}&include_rp",
|
|
||||||
start_height, end_height
|
|
||||||
);
|
|
||||||
|
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"{}/v1/chain/outputs/byheight?{}",
|
"{}/v1/txhashset/outputs?{}",
|
||||||
config.check_node_api_http_addr, query_param,
|
config.check_node_api_http_addr, query_param,
|
||||||
);
|
);
|
||||||
|
|
||||||
match api::client::get::<Vec<api::BlockOutputs>>(url.as_str()) {
|
match api::client::get::<api::OutputListing>(url.as_str()) {
|
||||||
Ok(outputs) => Ok(outputs),
|
Ok(o) => Ok(o),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// if we got anything other than 200 back from server, bye
|
// if we got anything other than 200 back from server, bye
|
||||||
error!(
|
error!(
|
||||||
LOGGER,
|
LOGGER,
|
||||||
"outputs_batch_block: Restore failed... unable to contact API {}. Error: {}",
|
"outputs_batch: Restore failed... unable to contact API {}. Error: {}",
|
||||||
config.check_node_api_http_addr,
|
config.check_node_api_http_addr,
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
|
@ -79,23 +76,18 @@ pub fn outputs_batch_block(
|
||||||
// TODO - wrap the many return values in a struct
|
// TODO - wrap the many return values in a struct
|
||||||
fn find_outputs_with_key(
|
fn find_outputs_with_key(
|
||||||
keychain: &Keychain,
|
keychain: &Keychain,
|
||||||
block_outputs: api::BlockOutputs,
|
outputs: Vec<api::OutputPrintable>,
|
||||||
key_iterations: &mut usize,
|
|
||||||
) -> Vec<(pedersen::Commitment, Identifier, u32, u64, u64, u64, bool)> {
|
) -> Vec<(pedersen::Commitment, Identifier, u32, u64, u64, u64, bool)> {
|
||||||
let mut wallet_outputs: 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::new();
|
||||||
|
|
||||||
info!(
|
let max_derivations = 1_000_000;
|
||||||
LOGGER,
|
|
||||||
"Scanning block {}, {} outputs, over {} key derivations",
|
info!(LOGGER, "Scanning {} outputs", outputs.len(),);
|
||||||
block_outputs.header.height,
|
|
||||||
block_outputs.outputs.len(),
|
|
||||||
*key_iterations,
|
|
||||||
);
|
|
||||||
|
|
||||||
// skey doesn't matter in this case
|
// skey doesn't matter in this case
|
||||||
let skey = keychain.derive_key_id(1).unwrap();
|
let skey = keychain.derive_key_id(1).unwrap();
|
||||||
for output in block_outputs.outputs.iter().filter(|x| !x.spent) {
|
for output in outputs.iter().filter(|x| !x.spent) {
|
||||||
// attempt to unwind message from the RP and get a value.. note
|
// attempt to unwind message from the RP and get a value.. note
|
||||||
// this will only return okay if the value is included in the
|
// this will only return okay if the value is included in the
|
||||||
// message 3 times, indicating a strong match. Also, sec_key provided
|
// message 3 times, indicating a strong match. Also, sec_key provided
|
||||||
|
@ -111,7 +103,7 @@ fn find_outputs_with_key(
|
||||||
}
|
}
|
||||||
// we have a match, now check through our key iterations to find a partial match
|
// we have a match, now check through our key iterations to find a partial match
|
||||||
let mut found = false;
|
let mut found = false;
|
||||||
for i in 1..*key_iterations {
|
for i in 1..max_derivations {
|
||||||
let key_id = &keychain.derive_key_id(i as u32).unwrap();
|
let key_id = &keychain.derive_key_id(i as u32).unwrap();
|
||||||
if !message.compare_bf_first_8(key_id) {
|
if !message.compare_bf_first_8(key_id) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -143,7 +135,8 @@ fn find_outputs_with_key(
|
||||||
.commit_with_key_index(BigEndian::read_u64(&commit_id), i as u32)
|
.commit_with_key_index(BigEndian::read_u64(&commit_id), i as u32)
|
||||||
.expect("commit with key index");
|
.expect("commit with key index");
|
||||||
|
|
||||||
let height = block_outputs.header.height;
|
//let height = outputs.header.height;
|
||||||
|
let height = 0;
|
||||||
let lock_height = if is_coinbase {
|
let lock_height = if is_coinbase {
|
||||||
height + global::coinbase_maturity()
|
height + global::coinbase_maturity()
|
||||||
} else {
|
} else {
|
||||||
|
@ -159,31 +152,25 @@ fn find_outputs_with_key(
|
||||||
lock_height,
|
lock_height,
|
||||||
is_coinbase,
|
is_coinbase,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
warn!(
|
warn!(
|
||||||
LOGGER,
|
LOGGER,
|
||||||
"Very probable matching output found with amount: {} \
|
"Very probable matching output found with amount: {} \
|
||||||
run restore again with a larger value of key_iterations to claim",
|
but didn't match key child key up to {}",
|
||||||
message.value().unwrap()
|
message.value().unwrap(),
|
||||||
|
max_derivations,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
debug!(
|
debug!(LOGGER, "Found {} wallet_outputs", wallet_outputs.len(),);
|
||||||
LOGGER,
|
|
||||||
"Found {} wallet_outputs for block {}",
|
|
||||||
wallet_outputs.len(),
|
|
||||||
block_outputs.header.height,
|
|
||||||
);
|
|
||||||
|
|
||||||
wallet_outputs
|
wallet_outputs
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn restore(
|
pub fn restore(config: &WalletConfig, keychain: &Keychain) -> Result<(), Error> {
|
||||||
config: &WalletConfig,
|
|
||||||
keychain: &Keychain,
|
|
||||||
key_derivations: u32,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
// Don't proceed if wallet.dat has anything in it
|
// Don't proceed if wallet.dat has anything in it
|
||||||
let is_empty = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
|
let is_empty = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
|
||||||
Ok(wallet_data.outputs.len() == 0)
|
Ok(wallet_data.outputs.len() == 0)
|
||||||
|
@ -196,31 +183,24 @@ pub fn restore(
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get height of chain from node (we'll check again when done)
|
info!(LOGGER, "Starting restore.");
|
||||||
let chain_height = get_chain_height(config)?;
|
|
||||||
info!(
|
|
||||||
LOGGER,
|
|
||||||
"Starting restore: Chain height is {}.", chain_height
|
|
||||||
);
|
|
||||||
|
|
||||||
let batch_size = 100;
|
let batch_size = 1000;
|
||||||
|
let mut start_index = 1;
|
||||||
// this will start here, then lower as outputs are found, moving backwards on
|
// this will start here, then lower as outputs are found, moving backwards on
|
||||||
// the chain
|
// the chain
|
||||||
let mut key_iterations = key_derivations as usize;
|
loop {
|
||||||
let mut h = chain_height;
|
let output_listing = outputs_batch(config, start_index, batch_size)?;
|
||||||
while {
|
info!(
|
||||||
let end_batch = h;
|
LOGGER,
|
||||||
if h >= batch_size {
|
"Retrieved {} outputs, up to index {}. (Highest index: {})",
|
||||||
h -= batch_size;
|
output_listing.outputs.len(),
|
||||||
} else {
|
output_listing.last_retrieved_index,
|
||||||
h = 0;
|
output_listing.highest_index
|
||||||
}
|
);
|
||||||
let mut blocks = outputs_batch_block(config, h + 1, end_batch)?;
|
|
||||||
blocks.reverse();
|
|
||||||
|
|
||||||
let _ = WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
|
let _ = WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
|
||||||
for block in blocks {
|
let result_vec = find_outputs_with_key(keychain, output_listing.outputs.clone());
|
||||||
let result_vec = find_outputs_with_key(keychain, block, &mut key_iterations);
|
|
||||||
if result_vec.len() > 0 {
|
if result_vec.len() > 0 {
|
||||||
for output in result_vec.clone() {
|
for output in result_vec.clone() {
|
||||||
let root_key_id = keychain.root_key_id();
|
let root_key_id = keychain.root_key_id();
|
||||||
|
@ -240,9 +220,11 @@ pub fn restore(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
h > 0
|
if output_listing.highest_index == output_listing.last_retrieved_index {
|
||||||
} {}
|
break;
|
||||||
|
}
|
||||||
|
start_index = output_listing.last_retrieved_index + 1;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue