Skip to content

Commit d646977

Browse files
authored
Merge pull request #85 from carlaKC/81-customentries
audit: add custom categories to reporting
2 parents 438cb32 + 265247b commit d646977

File tree

14 files changed

+707
-197
lines changed

14 files changed

+707
-197
lines changed

accounting/categories.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package accounting
2+
3+
import "regexp"
4+
5+
// CustomCategory describes a custom category which can be used to identify
6+
// special case groups of transactions.
7+
type CustomCategory struct {
8+
// Name is the custom name for the category.
9+
Name string
10+
11+
// Regexes is a slice of regexes which we will use to match labels. If
12+
// a label matches any one expression in this set, it is considered
13+
// part of the category.
14+
Regexes []*regexp.Regexp
15+
}
16+
17+
// NewCustomCategory creates compiles the set of regexes provided and returning
18+
// a new category. This function assumes that each regex is unique.
19+
func NewCustomCategory(name string, regexes []string) (*CustomCategory, error) {
20+
category := &CustomCategory{
21+
Name: name,
22+
}
23+
24+
for _, regex := range regexes {
25+
exp, err := regexp.Compile(regex)
26+
if err != nil {
27+
return nil, err
28+
}
29+
30+
category.Regexes = append(category.Regexes, exp)
31+
}
32+
33+
return category, nil
34+
}
35+
36+
// isMember returns a boolean indicating whether a label belongs in a custom
37+
// category. If the label matches any one of the category's regexes, it is
38+
// considered a member of the category.
39+
func (c CustomCategory) isMember(label string) bool {
40+
for _, regex := range c.Regexes {
41+
isMatch := regex.Match([]byte(label))
42+
if isMatch {
43+
return true
44+
}
45+
}
46+
47+
return false
48+
}
49+
50+
// getCategory matches a label against a set of custom categories, and returns
51+
// the name of the category it belongs in (if any).
52+
func getCategory(label string, categories []CustomCategory) string {
53+
for _, category := range categories {
54+
if category.isMember(label) {
55+
return category.Name
56+
}
57+
}
58+
59+
return ""
60+
}

accounting/config.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ type CommonConfig struct {
8888
// Granularity specifies the level of granularity with which we want to
8989
// get fiat prices.
9090
Granularity *fiat.Granularity
91+
92+
// Categories is a set of custom categories which should be added to the
93+
// report.
94+
Categories []CustomCategory
9195
}
9296

