itest: refactor watchtower related tests

This commit is contained in:
Elle Mouton 2024-02-09 12:45:36 +02:00
parent 1c7a7543c7
commit d4424fbcfa
No known key found for this signature in database
GPG Key ID: D7D916376026F177
3 changed files with 335 additions and 337 deletions

View File

@ -519,16 +519,8 @@ var allTestCases = []*lntest.TestCase{
TestFunc: testLookupHtlcResolution,
},
{
Name: "watchtower session management",
TestFunc: testWatchtowerSessionManagement,
},
{
// NOTE: this test must be put in the same tranche as
// `testWatchtowerSessionManagement` to avoid parallel use of
// the default watchtower port.
Name: "revoked uncooperative close retribution altruist " +
"watchtower",
TestFunc: testRevokedCloseRetributionAltruistWatchtower,
Name: "watchtower",
TestFunc: testWatchtower,
},
{
Name: "channel fundmax",

View File

@ -4,7 +4,6 @@ import (
"bytes"
"fmt"
"testing"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
@ -12,7 +11,6 @@ import (
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/wtclientrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/stretchr/testify/require"
@ -707,304 +705,3 @@ func testRevokedCloseRetributionRemoteHodl(ht *lntest.HarnessTest) {
})
}
}
// testRevokedCloseRetributionAltruistWatchtower establishes a channel between
// Carol and Dave, where Carol is using a third node Willy as her watchtower.
// After sending some payments, Dave reverts his state and force closes to
// trigger a breach. Carol is kept offline throughout the process and the test
// asserts that Willy responds by broadcasting the justice transaction on
// Carol's behalf sweeping her funds without a reward.
func testRevokedCloseRetributionAltruistWatchtower(ht *lntest.HarnessTest) {
for _, commitType := range []lnrpc.CommitmentType{
lnrpc.CommitmentType_LEGACY,
lnrpc.CommitmentType_ANCHORS,
lnrpc.CommitmentType_SIMPLE_TAPROOT,
} {
testName := fmt.Sprintf("%v", commitType.String())
ct := commitType
testFunc := func(ht *lntest.HarnessTest) {
testRevokedCloseRetributionAltruistWatchtowerCase(
ht, ct,
)
}
success := ht.Run(testName, func(tt *testing.T) {
st := ht.Subtest(tt)
st.RunTestCase(&lntest.TestCase{
Name: testName,
TestFunc: testFunc,
})
})
if !success {
// Log failure time to help relate the lnd logs to the
// failure.
ht.Logf("Failure time: %v", time.Now().Format(
"2006-01-02 15:04:05.000",
))
break
}
}
}
func testRevokedCloseRetributionAltruistWatchtowerCase(ht *lntest.HarnessTest,
commitType lnrpc.CommitmentType) {
const (
chanAmt = funding.MaxBtcFundingAmount
paymentAmt = 10000
numInvoices = 6
externalIP = "1.2.3.4"
)
// Since we'd like to test some multi-hop failure scenarios, we'll
// introduce another node into our test network: Carol.
carolArgs := lntest.NodeArgsForCommitType(commitType)
carolArgs = append(carolArgs, "--hodl.exit-settle")
carol := ht.NewNode("Carol", carolArgs)
// Willy the watchtower will protect Dave from Carol's breach. He will
// remain online in order to punish Carol on Dave's behalf, since the
// breach will happen while Dave is offline.
willy := ht.NewNode(
"Willy", []string{
"--watchtower.active",
"--watchtower.externalip=" + externalIP,
},
)
willyInfo := willy.RPC.GetInfoWatchtower()
// Assert that Willy has one listener and it is 0.0.0.0:9911 or
// [::]:9911. Since no listener is explicitly specified, one of these
// should be the default depending on whether the host supports IPv6 or
// not.
require.Len(ht, willyInfo.Listeners, 1, "Willy should have 1 listener")
listener := willyInfo.Listeners[0]
if listener != "0.0.0.0:9911" && listener != "[::]:9911" {
ht.Fatalf("expected listener on 0.0.0.0:9911 or [::]:9911, "+
"got %v", listener)
}
// Assert the Willy's URIs properly display the chosen external IP.
require.Len(ht, willyInfo.Uris, 1, "Willy should have 1 uri")
require.Contains(ht, willyInfo.Uris[0], externalIP)
// Dave will be the breached party. We set --nolisten to ensure Carol
// won't be able to connect to him and trigger the channel data
// protection logic automatically.
daveArgs := lntest.NodeArgsForCommitType(commitType)
daveArgs = append(daveArgs, "--nolisten", "--wtclient.active")
dave := ht.NewNode("Dave", daveArgs)
addTowerReq := &wtclientrpc.AddTowerRequest{
Pubkey: willyInfo.Pubkey,
Address: listener,
}
dave.RPC.AddTower(addTowerReq)
// We must let Dave have an open channel before she can send a node
// announcement, so we open a channel with Carol,
ht.ConnectNodes(dave, carol)
// Before we make a channel, we'll load up Dave with some coins sent
// directly from the miner.
ht.FundCoins(btcutil.SatoshiPerBitcoin, dave)
// Send one more UTXOs if this is a neutrino backend.
if ht.IsNeutrinoBackend() {
ht.FundCoins(btcutil.SatoshiPerBitcoin, dave)
}
// In order to test Dave's response to an uncooperative channel
// closure by Carol, we'll first open up a channel between them with a
// 0.5 BTC value.
params := lntest.OpenChannelParams{
Amt: 3 * (chanAmt / 4),
PushAmt: chanAmt / 4,
CommitmentType: commitType,
Private: true,
}
chanPoint := ht.OpenChannel(dave, carol, params)
// With the channel open, we'll create a few invoices for Carol that
// Dave will pay to in order to advance the state of the channel.
carolPayReqs, _, _ := ht.CreatePayReqs(carol, paymentAmt, numInvoices)
// Next query for Carol's channel state, as we sent 0 payments, Carol
// should still see her balance as the push amount, which is 1/4 of the
// capacity.
carolChan := ht.AssertChannelLocalBalance(
carol, chanPoint, int64(chanAmt/4),
)
// Grab Carol's current commitment height (update number), we'll later
// revert her to this state after additional updates to force him to
// broadcast this soon to be revoked state.
carolStateNumPreCopy := int(carolChan.NumUpdates)
// With the temporary file created, copy Carol's current state into the
// temporary file we created above. Later after more updates, we'll
// restore this state.
ht.BackupDB(carol)
// Reconnect the peers after the restart that was needed for the db
// backup.
ht.EnsureConnected(dave, carol)
// Once connected, give Dave some time to enable the channel again.
ht.AssertTopologyChannelOpen(dave, chanPoint)
// Finally, send payments from Dave to Carol, consuming Carol's
// remaining payment hashes.
ht.CompletePaymentRequestsNoWait(dave, carolPayReqs, chanPoint)
daveBalResp := dave.RPC.WalletBalance()
davePreSweepBalance := daveBalResp.ConfirmedBalance
// Wait until the backup has been accepted by the watchtower before
// shutting down Dave.
err := wait.NoError(func() error {
bkpStats := dave.RPC.WatchtowerStats()
if bkpStats == nil {
return errors.New("no active backup sessions")
}
if bkpStats.NumBackups == 0 {
return errors.New("no backups accepted")
}
return nil
}, defaultTimeout)
require.NoError(ht, err, "unable to verify backup task completed")
// Shutdown Dave to simulate going offline for an extended period of
// time. Once he's not watching, Carol will try to breach the channel.
restart := ht.SuspendNode(dave)
// Now we shutdown Carol, copying over the his temporary database state
// which has the *prior* channel state over his current most up to date
// state. With this, we essentially force Carol to travel back in time
// within the channel's history.
ht.RestartNodeAndRestoreDB(carol)
// Now query for Carol's channel state, it should show that he's at a
// state number in the past, not the *latest* state.
ht.AssertChannelCommitHeight(carol, chanPoint, carolStateNumPreCopy)
// Now force Carol to execute a *force* channel closure by unilaterally
// broadcasting his current channel state. This is actually the
// commitment transaction of a prior *revoked* state, so he'll soon
// feel the wrath of Dave's retribution.
closeUpdates, closeTxID := ht.CloseChannelAssertPending(
carol, chanPoint, true,
)
// Finally, generate a single block, wait for the final close status
// update, then ensure that the closing transaction was included in the
// block.
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
breachTXID := ht.WaitForChannelCloseEvent(closeUpdates)
ht.Miner.AssertTxInBlock(block, breachTXID)
// The breachTXID should match the above closeTxID.
require.EqualValues(ht, breachTXID, closeTxID)
// Query the mempool for Dave's justice transaction, this should be
// broadcast as Carol's contract breaching transaction gets confirmed
// above.
justiceTXID := ht.Miner.AssertNumTxsInMempool(1)[0]
// Query for the mempool transaction found above. Then assert that all
// the inputs of this transaction are spending outputs generated by
// Carol's breach transaction above.
justiceTx := ht.Miner.GetRawTransaction(justiceTXID)
for _, txIn := range justiceTx.MsgTx().TxIn {
require.Equal(ht, breachTXID[:], txIn.PreviousOutPoint.Hash[:],
"justice tx not spending commitment utxo")
}
willyBalResp := willy.RPC.WalletBalance()
require.Zero(ht, willyBalResp.ConfirmedBalance,
"willy should have 0 balance before mining justice transaction")
// Now mine a block, this transaction should include Dave's justice
// transaction which was just accepted into the mempool.
block = ht.MineBlocksAndAssertNumTxes(1, 1)[0]
// The block should have exactly *two* transactions, one of which is
// the justice transaction.
require.Len(ht, block.Transactions, 2, "transaction wasn't mined")
justiceSha := block.Transactions[1].TxHash()
require.Equal(ht, justiceTx.Hash()[:], justiceSha[:],
"justice tx wasn't mined")
// Ensure that Willy doesn't get any funds, as he is acting as an
// altruist watchtower.
err = wait.NoError(func() error {
willyBalResp := willy.RPC.WalletBalance()
if willyBalResp.ConfirmedBalance != 0 {
return fmt.Errorf("expected Willy to have no funds "+
"after justice transaction was mined, found %v",
willyBalResp)
}
return nil
}, time.Second*5)
require.NoError(ht, err, "timeout checking willy's balance")
// Before restarting Dave, shutdown Carol so Dave won't sync with her.
// Otherwise, during the restart, Dave will realize Carol is falling
// behind and return `ErrCommitSyncRemoteDataLoss`, thus force closing
// the channel. Although this force close tx will be later replaced by
// the breach tx, it will create two anchor sweeping txes for neutrino
// backend, causing the confirmed wallet balance to be zero later on
// because the utxos are used in sweeping.
ht.Shutdown(carol)
// Restart Dave, who will still think his channel with Carol is open.
// We should him to detect the breach, but realize that the funds have
// then been swept to his wallet by Willy.
require.NoError(ht, restart(), "unable to restart dave")
err = wait.NoError(func() error {
daveBalResp := dave.RPC.ChannelBalance()
if daveBalResp.LocalBalance.Sat != 0 {
return fmt.Errorf("Dave should end up with zero "+
"channel balance, instead has %d",
daveBalResp.LocalBalance.Sat)
}
return nil
}, defaultTimeout)
require.NoError(ht, err, "timeout checking dave's channel balance")
ht.AssertNumPendingForceClose(dave, 0)
// If this is an anchor channel, Dave would sweep the anchor.
if lntest.CommitTypeHasAnchors(commitType) {
ht.MineBlocksAndAssertNumTxes(1, 1)
}
// Check that Dave's wallet balance is increased.
err = wait.NoError(func() error {
daveBalResp := dave.RPC.WalletBalance()
if daveBalResp.ConfirmedBalance <= davePreSweepBalance {
return fmt.Errorf("Dave should have more than %d "+
"after sweep, instead has %d",
davePreSweepBalance,
daveBalResp.ConfirmedBalance)
}
return nil
}, defaultTimeout)
require.NoError(ht, err, "timeout checking dave's wallet balance")
// Dave should have no open channels.
ht.AssertNodeNumChannels(dave, 0)
}

