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