Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wallet: Return an error if select utxos in txtoOutputs contain duplicates. #928

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ require (
github.com/jrick/logrotate v1.0.0
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf
github.com/lightninglabs/neutrino v0.16.0
github.com/lightninglabs/neutrino/cache v1.1.1
github.com/lightninglabs/neutrino/cache v1.1.2
github.com/lightningnetwork/lnd/fn v1.0.6
github.com/lightningnetwork/lnd/ticker v1.0.0
github.com/lightningnetwork/lnd/tlv v1.0.2
github.com/stretchr/testify v1.8.4
Expand All @@ -43,6 +44,7 @@ require (
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
go.etcd.io/bbolt v1.3.7 // indirect
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
Expand Down
8 changes: 6 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,12 @@ github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk=
github.com/lightninglabs/neutrino v0.16.0 h1:YNTQG32fPR/Zg0vvJVI65OBH8l3U18LSXXtX91hx0q0=
github.com/lightninglabs/neutrino v0.16.0/go.mod h1:x3OmY2wsA18+Kc3TSV2QpSUewOCiscw2mKpXgZv2kZk=
github.com/lightninglabs/neutrino/cache v1.1.1 h1:TllWOSlkABhpgbWJfzsrdUaDH2fBy/54VSIB4vVqV8M=
github.com/lightninglabs/neutrino/cache v1.1.1/go.mod h1:XJNcgdOw1LQnanGjw8Vj44CvguYA25IMKjWFZczwZuo=
github.com/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3shmlu5hIQ798g=
github.com/lightninglabs/neutrino/cache v1.1.2/go.mod h1:XJNcgdOw1LQnanGjw8Vj44CvguYA25IMKjWFZczwZuo=
github.com/lightningnetwork/lnd/clock v1.0.1 h1:QQod8+m3KgqHdvVMV+2DRNNZS1GRFir8mHZYA+Z2hFo=
github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg=
github.com/lightningnetwork/lnd/fn v1.0.6 h1:LOeKEPUMxXcu3wax6zxz7GzNFxSLGMUM6hbHLkujd1s=
github.com/lightningnetwork/lnd/fn v1.0.6/go.mod h1:P027+0CyELd92H9gnReUkGGAqbFA1HwjHWdfaDFD51U=
github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0=
github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms=
github.com/lightningnetwork/lnd/ticker v1.0.0 h1:S1b60TEGoTtCe2A0yeB+ecoj/kkS4qpwh6l+AkQEZwU=
Expand Down Expand Up @@ -137,6 +139,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4=
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
Expand Down
6 changes: 6 additions & 0 deletions wallet/createtx.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/btcsuite/btcwallet/wallet/txsizes"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/btcsuite/btcwallet/wtxmgr"
"github.com/lightningnetwork/lnd/fn"
)

func makeInputSource(eligible []Coin) txauthor.InputSource {
Expand Down Expand Up @@ -179,6 +180,11 @@ func (w *Wallet) txToOutputs(outputs []*wire.TxOut,

var inputSource txauthor.InputSource
if len(selectedUtxos) > 0 {
dedupUtxos := fn.NewSet(selectedUtxos...)
if len(dedupUtxos) != len(selectedUtxos) {
return errors.New("selected UTXOs contain " +
"duplicate values")
}
eligibleByOutpoint := make(
map[wire.OutPoint]wtxmgr.Credit,
)
Expand Down
124 changes: 96 additions & 28 deletions wallet/createtx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -449,42 +449,110 @@ func TestSelectUtxosTxoToOutpoint(t *testing.T) {
require.Len(t, unspent, 4, "expected 4 unspent "+
"utxos")

selectUtxos := []wire.OutPoint{
tCases := []struct {
name string
selectUtxos []wire.OutPoint
errString string
}{
{
Hash: incomingTx.TxHash(),
Index: 1,
name: "Duplicate utxo values",
selectUtxos: []wire.OutPoint{
{
Hash: incomingTx.TxHash(),
Index: 1,
},
{
Hash: incomingTx.TxHash(),
Index: 1,
},
},
errString: "selected UTXOs contain duplicate values",
},
{
Hash: incomingTx.TxHash(),
Index: 2,
name: "all selected utxos not eligible for spending",
selectUtxos: []wire.OutPoint{
{
Hash: chainhash.Hash([32]byte{1}),
Index: 1,
},
{
Hash: chainhash.Hash([32]byte{3}),
Index: 1,
},
},
errString: "selected outpoint not eligible for " +
"spending",
},
{
name: "some select utxos not eligible for spending",
selectUtxos: []wire.OutPoint{
{
Hash: chainhash.Hash([32]byte{1}),
Index: 1,
},
{
Hash: incomingTx.TxHash(),
Index: 1,
},
},
errString: "selected outpoint not eligible for " +
"spending",
},
{
name: "select utxo, no duplicates and all eligible " +
"for spending",
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, alwaysAllowUtxo,
)
require.NoError(t, err)
for _, tc := range tCases {
t.Run(tc.name, func(t *testing.T) {
// 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,
tc.selectUtxos, alwaysAllowUtxo,
)
if tc.errString != "" {
require.ErrorContains(t, err, tc.errString)
require.Nil(t, tx1)

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

lookupSelectUtxos := make(map[wire.OutPoint]struct{})
for _, utxo := range selectUtxos {
lookupSelectUtxos[utxo] = struct{}{}
}
require.NoError(t, err)
require.NotNil(t, tx1)

for _, tx := range tx1.Tx.TxIn {
_, ok := lookupSelectUtxos[tx.PreviousOutPoint]
require.True(t, ok, "unexpected outpoint in txin")
}
// We expect all and only our select utxos to be input
// in this transaction.
require.Len(t, tx1.Tx.TxIn, len(tc.selectUtxos))

// Expect two outputs, change and the actual payment to the address.
require.Len(t, tx1.Tx.TxOut, 2)
lookupSelectUtxos := make(map[wire.OutPoint]struct{})
for _, utxo := range tc.selectUtxos {
lookupSelectUtxos[utxo] = struct{}{}
}

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

// Expect two outputs, change and the actual payment to
// the address.
require.Len(t, tx1.Tx.TxOut, 2)
})
}
}
Loading