-
Notifications
You must be signed in to change notification settings - Fork 60
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
[commonware-storage/mmr/mem] API extensions to support generic proof generation + forgetting tweaks #456
Conversation
roberto-bayardo
commented
Feb 5, 2025
•
edited
Loading
edited
- add ability to initialize an mmr from a vector of nodes
- add ability to request individual nodes
- made proof generation dependent only on a simple Mmr trait instead of a specific implementation
- clarified some of the pruning documentation
- fixed edge case bug in ensuring position validity for proof generation after pruning
- removed ability to forget to arbitrary point in history since it doesn't properly detect how proving is affected by it. will re-work forgetting in a subsequent PR (tracking issue: [commonware-storage/mmr] Improve forgetting behavior in mem::Mmr #459)
e61a966
to
83afb8a
Compare
83afb8a
to
1bb824b
Compare
@@ -338,6 +353,12 @@ mod tests { | |||
"forget_max should forget to right-most leaf of leftmost peak" | |||
); | |||
assert_eq!(mmr.oldest_remembered_node_pos(), 11); | |||
|
|||
// After forgetting we shouldn't be able to prove elements at or before the oldest remaining. |
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.
Thanks for adding tests for this 🙏
5414a21
to
d01638e
Compare
@@ -1,5 +1,5 @@ | |||
use commonware_cryptography::{Hasher, Sha256}; | |||
use commonware_storage::mmr::mem::Mmr; | |||
use commonware_storage::mmr::{mem::Mmr, verification::VerifiableMmr}; |
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.
is there a non-verifiable MMR in verification?
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.
nit: I would call this verification::Storage
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.
Renamed "Storage"
@@ -1,5 +1,5 @@ | |||
use commonware_cryptography::{Hasher, Sha256}; | |||
use commonware_storage::mmr::mem::Mmr; | |||
use commonware_storage::mmr::{mem::Mmr, verification::VerifiableMmr}; |
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.
nit: I wonder if there is a clean way to put the in-memory MMR at mmr::Memory
(mmr::mem::Mmr
seems suboptimal)
pub fn oldest_required_element(&self) -> u64 { | ||
match self.peak_iterator().next() { | ||
None => { | ||
// Degenerate case, only happens when MMR is empty. | ||
0 | ||
} | ||
Some((pos, height)) => { | ||
// Rightmost leaf position in a tree is the position of its peak minus its height. | ||
pos - height as u64 |
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.
Was this a bug?
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.
not a bug technically in that nothing was broken, but it was just being more conservative than necessary.
- add ability to request individual nodes - clarified some of the pruning documentation - fixed edge case bug in ensuring position validity for proof generation after pruning
de81ef2
to
706e6de
Compare
storage/src/mmr/mem.rs
Outdated
@@ -1,10 +1,10 @@ | |||
//! A basic MMR where all nodes are stored in-memory. | |||
|
|||
use crate::mmr::hasher::Hasher; | |||
use crate::mmr::iterator::{nodes_needing_parents, PathIterator, PeakIterator}; | |||
use crate::mmr::verification::Proof; | |||
use crate::mmr::iterator::{nodes_needing_parents, PeakIterator}; |
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.
nit: you can do crate::mmr::{}
to avoid these duplicate prefix imports
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.
done
storage/src/mmr/verification.rs
Outdated
/// | ||
/// Assumes the caller has already verified that the MMR still stores all the nodes required to | ||
/// generate a proof the given range. | ||
pub fn range_proof<P: Provable<H>>( |
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.
Cool to see this broken out!
efbf81d
to
f7e38ca
Compare
hash = hasher.node_hash(parent_pos, &self.nodes[sibling_index], &hash); | ||
let parent_pos = self.index_to_pos(self.nodes.len()); | ||
let sibling_hash = &self.nodes[self.pos_to_index(sibling_pos)]; | ||
hash = Hasher::new(&mut self.hasher).node_hash(parent_pos, sibling_hash, &hash); |
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.
To avoid creating a new Hasher
on each iteration, you can do:
diff --git a/storage/src/mmr/mem.rs b/storage/src/mmr/mem.rs
index 78e7c494..245b389d 100644
--- a/storage/src/mmr/mem.rs
+++ b/storage/src/mmr/mem.rs
@@ -38,7 +38,8 @@ impl<H: CHasher> Storage<H> for Mmr<H> {
}
fn get_node(&self, position: u64) -> Option<&H::Digest> {
- self.nodes.get(self.pos_to_index(position))
+ self.nodes
+ .get(Self::pos_to_index(self.oldest_remembered_pos, position))
}
}
@@ -77,29 +78,31 @@ impl<H: CHasher> Mmr<H> {
}
/// Return the position of the element given its index in the current nodes vector.
- fn index_to_pos(&self, index: usize) -> u64 {
- index as u64 + self.oldest_remembered_pos
+ fn index_to_pos(oldest_remembered_pos: u64, index: usize) -> u64 {
+ oldest_remembered_pos + index as u64
}
/// Return the index of the element in the current nodes vector given its position in the MMR.
- fn pos_to_index(&self, pos: u64) -> usize {
- (pos - self.oldest_remembered_pos) as usize
+ fn pos_to_index(oldest_remembered_pos: u64, pos: u64) -> usize {
+ (pos - oldest_remembered_pos) as usize
}
/// Add an element to the MMR and return its position in the MMR.
pub fn add(&mut self, element: &H::Digest) -> u64 {
let peaks = nodes_needing_parents(self.peak_iterator());
- let element_pos = self.index_to_pos(self.nodes.len());
+ let element_pos = Self::index_to_pos(self.oldest_remembered_pos, self.nodes.len());
// Insert the element into the MMR as a leaf.
- let mut hash = Hasher::new(&mut self.hasher).leaf_hash(element_pos, element);
+ let mut hasher = Hasher::new(&mut self.hasher);
+ let mut hash = hasher.leaf_hash(element_pos, element);
self.nodes.push(hash.clone());
// Compute the new parent nodes if any, and insert them into the MMR.
for sibling_pos in peaks.into_iter().rev() {
- let parent_pos = self.index_to_pos(self.nodes.len());
- let sibling_hash = &self.nodes[self.pos_to_index(sibling_pos)];
- hash = Hasher::new(&mut self.hasher).node_hash(parent_pos, sibling_hash, &hash);
+ let parent_pos = Self::index_to_pos(self.oldest_remembered_pos, self.nodes.len());
+ let sibling_hash =
+ &self.nodes[Self::pos_to_index(self.oldest_remembered_pos, sibling_pos)];
+ hash = hasher.node_hash(parent_pos, sibling_hash, &hash);
self.nodes.push(hash.clone());
}
element_pos
@@ -158,7 +161,7 @@ impl<H: CHasher> Mmr<H> {
}
fn forget_to_pos(&mut self, pos: u64) {
- let nodes_to_remove = self.pos_to_index(pos);
+ let nodes_to_remove = Self::pos_to_index(self.oldest_remembered_pos, pos);
self.oldest_remembered_pos = pos;
self.nodes = self.nodes[nodes_to_remove..self.nodes.len()].to_vec();
}
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.
I'm not sure I "love" this per se (and think creating a new hasher at the start of any call is reasonable) but if you don't want to do that, this should be more efficient.
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.
Hmm, I think this dilutes the value of the "pos_to_index" abstraction. I suspect the compiler will optimize away the Hasher::new overhead so I think I prefer leaving it as is.
825d5e1
to
f716691
Compare
storage/src/mmr/#mem.rs#
Outdated
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.
I think you need to remove this?
f716691
to
6dfd069
Compare
…c implementations.
6dfd069
to
e572183
Compare
Codecov ReportAttention: Patch coverage is
@@ Coverage Diff @@
## main #456 +/- ##
==========================================
+ Coverage 94.17% 94.28% +0.10%
==========================================
Files 94 95 +1
Lines 27403 27750 +347
==========================================
+ Hits 25808 26163 +355
+ Misses 1595 1587 -8
... and 29 files with indirect coverage changes Continue to review full report in Codecov by Sentry.
|