From 7d818ec963bbd4caa00db13c014eb59b4c08f922 Mon Sep 17 00:00:00 2001 From: Davidson Souza Date: Fri, 7 Oct 2022 13:44:10 -0300 Subject: [PATCH] initial impl of UpdateData 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. --- src/accumulator/proof.rs | 5 +- src/accumulator/stump.rs | 239 +++++++++++++++++++++++++++-------- src/accumulator/util.rs | 63 ++++++++- test_values/cache_tests.json | 142 +++++++++++++++++++++ 4 files changed, 393 insertions(+), 56 deletions(-) create mode 100644 test_values/cache_tests.json diff --git a/src/accumulator/proof.rs b/src/accumulator/proof.rs index e08dada..34e6525 100644 --- a/src/accumulator/proof.rs +++ b/src/accumulator/proof.rs @@ -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")); ///``` @@ -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]]; diff --git a/src/accumulator/stump.rs b/src/accumulator/stump.rs index d5f8c68..0753523 100644 --- a/src/accumulator/stump.rs +++ b/src/accumulator/stump.rs @@ -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, } +#[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, + /// 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, del_hashes: &Vec, proof: &Proof, - ) -> Result { + ) -> 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, proof: &Proof, - ) -> Result, String> { + ) -> Result<(Vec<(u64, sha256::Hash)>, Vec), 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, utxos: &Vec, - mut leafs: u64, - ) -> Vec { - for i in utxos.iter() { - Stump::add_single(&mut roots, *i, leafs); - leafs += 1; - } + mut leaves: u64, + ) -> (Vec, Vec<(u64, sha256::Hash)>, Vec) { + 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, - 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, + leaves: u64, + /// New data to add + additional_preimages: Vec, + /// The hash of all targets to be deleted + del_hashes: Vec, + /// The hashes that are used to recompute a given Merkle path to the root + proof_hashes: Vec, + /// Which nodes are being proven, in this case, they'll be deleted + proof_targets: Vec, + /// Here are the expected values: + /// During addition, we create those nodes + new_add_pos: Vec, + new_add_hash: Vec, + + to_destroy: Vec, + } + 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::>(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(); @@ -213,10 +349,10 @@ mod test { .map(|hash| sha256::Hash::from_str(hash.as_str()).expect("Test case hashes are valid")) .collect::>(); - 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)] diff --git a/src/accumulator/util.rs b/src/accumulator/util.rs index 55eda44..c5daca3 100644 --- a/src/accumulator/util.rs +++ b/src/accumulator/util.rs @@ -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 { @@ -72,6 +74,44 @@ fn add_and_sort(mut vec: Vec, value: u64) -> Vec { pub fn is_left_niece(position: u64) -> bool { position & 1 == 0 } +pub fn left_sibling(position: u64) -> u64 { + (position | 1) ^ 1 +} +// roots_to_destroy returns the empty roots that get written over after num_adds +// amount of leaves have been added. +pub fn roots_to_destroy( + num_adds: u64, + mut num_leaves: u64, + orig_roots: &Vec, +) -> Vec { + 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, @@ -347,7 +387,11 @@ pub fn get_proof_positions(targets: &Vec, 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); @@ -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 diff --git a/test_values/cache_tests.json b/test_values/cache_tests.json new file mode 100644 index 0000000..ba7586a --- /dev/null +++ b/test_values/cache_tests.json @@ -0,0 +1,142 @@ +[ + { + "initial_stump": "Add no leaf", + "roots": [], + "leaves": 0, + "additional_preimages": [0, 1, 2, 3], + "del_hashes": [], + "proof_hashes": [], + "proof_targets": [], + "new_add_pos": [0, 1, 2, 3, 4, 5, 6], + "new_add_hash": [ + "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d", + "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a", + "dbc1b4c900ffe48d575b5da5c638040125f65db0fe3e24494b76ea986457d986", + "084fed08b978af4d7d196a7446a86b58009e636b611db16211b65a9aadff29c5", + "02242b37d8e851f1e86f46790298c7097df06893d6226b7c1453c213e91717de", + "9576f4ade6e9bc3a6458b506ce3e4e890df29cb14cb5d3d887672aef55647a2b", + "df46b17be5f66f0750a4b3efa26d4679db170a72d41eb56c3e4ff75a58c65386" + ], + "to_destroy": [] + }, + { + "initial_stump": "Add six leaves [0, 1, 2, 3, 4, 5] and remove them", + "roots": [ + "df46b17be5f66f0750a4b3efa26d4679db170a72d41eb56c3e4ff75a58c65386", + "9eec588c41d87b16b0ee226cb38da3864f9537632321d8be855a73d5616dcc73" + ], + "leaves": 6, + "additional_preimages": [0, 1, 2, 3, 4], + "del_hashes": [], + "proof_hashes": [], + "proof_targets": [], + "new_add_pos": [6, 7, 8, 9, 10, 18, 19, 20, 24, 25, 28], + "new_add_hash": [ + "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d", + "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a", + "dbc1b4c900ffe48d575b5da5c638040125f65db0fe3e24494b76ea986457d986", + "084fed08b978af4d7d196a7446a86b58009e636b611db16211b65a9aadff29c5", + "e52d9c508c502347344d8c07ad91cbd6068afc75ff6292f062a09ca381c89e71", + "9eec588c41d87b16b0ee226cb38da3864f9537632321d8be855a73d5616dcc73", + "02242b37d8e851f1e86f46790298c7097df06893d6226b7c1453c213e91717de", + "9576f4ade6e9bc3a6458b506ce3e4e890df29cb14cb5d3d887672aef55647a2b", + "df46b17be5f66f0750a4b3efa26d4679db170a72d41eb56c3e4ff75a58c65386", + "df12e87db0413ef51d5bbd374904eeeb5cc4615ddae80e45a7140983f30cd01c", + "b6b70236289cd59beebc247e8045f2c1293996ad881e756b79f01e275bc98fa1" + ], + "to_destroy": [] + }, + { + "initial_stump": "Add four leaves [0, 1, 2, 3] and remove them", + "roots": [ + "0000000000000000000000000000000000000000000000000000000000000000" + ], + "leaves": 4, + "additional_preimages": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + "del_hashes": [], + "proof_hashes": [], + "proof_targets": [], + "new_add_pos": [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 52, 56, 57, 60], + "new_add_hash": [ + "e52d9c508c502347344d8c07ad91cbd6068afc75ff6292f062a09ca381c89e71", + "e77b9a9ae9e30b0dbdb6f510a264ef9de781501d7b6b92ae89eb059c5ab743db", + "67586e98fad27da0b9968bc039a1ef34c939b9b8e523a8bef89d478608c5ecf6", + "ca358758f6d27e6cf45272937977a748fd88391db679ceda7dc7bf1f005ee879", + "beead77994cf573341ec17b58bbf7eb34d2711c993c1d976b128b3188dc1829a", + "2b4c342f5433ebe591a1da77e013d1b72475562d48578dca8b84bac6651c3cb9", + "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", + "e7cf46a078fed4fafd0b5e3aff144802b853f8ae459a4f0c14add3314b7cc3a6", + "ef6cbd2161eaea7943ce8693b9824d23d1793ffb1c0fca05b600d3899b44c977", + "9d1e0e2d9459d06523ad13e28a4093c2316baafe7aec5b25f30eba2e113599c4", + "4d7b3ef7300acf70c892d8327db8272f54434adbc61a4e130a563cb59a0d0f47", + "dc0e9c3658a1a3ed1ec94274d8b19925c93e1abb7ddba294923ad9bde30f8cb8", + "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d", + "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a", + "dbc1b4c900ffe48d575b5da5c638040125f65db0fe3e24494b76ea986457d986", + "084fed08b978af4d7d196a7446a86b58009e636b611db16211b65a9aadff29c5", + "9eec588c41d87b16b0ee226cb38da3864f9537632321d8be855a73d5616dcc73", + "34028bbc87000c39476cdc60cf80ca32d579b3a0e2d3f80e0ad8c3739a01aa91", + "cd9c77062a338e63a63ca623db438cb8676f15466641079ee61ec2dda98de796", + "c413035120e8c9b0ca3e40c93d06fe60a0d056866138300bb1f1dd172b4923c3", + "55d0a0ef8f5c25a9da266b36c0c5f4b31008ece82df2512c8966bddcc27a66a0", + "b43b92244de0b4c6ea8b03017286927bb4389bd9440c1839ff3234c24da776a7", + "02242b37d8e851f1e86f46790298c7097df06893d6226b7c1453c213e91717de", + "9576f4ade6e9bc3a6458b506ce3e4e890df29cb14cb5d3d887672aef55647a2b", + "29590a14c1b09384b94a2c0e94bf821ca75b62eacebc47893397ca88e3bbcbd7", + "9c053db406c1a077112189469a3aca0573d3481bef09fa3d2eda3304d7d44be8", + "e799acb98a071c4884707e4bc8c093ba22571c8d84cc0223ab0c2c9327313a5b", + "df46b17be5f66f0750a4b3efa26d4679db170a72d41eb56c3e4ff75a58c65386", + "4fa0cac492b54bd78886ad14002aa71ca9c038cc1bea035bc27f67803031f0bc", + "b50340a8c59e66c01d81923f4f834a07966866d2cdfdfba87204a7532f3368c5" + ], + "to_destroy": [48] + } , + { + "initial_stump": "Add four leaves [0, 1, 2, 3] and remove them", + "roots": [ + "0000000000000000000000000000000000000000000000000000000000000000" + ], + "leaves": 4, + "additional_preimages": [0, 1, 2, 3, 4], + "del_hashes": [], + "proof_hashes": [], + "proof_targets": [], + "new_add_pos": [8, 16, 17, 18, 19, 24, 25, 28], + "new_add_hash": [ + "e52d9c508c502347344d8c07ad91cbd6068afc75ff6292f062a09ca381c89e71", + "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d", + "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a", + "dbc1b4c900ffe48d575b5da5c638040125f65db0fe3e24494b76ea986457d986", + "084fed08b978af4d7d196a7446a86b58009e636b611db16211b65a9aadff29c5", + "02242b37d8e851f1e86f46790298c7097df06893d6226b7c1453c213e91717de", + "9576f4ade6e9bc3a6458b506ce3e4e890df29cb14cb5d3d887672aef55647a2b", + "df46b17be5f66f0750a4b3efa26d4679db170a72d41eb56c3e4ff75a58c65386" + ], + "to_destroy": [24] + }, + { + "initial_stump":"Add six leaves [0, 1, 2, 3, 4, 5] and remove [0, 1, 2, 3]", + "roots": [ + "0000000000000000000000000000000000000000000000000000000000000000", + "9eec588c41d87b16b0ee226cb38da3864f9537632321d8be855a73d5616dcc73" + ], + "leaves": 6, + "additional_preimages": [0, 1, 2, 3, 4], + "del_hashes": [], + "proof_hashes": [], + "proof_targets": [], + "new_add_pos": [8, 9, 10, 18, 19, 20, 24, 25, 28], + "new_add_hash": [ + "dbc1b4c900ffe48d575b5da5c638040125f65db0fe3e24494b76ea986457d986", + "084fed08b978af4d7d196a7446a86b58009e636b611db16211b65a9aadff29c5", + "e52d9c508c502347344d8c07ad91cbd6068afc75ff6292f062a09ca381c89e71", + "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d", + "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a", + "9576f4ade6e9bc3a6458b506ce3e4e890df29cb14cb5d3d887672aef55647a2b", + "9eec588c41d87b16b0ee226cb38da3864f9537632321d8be855a73d5616dcc73", + "02242b37d8e851f1e86f46790298c7097df06893d6226b7c1453c213e91717de", + "df12e87db0413ef51d5bbd374904eeeb5cc4615ddae80e45a7140983f30cd01c" + ], + "to_destroy": [24] + } +] \ No newline at end of file