package itest import ( "fmt" "strings" "testing" "time" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/node" "github.com/lightningnetwork/lnd/lntest/rpc" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/stretchr/testify/require" ) // testOpenChannelAfterReorg tests that in the case where we have an open // channel where the funding tx gets reorged out, the channel will no // longer be present in the node's routing table. func testOpenChannelAfterReorg(ht *lntest.HarnessTest) { // Skip test for neutrino, as we cannot disconnect the miner at will. // TODO(halseth): remove when either can disconnect at will, or restart // node with connection to new miner. if ht.IsNeutrinoBackend() { ht.Skipf("skipping reorg test for neutrino backend") } // Create a temp miner. tempMiner := ht.SpawnTempMiner() miner := ht.Miner() alice, bob := ht.Alice, ht.Bob // Create a new channel that requires 1 confs before it's considered // open, then broadcast the funding transaction params := lntest.OpenChannelParams{ Amt: funding.MaxBtcFundingAmount, Private: true, } pendingUpdate := ht.OpenChannelAssertPending(alice, bob, params) // Wait for miner to have seen the funding tx. The temporary miner is // disconnected, and won't see the transaction. ht.AssertNumTxsInMempool(1) // At this point, the channel's funding transaction will have been // broadcast, but not confirmed, and the channel should be pending. ht.AssertNodesNumPendingOpenChannels(alice, bob, 1) fundingTxID, err := chainhash.NewHash(pendingUpdate.Txid) require.NoError(ht, err, "convert funding txid into chainhash failed") // We now cause a fork, by letting our original miner mine 10 blocks, // and our new miner mine 15. This will also confirm our pending // channel on the original miner's chain, which should be considered // open. block := ht.MineBlocksAndAssertNumTxes(10, 1)[0] ht.AssertTxInBlock(block, fundingTxID) _, err = tempMiner.Client.Generate(15) require.NoError(ht, err, "unable to generate blocks") // Ensure the chain lengths are what we expect, with the temp miner // being 5 blocks ahead. miner.AssertMinerBlockHeightDelta(tempMiner, 5) chanPoint := &lnrpc.ChannelPoint{ FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{ FundingTxidBytes: pendingUpdate.Txid, }, OutputIndex: pendingUpdate.OutputIndex, } // Ensure channel is no longer pending. ht.AssertNodesNumPendingOpenChannels(alice, bob, 0) // Wait for Alice and Bob to recognize and advertise the new channel // generated above. ht.AssertTopologyChannelOpen(alice, chanPoint) ht.AssertTopologyChannelOpen(bob, chanPoint) // Alice should now have 1 edge in her graph. ht.AssertNumEdges(alice, 1, true) // Now we disconnect Alice's chain backend from the original miner, and // connect the two miners together. Since the temporary miner knows // about a longer chain, both miners should sync to that chain. ht.DisconnectMiner() // Connecting to the temporary miner should now cause our original // chain to be re-orged out. miner.ConnectMiner(tempMiner) // Once again they should be on the same chain. miner.AssertMinerBlockHeightDelta(tempMiner, 0) // Now we disconnect the two miners, and connect our original miner to // our chain backend once again. miner.DisconnectMiner(tempMiner) ht.ConnectMiner() // This should have caused a reorg, and Alice should sync to the longer // chain, where the funding transaction is not confirmed. _, tempMinerHeight, err := tempMiner.Client.GetBestBlock() require.NoError(ht, err, "unable to get current blockheight") ht.WaitForNodeBlockHeight(alice, tempMinerHeight) // Since the fundingtx was reorged out, Alice should now have no edges // in her graph. ht.AssertNumEdges(alice, 0, true) // Cleanup by mining the funding tx again, then closing the channel. block = ht.MineBlocksAndAssertNumTxes(1, 1)[0] ht.AssertTxInBlock(block, fundingTxID) ht.CloseChannel(alice, chanPoint) } // testOpenChannelFeePolicy checks if different channel fee scenarios are // correctly handled when the optional channel fee parameters baseFee and // feeRate are provided. If the OpenChannelRequest is not provided with a value // for baseFee/feeRate the expectation is that the default baseFee/feeRate is // applied. // // 1. No params provided to OpenChannelRequest: // ChannelUpdate --> defaultBaseFee, defaultFeeRate // 2. Only baseFee provided to OpenChannelRequest: // ChannelUpdate --> provided baseFee, defaultFeeRate // 3. Only feeRate provided to OpenChannelRequest: // ChannelUpdate --> defaultBaseFee, provided FeeRate // 4. baseFee and feeRate provided to OpenChannelRequest: // ChannelUpdate --> provided baseFee, provided feeRate // 5. Both baseFee and feeRate are set to a value lower than the default: // ChannelUpdate --> provided baseFee, provided feeRate func testOpenChannelUpdateFeePolicy(ht *lntest.HarnessTest) { const ( defaultBaseFee = 1000 defaultFeeRate = 1 defaultTimeLockDelta = chainreg.DefaultBitcoinTimeLockDelta defaultMinHtlc = 1000 optionalBaseFee = 1337 optionalFeeRate = 1337 lowBaseFee = 0 lowFeeRate = 900 ) defaultMaxHtlc := lntest.CalculateMaxHtlc(funding.MaxBtcFundingAmount) chanAmt := funding.MaxBtcFundingAmount pushAmt := chanAmt / 2 feeScenarios := []lntest.OpenChannelParams{ { Amt: chanAmt, PushAmt: pushAmt, UseBaseFee: false, UseFeeRate: false, }, { Amt: chanAmt, PushAmt: pushAmt, BaseFee: optionalBaseFee, UseBaseFee: true, UseFeeRate: false, }, { Amt: chanAmt, PushAmt: pushAmt, FeeRate: optionalFeeRate, UseBaseFee: false, UseFeeRate: true, }, { Amt: chanAmt, PushAmt: pushAmt, BaseFee: optionalBaseFee, FeeRate: optionalFeeRate, UseBaseFee: true, UseFeeRate: true, }, { Amt: chanAmt, PushAmt: pushAmt, BaseFee: lowBaseFee, FeeRate: lowFeeRate, UseBaseFee: true, UseFeeRate: true, }, } expectedPolicies := []lnrpc.RoutingPolicy{ { FeeBaseMsat: defaultBaseFee, FeeRateMilliMsat: defaultFeeRate, TimeLockDelta: defaultTimeLockDelta, MinHtlc: defaultMinHtlc, MaxHtlcMsat: defaultMaxHtlc, }, { FeeBaseMsat: optionalBaseFee, FeeRateMilliMsat: defaultFeeRate, TimeLockDelta: defaultTimeLockDelta, MinHtlc: defaultMinHtlc, MaxHtlcMsat: defaultMaxHtlc, }, { FeeBaseMsat: defaultBaseFee, FeeRateMilliMsat: optionalFeeRate, TimeLockDelta: defaultTimeLockDelta, MinHtlc: defaultMinHtlc, MaxHtlcMsat: defaultMaxHtlc, }, { FeeBaseMsat: optionalBaseFee, FeeRateMilliMsat: optionalFeeRate, TimeLockDelta: defaultTimeLockDelta, MinHtlc: defaultMinHtlc, MaxHtlcMsat: defaultMaxHtlc, }, { FeeBaseMsat: lowBaseFee, FeeRateMilliMsat: lowFeeRate, TimeLockDelta: defaultTimeLockDelta, MinHtlc: defaultMinHtlc, MaxHtlcMsat: defaultMaxHtlc, }, } bobExpectedPolicy := lnrpc.RoutingPolicy{ FeeBaseMsat: defaultBaseFee, FeeRateMilliMsat: defaultFeeRate, TimeLockDelta: defaultTimeLockDelta, MinHtlc: defaultMinHtlc, MaxHtlcMsat: defaultMaxHtlc, } // In this basic test, we'll need a third node, Carol, so we can forward // a payment through the channel we'll open with the different fee // policies. carol := ht.NewNode("Carol", nil) alice, bob := ht.Alice, ht.Bob nodes := []*node.HarnessNode{alice, bob, carol} runTestCase := func(ht *lntest.HarnessTest, chanParams lntest.OpenChannelParams, alicePolicy, bobPolicy *lnrpc.RoutingPolicy) { // Create a channel Alice->Bob. chanPoint := ht.OpenChannel(alice, bob, chanParams) defer ht.CloseChannel(alice, chanPoint) // Create a channel Carol->Alice. chanPoint2 := ht.OpenChannel( carol, alice, lntest.OpenChannelParams{ Amt: 500000, }, ) defer ht.CloseChannel(carol, chanPoint2) // Alice and Bob should see each other's ChannelUpdates, // advertising the preferred routing policies. assertNodesPolicyUpdate( ht, nodes, alice, alicePolicy, chanPoint, ) assertNodesPolicyUpdate(ht, nodes, bob, bobPolicy, chanPoint) // They should now know about the default policies. for _, n := range nodes { ht.AssertChannelPolicy( n, alice.PubKeyStr, alicePolicy, chanPoint, ) ht.AssertChannelPolicy( n, bob.PubKeyStr, bobPolicy, chanPoint, ) } // We should be able to forward a payment from Carol to Bob // through the new channel we opened. payReqs, _, _ := ht.CreatePayReqs(bob, paymentAmt, 1) ht.CompletePaymentRequests(carol, payReqs) } for i, feeScenario := range feeScenarios { ht.Run(fmt.Sprintf("%d", i), func(t *testing.T) { st := ht.Subtest(t) st.EnsureConnected(alice, bob) st.RestartNode(carol) // Because we're using ht.Subtest(), we need to restart // any node we have to refresh its runtime context. // Otherwise, we'll get a "context canceled" error on // RPC calls. st.EnsureConnected(alice, carol) // Send Carol enough coins to be able to open a channel // to Alice. ht.FundCoins(btcutil.SatoshiPerBitcoin, carol) runTestCase( st, feeScenario, &expectedPolicies[i], &bobExpectedPolicy, ) }) } } // testBasicChannelCreationAndUpdates tests multiple channel opening and // closing, and ensures that if a node is subscribed to channel updates they // will be received correctly for both cooperative and force closed channels. func testBasicChannelCreationAndUpdates(ht *lntest.HarnessTest) { runBasicChannelCreationAndUpdates(ht, ht.Alice, ht.Bob) } // runBasicChannelCreationAndUpdates tests multiple channel opening and closing, // and ensures that if a node is subscribed to channel updates they will be // received correctly for both cooperative and force closed channels. func runBasicChannelCreationAndUpdates(ht *lntest.HarnessTest, alice, bob *node.HarnessNode) { const ( numChannels = 2 amount = funding.MaxBtcFundingAmount ) // Subscribe Bob and Alice to channel event notifications. bobChanSub := bob.RPC.SubscribeChannelEvents() aliceChanSub := alice.RPC.SubscribeChannelEvents() // Open the channels between Alice and Bob, asserting that the channels // have been properly opened on-chain. We also attach the optional Memo // argument to one of the channels so we can test that it can be // retrieved correctly when querying the created channel. chanPoints := make([]*lnrpc.ChannelPoint, numChannels) openChannelParams := []lntest.OpenChannelParams{ {Amt: amount, Memo: "bob is a good peer"}, {Amt: amount}, } for i := 0; i < numChannels; i++ { chanPoints[i] = ht.OpenChannel( alice, bob, openChannelParams[i], ) } // Alice should see the memo when retrieving the first channel. channel := ht.QueryChannelByChanPoint(alice, chanPoints[0]) require.Equal(ht, "bob is a good peer", channel.Memo) // Bob shouldn't see the memo since it's for Alice only. channel = ht.QueryChannelByChanPoint(bob, chanPoints[0]) require.Empty(ht, channel.Memo, "Memo is not empty") // The second channel doesn't have a memo. channel = ht.QueryChannelByChanPoint(alice, chanPoints[1]) require.Empty(ht, channel.Memo, "Memo is not empty") // Since each of the channels just became open, Bob and Alice should // each receive an open and an active notification for each channel. const numExpectedOpenUpdates = 3 * numChannels verifyOpenUpdatesReceived := func(sub rpc.ChannelEventsClient) error { for i := 0; i < numExpectedOpenUpdates; i++ { update := ht.ReceiveChannelEvent(sub) switch update.Type { case lnrpc.ChannelEventUpdate_PENDING_OPEN_CHANNEL: if i%3 == 0 { continue } return fmt.Errorf("expected open or active" + "channel ntfn, got pending open " + "channel ntfn instead") case lnrpc.ChannelEventUpdate_OPEN_CHANNEL: if i%3 == 1 { continue } return fmt.Errorf("expected pending open or " + "active channel ntfn, got open" + "channel ntfn instead") case lnrpc.ChannelEventUpdate_ACTIVE_CHANNEL: if i%3 == 2 { continue } return fmt.Errorf("expected pending open or " + "open channel ntfn, got active " + "channel ntfn instead") default: return fmt.Errorf("update type mismatch: "+ "expected open or active channel "+ "notification, got: %v", update.Type) } } return nil } require.NoError(ht, verifyOpenUpdatesReceived(bobChanSub), "bob open channels") require.NoError(ht, verifyOpenUpdatesReceived(aliceChanSub), "alice open channels") // Close the channels between Alice and Bob, asserting that the // channels have been properly closed on-chain. for i, chanPoint := range chanPoints { // Force close the first of the two channels. force := i%2 == 0 if force { ht.ForceCloseChannel(alice, chanPoint) } else { ht.CloseChannel(alice, chanPoint) } } // If Bob now tries to open a channel with an invalid memo, reject it. invalidMemo := strings.Repeat("a", 501) params := lntest.OpenChannelParams{ Amt: funding.MaxBtcFundingAmount, Memo: invalidMemo, } expErr := fmt.Errorf("provided memo (%s) is of length 501, exceeds 500", invalidMemo) ht.OpenChannelAssertErr(bob, alice, params, expErr) // verifyCloseUpdatesReceived is used to verify that Alice and Bob // receive the correct channel updates in order. const numExpectedCloseUpdates = 3 * numChannels verifyCloseUpdatesReceived := func(sub rpc.ChannelEventsClient, forceType lnrpc.ChannelCloseSummary_ClosureType, closeInitiator lnrpc.Initiator) error { // Ensure one inactive and one closed notification is received // for each closed channel. for i := 0; i < numExpectedCloseUpdates; i++ { expectedCloseType := lnrpc. ChannelCloseSummary_COOPERATIVE_CLOSE // Every other channel should be force closed. If this // channel was force closed, set the expected close type // to the type passed in. force := (i/3)%2 == 0 if force { expectedCloseType = forceType } chanUpdate := ht.ReceiveChannelEvent(sub) err := verifyCloseUpdate( chanUpdate, expectedCloseType, closeInitiator, ) if err != nil { return err } } return nil } // Verify Bob receives all closed channel notifications. He should // receive a remote force close notification for force closed channels. // All channels (cooperatively and force closed) should have a remote // close initiator because Alice closed the channels. require.NoError( ht, verifyCloseUpdatesReceived( bobChanSub, lnrpc.ChannelCloseSummary_REMOTE_FORCE_CLOSE, lnrpc.Initiator_INITIATOR_REMOTE, ), "verifying bob close updates", ) // Verify Alice receives all closed channel notifications. She should // receive a remote force close notification for force closed channels. // All channels (cooperatively and force closed) should have a local // close initiator because Alice closed the channels. require.NoError( ht, verifyCloseUpdatesReceived( aliceChanSub, lnrpc.ChannelCloseSummary_LOCAL_FORCE_CLOSE, lnrpc.Initiator_INITIATOR_LOCAL, ), "verifying alice close updates", ) } // testUpdateOnPendingOpenChannels checks that `update_add_htlc` followed by // `channel_ready` is properly handled. In specific, when a node is in a state // that it's still processing a remote `channel_ready` message, meanwhile an // `update_add_htlc` is received, this HTLC message is cached and settled once // processing `channel_ready` is complete. func testUpdateOnPendingOpenChannels(ht *lntest.HarnessTest) { // Test funder's behavior. Funder sees the channel pending, but fundee // sees it active and sends an HTLC. ht.Run("pending on funder side", func(t *testing.T) { st := ht.Subtest(t) testUpdateOnFunderPendingOpenChannels(st) }) // Test fundee's behavior. Fundee sees the channel pending, but funder // sees it active and sends an HTLC. ht.Run("pending on fundee side", func(t *testing.T) { st := ht.Subtest(t) testUpdateOnFundeePendingOpenChannels(st) }) } // testUpdateOnFunderPendingOpenChannels checks that when the fundee sends an // `update_add_htlc` followed by `channel_ready` while the funder is still // processing the fundee's `channel_ready`, the HTLC will be cached and // eventually settled. func testUpdateOnFunderPendingOpenChannels(ht *lntest.HarnessTest) { // Grab the channel participants. alice, bob := ht.Alice, ht.Bob // Restart Alice with the config so she won't process Bob's // channel_ready msg immediately. ht.RestartNodeWithExtraArgs(alice, []string{ "--dev.processchannelreadywait=10s", }) // Make sure Alice and Bob are connected. ht.EnsureConnected(alice, bob) // Create a new channel that requires 1 confs before it's considered // open. params := lntest.OpenChannelParams{ Amt: funding.MaxBtcFundingAmount, PushAmt: funding.MaxBtcFundingAmount / 2, } pendingChan := ht.OpenChannelAssertPending(alice, bob, params) chanPoint := &lnrpc.ChannelPoint{ FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{ FundingTxidBytes: pendingChan.Txid, }, OutputIndex: pendingChan.OutputIndex, } // Alice and Bob should both consider the channel pending open. ht.AssertNumPendingOpenChannels(alice, 1) ht.AssertNumPendingOpenChannels(bob, 1) // Mine one block to confirm the funding transaction. ht.MineBlocksAndAssertNumTxes(1, 1) // TODO(yy): we've prematurely marked the channel as open before // processing channel ready messages. We need to mark it as open after // we've processed channel ready messages and change the check to, // ht.AssertNumPendingOpenChannels(alice, 1) ht.AssertNumPendingOpenChannels(alice, 0) // Bob will consider the channel open as there's no wait time to send // and receive Alice's channel_ready message. ht.AssertNumPendingOpenChannels(bob, 0) // Alice and Bob now have different view of the channel. For Bob, // since the channel_ready messages are processed, he will have a // working link to route HTLCs. For Alice, because she hasn't handled // Bob's channel_ready, there's no active link yet. // // Alice now adds an invoice. req := &lnrpc.Invoice{ RPreimage: ht.Random32Bytes(), Value: 10_000, } invoice := alice.RPC.AddInvoice(req) // Bob sends an `update_add_htlc`, which would result in this message // being cached in Alice's `peer.Brontide` and the payment will stay // in-flight instead of being failed by Alice. bobReq := &routerrpc.SendPaymentRequest{ PaymentRequest: invoice.PaymentRequest, TimeoutSeconds: 60, FeeLimitMsat: noFeeLimitMsat, } bobStream := bob.RPC.SendPayment(bobReq) ht.AssertPaymentStatusFromStream(bobStream, lnrpc.Payment_IN_FLIGHT) // Wait until Alice finishes processing Bob's channel_ready. // // NOTE: no effect before fixing the above TODO. ht.AssertNumPendingOpenChannels(alice, 0) // Once Alice sees the channel as active, she will process the cached // premature `update_add_htlc` and settles the payment. ht.AssertPaymentStatusFromStream(bobStream, lnrpc.Payment_SUCCEEDED) // Close the channel. ht.CloseChannel(alice, chanPoint) } // testUpdateOnFundeePendingOpenChannels checks that when the funder sends an // `update_add_htlc` followed by `channel_ready` while the fundee is still // processing the funder's `channel_ready`, the HTLC will be cached and // eventually settled. func testUpdateOnFundeePendingOpenChannels(ht *lntest.HarnessTest) { // Grab the channel participants. alice, bob := ht.Alice, ht.Bob // Restart Bob with the config so he won't process Alice's // channel_ready msg immediately. ht.RestartNodeWithExtraArgs(bob, []string{ "--dev.processchannelreadywait=10s", }) // Make sure Alice and Bob are connected. ht.EnsureConnected(alice, bob) // Create a new channel that requires 1 confs before it's considered // open. params := lntest.OpenChannelParams{ Amt: funding.MaxBtcFundingAmount, } pendingChan := ht.OpenChannelAssertPending(alice, bob, params) chanPoint := &lnrpc.ChannelPoint{ FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{ FundingTxidBytes: pendingChan.Txid, }, OutputIndex: pendingChan.OutputIndex, } // Alice and Bob should both consider the channel pending open. ht.AssertNumPendingOpenChannels(alice, 1) ht.AssertNumPendingOpenChannels(bob, 1) // Mine one block to confirm the funding transaction. ht.MineBlocksAndAssertNumTxes(1, 1) // Alice will consider the channel open as there's no wait time to send // and receive Bob's channel_ready message. ht.AssertNumPendingOpenChannels(alice, 0) // TODO(yy): we've prematurely marked the channel as open before // processing channel ready messages. We need to mark it as open after // we've processed channel ready messages and change the check to, // ht.AssertNumPendingOpenChannels(bob, 1) ht.AssertNumPendingOpenChannels(bob, 0) // Alice and Bob now have different view of the channel. For Alice, // since the channel_ready messages are processed, she will have a // working link to route HTLCs. For Bob, because he hasn't handled // Alice's channel_ready, there's no active link yet. // // Bob now adds an invoice. req := &lnrpc.Invoice{ RPreimage: ht.Random32Bytes(), Value: 10_000, } bobInvoice := bob.RPC.AddInvoice(req) // Alice sends an `update_add_htlc`, which would result in this message // being cached in Bob's `peer.Brontide` and the payment will stay // in-flight instead of being failed by Bob. aliceReq := &routerrpc.SendPaymentRequest{ PaymentRequest: bobInvoice.PaymentRequest, TimeoutSeconds: 60, FeeLimitMsat: noFeeLimitMsat, } aliceStream := alice.RPC.SendPayment(aliceReq) ht.AssertPaymentStatusFromStream(aliceStream, lnrpc.Payment_IN_FLIGHT) // Wait until Bob finishes processing Alice's channel_ready. // // NOTE: no effect before fixing the above TODO. ht.AssertNumPendingOpenChannels(bob, 0) // Once Bob sees the channel as active, he will process the cached // premature `update_add_htlc` and settles the payment. ht.AssertPaymentStatusFromStream(aliceStream, lnrpc.Payment_SUCCEEDED) // Close the channel. ht.CloseChannel(alice, chanPoint) } // verifyCloseUpdate is used to verify that a closed channel update is of the // expected type. func verifyCloseUpdate(chanUpdate *lnrpc.ChannelEventUpdate, closeType lnrpc.ChannelCloseSummary_ClosureType, closeInitiator lnrpc.Initiator) error { // We should receive one inactive and one closed notification // for each channel. switch update := chanUpdate.Channel.(type) { case *lnrpc.ChannelEventUpdate_InactiveChannel: if chanUpdate.Type != lnrpc.ChannelEventUpdate_INACTIVE_CHANNEL { return fmt.Errorf("update type mismatch: "+ "expected %v, got %v", lnrpc.ChannelEventUpdate_INACTIVE_CHANNEL, chanUpdate.Type) } case *lnrpc.ChannelEventUpdate_ClosedChannel: if chanUpdate.Type != lnrpc.ChannelEventUpdate_CLOSED_CHANNEL { return fmt.Errorf("update type mismatch: "+ "expected %v, got %v", lnrpc.ChannelEventUpdate_CLOSED_CHANNEL, chanUpdate.Type) } if update.ClosedChannel.CloseType != closeType { return fmt.Errorf("channel closure type "+ "mismatch: expected %v, got %v", closeType, update.ClosedChannel.CloseType) } if update.ClosedChannel.CloseInitiator != closeInitiator { return fmt.Errorf("expected close intiator: %v, "+ "got: %v", closeInitiator, update.ClosedChannel.CloseInitiator) } case *lnrpc.ChannelEventUpdate_FullyResolvedChannel: if chanUpdate.Type != lnrpc.ChannelEventUpdate_FULLY_RESOLVED_CHANNEL { return fmt.Errorf("update type mismatch: "+ "expected %v, got %v", lnrpc.ChannelEventUpdate_FULLY_RESOLVED_CHANNEL, chanUpdate.Type) } default: return fmt.Errorf("channel update channel of wrong type, "+ "expected closed channel, got %T", update) } return nil } // testFundingExpiryBlocksOnPending checks that after an OpenChannel, and // before the funding transaction is confirmed, that the FundingExpiryBlocks // field of a PendingChannels decreases. func testFundingExpiryBlocksOnPending(ht *lntest.HarnessTest) { alice, bob := ht.Alice, ht.Bob param := lntest.OpenChannelParams{Amt: 100000} update := ht.OpenChannelAssertPending(alice, bob, param) // At this point, the channel's funding transaction will have been // broadcast, but not confirmed. Alice and Bob's nodes should reflect // this when queried via RPC. FundingExpiryBlock should decrease // as blocks are mined, until the channel is confirmed. Empty blocks // won't confirm the funding transaction, so let's mine a few empty // blocks and verify the value of FundingExpiryBlock at each step. const numEmptyBlocks = 3 for i := int32(0); i < numEmptyBlocks; i++ { expectedVal := funding.MaxWaitNumBlocksFundingConf - i pending := ht.AssertNumPendingOpenChannels(alice, 1) require.Equal(ht, expectedVal, pending[0].FundingExpiryBlocks) pending = ht.AssertNumPendingOpenChannels(bob, 1) require.Equal(ht, expectedVal, pending[0].FundingExpiryBlocks) ht.MineEmptyBlocks(1) } // Mine 1 block to confirm the funding transaction, and then close the // channel. ht.MineBlocksAndAssertNumTxes(1, 1) chanPoint := lntest.ChanPointFromPendingUpdate(update) // TODO(yy): remove the sleep once the following bug is fixed. // // We may get the error `unable to gracefully close channel // while peer is offline (try force closing it instead): // channel link not found`. This happens because the channel // link hasn't been added yet but we now proceed to closing the // channel. We may need to revisit how the channel open event // is created and make sure the event is only sent after all // relevant states have been updated. time.Sleep(2 * time.Second) ht.CloseChannel(alice, chanPoint) } // testSimpleTaprootChannelActivation ensures that a simple taproot channel is // active if the initiator disconnects and reconnects in between channel opening // and channel confirmation. func testSimpleTaprootChannelActivation(ht *lntest.HarnessTest) { simpleTaprootChanArgs := lntest.NodeArgsForCommitType( lnrpc.CommitmentType_SIMPLE_TAPROOT, ) // Make the new set of participants. alice := ht.NewNode("alice", simpleTaprootChanArgs) defer ht.Shutdown(alice) bob := ht.NewNode("bob", simpleTaprootChanArgs) defer ht.Shutdown(bob) ht.FundCoins(btcutil.SatoshiPerBitcoin, alice) // Make sure Alice and Bob are connected. ht.EnsureConnected(alice, bob) // Create simple taproot channel opening parameters. params := lntest.OpenChannelParams{ FundMax: true, CommitmentType: lnrpc.CommitmentType_SIMPLE_TAPROOT, Private: true, } // Alice opens the channel to Bob. pendingChan := ht.OpenChannelAssertPending(alice, bob, params) // We'll create the channel point to be able to close the channel once // our test is done. chanPoint := &lnrpc.ChannelPoint{ FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{ FundingTxidBytes: pendingChan.Txid, }, OutputIndex: pendingChan.OutputIndex, } // We disconnect and reconnect Alice and Bob before the channel is // confirmed. Our expectation is that the channel is active once the // channel is confirmed. ht.DisconnectNodes(alice, bob) ht.EnsureConnected(alice, bob) // Mine six blocks to confirm the channel funding transaction. ht.MineBlocksAndAssertNumTxes(6, 1) // Verify that Alice sees an active channel to Bob. ht.AssertChannelActive(alice, chanPoint) // Our test is done and Alice closes her channel to Bob. ht.CloseChannel(alice, chanPoint) } // testOpenChannelLockedBalance tests that when a funding reservation is // made for opening a channel, the balance of the required outputs shows // up as locked balance in the WalletBalance response. func testOpenChannelLockedBalance(ht *lntest.HarnessTest) { var ( bob = ht.Bob req *lnrpc.ChannelAcceptRequest err error ) // Create a new node so we can assert exactly how much fund has been // locked later. alice := ht.NewNode("alice", nil) ht.FundCoins(btcutil.SatoshiPerBitcoin, alice) // Connect the nodes. ht.EnsureConnected(alice, bob) // We first make sure Alice has no locked wallet balance. balance := alice.RPC.WalletBalance() require.EqualValues(ht, 0, balance.LockedBalance) // Next, we register a ChannelAcceptor on Bob. This way, we can get // Alice's wallet balance after coin selection is done and outpoints // are locked. stream, cancel := bob.RPC.ChannelAcceptor() defer cancel() // Then, we request creation of a channel from Alice to Bob. We don't // use OpenChannelSync since we want to receive Bob's message in the // same goroutine. openChannelReq := &lnrpc.OpenChannelRequest{ NodePubkey: bob.PubKey[:], LocalFundingAmount: int64(funding.MaxBtcFundingAmount), TargetConf: 6, } _ = alice.RPC.OpenChannel(openChannelReq) // After that, we receive the request on Bob's side, get the wallet // balance from Alice, and ensure the locked balance is non-zero. err = wait.NoError(func() error { req, err = stream.Recv() return err }, defaultTimeout) require.NoError(ht, err) ht.AssertWalletLockedBalance(alice, btcutil.SatoshiPerBitcoin) // Next, we let Bob deny the request. resp := &lnrpc.ChannelAcceptResponse{ Accept: false, PendingChanId: req.PendingChanId, } err = wait.NoError(func() error { return stream.Send(resp) }, defaultTimeout) require.NoError(ht, err) // Finally, we check to make sure the balance is unlocked again. ht.AssertWalletLockedBalance(alice, 0) }