From 893e5985126cacf6e0e6e0a1a508ceae9b48a255 Mon Sep 17 00:00:00 2001 From: wiesche Date: Tue, 1 Sep 2026 20:51:13 +0200 Subject: [PATCH] make child key allocation atomic --- libwallet/src/backend.rs | 62 ++++++++++++++++++------ libwallet/src/contract/actions/README.md | 2 - 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/libwallet/src/backend.rs b/libwallet/src/backend.rs index 31378f0..9a1f958 100644 --- a/libwallet/src/backend.rs +++ b/libwallet/src/backend.rs @@ -466,13 +466,7 @@ where /// Return the current child index. pub fn current_child_index(&mut self, parent_key_id: &Identifier) -> Result { - let index = { - let batch = self.db.batch()?; - batch - .get_ser(Some(DERIV_PREFIX), &parent_key_id.to_bytes(), None)? - .unwrap_or_else(|| 0) - }; - Ok(index) + self.batch_no_mask()?.current_child_index(parent_key_id) } /// Next child ID for the given parent key, when we want to create a new output. @@ -483,17 +477,12 @@ where parent_key_id: &Identifier, keychain_mask: Option<&SecretKey>, ) -> Result { - let mut deriv_idx = { - let batch = self.db.batch()?; - batch - .get_ser(Some(DERIV_PREFIX), &parent_key_id.to_bytes(), None)? - .unwrap_or_else(|| 0) - }; + let mut batch = self.batch(keychain_mask)?; + let mut deriv_idx = batch.current_child_index(parent_key_id)?; let mut return_path = parent_key_id.to_path(); return_path.depth += 1; return_path.path[return_path.depth as usize - 1] = ChildNumber::from(deriv_idx); deriv_idx += 1; - let mut batch = self.batch(keychain_mask)?; batch.save_child_index(parent_key_id, deriv_idx)?; batch.commit()?; Ok(Identifier::from_path(&return_path)) @@ -624,6 +613,14 @@ where Ok(()) } + /// Return the current child index. + pub fn current_child_index(&self, parent_id: &Identifier) -> Result { + Ok(self + .db + .get_ser(Some(DERIV_PREFIX), &parent_id.to_bytes(), None)? + .unwrap_or_else(|| 0)) + } + /// Save last stored child index of a given parent. pub fn save_child_index(&mut self, parent_id: &Identifier, child_n: u32) -> Result<(), Error> { self.db @@ -785,3 +782,40 @@ fn to_key_u64>(k: K, val: u64) -> Vec { res.write_u64::(val).unwrap(); res } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn child_index_in_batch() -> Result<(), Error> { + grin_core::global::set_local_chain_type(grin_core::global::ChainTypes::AutomatedTesting); + let data_dir = std::env::temp_dir().join(format!("grin_wallet_{}", Uuid::new_v4())); + let db_dir = data_dir.join(DB_DIR); + fs::create_dir_all(&db_dir)?; + let store = Store::new( + db_dir.to_str().unwrap(), + None, + Some(DB_DIR), + DB_PREFIXES.to_vec(), + None, + None, + )?; + let parent = ExtKeychain::derive_key_id(2, 0, 0, 0, 0); + + { + let mut batch = WalletBatch:: { + db: store.batch()?, + keychain: None, + }; + assert_eq!(batch.current_child_index(&parent)?, 0); + batch.save_child_index(&parent, 1)?; + assert_eq!(batch.current_child_index(&parent)?, 1); + batch.commit()?; + } + + drop(store); + fs::remove_dir_all(data_dir)?; + Ok(()) + } +} diff --git a/libwallet/src/contract/actions/README.md b/libwallet/src/contract/actions/README.md index 795759e..8b5897b 100644 --- a/libwallet/src/contract/actions/README.md +++ b/libwallet/src/contract/actions/README.md @@ -36,8 +36,6 @@ Separating side effects until the 'save_step' part would make these functions mu #### TODOs - - `next_child_for` reads the derivation index and writes it back in two batches, so - concurrent steps can be handed the same child key. `next_child` has the same shape. - Honour `min_input_confirmation`; contract input selection currently uses one confirmation. - Decide whether `--use-inputs` or `--make-outputs` should imply early locking; currently only `--add-outputs` locks during `new`.