@@ -37,10 +37,19 @@ use {
37
37
crate :: Miniscript ,
38
38
crate :: Tap ,
39
39
std:: cmp:: Reverse ,
40
+ std:: collections:: BTreeMap ,
40
41
std:: collections:: { BinaryHeap , HashMap } ,
41
42
std:: sync:: Arc ,
42
43
} ;
43
44
45
+ /// [`TapTree`] -> ([`Policy`], satisfaction cost) cache
46
+ #[ cfg( feature = "compiler" ) ]
47
+ type PolicyTapCache < Pk > = BTreeMap < TapTree < Pk > , ( Policy < Pk > , f64 ) > ;
48
+
49
+ /// [`Miniscript`] -> leaf probability in policy cache
50
+ #[ cfg( feature = "compiler" ) ]
51
+ type MsTapCache < Pk > = BTreeMap < TapTree < Pk > , f64 > ;
52
+
44
53
/// Concrete policy which corresponds directly to a Miniscript structure,
45
54
/// and whose disjunctions are annotated with satisfaction probabilities
46
55
/// to assist the compiler
@@ -264,6 +273,62 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
264
273
}
265
274
}
266
275
276
+ /// Compile [`Policy`] into a [`TapTree Descriptor`][`Descriptor::Tr`]
277
+ ///
278
+ ///
279
+ /// This follows the heuristic as described in [`with_huffman_tree_eff`]
280
+ #[ cfg( feature = "compiler" ) ]
281
+ pub fn compile_tr ( & self , unspendable_key : Option < Pk > ) -> Result < Descriptor < Pk > , Error > {
282
+ self . is_valid ( ) ?; // Check for validity
283
+ match self . is_safe_nonmalleable ( ) {
284
+ ( false , _) => Err ( Error :: from ( CompilerError :: TopLevelNonSafe ) ) ,
285
+ ( _, false ) => Err ( Error :: from (
286
+ CompilerError :: ImpossibleNonMalleableCompilation ,
287
+ ) ) ,
288
+ _ => {
289
+ let ( internal_key, policy) = self . clone ( ) . extract_key ( unspendable_key) ?;
290
+ let tree = Descriptor :: new_tr (
291
+ internal_key,
292
+ match policy {
293
+ Policy :: Trivial => None ,
294
+ policy => {
295
+ let mut policy_cache = PolicyTapCache :: < Pk > :: new ( ) ;
296
+ let mut ms_cache = MsTapCache :: < Pk > :: new ( ) ;
297
+ // Obtain the policy compilations and populate the respective caches for
298
+ // creating the huffman tree later on
299
+ let leaf_compilations: Vec < _ > = policy
300
+ . to_tapleaf_prob_vec ( 1.0 )
301
+ . into_iter ( )
302
+ . filter ( |x| x. 1 != Policy :: Unsatisfiable )
303
+ . map ( |( prob, ref pol) | {
304
+ let compilation =
305
+ compiler:: best_compilation_sat :: < Pk , Tap > ( pol) . unwrap ( ) ;
306
+ policy_cache. insert (
307
+ TapTree :: Leaf ( Arc :: clone ( & compilation. 0 ) ) ,
308
+ ( pol. clone ( ) , compilation. 1 ) , // (policy, sat_cost)
309
+ ) ;
310
+ ms_cache. insert (
311
+ TapTree :: Leaf ( Arc :: from ( compilation. 0 . clone ( ) ) ) ,
312
+ prob,
313
+ ) ;
314
+ compilation. 0
315
+ } )
316
+ . collect ( ) ;
317
+ let taptree = with_huffman_tree_eff (
318
+ leaf_compilations,
319
+ & mut policy_cache,
320
+ & mut ms_cache,
321
+ )
322
+ . unwrap ( ) ;
323
+ Some ( taptree)
324
+ }
325
+ } ,
326
+ ) ?;
327
+ Ok ( tree)
328
+ }
329
+ }
330
+ }
331
+
267
332
/// Compile the descriptor into an optimized `Miniscript` representation
268
333
#[ cfg( feature = "compiler" ) ]
269
334
pub fn compile < Ctx : ScriptContext > ( & self ) -> Result < Miniscript < Pk , Ctx > , CompilerError > {
@@ -806,6 +871,36 @@ where
806
871
}
807
872
}
808
873
874
+ /// Average satisfaction cost for [`TapTree`] with the leaf [`Miniscript`] nodes having
875
+ /// probabilities corresponding to the (sub)policies they're compiled from.
876
+ ///
877
+ /// Average satisfaction cost for [`TapTree`] over script-spend paths is probability times
878
+ /// the size of control block + the script size.
879
+ #[ cfg( feature = "compiler" ) ]
880
+ fn taptree_cost < Pk : MiniscriptKey > (
881
+ tr : & TapTree < Pk > ,
882
+ ms_cache : & MsTapCache < Pk > ,
883
+ policy_cache : & PolicyTapCache < Pk > ,
884
+ depth : u32 ,
885
+ ) -> f64 {
886
+ match * tr {
887
+ TapTree :: Tree ( ref l, ref r) => {
888
+ taptree_cost ( l, ms_cache, policy_cache, depth + 1 )
889
+ + taptree_cost ( r, ms_cache, policy_cache, depth + 1 )
890
+ }
891
+ TapTree :: Leaf ( ref ms) => {
892
+ let prob = ms_cache
893
+ . get ( & TapTree :: Leaf ( Arc :: clone ( ms) ) )
894
+ . expect ( "Probability should exist for the given ms" ) ;
895
+ let sat_cost = policy_cache
896
+ . get ( & TapTree :: Leaf ( Arc :: clone ( ms) ) )
897
+ . expect ( "Cost should exist for the given ms" )
898
+ . 1 ;
899
+ prob * ( ms. script_size ( ) as f64 + sat_cost + 32.0 * depth as f64 )
900
+ }
901
+ }
902
+ }
903
+
809
904
/// Create a Huffman Tree from compiled [Miniscript] nodes
810
905
#[ cfg( feature = "compiler" ) ]
811
906
fn with_huffman_tree < Pk : MiniscriptKey > (
@@ -836,3 +931,117 @@ fn with_huffman_tree<Pk: MiniscriptKey>(
836
931
. 1 ;
837
932
Ok ( node)
838
933
}
934
+
935
+ /// Create a [`TapTree`] from the a list of [`Miniscript`]s having corresponding satisfaction
936
+ /// cost and probability.
937
+ ///
938
+ /// Given that satisfaction probability and cost for each script is known, constructing the
939
+ /// [`TapTree`] as a huffman tree over the net cost (as defined in [`taptree_cost`]) is
940
+ /// the optimal one.
941
+ /// For finding the optimal policy to taptree compilation, we are required to search
942
+ /// exhaustively over all policies which have the same leaf policies. Owing to the exponential
943
+ /// blow-up for such a method, we use a heuristic where we augment the merge to check if the
944
+ /// compilation of a new (sub)policy into a [`TapTree::Leaf`] with the policy corresponding to
945
+ /// the nodes as children is better than [`TapTree::Tree`] with the nodes as children.
946
+ #[ cfg( feature = "compiler" ) ]
947
+ fn with_huffman_tree_eff < Pk : MiniscriptKey > (
948
+ ms : Vec < Arc < Miniscript < Pk , Tap > > > ,
949
+ policy_cache : & mut PolicyTapCache < Pk > ,
950
+ ms_cache : & mut MsTapCache < Pk > ,
951
+ ) -> Result < TapTree < Pk > , Error > {
952
+ let mut node_weights = BinaryHeap :: < ( Reverse < OrdF64 > , OrdF64 , TapTree < Pk > ) > :: new ( ) ; // (cost, branch_prob, tree)
953
+ // Populate the heap with each `ms` as a TapLeaf, and the respective cost fields
954
+ for script in ms {
955
+ let wt = OrdF64 ( taptree_cost (
956
+ & TapTree :: Leaf ( Arc :: clone ( & script) ) ,
957
+ ms_cache,
958
+ policy_cache,
959
+ 0 ,
960
+ ) ) ;
961
+ let prob = OrdF64 (
962
+ * ms_cache
963
+ . get ( & TapTree :: Leaf ( Arc :: clone ( & script) ) )
964
+ . expect ( "Probability should exist for the given ms" ) ,
965
+ ) ;
966
+ node_weights. push ( ( Reverse ( wt) , prob, TapTree :: Leaf ( Arc :: clone ( & script) ) ) ) ;
967
+ }
968
+ if node_weights. is_empty ( ) {
969
+ return Err ( errstr ( "Empty Miniscript compilation" ) ) ;
970
+ }
971
+ while node_weights. len ( ) > 1 {
972
+ // Obtain the two least-weighted nodes from the heap for merging
973
+ let ( _prev_cost1, p1, ms1) = node_weights. pop ( ) . expect ( "len must atleast be two" ) ;
974
+ let ( _prev_cost2, p2, ms2) = node_weights. pop ( ) . expect ( "len must atleast be two" ) ;
975
+
976
+ // Retrieve the respective policies
977
+ let ( left_pol, _c1) = policy_cache
978
+ . get ( & ms1)
979
+ . ok_or_else ( || errstr ( "No corresponding policy found" ) ) ?
980
+ . clone ( ) ;
981
+
982
+ let ( right_pol, _c2) = policy_cache
983
+ . get ( & ms2)
984
+ . ok_or_else ( || errstr ( "No corresponding policy found" ) ) ?
985
+ . clone ( ) ;
986
+
987
+ // Create a parent policy with the respective node TapTrees as children (with odds
988
+ // weighted approximately in ratio to their probabilities)
989
+ let parent_policy = Policy :: Or ( vec ! [
990
+ ( ( p1. 0 * 1e4 ) . round( ) as usize , left_pol) ,
991
+ ( ( p2. 0 * 1e4 ) . round( ) as usize , right_pol) ,
992
+ ] ) ;
993
+
994
+ // Obtain compilation for the parent policy
995
+ let ( parent_compilation, parent_sat_cost) =
996
+ compiler:: best_compilation_sat :: < Pk , Tap > ( & parent_policy) ?;
997
+
998
+ // Probability of the parent node being satisfied equals the probability of either
999
+ // nodes to be satisfied. Since we weight the odds appropriately, the children nodes
1000
+ // still have approximately the same probabilities
1001
+ let p = p1. 0 + p2. 0 ;
1002
+ // Inserting parent policy's weights (sat_cost and probability) for later usage
1003
+ ms_cache. insert ( TapTree :: Leaf ( Arc :: clone ( & parent_compilation) ) , p) ;
1004
+ policy_cache. insert (
1005
+ TapTree :: Leaf ( Arc :: clone ( & parent_compilation) ) ,
1006
+ ( parent_policy. clone ( ) , parent_sat_cost) ,
1007
+ ) ;
1008
+
1009
+ let parent_cost = OrdF64 ( taptree_cost (
1010
+ & TapTree :: Leaf ( Arc :: clone ( & parent_compilation) ) ,
1011
+ ms_cache,
1012
+ policy_cache,
1013
+ 0 ,
1014
+ ) ) ;
1015
+ let children_cost = OrdF64 (
1016
+ taptree_cost ( & ms1, ms_cache, policy_cache, 0 )
1017
+ + taptree_cost ( & ms2, ms_cache, policy_cache, 0 ) ,
1018
+ ) ;
1019
+
1020
+ // Merge the children nodes into either TapLeaf of the parent compilation or
1021
+ // TapTree children nodes accordingly
1022
+ node_weights. push ( if parent_cost > children_cost {
1023
+ ms_cache. insert (
1024
+ TapTree :: Tree ( Arc :: from ( ms1. clone ( ) ) , Arc :: from ( ms2. clone ( ) ) ) ,
1025
+ p,
1026
+ ) ;
1027
+ policy_cache. insert (
1028
+ TapTree :: Tree ( Arc :: from ( ms1. clone ( ) ) , Arc :: from ( ms2. clone ( ) ) ) ,
1029
+ ( parent_policy, parent_sat_cost) ,
1030
+ ) ;
1031
+ (
1032
+ Reverse ( children_cost) ,
1033
+ OrdF64 ( p) ,
1034
+ TapTree :: Tree ( Arc :: from ( ms1) , Arc :: from ( ms2) ) ,
1035
+ )
1036
+ } else {
1037
+ let node = TapTree :: Leaf ( Arc :: from ( parent_compilation) ) ;
1038
+ ( Reverse ( parent_cost) , OrdF64 ( p) , node)
1039
+ } ) ;
1040
+ }
1041
+ debug_assert ! ( node_weights. len( ) == 1 ) ;
1042
+ let node = node_weights
1043
+ . pop ( )
1044
+ . expect ( "huffman tree algorithm is broken" )
1045
+ . 2 ;
1046
+ Ok ( node)
1047
+ }
0 commit comments