-
Notifications
You must be signed in to change notification settings - Fork 978
Optimize PMMR segments to only include hashes at pruning boundaries #3820
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
base: master
Are you sure you want to change the base?
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 |
|---|---|---|
|
|
@@ -57,7 +57,7 @@ impl Segmenter { | |
| let now = Instant::now(); | ||
| let txhashset = self.txhashset.read(); | ||
| let kernel_pmmr = txhashset.kernel_pmmr_at(&self.header); | ||
| let segment = Segment::from_pmmr(id, &kernel_pmmr, false)?; | ||
| let segment = Segment::from_pmmr(id, &kernel_pmmr, None)?; | ||
| debug!( | ||
| "kernel_segment: id: ({}, {}), leaves: {}, hashes: {}, proof hashes: {}, took {}ms", | ||
| segment.id().height, | ||
|
|
@@ -93,7 +93,7 @@ impl Segmenter { | |
| ) -> Result<(Segment<BitmapChunk>, Hash), Error> { | ||
| let now = Instant::now(); | ||
| let bitmap_pmmr = self.bitmap_snapshot.readonly_pmmr(); | ||
| let segment = Segment::from_pmmr(id, &bitmap_pmmr, false)?; | ||
| let segment = Segment::from_pmmr(id, &bitmap_pmmr, None)?; | ||
| let output_root = self.output_root()?; | ||
| debug!( | ||
| "bitmap_segment: id: ({}, {}), leaves: {}, hashes: {}, proof hashes: {}, took {}ms", | ||
|
|
@@ -113,9 +113,10 @@ impl Segmenter { | |
| id: SegmentIdentifier, | ||
| ) -> Result<(Segment<OutputIdentifier>, Hash), Error> { | ||
| let now = Instant::now(); | ||
| let bitmap = self.bitmap_snapshot.as_bitmap().ok(); | ||
| let txhashset = self.txhashset.read(); | ||
| let output_pmmr = txhashset.output_pmmr_at(&self.header); | ||
| let segment = Segment::from_pmmr(id, &output_pmmr, true)?; | ||
| let segment = Segment::from_pmmr(id, &output_pmmr, bitmap.as_ref())?; | ||
| let bitmap_root = self.bitmap_root()?; | ||
| debug!( | ||
| "output_segment: id: ({}, {}), leaves: {}, hashes: {}, proof hashes: {}, took {}ms", | ||
|
|
@@ -132,9 +133,10 @@ impl Segmenter { | |
| /// Create a rangeproof segment. | ||
| pub fn rangeproof_segment(&self, id: SegmentIdentifier) -> Result<Segment<RangeProof>, Error> { | ||
| let now = Instant::now(); | ||
| let bitmap = self.bitmap_snapshot.as_bitmap().ok(); | ||
|
Contributor
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. Same here, I think bitmap errors should be returned instead of being converted to None. |
||
| let txhashset = self.txhashset.read(); | ||
| let pmmr = txhashset.rangeproof_pmmr_at(&self.header); | ||
| let segment = Segment::from_pmmr(id, &pmmr, true)?; | ||
| let segment = Segment::from_pmmr(id, &pmmr, bitmap.as_ref())?; | ||
| debug!( | ||
| "rangeproof_segment: id: ({}, {}), leaves: {}, hashes: {}, proof hashes: {}, took {}ms", | ||
| segment.id().height, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -307,10 +307,11 @@ where | |
| T: Readable + Writeable + Debug, | ||
| { | ||
| /// Generate a segment from a PMMR | ||
| /// If bitmap is provided, only hashes at pruning boundaries are included. | ||
| pub fn from_pmmr<U, B>( | ||
| segment_id: SegmentIdentifier, | ||
| pmmr: &ReadonlyPMMR<'_, U, B>, | ||
| prunable: bool, | ||
| bitmap: Option<&Bitmap>, | ||
| ) -> Result<Self, SegmentError> | ||
| where | ||
| U: PMMRable<E = T>, | ||
|
|
@@ -331,15 +332,40 @@ where | |
| segment.leaf_data.push(data); | ||
| segment.leaf_pos.push(pos0); | ||
| continue; | ||
| } else if !prunable { | ||
| } else if bitmap.is_none() { | ||
| return Err(SegmentError::MissingLeaf(pos0)); | ||
| } | ||
| } | ||
| // TODO: optimize, no need to send every intermediary hash | ||
| if prunable { | ||
| if let Some(hash) = pmmr.get_from_file(pos0) { | ||
| segment.hashes.push(hash); | ||
| segment.hash_pos.push(pos0); | ||
| if let Some(bm) = bitmap { | ||
| // Only include hash if this subtree is fully pruned | ||
| // AND the sibling subtree is NOT fully pruned (pruning boundary) | ||
| if subtree_fully_pruned(pos0, bm, mmr_size) { | ||
| // Find sibling position | ||
| let height = pmmr::bintree_postorder_height(pos0); | ||
| let subtree_size = (1u64 << (height + 1)) - 1; | ||
| let sibling_pos0 = if pmmr::is_left_sibling(pos0) { | ||
| // Right sibling is at pos0 + size of this subtree | ||
| Some(pos0 + subtree_size) | ||
| } else { | ||
| // Left sibling: go back by sibling's subtree size | ||
| pos0.checked_sub(subtree_size) | ||
| }; | ||
| // Need hash if sibling exists in segment and is not fully pruned | ||
| let need_hash = match sibling_pos0 { | ||
| Some(sib) if sib >= segment_first_pos && sib <= segment_last_pos => { | ||
| !subtree_fully_pruned(sib, bm, mmr_size) | ||
| } | ||
| _ => { | ||
| // Sibling outside segment or underflow - may need hash for proof | ||
| true | ||
| } | ||
| }; | ||
| if need_hash { | ||
|
Contributor
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. If need_hash is true, silently skipping a missing hash can produce a segment that later fails validation with MissingHash or Invalid Root. Should this return an error instead? like:
Contributor
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. This also matches the failure pattern you observed, @ardocrat.
Contributor
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. I'll check this out |
||
| if let Some(hash) = pmmr.get_from_file(pos0) { | ||
| segment.hashes.push(hash); | ||
| segment.hash_pos.push(pos0); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -842,3 +868,14 @@ impl Writeable for SegmentProof { | |
| Ok(()) | ||
| } | ||
| } | ||
|
|
||
| /// Check if a subtree rooted at pos0 is fully pruned (no unspent leaves in bitmap) | ||
| fn subtree_fully_pruned(pos0: u64, bitmap: &Bitmap, mmr_size: u64) -> bool { | ||
| let leftmost = pmmr::bintree_leftmost(pos0); | ||
| let rightmost = pmmr::bintree_rightmost(pos0); | ||
| let n_leaves = pmmr::n_leaves(mmr_size); | ||
| let start_leaf = pmmr::n_leaves(leftmost + 1).saturating_sub(1); | ||
| let end_leaf = min(pmmr::n_leaves(rightmost + 1), n_leaves); | ||
| // If any leaf in range is in bitmap (unspent), subtree is not fully pruned | ||
| bitmap.range_cardinality(start_leaf as u32..end_leaf as u32) == 0 | ||
| } | ||
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.
Can we avoid .ok() here? If building the bitmap fails, I think this should return an error