Skip to content

Commit a66fa11

Browse files
committed
Record fees for utxo management transactions
1 parent 160d7f3 commit a66fa11

File tree

2 files changed

+116
-19
lines changed

2 files changed

+116
-19
lines changed

accounting/entries.go

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -231,15 +231,62 @@ func sweepEntries(tx lndclient.Transaction, u entryUtils) ([]*HarmonyEntry, erro
231231
return []*HarmonyEntry{txEntry, feeEntry}, nil
232232
}
233233

234+
// isUtxoManagementTx checks whether a transaction is restructuring our utxos.
235+
func isUtxoManagementTx(txn lndclient.Transaction) bool {
236+
// Check all inputs.
237+
for _, input := range txn.PreviousOutpoints {
238+
if !input.IsOurOutput {
239+
return false
240+
}
241+
}
242+
243+
// Check all outputs.
244+
for _, output := range txn.OutputDetails {
245+
if !output.IsOurAddress {
246+
return false
247+
}
248+
}
249+
250+
// If all inputs and outputs belong to our wallet, it's utxo management.
251+
return true
252+
}
253+
254+
// createOnchainFeeEntry creates a fee entry for an on chain transaction.
255+
func createOnchainFeeEntry(tx lndclient.Transaction, category string,
256+
note string, u entryUtils) (*HarmonyEntry, error) {
257+
258+
// Total fees are expressed as a positive value in sats, we convert to
259+
// msat here and make the value negative so that it reflects as a
260+
// debit.
261+
feeAmt := invertedSatsToMsats(tx.Fee)
262+
263+
feeEntry, err := newHarmonyEntry(
264+
tx.Timestamp, feeAmt, EntryTypeFee,
265+
tx.TxHash, FeeReference(tx.TxHash), note, category, true,
266+
u.getFiat,
267+
)
268+
269+
if err != nil {
270+
return nil, err
271+
}
272+
273+
return feeEntry, nil
274+
}
275+
276+
// utxoManagementFeeNote creates a note for utxo management fee types.
277+
func utxoManagementFeeNote(txid string) string {
278+
return fmt.Sprintf("fees for utxo management transaction: %v", txid)
279+
}
280+
234281
// onChainEntries produces relevant entries for an on chain transaction.
235282
func onChainEntries(tx lndclient.Transaction,
236283
u entryUtils) ([]*HarmonyEntry, error) {
237284

238285
var (
239-
amtMsat = satsToMsat(tx.Amount)
240-
entryType EntryType
241-
feeType = EntryTypeFee
242-
category = getCategory(tx.Label, u.customCategories)
286+
amtMsat = satsToMsat(tx.Amount)
287+
entryType EntryType
288+
category = getCategory(tx.Label, u.customCategories)
289+
utxoManagement bool
243290
)
244291

245292
// Determine the type of entry we are creating. If this is a sweep, we
@@ -252,6 +299,9 @@ func onChainEntries(tx lndclient.Transaction,
252299
case amtMsat > 0:
253300
entryType = EntryTypeReceipt
254301

302+
case isUtxoManagementTx(tx):
303+
utxoManagement = true
304+
255305
// If we have a zero amount on chain transaction, we do not create an
256306
// entry for it. This may happen when the remote party claims a htlc on
257307
// our commitment. We do not want to report 0 value transactions that
@@ -260,6 +310,17 @@ func onChainEntries(tx lndclient.Transaction,
260310
return nil, nil
261311
}
262312

313+
// If this is a utxo management transaction, we return a fee entry only.
314+
if utxoManagement {
315+
note := utxoManagementFeeNote(tx.TxHash)
316+
feeEntry, err := createOnchainFeeEntry(tx, category, note, u)
317+
if err != nil {
318+
return nil, err
319+
}
320+
321+
return []*HarmonyEntry{feeEntry}, nil
322+
}
323+
263324
txEntry, err := newHarmonyEntry(
264325
tx.Timestamp, amtMsat, entryType, tx.TxHash, tx.TxHash,
265326
tx.Label, category, true, u.getFiat,
@@ -273,15 +334,7 @@ func onChainEntries(tx lndclient.Transaction,
273334
return []*HarmonyEntry{txEntry}, nil
274335
}
275336

276-
// Total fees are expressed as a positive value in sats, we convert to
277-
// msat here and make the value negative so that it reflects as a
278-
// debit.
279-
feeAmt := invertedSatsToMsats(tx.Fee)
280-
281-
feeEntry, err := newHarmonyEntry(
282-
tx.Timestamp, feeAmt, feeType, tx.TxHash,
283-
FeeReference(tx.TxHash), "", category, true, u.getFiat,
284-
)
337+
feeEntry, err := createOnchainFeeEntry(tx, category, "", u)
285338
if err != nil {
286339
return nil, err
287340
}

accounting/entries_test.go

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -501,12 +501,13 @@ func TestSweepEntry(t *testing.T) {
501501
// TestOnChainEntry tests creation of entries for receipts and payments, and the
502502
// generation of a fee entry where applicable.
503503
func TestOnChainEntry(t *testing.T) {
504-
getOnChainEntry := func(amount btcutil.Amount,
505-
hasFee bool, label string) []*HarmonyEntry {
504+
getOnChainEntry := func(amount btcutil.Amount, hasFee bool,
505+
isUtxoManagement bool, label string, note string) []*HarmonyEntry {
506506

507507
var (
508-
entryType EntryType
509-
feeType = EntryTypeFee
508+
entryType EntryType
509+
feeType = EntryTypeFee
510+
utxoManagement bool
510511
)
511512

512513
switch {
@@ -516,10 +517,33 @@ func TestOnChainEntry(t *testing.T) {
516517
case amount > 0:
517518
entryType = EntryTypeReceipt
518519

520+
case isUtxoManagement:
521+
utxoManagement = true
522+
519523
default:
520524
return nil
521525
}
522526

527+
if utxoManagement {
528+
feeAmt := satsToMsat(onChainFeeSat)
529+
feeMsat := lnwire.MilliSatoshi(feeAmt)
530+
531+
feeEntry := &HarmonyEntry{
532+
Timestamp: onChainTimestamp,
533+
Amount: feeMsat,
534+
FiatValue: fiat.MsatToFiat(mockBTCPrice.Price, feeMsat),
535+
TxID: onChainTxID,
536+
Reference: FeeReference(onChainTxID),
537+
Note: note,
538+
Type: feeType,
539+
OnChain: true,
540+
Credit: false,
541+
BTCPrice: mockBTCPrice,
542+
}
543+
544+
return []*HarmonyEntry{feeEntry}
545+
}
546+
523547
amt := satsToMsat(onChainAmtSat)
524548
amtMsat := lnwire.MilliSatoshi(amt)
525549
entry := &HarmonyEntry{
@@ -549,7 +573,7 @@ func TestOnChainEntry(t *testing.T) {
549573
FiatValue: fiat.MsatToFiat(mockBTCPrice.Price, feeMsat),
550574
TxID: onChainTxID,
551575
Reference: FeeReference(onChainTxID),
552-
Note: "",
576+
Note: note,
553577
Type: feeType,
554578
OnChain: true,
555579
Credit: false,
@@ -569,8 +593,14 @@ func TestOnChainEntry(t *testing.T) {
569593
// Whether the transaction has a fee attached.
570594
hasFee bool
571595

596+
// Whether the transaction is a sweep.
597+
isUtxoManagement bool
598+
572599
// txLabel is an optional label on the rpc transaction.
573600
txLabel string
601+
602+
// Note is the expected note on the entry.
603+
note string
574604
}{
575605
{
576606
name: "receive with fee",
@@ -597,6 +627,13 @@ func TestOnChainEntry(t *testing.T) {
597627
amount: 0,
598628
hasFee: false,
599629
},
630+
{
631+
name: "zero amount utxo management tx",
632+
amount: 0,
633+
hasFee: true,
634+
isUtxoManagement: true,
635+
note: utxoManagementFeeNote(onChainTxID),
636+
},
600637
}
601638

602639
for _, test := range tests {
@@ -615,6 +652,13 @@ func TestOnChainEntry(t *testing.T) {
615652
chainTx.Fee = 0
616653
}
617654

655+
chainTx.PreviousOutpoints = []*lnrpc.PreviousOutPoint{{
656+
IsOurOutput: test.isUtxoManagement,
657+
}}
658+
chainTx.OutputDetails = []*lnrpc.OutputDetail{{
659+
IsOurAddress: test.isUtxoManagement,
660+
}}
661+
618662
// Set the label as per the test.
619663
chainTx.Label = test.txLabel
620664

@@ -624,7 +668,7 @@ func TestOnChainEntry(t *testing.T) {
624668
// Create the entries we expect based on the test
625669
// params.
626670
expected := getOnChainEntry(
627-
test.amount, test.hasFee, test.txLabel,
671+
test.amount, test.hasFee, test.isUtxoManagement, test.txLabel, test.note,
628672
)
629673

630674
require.Equal(t, expected, entries)

0 commit comments

Comments
 (0)