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
spendNtfn, err = b.cfg.Notifier.RegisterSpendNtfn(
&breachedOutput.outpoint,
breachInfo.breachHeight, true,
breachInfo.breachHeight,
)
if err != nil {
brarLog.Errorf("unable to check for spentness "+

@ -7,7 +7,6 @@ import (
"sync/atomic"
"time"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
@ -16,6 +15,7 @@ import (
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/wtxmgr"
"github.com/lightningnetwork/lnd/chainntnfs"
)
const (
@ -331,6 +331,14 @@ out:
// handleRelevantTx notifies any clients of a relevant transaction.
func (b *BitcoindNotifier) handleRelevantTx(tx chain.RelevantTx, bestHeight int32) {
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
// that has an existing spend notification for it.
for i, txIn := range msgTx.TxIn {
@ -349,57 +357,22 @@ func (b *BitcoindNotifier) handleRelevantTx(tx chain.RelevantTx, bestHeight int3
SpendingTx: &msgTx,
SpenderInputIndex: uint32(i),
}
// TODO(roasbeef): after change to
// loadfilter, only notify on block
// inclusion?
spendDetails.SpendingHeight = tx.Block.Height
confirmedSpend := false
if tx.Block != nil {
confirmedSpend = true
spendDetails.SpendingHeight = tx.Block.Height
} else {
spendDetails.SpendingHeight = bestHeight + 1
}
// Keep spendNotifications that are
// waiting for a confirmation around.
// They will be notified when we find
// the spend within a block.
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,
for _, ntfn := range clients {
chainntnfs.Log.Infof("Dispatching confirmed "+
"spend notification for outpoint=%v "+
"at height %v", ntfn.targetOutpoint,
spendDetails.SpendingHeight)
ntfn.spendChan <- spendDetails
// Close spendChan to ensure that any calls to Cancel will not
// block. This is safe to do since the channel is buffered, and the
// Close spendChan to ensure that any calls to
// Cancel 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)
}
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
heightHint uint32
mempool bool
}
// 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
// height in the chain where the transaction could have been spent in.
func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
heightHint uint32, mempool bool) (*chainntnfs.SpendEvent, error) {
heightHint uint32) (*chainntnfs.SpendEvent, error) {
ntfn := &spendNotification{
targetOutpoint: outpoint,
spendChan: make(chan *chainntnfs.SpendDetail, 1),
spendID: atomic.AddUint64(&b.spendClientCounter, 1),
mempool: mempool,
}
select {

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

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

@ -17,9 +17,6 @@ import (
"github.com/btcsuite/btcwallet/walletdb"
"github.com/lightninglabs/neutrino"
"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/btcsuite/btcd/btcec"
@ -30,6 +27,18 @@ import (
"github.com/btcsuite/btcd/wire"
"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.
_ "github.com/btcsuite/btcwallet/walletdb/bdb"
)
@ -403,7 +412,7 @@ func testSpendNotification(miner *rpctest.Harness,
spendClients := make([]*chainntnfs.SpendEvent, numClients)
for i := 0; i < numClients; i++ {
spentIntent, err := notifier.RegisterSpendNtfn(outpoint,
uint32(currentHeight), false)
uint32(currentHeight))
if err != nil {
t.Fatalf("unable to register for spend ntfn: %v", err)
}
@ -451,6 +460,22 @@ func testSpendNotification(miner *rpctest.Harness,
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
// notification should also be sent off.
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,
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
// already happened. The notifier should dispatch a spend notification
// immediately. We register one that also listen for mempool spends,
// both should be notified the same way, as the spend is already mined.
// immediately.
checkSpends := func() {
const numClients = 2
spendClients := make([]*chainntnfs.SpendEvent, numClients)
for i := 0; i < numClients; i++ {
spentIntent, err := notifier.RegisterSpendNtfn(outpoint,
uint32(currentHeight), i%2 == 0)
uint32(currentHeight))
if err != nil {
t.Fatalf("unable to register for spend ntfn: %v",
err)
@ -1149,7 +1040,7 @@ func testCancelSpendNtfn(node *rpctest.Harness,
spendClients := make([]*chainntnfs.SpendEvent, numClients)
for i := 0; i < numClients; i++ {
spentIntent, err := notifier.RegisterSpendNtfn(outpoint,
uint32(currentHeight), true)
uint32(currentHeight))
if err != nil {
t.Fatalf("unable to register for spend ntfn: %v", err)
}
@ -1446,10 +1337,6 @@ var ntfnTests = []testCase{
name: "spend ntfn",
test: testSpendNotification,
},
{
name: "spend ntfn mempool",
test: testSpendNotificationMempoolSpends,
},
{
name: "block epoch",
test: testBlockEpochNotification,

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

@ -497,13 +497,16 @@ func (c *ChainArbitrator) Stop() error {
// NOTE: This must be launched as a goroutine.
func (c *ChainArbitrator) watchForChannelClose(closeInfo *channeldb.ChannelCloseSummary) {
spendNtfn, err := c.cfg.Notifier.RegisterSpendNtfn(
&closeInfo.ChanPoint, closeInfo.CloseHeight, true,
&closeInfo.ChanPoint, closeInfo.CloseHeight,
)
if err != nil {
log.Errorf("unable to register for spend: %v", err)
return
}
log.Infof("Waiting for ChannelPoint(%v) to be coop closed on chain",
closeInfo.ChanPoint)
var (
commitSpend *chainntnfs.SpendDetail
ok bool
@ -517,35 +520,11 @@ func (c *ChainArbitrator) watchForChannelClose(closeInfo *channeldb.ChannelClose
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("ChannelPoint(%v) is fully closed, at height: %v",
closeInfo.ChanPoint, commitSpend.SpendingHeight)
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",
closeInfo.ChanPoint, confInfo.BlockHeight)
err := c.resolveContract(closeInfo.ChanPoint, nil)
if err != nil {
log.Errorf("unable to resolve contract: %v", err)
}
case <-c.quit:
return
if err := c.resolveContract(closeInfo.ChanPoint, nil); err != nil {
log.Errorf("unable to resolve contract: %v", err)
}
}

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

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

@ -169,45 +169,17 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
// spent, and the spending transaction has been fully confirmed.
waitForOutputResolution := func() error {
// We first need to register to see when the HTLC output itself
// has been spent so we can wait for the spending transaction
// to confirm.
// has been spent by a confirmed transaction.
spendNtfn, err := h.Notifier.RegisterSpendNtfn(
&h.htlcResolution.ClaimOutpoint,
h.broadcastHeight, true,
h.broadcastHeight,
)
if err != nil {
return err
}
var spendDetail *chainntnfs.SpendDetail
select {
case s, 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:
case _, ok := <-spendNtfn.Spend:
if !ok {
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
// been spent, then fully resolve the contract.
spendNtfn, err := h.Notifier.RegisterSpendNtfn(
&h.htlcResolution.ClaimOutpoint, h.broadcastHeight, true,
&h.htlcResolution.ClaimOutpoint, h.broadcastHeight,
)
if err != nil {
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
// the remote party sweeps with the pre-image, we'll be notified.
spendNtfn, err := h.Notifier.RegisterSpendNtfn(
&outPointToWatch,
h.broadcastHeight, true,
&outPointToWatch, h.broadcastHeight,
)
if err != nil {
return nil, err
@ -1316,7 +1287,7 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) {
// until the commitment output has been spent.
spendNtfn, err := c.Notifier.RegisterSpendNtfn(
&c.commitResolution.SelfOutPoint,
c.broadcastHeight, true,
c.broadcastHeight,
)
if err != nil {
return nil, err

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

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

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

@ -1771,7 +1771,7 @@ func testChannelForceClosure(net *lntest.NetworkHarness, t *harnessTest) {
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.
getAliceChanInfo := func() (*lnrpc.Channel, error) {
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")
}
// 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
// commitment transaction.
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
// broadcast as Carol's contract breaching transaction gets confirmed
// 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
// 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 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()
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 {
@ -5885,22 +5885,46 @@ func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness,
// of inputs.
tx, err := net.Miner.Node.GetRawTransaction(txid)
if err != nil {
predErr = fmt.Errorf("unable to query for "+
return nil, fmt.Errorf("unable to query for "+
"txs: %v", err)
return false
}
exNumInputs := 2 + numInvoices
if len(tx.MsgTx().TxIn) == exNumInputs {
justiceTxid = txid
return true
return txid, nil
}
}
return nil, errNotFound
}
err = lntest.WaitPredicate(func() bool {
txid, err := findJusticeTx()
if err != nil {
predErr = err
return false
}
predErr = fmt.Errorf("justice tx not found")
return false
}, time.Second*15)
justiceTxid = txid
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
// 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
}
justiceTxid = txid
return true
}, time.Second*10)
}
if err != nil {
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
// immediately, so he force closes his commitment transaction.
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
// 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
// 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
// second level transaction in the mempool, he will extract the
// preimage and broadcast a second level tx to claim the HTLC in his
// (already closed) channel with Alice.
secondLevelHashes, err := waitForNTxsInMempool(net.Miner.Node, 3,
// sweep his output in the channel with Carol. He can do this
// immediately, as the output is not timelocked since Carol was the one
// force closing.
commitSpends, err := waitForNTxsInMempool(net.Miner.Node, 2,
time.Second*20)
if err != nil {
t.Fatalf("transactions not found in mempool: %v", err)
}
// Carol's second level transaction should be spending from
// the commitment transaction.
var secondLevelHash *chainhash.Hash
for _, txid := range secondLevelHashes {
// Both Carol's second level transaction and Bob's sweep should be
// spending from the commitment transaction.
for _, txid := range commitSpends {
tx, err := net.Miner.Node.GetRawTransaction(txid)
if err != nil {
t.Fatalf("unable to get txn: %v", err)
}
if tx.MsgTx().TxIn[0].PreviousOutPoint.Hash == *commitHash {
secondLevelHash = txid
if tx.MsgTx().TxIn[0].PreviousOutPoint.Hash != *commitHash {
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
@ -8388,41 +8440,63 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest)
return false
}
}
return true
}, time.Second*15)
if err != nil {
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
// transactions and the commit sweep.
// We'll now mine a block which should confirm Bob's second layer
// transaction.
block = mineBlocks(t, net, 1)[0]
if len(block.Transactions) != 4 {
t.Fatalf("expected 4 transactions in block, got %v",
if len(block.Transactions) != 2 {
t.Fatalf("expected 2 transactions in block, got %v",
len(block.Transactions))
}
assertTxInBlock(t, block, secondLevelHash)
assertTxInBlock(t, block, bobSecondLvlTx)
// If we then mine 4 additional blocks, Bob and Carol should sweep the
// outputs destined for them.
if _, err := net.Miner.Node.Generate(defaultCSV); err != nil {
// Keep track of Bob's second level maturity, and decrement our track
// of Carol's.
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)
}
bobSecondLevelCSV -= carolSecondLevelCSV
sweepTxs, err := waitForNTxsInMempool(net.Miner.Node, 2, time.Second*10)
carolSweep, err := waitForTxInMempool(net.Miner.Node, time.Second*10)
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
// anymore, as this just resolved it by the confirmation of the sweep
// transaction we detected above.
block = mineBlocks(t, net, 1)[0]
for _, sweepTx := range sweepTxs {
assertTxInBlock(t, block, sweepTx)
// Mining one additional block, Bob's second level tx is mature, and he
// can sweep the output.
block = mineBlocks(t, net, bobSecondLevelCSV)[0]
assertTxInBlock(t, block, carolSweep)
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 {
pendingChanResp, err := net.Bob.PendingChannels(
ctxb, pendingChansRequest,
@ -8437,7 +8511,54 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest)
"but shouldn't: %v", spew.Sdump(pendingChanResp))
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
}, time.Second*15)
if err != nil {
@ -8506,7 +8627,8 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest
// immediately force close the channel by broadcast her commitment
// transaction.
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
// 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
// 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
// second level transaction in the mempool, he will extract the
// preimage and broadcast a second level tx to claim the HTLC in his
// (already closed) channel with Alice.
secondLevelHashes, err := waitForNTxsInMempool(net.Miner.Node, 3,
// sweep his output in the channel with Carol. He can do this
// immediately, as the output is not timelocked since Carol was the one
// force closing.
commitSpends, err := waitForNTxsInMempool(net.Miner.Node, 2,
time.Second*20)
if err != nil {
t.Fatalf("transactions not found in mempool: %v", err)
}
// Carol's second level transaction should be spending from
// the commitment transaction.
var secondLevelHash *chainhash.Hash
for _, txid := range secondLevelHashes {
// Both Carol's second level transaction and Bob's sweep should be
// spending from the commitment transaction.
for _, txid := range commitSpends {
tx, err := net.Miner.Node.GetRawTransaction(txid)
if err != nil {
t.Fatalf("unable to get txn: %v", err)
}
if tx.MsgTx().TxIn[0].PreviousOutPoint.Hash == *commitHash {
secondLevelHash = txid
if tx.MsgTx().TxIn[0].PreviousOutPoint.Hash != *commitHash {
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
// transactions and the commit sweep.
// Mine a block to confirm the two transactions (+ coinbase).
block = mineBlocks(t, net, 1)[0]
if len(block.Transactions) != 4 {
t.Fatalf("expected 4 transactions in block, got %v",
if len(block.Transactions) != 3 {
t.Fatalf("expected 3 transactions in block, got %v",
len(block.Transactions))
}
assertTxInBlock(t, block, secondLevelHash)
// 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)
for _, txid := range commitSpends {
assertTxInBlock(t, block, txid)
}
_, 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 {
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
// transaction that Bob broadcast in the prior stage.
if _, err := net.Miner.Node.Generate(1); err != nil {
t.Fatalf("unable to generate block: %v", err)
// It should spend from the commitment in the channel with Alice.
tx, err := net.Miner.Node.GetRawTransaction(bobHtlcSweep)
if err != nil {
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
// 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 {
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

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

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