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 25, 2022
1 parent efa63f6 commit 360e7ab
Show file tree
Hide file tree
Showing 4 changed files with 389 additions and 56 deletions.
5 changes: 3 additions & 2 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 @@ -288,7 +288,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
239 changes: 187 additions & 52 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 @@ -67,13 +78,19 @@ impl Stump {

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

let roots = 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 +101,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 +117,67 @@ 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;
}
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();
while (leaves >> h) & 1 == 1 {
let root = roots.pop();

if let Some(root) = root {
if root != sha256::Hash::default() {
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;
}
updated_subtree.push((pos, to_add));

roots
}
updated_subtree.sort();
updated_subtree.dedup();

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);
}
h += 1;
roots.push(to_add);
leaves += 1;
}

roots.push(to_add);
(roots, updated_subtree, all_deleted)
}
}

Expand Down Expand Up @@ -174,7 +205,112 @@ mod test {
assert!(s.leafs == 0);
assert!(s.roots.len() == 0);
}
#[test]
fn test_updated_data() {
/// This test initializes a Stump, with some utxos. Then, we add a couple more utxos
/// and delete some others to see what we get back for Modified.
#[derive(Debug, Deserialize)]
struct TestData {
roots: Vec<String>,
leaves: u64,
/// New data to add
additional_preimages: Vec<u64>,
/// The hash of all targets to be deleted
del_hashes: Vec<String>,
/// The hashes that are used to recompute a given Merkle path to the root
proof_hashes: Vec<String>,
/// Which nodes are being proven, in this case, they'll be deleted
proof_targets: Vec<u64>,
/// Here are the expected values:
/// During addition, we create those nodes
new_add_pos: Vec<u64>,
new_add_hash: Vec<String>,

to_destroy: Vec<u64>,
}
let contents = std::fs::read_to_string("test_values/cache_tests.json")
.expect("Something went wrong reading the file");

let tests = serde_json::from_str::<Vec<TestData>>(contents.as_str())
.expect("JSON deserialization error");

for data in tests {
let roots = data
.roots
.iter()
.map(|hash| sha256::Hash::from_str(hash).unwrap())
.collect();
let stump = Stump {
leafs: data.leaves,
roots,
};

let utxos = data
.additional_preimages
.iter()
.map(|preimage| hash_from_u8(*preimage as u8))
.collect();
let del_hashes = data
.del_hashes
.iter()
.map(|hash| sha256::Hash::from_str(hash).unwrap())
.collect();
let proof_hashes = data
.proof_hashes
.iter()
.map(|hash| sha256::Hash::from_str(hash).unwrap())
.collect();
let proof = Proof::new(data.proof_targets, proof_hashes);
let (_, updated) = stump.modify(&utxos, &del_hashes, &proof).unwrap();
// Positions returned after addition
let new_add_hash: Vec<_> = data
.new_add_hash
.iter()
.map(|hash| sha256::Hash::from_str(hash).unwrap())
.collect();
let new_add: Vec<_> = data
.new_add_pos
.into_iter()
.zip(new_add_hash.into_iter())
.collect();

assert_eq!(updated.prev_num_leaves, data.leaves);
assert_eq!(updated.to_destroy, data.to_destroy);
assert_eq!(updated.new_add, new_add);

}
}
#[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, 6];

let hashes: Vec<_> = vec![
"6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d",
"4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a",
"dbc1b4c900ffe48d575b5da5c638040125f65db0fe3e24494b76ea986457d986",
"084fed08b978af4d7d196a7446a86b58009e636b611db16211b65a9aadff29c5",
"02242b37d8e851f1e86f46790298c7097df06893d6226b7c1453c213e91717de",
"9576f4ade6e9bc3a6458b506ce3e4e890df29cb14cb5d3d887672aef55647a2b",
"df46b17be5f66f0750a4b3efa26d4679db170a72d41eb56c3e4ff75a58c65386",
]
.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 @@ -213,10 +349,10 @@ mod test {
.map(|hash| sha256::Hash::from_str(hash.as_str()).expect("Test case hashes are valid"))
.collect::<Vec<sha256::Hash>>();

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 @@ -231,7 +367,7 @@ mod test {
.map(|value| hash_from_u8(*value))
.collect();

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

Expand All @@ -241,7 +377,6 @@ mod test {
assert_eq!(roots[i].as_str(), s.roots[i].to_hex());
}
}

#[test]
fn test_undo() {
let mut hashes = vec![];
Expand All @@ -253,7 +388,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 All @@ -272,7 +408,6 @@ mod test {

assert!(s_new == s_old_copy);
}

#[test]
fn run_test_cases() {
#[derive(Deserialize)]
Expand Down
Loading

0 comments on commit 360e7ab

Please sign in to comment.