-
Notifications
You must be signed in to change notification settings - Fork 19
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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; | ||
|
||
|
@@ -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 | ||
|
@@ -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![]; | ||
|
||
|
@@ -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. | ||
|
@@ -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); | ||
|
@@ -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) | ||
} | ||
} | ||
|
||
|
@@ -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", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mean the current set of roots? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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(); | ||
|
||
|
@@ -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); | ||
} | ||
|
@@ -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"); | ||
|
||
|
@@ -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![]; | ||
|
@@ -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(); | ||
|
||
|
@@ -272,7 +408,6 @@ mod test { | |
|
||
assert!(s_new == s_old_copy); | ||
} | ||
|
||
#[test] | ||
fn run_test_cases() { | ||
#[derive(Deserialize)] | ||
|
There was a problem hiding this comment.
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.