Skip to content

Commit 32afe28

Browse files
committed
rework save so it does not double memory size and also can be updated from within pepper sync
1 parent b79a7de commit 32afe28

File tree

8 files changed

+81
-128
lines changed

8 files changed

+81
-128
lines changed

zingolib/src/commands.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ mod error;
2323
mod utils;
2424

2525
lazy_static! {
26-
static ref RT: Runtime = tokio::runtime::Runtime::new().unwrap();
26+
pub static ref RT: Runtime = tokio::runtime::Runtime::new().unwrap();
2727
}
2828

2929
/// This command interface is used both by cli and also consumers.

zingolib/src/lightclient.rs

Lines changed: 14 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22
33
use json::{array, object, JsonValue};
44
use log::error;
5-
use pepper_sync::{
6-
error::SyncError,
7-
wallet::{SyncMode, SyncResult},
8-
};
5+
use pepper_sync::{error::SyncError, wallet::SyncResult};
96
use serde::Serialize;
107
use std::sync::{atomic::AtomicU8, Arc};
118
use tokio::{
@@ -159,19 +156,6 @@ pub struct AccountBackupInfo {
159156
pub account_index: u32,
160157
}
161158

162-
#[derive(Default)]
163-
struct ZingoSaveBuffer {
164-
pub buffer: Arc<RwLock<Vec<u8>>>,
165-
}
166-
167-
impl ZingoSaveBuffer {
168-
fn new(buffer: Vec<u8>) -> Self {
169-
ZingoSaveBuffer {
170-
buffer: Arc::new(RwLock::new(buffer)),
171-
}
172-
}
173-
}
174-
175159
/// Balances that may be presented to a user in a wallet app.
176160
/// The goal is to present a user-friendly and useful view of what the user has or can soon expect
177161
/// *without* requiring the user to understand the details of the Zcash protocol.
@@ -247,7 +231,6 @@ pub struct LightClient {
247231
sync_mode: Arc<AtomicU8>,
248232
sync_handle: Option<JoinHandle<Result<SyncResult, SyncError>>>,
249233
latest_proposal: Arc<RwLock<Option<ZingoProposal>>>, // TODO: move to wallet
250-
save_buffer: ZingoSaveBuffer, // TODO: move save buffer to wallet itself?
251234
}
252235

253236
/// all the wonderfully intertwined ways to conjure a LightClient
@@ -266,7 +249,7 @@ pub mod instantiation {
266249

267250
use crate::config::ZingoConfig;
268251

269-
use super::{LightClient, ZingoSaveBuffer};
252+
use super::LightClient;
270253
use crate::wallet::{LightWallet, WalletBase};
271254

272255
impl LightClient {
@@ -285,7 +268,6 @@ pub mod instantiation {
285268
sync_mode: Arc::new(AtomicU8::new(SyncMode::NotRunning as u8)),
286269
sync_handle: None,
287270
latest_proposal: Arc::new(RwLock::new(None)),
288-
save_buffer: ZingoSaveBuffer::new(buffer),
289271
})
290272
}
291273

@@ -324,7 +306,7 @@ pub mod instantiation {
324306
));
325307
}
326308
}
327-
let mut lightclient = LightClient::create_from_wallet_async(
309+
let lightclient = LightClient::create_from_wallet_async(
328310
config.clone(),
329311
LightWallet::new(
330312
config.chain,
@@ -337,10 +319,7 @@ pub mod instantiation {
337319
)
338320
.await?;
339321

340-
lightclient
341-
.save_internal_rust()
342-
.await
343-
.map_err(|e| Error::new(ErrorKind::InvalidData, e))?;
322+
lightclient.wallet.lock().await.save_required = true;
344323

345324
debug!("Created new wallet!");
346325

@@ -370,19 +349,16 @@ pub mod instantiation {
370349

371350
fn create_with_new_wallet(config: &ZingoConfig, height: u64) -> io::Result<Self> {
372351
Runtime::new().unwrap().block_on(async move {
373-
let mut l =
352+
let lightclient =
374353
LightClient::create_unconnected(config, WalletBase::FreshEntropy, height)
375354
.await?;
376355

377356
debug!("Created new wallet with a new seed!");
378357
debug!("Created LightClient to {}", &config.get_lightwalletd_uri());
379358

380-
// Save
381-
l.save_internal_rust()
382-
.await
383-
.map_err(|s| io::Error::new(ErrorKind::PermissionDenied, s))?;
359+
lightclient.wallet.lock().await.save_required = true;
384360

385-
Ok(l)
361+
Ok(lightclient)
386362
})
387363
}
388364

@@ -476,17 +452,13 @@ impl LightClient {
476452
transparent: addr_type.contains('t'),
477453
};
478454

479-
let new_address = self
480-
.wallet
481-
.lock()
482-
.await
483-
.generate_unified_address(desired_receivers)
484-
.map_err(|e| e.to_string())?;
485-
486-
// FIXME: zingo2, rework wallet save to save while the wallet guard is acquired
487-
if SyncMode::from_atomic_u8(self.sync_mode.clone()) == SyncMode::NotRunning {
488-
self.save_internal_rust().await?;
489-
}
455+
let new_address = {
456+
let mut wallet = self.wallet.lock().await;
457+
wallet.save_required = true;
458+
wallet
459+
.generate_unified_address(desired_receivers)
460+
.map_err(|e| e.to_string())?
461+
};
490462

491463
Ok(array![new_address.encode(&self.config.chain)])
492464
}

zingolib/src/lightclient/save.rs

Lines changed: 32 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -5,90 +5,51 @@ use log::error;
55
use std::{
66
fs::{remove_file, File},
77
io::Write,
8-
path::{Path, PathBuf},
8+
path::PathBuf,
99
};
10-
use tokio::runtime::Runtime;
1110

1211
use super::LightClient;
13-
use crate::error::{ZingoLibError, ZingoLibResult};
12+
use crate::{error::ZingoLibError, wallet::LightWallet};
1413

1514
impl LightClient {
16-
// SAVE METHODS
17-
18-
/// Called internally at sync checkpoints to save state. Should not be called midway through sync.
19-
pub(super) async fn save_internal_rust(&mut self) -> ZingoLibResult<bool> {
20-
match self.save_internal_buffer().await {
21-
Ok(_vu8) => {
22-
// Save_internal_buffer ran without error. At this point, we assume that the save buffer is good to go. Depending on operating system, we may be able to write it to disk. (Otherwise, we wait for the FFI to offer save export.
23-
24-
#[cfg(not(any(target_os = "ios", target_os = "android")))]
25-
{
26-
self.rust_write_save_buffer_to_file().await?;
27-
Ok(true)
28-
}
29-
#[cfg(any(target_os = "ios", target_os = "android"))]
30-
{
31-
Ok(false)
32-
}
33-
}
34-
Err(err) => {
35-
error!("{}", err);
36-
Err(err)
37-
}
38-
}
39-
}
40-
41-
/// write down the state of the lightclient as a `Vec<u8>`
42-
pub async fn save_internal_buffer(&mut self) -> ZingoLibResult<Vec<u8>> {
15+
/// If the wallet state has changed since last save, serializes the wallet and returns the wallet bytes.
16+
/// For OSs that are not iOS and Android, also persists the wallet bytes to file.
17+
/// Returns `Ok(None)` if the wallet state has not changed and save is not required.
18+
/// Returns error if serialization or persistance fails.
19+
///
20+
/// Intended to be called from a save task which calls `save` repeatedly.
21+
// FIXME: zingo-cli needs a save task
22+
pub async fn save(&self, wallet: &mut LightWallet) -> std::io::Result<Option<Vec<u8>>> {
4323
let mut buffer: Vec<u8> = vec![];
44-
let mut wallet = self.wallet.lock().await;
45-
let network = wallet.network;
46-
wallet
47-
.write(&mut buffer, &network)
48-
.await
49-
.map_err(ZingoLibError::InternalWriteBufferError)?;
50-
(self.save_buffer.buffer.write().await).clone_from(&buffer);
51-
Ok(buffer)
52-
}
53-
54-
#[cfg(not(any(target_os = "ios", target_os = "android")))]
55-
/// If possible, write to disk.
56-
async fn rust_write_save_buffer_to_file(&self) -> ZingoLibResult<()> {
57-
{
58-
let read_buffer = self.save_buffer.buffer.read().await;
59-
if !read_buffer.is_empty() {
60-
LightClient::write_to_file(self.config.get_wallet_path(), &read_buffer)
61-
.map_err(ZingoLibError::WriteFileError)?;
62-
Ok(())
63-
} else {
64-
ZingoLibError::EmptySaveBuffer.handle()
65-
}
24+
if wallet.save_required {
25+
let network = wallet.network;
26+
wallet.write(&mut buffer, &network).await?;
27+
wallet.save_required = false;
6628
}
67-
}
6829

69-
#[cfg(not(any(target_os = "ios", target_os = "android")))]
70-
fn write_to_file(path: Box<Path>, buffer: &[u8]) -> std::io::Result<()> {
71-
let mut file = File::create(path)?;
72-
file.write_all(buffer)?;
73-
Ok(())
74-
}
30+
#[cfg(not(any(target_os = "ios", target_os = "android")))]
31+
if !buffer.is_empty() {
32+
let mut file = File::create(self.config.get_wallet_path())?;
33+
file.write_all(&buffer)?;
34+
}
7535

76-
/// TODO: Add Doc Comment Here!
77-
pub async fn export_save_buffer_async(&self) -> ZingoLibResult<Vec<u8>> {
78-
let read_buffer = self.save_buffer.buffer.read().await;
79-
if !read_buffer.is_empty() {
80-
Ok(read_buffer.clone())
36+
if buffer.is_empty() {
37+
Ok(None)
8138
} else {
82-
ZingoLibError::EmptySaveBuffer.handle()
39+
Ok(Some(buffer))
8340
}
8441
}
8542

86-
/// This function is the sole correct way to ask LightClient to save.
87-
pub fn export_save_buffer_runtime(&self) -> Result<Vec<u8>, String> {
88-
Runtime::new()
89-
.unwrap()
90-
.block_on(async move { self.export_save_buffer_async().await })
91-
.map_err(String::from)
43+
/// Calls `save` in a runtime and returns an empty buffer in the case save was not required.
44+
// FIXME: zingo2, this is kept in to make zingomobile integration easier but should be moved into zingo-mobile
45+
pub fn export_save_buffer_runtime(&mut self) -> Result<Vec<u8>, String> {
46+
crate::commands::RT.block_on(async move {
47+
match self.save(&mut *self.wallet.lock().await).await {
48+
Ok(Some(wallet_bytes)) => Ok(wallet_bytes),
49+
Ok(None) => Ok(vec![]),
50+
Err(e) => Err(e.to_string()),
51+
}
52+
})
9253
}
9354

9455
/// Only relevant in non-mobile, this function removes the save file.

zingolib/src/lightclient/send.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,14 @@ pub mod send_with_proposal {
101101
&self,
102102
proposal: &Proposal<zip317::FeeRule, NoteRef>,
103103
) -> Result<NonEmpty<TxId>, CompleteAndBroadcastError> {
104-
let mut wallet = self.wallet.lock().await;
105-
let calculated_txids = wallet.create_transactions(proposal).await?;
106-
107-
// FIXME: zingo2, save wallet here in case send fails and tx is lost
104+
let calculated_txids = {
105+
let mut wallet = self.wallet.lock().await;
106+
wallet.save_required = true;
107+
wallet.create_transactions(proposal).await?
108+
};
109+
// drop wallet lock to allow save task to acquire it.
108110

111+
let mut wallet = self.wallet.lock().await;
109112
let broadcast_result = wallet
110113
.broadcast_calculated_transactions(self.get_server_uri(), calculated_txids)
111114
.await;

zingolib/src/testutils/lightclient.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
//! This mod is mostly to take inputs, raw data amd convert it into lightclient actions
22
//! (obviously) in a test environment.
3-
use crate::{
4-
error::ZingoLibError,
5-
lightclient::{describe::UAReceivers, LightClient},
6-
};
3+
use crate::lightclient::{describe::UAReceivers, LightClient};
74
use zcash_client_backend::{PoolType, ShieldedProtocol};
85
use zcash_primitives::transaction::TxId;
96

107
/// Create a lightclient from the buffer of another
118
pub async fn new_client_from_save_buffer(
129
template_client: &mut LightClient,
13-
) -> Result<LightClient, ZingoLibError> {
14-
let buffer = template_client.save_internal_buffer().await?;
10+
) -> std::io::Result<LightClient> {
11+
let mut wallet = template_client.wallet.lock().await;
12+
wallet.save_required = true;
13+
let buffer = template_client
14+
.save(&mut wallet)
15+
.await?
16+
.expect("forced save_required true");
1517

16-
LightClient::read_wallet_from_buffer_async(template_client.config(), buffer.as_slice())
17-
.await
18-
.map_err(ZingoLibError::CantReadWallet)
18+
LightClient::read_wallet_from_buffer_async(template_client.config(), buffer.as_slice()).await
1919
}
2020
/// gets the first address that will allow a sender to send to a specific pool, as a string
2121
/// calling \[0] on json may panic? not sure -fv

zingolib/src/wallet.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,12 @@ pub struct LightWallet {
216216
pub price: Arc<RwLock<WalletZecPriceInfo>>,
217217
/// Progress of an outgoing transaction
218218
send_progress: Arc<RwLock<SendProgress>>,
219+
/// Boolean for tracking whether the wallet state has changed since last save.
220+
///
221+
/// When wallet state is changed due to sync, send or creating addresses, this will be set to `true` automatically.
222+
/// Calling [`crate::lightclient::LightClient::save`] will serialize (and in some cases persist) the wallet and
223+
/// reset `save_required` to false.
224+
pub save_required: bool,
219225
}
220226

221227
impl LightWallet {
@@ -363,6 +369,7 @@ impl LightWallet {
363369
transparent_addresses,
364370
unified_addresses,
365371
network,
372+
save_required: false,
366373
})
367374
}
368375

zingolib/src/wallet/disk.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ impl LightWallet {
300300
transparent_addresses: BTreeMap::new(),
301301
unified_addresses: BTreeMap::new(),
302302
network,
303+
save_required: false,
303304
};
304305

305306
Ok(lw)
@@ -413,6 +414,7 @@ impl LightWallet {
413414
wallet_options: Arc::new(RwLock::new(wallet_options)),
414415
price: Arc::new(RwLock::new(price)),
415416
send_progress: Arc::new(RwLock::new(SendProgress::new(0))),
417+
save_required: false,
416418
})
417419
}
418420
}

zingolib/src/wallet/disk/testing/tests.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,15 @@ async fn reload_wallet_from_buffer() {
234234
.await;
235235
let mid_client_network = mid_client.wallet.lock().await.network;
236236

237-
let mid_buffer = mid_client.export_save_buffer_async().await.unwrap();
237+
let mid_buffer = {
238+
let mut wallet = mid_client.wallet.lock().await;
239+
wallet.save_required = true;
240+
mid_client
241+
.save(&mut *wallet)
242+
.await
243+
.unwrap()
244+
.expect("forced save_required true")
245+
};
238246

239247
let client =
240248
LightClient::read_wallet_from_buffer_async(&ZingoConfig::create_testnet(), &mid_buffer[..])

0 commit comments

Comments
 (0)