Skip to content

perf: cache packed proof nonces for cheaper header/block hash#3899

Open
iho wants to merge 1 commit into
mimblewimble:stagingfrom
iho:perf/proof-pack-nonces-cache
Open

perf: cache packed proof nonces for cheaper header/block hash#3899
iho wants to merge 1 commit into
mimblewimble:stagingfrom
iho:perf/proof-pack-nonces-cache

Conversation

@iho

@iho iho commented Jul 9, 2026

Copy link
Copy Markdown

Summary

block.hash() / header.hash() ultimately hash the PoW Proof by bit-packing all nonces on every call. That packing is relatively expensive and runs very often during sync and validation.

This caches the packed form when a Proof is deserialized (the on-wire bytes we already read), so later hash/write uses that buffer instead of re-running pack_bits.

Closes #3372

Approach

  • Add private packed_nonces: Option<Vec<u8>> on Proof (ignored by PartialEq / serde)
  • Readable::read stores the packed bytes alongside extracted nonces
  • Writeable::write prefers the cache; otherwise packs on demand (mining / constructed proofs)
  • Proof::clear_packed_cache() for safe mutation of edge_bits / nonces
  • Stratum share path clears cache after applying a solution
  • Unit tests: round-trip cache, hash equivalence, mutation + clear

Locally constructed proofs (mining templates, tests, genesis) keep None and behave as before.

Test plan

  • cargo test -p grin_core --lib pow::types
  • cargo test -p grin_core --lib genesis
  • cargo check -p grin_servers

block.hash() / header.hash() re-bitpacked Proof nonces on every call.
Keep the on-wire packed form when deserializing a Proof so subsequent
Hash serialization skips pack_bits. Locally built proofs pack on demand;
clear_packed_cache after mutating edge_bits/nonces (stratum share path).

Closes mimblewimble#3372

@wiesche89 wiesche89 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This addresses the main idea of #3372, but not all of its proposed safeguards yet. The cache is populated on read and reused for hashing, but it is also used for full serialization, and the public fields can still be changed without invalidating it. I think those two points should be resolved in this PR before closing the issue. Some before/after numbers would also help confirm the expected benefit.

Comment thread core/src/pow/types.rs
/// **Important:** if you mutate `edge_bits` or `nonces` after construction,
/// call [`Proof::clear_packed_cache`] so a stale cache cannot poison hashes.
#[serde(skip)]
packed_nonces: Option<Vec<u8>>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The cache depends on edge_bits and nonces, but both are still publicly mutable. A missed clear_packed_cache() makes verification and hashing use different proof data. Could we make mutation invalidate the cache automatically in this PR?

Comment thread core/src/pow/types.rs
}
writer.write_fixed_bytes(&self.pack_nonces())
// Prefer the deserialized packed form (hash-hot path); otherwise pack on demand.
if let Some(ref packed) = self.packed_nonces {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could we use the cache only for hash serialization and always repack full wire/disk writes from the fields? That matches #3372 and prevents stale cached bytes from being emitted.

Comment thread core/src/pow/types.rs
let hash_before = proof.hash();
let packed = proof.compute_packed_nonces();

// Simulate a deserialized proof: same nonces, cache filled.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could this use a real serialize/deserialize round trip instead of setting the private cache directly? That would cover the actual read => cache => hash/write path.

Comment thread core/src/pow/types.rs

/// Pack the nonces of the proof to their exact bit size as described above.
/// Uses the deserialized cache when available.
pub fn pack_nonces(&self) -> Vec<u8> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you add before/after numbers for repeated header hashing? The cache keeps and clones an extra allocation per proof, so it would be good to confirm the net win.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants