Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Commit 6d8f7d9

Browse files
authored
Merge pull request #3449 from input-output-hk/feature/cbr-380-shuffle-tx-outs
[CBR-380] Shuffle tx outputs after coin selection
2 parents e13f756 + e8e027f commit 6d8f7d9

File tree

5 files changed

+76
-43
lines changed

5 files changed

+76
-43
lines changed

wallet-new/src/Cardano/Wallet/Kernel/CoinSelection/FromGeneric.hs

+6-4
Original file line numberDiff line numberDiff line change
@@ -185,16 +185,19 @@ feeOptions CoinSelectionOptions{..} = FeeOptions{
185185
-- multisignature transactions, etc.
186186
mkStdTx :: Monad m
187187
=> Core.ProtocolMagic
188+
-> (forall a. NonEmpty a -> m (NonEmpty a))
189+
-- ^ Shuffle function
188190
-> (Core.Address -> Either e Core.SafeSigner)
191+
-- ^ Signer for each input of the transaction
189192
-> NonEmpty (Core.TxIn, Core.TxOutAux)
190193
-- ^ Selected inputs
191194
-> NonEmpty Core.TxOutAux
192195
-- ^ Selected outputs
193196
-> [Core.TxOutAux]
194-
-- ^ A list of change addresess, in the form of 'TxOutAux'(s).
197+
-- ^ Change outputs
195198
-> m (Either e Core.TxAux)
196-
mkStdTx pm hdwSigners inps outs change = do
197-
let allOuts = foldl' (flip NE.cons) outs change
199+
mkStdTx pm shuffle hdwSigners inps outs change = do
200+
allOuts <- shuffle $ foldl' (flip NE.cons) outs change
198201
return $ Core.makeMPubKeyTxAddrs pm hdwSigners (fmap repack inps) allOuts
199202
where
200203
-- Repack a utxo-derived tuple into a format suitable for
@@ -250,7 +253,6 @@ runCoinSelT opts pickUtxo policy request utxo = do
250253
originalOuts = case outs of
251254
[] -> error "runCoinSelT: empty list of outputs"
252255
o:os -> o :| os
253-
-- TODO: We should shuffle allOuts CBR-380
254256
return . Right $ CoinSelFinalResult allInps
255257
originalOuts
256258
(concatMap coinSelChange css)

wallet-new/src/Cardano/Wallet/Kernel/Transactions.hs

+38-37
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import Cardano.Wallet.Kernel.Pending (newPending)
6161
import Cardano.Wallet.Kernel.Read (getWalletSnapshot)
6262
import Cardano.Wallet.Kernel.Types (AccountId (..),
6363
RawResolvedTx (..), WalletId (..))
64+
import Cardano.Wallet.Kernel.Util (shuffleNE)
6465
import Cardano.Wallet.Kernel.Util.Core (paymentAmount, utxoBalance,
6566
utxoRestrictToInputs)
6667
import Cardano.Wallet.WalletLayer.Kernel.Conv (exceptT)
@@ -198,7 +199,7 @@ newTransaction ActiveWallet{..} spendingPassword options accountId payees = runE
198199
(WalletIdHdRnd $ accountId ^. hdAccountIdParent)
199200
(walletPassive ^. walletKeystore)
200201
let signAddress = mkSigner spendingPassword mbEsk snapshot
201-
mkTx = mkStdTx walletProtocolMagic signAddress
202+
mkTx = mkStdTx walletProtocolMagic shuffleNE signAddress
202203

203204
txAux <- withExceptT NewTransactionErrorSignTxFailed $ ExceptT $
204205
mkTx inputs outputs changeAddresses
@@ -208,43 +209,43 @@ newTransaction ActiveWallet{..} spendingPassword options accountId payees = runE
208209
txMeta <- createNewMeta accountId txId inputs (toaOut <$> outputs)
209210
return (txAux, txMeta, availableUtxo)
210211
where
211-
-- Generate an initial seed for the random generator using the hash of
212-
-- the payees, which ensure that the coin selection (and the fee estimation)
213-
-- is \"pseudo deterministic\" and replicable.
214-
newEnvironment :: IO Env
215-
newEnvironment =
216-
let initialSeed = V.fromList . map fromIntegral
217-
. B.unpack
218-
. encodeUtf8 @Text @ByteString
219-
. sformat build
220-
$ hash payees
221-
in Env <$> initialize initialSeed
222-
223-
toTxOut :: (Address, Coin) -> TxOutAux
224-
toTxOut (a, c) = TxOutAux (TxOut a c)
225-
226-
-- | Generates the list of change outputs from a list of change coins.
227-
genChangeOuts :: MonadIO m
228-
=> [Coin]
229-
-> ExceptT Kernel.CreateAddressError m [TxOutAux]
230-
genChangeOuts css = forM css $ \change -> do
231-
changeAddr <- genChangeAddr
232-
return TxOutAux {
233-
toaOut = TxOut {
234-
txOutAddress = changeAddr
235-
, txOutValue = change
236-
}
212+
-- Generate an initial seed for the random generator using the hash of
213+
-- the payees, which ensure that the coin selection (and the fee estimation)
214+
-- is \"pseudo deterministic\" and replicable.
215+
newEnvironment :: IO Env
216+
newEnvironment =
217+
let initialSeed = V.fromList . map fromIntegral
218+
. B.unpack
219+
. encodeUtf8 @Text @ByteString
220+
. sformat build
221+
$ hash payees
222+
in Env <$> initialize initialSeed
223+
224+
toTxOut :: (Address, Coin) -> TxOutAux
225+
toTxOut (a, c) = TxOutAux (TxOut a c)
226+
227+
-- | Generates the list of change outputs from a list of change coins.
228+
genChangeOuts :: MonadIO m
229+
=> [Coin]
230+
-> ExceptT Kernel.CreateAddressError m [TxOutAux]
231+
genChangeOuts css = forM css $ \change -> do
232+
changeAddr <- genChangeAddr
233+
return TxOutAux {
234+
toaOut = TxOut {
235+
txOutAddress = changeAddr
236+
, txOutValue = change
237237
}
238-
239-
-- | Monadic computation to generate a new change 'Address'. This will
240-
-- run after coin selection, when we create the final transaction as
241-
-- part of 'mkTx'.
242-
genChangeAddr :: MonadIO m
243-
=> ExceptT Kernel.CreateAddressError m Address
244-
genChangeAddr = ExceptT $ liftIO $
245-
Kernel.createAddress spendingPassword
246-
(AccountIdHdRnd accountId)
247-
walletPassive
238+
}
239+
240+
-- | Monadic computation to generate a new change 'Address'. This will
241+
-- run after coin selection, when we create the final transaction as
242+
-- part of 'mkTx'.
243+
genChangeAddr :: MonadIO m
244+
=> ExceptT Kernel.CreateAddressError m Address
245+
genChangeAddr = ExceptT $ liftIO $
246+
Kernel.createAddress spendingPassword
247+
(AccountIdHdRnd accountId)
248+
walletPassive
248249

249250
createNewMeta :: HdAccountId -> TxId -> NonEmpty (TxIn, TxOutAux) -> NonEmpty TxOut -> ExceptT NewTransactionError IO TxMeta
250251
createNewMeta hdId txId inp out = do

wallet-new/src/Cardano/Wallet/Kernel/Util.hs

+30
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ module Cardano.Wallet.Kernel.Util (
66
-- * Lists
77
at
88
, neHead
9+
, shuffle
10+
, shuffleNE
911
-- * Maps and sets
1012
, disjoint
1113
, withoutKeys
@@ -34,9 +36,14 @@ module Cardano.Wallet.Kernel.Util (
3436
import Universum
3537

3638
import Control.Monad.Except (MonadError (..))
39+
import Crypto.Number.Generate (generateBetween)
40+
import qualified Data.List.NonEmpty as NE
3741
import qualified Data.Map.Merge.Strict as Map.Merge
3842
import qualified Data.Map.Strict as Map
3943
import qualified Data.Set as Set
44+
import qualified Data.Vector as V
45+
import Data.Vector.Mutable (IOVector)
46+
import qualified Data.Vector.Mutable as MV
4047
import Pos.Core.Chrono
4148
import qualified Test.QuickCheck as QC
4249

@@ -53,6 +60,29 @@ at (_:xs) i = at xs (i - 1)
5360
neHead :: Lens' (NonEmpty a) a
5461
neHead f (x :| xs) = (:| xs) <$> f x
5562

63+
shuffle :: [a] -> IO [a]
64+
shuffle = modifyInPlace $ \v -> do
65+
let (lo, hi) = (0, MV.length v - 1)
66+
forM_ [lo .. hi] $ \i -> do
67+
j <- fromInteger <$> generateBetween (fromIntegral lo) (fromIntegral hi)
68+
swapElems v i j
69+
where
70+
swapElems :: IOVector a -> Int -> Int -> IO ()
71+
swapElems v i j = do
72+
x <- MV.read v i
73+
y <- MV.read v j
74+
MV.write v i y
75+
MV.write v j x
76+
77+
shuffleNE :: NonEmpty a -> IO (NonEmpty a)
78+
shuffleNE = fmap NE.fromList . shuffle . NE.toList
79+
80+
modifyInPlace :: forall a. (IOVector a -> IO ()) -> [a] -> IO [a]
81+
modifyInPlace f xs = do
82+
v' <- V.thaw $ V.fromList xs
83+
f v'
84+
V.toList <$> V.freeze v'
85+
5686
{-------------------------------------------------------------------------------
5787
Maps and sets
5888
-------------------------------------------------------------------------------}

wallet-new/test/unit/Test/Spec/CoinSelection.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,7 @@ mkTx :: Core.ProtocolMagic
475475
-> [Core.TxOutAux]
476476
-- ^ A list of change addresess, in the form of 'TxOutAux'(s).
477477
-> Gen (Either CoinSelHardErr Core.TxAux)
478-
mkTx pm key = mkStdTx pm (\_addr -> Right (fakeSigner key))
478+
mkTx pm key = mkStdTx pm return (\_addr -> Right (fakeSigner key))
479479

480480

481481
payRestrictInputsTo :: Word64

wallet-new/test/unit/Wallet/Inductive/Generator.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import Data.Tree
2828
import Pos.Core.Chrono
2929
import Test.QuickCheck
3030

31-
import Cardano.Wallet.Kernel.Util
31+
import Cardano.Wallet.Kernel.Util (Probability, toss, withoutKeys)
3232
import UTxO.DSL
3333
import UTxO.Generator
3434
import Wallet.Inductive

0 commit comments

Comments
 (0)