• 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
/watchtower/wtdb/migration1/client_db.go
1
package migration1
2

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

8
        "github.com/lightningnetwork/lnd/kvdb"
9
)
10

11
var (
12
        // cSessionBkt is a top-level bucket storing:
13
        //   session-id => cSessionBody -> encoded ClientSessionBody
14
        //              => cSessionCommits => seqnum -> encoded CommittedUpdate
15
        //              => cSessionAcks => seqnum -> encoded BackupID
16
        cSessionBkt = []byte("client-session-bucket")
17

18
        // cSessionBody is a sub-bucket of cSessionBkt storing only the body of
19
        // the ClientSession.
20
        cSessionBody = []byte("client-session-body")
21

22
        // cTowerIDToSessionIDIndexBkt is a top-level bucket storing:
23
        //         tower-id -> session-id -> 1
24
        cTowerIDToSessionIDIndexBkt = []byte(
25
                "client-tower-to-session-index-bucket",
26
        )
27

28
        // ErrUninitializedDB signals that top-level buckets for the database
29
        // have not been initialized.
30
        ErrUninitializedDB = errors.New("db not initialized")
31

32
        // ErrClientSessionNotFound signals that the requested client session
33
        // was not found in the database.
34
        ErrClientSessionNotFound = errors.New("client session not found")
35

36
        // ErrCorruptClientSession signals that the client session's on-disk
37
        // structure deviates from what is expected.
38
        ErrCorruptClientSession = errors.New("client session corrupted")
39
)
40

41
// MigrateTowerToSessionIndex constructs a new towerID-to-sessionID for the
42
// watchtower client DB.
UNCOV
43
func MigrateTowerToSessionIndex(tx kvdb.RwTx) error {
×
UNCOV
44
        log.Infof("Migrating the tower client db to add a " +
×
UNCOV
45
                "towerID-to-sessionID index")
×
UNCOV
46

×
UNCOV
47
        // First, we collect all the entries we want to add to the index.
×
UNCOV
48
        entries, err := getIndexEntries(tx)
×
UNCOV
49
        if err != nil {
×
UNCOV
50
                return err
×
UNCOV
51
        }
×
52

53
        // Then we create a new top-level bucket for the index.
UNCOV
54
        indexBkt, err := tx.CreateTopLevelBucket(cTowerIDToSessionIDIndexBkt)
×
UNCOV
55
        if err != nil {
×
56
                return err
×
57
        }
×
58

59
        // Finally, we add all the collected entries to the index.
UNCOV
60
        for towerID, sessions := range entries {
×
UNCOV
61
                // Create a sub-bucket using the tower ID.
×
UNCOV
62
                towerBkt, err := indexBkt.CreateBucketIfNotExists(
×
UNCOV
63
                        towerID.Bytes(),
×
UNCOV
64
                )
×
UNCOV
65
                if err != nil {
×
66
                        return err
×
67
                }
×
68

UNCOV
69
                for sessionID := range sessions {
×
UNCOV
70
                        err := addIndex(towerBkt, sessionID)
×
UNCOV
71
                        if err != nil {
×
72
                                return err
×
73
                        }
×
74
                }
75
        }
76

UNCOV
77
        return nil
×
78
}
79

80
// addIndex adds a new towerID-sessionID pair to the given bucket. The
81
// session ID is used as a key within the bucket and a value of []byte{1} is
82
// used for each session ID key.
UNCOV
83
func addIndex(towerBkt kvdb.RwBucket, sessionID SessionID) error {
×
UNCOV
84
        session := towerBkt.Get(sessionID[:])
×
UNCOV
85
        if session != nil {
×
86
                return fmt.Errorf("session %x duplicated", sessionID)
×
87
        }
×
88

UNCOV
89
        return towerBkt.Put(sessionID[:], []byte{1})
×
90
}
91

92
// getIndexEntries collects all the towerID-sessionID entries that need to be
93
// added to the new index.
UNCOV
94
func getIndexEntries(tx kvdb.RwTx) (map[TowerID]map[SessionID]bool, error) {
×
UNCOV
95
        sessions := tx.ReadBucket(cSessionBkt)
×
UNCOV
96
        if sessions == nil {
×
97
                return nil, ErrUninitializedDB
×
98
        }
×
99

UNCOV
100
        index := make(map[TowerID]map[SessionID]bool)
×
UNCOV
101
        err := sessions.ForEach(func(k, _ []byte) error {
×
UNCOV
102
                session, err := getClientSession(sessions, k)
×
UNCOV
103
                if err != nil {
×
UNCOV
104
                        return err
×
UNCOV
105
                }
×
106

UNCOV
107
                if index[session.TowerID] == nil {
×
UNCOV
108
                        index[session.TowerID] = make(map[SessionID]bool)
×
UNCOV
109
                }
×
110

UNCOV
111
                index[session.TowerID][session.ID] = true
×
UNCOV
112
                return nil
×
113
        })
UNCOV
114
        if err != nil {
×
UNCOV
115
                return nil, err
×
UNCOV
116
        }
×
117

UNCOV
118
        return index, nil
×
119
}
120

121
// getClientSession fetches the session with the given ID from the db.
122
func getClientSession(sessions kvdb.RBucket, idBytes []byte) (*ClientSession,
UNCOV
123
        error) {
×
UNCOV
124

×
UNCOV
125
        sessionBkt := sessions.NestedReadBucket(idBytes)
×
UNCOV
126
        if sessionBkt == nil {
×
127
                return nil, ErrClientSessionNotFound
×
128
        }
×
129

130
        // Should never have a sessionBkt without also having its body.
UNCOV
131
        sessionBody := sessionBkt.Get(cSessionBody)
×
UNCOV
132
        if sessionBody == nil {
×
UNCOV
133
                return nil, ErrCorruptClientSession
×
UNCOV
134
        }
×
135

UNCOV
136
        var session ClientSession
×
UNCOV
137
        copy(session.ID[:], idBytes)
×
UNCOV
138

×
UNCOV
139
        err := session.Decode(bytes.NewReader(sessionBody))
×
UNCOV
140
        if err != nil {
×
141
                return nil, err
×
142
        }
×
143

UNCOV
144
        return &session, nil
×
145
}
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