• 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

70.83
/htlcswitch/resolution_store.go
1
package htlcswitch
2

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

7
        "github.com/go-errors/errors"
8
        "github.com/lightningnetwork/lnd/channeldb"
9
        "github.com/lightningnetwork/lnd/contractcourt"
10
        "github.com/lightningnetwork/lnd/kvdb"
11
        "github.com/lightningnetwork/lnd/lnwire"
12
)
13

14
var (
15
        // resBucketKey is used for the root level bucket that stores the
16
        // CircuitKey -> ResolutionMsg mapping.
17
        resBucketKey = []byte("resolution-store-bucket-key")
18

19
        // errResMsgNotFound is used to let callers know that the resolution
20
        // message was not found for the given CircuitKey. This is used in the
21
        // checkResolutionMsg function.
22
        errResMsgNotFound = errors.New("resolution message not found")
23
)
24

25
// resolutionStore contains ResolutionMsgs received from the contractcourt. The
26
// Switch deletes these from the store when the underlying circuit has been
27
// removed via DeleteCircuits. If the circuit hasn't been deleted, the Switch
28
// will dispatch the ResolutionMsg to a link if this was a multi-hop HTLC or to
29
// itself if the Switch initiated the payment.
30
type resolutionStore struct {
31
        backend kvdb.Backend
32
}
33

34
func newResolutionStore(db kvdb.Backend) *resolutionStore {
3✔
35
        return &resolutionStore{
3✔
36
                backend: db,
3✔
37
        }
3✔
38
}
3✔
39

40
// addResolutionMsg persists a ResolutionMsg to the resolutionStore.
41
func (r *resolutionStore) addResolutionMsg(
42
        resMsg *contractcourt.ResolutionMsg) error {
3✔
43

3✔
44
        // The outKey will be the database key.
3✔
45
        outKey := &CircuitKey{
3✔
46
                ChanID: resMsg.SourceChan,
3✔
47
                HtlcID: resMsg.HtlcIndex,
3✔
48
        }
3✔
49

3✔
50
        var resBuf bytes.Buffer
3✔
51
        if err := serializeResolutionMsg(&resBuf, resMsg); err != nil {
3✔
52
                return err
×
53
        }
×
54

55
        err := kvdb.Update(r.backend, func(tx kvdb.RwTx) error {
6✔
56
                resBucket, err := tx.CreateTopLevelBucket(resBucketKey)
3✔
57
                if err != nil {
3✔
58
                        return err
×
59
                }
×
60

61
                return resBucket.Put(outKey.Bytes(), resBuf.Bytes())
3✔
62
        }, func() {})
3✔
63
        if err != nil {
3✔
64
                return err
×
65
        }
×
66

67
        return nil
3✔
68
}
69

70
// checkResolutionMsg returns nil if the resolution message is found in the
71
// store. It returns an error if no resolution message was found for the
72
// passed outKey or if a database error occurred.
73
func (r *resolutionStore) checkResolutionMsg(outKey *CircuitKey) error {
3✔
74
        err := kvdb.View(r.backend, func(tx kvdb.RTx) error {
6✔
75
                resBucket := tx.ReadBucket(resBucketKey)
3✔
76
                if resBucket == nil {
3✔
77
                        // Return an error if the bucket doesn't exist.
×
78
                        return errResMsgNotFound
×
79
                }
×
80

81
                msg := resBucket.Get(outKey.Bytes())
3✔
82
                if msg == nil {
3✔
UNCOV
83
                        // Return the not found error since no message exists
×
UNCOV
84
                        // for this CircuitKey.
×
UNCOV
85
                        return errResMsgNotFound
×
UNCOV
86
                }
×
87

88
                // Return nil to indicate that the message was found.
89
                return nil
3✔
90
        }, func() {})
3✔
91
        if err != nil {
3✔
UNCOV
92
                return err
×
UNCOV
93
        }
×
94

95
        return nil
3✔
96
}
97