View File

@ -2,21 +2,38 @@ package itest
import (
"fmt"
"testing"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnrpc/wtclientrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/node"
"github.com/lightningnetwork/lnd/lntest/rpc"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/stretchr/testify/require"
)
// testWatchtowerSessionManagement tests that session deletion is done
// correctly.
func testWatchtowerSessionManagement(ht *lntest.HarnessTest) {
// testWatchtower tests the behaviour of the watchtower client and server.
func testWatchtower(ht *lntest.HarnessTest) {
ht.Run("revocation", func(t *testing.T) {
tt := ht.Subtest(t)
testRevokedCloseRetributionAltruistWatchtower(tt)
})
ht.Run("session deletion", func(t *testing.T) {
tt := ht.Subtest(t)
testTowerClientSessionDeletion(tt)
})
}
// testTowerClientSessionDeletion tests that sessions are correctly deleted
// when they are deemed closable.
func testTowerClientSessionDeletion(ht *lntest.HarnessTest) {
const (
chanAmt = funding.MaxBtcFundingAmount
paymentAmt = 10_000
@ -28,24 +45,7 @@ func testWatchtowerSessionManagement(ht *lntest.HarnessTest) {
// Set up Wallis the watchtower who will be used by Dave to watch over
// his channel commitment transactions.
wallis := ht.NewNode("Wallis", []string{
"--watchtower.active",
"--watchtower.externalip=" + externalIP,
})
wallisInfo := wallis.RPC.GetInfoWatchtower()
// Assert that Wallis has one listener and it is 0.0.0.0:9911 or
// [::]:9911. Since no listener is explicitly specified, one of these
// should be the default depending on whether the host supports IPv6 or
// not.
require.Len(ht, wallisInfo.Listeners, 1)
listener := wallisInfo.Listeners[0]
require.True(ht, listener == "0.0.0.0:9911" || listener == "[::]:9911")
// Assert the Wallis's URIs properly display the chosen external IP.
require.Len(ht, wallisInfo.Uris, 1)
require.Contains(ht, wallisInfo.Uris[0], externalIP)
wallisPk, listener, _ := setUpNewTower(ht, "Wallis", externalIP)
// Dave will be the tower client.
daveArgs := []string{
@ -58,7 +58,7 @@ func testWatchtowerSessionManagement(ht *lntest.HarnessTest) {
dave := ht.NewNode("Dave", daveArgs)
addTowerReq := &wtclientrpc.AddTowerRequest{
Pubkey: wallisInfo.Pubkey,
Pubkey: wallisPk,
Address: listener,
}
dave.RPC.AddTower(addTowerReq)
@ -66,7 +66,7 @@ func testWatchtowerSessionManagement(ht *lntest.HarnessTest) {
// Assert that there exists a session between Dave and Wallis.
err := wait.NoError(func() error {
info := dave.RPC.GetTowerInfo(&wtclientrpc.GetTowerInfoRequest{
Pubkey: wallisInfo.Pubkey,
Pubkey: wallisPk,
IncludeSessions: true,
})
@ -125,7 +125,7 @@ func testWatchtowerSessionManagement(ht *lntest.HarnessTest) {
err = wait.NoError(func() error {
info := dave.RPC.GetTowerInfo(
&wtclientrpc.GetTowerInfoRequest{
Pubkey: wallisInfo.Pubkey,
Pubkey: wallisPk,
IncludeSessions: true,
},
)
@ -170,3 +170,312 @@ func testWatchtowerSessionManagement(ht *lntest.HarnessTest) {
// ensure that the session deleting logic is run.
assertNumBackups(0, true)
}
// testRevokedCloseRetributionAltruistWatchtower establishes a channel between
// Carol and Dave, where Carol is using a third node Willy as her watchtower.
// After sending some payments, Dave reverts his state and force closes to
// trigger a breach. Carol is kept offline throughout the process and the test
// asserts that Willy responds by broadcasting the justice transaction on
// Carol's behalf sweeping her funds without a reward.
func testRevokedCloseRetributionAltruistWatchtower(ht *lntest.HarnessTest) {
for _, commitType := range []lnrpc.CommitmentType{
lnrpc.CommitmentType_LEGACY,
lnrpc.CommitmentType_ANCHORS,
lnrpc.CommitmentType_SIMPLE_TAPROOT,
} {
testName := fmt.Sprintf("%v", commitType.String())
ct := commitType
testFunc := func(ht *lntest.HarnessTest) {
testRevokedCloseRetributionAltruistWatchtowerCase(
ht, ct,
)
}
success := ht.Run(testName, func(tt *testing.T) {
st := ht.Subtest(tt)
st.RunTestCase(&lntest.TestCase{
Name: testName,
TestFunc: testFunc,
})
})
if !success {
// Log failure time to help relate the lnd logs to the
// failure.
ht.Logf("Failure time: %v", time.Now().Format(
"2006-01-02 15:04:05.000",
))
break
}
}
}
func testRevokedCloseRetributionAltruistWatchtowerCase(ht *lntest.HarnessTest,
commitType lnrpc.CommitmentType) {
const (
chanAmt = funding.MaxBtcFundingAmount
paymentAmt = 10000
numInvoices = 6
externalIP = "1.2.3.4"
)
// Since we'd like to test some multi-hop failure scenarios, we'll
// introduce another node into our test network: Carol.
carolArgs := lntest.NodeArgsForCommitType(commitType)
carolArgs = append(carolArgs, "--hodl.exit-settle")
carol := ht.NewNode("Carol", carolArgs)
// Set up Willy the watchtower who will protect Dave from Carol's
// breach. He will remain online in order to punish Carol on Dave's
// behalf, since the breach will happen while Dave is offline.
willyInfoPk, listener, willy := setUpNewTower(ht, "Willy", externalIP)
// Dave will be the breached party. We set --nolisten to ensure Carol
// won't be able to connect to him and trigger the channel data
// protection logic automatically.
daveArgs := lntest.NodeArgsForCommitType(commitType)
daveArgs = append(daveArgs, "--nolisten", "--wtclient.active")
dave := ht.NewNode("Dave", daveArgs)
addTowerReq := &wtclientrpc.AddTowerRequest{
Pubkey: willyInfoPk,
Address: listener,
}
dave.RPC.AddTower(addTowerReq)
// We must let Dave have an open channel before she can send a node
// announcement, so we open a channel with Carol,
ht.ConnectNodes(dave, carol)
// Before we make a channel, we'll load up Dave with some coins sent
// directly from the miner.
ht.FundCoins(btcutil.SatoshiPerBitcoin, dave)
// Send one more UTXOs if this is a neutrino backend.
if ht.IsNeutrinoBackend() {
ht.FundCoins(btcutil.SatoshiPerBitcoin, dave)
}
// In order to test Dave's response to an uncooperative channel
// closure by Carol, we'll first open up a channel between them with a
// 0.5 BTC value.
params := lntest.OpenChannelParams{
Amt: 3 * (chanAmt / 4),
PushAmt: chanAmt / 4,
CommitmentType: commitType,
Private: true,
}
chanPoint := ht.OpenChannel(dave, carol, params)
// With the channel open, we'll create a few invoices for Carol that
// Dave will pay to in order to advance the state of the channel.
carolPayReqs, _, _ := ht.CreatePayReqs(carol, paymentAmt, numInvoices)
// Next query for Carol's channel state, as we sent 0 payments, Carol
// should still see her balance as the push amount, which is 1/4 of the
// capacity.
carolChan := ht.AssertChannelLocalBalance(
carol, chanPoint, int64(chanAmt/4),
)
// Grab Carol's current commitment height (update number), we'll later
// revert her to this state after additional updates to force him to
// broadcast this soon to be revoked state.
carolStateNumPreCopy := int(carolChan.NumUpdates)
// With the temporary file created, copy Carol's current state into the
// temporary file we created above. Later after more updates, we'll
// restore this state.
ht.BackupDB(carol)
// Reconnect the peers after the restart that was needed for the db
// backup.
ht.EnsureConnected(dave, carol)
// Once connected, give Dave some time to enable the channel again.
ht.AssertTopologyChannelOpen(dave, chanPoint)
// Finally, send payments from Dave to Carol, consuming Carol's
// remaining payment hashes.
ht.CompletePaymentRequestsNoWait(dave, carolPayReqs, chanPoint)
daveBalResp := dave.RPC.WalletBalance()
davePreSweepBalance := daveBalResp.ConfirmedBalance
// Wait until the backup has been accepted by the watchtower before
// shutting down Dave.
err := wait.NoError(func() error {
bkpStats := dave.RPC.WatchtowerStats()
if bkpStats == nil {
return errors.New("no active backup sessions")
}
if bkpStats.NumBackups == 0 {
return errors.New("no backups accepted")
}
return nil
}, defaultTimeout)
require.NoError(ht, err, "unable to verify backup task completed")
// Shutdown Dave to simulate going offline for an extended period of
// time. Once he's not watching, Carol will try to breach the channel.
restart := ht.SuspendNode(dave)
// Now we shutdown Carol, copying over the his temporary database state
// which has the *prior* channel state over his current most up to date
// state. With this, we essentially force Carol to travel back in time
// within the channel's history.
ht.RestartNodeAndRestoreDB(carol)
// Now query for Carol's channel state, it should show that he's at a
// state number in the past, not the *latest* state.
ht.AssertChannelCommitHeight(carol, chanPoint, carolStateNumPreCopy)
// Now force Carol to execute a *force* channel closure by unilaterally
// broadcasting his current channel state. This is actually the
// commitment transaction of a prior *revoked* state, so he'll soon
// feel the wrath of Dave's retribution.
closeUpdates, closeTxID := ht.CloseChannelAssertPending(
carol, chanPoint, true,
)
// Finally, generate a single block, wait for the final close status
// update, then ensure that the closing transaction was included in the
// block.
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
breachTXID := ht.WaitForChannelCloseEvent(closeUpdates)
ht.Miner.AssertTxInBlock(block, breachTXID)
// The breachTXID should match the above closeTxID.
require.EqualValues(ht, breachTXID, closeTxID)
// Query the mempool for Dave's justice transaction, this should be
// broadcast as Carol's contract breaching transaction gets confirmed
// above.
justiceTXID := ht.Miner.AssertNumTxsInMempool(1)[0]
// Query for the mempool transaction found above. Then assert that all
// the inputs of this transaction are spending outputs generated by
// Carol's breach transaction above.
justiceTx := ht.Miner.GetRawTransaction(justiceTXID)
for _, txIn := range justiceTx.MsgTx().TxIn {
require.Equal(ht, breachTXID[:], txIn.PreviousOutPoint.Hash[:],
"justice tx not spending commitment utxo")
}
willyBalResp := willy.WalletBalance()
require.Zero(ht, willyBalResp.ConfirmedBalance,
"willy should have 0 balance before mining justice transaction")
// Now mine a block, this transaction should include Dave's justice
// transaction which was just accepted into the mempool.
block = ht.MineBlocksAndAssertNumTxes(1, 1)[0]
// The block should have exactly *two* transactions, one of which is
// the justice transaction.
require.Len(ht, block.Transactions, 2, "transaction wasn't mined")
justiceSha := block.Transactions[1].TxHash()
require.Equal(ht, justiceTx.Hash()[:], justiceSha[:],
"justice tx wasn't mined")
// Ensure that Willy doesn't get any funds, as he is acting as an
// altruist watchtower.
err = wait.NoError(func() error {
willyBalResp := willy.WalletBalance()
if willyBalResp.ConfirmedBalance != 0 {
return fmt.Errorf("expected Willy to have no funds "+
"after justice transaction was mined, found %v",
willyBalResp)
}
return nil
}, time.Second*5)
require.NoError(ht, err, "timeout checking willy's balance")
// Before restarting Dave, shutdown Carol so Dave won't sync with her.
// Otherwise, during the restart, Dave will realize Carol is falling
// behind and return `ErrCommitSyncRemoteDataLoss`, thus force closing
// the channel. Although this force close tx will be later replaced by
// the breach tx, it will create two anchor sweeping txes for neutrino
// backend, causing the confirmed wallet balance to be zero later on
// because the utxos are used in sweeping.
ht.Shutdown(carol)
// Restart Dave, who will still think his channel with Carol is open.
// We should him to detect the breach, but realize that the funds have
// then been swept to his wallet by Willy.
require.NoError(ht, restart(), "unable to restart dave")
err = wait.NoError(func() error {
daveBalResp := dave.RPC.ChannelBalance()
if daveBalResp.LocalBalance.Sat != 0 {
return fmt.Errorf("Dave should end up with zero "+
"channel balance, instead has %d",
daveBalResp.LocalBalance.Sat)
}
return nil
}, defaultTimeout)
require.NoError(ht, err, "timeout checking dave's channel balance")
ht.AssertNumPendingForceClose(dave, 0)
// If this is an anchor channel, Dave would sweep the anchor.
if lntest.CommitTypeHasAnchors(commitType) {
ht.MineBlocksAndAssertNumTxes(1, 1)
}
// Check that Dave's wallet balance is increased.
err = wait.NoError(func() error {
daveBalResp := dave.RPC.WalletBalance()
if daveBalResp.ConfirmedBalance <= davePreSweepBalance {
return fmt.Errorf("Dave should have more than %d "+
"after sweep, instead has %d",
davePreSweepBalance,
daveBalResp.ConfirmedBalance)
}
return nil
}, defaultTimeout)
require.NoError(ht, err, "timeout checking dave's wallet balance")
// Dave should have no open channels.
ht.AssertNodeNumChannels(dave, 0)
}
func setUpNewTower(ht *lntest.HarnessTest, name, externalIP string) ([]byte,
string, *rpc.HarnessRPC) {
port := node.NextAvailablePort()
listenAddr := fmt.Sprintf("0.0.0.0:%d", port)
// Set up the new watchtower.
tower := ht.NewNode(name, []string{
"--watchtower.active",
"--watchtower.externalip=" + externalIP,
"--watchtower.listen=" + listenAddr,
})
towerInfo := tower.RPC.GetInfoWatchtower()
require.Len(ht, towerInfo.Listeners, 1)
listener := towerInfo.Listeners[0]
require.True(
ht, listener == listenAddr ||
listener == fmt.Sprintf("[::]:%d", port),
)
// Assert the Tower's URIs properly display the chosen external IP.
require.Len(ht, towerInfo.Uris, 1)
require.Contains(ht, towerInfo.Uris[0], externalIP)
return towerInfo.Pubkey, towerInfo.Listeners[0], tower.RPC
}