itest: break testMultiHopHtlcClaims into smaller test cases

This commit breaks the large `testMultiHopHtlcClaims` into independent
test cases to take the advantage of parallel testing. The old setup will
put the stress of creating and syncing new blocks into a single core,
which causes a much longer test time and sometimes times out.
This commit is contained in:
yyforyongyu committed 2022-10-27 01:11:34 +08:00
1 parent 3f14ee5870
commit 78de9dae05
4 files changed
+295 -304

No files matched your search

+3
View File
@@ -298,6 +298,9 @@ func (h *HarnessTest) Subtest(t *testing.T) (*HarnessTest, func()) {
// Reset the standby nodes. // Reset the standby nodes.
st.resetStandbyNodes(t) st.resetStandbyNodes(t)
// Reset fee estimator.
st.SetFeeEstimate(DefaultFeeRateSatPerKw)
// Record block height. // Record block height.
_, startHeight := h.Miner.GetBestBlock() _, startHeight := h.Miner.GetBestBlock()
+4
View File
@@ -181,6 +181,10 @@ func (cfg *BaseNodeConfig) GenArgs() []string {
fmt.Sprintf("--trickledelay=%v", trickleDelay), fmt.Sprintf("--trickledelay=%v", trickleDelay),
fmt.Sprintf("--profile=%d", cfg.ProfilePort), fmt.Sprintf("--profile=%d", cfg.ProfilePort),
fmt.Sprintf("--caches.rpc-graph-cache-duration=%d", 0), fmt.Sprintf("--caches.rpc-graph-cache-duration=%d", 0),
// Use a small batch window so we can broadcast our sweep
// transactions faster.
"--sweeper.batchwindowduration=5s",
} }
args = append(args, nodeArgs...) args = append(args, nodeArgs...)
+26 -2
View File
@@ -16,8 +16,32 @@ var allTestCasesTemp = []*lntemp.TestCase{
TestFunc: testBasicChannelFunding, TestFunc: testBasicChannelFunding,
}, },
{ {
Name: "test multi-hop htlc", Name: "multi hop htlc local timeout",
TestFunc: testMultiHopHtlcClaims, TestFunc: testMultiHopHtlcLocalTimeout,
},
{
Name: "multi hop receiver chain claim",
TestFunc: testMultiHopReceiverChainClaim,
},
{
Name: "multi hop local force close on-chain htlc timeout",
TestFunc: testMultiHopLocalForceCloseOnChainHtlcTimeout,
},
{
Name: "multi hop remote force close on-chain htlc timeout",
TestFunc: testMultiHopRemoteForceCloseOnChainHtlcTimeout,
},
{
Name: "multi hop htlc local chain claim",
TestFunc: testMultiHopHtlcLocalChainClaim,
},
{
Name: "multi hop htlc remote chain claim",
TestFunc: testMultiHopHtlcRemoteChainClaim,
},
{
Name: "multi hop htlc aggregation",
TestFunc: testMultiHopHtlcAggregation,
}, },
{ {
Name: "external channel funding", Name: "external channel funding",
+262 -302
View File
@@ -26,140 +26,30 @@ const (
thawHeightDelta = finalCltvDelta * 2 // 36. thawHeightDelta = finalCltvDelta * 2 // 36.
) )
func testMultiHopHtlcClaims(ht *lntemp.HarnessTest) { var commitWithZeroConf = []struct {
type testCase struct { commitType lnrpc.CommitmentType
name string zeroConf bool
test func(ht *lntemp.HarnessTest, alice, bob *node.HarnessNode, }{
c lnrpc.CommitmentType, zeroConf bool) {
commitType lnrpc.CommitmentType commitType: lnrpc.CommitmentType_LEGACY,
} zeroConf: false,
},
subTests := []testCase{ {
{ commitType: lnrpc.CommitmentType_ANCHORS,
// bob: outgoing our commit timeout zeroConf: false,
// carol: incoming their commit watch and see timeout },
name: "local force close immediate expiry", {
test: testMultiHopHtlcLocalTimeout, commitType: lnrpc.CommitmentType_ANCHORS,
}, zeroConf: true,
{ },
// bob: outgoing watch and see, they sweep on chain {
// carol: incoming our commit, know preimage commitType: lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE,
name: "receiver chain claim", zeroConf: false,
test: testMultiHopReceiverChainClaim, },
}, {
{ commitType: lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE,
// bob: outgoing our commit watch and see timeout zeroConf: true,
// carol: incoming their commit watch and see timeout },
name: "local force close on-chain htlc timeout",
test: testMultiHopLocalForceCloseOnChainHtlcTimeout,
},
{
// bob: outgoing their commit watch and see timeout
// carol: incoming our commit watch and see timeout
name: "remote force close on-chain htlc timeout",
test: testMultiHopRemoteForceCloseOnChainHtlcTimeout,
},
{
// bob: outgoing our commit watch and see, they sweep
// on chain
// bob: incoming our commit watch and learn preimage
// carol: incoming their commit know preimage
name: "local chain claim",
test: testMultiHopHtlcLocalChainClaim,
},
{
// bob: outgoing their commit watch and see, they sweep
// on chain
// bob: incoming their commit watch and learn preimage
// carol: incoming our commit know preimage
name: "remote chain claim",
test: testMultiHopHtlcRemoteChainClaim,
},
{
// bob: outgoing and incoming, sweep all on chain
name: "local htlc aggregation",
test: testMultiHopHtlcAggregation,
},
}
commitWithZeroConf := []struct {
commitType lnrpc.CommitmentType
zeroConf bool
}{
{
commitType: lnrpc.CommitmentType_LEGACY,
zeroConf: false,
},
{
commitType: lnrpc.CommitmentType_ANCHORS,
zeroConf: false,
},
{
commitType: lnrpc.CommitmentType_ANCHORS,
zeroConf: true,
},
{
commitType: lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE,
zeroConf: false,
},
{
commitType: lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE,
zeroConf: true,
},
}
runTestCase := func(st *lntemp.HarnessTest, name string, tc testCase,
zeroConf bool) {
// Create the nodes here so that separate logs will be created
// for Alice and Bob.
args := nodeArgsForCommitType(tc.commitType)
if zeroConf {
args = append(
args, "--protocol.option-scid-alias",
"--protocol.zero-conf",
)
}
alice := st.NewNode("Alice", args)
bob := st.NewNode("Bob", args)
st.ConnectNodes(alice, bob)
// Start each test with the default static fee estimate.
st.SetFeeEstimate(12500)
// Add test name to the logs.
alice.AddToLogf("Running test case: %s", name)
bob.AddToLogf("Running test case: %s", name)
tc.test(st, alice, bob, tc.commitType, zeroConf)
}
for _, subTest := range subTests {
subTest := subTest
for _, typeAndConf := range commitWithZeroConf {
typeAndConf := typeAndConf
subTest.commitType = typeAndConf.commitType
name := fmt.Sprintf("%s/zeroconf=%v/committype=%v",
subTest.name, typeAndConf.zeroConf,
typeAndConf.commitType.String())
s := ht.Run(name, func(t1 *testing.T) {
st, cleanup := ht.Subtest(t1)
defer cleanup()
runTestCase(
st, name, subTest, typeAndConf.zeroConf,
)
})
if !s {
return
}
}
}
} }
// waitForInvoiceAccepted waits until the specified invoice moved to the // waitForInvoiceAccepted waits until the specified invoice moved to the
@@ -258,116 +148,52 @@ func assertAllTxesSpendFrom(t *harnessTest, txes []*wire.MsgTx,
} }
} }
// createThreeHopNetwork creates a topology of `Alice -> Bob -> Carol`. // caseRunner defines a single test case runner.
func createThreeHopNetwork(ht *lntemp.HarnessTest, type caseRunner func(ht *lntemp.HarnessTest, alice, bob *node.HarnessNode,
alice, bob *node.HarnessNode, carolHodl bool, c lnrpc.CommitmentType, c lnrpc.CommitmentType, zeroConf bool)
zeroConf bool) (*lnrpc.ChannelPoint,
*lnrpc.ChannelPoint, *node.HarnessNode) {
ht.EnsureConnected(alice, bob) // runMultiHopHtlcClaimTest is a helper method to build test cases based on
// different commitment types and zero-conf config and run them.
func runMultiHopHtlcClaimTest(ht *lntemp.HarnessTest, tester caseRunner) {
for _, typeAndConf := range commitWithZeroConf {
typeAndConf := typeAndConf
name := fmt.Sprintf("zeroconf=%v/committype=%v",
typeAndConf.zeroConf, typeAndConf.commitType.String())
// We'll create a new node "carol" and have Bob connect to her. // Create the nodes here so that separate logs will be created
// If the carolHodl flag is set, we'll make carol always hold onto the // for Alice and Bob.
// HTLC, this way it'll force Bob to go to chain to resolve the HTLC. args := nodeArgsForCommitType(typeAndConf.commitType)
carolFlags := nodeArgsForCommitType(c) if typeAndConf.zeroConf {
if carolHodl { args = append(
carolFlags = append(carolFlags, "--hodl.exit-settle") args, "--protocol.option-scid-alias",
"--protocol.zero-conf",
)
}
s := ht.Run(name, func(t1 *testing.T) {
st, cleanup := ht.Subtest(t1)
defer cleanup()
alice := st.NewNode("Alice", args)
bob := st.NewNode("Bob", args)
st.ConnectNodes(alice, bob)
// Start each test with the default static fee estimate.
st.SetFeeEstimate(12500)
// Add test name to the logs.
alice.AddToLogf("Running test case: %s", name)
bob.AddToLogf("Running test case: %s", name)
tester(
st, alice, bob,
typeAndConf.commitType, typeAndConf.zeroConf,
)
})
if !s {
return
}
} }
if zeroConf {
carolFlags = append(
carolFlags, "--protocol.option-scid-alias",
"--protocol.zero-conf",
)
}
carol := ht.NewNode("Carol", carolFlags)
ht.ConnectNodes(bob, carol)
// Make sure there are enough utxos for anchoring. Because the anchor
// by itself often doesn't meet the dust limit, a utxo from the wallet
// needs to be attached as an additional input. This can still lead to
// a positively-yielding transaction.
for i := 0; i < 2; i++ {
ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, alice)
ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, bob)
ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, carol)
// Mine 1 block to get the above coins confirmed.
ht.MineBlocksAssertNodesSync(1)
}
// We'll start the test by creating a channel between Alice and Bob,
// which will act as the first leg for out multi-hop HTLC.
const chanAmt = 1000000
var aliceFundingShim *lnrpc.FundingShim
var thawHeight uint32
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
_, minerHeight := ht.Miner.GetBestBlock()
thawHeight = uint32(minerHeight + thawHeightDelta)
aliceFundingShim, _, _ = deriveFundingShim(
ht, alice, bob, chanAmt, thawHeight, true,
)
}
var (
cancel context.CancelFunc
acceptStream rpc.AcceptorClient
)
// If a zero-conf channel is being opened, the nodes are signalling the
// zero-conf feature bit. Setup a ChannelAcceptor for the fundee.
if zeroConf {
acceptStream, cancel = bob.RPC.ChannelAcceptor()
go acceptChannel(ht.T, true, acceptStream)
}
aliceParams := lntemp.OpenChannelParams{
Amt: chanAmt,
CommitmentType: c,
FundingShim: aliceFundingShim,
ZeroConf: zeroConf,
}
aliceChanPoint := ht.OpenChannel(alice, bob, aliceParams)
// Remove the ChannelAcceptor for Bob.
if zeroConf {
cancel()
}
// We'll then create a channel from Bob to Carol. After this channel is
// open, our topology looks like: A -> B -> C.
var bobFundingShim *lnrpc.FundingShim
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
bobFundingShim, _, _ = deriveFundingShim(
ht, bob, carol, chanAmt, thawHeight, true,
)
}
// Setup a ChannelAcceptor for Carol if a zero-conf channel open is
// being attempted.
if zeroConf {
acceptStream, cancel = carol.RPC.ChannelAcceptor()
go acceptChannel(ht.T, true, acceptStream)
}
bobParams := lntemp.OpenChannelParams{
Amt: chanAmt,
CommitmentType: c,
FundingShim: bobFundingShim,
ZeroConf: zeroConf,
}
bobChanPoint := ht.OpenChannel(bob, carol, bobParams)
// Remove the ChannelAcceptor for Carol.
if zeroConf {
cancel()
}
// Make sure alice and carol know each other's channels.
ht.AssertTopologyChannelOpen(alice, bobChanPoint)
ht.AssertTopologyChannelOpen(carol, aliceChanPoint)
return aliceChanPoint, bobChanPoint, carol
} }
// testMultiHopHtlcLocalTimeout tests that in a multi-hop HTLC scenario, if the // testMultiHopHtlcLocalTimeout tests that in a multi-hop HTLC scenario, if the
@@ -375,7 +201,11 @@ func createThreeHopNetwork(ht *lntemp.HarnessTest,
// it using the HTLC timeout transaction. Any dust HTLC's should be immediately // 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 // canceled backwards. Once the timeout has been reached, then we should sweep
// it on-chain, and cancel the HTLC backwards. // it on-chain, and cancel the HTLC backwards.
func testMultiHopHtlcLocalTimeout(ht *lntemp.HarnessTest, func testMultiHopHtlcLocalTimeout(ht *lntemp.HarnessTest) {
runMultiHopHtlcClaimTest(ht, runMultiHopHtlcLocalTimeout)
}
func runMultiHopHtlcLocalTimeout(ht *lntemp.HarnessTest,
alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) { alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) {
// First, we'll create a three hop network: Alice -> Bob -> Carol, with // First, we'll create a three hop network: Alice -> Bob -> Carol, with
@@ -540,7 +370,11 @@ func testMultiHopHtlcLocalTimeout(ht *lntemp.HarnessTest,
// transaction. In this scenario, the node that sent the outgoing HTLC should // transaction. In this scenario, the node that sent the outgoing HTLC should
// extract the preimage from the sweep transaction, and finish settling the // extract the preimage from the sweep transaction, and finish settling the
// HTLC backwards into the route. // HTLC backwards into the route.
func testMultiHopReceiverChainClaim(ht *lntemp.HarnessTest, func testMultiHopReceiverChainClaim(ht *lntemp.HarnessTest) {
runMultiHopHtlcClaimTest(ht, runMultiHopReceiverChainClaim)
}
func runMultiHopReceiverChainClaim(ht *lntemp.HarnessTest,
alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) { alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) {
// First, we'll create a three hop network: Alice -> Bob -> Carol, with // First, we'll create a three hop network: Alice -> Bob -> Carol, with
@@ -603,7 +437,6 @@ func testMultiHopReceiverChainClaim(ht *lntemp.HarnessTest,
// Now we'll mine enough blocks to prompt carol to actually go to the // Now we'll mine enough blocks to prompt carol to actually go to the
// chain in order to sweep her HTLC since the value is high enough. // chain in order to sweep her HTLC since the value is high enough.
// TODO(roasbeef): modify once go to chain policy changes
numBlocks := padCLTV(uint32( numBlocks := padCLTV(uint32(
invoiceReq.CltvExpiry - lncfg.DefaultIncomingBroadcastDelta, invoiceReq.CltvExpiry - lncfg.DefaultIncomingBroadcastDelta,
)) ))
@@ -666,8 +499,6 @@ func testMultiHopReceiverChainClaim(ht *lntemp.HarnessTest,
// second layer transactions. // second layer transactions.
ht.MineBlocksAssertNodesSync(1) ht.MineBlocksAssertNodesSync(1)
// TODO(roasbeef): assert bob pending state as well
// Carol's pending channel report should now show two outputs under // Carol's pending channel report should now show two outputs under
// limbo: her commitment output, as well as the second-layer claim // limbo: her commitment output, as well as the second-layer claim
// output, and the pending HTLC should also now be in stage 2. // output, and the pending HTLC should also now be in stage 2.
@@ -737,7 +568,13 @@ func testMultiHopReceiverChainClaim(ht *lntemp.HarnessTest,
// commitment on-chain early, then it eventually recognizes this HTLC as one // commitment on-chain early, then it eventually recognizes this HTLC as one
// that's timed out. At this point, the node should timeout the HTLC using the // that's timed out. At this point, the node should timeout the HTLC using the
// HTLC timeout transaction, then cancel it backwards as normal. // HTLC timeout transaction, then cancel it backwards as normal.
func testMultiHopLocalForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest, func testMultiHopLocalForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest) {
runMultiHopHtlcClaimTest(
ht, runMultiHopLocalForceCloseOnChainHtlcTimeout,
)
}
func runMultiHopLocalForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest,
alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) { alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) {
// First, we'll create a three hop network: Alice -> Bob -> Carol, with // First, we'll create a three hop network: Alice -> Bob -> Carol, with
@@ -784,6 +621,10 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest,
bob, bobChanPoint, hasAnchors, stream, bob, bobChanPoint, hasAnchors, stream,
) )
// Record how many blocks have mined. At this step
// AssertStreamChannelForceClosed mines one block.
blocksMined := uint32(1)
// If the channel closed has anchors, we should expect to see a sweep // If the channel closed has anchors, we should expect to see a sweep
// transaction for Carol's anchor. // transaction for Carol's anchor.
htlcOutpoint := wire.OutPoint{Hash: *closeTx, Index: 0} htlcOutpoint := wire.OutPoint{Hash: *closeTx, Index: 0}
@@ -801,9 +642,9 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest,
if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
// The sweep is broadcast on the block immediately before the // The sweep is broadcast on the block immediately before the
// CSV expires and the commitment was already mined inside // CSV expires and the commitment was already mined inside
// closeChannelAndAssertType(), so mine one block less than // AssertStreamChannelForceClosed(), so mine one block less
// defaultCSV in order to perform mempool assertions. // than defaultCSV in order to perform mempool assertions.
ht.MineBlocksAssertNodesSync(defaultCSV - 1) ht.MineBlocksAssertNodesSync(defaultCSV - blocksMined)
commitSweepTx := ht.Miner.AssertOutpointInMempool( commitSweepTx := ht.Miner.AssertOutpointInMempool(
bobCommitOutpoint, bobCommitOutpoint,
@@ -811,19 +652,14 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest,
txid := commitSweepTx.TxHash() txid := commitSweepTx.TxHash()
block := ht.Miner.MineBlocksAndAssertNumTxes(1, 1)[0] block := ht.Miner.MineBlocksAndAssertNumTxes(1, 1)[0]
ht.Miner.AssertTxInBlock(block, &txid) ht.Miner.AssertTxInBlock(block, &txid)
blocksMined = defaultCSV + 1
} }
// We'll now mine enough blocks for the HTLC to expire. After this, Bob // We'll now mine enough blocks for the HTLC to expire. After this, Bob
// should hand off the now expired HTLC output to the utxo nursery. We // should hand off the now expired HTLC output to the utxo nursery.
// mine finalCltvDelta-1 blocks here as we've already mined 1 block numBlocks := padCLTV(finalCltvDelta)
// while confirming the closing tx. ht.MineBlocksAssertNodesSync(numBlocks - blocksMined)
numBlocks := padCLTV(finalCltvDelta - 1)
if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
// Subtract the number of blocks already mined to confirm Bob's
// commit sweep.
numBlocks -= defaultCSV
}
ht.MineBlocksAssertNodesSync(numBlocks)
// Bob's pending channel report should show that he has a single HTLC // Bob's pending channel report should show that he has a single HTLC
// that's now in stage one. // that's now in stage one.
@@ -888,7 +724,13 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest,
// channel, then we properly timeout the HTLC directly on *their* commitment // channel, then we properly timeout the HTLC directly on *their* commitment
// transaction once the timeout has expired. Once we sweep the transaction, we // transaction once the timeout has expired. Once we sweep the transaction, we
// should also cancel back the initial HTLC. // should also cancel back the initial HTLC.
func testMultiHopRemoteForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest, func testMultiHopRemoteForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest) {
runMultiHopHtlcClaimTest(
ht, runMultiHopRemoteForceCloseOnChainHtlcTimeout,
)
}
func runMultiHopRemoteForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest,
alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) { alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) {
// First, we'll create a three hop network: Alice -> Bob -> Carol, with // First, we'll create a three hop network: Alice -> Bob -> Carol, with
@@ -989,18 +831,6 @@ func testMultiHopRemoteForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest,
// Bob's sweeping transaction should now be found in the mempool at // Bob's sweeping transaction should now be found in the mempool at
// this point. // this point.
sweepTx := ht.Miner.AssertNumTxsInMempool(1)[0] sweepTx := ht.Miner.AssertNumTxsInMempool(1)[0]
// The following issue is believed to have been resolved. Keep the
// original comments here for future reference in case anything goes
// wrong.
//
// If Bob's transaction isn't yet in the mempool, then due to
// internal message passing and the low period between blocks
// being mined, it may have been detected as a late
// registration. As a result, we'll mine another block and
// repeat the check. If it doesn't go through this time, then
// we'll fail.
// TODO(halseth): can we use waitForChannelPendingForceClose to
// avoid this hack?
// If we mine an additional block, then this should confirm Bob's // If we mine an additional block, then this should confirm Bob's
// transaction which sweeps the direct HTLC output. // transaction which sweeps the direct HTLC output.
@@ -1050,7 +880,11 @@ func testMultiHopRemoteForceCloseOnChainHtlcTimeout(ht *lntemp.HarnessTest,
// we force close a channel with an incoming HTLC, and later find out the // we force close a channel with an incoming HTLC, and later find out the
// preimage via the witness beacon, we properly settle the HTLC on-chain using // preimage via the witness beacon, we properly settle the HTLC on-chain using
// the HTLC success transaction in order to ensure we don't lose any funds. // the HTLC success transaction in order to ensure we don't lose any funds.
func testMultiHopHtlcLocalChainClaim(ht *lntemp.HarnessTest, func testMultiHopHtlcLocalChainClaim(ht *lntemp.HarnessTest) {
runMultiHopHtlcClaimTest(ht, runMultiHopHtlcLocalChainClaim)
}
func runMultiHopHtlcLocalChainClaim(ht *lntemp.HarnessTest,
alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) { alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) {
// First, we'll create a three hop network: Alice -> Bob -> Carol, with // First, we'll create a three hop network: Alice -> Bob -> Carol, with
@@ -1147,7 +981,7 @@ func testMultiHopHtlcLocalChainClaim(ht *lntemp.HarnessTest,
// We'll now mine enough blocks so Carol decides that she needs to go // We'll now mine enough blocks so Carol decides that she needs to go
// on-chain to claim the HTLC as Bob has been inactive. // on-chain to claim the HTLC as Bob has been inactive.
numBlocks := padCLTV(uint32(invoiceReq.CltvExpiry- numBlocks := padCLTV(uint32(invoiceReq.CltvExpiry-
lncfg.DefaultIncomingBroadcastDelta) - 1) lncfg.DefaultIncomingBroadcastDelta)) - 1
ht.MineBlocksAssertNodesSync(numBlocks) ht.MineBlocksAssertNodesSync(numBlocks)
// Carol's commitment transaction should now be in the mempool. If // Carol's commitment transaction should now be in the mempool. If
@@ -1340,7 +1174,11 @@ func testMultiHopHtlcLocalChainClaim(ht *lntemp.HarnessTest,
// we found out the preimage via the witness beacon, we properly settle the // we found out the preimage via the witness beacon, we properly settle the
// HTLC directly on-chain using the preimage in order to ensure that we don't // HTLC directly on-chain using the preimage in order to ensure that we don't
// lose any funds. // lose any funds.
func testMultiHopHtlcRemoteChainClaim(ht *lntemp.HarnessTest, func testMultiHopHtlcRemoteChainClaim(ht *lntemp.HarnessTest) {
runMultiHopHtlcClaimTest(ht, runMultiHopHtlcRemoteChainClaim)
}
func runMultiHopHtlcRemoteChainClaim(ht *lntemp.HarnessTest,
alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) { alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) {
// First, we'll create a three hop network: Alice -> Bob -> Carol, with // First, we'll create a three hop network: Alice -> Bob -> Carol, with
@@ -1403,24 +1241,29 @@ func testMultiHopHtlcRemoteChainClaim(ht *lntemp.HarnessTest,
alice, aliceChanPoint, hasAnchors, closeStream, alice, aliceChanPoint, hasAnchors, closeStream,
) )
// Record how many blocks have mined. At this step
// AssertStreamChannelForceClosed mines one block.
blocksMined := uint32(1)
// Wait for the channel to be marked pending force close. // Wait for the channel to be marked pending force close.
ht.AssertChannelPendingForceClose(alice, aliceChanPoint) ht.AssertChannelPendingForceClose(alice, aliceChanPoint)
// After closeChannelAndAssertType returns, it has mined a block so now // After AssertStreamChannelForceClosed returns, it has mined a block
// bob will attempt to redeem his anchor commitment (if the channel // so now bob will attempt to redeem his anchor commitment (if the
// type is of that type). // channel type is of that type).
if hasAnchors { if hasAnchors {
ht.Miner.AssertNumTxsInMempool(1) ht.Miner.AssertNumTxsInMempool(1)
} }
if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
// Mine enough blocks for Alice to sweep her funds from the // Mine enough blocks for Alice to sweep her funds from the
// force closed channel. closeChannelAndAssertType() already // force closed channel. AssertStreamChannelForceClosed()
// mined a block containing the commitment tx and the commit // already mined a block containing the commitment tx and the
// sweep tx will be broadcast immediately before it can be // commit sweep tx will be broadcast immediately before it can
// included in a block, so mine one less than defaultCSV in // be included in a block, so mine one less than defaultCSV in
// order to perform mempool assertions. // order to perform mempool assertions.
ht.MineBlocksAssertNodesSync(defaultCSV - 1) ht.MineBlocksAssertNodesSync(defaultCSV - blocksMined)
blocksMined = defaultCSV
// Alice should now sweep her funds. // Alice should now sweep her funds.
ht.Miner.AssertNumTxsInMempool(1) ht.Miner.AssertNumTxsInMempool(1)
@@ -1439,12 +1282,9 @@ func testMultiHopHtlcRemoteChainClaim(ht *lntemp.HarnessTest,
// We'll now mine enough blocks so Carol decides that she needs to go // We'll now mine enough blocks so Carol decides that she needs to go
// on-chain to claim the HTLC as Bob has been inactive. // on-chain to claim the HTLC as Bob has been inactive.
numBlocks := padCLTV(uint32( numBlocks := padCLTV(uint32(
invoiceReq.CltvExpiry - lncfg.DefaultIncomingBroadcastDelta - 1, invoiceReq.CltvExpiry - lncfg.DefaultIncomingBroadcastDelta,
)) ))
if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { ht.MineBlocksAssertNodesSync(numBlocks - blocksMined)
numBlocks -= defaultCSV
}
ht.MineBlocksAssertNodesSync(numBlocks)
expectedTxes := 1 expectedTxes := 1
if hasAnchors { if hasAnchors {
@@ -1604,7 +1444,11 @@ func testMultiHopHtlcRemoteChainClaim(ht *lntemp.HarnessTest,
// resolve them using the second level timeout and success transactions. In // resolve them using the second level timeout and success transactions. In
// case of anchor channels, the second-level spends can also be aggregated and // case of anchor channels, the second-level spends can also be aggregated and
// properly feebumped, so we'll check that as well. // properly feebumped, so we'll check that as well.
func testMultiHopHtlcAggregation(ht *lntemp.HarnessTest, func testMultiHopHtlcAggregation(ht *lntemp.HarnessTest) {
runMultiHopHtlcClaimTest(ht, runMultiHopHtlcAggregation)
}
func runMultiHopHtlcAggregation(ht *lntemp.HarnessTest,
alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) { alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) {
// First, we'll create a three hop network: Alice -> Bob -> Carol. // First, we'll create a three hop network: Alice -> Bob -> Carol.
@@ -1623,14 +1467,7 @@ func testMultiHopHtlcAggregation(ht *lntemp.HarnessTest,
const reBalanceAmt = 500_000 const reBalanceAmt = 500_000
invoice := &lnrpc.Invoice{Value: reBalanceAmt} invoice := &lnrpc.Invoice{Value: reBalanceAmt}
resp := carol.RPC.AddInvoice(invoice) resp := carol.RPC.AddInvoice(invoice)
ht.CompletePaymentRequests(alice, []string{resp.PaymentRequest})
sendReq := &routerrpc.SendPaymentRequest{
PaymentRequest: resp.PaymentRequest,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
}
stream := alice.RPC.SendPayment(sendReq)
ht.AssertPaymentStatusFromStream(stream, lnrpc.Payment_SUCCEEDED)
// With the network active, we'll now add a new hodl invoices at both // With the network active, we'll now add a new hodl invoices at both
// Alice's and Carol's end. Make sure the cltv expiry delta is large // Alice's and Carol's end. Make sure the cltv expiry delta is large
@@ -1675,7 +1512,7 @@ func testMultiHopHtlcAggregation(ht *lntemp.HarnessTest,
payHash := preimage.Hash() payHash := preimage.Hash()
invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{ invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{
Value: invoiceAmt, Value: invoiceAmt,
CltvExpiry: 2 * finalCltvDelta, CltvExpiry: thawHeightDelta - 4,
Hash: payHash[:], Hash: payHash[:],
} }
aliceInvoice := alice.RPC.AddHoldInvoice(invoiceReq) aliceInvoice := alice.RPC.AddHoldInvoice(invoiceReq)
@@ -1762,6 +1599,12 @@ func testMultiHopHtlcAggregation(ht *lntemp.HarnessTest,
) )
closeTxid := closeTx.TxHash() closeTxid := closeTx.TxHash()
// Restart Bob to increase the batch window duration so the sweeper
// will aggregate all the pending inputs.
ht.RestartNodeWithExtraArgs(
bob, []string{"--sweeper.batchwindowduration=15s"},
)
// Go through the closing transaction outputs, and make an index for // Go through the closing transaction outputs, and make an index for
// the HTLC outputs. // the HTLC outputs.
successOuts := make(map[wire.OutPoint]struct{}) successOuts := make(map[wire.OutPoint]struct{})
@@ -1866,8 +1709,7 @@ func testMultiHopHtlcAggregation(ht *lntemp.HarnessTest,
// Mine a block to confirm the all the transactions, including Carol's // Mine a block to confirm the all the transactions, including Carol's
// commitment tx, anchor tx(optional), and the second-level timeout and // commitment tx, anchor tx(optional), and the second-level timeout and
// success txes. // success txes.
block := ht.Miner.MineBlocksAndAssertNumTxes(1, expectedTxes)[0] ht.Miner.MineBlocksAndAssertNumTxes(1, expectedTxes)
require.Len(ht, block.Transactions, expectedTxes+1)
// At this point, Bob should have broadcast his second layer success // At this point, Bob should have broadcast his second layer success
// transaction, and should have sent it to the nursery for incubation, // transaction, and should have sent it to the nursery for incubation,
@@ -1903,6 +1745,12 @@ func testMultiHopHtlcAggregation(ht *lntemp.HarnessTest,
} }
} }
// We now restart Bob with a much larger batch window duration since it
// takes some time to aggregate all the 10 inputs below.
ht.RestartNodeWithExtraArgs(
bob, []string{"--sweeper.batchwindowduration=45s"},
)
switch c { switch c {
// In case this is a non-anchor channel type, we must mine 2 blocks, as // In case this is a non-anchor channel type, we must mine 2 blocks, as
// the nursery waits an extra block before sweeping. Before the blocks // the nursery waits an extra block before sweeping. Before the blocks
@@ -1962,7 +1810,7 @@ func testMultiHopHtlcAggregation(ht *lntemp.HarnessTest,
// When we mine one additional block, that will confirm Bob's second // When we mine one additional block, that will confirm Bob's second
// level sweep. Now Bob should have no pending channels anymore, as // level sweep. Now Bob should have no pending channels anymore, as
// this just resolved it by the confirmation of the sweep transaction. // this just resolved it by the confirmation of the sweep transaction.
block = ht.Miner.MineBlocksAndAssertNumTxes(1, 1)[0] block := ht.Miner.MineBlocksAndAssertNumTxes(1, 1)[0]
ht.Miner.AssertTxInBlock(block, &bobSweep) ht.Miner.AssertTxInBlock(block, &bobSweep)
ht.AssertNumPendingForceClose(bob, 0) ht.AssertNumPendingForceClose(bob, 0)
@@ -1976,3 +1824,115 @@ func testMultiHopHtlcAggregation(ht *lntemp.HarnessTest,
// Coop close, no anchors. // Coop close, no anchors.
ht.CloseChannel(alice, aliceChanPoint) ht.CloseChannel(alice, aliceChanPoint)
} }
// createThreeHopNetwork creates a topology of `Alice -> Bob -> Carol`.
func createThreeHopNetwork(ht *lntemp.HarnessTest,
alice, bob *node.HarnessNode, carolHodl bool, c lnrpc.CommitmentType,
zeroConf bool) (*lnrpc.ChannelPoint,
*lnrpc.ChannelPoint, *node.HarnessNode) {
ht.EnsureConnected(alice, bob)
// We'll create a new node "carol" and have Bob connect to her.
// If the carolHodl flag is set, we'll make carol always hold onto the
// HTLC, this way it'll force Bob to go to chain to resolve the HTLC.
carolFlags := nodeArgsForCommitType(c)
if carolHodl {
carolFlags = append(carolFlags, "--hodl.exit-settle")
}
if zeroConf {
carolFlags = append(
carolFlags, "--protocol.option-scid-alias",
"--protocol.zero-conf",
)
}
carol := ht.NewNode("Carol", carolFlags)
ht.ConnectNodes(bob, carol)
// Make sure there are enough utxos for anchoring. Because the anchor
// by itself often doesn't meet the dust limit, a utxo from the wallet
// needs to be attached as an additional input. This can still lead to
// a positively-yielding transaction.
for i := 0; i < 2; i++ {
ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, alice)
ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, bob)
ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, carol)
// Mine 1 block to get the above coins confirmed.
ht.MineBlocksAssertNodesSync(1)
}
// We'll start the test by creating a channel between Alice and Bob,
// which will act as the first leg for out multi-hop HTLC.
const chanAmt = 1000000
var aliceFundingShim *lnrpc.FundingShim
var thawHeight uint32
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
_, minerHeight := ht.Miner.GetBestBlock()
thawHeight = uint32(minerHeight + thawHeightDelta)
aliceFundingShim, _, _ = deriveFundingShim(
ht, alice, bob, chanAmt, thawHeight, true,
)
}
var (
cancel context.CancelFunc
acceptStream rpc.AcceptorClient
)
// If a zero-conf channel is being opened, the nodes are signalling the
// zero-conf feature bit. Setup a ChannelAcceptor for the fundee.
if zeroConf {
acceptStream, cancel = bob.RPC.ChannelAcceptor()
go acceptChannel(ht.T, true, acceptStream)
}
aliceParams := lntemp.OpenChannelParams{
Amt: chanAmt,
CommitmentType: c,
FundingShim: aliceFundingShim,
ZeroConf: zeroConf,
}
aliceChanPoint := ht.OpenChannel(alice, bob, aliceParams)
// Remove the ChannelAcceptor for Bob.
if zeroConf {
cancel()
}
// We'll then create a channel from Bob to Carol. After this channel is
// open, our topology looks like: A -> B -> C.
var bobFundingShim *lnrpc.FundingShim
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
bobFundingShim, _, _ = deriveFundingShim(
ht, bob, carol, chanAmt, thawHeight, true,
)
}
// Setup a ChannelAcceptor for Carol if a zero-conf channel open is
// being attempted.
if zeroConf {
acceptStream, cancel = carol.RPC.ChannelAcceptor()
go acceptChannel(ht.T, true, acceptStream)
}
bobParams := lntemp.OpenChannelParams{
Amt: chanAmt,
CommitmentType: c,
FundingShim: bobFundingShim,
ZeroConf: zeroConf,
}
bobChanPoint := ht.OpenChannel(bob, carol, bobParams)
// Remove the ChannelAcceptor for Carol.
if zeroConf {
cancel()
}
// Make sure alice and carol know each other's channels.
ht.AssertTopologyChannelOpen(alice, bobChanPoint)
ht.AssertTopologyChannelOpen(carol, aliceChanPoint)
return aliceChanPoint, bobChanPoint, carol
}