• 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

81.13
/lnwire/custom_records.go
1
package lnwire
2

3
import (
4
        "bytes"
5
        "fmt"
6
        "io"
7
        "sort"
8

9
        "github.com/lightningnetwork/lnd/fn/v2"
10
        "github.com/lightningnetwork/lnd/tlv"
11
)
12

13
const (
14
        // MinCustomRecordsTlvType is the minimum custom records TLV type as
15
        // defined in BOLT 01.
16
        MinCustomRecordsTlvType = 65536
17
)
18

19
// CustomRecords stores a set of custom key/value pairs. Map keys are TLV types
20
// which must be greater than or equal to MinCustomRecordsTlvType.
21
type CustomRecords map[uint64][]byte
22

23
// NewCustomRecords creates a new CustomRecords instance from a
24
// tlv.TypeMap.
25
func NewCustomRecords(tlvMap tlv.TypeMap) (CustomRecords, error) {
3✔
26
        // Make comparisons in unit tests easy by returning nil if the map is
3✔
27
        // empty.
3✔
28
        if len(tlvMap) == 0 {
6✔
29
                return nil, nil
3✔
30
        }
3✔
31

32
        customRecords := make(CustomRecords, len(tlvMap))
3✔
33
        for k, v := range tlvMap {
6✔
34
                customRecords[uint64(k)] = v
3✔
35
        }
3✔
36

37
        // Validate the custom records.
38
        err := customRecords.Validate()
3✔
39
        if err != nil {
3✔
UNCOV
40
                return nil, fmt.Errorf("custom records from tlv map "+
×
UNCOV
41
                        "validation error: %w", err)
×
UNCOV
42
        }
×
43

44
        return customRecords, nil
3✔
45
}
46

47
// ParseCustomRecords creates a new CustomRecords instance from a tlv.Blob.
48
func ParseCustomRecords(b tlv.Blob) (CustomRecords, error) {
3✔
49
        return ParseCustomRecordsFrom(bytes.NewReader(b))
3✔
50
}
3✔
51

52
// ParseCustomRecordsFrom creates a new CustomRecords instance from a reader.
53
func ParseCustomRecordsFrom(r io.Reader) (CustomRecords, error) {
3✔
54
        typeMap, err := DecodeRecords(r)
3✔
55
        if err != nil {
3✔
56
                return nil, fmt.Errorf("error decoding HTLC record: %w", err)
×
57
        }
×
58

59
        return NewCustomRecords(typeMap)
3✔
60
}
61

62
// Validate checks that all custom records are in the custom type range.
63
func (c CustomRecords) Validate() error {
3✔
64
        if c == nil {
6✔
65
                return nil
3✔
66
        }
3✔
67

68
        for key := range c {
6✔
69
                if key < MinCustomRecordsTlvType {
3✔
UNCOV
70
                        return fmt.Errorf("custom records entry with TLV "+
×
UNCOV
71
                                "type below min: %d", MinCustomRecordsTlvType)
×
UNCOV
72
                }
×
73
        }
74

75
        return nil
3✔
76
}
77

78
// Copy returns a copy of the custom records.
79
func (c CustomRecords) Copy() CustomRecords {
3✔
80
        if c == nil {
6✔
81
                return nil
3✔
82
        }
3✔
83

84
        customRecords := make(CustomRecords, len(c))
3✔
85
        for k, v := range c {
6✔
86
                customRecords[k] = v
3✔
87
        }
3✔
88

89
        return customRecords
3✔
90
}
91

92
// MergedCopy creates a copy of the records and merges them with the given
93
// records. If the same key is present in both sets, the value from the other
94
// records will be used.
95
func (c CustomRecords) MergedCopy(other CustomRecords) CustomRecords {
3✔
96
        copiedRecords := make(CustomRecords, len(c))
3✔
97
        for k, v := range c {
6✔
98
                copiedRecords[k] = v
3✔
99
        }
3✔
100

101
        for k, v := range other {
6✔
102
                copiedRecords[k] = v
3✔
103
        }
3✔
104

105
        return copiedRecords
3✔
106
}
107

