• 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

76.6
/lnwire/extra_bytes.go
1
package lnwire
2

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

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

12
// ExtraOpaqueData is the set of data that was appended to this message, some
13
// of which we may not actually know how to iterate or parse. By holding onto
14
// this data, we ensure that we're able to properly validate the set of
15
// signatures that cover these new fields, and ensure we're able to make
16
// upgrades to the network in a forwards compatible manner.
17
type ExtraOpaqueData []byte
18

19
// NewExtraOpaqueData creates a new ExtraOpaqueData instance from a tlv.TypeMap.
20
func NewExtraOpaqueData(tlvMap tlv.TypeMap) (ExtraOpaqueData, error) {
3✔
21
        // If the tlv map is empty, we'll want to mirror the behavior of
3✔
22
        // decoding an empty extra opaque data field (see Decode method).
3✔
23
        if len(tlvMap) == 0 {
6✔
24
                return make([]byte, 0), nil
3✔
25
        }
3✔
26

27
        // Convert the TLV map into a slice of records.
UNCOV
28
        records := TlvMapToRecords(tlvMap)
×
UNCOV
29

×
UNCOV
30
        // Encode the records into the extra data byte slice.
×
UNCOV
31
        return EncodeRecords(records)
×
32
}
33

34
// Encode attempts to encode the raw extra bytes into the passed io.Writer.
35
func (e *ExtraOpaqueData) Encode(w *bytes.Buffer) error {
3✔
36
        eBytes := []byte((*e)[:])
3✔
37
        if err := WriteBytes(w, eBytes); err != nil {
3✔
38
                return err
×
39
        }
×
40

41
        return nil
3✔
42
}
43

44
// Decode attempts to unpack the raw bytes encoded in the passed-in io.Reader as
45
// a set of extra opaque data.
46
func (e *ExtraOpaqueData) Decode(r io.Reader) error {
3✔
47
        // First, we'll attempt to read a set of bytes contained within the
3✔
48
        // passed io.Reader (if any exist).
3✔
49
        rawBytes, err := io.ReadAll(r)
3✔
50
        if err != nil {
3✔
51
                return err
×
52
        }
×
53

54
        // If we _do_ have some bytes, then we'll swap out our backing pointer.
55
        // This ensures that any struct that embeds this type will properly
56
        // store the bytes once this method exits.
57
        if len(rawBytes) > 0 {
6✔
58
                *e = rawBytes
3✔
59
        } else {
6✔
60
                *e = make([]byte, 0)
3✔
61
        }
3✔
62

63
        return nil
3✔
64
}
65

66
// PackRecords attempts to encode the set of tlv records into the target
67
// ExtraOpaqueData instance. The records will be encoded as a raw TLV stream
68
// and stored within the backing slice pointer.
69
func (e *ExtraOpaqueData) PackRecords(
70
        recordProducers ...tlv.RecordProducer) error {
3✔
71

3✔
72
        // Assemble all the records passed in series, then encode them.
3✔
73
        records := ProduceRecordsSorted(recordProducers...)
3✔
74
        encoded, err := EncodeRecords(records)
3✔
75
        if err != nil {
3✔
76
                return err
×
77
        }
×
78

79
        *e = encoded
3✔
80

3✔
81
        return nil
3✔
82
}
83

84
// ExtractRecords attempts to decode any types in the internal raw bytes as if
85
// it were a tlv stream. The set of raw parsed types is returned, and any
86
// passed records (if found in the stream) will be parsed into the proper
87
// tlv.Record.
88
func (e *ExtraOpaqueData) ExtractRecords(
89
        recordProducers ...tlv.RecordProducer) (tlv.TypeMap, error) {
3✔
90

3✔
91
        // First, assemble all the records passed in series.
3✔
92
        records := ProduceRecordsSorted(recordProducers...)
3✔
93
        extraBytesReader := bytes.NewReader(*e)
3✔
94

3✔
95
        // Since ExtraOpaqueData is provided by a potentially malicious peer,
3✔
96
        // pass it into the P2P decoding variant.
3✔
97
        return DecodeRecordsP2P(extraBytesReader, records...)
3✔
98
}
3✔
99

100
// RecordProducers parses ExtraOpaqueData into a slice of TLV record producers
101
// by interpreting it as a TLV map.
102
func (e *ExtraOpaqueData) RecordProducers() ([]tlv.RecordProducer, error) {
3✔
103
        var recordProducers []tlv.RecordProducer
3✔
104

3✔
105
        // If the instance is nil or empty, return an empty slice.
3✔
106
        if e == nil || len(*e) == 0 {
6✔
107
                return recordProducers, nil
3✔
108
        }
3✔
109

110
        // Parse the extra opaque data as a TLV map.
UNCOV
111
        tlvMap, err := e.ExtractRecords()
×
UNCOV
112
        if err != nil {
×
113
                return nil, err
×
114
        }
×
115

116
        // Convert the TLV map into a slice of record producers.
UNCOV
117
        records := TlvMapToRecords(tlvMap)
×
UNCOV
118

×
UNCOV
119
        return RecordsAsProducers(records), nil
×
120
}
121

