33
44module BotPlutusInterface.CoinSelection (valueToVec , valuesToVecs , selectTxIns , uniqueAssetClasses ) where
55
6- import Control.Lens (foldOf , folded , ix , over , to , uncons , (^..) , (^?) , _Just )
6+ import BotPlutusInterface.Effects (PABEffect , printBpiLog )
7+ import BotPlutusInterface.Types (LogLevel (Debug ), LogType (CoinSelectionLog ))
8+ import Control.Lens (
9+ foldOf ,
10+ folded ,
11+ ifolded ,
12+ ix ,
13+ over ,
14+ to ,
15+ uncons ,
16+ withIndex ,
17+ (%~) ,
18+ (&) ,
19+ (^..) ,
20+ (^?) ,
21+ _Just ,
22+ )
23+ import Control.Monad.Except (throwError )
724import Control.Monad.Freer (Eff , Member )
825import Control.Monad.Trans.Class (lift )
926import Control.Monad.Trans.Either (hoistEither , newEitherT , runEitherT )
27+ import Data.Default (Default (def ))
1028import Data.Either.Combinators (isRight , maybeToRight )
1129import Data.Kind (Type )
1230import Data.List qualified as List
@@ -25,20 +43,26 @@ import Ledger.Tx (
2543 )
2644import Ledger.Value (AssetClass , Value )
2745import Ledger.Value qualified as Value
28-
2946import Plutus.V1.Ledger.Api (
3047 Credential (PubKeyCredential , ScriptCredential ),
3148 )
32- import BotPlutusInterface.Effects (PABEffect , printBpiLog )
33- import BotPlutusInterface.Types (LogLevel (Debug ), LogType (CoinSelectionLog ))
3449import Prettyprinter (pretty , (<+>) )
3550import Prelude
3651
52+ -- 'Search' represents the possible search strategy.
3753data Search
38- = Greedy
39- | GreedyPruning
40- deriving stock (Show )
41-
54+ = -- | This is a greedy search that searches for nearest utxo using l2norm.
55+ Greedy
56+ | -- | This is like greedy search, but here there's
57+ -- additonal goal that the change utxo should be equal to the output utxo.
58+ GreedyApprox
59+ deriving stock (Eq , Show )
60+
61+ instance Default Search where
62+ def = GreedyApprox
63+
64+ -- 'selectTxIns' selects utxos using default search strategy, it also preprocesses
65+ -- the utxos values in to normalized vectors. So that distances between utxos can be calculated.
4266selectTxIns ::
4367 forall (w :: Type ) (effs :: [Type -> Type ]).
4468 Member (PABEffect w ) effs =>
@@ -48,17 +72,20 @@ selectTxIns ::
4872 Eff effs (Either Text (Set TxIn ))
4973selectTxIns originalTxIns utxosIndex outValue =
5074 runEitherT $ do
51- let txInsValue :: Value
75+ let -- This represents the input value.
76+ txInsValue :: Value
5277 txInsValue =
5378 foldOf (folded . to ((`Map.lookup` utxosIndex) . txInRef) . folded . to txOutValue) originalTxIns
5479
80+ -- This is set of all the asset classes present in outValue, inputValue and all the utxos combined
5581 allAssetClasses :: Set AssetClass
5682 allAssetClasses =
5783 uniqueAssetClasses $ txInsValue : outValue : utxosIndex ^.. folded . to txOutValue
5884
5985 txInRefs :: [TxOutRef ]
6086 txInRefs = originalTxIns ^.. folded . to txInRef
6187
88+ -- All the remainingUtxos that has not been used as an input to the transaction yet.
6289 remainingUtxos :: [(TxOutRef , TxOut )]
6390 remainingUtxos =
6491 Map. toList $
@@ -68,29 +95,42 @@ selectTxIns originalTxIns utxosIndex outValue =
6895
6996 lift $ printBpiLog @ w (Debug [CoinSelectionLog ]) $ " Remaining UTxOs: " <+> pretty remainingUtxos
7097
98+ -- the input vector for the current transaction, this can be a zero vector when there are no
99+ -- inputs the transaction.
71100 txInsVec <-
72101 hoistEither $
73102 if Value. isZero txInsValue
74103 then Right $ zeroVec (length allAssetClasses)
75104 else valueToVec allAssetClasses txInsValue
76105
106+ -- the output vector of the current transaction, this is all the values of TxOut combined.
77107 outVec <- hoistEither $ valueToVec allAssetClasses outValue
78108
109+ -- all the remainingUtxos converted to the vectors.
79110 remainingUtxosVec <- hoistEither $ mapM (valueToVec allAssetClasses . txOutValue . snd ) remainingUtxos
80111
81- selectedUtxosIdxs <- newEitherT $ selectTxIns' @ w GreedyPruning (isSufficient outVec) outVec txInsVec remainingUtxosVec
112+ -- we use the default search strategy to get indexes of optimal utxos, these indexes are for the
113+ -- remainingUtxos, as we are sampling utxos from that set.
114+ selectedUtxosIdxs <- newEitherT $ selectTxIns' @ w def (isSufficient outVec) outVec txInsVec remainingUtxosVec
82115
83116 lift $ printBpiLog @ w (Debug [CoinSelectionLog ]) $ " " <+> " Selected UTxOs Index: " <+> pretty selectedUtxosIdxs
84117
85- let selectedUtxos :: [(TxOutRef , TxOut )]
118+ let -- These are the selected utxos that we get using `selectedUtxosIdxs`.
119+ selectedUtxos :: [(TxOutRef , TxOut )]
86120 selectedUtxos = selectedUtxosIdxs ^.. folded . to (\ idx -> remainingUtxos ^? ix idx) . folded
87121
88122 selectedTxIns <- hoistEither $ mapM txOutToTxIn selectedUtxos
89123
90124 lift $ printBpiLog @ w (Debug [CoinSelectionLog ]) $ " Selected TxIns: " <+> pretty selectedTxIns
91125
126+ -- Now we add the selected utxos to originalTxIns present in the transaction previously.
92127 return $ originalTxIns <> Set. fromList selectedTxIns
93128 where
129+ -- This represents the condition when we can stop searching for utxos.
130+ -- First condition is that the input vector must not be zero vector, i.e.
131+ -- There must be atleast some input to the transaction.
132+ -- Second condition is that all the values of input vector must be greater than
133+ -- or equal to the output vector.
94134 isSufficient :: Vector Integer -> Vector Integer -> Bool
95135 isSufficient outVec txInsVec =
96136 Vec. all (== True ) (Vec. zipWith (<=) outVec txInsVec)
@@ -105,8 +145,14 @@ selectTxIns' ::
105145 Vector Integer ->
106146 [Vector Integer ] ->
107147 Eff effs (Either Text [Int ])
108- selectTxIns' Greedy = greedySearch @ w
109- selectTxIns' GreedyPruning = greedyPruning @ w
148+ selectTxIns' searchStrategy stopSearch outVec txInsVec utxosVec
149+ | searchStrategy == Greedy =
150+ printBpiLog @ w (Debug [CoinSelectionLog ]) " Selecting UTxOs via greedy search"
151+ >> greedySearch @ w stopSearch outVec txInsVec utxosVec
152+ | searchStrategy == GreedyApprox =
153+ printBpiLog @ w (Debug [CoinSelectionLog ]) " Selecting UTxOs via greedy pruning search"
154+ >> greedyApprox @ w stopSearch outVec txInsVec utxosVec
155+ | otherwise = return $ throwError " Not a valid search strategy."
110156
111157greedySearch ::
112158 forall (w :: Type ) (effs :: [Type -> Type ]).
@@ -125,13 +171,12 @@ greedySearch stopSearch outVec txInsVec utxosVec
125171 >> return (Right mempty )
126172 | otherwise =
127173 runEitherT $ do
128- x <- hoistEither $ mapM (addVec txInsVec) utxosVec
129- utxosDist <- hoistEither $ mapM (l2norm outVec) x
174+ utxosDist <- hoistEither $ mapM (addVec txInsVec) utxosVec >>= mapM (l2norm outVec)
130175
131176 let sortedDist :: [(Int , Float )]
132177 sortedDist =
133- List. sortBy ( \ a b -> compare ( snd a) ( snd b)) $
134- zip [ 0 .. length utxosVec - 1 ] utxosDist
178+ utxosDist ^.. ifolded . withIndex
179+ & id %~ List. sortBy ( \ a b -> compare ( snd a) ( snd b))
135180
136181 newEitherT $ loop sortedDist txInsVec
137182 where
@@ -153,15 +198,15 @@ greedySearch stopSearch outVec txInsVec utxosVec
153198
154199 (idx : ) <$> newEitherT (loop remSortedDist newTxInsVec')
155200
156- greedyPruning ::
201+ greedyApprox ::
157202 forall (w :: Type ) (effs :: [Type -> Type ]).
158203 Member (PABEffect w ) effs =>
159204 (Vector Integer -> Bool ) ->
160205 Vector Integer ->
161206 Vector Integer ->
162207 [Vector Integer ] ->
163208 Eff effs (Either Text [Int ])
164- greedyPruning stopSearch outVec txInsVec utxosVec
209+ greedyApprox stopSearch outVec txInsVec utxosVec
165210 | null utxosVec =
166211 printBpiLog @ w (Debug [CoinSelectionLog ]) " Greedy Pruning: The list of remanining UTxO vectors in null."
167212 >> return (Right mempty )
@@ -244,8 +289,8 @@ valuesToVecs allAssetClasses values = Vec.fromList $ map toVec values
244289 where
245290 toVec :: Value -> Vector Integer
246291 toVec v =
247- Vec. map (Value. assetClassValueOf v) $
248- Vec. fromList $ Set. toList allAssetClasses
292+ fmap (Value. assetClassValueOf v) $
293+ allAssetClasses & id %~ ( Vec. fromList . Set. toList)
249294
250295uniqueAssetClasses :: [Value ] -> Set AssetClass
251296uniqueAssetClasses = Set. fromList . concatMap valueToAssetClass
0 commit comments