Skip to content

Commit

Permalink
initial impl of UpdateData
Browse files Browse the repository at this point in the history
UpdateData will be useful for caching wallet's utxos. This commit
changes `update`'s API to return all data modified while processing a
block. It also returns what roots changed during addition.
  • Loading branch information
Davidson-Souza committed Oct 10, 2022
1 parent 989be56 commit e79f3dd
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 55 deletions.
8 changes: 5 additions & 3 deletions src/accumulator/proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ impl Proof {
/// let hash = Sha256::from_engine(engine);
/// hashes.push(hash);
/// }
/// let s = s.modify(&hashes, &vec![], &Proof::default()).unwrap();
/// let s = s.modify(&hashes, &vec![], &Proof::default()).unwrap().0;
/// let p = Proof::new(targets, proof_hashes);
/// assert!(p.verify(&vec![hashes[0]] , &s).expect("This proof is valid"));
///```
Expand Down Expand Up @@ -278,7 +278,8 @@ mod tests {
// Create a new stump with 8 leaves and 1 root
let s = Stump::new()
.modify(&hashes, &vec![], &Proof::default())
.expect("This stump is valid");
.expect("This stump is valid")
.0;

// Nodes that will be deleted
let del_hashes = vec![hashes[0], hashes[2], hashes[4], hashes[6]];
Expand Down Expand Up @@ -365,7 +366,8 @@ mod tests {

s = s
.modify(&hashes, &vec![], &Proof::default())
.expect("Test stump is valid");
.expect("Test stump is valid")
.0;
} else {
panic!("Missing test data");
}
Expand Down
156 changes: 106 additions & 50 deletions src/accumulator/stump.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{proof::Proof, types};
use super::{proof::Proof, types, util};
use bitcoin_hashes::sha256;
use std::vec;

Expand All @@ -7,6 +7,15 @@ pub struct Stump {
pub leafs: u64,
pub roots: Vec<bitcoin_hashes::sha256::Hash>,
}
#[derive(Debug, Clone, Default)]
pub struct UpdateData {
/// to_destroy is the positions of the empty roots removed after the add.
pub(crate) to_destroy: Vec<u64>,
/// pre_num_leaves is the numLeaves of the stump before the add.
pub(crate) prev_num_leaves: u64,
/// new_add are the new hashes for the newly created roots after the addition.
pub(crate) new_add: Vec<(u64, sha256::Hash)>,
}

impl Stump {
/// Creates an empty Stump
Expand Down Expand Up @@ -36,21 +45,23 @@ impl Stump {
/// let stxos = vec![];
/// let s = s.modify(&utxos, &stxos, &Proof::default());
/// assert!(s.is_ok());
/// assert_eq!(s.unwrap().roots, utxos);
/// assert_eq!(s.unwrap().0.roots, utxos);
/// ```
pub fn modify(
&self,
utxos: &Vec<bitcoin_hashes::sha256::Hash>,
del_hashes: &Vec<bitcoin_hashes::sha256::Hash>,
proof: &Proof,
) -> Result<Stump, String> {
) -> Result<(Stump, UpdateData), String> {
let mut root_candidates = proof
.calculate_hashes(del_hashes, self)?
.1
.into_iter()
.rev()
.peekable();
let mut computed_roots = self.remove(del_hashes, proof)?.into_iter().rev();

let (_, computed_roots) = self.remove(del_hashes, proof)?;
let mut computed_roots = computed_roots.into_iter().rev();

let mut new_roots = vec![];

Expand All @@ -68,12 +79,19 @@ impl Stump {
new_roots.push(*root);
}

let roots = Stump::add(new_roots, utxos, self.leafs);
let (roots, updated, destroyed) = Stump::add(new_roots, utxos, self.leafs);

Ok(Stump {
let new_stump = Stump {
leafs: self.leafs + utxos.len() as u64,
roots: roots,
})
roots,
};
let update_data = UpdateData {
new_add: updated,
prev_num_leaves: self.leafs,
to_destroy: destroyed,
};

Ok((new_stump, update_data))
}

/// Rewinds old tree state, this should be used in case of reorgs.
Expand All @@ -84,9 +102,9 @@ impl Stump {
/// let s_old = Stump::new();
/// let mut s_new = Stump::new();
///
/// let s_old = s_old.modify(&vec![], &vec![], &Proof::default()).unwrap();
/// let s_old = s_old.modify(&vec![], &vec![], &Proof::default()).unwrap().0;
/// s_new = s_old.clone();
/// s_new = s_new.modify(&vec![], &vec![], &Proof::default()).unwrap();
/// s_new = s_new.modify(&vec![], &vec![], &Proof::default()).unwrap().0;
///
/// // A reorg happened
/// s_new.undo(s_old);
Expand All @@ -100,53 +118,61 @@ impl Stump {
&self,
del_hashes: &Vec<bitcoin_hashes::sha256::Hash>,
proof: &Proof,
) -> Result<Vec<bitcoin_hashes::sha256::Hash>, String> {
) -> Result<(Vec<(u64, sha256::Hash)>, Vec<sha256::Hash>), String> {
if del_hashes.len() == 0 {
return Ok(self.roots.clone());
return Ok((vec![], self.roots.clone()));
}

let del_hashes = vec![sha256::Hash::default(); proof.targets()];
let (_, new_roots) = proof.calculate_hashes(&del_hashes, self)?;

Ok(new_roots)
proof.calculate_hashes(&del_hashes, self)
}
/// Adds new leafs into the root
fn add(
mut roots: Vec<bitcoin_hashes::sha256::Hash>,
utxos: &Vec<bitcoin_hashes::sha256::Hash>,
mut leafs: u64,
) -> Vec<bitcoin_hashes::sha256::Hash> {
for i in utxos.iter() {
Stump::add_single(&mut roots, *i, leafs);
leafs += 1;
}

roots
}

fn add_single(
roots: &mut Vec<bitcoin_hashes::sha256::Hash>,
node: bitcoin_hashes::sha256::Hash,
leafs: u64,
) {
let mut h = 0;
// Iterates over roots, if we find a root that is not empty, we concatenate with
// the one we are adding and create new root, leaving this position empty. Stops
// when find an empty root.

// You can say if a root is empty, by looking a the binary representations of the
// number of leafs. If the h'th bit is one, then this position is occupied, empty
// otherwise.
let mut to_add = node;
while (leafs >> h) & 1 == 1 {
let root = roots.pop();
if let Some(root) = root {
to_add = types::parent_hash(&root, &to_add);
mut leaves: u64,
) -> (Vec<sha256::Hash>, Vec<(u64, sha256::Hash)>, Vec<u64>) {
let after_rows = util::tree_rows(leaves + (utxos.len() as u64));
let mut updated_subtree: Vec<(u64, sha256::Hash)> = vec![];
let all_deleted = util::roots_to_destroy(utxos.len() as u64, leaves, &roots);

for (i, add) in utxos.iter().enumerate() {
let mut pos = leaves;

// deleted is the empty roots that are being added over. These force
// the current root to move up.
let deleted = util::roots_to_destroy((utxos.len() - i) as u64, leaves, &roots);
for del in deleted {
if util::is_ancestor(util::parent(del, after_rows), pos, after_rows).unwrap() {
pos = util::calc_next_pos(pos, del, after_rows).unwrap();
}
}
let mut h = 0;
// Iterates over roots, if we find a root that is not empty, we concatenate with
// the one we are adding and create new root, leaving this position empty. Stops
// when find an empty root.

// You can say if a root is empty, by looking a the binary representations of the
// number of leafs. If the h'th bit is one, then this position is occupied, empty
// otherwise.
let mut to_add = add.clone();
let mut pos = leaves;
while (leaves >> h) & 1 == 1 {
let root = roots.pop();
if let Some(root) = root {
updated_subtree.push((util::left_sibling(pos), root));
updated_subtree.push((pos, to_add));

pos = util::parent(pos, after_rows);
to_add = types::parent_hash(&root, &to_add);
}
h += 1;
}
h += 1;
}

roots.push(to_add);
roots.push(to_add);
leaves += 1;
}
(roots, updated_subtree, all_deleted)
}
}

Expand All @@ -167,7 +193,36 @@ mod test {
assert!(s.leafs == 0);
assert!(s.roots.len() == 0);
}
#[test]
fn test_update_data_add() {
let preimages = vec![0, 1, 2, 3];
let hashes = preimages
.into_iter()
.map(|preimage| hash_from_u8(preimage))
.collect();

let (_, updated) = Stump::new()
.modify(&hashes, &vec![], &Proof::default())
.unwrap();

let positions = vec![0, 1, 2, 3, 4, 5];

let hashes: Vec<_> = vec![
"6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d",
"4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a",
"dbc1b4c900ffe48d575b5da5c638040125f65db0fe3e24494b76ea986457d986",
"084fed08b978af4d7d196a7446a86b58009e636b611db16211b65a9aadff29c5",
"02242b37d8e851f1e86f46790298c7097df06893d6226b7c1453c213e91717de",
"9576f4ade6e9bc3a6458b506ce3e4e890df29cb14cb5d3d887672aef55647a2b",
]
.iter()
.map(|hash| sha256::Hash::from_str(*hash).unwrap())
.collect();

let positions: Vec<_> = positions.into_iter().zip(hashes.into_iter()).collect();

assert_eq!(positions, updated.new_add);
}
fn hash_from_u8(value: u8) -> sha256::Hash {
let mut engine = bitcoin_hashes::sha256::Hash::engine();

Expand Down Expand Up @@ -199,11 +254,11 @@ mod test {

let proof = Proof::new(target_values, proof_hashes);

let stump = Stump::new()
let (stump, _) = Stump::new()
.modify(&leaf_hashes, &vec![], &Proof::default())
.expect("This stump is valid");

let stump = stump.modify(&vec![], &target_hashes, &proof).unwrap();
let (stump, _) = stump.modify(&vec![], &target_hashes, &proof).unwrap();

assert_eq!(stump.roots, roots);
}
Expand All @@ -224,7 +279,7 @@ mod test {
hashes.push(hash_from_u8(i.as_u64().unwrap() as u8));
}

let s = s
let (s, _) = s
.modify(&hashes, &vec![], &Proof::default())
.expect("Stump from test cases are valid");

Expand All @@ -246,7 +301,8 @@ mod test {
let s_old = Stump::new();
let s_old = s_old
.modify(&hashes, &vec![], &Proof::default())
.expect("Stump from test cases are valid");
.expect("Stump from test cases are valid")
.0;

let mut s_new = s_old.clone();

Expand Down
63 changes: 61 additions & 2 deletions src/accumulator/util.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Rustreexo

use std::vec::Vec;

use bitcoin_hashes::{hex::FromHex, sha256};
// isRootPosition checks if the current position is a root given the number of
// leaves and the entire rows of the forest.
pub fn is_root_position(position: u64, num_leaves: u64, forest_rows: u8) -> bool {
Expand Down Expand Up @@ -72,6 +74,44 @@ fn add_and_sort(mut vec: Vec<u64>, value: u64) -> Vec<u64> {
pub fn is_left_niece(position: u64) -> bool {
position & 1 == 0
}
pub fn left_sibling(position: u64) -> u64 {
(position | 1) ^ 1
}
// rootsToDestory returns the empty roots that get written over after numAdds
// amount of leaves have been added.
pub fn roots_to_destroy(
num_adds: u64,
mut num_leaves: u64,
orig_roots: &Vec<sha256::Hash>,
) -> Vec<u64> {
let mut roots = orig_roots.clone();

let mut deleted = vec![];
let mut h = 0;
for add in 0..num_adds {
while (num_leaves >> h) & 1 == 1 {
let root = roots
.pop()
.expect("If (num_leaves >> h) & 1 == 1, it must have at least one root left");
if root == sha256::Hash::default() {
let root_pos =
root_position(num_leaves, h, tree_rows(num_leaves + (num_adds - add)));
deleted.push(root_pos);
}
h += 1;
}
// Just adding a non-zero value to the slice.
roots.push(
sha256::Hash::from_hex(
"0000000000000000000000000000000000000000000000000000000000000001",
)
.unwrap(),
);
num_leaves += 1;
}

deleted
}
// detectSubTreeHight finds the rows of the subtree a given LEAF position and
// the number of leaves (& the forest rows which is redundant)
// Go left to right through the bits of numLeaves,
Expand Down Expand Up @@ -347,7 +387,11 @@ pub fn get_proof_positions(targets: &Vec<u64>, num_leaves: u64, forest_rows: u8)

#[cfg(test)]
mod tests {
use std::vec;
use std::{str::FromStr, vec};

use bitcoin_hashes::sha256;

use super::roots_to_destroy;
#[test]
fn test_is_sibling() {
assert_eq!(super::is_sibling(0, 1), true);
Expand All @@ -368,7 +412,22 @@ mod tests {
fn test_is_right_sibling() {
assert!(super::is_right_sibling(0, 1));
}

#[test]
fn test_roots_to_destroy() {
let roots = vec![
"0000000000000000000000000000000000000000000000000000000000000000",
"aad41f1d55e1a111ca193f6fa4e13dfc0cbdfbea851b30f3eacfe8d9d6be4302",
"0000000000000000000000000000000000000000000000000000000000000000",
"3c2d8cbe4336bbe05fff898102d413ab6356de2598aad4d5a7f916c5b316cb42",
];
let roots = roots
.iter()
.map(|hash| sha256::Hash::from_str(*hash).unwrap())
.collect();
let deleted = roots_to_destroy(1, 15, &roots);

assert_eq!(deleted, vec![22, 28])
}
#[test]
fn test_remove_bit() {
// This should remove just one bit from the final number
Expand Down

0 comments on commit e79f3dd

Please sign in to comment.