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
9 changes: 9 additions & 0 deletions docs/release-notes/release-notes-0.22.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@

# Bug Fixes

* [Fixed incorrect onion error](https://github.com/lightningnetwork/lnd/pull/6603)
returned when forwarding an HTLC to an offline peer. LND was returning
`unknown_next_peer` (which carries the permanent failure bit), causing
senders to permanently remove the channel from their routing graph and
lose routing revenue. The correct error per BOLT-4 is
`temporary_channel_failure` with a `channel_update` reflecting the
disabled channel, allowing senders to retry via a different path while
keeping the channel available for future routing once the peer reconnects.

* Bitcoind outbound peer health checks [now use](https://github.com/lightningnetwork/lnd/pull/10686)
`getnetworkinfo.connections_out` instead of `getpeerinfo`. The same PR also
[clarifies](https://github.com/lightningnetwork/lnd/issues/10568) the ZMQ
Expand Down
31 changes: 26 additions & 5 deletions htlcswitch/switch.go
Original file line number Diff line number Diff line change
Expand Up @@ -2671,6 +2671,25 @@ func (s *Switch) failMailboxUpdate(outgoingScid,
return lnwire.NewTemporaryChannelFailure(update)
}

// temporaryChannelFailure constructs a temporary_channel_failure for the given
// outgoing SCID. It attempts to include the latest channel update so the
// sender can immediately update their routing graph. If no update is
// available it falls back to FailTemporaryNodeFailure.
func (s *Switch) temporaryChannelFailure(
scid lnwire.ShortChannelID) lnwire.FailureMessage {

update := s.failAliasUpdate(scid, false)
if update == nil {
var err error
update, err = s.cfg.FetchLastChannelUpdate(scid)
if err != nil || update == nil {
return &lnwire.FailTemporaryNodeFailure{}
}
}

return lnwire.NewTemporaryChannelFailure(update)
Comment thread
murraystewart96 marked this conversation as resolved.
}

// failAliasUpdate prepares a ChannelUpdate for a failed incoming or outgoing
// HTLC on a channel where the option-scid-alias feature bit was negotiated. If
// the associated channel is not one of these, this function will return nil
Expand Down Expand Up @@ -2886,8 +2905,9 @@ func (s *Switch) handlePacketAdd(packet *htlcPacket,
log.Debugf("unable to find link with "+
"destination %v", packet.outgoingChanID)

// If packet was forwarded from another channel link than we
// should notify this link that some error occurred.
// The link was not found in the forwarding index — it has been
// removed (e.g. channel closed). Signal to the sender that the
// next hop is unknown.
linkError := NewLinkError(
&lnwire.FailUnknownNextPeer{},
)
Expand All @@ -2909,11 +2929,12 @@ func (s *Switch) handlePacketAdd(packet *htlcPacket,
for _, link := range interfaceLinks {
var failure *LinkError

// We'll skip any links that aren't yet eligible for
// forwarding.
// We'll skip any links that aren't yet eligible for forwarding.
// Use temporary_channel_failure with latest channel update so the
// sender can update their routing graph.
if !link.EligibleToForward() {
failure = NewDetailedLinkError(
&lnwire.FailUnknownNextPeer{},
s.temporaryChannelFailure(link.ShortChanID()),
OutgoingFailureLinkNotEligible,
)
} else {
Expand Down
2 changes: 1 addition & 1 deletion htlcswitch/switch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2143,7 +2143,7 @@ func TestSkipIneligibleLinksMultiHopForward(t *testing.T) {
// None of the channels is eligible.
{
name: "not eligible",
expectedReply: lnwire.CodeUnknownNextPeer,
expectedReply: lnwire.CodeTemporaryChannelFailure,
},

// Channel one has a policy failure and the other channel isn't
Expand Down
Loading