9397
// NewOnChainConfig returns an on chain config from the lnd services provided.
@@ -96,7 +100,7 @@ type CommonConfig struct {
96100
// that fee lookups are not possible in certain cases.
97101
func NewOnChainConfig(ctx context.Context, lnd lndclient.LndServices, startTime,
98102
endTime time.Time, disableFiat bool, txLookup fees.GetDetailsFunc,
99-
granularity *fiat.Granularity) *OnChainConfig {
103+
granularity *fiat.Granularity, categories []CustomCategory) *OnChainConfig {
100104

101105
var getFee func(chainhash.Hash) (btcutil.Amount, error)
102106
if txLookup != nil {
@@ -126,6 +130,7 @@ func NewOnChainConfig(ctx context.Context, lnd lndclient.LndServices, startTime,
126130
EndTime: endTime,
127131
DisableFiat: disableFiat,
128132
Granularity: granularity,
133+
Categories: categories,
129134
},
130135
GetFee: getFee,
131136
}
@@ -137,7 +142,7 @@ func NewOnChainConfig(ctx context.Context, lnd lndclient.LndServices, startTime,
137142
func NewOffChainConfig(ctx context.Context, lnd lndclient.LndServices,
138143
maxInvoices, maxPayments, maxForwards uint64, ownPubkey route.Vertex,
139144
startTime, endTime time.Time, disableFiat bool,
140-
granularity *fiat.Granularity) *OffChainConfig {
145+
granularity *fiat.Granularity, categories []CustomCategory) *OffChainConfig {
141146

142147
return &OffChainConfig{
143148
ListInvoices: func() ([]lndclient.Invoice, error) {
@@ -169,6 +174,7 @@ func NewOffChainConfig(ctx context.Context, lnd lndclient.LndServices,
169174
EndTime: endTime,
170175
DisableFiat: disableFiat,
171176
Granularity: granularity,
177+
Categories: categories,
172178
},
173179
}
174180
}

accounting/entries.go

Lines changed: 55 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,21 @@ import (
1111
"github.com/lightningnetwork/lnd/routing/route"
1212
)
1313

14+
// entryUtils contains the utility functions required to create an entry.
15+
type entryUtils struct {
16+
// getFee looks up the fees for an on chain transaction, this function
17+
// may be nil.
18+
getFee getFeeFunc
19+
20+
// getFiat provides a USD price for the btc value provided at its
21+
// timestamp.
22+
getFiat usdPrice
23+
24+
// customCategories is a set of custom categories which are set for the
25+
// report.
26+
customCategories []CustomCategory
27+
}
28+
1429
// FeeReference returns a special unique reference for the fee paid on a
1530
// transaction. We use the reference of the original entry with :-1 to denote
1631
// that this entry is associated with the original entry.
@@ -39,7 +54,7 @@ func channelOpenFeeNote(channelID lnwire.ShortChannelID) string {
3954

4055
// channelOpenEntries produces entries for channel opens and their fees.
4156
func channelOpenEntries(channel channelInfo, tx lndclient.Transaction,
42-
convert usdPrice) ([]*HarmonyEntry, error) {
57+
u entryUtils) ([]*HarmonyEntry, error) {
4358

4459
// If the transaction has a negative amount, we can infer that this
4560
// transaction was a local channel open, because a remote party opening
@@ -57,10 +72,12 @@ func channelOpenEntries(channel channelInfo, tx lndclient.Transaction,
5772
channel.capacity,
5873
)
5974

75+
category := getCategory(tx.Label, u.customCategories)
76+
6077
openEntry, err := newHarmonyEntry(
6178
tx.Timestamp, amtMsat, entryType, tx.TxHash,
62-
channel.channelID.String(), note,
63-
true, convert,
79+
channel.channelID.String(), note, category,
80+
true, u.getFiat,
6481
)
6582
if err != nil {
6683
return nil, err
@@ -80,8 +97,8 @@ func channelOpenEntries(channel channelInfo, tx lndclient.Transaction,
8097

8198
note = channelOpenFeeNote(channel.channelID)
8299
feeEntry, err := newHarmonyEntry(
83-
tx.Timestamp, feeMsat, EntryTypeChannelOpenFee,
84-
tx.TxHash, FeeReference(tx.TxHash), note, true, convert,
100+
tx.Timestamp, feeMsat, EntryTypeChannelOpenFee, tx.TxHash,
101+
FeeReference(tx.TxHash), note, category, true, u.getFiat,
85102
)
86103
if err != nil {
87104
return nil, err
@@ -104,16 +121,18 @@ func channelCloseNote(channelID lnwire.ShortChannelID, closeType,
104121
// it is excluding htlcs that are resolved on chain, and will not reflect our
105122
// balance when we force close (because it is behind a timelock).
106123
func closedChannelEntries(channel closedChannelInfo, tx lndclient.Transaction,
107-
getFee getFeeFunc, convert usdPrice) ([]*HarmonyEntry, error) {
124+
u entryUtils) ([]*HarmonyEntry, error) {
108125

109126
amtMsat := satsToMsat(tx.Amount)
110127
note := channelCloseNote(
111128
channel.channelID, channel.closeType, channel.closeInitiator,
112129
)
113130

131+
category := getCategory(tx.Label, u.customCategories)
132+
114133
closeEntry, err := newHarmonyEntry(
115134
tx.Timestamp, amtMsat, EntryTypeChannelClose, tx.TxHash,
116-
tx.TxHash, note, true, convert,
135+
tx.TxHash, note, category, true, u.getFiat,
117136
)
118137
if err != nil {
119138
return nil, err
@@ -143,15 +162,15 @@ func closedChannelEntries(channel closedChannelInfo, tx lndclient.Transaction,
143162
// we paid the fees (because we initiated the channel). If we do not
144163
// have a fee lookup function, we cannot get fees for this channel so
145164
// we log a warning and return without a fee entry.
146-
if getFee == nil {
165+
if u.getFee == nil {
147166
log.Warnf("no bitcoin backend provided to lookup fees, "+
148167
"channel close fee entry for: %v omitted",
149168
channel.channelPoint)
150169

151170
return []*HarmonyEntry{closeEntry}, nil
152171
}
153172

154-
fees, err := getFee(tx.Tx.TxHash())
173+
fees, err := u.getFee(tx.Tx.TxHash())
155174
if err != nil {
156175
return nil, err
157176
}
@@ -162,8 +181,8 @@ func closedChannelEntries(channel closedChannelInfo, tx lndclient.Transaction,
162181

163182
feeEntry, err := newHarmonyEntry(
164183
tx.Timestamp, feeAmt, EntryTypeChannelCloseFee,
165-
tx.TxHash, FeeReference(tx.TxHash), "",
166-
true, convert,
184+
tx.TxHash, FeeReference(tx.TxHash), "", category,
185+
true, u.getFiat,
167186
)
168187
if err != nil {
169188
return nil, err
@@ -174,12 +193,12 @@ func closedChannelEntries(channel closedChannelInfo, tx lndclient.Transaction,
174193

175194
// sweepEntries creates a sweep entry and looks up its fee to create a fee
176195
// entry.
177-
func sweepEntries(tx lndclient.Transaction, getFees getFeeFunc,
178-
convert usdPrice) ([]*HarmonyEntry, error) {
196+
func sweepEntries(tx lndclient.Transaction, u entryUtils) ([]*HarmonyEntry, error) {
197+
category := getCategory(tx.Label, u.customCategories)
179198

180199
txEntry, err := newHarmonyEntry(
181200
tx.Timestamp, satsToMsat(tx.Amount), EntryTypeSweep, tx.TxHash,
182-
tx.TxHash, tx.Label, true, convert,
201+
tx.TxHash, tx.Label, category, true, u.getFiat,
183202
)
184203
if err != nil {
185204
return nil, err
@@ -188,21 +207,22 @@ func sweepEntries(tx lndclient.Transaction, getFees getFeeFunc,
188207
// If we do not have a fee lookup function set, we log a warning that
189208
// we cannot record fees for the sweep transaction and return wihtout
190209
// adding a fee entry.
191-
if getFees == nil {
210+
if u.getFee == nil {
192211
log.Warnf("no bitcoin backend provided to lookup fees, "+
193212
"sweep fee entry for: %v omitted", tx.TxHash)
194213

195214
return []*HarmonyEntry{txEntry}, nil
196215
}
197216

198-
fee, err := getFees(tx.Tx.TxHash())
217+
fee, err := u.getFee(tx.Tx.TxHash())
199218
if err != nil {
200219
return nil, err
201220
}
202221

203222
feeEntry, err := newHarmonyEntry(
204223
tx.Timestamp, invertedSatsToMsats(fee), EntryTypeSweepFee,
205-
tx.TxHash, FeeReference(tx.TxHash), "", true, convert,
224+
tx.TxHash, FeeReference(tx.TxHash), "", category, true,
225+
u.getFiat,
206226
)
207227
if err != nil {
208228
return nil, err
@@ -213,12 +233,13 @@ func sweepEntries(tx lndclient.Transaction, getFees getFeeFunc,
213233

214234
// onChainEntries produces relevant entries for an on chain transaction.
215235
func onChainEntries(tx lndclient.Transaction,
216-
convert usdPrice) ([]*HarmonyEntry, error) {
236+
u entryUtils) ([]*HarmonyEntry, error) {
217237

218238
var (
219239
amtMsat = satsToMsat(tx.Amount)
220240
entryType EntryType
221241
feeType = EntryTypeFee
242+
category = getCategory(tx.Label, u.customCategories)
222243
)
223244

224245
// Determine the type of entry we are creating. If this is a sweep, we
@@ -241,7 +262,7 @@ func onChainEntries(tx lndclient.Transaction,
241262

242263
txEntry, err := newHarmonyEntry(
243264
tx.Timestamp, amtMsat, entryType, tx.TxHash, tx.TxHash,
244-
tx.Label, true, convert,
265+
tx.Label, category, true, u.getFiat,
245266
)
246267
if err != nil {
247268
return nil, err
@@ -259,7 +280,7 @@ func onChainEntries(tx lndclient.Transaction,
259280

260281
feeEntry, err := newHarmonyEntry(
261282
tx.Timestamp, feeAmt, feeType, tx.TxHash,
262-
FeeReference(tx.TxHash), "", true, convert,
283+
FeeReference(tx.TxHash), "", category, true, u.getFiat,
263284
)
264285
if err != nil {
265286
return nil, err
@@ -297,7 +318,9 @@ func invoiceNote(memo string, amt, amtPaid lnwire.MilliSatoshi,
297318

298319
// invoiceEntry creates an entry for an invoice.
299320
func invoiceEntry(invoice lndclient.Invoice, circularReceipt bool,
300-
convert usdPrice) (*HarmonyEntry, error) {
321+
u entryUtils) (*HarmonyEntry, error) {
322+
323+
category := getCategory(invoice.Memo, u.customCategories)
301324

302325
eventType := EntryTypeReceipt
303326
if circularReceipt {
@@ -311,8 +334,8 @@ func invoiceEntry(invoice lndclient.Invoice, circularReceipt bool,
311334

312335
return newHarmonyEntry(
313336
invoice.SettleDate, int64(invoice.AmountPaid), eventType,
314-
invoice.Hash.String(), invoice.Preimage.String(), note, false,
315-
convert,
337+
invoice.Hash.String(), invoice.Preimage.String(), note,
338+
category, false, u.getFiat,
316339
)
317340
}
318341

@@ -335,7 +358,7 @@ func paymentNote(dest *route.Vertex) string {
335358
// paymentEntry creates an entry for an off chain payment, including fee entries
336359
// where required.
337360
func paymentEntry(payment paymentInfo, paidToSelf bool,
338-
convert usdPrice) ([]*HarmonyEntry, error) {
361+
u entryUtils) ([]*HarmonyEntry, error) {
339362

340363
// It is possible to make a payment to ourselves as part of a circular
341364
// rebalance which is operationally used to shift funds between
@@ -364,8 +387,8 @@ func paymentEntry(payment paymentInfo, paidToSelf bool,
364387
amt := invertMsat(int64(payment.Amount))
365388

366389
paymentEntry, err := newHarmonyEntry(
367-
payment.settleTime, amt, paymentType,
368-
payment.Hash.String(), ref, note, false, convert,
390+
payment.settleTime, amt, paymentType, payment.Hash.String(),
391+
ref, note, "", false, u.getFiat,
369392
)
370393
if err != nil {
371394
return nil, err
@@ -381,8 +404,8 @@ func paymentEntry(payment paymentInfo, paidToSelf bool,
381404
feeAmt := invertMsat(int64(payment.Fee))
382405

383406
feeEntry, err := newHarmonyEntry(
384-
payment.settleTime, feeAmt, feeType,
385-
payment.Hash.String(), feeRef, note, false, convert,
407+
payment.settleTime, feeAmt, feeType, payment.Hash.String(),
408+
feeRef, note, "", false, u.getFiat,
386409
)
387410
if err != nil {
388411
return nil, err
@@ -409,14 +432,14 @@ func forwardNote(amtIn, amtOut lnwire.MilliSatoshi) string {
409432
// shifting of funds in our channels, and fees entry which reflects the fees we
410433
// earned form the forward.
411434
func forwardingEntry(forward lndclient.ForwardingEvent,
412-
convert usdPrice) ([]*HarmonyEntry, error) {
435+
u entryUtils) ([]*HarmonyEntry, error) {
413436

414437
txid := forwardTxid(forward)
415438
note := forwardNote(forward.AmountMsatIn, forward.AmountMsatOut)
416439

417440
fwdEntry, err := newHarmonyEntry(
418-
forward.Timestamp, 0, EntryTypeForward, txid, "", note,
419-
false, convert,
441+
forward.Timestamp, 0, EntryTypeForward, txid, "", note, "",
442+
false, u.getFiat,
420443
)
421444
if err != nil {
422445
return nil, err
@@ -429,7 +452,7 @@ func forwardingEntry(forward lndclient.ForwardingEvent,
429452

430453
feeEntry, err := newHarmonyEntry(
431454
forward.Timestamp, int64(forward.FeeMsat),
432-
EntryTypeForwardFee, txid, "", "", false, convert,
455+
EntryTypeForwardFee, txid, "", "", "", false, u.getFiat,
433456
)
434457
if err != nil {
435458
return nil, err

0 commit comments

Comments
 (0)