Skip to content

Commit

Permalink
Record fees for utxo management transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
mrfelton committed Jan 28, 2024
1 parent dfa3fcb commit dc2efc5
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 32 deletions.
71 changes: 58 additions & 13 deletions accounting/entries.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,15 +231,55 @@ func sweepEntries(tx lndclient.Transaction, u entryUtils) ([]*HarmonyEntry, erro
return []*HarmonyEntry{txEntry, feeEntry}, nil
}

// isUtxoManagementTx checks whether a transaction is restructuring our utxos.
func isUtxoManagementTx(txn lndclient.Transaction) bool {
// Check all inputs.
for _, input := range txn.PreviousOutpoints {
if !input.IsOurOutput {
return false
}
}

// Check all outputs.
for _, output := range txn.OutputDetails {
if !output.IsOurAddress {
return false
}
}

// If all inputs and outputs belong to our wallet, it's utxo management.
return true
}

// createFeeEntry creates a fee entry for an on chain transaction.
func createFeeEntry(tx lndclient.Transaction, category string, u entryUtils) (*HarmonyEntry, error) {
// Total fees are expressed as a positive value in sats, we convert to
// msat here and make the value negative so that it reflects as a
// debit.
feeAmt := invertedSatsToMsats(tx.Fee)

feeEntry, err := newHarmonyEntry(
tx.Timestamp, feeAmt, EntryTypeFee,
tx.TxHash, FeeReference(tx.TxHash), "", category, true,
u.getFiat,
)

if err != nil {
return nil, err
}

return feeEntry, nil
}

// onChainEntries produces relevant entries for an on chain transaction.
func onChainEntries(tx lndclient.Transaction,
u entryUtils) ([]*HarmonyEntry, error) {

var (
amtMsat = satsToMsat(tx.Amount)
entryType EntryType
feeType = EntryTypeFee
category = getCategory(tx.Label, u.customCategories)
amtMsat = satsToMsat(tx.Amount)
entryType EntryType
category = getCategory(tx.Label, u.customCategories)
utxoManagement bool
)

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

case isUtxoManagementTx(tx):
utxoManagement = true

// If we have a zero amount on chain transaction, we do not create an
// entry for it. This may happen when the remote party claims a htlc on
// our commitment. We do not want to report 0 value transactions that
Expand All @@ -260,6 +303,16 @@ func onChainEntries(tx lndclient.Transaction,
return nil, nil
}

// If this is a utxo management transaction, we return a fee entry only.
if utxoManagement {
feeEntry, err := createFeeEntry(tx, category, u)
if err != nil {
return nil, err
}

return []*HarmonyEntry{feeEntry}, nil
}

txEntry, err := newHarmonyEntry(
tx.Timestamp, amtMsat, entryType, tx.TxHash, tx.TxHash,
tx.Label, category, true, u.getFiat,
Expand All @@ -273,15 +326,7 @@ func onChainEntries(tx lndclient.Transaction,
return []*HarmonyEntry{txEntry}, nil
}

// Total fees are expressed as a positive value in sats, we convert to
// msat here and make the value negative so that it reflects as a
// debit.
feeAmt := invertedSatsToMsats(tx.Fee)

feeEntry, err := newHarmonyEntry(
tx.Timestamp, feeAmt, feeType, tx.TxHash,
FeeReference(tx.TxHash), "", category, true, u.getFiat,
)
feeEntry, err := createFeeEntry(tx, category, u)
if err != nil {
return nil, err
}
Expand Down
83 changes: 64 additions & 19 deletions accounting/entries_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -502,11 +502,12 @@ func TestSweepEntry(t *testing.T) {
// generation of a fee entry where applicable.
func TestOnChainEntry(t *testing.T) {
getOnChainEntry := func(amount btcutil.Amount,
hasFee bool, label string) []*HarmonyEntry {
hasFee bool, isUtxoManegement bool, label string) []*HarmonyEntry {

var (
entryType EntryType
feeType = EntryTypeFee
entryType EntryType
feeType = EntryTypeFee
utxoManagement bool
)

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

case isUtxoManegement:
utxoManagement = true

default:
return nil
}

if utxoManagement {
feeAmt := satsToMsat(onChainFeeSat)
feeMsat := lnwire.MilliSatoshi(feeAmt)

feeEntry := &HarmonyEntry{
Timestamp: onChainTimestamp,
Amount: feeMsat,
FiatValue: fiat.MsatToFiat(mockBTCPrice.Price, feeMsat),
TxID: onChainTxID,
Reference: FeeReference(onChainTxID),
Note: "",
Type: feeType,
OnChain: true,
Credit: false,
BTCPrice: mockBTCPrice,
}

return []*HarmonyEntry{feeEntry}
}

amt := satsToMsat(onChainAmtSat)
amtMsat := lnwire.MilliSatoshi(amt)
entry := &HarmonyEntry{
Expand Down Expand Up @@ -569,33 +593,47 @@ func TestOnChainEntry(t *testing.T) {
// Whether the transaction has a fee attached.
hasFee bool

// Whether the transaction is a sweep
isUtxoManagement bool

// txLabel is an optional label on the rpc transaction.
txLabel string
}{
{
name: "receive with fee",
amount: onChainAmtSat,
hasFee: true,
name: "receive with fee",
amount: onChainAmtSat,
hasFee: true,
isUtxoManagement: false,
},
{
name: "receive without fee",
amount: onChainAmtSat,
hasFee: false,
isUtxoManagement: false,
},
{
name: "receive without fee",
amount: onChainAmtSat,
hasFee: false,
name: "payment without fee",
amount: onChainAmtSat * -1,
hasFee: false,
isUtxoManagement: false,
},
{
name: "payment without fee",
amount: onChainAmtSat * -1,
hasFee: false,
name: "payment with fee",
amount: onChainAmtSat * -1,
hasFee: true,
isUtxoManagement: false,
},
{
name: "payment with fee",
amount: onChainAmtSat * -1,
hasFee: true,
name: "zero amount tx",
amount: 0,
hasFee: false,
isUtxoManagement: false,
},
{
name: "zero amount tx",
amount: 0,
hasFee: false,
name: "zero amount utxo management tx",
amount: 0,
hasFee: true,
isUtxoManagement: true,
},
}

Expand All @@ -615,6 +653,13 @@ func TestOnChainEntry(t *testing.T) {
chainTx.Fee = 0
}

chainTx.PreviousOutpoints = []*lnrpc.PreviousOutPoint{{
IsOurOutput: test.isUtxoManagement,
}}
chainTx.OutputDetails = []*lnrpc.OutputDetail{{
IsOurAddress: test.isUtxoManagement,
}}

// Set the label as per the test.
chainTx.Label = test.txLabel

Expand All @@ -624,7 +669,7 @@ func TestOnChainEntry(t *testing.T) {
// Create the entries we expect based on the test
// params.
expected := getOnChainEntry(
test.amount, test.hasFee, test.txLabel,
test.amount, test.hasFee, test.isUtxoManagement, test.txLabel,
)

require.Equal(t, expected, entries)
Expand Down

0 comments on commit dc2efc5

Please sign in to comment.