|
1 | 1 | package itest
|
2 | 2 |
|
3 | 3 | import (
|
| 4 | + "context" |
4 | 5 | "testing"
|
5 | 6 | "time"
|
6 | 7 |
|
| 8 | + "github.com/btcsuite/btcd/btcjson" |
7 | 9 | "github.com/btcsuite/btcd/btcutil"
|
| 10 | + "github.com/btcsuite/btcd/integration/rpctest" |
8 | 11 | "github.com/go-errors/errors"
|
| 12 | + "github.com/lightningnetwork/lnd/aliasmgr" |
9 | 13 | "github.com/lightningnetwork/lnd/chainreg"
|
10 | 14 | "github.com/lightningnetwork/lnd/lnrpc"
|
11 | 15 | "github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
12 | 16 | "github.com/lightningnetwork/lnd/lntemp"
|
13 | 17 | "github.com/lightningnetwork/lnd/lntemp/node"
|
14 | 18 | "github.com/lightningnetwork/lnd/lntemp/rpc"
|
| 19 | + "github.com/lightningnetwork/lnd/lntest" |
15 | 20 | "github.com/lightningnetwork/lnd/lntest/wait"
|
16 | 21 | "github.com/lightningnetwork/lnd/lnwire"
|
17 | 22 | "github.com/stretchr/testify/require"
|
@@ -883,3 +888,169 @@ func acceptChannel(t *testing.T, zeroConf bool, stream rpc.AcceptorClient) {
|
883 | 888 | err = stream.Send(resp)
|
884 | 889 | require.NoError(t, err)
|
885 | 890 | }
|
| 891 | + |
| 892 | +// testZeroConfReorg tests that a reorg does not cause a zero-conf channel to |
| 893 | +// be deleted from the channel graph. This was previously the case due to logic |
| 894 | +// in the function DisconnectBlockAtHeight. |
| 895 | +func testZeroConfReorg(ht *lntemp.HarnessTest) { |
| 896 | + if ht.ChainBackendName() == lntest.NeutrinoBackendName { |
| 897 | + ht.Skipf("skipping zero-conf reorg test for neutrino backend") |
| 898 | + } |
| 899 | + |
| 900 | + var ( |
| 901 | + ctxb = context.Background() |
| 902 | + temp = "temp" |
| 903 | + ) |
| 904 | + |
| 905 | + // Since zero-conf is opt in, the harness nodes provided won't be able |
| 906 | + // to open zero-conf channels. In that case, we just spin up new nodes. |
| 907 | + zeroConfArgs := []string{ |
| 908 | + "--protocol.option-scid-alias", |
| 909 | + "--protocol.zero-conf", |
| 910 | + "--protocol.anchors", |
| 911 | + } |
| 912 | + |
| 913 | + carol := ht.NewNode("Carol", zeroConfArgs) |
| 914 | + |
| 915 | + // Spin-up Dave so Carol can open a zero-conf channel to him. |
| 916 | + dave := ht.NewNode("Dave", zeroConfArgs) |
| 917 | + |
| 918 | + // We'll give Carol some coins in order to fund the channel. |
| 919 | + ht.FundCoins(btcutil.SatoshiPerBitcoin, carol) |
| 920 | + |
| 921 | + // Ensure that both Carol and Dave are connected. |
| 922 | + ht.EnsureConnected(carol, dave) |
| 923 | + |
| 924 | + // Setup a ChannelAcceptor for Dave. |
| 925 | + acceptStream, cancel := dave.RPC.ChannelAcceptor() |
| 926 | + go acceptChannel(ht.T, true, acceptStream) |
| 927 | + |
| 928 | + // Open a private zero-conf anchors channel of 1M satoshis. |
| 929 | + params := lntemp.OpenChannelParams{ |
| 930 | + Amt: btcutil.Amount(1_000_000), |
| 931 | + CommitmentType: lnrpc.CommitmentType_ANCHORS, |
| 932 | + ZeroConf: true, |
| 933 | + } |
| 934 | + _ = ht.OpenChannelNoAnnounce(carol, dave, params) |
| 935 | + |
| 936 | + // Remove the ChannelAcceptor. |
| 937 | + cancel() |
| 938 | + |
| 939 | + // Attempt to send a 10K satoshi payment from Carol to Dave. This |
| 940 | + // requires that the edge exists in the graph. |
| 941 | + daveInvoiceParams := &lnrpc.Invoice{ |
| 942 | + Value: int64(10_000), |
| 943 | + } |
| 944 | + daveInvoiceResp := dave.RPC.AddInvoice(daveInvoiceParams) |
| 945 | + |
| 946 | + payReqs := []string{daveInvoiceResp.PaymentRequest} |
| 947 | + ht.CompletePaymentRequests(carol, payReqs) |
| 948 | + |
| 949 | + // We will now attempt to query for the alias SCID in Carol's graph. |
| 950 | + // We will query for the starting alias, which is exported by the |
| 951 | + // aliasmgr package. |
| 952 | + _, err := carol.RPC.LN.GetChanInfo(ctxb, &lnrpc.ChanInfoRequest{ |
| 953 | + ChanId: aliasmgr.StartingAlias.ToUint64(), |
| 954 | + }) |
| 955 | + require.NoError(ht.T, err) |
| 956 | + |
| 957 | + // Now we will trigger a reorg and we'll assert that the edge still |
| 958 | + // exists in the graph. |
| 959 | + // |
| 960 | + // First, we'll setup a new miner that we can use to cause a reorg. |
| 961 | + tempLogDir := ".tempminerlogs" |
| 962 | + logFilename := "output-open_channel_reorg-temp_miner.log" |
| 963 | + tempMiner := lntemp.NewTempMiner( |
| 964 | + ht.Context(), ht.T, tempLogDir, logFilename, |
| 965 | + ) |
| 966 | + defer tempMiner.Stop() |
| 967 | + |
| 968 | + require.NoError( |
| 969 | + ht.T, tempMiner.SetUp(false, 0), "unable to setup mining node", |
| 970 | + ) |
| 971 | + |
| 972 | + // We start by connecting the new miner to our original miner, such |
| 973 | + // that it will sync to our original chain. |
| 974 | + err = ht.Miner.Client.Node( |
| 975 | + btcjson.NConnect, tempMiner.P2PAddress(), &temp, |
| 976 | + ) |
| 977 | + require.NoError(ht.T, err, "unable to connect node") |
| 978 | + |
| 979 | + nodeSlice := []*rpctest.Harness{ht.Miner.Harness, tempMiner.Harness} |
| 980 | + err = rpctest.JoinNodes(nodeSlice, rpctest.Blocks) |
| 981 | + require.NoError(ht.T, err, "unable to join node on blocks") |
| 982 | + |
| 983 | + // The two miners should be on the same block height. |
| 984 | + assertMinerBlockHeightDelta(ht, ht.Miner, tempMiner, 0) |
| 985 | + |
| 986 | + // We disconnect the two miners, such that we can mine two chains and |
| 987 | + // cause a reorg later. |
| 988 | + err = ht.Miner.Client.Node( |
| 989 | + btcjson.NDisconnect, tempMiner.P2PAddress(), &temp, |
| 990 | + ) |
| 991 | + require.NoError(ht.T, err, "unable to remove node") |
| 992 | + |
| 993 | + // We now cause a fork, by letting our original miner mine 1 block and |
| 994 | + // our new miner will mine 2. |
| 995 | + ht.MineBlocks(1) |
| 996 | + _, err = tempMiner.Client.Generate(2) |
| 997 | + require.NoError(ht.T, err, "unable to generate blocks") |
| 998 | + |
| 999 | + // Ensure the temp miner is one block ahead. |
| 1000 | + assertMinerBlockHeightDelta(ht, ht.Miner, tempMiner, 1) |
| 1001 | + |
| 1002 | + // Wait for Carol to sync to the original miner's chain. |
| 1003 | + _, minerHeight, err := ht.Miner.Client.GetBestBlock() |
| 1004 | + require.NoError(ht.T, err, "unable to get current blockheight") |
| 1005 | + |
| 1006 | + ht.WaitForNodeBlockHeight(carol, minerHeight) |
| 1007 | + |
| 1008 | + // Now we'll disconnect Carol's chain backend from the original miner |
| 1009 | + // so that we can connect the two miners together and let the original |
| 1010 | + // miner sync to the temp miner's chain. |
| 1011 | + ht.DisconnectMiner() |
| 1012 | + |
| 1013 | + // Connecting to the temporary miner should cause the original miner to |
| 1014 | + // reorg to the longer chain. |
| 1015 | + err = ht.Miner.Client.Node( |
| 1016 | + btcjson.NConnect, tempMiner.P2PAddress(), &temp, |
| 1017 | + ) |
| 1018 | + require.NoError(ht.T, err, "unable to remove node") |
| 1019 | + |
| 1020 | + nodes := []*rpctest.Harness{tempMiner.Harness, ht.Miner.Harness} |
| 1021 | + err = rpctest.JoinNodes(nodes, rpctest.Blocks) |
| 1022 | + require.NoError(ht.T, err, "unable to join node on blocks") |
| 1023 | + |
| 1024 | + // They should now be on the same chain. |
| 1025 | + assertMinerBlockHeightDelta(ht, ht.Miner, tempMiner, 0) |
| 1026 | + |
| 1027 | + // Now we disconnect the two miners and reconnect our original chain |
| 1028 | + // backend. |
| 1029 | + err = ht.Miner.Client.Node( |
| 1030 | + btcjson.NDisconnect, tempMiner.P2PAddress(), &temp, |
| 1031 | + ) |
| 1032 | + require.NoError(ht.T, err, "unable to remove node") |
| 1033 | + |
| 1034 | + ht.ConnectMiner() |
| 1035 | + |
| 1036 | + // This should have caused a reorg and Alice should sync to the new |
| 1037 | + // chain. |
| 1038 | + _, tempMinerHeight, err := tempMiner.Client.GetBestBlock() |
| 1039 | + require.NoError(ht.T, err, "unable to get current blockheight") |
| 1040 | + |
| 1041 | + ht.WaitForNodeBlockHeight(carol, tempMinerHeight) |
| 1042 | + |
| 1043 | + err = wait.Predicate(func() bool { |
| 1044 | + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) |
| 1045 | + |
| 1046 | + _, err = carol.RPC.LN.GetChanInfo(ctxt, &lnrpc.ChanInfoRequest{ |
| 1047 | + ChanId: aliasmgr.StartingAlias.ToUint64(), |
| 1048 | + }) |
| 1049 | + |
| 1050 | + return err == nil |
| 1051 | + }, defaultTimeout) |
| 1052 | + require.NoError(ht.T, err, "carol doesn't have zero-conf edge") |
| 1053 | + |
| 1054 | + // Mine the zero-conf funding transaction so the test doesn't fail. |
| 1055 | + ht.MineBlocks(1) |
| 1056 | +} |
0 commit comments