@@ -33,12 +33,21 @@ use {
33
33
policy:: Concrete ,
34
34
policy:: { compiler, Liftable , Semantic } ,
35
35
std:: cmp:: Reverse ,
36
+ std:: collections:: BTreeMap ,
36
37
std:: collections:: { BinaryHeap , HashMap } ,
37
38
std:: sync:: Arc ,
38
39
Descriptor , Miniscript , Tap ,
39
40
} ;
40
41
use { Error , ForEach , ForEachKey , MiniscriptKey } ;
41
42
43
+ /// [`TapTree`] -> ([`Policy`], satisfaction cost) cache
44
+ #[ cfg( feature = "compiler" ) ]
45
+ type PolicyTapCache < Pk > = BTreeMap < TapTree < Pk > , ( Policy < Pk > , f64 ) > ;
46
+
47
+ /// [`Miniscript`] -> leaf probability in policy cache
48
+ #[ cfg( feature = "compiler" ) ]
49
+ type MsTapCache < Pk > = BTreeMap < TapTree < Pk > , f64 > ;
50
+
42
51
/// Concrete policy which corresponds directly to a Miniscript structure,
43
52
/// and whose disjunctions are annotated with satisfaction probabilities
44
53
/// to assist the compiler
@@ -169,9 +178,154 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
169
178
Ok ( node)
170
179
}
171
180
172
- /// 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)
181
+ /// Average satisfaction cost for [`TapTree`] with the leaf [`Miniscript`] nodes having
182
+ /// probabilities corresponding to the (sub)policies they're compiled from.
183
+ ///
184
+ /// Average satisfaction cost for [`TapTree`] over script-spend paths is probability times
185
+ /// the size of control block + the script size.
186
+ #[ cfg( feature = "compiler" ) ]
187
+ fn taptree_cost (
188
+ tr : & TapTree < Pk > ,
189
+ ms_cache : & MsTapCache < Pk > ,
190
+ policy_cache : & PolicyTapCache < Pk > ,
191
+ depth : u32 ,
192
+ ) -> f64 {
193
+ match * tr {
194
+ TapTree :: Tree ( ref l, ref r) => {
195
+ Self :: taptree_cost ( l, ms_cache, policy_cache, depth + 1 )
196
+ + Self :: taptree_cost ( r, ms_cache, policy_cache, depth + 1 )
197
+ }
198
+ TapTree :: Leaf ( ref ms) => {
199
+ let prob = ms_cache
200
+ . get ( & TapTree :: Leaf ( Arc :: clone ( ms) ) )
201
+ . expect ( "Probability should exist for the given ms" ) ;
202
+ let sat_cost = policy_cache
203
+ . get ( & TapTree :: Leaf ( Arc :: clone ( ms) ) )
204
+ . expect ( "Cost should exist for the given ms" )
205
+ . 1 ;
206
+ prob * ( ms. script_size ( ) as f64 + sat_cost + 32.0 * depth as f64 )
207
+ }
208
+ }
209
+ }
210
+
211
+ /// Create a [`TapTree`] from the a list of [`Miniscript`]s having corresponding satisfaction
212
+ /// cost and probability.
213
+ ///
214
+ /// Given that satisfaction probability and cost for each script is known, constructing the
215
+ /// [`TapTree`] as a huffman tree over the net cost (as defined in [`Policy::taptree_cost()`]) is
216
+ /// the optimal one.
217
+ /// For finding the optimal policy to taptree compilation, we are required to search
218
+ /// exhaustively over all policies which have the same leaf policies. Owing to the exponential
219
+ /// blow-up for such a method, we use a heuristic where we augment the merge to check if the
220
+ /// compilation of a new (sub)policy into a [`TapTree::Leaf`] with the policy corresponding to
221
+ /// the nodes as children is better than [`TapTree::Tree`] with the nodes as children.
222
+ #[ cfg( feature = "compiler" ) ]
223
+ fn with_huffman_tree_eff (
224
+ ms : Vec < Arc < Miniscript < Pk , Tap > > > ,
225
+ policy_cache : & mut PolicyTapCache < Pk > ,
226
+ ms_cache : & mut MsTapCache < Pk > ,
227
+ ) -> Result < TapTree < Pk > , Error > {
228
+ let mut node_weights = BinaryHeap :: < ( Reverse < OrdF64 > , OrdF64 , TapTree < Pk > ) > :: new ( ) ; // (cost, branch_prob, tree)
229
+ // Populate the heap with each `ms` as a TapLeaf, and the respective cost fields
230
+ for script in ms {
231
+ let wt = OrdF64 ( Self :: taptree_cost (
232
+ & TapTree :: Leaf ( Arc :: clone ( & script) ) ,
233
+ ms_cache,
234
+ policy_cache,
235
+ 0 ,
236
+ ) ) ;
237
+ let prob = OrdF64 (
238
+ * ms_cache
239
+ . get ( & TapTree :: Leaf ( Arc :: clone ( & script) ) )
240
+ . expect ( "Probability should exist for the given ms" ) ,
241
+ ) ;
242
+ node_weights. push ( ( Reverse ( wt) , prob, TapTree :: Leaf ( Arc :: clone ( & script) ) ) ) ;
243
+ }
244
+ if node_weights. is_empty ( ) {
245
+ return Err ( errstr ( "Empty Miniscript compilation" ) ) ;
246
+ }
247
+ while node_weights. len ( ) > 1 {
248
+ // Obtain the two least-weighted nodes from the heap for merging
249
+ let ( _prev_cost1, p1, ms1) = node_weights. pop ( ) . expect ( "len must atleast be two" ) ;
250
+ let ( _prev_cost2, p2, ms2) = node_weights. pop ( ) . expect ( "len must atleast be two" ) ;
251
+
252
+ // Retrieve the respective policies
253
+ let ( left_pol, _c1) = policy_cache
254
+ . get ( & ms1)
255
+ . ok_or_else ( || errstr ( "No corresponding policy found" ) ) ?
256
+ . clone ( ) ;
257
+
258
+ let ( right_pol, _c2) = policy_cache
259
+ . get ( & ms2)
260
+ . ok_or_else ( || errstr ( "No corresponding policy found" ) ) ?
261
+ . clone ( ) ;
262
+
263
+ // Create a parent policy with the respective node TapTrees as children (with odds
264
+ // weighted approximately in ratio to their probabilities)
265
+ let parent_policy = Policy :: Or ( vec ! [
266
+ ( ( p1. 0 * 1e4 ) . round( ) as usize , left_pol) ,
267
+ ( ( p2. 0 * 1e4 ) . round( ) as usize , right_pol) ,
268
+ ] ) ;
269
+
270
+ // Obtain compilation for the parent policy
271
+ let ( parent_compilation, parent_sat_cost) =
272
+ compiler:: best_compilation_sat :: < Pk , Tap > ( & parent_policy) ?;
273
+
274
+ // Probability of the parent node being satisfied equals the probability of either
275
+ // nodes to be satisfied. Since we weight the odds appropriately, the children nodes
276
+ // still have approximately the same probabilities
277
+ let p = p1. 0 + p2. 0 ;
278
+ // Inserting parent policy's weights (sat_cost and probability) for later usage
279
+ ms_cache. insert ( TapTree :: Leaf ( Arc :: clone ( & parent_compilation) ) , p) ;
280
+ policy_cache. insert (
281
+ TapTree :: Leaf ( Arc :: clone ( & parent_compilation) ) ,
282
+ ( parent_policy. clone ( ) , parent_sat_cost) ,
283
+ ) ;
284
+
285
+ let parent_cost = OrdF64 ( Self :: taptree_cost (
286
+ & TapTree :: Leaf ( Arc :: clone ( & parent_compilation) ) ,
287
+ ms_cache,
288
+ policy_cache,
289
+ 0 ,
290
+ ) ) ;
291
+ let children_cost = OrdF64 (
292
+ Self :: taptree_cost ( & ms1, ms_cache, policy_cache, 0 )
293
+ + Self :: taptree_cost ( & ms2, ms_cache, policy_cache, 0 ) ,
294
+ ) ;
295
+
296
+ // Merge the children nodes into either TapLeaf of the parent compilation or
297
+ // TapTree children nodes accordingly
298
+ node_weights. push ( if parent_cost > children_cost {
299
+ ms_cache. insert (
300
+ TapTree :: Tree ( Arc :: from ( ms1. clone ( ) ) , Arc :: from ( ms2. clone ( ) ) ) ,
301
+ p,
302
+ ) ;
303
+ policy_cache. insert (
304
+ TapTree :: Tree ( Arc :: from ( ms1. clone ( ) ) , Arc :: from ( ms2. clone ( ) ) ) ,
305
+ ( parent_policy, parent_sat_cost) ,
306
+ ) ;
307
+ (
308
+ Reverse ( children_cost) ,
309
+ OrdF64 ( p) ,
310
+ TapTree :: Tree ( Arc :: from ( ms1) , Arc :: from ( ms2) ) ,
311
+ )
312
+ } else {
313
+ let node = TapTree :: Leaf ( Arc :: from ( parent_compilation) ) ;
314
+ ( Reverse ( parent_cost) , OrdF64 ( p) , node)
315
+ } ) ;
316
+ }
317
+ debug_assert ! ( node_weights. len( ) == 1 ) ;
318
+ let node = node_weights
319
+ . pop ( )
320
+ . expect ( "huffman tree algorithm is broken" )
321
+ . 2 ;
322
+ Ok ( node)
323
+ }
324
+
325
+ /// Flatten the [`Policy`] tree structure into a Vector of (sub)-policies with corresponding
326
+ /// leaf probabilities by using odds for calculating branch probabilities.
327
+ /// We consider splitting the [`Policy`] tree into respective (sub)-policies considering
328
+ /// disjunction over [`Policy::Or`] and [`Policy::Threshold`](1, ...)
175
329
#[ cfg( feature = "compiler" ) ]
176
330
fn to_tapleaf_prob_vec ( & self , prob : f64 ) -> Vec < ( f64 , Policy < Pk > ) > {
177
331
match * self {
@@ -280,6 +434,62 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
280
434
}
281
435
}
282
436
437
+ /// Compile [`Policy`] into a [`TapTree Descriptor`][`Descriptor::Tr`]
438
+ ///
439
+ ///
440
+ /// This follows the heuristic as described in [`Policy::with_huffman_tree_eff`]
441
+ #[ cfg( feature = "compiler" ) ]
442
+ pub fn compile_tr ( & self , unspendable_key : Option < Pk > ) -> Result < Descriptor < Pk > , Error > {
443
+ self . is_valid ( ) ?; // Check for validity
444
+ match self . is_safe_nonmalleable ( ) {
445
+ ( false , _) => Err ( Error :: from ( CompilerError :: TopLevelNonSafe ) ) ,
446
+ ( _, false ) => Err ( Error :: from (
447
+ CompilerError :: ImpossibleNonMalleableCompilation ,
448
+ ) ) ,
449
+ _ => {
450
+ let ( internal_key, policy) = self . clone ( ) . extract_key ( unspendable_key) ?;
451
+ let tree = Descriptor :: new_tr (
452
+ internal_key,
453
+ match policy {
454
+ Policy :: Trivial => None ,
455
+ policy => {
456
+ let mut policy_cache = PolicyTapCache :: < Pk > :: new ( ) ;
457
+ let mut ms_cache = MsTapCache :: < Pk > :: new ( ) ;
458
+ // Obtain the policy compilations and populate the respective caches for
459
+ // creating the huffman tree later on
460
+ let leaf_compilations: Vec < _ > = policy
461
+ . to_tapleaf_prob_vec ( 1.0 )
462
+ . into_iter ( )
463
+ . filter ( |x| x. 1 != Policy :: Unsatisfiable )
464
+ . map ( |( prob, ref pol) | {
465
+ let compilation =
466
+ compiler:: best_compilation_sat :: < Pk , Tap > ( pol) . unwrap ( ) ;
467
+ policy_cache. insert (
468
+ TapTree :: Leaf ( Arc :: clone ( & compilation. 0 ) ) ,
469
+ ( pol. clone ( ) , compilation. 1 ) , // (policy, sat_cost)
470
+ ) ;
471
+ ms_cache. insert (
472
+ TapTree :: Leaf ( Arc :: from ( compilation. 0 . clone ( ) ) ) ,
473
+ prob,
474
+ ) ;
475
+ compilation. 0
476
+ } )
477
+ . collect ( ) ;
478
+ let taptree = Self :: with_huffman_tree_eff (
479
+ leaf_compilations,
480
+ & mut policy_cache,
481
+ & mut ms_cache,
482
+ )
483
+ . unwrap ( ) ;
484
+ Some ( taptree)
485
+ }
486
+ } ,
487
+ ) ?;
488
+ Ok ( tree)
489
+ }
490
+ }
491
+ }
492
+
283
493
/// Compile the descriptor into an optimized `Miniscript` representation
284
494
#[ cfg( feature = "compiler" ) ]
285
495
pub fn compile < Ctx : ScriptContext > ( & self ) -> Result < Miniscript < Pk , Ctx > , CompilerError > {
0 commit comments