Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

initial impl of UpdateData #13

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the go one I had a vector of subtrees because you could avoid the sort & dedupe here.

Maybe in rust those operations aren't as slow but just noting here for future reference.

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",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just leaving a note:

The go library currently also returns the roots as well so if we don't return them here, the behaviors won't be the same.

Not sure if returning the root or not returning the root is better.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean the current set of roots?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not the current set of roots. I mean the roots that were generated by the additions.

So here, the final root after adding all 6 elements was df46b17be5f66f0750a4b3efa26d4679db170a72d41eb56c3e4ff75a58c65386.

In the go implementation, this root would also be returned.

"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