{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
module Cardano.CoinSelection.Algorithm.RandomImprove
( randomImprove
) where
import Prelude
import Cardano.CoinSelection
( CoinMap (..)
, CoinMapEntry (..)
, CoinSelection (..)
, CoinSelectionAlgorithm (..)
, CoinSelectionError (..)
, CoinSelectionLimit (..)
, CoinSelectionParameters (..)
, CoinSelectionResult (..)
, InputCountInsufficientError (..)
, InputLimitExceededError (..)
, InputValueInsufficientError (..)
, InputsExhaustedError (..)
, coinMapFromList
, coinMapRandomEntry
, coinMapToList
, coinMapValue
)
import Control.Monad ( foldM )
import Control.Monad.Trans.Class ( lift )
import Control.Monad.Trans.Except ( ExceptT (..), throwE )
import Control.Monad.Trans.Maybe ( MaybeT (..), runMaybeT )
import Crypto.Random.Types ( MonadRandom )
import Data.Ord ( Down (..) )
import Internal.Coin ( Coin )
import qualified Data.List as L
import qualified Internal.Coin as C
randomImprove
:: (Ord i, Ord o, MonadRandom m)
=> CoinSelectionAlgorithm i o m
randomImprove :: forall i o (m :: * -> *).
(Ord i, Ord o, MonadRandom m) =>
CoinSelectionAlgorithm i o m
randomImprove = (CoinSelectionParameters i o
-> ExceptT CoinSelectionError m (CoinSelectionResult i o))
-> CoinSelectionAlgorithm i o m
forall i o (m :: * -> *).
(CoinSelectionParameters i o
-> ExceptT CoinSelectionError m (CoinSelectionResult i o))
-> CoinSelectionAlgorithm i o m
CoinSelectionAlgorithm CoinSelectionParameters i o
-> ExceptT CoinSelectionError m (CoinSelectionResult i o)
forall i o (m :: * -> *).
(Ord i, Ord o, MonadRandom m) =>
CoinSelectionParameters i o
-> ExceptT CoinSelectionError m (CoinSelectionResult i o)
payForOutputs
payForOutputs
:: (Ord i, Ord o, MonadRandom m)
=> CoinSelectionParameters i o
-> ExceptT CoinSelectionError m (CoinSelectionResult i o)
payForOutputs :: forall i o (m :: * -> *).
(Ord i, Ord o, MonadRandom m) =>
CoinSelectionParameters i o
-> ExceptT CoinSelectionError m (CoinSelectionResult i o)
payForOutputs CoinSelectionParameters i o
params = do
Maybe (Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)])
mRandomSelections <- m (Maybe
(Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)]))
-> ExceptT
CoinSelectionError
m
(Maybe (Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)]))
forall (m :: * -> *) a.
Monad m =>
m a -> ExceptT CoinSelectionError m a
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (m (Maybe
(Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)]))
-> ExceptT
CoinSelectionError
m
(Maybe (Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)])))
-> m (Maybe
(Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)]))
-> ExceptT
CoinSelectionError
m
(Maybe (Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)]))
forall a b. (a -> b) -> a -> b
$ MaybeT m (Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)])
-> m (Maybe
(Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)]))
forall (m :: * -> *) a. MaybeT m a -> m (Maybe a)
runMaybeT (MaybeT
m (Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)])
-> m (Maybe
(Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)])))
-> MaybeT
m (Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)])
-> m (Maybe
(Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)]))
forall a b. (a -> b) -> a -> b
$ ((Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)])
-> CoinMapEntry o
-> MaybeT
m (Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)]))
-> (Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)])
-> [CoinMapEntry o]
-> MaybeT
m (Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)])
forall (t :: * -> *) (m :: * -> *) b a.
(Foldable t, Monad m) =>
(b -> a -> m b) -> b -> t a -> m b
foldM (Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)])
-> CoinMapEntry o
-> MaybeT
m (Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)])
forall i o (m :: * -> *).
MonadRandom m =>
(Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)])
-> CoinMapEntry o
-> MaybeT
m (Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)])
makeRandomSelection
(Integer
inputCountMax, CoinSelectionParameters i o -> CoinMap i
forall i o. CoinSelectionParameters i o -> CoinMap i
inputsAvailable CoinSelectionParameters i o
params, []) [CoinMapEntry o]
outputsDescending
case Maybe (Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)])
mRandomSelections of
Just (Integer
inputCountRemaining, CoinMap i
utxoRemaining, [([CoinMapEntry i], CoinMapEntry o)]
randomSelections) -> do
(Integer
_, CoinSelection i o
finalSelection, CoinMap i
utxoRemaining') <- m (Integer, CoinSelection i o, CoinMap i)
-> ExceptT
CoinSelectionError m (Integer, CoinSelection i o, CoinMap i)
forall (m :: * -> *) a.
Monad m =>
m a -> ExceptT CoinSelectionError m a
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (m (Integer, CoinSelection i o, CoinMap i)
-> ExceptT
CoinSelectionError m (Integer, CoinSelection i o, CoinMap i))
-> m (Integer, CoinSelection i o, CoinMap i)
-> ExceptT
CoinSelectionError m (Integer, CoinSelection i o, CoinMap i)
forall a b. (a -> b) -> a -> b
$ ((Integer, CoinSelection i o, CoinMap i)
-> ([CoinMapEntry i], CoinMapEntry o)
-> m (Integer, CoinSelection i o, CoinMap i))
-> (Integer, CoinSelection i o, CoinMap i)
-> [([CoinMapEntry i], CoinMapEntry o)]
-> m (Integer, CoinSelection i o, CoinMap i)
forall (t :: * -> *) (m :: * -> *) b a.
(Foldable t, Monad m) =>
(b -> a -> m b) -> b -> t a -> m b
foldM
(Integer, CoinSelection i o, CoinMap i)
-> ([CoinMapEntry i], CoinMapEntry o)
-> m (Integer, CoinSelection i o, CoinMap i)
forall i o (m :: * -> *).
(MonadRandom m, Ord i, Ord o) =>
(Integer, CoinSelection i o, CoinMap i)
-> ([CoinMapEntry i], CoinMapEntry o)
-> m (Integer, CoinSelection i o, CoinMap i)
improveSelection
(Integer
inputCountRemaining, CoinSelection i o
forall a. Monoid a => a
mempty, CoinMap i
utxoRemaining)
([([CoinMapEntry i], CoinMapEntry o)]
-> [([CoinMapEntry i], CoinMapEntry o)]
forall a. [a] -> [a]
reverse [([CoinMapEntry i], CoinMapEntry o)]
randomSelections)
CoinSelectionResult i o
-> ExceptT CoinSelectionError m (CoinSelectionResult i o)
forall a. a -> ExceptT CoinSelectionError m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (CoinSelectionResult i o
-> ExceptT CoinSelectionError m (CoinSelectionResult i o))
-> CoinSelectionResult i o
-> ExceptT CoinSelectionError m (CoinSelectionResult i o)
forall a b. (a -> b) -> a -> b
$ CoinSelection i o -> CoinMap i -> CoinSelectionResult i o
forall i o.
CoinSelection i o -> CoinMap i -> CoinSelectionResult i o
CoinSelectionResult CoinSelection i o
finalSelection CoinMap i
utxoRemaining'
Maybe (Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)])
Nothing ->
CoinSelectionError
-> ExceptT CoinSelectionError m (CoinSelectionResult i o)
forall (m :: * -> *) e a. Monad m => e -> ExceptT e m a
throwE CoinSelectionError
errorCondition
where
errorCondition :: CoinSelectionError
errorCondition
| Coin
amountAvailable Coin -> Coin -> Bool
forall a. Ord a => a -> a -> Bool
< Coin
amountRequested =
InputValueInsufficientError -> CoinSelectionError
InputValueInsufficient (InputValueInsufficientError -> CoinSelectionError)
-> InputValueInsufficientError -> CoinSelectionError
forall a b. (a -> b) -> a -> b
$
Coin -> Coin -> InputValueInsufficientError
InputValueInsufficientError
Coin
amountAvailable Coin
amountRequested
| Natural
utxoCount Natural -> Natural -> Bool
forall a. Ord a => a -> a -> Bool
< Natural
outputCount =
InputCountInsufficientError -> CoinSelectionError
InputCountInsufficient (InputCountInsufficientError -> CoinSelectionError)
-> InputCountInsufficientError -> CoinSelectionError
forall a b. (a -> b) -> a -> b
$
Natural -> Natural -> InputCountInsufficientError
InputCountInsufficientError
Natural
utxoCount Natural
outputCount
| Natural
utxoCount Natural -> Natural -> Bool
forall a. Ord a => a -> a -> Bool
<= Integer -> Natural
forall a b. (Integral a, Num b) => a -> b
fromIntegral Integer
inputCountMax =
InputsExhaustedError -> CoinSelectionError
InputsExhausted
InputsExhaustedError
InputsExhaustedError
| Bool
otherwise =
InputLimitExceededError -> CoinSelectionError
InputLimitExceeded (InputLimitExceededError -> CoinSelectionError)
-> InputLimitExceededError -> CoinSelectionError
forall a b. (a -> b) -> a -> b
$
Word16 -> InputLimitExceededError
InputLimitExceededError (Word16 -> InputLimitExceededError)
-> Word16 -> InputLimitExceededError
forall a b. (a -> b) -> a -> b
$
Integer -> Word16
forall a b. (Integral a, Num b) => a -> b
fromIntegral Integer
inputCountMax
amountAvailable :: Coin
amountAvailable =
CoinMap i -> Coin
forall a. CoinMap a -> Coin
coinMapValue (CoinMap i -> Coin) -> CoinMap i -> Coin
forall a b. (a -> b) -> a -> b
$ CoinSelectionParameters i o -> CoinMap i
forall i o. CoinSelectionParameters i o -> CoinMap i
inputsAvailable CoinSelectionParameters i o
params
amountRequested :: Coin
amountRequested =
CoinMap o -> Coin
forall a. CoinMap a -> Coin
coinMapValue (CoinMap o -> Coin) -> CoinMap o -> Coin
forall a b. (a -> b) -> a -> b
$ CoinSelectionParameters i o -> CoinMap o
forall i o. CoinSelectionParameters i o -> CoinMap o
outputsRequested CoinSelectionParameters i o
params
inputCountMax :: Integer
inputCountMax =
Word16 -> Integer
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word16 -> Integer) -> Word16 -> Integer
forall a b. (a -> b) -> a -> b
$ CoinSelectionLimit -> Word16 -> Word16
calculateLimit (CoinSelectionParameters i o -> CoinSelectionLimit
forall i o. CoinSelectionParameters i o -> CoinSelectionLimit
limit CoinSelectionParameters i o
params) (Word16 -> Word16) -> Word16 -> Word16
forall a b. (a -> b) -> a -> b
$ Natural -> Word16
forall a b. (Integral a, Num b) => a -> b
fromIntegral Natural
outputCount
outputCount :: Natural
outputCount =
Int -> Natural
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int -> Natural) -> Int -> Natural
forall a b. (a -> b) -> a -> b
$ [CoinMapEntry o] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length ([CoinMapEntry o] -> Int) -> [CoinMapEntry o] -> Int
forall a b. (a -> b) -> a -> b
$ CoinMap o -> [CoinMapEntry o]
forall a. CoinMap a -> [CoinMapEntry a]
coinMapToList (CoinMap o -> [CoinMapEntry o]) -> CoinMap o -> [CoinMapEntry o]
forall a b. (a -> b) -> a -> b
$ CoinSelectionParameters i o -> CoinMap o
forall i o. CoinSelectionParameters i o -> CoinMap o
outputsRequested CoinSelectionParameters i o
params
outputsDescending :: [CoinMapEntry o]
outputsDescending =
(CoinMapEntry o -> Down Coin)
-> [CoinMapEntry o] -> [CoinMapEntry o]
forall b a. Ord b => (a -> b) -> [a] -> [a]
L.sortOn (Coin -> Down Coin
forall a. a -> Down a
Down (Coin -> Down Coin)
-> (CoinMapEntry o -> Coin) -> CoinMapEntry o -> Down Coin
forall b c a. (b -> c) -> (a -> b) -> a -> c
. CoinMapEntry o -> Coin
forall a. CoinMapEntry a -> Coin
entryValue) ([CoinMapEntry o] -> [CoinMapEntry o])
-> [CoinMapEntry o] -> [CoinMapEntry o]
forall a b. (a -> b) -> a -> b
$ CoinMap o -> [CoinMapEntry o]
forall a. CoinMap a -> [CoinMapEntry a]
coinMapToList (CoinMap o -> [CoinMapEntry o]) -> CoinMap o -> [CoinMapEntry o]
forall a b. (a -> b) -> a -> b
$ CoinSelectionParameters i o -> CoinMap o
forall i o. CoinSelectionParameters i o -> CoinMap o
outputsRequested CoinSelectionParameters i o
params
utxoCount :: Natural
utxoCount =
Int -> Natural
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int -> Natural) -> Int -> Natural
forall a b. (a -> b) -> a -> b
$ [CoinMapEntry i] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
L.length ([CoinMapEntry i] -> Int) -> [CoinMapEntry i] -> Int
forall a b. (a -> b) -> a -> b
$ CoinMap i -> [CoinMapEntry i]
forall a. CoinMap a -> [CoinMapEntry a]
coinMapToList (CoinMap i -> [CoinMapEntry i]) -> CoinMap i -> [CoinMapEntry i]
forall a b. (a -> b) -> a -> b
$ CoinSelectionParameters i o -> CoinMap i
forall i o. CoinSelectionParameters i o -> CoinMap i
inputsAvailable CoinSelectionParameters i o
params
makeRandomSelection
:: forall i o m . MonadRandom m
=> (Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)])
-> CoinMapEntry o
-> MaybeT m (Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)])
makeRandomSelection :: forall i o (m :: * -> *).
MonadRandom m =>
(Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)])
-> CoinMapEntry o
-> MaybeT
m (Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)])
makeRandomSelection
(Integer
inputCountRemaining, CoinMap i
utxoRemaining, [([CoinMapEntry i], CoinMapEntry o)]
existingSelections) CoinMapEntry o
txout = do
([CoinMapEntry i]
utxoSelected, CoinMap i
utxoRemaining') <- ([CoinMapEntry i], CoinMap i)
-> MaybeT m ([CoinMapEntry i], CoinMap i)
coverRandomly ([], CoinMap i
utxoRemaining)
(Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)])
-> MaybeT
m (Integer, CoinMap i, [([CoinMapEntry i], CoinMapEntry o)])
forall a. a -> MaybeT m a
forall (m :: * -> *) a. Monad m => a -> m a
return
( Integer
inputCountRemaining Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Int -> Integer
forall a b. (Integral a, Num b) => a -> b
fromIntegral ([CoinMapEntry i] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
L.length [CoinMapEntry i]
utxoSelected)
, CoinMap i
utxoRemaining'
, ([CoinMapEntry i]
utxoSelected, CoinMapEntry o
txout) ([CoinMapEntry i], CoinMapEntry o)
-> [([CoinMapEntry i], CoinMapEntry o)]
-> [([CoinMapEntry i], CoinMapEntry o)]
forall a. a -> [a] -> [a]
: [([CoinMapEntry i], CoinMapEntry o)]
existingSelections
)
where
coverRandomly
:: ([CoinMapEntry i], CoinMap i)
-> MaybeT m ([CoinMapEntry i], CoinMap i)
coverRandomly :: ([CoinMapEntry i], CoinMap i)
-> MaybeT m ([CoinMapEntry i], CoinMap i)
coverRandomly ([CoinMapEntry i]
selected, CoinMap i
remaining)
| [CoinMapEntry i] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
L.length [CoinMapEntry i]
selected Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Integer -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Integer
inputCountRemaining =
m (Maybe ([CoinMapEntry i], CoinMap i))
-> MaybeT m ([CoinMapEntry i], CoinMap i)
forall (m :: * -> *) a. m (Maybe a) -> MaybeT m a
MaybeT (m (Maybe ([CoinMapEntry i], CoinMap i))
-> MaybeT m ([CoinMapEntry i], CoinMap i))
-> m (Maybe ([CoinMapEntry i], CoinMap i))
-> MaybeT m ([CoinMapEntry i], CoinMap i)
forall a b. (a -> b) -> a -> b
$ Maybe ([CoinMapEntry i], CoinMap i)
-> m (Maybe ([CoinMapEntry i], CoinMap i))
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe ([CoinMapEntry i], CoinMap i)
forall a. Maybe a
Nothing
| [CoinMapEntry i] -> Coin
forall i. [CoinMapEntry i] -> Coin
sumEntries [CoinMapEntry i]
selected Coin -> Coin -> Bool
forall a. Ord a => a -> a -> Bool
>= TargetRange -> Coin
targetMin (CoinMapEntry o -> TargetRange
forall o. CoinMapEntry o -> TargetRange
mkTargetRange CoinMapEntry o
txout) =
m (Maybe ([CoinMapEntry i], CoinMap i))
-> MaybeT m ([CoinMapEntry i], CoinMap i)
forall (m :: * -> *) a. m (Maybe a) -> MaybeT m a
MaybeT (m (Maybe ([CoinMapEntry i], CoinMap i))
-> MaybeT m ([CoinMapEntry i], CoinMap i))
-> m (Maybe ([CoinMapEntry i], CoinMap i))
-> MaybeT m ([CoinMapEntry i], CoinMap i)
forall a b. (a -> b) -> a -> b
$ Maybe ([CoinMapEntry i], CoinMap i)
-> m (Maybe ([CoinMapEntry i], CoinMap i))
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe ([CoinMapEntry i], CoinMap i)
-> m (Maybe ([CoinMapEntry i], CoinMap i)))
-> Maybe ([CoinMapEntry i], CoinMap i)
-> m (Maybe ([CoinMapEntry i], CoinMap i))
forall a b. (a -> b) -> a -> b
$ ([CoinMapEntry i], CoinMap i)
-> Maybe ([CoinMapEntry i], CoinMap i)
forall a. a -> Maybe a
Just ([CoinMapEntry i]
selected, CoinMap i
remaining)
| Bool
otherwise =
m (Maybe (CoinMapEntry i, CoinMap i))
-> MaybeT m (CoinMapEntry i, CoinMap i)
forall (m :: * -> *) a. m (Maybe a) -> MaybeT m a
MaybeT (CoinMap i -> m (Maybe (CoinMapEntry i, CoinMap i))
forall (m :: * -> *) a.
MonadRandom m =>
CoinMap a -> m (Maybe (CoinMapEntry a, CoinMap a))
coinMapRandomEntry CoinMap i
remaining) MaybeT m (CoinMapEntry i, CoinMap i)
-> ((CoinMapEntry i, CoinMap i)
-> MaybeT m ([CoinMapEntry i], CoinMap i))
-> MaybeT m ([CoinMapEntry i], CoinMap i)
forall a b. MaybeT m a -> (a -> MaybeT m b) -> MaybeT m b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \(CoinMapEntry i
picked, CoinMap i
remaining') ->
([CoinMapEntry i], CoinMap i)
-> MaybeT m ([CoinMapEntry i], CoinMap i)
coverRandomly (CoinMapEntry i
picked CoinMapEntry i -> [CoinMapEntry i] -> [CoinMapEntry i]
forall a. a -> [a] -> [a]
: [CoinMapEntry i]
selected, CoinMap i
remaining')
improveSelection
:: forall i o m . (MonadRandom m, Ord i, Ord o)
=> (Integer, CoinSelection i o, CoinMap i)
-> ([CoinMapEntry i], CoinMapEntry o)
-> m (Integer, CoinSelection i o, CoinMap i)
improveSelection :: forall i o (m :: * -> *).
(MonadRandom m, Ord i, Ord o) =>
(Integer, CoinSelection i o, CoinMap i)
-> ([CoinMapEntry i], CoinMapEntry o)
-> m (Integer, CoinSelection i o, CoinMap i)
improveSelection (Integer
maxN0, CoinSelection i o
selection, CoinMap i
utxo0) ([CoinMapEntry i]
inps0, CoinMapEntry o
txout) = do
(Integer
maxN, [CoinMapEntry i]
inps, CoinMap i
utxo) <- (Integer, [CoinMapEntry i], CoinMap i)
-> m (Integer, [CoinMapEntry i], CoinMap i)
improve (Integer
maxN0, [CoinMapEntry i]
inps0, CoinMap i
utxo0)
(Integer, CoinSelection i o, CoinMap i)
-> m (Integer, CoinSelection i o, CoinMap i)
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return
( Integer
maxN
, CoinSelection i o
selection CoinSelection i o -> CoinSelection i o -> CoinSelection i o
forall a. Semigroup a => a -> a -> a
<> CoinSelection
{ inputs :: CoinMap i
inputs = [CoinMapEntry i] -> CoinMap i
forall a. Ord a => [CoinMapEntry a] -> CoinMap a
coinMapFromList [CoinMapEntry i]
inps
, outputs :: CoinMap o
outputs = [CoinMapEntry o] -> CoinMap o
forall a. Ord a => [CoinMapEntry a] -> CoinMap a
coinMapFromList [CoinMapEntry o
txout]
, change :: [Coin]
change = CoinMapEntry o -> [CoinMapEntry i] -> [Coin]
forall o i. CoinMapEntry o -> [CoinMapEntry i] -> [Coin]
mkChange CoinMapEntry o
txout [CoinMapEntry i]
inps
}
, CoinMap i
utxo
)
where
target :: TargetRange
target = CoinMapEntry o -> TargetRange
forall o. CoinMapEntry o -> TargetRange
mkTargetRange CoinMapEntry o
txout
improve
:: (Integer, [CoinMapEntry i], CoinMap i)
-> m (Integer, [CoinMapEntry i], CoinMap i)
improve :: (Integer, [CoinMapEntry i], CoinMap i)
-> m (Integer, [CoinMapEntry i], CoinMap i)
improve (Integer
maxN, [CoinMapEntry i]
inps, CoinMap i
utxo)
| Integer
maxN Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
>= Integer
1 Bool -> Bool -> Bool
&& [CoinMapEntry i] -> Coin
forall i. [CoinMapEntry i] -> Coin
sumEntries [CoinMapEntry i]
inps Coin -> Coin -> Bool
forall a. Ord a => a -> a -> Bool
< TargetRange -> Coin
targetAim TargetRange
target = do
CoinMap i -> m (Maybe (CoinMapEntry i, CoinMap i))
forall (m :: * -> *) a.
MonadRandom m =>
CoinMap a -> m (Maybe (CoinMapEntry a, CoinMap a))
coinMapRandomEntry CoinMap i
utxo m (Maybe (CoinMapEntry i, CoinMap i))
-> (Maybe (CoinMapEntry i, CoinMap i)
-> m (Integer, [CoinMapEntry i], CoinMap i))
-> m (Integer, [CoinMapEntry i], CoinMap i)
forall a b. m a -> (a -> m b) -> m b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Maybe (CoinMapEntry i, CoinMap i)
Nothing ->
(Integer, [CoinMapEntry i], CoinMap i)
-> m (Integer, [CoinMapEntry i], CoinMap i)
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (Integer
maxN, [CoinMapEntry i]
inps, CoinMap i
utxo)
Just (CoinMapEntry i
io, CoinMap i
utxo') | CoinMapEntry i -> [CoinMapEntry i] -> Bool
isImprovement CoinMapEntry i
io [CoinMapEntry i]
inps -> do
let inps' :: [CoinMapEntry i]
inps' = CoinMapEntry i
io CoinMapEntry i -> [CoinMapEntry i] -> [CoinMapEntry i]
forall a. a -> [a] -> [a]
: [CoinMapEntry i]
inps
let maxN' :: Integer
maxN' = Integer
maxN Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Integer
1
(Integer, [CoinMapEntry i], CoinMap i)
-> m (Integer, [CoinMapEntry i], CoinMap i)
improve (Integer
maxN', [CoinMapEntry i]
inps', CoinMap i
utxo')
Just (CoinMapEntry i, CoinMap i)
_ ->
(Integer, [CoinMapEntry i], CoinMap i)
-> m (Integer, [CoinMapEntry i], CoinMap i)
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (Integer
maxN, [CoinMapEntry i]
inps, CoinMap i
utxo)
| Bool
otherwise =
(Integer, [CoinMapEntry i], CoinMap i)
-> m (Integer, [CoinMapEntry i], CoinMap i)
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (Integer
maxN, [CoinMapEntry i]
inps, CoinMap i
utxo)
isImprovement :: CoinMapEntry i -> [CoinMapEntry i] -> Bool
isImprovement :: CoinMapEntry i -> [CoinMapEntry i] -> Bool
isImprovement CoinMapEntry i
io [CoinMapEntry i]
selected =
let
condA :: Bool
condA =
[CoinMapEntry i] -> Coin
forall i. [CoinMapEntry i] -> Coin
sumEntries (CoinMapEntry i
io CoinMapEntry i -> [CoinMapEntry i] -> [CoinMapEntry i]
forall a. a -> [a] -> [a]
: [CoinMapEntry i]
selected) Coin -> Coin -> Bool
forall a. Ord a => a -> a -> Bool
< TargetRange -> Coin
targetMax TargetRange
target
condB :: Bool
condB =
Coin
distanceA Coin -> Coin -> Bool
forall a. Ord a => a -> a -> Bool
< Coin
distanceB
where
distanceA :: Coin
distanceA = Coin -> Coin -> Coin
C.distance
(TargetRange -> Coin
targetAim TargetRange
target)
([CoinMapEntry i] -> Coin
forall i. [CoinMapEntry i] -> Coin
sumEntries (CoinMapEntry i
io CoinMapEntry i -> [CoinMapEntry i] -> [CoinMapEntry i]
forall a. a -> [a] -> [a]
: [CoinMapEntry i]
selected))
distanceB :: Coin
distanceB = Coin -> Coin -> Coin
C.distance
(TargetRange -> Coin
targetAim TargetRange
target)
([CoinMapEntry i] -> Coin
forall i. [CoinMapEntry i] -> Coin
sumEntries [CoinMapEntry i]
selected)
in
Bool
condA Bool -> Bool -> Bool
&& Bool
condB
data TargetRange = TargetRange
{ TargetRange -> Coin
targetMin :: Coin
, TargetRange -> Coin
targetAim :: Coin
, TargetRange -> Coin
targetMax :: Coin
}
mkTargetRange :: CoinMapEntry o -> TargetRange
mkTargetRange :: forall o. CoinMapEntry o -> TargetRange
mkTargetRange (CoinMapEntry o
_ Coin
c) = TargetRange
{ targetMin :: Coin
targetMin = Coin
c
, targetAim :: Coin
targetAim = Coin
c Coin -> Coin -> Coin
`C.add` Coin
c
, targetMax :: Coin
targetMax = Coin
c Coin -> Coin -> Coin
`C.add` Coin
c Coin -> Coin -> Coin
`C.add` Coin
c
}
mkChange :: CoinMapEntry o -> [CoinMapEntry i] -> [Coin]
mkChange :: forall o i. CoinMapEntry o -> [CoinMapEntry i] -> [Coin]
mkChange (CoinMapEntry o
_ Coin
out) [CoinMapEntry i]
inps =
case Maybe Coin
difference of
Maybe Coin
Nothing ->
[Char] -> [Coin]
forall a. HasCallStack => [Char] -> a
error ([Char] -> [Coin]) -> [Char] -> [Coin]
forall a b. (a -> b) -> a -> b
$ [[Char]] -> [Char]
forall a. Monoid a => [a] -> a
mconcat
[ [Char]
"mkChange: "
, [Char]
"output must be less than or equal to sum of inputs"
]
Just Coin
d | Coin -> Bool
C.isZero Coin
d ->
[]
Just Coin
d ->
[Coin
d]
where
difference :: Maybe Coin
difference = [CoinMapEntry i] -> Coin
forall i. [CoinMapEntry i] -> Coin
sumEntries [CoinMapEntry i]
inps Coin -> Coin -> Maybe Coin
`C.sub` Coin
out
sumEntries :: [CoinMapEntry i] -> Coin
sumEntries :: forall i. [CoinMapEntry i] -> Coin
sumEntries = [Coin] -> Coin
forall a. Monoid a => [a] -> a
mconcat ([Coin] -> Coin)
-> ([CoinMapEntry i] -> [Coin]) -> [CoinMapEntry i] -> Coin
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (CoinMapEntry i -> Coin) -> [CoinMapEntry i] -> [Coin]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap CoinMapEntry i -> Coin
forall a. CoinMapEntry a -> Coin
entryValue