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::io::{self, Read, Write};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||||
use pairing::bls12_381::{Bls12};
|
use pairing::bls12_381::{Bls12};
|
||||||
@@ -26,6 +28,24 @@ use zcash_primitives::{
|
|||||||
use zcash_primitives::zip32::ExtendedSpendingKey;
|
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 struct BlockData {
|
||||||
pub height: i32,
|
pub height: i32,
|
||||||
pub hash: BlockHash,
|
pub hash: BlockHash,
|
||||||
@@ -147,7 +167,7 @@ impl SaplingNoteData {
|
|||||||
|
|
||||||
let account = reader.read_u64::<LittleEndian>()? as usize;
|
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];
|
let mut diversifier_bytes = [0u8; 11];
|
||||||
reader.read_exact(&mut diversifier_bytes)?;
|
reader.read_exact(&mut diversifier_bytes)?;
|
||||||
|
|||||||
Reference in New Issue
Block a user