Skip to content

Commit 7195bd7

Browse files
apoelstrasanket1729
authored andcommitted
tr: clean up TaprootSpendInfo locking logic
1. `RwLock` is typically much slower than `Mutex`, so the Rust developers discourage its use unless you really need lots of simultaneous readers. We do not, we only need to unlock this mutex for tiny amounts of time locking logic 2. The `clone` implementation missed an opportunity to clone an internal `Arc`, instead using `Mutex::new(None)` and forcing the clone to recompute the spend info. 3. Minor idiomatic cleanups.
1 parent 954b12f commit 7195bd7

File tree

1 file changed

+58
-66
lines changed

1 file changed

+58
-66
lines changed

src/descriptor/tr.rs

+58-66
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use expression::{self, FromTree};
1717
use miniscript::{limits::TAPROOT_MAX_NODE_COUNT, Miniscript};
1818
use std::cmp::{self, max};
1919
use std::hash;
20-
use std::sync::{Arc, RwLock};
20+
use std::sync::{Arc, Mutex};
2121
use std::{fmt, str::FromStr};
2222
use Tap;
2323
use {Error, MiniscriptKey};
@@ -46,19 +46,21 @@ pub struct Tr<Pk: MiniscriptKey> {
4646
/// This will be [`None`] when the descriptor is not derived.
4747
/// This information will be cached automatically when it is required
4848
//
49-
// It is also possible to wrap this in a further Arc<RwLock...> so that the cache
50-
// is shared across clones of the this descriptor. But this (slightly) complicates
51-
// the code, and it might be desirable for clone to keep separate caches
52-
spend_info: RwLock<Option<Arc<TaprootSpendInfo>>>,
49+
// The inner `Arc` here is because Rust does not allow us to return a reference
50+
// to the contents of the `Option` from inside a `MutexGuard`. There is no outer
51+
// `Arc` because when this structure is cloned, we create a whole new mutex.
52+
spend_info: Mutex<Option<Arc<TaprootSpendInfo>>>,
5353
}
5454

