Skip to content

Commit e08d308

Browse files
authored
removes coin selection and conduit dependency (#26)
* removes coin selection and conduit dependency * remove rest of coin selection tools
1 parent e2fa879 commit e08d308

File tree

3 files changed

+15
-276
lines changed

3 files changed

+15
-276
lines changed

bitcoin.cabal

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ library
101101
, bytes >=0.17
102102
, bytestring >=0.10.10.0
103103
, cereal >=0.5.8
104-
, conduit >=1.3.1.2
105104
, containers >=0.6.2.1
106105
, cryptonite >=0.26
107106
, deepseq >=1.4.4.0
@@ -158,7 +157,6 @@ test-suite spec
158157
, bytes >=0.17
159158
, bytestring >=0.10.10.0
160159
, cereal >=0.5.8
161-
, conduit >=1.3.1.2
162160
, containers >=0.6.2.1
163161
, cryptonite >=0.26
164162
, deepseq >=1.4.4.0

package.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ dependencies:
2727
- bytes >= 0.17
2828
- bytestring >= 0.10.10.0
2929
- cereal >= 0.5.8
30-
- conduit >= 1.3.1.2
3130
- containers >= 0.6.2.1
3231
- cryptonite >= 0.26
3332
- deepseq >= 1.4.4.0

src/Bitcoin/Transaction/Builder.hs

Lines changed: 15 additions & 273 deletions
Original file line numberDiff line numberDiff line change
@@ -25,44 +25,8 @@ module Bitcoin.Transaction.Builder (
2525
mergeTxInput,
2626
findSigInput,
2727
verifyStdInput,
28-
29-
-- * Coin Selection
30-
Coin (..),
31-
chooseCoins,
32-
chooseCoinsSink,
33-
chooseMSCoins,
34-
chooseMSCoinsSink,
35-
countMulSig,
36-
greedyAddSink,
37-
guessTxFee,
38-
guessMSTxFee,
39-
guessTxSize,
40-
guessMSSize,
4128
) where
4229

43-
import Control.Applicative ((<|>))
44-
import Control.Arrow (first)
45-
import Control.Monad (foldM, unless)
46-
import Control.Monad.Identity (runIdentity)
47-
import Crypto.Secp256k1
48-
import qualified Data.ByteString as B
49-
import Data.Bytes.Get
50-
import Data.Bytes.Put
51-
import Data.Bytes.Serial
52-
import Data.Conduit (
53-
ConduitT,
54-
Void,
55-
await,
56-
runConduit,
57-
(.|),
58-
)
59-
import Data.Conduit.List (sourceList)
60-
import Data.Either (fromRight)
61-
import Data.List (nub)
62-
import Data.Maybe (catMaybes, fromJust, isJust)
63-
import Data.String.Conversions (cs)
64-
import Data.Text (Text)
65-
import Data.Word (Word64)
6630
import Bitcoin.Address
6731
import Bitcoin.Crypto.Hash (Hash256, addressHash)
6832
import Bitcoin.Crypto.Signature
@@ -84,243 +48,21 @@ import Bitcoin.Transaction.Segwit (
8448
viewWitnessProgram,
8549
)
8650
import Bitcoin.Util
87-
88-
89-
-- | Any type can be used as a Coin if it can provide a value in Satoshi.
90-
-- The value is used in coin selection algorithms.
91-
class Coin c where
92-
coinValue :: c -> Word64
93-
94-
95-
-- | Coin selection algorithm for normal (non-multisig) transactions. This
96-
-- function returns the selected coins together with the amount of change to
97-
-- send back to yourself, taking the fee into account.
98-
chooseCoins ::
99-
Coin c =>
100-
-- | value to send
101-
Word64 ->
102-
-- | fee per byte
103-
Word64 ->
104-
-- | number of outputs (including change)
105-
Int ->
106-
-- | try to find better solutions
107-
Bool ->
108-
-- | list of ordered coins to choose from
109-
[c] ->
110-
-- | coin selection and change
111-
Either String ([c], Word64)
112-
chooseCoins target fee nOut continue coins =
113-
runIdentity . runConduit $
114-
sourceList coins .| chooseCoinsSink target fee nOut continue
115-
116-
117-
-- | Coin selection algorithm for normal (non-multisig) transactions. This
118-
-- function returns the selected coins together with the amount of change to
119-
-- send back to yourself, taking the fee into account. This version uses a Sink
120-
-- for conduit-based coin selection.
121-
chooseCoinsSink ::
122-
(Monad m, Coin c) =>
123-
-- | value to send
124-
Word64 ->
125-
-- | fee per byte
126-
Word64 ->
127-
-- | number of outputs (including change)
128-
Int ->
129-
-- | try to find better solution
130-
Bool ->
131-
-- | coin selection and change
132-
ConduitT c Void m (Either String ([c], Word64))
133-
chooseCoinsSink target fee nOut continue
134-
| target > 0 =
135-
maybeToEither err
136-
<$> greedyAddSink target (guessTxFee fee nOut) continue
137-
| otherwise = return $ Left "chooseCoins: Target must be > 0"
138-
where
139-
err = "chooseCoins: No solution found"
140-
141-
142-
-- | Coin selection algorithm for multisig transactions. This function returns
143-
-- the selected coins together with the amount of change to send back to
144-
-- yourself, taking the fee into account. This function assumes all the coins
145-
-- are script hash outputs that send funds to a multisignature address.
146-
chooseMSCoins ::
147-
Coin c =>
148-
-- | value to send
149-
Word64 ->
150-
-- | fee per byte
151-
Word64 ->
152-
-- | m of n multisig
153-
(Int, Int) ->
154-
-- | number of outputs (including change)
155-
Int ->
156-
-- | try to find better solution
157-
Bool ->
158-
[c] ->
159-
-- | coin selection change amount
160-
Either String ([c], Word64)
161-
chooseMSCoins target fee ms nOut continue coins =
162-
runIdentity . runConduit $
163-
sourceList coins .| chooseMSCoinsSink target fee ms nOut continue
164-
165-
166-
-- | Coin selection algorithm for multisig transactions. This function returns
167-
-- the selected coins together with the amount of change to send back to
168-
-- yourself, taking the fee into account. This function assumes all the coins
169-
-- are script hash outputs that send funds to a multisignature address. This
170-
-- version uses a Sink if you need conduit-based coin selection.
171-
chooseMSCoinsSink ::
172-
(Monad m, Coin c) =>
173-
-- | value to send
174-
Word64 ->
175-
-- | fee per byte
176-
Word64 ->
177-
-- | m of n multisig
178-
(Int, Int) ->
179-
-- | number of outputs (including change)
180-
Int ->
181-
-- | try to find better solution
182-
Bool ->
183-
-- | coin selection and change
184-
ConduitT c Void m (Either String ([c], Word64))
185-
chooseMSCoinsSink target fee ms nOut continue
186-
| target > 0 =
187-
maybeToEither err
188-
<$> greedyAddSink target (guessMSTxFee fee ms nOut) continue
189-
| otherwise = return $ Left "chooseMSCoins: Target must be > 0"
190-
where
191-
err = "chooseMSCoins: No solution found"
192-
193-
194-
-- | Select coins greedily by starting from an empty solution. If the 'continue'
195-
-- flag is set, the algorithm will try to find a better solution in the stream
196-
-- after a solution is found. If the next solution found is not strictly better
197-
-- than the previously found solution, the algorithm stops and returns the
198-
-- previous solution. If the continue flag is not set, the algorithm will return
199-
-- the first solution it finds in the stream.
200-
greedyAddSink ::
201-
(Monad m, Coin c) =>
202-
-- | value to send
203-
Word64 ->
204-
-- | coin count to fee function
205-
(Int -> Word64) ->
206-
-- | try to find better solutions
207-
Bool ->
208-
-- | coin selection and change
209-
ConduitT c Void m (Maybe ([c], Word64))
210-
greedyAddSink target guessFee continue =
211-
go [] 0 [] 0
212-
where
213-
-- The goal is the value we must reach (including the fee) for a certain
214-
-- amount of selected coins.
215-
goal c = target + guessFee c
216-
go acc aTot ps pTot =
217-
await >>= \case
218-
-- A coin is available in the stream
219-
Just coin -> do
220-
let val = coinValue coin
221-
-- We have reached the goal using this coin
222-
if val + aTot >= goal (length acc + 1)
223-
then -- If we want to continue searching for better solutions
224-
225-
if continue
226-
then -- This solution is the first one or
227-
-- This solution is better than the previous one
228-
229-
if pTot == 0 || val + aTot < pTot
230-
then -- Continue searching for better solutions in the stream
231-
go [] 0 (coin : acc) (val + aTot)
232-
else -- Otherwise, we stop here and return the previous
233-
-- solution
234-
return $ Just (ps, pTot - goal (length ps))
235-
else -- Otherwise, return this solution
236-
237-
return $
238-
Just (coin : acc, val + aTot - goal (length acc + 1))
239-
else -- We have not yet reached the goal. Add the coin to the
240-
-- accumulator
241-
go (coin : acc) (val + aTot) ps pTot
242-
-- We reached the end of the stream
243-
Nothing ->
244-
return $
245-
if null ps
246-
then -- If no solution was found, return Nothing
247-
Nothing
248-
else -- If we have a solution, return it
249-
Just (ps, pTot - goal (length ps))
250-
251-
252-
-- | Estimate tranasction fee to pay based on transaction size estimation.
253-
guessTxFee :: Word64 -> Int -> Int -> Word64
254-
guessTxFee byteFee nOut nIn =
255-
byteFee * fromIntegral (guessTxSize nIn [] nOut 0)
256-
257-
258-
-- | Same as 'guessTxFee' but for multisig transactions.
259-
guessMSTxFee :: Word64 -> (Int, Int) -> Int -> Int -> Word64
260-
guessMSTxFee byteFee ms nOut nIn =
261-
byteFee * fromIntegral (guessTxSize 0 (replicate nIn ms) nOut 0)
262-
263-
264-
-- | Computes an upper bound on the size of a transaction based on some known
265-
-- properties of the transaction.
266-
guessTxSize ::
267-
-- | number of regular transaction inputs
268-
Int ->
269-
-- | multisig m of n for each input
270-
[(Int, Int)] ->
271-
-- | number of P2PKH outputs
272-
Int ->
273-
-- | number of P2SH outputs
274-
Int ->
275-
-- | upper bound on transaction size
276-
Int
277-
guessTxSize pki msi pkout msout =
278-
8 + inpLen + inp + outLen + out
279-
where
280-
inpLen =
281-
B.length
282-
. runPutS
283-
. serialize
284-
. VarInt
285-
. fromIntegral
286-
$ length msi + pki
287-
outLen =
288-
B.length
289-
. runPutS
290-
. serialize
291-
. VarInt
292-
. fromIntegral
293-
$ pkout + msout
294-
inp = pki * 148 + sum (map guessMSSize msi)
295-
-- (20: hash160) + (5: opcodes) +
296-
-- (1: script len) + (8: Word64)
297-
out =
298-
pkout * 34
299-
+
300-
-- (20: hash160) + (3: opcodes) +
301-
-- (1: script len) + (8: Word64)
302-
msout * 32
303-
304-
305-
-- | Size of a multisig P2SH input.
306-
guessMSSize :: (Int, Int) -> Int
307-
guessMSSize (m, n) =
308-
-- OutPoint (36) + Sequence (4) + Script
309-
40
310-
+ fromIntegral (B.length $ runPutS . serialize $ VarInt $ fromIntegral scp)
311-
+ scp
312-
where
313-
-- OP_M + n*PubKey + OP_N + OP_CHECKMULTISIG
314-
315-
rdm =
316-
fromIntegral
317-
. B.length
318-
. runPutS
319-
. serialize
320-
. opPushData
321-
$ B.replicate (n * 34 + 3) 0
322-
-- Redeem + m*sig + OP_0
323-
scp = rdm + m * 73 + 1
51+
import Control.Applicative ((<|>))
52+
import Control.Arrow (first)
53+
import Control.Monad (foldM, unless)
54+
import Control.Monad.Identity (runIdentity)
55+
import Crypto.Secp256k1
56+
import qualified Data.ByteString as B
57+
import Data.Bytes.Get
58+
import Data.Bytes.Put
59+
import Data.Bytes.Serial
60+
import Data.Either (fromRight)
61+
import Data.List (nub)
62+
import Data.Maybe (catMaybes, fromJust, isJust)
63+
import Data.String.Conversions (cs)
64+
import Data.Text (Text)
65+
import Data.Word (Word64)
32466

32567

32668
{- Build a new Tx -}

0 commit comments

Comments
 (0)