lnd/itest/lnd_network_test.go
Oliver Gugger 9cd7285439
itest+lntest: use system wide unique ports everywhere
With this commit we create a new function that returns system wide
unique ports by using a single file to keep track of previously used
ports. We'll want to use this everywhere whenever we need to listen on a
new, random port during unit or integration tests.
Because we now have a unique source, we don't need to apply the port
offset that was used for the different tranches of parallel running
integration tests before.
2024-03-18 16:13:39 +01:00

251 lines
8.3 KiB
Go

package itest
import (
"fmt"
"net"
"time"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/node"
"github.com/lightningnetwork/lnd/lntest/port"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/stretchr/testify/require"
)
// testNetworkConnectionTimeout checks that the connectiontimeout is taking
// effect. It creates a node with a small connection timeout value, and
// connects it to a non-routable IP address.
func testNetworkConnectionTimeout(ht *lntest.HarnessTest) {
// Bind to a random port on localhost but never actually accept any
// connections. This makes any connection attempts timeout.
l, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(ht, err)
defer l.Close()
var (
// testPub is a random public key for testing only.
testPub = "0332bda7da70fefe4b6ab92f53b3c4f4ee7999" +
"f312284a8e89c8670bb3f67dbee2"
// testHost is the previously bound address that will never
// accept any conns.
testHost = l.Addr().String()
)
// First, test the global timeout settings.
// Create Carol with a connection timeout of 1 millisecond.
carol := ht.NewNode("Carol", []string{"--connectiontimeout=1ms"})
// Try to connect Carol to a non-routable IP address, which should give
// us a timeout error.
req := &lnrpc.ConnectPeerRequest{
Addr: &lnrpc.LightningAddress{
Pubkey: testPub,
Host: testHost,
},
}
// assertTimeoutError asserts that a connection timeout error is
// raised. A context with a default timeout is used to make the
// request. If our customized connection timeout is less than the
// default, we won't see the request context times out, instead a
// network connection timeout will be returned.
assertTimeoutError := func(hn *node.HarnessNode,
req *lnrpc.ConnectPeerRequest) {
err := hn.RPC.ConnectPeerAssertErr(req)
// Check that the network returns a timeout error.
require.Containsf(ht, err.Error(), "i/o timeout",
"expected to get a timeout error, instead got: %v", err)
}
assertTimeoutError(carol, req)
// Second, test timeout on the connect peer request.
// Create Dave with the default timeout setting.
dave := ht.NewNode("Dave", nil)
// Try to connect Dave to a non-routable IP address, using a timeout
// value of 1s, which should give us a timeout error immediately.
req = &lnrpc.ConnectPeerRequest{
Addr: &lnrpc.LightningAddress{
Pubkey: testPub,
Host: testHost,
},
Timeout: 1,
}
assertTimeoutError(dave, req)
}
// testReconnectAfterIPChange verifies that if a persistent inbound node changes
// its listening address then it's peer will still be able to reconnect to it.
func testReconnectAfterIPChange(ht *lntest.HarnessTest) {
// In this test, the following network will be set up. A single
// dash line represents a peer connection and a double dash line
// represents a channel.
// Charlie will create a connection to Dave so that Dave is the inbound
// peer. This will be made a persistent connection for Charlie so that
// Charlie will attempt to reconnect to Dave if Dave restarts.
// A channel will be opened between Dave and Alice to ensure that any
// NodeAnnouncements that Dave sends will reach Alice.
// The connection between Alice and Charlie ensures that Charlie
// receives all of Dave's NodeAnnouncements.
// The desired behaviour is that if Dave changes his P2P IP address then
// Charlie should still be able to reconnect to him.
//
// /------- Charlie <-----\
// | |
// v |
// Dave <===============> Alice
// The first thing we will test is the case where Dave advertises two
// external IP addresses and then switches from the first one listed
// to the second one listed. The desired behaviour is that Charlie will
// attempt both of Dave's advertised addresses when attempting to
// reconnect.
// Create a new node, Charlie.
charlie := ht.NewNode("Charlie", nil)
// We derive an extra port for Dave, and we initialise his node with
// the port advertised as `--externalip` arguments.
ip2 := port.NextAvailablePort()
// Create a new node, Dave, which will initialize a P2P port for him.
daveArgs := []string{fmt.Sprintf("--externalip=127.0.0.1:%d", ip2)}
dave := ht.NewNode("Dave", daveArgs)
// We now have two ports, the initial P2P port from creating the node,
// and the `externalip` specified above.
advertisedAddrs := []string{
fmt.Sprintf("127.0.0.1:%d", dave.Cfg.P2PPort),
fmt.Sprintf("127.0.0.1:%d", ip2),
}
// Connect Alice to Dave and Charlie.
alice := ht.Alice
ht.ConnectNodes(alice, dave)
ht.ConnectNodes(alice, charlie)
// We'll then go ahead and open a channel between Alice and Dave. This
// ensures that Charlie receives the node announcement from Alice as
// part of the announcement broadcast.
chanPoint := ht.OpenChannel(
alice, dave, lntest.OpenChannelParams{Amt: 1000000},
)
// waitForNodeAnnouncement is a closure used to wait on the given graph
// subscription for a node announcement from a node with the given
// public key. It also waits for the node announcement that advertises
// a particular set of addresses.
waitForNodeAnnouncement := func(nodePubKey string, addrs []string) {
err := wait.NoError(func() error {
// Expect to have at least 1 node announcement now.
updates := ht.AssertNumNodeAnns(charlie, nodePubKey, 1)
// Get latest node update from the node.
update := updates[len(updates)-1]
addrMap := make(map[string]bool)
for _, addr := range update.NodeAddresses {
addrMap[addr.GetAddr()] = true
}
// Check that our wanted addresses can be found from
// the node update.
for _, addr := range addrs {
if !addrMap[addr] {
return fmt.Errorf("address %s not "+
"found", addr)
}
}
return nil
}, defaultTimeout)
require.NoError(ht, err, "timeout checking node ann")
}
// Wait for Charlie to receive Dave's initial NodeAnnouncement.
waitForNodeAnnouncement(dave.PubKeyStr, advertisedAddrs)
// Now create a persistent connection between Charlie and Dave with no
// channels. Charlie is the outbound node and Dave is the inbound node.
ht.ConnectNodesPerm(charlie, dave)
// Change Dave's P2P port to the second IP address that he advertised
// and restart his node.
dave.Cfg.P2PPort = ip2
ht.RestartNode(dave)
// assert that Dave and Charlie reconnect successfully after Dave
// changes to his second advertised address.
ht.AssertConnected(dave, charlie)
// Next we test the case where Dave changes his listening address to one
// that was not listed in his original advertised addresses. The desired
// behaviour is that Charlie will update his connection requests to Dave
// when he receives the Node Announcement from Dave with his updated
// address.
// Change Dave's listening port and restart.
dave.Cfg.P2PPort = port.NextAvailablePort()
dave.Cfg.ExtraArgs = []string{
fmt.Sprintf(
"--externalip=127.0.0.1:%d", dave.Cfg.P2PPort,
),
}
ht.RestartNode(dave)
// Show that Charlie does receive Dave's new listening address in
// a Node Announcement.
waitForNodeAnnouncement(
dave.PubKeyStr,
[]string{fmt.Sprintf("127.0.0.1:%d", dave.Cfg.P2PPort)},
)
// assert that Dave and Charlie do reconnect after Dave changes his P2P
// address to one not listed in Dave's original advertised list of
// addresses.
ht.AssertConnected(dave, charlie)
// Finally, close the channel.
ht.CloseChannel(alice, chanPoint)
}
// testAddPeerConfig tests that the "--addpeer" config flag successfully adds
// a new peer.
func testAddPeerConfig(ht *lntest.HarnessTest) {
alice := ht.Alice
info := alice.RPC.GetInfo()
alicePeerAddress := info.Uris[0]
// Create a new node (Carol) with Alice as a peer.
args := []string{fmt.Sprintf("--addpeer=%v", alicePeerAddress)}
carol := ht.NewNode("Carol", args)
// TODO(yy): remove this once the peer conn race is fixed.
time.Sleep(1 * time.Second)
ht.EnsureConnected(alice, carol)
// If we list Carol's peers, Alice should already be
// listed as one, since we specified her using the
// addpeer flag.
listPeersResp := carol.RPC.ListPeers()
parsedPeerAddr, err := lncfg.ParseLNAddressString(
alicePeerAddress, "9735", net.ResolveTCPAddr,
)
require.NoError(ht, err)
parsedKeyStr := fmt.Sprintf(
"%x", parsedPeerAddr.IdentityKey.SerializeCompressed(),
)
require.Equal(ht, parsedKeyStr, listPeersResp.Peers[0].PubKey)
}