lntemp+itest: refactor testRevokedCloseRetributionAltruistWatchtower
This commit is contained in:
4 files changed
+150
-236
No files matched your search
@@ -1,5 +1,52 @@
|
|||||||
package rpc
|
package rpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc/wtclientrpc"
|
||||||
|
)
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
// WatchtowerClient and WatchtowerClientClient related RPCs.
|
// WatchtowerClient and WatchtowerClientClient related RPCs.
|
||||||
// =====================
|
// =====================
|
||||||
|
|
||||||
|
// GetInfoWatchtower makes a RPC call to the watchtower of the given node and
|
||||||
|
// asserts.
|
||||||
|
func (h *HarnessRPC) GetInfoWatchtower() *watchtowerrpc.GetInfoResponse {
|
||||||
|
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
req := &watchtowerrpc.GetInfoRequest{}
|
||||||
|
info, err := h.Watchtower.GetInfo(ctxt, req)
|
||||||
|
h.NoError(err, "GetInfo from Watchtower")
|
||||||
|
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTower makes a RPC call to the WatchtowerClient of the given node and
|
||||||
|
// asserts.
|
||||||
|
func (h *HarnessRPC) AddTower(
|
||||||
|
req *wtclientrpc.AddTowerRequest) *wtclientrpc.AddTowerResponse {
|
||||||
|
|
||||||
|
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resp, err := h.WatchtowerClient.AddTower(ctxt, req)
|
||||||
|
h.NoError(err, "AddTower")
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchtowerStats makes a RPC call to the WatchtowerClient of the given node
|
||||||
|
// and asserts.
|
||||||
|
func (h *HarnessRPC) WatchtowerStats() *wtclientrpc.StatsResponse {
|
||||||
|
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
req := &wtclientrpc.StatsRequest{}
|
||||||
|
resp, err := h.WatchtowerClient.Stats(ctxt, req)
|
||||||
|
h.NoError(err, "Stats from Watchtower")
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
@@ -304,4 +304,8 @@ var allTestCasesTemp = []*lntemp.TestCase{
|
|||||||
Name: "revoked uncooperative close retribution remote hodl",
|
Name: "revoked uncooperative close retribution remote hodl",
|
||||||
TestFunc: testRevokedCloseRetributionRemoteHodl,
|
TestFunc: testRevokedCloseRetributionRemoteHodl,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "revoked uncooperative close retribution altruist watchtower",
|
||||||
|
TestFunc: testRevokedCloseRetributionAltruistWatchtower,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
@@ -2,9 +2,7 @@ package itest
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -13,7 +11,6 @@ import (
|
|||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
"github.com/lightningnetwork/lnd/funding"
|
"github.com/lightningnetwork/lnd/funding"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc"
|
|
||||||
"github.com/lightningnetwork/lnd/lnrpc/wtclientrpc"
|
"github.com/lightningnetwork/lnd/lnrpc/wtclientrpc"
|
||||||
"github.com/lightningnetwork/lnd/lntemp"
|
"github.com/lightningnetwork/lnd/lntemp"
|
||||||
"github.com/lightningnetwork/lnd/lntest"
|
"github.com/lightningnetwork/lnd/lntest"
|
||||||
@@ -581,9 +578,7 @@ func testRevokedCloseRetributionRemoteHodl(ht *lntemp.HarnessTest) {
|
|||||||
// trigger a breach. Carol is kept offline throughout the process and the test
|
// trigger a breach. Carol is kept offline throughout the process and the test
|
||||||
// asserts that Willy responds by broadcasting the justice transaction on
|
// asserts that Willy responds by broadcasting the justice transaction on
|
||||||
// Carol's behalf sweeping her funds without a reward.
|
// Carol's behalf sweeping her funds without a reward.
|
||||||
func testRevokedCloseRetributionAltruistWatchtower(net *lntest.NetworkHarness,
|
func testRevokedCloseRetributionAltruistWatchtower(ht *lntemp.HarnessTest) {
|
||||||
t *harnessTest) {
|
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
anchors bool
|
anchors bool
|
||||||
@@ -597,23 +592,25 @@ func testRevokedCloseRetributionAltruistWatchtower(net *lntest.NetworkHarness,
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
tc := tc
|
tc := tc
|
||||||
|
testFunc := func(ht *lntemp.HarnessTest) {
|
||||||
|
testRevokedCloseRetributionAltruistWatchtowerCase(
|
||||||
|
ht, tc.anchors,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
success := t.t.Run(tc.name, func(tt *testing.T) {
|
success := ht.Run(tc.name, func(tt *testing.T) {
|
||||||
ht := newHarnessTest(tt, net)
|
st := ht.Subtest(tt)
|
||||||
ht.RunTestCase(&testCase{
|
|
||||||
name: tc.name,
|
st.RunTestCase(&lntemp.TestCase{
|
||||||
test: func(net1 *lntest.NetworkHarness, t1 *harnessTest) {
|
Name: tc.name,
|
||||||
testRevokedCloseRetributionAltruistWatchtowerCase(
|
TestFunc: testFunc,
|
||||||
net1, t1, tc.anchors,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
if !success {
|
if !success {
|
||||||
// Log failure time to help relate the lnd logs to the
|
// Log failure time to help relate the lnd logs to the
|
||||||
// failure.
|
// failure.
|
||||||
t.Logf("Failure time: %v", time.Now().Format(
|
ht.Logf("Failure time: %v", time.Now().Format(
|
||||||
"2006-01-02 15:04:05.000",
|
"2006-01-02 15:04:05.000",
|
||||||
))
|
))
|
||||||
|
|
||||||
@@ -622,10 +619,9 @@ func testRevokedCloseRetributionAltruistWatchtower(net *lntest.NetworkHarness,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRevokedCloseRetributionAltruistWatchtowerCase(
|
func testRevokedCloseRetributionAltruistWatchtowerCase(ht *lntemp.HarnessTest,
|
||||||
net *lntest.NetworkHarness, t *harnessTest, anchors bool) {
|
anchors bool) {
|
||||||
|
|
||||||
ctxb := context.Background()
|
|
||||||
const (
|
const (
|
||||||
chanAmt = funding.MaxBtcFundingAmount
|
chanAmt = funding.MaxBtcFundingAmount
|
||||||
paymentAmt = 10000
|
paymentAmt = 10000
|
||||||
@@ -639,49 +635,32 @@ func testRevokedCloseRetributionAltruistWatchtowerCase(
|
|||||||
if anchors {
|
if anchors {
|
||||||
carolArgs = append(carolArgs, "--protocol.anchors")
|
carolArgs = append(carolArgs, "--protocol.anchors")
|
||||||
}
|
}
|
||||||
carol := net.NewNode(t.t, "Carol", carolArgs)
|
carol := ht.NewNode("Carol", carolArgs)
|
||||||
defer shutdownAndAssert(net, t, carol)
|
|
||||||
|
|
||||||
// Willy the watchtower will protect Dave from Carol's breach. He will
|
// 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
|
// remain online in order to punish Carol on Dave's behalf, since the
|
||||||
// breach will happen while Dave is offline.
|
// breach will happen while Dave is offline.
|
||||||
willy := net.NewNode(t.t, "Willy", []string{
|
willy := ht.NewNode(
|
||||||
"--watchtower.active",
|
"Willy", []string{"--watchtower.active",
|
||||||
"--watchtower.externalip=" + externalIP,
|
"--watchtower.externalip=" + externalIP},
|
||||||
})
|
|
||||||
defer shutdownAndAssert(net, t, willy)
|
|
||||||
|
|
||||||
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
willyInfo, err := willy.Watchtower.GetInfo(
|
|
||||||
ctxt, &watchtowerrpc.GetInfoRequest{},
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to getinfo from willy: %v", err)
|
willyInfo := willy.RPC.GetInfoWatchtower()
|
||||||
}
|
|
||||||
|
|
||||||
// Assert that Willy has one listener and it is 0.0.0.0:9911 or
|
// 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
|
// [::]:9911. Since no listener is explicitly specified, one of these
|
||||||
// should be the default depending on whether the host supports IPv6 or
|
// should be the default depending on whether the host supports IPv6 or
|
||||||
// not.
|
// not.
|
||||||
if len(willyInfo.Listeners) != 1 {
|
require.Len(ht, willyInfo.Listeners, 1, "Willy should have 1 listener")
|
||||||
t.Fatalf("Willy should have 1 listener, has %d",
|
|
||||||
len(willyInfo.Listeners))
|
|
||||||
}
|
|
||||||
listener := willyInfo.Listeners[0]
|
listener := willyInfo.Listeners[0]
|
||||||
if listener != "0.0.0.0:9911" && listener != "[::]:9911" {
|
if listener != "0.0.0.0:9911" && listener != "[::]:9911" {
|
||||||
t.Fatalf("expected listener on 0.0.0.0:9911 or [::]:9911, "+
|
ht.Fatalf("expected listener on 0.0.0.0:9911 or [::]:9911, "+
|
||||||
"got %v", listener)
|
"got %v", listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assert the Willy's URIs properly display the chosen external IP.
|
// Assert the Willy's URIs properly display the chosen external IP.
|
||||||
if len(willyInfo.Uris) != 1 {
|
require.Len(ht, willyInfo.Uris, 1, "Willy should have 1 uri")
|
||||||
t.Fatalf("Willy should have 1 uri, has %d",
|
require.Contains(ht, willyInfo.Uris[0], externalIP)
|
||||||
len(willyInfo.Uris))
|
|
||||||
}
|
|
||||||
if !strings.Contains(willyInfo.Uris[0], externalIP) {
|
|
||||||
t.Fatalf("expected uri with %v, got %v",
|
|
||||||
externalIP, willyInfo.Uris[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dave will be the breached party. We set --nolisten to ensure Carol
|
// 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
|
// won't be able to connect to him and trigger the channel data
|
||||||
@@ -693,110 +672,67 @@ func testRevokedCloseRetributionAltruistWatchtowerCase(
|
|||||||
if anchors {
|
if anchors {
|
||||||
daveArgs = append(daveArgs, "--protocol.anchors")
|
daveArgs = append(daveArgs, "--protocol.anchors")
|
||||||
}
|
}
|
||||||
dave := net.NewNode(t.t, "Dave", daveArgs)
|
dave := ht.NewNode("Dave", daveArgs)
|
||||||
defer shutdownAndAssert(net, t, dave)
|
|
||||||
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
addTowerReq := &wtclientrpc.AddTowerRequest{
|
addTowerReq := &wtclientrpc.AddTowerRequest{
|
||||||
Pubkey: willyInfo.Pubkey,
|
Pubkey: willyInfo.Pubkey,
|
||||||
Address: listener,
|
Address: listener,
|
||||||
}
|
}
|
||||||
if _, err := dave.WatchtowerClient.AddTower(ctxt, addTowerReq); err != nil {
|
dave.RPC.AddTower(addTowerReq)
|
||||||
t.Fatalf("unable to add willy's watchtower: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We must let Dave have an open channel before she can send a node
|
// We must let Dave have an open channel before she can send a node
|
||||||
// announcement, so we open a channel with Carol,
|
// announcement, so we open a channel with Carol,
|
||||||
net.ConnectNodes(t.t, dave, carol)
|
ht.ConnectNodes(dave, carol)
|
||||||
|
|
||||||
// Before we make a channel, we'll load up Dave with some coins sent
|
// Before we make a channel, we'll load up Dave with some coins sent
|
||||||
// directly from the miner.
|
// directly from the miner.
|
||||||
net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, dave)
|
ht.FundCoins(btcutil.SatoshiPerBitcoin, dave)
|
||||||
|
|
||||||
// In order to test Dave's response to an uncooperative channel
|
// 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
|
// closure by Carol, we'll first open up a channel between them with a
|
||||||
// 0.5 BTC value.
|
// 0.5 BTC value.
|
||||||
chanPoint := openChannelAndAssert(
|
params := lntemp.OpenChannelParams{
|
||||||
t, net, dave, carol,
|
Amt: 3 * (chanAmt / 4),
|
||||||
lntest.OpenChannelParams{
|
PushAmt: chanAmt / 4,
|
||||||
Amt: 3 * (chanAmt / 4),
|
}
|
||||||
PushAmt: chanAmt / 4,
|
chanPoint := ht.OpenChannel(dave, carol, params)
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
// With the channel open, we'll create a few invoices for Carol that
|
// 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.
|
// Dave will pay to in order to advance the state of the channel.
|
||||||
carolPayReqs, _, _, err := createPayReqs(
|
carolPayReqs, _, _ := ht.CreatePayReqs(carol, paymentAmt, numInvoices)
|
||||||
carol, paymentAmt, numInvoices,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to create pay reqs: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for Dave to receive the channel edge from the funding manager.
|
|
||||||
err = dave.WaitForNetworkChannelOpen(chanPoint)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("dave didn't see the dave->carol channel before "+
|
|
||||||
"timeout: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next query for Carol's channel state, as we sent 0 payments, Carol
|
// 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
|
// should still see her balance as the push amount, which is 1/4 of the
|
||||||
// capacity.
|
// capacity.
|
||||||
carolChan, err := getChanInfo(carol)
|
carolChan := ht.AssertChannelLocalBalance(
|
||||||
if err != nil {
|
carol, chanPoint, int64(chanAmt/4),
|
||||||
t.Fatalf("unable to get carol's channel info: %v", err)
|
)
|
||||||
}
|
|
||||||
if carolChan.LocalBalance != int64(chanAmt/4) {
|
|
||||||
t.Fatalf("carol's balance is incorrect, got %v, expected %v",
|
|
||||||
carolChan.LocalBalance, chanAmt/4)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grab Carol's current commitment height (update number), we'll later
|
// Grab Carol's current commitment height (update number), we'll later
|
||||||
// revert her to this state after additional updates to force him to
|
// revert her to this state after additional updates to force him to
|
||||||
// broadcast this soon to be revoked state.
|
// broadcast this soon to be revoked state.
|
||||||
carolStateNumPreCopy := carolChan.NumUpdates
|
carolStateNumPreCopy := int(carolChan.NumUpdates)
|
||||||
|
|
||||||
// With the temporary file created, copy Carol's current state into the
|
// With the temporary file created, copy Carol's current state into the
|
||||||
// temporary file we created above. Later after more updates, we'll
|
// temporary file we created above. Later after more updates, we'll
|
||||||
// restore this state.
|
// restore this state.
|
||||||
if err := net.BackupDb(carol); err != nil {
|
ht.BackupDB(carol)
|
||||||
t.Fatalf("unable to copy database files: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reconnect the peers after the restart that was needed for the db
|
// Reconnect the peers after the restart that was needed for the db
|
||||||
// backup.
|
// backup.
|
||||||
net.EnsureConnected(t.t, dave, carol)
|
ht.EnsureConnected(dave, carol)
|
||||||
|
|
||||||
// Finally, send payments from Dave to Carol, consuming Carol's remaining
|
// Finally, send payments from Dave to Carol, consuming Carol's
|
||||||
// payment hashes.
|
// remaining payment hashes.
|
||||||
err = completePaymentRequests(
|
ht.CompletePaymentRequestsNoWait(dave, carolPayReqs, chanPoint)
|
||||||
dave, dave.RouterClient, carolPayReqs, false,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to send payments: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
daveBalReq := &lnrpc.WalletBalanceRequest{}
|
|
||||||
daveBalResp, err := dave.WalletBalance(ctxt, daveBalReq)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to get dave's balance: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
daveBalResp := dave.RPC.WalletBalance()
|
||||||
davePreSweepBalance := daveBalResp.ConfirmedBalance
|
davePreSweepBalance := daveBalResp.ConfirmedBalance
|
||||||
|
|
||||||
// Wait until the backup has been accepted by the watchtower before
|
// Wait until the backup has been accepted by the watchtower before
|
||||||
// shutting down Dave.
|
// shutting down Dave.
|
||||||
err = wait.NoError(func() error {
|
err := wait.NoError(func() error {
|
||||||
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
|
bkpStats := dave.RPC.WatchtowerStats()
|
||||||
defer cancel()
|
|
||||||
bkpStats, err := dave.WatchtowerClient.Stats(ctxt,
|
|
||||||
&wtclientrpc.StatsRequest{},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if bkpStats == nil {
|
if bkpStats == nil {
|
||||||
return errors.New("no active backup sessions")
|
return errors.New("no active backup sessions")
|
||||||
}
|
}
|
||||||
@@ -806,197 +742,128 @@ func testRevokedCloseRetributionAltruistWatchtowerCase(
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}, defaultTimeout)
|
}, defaultTimeout)
|
||||||
if err != nil {
|
require.NoError(ht, err, "unable to verify backup task completed")
|
||||||
t.Fatalf("unable to verify backup task completed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown Dave to simulate going offline for an extended period of
|
// Shutdown Dave to simulate going offline for an extended period of
|
||||||
// time. Once he's not watching, Carol will try to breach the channel.
|
// time. Once he's not watching, Carol will try to breach the channel.
|
||||||
restart, err := net.SuspendNode(dave)
|
restart := ht.SuspendNode(dave)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to suspend Dave: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we shutdown Carol, copying over the his temporary database state
|
// Now we shutdown Carol, copying over the his temporary database state
|
||||||
// which has the *prior* channel state over his current most up to date
|
// 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
|
// state. With this, we essentially force Carol to travel back in time
|
||||||
// within the channel's history.
|
// within the channel's history.
|
||||||
if err = net.RestartNode(carol, func() error {
|
ht.RestartNodeAndRestoreDB(carol)
|
||||||
return net.RestoreDb(carol)
|
|
||||||
}); err != nil {
|
|
||||||
t.Fatalf("unable to restart node: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now query for Carol's channel state, it should show that he's at a
|
// Now query for Carol's channel state, it should show that he's at a
|
||||||
// state number in the past, not the *latest* state.
|
// state number in the past, not the *latest* state.
|
||||||
carolChan, err = getChanInfo(carol)
|
ht.AssertChannelCommitHeight(carol, chanPoint, carolStateNumPreCopy)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to get carol chan info: %v", err)
|
|
||||||
}
|
|
||||||
if carolChan.NumUpdates != carolStateNumPreCopy {
|
|
||||||
t.Fatalf("db copy failed: %v", carolChan.NumUpdates)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now force Carol to execute a *force* channel closure by unilaterally
|
// Now force Carol to execute a *force* channel closure by unilaterally
|
||||||
// broadcasting his current channel state. This is actually the
|
// broadcasting his current channel state. This is actually the
|
||||||
// commitment transaction of a prior *revoked* state, so he'll soon
|
// commitment transaction of a prior *revoked* state, so he'll soon
|
||||||
// feel the wrath of Dave's retribution.
|
// feel the wrath of Dave's retribution.
|
||||||
closeUpdates, closeTxID, err := net.CloseChannel(carol, chanPoint, true)
|
closeUpdates, closeTxID := ht.CloseChannelAssertPending(
|
||||||
if err != nil {
|
carol, chanPoint, true,
|
||||||
t.Fatalf("unable to close channel: %v", err)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// Query the mempool for the breaching closing transaction, this should
|
|
||||||
// be broadcast by Carol when she force closes the channel above.
|
|
||||||
txid, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to find Carol's force close tx in mempool: %v",
|
|
||||||
err)
|
|
||||||
}
|
|
||||||
if *txid != *closeTxID {
|
|
||||||
t.Fatalf("expected closeTx(%v) in mempool, instead found %v",
|
|
||||||
closeTxID, txid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, generate a single block, wait for the final close status
|
// Finally, generate a single block, wait for the final close status
|
||||||
// update, then ensure that the closing transaction was included in the
|
// update, then ensure that the closing transaction was included in the
|
||||||
// block.
|
// block.
|
||||||
block := mineBlocks(t, net, 1, 1)[0]
|
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
|
||||||
|
|
||||||
breachTXID, err := net.WaitForChannelClose(closeUpdates)
|
breachTXID := ht.WaitForChannelCloseEvent(closeUpdates)
|
||||||
if err != nil {
|
ht.Miner.AssertTxInBlock(block, breachTXID)
|
||||||
t.Fatalf("error while waiting for channel close: %v", err)
|
|
||||||
}
|
// The breachTXID should match the above closeTxID.
|
||||||
assertTxInBlock(t, block, breachTXID)
|
require.EqualValues(ht, breachTXID, closeTxID)
|
||||||
|
|
||||||
// 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.
|
// above.
|
||||||
justiceTXID, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout)
|
justiceTXID := ht.Miner.AssertNumTxsInMempool(1)[0]
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to find Dave's justice tx in mempool: %v",
|
|
||||||
err)
|
|
||||||
}
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
|
|
||||||
// Query for the mempool transaction found above. Then assert that all
|
// Query for the mempool transaction found above. Then assert that all
|
||||||
// the inputs of this transaction are spending outputs generated by
|
// the inputs of this transaction are spending outputs generated by
|
||||||
// Carol's breach transaction above.
|
// Carol's breach transaction above.
|
||||||
justiceTx, err := net.Miner.Client.GetRawTransaction(justiceTXID)
|
justiceTx := ht.Miner.GetRawTransaction(justiceTXID)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to query for justice tx: %v", err)
|
|
||||||
}
|
|
||||||
for _, txIn := range justiceTx.MsgTx().TxIn {
|
for _, txIn := range justiceTx.MsgTx().TxIn {
|
||||||
if !bytes.Equal(txIn.PreviousOutPoint.Hash[:], breachTXID[:]) {
|
require.Equal(ht, breachTXID[:], txIn.PreviousOutPoint.Hash[:],
|
||||||
t.Fatalf("justice tx not spending commitment utxo "+
|
"justice tx not spending commitment utxo")
|
||||||
"instead is: %v", txIn.PreviousOutPoint)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
willyBalResp := willy.RPC.WalletBalance()
|
||||||
willyBalReq := &lnrpc.WalletBalanceRequest{}
|
require.Zero(ht, willyBalResp.ConfirmedBalance,
|
||||||
willyBalResp, err := willy.WalletBalance(ctxt, willyBalReq)
|
"willy should have 0 balance before mining justice transaction")
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to get willy's balance: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if willyBalResp.ConfirmedBalance != 0 {
|
|
||||||
t.Fatalf("willy should have 0 balance before mining "+
|
|
||||||
"justice transaction, instead has %d",
|
|
||||||
willyBalResp.ConfirmedBalance)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now mine a block, this transaction should include Dave's justice
|
// Now mine a block, this transaction should include Dave's justice
|
||||||
// transaction which was just accepted into the mempool.
|
// transaction which was just accepted into the mempool.
|
||||||
block = mineBlocks(t, net, 1, 1)[0]
|
block = ht.MineBlocksAndAssertNumTxes(1, 1)[0]
|
||||||
|
|
||||||
// The block should have exactly *two* transactions, one of which is
|
// The block should have exactly *two* transactions, one of which is
|
||||||
// the justice transaction.
|
// the justice transaction.
|
||||||
if len(block.Transactions) != 2 {
|
require.Len(ht, block.Transactions, 2, "transaction wasn't mined")
|
||||||
t.Fatalf("transaction wasn't mined")
|
|
||||||
}
|
|
||||||
justiceSha := block.Transactions[1].TxHash()
|
justiceSha := block.Transactions[1].TxHash()
|
||||||
if !bytes.Equal(justiceTx.Hash()[:], justiceSha[:]) {
|
require.Equal(ht, justiceTx.Hash()[:], justiceSha[:],
|
||||||
t.Fatalf("justice tx wasn't mined")
|
"justice tx wasn't mined")
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that Willy doesn't get any funds, as he is acting as an
|
// Ensure that Willy doesn't get any funds, as he is acting as an
|
||||||
// altruist watchtower.
|
// altruist watchtower.
|
||||||
var predErr error
|
err = wait.NoError(func() error {
|
||||||
err = wait.Invariant(func() bool {
|
willyBalResp := willy.RPC.WalletBalance()
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
willyBalReq := &lnrpc.WalletBalanceRequest{}
|
|
||||||
willyBalResp, err := willy.WalletBalance(ctxt, willyBalReq)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to get willy's balance: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if willyBalResp.ConfirmedBalance != 0 {
|
if willyBalResp.ConfirmedBalance != 0 {
|
||||||
predErr = fmt.Errorf("Expected Willy to have no funds "+
|
return fmt.Errorf("Expected Willy to have no funds "+
|
||||||
"after justice transaction was mined, found %v",
|
"after justice transaction was mined, found %v",
|
||||||
willyBalResp)
|
willyBalResp)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return nil
|
||||||
}, time.Second*5)
|
}, time.Second*5)
|
||||||
if err != nil {
|
require.NoError(ht, err, "timeout checking willy's balance")
|
||||||
t.Fatalf("%v", predErr)
|
|
||||||
}
|
// 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.
|
// 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
|
// We should him to detect the breach, but realize that the funds have
|
||||||
// then been swept to his wallet by Willy.
|
// then been swept to his wallet by Willy.
|
||||||
err = restart()
|
require.NoError(ht, restart(), "unable to restart dave")
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to restart dave: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = wait.Predicate(func() bool {
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
daveBalReq := &lnrpc.ChannelBalanceRequest{}
|
|
||||||
daveBalResp, err := dave.ChannelBalance(ctxt, daveBalReq)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to get dave's balance: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
err = wait.NoError(func() error {
|
||||||
|
daveBalResp := dave.RPC.ChannelBalance()
|
||||||
if daveBalResp.LocalBalance.Sat != 0 {
|
if daveBalResp.LocalBalance.Sat != 0 {
|
||||||
predErr = fmt.Errorf("Dave should end up with zero "+
|
return fmt.Errorf("Dave should end up with zero "+
|
||||||
"channel balance, instead has %d",
|
"channel balance, instead has %d",
|
||||||
daveBalResp.LocalBalance.Sat)
|
daveBalResp.LocalBalance.Sat)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return nil
|
||||||
}, defaultTimeout)
|
}, defaultTimeout)
|
||||||
if err != nil {
|
require.NoError(ht, err, "timeout checking dave's channel balance")
|
||||||
t.Fatalf("%v", predErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
assertNumPendingChannels(t, dave, 0, 0)
|
ht.AssertNumPendingForceClose(dave, 0)
|
||||||
|
|
||||||
err = wait.Predicate(func() bool {
|
// Check that Dave's wallet balance is increased.
|
||||||
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
|
err = wait.NoError(func() error {
|
||||||
daveBalReq := &lnrpc.WalletBalanceRequest{}
|
daveBalResp := dave.RPC.WalletBalance()
|
||||||
daveBalResp, err := dave.WalletBalance(ctxt, daveBalReq)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to get dave's balance: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if daveBalResp.ConfirmedBalance <= davePreSweepBalance {
|
if daveBalResp.ConfirmedBalance <= davePreSweepBalance {
|
||||||
predErr = fmt.Errorf("Dave should have more than %d "+
|
return fmt.Errorf("Dave should have more than %d "+
|
||||||
"after sweep, instead has %d",
|
"after sweep, instead has %d",
|
||||||
davePreSweepBalance,
|
davePreSweepBalance,
|
||||||
daveBalResp.ConfirmedBalance)
|
daveBalResp.ConfirmedBalance)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return nil
|
||||||
}, defaultTimeout)
|
}, defaultTimeout)
|
||||||
if err != nil {
|
require.NoError(ht, err, "timeout checking dave's wallet balance")
|
||||||
t.Fatalf("%v", predErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dave should have no open channels.
|
// Dave should have no open channels.
|
||||||
assertNodeNumChannels(t, dave, 0)
|
ht.AssertNodeNumChannels(dave, 0)
|
||||||
}
|
}
|
||||||
@@ -72,10 +72,6 @@ var allTestCases = []*testCase{
|
|||||||
name: "switch offline delivery outgoing offline",
|
name: "switch offline delivery outgoing offline",
|
||||||
test: testSwitchOfflineDeliveryOutgoingOffline,
|
test: testSwitchOfflineDeliveryOutgoingOffline,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "revoked uncooperative close retribution altruist watchtower",
|
|
||||||
test: testRevokedCloseRetributionAltruistWatchtower,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "query routes",
|
name: "query routes",
|
||||||
test: testQueryRoutes,
|
test: testQueryRoutes,
|
||||||
|
|||||||
Reference in new issue
Block a user