Skip to content

Commit d8bf5b5

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 b01f99c commit d8bf5b5

File tree

9 files changed

+253
-66
lines changed

9 files changed

+253
-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+
holdTime := payload
213+
214+
holdTimes = append(
215+
holdTimes,
216+
fmt.Sprintf("%vms", holdTime*100),
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.Debugf("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: 103 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@ package hop
22

33
import (
44
"bytes"
5+
"errors"
56
"fmt"
67
"io"
8+
"time"
79

810
"github.com/btcsuite/btcd/btcec/v2"
911
sphinx "github.com/lightningnetwork/lightning-onion"
1012
"github.com/lightningnetwork/lnd/lnwire"
13+
"github.com/lightningnetwork/lnd/tlv"
1114
)
1215

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

4263
// IsBlinded returns a boolean indicating whether the error encrypter belongs
@@ -58,8 +79,8 @@ type ErrorEncrypter interface {
5879
// encrypted opaque failure reason. This method will be used at the
5980
// source that the error occurs. It differs from IntermediateEncrypt
6081
// slightly, in that it computes a proper MAC over the error.
61-
EncryptFirstHop(lnwire.FailureMessage) (lnwire.OpaqueReason, []byte,
62-
error)
82+
EncryptFirstHop(lnwire.FailureMessage) (lnwire.OpaqueReason,
83+
[]byte, error)
6384

6485
// EncryptMalformedError is similar to EncryptFirstHop (it adds the
6586
// MAC), but it accepts an opaque failure reason rather than a failure
@@ -104,6 +125,7 @@ type SphinxErrorEncrypter struct {
104125
*sphinx.OnionErrorEncrypter
105126

106127
EphemeralKey *btcec.PublicKey
128+
CreatedAt time.Time
107129
}
108130

109131
// NewSphinxErrorEncrypterUninitialized initializes a blank sphinx error
@@ -115,8 +137,7 @@ type SphinxErrorEncrypter struct {
115137
// OnionProcessor.
116138
func NewSphinxErrorEncrypterUninitialized() *SphinxErrorEncrypter {
117139
return &SphinxErrorEncrypter{
118-
OnionErrorEncrypter: nil,
119-
EphemeralKey: &btcec.PublicKey{},
140+
EphemeralKey: &btcec.PublicKey{},
120141
}
121142
}
122143

@@ -131,15 +152,35 @@ func NewSphinxErrorEncrypter(ephemeralKey *btcec.PublicKey,
131152
EphemeralKey: ephemeralKey,
132153
}
133154

155+
// Set creation time rounded to nanosecond to avoid differences after
156+
// serialization.
157+
encrypter.CreatedAt = time.Now().Truncate(time.Nanosecond)
158+
134159
encrypter.initialize(sharedSecret)
135160

136161
return encrypter
137162
}
138163

164+
// getHoldTime returns the hold time in decaseconds since the first
165+
// instantiation of this sphinx error encrypter.
166+
func (s *SphinxErrorEncrypter) getHoldTime() uint32 {
167+
return uint32(time.Since(s.CreatedAt).Milliseconds() / 100)
168+
}
169+
170+
// encrypt is a thin wrapper around the main encryption method, mainly used to
171+
// automatically derive the hold time to encode in the attribution structure.
172+
func (s *SphinxErrorEncrypter) encrypt(initial bool,
173+
data, attrData []byte) (lnwire.OpaqueReason, []byte, error) {
174+
175+
holdTime := s.getHoldTime()
176+
177+
return s.EncryptError(initial, data, attrData, holdTime)
178+
}
179+
139180
// initialize creates the underlying instance of the sphinx error encrypter.
140181
func (s *SphinxErrorEncrypter) initialize(sharedSecret sphinx.Hash256) {
141182
s.OnionErrorEncrypter = sphinx.NewOnionErrorEncrypter(
142-
sharedSecret, nil,
183+
sharedSecret, AttrErrorStruct,
143184
)
144185
}
145186

@@ -157,9 +198,7 @@ func (s *SphinxErrorEncrypter) EncryptFirstHop(
157198
return nil, nil, err
158199
}
159200

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)
201+
return s.encrypt(true, b.Bytes(), nil)
163202
}
164203

165204
// EncryptMalformedError is similar to EncryptFirstHop (it adds the MAC), but
@@ -172,7 +211,7 @@ func (s *SphinxErrorEncrypter) EncryptFirstHop(
172211
func (s *SphinxErrorEncrypter) EncryptMalformedError(
173212
reason lnwire.OpaqueReason) (lnwire.OpaqueReason, []byte, error) {
174213

175-
return s.EncryptError(true, reason, nil, 0)
214+
return s.encrypt(true, reason, nil)
176215
}
177216

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

189-
return s.EncryptError(false, reason, nil, 0)
243+
return encrypted, attrData, nil
190244
}
191245

192246
// Type returns the identifier for a sphinx error encrypter.
@@ -199,7 +253,20 @@ func (s *SphinxErrorEncrypter) Type() EncrypterType {
199253
func (s *SphinxErrorEncrypter) Encode(w io.Writer) error {
200254
ephemeral := s.EphemeralKey.SerializeCompressed()
201255
_, err := w.Write(ephemeral)
202-
return err
256+
if err != nil {
257+
return err
258+
}
259+
260+
var creationTime = uint64(s.CreatedAt.UnixNano())
261+
262+
tlvStream, err := tlv.NewStream(
263+
tlv.MakePrimitiveRecord(creationTimeType, &creationTime),
264+
)
265+
if err != nil {
266+
return err
267+
}
268+
269+
return tlvStream.Encode(w)
203270
}
204271

205272
// Decode reconstructs the error encrypter's ephemeral public key from the
@@ -216,6 +283,29 @@ func (s *SphinxErrorEncrypter) Decode(r io.Reader) error {
216283
return err
217284
}
218285

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

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
@@ -3573,13 +3573,20 @@ func (l *channelLink) sendHTLCError(add lnwire.UpdateAddHTLC,
35733573
sourceRef channeldb.AddRef, failure *LinkError,
35743574
e hop.ErrorEncrypter, isReceive bool) {
35753575

3576-
reason, _, err := e.EncryptFirstHop(failure.WireMessage())
3576+
reason, attrData, err := e.EncryptFirstHop(failure.WireMessage())
35773577
if err != nil {
35783578
l.log.Errorf("unable to obfuscate error: %v", err)
35793579
return
35803580
}
35813581

3582-
err = l.channel.FailHTLC(add.ID, reason, nil, &sourceRef, nil, nil)
3582+
extraData, err := lnwire.AttrDataToExtraData(attrData)
3583+
if err != nil {
3584+
return
3585+
}
3586+
3587+
err = l.channel.FailHTLC(
3588+
add.ID, reason, extraData, &sourceRef, nil, nil,
3589+
)
35833590
if err != nil {
35843591
l.log.Errorf("unable cancel htlc: %v", err)
35853592
return
@@ -3588,7 +3595,7 @@ func (l *channelLink) sendHTLCError(add lnwire.UpdateAddHTLC,
35883595
// Send the appropriate failure message depending on whether we're
35893596
// in a blinded route or not.
35903597
if err := l.sendIncomingHTLCFailureMsg(
3591-
add.ID, e, reason, nil,
3598+
add.ID, e, reason, extraData,
35923599
); err != nil {
35933600
l.log.Errorf("unable to send HTLC failure: %v", err)
35943601
return

0 commit comments

Comments
 (0)