@@ -20,7 +20,7 @@ import Control.Lens (
2020 (^?) ,
2121 _Just ,
2222 )
23- import Control.Monad.Except (throwError )
23+ import Control.Monad.Except (foldM , throwError , unless )
2424import Control.Monad.Freer (Eff , Member )
2525import Control.Monad.Trans.Class (lift )
2626import Control.Monad.Trans.Either (hoistEither , newEitherT , runEitherT )
@@ -49,16 +49,16 @@ import Plutus.V1.Ledger.Api (
4949import Prettyprinter (pretty , (<+>) )
5050import Prelude
5151
52- -- 'Search ' represents the possible search strategy.
53- data Search
52+ -- 'searchStrategy ' represents the possible search strategy.
53+ data SearchStrategy
5454 = -- | This is a greedy search that searches for nearest utxo using l2norm.
5555 Greedy
5656 | -- | This is like greedy search, but here there's
5757 -- additonal goal that the change utxo should be equal to the output utxo.
5858 GreedyApprox
5959 deriving stock (Eq , Show )
6060
61- instance Default Search where
61+ instance Default SearchStrategy where
6262 def = GreedyApprox
6363
6464-- 'selectTxIns' selects utxos using default search strategy, it also preprocesses
@@ -111,14 +111,20 @@ selectTxIns originalTxIns utxosIndex outValue =
111111
112112 -- we use the default search strategy to get indexes of optimal utxos, these indexes are for the
113113 -- remainingUtxos, as we are sampling utxos from that set.
114- selectedUtxosIdxs <- newEitherT $ selectTxIns' @ w def (isSufficient outVec) outVec txInsVec remainingUtxosVec
114+ selectedUtxosIdxs <- newEitherT $ searchTxIns @ w def (isSufficient outVec) outVec txInsVec remainingUtxosVec
115115
116116 lift $ printBpiLog @ w (Debug [CoinSelectionLog ]) $ " " <+> " Selected UTxOs Index: " <+> pretty selectedUtxosIdxs
117117
118118 let -- These are the selected utxos that we get using `selectedUtxosIdxs`.
119119 selectedUtxos :: [(TxOutRef , TxOut )]
120120 selectedUtxos = selectedUtxosIdxs ^.. folded . to (\ idx -> remainingUtxos ^? ix idx) . folded
121121
122+ selectedVectors :: [Vector Integer ]
123+ selectedVectors = selectedUtxosIdxs ^.. folded . to (\ idx -> remainingUtxosVec ^? ix idx) . folded
124+
125+ finalTxInputVector <- hoistEither $ foldM addVec txInsVec selectedVectors
126+ unless (isSufficient outVec finalTxInputVector) (throwError " Insufficient Funds" )
127+
122128 selectedTxIns <- hoistEither $ mapM txOutToTxIn selectedUtxos
123129
124130 lift $ printBpiLog @ w (Debug [CoinSelectionLog ]) $ " Selected TxIns: " <+> pretty selectedTxIns
@@ -136,16 +142,18 @@ selectTxIns originalTxIns utxosIndex outValue =
136142 Vec. all (== True ) (Vec. zipWith (<=) outVec txInsVec)
137143 && txInsVec /= zeroVec (length txInsVec)
138144
139- selectTxIns' ::
145+ -- `searchTxIns` searches for optimal utxos for a transaction as input given
146+ -- current input vector, output vector and a list of all the remaining utxo vectors.
147+ searchTxIns ::
140148 forall (w :: Type ) (effs :: [Type -> Type ]).
141149 Member (PABEffect w ) effs =>
142- Search ->
150+ SearchStrategy ->
143151 (Vector Integer -> Bool ) ->
144152 Vector Integer ->
145153 Vector Integer ->
146154 [Vector Integer ] ->
147155 Eff effs (Either Text [Int ])
148- selectTxIns' searchStrategy stopSearch outVec txInsVec utxosVec
156+ searchTxIns searchStrategy stopSearch outVec txInsVec utxosVec
149157 | searchStrategy == Greedy =
150158 printBpiLog @ w (Debug [CoinSelectionLog ]) " Selecting UTxOs via greedy search"
151159 >> greedySearch @ w stopSearch outVec txInsVec utxosVec
@@ -154,6 +162,9 @@ selectTxIns' searchStrategy stopSearch outVec txInsVec utxosVec
154162 >> greedyApprox @ w stopSearch outVec txInsVec utxosVec
155163 | otherwise = return $ throwError " Not a valid search strategy."
156164
165+ -- `greedySearch` searches for utxos vectors for input to a transaction,
166+ -- this is achieved by selecting the utxo vector that have closest euclidean distance
167+ -- from output vector.
157168greedySearch ::
158169 forall (w :: Type ) (effs :: [Type -> Type ]).
159170 Member (PABEffect w ) effs =>
@@ -163,16 +174,24 @@ greedySearch ::
163174 [Vector Integer ] ->
164175 Eff effs (Either Text [Int ])
165176greedySearch stopSearch outVec txInsVec utxosVec
177+ -- we stop the search if there are no utxos vectors left, as we will not be able to
178+ -- select any further utxos as input to a transaction.
166179 | null utxosVec =
167180 printBpiLog @ w (Debug [CoinSelectionLog ]) " Greedy: The list of remanining UTxO vectors in null."
168181 >> return (Right mempty )
182+ -- we stop the search is the predicate `stopSearch` is true.
169183 | stopSearch txInsVec =
170184 printBpiLog @ w (Debug [CoinSelectionLog ]) " Greedy: Stopping search early."
171185 >> return (Right mempty )
172186 | otherwise =
173187 runEitherT $ do
188+ -- Here, we calculate the euclidean distance of the following vectors:
189+ -- l2norm(inputVec + remaining UTxO vector (U1), output vector).
190+ -- where U1 is just a vector from a list utxosVec.
174191 utxosDist <- hoistEither $ mapM (addVec txInsVec) utxosVec >>= mapM (l2norm outVec)
175192
193+ -- Now, we fold the distances with their current indexes, and then
194+ -- sort (lowest to highest) them using the distance from output vector.
176195 let sortedDist :: [(Int , Float )]
177196 sortedDist =
178197 utxosDist ^.. ifolded . withIndex
@@ -183,11 +202,14 @@ greedySearch stopSearch outVec txInsVec utxosVec
183202 loop :: [(Int , Float )] -> Vector Integer -> Eff effs (Either Text [Int ])
184203 loop [] _ = return $ Right mempty
185204 loop ((idx, _) : remSortedDist) newTxInsVec =
186- if stopSearch newTxInsVec
205+ if stopSearch newTxInsVec -- we check if we should stop the search.
187206 then return $ Right mempty
188207 else runEitherT $ do
208+ -- Get the selected utxo vector given the current idx.
189209 selectedUtxoVec <-
190210 hoistEither $ maybeToRight " Out of bounds" (utxosVec ^? ix idx)
211+
212+ -- Add the selected utxo vector to the current tx input vector.
191213 newTxInsVec' <- hoistEither $ addVec newTxInsVec selectedUtxoVec
192214
193215 lift $
@@ -198,6 +220,21 @@ greedySearch stopSearch outVec txInsVec utxosVec
198220
199221 (idx : ) <$> newEitherT (loop remSortedDist newTxInsVec')
200222
223+ -- 'greedyApprox' uses greedy search, but then it filters and add the utxo(s)
224+ -- such that the change vector is close to the output vector.
225+ --
226+ -- Eg: output vector: [100]
227+ -- input vector: [0]
228+ --
229+ -- utxos vector: [50],[210],[500],[10]
230+ --
231+ -- so, now the greedy search will select the following utxos:
232+ -- -- [10], [50], [210]
233+ --
234+ -- But, if we are using utxo vector with [210] then we don't need to
235+ -- consume vectors like [10] and [50].
236+ -- So, we filter such unnecessary vectors.
237+ --
201238greedyApprox ::
202239 forall (w :: Type ) (effs :: [Type -> Type ]).
203240 Member (PABEffect w ) effs =>
@@ -207,17 +244,26 @@ greedyApprox ::
207244 [Vector Integer ] ->
208245 Eff effs (Either Text [Int ])
209246greedyApprox stopSearch outVec txInsVec utxosVec
247+ -- we stop the search if there are no utxos vectors left, as we will not be able to
248+ -- select any further utxos as input to a transaction.
210249 | null utxosVec =
211250 printBpiLog @ w (Debug [CoinSelectionLog ]) " Greedy Pruning: The list of remanining UTxO vectors in null."
212251 >> return (Right mempty )
252+ -- we stop the search is the predicate `stopSearch` is true.
213253 | stopSearch txInsVec =
214254 printBpiLog @ w (Debug [CoinSelectionLog ]) " Greedy Pruning: Stopping search early."
215255 >> return (Right mempty )
216256 | otherwise =
217257 runEitherT $ do
258+ -- Here, we get the selected indexes of utxo vectors using greedy search.
218259 selectedUtxosIdx <- newEitherT $ greedySearch @ w stopSearch outVec txInsVec utxosVec
219260
220- let revSelectedUtxosVec :: [Vector Integer ]
261+ let -- Reverse the order of the selected vectors
262+ -- The Idea here is that, the vectors that are selected at
263+ -- last will have greater distance from the output vector.
264+ -- Hence, they may contain all the values that's required for
265+ -- the output vector.
266+ revSelectedUtxosVec :: [Vector Integer ]
221267 revSelectedUtxosVec =
222268 List. reverse $ selectedUtxosIdx ^.. folded . to (\ idx -> utxosVec ^? ix idx) . folded
223269
@@ -228,17 +274,29 @@ greedyApprox stopSearch outVec txInsVec utxosVec
228274 where
229275 loop :: Vector Integer -> [Int ] -> [Vector Integer ] -> Either Text [Int ]
230276 loop newTxInsVec (idx : idxs) (vec : vecs) = do
277+ -- Add the selected utxo vector to the current tx input vector.
231278 newTxInsVec' <- addVec newTxInsVec vec
279+
280+ -- Get the old change vector
232281 changeVec <- subVec outVec newTxInsVec
282+
283+ -- Get the new change vector
233284 changeVec' <- subVec outVec newTxInsVec'
234285
286+ -- compare the distance between old change vector with output vector
287+ -- and new change vector with the output vector.
235288 case l2norm outVec changeVec' < l2norm outVec changeVec of
289+ -- If the distance between new change vector and output
290+ -- vector is smaller then we add that utxo vector
236291 True -> (idx : ) <$> loop newTxInsVec' idxs vecs
292+ -- Else we check if we should stop the search here.
237293 False | stopSearch newTxInsVec -> Right mempty
294+ -- We add the current utxo vector.
238295 False -> (idx : ) <$> loop newTxInsVec' idxs vecs
239296 loop _newTxInsVec [] [] = pure mempty
240297 loop _newTxInsVec _idxs _vecs = Left " Length of idxs and list of vecs are not same."
241298
299+ -- calculate euclidean distance of two vectors, of same length/dimension.
242300l2norm :: Vector Integer -> Vector Integer -> Either Text Float
243301l2norm v1 v2
244302 | length v1 == length v2 = Right $ sqrt $ fromInteger $ sum $ Vec. zipWith formula v1 v2
@@ -256,34 +314,25 @@ l2norm v1 v2
256314 formula :: Integer -> Integer -> Integer
257315 formula n1 n2 = (n1 - n2) ^ (2 :: Integer )
258316
317+ -- Add two vectors of same length.
259318addVec :: Num n => Vector n -> Vector n -> Either Text (Vector n )
260319addVec = opVec (+)
261320
321+ -- Substract two vectors of same length.
262322subVec :: Num n => Vector n -> Vector n -> Either Text (Vector n )
263323subVec = opVec (-)
264324
265- opVec :: Num n => (forall a . Num a => a -> a -> a ) -> Vector n -> Vector n -> Either Text (Vector n )
266- opVec f v1 v2
267- | length v1 == length v2 = Right $ Vec. zipWith f v1 v2
268- | otherwise =
269- Left $
270- pack $
271- " Error: The length of the vectors should be same for addition."
272- <> " length of vector v1: "
273- <> show (length v1)
274- <> " "
275- <> " length of vector v2: "
276- <> show (length v2)
277- <> " ."
278-
325+ -- create zero vector of specified length.
279326zeroVec :: Int -> Vector Integer
280327zeroVec n = Vec. fromList $ replicate n 0
281328
329+ -- convert a value to a vector.
282330valueToVec :: Set AssetClass -> Value -> Either Text (Vector Integer )
283331valueToVec allAssetClasses v =
284332 maybeToRight " Error: Not able to uncons from empty vector." $
285333 (over _Just fst . uncons) $ valuesToVecs allAssetClasses [v]
286334
335+ -- convert values to a list of vectors.
287336valuesToVecs :: Set AssetClass -> [Value ] -> Vector (Vector Integer )
288337valuesToVecs allAssetClasses values = Vec. fromList $ map toVec values
289338 where
@@ -292,6 +341,7 @@ valuesToVecs allAssetClasses values = Vec.fromList $ map toVec values
292341 fmap (Value. assetClassValueOf v) $
293342 allAssetClasses & id %~ (Vec. fromList . Set. toList)
294343
344+ -- As the name suggests, we get a set of all the unique assetclass from given the lists of values.
295345uniqueAssetClasses :: [Value ] -> Set AssetClass
296346uniqueAssetClasses = Set. fromList . concatMap valueToAssetClass
297347 where
@@ -304,3 +354,18 @@ txOutToTxIn (txOutRef, txOut) =
304354 case Ledger. addressCredential (txOutAddress txOut) of
305355 PubKeyCredential _ -> Right $ Ledger. pubKeyTxIn txOutRef
306356 ScriptCredential _ -> Left " Cannot covert a script output to TxIn"
357+
358+ -- Apply a binary operation on two vectors of same length.
359+ opVec :: Num n => (forall a . Num a => a -> a -> a ) -> Vector n -> Vector n -> Either Text (Vector n )
360+ opVec f v1 v2
361+ | length v1 == length v2 = Right $ Vec. zipWith f v1 v2
362+ | otherwise =
363+ Left $
364+ pack $
365+ " Error: The length of the vectors should be same for addition."
366+ <> " length of vector v1: "
367+ <> show (length v1)
368+ <> " "
369+ <> " length of vector v2: "
370+ <> show (length v2)
371+ <> " ."
0 commit comments