Files
lnd/itest/lnd_access_perm_test.go
2025-06-19 16:05:59 +08:00

342 lines
11 KiB
Go

package itest
import (
"github.com/lightningnetwork/lnd/lntest"
"github.com/stretchr/testify/require"
)
var peerConnTestCases = []*lntest.TestCase{
{
Name: "restricted on inbound",
TestFunc: testPeerConnRestrictedOnInbound,
},
{
Name: "already connected",
TestFunc: testPeerConnAlreadyConnected,
},
{
Name: "unlimited outbound",
TestFunc: testPeerConnUnlimitedOutbound,
},
{
Name: "upgrade access perm",
TestFunc: testPeerConnUpgradeAccessPerm,
},
{
Name: "node restart",
TestFunc: testPeerConnNodeRestart,
},
{
Name: "peer reconnect",
TestFunc: testPeerConnPeerReconnect,
},
}
// testPeerConnRestrictedOnInbound checks that when the `num-restricted-slots`
// is reached, no more inbound connection is allowed. In addition, when a slot
// becomes available, new inbound connection should be allowed.
func testPeerConnRestrictedOnInbound(ht *lntest.HarnessTest) {
args := []string{"--num-restricted-slots=1"}
// Create a new node with only one slot available.
alice := ht.NewNode("Alice", args)
// Create two nodes Bob and Carol. Bob will connect to Alice first,
// taking up Alice's only slot, and we expect the connection from Carol
// to Alice to be failed.
bob := ht.NewNode("Bob", nil)
carol := ht.NewNode("Carol", nil)
// Bob now connects to Alice - from Alice's PoV, this is her inbound
// connection from Bob. We expect Bob to connect to Alice successfully.
_, err := ht.ConnectNodesNoAssert(bob, alice)
require.NoError(ht, err)
ht.AssertConnected(alice, bob)
// Carol now connects to Alice - from Alice's PoV, this is her inbound
// connection from Carol. Carol's connection should be failed.
_, err = ht.ConnectNodesNoAssert(carol, alice)
require.NoError(ht, err)
ht.AssertNotConnected(alice, carol)
// Bob now disconnects, which will free up Alice's slot.
ht.DisconnectNodes(bob, alice)
// Carol connects again - since Alice's slot is freed, this connection
// should succeed.
_, err = ht.ConnectNodesNoAssert(carol, alice)
require.NoError(ht, err)
ht.AssertConnected(alice, carol)
ht.AssertNotConnected(alice, bob)
}
// testPeerConnAlreadyConnected checks that when there's already a connection
// alive, another attempt to connect doesn't take up the slot or kill the
// existing connection, in specific,
// - When Alice has an inbound connection from Bob, another inbound connection
// from Bob will be noop.
// - When Bob has an outbound connection to Alice, another outbound connection
// to Alice will be noop.
//
// In this test we will create nodes using the dev flag `unsafeconnect` to mimic
// the behaviour of persistent/permanent peer connections, where it's likely
// inbound and outbound connections are happening at the same time.
func testPeerConnAlreadyConnected(ht *lntest.HarnessTest) {
args := []string{
"--num-restricted-slots=1",
}
// Create a new node with two slots available.
alice := ht.NewNode("Alice", args)
bob := ht.NewNode("Bob", []string{"--dev.unsafeconnect"})
carol := ht.NewNode("Carol", nil)
// Bob now connects to Alice - from Alice's PoV, this is her inbound
// connection from Bob. We expect Bob to connect to Alice successfully.
_, err := ht.ConnectNodesNoAssert(bob, alice)
require.NoError(ht, err)
ht.AssertConnected(alice, bob)
// Assert Alice's slot has been filled up by connecting Carol to Alice.
_, err = ht.ConnectNodesNoAssert(carol, alice)
require.NoError(ht, err)
ht.AssertNotConnected(alice, carol)
// Bob connects to Alice again - from Alice's PoV, she already has an
// inbound connection from Bob so this connection attempt should be
// noop. We expect Alice and Bob to stay connected.
//
// TODO(yy): There's no way to assert the connection stays the same atm,
// need to update the RPC to return the socket port. As for now we need
// to visit the logs to check the connection is the same or not.
_, err = ht.ConnectNodesNoAssert(bob, alice)
require.NoError(ht, err)
ht.AssertConnected(alice, bob)
}
// testPeerConnUnlimitedOutbound checks that for outbound connections, they are
// not restricted by the `num-restricted-slots` config.
func testPeerConnUnlimitedOutbound(ht *lntest.HarnessTest) {
args := []string{"--num-restricted-slots=1"}
// Create a new node with one slot available.
alice := ht.NewNode("Alice", args)
// Create three nodes. Alice will have an inbound connection with Bob
// and outbound connections with Carol and Dave.
bob := ht.NewNode("Bob", args)
carol := ht.NewNode("Carol", args)
dave := ht.NewNode("Dave", args)
// Bob now connects to Alice - from Alice's PoV, this is her inbound
// connection from Bob. We expect Bob to connect to Alice successfully.
_, err := ht.ConnectNodesNoAssert(bob, alice)
require.NoError(ht, err)
ht.AssertConnected(alice, bob)
// Assert Alice's slot has been filled up by connecting Carol to Alice.
_, err = ht.ConnectNodesNoAssert(carol, alice)
require.NoError(ht, err)
ht.AssertNotConnected(alice, carol)
// Now let Alice make an outbound connection to Carol and assert it's
// succeeded.
_, err = ht.ConnectNodesNoAssert(alice, carol)
require.NoError(ht, err)
ht.AssertConnected(alice, carol)
// Alice can also make an outbound connection to Dave and assert it's
// succeeded since outbound connection is not restricted.
_, err = ht.ConnectNodesNoAssert(alice, dave)
require.NoError(ht, err)
ht.AssertConnected(alice, dave)
}
// testPeerConnUpgradeAccessPerm checks that when a peer has, or used to have a
// channel with Alice, it won't use her restricted slots.
func testPeerConnUpgradeAccessPerm(ht *lntest.HarnessTest) {
args := []string{"--num-restricted-slots=1"}
// Create a new node with one slot available.
alice := ht.NewNodeWithCoins("Alice", args)
bob := ht.NewNode("Bob", nil)
carol := ht.NewNodeWithCoins("Carol", nil)
dave := ht.NewNode("Dave", nil)
eve := ht.NewNode("Eve", nil)
// Connect Bob to Alice, which will use Alice's available slot.
ht.ConnectNodes(bob, alice)
// Assert Alice's slot has been filled up by connecting Carol to Alice.
_, err := ht.ConnectNodesNoAssert(carol, alice)
require.NoError(ht, err)
ht.AssertNotConnected(alice, carol)
// Open a channel from Alice to Bob and let it stay pending.
p := lntest.OpenChannelParams{
Amt: chanAmt,
}
pendingUpdate := ht.OpenChannelAssertPending(alice, bob, p)
cpAB := lntest.ChanPointFromPendingUpdate(pendingUpdate)
// Connect Carol to Alice - since Bob now has a pending channel with
// Alice, there's a slot available for Carol to connect.
ht.ConnectNodes(carol, alice)
// Open a channel from Carol to Alice and let it stay pending.
pendingUpdate = ht.OpenChannelAssertPending(carol, alice, p)
cpCA := lntest.ChanPointFromPendingUpdate(pendingUpdate)
// Mine the funding txns.
ht.MineBlocksAndAssertNumTxes(1, 2)
// Dave should be able to connect to Alice.
ht.ConnectNodes(dave, alice)
// Open a channel from Alice to Dave, which will free up Alice's slot.
cpAD := ht.OpenChannel(alice, dave, p)
// Close the channels.
ht.CloseChannel(bob, cpAB)
ht.CloseChannel(carol, cpCA)
ht.CloseChannel(dave, cpAD)
// Alice should have one slot available, connect Eve to Alice now.
ht.ConnectNodes(eve, alice)
}
// testPeerConnNodeRestart checks that when a peer has or used to have a channel
// with Alice, when Alice restarts, she should still have the available slot for
// more inbound connections.
func testPeerConnNodeRestart(ht *lntest.HarnessTest) {
args := []string{"--num-restricted-slots=1"}
// Create a new node with one slot available.
alice := ht.NewNodeWithCoins("Alice", args)
bob := ht.NewNode("Bob", []string{"--dev.unsafeconnect"})
carol := ht.NewNode("Carol", nil)
// Connect Bob to Alice, which will use Alice's available slot.
ht.ConnectNodes(bob, alice)
// Assert Alice's slot has been filled up by connecting Carol to Alice.
_, err := ht.ConnectNodesNoAssert(carol, alice)
require.NoError(ht, err)
ht.AssertNotConnected(alice, carol)
// Restart Alice, which will reset her current slots, allowing Bob to
// connect to her again.
ht.RestartNode(alice)
ht.ConnectNodes(bob, alice)
// Open a channel from Alice to Bob and let it stay pending.
p := lntest.OpenChannelParams{
Amt: chanAmt,
}
pendingUpdate := ht.OpenChannelAssertPending(alice, bob, p)
cp := lntest.ChanPointFromPendingUpdate(pendingUpdate)
// Restart Alice and let Bob connect to her - since Bob has a pending
// channel, it shouldn't take any slot.
ht.RestartNode(alice)
ht.ConnectNodes(bob, alice)
// Connect Carol to Alice since Alice has a free slot.
ht.ConnectNodes(carol, alice)
// Mine the funding tx.
ht.MineBlocksAndAssertNumTxes(1, 1)
// Restart Alice and let Bob connect to her - since Bob has an open
// channel, it shouldn't take any slot.
ht.RestartNode(alice)
ht.ConnectNodes(bob, alice)
// Connect Carol to Alice since Alice has a free slot.
ht.ConnectNodes(carol, alice)
// Close the channel.
ht.CloseChannel(alice, cp)
// Restart Alice and let Bob connect to her - since Bob has a closed
// channel, it shouldn't take any slot.
ht.RestartNode(alice)
ht.ConnectNodes(bob, alice)
// Connect Carol to Alice since Alice has a free slot.
ht.ConnectNodes(carol, alice)
}
// testPeerConnPeerReconnect checks that when a peer has or used to have a
// channel with Alice, it will not account for the restricted slot during
// reconnection.
func testPeerConnPeerReconnect(ht *lntest.HarnessTest) {
args := []string{"--num-restricted-slots=1"}
// Create a new node with one slot available.
alice := ht.NewNodeWithCoins("Alice", args)
bob := ht.NewNode("Bob", []string{"--dev.unsafeconnect"})
carol := ht.NewNode("Carol", nil)
// Connect Bob to Alice, which will use Alice's available slot.
ht.ConnectNodes(bob, alice)
// Let Bob connect to Alice again, which put Bob in Alice's
// `scheduledPeerConnection` map.
ht.ConnectNodes(bob, alice)
// Assert Alice's slot has been filled up by connecting Carol to Alice.
_, err := ht.ConnectNodesNoAssert(carol, alice)
require.NoError(ht, err)
ht.AssertNotConnected(alice, carol)
// Open a channel from Alice to Bob and let it stay pending.
p := lntest.OpenChannelParams{
Amt: chanAmt,
}
pendingUpdate := ht.OpenChannelAssertPending(alice, bob, p)
cp := lntest.ChanPointFromPendingUpdate(pendingUpdate)
// Bob now perform a reconnection - since Bob has a pending channel, it
// shouldn't take any slot.
ht.DisconnectNodes(bob, alice)
ht.AssertNotConnected(alice, bob)
ht.ConnectNodes(bob, alice)
// Connect Carol to Alice since Alice has a free slot.
ht.ConnectNodes(carol, alice)
// Once the above connection succeeded we let Carol disconnect to free
// the slot.
ht.DisconnectNodes(carol, alice)
// Mine the funding tx.
ht.MineBlocksAndAssertNumTxes(1, 1)
// Bob now perform a reconnection - since Bob has a pending channel, it
// shouldn't take any slot.
ht.DisconnectNodes(bob, alice)
ht.AssertNotConnected(alice, bob)
ht.ConnectNodes(bob, alice)
// Connect Carol to Alice since Alice has a free slot.
ht.ConnectNodes(carol, alice)
// Once the above connection succeeded we let Carol disconnect to free
// the slot.
ht.DisconnectNodes(carol, alice)
// Close the channel.
ht.CloseChannel(alice, cp)
// Bob now perform a reconnection - since Bob has a pending channel, it
// shouldn't take any slot.
ht.DisconnectNodes(bob, alice)
ht.AssertNotConnected(alice, bob)
ht.ConnectNodes(bob, alice)
// Connect Carol to Alice since Alice has a free slot.
ht.ConnectNodes(carol, alice)
}