package itest import ( "context" "fmt" "time" "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/integration/rpctest" "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntest" "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(net *lntest.NetworkHarness, t *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 net.BackendCfg.Name() == lntest.NeutrinoBackendName { t.Skipf("skipping reorg test for neutrino backend") } var ( ctxb = context.Background() temp = "temp" ) // Set up a new miner that we can use to cause a reorg. tempLogDir := fmt.Sprintf("%s/.tempminerlogs", lntest.GetLogDir()) logFilename := "output-open_channel_reorg-temp_miner.log" tempMiner, tempMinerCleanUp, err := lntest.NewMiner( tempLogDir, logFilename, harnessNetParams, &rpcclient.NotificationHandlers{}, lntest.GetBtcdBinary(), ) require.NoError(t.t, err, "failed to create temp miner") defer func() { require.NoError( t.t, tempMinerCleanUp(), "failed to clean up temp miner", ) }() // Setup the temp miner require.NoError( t.t, tempMiner.SetUp(false, 0), "unable to set up mining node", ) // We start by connecting the new miner to our original miner, // such that it will sync to our original chain. err = net.Miner.Client.Node( btcjson.NConnect, tempMiner.P2PAddress(), &temp, ) if err != nil { t.Fatalf("unable to remove node: %v", err) } nodeSlice := []*rpctest.Harness{net.Miner, tempMiner} if err := rpctest.JoinNodes(nodeSlice, rpctest.Blocks); err != nil { t.Fatalf("unable to join node on blocks: %v", err) } // The two miners should be on the same blockheight. assertMinerBlockHeightDelta(t, net.Miner, tempMiner, 0) // We disconnect the two miners, such that we can mine two different // chains and can cause a reorg later. err = net.Miner.Client.Node( btcjson.NDisconnect, tempMiner.P2PAddress(), &temp, ) if err != nil { t.Fatalf("unable to remove node: %v", err) } // Create a new channel that requires 1 confs before it's considered // open, then broadcast the funding transaction chanAmt := funding.MaxBtcFundingAmount pushAmt := btcutil.Amount(0) ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) pendingUpdate, err := net.OpenPendingChannel(ctxt, net.Alice, net.Bob, chanAmt, pushAmt) if err != nil { t.Fatalf("unable to open channel: %v", err) } // Wait for miner to have seen the funding tx. The temporary miner is // disconnected, and won't see the transaction. _, err = waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) if err != nil { t.Fatalf("failed to find funding tx in mempool: %v", err) } // At this point, the channel's funding transaction will have been // broadcast, but not confirmed, and the channel should be pending. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) assertNumOpenChannelsPending(ctxt, t, net.Alice, net.Bob, 1) fundingTxID, err := chainhash.NewHash(pendingUpdate.Txid) if err != nil { t.Fatalf("unable to convert funding txid into chainhash.Hash:"+ " %v", err) } // 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 := mineBlocks(t, net, 10, 1)[0] assertTxInBlock(t, block, fundingTxID) if _, err := tempMiner.Client.Generate(15); err != nil { t.Fatalf("unable to generate blocks: %v", err) } // Ensure the chain lengths are what we expect, with the temp miner // being 5 blocks ahead. assertMinerBlockHeightDelta(t, net.Miner, tempMiner, 5) // Wait for Alice to sync to the original miner's chain. _, minerHeight, err := net.Miner.Client.GetBestBlock() if err != nil { t.Fatalf("unable to get current blockheight %v", err) } ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) err = waitForNodeBlockHeight(ctxt, net.Alice, minerHeight) if err != nil { t.Fatalf("unable to sync to chain: %v", err) } chanPoint := &lnrpc.ChannelPoint{ FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{ FundingTxidBytes: pendingUpdate.Txid, }, OutputIndex: pendingUpdate.OutputIndex, } // Ensure channel is no longer pending. assertNumOpenChannelsPending(ctxt, t, net.Alice, net.Bob, 0) // Wait for Alice and Bob to recognize and advertise the new channel // generated above. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) if err != nil { t.Fatalf("alice didn't advertise channel before "+ "timeout: %v", err) } ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint) if err != nil { t.Fatalf("bob didn't advertise channel before "+ "timeout: %v", err) } // Alice should now have 1 edge in her graph. req := &lnrpc.ChannelGraphRequest{ IncludeUnannounced: true, } ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) chanGraph, err := net.Alice.DescribeGraph(ctxt, req) if err != nil { t.Fatalf("unable to query for alice's routing table: %v", err) } numEdges := len(chanGraph.Edges) if numEdges != 1 { t.Fatalf("expected to find one edge in the graph, found %d", numEdges) } // 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. err = net.BackendCfg.DisconnectMiner() if err != nil { t.Fatalf("unable to remove node: %v", err) } // Connecting to the temporary miner should now cause our original // chain to be re-orged out. err = net.Miner.Client.Node( btcjson.NConnect, tempMiner.P2PAddress(), &temp, ) if err != nil { t.Fatalf("unable to remove node: %v", err) } nodes := []*rpctest.Harness{tempMiner, net.Miner} if err := rpctest.JoinNodes(nodes, rpctest.Blocks); err != nil { t.Fatalf("unable to join node on blocks: %v", err) } // Once again they should be on the same chain. assertMinerBlockHeightDelta(t, net.Miner, tempMiner, 0) // Now we disconnect the two miners, and connect our original miner to // our chain backend once again. err = net.Miner.Client.Node( btcjson.NDisconnect, tempMiner.P2PAddress(), &temp, ) if err != nil { t.Fatalf("unable to remove node: %v", err) } err = net.BackendCfg.ConnectMiner() if err != nil { t.Fatalf("unable to remove node: %v", err) } // 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() if err != nil { t.Fatalf("unable to get current blockheight %v", err) } ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) err = waitForNodeBlockHeight(ctxt, net.Alice, tempMinerHeight) if err != nil { t.Fatalf("unable to sync to chain: %v", err) } // Since the fundingtx was reorged out, Alice should now have no edges // in her graph. req = &lnrpc.ChannelGraphRequest{ IncludeUnannounced: true, } var predErr error err = wait.Predicate(func() bool { ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) chanGraph, err = net.Alice.DescribeGraph(ctxt, req) if err != nil { predErr = fmt.Errorf("unable to query for alice's routing table: %v", err) return false } numEdges = len(chanGraph.Edges) if numEdges != 0 { predErr = fmt.Errorf("expected to find no edge in the graph, found %d", numEdges) return false } return true }, defaultTimeout) if err != nil { t.Fatalf(predErr.Error()) } // Cleanup by mining the funding tx again, then closing the channel. block = mineBlocks(t, net, 1, 1)[0] assertTxInBlock(t, block, fundingTxID) ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) closeReorgedChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false) } // 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(net *lntest.NetworkHarness, t *harnessTest) { ctxb := context.Background() const ( numChannels = 2 amount = funding.MaxBtcFundingAmount ) // Subscribe Bob and Alice to channel event notifications. bobChanSub := subscribeChannelNotifications(ctxb, t, net.Bob) defer close(bobChanSub.quit) aliceChanSub := subscribeChannelNotifications(ctxb, t, net.Alice) defer close(aliceChanSub.quit) // Open the channel between Alice and Bob, asserting that the // channel has been properly open on-chain. chanPoints := make([]*lnrpc.ChannelPoint, numChannels) for i := 0; i < numChannels; i++ { ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) chanPoints[i] = openChannelAndAssert( ctxt, t, net, net.Alice, net.Bob, lntest.OpenChannelParams{ Amt: amount, }, ) } // Since each of the channels just became open, Bob and Alice should // each receive an open and an active notification for each channel. var numChannelUpds int const totalNtfns = 3 * numChannels verifyOpenUpdatesReceived := func(sub channelSubscription) error { numChannelUpds = 0 for numChannelUpds < totalNtfns { select { case update := <-sub.updateChan: switch update.Type { case lnrpc.ChannelEventUpdate_PENDING_OPEN_CHANNEL: if numChannelUpds%3 != 0 { return fmt.Errorf("expected " + "open or active" + "channel ntfn, got pending open " + "channel ntfn instead") } case lnrpc.ChannelEventUpdate_OPEN_CHANNEL: if numChannelUpds%3 != 1 { return fmt.Errorf("expected " + "pending open or active" + "channel ntfn, got open" + "channel ntfn instead") } case lnrpc.ChannelEventUpdate_ACTIVE_CHANNEL: if numChannelUpds%3 != 2 { 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) } numChannelUpds++ case <-time.After(time.Second * 10): return fmt.Errorf("timeout waiting for channel "+ "notifications, only received %d/%d "+ "chanupds", numChannelUpds, totalNtfns) } } return nil } if err := verifyOpenUpdatesReceived(bobChanSub); err != nil { t.Fatalf("error verifying open updates: %v", err) } if err := verifyOpenUpdatesReceived(aliceChanSub); err != nil { t.Fatalf("error verifying open updates: %v", err) } // Close the channel between Alice and Bob, asserting that the channel // has been properly closed on-chain. for i, chanPoint := range chanPoints { ctx, _ := context.WithTimeout(context.Background(), defaultTimeout) // Force close half of the channels. force := i%2 == 0 closeChannelAndAssert(ctx, t, net, net.Alice, chanPoint, force) if force { cleanupForceClose(t, net, net.Alice, chanPoint) } } // verifyCloseUpdatesReceived is used to verify that Alice and Bob // receive the correct channel updates in order. verifyCloseUpdatesReceived := func(sub channelSubscription, forceType lnrpc.ChannelCloseSummary_ClosureType, closeInitiator lnrpc.Initiator) error { // Ensure one inactive and one closed notification is received for each // closed channel. numChannelUpds := 0 for numChannelUpds < 2*numChannels { expectedCloseType := lnrpc.ChannelCloseSummary_COOPERATIVE_CLOSE // Every other channel should be force closed. If this // channel was force closed, set the expected close type // the the type passed in. force := (numChannelUpds/2)%2 == 0 if force { expectedCloseType = forceType } select { case chanUpdate := <-sub.updateChan: err := verifyCloseUpdate( chanUpdate, expectedCloseType, closeInitiator, ) if err != nil { return err } numChannelUpds++ case err := <-sub.errChan: return err case <-time.After(time.Second * 10): return fmt.Errorf("timeout waiting "+ "for channel notifications, only "+ "received %d/%d chanupds", numChannelUpds, 2*numChannels) } } 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. if err := verifyCloseUpdatesReceived(bobChanSub, lnrpc.ChannelCloseSummary_REMOTE_FORCE_CLOSE, lnrpc.Initiator_INITIATOR_REMOTE); err != nil { t.Fatalf("errored verifying close updates: %v", err) } // 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. if err := verifyCloseUpdatesReceived(aliceChanSub, lnrpc.ChannelCloseSummary_LOCAL_FORCE_CLOSE, lnrpc.Initiator_INITIATOR_LOCAL); err != nil { t.Fatalf("errored verifying close updates: %v", err) } }