5555
impl<Pk: MiniscriptKey> Clone for Tr<Pk> {
5656
fn clone(&self) -> Self {
57+
// When cloning, construct a new Mutex so that distinct clones don't
58+
// cause blocking between each other. We clone only the internal `Arc`,
59+
// so the clone is always cheap (in both time and space)
5760
Self {
5861
internal_key: self.internal_key.clone(),
5962
tree: self.tree.clone(),
60-
// Cloning creates a new instance of RwLock
61-
spend_info: RwLock::new(None),
63+
spend_info: Mutex::new(self.spend_info.lock().expect("Lock poisoned").clone()),
6264
}
6365
}
6466
}
@@ -169,7 +171,7 @@ impl<Pk: MiniscriptKey> Tr<Pk> {
169171
Ok(Self {
170172
internal_key,
171173
tree,
172-
spend_info: RwLock::new(None),
174+
spend_info: Mutex::new(None),
173175
})
174176
} else {
175177
Err(Error::MaxRecursiveDepthExceeded)
@@ -212,64 +214,54 @@ impl<Pk: MiniscriptKey> Tr<Pk> {
212214
{
213215
// If the value is already cache, read it
214216
// read only panics if the lock is poisoned (meaning other thread having a lock panicked)
215-
let spend_info = self
216-
.spend_info
217-
.read()
218-
.expect("Lock poisoned")
219-
.as_ref()
220-
.map(Arc::clone);
221-
222-
match spend_info {
223-
Some(spend_info) => spend_info,
224-
None => {
225-
// Get a new secp context
226-
// This would be cheap operation after static context support from upstream
227-
let secp = secp256k1::Secp256k1::verification_only();
228-
// Key spend path with no merkle root
229-
let data = if self.tree.is_none() {
230-
TaprootSpendInfo::new_key_spend(
231-
&secp,
232-
self.internal_key.to_x_only_pubkey(),
233-
None,
234-
)
235-
} else {
236-
let mut builder = TaprootBuilder::new();
237-
for (depth, ms) in self.iter_scripts() {
238-
let script = ms.encode();
239-
builder = builder
240-
.add_leaf(depth, script)
241-
.expect("Computing spend data on a valid Tree should always succeed");
217+
let read_lock = self.spend_info.lock().expect("Lock poisoned");
218+
if let Some(ref spend_info) = *read_lock {
219+
return spend_info.clone();
220+
}
221+
drop(read_lock);
222+
223+
// Get a new secp context
224+
// This would be cheap operation after static context support from upstream
225+
let secp = secp256k1::Secp256k1::verification_only();
226+
// Key spend path with no merkle root
227+
let data = if self.tree.is_none() {
228+
TaprootSpendInfo::new_key_spend(&secp, self.internal_key.to_x_only_pubkey(), None)
229+
} else {
230+
let mut builder = TaprootBuilder::new();
231+
for (depth, ms) in self.iter_scripts() {
232+
let script = ms.encode();
233+
builder = builder
234+
.add_leaf(depth, script)
235+
.expect("Computing spend data on a valid Tree should always succeed");
236+
}
237+
// Assert builder cannot error here because we have a well formed descriptor
238+
match builder.finalize(&secp, self.internal_key.to_x_only_pubkey()) {
239+
Ok(data) => data,
240+
Err(e) => match e {
241+
TaprootBuilderError::InvalidMerkleTreeDepth(_) => {
242+
unreachable!("Depth checked in struct construction")
243+
}
244+
TaprootBuilderError::NodeNotInDfsOrder => {
245+
unreachable!("Insertion is called in DFS order")
246+
}
247+
TaprootBuilderError::OverCompleteTree => {
248+
unreachable!("Taptree is a well formed tree")
242249
}
243-
// Assert builder cannot error here because we have a well formed descriptor
244-
match builder.finalize(&secp, self.internal_key.to_x_only_pubkey()) {
245-
Ok(data) => data,
246-
Err(e) => match e {
247-
TaprootBuilderError::InvalidMerkleTreeDepth(_) => {
248-
unreachable!("Depth checked in struct construction")
249-
}
250-
TaprootBuilderError::NodeNotInDfsOrder => {
251-
unreachable!("Insertion is called in DFS order")
252-
}
253-
TaprootBuilderError::OverCompleteTree => {
254-
unreachable!("Taptree is a well formed tree")
255-
}
256-
TaprootBuilderError::InvalidInternalKey(_) => {
257-
unreachable!("Internal key checked for validity")
258-
}
259-
TaprootBuilderError::IncompleteTree => {
260-
unreachable!("Taptree is a well formed tree")
261-
}
262-
TaprootBuilderError::EmptyTree => {
263-
unreachable!("Taptree is a well formed tree with atleast 1 element")
264-
}
265-
},
250+
TaprootBuilderError::InvalidInternalKey(_) => {
251+
unreachable!("Internal key checked for validity")
266252
}
267-
};
268-
let spend_info = Arc::new(data);
269-
*self.spend_info.write().expect("Lock poisoned") = Some(Arc::clone(&spend_info));
270-
spend_info
253+
TaprootBuilderError::IncompleteTree => {
254+
unreachable!("Taptree is a well formed tree")
255+
}
256+
TaprootBuilderError::EmptyTree => {
257+
unreachable!("Taptree is a well formed tree with atleast 1 element")
258+
}
259+
},
271260
}
272-
}
261+
};
262+
let spend_info = Arc::new(data);
263+
*self.spend_info.lock().expect("Lock poisoned") = Some(spend_info.clone());
264+
spend_info
273265
}
274266
}
275267

@@ -387,7 +379,7 @@ where
387379
Ok(Tr {
388380
internal_key: expression::terminal(key, Pk::from_str)?,
389381
tree: None,
390-
spend_info: RwLock::new(None),
382+
spend_info: Mutex::new(None),
391383
})
392384
}
393385
2 => {
@@ -403,7 +395,7 @@ where
403395
Ok(Tr {
404396
internal_key: expression::terminal(key, Pk::from_str)?,
405397
tree: Some(ret),
406-
spend_info: RwLock::new(None),
398+
spend_info: Mutex::new(None),
407399
})
408400
}
409401
_ => {
@@ -668,7 +660,7 @@ impl<P: MiniscriptKey, Q: MiniscriptKey> TranslatePk<P, Q> for Tr<P> {
668660
Some(tree) => Some(tree.translate_helper(&mut translatefpk, &mut translatefpkh)?),
669661
None => None,
670662
},
671-
spend_info: RwLock::new(None),
663+
spend_info: Mutex::new(None),
672664
};
673665
Ok(translate_desc)
674666
}

0 commit comments

Comments
 (0)