• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 14358372723

09 Apr 2025 01:26PM UTC coverage: 56.696% (-12.3%) from 69.037%
14358372723

Pull #9696

github

web-flow
Merge e2837e400 into 867d27d68
Pull Request #9696: Add `development_guidelines.md` for both human and machine

107055 of 188823 relevant lines covered (56.7%)

22721.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/lnrpc/routerrpc/forward_interceptor.go
1
package routerrpc
2

3
import (
4
        "errors"
5

6
        "github.com/lightningnetwork/lnd/fn/v2"
7
        "github.com/lightningnetwork/lnd/graph/db/models"
8
        "github.com/lightningnetwork/lnd/htlcswitch"
9
        "github.com/lightningnetwork/lnd/lnrpc"
10
        "github.com/lightningnetwork/lnd/lntypes"
11
        "github.com/lightningnetwork/lnd/lnutils"
12
        "github.com/lightningnetwork/lnd/lnwire"
13
        "google.golang.org/grpc/codes"
14
        "google.golang.org/grpc/status"
15
)
16

17
var (
18
        // ErrFwdNotExists is an error returned when the caller tries to resolve
19
        // a forward that doesn't exist anymore.
20
        ErrFwdNotExists = errors.New("forward does not exist")
21

22
        // ErrMissingPreimage is an error returned when the caller tries to settle
23
        // a forward and doesn't provide a preimage.
24
        ErrMissingPreimage = errors.New("missing preimage")
25
)
26

27
// forwardInterceptor is a helper struct that handles the lifecycle of an RPC
28
// interceptor streaming session.
29
// It is created when the stream opens and disconnects when the stream closes.
30
type forwardInterceptor struct {
31
        // stream is the bidirectional RPC stream
32
        stream Router_HtlcInterceptorServer
33

34
        htlcSwitch htlcswitch.InterceptableHtlcForwarder
35
}
36

37
// newForwardInterceptor creates a new forwardInterceptor.
38
func newForwardInterceptor(htlcSwitch htlcswitch.InterceptableHtlcForwarder,
39
        stream Router_HtlcInterceptorServer) *forwardInterceptor {
×
40

×
41
        return &forwardInterceptor{
×
42
                htlcSwitch: htlcSwitch,
×
43
                stream:     stream,
×
44
        }
×
45
}
×
46

47
// run sends the intercepted packets to the client and receives the
48
// corresponding responses. On one hand it registered itself as an interceptor
49
// that receives the switch packets and on the other hand launches a go routine
50
// to read from the client stream.
51
// To coordinate all this and make sure it is safe for concurrent access all
52
// packets are sent to the main where they are handled.
53
func (r *forwardInterceptor) run() error {
×
54
        // Register our interceptor so we receive all forwarded packets.
×
55
        r.htlcSwitch.SetInterceptor(r.onIntercept)
×
56
        defer r.htlcSwitch.SetInterceptor(nil)
×
57

×
58
        for {
×
59
                resp, err := r.stream.Recv()
×
60
                if err != nil {
×
61
                        return err
×
62
                }
×
63

64
                log.Tracef("Received packet from stream: %v",
×
65
                        lnutils.SpewLogClosure(resp))
×
66

×
67
                if err := r.resolveFromClient(resp); err != nil {
×
68
                        return err
×
69
                }
×
70
        }
71
}
72

73
// onIntercept is the function that is called by the switch for every forwarded
74
// packet. Our interceptor makes sure we hold the packet and then signal to the
75
// main loop to handle the packet. We only return true if we were able
76
// to deliver the packet to the main loop.
77
func (r *forwardInterceptor) onIntercept(
78
        htlc htlcswitch.InterceptedPacket) error {
×
79

×
80
        log.Tracef("Sending intercepted packet to client %v",
×
81
                lnutils.SpewLogClosure(htlc))
×
82

×
83
        inKey := htlc.IncomingCircuit
×
84

×
85
        // First hold the forward, then send to client.
×
86
        interceptionRequest := &ForwardHtlcInterceptRequest{
×
87
                IncomingCircuitKey: &CircuitKey{
×
88
                        ChanId: inKey.ChanID.ToUint64(),
×
89
                        HtlcId: inKey.HtlcID,
×
90
                },
×
91
                OutgoingRequestedChanId: htlc.OutgoingChanID.ToUint64(),
×
92
                PaymentHash:             htlc.Hash[:],
×
93
                OutgoingAmountMsat:      uint64(htlc.OutgoingAmount),
×
94
                OutgoingExpiry:          htlc.OutgoingExpiry,
×
95
                IncomingAmountMsat:      uint64(htlc.IncomingAmount),
×
96
                IncomingExpiry:          htlc.IncomingExpiry,
×
97
                CustomRecords:           htlc.InOnionCustomRecords,
×
98
                OnionBlob:               htlc.OnionBlob[:],
×
99
                AutoFailHeight:          htlc.AutoFailHeight,
×
100
                InWireCustomRecords:     htlc.InWireCustomRecords,
×
101
        }
×
102

×
103
        return r.stream.Send(interceptionRequest)
×
104
}
×
105

