• 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

66.32
/watchtower/wtserver/state_update.go
1
package wtserver
2

3
import (
4
        "fmt"
5

6
        "github.com/lightningnetwork/lnd/watchtower/wtdb"
7
        "github.com/lightningnetwork/lnd/watchtower/wtwire"
8
)
9

10
// handleStateUpdates processes a stream of StateUpdate requests from the
11
// client. The provided update should be the first such update read, subsequent
12
// updates will be consumed if the peer does not signal IsComplete on a
13
// particular update.
14
func (s *Server) handleStateUpdates(peer Peer, id *wtdb.SessionID,
15
        update *wtwire.StateUpdate) error {
3✔
16

3✔
17
        // Set the current update to the first update read off the wire.
3✔
18
        // Additional updates will be read if this value is set to nil after
3✔
19
        // processing the first.
3✔
20
        var curUpdate = update
3✔
21
        for {
6✔
22
                // If this is not the first update, read the next state update
3✔
23
                // from the peer.
3✔
24
                if curUpdate == nil {
6✔
25
                        nextMsg, err := s.readMessage(peer)
3✔
26
                        if err != nil {
3✔
UNCOV
27
                                return err
×
UNCOV
28
                        }
×
29

30
                        var ok bool
3✔
31
                        curUpdate, ok = nextMsg.(*wtwire.StateUpdate)
3✔
32
                        if !ok {
3✔
33
                                return fmt.Errorf("client sent %T after "+
×
34
                                        "StateUpdate", nextMsg)
×
35
                        }
×
36
                }
37

38
                // Try to accept the state update from the client.
39
                err := s.handleStateUpdate(peer, id, curUpdate)
3✔
40
                if err != nil {
3✔
UNCOV
41
                        return err
×
UNCOV
42
                }
×
43

44
                // If the client signals that this is last StateUpdate
45
                // message, we can disconnect the client.
46
                if curUpdate.IsComplete == 1 {
6✔
47
                        return nil
3✔
48
                }
3✔
49

50
                // Reset the current update to read subsequent updates in the
51
                // stream.
52
                curUpdate = nil
3✔
53

3✔
54
                select {
3✔
55
                case <-s.quit:
×
56
                        return ErrServerExiting
×
57
                default:
3✔
58
                }
59
        }
60
}
61

62
// handleStateUpdate processes a StateUpdate message request from a client. An
63
// attempt will be made to insert the update into the db, where it is validated
64
// against the client's session. The possible errors are then mapped back to
65
// StateUpdateCodes specified by the watchtower wire protocol, and sent back
66
// using a StateUpdateReply message.
67
func (s *Server) handleStateUpdate(peer Peer, id *wtdb.SessionID,
68
        update *wtwire.StateUpdate) error {
3✔
69

3✔
70
        var (
3✔
71
                lastApplied uint16
3✔
72
                failCode    wtwire.ErrorCode
3✔
73
                err         error
3✔
74
        )
3✔
75

3✔
76
        sessionUpdate := wtdb.SessionStateUpdate{
3✔
77
                ID:            *id,
3✔
78
                Hint:          update.Hint,
3✔
79
                SeqNum:        update.SeqNum,
3✔
80
                LastApplied:   update.LastApplied,
3✔
81
                EncryptedBlob: update.EncryptedBlob,
3✔
82
        }
3✔
83

3✔
84
        lastApplied, err = s.cfg.DB.InsertStateUpdate(&sessionUpdate)
3✔
85
        switch {
3✔
86
        case err == nil:
3✔
87
                log.Debugf("State update %d accepted for %s",
3✔
88
                        update.SeqNum, id)
3✔
89

3✔
90
                failCode = wtwire.CodeOK
3✔
91

92
        // Return a permanent failure if a client tries to send an update for
93
        // which we have no session.
94
        case err == wtdb.ErrSessionNotFound:
×
95
                failCode = wtwire.CodePermanentFailure
×
96

UNCOV
97
        case err == wtdb.ErrSeqNumAlreadyApplied:
×
UNCOV
98
                failCode = wtwire.CodePermanentFailure
×
99

100
                // TODO(conner): remove session state for protocol
101
                // violation. Could also double as clean up method for
102
                // session-related state.
103

UNCOV
104
        case err == wtdb.ErrLastAppliedReversion:
×
UNCOV
105
                failCode = wtwire.StateUpdateCodeClientBehind
×
106

UNCOV
107
        case err == wtdb.ErrSessionConsumed:
×
UNCOV
108
                failCode = wtwire.StateUpdateCodeMaxUpdatesExceeded
×
109

UNCOV
110
        case err == wtdb.ErrUpdateOutOfOrder:
×
UNCOV
111
                failCode = wtwire.StateUpdateCodeSeqNumOutOfOrder
×
112

113
        default:
×
114
                failCode = wtwire.CodeTemporaryFailure
×
115
        }
116

117
        if s.cfg.NoAckUpdates {
3✔
UNCOV
118
                return &connFailure{
×
UNCOV
119
                        ID:   *id,
×
UNCOV
120
                        Code: failCode,
×
UNCOV
121
                }
×
UNCOV
122
        }
×
123

124
        return s.replyStateUpdate(
3✔
125
                peer, id, failCode, lastApplied,
3✔
126
        )
3✔
127
}
128

129
// replyStateUpdate sends a response to a StateUpdate from a client. If the
130
// status code in the reply is OK, the error from the write will be bubbled up.
131
// Otherwise, this method returns a connection error to ensure we don't continue
132
// communication with the client.
133
func (s *Server) replyStateUpdate(peer Peer, id *wtdb.SessionID,
134
        code wtwire.StateUpdateCode, lastApplied uint16) error {
3✔
135

3✔
136
        msg := &wtwire.StateUpdateReply{
3✔
137
                Code:        code,
3✔
138
                LastApplied: lastApplied,
3✔
139
        }
3✔
140

3✔
141
        err := s.sendMessage(peer, msg)
3✔
142
        if err != nil {
3✔
UNCOV
143
                log.Errorf("unable to send StateUpdateReply to %s", id)
×
UNCOV
144
        }
×
145

146
        // Return the write error if the request succeeded.
147
        if code == wtwire.CodeOK {
6✔
148
                return err
3✔
149
        }
3✔
150

151
        // Otherwise the request failed, return a connection failure to
152
        // disconnect the client.
UNCOV
153
        return &connFailure{
×
UNCOV
154
                ID:   *id,
×
UNCOV
155
                Code: code,
×
UNCOV
156
        }
×
157
}
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