Skip to content

Commit af6a3c0

Browse files
Add tr-compiler_v2!
1 parent 96b4e66 commit af6a3c0

File tree

4 files changed

+150
-10
lines changed

4 files changed

+150
-10
lines changed

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
#![deny(non_camel_case_types)]
9595
#![deny(non_snake_case)]
9696
#![deny(unused_mut)]
97-
#![deny(dead_code)]
97+
// #![deny(dead_code)]
9898
#![deny(unused_imports)]
9999
#![deny(missing_docs)]
100100

src/policy/compiler.rs

+14
Original file line numberDiff line numberDiff line change
@@ -1117,6 +1117,20 @@ pub fn best_compilation<Pk: MiniscriptKey, Ctx: ScriptContext>(
11171117
}
11181118
}
11191119

1120+
/// Obtain the best compilation of for p=1.0 and q=0, along with the satisfaction cost for the script
1121+
pub(crate) fn best_compilation_sat<Pk: MiniscriptKey, Ctx: ScriptContext>(
1122+
policy: &Concrete<Pk>,
1123+
) -> Result<(Arc<Miniscript<Pk, Ctx>>, f64), CompilerError> {
1124+
let mut policy_cache = PolicyCache::<Pk, Ctx>::new();
1125+
let x: AstElemExt<Pk, Ctx> = best_t(&mut policy_cache, policy, 1.0, None)?;
1126+
if !x.ms.ty.mall.safe {
1127+
Err(CompilerError::TopLevelNonSafe)
1128+
} else if !x.ms.ty.mall.non_malleable {
1129+
Err(CompilerError::ImpossibleNonMalleableCompilation)
1130+
} else {
1131+
Ok((x.ms, x.comp_ext_data.sat_cost))
1132+
}
1133+
}
11201134
/// Obtain the best B expression with given sat and dissat
11211135
fn best_t<Pk, Ctx>(
11221136
policy_cache: &mut PolicyCache<Pk, Ctx>,

src/policy/concrete.rs

+125-5
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,17 @@ use {
3333
policy::Concrete,
3434
policy::{compiler, Liftable, Semantic},
3535
std::cmp::Reverse,
36+
std::collections::BTreeMap,
3637
std::collections::{BinaryHeap, HashMap},
3738
std::sync::Arc,
3839
Descriptor, Miniscript, Tap,
3940
};
4041
use {Error, ForEach, ForEachKey, MiniscriptKey};
4142

43+
/// [`TapTree`] -> ([`Policy`], satisfaction cost for TapTree) cache
44+
#[cfg(feature = "compiler")]
45+
type PolicyTapCache<Pk> = BTreeMap<TapTree<Pk>, (Policy<Pk>, f64)>;
46+
4247
/// Concrete policy which corresponds directly to a Miniscript structure,
4348
/// and whose disjunctions are annotated with satisfaction probabilities
4449
/// to assist the compiler
@@ -169,9 +174,93 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
169174
Ok(node)
170175
}
171176

177+
/// [`TapTree::Leaf`] average satisfaction cost + script size
178+
#[cfg(feature = "compiler")]
179+
fn tr_node_cost(ms: &Arc<Miniscript<Pk, Tap>>, prob: f64, cost: &f64) -> OrdF64 {
180+
OrdF64(prob * (ms.script_size() as f64 + cost))
181+
}
182+
183+
/// Create a [`TapTree`] from the miniscript as leaf nodes
184+
#[cfg(feature = "compiler")]
185+
fn with_huffman_tree_eff(
186+
ms: Vec<(OrdF64, (Arc<Miniscript<Pk, Tap>>, f64))>,
187+
policy_cache: &mut PolicyTapCache<Pk>,
188+
) -> Result<TapTree<Pk>, Error> {
189+
let mut node_weights = BinaryHeap::<(Reverse<OrdF64>, OrdF64, TapTree<Pk>)>::new(); // (cost, branch_prob, tree)
190+
for (prob, script) in ms {
191+
let wt = Self::tr_node_cost(&script.0, prob.0, &script.1);
192+
node_weights.push((Reverse(wt), prob, TapTree::Leaf(Arc::clone(&script.0))));
193+
}
194+
if node_weights.is_empty() {
195+
return Err(errstr("Empty Miniscript compilation"));
196+
}
197+
while node_weights.len() > 1 {
198+
let (prev_cost1, p1, s1) = node_weights.pop().expect("len must atleast be two");
199+
let (prev_cost2, p2, s2) = node_weights.pop().expect("len must atleast be two");
200+
201+
match (s1, s2) {
202+
(TapTree::Leaf(ms1), TapTree::Leaf(ms2)) => {
203+
// Retrieve the respective policies
204+
let (left_pol, _c1) = policy_cache
205+
.get(&TapTree::Leaf(Arc::clone(&ms1)))
206+
.ok_or_else(|| errstr("No corresponding policy found"))?
207+
.clone();
208+
209+
let (right_pol, _c2) = policy_cache
210+
.get(&TapTree::Leaf(Arc::clone(&ms2)))
211+
.ok_or_else(|| errstr("No corresponding policy found"))?
212+
.clone();
213+
214+
let parent_policy = Policy::Or(vec![
215+
((p1.0 * 1e4).round() as usize, left_pol),
216+
((p2.0 * 1e4).round() as usize, right_pol),
217+
]);
218+
219+
let (parent_compilation, cost) =
220+
compiler::best_compilation_sat::<Pk, Tap>(&parent_policy)?;
221+
222+
let parent_cost = Self::tr_node_cost(&parent_compilation, p1.0 + p2.0, &cost);
223+
let children_cost =
224+
OrdF64((prev_cost1.0).0 + (prev_cost2.0).0 + 32. * (p1.0 + p2.0));
225+
226+
policy_cache.remove(&TapTree::Leaf(Arc::clone(&ms1)));
227+
policy_cache.remove(&TapTree::Leaf(Arc::clone(&ms2)));
228+
let p = p1.0 + p2.0;
229+
node_weights.push(if parent_cost > children_cost {
230+
(
231+
Reverse(children_cost),
232+
OrdF64(p),
233+
TapTree::Tree(
234+
Arc::from(TapTree::Leaf(ms1)),
235+
Arc::from(TapTree::Leaf(ms2)),
236+
),
237+
)
238+
} else {
239+
let node = TapTree::Leaf(Arc::from(parent_compilation));
240+
policy_cache.insert(node.clone(), (parent_policy, parent_cost.0));
241+
(Reverse(parent_cost), OrdF64(p), node)
242+
});
243+
}
244+
(ms1, ms2) => {
245+
let p = p1.0 + p2.0;
246+
let cost = OrdF64((prev_cost1.0).0 + (prev_cost2.0).0 + 32.0);
247+
node_weights.push((
248+
Reverse(cost),
249+
OrdF64(p),
250+
TapTree::Tree(Arc::from(ms1), Arc::from(ms2)),
251+
));
252+
}
253+
}
254+
}
255+
debug_assert!(node_weights.len() == 1);
256+
let node = node_weights
257+
.pop()
258+
.expect("huffman tree algorithm is broken")
259+
.2;
260+
Ok(node)
261+
}
262+
172263
/// Flatten the [`Policy`] tree structure into a Vector with corresponding leaf probability
173-
// TODO: 1. Can try to push the maximum of scaling factors and accordingly update later for
174-
// TODO: 1. integer metric. (Accordingly change metrics everywhere)
175264
#[cfg(feature = "compiler")]
176265
fn to_tapleaf_prob_vec(&self, prob: f64) -> Vec<(f64, Policy<Pk>)> {
177266
match *self {
@@ -197,7 +286,7 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
197286

198287
/// Compile [`Policy::Or`] and [`Policy::Threshold`] according to odds
199288
#[cfg(feature = "compiler")]
200-
fn compile_tr_policy(&self) -> Result<TapTree<Pk>, Error> {
289+
fn compile_tr_private(&self) -> Result<TapTree<Pk>, Error> {
201290
let leaf_compilations: Vec<_> = self
202291
.to_tapleaf_prob_vec(1.0)
203292
.into_iter()
@@ -208,6 +297,27 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
208297
Ok(taptree)
209298
}
210299

300+
/// Compile [`Policy`] into a [`TapTree`]
301+
#[cfg(feature = "compiler")]
302+
fn compile_tr_efficient(&self) -> Result<TapTree<Pk>, Error> {
303+
let mut policy_cache = PolicyTapCache::<Pk>::new();
304+
let leaf_compilations: Vec<_> = self
305+
.to_tapleaf_prob_vec(1.0)
306+
.into_iter()
307+
.filter(|x| x.1 != Policy::Unsatisfiable)
308+
.map(|(prob, ref policy)| {
309+
let compilation = compiler::best_compilation_sat::<Pk, Tap>(policy).unwrap();
310+
policy_cache.insert(
311+
TapTree::Leaf(Arc::clone(&compilation.0)),
312+
(policy.clone(), compilation.1), // (policy, sat_cost)
313+
);
314+
(OrdF64(prob), compilation) // (branch_prob, comp=(ms, sat_cost))
315+
})
316+
.collect();
317+
let taptree = Self::with_huffman_tree_eff(leaf_compilations, &mut policy_cache).unwrap();
318+
Ok(taptree)
319+
}
320+
211321
/// Extract the internal_key from policy tree.
212322
#[cfg(feature = "compiler")]
213323
fn extract_key(self, unspendable_key: Option<Pk>) -> Result<(Pk, Policy<Pk>), Error> {
@@ -255,7 +365,11 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
255365
/// Compile the [`Tr`] descriptor into optimized [`TapTree`] implementation
256366
// TODO: We might require other compile errors for Taproot. Will discuss and update.
257367
#[cfg(feature = "compiler")]
258-
pub fn compile_tr(&self, unspendable_key: Option<Pk>) -> Result<Descriptor<Pk>, Error> {
368+
pub fn compile_tr(
369+
&self,
370+
unspendable_key: Option<Pk>,
371+
eff: bool,
372+
) -> Result<Descriptor<Pk>, Error> {
259373
self.is_valid()?; // Check for validity
260374
match self.is_safe_nonmalleable() {
261375
(false, _) => Err(Error::from(CompilerError::TopLevelNonSafe)),
@@ -268,7 +382,13 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
268382
internal_key,
269383
match policy {
270384
Policy::Trivial => None,
271-
policy => Some(policy.compile_tr_policy()?),
385+
policy => {
386+
if eff {
387+
Some(policy.compile_tr_efficient()?)
388+
} else {
389+
Some(policy.compile_tr_private()?)
390+
}
391+
}
272392
},
273393
)?;
274394
Ok(tree)

src/policy/mod.rs

+10-4
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,9 @@ mod tests {
382382
let unspendable_key: String = "UNSPENDABLE".to_string();
383383
{
384384
let policy: Concrete<String> = policy_str!("thresh(2,pk(A),pk(B),pk(C),pk(D))");
385-
let descriptor = policy.compile_tr(Some(unspendable_key.clone())).unwrap();
385+
let descriptor = policy
386+
.compile_tr(Some(unspendable_key.clone()), false)
387+
.unwrap();
386388

387389
let ms_compilation: Miniscript<String, Tap> = ms_str!("multi_a(2,A,B,C,D)");
388390
let tree: TapTree<String> = TapTree::Leaf(Arc::new(ms_compilation));
@@ -394,7 +396,9 @@ mod tests {
394396
// Trivial multi-node compilation
395397
{
396398
let policy: Concrete<String> = policy_str!("or(and(pk(A),pk(B)),and(pk(C),pk(D)))");
397-
let descriptor = policy.compile_tr(Some(unspendable_key.clone())).unwrap();
399+
let descriptor = policy
400+
.compile_tr(Some(unspendable_key.clone()), false)
401+
.unwrap();
398402

399403
let left_ms_compilation: Arc<Miniscript<String, Tap>> =
400404
Arc::new(ms_str!("and_v(v:pk(C),pk(D))"));
@@ -411,7 +415,7 @@ mod tests {
411415
{
412416
// Invalid policy compilation (Duplicate PubKeys)
413417
let policy: Concrete<String> = policy_str!("or(and(pk(A),pk(B)),and(pk(A),pk(D)))");
414-
let descriptor = policy.compile_tr(Some(unspendable_key.clone()));
418+
let descriptor = policy.compile_tr(Some(unspendable_key.clone()), false);
415419

416420
assert_eq!(
417421
descriptor.unwrap_err().to_string(),
@@ -448,7 +452,9 @@ mod tests {
448452
node_policies[6]
449453
)
450454
);
451-
let descriptor = policy.compile_tr(Some(unspendable_key.clone())).unwrap();
455+
let descriptor = policy
456+
.compile_tr(Some(unspendable_key.clone()), false)
457+
.unwrap();
452458

453459
let mut sorted_policy_prob = node_policies
454460
.into_iter()

0 commit comments

Comments
 (0)