Skip to content

Commit e1db09a

Browse files
committed
f Retain wallet changes across cancellation
Keep wallet changes queued until persistence completes so dropped futures cannot discard state already taken from BDK. Cover cancellation during store I/O with a regression test. Co-Authored-By: HAL 9000
1 parent 2913a1f commit e1db09a

1 file changed

Lines changed: 99 additions & 12 deletions

File tree

‎src/wallet/persist.rs‎

Lines changed: 99 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -174,15 +174,11 @@ impl KVStoreWalletPersister {
174174
pub(super) async fn persist_changeset(
175175
&mut self, change_set: ChangeSet,
176176
) -> Result<(), std::io::Error> {
177-
let mut pending_change_set = std::mem::take(&mut self.pending_change_set);
178-
pending_change_set.merge(change_set);
179-
match self.persist_inner(&pending_change_set).await {
180-
Ok(()) => Ok(()),
181-
Err(e) => {
182-
self.pending_change_set = pending_change_set;
183-
Err(e)
184-
},
185-
}
177+
self.pending_change_set.merge(change_set);
178+
let pending_change_set = self.pending_change_set.clone();
179+
self.persist_inner(&pending_change_set).await?;
180+
let _ = std::mem::take(&mut self.pending_change_set);
181+
Ok(())
186182
}
187183
}
188184

@@ -210,21 +206,112 @@ impl AsyncWalletPersister for KVStoreWalletPersister {
210206

211207
#[cfg(test)]
212208
mod tests {
209+
use std::future::Future;
213210
use std::sync::Arc;
211+
use std::time::Duration;
214212

215213
use bdk_wallet::{AsyncWalletPersister, ChangeSet, Wallet as BdkWallet};
216214
use bitcoin::Network;
215+
use lightning::io;
216+
use lightning::util::persist::{KVStore, PageToken, PaginatedKVStore, PaginatedListResponse};
217217

218218
use super::KVStoreWalletPersister;
219219
use crate::io::test_utils::InMemoryStore;
220220
use crate::logger::Logger;
221221
use crate::types::{DynStore, DynStoreWrapper};
222222

223+
const EXTERNAL_DESCRIPTOR: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
224+
const INTERNAL_DESCRIPTOR: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
225+
226+
#[derive(Clone)]
227+
struct GatedStore {
228+
inner: Arc<InMemoryStore>,
229+
write_gate: Arc<tokio::sync::RwLock<()>>,
230+
}
231+
232+
impl KVStore for GatedStore {
233+
fn read(
234+
&self, primary_namespace: &str, secondary_namespace: &str, key: &str,
235+
) -> impl Future<Output = Result<Vec<u8>, io::Error>> + 'static + Send {
236+
KVStore::read(&*self.inner, primary_namespace, secondary_namespace, key)
237+
}
238+
239+
fn write(
240+
&self, primary_namespace: &str, secondary_namespace: &str, key: &str, buf: Vec<u8>,
241+
) -> impl Future<Output = Result<(), io::Error>> + 'static + Send {
242+
let inner = Arc::clone(&self.inner);
243+
let write_gate = Arc::clone(&self.write_gate);
244+
let primary_namespace = primary_namespace.to_string();
245+
let secondary_namespace = secondary_namespace.to_string();
246+
let key = key.to_string();
247+
async move {
248+
let _guard = write_gate.read().await;
249+
KVStore::write(&*inner, &primary_namespace, &secondary_namespace, &key, buf).await
250+
}
251+
}
252+
253+
fn remove(
254+
&self, primary_namespace: &str, secondary_namespace: &str, key: &str, lazy: bool,
255+
) -> impl Future<Output = Result<(), io::Error>> + 'static + Send {
256+
KVStore::remove(&*self.inner, primary_namespace, secondary_namespace, key, lazy)
257+
}
258+
259+
fn list(
260+
&self, primary_namespace: &str, secondary_namespace: &str,
261+
) -> impl Future<Output = Result<Vec<String>, io::Error>> + 'static + Send {
262+
KVStore::list(&*self.inner, primary_namespace, secondary_namespace)
263+
}
264+
}
265+
266+
impl PaginatedKVStore for GatedStore {
267+
fn list_paginated(
268+
&self, primary_namespace: &str, secondary_namespace: &str,
269+
page_token: Option<PageToken>,
270+
) -> impl Future<Output = Result<PaginatedListResponse, io::Error>> + 'static + Send {
271+
PaginatedKVStore::list_paginated(
272+
&*self.inner,
273+
primary_namespace,
274+
secondary_namespace,
275+
page_token,
276+
)
277+
}
278+
}
279+
223280
#[tokio::test]
224-
async fn retries_changes_after_persistence_failure() {
225-
const EXTERNAL_DESCRIPTOR: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
226-
const INTERNAL_DESCRIPTOR: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
281+
async fn retains_pending_changes_when_persist_is_cancelled() {
282+
let gated_store = GatedStore {
283+
inner: Arc::new(InMemoryStore::new()),
284+
write_gate: Arc::new(tokio::sync::RwLock::new(())),
285+
};
286+
let store: Arc<DynStore> = Arc::new(DynStoreWrapper(gated_store.clone()));
287+
let logger = Arc::new(Logger::new_log_facade());
288+
let mut persister = KVStoreWalletPersister::new(Arc::clone(&store), Arc::clone(&logger));
289+
AsyncWalletPersister::initialize(&mut persister).await.unwrap();
227290

291+
let mut wallet = BdkWallet::create(EXTERNAL_DESCRIPTOR, INTERNAL_DESCRIPTOR)
292+
.network(Network::Regtest)
293+
.create_wallet_no_persist()
294+
.unwrap();
295+
let change_set = wallet.take_staged().unwrap();
296+
297+
let gate_guard = gated_store.write_gate.write().await;
298+
{
299+
let persist_fut = persister.persist_changeset(change_set);
300+
tokio::pin!(persist_fut);
301+
let poll_res = tokio::time::timeout(Duration::from_millis(100), &mut persist_fut).await;
302+
assert!(poll_res.is_err(), "persist should be parked on the gated store write");
303+
}
304+
drop(gate_guard);
305+
306+
persister.persist_changeset(ChangeSet::default()).await.unwrap();
307+
308+
let mut reloaded_persister = KVStoreWalletPersister::new(store, logger);
309+
let reloaded = AsyncWalletPersister::initialize(&mut reloaded_persister).await.unwrap();
310+
assert_eq!(reloaded.network, Some(Network::Regtest));
311+
}
312+
313+
#[tokio::test]
314+
async fn retries_changes_after_persistence_failure() {
228315
let store: Arc<DynStore> = Arc::new(DynStoreWrapper(InMemoryStore::new()));
229316
let logger = Arc::new(Logger::new_log_facade());
230317
let mut persister = KVStoreWalletPersister::new(Arc::clone(&store), Arc::clone(&logger));

0 commit comments

Comments
 (0)