github.com/decred/dcrlnd@v0.7.6/lntest/itest/lnd_network_test.go (about)

     1  package itest
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	stdnet "net"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/decred/dcrlnd"
    11  	"github.com/decred/dcrlnd/lnrpc"
    12  	"github.com/decred/dcrlnd/lntest"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  // testNetworkConnectionTimeout checks that the connectiontimeout is taking
    17  // effect. It creates a node with a small connection timeout value, and connects
    18  // it to a non-routable IP address.
    19  func testNetworkConnectionTimeout(net *lntest.NetworkHarness, t *harnessTest) {
    20  	// Bind to a random port on localhost but never actually accept any
    21  	// connections. This makes any connection attempts timeout.
    22  	l, err := stdnet.Listen("tcp", "127.0.0.1:0")
    23  	require.NoError(t.t, err)
    24  	defer l.Close()
    25  
    26  	var (
    27  		ctxt, _ = context.WithTimeout(
    28  			context.Background(), defaultTimeout,
    29  		)
    30  		// testPub is a random public key for testing only.
    31  		testPub = "0332bda7da70fefe4b6ab92f53b3c4f4ee7999" +
    32  			"f312284a8e89c8670bb3f67dbee2"
    33  
    34  		// testHost is the previously bound addr that will not answer
    35  		// to any conn attempts.
    36  		testHost = l.Addr().String()
    37  	)
    38  
    39  	// First, test the global timeout settings.
    40  	// Create Carol with a connection timeout of 1 millisecond.
    41  	carol := net.NewNode(t.t, "Carol", []string{"--connectiontimeout=1ms"})
    42  	defer shutdownAndAssert(net, t, carol)
    43  
    44  	// Try to connect Carol to a non-routable IP address, which should give
    45  	// us a timeout error.
    46  	req := &lnrpc.ConnectPeerRequest{
    47  		Addr: &lnrpc.LightningAddress{
    48  			Pubkey: testPub,
    49  			Host:   testHost,
    50  		},
    51  	}
    52  	assertTimeoutError(ctxt, t, carol, req)
    53  
    54  	// Second, test timeout on the connect peer request.
    55  	// Create Dave with the default timeout setting.
    56  	dave := net.NewNode(t.t, "Dave", nil)
    57  	defer shutdownAndAssert(net, t, dave)
    58  
    59  	// Try to connect Dave to a non-routable IP address, using a timeout
    60  	// value of 1ms, which should give us a timeout error immediately.
    61  	req = &lnrpc.ConnectPeerRequest{
    62  		Addr: &lnrpc.LightningAddress{
    63  			Pubkey: testPub,
    64  			Host:   testHost,
    65  		},
    66  		Timeout: 1,
    67  	}
    68  	assertTimeoutError(ctxt, t, dave, req)
    69  }
    70  
    71  // testReconnectAfterIPChange verifies that if a persistent inbound node changes
    72  // its listening address then it's peer will still be able to reconnect to it.
    73  func testReconnectAfterIPChange(net *lntest.NetworkHarness, t *harnessTest) {
    74  	// In this test, the following network will be set up. A single
    75  	// dash line represents a peer connection and a double dash line
    76  	// represents a channel.
    77  	// Charlie will create a connection to Dave so that Dave is the inbound
    78  	// peer. This will be made a persistent connection for Charlie so that
    79  	// Charlie will attempt to reconnect to Dave if Dave restarts.
    80  	// A channel will be opened between Dave and Alice to ensure that any
    81  	// NodeAnnouncements that Dave sends will reach Alice.
    82  	// The connection between Alice and Charlie ensures that Charlie
    83  	// receives all of Dave's NodeAnnouncements.
    84  	// The desired behaviour is that if Dave changes his P2P IP address then
    85  	// Charlie should still be able to reconnect to him.
    86  	//
    87  	//    /------- Charlie <-----\
    88  	//    |                      |
    89  	//    v                      |
    90  	//   Dave <===============> Alice
    91  
    92  	// The first thing we will test is the case where Dave advertises two
    93  	// external IP addresses and then switches from the first one listed
    94  	// to the second one listed. The desired behaviour is that Charlie will
    95  	// attempt both of Dave's advertised addresses when attempting to
    96  	// reconnect.
    97  
    98  	// Create a new node, Charlie.
    99  	charlie := net.NewNode(t.t, "Charlie", nil)
   100  	defer shutdownAndAssert(net, t, charlie)
   101  
   102  	// We derive two ports for Dave, and we initialise his node with
   103  	// these ports advertised as `--externalip` arguments.
   104  	ip1 := lntest.NextAvailablePort()
   105  	ip2 := lntest.NextAvailablePort()
   106  
   107  	advertisedAddrs := []string{
   108  		fmt.Sprintf("127.0.0.1:%d", ip1),
   109  		fmt.Sprintf("127.0.0.1:%d", ip2),
   110  	}
   111  
   112  	var daveArgs []string
   113  	for _, addr := range advertisedAddrs {
   114  		daveArgs = append(daveArgs, "--externalip="+addr)
   115  	}
   116  
   117  	// withP2PPort is a helper closure used to set the P2P port that a node
   118  	// should use.
   119  	var withP2PPort = func(port int) lntest.NodeOption {
   120  		return func(cfg *lntest.BaseNodeConfig) {
   121  			cfg.P2PPort = port
   122  		}
   123  	}
   124  
   125  	// Create a new node, Dave, and ensure that his initial P2P port is
   126  	// ip1 derived above.
   127  	dave := net.NewNode(t.t, "Dave", daveArgs, withP2PPort(ip1))
   128  	defer shutdownAndAssert(net, t, dave)
   129  
   130  	// Subscribe to graph notifications from Charlie so that we can tell
   131  	// when he receives Dave's NodeAnnouncements.
   132  	ctxb := context.Background()
   133  	charlieSub := subscribeGraphNotifications(ctxb, t, charlie)
   134  	defer close(charlieSub.quit)
   135  
   136  	// Connect Alice to Dave and Charlie.
   137  	net.ConnectNodes(t.t, net.Alice, dave)
   138  	net.ConnectNodes(t.t, net.Alice, charlie)
   139  
   140  	// We'll then go ahead and open a channel between Alice and Dave. This
   141  	// ensures that Charlie receives the node announcement from Alice as
   142  	// part of the announcement broadcast.
   143  	chanPoint := openChannelAndAssert(
   144  		t, net, net.Alice, dave, lntest.OpenChannelParams{
   145  			Amt: 1000000,
   146  		},
   147  	)
   148  	defer closeChannelAndAssert(t, net, net.Alice, chanPoint, false)
   149  
   150  	// waitForNodeAnnouncement is a closure used to wait on the given graph
   151  	// subscription for a node announcement from a node with the given
   152  	// public key. It also waits for the node announcement that advertises
   153  	// a particular set of addresses.
   154  	waitForNodeAnnouncement := func(graphSub graphSubscription,
   155  		nodePubKey string, addrs []string) {
   156  
   157  		for {
   158  			select {
   159  			case graphUpdate := <-graphSub.updateChan:
   160  			nextUpdate:
   161  				for _, update := range graphUpdate.NodeUpdates {
   162  					if update.IdentityKey != nodePubKey {
   163  						continue
   164  					}
   165  
   166  					addrMap := make(map[string]bool)
   167  					for _, addr := range update.NodeAddresses {
   168  						addrMap[addr.GetAddr()] = true
   169  					}
   170  
   171  					for _, addr := range addrs {
   172  						if !addrMap[addr] {
   173  							continue nextUpdate
   174  						}
   175  					}
   176  
   177  					return
   178  				}
   179  
   180  			case err := <-graphSub.errChan:
   181  				t.Fatalf("unable to recv graph update: %v", err)
   182  
   183  			case <-time.After(defaultTimeout):
   184  				t.Fatalf("did not receive node ann update")
   185  			}
   186  		}
   187  	}
   188  
   189  	// Wait for Charlie to receive Dave's initial NodeAnnouncement.
   190  	waitForNodeAnnouncement(charlieSub, dave.PubKeyStr, advertisedAddrs)
   191  
   192  	// Now create a persistent connection between Charlie and Bob with no
   193  	// channels. Charlie is the outbound node and Bob is the inbound node.
   194  	net.ConnectNodesPerm(t.t, charlie, dave)
   195  
   196  	// Assert that Dave and Charlie are connected
   197  	assertConnected(t, dave, charlie)
   198  
   199  	// Change Dave's P2P port to the second IP address that he advertised
   200  	// and restart his node.
   201  	dave.Cfg.P2PPort = ip2
   202  	err := net.RestartNode(dave, nil)
   203  	require.NoError(t.t, err)
   204  
   205  	// assert that Dave and Charlie reconnect successfully after Dave
   206  	// changes to his second advertised address.
   207  	assertConnected(t, dave, charlie)
   208  
   209  	// Next we test the case where Dave changes his listening address to one
   210  	// that was not listed in his original advertised addresses. The desired
   211  	// behaviour is that Charlie will update his connection requests to Dave
   212  	// when he receives the Node Announcement from Dave with his updated
   213  	// address.
   214  
   215  	// Change Dave's listening port and restart.
   216  	dave.Cfg.P2PPort = lntest.NextAvailablePort()
   217  	dave.Cfg.ExtraArgs = []string{
   218  		fmt.Sprintf(
   219  			"--externalip=127.0.0.1:%d", dave.Cfg.P2PPort,
   220  		),
   221  	}
   222  	err = net.RestartNode(dave, nil)
   223  	require.NoError(t.t, err)
   224  
   225  	// Show that Charlie does receive Dave's new listening address in
   226  	// a Node Announcement.
   227  	waitForNodeAnnouncement(
   228  		charlieSub, dave.PubKeyStr,
   229  		[]string{fmt.Sprintf("127.0.0.1:%d", dave.Cfg.P2PPort)},
   230  	)
   231  
   232  	// assert that Dave and Charlie do reconnect after Dave changes his P2P
   233  	// address to one not listed in Dave's original advertised list of
   234  	// addresses.
   235  	assertConnected(t, dave, charlie)
   236  }
   237  
   238  // assertTimeoutError asserts that a connection timeout error is raised. A
   239  // context with a default timeout is used to make the request. If our customized
   240  // connection timeout is less than the default, we won't see the request context
   241  // times out, instead a network connection timeout will be returned.
   242  func assertTimeoutError(ctxt context.Context, t *harnessTest,
   243  	node *lntest.HarnessNode, req *lnrpc.ConnectPeerRequest) {
   244  
   245  	t.t.Helper()
   246  
   247  	err := connect(ctxt, node, req)
   248  
   249  	// a DeadlineExceeded error will appear in the context if the above
   250  	// ctxtTimeout value is reached.
   251  	require.NoError(t.t, ctxt.Err(), "context time out")
   252  
   253  	// Check that the network returns a timeout error.
   254  	require.Containsf(
   255  		t.t, err.Error(), "i/o timeout",
   256  		"expected to get a timeout error, instead got: %v", err,
   257  	)
   258  }
   259  
   260  func connect(ctxt context.Context, node *lntest.HarnessNode,
   261  	req *lnrpc.ConnectPeerRequest) error {
   262  
   263  	syncTimeout := time.After(15 * time.Second)
   264  	ticker := time.NewTicker(time.Millisecond * 100)
   265  	defer ticker.Stop()
   266  
   267  	for {
   268  		select {
   269  		case <-ticker.C:
   270  			_, err := node.ConnectPeer(ctxt, req)
   271  			// If there's no error, return nil
   272  			if err == nil {
   273  				return err
   274  			}
   275  			// If the error is no ErrServerNotActive, return it.
   276  			// Otherwise, we will retry until timeout.
   277  			if !strings.Contains(err.Error(),
   278  				dcrlnd.ErrServerNotActive.Error()) {
   279  
   280  				return err
   281  			}
   282  		case <-syncTimeout:
   283  			return fmt.Errorf("chain backend did not " +
   284  				"finish syncing")
   285  		}
   286  	}
   287  	return nil
   288  }