Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions channeldb/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ var (
// specific identity can't be found.
ErrNodeNotFound = fmt.Errorf("link node with target identity not found")

// ErrNodeAddressNotFound is returned when a LinkNode entry exists
// for the target peer, but the requested address is not among its
// stored addresses.
ErrNodeAddressNotFound = fmt.Errorf("address not stored for peer")

// ErrLastPeerAddress is returned by RemoveAddressForPeer when the
// caller has not opted in to deleting the LinkNode entry (via
// allowLast) and the requested address is the last stored address
// for the peer.
ErrLastPeerAddress = fmt.Errorf("refusing to remove last stored " +
"address for peer")

// ErrChannelNotFound is returned when we attempt to locate a channel
// for a specific chain, but it is not found.
ErrChannelNotFound = fmt.Errorf("channel not found")
Expand Down
96 changes: 96 additions & 0 deletions channeldb/nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,62 @@ func (l *LinkNodeDB) CreateLinkNodes(tx kvdb.RwTx,
return createNodes(tx)
}

// RemoveAddressForPeer removes addr from the peer's stored LinkNode
// address list.
//
// If the removal would leave the LinkNode with no addresses (i.e. addr
// was the last stored address for the peer):
// - if allowLast is true, the LinkNode entry is deleted and
// deletedEntry is set to true.
// - if allowLast is false, ErrLastPeerAddress is returned and the
// LinkNode is left untouched.
//
// Returns ErrNodeNotFound if no LinkNode exists for the peer, and
// ErrNodeAddressNotFound if the LinkNode exists but does not contain
// addr.
func (l *LinkNodeDB) RemoveAddressForPeer(pub *btcec.PublicKey,
addr net.Addr, allowLast bool) (deletedEntry bool, err error) {

err = kvdb.Update(l.backend, func(tx kvdb.RwTx) error {
nodeMetaBucket := tx.ReadWriteBucket(nodeInfoBucket)
if nodeMetaBucket == nil {
return ErrLinkNodesNotFound
}

existing, ferr := fetchLinkNode(tx, pub)
if ferr != nil {
return ferr
}

target := addr.String()
remaining := existing.Addresses[:0]
found := false
for _, a := range existing.Addresses {
if !found && a.String() == target {
found = true
continue
}
remaining = append(remaining, a)
}
if !found {
return ErrNodeAddressNotFound
}

if len(remaining) == 0 {
if !allowLast {
return ErrLastPeerAddress
}
deletedEntry = true
return deleteLinkNode(tx, pub)
}
existing.Addresses = remaining
return putLinkNode(nodeMetaBucket, existing)
}, func() {
deletedEntry = false
})
return deletedEntry, err
}

// DeleteLinkNode removes the link node with the given identity from the
// database.
func (l *LinkNodeDB) DeleteLinkNode(identity *btcec.PublicKey) error {
Expand All @@ -243,6 +299,46 @@ func deleteLinkNode(tx kvdb.RwTx, identity *btcec.PublicKey) error {
return nodeMetaBucket.Delete(pubKey)
}

// AddAddressIfPeerKnown appends addr to the address list of the LinkNode entry
// for identity, but only if an entry already exists. If no LinkNode is stored
// for the peer this is a no-op and returns (false, nil). Duplicate addresses
// are ignored (matched by String()). Returns true if the address was newly
// stored.
func (l *LinkNodeDB) AddAddressIfPeerKnown(identity *btcec.PublicKey,
addr net.Addr) (bool, error) {

var added bool
err := kvdb.Update(l.backend, func(tx kvdb.RwTx) error {
nodeMetaBucket := tx.ReadWriteBucket(nodeInfoBucket)
if nodeMetaBucket == nil {
return nil
}

node, err := fetchLinkNode(tx, identity)
if errors.Is(err, ErrNodeNotFound) {
return nil
}
if err != nil {
return err
}

for _, a := range node.Addresses {
if a.String() == addr.String() {
return nil
}
}

node.Addresses = append(node.Addresses, addr)
added = true

return putLinkNode(nodeMetaBucket, node)
}, func() {
added = false
})

return added, err
}

// FetchLinkNode attempts to lookup the data for a LinkNode based on a target
// identity public key. If a particular LinkNode for the passed identity public
// key cannot be found, then ErrNodeNotFound if returned.
Expand Down
2 changes: 1 addition & 1 deletion chanrestore.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ func (s *server) ConnectPeer(nodePub *btcec.PublicKey, addrs []net.Addr) error {
// Before we connect to the remote peer, we'll remove any connections
// to ensure the new connection is created after this new link/channel
// is known.
if err := s.DisconnectPeer(nodePub); err != nil {
if err := s.DisconnectPeer(nodePub, false, false); err != nil {
ltndLog.Infof("Peer(%x) is already connected, proceeding "+
"with chan restore", nodePub.SerializeCompressed())
}
Expand Down
95 changes: 93 additions & 2 deletions cmd/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -978,12 +978,89 @@
Usage: "Disconnect a remote lightning peer identified by " +
"public key.",
ArgsUsage: "<pubkey>",
Description: `
Disconnect a remote lightning peer identified by public key.

By default this only drops the active connection; the peer's stored
LinkNode entry (and any addresses in it) is left in place, so lnd
will still reconnect to the peer on the next restart via its
remembered addresses or the gossip graph.

With --forget_node lnd drops the active connection AND removes the
peer's stored LinkNode entry. The LinkNode delete is refused if open
channels still exist with the peer unless --force is also passed.
--forget_node removes all of the peer's stored addresses at once;
use --forget_address to remove just one.

No reconnect is attempted from our side in the current session
after --forget_node. Note that the remote peer may still re-dial us if
they have us in their own persistent set (typical when a channel
exists), in which case we'll accept the inbound connection. On the
next lnd restart, if the peer still has open channels, the
channel-driven persistence path will reconnect using
NodeAnnouncement addresses from the gossip graph.

For a peer with no open channels, --forget_node is a shortcut for
work that would happen on the next lnd restart anyway: at startup
lnd runs PruneLinkNodes, which deletes stored LinkNode entries for
peers with no open channels. --forget_node on such a peer just
performs that delete now, and additionally drops the live
connection. --force is not required in this case.

With --forget_address <host:port> a single stored address is removed
from the peer's LinkNode entry. The live connection is left alone
unless the removed address happens to be the one currently in use,
in which case the connection is dropped so lnd can re-dial via the
remaining stored/gossip addresses. If the removed address would be
the last stored address for the peer, behaviour depends on whether
there are open channels with the peer: when there are no open
channels the LinkNode entry is deleted automatically; when there
are open channels the request is refused unless --force is also
passed.

--forget_node and --forget_address are mutually exclusive. Passing
--force without either is an error.

Note: after --forget_address --force removes the last stored address
for a channel peer, the peer stays in the persistent-reconnect set
— the peer-termination watcher will fetch the peer's advertised
addresses from its NodeAnnouncement (via the gossip graph) and
reconnect using those. Use --forget_node instead if you want to
remove the last stored address AND stop reconnecting from our side
until lnd next restarts.
Even with --forget_address --force, a channel peer is only truly
orphaned when it also has no NodeAnnouncement.
`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "node_key",
Usage: "The hex-encoded compressed public key of the peer " +
"to disconnect from",
},
cli.BoolFlag{
Name: "forget_node",
Usage: "Also remove the peer's stored LinkNode entry so " +

Check failure on line 1042 in cmd/commands/commands.go

View workflow job for this annotation

GitHub Actions / Lint code

the line is 83 characters long, which exceeds the maximum of 80 characters. (ll)
"lnd will not auto-reconnect on the next " +
"restart. Refused if open channels exist unless " +

Check failure on line 1044 in cmd/commands/commands.go

View workflow job for this annotation

GitHub Actions / Lint code

the line is 83 characters long, which exceeds the maximum of 80 characters. (ll)
"'--force' is also set.",
},
cli.BoolFlag{
Name: "force",
Usage: "Only meaningful with '--forget_node' or " +
"'--forget_address'. With '--forget_node', " +
"bypasses the open-channel guard. With " +
"'--forget_address', allows removing the last " +

Check failure on line 1052 in cmd/commands/commands.go

View workflow job for this annotation

GitHub Actions / Lint code

the line is 81 characters long, which exceeds the maximum of 80 characters. (ll)
"stored address (which deletes the LinkNode " +
"entry itself).",
},
cli.StringFlag{
Name: "forget_address",
Usage: "Remove a single stored address from the peer's " +

Check failure on line 1058 in cmd/commands/commands.go

View workflow job for this annotation

GitHub Actions / Lint code

the line is 82 characters long, which exceeds the maximum of 80 characters. (ll)
"LinkNode entry (does not drop the live " +
"connection unless the removed address is the " +

Check failure on line 1060 in cmd/commands/commands.go

View workflow job for this annotation

GitHub Actions / Lint code

the line is 81 characters long, which exceeds the maximum of 80 characters. (ll)
"one currently in use). Mutually exclusive with " +

Check failure on line 1061 in cmd/commands/commands.go

View workflow job for this annotation

GitHub Actions / Lint code

the line is 83 characters long, which exceeds the maximum of 80 characters. (ll)
"'--forget_node'.",
},
},
Action: actionDecorator(disconnectPeer),
}
Expand All @@ -1004,7 +1081,10 @@
}

