Skip to content

Commit

Permalink
wallet: Add selectedutxos to txToOutputs
Browse files Browse the repository at this point in the history
Signed-off-by: Ononiwu Maureen <[email protected]>
  • Loading branch information
Ononiwu Maureen committed Mar 14, 2024
1 parent fe15d37 commit d75f2c3
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 29 deletions.
76 changes: 53 additions & 23 deletions wallet/createtx.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ func (s secretSource) GetScript(addr btcutil.Address) ([]byte, error) {
func (w *Wallet) txToOutputs(outputs []*wire.TxOut,
coinSelectKeyScope, changeKeyScope *waddrmgr.KeyScope,
account uint32, minconf int32, feeSatPerKb btcutil.Amount,
coinSelectionStrategy CoinSelectionStrategy, dryRun bool) (
*txauthor.AuthoredTx, error) {
strategy CoinSelectionStrategy, dryRun bool,
selectedUtxos []wire.OutPoint) (*txauthor.AuthoredTx, error) {

chainClient, err := w.requireChainClient()
if err != nil {
Expand All @@ -154,8 +154,8 @@ func (w *Wallet) txToOutputs(outputs []*wire.TxOut,
}

// Fall back to default coin selection strategy if none is supplied.
if coinSelectionStrategy == nil {
coinSelectionStrategy = CoinSelectionLargest
if strategy == nil {
strategy = CoinSelectionLargest
}

var tx *txauthor.AuthoredTx
Expand All @@ -174,27 +174,57 @@ func (w *Wallet) txToOutputs(outputs []*wire.TxOut,
return err
}

// Wrap our coins in a type that implements the SelectableCoin
// interface, so we can arrange them according to the selected
// coin selection strategy.
wrappedEligible := make([]Coin, len(eligible))
for i := range eligible {
wrappedEligible[i] = Coin{
TxOut: wire.TxOut{
Value: int64(eligible[i].Amount),
PkScript: eligible[i].PkScript,
},
OutPoint: eligible[i].OutPoint,
var inputSource txauthor.InputSource
if len(selectedUtxos) > 0 {
eligibleByOutpoint := make(
map[wire.OutPoint]wtxmgr.Credit,
)

for _, e := range eligible {
eligibleByOutpoint[e.OutPoint] = e
}

var eligibleSelectedUtxo []wtxmgr.Credit
for _, outpoint := range selectedUtxos {
e, ok := eligibleByOutpoint[outpoint]

if !ok {
return fmt.Errorf("selected outpoint "+
"not eligible for "+
"spending: %v", outpoint)
}
eligibleSelectedUtxo = append(
eligibleSelectedUtxo, e,
)
}
}
arrangedCoins, err := coinSelectionStrategy.ArrangeCoins(
wrappedEligible, feeSatPerKb,
)
if err != nil {
return err
}

inputSource := makeInputSource(arrangedCoins)
inputSource = constantInputSource(eligibleSelectedUtxo)

} else {
// Wrap our coins in a type that implements the
// SelectableCoin interface, so we can arrange them
// according to the selected coin selection strategy.
wrappedEligible := make([]Coin, len(eligible))
for i := range eligible {
wrappedEligible[i] = Coin{
TxOut: wire.TxOut{
Value: int64(
eligible[i].Amount,
),
PkScript: eligible[i].PkScript,
},
OutPoint: eligible[i].OutPoint,
}
}

arrangedCoins, err := strategy.ArrangeCoins(
wrappedEligible, feeSatPerKb,
)
if err != nil {
return err
}
inputSource = makeInputSource(arrangedCoins)
}

tx, err = txauthor.NewUnsignedTransaction(
outputs, feeSatPerKb, inputSource, changeSource,
Expand Down
93 changes: 90 additions & 3 deletions wallet/createtx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func TestTxToOutputsDryRun(t *testing.T) {
// database us not inflated.
dryRunTx, err := w.txToOutputs(
txOuts, nil, nil, 0, 1, 1000, CoinSelectionLargest, true,
nil,
)
if err != nil {
t.Fatalf("unable to author tx: %v", err)
Expand All @@ -96,6 +97,7 @@ func TestTxToOutputsDryRun(t *testing.T) {

dryRunTx2, err := w.txToOutputs(
txOuts, nil, nil, 0, 1, 1000, CoinSelectionLargest, true,
nil,
)
if err != nil {
t.Fatalf("unable to author tx: %v", err)
Expand Down Expand Up @@ -131,6 +133,7 @@ func TestTxToOutputsDryRun(t *testing.T) {
// to the database.
tx, err := w.txToOutputs(
txOuts, nil, nil, 0, 1, 1000, CoinSelectionLargest, false,
nil,
)
if err != nil {
t.Fatalf("unable to author tx: %v", err)
Expand Down Expand Up @@ -280,7 +283,7 @@ func TestTxToOutputsRandom(t *testing.T) {
createTx := func() *txauthor.AuthoredTx {
tx, err := w.txToOutputs(
txOuts, nil, nil, 0, 1, feeSatPerKb,
CoinSelectionRandom, true,
CoinSelectionRandom, true, nil,
)
require.NoError(t, err)
return tx
Expand Down Expand Up @@ -352,7 +355,7 @@ func TestCreateSimpleCustomChange(t *testing.T) {
}
tx1, err := w.txToOutputs(
[]*wire.TxOut{targetTxOut}, nil, nil, 0, 1, 1000,
CoinSelectionLargest, true,
CoinSelectionLargest, true, nil,
)
require.NoError(t, err)

Expand All @@ -378,7 +381,7 @@ func TestCreateSimpleCustomChange(t *testing.T) {
tx2, err := w.txToOutputs(
[]*wire.TxOut{targetTxOut}, &waddrmgr.KeyScopeBIP0086,
&waddrmgr.KeyScopeBIP0084, 0, 1, 1000, CoinSelectionLargest,
true,
true, nil,
)
require.NoError(t, err)

Expand All @@ -399,3 +402,87 @@ func TestCreateSimpleCustomChange(t *testing.T) {
require.Equal(t, scriptType, txscript.WitnessV0PubKeyHashTy)
}
}

// TestSelectUtxosTxoToOutpoint tests that it is possible to use passed
// selected utxos to craft a transaction in `txToOutpoint`.
func TestSelectUtxosTxoToOutpoint(t *testing.T) {
t.Parallel()

w, cleanup := testWallet(t)
defer cleanup()

// First, we'll make a P2TR and a P2WKH address to send some coins to.
p2wkhAddr, err := w.CurrentAddress(0, waddrmgr.KeyScopeBIP0084)
require.NoError(t, err)

p2trAddr, err := w.CurrentAddress(0, waddrmgr.KeyScopeBIP0086)
require.NoError(t, err)

// We'll now make a transaction that'll send coins to both outputs,
// then "credit" the wallet for that send.
p2wkhScript, err := txscript.PayToAddrScript(p2wkhAddr)
require.NoError(t, err)

p2trScript, err := txscript.PayToAddrScript(p2trAddr)
require.NoError(t, err)

incomingTx := &wire.MsgTx{
TxIn: []*wire.TxIn{
{},
},
TxOut: []*wire.TxOut{
wire.NewTxOut(1_000_000, p2wkhScript),
wire.NewTxOut(2_000_000, p2trScript),
wire.NewTxOut(3_000_000, p2trScript),
wire.NewTxOut(7_000_000, p2trScript),
},
}
addUtxo(t, w, incomingTx)

// We expect 4 unspent utxos.
unspent, err := w.ListUnspent(0, 80, "")
require.NoError(t, err, "unexpected error while calling "+
"list unspent")

require.Len(t, unspent, 4, "expected 4 unspent "+
"utxos")

selectUtxos := []wire.OutPoint{
{
Hash: incomingTx.TxHash(),
Index: 1,
},
{
Hash: incomingTx.TxHash(),
Index: 2,
},
}

// Test by sending 200_000.
targetTxOut := &wire.TxOut{
Value: 200_000,
PkScript: p2trScript,
}
tx1, err := w.txToOutputs(
[]*wire.TxOut{targetTxOut}, nil, nil, 0, 1, 1000,
CoinSelectionLargest, true, selectUtxos,
)
require.NoError(t, err)

// We expect all and only our select utxos to be input in this
// transaction.
require.Len(t, tx1.Tx.TxIn, len(selectUtxos))

lookupSelectUtxos := make(map[wire.OutPoint]struct{})
for _, utxo := range selectUtxos {
lookupSelectUtxos[utxo] = struct{}{}
}

for _, tx := range tx1.Tx.TxIn {
_, ok := lookupSelectUtxos[tx.PreviousOutPoint]
require.True(t, ok, "unexpected outpoint in txin")
}

// Expect two outputs, change and the actual payment to the address.
require.Len(t, tx1.Tx.TxOut, 2)
}
6 changes: 3 additions & 3 deletions wallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -1241,7 +1241,7 @@ out:
tx, err := w.txToOutputs(
txr.outputs, txr.coinSelectKeyScope, txr.changeKeyScope,
txr.account, txr.minconf, txr.feeSatPerKB,
txr.coinSelectionStrategy, txr.dryRun,
txr.coinSelectionStrategy, txr.dryRun, txr.selectUtxos,
)

release()
Expand Down Expand Up @@ -3414,8 +3414,8 @@ func (w *Wallet) SendOutputsWithInput(outputs []*wire.TxOut,
coinSelectionStrategy, label, selectedUtxos...)
}

// sendOutputs creates and sends payment transactions.It returns the transaction
// upon success.
// 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,
Expand Down

0 comments on commit d75f2c3

Please sign in to comment.