11// Copyright (c) 2013-2016 The btcsuite developers
2- // Copyright (c) 2015-2017 The Decred developers
2+ // Copyright (c) 2015-2019 The Decred developers
33// Use of this source code is governed by an ISC
44// license that can be found in the LICENSE file.
55
@@ -8,6 +8,7 @@ package wallet
88import (
99 "context"
1010 "encoding/binary"
11+ "sync"
1112 "time"
1213
1314 "github.com/decred/dcrd/blockchain"
@@ -74,7 +75,7 @@ const (
7475 OutputSelectionAlgorithmDefault = iota
7576
7677 // OutputSelectionAlgorithmAll describes the output selection algorithm of
77- // picking every possible availble output. This is useful for sweeping.
78+ // picking every possible available output. This is useful for sweeping.
7879 OutputSelectionAlgorithmAll
7980)
8081
@@ -89,6 +90,21 @@ func (w *Wallet) NewUnsignedTransaction(outputs []*wire.TxOut, relayFeePerKb dcr
8990
9091 const op errors.Op = "wallet.NewUnsignedTransaction"
9192
93+ var unlockOutpoints []* wire.OutPoint
94+ defer func () {
95+ if len (unlockOutpoints ) != 0 {
96+ w .lockedOutpointMu .Lock ()
97+ for _ , op := range unlockOutpoints {
98+ delete (w .lockedOutpoints , * op )
99+ }
100+ w .lockedOutpointMu .Unlock ()
101+ }
102+ }()
103+ ignoreInput := func (op * wire.OutPoint ) bool {
104+ _ , ok := w .lockedOutpoints [* op ]
105+ return ok
106+ }
107+
92108 var authoredTx * txauthor.AuthoredTx
93109 var changeSourceUpdates []func (walletdb.ReadWriteTx ) error
94110 err := walletdb .View (w .db , func (dbtx walletdb.ReadTx ) error {
@@ -106,8 +122,8 @@ func (w *Wallet) NewUnsignedTransaction(outputs []*wire.TxOut, relayFeePerKb dcr
106122 }
107123 }
108124
109- sourceImpl := w .TxStore .MakeInputSource (txmgrNs , addrmgrNs , account ,
110- minConf , tipHeight )
125+ sourceImpl := w .TxStore .MakeIgnoredInputSource (txmgrNs , addrmgrNs , account ,
126+ minConf , tipHeight , ignoreInput )
111127 var inputSource txauthor.InputSource
112128 switch algo {
113129 case OutputSelectionAlgorithmDefault :
@@ -135,10 +151,20 @@ func (w *Wallet) NewUnsignedTransaction(outputs []*wire.TxOut, relayFeePerKb dcr
135151 }
136152 }
137153
154+ defer w .lockedOutpointMu .Unlock ()
155+ w .lockedOutpointMu .Lock ()
156+
138157 var err error
139158 authoredTx , err = txauthor .NewUnsignedTransaction (outputs , relayFeePerKb ,
140159 inputSource , changeSource )
141- return err
160+ if err != nil {
161+ return err
162+ }
163+ for _ , in := range authoredTx .Tx .TxIn {
164+ w .lockedOutpoints [in .PreviousOutPoint ] = struct {}{}
165+ unlockOutpoints = append (unlockOutpoints , & in .PreviousOutPoint )
166+ }
167+ return nil
142168 })
143169 if err != nil {
144170 return nil , errors .E (op , err )
@@ -320,17 +346,36 @@ func (w *Wallet) txToOutputsInternal(op errors.Op, outputs []*wire.TxOut, accoun
320346 options = w .NewCreateTxOptions (false )
321347 }
322348
349+ var unlockOutpoints []* wire.OutPoint
350+ defer func () {
351+ if len (unlockOutpoints ) != 0 {
352+ w .lockedOutpointMu .Lock ()
353+ for _ , op := range unlockOutpoints {
354+ delete (w .lockedOutpoints , * op )
355+ }
356+ w .lockedOutpointMu .Unlock ()
357+ }
358+ }()
359+ ignoreInput := func (op * wire.OutPoint ) bool {
360+ _ , ok := w .lockedOutpoints [* op ]
361+ return ok
362+ }
363+
323364 var atx * txauthor.AuthoredTx
324365 var changeSourceUpdates []func (walletdb.ReadWriteTx ) error
325366
326367 err := walletdb .View (w .db , func (dbtx walletdb.ReadTx ) error {
327368 addrmgrNs := dbtx .ReadBucket (waddrmgrNamespaceKey )
328369 txmgrNs := dbtx .ReadBucket (wtxmgrNamespaceKey )
329370
371+ var once sync.Once
372+ defer once .Do (w .lockedOutpointMu .Unlock )
373+ w .lockedOutpointMu .Lock ()
374+
330375 // Create the unsigned transaction.
331376 _ , tipHeight := w .TxStore .MainChainTip (txmgrNs )
332- inputSource := w .TxStore .MakeInputSource (txmgrNs , addrmgrNs , account ,
333- minconf , tipHeight )
377+ inputSource := w .TxStore .MakeIgnoredInputSource (txmgrNs , addrmgrNs , account ,
378+ minconf , tipHeight , ignoreInput )
334379 changeSource := & p2PKHChangeSource {
335380 persist : w .deferPersistReturnedChild (& changeSourceUpdates ),
336381 account : account ,
@@ -355,6 +400,11 @@ func (w *Wallet) txToOutputsInternal(op errors.Op, outputs []*wire.TxOut, accoun
355400 if err != nil {
356401 return err
357402 }
403+ for _ , in := range atx .Tx .TxIn {
404+ w .lockedOutpoints [in .PreviousOutPoint ] = struct {}{}
405+ unlockOutpoints = append (unlockOutpoints , & in .PreviousOutPoint )
406+ }
407+ once .Do (w .lockedOutpointMu .Unlock )
358408
359409 // Randomize change position, if change exists, before signing. This
360410 // doesn't affect the serialize size, so the change amount will still be
@@ -681,7 +731,7 @@ func (w *Wallet) compressWalletInternal(op errors.Op, dbtx walletdb.ReadWriteTx,
681731 return nil , errors .E (op , "too few outputs to consolidate" )
682732 }
683733
684- // Check if output address is default, and generate a new adress if needed
734+ // Check if output address is default, and generate a new address if needed
685735 if changeAddr == nil {
686736 changeAddr , err = w .newChangeAddress (op , w .persistReturnedChild (dbtx ), account )
687737 if err != nil {
@@ -886,33 +936,28 @@ func makeTicket(params *chaincfg.Params, inputPool *extendedOutPoint, input *ext
886936// wallet instance will be used. Also, when the spend limit in the request is
887937// greater than or equal to 0, tickets that cost more than that limit will
888938// return an error that not enough funds are available.
889- func (w * Wallet ) purchaseTickets (op errors.Op , req purchaseTicketRequest ) ([]* chainhash.Hash , error ) {
890- n , err := w .NetworkBackend ()
891- if err != nil {
892- return nil , errors .E (op , err )
893- }
894-
939+ func (w * Wallet ) purchaseTickets (ctx context.Context , op errors.Op , n NetworkBackend , req * PurchaseTicketsRequest ) ([]* chainhash.Hash , error ) {
895940 // Ensure the minimum number of required confirmations is positive.
896- if req .minConf < 0 {
941+ if req .MinConf < 0 {
897942 return nil , errors .E (op , errors .Invalid , "negative minconf" )
898943 }
899944 // Need a positive or zero expiry that is higher than the next block to
900945 // generate.
901- if req .expiry < 0 {
946+ if req .Expiry < 0 {
902947 return nil , errors .E (op , errors .Invalid , "negative expiry" )
903948 }
904949
905950 // Perform a sanity check on expiry.
906951 var tipHeight int32
907- err = walletdb .View (w .db , func (tx walletdb.ReadTx ) error {
952+ err : = walletdb .View (w .db , func (tx walletdb.ReadTx ) error {
908953 ns := tx .ReadBucket (wtxmgrNamespaceKey )
909954 _ , tipHeight = w .TxStore .MainChainTip (ns )
910955 return nil
911956 })
912957 if err != nil {
913958 return nil , err
914959 }
915- if req .expiry <= tipHeight + 1 && req .expiry > 0 {
960+ if req .Expiry <= tipHeight + 1 && req .Expiry > 0 {
916961 return nil , errors .E (op , errors .Invalid , "expiry height must be above next block height" )
917962 }
918963
@@ -939,20 +984,20 @@ func (w *Wallet) purchaseTickets(op errors.Op, req purchaseTicketRequest) ([]*ch
939984 // required plus fees for the split is larger than the
940985 // balance we have, wasting an address. In the future,
941986 // address this better and prevent address burning.
942- account := req .account
987+ account := req .SourceAccount
943988
944989 // Calculate the current ticket price. If the DCP0001 deployment is not
945990 // active, fallback to querying the ticket price over RPC.
946991 ticketPrice , err := w .NextStakeDifficulty ()
947992 if errors .Is (errors .Deployment , err ) {
948- ticketPrice , err = n .StakeDifficulty (context . TODO () )
993+ ticketPrice , err = n .StakeDifficulty (ctx )
949994 }
950995 if err != nil {
951996 return nil , err
952997 }
953998
954999 // Ensure the ticket price does not exceed the spend limit if set.
955- if req .spendLimit >= 0 && ticketPrice > req .spendLimit {
1000+ if req .spendLimit > 0 && ticketPrice > req .spendLimit {
9561001 return nil , errors .E (op , errors .Invalid ,
9571002 errors .Errorf ("ticket price %v above spend limit %v" , ticketPrice , req .spendLimit ))
9581003 }
@@ -975,7 +1020,7 @@ func (w *Wallet) purchaseTickets(op errors.Op, req purchaseTicketRequest) ([]*ch
9751020 var stakeSubmissionPkScriptSize int
9761021
9771022 // The stake submission pkScript is tagged by an OP_SSTX.
978- switch req .ticketAddr .(type ) {
1023+ switch req .VotingAddress .(type ) {
9791024 case * dcrutil.AddressScriptHash :
9801025 stakeSubmissionPkScriptSize = txsizes .P2SHPkScriptSize + 1
9811026 case * dcrutil.AddressPubKeyHash , nil :
@@ -1044,12 +1089,12 @@ func (w *Wallet) purchaseTickets(op errors.Op, req purchaseTicketRequest) ([]*ch
10441089 // end user through the legacy RPC, so it should only ever be
10451090 // set by internal calls e.g. automatic ticket purchase.
10461091 if req .minBalance > 0 {
1047- bal , err := w .CalculateAccountBalance (account , req .minConf )
1092+ bal , err := w .CalculateAccountBalance (account , req .MinConf )
10481093 if err != nil {
10491094 return nil , err
10501095 }
10511096
1052- estimatedFundsUsed := neededPerTicket * dcrutil .Amount (req .numTickets )
1097+ estimatedFundsUsed := neededPerTicket * dcrutil .Amount (req .Count )
10531098 if req .minBalance + estimatedFundsUsed > bal .Spendable {
10541099 return nil , errors .E (op , errors .InsufficientBalance , errors .Errorf (
10551100 "estimated ending balance %v is below minimum requested balance %v" ,
@@ -1061,7 +1106,7 @@ func (w *Wallet) purchaseTickets(op errors.Op, req purchaseTicketRequest) ([]*ch
10611106 // immediately be consumed as tickets.
10621107 //
10631108 // This opens a write transaction.
1064- splitTxAddr , err := w .NewInternalAddress (req .account , WithGapPolicyWrap ())
1109+ splitTxAddr , err := w .NewInternalAddress (req .SourceAccount , WithGapPolicyWrap ())
10651110 if err != nil {
10661111 return nil , err
10671112 }
@@ -1078,7 +1123,7 @@ func (w *Wallet) purchaseTickets(op errors.Op, req purchaseTicketRequest) ([]*ch
10781123 // first ticket commitment of a smaller amount to the pool, while
10791124 // paying themselves with the larger ticket commitment.
10801125 var splitOuts []* wire.TxOut
1081- for i := 0 ; i < req .numTickets ; i ++ {
1126+ for i := 0 ; i < req .Count ; i ++ {
10821127 // No pool used.
10831128 if poolAddress == nil {
10841129 splitOuts = append (splitOuts , wire .NewTxOut (int64 (neededPerTicket ), splitPkScript ))
@@ -1099,10 +1144,8 @@ func (w *Wallet) purchaseTickets(op errors.Op, req purchaseTicketRequest) ([]*ch
10991144 if txFeeIncrement == 0 {
11001145 txFeeIncrement = w .RelayFee ()
11011146 }
1102-
1103- options := w .NewCreateTxOptions (false )
1104- splitTx , err := w .txToOutputsInternal (op , splitOuts , account , req .minConf ,
1105- n , false , txFeeIncrement , options )
1147+ splitTx , err := w .txToOutputsInternal (op , splitOuts , account , req .MinConf ,
1148+ n , false , txFeeIncrement , nil )
11061149 if err != nil {
11071150 return nil , err
11081151 }
@@ -1119,16 +1162,16 @@ func (w *Wallet) purchaseTickets(op errors.Op, req purchaseTicketRequest) ([]*ch
11191162 "purchases: %v" , err )
11201163 }
11211164 if len (watchOutPoints ) > 0 {
1122- err := n .LoadTxFilter (context . TODO () , false , nil , watchOutPoints )
1165+ err := n .LoadTxFilter (ctx , false , nil , watchOutPoints )
11231166 if err != nil {
11241167 log .Errorf ("Failed to watch outpoints: %v" , err )
11251168 }
11261169 }
11271170 }()
11281171
11291172 // Generate the tickets individually.
1130- ticketHashes := make ([]* chainhash.Hash , 0 , req .numTickets )
1131- for i := 0 ; i < req .numTickets ; i ++ {
1173+ ticketHashes := make ([]* chainhash.Hash , 0 , req .Count )
1174+ for i := 0 ; i < req .Count ; i ++ {
11321175 // Generate the extended outpoints that we need to use for ticket
11331176 // inputs. There are two inputs for pool tickets corresponding to the
11341177 // fees and the user subsidy, while user-handled tickets have only one
@@ -1180,18 +1223,18 @@ func (w *Wallet) purchaseTickets(op errors.Op, req purchaseTicketRequest) ([]*ch
11801223 // an address.
11811224 var addrVote , addrSubsidy dcrutil.Address
11821225 err := walletdb .Update (w .db , func (dbtx walletdb.ReadWriteTx ) error {
1183- addrVote = req .ticketAddr
1226+ addrVote = req .VotingAddress
11841227 if addrVote == nil {
11851228 addrVote = w .ticketAddress
11861229 if addrVote == nil {
1187- addrVote , err = addrFunc (op , w .persistReturnedChild (dbtx ), req .account )
1230+ addrVote , err = addrFunc (op , w .persistReturnedChild (dbtx ), req .SourceAccount )
11881231 if err != nil {
11891232 return err
11901233 }
11911234 }
11921235 }
11931236
1194- addrSubsidy , err = addrFunc (op , w .persistReturnedChild (dbtx ), req .account )
1237+ addrSubsidy , err = addrFunc (op , w .persistReturnedChild (dbtx ), req .SourceAccount )
11951238 return err
11961239 })
11971240 if err != nil {
@@ -1227,7 +1270,7 @@ func (w *Wallet) purchaseTickets(op errors.Op, req purchaseTicketRequest) ([]*ch
12271270 forSigning = append (forSigning , eopCredit )
12281271
12291272 // Set the expiry.
1230- ticket .Expiry = uint32 (req .expiry )
1273+ ticket .Expiry = uint32 (req .Expiry )
12311274
12321275 err = walletdb .View (w .db , func (tx walletdb.ReadTx ) error {
12331276 ns := tx .ReadBucket (waddrmgrNamespaceKey )
@@ -1261,7 +1304,7 @@ func (w *Wallet) purchaseTickets(op errors.Op, req purchaseTicketRequest) ([]*ch
12611304 }
12621305 // TODO: Send all tickets, and all split transactions, together. Purge
12631306 // transactions from DB if tickets cannot be sent.
1264- err = n .PublishTransactions (context . TODO () , ticket )
1307+ err = n .PublishTransactions (ctx , ticket )
12651308 if err != nil {
12661309 return ticketHashes , errors .E (op , err )
12671310 }
@@ -1288,7 +1331,7 @@ func (w *Wallet) findEligibleOutputs(dbtx walletdb.ReadTx, account uint32, minco
12881331 // should be handled by the call to UnspentOutputs (or similar).
12891332 // Because one of these filters requires matching the output script to
12901333 // the desired account, this change depends on making wtxmgr a waddrmgr
1291- // dependancy and requesting unspent outputs for a single account.
1334+ // dependency and requesting unspent outputs for a single account.
12921335 eligible := make ([]udb.Credit , 0 , len (unspent ))
12931336 for i := range unspent {
12941337 output := unspent [i ]
@@ -1543,7 +1586,7 @@ func (w *Wallet) signRevocation(addrmgrNs walletdb.ReadBucket, ticketPurchase, r
15431586func newVoteScript (voteBits stake.VoteBits ) ([]byte , error ) {
15441587 b := make ([]byte , 2 + len (voteBits .ExtendedBits ))
15451588 binary .LittleEndian .PutUint16 (b [0 :2 ], voteBits .Bits )
1546- copy (b [2 :], voteBits .ExtendedBits [:] )
1589+ copy (b [2 :], voteBits .ExtendedBits )
15471590 return txscript .GenerateProvablyPruneableOut (b )
15481591}
15491592
@@ -1655,11 +1698,11 @@ func createUnsignedRevocation(ticketHash *chainhash.Hash, ticketPurchase *wire.M
16551698 sizeEstimate := txsizes .EstimateSerializeSize (scriptSizes , revocation .TxOut , 0 )
16561699 feeEstimate := txrules .FeeForSerializeSize (feePerKB , sizeEstimate )
16571700
1658- // Reduce the output value of one of the outputs to accomodate for the relay
1701+ // Reduce the output value of one of the outputs to accommodate for the relay
16591702 // fee. To avoid creating dust outputs, a suitable output value is reduced
16601703 // by the fee estimate only if it is large enough to not create dust. This
16611704 // code does not currently handle reducing the output values of multiple
1662- // commitment outputs to accomodate for the fee.
1705+ // commitment outputs to accommodate for the fee.
16631706 for _ , output := range revocation .TxOut {
16641707 if dcrutil .Amount (output .Value ) > feeEstimate {
16651708 amount := dcrutil .Amount (output .Value ) - feeEstimate
0 commit comments