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:
yyforyongyu 2024-10-18 05:26:52 +08:00
parent e45005b310
commit 9ab9cd5f99
No known key found for this signature in database
GPG key ID: 9BCD95C4FF296868
5 changed files with 554 additions and 246 deletions

View file

@ -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...)
}

View 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)
}

View file

@ -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

View file

@ -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
}

View file

@ -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