From 42a494f762bde95a5a2d3f3450a7df3020be6500 Mon Sep 17 00:00:00 2001 From: Josh Rickmar Date: Mon, 11 Aug 2014 21:43:59 -0500 Subject: [PATCH] Serialize transaction creation. Fixes #120. --- rpcserver.go | 2 +- wallet.go | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/rpcserver.go b/rpcserver.go index 9949290..5db8612 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -2217,7 +2217,7 @@ func sendPairs(w *Wallet, chainSvr *chain.Client, cmd btcjson.Cmd, // Create transaction, replying with an error if the creation // was not successful. - createdTx, err := w.txToPairs(amounts, minconf) + createdTx, err := w.CreateSimpleTx(amounts, minconf) if err != nil { switch err { case ErrNonPositiveAmount: diff --git a/wallet.go b/wallet.go index f41c84b..1ac1160 100644 --- a/wallet.go +++ b/wallet.go @@ -85,6 +85,9 @@ type Wallet struct { rescanProgress chan *RescanProgressMsg rescanFinished chan *RescanFinishedMsg + // Channel for transaction creation requests. + createTxRequests chan createTxRequest + // Channels for the keystore locker. unlockRequests chan unlockRequest lockRequests chan struct{} @@ -121,6 +124,7 @@ func newWallet(keys *keystore.Store, txs *txstore.Store) *Wallet { rescanNotifications: make(chan interface{}), rescanProgress: make(chan *RescanProgressMsg), rescanFinished: make(chan *RescanFinishedMsg), + createTxRequests: make(chan createTxRequest), unlockRequests: make(chan unlockRequest), lockRequests: make(chan struct{}), holdUnlockRequests: make(chan chan HeldUnlock), @@ -356,9 +360,10 @@ func (w *Wallet) Start(chainServer *chain.Client) { w.chainSvr = chainServer w.chainSvrLock = noopLocker{} - w.wg.Add(6) + w.wg.Add(7) go w.diskWriter() go w.handleChainNotifications() + go w.txCreator() go w.keystoreLocker() go w.rescanBatchHandler() go w.rescanProgressHandler() @@ -480,6 +485,56 @@ func (w *Wallet) syncWithChain() (err error) { return w.RescanActiveAddresses() } +type ( + createTxRequest struct { + pairs map[string]btcutil.Amount + minconf int + resp chan createTxResponse + } + createTxResponse struct { + tx *CreatedTx + err error + } +) + +// txCreator is responsible for the input selection and creation of +// transactions. These functions are the responsibility of this method +// (designed to be run as its own goroutine) since input selection must be +// serialized, or else it is possible to create double spends by choosing the +// same inputs for multiple transactions. Along with input selection, this +// method is also responsible for the signing of transactions, since we don't +// want to end up in a situation where we run out of inputs as multiple +// transactions are being created. In this situation, it would then be possible +// for both requests, rather than just one, to fail due to not enough available +// inputs. +func (w *Wallet) txCreator() { +out: + for { + select { + case txr := <-w.createTxRequests: + tx, err := w.txToPairs(txr.pairs, txr.minconf) + txr.resp <- createTxResponse{tx, err} + + case <-w.quit: + break out + } + } + w.wg.Done() +} + +func (w *Wallet) CreateSimpleTx(pairs map[string]btcutil.Amount, + minconf int) (*CreatedTx, error) { + + req := createTxRequest{ + pairs: pairs, + minconf: minconf, + resp: make(chan createTxResponse), + } + w.createTxRequests <- req + resp := <-req.resp + return resp.tx, resp.err +} + type ( unlockRequest struct { passphrase []byte