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

lightningnetwork / lnd / 13786526560

11 Mar 2025 11:06AM UTC coverage: 68.603% (-0.009%) from 68.612%
13786526560

Pull #9600

github

web-flow
Merge 39f57cbe0 into a673826de
Pull Request #9600: lntest+itest: document and fix more flakes

14 of 29 new or added lines in 4 files covered. (48.28%)

67 existing lines in 16 files now uncovered.

129988 of 189479 relevant lines covered (68.6%)

23728.64 hits per line

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

66.67
/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 {
3✔
40

3✔
41
        return &forwardInterceptor{
3✔
42
                htlcSwitch: htlcSwitch,
3✔
43
                stream:     stream,
3✔
44
        }
3✔
45
}
3✔
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 {
3✔
54
        // Register our interceptor so we receive all forwarded packets.
3✔
55
        r.htlcSwitch.SetInterceptor(r.onIntercept)
3✔
56
        defer r.htlcSwitch.SetInterceptor(nil)
3✔
57

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

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

3✔
67
                if err := r.resolveFromClient(resp); err != nil {
3✔
UNCOV
68
                        return err
×
UNCOV
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 {
3✔
79

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

3✔
83
        inKey := htlc.IncomingCircuit
3✔
84

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

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

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

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

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

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

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

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

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

147
                outWireCustomRecords := fn.None[lnwire.CustomRecords]()
3✔
148
                if len(in.OutWireCustomRecords) > 0 {
6✔
149
                        // Validate custom records.
3✔
150
                        cr := lnwire.CustomRecords(in.OutWireCustomRecords)
3✔
151
                        if err := cr.Validate(); err != nil {
3✔
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)
3✔
160
                }
161

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

171
        case ResolveHoldForwardAction_FAIL:
3✔
172
                // Fail with an encrypted reason.
3✔
173
                if in.FailureMessage != nil {
3✔
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
3✔
202
                switch in.FailureCode {
3✔
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:
3✔
214
                        code = lnwire.CodeTemporaryChannelFailure
3✔
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{
3✔
224
                        Key:         circuitKey,
3✔
225
                        Action:      htlcswitch.FwdActionFail,
3✔
226
                        FailureCode: code,
3✔
227
                })
3✔
228

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

238
                return r.htlcSwitch.Resolve(&htlcswitch.FwdResolution{
3✔
239
                        Key:      circuitKey,
3✔
240
                        Action:   htlcswitch.FwdActionSettle,
3✔
241
                        Preimage: preimage,
3✔
242
                })
3✔
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