perf(lite-backend): borrow embedded Sapling params instead of copying
Every LightClient copied the Sapling spend and output parameters, which rust-embed already compiles into the binary, onto the heap: 51,551,256 bytes (49.2 MiB) of private memory per live client, whether or not it ever sent anything. LightClient::sapling_output and sapling_spend are now Cow<'static, [u8]> and borrow the embedded image. set_sapling_params still stores an owned copy, with its is-empty guard, hash checks and disk write unchanged. The prover receives the same bytes. Behaviour changes: - about 49 MiB less anonymous memory per live LightClient; the wallet file and JSON are unchanged; - the first send or shield pages the 51.5 MB of embedded params in from the binary, where client creation used to copy them (an estimated 0.3-1 s from the AppImage); - the public fields sapling_output and sapling_spend change type from Vec<u8> to Cow<'static, [u8]>: a Rust API change, with no C ABI or exported-symbol change. tests/sapling_params.rs checks that a restored, a new and a reloaded client all borrow the embedded image (same pointer, pinned lengths and SHA-256), that set_sapling_params keeps its guard and hash checks, and that the borrowed params parse like a heap copy. An ignored test measures 576 KiB of RssAnon growth for a second client, against 46.5 MiB with the old heap copy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+17
-16
@@ -2,6 +2,7 @@
|
||||
// Released under the GPLv3
|
||||
use crate::lightwallet::LightWallet;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::sync::{Arc, RwLock, Mutex, mpsc::channel};
|
||||
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicUsize, Ordering};
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -302,8 +303,8 @@ pub struct LightClient {
|
||||
pub config : LightClientConfig,
|
||||
|
||||
// zcash-params
|
||||
pub sapling_output : Vec<u8>,
|
||||
pub sapling_spend : Vec<u8>,
|
||||
pub sapling_output : Cow<'static, [u8]>,
|
||||
pub sapling_spend : Cow<'static, [u8]>,
|
||||
|
||||
sync_lock : Mutex<()>,
|
||||
sync_status : Arc<RwLock<WalletStatus>>, // The current syncing status of the Wallet.
|
||||
@@ -338,8 +339,8 @@ impl LightClient {
|
||||
fn read_sapling_params(&mut self) {
|
||||
// Read Sapling Params
|
||||
use crate::SaplingParams;
|
||||
self.sapling_output.extend_from_slice(SaplingParams::get("sapling-output.params").unwrap().as_ref());
|
||||
self.sapling_spend.extend_from_slice(SaplingParams::get("sapling-spend.params").unwrap().as_ref());
|
||||
self.sapling_output = SaplingParams::get("sapling-output.params").unwrap();
|
||||
self.sapling_spend = SaplingParams::get("sapling-spend.params").unwrap();
|
||||
}
|
||||
pub fn set_sapling_params(&mut self, sapling_output: &[u8], sapling_spend: &[u8]) -> Result<(), String> {
|
||||
use sha2::{Sha256, Digest};
|
||||
@@ -354,10 +355,10 @@ impl LightClient {
|
||||
}
|
||||
// Will not overwrite previous params
|
||||
if self.sapling_output.is_empty() {
|
||||
self.sapling_output.extend_from_slice(sapling_output);
|
||||
self.sapling_output = Cow::Owned(sapling_output.to_vec());
|
||||
}
|
||||
if self.sapling_spend.is_empty() {
|
||||
self.sapling_spend.extend_from_slice(sapling_spend);
|
||||
self.sapling_spend = Cow::Owned(sapling_spend.to_vec());
|
||||
}
|
||||
|
||||
// Ensure that the sapling params are stored on disk properly as well.
|
||||
@@ -389,8 +390,8 @@ impl LightClient {
|
||||
let mut l = LightClient {
|
||||
wallet : Arc::new(RwLock::new(LightWallet::new(Some(seed_phrase), &config, 0,0 )?)),
|
||||
config : config.clone(),
|
||||
sapling_output : vec![],
|
||||
sapling_spend : vec![],
|
||||
sapling_output : Cow::Borrowed(&[]),
|
||||
sapling_spend : Cow::Borrowed(&[]),
|
||||
sync_lock : Mutex::new(()),
|
||||
sync_status : Arc::new(RwLock::new(WalletStatus::new())),
|
||||
shutdown_flag : Arc::new(AtomicBool::new(false)),
|
||||
@@ -418,8 +419,8 @@ impl LightClient {
|
||||
let mut l = LightClient {
|
||||
wallet : Arc::new(RwLock::new(LightWallet::new(None, config, latest_block, 0)?)),
|
||||
config : config.clone(),
|
||||
sapling_output : vec![],
|
||||
sapling_spend : vec![],
|
||||
sapling_output : Cow::Borrowed(&[]),
|
||||
sapling_spend : Cow::Borrowed(&[]),
|
||||
sync_lock : Mutex::new(()),
|
||||
sync_status : Arc::new(RwLock::new(WalletStatus::new())),
|
||||
shutdown_flag : Arc::new(AtomicBool::new(false)),
|
||||
@@ -446,8 +447,8 @@ impl LightClient {
|
||||
let mut l = LightClient {
|
||||
wallet : Arc::new(RwLock::new(LightWallet::new(Some(seed_phrase), config, birthday, number)?)),
|
||||
config : config.clone(),
|
||||
sapling_output : vec![],
|
||||
sapling_spend : vec![],
|
||||
sapling_output : Cow::Borrowed(&[]),
|
||||
sapling_spend : Cow::Borrowed(&[]),
|
||||
sync_lock : Mutex::new(()),
|
||||
sync_status : Arc::new(RwLock::new(WalletStatus::new())),
|
||||
shutdown_flag : Arc::new(AtomicBool::new(false)),
|
||||
@@ -503,8 +504,8 @@ impl LightClient {
|
||||
let mut lc = LightClient {
|
||||
wallet : Arc::new(RwLock::new(wallet)),
|
||||
config : config.clone(),
|
||||
sapling_output : vec![],
|
||||
sapling_spend : vec![],
|
||||
sapling_output : Cow::Borrowed(&[]),
|
||||
sapling_spend : Cow::Borrowed(&[]),
|
||||
sync_lock : Mutex::new(()),
|
||||
sync_status : Arc::new(RwLock::new(WalletStatus::new())),
|
||||
shutdown_flag : Arc::new(AtomicBool::new(false)),
|
||||
@@ -1837,8 +1838,8 @@ pub mod tests {
|
||||
let lc = LightClient {
|
||||
wallet : Arc::new(RwLock::new(LightWallet::new(None, &config, 0, 0).unwrap())),
|
||||
config : config,
|
||||
sapling_output : vec![],
|
||||
sapling_spend : vec![],
|
||||
sapling_output : std::borrow::Cow::Borrowed(&[]),
|
||||
sapling_spend : std::borrow::Cow::Borrowed(&[]),
|
||||
sync_lock : Mutex::new(()),
|
||||
sync_status : Arc::new(RwLock::new(WalletStatus::new())),
|
||||
shutdown_flag : Arc::new(AtomicBool::new(false)),
|
||||
|
||||
Reference in New Issue
Block a user