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
@@ -283,6 +292,69 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
283292 }
284293 }
285294
295+ /// Compile [`Policy`] into a [`TapTree Descriptor`][`Descriptor::Tr`]
296+ ///
297+ ///
298+ /// This follows the heuristic as described in [`with_huffman_tree_eff`]
299+ #[ cfg( feature = "compiler" ) ]
300+ pub fn compile_tr ( & self , unspendable_key : Option < Pk > ) -> Result < Descriptor < Pk > , Error > {
301+ self . is_valid ( ) ?; // Check for validity
302+ match self . is_safe_nonmalleable ( ) {
303+ ( false , _) => Err ( Error :: from ( CompilerError :: TopLevelNonSafe ) ) ,
304+ ( _, false ) => Err ( Error :: from (
305+ CompilerError :: ImpossibleNonMalleableCompilation ,
306+ ) ) ,
307+ _ => {
308+ let ( internal_key, policy) = self . clone ( ) . extract_key ( unspendable_key) ?;
309+ let tree = Descriptor :: new_tr (
310+ internal_key,
311+ match policy {
312+ Policy :: Trivial => None ,
313+ policy => {
314+ let mut policy_cache = PolicyTapCache :: < Pk > :: new ( ) ;
315+ let mut ms_cache = MsTapCache :: < Pk > :: new ( ) ;
316+ // Obtain the policy compilations and populate the respective caches for
317+ // creating the huffman tree later on
318+ let vec_policies: Vec < _ > = policy. to_tapleaf_prob_vec ( 1.0 ) ;
319+ let mut leaf_compilations: Vec < Arc < Miniscript < Pk , Tap > > > = vec ! [ ] ;
320+ for ( prob, pol) in vec_policies {
321+ // policy corresponding to the key (replaced by unsatisfiable) is skipped
322+ if pol == Policy :: Unsatisfiable {
323+ continue ;
324+ }
325+ let compilation = compiler:: best_compilation_sat :: < Pk , Tap > ( & pol) ?;
326+ compilation. 0 . sanity_check ( ) ?;
327+ let leaf_comp = TapTree :: Leaf ( compilation. 0 . clone ( ) ) ;
328+ policy_cache. insert (
329+ TapTree :: Leaf ( Arc :: clone ( & compilation. 0 ) ) ,
330+ ( pol. clone ( ) , compilation. 1 ) , // (policy, sat_cost)
331+ ) ;
332+ // In case we hit duplication compilations for sub-policies, we add
333+ // their respective probabilities without pushing the node back again.
334+ match ms_cache. get ( & leaf_comp) {
335+ Some ( p) => {
336+ ms_cache. insert ( leaf_comp, p + prob) ;
337+ }
338+ None => {
339+ ms_cache. insert ( leaf_comp, prob) ;
340+ leaf_compilations. push ( compilation. 0 ) ;
341+ }
342+ } ;
343+ }
344+ let taptree = with_huffman_tree_eff (
345+ leaf_compilations,
346+ & mut policy_cache,
347+ & mut ms_cache,
348+ ) ?;
349+ Some ( taptree)
350+ }
351+ } ,
352+ ) ?;
353+ Ok ( tree)
354+ }
355+ }
356+ }
357+
286358 /// Compile the descriptor into an optimized `Miniscript` representation
287359 #[ cfg( feature = "compiler" ) ]
288360 pub fn compile < Ctx : ScriptContext > ( & self ) -> Result < Miniscript < Pk , Ctx > , CompilerError > {
@@ -825,6 +897,50 @@ where
825897 }
826898}
827899
900+ /// Average satisfaction cost for [`TapTree`] with the leaf [`Miniscript`] nodes at some depth having
901+ /// probabilities corresponding to the (sub)policies they're compiled from.
902+ ///
903+ /// Average satisfaction cost for [`TapTree`] over script-spend paths is probability times
904+ /// the size of control block at depth + the script size.
905+ #[ cfg( feature = "compiler" ) ]
906+ fn at_depth_taptree_cost < Pk : MiniscriptKey > (
907+ tr : & TapTree < Pk > ,
908+ ms_cache : & MsTapCache < Pk > ,
909+ policy_cache : & PolicyTapCache < Pk > ,
910+ depth : u32 ,
911+ ) -> f64 {
912+ match * tr {
913+ TapTree :: Tree ( ref l, ref r) => {
914+ at_depth_taptree_cost ( l, ms_cache, policy_cache, depth + 1 )
915+ + at_depth_taptree_cost ( r, ms_cache, policy_cache, depth + 1 )
916+ }
917+ TapTree :: Leaf ( ref ms) => {
918+ let prob = ms_cache
919+ . get ( & TapTree :: Leaf ( Arc :: clone ( ms) ) )
920+ . expect ( "Probability should exist for the given ms" ) ;
921+ let sat_cost = policy_cache
922+ . get ( & TapTree :: Leaf ( Arc :: clone ( ms) ) )
923+ . expect ( "Cost should exist for the given ms" )
924+ . 1 ;
925+ prob * ( ms. script_size ( ) as f64 + sat_cost + 32.0 * depth as f64 )
926+ }
927+ }
928+ }
929+
930+ /// Average net satisfaction cost for [`TapTree`] with the leaf [`Miniscript`] nodes having
931+ /// probabilities corresponding to the (sub)policies they're compiled from.
932+ ///
933+ /// Average satisfaction cost for [`TapTree`] over script-spend paths is probability times
934+ /// the size of control block + the script size.
935+ #[ cfg( feature = "compiler" ) ]
936+ fn taptree_cost < Pk : MiniscriptKey > (
937+ tr : & TapTree < Pk > ,
938+ ms_cache : & MsTapCache < Pk > ,
939+ policy_cache : & PolicyTapCache < Pk > ,
940+ ) -> f64 {
941+ at_depth_taptree_cost ( tr, ms_cache, policy_cache, 0 )
942+ }
943+
828944/// Create a Huffman Tree from compiled [Miniscript] nodes
829945#[ cfg( feature = "compiler" ) ]
830946fn with_huffman_tree < Pk : MiniscriptKey > (
@@ -855,3 +971,122 @@ fn with_huffman_tree<Pk: MiniscriptKey>(
855971 . 1 ;
856972 Ok ( node)
857973}
974+
975+ /// Create a [`TapTree`] from the a list of [`Miniscript`]s having corresponding satisfaction
976+ /// cost and probability.
977+ ///
978+ /// Given that satisfaction probability and cost for each script is known, constructing the
979+ /// [`TapTree`] as a huffman tree over the net cost (as defined in [`taptree_cost`]) is
980+ /// the optimal one.
981+ /// For finding the optimal policy to taptree compilation, we are required to search
982+ /// exhaustively over all policies which have the same leaf policies. Owing to the exponential
983+ /// blow-up for such a method, we use a heuristic where we augment the merge to check if the
984+ /// compilation of a new (sub)policy into a [`TapTree::Leaf`] with the policy corresponding to
985+ /// the nodes as children is better than [`TapTree::Tree`] with the nodes as children.
986+ ///
987+ /// # Assumption
988+ ///
989+ /// We have no two duplicate policies/ compilations in the given list.
990+ /// In any other case, we'd need to re-engineer the node-merging algorithm here to gracefully
991+ /// handle duplicate intermediate policies/ miniscript compilations by dis-disambiguating them.
992+ #[ cfg( feature = "compiler" ) ]
993+ fn with_huffman_tree_eff < Pk : MiniscriptKey > (
994+ ms : Vec < Arc < Miniscript < Pk , Tap > > > ,
995+ policy_cache : & mut PolicyTapCache < Pk > ,
996+ ms_cache : & mut MsTapCache < Pk > ,
997+ ) -> Result < TapTree < Pk > , Error > {
998+ let mut node_weights = BinaryHeap :: < ( Reverse < OrdF64 > , OrdF64 , TapTree < Pk > ) > :: new ( ) ; // (cost, branch_prob, tree)
999+ // Populate the heap with each `ms` as a TapLeaf, and the respective cost fields
1000+ for script in ms {
1001+ let wt = OrdF64 ( taptree_cost (
1002+ & TapTree :: Leaf ( Arc :: clone ( & script) ) ,
1003+ ms_cache,
1004+ policy_cache,
1005+ ) ) ;
1006+ let prob = OrdF64 (
1007+ * ms_cache
1008+ . get ( & TapTree :: Leaf ( Arc :: clone ( & script) ) )
1009+ . expect ( "Probability should exist for the given ms" ) ,
1010+ ) ;
1011+ node_weights. push ( ( Reverse ( wt) , prob, TapTree :: Leaf ( Arc :: clone ( & script) ) ) ) ;
1012+ }
1013+ if node_weights. is_empty ( ) {
1014+ return Err ( errstr ( "Empty Miniscript compilation" ) ) ;
1015+ }
1016+ while node_weights. len ( ) > 1 {
1017+ // Obtain the two least-weighted nodes from the heap for merging
1018+ let ( _prev_cost1, p1, ms1) = node_weights. pop ( ) . expect ( "len must atleast be two" ) ;
1019+ let ( _prev_cost2, p2, ms2) = node_weights. pop ( ) . expect ( "len must atleast be two" ) ;
1020+
1021+ // Retrieve the respective policies
1022+ let ( left_pol, _c1) = policy_cache
1023+ . get ( & ms1)
1024+ . ok_or_else ( || errstr ( "No corresponding policy found" ) ) ?
1025+ . clone ( ) ;
1026+
1027+ let ( right_pol, _c2) = policy_cache
1028+ . get ( & ms2)
1029+ . ok_or_else ( || errstr ( "No corresponding policy found" ) ) ?
1030+ . clone ( ) ;
1031+
1032+ // Create a parent policy with the respective node TapTrees as children (with odds
1033+ // weighted approximately in ratio to their probabilities)
1034+ let parent_policy = Policy :: Or ( vec ! [
1035+ ( ( p1. 0 * 1e4 ) . round( ) as usize , left_pol) ,
1036+ ( ( p2. 0 * 1e4 ) . round( ) as usize , right_pol) ,
1037+ ] ) ;
1038+
1039+ // Obtain compilation for the parent policy
1040+ let ( parent_compilation, parent_sat_cost) =
1041+ compiler:: best_compilation_sat :: < Pk , Tap > ( & parent_policy) ?;
1042+ parent_compilation. sanity_check ( ) ?;
1043+
1044+ // Probability of the parent node being satisfied equals the probability of either
1045+ // nodes to be satisfied. Since we weight the odds appropriately, the children nodes
1046+ // still have approximately the same probabilities
1047+ let p = p1. 0 + p2. 0 ;
1048+ // Inserting parent policy's weights (sat_cost and probability) for later usage, assuming
1049+ // we don't hit duplicate policy/ compilation here.
1050+ ms_cache. insert ( TapTree :: Leaf ( Arc :: clone ( & parent_compilation) ) , p) ;
1051+ policy_cache. insert (
1052+ TapTree :: Leaf ( Arc :: clone ( & parent_compilation) ) ,
1053+ ( parent_policy. clone ( ) , parent_sat_cost) ,
1054+ ) ;
1055+
1056+ let parent_cost = OrdF64 ( taptree_cost (
1057+ & TapTree :: Leaf ( Arc :: clone ( & parent_compilation) ) ,
1058+ ms_cache,
1059+ policy_cache,
1060+ ) ) ;
1061+ let children_cost = OrdF64 (
1062+ taptree_cost ( & ms1, ms_cache, policy_cache) + taptree_cost ( & ms2, ms_cache, policy_cache) ,
1063+ ) ;
1064+
1065+ // Merge the children nodes into either TapLeaf of the parent compilation or
1066+ // TapTree children nodes accordingly
1067+ node_weights. push ( if parent_cost > children_cost {
1068+ ms_cache. insert (
1069+ TapTree :: Tree ( Arc :: from ( ms1. clone ( ) ) , Arc :: from ( ms2. clone ( ) ) ) ,
1070+ p,
1071+ ) ;
1072+ policy_cache. insert (
1073+ TapTree :: Tree ( Arc :: from ( ms1. clone ( ) ) , Arc :: from ( ms2. clone ( ) ) ) ,
1074+ ( parent_policy, parent_sat_cost) ,
1075+ ) ;
1076+ (
1077+ Reverse ( children_cost) ,
1078+ OrdF64 ( p) ,
1079+ TapTree :: Tree ( Arc :: from ( ms1) , Arc :: from ( ms2) ) ,
1080+ )
1081+ } else {
1082+ let node = TapTree :: Leaf ( Arc :: from ( parent_compilation) ) ;
1083+ ( Reverse ( parent_cost) , OrdF64 ( p) , node)
1084+ } ) ;
1085+ }
1086+ debug_assert ! ( node_weights. len( ) == 1 ) ;
1087+ let node = node_weights
1088+ . pop ( )
1089+ . expect ( "huffman tree algorithm is broken" )
1090+ . 2 ;
1091+ Ok ( node)
1092+ }
0 commit comments