Skip to content

Commit

Permalink
Fix conflicts
Browse files Browse the repository at this point in the history
  • Loading branch information
Sef committed May 16, 2019
2 parents 065b967 + 87f0a52 commit bf8adb2
Show file tree
Hide file tree
Showing 2 changed files with 295 additions and 435 deletions.
127 changes: 85 additions & 42 deletions wallet/createtx.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2017 The Decred developers
// Copyright (c) 2015-2019 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

Expand All @@ -8,6 +8,7 @@ package wallet
import (
"context"
"encoding/binary"
"sync"
"time"

"github.com/decred/dcrd/blockchain"
Expand Down Expand Up @@ -74,7 +75,7 @@ const (
OutputSelectionAlgorithmDefault = iota

// OutputSelectionAlgorithmAll describes the output selection algorithm of
// picking every possible availble output. This is useful for sweeping.
// picking every possible available output. This is useful for sweeping.
OutputSelectionAlgorithmAll
)

Expand All @@ -89,6 +90,21 @@ func (w *Wallet) NewUnsignedTransaction(outputs []*wire.TxOut, relayFeePerKb dcr

const op errors.Op = "wallet.NewUnsignedTransaction"

var unlockOutpoints []*wire.OutPoint
defer func() {
if len(unlockOutpoints) != 0 {
w.lockedOutpointMu.Lock()
for _, op := range unlockOutpoints {
delete(w.lockedOutpoints, *op)
}
w.lockedOutpointMu.Unlock()
}
}()
ignoreInput := func(op *wire.OutPoint) bool {
_, ok := w.lockedOutpoints[*op]
return ok
}

var authoredTx *txauthor.AuthoredTx
var changeSourceUpdates []func(walletdb.ReadWriteTx) error
err := walletdb.View(w.db, func(dbtx walletdb.ReadTx) error {
Expand All @@ -106,8 +122,8 @@ func (w *Wallet) NewUnsignedTransaction(outputs []*wire.TxOut, relayFeePerKb dcr
}
}

sourceImpl := w.TxStore.MakeInputSource(txmgrNs, addrmgrNs, account,
minConf, tipHeight)
sourceImpl := w.TxStore.MakeIgnoredInputSource(txmgrNs, addrmgrNs, account,
minConf, tipHeight, ignoreInput)
var inputSource txauthor.InputSource
switch algo {
case OutputSelectionAlgorithmDefault:
Expand Down Expand Up @@ -135,10 +151,20 @@ func (w *Wallet) NewUnsignedTransaction(outputs []*wire.TxOut, relayFeePerKb dcr
}
}

defer w.lockedOutpointMu.Unlock()
w.lockedOutpointMu.Lock()

var err error
authoredTx, err = txauthor.NewUnsignedTransaction(outputs, relayFeePerKb,
inputSource, changeSource)
return err
if err != nil {
return err
}
for _, in := range authoredTx.Tx.TxIn {
w.lockedOutpoints[in.PreviousOutPoint] = struct{}{}
unlockOutpoints = append(unlockOutpoints, &in.PreviousOutPoint)
}
return nil
})
if err != nil {
return nil, errors.E(op, err)
Expand Down Expand Up @@ -320,17 +346,36 @@ func (w *Wallet) txToOutputsInternal(op errors.Op, outputs []*wire.TxOut, accoun
options = w.NewCreateTxOptions(false)
}

var unlockOutpoints []*wire.OutPoint
defer func() {
if len(unlockOutpoints) != 0 {
w.lockedOutpointMu.Lock()
for _, op := range unlockOutpoints {
delete(w.lockedOutpoints, *op)
}
w.lockedOutpointMu.Unlock()
}
}()
ignoreInput := func(op *wire.OutPoint) bool {
_, ok := w.lockedOutpoints[*op]
return ok
}

var atx *txauthor.AuthoredTx
var changeSourceUpdates []func(walletdb.ReadWriteTx) error

err := walletdb.View(w.db, func(dbtx walletdb.ReadTx) error {
addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)

var once sync.Once
defer once.Do(w.lockedOutpointMu.Unlock)
w.lockedOutpointMu.Lock()

// Create the unsigned transaction.
_, tipHeight := w.TxStore.MainChainTip(txmgrNs)
inputSource := w.TxStore.MakeInputSource(txmgrNs, addrmgrNs, account,
minconf, tipHeight)
inputSource := w.TxStore.MakeIgnoredInputSource(txmgrNs, addrmgrNs, account,
minconf, tipHeight, ignoreInput)
changeSource := &p2PKHChangeSource{
persist: w.deferPersistReturnedChild(&changeSourceUpdates),
account: account,
Expand All @@ -355,6 +400,11 @@ func (w *Wallet) txToOutputsInternal(op errors.Op, outputs []*wire.TxOut, accoun
if err != nil {
return err
}
for _, in := range atx.Tx.TxIn {
w.lockedOutpoints[in.PreviousOutPoint] = struct{}{}
unlockOutpoints = append(unlockOutpoints, &in.PreviousOutPoint)
}
once.Do(w.lockedOutpointMu.Unlock)

// Randomize change position, if change exists, before signing. This
// doesn't affect the serialize size, so the change amount will still be
Expand Down Expand Up @@ -681,7 +731,7 @@ func (w *Wallet) compressWalletInternal(op errors.Op, dbtx walletdb.ReadWriteTx,
return nil, errors.E(op, "too few outputs to consolidate")
}

// Check if output address is default, and generate a new adress if needed
// Check if output address is default, and generate a new address if needed
if changeAddr == nil {
changeAddr, err = w.newChangeAddress(op, w.persistReturnedChild(dbtx), account)
if err != nil {
Expand Down Expand Up @@ -886,33 +936,28 @@ func makeTicket(params *chaincfg.Params, inputPool *extendedOutPoint, input *ext
// wallet instance will be used. Also, when the spend limit in the request is
// greater than or equal to 0, tickets that cost more than that limit will
// return an error that not enough funds are available.
func (w *Wallet) purchaseTickets(op errors.Op, req purchaseTicketRequest) ([]*chainhash.Hash, error) {
n, err := w.NetworkBackend()
if err != nil {
return nil, errors.E(op, err)
}

func (w *Wallet) purchaseTickets(ctx context.Context, op errors.Op, n NetworkBackend, req *PurchaseTicketsRequest) ([]*chainhash.Hash, error) {
// Ensure the minimum number of required confirmations is positive.
if req.minConf < 0 {
if req.MinConf < 0 {
return nil, errors.E(op, errors.Invalid, "negative minconf")
}
// Need a positive or zero expiry that is higher than the next block to
// generate.
if req.expiry < 0 {
if req.Expiry < 0 {
return nil, errors.E(op, errors.Invalid, "negative expiry")
}

// Perform a sanity check on expiry.
var tipHeight int32
err = walletdb.View(w.db, func(tx walletdb.ReadTx) error {
err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(wtxmgrNamespaceKey)
_, tipHeight = w.TxStore.MainChainTip(ns)
return nil
})
if err != nil {
return nil, err
}
if req.expiry <= tipHeight+1 && req.expiry > 0 {
if req.Expiry <= tipHeight+1 && req.Expiry > 0 {
return nil, errors.E(op, errors.Invalid, "expiry height must be above next block height")
}

Expand All @@ -939,20 +984,20 @@ func (w *Wallet) purchaseTickets(op errors.Op, req purchaseTicketRequest) ([]*ch
// required plus fees for the split is larger than the
// balance we have, wasting an address. In the future,
// address this better and prevent address burning.
account := req.account
account := req.SourceAccount

// Calculate the current ticket price. If the DCP0001 deployment is not
// active, fallback to querying the ticket price over RPC.
ticketPrice, err := w.NextStakeDifficulty()
if errors.Is(errors.Deployment, err) {
ticketPrice, err = n.StakeDifficulty(context.TODO())
ticketPrice, err = n.StakeDifficulty(ctx)
}
if err != nil {
return nil, err
}

// Ensure the ticket price does not exceed the spend limit if set.
if req.spendLimit >= 0 && ticketPrice > req.spendLimit {
if req.spendLimit > 0 && ticketPrice > req.spendLimit {
return nil, errors.E(op, errors.Invalid,
errors.Errorf("ticket price %v above spend limit %v", ticketPrice, req.spendLimit))
}
Expand All @@ -975,7 +1020,7 @@ func (w *Wallet) purchaseTickets(op errors.Op, req purchaseTicketRequest) ([]*ch
var stakeSubmissionPkScriptSize int

// The stake submission pkScript is tagged by an OP_SSTX.
switch req.ticketAddr.(type) {
switch req.VotingAddress.(type) {
case *dcrutil.AddressScriptHash:
stakeSubmissionPkScriptSize = txsizes.P2SHPkScriptSize + 1
case *dcrutil.AddressPubKeyHash, nil:
Expand Down Expand Up @@ -1044,12 +1089,12 @@ func (w *Wallet) purchaseTickets(op errors.Op, req purchaseTicketRequest) ([]*ch
// end user through the legacy RPC, so it should only ever be
// set by internal calls e.g. automatic ticket purchase.
if req.minBalance > 0 {
bal, err := w.CalculateAccountBalance(account, req.minConf)
bal, err := w.CalculateAccountBalance(account, req.MinConf)
if err != nil {
return nil, err
}

estimatedFundsUsed := neededPerTicket * dcrutil.Amount(req.numTickets)
estimatedFundsUsed := neededPerTicket * dcrutil.Amount(req.Count)
if req.minBalance+estimatedFundsUsed > bal.Spendable {
return nil, errors.E(op, errors.InsufficientBalance, errors.Errorf(
"estimated ending balance %v is below minimum requested balance %v",
Expand All @@ -1061,7 +1106,7 @@ func (w *Wallet) purchaseTickets(op errors.Op, req purchaseTicketRequest) ([]*ch
// immediately be consumed as tickets.
//
// This opens a write transaction.
splitTxAddr, err := w.NewInternalAddress(req.account, WithGapPolicyWrap())
splitTxAddr, err := w.NewInternalAddress(req.SourceAccount, WithGapPolicyWrap())
if err != nil {
return nil, err
}
Expand All @@ -1078,7 +1123,7 @@ func (w *Wallet) purchaseTickets(op errors.Op, req purchaseTicketRequest) ([]*ch
// first ticket commitment of a smaller amount to the pool, while
// paying themselves with the larger ticket commitment.
var splitOuts []*wire.TxOut
for i := 0; i < req.numTickets; i++ {
for i := 0; i < req.Count; i++ {
// No pool used.
if poolAddress == nil {
splitOuts = append(splitOuts, wire.NewTxOut(int64(neededPerTicket), splitPkScript))
Expand All @@ -1099,10 +1144,8 @@ func (w *Wallet) purchaseTickets(op errors.Op, req purchaseTicketRequest) ([]*ch
if txFeeIncrement == 0 {
txFeeIncrement = w.RelayFee()
}

options := w.NewCreateTxOptions(false)
splitTx, err := w.txToOutputsInternal(op, splitOuts, account, req.minConf,
n, false, txFeeIncrement, options)
splitTx, err := w.txToOutputsInternal(op, splitOuts, account, req.MinConf,
n, false, txFeeIncrement, nil)
if err != nil {
return nil, err
}
Expand All @@ -1119,16 +1162,16 @@ func (w *Wallet) purchaseTickets(op errors.Op, req purchaseTicketRequest) ([]*ch
"purchases: %v", err)
}
if len(watchOutPoints) > 0 {
err := n.LoadTxFilter(context.TODO(), false, nil, watchOutPoints)
err := n.LoadTxFilter(ctx, false, nil, watchOutPoints)
if err != nil {
log.Errorf("Failed to watch outpoints: %v", err)
}
}
}()

// Generate the tickets individually.
ticketHashes := make([]*chainhash.Hash, 0, req.numTickets)
for i := 0; i < req.numTickets; i++ {
ticketHashes := make([]*chainhash.Hash, 0, req.Count)
for i := 0; i < req.Count; i++ {
// Generate the extended outpoints that we need to use for ticket
// inputs. There are two inputs for pool tickets corresponding to the
// fees and the user subsidy, while user-handled tickets have only one
Expand Down Expand Up @@ -1180,18 +1223,18 @@ func (w *Wallet) purchaseTickets(op errors.Op, req purchaseTicketRequest) ([]*ch
// an address.
var addrVote, addrSubsidy dcrutil.Address
err := walletdb.Update(w.db, func(dbtx walletdb.ReadWriteTx) error {
addrVote = req.ticketAddr
addrVote = req.VotingAddress
if addrVote == nil {
addrVote = w.ticketAddress
if addrVote == nil {
addrVote, err = addrFunc(op, w.persistReturnedChild(dbtx), req.account)
addrVote, err = addrFunc(op, w.persistReturnedChild(dbtx), req.SourceAccount)
if err != nil {
return err
}
}
}

addrSubsidy, err = addrFunc(op, w.persistReturnedChild(dbtx), req.account)
addrSubsidy, err = addrFunc(op, w.persistReturnedChild(dbtx), req.SourceAccount)
return err
})
if err != nil {
Expand Down Expand Up @@ -1227,7 +1270,7 @@ func (w *Wallet) purchaseTickets(op errors.Op, req purchaseTicketRequest) ([]*ch
forSigning = append(forSigning, eopCredit)

// Set the expiry.
ticket.Expiry = uint32(req.expiry)
ticket.Expiry = uint32(req.Expiry)

err = walletdb.View(w.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
Expand Down Expand Up @@ -1261,7 +1304,7 @@ func (w *Wallet) purchaseTickets(op errors.Op, req purchaseTicketRequest) ([]*ch
}
// TODO: Send all tickets, and all split transactions, together. Purge
// transactions from DB if tickets cannot be sent.
err = n.PublishTransactions(context.TODO(), ticket)
err = n.PublishTransactions(ctx, ticket)
if err != nil {
return ticketHashes, errors.E(op, err)
}
Expand All @@ -1288,7 +1331,7 @@ func (w *Wallet) findEligibleOutputs(dbtx walletdb.ReadTx, account uint32, minco
// should be handled by the call to UnspentOutputs (or similar).
// Because one of these filters requires matching the output script to
// the desired account, this change depends on making wtxmgr a waddrmgr
// dependancy and requesting unspent outputs for a single account.
// dependency and requesting unspent outputs for a single account.
eligible := make([]udb.Credit, 0, len(unspent))
for i := range unspent {
output := unspent[i]
Expand Down Expand Up @@ -1543,7 +1586,7 @@ func (w *Wallet) signRevocation(addrmgrNs walletdb.ReadBucket, ticketPurchase, r
func newVoteScript(voteBits stake.VoteBits) ([]byte, error) {
b := make([]byte, 2+len(voteBits.ExtendedBits))
binary.LittleEndian.PutUint16(b[0:2], voteBits.Bits)
copy(b[2:], voteBits.ExtendedBits[:])
copy(b[2:], voteBits.ExtendedBits)
return txscript.GenerateProvablyPruneableOut(b)
}

Expand Down Expand Up @@ -1655,11 +1698,11 @@ func createUnsignedRevocation(ticketHash *chainhash.Hash, ticketPurchase *wire.M
sizeEstimate := txsizes.EstimateSerializeSize(scriptSizes, revocation.TxOut, 0)
feeEstimate := txrules.FeeForSerializeSize(feePerKB, sizeEstimate)

// Reduce the output value of one of the outputs to accomodate for the relay
// Reduce the output value of one of the outputs to accommodate for the relay
// fee. To avoid creating dust outputs, a suitable output value is reduced
// by the fee estimate only if it is large enough to not create dust. This
// code does not currently handle reducing the output values of multiple
// commitment outputs to accomodate for the fee.
// commitment outputs to accommodate for the fee.
for _, output := range revocation.TxOut {
if dcrutil.Amount(output.Value) > feeEstimate {
amount := dcrutil.Amount(output.Value) - feeEstimate
Expand Down

0 comments on commit bf8adb2

Please sign in to comment.