108
// ExtendRecordProducers extends the given records slice with the custom
109
// records. The resultant records slice will be sorted if the given records
110
// slice contains TLV types greater than or equal to MinCustomRecordsTlvType.
111
func (c CustomRecords) ExtendRecordProducers(
112
        producers []tlv.RecordProducer) ([]tlv.RecordProducer, error) {
3✔
113

3✔
114
        // If the custom records are nil or empty, there is nothing to do.
3✔
115
        if len(c) == 0 {
6✔
116
                return producers, nil
3✔
117
        }
3✔
118

119
        // Validate the custom records.
120
        err := c.Validate()
3✔
121
        if err != nil {
3✔
UNCOV
122
                return nil, err
×
UNCOV
123
        }
×
124

125
        // Ensure that the existing records slice TLV types are not also present
126
        // in the custom records. If they are, the resultant extended records
127
        // slice would erroneously contain duplicate TLV types.
128
        for _, rp := range producers {
6✔
129
                record := rp.Record()
3✔
130
                recordTlvType := uint64(record.Type())
3✔
131

3✔
132
                _, foundDuplicateTlvType := c[recordTlvType]
3✔
133
                if foundDuplicateTlvType {
3✔
UNCOV
134
                        return nil, fmt.Errorf("custom records contains a TLV "+
×
UNCOV
135
                                "type that is already present in the "+
×
UNCOV
136
                                "existing records: %d", recordTlvType)
×
UNCOV
137
                }
×
138
        }
139

140
        // Convert the custom records map to a TLV record producer slice and
141
        // append them to the exiting records slice.
142
        customRecordProducers := RecordsAsProducers(tlv.MapToRecords(c))
3✔
143
        producers = append(producers, customRecordProducers...)
3✔
144

3✔
145
        // If the records slice which was given as an argument included TLV
3✔
146
        // values greater than or equal to the minimum custom records TLV type
3✔
147
        // we will sort the extended records slice to ensure that it is ordered
3✔
148
        // correctly.
3✔
149
        SortProducers(producers)
3✔
150

3✔
151
        return producers, nil
3✔
152
}
153

154
// RecordProducers returns a slice of record producers for the custom records.
155
func (c CustomRecords) RecordProducers() []tlv.RecordProducer {
3✔
156
        // If the custom records are nil or empty, return an empty slice.
3✔
157
        if len(c) == 0 {
6✔
158
                return nil
3✔
159
        }
3✔
160

161
        // Convert the custom records map to a TLV record producer slice.
162
        records := tlv.MapToRecords(c)
3✔
163

3✔
164
        return RecordsAsProducers(records)
3✔
165
}
166

167
// Serialize serializes the custom records into a byte slice.
168
func (c CustomRecords) Serialize() ([]byte, error) {
3✔
169
        records := tlv.MapToRecords(c)
3✔
170
        return EncodeRecords(records)
3✔
171
}
3✔
172

173
// SerializeTo serializes the custom records into the given writer.
174
func (c CustomRecords) SerializeTo(w io.Writer) error {
3✔
175
        records := tlv.MapToRecords(c)
3✔
176
        return EncodeRecordsTo(w, records)
3✔
177
}
3✔
178

179
// ProduceRecordsSorted converts a slice of record producers into a slice of
180
// records and then sorts it by type.
181
func ProduceRecordsSorted(recordProducers ...tlv.RecordProducer) []tlv.Record {
3✔
182
        records := fn.Map(
3✔
183
                recordProducers,
3✔
184
                func(producer tlv.RecordProducer) tlv.Record {
6✔
185
                        return producer.Record()
3✔
186
                },
3✔
187
        )
188

189
        // Ensure that the set of records are sorted before we attempt to
190
        // decode from the stream, to ensure they're canonical.
191
        tlv.SortRecords(records)
3✔
192

3✔
193
        return records
3✔
194
}
195

