mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-01-19 05:45:21 +01:00
itest: refactor testRevokedCloseRetributionZeroValueRemoteOutput
This commit is contained in:
parent
222c06ca48
commit
f590a96372
@ -295,4 +295,9 @@ var allTestCasesTemp = []*lntemp.TestCase{
|
||||
Name: "revoked uncooperative close retribution",
|
||||
TestFunc: testRevokedCloseRetribution,
|
||||
},
|
||||
{
|
||||
Name: "revoked uncooperative close retribution zero value " +
|
||||
"remote output",
|
||||
TestFunc: testRevokedCloseRetributionZeroValueRemoteOutput,
|
||||
},
|
||||
}
|
||||
|
@ -169,9 +169,7 @@ func testRevokedCloseRetribution(ht *lntemp.HarnessTest) {
|
||||
// testRevokedCloseRetributionZeroValueRemoteOutput tests that Dave is able
|
||||
// carry out retribution in the event that he fails in state where the remote
|
||||
// commitment output has zero-value.
|
||||
func testRevokedCloseRetributionZeroValueRemoteOutput(net *lntest.NetworkHarness,
|
||||
t *harnessTest) {
|
||||
|
||||
func testRevokedCloseRetributionZeroValueRemoteOutput(ht *lntemp.HarnessTest) {
|
||||
const (
|
||||
chanAmt = funding.MaxBtcFundingAmount
|
||||
paymentAmt = 10000
|
||||
@ -180,177 +178,103 @@ func testRevokedCloseRetributionZeroValueRemoteOutput(net *lntest.NetworkHarness
|
||||
|
||||
// Since we'd like to test some multi-hop failure scenarios, we'll
|
||||
// introduce another node into our test network: Carol.
|
||||
carol := net.NewNode(t.t, "Carol", []string{"--hodl.exit-settle"})
|
||||
defer shutdownAndAssert(net, t, carol)
|
||||
carol := ht.NewNode("Carol", []string{"--hodl.exit-settle"})
|
||||
|
||||
// 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. We also can't have Dave automatically
|
||||
// re-connect too early, otherwise DLP would be initiated instead of the
|
||||
// breach we want to provoke.
|
||||
dave := net.NewNode(
|
||||
t.t, "Dave",
|
||||
dave := ht.NewNode(
|
||||
"Dave",
|
||||
[]string{"--hodl.exit-settle", "--nolisten", "--minbackoff=1h"},
|
||||
)
|
||||
defer shutdownAndAssert(net, t, dave)
|
||||
|
||||
// We must let Dave have an open channel before he can send a node
|
||||
// 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
|
||||
// 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
|
||||
// closure by Carol, we'll first open up a channel between them with a
|
||||
// 0.5 BTC value.
|
||||
chanPoint := openChannelAndAssert(
|
||||
t, net, dave, carol,
|
||||
lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
},
|
||||
chanPoint := ht.OpenChannel(
|
||||
dave, carol, lntemp.OpenChannelParams{Amt: chanAmt},
|
||||
)
|
||||
|
||||
// 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, _, _, err := createPayReqs(
|
||||
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)
|
||||
}
|
||||
carolPayReqs, _, _ := ht.CreatePayReqs(carol, paymentAmt, numInvoices)
|
||||
|
||||
// Next query for Carol's channel state, as we sent 0 payments, Carol
|
||||
// should now see her balance as being 0 satoshis.
|
||||
carolChan, err := getChanInfo(carol)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get carol's channel info: %v", err)
|
||||
}
|
||||
if carolChan.LocalBalance != 0 {
|
||||
t.Fatalf("carol's balance is incorrect, got %v, expected %v",
|
||||
carolChan.LocalBalance, 0)
|
||||
}
|
||||
carolChan := ht.AssertChannelLocalBalance(carol, chanPoint, 0)
|
||||
|
||||
// Grab Carol's current commitment height (update number), we'll later
|
||||
// revert her to this state after additional updates to force her to
|
||||
// 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
|
||||
// temporary file we created above. Later after more updates, we'll
|
||||
// restore this state.
|
||||
if err := net.BackupDb(carol); err != nil {
|
||||
t.Fatalf("unable to copy database files: %v", err)
|
||||
}
|
||||
ht.BackupDB(carol)
|
||||
|
||||
// Reconnect the peers after the restart that was needed for the db
|
||||
// backup.
|
||||
net.EnsureConnected(t.t, dave, carol)
|
||||
ht.EnsureConnected(dave, carol)
|
||||
|
||||
// Finally, send payments from Dave to Carol, consuming Carol's
|
||||
// remaining payment hashes.
|
||||
err = completePaymentRequests(
|
||||
dave, dave.RouterClient, carolPayReqs, false,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to send payments: %v", err)
|
||||
}
|
||||
|
||||
_, err = getChanInfo(carol)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get carol chan info: %v", err)
|
||||
}
|
||||
ht.CompletePaymentRequestsNoWait(dave, carolPayReqs, chanPoint)
|
||||
|
||||
// Now we shutdown Carol, copying over the her temporary database state
|
||||
// which has the *prior* channel state over her current most up to date
|
||||
// state. With this, we essentially force Carol to travel back in time
|
||||
// within the channel's history.
|
||||
if err = net.RestartNode(carol, func() error {
|
||||
return net.RestoreDb(carol)
|
||||
}); err != nil {
|
||||
t.Fatalf("unable to restart node: %v", err)
|
||||
}
|
||||
ht.RestartNodeAndRestoreDB(carol)
|
||||
|
||||
// Now query for Carol's channel state, it should show that she's at a
|
||||
// state number in the past, not the *latest* state.
|
||||
carolChan, err = getChanInfo(carol)
|
||||
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)
|
||||
}
|
||||
ht.AssertChannelCommitHeight(carol, chanPoint, carolStateNumPreCopy)
|
||||
|
||||
// Now force Carol to execute a *force* channel closure by unilaterally
|
||||
// broadcasting her current channel state. This is actually the
|
||||
// commitment transaction of a prior *revoked* state, so she'll soon
|
||||
// feel the wrath of Dave's retribution.
|
||||
force := true
|
||||
closeUpdates, closeTxID, closeErr := net.CloseChannel(
|
||||
carol, chanPoint, force,
|
||||
stream, closeTxID := ht.CloseChannelAssertPending(
|
||||
carol, chanPoint, true,
|
||||
)
|
||||
require.NoError(t.t, closeErr, "unable to close channel")
|
||||
|
||||
// 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
|
||||
// update, then ensure that the closing transaction was included in the
|
||||
// block.
|
||||
block := mineBlocks(t, net, 1, 1)[0]
|
||||
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||
|
||||
// Here, Dave receives a confirmation of Carol's breach transaction.
|
||||
// We restart Dave to ensure that he is persisting his retribution
|
||||
// state and continues exacting justice after his node restarts.
|
||||
if err := net.RestartNode(dave, nil); err != nil {
|
||||
t.Fatalf("unable to stop Dave's node: %v", err)
|
||||
}
|
||||
ht.RestartNode(dave)
|
||||
|
||||
breachTXID, err := net.WaitForChannelClose(closeUpdates)
|
||||
if err != nil {
|
||||
t.Fatalf("error while waiting for channel close: %v", err)
|
||||
}
|
||||
assertTxInBlock(t, block, breachTXID)
|
||||
// The breachTXID should match the above closeTxID.
|
||||
breachTXID := ht.WaitForChannelCloseEvent(stream)
|
||||
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, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to find Dave's justice tx in mempool: %v",
|
||||
err)
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
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, err := net.Miner.Client.GetRawTransaction(justiceTXID)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to query for justice tx: %v", err)
|
||||
}
|
||||
justiceTx := ht.Miner.GetRawTransaction(justiceTXID)
|
||||
for _, txIn := range justiceTx.MsgTx().TxIn {
|
||||
if !bytes.Equal(txIn.PreviousOutPoint.Hash[:], breachTXID[:]) {
|
||||
t.Fatalf("justice tx not spending commitment utxo "+
|
||||
"instead is: %v", txIn.PreviousOutPoint)
|
||||
}
|
||||
require.Equal(ht, breachTXID[:], txIn.PreviousOutPoint.Hash[:],
|
||||
"justice tx not spending commitment utxo ")
|
||||
}
|
||||
|
||||
// We restart Dave here to ensure that he persists his retribution state
|
||||
@ -358,25 +282,20 @@ func testRevokedCloseRetributionZeroValueRemoteOutput(net *lntest.NetworkHarness
|
||||
// this point, Dave has broadcast the justice transaction, but it hasn't
|
||||
// been confirmed yet; when Dave restarts, he should start waiting for
|
||||
// the justice transaction to confirm again.
|
||||
if err := net.RestartNode(dave, nil); err != nil {
|
||||
t.Fatalf("unable to restart Dave's node: %v", err)
|
||||
}
|
||||
ht.RestartNode(dave)
|
||||
|
||||
// Now mine a block, this transaction should include Dave's justice
|
||||
// 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 justice transaction.
|
||||
if len(block.Transactions) != 2 {
|
||||
t.Fatalf("transaction wasn't mined")
|
||||
}
|
||||
require.Len(ht, block.Transactions, 2, "transaction wasn't mined")
|
||||
justiceSha := block.Transactions[1].TxHash()
|
||||
if !bytes.Equal(justiceTx.Hash()[:], justiceSha[:]) {
|
||||
t.Fatalf("justice tx wasn't mined")
|
||||
}
|
||||
require.Equal(ht, justiceTx.Hash()[:], justiceSha[:],
|
||||
"justice tx wasn't mined")
|
||||
|
||||
assertNodeNumChannels(t, dave, 0)
|
||||
ht.AssertNodeNumChannels(dave, 0)
|
||||
}
|
||||
|
||||
// testRevokedCloseRetributionRemoteHodl tests that Dave properly responds to a
|
||||
|
@ -72,10 +72,6 @@ var allTestCases = []*testCase{
|
||||
name: "switch offline delivery outgoing offline",
|
||||
test: testSwitchOfflineDeliveryOutgoingOffline,
|
||||
},
|
||||
{
|
||||
name: "revoked uncooperative close retribution zero value remote output",
|
||||
test: testRevokedCloseRetributionZeroValueRemoteOutput,
|
||||
},
|
||||
{
|
||||
name: "revoked uncooperative close retribution remote hodl",
|
||||
test: testRevokedCloseRetributionRemoteHodl,
|
||||
|
Loading…
Reference in New Issue
Block a user