e01600fdb8
This commit allows sweeper to sweep inputs that on its own are not able to form a sweep transaction that meets the dust limit. This functionality is useful for sweeping small outputs. In the future, this will be particularly important to sweep anchors. Anchors will typically be spent with a relatively large fee to pay for the parent tx. It will then be necessary to attach an additional wallet utxo.
142 lines
3.5 KiB
Go
142 lines
3.5 KiB
Go
package sweep
|
|
|
|
import (
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
|
)
|
|
|
|
// mockBackend simulates a chain backend for realistic behaviour in unit tests
|
|
// around double spends.
|
|
type mockBackend struct {
|
|
t *testing.T
|
|
|
|
lock sync.Mutex
|
|
|
|
notifier *MockNotifier
|
|
|
|
confirmedSpendInputs map[wire.OutPoint]struct{}
|
|
|
|
unconfirmedTxes map[chainhash.Hash]*wire.MsgTx
|
|
unconfirmedSpendInputs map[wire.OutPoint]struct{}
|
|
|
|
publishChan chan wire.MsgTx
|
|
|
|
walletUtxos []*lnwallet.Utxo
|
|
}
|
|
|
|
func newMockBackend(t *testing.T, notifier *MockNotifier) *mockBackend {
|
|
return &mockBackend{
|
|
t: t,
|
|
notifier: notifier,
|
|
unconfirmedTxes: make(map[chainhash.Hash]*wire.MsgTx),
|
|
confirmedSpendInputs: make(map[wire.OutPoint]struct{}),
|
|
unconfirmedSpendInputs: make(map[wire.OutPoint]struct{}),
|
|
publishChan: make(chan wire.MsgTx, 2),
|
|
}
|
|
}
|
|
|
|
func (b *mockBackend) publishTransaction(tx *wire.MsgTx) error {
|
|
b.lock.Lock()
|
|
defer b.lock.Unlock()
|
|
|
|
txHash := tx.TxHash()
|
|
if _, ok := b.unconfirmedTxes[txHash]; ok {
|
|
// Tx already exists
|
|
testLog.Tracef("mockBackend duplicate tx %v", tx.TxHash())
|
|
return lnwallet.ErrDoubleSpend
|
|
}
|
|
|
|
for _, in := range tx.TxIn {
|
|
if _, ok := b.unconfirmedSpendInputs[in.PreviousOutPoint]; ok {
|
|
// Double spend
|
|
testLog.Tracef("mockBackend double spend tx %v", tx.TxHash())
|
|
return lnwallet.ErrDoubleSpend
|
|
}
|
|
|
|
if _, ok := b.confirmedSpendInputs[in.PreviousOutPoint]; ok {
|
|
// Already included in block
|
|
testLog.Tracef("mockBackend already in block tx %v", tx.TxHash())
|
|
return lnwallet.ErrDoubleSpend
|
|
}
|
|
}
|
|
|
|
b.unconfirmedTxes[txHash] = tx
|
|
for _, in := range tx.TxIn {
|
|
b.unconfirmedSpendInputs[in.PreviousOutPoint] = struct{}{}
|
|
}
|
|
|
|
testLog.Tracef("mockBackend publish tx %v", tx.TxHash())
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *mockBackend) PublishTransaction(tx *wire.MsgTx) error {
|
|
log.Tracef("Publishing tx %v", tx.TxHash())
|
|
err := b.publishTransaction(tx)
|
|
select {
|
|
case b.publishChan <- *tx:
|
|
case <-time.After(defaultTestTimeout):
|
|
b.t.Fatalf("unexpected tx published")
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (b *mockBackend) ListUnspentWitness(minconfirms, maxconfirms int32) (
|
|
[]*lnwallet.Utxo, error) {
|
|
|
|
return b.walletUtxos, nil
|
|
}
|
|
|
|
func (b *mockBackend) WithCoinSelectLock(f func() error) error {
|
|
return f()
|
|
}
|
|
|
|
func (b *mockBackend) deleteUnconfirmed(txHash chainhash.Hash) {
|
|
b.lock.Lock()
|
|
defer b.lock.Unlock()
|
|
|
|
tx, ok := b.unconfirmedTxes[txHash]
|
|
if !ok {
|
|
// Tx already exists
|
|
testLog.Errorf("mockBackend delete tx not existing %v", txHash)
|
|
return
|
|
}
|
|
|
|
testLog.Tracef("mockBackend delete tx %v", tx.TxHash())
|
|
delete(b.unconfirmedTxes, txHash)
|
|
for _, in := range tx.TxIn {
|
|
delete(b.unconfirmedSpendInputs, in.PreviousOutPoint)
|
|
}
|
|
}
|
|
|
|
func (b *mockBackend) mine() {
|
|
b.lock.Lock()
|
|
defer b.lock.Unlock()
|
|
|
|
notifications := make(map[wire.OutPoint]*wire.MsgTx)
|
|
for _, tx := range b.unconfirmedTxes {
|
|
testLog.Tracef("mockBackend mining tx %v", tx.TxHash())
|
|
for _, in := range tx.TxIn {
|
|
b.confirmedSpendInputs[in.PreviousOutPoint] = struct{}{}
|
|
notifications[in.PreviousOutPoint] = tx
|
|
}
|
|
}
|
|
b.unconfirmedSpendInputs = make(map[wire.OutPoint]struct{})
|
|
b.unconfirmedTxes = make(map[chainhash.Hash]*wire.MsgTx)
|
|
|
|
for outpoint, tx := range notifications {
|
|
testLog.Tracef("mockBackend delivering spend ntfn for %v",
|
|
outpoint)
|
|
b.notifier.SpendOutpoint(outpoint, *tx)
|
|
}
|
|
}
|
|
|
|
func (b *mockBackend) isDone() bool {
|
|
return len(b.unconfirmedTxes) == 0
|
|
}
|