Merge pull request #1531 from halseth/only-vonfirmed-spends

Only act on confirmed spends
This commit is contained in:
Olaoluwa Osuntokun 2018-07-23 17:36:09 -07:00 committed by GitHub
commit 2eeced5f5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 349 additions and 399 deletions

@ -357,7 +357,7 @@ func (b *breachArbiter) waitForSpendEvent(breachInfo *retributionInfo,
var err error var err error
spendNtfn, err = b.cfg.Notifier.RegisterSpendNtfn( spendNtfn, err = b.cfg.Notifier.RegisterSpendNtfn(
&breachedOutput.outpoint, &breachedOutput.outpoint,
breachInfo.breachHeight, true, breachInfo.breachHeight,
) )
if err != nil { if err != nil {
brarLog.Errorf("unable to check for spentness "+ brarLog.Errorf("unable to check for spentness "+

@ -7,7 +7,6 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
@ -16,6 +15,7 @@ import (
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/wtxmgr" "github.com/btcsuite/btcwallet/wtxmgr"
"github.com/lightningnetwork/lnd/chainntnfs"
) )
const ( const (
@ -331,6 +331,14 @@ out:
// handleRelevantTx notifies any clients of a relevant transaction. // handleRelevantTx notifies any clients of a relevant transaction.
func (b *BitcoindNotifier) handleRelevantTx(tx chain.RelevantTx, bestHeight int32) { func (b *BitcoindNotifier) handleRelevantTx(tx chain.RelevantTx, bestHeight int32) {
msgTx := tx.TxRecord.MsgTx msgTx := tx.TxRecord.MsgTx
// We only care about notifying on confirmed spends, so in case this is
// a mempool spend, we can continue, and wait for the spend to appear
// in chain.
if tx.Block == nil {
return
}
// First, check if this transaction spends an output // First, check if this transaction spends an output
// that has an existing spend notification for it. // that has an existing spend notification for it.
for i, txIn := range msgTx.TxIn { for i, txIn := range msgTx.TxIn {
@ -349,57 +357,22 @@ func (b *BitcoindNotifier) handleRelevantTx(tx chain.RelevantTx, bestHeight int3
SpendingTx: &msgTx, SpendingTx: &msgTx,
SpenderInputIndex: uint32(i), SpenderInputIndex: uint32(i),
} }
// TODO(roasbeef): after change to
// loadfilter, only notify on block
// inclusion?
confirmedSpend := false
if tx.Block != nil {
confirmedSpend = true
spendDetails.SpendingHeight = tx.Block.Height spendDetails.SpendingHeight = tx.Block.Height
} else {
spendDetails.SpendingHeight = bestHeight + 1
}
// Keep spendNotifications that are for _, ntfn := range clients {
// waiting for a confirmation around. chainntnfs.Log.Infof("Dispatching confirmed "+
// They will be notified when we find "spend notification for outpoint=%v "+
// the spend within a block. "at height %v", ntfn.targetOutpoint,
rem := make(map[uint64]*spendNotification)
for c, ntfn := range clients {
// If this is a mempool spend,
// and this client didn't want
// to be notified on mempool
// spends, store it for later.
if !confirmedSpend && !ntfn.mempool {
rem[c] = ntfn
continue
}
confStr := "unconfirmed"
if confirmedSpend {
confStr = "confirmed"
}
chainntnfs.Log.Infof("Dispatching %s "+
"spend notification for "+
"outpoint=%v at height %v",
confStr, ntfn.targetOutpoint,
spendDetails.SpendingHeight) spendDetails.SpendingHeight)
ntfn.spendChan <- spendDetails ntfn.spendChan <- spendDetails
// Close spendChan to ensure that any calls to Cancel will not // Close spendChan to ensure that any calls to
// block. This is safe to do since the channel is buffered, and the // Cancel will not block. This is safe to do
// since the channel is buffered, and the
// message can still be read by the receiver. // message can still be read by the receiver.
close(ntfn.spendChan) close(ntfn.spendChan)
} }
delete(b.spendNotifications, prevOut) delete(b.spendNotifications, prevOut)
// If we had any clients left, add them
// back to the map.
if len(rem) > 0 {
b.spendNotifications[prevOut] = rem
}
} }
} }
} }
@ -560,8 +533,6 @@ type spendNotification struct {
spendID uint64 spendID uint64
heightHint uint32 heightHint uint32
mempool bool
} }
// spendCancel is a message sent to the BitcoindNotifier when a client wishes // spendCancel is a message sent to the BitcoindNotifier when a client wishes
@ -580,13 +551,12 @@ type spendCancel struct {
// across the 'Spend' channel. The heightHint should represent the earliest // across the 'Spend' channel. The heightHint should represent the earliest
// height in the chain where the transaction could have been spent in. // height in the chain where the transaction could have been spent in.
func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
heightHint uint32, mempool bool) (*chainntnfs.SpendEvent, error) { heightHint uint32) (*chainntnfs.SpendEvent, error) {
ntfn := &spendNotification{ ntfn := &spendNotification{
targetOutpoint: outpoint, targetOutpoint: outpoint,
spendChan: make(chan *chainntnfs.SpendDetail, 1), spendChan: make(chan *chainntnfs.SpendDetail, 1),
spendID: atomic.AddUint64(&b.spendClientCounter, 1), spendID: atomic.AddUint64(&b.spendClientCounter, 1),
mempool: mempool,
} }
select { select {

@ -377,6 +377,14 @@ out:
// rescan spends. It might get removed entirely in the future. // rescan spends. It might get removed entirely in the future.
case item := <-b.txUpdates.ChanOut(): case item := <-b.txUpdates.ChanOut():
newSpend := item.(*txUpdate) newSpend := item.(*txUpdate)
// We only care about notifying on confirmed spends, so
// in case this is a mempool spend, we can continue,
// and wait for the spend to appear in chain.
if newSpend.details == nil {
continue
}
spendingTx := newSpend.tx spendingTx := newSpend.tx
// First, check if this transaction spends an output // First, check if this transaction spends an output
@ -397,57 +405,27 @@ out:
SpendingTx: spendingTx.MsgTx(), SpendingTx: spendingTx.MsgTx(),
SpenderInputIndex: uint32(i), SpenderInputIndex: uint32(i),
} }
// TODO(roasbeef): after change to
// loadfilter, only notify on block
// inclusion?
confirmedSpend := false
if newSpend.details != nil {
confirmedSpend = true
spendDetails.SpendingHeight = newSpend.details.Height spendDetails.SpendingHeight = newSpend.details.Height
} else {
spendDetails.SpendingHeight = currentHeight + 1
}
// Keep spendNotifications that are for _, ntfn := range clients {
// waiting for a confirmation around. chainntnfs.Log.Infof("Dispatching "+
// They will be notified when we find "confirmed spend "+
// the spend within a block. "notification for "+
rem := make(map[uint64]*spendNotification)
for c, ntfn := range clients {
// If this is a mempool spend,
// and this client didn't want
// to be notified on mempool
// spends, store it for later.
if !confirmedSpend && !ntfn.mempool {
rem[c] = ntfn
continue
}
confStr := "unconfirmed"
if confirmedSpend {
confStr = "confirmed"
}
chainntnfs.Log.Infof("Dispatching %s "+
"spend notification for "+
"outpoint=%v at height %v", "outpoint=%v at height %v",
confStr, ntfn.targetOutpoint, ntfn.targetOutpoint,
spendDetails.SpendingHeight) spendDetails.SpendingHeight)
ntfn.spendChan <- spendDetails ntfn.spendChan <- spendDetails
// Close spendChan to ensure that any calls to Cancel will not // Close spendChan to ensure
// block. This is safe to do since the channel is buffered, and the // that any calls to Cancel
// message can still be read by the receiver. // will not block. This is safe
// to do since the channel is
// buffered, and the message
// can still be read by the
// receiver.
close(ntfn.spendChan) close(ntfn.spendChan)
} }
delete(b.spendNotifications, prevOut) delete(b.spendNotifications, prevOut)
// If we had any clients left, add them
// back to the map.
if len(rem) > 0 {
b.spendNotifications[prevOut] = rem
}
} }
} }
@ -611,8 +589,6 @@ func (b *BtcdNotifier) handleBlockConnected(newBlock *filteredBlock) error {
continue continue
} }
// TODO(roasbeef): many integration tests expect spend to be
// notified within the mempool.
spendDetails := &chainntnfs.SpendDetail{ spendDetails := &chainntnfs.SpendDetail{
SpentOutPoint: &prevOut, SpentOutPoint: &prevOut,
SpenderTxHash: &txSha, SpenderTxHash: &txSha,
@ -673,8 +649,6 @@ type spendNotification struct {
spendID uint64 spendID uint64
mempool bool
heightHint uint32 heightHint uint32
} }
@ -694,14 +668,13 @@ type spendCancel struct {
// across the 'Spend' channel. The heightHint should represent the earliest // across the 'Spend' channel. The heightHint should represent the earliest
// height in the chain where the transaction could have been spent in. // height in the chain where the transaction could have been spent in.
func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
heightHint uint32, mempool bool) (*chainntnfs.SpendEvent, error) { heightHint uint32) (*chainntnfs.SpendEvent, error) {
ntfn := &spendNotification{ ntfn := &spendNotification{
targetOutpoint: outpoint, targetOutpoint: outpoint,
spendChan: make(chan *chainntnfs.SpendDetail, 1), spendChan: make(chan *chainntnfs.SpendDetail, 1),
spendID: atomic.AddUint64(&b.spendClientCounter, 1), spendID: atomic.AddUint64(&b.spendClientCounter, 1),
heightHint: heightHint, heightHint: heightHint,
mempool: mempool,
} }
select { select {

@ -43,15 +43,13 @@ type ChainNotifier interface {
// The heightHint denotes the earliest height in the blockchain in // The heightHint denotes the earliest height in the blockchain in
// which the target output could have been created. // which the target output could have been created.
// //
// NOTE: If mempool=true is set, then this notification should be // NOTE: The notification should only be triggered when the spending
// triggered on a best-effort basis once the transaction is *seen* on // transaction receives a single confirmation.
// the network. If mempool=false, it should only be triggered when the
// spending transaction receives a single confirmation.
// //
// NOTE: Dispatching notifications to multiple clients subscribed to a // NOTE: Dispatching notifications to multiple clients subscribed to a
// spend of the same outpoint MUST be supported. // spend of the same outpoint MUST be supported.
RegisterSpendNtfn(outpoint *wire.OutPoint, heightHint uint32, RegisterSpendNtfn(outpoint *wire.OutPoint,
mempool bool) (*SpendEvent, error) heightHint uint32) (*SpendEvent, error)
// RegisterBlockEpochNtfn registers an intent to be notified of each // RegisterBlockEpochNtfn registers an intent to be notified of each
// new block connected to the tip of the main chain. The returned // new block connected to the tip of the main chain. The returned

@ -17,9 +17,6 @@ import (
"github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/btcwallet/walletdb"
"github.com/lightninglabs/neutrino" "github.com/lightninglabs/neutrino"
"github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/chainntnfs/bitcoindnotify"
"github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
"github.com/lightningnetwork/lnd/chainntnfs/neutrinonotify"
"github.com/ltcsuite/ltcd/btcjson" "github.com/ltcsuite/ltcd/btcjson"
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
@ -30,6 +27,18 @@ import (
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
// Required to auto-register the bitcoind backed ChainNotifier
// implementation.
_ "github.com/lightningnetwork/lnd/chainntnfs/bitcoindnotify"
// Required to auto-register the btcd backed ChainNotifier
// implementation.
_ "github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
// Required to auto-register the neutrino backed ChainNotifier
// implementation.
_ "github.com/lightningnetwork/lnd/chainntnfs/neutrinonotify"
// Required to register the boltdb walletdb implementation. // Required to register the boltdb walletdb implementation.
_ "github.com/btcsuite/btcwallet/walletdb/bdb" _ "github.com/btcsuite/btcwallet/walletdb/bdb"
) )
@ -403,7 +412,7 @@ func testSpendNotification(miner *rpctest.Harness,
spendClients := make([]*chainntnfs.SpendEvent, numClients) spendClients := make([]*chainntnfs.SpendEvent, numClients)
for i := 0; i < numClients; i++ { for i := 0; i < numClients; i++ {
spentIntent, err := notifier.RegisterSpendNtfn(outpoint, spentIntent, err := notifier.RegisterSpendNtfn(outpoint,
uint32(currentHeight), false) uint32(currentHeight))
if err != nil { if err != nil {
t.Fatalf("unable to register for spend ntfn: %v", err) t.Fatalf("unable to register for spend ntfn: %v", err)
} }
@ -451,6 +460,22 @@ func testSpendNotification(miner *rpctest.Harness,
case <-time.After(mempoolSpendTimeout): case <-time.After(mempoolSpendTimeout):
} }
// Make sure registering a client after the tx is in the mempool still
// doesn't trigger a notification.
spentIntent, err := notifier.RegisterSpendNtfn(outpoint,
uint32(currentHeight))
if err != nil {
t.Fatalf("unable to register for spend ntfn: %v", err)
}
select {
case <-spentIntent.Spend:
t.Fatalf("did not expect to get notification before " +
"block was mined")
case <-time.After(mempoolSpendTimeout):
}
spendClients = append(spendClients, spentIntent)
// Now we mine a single block, which should include our spend. The // Now we mine a single block, which should include our spend. The
// notification should also be sent off. // notification should also be sent off.
if _, err := miner.Node.Generate(1); err != nil { if _, err := miner.Node.Generate(1); err != nil {
@ -475,139 +500,6 @@ func testSpendNotification(miner *rpctest.Harness,
} }
} }
func testSpendNotificationMempoolSpends(miner *rpctest.Harness,
notifier chainntnfs.ChainNotifier, t *testing.T) {
// Skip this test for neutrino and bitcoind backends, as they currently
// don't support notifying about mempool spends.
switch notifier.(type) {
case *neutrinonotify.NeutrinoNotifier:
return
case *bitcoindnotify.BitcoindNotifier:
return
case *btcdnotify.BtcdNotifier:
// Go on to test this implementation.
default:
t.Fatalf("unknown notifier type: %T", notifier)
}
// We first create a new output to our test target address.
outpoint, pkScript := createSpendableOutput(miner, t)
_, currentHeight, err := miner.Node.GetBestBlock()
if err != nil {
t.Fatalf("unable to get current height: %v", err)
}
// Now that we have a output index and the pkScript, register for a
// spentness notification for the newly created output with multiple
// clients in order to ensure the implementation can support
// multi-client spend notifications.
// We first create a list of clients that will be notified on mempool
// spends.
const numClients = 5
spendClientsMempool := make([]*chainntnfs.SpendEvent, numClients)
for i := 0; i < numClients; i++ {
spentIntent, err := notifier.RegisterSpendNtfn(outpoint,
uint32(currentHeight), true)
if err != nil {
t.Fatalf("unable to register for spend ntfn: %v", err)
}
spendClientsMempool[i] = spentIntent
}
// Next, create a new transaction spending that output.
spendingTx := createSpendTx(outpoint, pkScript, t)
// Broadcast our spending transaction.
spenderSha, err := miner.Node.SendRawTransaction(spendingTx, true)
if err != nil {
t.Fatalf("unable to broadcast tx: %v", err)
}
err = waitForMempoolTx(miner, spenderSha)
if err != nil {
t.Fatalf("tx not relayed to miner: %v", err)
}
// Make sure the mempool spend clients are correctly notified.
for _, client := range spendClientsMempool {
select {
case ntfn, ok := <-client.Spend:
if !ok {
t.Fatalf("channel closed unexpectedly")
}
checkNotificationFields(ntfn, outpoint, spenderSha,
currentHeight+1, t)
case <-time.After(5 * time.Second):
t.Fatalf("did not receive notification")
}
}
// Create new clients that register after the tx is in the mempool
// already, but should still be notified.
newSpendClientsMempool := make([]*chainntnfs.SpendEvent, numClients)
for i := 0; i < numClients; i++ {
spentIntent, err := notifier.RegisterSpendNtfn(outpoint,
uint32(currentHeight), true)
if err != nil {
t.Fatalf("unable to register for spend ntfn: %v", err)
}
newSpendClientsMempool[i] = spentIntent
}
// Make sure the new mempool spend clients are correctly notified.
for _, client := range newSpendClientsMempool {
select {
case ntfn, ok := <-client.Spend:
if !ok {
t.Fatalf("channel closed unexpectedly")
}
checkNotificationFields(ntfn, outpoint, spenderSha,
currentHeight+1, t)
case <-time.After(5 * time.Second):
t.Fatalf("did not receive notification")
}
}
// Now we mine a single block, which should include our spend. The
// notification should not be sent off again.
if _, err := miner.Node.Generate(1); err != nil {
t.Fatalf("unable to generate single block: %v", err)
}
// When a block is mined, the mempool notifications we registered should
// not be sent off again, and the channel should be closed.
for _, c := range spendClientsMempool {
select {
case _, ok := <-c.Spend:
if ok {
t.Fatalf("channel should have been closed")
}
case <-time.After(30 * time.Second):
t.Fatalf("expected clients to be closed.")
}
}
for _, c := range newSpendClientsMempool {
select {
case _, ok := <-c.Spend:
if ok {
t.Fatalf("channel should have been closed")
}
case <-time.After(30 * time.Second):
t.Fatalf("expected clients to be closed.")
}
}
}
func testBlockEpochNotification(miner *rpctest.Harness, func testBlockEpochNotification(miner *rpctest.Harness,
notifier chainntnfs.ChainNotifier, t *testing.T) { notifier chainntnfs.ChainNotifier, t *testing.T) {
@ -1062,14 +954,13 @@ func testSpendBeforeNtfnRegistration(miner *rpctest.Harness,
// checkSpends registers two clients to be notified of a spend that has // checkSpends registers two clients to be notified of a spend that has
// already happened. The notifier should dispatch a spend notification // already happened. The notifier should dispatch a spend notification
// immediately. We register one that also listen for mempool spends, // immediately.
// both should be notified the same way, as the spend is already mined.
checkSpends := func() { checkSpends := func() {
const numClients = 2 const numClients = 2
spendClients := make([]*chainntnfs.SpendEvent, numClients) spendClients := make([]*chainntnfs.SpendEvent, numClients)
for i := 0; i < numClients; i++ { for i := 0; i < numClients; i++ {
spentIntent, err := notifier.RegisterSpendNtfn(outpoint, spentIntent, err := notifier.RegisterSpendNtfn(outpoint,
uint32(currentHeight), i%2 == 0) uint32(currentHeight))
if err != nil { if err != nil {
t.Fatalf("unable to register for spend ntfn: %v", t.Fatalf("unable to register for spend ntfn: %v",
err) err)
@ -1149,7 +1040,7 @@ func testCancelSpendNtfn(node *rpctest.Harness,
spendClients := make([]*chainntnfs.SpendEvent, numClients) spendClients := make([]*chainntnfs.SpendEvent, numClients)
for i := 0; i < numClients; i++ { for i := 0; i < numClients; i++ {
spentIntent, err := notifier.RegisterSpendNtfn(outpoint, spentIntent, err := notifier.RegisterSpendNtfn(outpoint,
uint32(currentHeight), true) uint32(currentHeight))
if err != nil { if err != nil {
t.Fatalf("unable to register for spend ntfn: %v", err) t.Fatalf("unable to register for spend ntfn: %v", err)
} }
@ -1446,10 +1337,6 @@ var ntfnTests = []testCase{
name: "spend ntfn", name: "spend ntfn",
test: testSpendNotification, test: testSpendNotification,
}, },
{
name: "spend ntfn mempool",
test: testSpendNotificationMempoolSpends,
},
{ {
name: "block epoch", name: "block epoch",
test: testBlockEpochNotification, test: testBlockEpochNotification,

@ -566,7 +566,7 @@ type spendCancel struct {
// target outpoint has been detected, the details of the spending event will be // target outpoint has been detected, the details of the spending event will be
// sent across the 'Spend' channel. // sent across the 'Spend' channel.
func (n *NeutrinoNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, func (n *NeutrinoNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
heightHint uint32, _ bool) (*chainntnfs.SpendEvent, error) { heightHint uint32) (*chainntnfs.SpendEvent, error) {
n.heightMtx.RLock() n.heightMtx.RLock()
currentHeight := n.bestHeight currentHeight := n.bestHeight

@ -497,13 +497,16 @@ func (c *ChainArbitrator) Stop() error {
// NOTE: This must be launched as a goroutine. // NOTE: This must be launched as a goroutine.
func (c *ChainArbitrator) watchForChannelClose(closeInfo *channeldb.ChannelCloseSummary) { func (c *ChainArbitrator) watchForChannelClose(closeInfo *channeldb.ChannelCloseSummary) {
spendNtfn, err := c.cfg.Notifier.RegisterSpendNtfn( spendNtfn, err := c.cfg.Notifier.RegisterSpendNtfn(
&closeInfo.ChanPoint, closeInfo.CloseHeight, true, &closeInfo.ChanPoint, closeInfo.CloseHeight,
) )
if err != nil { if err != nil {
log.Errorf("unable to register for spend: %v", err) log.Errorf("unable to register for spend: %v", err)
return return
} }
log.Infof("Waiting for ChannelPoint(%v) to be coop closed on chain",
closeInfo.ChanPoint)
var ( var (
commitSpend *chainntnfs.SpendDetail commitSpend *chainntnfs.SpendDetail
ok bool ok bool
@ -517,37 +520,13 @@ func (c *ChainArbitrator) watchForChannelClose(closeInfo *channeldb.ChannelClose
return return
} }
confNtfn, err := c.cfg.Notifier.RegisterConfirmationsNtfn(
commitSpend.SpenderTxHash, 1,
uint32(commitSpend.SpendingHeight),
)
if err != nil {
log.Errorf("unable to register for "+
"conf: %v", err)
return
}
log.Infof("Waiting for txid=%v to close ChannelPoint(%v) on chain",
commitSpend.SpenderTxHash, closeInfo.ChanPoint)
select {
case confInfo, ok := <-confNtfn.Confirmed:
if !ok {
return
}
log.Infof("ChannelPoint(%v) is fully closed, at height: %v", log.Infof("ChannelPoint(%v) is fully closed, at height: %v",
closeInfo.ChanPoint, confInfo.BlockHeight) closeInfo.ChanPoint, commitSpend.SpendingHeight)
err := c.resolveContract(closeInfo.ChanPoint, nil) if err := c.resolveContract(closeInfo.ChanPoint, nil); err != nil {
if err != nil {
log.Errorf("unable to resolve contract: %v", err) log.Errorf("unable to resolve contract: %v", err)
} }
case <-c.quit:
return
}
} }
// ContractSignals wraps the two signals that affect the state of a channel // ContractSignals wraps the two signals that affect the state of a channel

@ -173,7 +173,7 @@ func (c *chainWatcher) Start() error {
} }
spendNtfn, err := c.cfg.notifier.RegisterSpendNtfn( spendNtfn, err := c.cfg.notifier.RegisterSpendNtfn(
fundingOut, heightHint, false, fundingOut, heightHint,
) )
if err != nil { if err != nil {
return err return err

@ -36,7 +36,7 @@ func (m *mockNotifier) Stop() error {
return nil return nil
} }
func (m *mockNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, func (m *mockNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
heightHint uint32, _ bool) (*chainntnfs.SpendEvent, error) { heightHint uint32) (*chainntnfs.SpendEvent, error) {
return &chainntnfs.SpendEvent{ return &chainntnfs.SpendEvent{
Spend: m.spendChan, Spend: m.spendChan,
Cancel: func() {}, Cancel: func() {},

@ -169,45 +169,17 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
// spent, and the spending transaction has been fully confirmed. // spent, and the spending transaction has been fully confirmed.
waitForOutputResolution := func() error { waitForOutputResolution := func() error {
// We first need to register to see when the HTLC output itself // We first need to register to see when the HTLC output itself
// has been spent so we can wait for the spending transaction // has been spent by a confirmed transaction.
// to confirm.
spendNtfn, err := h.Notifier.RegisterSpendNtfn( spendNtfn, err := h.Notifier.RegisterSpendNtfn(
&h.htlcResolution.ClaimOutpoint, &h.htlcResolution.ClaimOutpoint,
h.broadcastHeight, true, h.broadcastHeight,
) )
if err != nil { if err != nil {
return err return err
} }
var spendDetail *chainntnfs.SpendDetail
select { select {
case s, ok := <-spendNtfn.Spend: case _, ok := <-spendNtfn.Spend:
if !ok {
return fmt.Errorf("notifier quit")
}
spendDetail = s
case <-h.Quit:
return fmt.Errorf("quitting")
}
// Now that the output has been spent, we'll also wait for the
// transaction to be confirmed before proceeding.
confNtfn, err := h.Notifier.RegisterConfirmationsNtfn(
spendDetail.SpenderTxHash, 1,
uint32(spendDetail.SpendingHeight-1),
)
if err != nil {
return err
}
log.Infof("%T(%v): waiting for spending (txid=%v) to be fully "+
"confirmed", h, h.htlcResolution.ClaimOutpoint,
spendDetail.SpenderTxHash)
select {
case _, ok := <-confNtfn.Confirmed:
if !ok { if !ok {
return fmt.Errorf("notifier quit") return fmt.Errorf("notifier quit")
} }
@ -608,7 +580,7 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
// To wrap this up, we'll wait until the second-level transaction has // To wrap this up, we'll wait until the second-level transaction has
// been spent, then fully resolve the contract. // been spent, then fully resolve the contract.
spendNtfn, err := h.Notifier.RegisterSpendNtfn( spendNtfn, err := h.Notifier.RegisterSpendNtfn(
&h.htlcResolution.ClaimOutpoint, h.broadcastHeight, true, &h.htlcResolution.ClaimOutpoint, h.broadcastHeight,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -819,8 +791,7 @@ func (h *htlcOutgoingContestResolver) Resolve() (ContractResolver, error) {
// First, we'll register for a spend notification for this output. If // First, we'll register for a spend notification for this output. If
// the remote party sweeps with the pre-image, we'll be notified. // the remote party sweeps with the pre-image, we'll be notified.
spendNtfn, err := h.Notifier.RegisterSpendNtfn( spendNtfn, err := h.Notifier.RegisterSpendNtfn(
&outPointToWatch, &outPointToWatch, h.broadcastHeight,
h.broadcastHeight, true,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -1316,7 +1287,7 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) {
// until the commitment output has been spent. // until the commitment output has been spent.
spendNtfn, err := c.Notifier.RegisterSpendNtfn( spendNtfn, err := c.Notifier.RegisterSpendNtfn(
&c.commitResolution.SelfOutPoint, &c.commitResolution.SelfOutPoint,
c.broadcastHeight, true, c.broadcastHeight,
) )
if err != nil { if err != nil {
return nil, err return nil, err

@ -263,8 +263,8 @@ func (m *mockNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
return nil, nil return nil, nil
} }
func (m *mockNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, _ uint32, func (m *mockNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
_ bool) (*chainntnfs.SpendEvent, error) { _ uint32) (*chainntnfs.SpendEvent, error) {
return nil, nil return nil, nil
} }

@ -126,7 +126,7 @@ func (m *mockNotifier) Stop() error {
} }
func (m *mockNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, func (m *mockNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
heightHint uint32, _ bool) (*chainntnfs.SpendEvent, error) { heightHint uint32) (*chainntnfs.SpendEvent, error) {
return &chainntnfs.SpendEvent{ return &chainntnfs.SpendEvent{
Spend: make(chan *chainntnfs.SpendDetail), Spend: make(chan *chainntnfs.SpendDetail),
Cancel: func() {}, Cancel: func() {},

@ -801,7 +801,7 @@ func (m *mockNotifier) Stop() error {
} }
func (m *mockNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, func (m *mockNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
heightHint uint32, mempool bool) (*chainntnfs.SpendEvent, error) { heightHint uint32) (*chainntnfs.SpendEvent, error) {
return &chainntnfs.SpendEvent{ return &chainntnfs.SpendEvent{
Spend: make(chan *chainntnfs.SpendDetail), Spend: make(chan *chainntnfs.SpendDetail),

@ -1771,7 +1771,7 @@ func testChannelForceClosure(net *lntest.NetworkHarness, t *harnessTest) {
t.Fatalf("htlc mismatch: %v", err) t.Fatalf("htlc mismatch: %v", err)
} }
// As we'll be querying the state of Carol's channels frequently we'll // As we'll be querying the state of Alice's channels frequently we'll
// create a closure helper function for the purpose. // create a closure helper function for the purpose.
getAliceChanInfo := func() (*lnrpc.Channel, error) { getAliceChanInfo := func() (*lnrpc.Channel, error) {
req := &lnrpc.ListChannelsRequest{} req := &lnrpc.ListChannelsRequest{}
@ -2253,7 +2253,7 @@ func testChannelForceClosure(net *lntest.NetworkHarness, t *harnessTest) {
t.Fatalf("no user funds should be left in limbo after incubation") t.Fatalf("no user funds should be left in limbo after incubation")
} }
// At this point, Carol should now be aware of his new immediately // At this point, Bob should now be aware of his new immediately
// spendable on-chain balance, as it was Alice who broadcast the // spendable on-chain balance, as it was Alice who broadcast the
// commitment transaction. // commitment transaction.
carolBalResp, err = net.Bob.WalletBalance(ctxb, carolBalReq) carolBalResp, err = net.Bob.WalletBalance(ctxb, carolBalReq)
@ -5867,17 +5867,17 @@ func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness,
// Query the mempool for Dave's justice transaction, this should be // Query the mempool for Dave's justice transaction, this should be
// broadcast as Carol's contract breaching transaction gets confirmed // broadcast as Carol's contract breaching transaction gets confirmed
// above. Since Carol might have had the time to take some of the HTLC // above. Since Carol might have had the time to take some of the HTLC
// outputs to the second level before Alice broadcasts her justice tx, // outputs to the second level before Dave broadcasts his justice tx,
// we'll search through the mempool for a tx that matches the number of // we'll search through the mempool for a tx that matches the number of
// expected inputs in the justice tx. // expected inputs in the justice tx.
// TODO(halseth): change to deterministic check if/when only acting on
// confirmed second level spends?
var predErr error var predErr error
var justiceTxid *chainhash.Hash var justiceTxid *chainhash.Hash
err = lntest.WaitPredicate(func() bool { errNotFound := errors.New("justice tx not found")
findJusticeTx := func() (*chainhash.Hash, error) {
mempool, err := net.Miner.Node.GetRawMempool() mempool, err := net.Miner.Node.GetRawMempool()
if err != nil { if err != nil {
t.Fatalf("unable to get mempool from miner: %v", err) return nil, fmt.Errorf("unable to get mempool from "+
"miner: %v", err)
} }
for _, txid := range mempool { for _, txid := range mempool {
@ -5885,22 +5885,46 @@ func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness,
// of inputs. // of inputs.
tx, err := net.Miner.Node.GetRawTransaction(txid) tx, err := net.Miner.Node.GetRawTransaction(txid)
if err != nil { if err != nil {
predErr = fmt.Errorf("unable to query for "+ return nil, fmt.Errorf("unable to query for "+
"txs: %v", err) "txs: %v", err)
return false
} }
exNumInputs := 2 + numInvoices exNumInputs := 2 + numInvoices
if len(tx.MsgTx().TxIn) == exNumInputs { if len(tx.MsgTx().TxIn) == exNumInputs {
return txid, nil
}
}
return nil, errNotFound
}
err = lntest.WaitPredicate(func() bool {
txid, err := findJusticeTx()
if err != nil {
predErr = err
return false
}
justiceTxid = txid justiceTxid = txid
return true return true
} }, time.Second*10)
if err != nil && predErr == errNotFound {
} // If Dave is unable to broadcast his justice tx on first
// attempt because of the second layer transactions, he will
predErr = fmt.Errorf("justice tx not found") // wait until the next block epoch before trying again. Because
// of this, we'll mine a block if we cannot find the justice tx
// immediately.
mineBlocks(t, net, 1)
err = lntest.WaitPredicate(func() bool {
txid, err := findJusticeTx()
if err != nil {
predErr = err
return false return false
}, time.Second*15) }
justiceTxid = txid
return true
}, time.Second*10)
}
if err != nil { if err != nil {
t.Fatalf(predErr.Error()) t.Fatalf(predErr.Error())
} }
@ -8276,7 +8300,8 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest)
// At this point, Bob decides that he wants to exit the channel // At this point, Bob decides that he wants to exit the channel
// immediately, so he force closes his commitment transaction. // immediately, so he force closes his commitment transaction.
ctxt, _ := context.WithTimeout(ctxb, timeout) ctxt, _ := context.WithTimeout(ctxb, timeout)
closeChannelAndAssert(ctxt, t, net, net.Bob, aliceChanPoint, true) bobForceClose := closeChannelAndAssert(ctxt, t, net, net.Bob,
aliceChanPoint, true)
// We'll now mine enough blocks so Carol decides that she needs to go // We'll now mine enough blocks so Carol decides that she needs to go
// on-chain to claim the HTLC as Bob has been inactive. // on-chain to claim the HTLC as Bob has been inactive.
@ -8324,31 +8349,58 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest)
// After the force close transacion is mined, Carol should broadcast // After the force close transacion is mined, Carol should broadcast
// her second level HTLC transacion. Bob will braodcast a sweep tx to // her second level HTLC transacion. Bob will braodcast a sweep tx to
// sweep his output in the channel with Carol. When Bob notices Carol's // sweep his output in the channel with Carol. He can do this
// second level transaction in the mempool, he will extract the // immediately, as the output is not timelocked since Carol was the one
// preimage and broadcast a second level tx to claim the HTLC in his // force closing.
// (already closed) channel with Alice. commitSpends, err := waitForNTxsInMempool(net.Miner.Node, 2,
secondLevelHashes, err := waitForNTxsInMempool(net.Miner.Node, 3,
time.Second*20) time.Second*20)
if err != nil { if err != nil {
t.Fatalf("transactions not found in mempool: %v", err) t.Fatalf("transactions not found in mempool: %v", err)
} }
// Carol's second level transaction should be spending from // Both Carol's second level transaction and Bob's sweep should be
// the commitment transaction. // spending from the commitment transaction.
var secondLevelHash *chainhash.Hash for _, txid := range commitSpends {
for _, txid := range secondLevelHashes {
tx, err := net.Miner.Node.GetRawTransaction(txid) tx, err := net.Miner.Node.GetRawTransaction(txid)
if err != nil { if err != nil {
t.Fatalf("unable to get txn: %v", err) t.Fatalf("unable to get txn: %v", err)
} }
if tx.MsgTx().TxIn[0].PreviousOutPoint.Hash == *commitHash { if tx.MsgTx().TxIn[0].PreviousOutPoint.Hash != *commitHash {
secondLevelHash = txid t.Fatalf("tx did not spend from commitment tx")
} }
} }
if secondLevelHash == nil {
t.Fatalf("Carol's second level tx not found") // Mine a block to confirm the two transactions (+ the coinbase).
block = mineBlocks(t, net, 1)[0]
if len(block.Transactions) != 3 {
t.Fatalf("expected 3 transactions in block, got %v",
len(block.Transactions))
}
for _, txid := range commitSpends {
assertTxInBlock(t, block, txid)
}
// Keep track of the second level tx maturity.
carolSecondLevelCSV := defaultCSV
// When Bob notices Carol's second level transaction in the block, he
// will extract the preimage and broadcast a second level tx to claim
// the HTLC in his (already closed) channel with Alice.
bobSecondLvlTx, err := waitForTxInMempool(net.Miner.Node,
time.Second*20)
if err != nil {
t.Fatalf("transactions not found in mempool: %v", err)
}
// It should spend from the commitment in the channel with Alice.
tx, err := net.Miner.Node.GetRawTransaction(bobSecondLvlTx)
if err != nil {
t.Fatalf("unable to get txn: %v", err)
}
if tx.MsgTx().TxIn[0].PreviousOutPoint.Hash != *bobForceClose {
t.Fatalf("tx did not spend from bob's force close tx")
} }
// At this point, Bob should have broadcast his second layer success // At this point, Bob should have broadcast his second layer success
@ -8388,41 +8440,63 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest)
return false return false
} }
} }
return true return true
}, time.Second*15) }, time.Second*15)
if err != nil { if err != nil {
t.Fatalf("bob didn't hand off time-locked HTLC: %v", predErr) t.Fatalf("bob didn't hand off time-locked HTLC: %v", predErr)
} }
// We'll now mine a block which should confirm the two second layer // We'll now mine a block which should confirm Bob's second layer
// transactions and the commit sweep. // transaction.
block = mineBlocks(t, net, 1)[0] block = mineBlocks(t, net, 1)[0]
if len(block.Transactions) != 4 { if len(block.Transactions) != 2 {
t.Fatalf("expected 4 transactions in block, got %v", t.Fatalf("expected 2 transactions in block, got %v",
len(block.Transactions)) len(block.Transactions))
} }
assertTxInBlock(t, block, secondLevelHash) assertTxInBlock(t, block, bobSecondLvlTx)
// If we then mine 4 additional blocks, Bob and Carol should sweep the // Keep track of Bob's second level maturity, and decrement our track
// outputs destined for them. // of Carol's.
if _, err := net.Miner.Node.Generate(defaultCSV); err != nil { bobSecondLevelCSV := defaultCSV
carolSecondLevelCSV--
// If we then mine 3 additional blocks, Carol's second level tx should
// mature, and she can pull the funds from it with a sweep tx.
if _, err := net.Miner.Node.Generate(carolSecondLevelCSV); err != nil {
t.Fatalf("unable to generate block: %v", err) t.Fatalf("unable to generate block: %v", err)
} }
bobSecondLevelCSV -= carolSecondLevelCSV
sweepTxs, err := waitForNTxsInMempool(net.Miner.Node, 2, time.Second*10) carolSweep, err := waitForTxInMempool(net.Miner.Node, time.Second*10)
if err != nil { if err != nil {
t.Fatalf("unable to find sweeping transactions: %v", err) t.Fatalf("unable to find Carol's sweeping transaction: %v", err)
} }
// At this point, Bob should detect that he has no pending channels // Mining one additional block, Bob's second level tx is mature, and he
// anymore, as this just resolved it by the confirmation of the sweep // can sweep the output.
// transaction we detected above. block = mineBlocks(t, net, bobSecondLevelCSV)[0]
block = mineBlocks(t, net, 1)[0] assertTxInBlock(t, block, carolSweep)
for _, sweepTx := range sweepTxs {
assertTxInBlock(t, block, sweepTx) bobSweep, err := waitForTxInMempool(net.Miner.Node, time.Second*10)
if err != nil {
t.Fatalf("unable to find bob's sweeping transaction")
} }
// Make sure it spends from the second level tx.
tx, err = net.Miner.Node.GetRawTransaction(bobSweep)
if err != nil {
t.Fatalf("unable to get txn: %v", err)
}
if tx.MsgTx().TxIn[0].PreviousOutPoint.Hash != *bobSecondLvlTx {
t.Fatalf("tx did not spend from bob's second level tx")
}
// When we mine one additional block, that will confirm Bob's sweep.
// Now Bob should have no pending channels anymore, as this just
// resolved it by the confirmation of the sweep transaction.
block = mineBlocks(t, net, 1)[0]
assertTxInBlock(t, block, bobSweep)
err = lntest.WaitPredicate(func() bool { err = lntest.WaitPredicate(func() bool {
pendingChanResp, err := net.Bob.PendingChannels( pendingChanResp, err := net.Bob.PendingChannels(
ctxb, pendingChansRequest, ctxb, pendingChansRequest,
@ -8437,7 +8511,54 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest)
"but shouldn't: %v", spew.Sdump(pendingChanResp)) "but shouldn't: %v", spew.Sdump(pendingChanResp))
return false return false
} }
req := &lnrpc.ListChannelsRequest{}
chanInfo, err := net.Bob.ListChannels(ctxb, req)
if err != nil {
predErr = fmt.Errorf("unable to query for open "+
"channels: %v", err)
return false
}
if len(chanInfo.Channels) != 0 {
predErr = fmt.Errorf("Bob should have no open "+
"channels, instead he has %v",
len(chanInfo.Channels))
return false
}
return true
}, time.Second*15)
if err != nil {
t.Fatalf(predErr.Error())
}
// Also Carol should have no channels left (open nor pending).
err = lntest.WaitPredicate(func() bool {
pendingChanResp, err := carol.PendingChannels(
ctxb, pendingChansRequest,
)
if err != nil {
predErr = fmt.Errorf("unable to query for pending "+
"channels: %v", err)
return false
}
if len(pendingChanResp.PendingForceClosingChannels) != 0 {
predErr = fmt.Errorf("bob carol has pending channels "+
"but shouldn't: %v", spew.Sdump(pendingChanResp))
return false
}
req := &lnrpc.ListChannelsRequest{}
chanInfo, err := carol.ListChannels(ctxb, req)
if err != nil {
predErr = fmt.Errorf("unable to query for open "+
"channels: %v", err)
return false
}
if len(chanInfo.Channels) != 0 {
predErr = fmt.Errorf("carol should have no open "+
"channels, instead she has %v",
len(chanInfo.Channels))
return false
}
return true return true
}, time.Second*15) }, time.Second*15)
if err != nil { if err != nil {
@ -8506,7 +8627,8 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest
// immediately force close the channel by broadcast her commitment // immediately force close the channel by broadcast her commitment
// transaction. // transaction.
ctxt, _ := context.WithTimeout(ctxb, timeout) ctxt, _ := context.WithTimeout(ctxb, timeout)
closeChannelAndAssert(ctxt, t, net, net.Alice, aliceChanPoint, true) aliceForceClose := closeChannelAndAssert(ctxt, t, net, net.Alice,
aliceChanPoint, true)
// We'll now mine enough blocks so Carol decides that she needs to go // We'll now mine enough blocks so Carol decides that she needs to go
// on-chain to claim the HTLC as Bob has been inactive. // on-chain to claim the HTLC as Bob has been inactive.
@ -8555,58 +8677,68 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest
// After the force close transacion is mined, Carol should broadcast // After the force close transacion is mined, Carol should broadcast
// her second level HTLC transacion. Bob will braodcast a sweep tx to // her second level HTLC transacion. Bob will braodcast a sweep tx to
// sweep his output in the channel with Carol. When Bob notices Carol's // sweep his output in the channel with Carol. He can do this
// second level transaction in the mempool, he will extract the // immediately, as the output is not timelocked since Carol was the one
// preimage and broadcast a second level tx to claim the HTLC in his // force closing.
// (already closed) channel with Alice. commitSpends, err := waitForNTxsInMempool(net.Miner.Node, 2,
secondLevelHashes, err := waitForNTxsInMempool(net.Miner.Node, 3,
time.Second*20) time.Second*20)
if err != nil { if err != nil {
t.Fatalf("transactions not found in mempool: %v", err) t.Fatalf("transactions not found in mempool: %v", err)
} }
// Carol's second level transaction should be spending from // Both Carol's second level transaction and Bob's sweep should be
// the commitment transaction. // spending from the commitment transaction.
var secondLevelHash *chainhash.Hash for _, txid := range commitSpends {
for _, txid := range secondLevelHashes {
tx, err := net.Miner.Node.GetRawTransaction(txid) tx, err := net.Miner.Node.GetRawTransaction(txid)
if err != nil { if err != nil {
t.Fatalf("unable to get txn: %v", err) t.Fatalf("unable to get txn: %v", err)
} }
if tx.MsgTx().TxIn[0].PreviousOutPoint.Hash == *commitHash { if tx.MsgTx().TxIn[0].PreviousOutPoint.Hash != *commitHash {
secondLevelHash = txid t.Fatalf("tx did not spend from commitment tx")
} }
} }
if secondLevelHash == nil {
t.Fatalf("Carol's second level tx not found")
}
// We'll now mine a block which should confirm the two second layer // Mine a block to confirm the two transactions (+ coinbase).
// transactions and the commit sweep.
block = mineBlocks(t, net, 1)[0] block = mineBlocks(t, net, 1)[0]
if len(block.Transactions) != 4 { if len(block.Transactions) != 3 {
t.Fatalf("expected 4 transactions in block, got %v", t.Fatalf("expected 3 transactions in block, got %v",
len(block.Transactions)) len(block.Transactions))
} }
assertTxInBlock(t, block, secondLevelHash) for _, txid := range commitSpends {
assertTxInBlock(t, block, txid)
// If we then mine 4 additional blocks, Bob should pull the output
// destined for him.
if _, err := net.Miner.Node.Generate(defaultCSV); err != nil {
t.Fatalf("unable to generate block: %v", err)
} }
_, err = waitForNTxsInMempool(net.Miner.Node, 1, time.Second*15) // Keep track of the second level tx maturity.
carolSecondLevelCSV := defaultCSV
// When Bob notices Carol's second level transaction in the block, he
// will extract the preimage and broadcast a sweep tx to directly claim
// the HTLC in his (already closed) channel with Alice.
bobHtlcSweep, err := waitForTxInMempool(net.Miner.Node,
time.Second*20)
if err != nil { if err != nil {
t.Fatalf("unable to find bob's sweeping transaction: %v", err) t.Fatalf("transactions not found in mempool: %v", err)
} }
// We'll now mine another block, this should confirm the sweep // It should spend from the commitment in the channel with Alice.
// transaction that Bob broadcast in the prior stage. tx, err := net.Miner.Node.GetRawTransaction(bobHtlcSweep)
if _, err := net.Miner.Node.Generate(1); err != nil { if err != nil {
t.Fatalf("unable to generate block: %v", err) t.Fatalf("unable to get txn: %v", err)
} }
if tx.MsgTx().TxIn[0].PreviousOutPoint.Hash != *aliceForceClose {
t.Fatalf("tx did not spend from alice's force close tx")
}
// We'll now mine a block which should confirm Bob's HTLC sweep
// transaction.
block = mineBlocks(t, net, 1)[0]
if len(block.Transactions) != 2 {
t.Fatalf("expected 2 transactions in block, got %v",
len(block.Transactions))
}
assertTxInBlock(t, block, bobHtlcSweep)
carolSecondLevelCSV--
// Now that the sweeping transaction has been confirmed, Bob should now // Now that the sweeping transaction has been confirmed, Bob should now
// recognize that all contracts have been fully resolved, and show no // recognize that all contracts have been fully resolved, and show no
@ -8632,6 +8764,44 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest
if err != nil { if err != nil {
t.Fatalf(predErr.Error()) t.Fatalf(predErr.Error())
} }
// If we then mine 3 additional blocks, Carol's second level tx will
// mature, and she should pull the funds.
if _, err := net.Miner.Node.Generate(carolSecondLevelCSV); err != nil {
t.Fatalf("unable to generate block: %v", err)
}
carolSweep, err := waitForTxInMempool(net.Miner.Node, time.Second*10)
if err != nil {
t.Fatalf("unable to find Carol's sweeping transaction: %v", err)
}
// When Carol's sweep gets confirmed, she should have no more pending
// channels.
block = mineBlocks(t, net, 1)[0]
assertTxInBlock(t, block, carolSweep)
pendingChansRequest = &lnrpc.PendingChannelsRequest{}
err = lntest.WaitPredicate(func() bool {
pendingChanResp, err := carol.PendingChannels(
ctxb, pendingChansRequest,
)
if err != nil {
predErr = fmt.Errorf("unable to query for pending "+
"channels: %v", err)
return false
}
if len(pendingChanResp.PendingForceClosingChannels) != 0 {
predErr = fmt.Errorf("carol still has pending channels "+
"but shouldn't: %v", spew.Sdump(pendingChanResp))
return false
}
return true
}, time.Second*15)
if err != nil {
t.Fatalf(predErr.Error())
}
} }
// testSwitchCircuitPersistence creates a multihop network to ensure the sender // testSwitchCircuitPersistence creates a multihop network to ensure the sender

@ -335,9 +335,11 @@ func (hn *HarnessNode) start(lndError chan<- error) error {
// Launch a new goroutine which that bubbles up any potential fatal // Launch a new goroutine which that bubbles up any potential fatal
// process errors to the goroutine running the tests. // process errors to the goroutine running the tests.
hn.processExit = make(chan struct{}) hn.processExit = make(chan struct{})
hn.wg.Add(1)
go func() { go func() {
err := hn.cmd.Wait() defer hn.wg.Done()
err := hn.cmd.Wait()
if err != nil { if err != nil {
lndError <- errors.Errorf("%v\n%v\n", err, errb.String()) lndError <- errors.Errorf("%v\n%v\n", err, errb.String())
} }

@ -106,7 +106,7 @@ func (m *mockNotfier) Stop() error {
return nil return nil
} }
func (m *mockNotfier) RegisterSpendNtfn(outpoint *wire.OutPoint, func (m *mockNotfier) RegisterSpendNtfn(outpoint *wire.OutPoint,
heightHint uint32, _ bool) (*chainntnfs.SpendEvent, error) { heightHint uint32) (*chainntnfs.SpendEvent, error) {
return &chainntnfs.SpendEvent{ return &chainntnfs.SpendEvent{
Spend: make(chan *chainntnfs.SpendDetail), Spend: make(chan *chainntnfs.SpendDetail),
Cancel: func() {}, Cancel: func() {},
@ -131,7 +131,7 @@ func makeMockSpendNotifier() *mockSpendNotifier {
} }
func (m *mockSpendNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, func (m *mockSpendNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
heightHint uint32, _ bool) (*chainntnfs.SpendEvent, error) { heightHint uint32) (*chainntnfs.SpendEvent, error) {
m.mtx.Lock() m.mtx.Lock()
defer m.mtx.Unlock() defer m.mtx.Unlock()