2020-03-23 11:19:18 +01:00
|
|
|
package itest
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"testing"
|
|
|
|
|
2022-02-23 14:48:00 +01:00
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
2020-03-04 13:21:29 +01:00
|
|
|
"github.com/btcsuite/btcd/wire"
|
2023-08-10 21:53:47 -07:00
|
|
|
"github.com/lightningnetwork/lnd/chainreg"
|
2022-10-25 14:29:46 +08:00
|
|
|
"github.com/lightningnetwork/lnd/lncfg"
|
2020-03-23 11:19:18 +01:00
|
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
|
|
|
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
2022-10-25 14:29:46 +08:00
|
|
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
2022-08-12 17:03:44 +08:00
|
|
|
"github.com/lightningnetwork/lnd/lntest"
|
|
|
|
"github.com/lightningnetwork/lnd/lntest/node"
|
|
|
|
"github.com/lightningnetwork/lnd/lntest/rpc"
|
2022-08-02 02:16:17 +08:00
|
|
|
"github.com/lightningnetwork/lnd/routing"
|
2022-10-25 14:29:46 +08:00
|
|
|
"github.com/stretchr/testify/require"
|
2022-08-02 02:16:17 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
finalCltvDelta = routing.MinCLTVDelta // 18.
|
|
|
|
thawHeightDelta = finalCltvDelta * 2 // 36.
|
2020-03-23 11:19:18 +01:00
|
|
|
)
|
|
|
|
|
2022-08-15 19:03:14 +08:00
|
|
|
var commitWithZeroConf = []struct {
|
|
|
|
commitType lnrpc.CommitmentType
|
|
|
|
zeroConf bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
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,
|
|
|
|
},
|
2023-08-10 21:53:47 -07:00
|
|
|
{
|
|
|
|
commitType: lnrpc.CommitmentType_SIMPLE_TAPROOT,
|
|
|
|
zeroConf: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
commitType: lnrpc.CommitmentType_SIMPLE_TAPROOT,
|
|
|
|
zeroConf: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// makeRouteHints creates a route hints that will allow Carol to be reached
|
|
|
|
// using an unadvertised channel created by Bob (Bob -> Carol). If the zeroConf
|
|
|
|
// bool is set, then the scid alias of Bob will be used in place.
|
|
|
|
func makeRouteHints(bob, carol *node.HarnessNode,
|
|
|
|
zeroConf bool) []*lnrpc.RouteHint {
|
|
|
|
|
|
|
|
carolChans := carol.RPC.ListChannels(
|
|
|
|
&lnrpc.ListChannelsRequest{},
|
|
|
|
)
|
|
|
|
|
|
|
|
carolChan := carolChans.Channels[0]
|
|
|
|
|
|
|
|
hopHint := &lnrpc.HopHint{
|
|
|
|
NodeId: carolChan.RemotePubkey,
|
|
|
|
ChanId: carolChan.ChanId,
|
|
|
|
FeeBaseMsat: uint32(
|
|
|
|
chainreg.DefaultBitcoinBaseFeeMSat,
|
|
|
|
),
|
|
|
|
FeeProportionalMillionths: uint32(
|
|
|
|
chainreg.DefaultBitcoinFeeRate,
|
|
|
|
),
|
|
|
|
CltvExpiryDelta: chainreg.DefaultBitcoinTimeLockDelta,
|
|
|
|
}
|
|
|
|
|
|
|
|
if zeroConf {
|
|
|
|
bobChans := bob.RPC.ListChannels(
|
|
|
|
&lnrpc.ListChannelsRequest{},
|
|
|
|
)
|
|
|
|
|
|
|
|
// Now that we have Bob's channels, scan for the channel he has
|
|
|
|
// open to Carol so we can use the proper scid.
|
|
|
|
var found bool
|
|
|
|
for _, bobChan := range bobChans.Channels {
|
|
|
|
if bobChan.RemotePubkey == carol.PubKeyStr {
|
|
|
|
hopHint.ChanId = bobChan.AliasScids[0]
|
|
|
|
|
|
|
|
found = true
|
|
|
|
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found {
|
|
|
|
bob.Fatalf("unable to create route hint")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return []*lnrpc.RouteHint{
|
|
|
|
{
|
|
|
|
HopHints: []*lnrpc.HopHint{hopHint},
|
|
|
|
},
|
|
|
|
}
|
2020-03-23 11:19:18 +01:00
|
|
|
}
|
|
|
|
|
2022-08-15 19:03:14 +08:00
|
|
|
// caseRunner defines a single test case runner.
|
2022-08-12 17:03:44 +08:00
|
|
|
type caseRunner func(ht *lntest.HarnessTest, alice, bob *node.HarnessNode,
|
2022-08-15 19:03:14 +08:00
|
|
|
c lnrpc.CommitmentType, zeroConf bool)
|
2022-07-28 20:15:22 +08:00
|
|
|
|
2022-08-15 19:03:14 +08:00
|
|
|
// runMultiHopHtlcClaimTest is a helper method to build test cases based on
|
|
|
|
// different commitment types and zero-conf config and run them.
|
2024-04-06 13:11:00 +08:00
|
|
|
//
|
|
|
|
// TODO(yy): flatten this test.
|
2022-08-12 17:03:44 +08:00
|
|
|
func runMultiHopHtlcClaimTest(ht *lntest.HarnessTest, tester caseRunner) {
|
2022-08-15 19:03:14 +08:00
|
|
|
for _, typeAndConf := range commitWithZeroConf {
|
|
|
|
typeAndConf := typeAndConf
|
|
|
|
name := fmt.Sprintf("zeroconf=%v/committype=%v",
|
|
|
|
typeAndConf.zeroConf, typeAndConf.commitType.String())
|
2022-07-28 20:15:22 +08:00
|
|
|
|
2022-08-15 19:03:14 +08:00
|
|
|
// Create the nodes here so that separate logs will be created
|
|
|
|
// for Alice and Bob.
|
2022-08-12 17:03:44 +08:00
|
|
|
args := lntest.NodeArgsForCommitType(typeAndConf.commitType)
|
2022-08-15 19:03:14 +08:00
|
|
|
if typeAndConf.zeroConf {
|
|
|
|
args = append(
|
|
|
|
args, "--protocol.option-scid-alias",
|
|
|
|
"--protocol.zero-conf",
|
|
|
|
)
|
|
|
|
}
|
2022-07-28 20:15:22 +08:00
|
|
|
|
2022-08-15 19:03:14 +08:00
|
|
|
s := ht.Run(name, func(t1 *testing.T) {
|
2022-08-30 04:35:13 +08:00
|
|
|
st := ht.Subtest(t1)
|
2022-07-28 20:15:22 +08:00
|
|
|
|
2022-08-15 19:03:14 +08:00
|
|
|
alice := st.NewNode("Alice", args)
|
|
|
|
bob := st.NewNode("Bob", args)
|
|
|
|
st.ConnectNodes(alice, bob)
|
2022-07-28 20:15:22 +08:00
|
|
|
|
2022-08-15 19:03:14 +08:00
|
|
|
// Start each test with the default static fee estimate.
|
|
|
|
st.SetFeeEstimate(12500)
|
2022-07-28 20:15:22 +08:00
|
|
|
|
2022-08-15 19:03:14 +08:00
|
|
|
// Add test name to the logs.
|
|
|
|
alice.AddToLogf("Running test case: %s", name)
|
|
|
|
bob.AddToLogf("Running test case: %s", name)
|
2022-07-28 20:15:22 +08:00
|
|
|
|
2022-08-15 19:03:14 +08:00
|
|
|
tester(
|
|
|
|
st, alice, bob,
|
|
|
|
typeAndConf.commitType, typeAndConf.zeroConf,
|
|
|
|
)
|
|
|
|
})
|
|
|
|
if !s {
|
|
|
|
return
|
|
|
|
}
|
2022-07-28 20:15:22 +08:00
|
|
|
}
|
|
|
|
}
|
2022-10-25 14:29:46 +08:00
|
|
|
|
2022-08-15 19:03:14 +08:00
|
|
|
// createThreeHopNetwork creates a topology of `Alice -> Bob -> Carol`.
|
2022-08-12 17:03:44 +08:00
|
|
|
func createThreeHopNetwork(ht *lntest.HarnessTest,
|
2022-08-15 19:03:14 +08:00
|
|
|
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.
|
2022-08-12 17:03:44 +08:00
|
|
|
carolFlags := lntest.NodeArgsForCommitType(c)
|
2022-08-15 19:03:14 +08:00
|
|
|
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.
|
2024-05-20 23:06:20 +08:00
|
|
|
ht.MineBlocksAndAssertNumTxes(1, 3)
|
2022-08-15 19:03:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
2024-05-02 14:05:09 +08:00
|
|
|
minerHeight := ht.CurrentHeight()
|
|
|
|
thawHeight = minerHeight + thawHeightDelta
|
2022-08-12 15:49:54 +08:00
|
|
|
aliceFundingShim, _ = deriveFundingShim(
|
2023-09-25 21:44:58 +02:00
|
|
|
ht, alice, bob, chanAmt, thawHeight, true, c,
|
2022-08-15 19:03:14 +08:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-08-10 21:53:47 -07:00
|
|
|
var privateChan bool
|
|
|
|
if c == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
|
|
|
privateChan = true
|
|
|
|
}
|
|
|
|
|
2022-08-12 17:03:44 +08:00
|
|
|
aliceParams := lntest.OpenChannelParams{
|
2023-08-10 21:53:47 -07:00
|
|
|
Private: privateChan,
|
2022-08-15 19:03:14 +08:00
|
|
|
Amt: chanAmt,
|
|
|
|
CommitmentType: c,
|
|
|
|
FundingShim: aliceFundingShim,
|
|
|
|
ZeroConf: zeroConf,
|
|
|
|
}
|
|
|
|
|
2023-08-18 17:08:49 -07:00
|
|
|
// If the channel type is taproot, then use an explicit channel type to
|
|
|
|
// open it.
|
|
|
|
if c == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
|
|
|
aliceParams.CommitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
|
|
|
|
}
|
|
|
|
|
2023-04-20 06:08:15 +08:00
|
|
|
// We'll create a channel from Bob to Carol. After this channel is
|
2022-08-15 19:03:14 +08:00
|
|
|
// open, our topology looks like: A -> B -> C.
|
|
|
|
var bobFundingShim *lnrpc.FundingShim
|
|
|
|
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
2022-08-12 15:49:54 +08:00
|
|
|
bobFundingShim, _ = deriveFundingShim(
|
2023-09-25 21:44:58 +02:00
|
|
|
ht, bob, carol, chanAmt, thawHeight, true, c,
|
2022-08-15 19:03:14 +08:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-04-20 06:08:15 +08:00
|
|
|
// Prepare params for Bob.
|
2022-08-12 17:03:44 +08:00
|
|
|
bobParams := lntest.OpenChannelParams{
|
2022-08-15 19:03:14 +08:00
|
|
|
Amt: chanAmt,
|
2023-08-10 21:53:47 -07:00
|
|
|
Private: privateChan,
|
2022-08-15 19:03:14 +08:00
|
|
|
CommitmentType: c,
|
|
|
|
FundingShim: bobFundingShim,
|
|
|
|
ZeroConf: zeroConf,
|
|
|
|
}
|
|
|
|
|
2023-08-18 17:08:49 -07:00
|
|
|
// If the channel type is taproot, then use an explicit channel type to
|
|
|
|
// open it.
|
|
|
|
if c == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
|
|
|
bobParams.CommitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
|
|
|
|
}
|
|
|
|
|
2023-04-20 06:08:15 +08:00
|
|
|
var (
|
|
|
|
acceptStreamBob rpc.AcceptorClient
|
|
|
|
acceptStreamCarol rpc.AcceptorClient
|
|
|
|
cancelBob context.CancelFunc
|
|
|
|
cancelCarol context.CancelFunc
|
|
|
|
)
|
|
|
|
|
|
|
|
// 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 {
|
|
|
|
acceptStreamBob, cancelBob = bob.RPC.ChannelAcceptor()
|
|
|
|
go acceptChannel(ht.T, true, acceptStreamBob)
|
|
|
|
|
|
|
|
acceptStreamCarol, cancelCarol = carol.RPC.ChannelAcceptor()
|
|
|
|
go acceptChannel(ht.T, true, acceptStreamCarol)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open channels in batch to save blocks mined.
|
|
|
|
reqs := []*lntest.OpenChannelRequest{
|
|
|
|
{Local: alice, Remote: bob, Param: aliceParams},
|
|
|
|
{Local: bob, Remote: carol, Param: bobParams},
|
|
|
|
}
|
|
|
|
resp := ht.OpenMultiChannelsAsync(reqs)
|
|
|
|
aliceChanPoint := resp[0]
|
|
|
|
bobChanPoint := resp[1]
|
|
|
|
|
2022-08-15 19:03:14 +08:00
|
|
|
// Make sure alice and carol know each other's channels.
|
2023-08-10 21:53:47 -07:00
|
|
|
//
|
|
|
|
// We'll only do this though if it wasn't a private channel we opened
|
|
|
|
// earlier.
|
|
|
|
if !privateChan {
|
2024-10-30 22:47:36 +08:00
|
|
|
ht.AssertChannelInGraph(alice, bobChanPoint)
|
|
|
|
ht.AssertChannelInGraph(carol, aliceChanPoint)
|
2023-08-10 21:53:47 -07:00
|
|
|
} else {
|
|
|
|
// Otherwise, we want to wait for all the channels to be shown
|
|
|
|
// as active before we proceed.
|
|
|
|
ht.AssertChannelExists(alice, aliceChanPoint)
|
|
|
|
ht.AssertChannelExists(carol, bobChanPoint)
|
|
|
|
}
|
2022-08-15 19:03:14 +08:00
|
|
|
|
2024-03-14 20:39:22 +08:00
|
|
|
// Remove the ChannelAcceptor for Bob and Carol.
|
|
|
|
if zeroConf {
|
|
|
|
cancelBob()
|
|
|
|
cancelCarol()
|
|
|
|
}
|
|
|
|
|
2022-08-15 19:03:14 +08:00
|
|
|
return aliceChanPoint, bobChanPoint, carol
|
|
|
|
}
|
2023-04-07 19:10:27 +08:00
|
|
|
|
|
|
|
// testHtlcTimeoutResolverExtractPreimageRemote tests that in the multi-hop
|
|
|
|
// setting, Alice->Bob->Carol, when Bob's outgoing HTLC is swept by Carol using
|
|
|
|
// the 2nd level success tx2nd level success tx, Bob's timeout resolver will
|
|
|
|
// extract the preimage from the sweep tx found in mempool or blocks(for
|
|
|
|
// neutrino). The 2nd level success tx is broadcast by Carol and spends the
|
|
|
|
// outpoint on her commit tx.
|
|
|
|
func testHtlcTimeoutResolverExtractPreimageRemote(ht *lntest.HarnessTest) {
|
|
|
|
runMultiHopHtlcClaimTest(ht, runExtraPreimageFromRemoteCommit)
|
|
|
|
}
|
|
|
|
|
|
|
|
// runExtraPreimageFromRemoteCommit checks that Bob's htlc timeout resolver
|
|
|
|
// will extract the preimage from the 2nd level success tx broadcast by Carol
|
|
|
|
// which spends the htlc output on her commitment tx.
|
|
|
|
func runExtraPreimageFromRemoteCommit(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, false, c, zeroConf,
|
|
|
|
)
|
|
|
|
|
2024-04-18 04:54:10 +08:00
|
|
|
if ht.IsNeutrinoBackend() {
|
|
|
|
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
|
|
|
}
|
|
|
|
|
2023-08-22 16:19:51 -07:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2023-04-07 19:10:27 +08:00
|
|
|
// With the network active, we'll now add a new hodl invoice at Carol's
|
|
|
|
// end. Make sure the cltv expiry delta is large enough, otherwise Bob
|
|
|
|
// won't send out the outgoing htlc.
|
|
|
|
preimage := ht.RandomPreimage()
|
|
|
|
payHash := preimage.Hash()
|
|
|
|
invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{
|
|
|
|
Value: 100_000,
|
|
|
|
CltvExpiry: finalCltvDelta,
|
|
|
|
Hash: payHash[:],
|
2023-08-22 16:19:51 -07:00
|
|
|
RouteHints: routeHints,
|
2023-04-07 19:10:27 +08:00
|
|
|
}
|
|
|
|
eveInvoice := carol.RPC.AddHoldInvoice(invoiceReq)
|
|
|
|
|
|
|
|
// Subscribe the invoice.
|
|
|
|
stream := carol.RPC.SubscribeSingleInvoice(payHash[:])
|
|
|
|
|
|
|
|
// Now that we've created the invoice, we'll send a single payment from
|
|
|
|
// Alice to Carol. We won't wait for the response however, as Carol
|
|
|
|
// will not immediately settle the payment.
|
|
|
|
req := &routerrpc.SendPaymentRequest{
|
|
|
|
PaymentRequest: eveInvoice.PaymentRequest,
|
|
|
|
TimeoutSeconds: 60,
|
|
|
|
FeeLimitMsat: noFeeLimitMsat,
|
|
|
|
}
|
|
|
|
alice.RPC.SendPayment(req)
|
|
|
|
|
|
|
|
// Once the payment sent, Alice should have one outgoing HTLC active.
|
|
|
|
ht.AssertOutgoingHTLCActive(alice, aliceChanPoint, payHash[:])
|
|
|
|
|
|
|
|
// Bob should have two HTLCs active. One incoming HTLC from Alice, and
|
|
|
|
// one outgoing to Carol.
|
|
|
|
ht.AssertIncomingHTLCActive(bob, aliceChanPoint, payHash[:])
|
|
|
|
htlc := ht.AssertOutgoingHTLCActive(bob, bobChanPoint, payHash[:])
|
|
|
|
|
|
|
|
// Carol should have one incoming HTLC from Bob.
|
|
|
|
ht.AssertIncomingHTLCActive(carol, bobChanPoint, payHash[:])
|
|
|
|
|
|
|
|
// Wait for Carol to mark invoice as accepted. There is a small gap to
|
|
|
|
// bridge between adding the htlc to the channel and executing the exit
|
|
|
|
// hop logic.
|
|
|
|
ht.AssertInvoiceState(stream, lnrpc.Invoice_ACCEPTED)
|
|
|
|
|
|
|
|
// Bob now goes offline so the link between Bob and Carol is broken.
|
|
|
|
restartBob := ht.SuspendNode(bob)
|
|
|
|
|
|
|
|
// Carol now settles the invoice, since her link with Bob is broken,
|
|
|
|
// Bob won't know the preimage.
|
|
|
|
carol.RPC.SettleInvoice(preimage[:])
|
|
|
|
|
|
|
|
// We'll now mine enough blocks to trigger Carol's broadcast of her
|
|
|
|
// commitment transaction due to the fact that the HTLC is about to
|
|
|
|
// timeout. With the default incoming broadcast delta of 10, this
|
|
|
|
// will be the htlc expiry height minus 10.
|
|
|
|
numBlocks := padCLTV(uint32(
|
|
|
|
invoiceReq.CltvExpiry - lncfg.DefaultIncomingBroadcastDelta,
|
|
|
|
))
|
2024-04-06 13:11:00 +08:00
|
|
|
ht.MineEmptyBlocks(int(numBlocks))
|
2023-04-07 19:10:27 +08:00
|
|
|
|
|
|
|
// Carol's force close transaction should now be found in the mempool.
|
2024-04-06 13:11:00 +08:00
|
|
|
// If there are anchors, we also expect Carol's contractcourt to offer
|
|
|
|
// the anchors to her sweeper - one from the local commitment and the
|
|
|
|
// other from the remote.
|
|
|
|
ht.AssertNumPendingSweeps(carol, 2)
|
|
|
|
|
|
|
|
// We now mine a block to confirm Carol's closing transaction, which
|
|
|
|
// will trigger her sweeper to sweep her CPFP anchor sweeping.
|
|
|
|
ht.MineClosingTx(bobChanPoint)
|
2023-04-07 19:10:27 +08:00
|
|
|
|
|
|
|
// With the closing transaction confirmed, we should expect Carol's
|
2024-04-06 13:11:00 +08:00
|
|
|
// HTLC success transaction to be offered to the sweeper along with her
|
|
|
|
// anchor output.
|
|
|
|
ht.AssertNumPendingSweeps(carol, 2)
|
2024-03-14 20:39:22 +08:00
|
|
|
|
2024-04-06 13:11:00 +08:00
|
|
|
// Mine a block to trigger the sweep, and clean up the anchor sweeping
|
|
|
|
// tx.
|
|
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
2024-05-01 19:21:00 +08:00
|
|
|
ht.AssertNumTxsInMempool(1)
|
2023-04-07 19:10:27 +08:00
|
|
|
|
|
|
|
// Restart Bob. Once he finishes syncing the channel state, he should
|
|
|
|
// notice the force close from Carol.
|
|
|
|
require.NoError(ht, restartBob())
|
|
|
|
|
|
|
|
// Get the current height to compute number of blocks to mine to
|
|
|
|
// trigger the htlc timeout resolver from Bob.
|
2024-05-02 14:05:09 +08:00
|
|
|
height := ht.CurrentHeight()
|
2023-04-07 19:10:27 +08:00
|
|
|
|
|
|
|
// We'll now mine enough blocks to trigger Bob's timeout resolver.
|
2024-05-02 14:05:09 +08:00
|
|
|
numBlocks = htlc.ExpirationHeight - height -
|
2023-04-07 19:10:27 +08:00
|
|
|
lncfg.DefaultOutgoingBroadcastDelta
|
|
|
|
|
2023-10-13 02:38:56 +08:00
|
|
|
// We should now have Carol's htlc success tx in the mempool.
|
2023-05-18 17:56:39 +08:00
|
|
|
numTxesMempool := 1
|
2024-05-01 19:21:00 +08:00
|
|
|
ht.AssertNumTxsInMempool(numTxesMempool)
|
2023-04-07 19:10:27 +08:00
|
|
|
|
|
|
|
// For neutrino backend, the timeout resolver needs to extract the
|
|
|
|
// preimage from the blocks.
|
|
|
|
if ht.IsNeutrinoBackend() {
|
|
|
|
// Mine a block to confirm Carol's 2nd level success tx.
|
|
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
2023-10-13 02:38:56 +08:00
|
|
|
numBlocks--
|
2023-04-07 19:10:27 +08:00
|
|
|
}
|
2023-10-13 02:38:56 +08:00
|
|
|
|
2023-05-18 17:56:39 +08:00
|
|
|
// Mine empty blocks so Carol's htlc success tx stays in mempool. Once
|
|
|
|
// the height is reached, Bob's timeout resolver will resolve the htlc
|
|
|
|
// by extracing the preimage from the mempool.
|
|
|
|
ht.MineEmptyBlocks(int(numBlocks))
|
2023-04-07 19:10:27 +08:00
|
|
|
|
|
|
|
// Finally, check that the Alice's payment is marked as succeeded as
|
|
|
|
// Bob has settled the htlc using the preimage extracted from Carol's
|
|
|
|
// 2nd level success tx.
|
|
|
|
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_SUCCEEDED)
|
|
|
|
|
2023-05-18 17:56:39 +08:00
|
|
|
switch c {
|
2024-04-06 13:11:00 +08:00
|
|
|
// For anchor channel type, we should expect to see Bob's commit output
|
|
|
|
// and his anchor output be swept in a single tx in the mempool.
|
2023-08-22 17:43:51 -07:00
|
|
|
case lnrpc.CommitmentType_ANCHORS, lnrpc.CommitmentType_SIMPLE_TAPROOT:
|
2024-04-18 04:54:10 +08:00
|
|
|
numTxesMempool++
|
2023-05-18 17:56:39 +08:00
|
|
|
|
2024-04-06 13:11:00 +08:00
|
|
|
// For script-enforced leased channel, Bob's anchor sweep tx won't
|
|
|
|
// happen as it's not used for CPFP, hence no wallet utxo is used so
|
|
|
|
// it'll be uneconomical.
|
2023-05-18 17:56:39 +08:00
|
|
|
case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE:
|
|
|
|
}
|
|
|
|
|
2024-05-16 23:22:19 +08:00
|
|
|
// For neutrino backend, Carol's second-stage sweep should be offered
|
|
|
|
// to her sweeper.
|
|
|
|
if ht.IsNeutrinoBackend() {
|
|
|
|
ht.AssertNumPendingSweeps(carol, 1)
|
|
|
|
|
|
|
|
// Mine a block to trigger the sweep.
|
|
|
|
ht.MineEmptyBlocks(1)
|
|
|
|
}
|
|
|
|
|
2023-05-18 17:56:39 +08:00
|
|
|
// Mine a block to clean the mempool.
|
|
|
|
ht.MineBlocksAndAssertNumTxes(1, numTxesMempool)
|
|
|
|
|
2023-04-07 19:10:27 +08:00
|
|
|
// NOTE: for non-standby nodes there's no need to clean up the force
|
|
|
|
// close as long as the mempool is cleaned.
|
|
|
|
ht.CleanShutDown()
|
|
|
|
}
|
|
|
|
|
|
|
|
// testHtlcTimeoutResolverExtractPreimage tests that in the multi-hop setting,
|
|
|
|
// Alice->Bob->Carol, when Bob's outgoing HTLC is swept by Carol using the
|
|
|
|
// direct preimage spend, Bob's timeout resolver will extract the preimage from
|
|
|
|
// the sweep tx found in mempool or blocks(for neutrino). The direct spend tx
|
|
|
|
// is broadcast by Carol and spends the outpoint on Bob's commit tx.
|
|
|
|
func testHtlcTimeoutResolverExtractPreimageLocal(ht *lntest.HarnessTest) {
|
|
|
|
runMultiHopHtlcClaimTest(ht, runExtraPreimageFromLocalCommit)
|
|
|
|
}
|
|
|
|
|
|
|
|
// runExtraPreimageFromLocalCommit checks that Bob's htlc timeout resolver will
|
|
|
|
// extract the preimage from the direct spend broadcast by Carol which spends
|
|
|
|
// the htlc output on Bob's commitment tx.
|
|
|
|
func runExtraPreimageFromLocalCommit(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, false, c, zeroConf,
|
|
|
|
)
|
|
|
|
|
2023-08-22 16:19:51 -07:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2023-04-07 19:10:27 +08:00
|
|
|
// With the network active, we'll now add a new hodl invoice at Carol's
|
|
|
|
// end. Make sure the cltv expiry delta is large enough, otherwise Bob
|
|
|
|
// won't send out the outgoing htlc.
|
|
|
|
preimage := ht.RandomPreimage()
|
|
|
|
payHash := preimage.Hash()
|
|
|
|
invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{
|
|
|
|
Value: 100_000,
|
|
|
|
CltvExpiry: finalCltvDelta,
|
|
|
|
Hash: payHash[:],
|
2023-08-22 16:19:51 -07:00
|
|
|
RouteHints: routeHints,
|
2023-04-07 19:10:27 +08:00
|
|
|
}
|
2024-03-14 20:39:22 +08:00
|
|
|
carolInvoice := carol.RPC.AddHoldInvoice(invoiceReq)
|
2023-04-07 19:10:27 +08:00
|
|
|
|
|
|
|
// Subscribe the invoice.
|
|
|
|
stream := carol.RPC.SubscribeSingleInvoice(payHash[:])
|
|
|
|
|
|
|
|
// Now that we've created the invoice, we'll send a single payment from
|
|
|
|
// Alice to Carol. We won't wait for the response however, as Carol
|
|
|
|
// will not immediately settle the payment.
|
|
|
|
req := &routerrpc.SendPaymentRequest{
|
2024-03-14 20:39:22 +08:00
|
|
|
PaymentRequest: carolInvoice.PaymentRequest,
|
2023-04-07 19:10:27 +08:00
|
|
|
TimeoutSeconds: 60,
|
|
|
|
FeeLimitMsat: noFeeLimitMsat,
|
|
|
|
}
|
|
|
|
alice.RPC.SendPayment(req)
|
|
|
|
|
|
|
|
// Once the payment sent, Alice should have one outgoing HTLC active.
|
|
|
|
ht.AssertOutgoingHTLCActive(alice, aliceChanPoint, payHash[:])
|
|
|
|
|
|
|
|
// Bob should have two HTLCs active. One incoming HTLC from Alice, and
|
|
|
|
// one outgoing to Carol.
|
|
|
|
ht.AssertIncomingHTLCActive(bob, aliceChanPoint, payHash[:])
|
|
|
|
htlc := ht.AssertOutgoingHTLCActive(bob, bobChanPoint, payHash[:])
|
|
|
|
|
|
|
|
// Carol should have one incoming HTLC from Bob.
|
|
|
|
ht.AssertIncomingHTLCActive(carol, bobChanPoint, payHash[:])
|
|
|
|
|
|
|
|
// Wait for Carol to mark invoice as accepted. There is a small gap to
|
|
|
|
// bridge between adding the htlc to the channel and executing the exit
|
|
|
|
// hop logic.
|
|
|
|
ht.AssertInvoiceState(stream, lnrpc.Invoice_ACCEPTED)
|
|
|
|
|
|
|
|
// Bob now goes offline so the link between Bob and Carol is broken.
|
|
|
|
restartBob := ht.SuspendNode(bob)
|
|
|
|
|
|
|
|
// Carol now settles the invoice, since her link with Bob is broken,
|
|
|
|
// Bob won't know the preimage.
|
|
|
|
carol.RPC.SettleInvoice(preimage[:])
|
|
|
|
|
|
|
|
// Stop Carol so it's easier to check the mempool's state since she
|
|
|
|
// will broadcast the anchor sweeping once Bob force closes.
|
|
|
|
restartCarol := ht.SuspendNode(carol)
|
|
|
|
|
|
|
|
// Restart Bob to force close the channel.
|
|
|
|
require.NoError(ht, restartBob())
|
|
|
|
|
|
|
|
// Bob force closes the channel, which gets his commitment tx into the
|
|
|
|
// mempool.
|
|
|
|
ht.CloseChannelAssertPending(bob, bobChanPoint, true)
|
|
|
|
|
2024-04-06 13:11:00 +08:00
|
|
|
// Bob should now has offered his anchors to his sweeper - both local
|
|
|
|
// and remote versions.
|
|
|
|
ht.AssertNumPendingSweeps(bob, 2)
|
|
|
|
|
2023-04-07 19:10:27 +08:00
|
|
|
// Mine Bob's force close tx.
|
2024-04-06 13:11:00 +08:00
|
|
|
closeTx := ht.MineClosingTx(bobChanPoint)
|
|
|
|
|
|
|
|
// Mine Bob's anchor sweeping tx.
|
|
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
|
|
blocksMined := 1
|
2023-04-07 19:10:27 +08:00
|
|
|
|
|
|
|
// We'll now mine enough blocks to trigger Carol's sweeping of the htlc
|
|
|
|
// via the direct spend. With the default incoming broadcast delta of
|
|
|
|
// 10, this will be the htlc expiry height minus 10.
|
|
|
|
//
|
|
|
|
// NOTE: we need to mine 1 fewer block as we've already mined one to
|
|
|
|
// confirm Bob's force close tx.
|
|
|
|
numBlocks := padCLTV(uint32(
|
|
|
|
invoiceReq.CltvExpiry - lncfg.DefaultIncomingBroadcastDelta - 1,
|
|
|
|
))
|
|
|
|
|
2024-03-14 20:39:22 +08:00
|
|
|
// If this is a nont script-enforced channel, Bob will be able to sweep
|
|
|
|
// his commit output after 4 blocks.
|
|
|
|
if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
|
|
|
// Mine 3 blocks so the output will be offered to the sweeper.
|
2024-04-06 13:11:00 +08:00
|
|
|
ht.MineEmptyBlocks(defaultCSV - blocksMined - 1)
|
2024-03-14 20:39:22 +08:00
|
|
|
|
|
|
|
// Assert the commit output has been offered to the sweeper.
|
|
|
|
ht.AssertNumPendingSweeps(bob, 1)
|
|
|
|
|
|
|
|
// Mine a block to trigger the sweep.
|
2024-04-06 13:11:00 +08:00
|
|
|
ht.MineEmptyBlocks(1)
|
2024-03-14 20:39:22 +08:00
|
|
|
blocksMined = defaultCSV
|
|
|
|
}
|
|
|
|
|
2023-04-07 19:10:27 +08:00
|
|
|
// Mine empty blocks so it's easier to check Bob's sweeping txes below.
|
2024-03-14 20:39:22 +08:00
|
|
|
ht.MineEmptyBlocks(int(numBlocks) - blocksMined)
|
|
|
|
|
|
|
|
// With the above blocks mined, we should expect Carol's to offer the
|
|
|
|
// htlc output on Bob's commitment to the sweeper.
|
|
|
|
//
|
|
|
|
// TODO(yy): it's not offered to the sweeper yet, instead, the utxo
|
|
|
|
// nursery is creating and broadcasting the sweep tx - we should unify
|
|
|
|
// this behavior and offer it to the sweeper.
|
|
|
|
// ht.AssertNumPendingSweeps(carol, 1)
|
2023-04-07 19:10:27 +08:00
|
|
|
|
|
|
|
// Increase the fee rate used by the sweeper so Carol's direct spend tx
|
|
|
|
// won't be replaced by Bob's timeout tx.
|
|
|
|
ht.SetFeeEstimate(30000)
|
|
|
|
|
|
|
|
// Restart Carol to sweep the htlc output.
|
|
|
|
require.NoError(ht, restartCarol())
|
|
|
|
|
2024-04-06 13:11:00 +08:00
|
|
|
ht.AssertNumPendingSweeps(carol, 2)
|
|
|
|
ht.MineEmptyBlocks(1)
|
|
|
|
|
2023-04-07 19:10:27 +08:00
|
|
|
// Construct the htlc output on Bob's commitment tx, and decide its
|
|
|
|
// index based on the commit type below.
|
|
|
|
htlcOutpoint := wire.OutPoint{Hash: closeTx.TxHash()}
|
|
|
|
|
|
|
|
// Check the current mempool state and we should see,
|
|
|
|
// - Carol's direct spend tx.
|
|
|
|
// - Bob's local output sweep tx, if this is NOT script enforced lease.
|
2024-04-06 13:11:00 +08:00
|
|
|
// - Carol's anchor sweep tx cannot be broadcast as it's uneconomical.
|
2023-04-07 19:10:27 +08:00
|
|
|
switch c {
|
2023-08-22 17:43:51 -07:00
|
|
|
case lnrpc.CommitmentType_ANCHORS, lnrpc.CommitmentType_SIMPLE_TAPROOT:
|
2023-04-07 19:10:27 +08:00
|
|
|
htlcOutpoint.Index = 2
|
2024-05-01 19:21:00 +08:00
|
|
|
ht.AssertNumTxsInMempool(2)
|
2023-04-07 19:10:27 +08:00
|
|
|
|
|
|
|
case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE:
|
|
|
|
htlcOutpoint.Index = 2
|
2024-05-01 19:21:00 +08:00
|
|
|
ht.AssertNumTxsInMempool(1)
|
2023-04-07 19:10:27 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get the current height to compute number of blocks to mine to
|
|
|
|
// trigger the timeout resolver from Bob.
|
2024-05-02 14:05:09 +08:00
|
|
|
height := ht.CurrentHeight()
|
2023-04-07 19:10:27 +08:00
|
|
|
|
|
|
|
// We'll now mine enough blocks to trigger Bob's htlc timeout resolver
|
|
|
|
// to act. Once his timeout resolver starts, it will extract the
|
|
|
|
// preimage from Carol's direct spend tx found in the mempool.
|
2024-05-02 14:05:09 +08:00
|
|
|
numBlocks = htlc.ExpirationHeight - height -
|
2023-04-07 19:10:27 +08:00
|
|
|
lncfg.DefaultOutgoingBroadcastDelta
|
|
|
|
|
|
|
|
// Decrease the fee rate used by the sweeper so Bob's timeout tx will
|
|
|
|
// not replace Carol's direct spend tx.
|
|
|
|
ht.SetFeeEstimate(1000)
|
|
|
|
|
|
|
|
// Mine empty blocks so Carol's direct spend tx stays in mempool. Once
|
|
|
|
// the height is reached, Bob's timeout resolver will resolve the htlc
|
|
|
|
// by extracing the preimage from the mempool.
|
|
|
|
ht.MineEmptyBlocks(int(numBlocks))
|
|
|
|
|
|
|
|
// For neutrino backend, the timeout resolver needs to extract the
|
|
|
|
// preimage from the blocks.
|
|
|
|
if ht.IsNeutrinoBackend() {
|
|
|
|
// Make sure the direct spend tx is still in the mempool.
|
2024-05-01 19:40:05 +08:00
|
|
|
ht.AssertOutpointInMempool(htlcOutpoint)
|
2023-04-07 19:10:27 +08:00
|
|
|
|
2024-05-20 23:06:20 +08:00
|
|
|
// Mine a block to confirm two txns,
|
|
|
|
// - Carol's direct spend tx.
|
|
|
|
// - Bob's to_local output sweep tx.
|
|
|
|
if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
|
|
|
ht.MineBlocksAndAssertNumTxes(1, 2)
|
|
|
|
} else {
|
|
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
|
|
}
|
2023-04-07 19:10:27 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Finally, check that the Alice's payment is marked as succeeded as
|
|
|
|
// Bob has settled the htlc using the preimage extracted from Carol's
|
|
|
|
// direct spend tx.
|
|
|
|
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_SUCCEEDED)
|
|
|
|
|
|
|
|
// NOTE: for non-standby nodes there's no need to clean up the force
|
|
|
|
// close as long as the mempool is cleaned.
|
|
|
|
ht.CleanShutDown()
|
|
|
|
}
|