perf(litebe): memoize per-note viewing-key decode on wallet open
Opening a wallet reads each shielded note from disk, and every note record embeds a 169-byte extended full viewing key that was decoded from scratch for every note. That decode is an elliptic-curve parse; a wallet with many notes sharing a handful of keys paid for it once per note, which dominated open time. - Note reads now go through a small content-addressed cache keyed on the exact 169 raw key bytes. The first note with a given key decodes it; the rest reuse the result. Because the cache is keyed on the raw bytes and the decoder is deterministic, the cached key is identical to a fresh parse. - The cache is thread-local and naturally bounded by the few distinct keys a wallet holds, so it adds no cross-thread state and cannot grow without bound. The bytes on disk are read exactly as before - this only avoids re-parsing identical bytes - so the loaded wallet is byte-identical, verified against the differential harness. The wallet file format is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+21
-1
@@ -1,5 +1,7 @@
|
||||
use std::io::{self, Read, Write};
|
||||
use std::sync::Arc;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
use pairing::bls12_381::{Bls12};
|
||||
@@ -26,6 +28,24 @@ use zcash_primitives::{
|
||||
use zcash_primitives::zip32::ExtendedSpendingKey;
|
||||
|
||||
|
||||
// C241: wallet open re-parsed the 169-byte ExtendedFullViewingKey once PER NOTE. Memoize the decode,
|
||||
// keyed by the exact 169 raw bytes (content-addressed, so the result is always identical to a fresh
|
||||
// parse). Thread-local and naturally bounded by the handful of distinct keys a wallet holds.
|
||||
thread_local! {
|
||||
static EFVK_CACHE: RefCell<HashMap<[u8; 169], ExtendedFullViewingKey>> = RefCell::new(HashMap::new());
|
||||
}
|
||||
|
||||
fn read_efvk_cached<R: Read>(r: &mut R) -> io::Result<ExtendedFullViewingKey> {
|
||||
let mut raw = [0u8; 169];
|
||||
r.read_exact(&mut raw)?;
|
||||
if let Some(k) = EFVK_CACHE.with(|c| c.borrow().get(&raw).cloned()) {
|
||||
return Ok(k);
|
||||
}
|
||||
let k = ExtendedFullViewingKey::read(&raw[..])?; // identical parser on identical bytes
|
||||
EFVK_CACHE.with(|c| c.borrow_mut().insert(raw, k.clone()));
|
||||
Ok(k)
|
||||
}
|
||||
|
||||
pub struct BlockData {
|
||||
pub height: i32,
|
||||
pub hash: BlockHash,
|
||||
@@ -147,7 +167,7 @@ impl SaplingNoteData {
|
||||
|
||||
let account = reader.read_u64::<LittleEndian>()? as usize;
|
||||
|
||||
let extfvk = ExtendedFullViewingKey::read(&mut reader)?;
|
||||
let extfvk = read_efvk_cached(&mut reader)?;
|
||||
|
||||
let mut diversifier_bytes = [0u8; 11];
|
||||
reader.read_exact(&mut diversifier_bytes)?;
|
||||
|
||||
Reference in New Issue
Block a user