Skip to content

Commit 3550448

Browse files
committed
htlcswitch+routing: use attributable failures
We now call into the new encryption/decryption methods and provide the received attribution data as an argument. The result is then also returned to our upstream peer.
1 parent b9c6995 commit 3550448

File tree

9 files changed

+256
-66
lines changed

9 files changed

+256
-66
lines changed

htlcswitch/failure.go

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package htlcswitch
33
import (
44
"bytes"
55
"fmt"
6+
"strings"
67

78
sphinx "github.com/lightningnetwork/lightning-onion"
89
"github.com/lightningnetwork/lnd/htlcswitch/hop"
@@ -92,6 +93,13 @@ type ForwardingError struct {
9293
// be nil in the case where we fail to decode failure message sent by
9394
// a peer.
9495
msg lnwire.FailureMessage
96+
97+
// HoldTimes is an array of hold times (in ms) as reported from the
98+
// nodes of the route. It is the time for which a node held the HTLC for
99+
// from that nodes local perspective. The first element corresponds to
100+
// the first node after the sender node, with greater indices indicating
101+
// nodes further down the route.
102+
HoldTimes []uint32
95103
}
96104

97105
// WireMessage extracts a valid wire failure message from an internal
@@ -116,11 +124,12 @@ func (f *ForwardingError) Error() string {
116124
// NewForwardingError creates a new payment error which wraps a wire error
117125
// with additional metadata.
118126
func NewForwardingError(failure lnwire.FailureMessage,
119-
index int) *ForwardingError {
127+
index int, holdTimes []uint32) *ForwardingError {
120128

121129
return &ForwardingError{
122130
FailureSourceIdx: index,
123131
msg: failure,
132+
HoldTimes: holdTimes,
124133
}
125134
}
126135

@@ -140,7 +149,7 @@ type ErrorDecrypter interface {
140149
// hop, to the source of the error. A fully populated
141150
// lnwire.FailureMessage is returned along with the source of the
142151
// error.
143-
DecryptError(lnwire.OpaqueReason) (*ForwardingError, error)
152+
DecryptError(lnwire.OpaqueReason, []byte) (*ForwardingError, error)
144153
}
145154

146155
// UnknownEncrypterType is an error message used to signal that an unexpected
@@ -160,37 +169,70 @@ type OnionErrorDecrypter interface {
160169
// node where error have occurred. As a result, in order to decrypt the
161170
// error we need get all shared secret and apply decryption in the
162171
// reverse order.
163-
DecryptError(encryptedData, _ []byte, _ bool) (*sphinx.DecryptedError, error)
172+
DecryptError(encryptedData, attrData []byte) (*sphinx.DecryptedError,
173+
error)
164174
}
165175

166176
// SphinxErrorDecrypter wraps the sphinx data SphinxErrorDecrypter and maps the
167177
// returned errors to concrete lnwire.FailureMessage instances.
168178
type SphinxErrorDecrypter struct {
169-
OnionErrorDecrypter
179+
decrypter *sphinx.OnionErrorDecrypter
180+
}
181+
182+
// NewSphinxErrorDecrypter instantiates a new error decrypter.
183+
func NewSphinxErrorDecrypter(circuit *sphinx.Circuit) *SphinxErrorDecrypter {
184+
return &SphinxErrorDecrypter{
185+
decrypter: sphinx.NewOnionErrorDecrypter(
186+
circuit, hop.AttrErrorStruct,
187+
),
188+
}
170189
}
171190

172191
// DecryptError peels off each layer of onion encryption from the first hop, to
173192
// the source of the error. A fully populated lnwire.FailureMessage is returned
174193
// along with the source of the error.
175194
//
176195
// NOTE: Part of the ErrorDecrypter interface.
177-
func (s *SphinxErrorDecrypter) DecryptError(reason lnwire.OpaqueReason) (
178-
*ForwardingError, error) {
179-
180-
failure, err := s.OnionErrorDecrypter.DecryptError(reason, nil, false)
196+
func (s *SphinxErrorDecrypter) DecryptError(reason lnwire.OpaqueReason,
197+
attrData []byte) (*ForwardingError, error) {
198+
199+
// We do not set the strict attribution flag, as we want to account for
200+
// the grace period during which nodes are still upgrading to support
201+
// this feature. If set prematurely it can lead to early blame of our
202+
// direct peers that may not support this feature yet, blacklisting our
203+
// channels and failing our payments.
204+
attrErr, err := s.decrypter.DecryptError(reason, attrData, false)
181205
if err != nil {
182206
return nil, err
183207
}
184208

209+
var holdTimes []string
210+
for _, payload := range attrErr.HoldTimes {
211+
// Read hold time.
212+
holdTimeMs := payload
213+
214+
holdTimes = append(
215+
holdTimes,
216+
fmt.Sprintf("%v", holdTimeMs),
217+
)
218+
}
219+
220+
// For now just log the hold times, the collector of the payment result
221+
// should handle this in a more sophisticated way.
222+
log.Tracef("Extracted hold times from onion error: %v",
223+
strings.Join(holdTimes, "/"))
224+
185225
// Decode the failure. If an error occurs, we leave the failure message
186226
// field nil.
187-
r := bytes.NewReader(failure.Message)
227+
r := bytes.NewReader(attrErr.Message)
188228
failureMsg, err := lnwire.DecodeFailure(r, 0)
189229
if err != nil {
190-
return NewUnknownForwardingError(failure.SenderIdx), nil
230+
return NewUnknownForwardingError(attrErr.SenderIdx), nil
191231
}
192232

193-
return NewForwardingError(failureMsg, failure.SenderIdx), nil
233+
return NewForwardingError(
234+
failureMsg, attrErr.SenderIdx, attrErr.HoldTimes,
235+
), nil
194236
}
195237

196238
// A compile time check to ensure ErrorDecrypter implements the Deobfuscator

htlcswitch/hop/error_encryptor.go

Lines changed: 105 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@ package hop
22

33
import (
44
"bytes"
5+
"encoding/binary"
6+
"errors"
57
"fmt"
68
"io"
9+
"time"
710

811
"github.com/btcsuite/btcd/btcec/v2"
912
sphinx "github.com/lightningnetwork/lightning-onion"
1013
"github.com/lightningnetwork/lnd/lnwire"
14+
"github.com/lightningnetwork/lnd/tlv"
1115
)
1216

1317
// EncrypterType establishes an enum used in serialization to indicate how to
@@ -37,6 +41,25 @@ const (
3741
// the same functionality as a EncrypterTypeSphinx, but is used to mark
3842
// our special-case error handling.
3943
EncrypterTypeRelaying = 4
44+
45+
// A set of tlv type definitions used to serialize the encrypter to the
46+
// database.
47+
//
48+
// NOTE: A migration should be added whenever this list changes. This
49+
// prevents against the database being rolled back to an older
50+
// format where the surrounding logic might assume a different set of
51+
// fields are known.
52+
creationTimeType tlv.Type = 0
53+
)
54+
55+
// AttrErrorStruct defines the message structure for an attributable error. Use
56+
// a maximum route length of 20, a fixed payload length of 4 bytes to
57+
// accommodate the a 32-bit hold time in milliseconds and use 4 byte hmacs.
58+
// Total size including a 256 byte message from the error source works out to
59+
// 1200 bytes.
60+
var (
61+
AttrErrorStruct = sphinx.NewAttrErrorStructure(20, 4, 4)
62+
byteOrder = binary.BigEndian
4063
)
4164

4265
// IsBlinded returns a boolean indicating whether the error encrypter belongs
@@ -58,8 +81,8 @@ type ErrorEncrypter interface {
5881
// encrypted opaque failure reason. This method will be used at the
5982
// source that the error occurs. It differs from IntermediateEncrypt
6083
// slightly, in that it computes a proper MAC over the error.
61-
EncryptFirstHop(lnwire.FailureMessage) (lnwire.OpaqueReason, []byte,
62-
error)
84+
EncryptFirstHop(lnwire.FailureMessage) (lnwire.OpaqueReason,
85+
[]byte, error)
6386

6487
// EncryptMalformedError is similar to EncryptFirstHop (it adds the
6588
// MAC), but it accepts an opaque failure reason rather than a failure
@@ -104,6 +127,7 @@ type SphinxErrorEncrypter struct {
104127
*sphinx.OnionErrorEncrypter
105128

106129
EphemeralKey *btcec.PublicKey
130+
CreatedAt time.Time
107131
}
108132

109133
// NewSphinxErrorEncrypterUninitialized initializes a blank sphinx error
@@ -115,8 +139,7 @@ type SphinxErrorEncrypter struct {
115139
// OnionProcessor.
116140
func NewSphinxErrorEncrypterUninitialized() *SphinxErrorEncrypter {
117141
return &SphinxErrorEncrypter{
118-
OnionErrorEncrypter: nil,
119-
EphemeralKey: &btcec.PublicKey{},
142+
EphemeralKey: &btcec.PublicKey{},
120143
}
121144
}
122145

@@ -131,15 +154,35 @@ func NewSphinxErrorEncrypter(ephemeralKey *btcec.PublicKey,
131154
EphemeralKey: ephemeralKey,
132155
}
133156

157+
// Set creation time rounded to nanosecond to avoid differences after
158+
// serialization.
159+
encrypter.CreatedAt = time.Now().Truncate(time.Nanosecond)
160+
134161
encrypter.initialize(sharedSecret)
135162

136163
return encrypter
137164
}
138165

166+
// getHoldTimeMs returns the hold time in milliseconds since the first
167+
// instantiation of this sphinx error encrypter.
168+
func (s *SphinxErrorEncrypter) getHoldTimeMs() uint32 {
169+
return uint32(time.Since(s.CreatedAt).Milliseconds())
170+
}
171+
172+
// encrypt is a thin wrapper around the main encryption method, mainly used to
173+
// automatically derive the hold time to encode in the attribution structure.
174+
func (s *SphinxErrorEncrypter) encrypt(initial bool,
175+
data, attrData []byte) (lnwire.OpaqueReason, []byte, error) {
176+
177+
holdTimeMs := s.getHoldTimeMs()
178+
179+
return s.EncryptError(initial, data, attrData, holdTimeMs)
180+
}
181+
139182
// initialize creates the underlying instance of the sphinx error encrypter.
140183
func (s *SphinxErrorEncrypter) initialize(sharedSecret sphinx.Hash256) {
141184
s.OnionErrorEncrypter = sphinx.NewOnionErrorEncrypter(
142-
sharedSecret, nil,
185+
sharedSecret, AttrErrorStruct,
143186
)
144187
}
145188

@@ -157,9 +200,7 @@ func (s *SphinxErrorEncrypter) EncryptFirstHop(
157200
return nil, nil, err
158201
}
159202

160-
// We pass a true as the first parameter to indicate that a MAC should
161-
// be added.
162-
return s.EncryptError(true, b.Bytes(), nil, 0)
203+
return s.encrypt(true, b.Bytes(), nil)
163204
}
164205

165206
// EncryptMalformedError is similar to EncryptFirstHop (it adds the MAC), but
@@ -172,7 +213,7 @@ func (s *SphinxErrorEncrypter) EncryptFirstHop(
172213
func (s *SphinxErrorEncrypter) EncryptMalformedError(
173214
reason lnwire.OpaqueReason) (lnwire.OpaqueReason, []byte, error) {
174215

175-
return s.EncryptError(true, reason, nil, 0)
216+
return s.encrypt(true, reason, nil)
176217
}
177218

178219
// IntermediateEncrypt wraps an already encrypted opaque reason error in an
@@ -183,10 +224,25 @@ func (s *SphinxErrorEncrypter) EncryptMalformedError(
183224
//
184225
// NOTE: Part of the ErrorEncrypter interface.
185226
func (s *SphinxErrorEncrypter) IntermediateEncrypt(
186-
reason lnwire.OpaqueReason, _ []byte) (lnwire.OpaqueReason, []byte,
187-
error) {
227+
reason lnwire.OpaqueReason, attrData []byte) (lnwire.OpaqueReason,
228+
[]byte, error) {
229+
230+
encrypted, attrData, err := s.encrypt(false, reason, attrData)
231+
232+
switch {
233+
// If the structure of the error received from downstream is invalid,
234+
// then generate a new attribution structure so that the sender is able
235+
// to penalize the offending node.
236+
case errors.Is(err, sphinx.ErrInvalidAttrStructure):
237+
// Preserve the error message and initialize fresh attribution
238+
// data.
239+
return s.encrypt(true, reason, nil)
240+
241+
case err != nil:
242+
return lnwire.OpaqueReason{}, nil, err
243+
}
188244

189-
return s.EncryptError(false, reason, nil, 0)
245+
return encrypted, attrData, nil
190246
}
191247

192248
// Type returns the identifier for a sphinx error encrypter.
@@ -199,7 +255,20 @@ func (s *SphinxErrorEncrypter) Type() EncrypterType {
199255
func (s *SphinxErrorEncrypter) Encode(w io.Writer) error {
200256
ephemeral := s.EphemeralKey.SerializeCompressed()
201257
_, err := w.Write(ephemeral)
202-
return err
258+
if err != nil {
259+
return err
260+
}
261+
262+
var creationTime = uint64(s.CreatedAt.UnixNano())
263+
264+
tlvStream, err := tlv.NewStream(
265+
tlv.MakePrimitiveRecord(creationTimeType, &creationTime),
266+
)
267+
if err != nil {
268+
return err
269+
}
270+
271+
return tlvStream.Encode(w)
203272
}
204273

205274
// Decode reconstructs the error encrypter's ephemeral public key from the
@@ -216,6 +285,29 @@ func (s *SphinxErrorEncrypter) Decode(r io.Reader) error {
216285
return err
217286
}
218287

288+
// Try decode attributable error structure.
289+
var creationTime uint64
290+
291+
tlvStream, err := tlv.NewStream(
292+
tlv.MakePrimitiveRecord(creationTimeType, &creationTime),
293+
)
294+
if err != nil {
295+
return err
296+
}
297+
298+
typeMap, err := tlvStream.DecodeWithParsedTypes(r)
299+
if err != nil {
300+
return err
301+
}
302+
303+
// Return early if this encrypter is not for attributable errors.
304+
if len(typeMap) == 0 {
305+
return nil
306+
}
307+
308+
// Set attributable error creation time.
309+
s.CreatedAt = time.Unix(0, int64(creationTime))
310+
219311
return nil
220312
}
221313

htlcswitch/interceptable_switch.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -809,13 +809,19 @@ func (f *interceptedForward) FailWithCode(code lnwire.FailCode) error {
809809

810810
// Encrypt the failure for the first hop. This node will be the origin
811811
// of the failure.
812-
reason, _, err := f.packet.obfuscator.EncryptFirstHop(failureMsg)
812+
reason, attrData, err := f.packet.obfuscator.EncryptFirstHop(failureMsg)
813813
if err != nil {
814814
return fmt.Errorf("failed to encrypt failure reason %w", err)
815815
}
816816

817+
extraData, err := lnwire.AttrDataToExtraData(attrData)
818+
if err != nil {
819+
return err
820+
}
821+
817822
return f.resolve(&lnwire.UpdateFailHTLC{
818-
Reason: reason,
823+
Reason: reason,
824+
ExtraData: extraData,
819825
})
820826
}
821827

htlcswitch/link.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4361,13 +4361,20 @@ func (l *channelLink) sendHTLCError(add lnwire.UpdateAddHTLC,
43614361
sourceRef channeldb.AddRef, failure *LinkError,
43624362
e hop.ErrorEncrypter, isReceive bool) {
43634363

4364-
reason, _, err := e.EncryptFirstHop(failure.WireMessage())
4364+
reason, attrData, err := e.EncryptFirstHop(failure.WireMessage())
43654365
if err != nil {
43664366
l.log.Errorf("unable to obfuscate error: %v", err)
43674367
return
43684368
}
43694369

4370-
err = l.channel.FailHTLC(add.ID, reason, nil, &sourceRef, nil, nil)
4370+
extraData, err := lnwire.AttrDataToExtraData(attrData)
4371+
if err != nil {
4372+
return
4373+
}
4374+
4375+
err = l.channel.FailHTLC(
4376+
add.ID, reason, extraData, &sourceRef, nil, nil,
4377+
)
43714378
if err != nil {
43724379
l.log.Errorf("unable cancel htlc: %v", err)
43734380
return
@@ -4376,7 +4383,7 @@ func (l *channelLink) sendHTLCError(add lnwire.UpdateAddHTLC,
43764383
// Send the appropriate failure message depending on whether we're
43774384
// in a blinded route or not.
43784385
if err := l.sendIncomingHTLCFailureMsg(
4379-
add.ID, e, reason, nil,
4386+
add.ID, e, reason, extraData,
43804387
); err != nil {
43814388
l.log.Errorf("unable to send HTLC failure: %v", err)
43824389
return

0 commit comments

Comments
 (0)