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 8, 2024
1 parent 10dd82a commit 508c3a0
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 25 deletions.
69 changes: 48 additions & 21 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) {
coinSelectionStrategy CoinSelectionStrategy, dryRun bool,
selectedUtxos []wire.OutPoint) (*txauthor.AuthoredTx, error) {

chainClient, err := w.requireChainClient()
if err != nil {
Expand Down Expand Up @@ -174,27 +174,54 @@ 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 {
mapEligibleToOutpoint := make(map[wire.OutPoint]wtxmgr.
Credit)

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

var eligibleSelectedUtxo []wtxmgr.Credit
for _, outpoint := range selectedUtxos {
e, ok := mapEligibleToOutpoint[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 := coinSelectionStrategy.
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)
}
2 changes: 1 addition & 1 deletion 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

0 comments on commit 508c3a0

Please sign in to comment.