req := &lnrpc.DisconnectPeerRequest{
PubKey: pubKey,
PubKey: pubKey,
ForgetNode: ctx.Bool("forget_node"),
Force: ctx.Bool("force"),
ForgetAddress: ctx.String("forget_address"),
}

lnid, err := client.DisconnectPeer(ctxc, req)
Expand Down Expand Up @@ -1633,12 +1713,20 @@
var listPeersCommand = cli.Command{
Name: "listpeers",
Category: "Peers",
Usage: "List all active, currently connected peers.",
Usage: "List currently connected peers, and optionally also " +
"offline peers that lnd is auto-reconnecting to.",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "list_errors",
Usage: "list a full set of most recent errors for the peer",
},
cli.BoolFlag{
Name: "include_offline_persistent_peers",
Usage: "also list peers that lnd is auto-reconnecting " +
"to but is not currently connected to — " +
"either via a stored LinkNode record or " +
"because of an open channel",
},
},
Action: actionDecorator(listPeers),
}
Expand All @@ -1652,6 +1740,9 @@
// specifically requests a full error set, then we will provide it.
req := &lnrpc.ListPeersRequest{
LatestError: !ctx.IsSet("list_errors"),
IncludeOfflinePersistentPeers: ctx.Bool(
"include_offline_persistent_peers",
),
}
resp, err := client.ListPeers(ctxc, req)
if err != nil {
Expand Down
55 changes: 55 additions & 0 deletions docs/release-notes/release-notes-0.22.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,49 @@
the chain backend via bitcoind's `submitpackage`, allowing a zero-fee v3/TRUC
parent to be accepted together with a fee-paying CPFP child.

* [`ListPeers` gains address-source
detail](https://github.com/lightningnetwork/lnd/pull/10973). Each `Peer`
now carries `remembered_addresses` (addresses lnd has stored for the peer),
`gossip_addresses` (from the peer's current `NodeAnnouncement`),
`is_persistent` (true if lnd is auto-reconnecting to the peer), and
`reconnect_pending` (true if a retry is in flight). A new
`ListPeersRequest.include_offline_persistent_peers` flag opts into
surfacing peers in the reconnect set we are not currently connected to.

* [`DisconnectPeer` gains `forget_node`, `force`, and `forget_address`
fields](https://github.com/lightningnetwork/lnd/pull/10976).
`forget_node=true` also removes the peer's stored LinkNode entry so no
auto-reconnect is attempted on the next lnd restart; refused if open
channels exist unless `force=true`. `forget_address="<host:port>"`
removes a single stored address for the peer without dropping the live
connection (unless the removed address is the currently-connected one,
in which case the connection is dropped so lnd can re-dial via remaining
stored/gossip addresses). If `forget_address` would remove the last
stored address for the peer, behaviour depends on whether open channels
exist: with no open channels the LinkNode entry is deleted
automatically; with open channels the request is refused unless
`force=true`. `forget_node` and `forget_address` are mutually exclusive;
`force` on its own is rejected. Note: after `force` removes the last
stored address for a channel peer, the peer-termination watcher falls
back to the peer's NodeAnnouncement addresses for reconnect, so a
channel peer is only truly orphaned when it also has no
NodeAnnouncement.

## lncli Additions

* The `estimateroutefee` command now supports [restricting fee estimates to
specific first-hop outgoing
channels](https://github.com/lightningnetwork/lnd/pull/10501) via the new
`--outgoing_chan_id` flag.

* `lncli listpeers` gains
[`--include_offline_persistent_peers`](https://github.com/lightningnetwork/lnd/pull/10973)
(see the `ListPeers` RPC entry above).

* `lncli disconnect` gains
[`--forget_node`, `--force`, and `--forget_address`](https://github.com/lightningnetwork/lnd/pull/10976)
(see the `DisconnectPeer` RPC entry above).

* A new
[`wallet submitpackage`](https://github.com/lightningnetwork/lnd/pull/10900)
command submits a package of hex-encoded transactions via the new
Expand All @@ -88,6 +124,24 @@

## Functional Updates

* [Peer addresses now auto-persist on
connect](https://github.com/lightningnetwork/lnd/pull/10975) for peers we
have an open channel with. When lnd completes an outbound connection to
a channel peer, it records the dialed address in the peer's `LinkNode`
entry if it isn't already listed. On restart lnd attempts this address
in addition to those from the peer's current `NodeAnnouncement`, so a
channel peer remains reachable across restarts even when the address we
last used to reach them isn't in their gossip entry — because they have
since removed it from their `NodeAnnouncement` but are still listening
on the same host and port, because they moved to a new host and/or port
and we learned the new address out-of-band faster than their
re-broadcast `NodeAnnouncement` could catch up, or because they never
advertised it in gossip in the first place. Only outbound connections
trigger this — for inbound TCP the peer's remote address is an
ephemeral source port rather than a dialable listener. Non-channel
peers are also skipped so casual connections do not grow the on-disk
address list.

## RPC Updates

## lncli Updates
Expand Down Expand Up @@ -148,3 +202,4 @@
* Boris Nagaev
* Erick Cestari
* Jared Tobin
* ZZiigguurraatt
12 changes: 12 additions & 0 deletions itest/list_on_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,18 @@ var allTestCases = []*lntest.TestCase{
Name: "reconnect after ip change",
TestFunc: testReconnectAfterIPChange,
},
{
Name: "listpeers address fields",
TestFunc: testListPeersAddressFields,
},
{
Name: "auto persist channel peer address",
TestFunc: testAutoPersistChannelPeerAddress,
},
{
Name: "disconnect forget",
TestFunc: testDisconnectForget,
},
{
Name: "addpeer config",
TestFunc: testAddPeerConfig,
Expand Down
Loading
Loading