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