mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-02-23 14:40:30 +01:00
lntest+itest: start flattening the multi-hop tests
Starting from this commit, we begin the process of flattening the multi-hop itests to make them easier to be maintained. The tests are refactored into their own test cases, with each test focusing on testing one channel type. This is necessary to save effort for future development. These tests are also updated to reflect the new `blockbeat` behavior.
This commit is contained in:
parent
e45005b310
commit
9ab9cd5f99
5 changed files with 554 additions and 246 deletions
|
@ -153,10 +153,6 @@ var allTestCases = []*lntest.TestCase{
|
|||
Name: "addpeer config",
|
||||
TestFunc: testAddPeerConfig,
|
||||
},
|
||||
{
|
||||
Name: "multi hop htlc local timeout",
|
||||
TestFunc: testMultiHopHtlcLocalTimeout,
|
||||
},
|
||||
{
|
||||
Name: "multi hop local force close on-chain htlc timeout",
|
||||
TestFunc: testMultiHopLocalForceCloseOnChainHtlcTimeout,
|
||||
|
@ -715,3 +711,8 @@ var allTestCases = []*lntest.TestCase{
|
|||
TestFunc: testQuiescence,
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Register subtests.
|
||||
allTestCases = append(allTestCases, multiHopForceCloseTestCases...)
|
||||
}
|
||||
|
|
361
itest/lnd_multi-hop_force_close_test.go
Normal file
361
itest/lnd_multi-hop_force_close_test.go
Normal file
|
@ -0,0 +1,361 @@
|
|||
package itest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/lightningnetwork/lnd/lncfg"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||
"github.com/lightningnetwork/lnd/lntest"
|
||||
"github.com/lightningnetwork/lnd/lntest/node"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const chanAmt = 1000000
|
||||
|
||||
var leasedType = lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE
|
||||
|
||||
// multiHopForceCloseTestCases defines a set of tests that focuses on the
|
||||
// behavior of the force close in a multi-hop scenario.
|
||||
var multiHopForceCloseTestCases = []*lntest.TestCase{
|
||||
{
|
||||
Name: "multihop local claim outgoing htlc anchor",
|
||||
TestFunc: testLocalClaimOutgoingHTLCAnchor,
|
||||
},
|
||||
{
|
||||
Name: "multihop local claim outgoing htlc simple taproot",
|
||||
TestFunc: testLocalClaimOutgoingHTLCSimpleTaproot,
|
||||
},
|
||||
{
|
||||
Name: "multihop local claim outgoing htlc leased",
|
||||
TestFunc: testLocalClaimOutgoingHTLCLeased,
|
||||
},
|
||||
}
|
||||
|
||||
// testLocalClaimOutgoingHTLCAnchor tests `runLocalClaimOutgoingHTLC` with
|
||||
// anchor channel.
|
||||
func testLocalClaimOutgoingHTLCAnchor(ht *lntest.HarnessTest) {
|
||||
success := ht.Run("no zero conf", func(t *testing.T) {
|
||||
st := ht.Subtest(t)
|
||||
|
||||
// Create a three hop network: Alice -> Bob -> Carol, using
|
||||
// anchor channels.
|
||||
//
|
||||
// Prepare params.
|
||||
openChannelParams := lntest.OpenChannelParams{Amt: chanAmt}
|
||||
|
||||
cfg := node.CfgAnchor
|
||||
cfgCarol := append([]string{"--hodl.exit-settle"}, cfg...)
|
||||
cfgs := [][]string{cfg, cfg, cfgCarol}
|
||||
|
||||
runLocalClaimOutgoingHTLC(st, cfgs, openChannelParams)
|
||||
})
|
||||
if !success {
|
||||
return
|
||||
}
|
||||
|
||||
ht.Run("zero conf", func(t *testing.T) {
|
||||
st := ht.Subtest(t)
|
||||
|
||||
// Create a three hop network: Alice -> Bob -> Carol, using
|
||||
// zero-conf anchor channels.
|
||||
//
|
||||
// Prepare params.
|
||||
openChannelParams := lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
ZeroConf: true,
|
||||
CommitmentType: lnrpc.CommitmentType_ANCHORS,
|
||||
}
|
||||
|
||||
// Prepare Carol's node config to enable zero-conf and anchor.
|
||||
cfg := node.CfgZeroConf
|
||||
cfgCarol := append([]string{"--hodl.exit-settle"}, cfg...)
|
||||
cfgs := [][]string{cfg, cfg, cfgCarol}
|
||||
|
||||
runLocalClaimOutgoingHTLC(st, cfgs, openChannelParams)
|
||||
})
|
||||
}
|
||||
|
||||
// testLocalClaimOutgoingHTLCSimpleTaproot tests `runLocalClaimOutgoingHTLC`
|
||||
// with simple taproot channel.
|
||||
func testLocalClaimOutgoingHTLCSimpleTaproot(ht *lntest.HarnessTest) {
|
||||
c := lnrpc.CommitmentType_SIMPLE_TAPROOT
|
||||
|
||||
success := ht.Run("no zero conf", func(t *testing.T) {
|
||||
st := ht.Subtest(t)
|
||||
|
||||
// Create a three hop network: Alice -> Bob -> Carol, using
|
||||
// simple taproot channels.
|
||||
//
|
||||
// Prepare params.
|
||||
openChannelParams := lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
CommitmentType: c,
|
||||
Private: true,
|
||||
}
|
||||
|
||||
cfg := node.CfgSimpleTaproot
|
||||
cfgCarol := append([]string{"--hodl.exit-settle"}, cfg...)
|
||||
cfgs := [][]string{cfg, cfg, cfgCarol}
|
||||
|
||||
runLocalClaimOutgoingHTLC(st, cfgs, openChannelParams)
|
||||
})
|
||||
if !success {
|
||||
return
|
||||
}
|
||||
|
||||
ht.Run("zero conf", func(t *testing.T) {
|
||||
st := ht.Subtest(t)
|
||||
|
||||
// Create a three hop network: Alice -> Bob -> Carol, using
|
||||
// zero-conf simple taproot channels.
|
||||
//
|
||||
// Prepare params.
|
||||
openChannelParams := lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
ZeroConf: true,
|
||||
CommitmentType: c,
|
||||
Private: true,
|
||||
}
|
||||
|
||||
// Prepare Carol's node config to enable zero-conf and leased
|
||||
// channel.
|
||||
cfg := node.CfgSimpleTaproot
|
||||
cfg = append(cfg, node.CfgZeroConf...)
|
||||
cfgCarol := append([]string{"--hodl.exit-settle"}, cfg...)
|
||||
cfgs := [][]string{cfg, cfg, cfgCarol}
|
||||
|
||||
runLocalClaimOutgoingHTLC(st, cfgs, openChannelParams)
|
||||
})
|
||||
}
|
||||
|
||||
// testLocalClaimOutgoingHTLCLeased tests `runLocalClaimOutgoingHTLC` with
|
||||
// script enforced lease channel.
|
||||
func testLocalClaimOutgoingHTLCLeased(ht *lntest.HarnessTest) {
|
||||
success := ht.Run("no zero conf", func(t *testing.T) {
|
||||
st := ht.Subtest(t)
|
||||
|
||||
// Create a three hop network: Alice -> Bob -> Carol, using
|
||||
// leased channels.
|
||||
//
|
||||
// Prepare params.
|
||||
openChannelParams := lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
CommitmentType: leasedType,
|
||||
}
|
||||
|
||||
cfg := node.CfgLeased
|
||||
cfgCarol := append([]string{"--hodl.exit-settle"}, cfg...)
|
||||
cfgs := [][]string{cfg, cfg, cfgCarol}
|
||||
|
||||
runLocalClaimOutgoingHTLC(st, cfgs, openChannelParams)
|
||||
})
|
||||
if !success {
|
||||
return
|
||||
}
|
||||
|
||||
ht.Run("zero conf", func(t *testing.T) {
|
||||
st := ht.Subtest(t)
|
||||
|
||||
// Create a three hop network: Alice -> Bob -> Carol, using
|
||||
// zero-conf anchor channels.
|
||||
//
|
||||
// Prepare params.
|
||||
openChannelParams := lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
ZeroConf: true,
|
||||
CommitmentType: leasedType,
|
||||
}
|
||||
|
||||
// Prepare Carol's node config to enable zero-conf and leased
|
||||
// channel.
|
||||
cfg := node.CfgLeased
|
||||
cfg = append(cfg, node.CfgZeroConf...)
|
||||
cfgCarol := append([]string{"--hodl.exit-settle"}, cfg...)
|
||||
cfgs := [][]string{cfg, cfg, cfgCarol}
|
||||
|
||||
runLocalClaimOutgoingHTLC(st, cfgs, openChannelParams)
|
||||
})
|
||||
}
|
||||
|
||||
// runLocalClaimOutgoingHTLC tests that in a multi-hop scenario, if the
|
||||
// outgoing HTLC is about to time out, then we'll go to chain in order to claim
|
||||
// it using the HTLC timeout transaction. Any dust HTLC's should be immediately
|
||||
// canceled backwards. Once the timeout has been reached, then we should sweep
|
||||
// it on-chain, and cancel the HTLC backwards.
|
||||
func runLocalClaimOutgoingHTLC(ht *lntest.HarnessTest,
|
||||
cfgs [][]string, params lntest.OpenChannelParams) {
|
||||
|
||||
// Create a three hop network: Alice -> Bob -> Carol.
|
||||
_, nodes := ht.CreateSimpleNetwork(cfgs, params)
|
||||
alice, bob, carol := nodes[0], nodes[1], nodes[2]
|
||||
|
||||
// For neutrino backend, we need to fund one more UTXO for Bob so he
|
||||
// can sweep his outputs.
|
||||
if ht.IsNeutrinoBackend() {
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, bob)
|
||||
}
|
||||
|
||||
// Now that our channels are set up, we'll send two HTLC's from Alice
|
||||
// to Carol. The first HTLC will be universally considered "dust",
|
||||
// while the second will be a proper fully valued HTLC.
|
||||
const (
|
||||
dustHtlcAmt = btcutil.Amount(100)
|
||||
htlcAmt = btcutil.Amount(300_000)
|
||||
)
|
||||
|
||||
// We'll create two random payment hashes unknown to carol, then send
|
||||
// each of them by manually specifying the HTLC details.
|
||||
carolPubKey := carol.PubKey[:]
|
||||
dustPayHash := ht.Random32Bytes()
|
||||
payHash := ht.Random32Bytes()
|
||||
|
||||
// If this is a taproot channel, then we'll need to make some manual
|
||||
// route hints so Alice can actually find a route.
|
||||
var routeHints []*lnrpc.RouteHint
|
||||
if params.CommitmentType == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
||||
routeHints = makeRouteHints(bob, carol, params.ZeroConf)
|
||||
}
|
||||
|
||||
alice.RPC.SendPayment(&routerrpc.SendPaymentRequest{
|
||||
Dest: carolPubKey,
|
||||
Amt: int64(dustHtlcAmt),
|
||||
PaymentHash: dustPayHash,
|
||||
FinalCltvDelta: finalCltvDelta,
|
||||
TimeoutSeconds: 60,
|
||||
FeeLimitMsat: noFeeLimitMsat,
|
||||
RouteHints: routeHints,
|
||||
})
|
||||
|
||||
alice.RPC.SendPayment(&routerrpc.SendPaymentRequest{
|
||||
Dest: carolPubKey,
|
||||
Amt: int64(htlcAmt),
|
||||
PaymentHash: payHash,
|
||||
FinalCltvDelta: finalCltvDelta,
|
||||
TimeoutSeconds: 60,
|
||||
FeeLimitMsat: noFeeLimitMsat,
|
||||
RouteHints: routeHints,
|
||||
})
|
||||
|
||||
// Verify that all nodes in the path now have two HTLC's with the
|
||||
// proper parameters.
|
||||
ht.AssertActiveHtlcs(alice, dustPayHash, payHash)
|
||||
ht.AssertActiveHtlcs(bob, dustPayHash, payHash)
|
||||
ht.AssertActiveHtlcs(carol, dustPayHash, payHash)
|
||||
|
||||
// We'll now mine enough blocks to trigger Bob's force close the
|
||||
// channel Bob=>Carol due to the fact that the HTLC is about to
|
||||
// timeout. With the default outgoing broadcast delta of zero, this
|
||||
// will be the same height as the htlc expiry height.
|
||||
numBlocks := padCLTV(
|
||||
uint32(finalCltvDelta - lncfg.DefaultOutgoingBroadcastDelta),
|
||||
)
|
||||
ht.MineBlocks(int(numBlocks))
|
||||
|
||||
// Bob's force close tx should have the following outputs,
|
||||
// 1. anchor output.
|
||||
// 2. to_local output, which is CSV locked.
|
||||
// 3. outgoing HTLC output, which has expired.
|
||||
//
|
||||
// Bob's anchor output should be offered to his sweeper since Bob has
|
||||
// time-sensitive HTLCs - we expect both anchors to be offered, while
|
||||
// the sweeping of the remote anchor will be marked as failed due to
|
||||
// `testmempoolaccept` check.
|
||||
//
|
||||
// For neutrino backend, there's no way to know the sweeping of the
|
||||
// remote anchor is failed, so Bob still sees two pending sweeps.
|
||||
if ht.IsNeutrinoBackend() {
|
||||
ht.AssertNumPendingSweeps(bob, 2)
|
||||
} else {
|
||||
ht.AssertNumPendingSweeps(bob, 1)
|
||||
}
|
||||
|
||||
// We expect to see tow txns in the mempool,
|
||||
// 1. Bob's force close tx.
|
||||
// 2. Bob's anchor sweep tx.
|
||||
ht.AssertNumTxsInMempool(2)
|
||||
|
||||
// Mine a block to confirm the closing tx and the anchor sweeping tx.
|
||||
ht.MineBlocksAndAssertNumTxes(1, 2)
|
||||
|
||||
// At this point, Bob should have canceled backwards the dust HTLC that
|
||||
// we sent earlier. This means Alice should now only have a single HTLC
|
||||
// on her channel.
|
||||
ht.AssertActiveHtlcs(alice, payHash)
|
||||
|
||||
// With the closing transaction confirmed, we should expect Bob's HTLC
|
||||
// timeout transaction to be offered to the sweeper due to the expiry
|
||||
// being reached. we also expect Carol's anchor sweeps.
|
||||
ht.AssertNumPendingSweeps(bob, 1)
|
||||
ht.AssertNumPendingSweeps(carol, 1)
|
||||
|
||||
// Bob's sweeper should sweep his outgoing HTLC immediately since it's
|
||||
// expired. His to_local output cannot be swept due to the CSV lock.
|
||||
// Carol's anchor sweep should be failed due to output being dust.
|
||||
ht.AssertNumTxsInMempool(1)
|
||||
|
||||
// Mine a block to confirm Bob's outgoing HTLC sweeping tx.
|
||||
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||
|
||||
// With Bob's HTLC timeout transaction confirmed, there should be no
|
||||
// active HTLC's on the commitment transaction from Alice -> Bob.
|
||||
ht.AssertNumActiveHtlcs(alice, 0)
|
||||
|
||||
// At this point, Bob should show that the pending HTLC has advanced to
|
||||
// the second stage and is ready to be swept once the timelock is up.
|
||||
resp := ht.AssertNumPendingForceClose(bob, 1)[0]
|
||||
require.NotZero(ht, resp.LimboBalance)
|
||||
require.Positive(ht, resp.BlocksTilMaturity)
|
||||
require.Equal(ht, 1, len(resp.PendingHtlcs))
|
||||
require.Equal(ht, uint32(2), resp.PendingHtlcs[0].Stage)
|
||||
|
||||
ht.Logf("Bob's timelock to_local output=%v, timelock on second stage "+
|
||||
"htlc=%v", resp.BlocksTilMaturity,
|
||||
resp.PendingHtlcs[0].BlocksTilMaturity)
|
||||
|
||||
if params.CommitmentType == leasedType {
|
||||
// Since Bob is the initiator of the script-enforced leased
|
||||
// channel between him and Carol, he will incur an additional
|
||||
// CLTV on top of the usual CSV delay on any outputs that he
|
||||
// can sweep back to his wallet.
|
||||
//
|
||||
// We now mine enough blocks so the CLTV lock expires, which
|
||||
// will trigger the sweep of the to_local and outgoing HTLC
|
||||
// outputs.
|
||||
ht.MineBlocks(int(resp.BlocksTilMaturity))
|
||||
|
||||
// Check that Bob has a pending sweeping tx which sweeps his
|
||||
// to_local and outgoing HTLC outputs.
|
||||
ht.AssertNumPendingSweeps(bob, 2)
|
||||
|
||||
// Mine a block to confirm the sweeping tx.
|
||||
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||
} else {
|
||||
// Since Bob force closed the channel between him and Carol, he
|
||||
// will incur the usual CSV delay on any outputs that he can
|
||||
// sweep back to his wallet. We'll subtract one block from our
|
||||
// current maturity period to assert on the mempool.
|
||||
ht.MineBlocks(int(resp.BlocksTilMaturity - 1))
|
||||
|
||||
// Check that Bob has a pending sweeping tx which sweeps his
|
||||
// to_local output.
|
||||
ht.AssertNumPendingSweeps(bob, 1)
|
||||
|
||||
// Mine a block to confirm the to_local sweeping tx, which also
|
||||
// triggers the sweeping of the second stage HTLC output.
|
||||
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||
|
||||
// Bob's sweeper should now broadcast his second layer sweep
|
||||
// due to the CSV on the HTLC timeout output.
|
||||
ht.AssertNumTxsInMempool(1)
|
||||
|
||||
// Next, we'll mine a final block that should confirm the
|
||||
// sweeping transactions left.
|
||||
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||
}
|
||||
|
||||
// Once this transaction has been confirmed, Bob should detect that he
|
||||
// no longer has any pending channels.
|
||||
ht.AssertNumPendingForceClose(bob, 0)
|
||||
}
|
|
@ -160,248 +160,6 @@ func runMultiHopHtlcClaimTest(ht *lntest.HarnessTest, tester caseRunner) {
|
|||
}
|
||||
}
|
||||
|
||||
// testMultiHopHtlcLocalTimeout tests that in a multi-hop HTLC scenario, if the
|
||||
// outgoing HTLC is about to time out, then we'll go to chain in order to claim
|
||||
// it using the HTLC timeout transaction. Any dust HTLC's should be immediately
|
||||
// canceled backwards. Once the timeout has been reached, then we should sweep
|
||||
// it on-chain, and cancel the HTLC backwards.
|
||||
func testMultiHopHtlcLocalTimeout(ht *lntest.HarnessTest) {
|
||||
runMultiHopHtlcClaimTest(ht, runMultiHopHtlcLocalTimeout)
|
||||
}
|
||||
|
||||
func runMultiHopHtlcLocalTimeout(ht *lntest.HarnessTest,
|
||||
alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) {
|
||||
|
||||
// First, we'll create a three hop network: Alice -> Bob -> Carol, with
|
||||
// Carol refusing to actually settle or directly cancel any HTLC's
|
||||
// self.
|
||||
aliceChanPoint, bobChanPoint, carol := createThreeHopNetwork(
|
||||
ht, alice, bob, true, c, zeroConf,
|
||||
)
|
||||
|
||||
// For neutrino backend, we need to fund one more UTXO for Bob so he
|
||||
// can sweep his outputs.
|
||||
if ht.IsNeutrinoBackend() {
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, bob)
|
||||
}
|
||||
|
||||
// Now that our channels are set up, we'll send two HTLC's from Alice
|
||||
// to Carol. The first HTLC will be universally considered "dust",
|
||||
// while the second will be a proper fully valued HTLC.
|
||||
const (
|
||||
dustHtlcAmt = btcutil.Amount(100)
|
||||
htlcAmt = btcutil.Amount(300_000)
|
||||
)
|
||||
|
||||
// We'll create two random payment hashes unknown to carol, then send
|
||||
// each of them by manually specifying the HTLC details.
|
||||
carolPubKey := carol.PubKey[:]
|
||||
dustPayHash := ht.Random32Bytes()
|
||||
payHash := ht.Random32Bytes()
|
||||
|
||||
// If this is a taproot channel, then we'll need to make some manual
|
||||
// route hints so Alice can actually find a route.
|
||||
var routeHints []*lnrpc.RouteHint
|
||||
if c == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
||||
routeHints = makeRouteHints(bob, carol, zeroConf)
|
||||
}
|
||||
|
||||
alice.RPC.SendPayment(&routerrpc.SendPaymentRequest{
|
||||
Dest: carolPubKey,
|
||||
Amt: int64(dustHtlcAmt),
|
||||
PaymentHash: dustPayHash,
|
||||
FinalCltvDelta: finalCltvDelta,
|
||||
TimeoutSeconds: 60,
|
||||
FeeLimitMsat: noFeeLimitMsat,
|
||||
RouteHints: routeHints,
|
||||
})
|
||||
|
||||
alice.RPC.SendPayment(&routerrpc.SendPaymentRequest{
|
||||
Dest: carolPubKey,
|
||||
Amt: int64(htlcAmt),
|
||||
PaymentHash: payHash,
|
||||
FinalCltvDelta: finalCltvDelta,
|
||||
TimeoutSeconds: 60,
|
||||
FeeLimitMsat: noFeeLimitMsat,
|
||||
RouteHints: routeHints,
|
||||
})
|
||||
|
||||
// Verify that all nodes in the path now have two HTLC's with the
|
||||
// proper parameters.
|
||||
ht.AssertActiveHtlcs(alice, dustPayHash, payHash)
|
||||
ht.AssertActiveHtlcs(bob, dustPayHash, payHash)
|
||||
ht.AssertActiveHtlcs(carol, dustPayHash, payHash)
|
||||
|
||||
// Increase the fee estimate so that the following force close tx will
|
||||
// be cpfp'ed.
|
||||
ht.SetFeeEstimate(30000)
|
||||
|
||||
// We'll now mine enough blocks to trigger Bob's broadcast of his
|
||||
// commitment transaction due to the fact that the HTLC is about to
|
||||
// timeout. With the default outgoing broadcast delta of zero, this will
|
||||
// be the same height as the htlc expiry height.
|
||||
numBlocks := padCLTV(
|
||||
uint32(finalCltvDelta - lncfg.DefaultOutgoingBroadcastDelta),
|
||||
)
|
||||
ht.MineBlocks(int(numBlocks))
|
||||
|
||||
// Bob's force close transaction should now be found in the mempool.
|
||||
ht.AssertNumTxsInMempool(1)
|
||||
op := ht.OutPointFromChannelPoint(bobChanPoint)
|
||||
closeTx := ht.AssertOutpointInMempool(op)
|
||||
|
||||
// Dust HTLCs are immediately canceled backwards as soon as the local
|
||||
// commitment tx is successfully broadcasted to the local mempool.
|
||||
ht.AssertActiveHtlcs(alice, payHash)
|
||||
|
||||
// Bob's anchor output should be offered to his sweep since Bob has
|
||||
// time-sensitive HTLCs - we expect both anchors are offered.
|
||||
ht.AssertNumPendingSweeps(bob, 2)
|
||||
|
||||
// Mine a block to confirm the closing transaction.
|
||||
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||
|
||||
// With the closing transaction confirmed, we should expect Bob's HTLC
|
||||
// timeout transaction to be offered to the sweeper due to the expiry
|
||||
// being reached. we also expect Bon and Carol's anchor sweeps.
|
||||
ht.AssertNumPendingSweeps(bob, 2)
|
||||
ht.AssertNumPendingSweeps(carol, 1)
|
||||
|
||||
// Mine a block to trigger Bob's sweeper to sweep.
|
||||
ht.MineEmptyBlocks(1)
|
||||
|
||||
// The above mined block would trigger Bob and Carol's sweepers to take
|
||||
// action. We now expect two txns:
|
||||
// 1. Bob's sweeping tx anchor sweep should now be found in the mempool.
|
||||
// 2. Bob's HTLC timeout tx sweep should now be found in the mempool.
|
||||
// Carol's anchor sweep should be failed due to output being dust.
|
||||
ht.AssertNumTxsInMempool(2)
|
||||
|
||||
htlcOutpoint := wire.OutPoint{Hash: closeTx.TxHash(), Index: 2}
|
||||
commitOutpoint := wire.OutPoint{Hash: closeTx.TxHash(), Index: 3}
|
||||
htlcTimeoutTxid := ht.AssertOutpointInMempool(
|
||||
htlcOutpoint,
|
||||
).TxHash()
|
||||
|
||||
// Mine a block to confirm the above two sweeping txns.
|
||||
ht.MineBlocksAndAssertNumTxes(1, 2)
|
||||
|
||||
// With Bob's HTLC timeout transaction confirmed, there should be no
|
||||
// active HTLC's on the commitment transaction from Alice -> Bob.
|
||||
ht.AssertNumActiveHtlcs(alice, 0)
|
||||
|
||||
// At this point, Bob should show that the pending HTLC has advanced to
|
||||
// the second stage and is ready to be swept once the timelock is up.
|
||||
pendingChanResp := bob.RPC.PendingChannels()
|
||||
require.Equal(ht, 1, len(pendingChanResp.PendingForceClosingChannels))
|
||||
forceCloseChan := pendingChanResp.PendingForceClosingChannels[0]
|
||||
require.NotZero(ht, forceCloseChan.LimboBalance)
|
||||
require.Positive(ht, forceCloseChan.BlocksTilMaturity)
|
||||
require.Equal(ht, 1, len(forceCloseChan.PendingHtlcs))
|
||||
require.Equal(ht, uint32(2), forceCloseChan.PendingHtlcs[0].Stage)
|
||||
|
||||
ht.Logf("Bob's timelock on commit=%v, timelock on htlc=%v",
|
||||
forceCloseChan.BlocksTilMaturity,
|
||||
forceCloseChan.PendingHtlcs[0].BlocksTilMaturity)
|
||||
|
||||
htlcTimeoutOutpoint := wire.OutPoint{Hash: htlcTimeoutTxid, Index: 0}
|
||||
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
||||
// Since Bob is the initiator of the script-enforced leased
|
||||
// channel between him and Carol, he will incur an additional
|
||||
// CLTV on top of the usual CSV delay on any outputs that he
|
||||
// can sweep back to his wallet.
|
||||
blocksTilMaturity := int(forceCloseChan.BlocksTilMaturity)
|
||||
|
||||
// We now mine enough blocks to trigger the sweep of the HTLC
|
||||
// timeout tx.
|
||||
ht.MineEmptyBlocks(blocksTilMaturity - 1)
|
||||
|
||||
// Check that Bob has one pending sweeping tx - the HTLC
|
||||
// timeout tx.
|
||||
ht.AssertNumPendingSweeps(bob, 1)
|
||||
|
||||
// Mine one more blocks, then his commit output will mature.
|
||||
// This will also trigger the sweeper to sweep his HTLC timeout
|
||||
// tx.
|
||||
ht.MineEmptyBlocks(1)
|
||||
|
||||
// Check that Bob has two pending sweeping txns.
|
||||
ht.AssertNumPendingSweeps(bob, 2)
|
||||
|
||||
// Assert that the HTLC timeout tx is now in the mempool.
|
||||
ht.AssertOutpointInMempool(htlcTimeoutOutpoint)
|
||||
|
||||
// We now wait for 30 seconds to overcome the flake - there's a
|
||||
// block race between contractcourt and sweeper, causing the
|
||||
// sweep to be broadcast earlier.
|
||||
//
|
||||
// TODO(yy): remove this once `blockbeat` is in place.
|
||||
numExpected := 1
|
||||
err := wait.NoError(func() error {
|
||||
mem := ht.GetRawMempool()
|
||||
if len(mem) == 2 {
|
||||
numExpected = 2
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("want %d, got %v in mempool: %v",
|
||||
numExpected, len(mem), mem)
|
||||
}, wait.DefaultTimeout)
|
||||
ht.Logf("Checking mempool got: %v", err)
|
||||
|
||||
// Mine a block to trigger the sweep of his commit output and
|
||||
// confirm his HTLC timeout sweep.
|
||||
ht.MineBlocksAndAssertNumTxes(1, numExpected)
|
||||
|
||||
// For leased channels, we need to mine one more block to
|
||||
// confirm Bob's commit output sweep.
|
||||
//
|
||||
// NOTE: we mine this block conditionally, as the commit output
|
||||
// may have already been swept one block earlier due to the
|
||||
// race in block consumption among subsystems.
|
||||
pendingChanResp := bob.RPC.PendingChannels()
|
||||
if len(pendingChanResp.PendingForceClosingChannels) != 0 {
|
||||
// Check that the sweep spends the expected inputs.
|
||||
ht.AssertOutpointInMempool(commitOutpoint)
|
||||
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||
}
|
||||
} else {
|
||||
// Since Bob force closed the channel between him and Carol, he
|
||||
// will incur the usual CSV delay on any outputs that he can
|
||||
// sweep back to his wallet. We'll subtract one block from our
|
||||
// current maturity period to assert on the mempool.
|
||||
numBlocks := int(forceCloseChan.BlocksTilMaturity - 1)
|
||||
ht.MineEmptyBlocks(numBlocks)
|
||||
|
||||
// Check that Bob has a pending sweeping tx.
|
||||
ht.AssertNumPendingSweeps(bob, 1)
|
||||
|
||||
// Mine a block the trigger the sweeping behavior.
|
||||
ht.MineEmptyBlocks(1)
|
||||
|
||||
// Check that the sweep spends from the mined commitment.
|
||||
ht.AssertOutpointInMempool(commitOutpoint)
|
||||
|
||||
// Mine one more block to trigger the timeout path.
|
||||
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||
|
||||
// Bob's sweeper should now broadcast his second layer sweep
|
||||
// due to the CSV on the HTLC timeout output.
|
||||
ht.AssertOutpointInMempool(htlcTimeoutOutpoint)
|
||||
|
||||
// Next, we'll mine a final block that should confirm the
|
||||
// sweeping transactions left.
|
||||
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||
}
|
||||
|
||||
// Once this transaction has been confirmed, Bob should detect that he
|
||||
// no longer has any pending channels.
|
||||
ht.AssertNumPendingForceClose(bob, 0)
|
||||
|
||||
// Coop close channel, expect no anchors.
|
||||
ht.CloseChannel(alice, aliceChanPoint)
|
||||
}
|
||||
|
||||
// testMultiHopReceiverChainClaim tests that in the multi-hop setting, if the
|
||||
// receiver of an HTLC knows the preimage, but wasn't able to settle the HTLC
|
||||
// off-chain, then it goes on chain to claim the HTLC uing the HTLC success
|
||||
|
|
|
@ -8,16 +8,19 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/blockchain"
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/lightningnetwork/lnd/fn/v2"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/kvdb/etcd"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
||||
"github.com/lightningnetwork/lnd/lntest/miner"
|
||||
"github.com/lightningnetwork/lnd/lntest/node"
|
||||
|
@ -26,6 +29,7 @@ import (
|
|||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/routing"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -49,6 +53,9 @@ const (
|
|||
// maxBlocksAllowed specifies the max allowed value to be used when
|
||||
// mining blocks.
|
||||
maxBlocksAllowed = 100
|
||||
|
||||
finalCltvDelta = routing.MinCLTVDelta // 18.
|
||||
thawHeightDelta = finalCltvDelta * 2 // 36.
|
||||
)
|
||||
|
||||
// TestCase defines a test case that's been used in the integration test.
|
||||
|
@ -2308,12 +2315,40 @@ func (h *HarnessTest) openChannelsForNodes(nodes []*node.HarnessNode,
|
|||
// Sanity check the params.
|
||||
require.Greater(h, len(nodes), 1, "need at least 2 nodes")
|
||||
|
||||
// attachFundingShim is a helper closure that optionally attaches a
|
||||
// funding shim to the open channel params and returns it.
|
||||
attachFundingShim := func(
|
||||
nodeA, nodeB *node.HarnessNode) OpenChannelParams {
|
||||
|
||||
// If this channel is not a script enforced lease channel,
|
||||
// we'll do nothing and return the params.
|
||||
leasedType := lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE
|
||||
if p.CommitmentType != leasedType {
|
||||
return p
|
||||
}
|
||||
|
||||
// Otherwise derive the funding shim, attach it to the original
|
||||
// open channel params and return it.
|
||||
minerHeight := h.CurrentHeight()
|
||||
thawHeight := minerHeight + thawHeightDelta
|
||||
fundingShim, _ := h.deriveFundingShim(
|
||||
nodeA, nodeB, p.Amt, thawHeight, true, leasedType,
|
||||
)
|
||||
|
||||
p.FundingShim = fundingShim
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// Open channels in batch to save blocks mined.
|
||||
reqs := make([]*OpenChannelRequest, 0, len(nodes)-1)
|
||||
for i := 0; i < len(nodes)-1; i++ {
|
||||
nodeA := nodes[i]
|
||||
nodeB := nodes[i+1]
|
||||
|
||||
// Optionally attach a funding shim to the open channel params.
|
||||
p = attachFundingShim(nodeA, nodeB)
|
||||
|
||||
req := &OpenChannelRequest{
|
||||
Local: nodeA,
|
||||
Remote: nodeB,
|
||||
|
@ -2364,3 +2399,122 @@ func (h *HarnessTest) openZeroConfChannelsForNodes(nodes []*node.HarnessNode,
|
|||
|
||||
return resp
|
||||
}
|
||||
|
||||
// deriveFundingShim creates a channel funding shim by deriving the necessary
|
||||
// keys on both sides.
|
||||
func (h *HarnessTest) deriveFundingShim(alice, bob *node.HarnessNode,
|
||||
chanSize btcutil.Amount, thawHeight uint32, publish bool,
|
||||
commitType lnrpc.CommitmentType) (*lnrpc.FundingShim,
|
||||
*lnrpc.ChannelPoint) {
|
||||
|
||||
keyLoc := &signrpc.KeyLocator{KeyFamily: 9999}
|
||||
carolFundingKey := alice.RPC.DeriveKey(keyLoc)
|
||||
daveFundingKey := bob.RPC.DeriveKey(keyLoc)
|
||||
|
||||
// Now that we have the multi-sig keys for each party, we can manually
|
||||
// construct the funding transaction. We'll instruct the backend to
|
||||
// immediately create and broadcast a transaction paying out an exact
|
||||
// amount. Normally this would reside in the mempool, but we just
|
||||
// confirm it now for simplicity.
|
||||
var (
|
||||
fundingOutput *wire.TxOut
|
||||
musig2 bool
|
||||
err error
|
||||
)
|
||||
|
||||
if commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT ||
|
||||
commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT_OVERLAY {
|
||||
|
||||
var carolKey, daveKey *btcec.PublicKey
|
||||
carolKey, err = btcec.ParsePubKey(carolFundingKey.RawKeyBytes)
|
||||
require.NoError(h, err)
|
||||
daveKey, err = btcec.ParsePubKey(daveFundingKey.RawKeyBytes)
|
||||
require.NoError(h, err)
|
||||
|
||||
_, fundingOutput, err = input.GenTaprootFundingScript(
|
||||
carolKey, daveKey, int64(chanSize),
|
||||
fn.None[chainhash.Hash](),
|
||||
)
|
||||
require.NoError(h, err)
|
||||
|
||||
musig2 = true
|
||||
} else {
|
||||
_, fundingOutput, err = input.GenFundingPkScript(
|
||||
carolFundingKey.RawKeyBytes, daveFundingKey.RawKeyBytes,
|
||||
int64(chanSize),
|
||||
)
|
||||
require.NoError(h, err)
|
||||
}
|
||||
|
||||
var txid *chainhash.Hash
|
||||
targetOutputs := []*wire.TxOut{fundingOutput}
|
||||
if publish {
|
||||
txid = h.SendOutputsWithoutChange(targetOutputs, 5)
|
||||
} else {
|
||||
tx := h.CreateTransaction(targetOutputs, 5)
|
||||
|
||||
txHash := tx.TxHash()
|
||||
txid = &txHash
|
||||
}
|
||||
|
||||
// At this point, we can being our external channel funding workflow.
|
||||
// We'll start by generating a pending channel ID externally that will
|
||||
// be used to track this new funding type.
|
||||
pendingChanID := h.Random32Bytes()
|
||||
|
||||
// Now that we have the pending channel ID, Dave (our responder) will
|
||||
// register the intent to receive a new channel funding workflow using
|
||||
// the pending channel ID.
|
||||
chanPoint := &lnrpc.ChannelPoint{
|
||||
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
|
||||
FundingTxidBytes: txid[:],
|
||||
},
|
||||
}
|
||||
chanPointShim := &lnrpc.ChanPointShim{
|
||||
Amt: int64(chanSize),
|
||||
ChanPoint: chanPoint,
|
||||
LocalKey: &lnrpc.KeyDescriptor{
|
||||
RawKeyBytes: daveFundingKey.RawKeyBytes,
|
||||
KeyLoc: &lnrpc.KeyLocator{
|
||||
KeyFamily: daveFundingKey.KeyLoc.KeyFamily,
|
||||
KeyIndex: daveFundingKey.KeyLoc.KeyIndex,
|
||||
},
|
||||
},
|
||||
RemoteKey: carolFundingKey.RawKeyBytes,
|
||||
PendingChanId: pendingChanID,
|
||||
ThawHeight: thawHeight,
|
||||
Musig2: musig2,
|
||||
}
|
||||
fundingShim := &lnrpc.FundingShim{
|
||||
Shim: &lnrpc.FundingShim_ChanPointShim{
|
||||
ChanPointShim: chanPointShim,
|
||||
},
|
||||
}
|
||||
bob.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{
|
||||
Trigger: &lnrpc.FundingTransitionMsg_ShimRegister{
|
||||
ShimRegister: fundingShim,
|
||||
},
|
||||
})
|
||||
|
||||
// If we attempt to register the same shim (has the same pending chan
|
||||
// ID), then we should get an error.
|
||||
bob.RPC.FundingStateStepAssertErr(&lnrpc.FundingTransitionMsg{
|
||||
Trigger: &lnrpc.FundingTransitionMsg_ShimRegister{
|
||||
ShimRegister: fundingShim,
|
||||
},
|
||||
})
|
||||
|
||||
// We'll take the chan point shim we just registered for Dave (the
|
||||
// responder), and swap the local/remote keys before we feed it in as
|
||||
// Carol's funding shim as the initiator.
|
||||
fundingShim.GetChanPointShim().LocalKey = &lnrpc.KeyDescriptor{
|
||||
RawKeyBytes: carolFundingKey.RawKeyBytes,
|
||||
KeyLoc: &lnrpc.KeyLocator{
|
||||
KeyFamily: carolFundingKey.KeyLoc.KeyFamily,
|
||||
KeyIndex: carolFundingKey.KeyLoc.KeyIndex,
|
||||
},
|
||||
}
|
||||
fundingShim.GetChanPointShim().RemoteKey = daveFundingKey.RawKeyBytes
|
||||
|
||||
return fundingShim, chanPoint
|
||||
}
|
||||
|
|
|
@ -41,6 +41,40 @@ var (
|
|||
btcdExecutable = flag.String(
|
||||
"btcdexec", "", "full path to btcd binary",
|
||||
)
|
||||
|
||||
// CfgLegacy specifies the config used to create a node that uses the
|
||||
// legacy channel format.
|
||||
CfgLegacy = []string{"--protocol.legacy.committweak"}
|
||||
|
||||
// CfgStaticRemoteKey specifies the config used to create a node that
|
||||
// uses the static remote key feature.
|
||||
CfgStaticRemoteKey = []string{}
|
||||
|
||||
// CfgAnchor specifies the config used to create a node that uses the
|
||||
// anchor output feature.
|
||||
CfgAnchor = []string{"--protocol.anchors"}
|
||||
|
||||
// CfgLeased specifies the config used to create a node that uses the
|
||||
// leased channel feature.
|
||||
CfgLeased = []string{
|
||||
"--protocol.anchors",
|
||||
"--protocol.script-enforced-lease",
|
||||
}
|
||||
|
||||
// CfgSimpleTaproot specifies the config used to create a node that
|
||||
// uses the simple taproot feature.
|
||||
CfgSimpleTaproot = []string{
|
||||
"--protocol.anchors",
|
||||
"--protocol.simple-taproot-chans",
|
||||
}
|
||||
|
||||
// CfgZeroConf specifies the config used to create a node that uses the
|
||||
// zero-conf channel feature.
|
||||
CfgZeroConf = []string{
|
||||
"--protocol.anchors",
|
||||
"--protocol.option-scid-alias",
|
||||
"--protocol.zero-conf",
|
||||
}
|
||||
)
|
||||
|
||||
type DatabaseBackend int
|
||||
|
|
Loading…
Add table
Reference in a new issue