98
// fetchAllResolutionMsg returns a slice of all stored ResolutionMsgs. This is
99
// used by the Switch on start-up.
100
func (r *resolutionStore) fetchAllResolutionMsg() (
101
        []*contractcourt.ResolutionMsg, error) {
3✔
102

3✔
103
        var msgs []*contractcourt.ResolutionMsg
3✔
104

3✔
105
        err := kvdb.View(r.backend, func(tx kvdb.RTx) error {
6✔
106
                resBucket := tx.ReadBucket(resBucketKey)
3✔
107
                if resBucket == nil {
6✔
108
                        return nil
3✔
109
                }
3✔
110

111
                return resBucket.ForEach(func(k, v []byte) error {
6✔
112
                        kr := bytes.NewReader(k)
3✔
113
                        outKey := &CircuitKey{}
3✔
114
                        if err := outKey.Decode(kr); err != nil {
3✔
115
                                return err
×
116
                        }
×
117

118
                        vr := bytes.NewReader(v)
3✔
119
                        resMsg, err := deserializeResolutionMsg(vr)
3✔
120
                        if err != nil {
3✔
121
                                return err
×
122
                        }
×
123

124
                        // Set the CircuitKey values on the ResolutionMsg.
125
                        resMsg.SourceChan = outKey.ChanID
3✔
126
                        resMsg.HtlcIndex = outKey.HtlcID
3✔
127

3✔
128
                        msgs = append(msgs, resMsg)
3✔
129
                        return nil
3✔
130
                })
131
        }, func() {
3✔
132
                msgs = nil
3✔
133
        })
3✔
134
        if err != nil {
3✔
135
                return nil, err
×
136
        }
×
137

138
        return msgs, nil
3✔
139
}
140

141
// deleteResolutionMsg removes a ResolutionMsg with the passed-in CircuitKey.
142
func (r *resolutionStore) deleteResolutionMsg(outKey *CircuitKey) error {
3✔
143
        err := kvdb.Update(r.backend, func(tx kvdb.RwTx) error {
6✔
144
                resBucket, err := tx.CreateTopLevelBucket(resBucketKey)
3✔
145
                if err != nil {
3✔
146
                        return err
×
147
                }
×
148

149
                return resBucket.Delete(outKey.Bytes())
3✔
150
        }, func() {})
3✔
151
        return err
3✔
152
}
153

154
// serializeResolutionMsg writes part of a ResolutionMsg to the passed
155
// io.Writer.
156
func serializeResolutionMsg(w io.Writer,
157
        resMsg *contractcourt.ResolutionMsg) error {
3✔
158

3✔
159
        isFail := resMsg.Failure != nil
3✔
160

3✔
161
        if err := channeldb.WriteElement(w, isFail); err != nil {
3✔
162
                return err
×
163
        }
×
164

165
        // If this is a failure message, then we're done serializing.
166
        if isFail {
6✔
167
                return nil
3✔
168
        }
3✔
169

170
        // Else this is a settle message, and we need to write the preimage.
171
        return channeldb.WriteElement(w, *resMsg.PreImage)
3✔
172
}
173

174
// deserializeResolutionMsg reads part of a ResolutionMsg from the passed
175
// io.Reader.
176
func deserializeResolutionMsg(r io.Reader) (*contractcourt.ResolutionMsg,
177
        error) {
3✔
178

3✔
179
        resMsg := &contractcourt.ResolutionMsg{}
3✔
180
        var isFail bool
3✔
181

3✔
182
        if err := channeldb.ReadElements(r, &isFail); err != nil {
3✔
183
                return nil, err
×
184
        }
×
185

186
        // If a failure resolution msg was stored, set the Failure field.
187
        if isFail {
6✔
188
                failureMsg := &lnwire.FailPermanentChannelFailure{}
3✔
189
                resMsg.Failure = failureMsg
3✔
190
                return resMsg, nil
3✔
191
        }
3✔
192

UNCOV
193
        var preimage [32]byte
×
UNCOV
194
        resMsg.PreImage = &preimage
×
UNCOV
195

×
UNCOV
196
        // Else this is a settle resolution msg and we will read the preimage.
×
UNCOV
197
        if err := channeldb.ReadElement(r, resMsg.PreImage); err != nil {
×
198
                return nil, err
×
199
        }
×
200

UNCOV
201
        return resMsg, nil
×
202
}
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