196
// SortProducers sorts the given record producers by their type.
197
func SortProducers(producers []tlv.RecordProducer) {
3✔
198
        sort.Slice(producers, func(i, j int) bool {
6✔
199
                recordI := producers[i].Record()
3✔
200
                recordJ := producers[j].Record()
3✔
201
                return recordI.Type() < recordJ.Type()
3✔
202
        })
3✔
203
}
204

205
// TlvMapToRecords converts a TLV map into a slice of records.
UNCOV
206
func TlvMapToRecords(tlvMap tlv.TypeMap) []tlv.Record {
×
UNCOV
207
        tlvMapGeneric := make(map[uint64][]byte)
×
UNCOV
208
        for k, v := range tlvMap {
×
UNCOV
209
                tlvMapGeneric[uint64(k)] = v
×
UNCOV
210
        }
×
211

UNCOV
212
        return tlv.MapToRecords(tlvMapGeneric)
×
213
}
214

215
// RecordsAsProducers converts a slice of records into a slice of record
216
// producers.
217
func RecordsAsProducers(records []tlv.Record) []tlv.RecordProducer {
3✔
218
        return fn.Map(records, func(record tlv.Record) tlv.RecordProducer {
6✔
219
                return &record
3✔
220
        })
3✔
221
}
222

223
// EncodeRecords encodes the given records into a byte slice.
224
func EncodeRecords(records []tlv.Record) ([]byte, error) {
3✔
225
        var buf bytes.Buffer
3✔
226
        if err := EncodeRecordsTo(&buf, records); err != nil {
3✔
227
                return nil, err
×
228
        }
×
229

230
        return buf.Bytes(), nil
3✔
231
}
232

233
// EncodeRecordsTo encodes the given records into the given writer.
234
func EncodeRecordsTo(w io.Writer, records []tlv.Record) error {
3✔
235
        tlvStream, err := tlv.NewStream(records...)
3✔
236
        if err != nil {
3✔
237
                return err
×
238
        }
×
239

240
        return tlvStream.Encode(w)
3✔
241
}
242

243
// DecodeRecords decodes the given byte slice into the given records and returns
244
// the rest as a TLV type map.
245
func DecodeRecords(r io.Reader,
246
        records ...tlv.Record) (tlv.TypeMap, error) {
3✔
247

3✔
248
        tlvStream, err := tlv.NewStream(records...)
3✔
249
        if err != nil {
3✔
250
                return nil, err
×
251
        }
×
252

253
        return tlvStream.DecodeWithParsedTypes(r)
3✔
254
}
255

256
// DecodeRecordsP2P decodes the given byte slice into the given records and
257
// returns the rest as a TLV type map. This function is identical to
258
// DecodeRecords except that the record size is capped at 65535.
259
func DecodeRecordsP2P(r *bytes.Reader,
260
        records ...tlv.Record) (tlv.TypeMap, error) {
3✔
261

3✔
262
        tlvStream, err := tlv.NewStream(records...)
3✔
263
        if err != nil {
3✔
264
                return nil, err
×
265
        }
×
266

267
        return tlvStream.DecodeWithParsedTypesP2P(r)
3✔
268
}
269

270
// AssertUniqueTypes asserts that the given records have unique types.
271
func AssertUniqueTypes(r []tlv.Record) error {
3✔
272
        seen := make(fn.Set[tlv.Type], len(r))
3✔
273
        for _, record := range r {
6✔
274
                t := record.Type()
3✔
275
                if seen.Contains(t) {
3✔
UNCOV
276
                        return fmt.Errorf("duplicate record type: %d", t)
×
UNCOV
277
                }
×
278
                seen.Add(t)
3✔
279
        }
280

281
        return nil
3✔
282
}
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