Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 29 additions & 13 deletions wallet/createtx.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ func (w *Wallet) txToOutputs(outputs []*wire.TxOut,
account uint32, minconf int32, feeSatPerKb btcutil.Amount,
strategy CoinSelectionStrategy, dryRun bool,
selectedUtxos []wire.OutPoint,
allowUtxo func(utxo wtxmgr.Credit) bool) (
allowUtxo func(utxo wtxmgr.Credit) bool,
changeAddr btcutil.Address) (
*txauthor.AuthoredTx, error) {

chainClient, err := w.requireChainClient()
Expand Down Expand Up @@ -176,7 +177,7 @@ func (w *Wallet) txToOutputs(outputs []*wire.TxOut,
var tx *txauthor.AuthoredTx
err = walletdb.Update(w.db, func(dbtx walletdb.ReadWriteTx) error {
addrmgrNs, changeSource, err := w.addrMgrWithChangeSource(
dbtx, changeKeyScope, account,
dbtx, changeKeyScope, account, changeAddr,
)
if err != nil {
return err
Expand Down Expand Up @@ -429,14 +430,30 @@ func inputYieldsPositively(credit *wire.TxOut,
}

// addrMgrWithChangeSource returns the address manager bucket and a change
// source that returns change addresses from said address manager. The change
// addresses will come from the specified key scope and account, unless a key
// scope is not specified. In that case, change addresses will always come from
// the P2WKH key scope.
// source. If changeAddr is non-nil, the change source always pays to that
// address. Otherwise, change addresses are derived from the wallet using the
// specified key scope and account (defaulting to P2TR if no scope is given).
func (w *Wallet) addrMgrWithChangeSource(dbtx walletdb.ReadWriteTx,
changeKeyScope *waddrmgr.KeyScope, account uint32) (
changeKeyScope *waddrmgr.KeyScope, account uint32,
changeAddr btcutil.Address) (
walletdb.ReadWriteBucket, *txauthor.ChangeSource, error) {

addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey)

// If a custom change address is provided, build a static change source
// from it. The address manager bucket is still returned because callers
// need it for IsWatchOnlyAccount checks and signing.
if changeAddr != nil {
Comment thread
murraystewart96 marked this conversation as resolved.
pkScript, err := txscript.PayToAddrScript(changeAddr)
Comment thread
murraystewart96 marked this conversation as resolved.
if err != nil {
return nil, nil, err
}
return addrmgrNs, &txauthor.ChangeSource{
Comment thread
murraystewart96 marked this conversation as resolved.
ScriptSize: len(pkScript),
NewScript: func() ([]byte, error) { return pkScript, nil },
}, nil
}

// Determine the address type for change addresses of the given
// account.
if changeKeyScope == nil {
Expand All @@ -446,7 +463,6 @@ func (w *Wallet) addrMgrWithChangeSource(dbtx walletdb.ReadWriteTx,

// It's possible for the account to have an address schema override, so
// prefer that if it exists.
addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey)
scopeMgr, err := w.Manager.FetchScopedKeyManager(*changeKeyScope)
if err != nil {
return nil, nil, err
Expand Down Expand Up @@ -480,22 +496,22 @@ func (w *Wallet) addrMgrWithChangeSource(dbtx walletdb.ReadWriteTx,
// from the imported account, change addresses are created from
// account 0.
var (
changeAddr btcutil.Address
err error
derivedAddr btcutil.Address
err error
)
if account == waddrmgr.ImportedAddrAccount {
changeAddr, err = w.newChangeAddress(
derivedAddr, err = w.newChangeAddress(
addrmgrNs, 0, *changeKeyScope,
)
} else {
changeAddr, err = w.newChangeAddress(
derivedAddr, err = w.newChangeAddress(
addrmgrNs, account, *changeKeyScope,
)
}
if err != nil {
return nil, err
}
return txscript.PayToAddrScript(changeAddr)
return txscript.PayToAddrScript(derivedAddr)
}

return addrmgrNs, &txauthor.ChangeSource{
Expand Down
6 changes: 4 additions & 2 deletions wallet/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,15 +247,17 @@ type Interface interface {
SendOutputs(outputs []*wire.TxOut,
coinSelectKeyScope *waddrmgr.KeyScope, account uint32,
minconf int32, satPerKb btcutil.Amount,
strategy CoinSelectionStrategy, label string) (*wire.MsgTx, error)
strategy CoinSelectionStrategy, label string,
optFuncs ...TxCreateOption) (*wire.MsgTx, error)

// SendOutputsWithInput is a variant of SendOutputs that allows
// specifying a particular input to use for the transaction.
SendOutputsWithInput(outputs []*wire.TxOut,
coinSelectKeyScope *waddrmgr.KeyScope, account uint32,
minconf int32, satPerKb btcutil.Amount,
strategy CoinSelectionStrategy, label string,
inputs []wire.OutPoint) (*wire.MsgTx, error)
inputs []wire.OutPoint,
optFuncs ...TxCreateOption) (*wire.MsgTx, error)

// PublishTransaction broadcasts a transaction to the network.
PublishTransaction(tx *wire.MsgTx, label string) error
Expand Down
2 changes: 1 addition & 1 deletion wallet/psbt.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func (w *Wallet) FundPsbt(packet *psbt.Packet, keyScope *waddrmgr.KeyScope,
// a new change address into the database.
err = walletdb.Update(w.db, func(dbtx walletdb.ReadWriteTx) error {
_, changeSource, err := w.addrMgrWithChangeSource(
dbtx, opts.changeKeyScope, account,
dbtx, opts.changeKeyScope, account, nil,
Comment thread
murraystewart96 marked this conversation as resolved.
)
if err != nil {
return err
Expand Down
36 changes: 27 additions & 9 deletions wallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,7 @@ type (
resp chan createTxResponse
selectUtxos []wire.OutPoint
allowUtxo func(wtxmgr.Credit) bool
changeAddr btcutil.Address
}
createTxResponse struct {
tx *txauthor.AuthoredTx
Expand Down Expand Up @@ -1256,6 +1257,7 @@ out:
txr.changeKeyScope, txr.account, txr.minconf,
txr.feeSatPerKB, txr.coinSelectionStrategy,
txr.dryRun, txr.selectUtxos, txr.allowUtxo,
txr.changeAddr,
)

release()
Expand All @@ -1274,6 +1276,7 @@ type txCreateOptions struct {
changeKeyScope *waddrmgr.KeyScope
selectUtxos []wire.OutPoint
allowUtxo func(wtxmgr.Credit) bool
changeAddr btcutil.Address
}

// TxCreateOption is a set of optional arguments to modify the tx creation
Expand Down Expand Up @@ -1313,6 +1316,15 @@ func WithUtxoFilter(allowUtxo func(utxo wtxmgr.Credit) bool) TxCreateOption {
}
}

// WithChangeAddress can be used to specify a custom address for the change
// output. If unspecified, a change address is derived from the wallet's HD
// tree using the configured change key scope.
func WithChangeAddress(addr btcutil.Address) TxCreateOption {
Comment thread
murraystewart96 marked this conversation as resolved.
return func(opts *txCreateOptions) {
opts.changeAddr = addr
}
}

// CreateSimpleTx creates a new signed transaction spending unspent outputs with
// at least minconf confirmations spending to any number of address/amount
// pairs. Only unspent outputs belonging to the given key scope and account will
Expand Down Expand Up @@ -1358,6 +1370,7 @@ func (w *Wallet) CreateSimpleTx(coinSelectKeyScope *waddrmgr.KeyScope,
resp: make(chan createTxResponse),
selectUtxos: opts.selectUtxos,
allowUtxo: opts.allowUtxo,
changeAddr: opts.changeAddr,
}
w.createTxRequests <- req
resp := <-req.resp
Expand Down Expand Up @@ -3524,12 +3537,12 @@ func (w *Wallet) TotalReceivedForAddr(addr btcutil.Address, minConf int32) (btcu
// returns the transaction upon success.
func (w *Wallet) SendOutputs(outputs []*wire.TxOut, keyScope *waddrmgr.KeyScope,
account uint32, minconf int32, satPerKb btcutil.Amount,
coinSelectionStrategy CoinSelectionStrategy, label string) (*wire.MsgTx,
error) {
coinSelectionStrategy CoinSelectionStrategy, label string,
optFuncs ...TxCreateOption) (*wire.MsgTx, error) {

return w.sendOutputs(
outputs, keyScope, account, minconf, satPerKb,
coinSelectionStrategy, label,
coinSelectionStrategy, label, nil, optFuncs...,
)
}

Expand All @@ -3539,18 +3552,20 @@ func (w *Wallet) SendOutputsWithInput(outputs []*wire.TxOut,
keyScope *waddrmgr.KeyScope,
account uint32, minconf int32, satPerKb btcutil.Amount,
coinSelectionStrategy CoinSelectionStrategy, label string,
selectedUtxos []wire.OutPoint) (*wire.MsgTx, error) {
selectedUtxos []wire.OutPoint,
optFuncs ...TxCreateOption) (*wire.MsgTx, error) {

return w.sendOutputs(outputs, keyScope, account, minconf, satPerKb,
coinSelectionStrategy, label, selectedUtxos...)
coinSelectionStrategy, label, selectedUtxos, optFuncs...)
}

// sendOutputs creates and sends payment transactions. It returns the
// transaction upon success.
func (w *Wallet) sendOutputs(outputs []*wire.TxOut, keyScope *waddrmgr.KeyScope,
account uint32, minconf int32, satPerKb btcutil.Amount,
coinSelectionStrategy CoinSelectionStrategy, label string,
selectedUtxos ...wire.OutPoint) (*wire.MsgTx, error) {
selectedUtxos []wire.OutPoint, optFuncs ...TxCreateOption) (*wire.MsgTx,
error) {

// Ensure the outputs to be created adhere to the network's consensus
// rules.
Expand All @@ -3563,15 +3578,18 @@ func (w *Wallet) sendOutputs(outputs []*wire.TxOut, keyScope *waddrmgr.KeyScope,
}
}

allOpts := append(
[]TxCreateOption{WithCustomSelectUtxos(selectedUtxos)},
optFuncs...,
)

// Create the transaction and broadcast it to the network. The
// transaction will be added to the database in order to ensure that we
// continue to re-broadcast the transaction upon restarts until it has
// been confirmed.
createdTx, err := w.CreateSimpleTx(
keyScope, account, outputs, minconf, satPerKb,
coinSelectionStrategy, false, WithCustomSelectUtxos(
selectedUtxos,
),
coinSelectionStrategy, false, allOpts...,
)
if err != nil {
return nil, err
Expand Down