• 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

64.41
/channeldb/paginate.go
1
package channeldb
2

3
import "github.com/lightningnetwork/lnd/kvdb"
4

5
type paginator struct {
6
        // cursor is the cursor which we are using to iterate through a bucket.
7
        cursor kvdb.RCursor
8

9
        // reversed indicates whether we are paginating forwards or backwards.
10
        reversed bool
11

12
        // indexOffset is the index from which we will begin querying.
13
        indexOffset uint64
14

15
        // totalItems is the total number of items we allow in our response.
16
        totalItems uint64
17
}
18

19
// newPaginator returns a struct which can be used to query an indexed bucket
20
// in pages.
21
func newPaginator(c kvdb.RCursor, reversed bool,
22
        indexOffset, totalItems uint64) paginator {
3✔
23

3✔
24
        return paginator{
3✔
25
                cursor:      c,
3✔
26
                reversed:    reversed,
3✔
27
                indexOffset: indexOffset,
3✔
28
                totalItems:  totalItems,
3✔
29
        }
3✔
30
}
3✔
31

32
// keyValueForIndex seeks our cursor to a given index and returns the key and
33
// value at that position.
34
func (p paginator) keyValueForIndex(index uint64) ([]byte, []byte) {
3✔
35
        var keyIndex [8]byte
3✔
36
        byteOrder.PutUint64(keyIndex[:], index)
3✔
37
        return p.cursor.Seek(keyIndex[:])
3✔
38
}
3✔
39

40
// lastIndex returns the last value in our index, if our index is empty it
41
// returns 0.
UNCOV
42
func (p paginator) lastIndex() uint64 {
×
UNCOV
43
        keyIndex, _ := p.cursor.Last()
×
UNCOV
44
        if keyIndex == nil {
×
45
                return 0
×
46
        }
×
47

UNCOV
48
        return byteOrder.Uint64(keyIndex)
×
49
}
50

51
// nextKey is a helper closure to determine what key we should use next when
52
// we are iterating, depending on whether we are iterating forwards or in
53
// reverse.
54
func (p paginator) nextKey() ([]byte, []byte) {
3✔
55
        if p.reversed {
3✔
UNCOV
56
                return p.cursor.Prev()
×
UNCOV
57
        }
×
58
        return p.cursor.Next()
3✔
59
}
60

61
// cursorStart gets the index key and value for the first item we are looking
62
// up, taking into account that we may be paginating in reverse. The index
63
// offset provided is *excusive* so we will start with the item after the offset
64
// for forwards queries, and the item before the index for backwards queries.
65
func (p paginator) cursorStart() ([]byte, []byte) {
3✔
66
        indexKey, indexValue := p.keyValueForIndex(p.indexOffset + 1)
3✔
67

3✔
68
        // If the query is specifying reverse iteration, then we must
3✔
69
        // handle a few offset cases.
3✔
70
        if p.reversed {
3✔
UNCOV
71
                switch {
×
72
                // This indicates the default case, where no offset was
73
                // specified. In that case we just start from the last
74
                // entry.
UNCOV
75
                case p.indexOffset == 0:
×
UNCOV
76
                        indexKey, indexValue = p.cursor.Last()
×
77

78
                // This indicates the offset being set to the very
79
                // first entry. Since there are no entries before
80
                // this offset, and the direction is reversed, we can
81
                // return without adding any invoices to the response.
UNCOV
82
                case p.indexOffset == 1:
×
UNCOV
83
                        return nil, nil
×
84

85
                // If we have been given an index offset that is beyond our last
86
                // index value, we just return the last indexed value in our set
87
                // since we are querying in reverse. We do not cover the case
88
                // where our index offset equals our last index value, because
89
                // index offset is exclusive, so we would want to start at the
90
                // value before our last index.
UNCOV
91
                case p.indexOffset > p.lastIndex():
×
UNCOV
92
                        return p.cursor.Last()
×
93

94
                // Otherwise we have an index offset which is within our set of
95
                // indexed keys, and we want to start at the item before our
96
                // offset. We seek to our index offset, then return the element
97
                // before it. We do this rather than p.indexOffset-1 to account
98
                // for indexes that have gaps.
UNCOV
99
                default:
×
UNCOV
100
                        p.keyValueForIndex(p.indexOffset)
×
UNCOV
101
                        indexKey, indexValue = p.cursor.Prev()
×
102
                }
103
        }
104

105
        return indexKey, indexValue
3✔
106
}
107

108
// query gets the start point for our index offset and iterates through keys
109
// in our index until we reach the total number of items required for the query
110
// or we run out of cursor values. This function takes a fetchAndAppend function
111
// which is responsible for looking up the entry at that index, adding the entry
112
// to its set of return items (if desired) and return a boolean which indicates
113
// whether the item was added. This is required to allow the paginator to
114
// determine when the response has the maximum number of required items.
115
func (p paginator) query(fetchAndAppend func(k, v []byte) (bool, error)) error {
3✔
116
        indexKey, indexValue := p.cursorStart()
3✔
117

3✔
118
        var totalItems int
3✔
119
        for ; indexKey != nil; indexKey, indexValue = p.nextKey() {
6✔
120
                // If our current return payload exceeds the max number
3✔
121
                // of invoices, then we'll exit now.
3✔
122
                if uint64(totalItems) >= p.totalItems {
3✔
UNCOV
123
                        break
×
124
                }
125

126
                added, err := fetchAndAppend(indexKey, indexValue)
3✔
127
                if err != nil {
3✔
128
                        return err
×
129
                }
×
130

131
                // If we added an item to our set in the latest fetch and append
132
                // we increment our total count.
133
                if added {
6✔
134
                        totalItems++
3✔
135
                }
3✔
136
        }
137

138
        return nil
3✔
139
}
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