122
// EncodeMessageExtraData encodes the given recordProducers into the given
123
// extraData.
124
func EncodeMessageExtraData(extraData *ExtraOpaqueData,
125
        recordProducers ...tlv.RecordProducer) error {
3✔
126

3✔
127
        // Treat extraData as a mutable reference.
3✔
128
        if extraData == nil {
3✔
129
                return fmt.Errorf("extra data cannot be nil")
×
130
        }
×
131

132
        // Pack in the series of TLV records into this message. The order we
133
        // pass them in doesn't matter, as the method will ensure that things
134
        // are all properly sorted.
135
        return extraData.PackRecords(recordProducers...)
3✔
136
}
137

138
// ParseAndExtractCustomRecords parses the given extra data into the passed-in
139
// records, then returns any remaining records split into custom records and
140
// extra data.
141
func ParseAndExtractCustomRecords(allExtraData ExtraOpaqueData,
142
        knownRecords ...tlv.RecordProducer) (CustomRecords,
143
        fn.Set[tlv.Type], ExtraOpaqueData, error) {
3✔
144

3✔
145
        extraDataTlvMap, err := allExtraData.ExtractRecords(knownRecords...)
3✔
146
        if err != nil {
3✔
UNCOV
147
                return nil, nil, nil, err
×
UNCOV
148
        }
×
149

150
        // Remove the known and now extracted records from the leftover extra
151
        // data map.
152
        parsedKnownRecords := make(fn.Set[tlv.Type], len(knownRecords))
3✔
153
        for _, producer := range knownRecords {
6✔
154
                r := producer.Record()
3✔
155

3✔
156
                // Only remove the records if it was parsed (remainder is nil).
3✔
157
                // We'll just store the type so we can tell the caller which
3✔
158
                // records were actually parsed fully.
3✔
159
                val, ok := extraDataTlvMap[r.Type()]
3✔
160
                if ok && val == nil {
6✔
161
                        parsedKnownRecords.Add(r.Type())
3✔
162
                        delete(extraDataTlvMap, r.Type())
3✔
163
                }
3✔
164
        }
165

166
        // Any records from the extra data TLV map which are in the custom
167
        // records TLV type range will be included in the custom records field
168
        // and removed from the extra data field.
169
        customRecordsTlvMap := make(tlv.TypeMap, len(extraDataTlvMap))
3✔
170
        for k, v := range extraDataTlvMap {
6✔
171
                // Skip records that are not in the custom records TLV type
3✔
172
                // range.
3✔
173
                if k < MinCustomRecordsTlvType {
3✔
UNCOV
174
                        continue
×
175
                }
176

177
                // Include the record in the custom records map.
178
                customRecordsTlvMap[k] = v
3✔
179

3✔
180
                // Now that the record is included in the custom records map,
3✔
181
                // we can remove it from the extra data TLV map.
3✔
182
                delete(extraDataTlvMap, k)
3✔
183
        }
184

185
        // Set the custom records field to the custom records specific TLV
186
        // record map.
187
        customRecords, err := NewCustomRecords(customRecordsTlvMap)
3✔
188
        if err != nil {
3✔
189
                return nil, nil, nil, err
×
190
        }
×
191

192
        // Encode the remaining records back into the extra data field. These
193
        // records are not in the custom records TLV type range and do not
194
        // have associated fields in the struct that produced the records.
195
        extraData, err := NewExtraOpaqueData(extraDataTlvMap)
3✔
196
        if err != nil {
3✔
197
                return nil, nil, nil, err
×
198
        }
×
199

200
        // Help with unit testing where we might have the empty value (nil) for
201
        // the extra data instead of the default that's returned by the
202
        // constructor (empty slice).
203
        if len(extraData) == 0 {
6✔
204
                extraData = nil
3✔
205
        }
3✔
206

207
        return customRecords, parsedKnownRecords, extraData, nil
3✔
208
}
209

210
// MergeAndEncode merges the known records with the extra data and custom
211
// records, then encodes the merged records into raw bytes.
212
func MergeAndEncode(knownRecords []tlv.RecordProducer,
213
        extraData ExtraOpaqueData, customRecords CustomRecords) ([]byte,
214
        error) {
3✔
215

3✔
216
        // Construct a slice of all the records that we should include in the
3✔
217
        // message extra data field. We will start by including any records from
3✔
218
        // the extra data field.
3✔
219
        mergedRecords, err := extraData.RecordProducers()
3✔
220
        if err != nil {
3✔
221
                return nil, err
×
222
        }
×
223

224
        // Merge the known and extra data records.
225
        mergedRecords = append(mergedRecords, knownRecords...)
3✔
226

3✔
227
        // Include custom records in the extra data wire field if they are
3✔
228
        // present. Ensure that the custom records are validated before encoding
3✔
229
        // them.
3✔
230
        if err := customRecords.Validate(); err != nil {
3✔
UNCOV
231
                return nil, fmt.Errorf("custom records validation error: %w",
×
UNCOV
232
                        err)
×
UNCOV
233
        }
×
234

235
        // Extend the message extra data records slice with TLV records from the
236
        // custom records field.
237
        mergedRecords = append(
3✔
238
                mergedRecords, customRecords.RecordProducers()...,
3✔
239
        )
3✔
240

3✔
241
        // Now we can sort the records and make sure there are no records with
3✔
242
        // the same type that would collide when encoding.
3✔
243
        sortedRecords := ProduceRecordsSorted(mergedRecords...)
3✔
244
        if err := AssertUniqueTypes(sortedRecords); err != nil {
3✔
UNCOV
245
                return nil, err
×
UNCOV
246
        }
×
247

248
        return EncodeRecords(sortedRecords)
3✔
249
}
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