106
// resolveFromClient handles a resolution arrived from the client.
107
func (r *forwardInterceptor) resolveFromClient(
108
        in *ForwardHtlcInterceptResponse) error {
×
109

×
110
        if in.IncomingCircuitKey == nil {
×
111
                return status.Errorf(codes.InvalidArgument,
×
112
                        "CircuitKey missing from ForwardHtlcInterceptResponse")
×
113
        }
×
114

115
        log.Tracef("Resolving intercepted packet %v", in)
×
116

×
117
        circuitKey := models.CircuitKey{
×
118
                ChanID: lnwire.NewShortChanIDFromInt(
×
119
                        in.IncomingCircuitKey.ChanId,
×
120
                ),
×
121
                HtlcID: in.IncomingCircuitKey.HtlcId,
×
122
        }
×
123

×
124
        switch in.Action {
×
125
        case ResolveHoldForwardAction_RESUME:
×
126
                return r.htlcSwitch.Resolve(&htlcswitch.FwdResolution{
×
127
                        Key:    circuitKey,
×
128
                        Action: htlcswitch.FwdActionResume,
×
129
                })
×
130

131
        case ResolveHoldForwardAction_RESUME_MODIFIED:
×
132
                // Modify HTLC and resume forward.
×
133
                inAmtMsat := fn.None[lnwire.MilliSatoshi]()
×
134
                if in.InAmountMsat > 0 {
×
135
                        inAmtMsat = fn.Some(lnwire.MilliSatoshi(
×
136
                                in.InAmountMsat,
×
137
                        ))
×
138
                }
×
139

140
                outAmtMsat := fn.None[lnwire.MilliSatoshi]()
×
141
                if in.OutAmountMsat > 0 {
×
142
                        outAmtMsat = fn.Some(lnwire.MilliSatoshi(
×
143
                                in.OutAmountMsat,
×
144
                        ))
×
145
                }
×
146

147
                outWireCustomRecords := fn.None[lnwire.CustomRecords]()
×
148
                if len(in.OutWireCustomRecords) > 0 {
×
149
                        // Validate custom records.
×
150
                        cr := lnwire.CustomRecords(in.OutWireCustomRecords)
×
151
                        if err := cr.Validate(); err != nil {
×
152
                                return status.Errorf(
×
153
                                        codes.InvalidArgument,
×
154
                                        "failed to validate custom records: %v",
×
155
                                        err,
×
156
                                )
×
157
                        }
×
158

159
                        outWireCustomRecords = fn.Some[lnwire.CustomRecords](cr)
×
160
                }
161

162
                //nolint:ll
163
                return r.htlcSwitch.Resolve(&htlcswitch.FwdResolution{
×
164
                        Key:                  circuitKey,
×
165
                        Action:               htlcswitch.FwdActionResumeModified,
×
166
                        InAmountMsat:         inAmtMsat,
×
167
                        OutAmountMsat:        outAmtMsat,
×
168
                        OutWireCustomRecords: outWireCustomRecords,
×
169
                })
×
170

171
        case ResolveHoldForwardAction_FAIL:
×
172
                // Fail with an encrypted reason.
×
173
                if in.FailureMessage != nil {
×
174
                        if in.FailureCode != 0 {
×
175
                                return status.Errorf(
×
176
                                        codes.InvalidArgument,
×
177
                                        "failure message and failure code "+
×
178
                                                "are mutually exclusive",
×
179
                                )
×
180
                        }
×
181

182
                        // Verify that the size is equal to the fixed failure
183
                        // message size + hmac + two uint16 lengths. See BOLT
184
                        // #4.
185
                        if len(in.FailureMessage) !=
×
186
                                lnwire.FailureMessageLength+32+2+2 {
×
187

×
188
                                return status.Errorf(
×
189
                                        codes.InvalidArgument,
×
190
                                        "failure message length invalid",
×
191
                                )
×
192
                        }
×
193

194
                        return r.htlcSwitch.Resolve(&htlcswitch.FwdResolution{
×
195
                                Key:            circuitKey,
×
196
                                Action:         htlcswitch.FwdActionFail,
×
197
                                FailureMessage: in.FailureMessage,
×
198
                        })
×
199
                }
200

201
                var code lnwire.FailCode
×
202
                switch in.FailureCode {
×
203
                case lnrpc.Failure_INVALID_ONION_HMAC:
×
204
                        code = lnwire.CodeInvalidOnionHmac
×
205

206
                case lnrpc.Failure_INVALID_ONION_KEY:
×
207
                        code = lnwire.CodeInvalidOnionKey
×
208

209
                case lnrpc.Failure_INVALID_ONION_VERSION:
×
210
                        code = lnwire.CodeInvalidOnionVersion
×
211

212
                // Default to TemporaryChannelFailure.
213
                case 0, lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE:
×
214
                        code = lnwire.CodeTemporaryChannelFailure
×
215

216
                default:
×
217
                        return status.Errorf(
×
218
                                codes.InvalidArgument,
×
219
                                "unsupported failure code: %v", in.FailureCode,
×
220
                        )
×
221
                }
222

223
                return r.htlcSwitch.Resolve(&htlcswitch.FwdResolution{
×
224
                        Key:         circuitKey,
×
225
                        Action:      htlcswitch.FwdActionFail,
×
226
                        FailureCode: code,
×
227
                })
×
228

229
        case ResolveHoldForwardAction_SETTLE:
×
230
                if in.Preimage == nil {
×
231
                        return ErrMissingPreimage
×
232
                }
×
233
                preimage, err := lntypes.MakePreimage(in.Preimage)
×
234
                if err != nil {
×
235
                        return err
×
236
                }
×
237

238
                return r.htlcSwitch.Resolve(&htlcswitch.FwdResolution{
×
239
                        Key:      circuitKey,
×
240
                        Action:   htlcswitch.FwdActionSettle,
×
241
                        Preimage: preimage,
×
242
                })
×
243

244
        default:
×
245
                return status.Errorf(
×
246
                        codes.InvalidArgument,
×
247
                        "unrecognized resolve action %v", in.Action,
×
248
                )
×
249
        }
250
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc