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

lightningnetwork / lnd / 13211764208

08 Feb 2025 03:08AM UTC coverage: 49.288% (-9.5%) from 58.815%
13211764208

Pull #9489

github

calvinrzachman
itest: verify switchrpc server enforces send then track

We prevent the rpc server from allowing onion dispatches for
attempt IDs which have already been tracked by rpc clients.

This helps protect the client from leaking a duplicate onion
attempt. NOTE: This is not the only method for solving this
issue! The issue could be addressed via careful client side
programming which accounts for the uncertainty and async
nature of dispatching onions to a remote process via RPC.
This would require some lnd ChannelRouter changes for how
we intend to use these RPCs though.
Pull Request #9489: multi: add BuildOnion, SendOnion, and TrackOnion RPCs

474 of 990 new or added lines in 11 files covered. (47.88%)

27321 existing lines in 435 files now uncovered.

101192 of 205306 relevant lines covered (49.29%)

1.54 hits per line

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

0.0
/channeldb/migration_01_to_11/route.go
1
package migration_01_to_11
2

3
import (
4
        "bytes"
5
        "encoding/binary"
6
        "encoding/hex"
7
        "fmt"
8
        "io"
9
        "strconv"
10
        "strings"
11

12
        "github.com/btcsuite/btcd/btcec/v2"
13
        sphinx "github.com/lightningnetwork/lightning-onion"
14
        lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
15
        "github.com/lightningnetwork/lnd/record"
16
        "github.com/lightningnetwork/lnd/tlv"
17
)
18

19
// VertexSize is the size of the array to store a vertex.
20
const VertexSize = 33
21

22
// ErrNoRouteHopsProvided is returned when a caller attempts to construct a new
23
// sphinx packet, but provides an empty set of hops for each route.
24
var ErrNoRouteHopsProvided = fmt.Errorf("empty route hops provided")
25

26
// Vertex is a simple alias for the serialization of a compressed Bitcoin
27
// public key.
28
type Vertex [VertexSize]byte
29

30
// NewVertex returns a new Vertex given a public key.
UNCOV
31
func NewVertex(pub *btcec.PublicKey) Vertex {
×
UNCOV
32
        var v Vertex
×
UNCOV
33
        copy(v[:], pub.SerializeCompressed())
×
UNCOV
34
        return v
×
UNCOV
35
}
×
36

37
// NewVertexFromBytes returns a new Vertex based on a serialized pubkey in a
38
// byte slice.
39
func NewVertexFromBytes(b []byte) (Vertex, error) {
×
40
        vertexLen := len(b)
×
41
        if vertexLen != VertexSize {
×
42
                return Vertex{}, fmt.Errorf("invalid vertex length of %v, "+
×
43
                        "want %v", vertexLen, VertexSize)
×
44
        }
×
45

46
        var v Vertex
×
47
        copy(v[:], b)
×
48
        return v, nil
×
49
}
50

51
// NewVertexFromStr returns a new Vertex given its hex-encoded string format.
52
func NewVertexFromStr(v string) (Vertex, error) {
×
53
        // Return error if hex string is of incorrect length.
×
54
        if len(v) != VertexSize*2 {
×
55
                return Vertex{}, fmt.Errorf("invalid vertex string length of "+
×
56
                        "%v, want %v", len(v), VertexSize*2)
×
57
        }
×
58

59
        vertex, err := hex.DecodeString(v)
×
60
        if err != nil {
×
61
                return Vertex{}, err
×
62
        }
×
63

64
        return NewVertexFromBytes(vertex)
×
65
}
66

67
// String returns a human readable version of the Vertex which is the
68
// hex-encoding of the serialized compressed public key.
69
func (v Vertex) String() string {
×
70
        return fmt.Sprintf("%x", v[:])
×
71
}
×
72

73
// Hop represents an intermediate or final node of the route. This naming
74
// is in line with the definition given in BOLT #4: Onion Routing Protocol.
75
// The struct houses the channel along which this hop can be reached and
76
// the values necessary to create the HTLC that needs to be sent to the
77
// next hop. It is also used to encode the per-hop payload included within
78
// the Sphinx packet.
79
type Hop struct {
80
        // PubKeyBytes is the raw bytes of the public key of the target node.
81
        PubKeyBytes Vertex
82

83
        // ChannelID is the unique channel ID for the channel. The first 3
84
        // bytes are the block height, the next 3 the index within the block,
85
        // and the last 2 bytes are the output index for the channel.
86
        ChannelID uint64
87

88
        // OutgoingTimeLock is the timelock value that should be used when
89
        // crafting the _outgoing_ HTLC from this hop.
90
        OutgoingTimeLock uint32
91

92
        // AmtToForward is the amount that this hop will forward to the next
93
        // hop. This value is less than the value that the incoming HTLC
94
        // carries as a fee will be subtracted by the hop.
95
        AmtToForward lnwire.MilliSatoshi
96

97
        // TLVRecords if non-nil are a set of additional TLV records that
98
        // should be included in the forwarding instructions for this node.
99
        TLVRecords []tlv.Record
100

101
        // LegacyPayload if true, then this signals that this node doesn't
102
        // understand the new TLV payload, so we must instead use the legacy
103
        // payload.
104
        LegacyPayload bool
105
}
106

107
// PackHopPayload writes to the passed io.Writer, the series of byes that can
108
// be placed directly into the per-hop payload (EOB) for this hop. This will
109
// include the required routing fields, as well as serializing any of the
110
// passed optional TLVRecords.  nextChanID is the unique channel ID that
111
// references the _outgoing_ channel ID that follows this hop. This field
112
// follows the same semantics as the NextAddress field in the onion: it should
113
// be set to zero to indicate the terminal hop.
114
func (h *Hop) PackHopPayload(w io.Writer, nextChanID uint64) error {
×
115
        // If this is a legacy payload, then we'll exit here as this method
×
116
        // shouldn't be called.
×
117
        if h.LegacyPayload == true {
×
118
                return fmt.Errorf("cannot pack hop payloads for legacy " +
×
119
                        "payloads")
×
120
        }
×
121

122
        // Otherwise, we'll need to make a new stream that includes our
123
        // required routing fields, as well as these optional values.
124
        var records []tlv.Record
×
125

×
126
        // Every hop must have an amount to forward and CLTV expiry.
×
127
        amt := uint64(h.AmtToForward)
×
128
        records = append(records,
×
129
                record.NewAmtToFwdRecord(&amt),
×
130
                record.NewLockTimeRecord(&h.OutgoingTimeLock),
×
131
        )
×
132

×
133
        // BOLT 04 says the next_hop_id should be omitted for the final hop,
×
134
        // but present for all others.
×
135
        //
×
136
        // TODO(conner): test using hop.Exit once available
×
137
        if nextChanID != 0 {
×
138
                records = append(records,
×
139
                        record.NewNextHopIDRecord(&nextChanID),
×
140
                )
×
141
        }
×
142

143
        // Append any custom types destined for this hop.
144
        records = append(records, h.TLVRecords...)
×
145

×
146
        // To ensure we produce a canonical stream, we'll sort the records
×
147
        // before encoding them as a stream in the hop payload.
×
148
        tlv.SortRecords(records)
×
149

×
150
        tlvStream, err := tlv.NewStream(records...)
×
151
        if err != nil {
×
152
                return err
×
153
        }
×
154

155
        return tlvStream.Encode(w)
×
156
}
157

158
// Route represents a path through the channel graph which runs over one or
159
// more channels in succession. This struct carries all the information
160
// required to craft the Sphinx onion packet, and send the payment along the
161
// first hop in the path. A route is only selected as valid if all the channels
162
// have sufficient capacity to carry the initial payment amount after fees are
163
// accounted for.
164
type Route struct {
165
        // TotalTimeLock is the cumulative (final) time lock across the entire
166
        // route. This is the CLTV value that should be extended to the first
167
        // hop in the route. All other hops will decrement the time-lock as
168
        // advertised, leaving enough time for all hops to wait for or present
169
        // the payment preimage to complete the payment.
170
        TotalTimeLock uint32
171

172
        // TotalAmount is the total amount of funds required to complete a
173
        // payment over this route. This value includes the cumulative fees at
174
        // each hop. As a result, the HTLC extended to the first-hop in the
175
        // route will need to have at least this many satoshis, otherwise the
176
        // route will fail at an intermediate node due to an insufficient
177
        // amount of fees.
178
        TotalAmount lnwire.MilliSatoshi
179

180
        // SourcePubKey is the pubkey of the node where this route originates
181
        // from.
182
        SourcePubKey Vertex
183

184
        // Hops contains details concerning the specific forwarding details at
185
        // each hop.
186
        Hops []*Hop
187
}
188

189
// HopFee returns the fee charged by the route hop indicated by hopIndex.
190
func (r *Route) HopFee(hopIndex int) lnwire.MilliSatoshi {
×
191
        var incomingAmt lnwire.MilliSatoshi
×
192
        if hopIndex == 0 {
×
193
                incomingAmt = r.TotalAmount
×
194
        } else {
×
195
                incomingAmt = r.Hops[hopIndex-1].AmtToForward
×
196
        }
×
197

198
        // Fee is calculated as difference between incoming and outgoing amount.
199
        return incomingAmt - r.Hops[hopIndex].AmtToForward
×
200
}
201

202
// TotalFees is the sum of the fees paid at each hop within the final route. In
203
// the case of a one-hop payment, this value will be zero as we don't need to
204
// pay a fee to ourself.
UNCOV
205
func (r *Route) TotalFees() lnwire.MilliSatoshi {
×
UNCOV
206
        if len(r.Hops) == 0 {
×
207
                return 0
×
208
        }
×
209

UNCOV
210
        return r.TotalAmount - r.Hops[len(r.Hops)-1].AmtToForward
×
211
}
212

