diff --git a/htlcswitch/hop/error_encryptor.go b/htlcswitch/hop/error_encryptor.go index 127370e4d..23272ec00 100644 --- a/htlcswitch/hop/error_encryptor.go +++ b/htlcswitch/hop/error_encryptor.go @@ -39,6 +39,12 @@ const ( EncrypterTypeRelaying = 4 ) +// IsBlinded returns a boolean indicating whether the error encrypter belongs +// to a blinded route. +func (e EncrypterType) IsBlinded() bool { + return e == EncrypterTypeIntroduction || e == EncrypterTypeRelaying +} + // ErrorEncrypterExtracter defines a function signature that extracts an // ErrorEncrypter from an sphinx OnionPacket. type ErrorEncrypterExtracter func(*btcec.PublicKey) (ErrorEncrypter, diff --git a/htlcswitch/link.go b/htlcswitch/link.go index b5cba787c..ed3151728 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -1793,8 +1793,20 @@ func (l *channelLink) handleDownstreamPkt(pkt *htlcPacket) { htlc.ID = pkt.incomingHTLCID // We send the HTLC message to the peer which initially created - // the HTLC. - l.cfg.Peer.SendMessage(false, htlc) + // the HTLC. If the incoming blinding point is non-nil, we + // know that we are a relaying node in a blinded path. + // Otherwise, we're either an introduction node or not part of + // a blinded path at all. + if err := l.sendIncomingHTLCFailureMsg( + htlc.ID, + pkt.obfuscator, + htlc.Reason, + ); err != nil { + l.log.Errorf("unable to send HTLC failure: %v", + err) + + return + } // If the packet does not have a link failure set, it failed // further down the route so we notify a forwarding failure. @@ -3720,11 +3732,14 @@ func (l *channelLink) sendHTLCError(pd *lnwallet.PaymentDescriptor, return } - l.cfg.Peer.SendMessage(false, &lnwire.UpdateFailHTLC{ - ChanID: l.ChanID(), - ID: pd.HtlcIndex, - Reason: reason, - }) + // Send the appropriate failure message depending on whether we're + // in a blinded route or not. + if err := l.sendIncomingHTLCFailureMsg( + pd.HtlcIndex, e, reason, + ); err != nil { + l.log.Errorf("unable to send HTLC failure: %v", err) + return + } // Notify a link failure on our incoming link. Outgoing htlc information // is not available at this point, because we have not decrypted the @@ -3753,6 +3768,95 @@ func (l *channelLink) sendHTLCError(pd *lnwallet.PaymentDescriptor, ) } +// sendPeerHTLCFailure handles sending a HTLC failure message back to the +// peer from which the HTLC was received. This function is primarily used to +// handle the special requirements of route blinding, specifically: +// - Forwarding nodes must switch out any errors with MalformedFailHTLC +// - Introduction nodes should return regular HTLC failure messages. +// +// It accepts the original opaque failure, which will be used in the case +// that we're not part of a blinded route and an error encrypter that'll be +// used if we are the introduction node and need to present an error as if +// we're the failing party. +// +// Note: this function does not yet handle special error cases for receiving +// nodes in blinded paths, as LND does not support blinded receives. +func (l *channelLink) sendIncomingHTLCFailureMsg(htlcIndex uint64, + e hop.ErrorEncrypter, + originalFailure lnwire.OpaqueReason) error { + + var msg lnwire.Message + switch { + // Our circuit's error encrypter will be nil if this was a locally + // initiated payment. We can only hit a blinded error for a locally + // initiated payment if we allow ourselves to be picked as the + // introduction node for our own payments and in that case we + // shouldn't reach this code. To prevent the HTLC getting stuck, + // we fail it back and log an error. + // code. + case e == nil: + msg = &lnwire.UpdateFailHTLC{ + ChanID: l.ChanID(), + ID: htlcIndex, + Reason: originalFailure, + } + + l.log.Errorf("Unexpected blinded failure when "+ + "we are the sending node, incoming htlc: %v(%v)", + l.ShortChanID(), htlcIndex) + + // For cleartext hops (ie, non-blinded/normal) we don't need any + // transformation on the error message and can just send the original. + case !e.Type().IsBlinded(): + msg = &lnwire.UpdateFailHTLC{ + ChanID: l.ChanID(), + ID: htlcIndex, + Reason: originalFailure, + } + + // When we're the introduction node, we need to convert the error to + // a UpdateFailHTLC. + case e.Type() == hop.EncrypterTypeIntroduction: + l.log.Debugf("Introduction blinded node switching out failure "+ + "error: %v", htlcIndex) + + // The specification does not require that we set the onion + // blob. + failureMsg := lnwire.NewInvalidBlinding(nil) + reason, err := e.EncryptFirstHop(failureMsg) + if err != nil { + return err + } + + msg = &lnwire.UpdateFailHTLC{ + ChanID: l.ChanID(), + ID: htlcIndex, + Reason: reason, + } + + // If we are a relaying node, we need to switch out any error that + // we've received to a malformed HTLC error. + case e.Type() == hop.EncrypterTypeRelaying: + l.log.Debugf("Relaying blinded node switching out malformed "+ + "error: %v", htlcIndex) + + msg = &lnwire.UpdateFailMalformedHTLC{ + ChanID: l.ChanID(), + ID: htlcIndex, + FailureCode: lnwire.CodeInvalidBlinding, + } + + default: + return fmt.Errorf("unexpected encrypter: %d", e) + } + + if err := l.cfg.Peer.SendMessage(false, msg); err != nil { + l.log.Warnf("Send update fail failed: %v", err) + } + + return nil +} + // sendMalformedHTLCError helper function which sends the malformed HTLC update // to the payment sender. func (l *channelLink) sendMalformedHTLCError(htlcIndex uint64,