Skip to content

Commit

Permalink
Merge pull request #13 from Davidson-Souza/feature/update-stump-for-c…
Browse files Browse the repository at this point in the history
…ache

initial impl of UpdateData
  • Loading branch information
kcalvinalvin authored Oct 30, 2022
2 parents be48e70 + a337fa4 commit a2cbce1
Show file tree
Hide file tree
Showing 4 changed files with 393 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 a2cbce1

Please sign in to comment.