213
// NewRouteFromHops creates a new Route structure from the minimally required
214
// information to perform the payment. It infers fee amounts and populates the
215
// node, chan and prev/next hop maps.
216
func NewRouteFromHops(amtToSend lnwire.MilliSatoshi, timeLock uint32,
217
        sourceVertex Vertex, hops []*Hop) (*Route, error) {
×
218

×
219
        if len(hops) == 0 {
×
220
                return nil, ErrNoRouteHopsProvided
×
221
        }
×
222

223
        // First, we'll create a route struct and populate it with the fields
224
        // for which the values are provided as arguments of this function.
225
        // TotalFees is determined based on the difference between the amount
226
        // that is send from the source and the final amount that is received
227
        // by the destination.
228
        route := &Route{
×
229
                SourcePubKey:  sourceVertex,
×
230
                Hops:          hops,
×
231
                TotalTimeLock: timeLock,
×
232
                TotalAmount:   amtToSend,
×
233
        }
×
234

×
235
        return route, nil
×
236
}
237

238
// ToSphinxPath converts a complete route into a sphinx PaymentPath that
239
// contains the per-hop paylods used to encoding the HTLC routing data for each
240
// hop in the route. This method also accepts an optional EOB payload for the
241
// final hop.
242
func (r *Route) ToSphinxPath() (*sphinx.PaymentPath, error) {
×
243
        var path sphinx.PaymentPath
×
244

×
245
        // For each hop encoded within the route, we'll convert the hop struct
×
246
        // to an OnionHop with matching per-hop payload within the path as used
×
247
        // by the sphinx package.
×
248
        for i, hop := range r.Hops {
×
249
                pub, err := btcec.ParsePubKey(hop.PubKeyBytes[:])
×
250
                if err != nil {
×
251
                        return nil, err
×
252
                }
×
253

254
                // As a base case, the next hop is set to all zeroes in order
255
                // to indicate that the "last hop" as no further hops after it.
256
                nextHop := uint64(0)
×
257

×
258
                // If we aren't on the last hop, then we set the "next address"
×
259
                // field to be the channel that directly follows it.
×
260
                if i != len(r.Hops)-1 {
×
261
                        nextHop = r.Hops[i+1].ChannelID
×
262
                }
×
263

264
                var payload sphinx.HopPayload
×
265

×
266
                // If this is the legacy payload, then we can just include the
×
267
                // hop data as normal.
×
268
                if hop.LegacyPayload {
×
269
                        // Before we encode this value, we'll pack the next hop
×
270
                        // into the NextAddress field of the hop info to ensure
×
271
                        // we point to the right now.
×
272
                        hopData := sphinx.HopData{
×
273
                                ForwardAmount: uint64(hop.AmtToForward),
×
274
                                OutgoingCltv:  hop.OutgoingTimeLock,
×
275
                        }
×
276
                        binary.BigEndian.PutUint64(
×
277
                                hopData.NextAddress[:], nextHop,
×
278
                        )
×
279

×
280
                        payload, err = sphinx.NewLegacyHopPayload(&hopData)
×
281
                        if err != nil {
×
282
                                return nil, err
×
283
                        }
×
284
                } else {
×
285
                        // For non-legacy payloads, we'll need to pack the
×
286
                        // routing information, along with any extra TLV
×
287
                        // information into the new per-hop payload format.
×
288
                        // We'll also pass in the chan ID of the hop this
×
289
                        // channel should be forwarded to so we can construct a
×
290
                        // valid payload.
×
291
                        var b bytes.Buffer
×
292
                        err := hop.PackHopPayload(&b, nextHop)
×
293
                        if err != nil {
×
294
                                return nil, err
×
295
                        }
×
296

297
                        payload, err = sphinx.NewTLVHopPayload(b.Bytes())
×
298
                        if err != nil {
×
299
                                return nil, err
×
300
                        }
×
301
                }
302

303
                path[i] = sphinx.OnionHop{
×
304
                        NodePub:    *pub,
×
305
                        HopPayload: payload,
×
306
                }
×
307
        }
308

309
        return &path, nil
×
310
}
311

312
// String returns a human readable representation of the route.
313
func (r *Route) String() string {
×
314
        var b strings.Builder
×
315

×
316
        for i, hop := range r.Hops {
×
317
                if i > 0 {
×
318
                        b.WriteString(",")
×
319
                }
×
320
                b.WriteString(strconv.FormatUint(hop.ChannelID, 10))
×
321
        }
322

323
        return fmt.Sprintf("amt=%v, fees=%v, tl=%v, chans=%v",
×
324
                r.TotalAmount-r.TotalFees(), r.TotalFees(), r.TotalTimeLock,
×
325
                b.String(),
×
326
        )
×
327
}
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