coin_mvt: record new 'fees' field on htlc channel moves

We record the amount of fees collected for a routed payment. For
simplicity's sake on the data agg side, we record the fee payment on
*BOTH* the incoming htlc and the outgoing htlc. Note that this results
in double counting if you add up the fees from both an in-routed and
out-routed payment.
This commit is contained in:
niftynei 2021-12-07 10:05:29 -06:00 committed by Rusty Russell
parent b6463174d6
commit 29c6718297
9 changed files with 105 additions and 38 deletions

View file

@ -61,7 +61,8 @@ struct channel_coin_mvt *new_channel_coin_mvt(const tal_t *ctx,
u64 *part_id,
struct amount_msat amount,
enum mvt_tag *tags STEALS,
bool is_credit)
bool is_credit,
struct amount_msat fees)
{
struct channel_coin_mvt *mvt = tal(ctx, struct channel_coin_mvt);
@ -78,6 +79,8 @@ struct channel_coin_mvt *new_channel_coin_mvt(const tal_t *ctx,
mvt->credit = AMOUNT_MSAT(0);
}
mvt->fees = fees;
return mvt;
}
@ -326,7 +329,8 @@ struct channel_coin_mvt *new_coin_pushed(const tal_t *ctx,
return new_channel_coin_mvt(ctx, cid, empty_hash,
NULL, amount,
new_tag_arr(ctx, PUSHED), false);
new_tag_arr(ctx, PUSHED), false,
AMOUNT_MSAT(0));
}
struct coin_mvt *finalize_chain_mvt(const tal_t *ctx,
@ -351,6 +355,7 @@ struct coin_mvt *finalize_chain_mvt(const tal_t *ctx,
mvt->output_val = tal(mvt, struct amount_sat);
*mvt->output_val = chain_mvt->output_val;
mvt->fees = NULL;
mvt->timestamp = timestamp;
mvt->blockheight = chain_mvt->blockheight;
@ -379,6 +384,8 @@ struct coin_mvt *finalize_channel_mvt(const tal_t *ctx,
mvt->credit = chan_mvt->credit;
mvt->debit = chan_mvt->debit;
mvt->output_val = NULL;
mvt->fees = tal(mvt, struct amount_msat);
*mvt->fees = chan_mvt->fees;
mvt->timestamp = timestamp;
/* channel movements don't have a blockheight */
mvt->blockheight = 0;

View file

@ -58,6 +58,8 @@ struct channel_coin_mvt {
struct amount_msat credit;
struct amount_msat debit;
/* Fees collected (or paid) on this mvt */
struct amount_msat fees;
};
struct chain_coin_mvt {
@ -114,6 +116,9 @@ struct coin_mvt {
* our credit/debit amount, eg channel opens */
struct amount_sat *output_val;
/* Amount of fees collected/paid by channel mvt */
struct amount_msat *fees;
u32 timestamp;
u32 blockheight;
@ -133,7 +138,8 @@ struct channel_coin_mvt *new_channel_coin_mvt(const tal_t *ctx,
u64 *part_id,
struct amount_msat amount,
enum mvt_tag *tags STEALS,
bool is_credit);
bool is_credit,
struct amount_msat fees);
struct chain_coin_mvt *new_onchaind_withdraw(const tal_t *ctx,
const struct bitcoin_outpoint *outpoint,

View file

@ -708,6 +708,8 @@ i.e. only definitively resolved HTLCs or confirmed bitcoin transactions.
"part_id": 0, // (`channel_mvt` type only, mandatory)
"credit":"2000000000msat",
"debit":"0msat",
"output_value": "2000000000msat", // ('chain_mvt' only)
"fees": "382msat", // ('channel_mvt' only, optional)
"tags": ["deposit"],
"blockheight":102, // (May be null)
"timestamp":1585948198,
@ -747,6 +749,16 @@ multiple times. `channel_mvt` only
`credit` and `debit` are millisatoshi denominated amounts of the fund movement. A
'credit' is funds deposited into an account; a `debit` is funds withdrawn.
`output_value` is the total value of the on-chain UTXO. Note that for
channel opens/closes the total output value will not necessarily correspond
to the amount that's credited/debited.
`fees` is an HTLC annotation for the amount of fees either paid or
earned. For "invoice" tagged events, the fees are the total fees
paid to send that payment. The end amount can be found by subtracting
the total fees from the `debited` amount. For "routed" tagged events,
both the debit/credit contain fees. Technically routed debits are the
'fee generating' event, however we include them on routed credits as well.
`tag` is a movement descriptor. Current tags are as follows:
- `deposit`: funds deposited

View file

@ -1,4 +1,5 @@
#include "config.h"
#include <common/onion.h>
#include <lightningd/channel.h>
#include <lightningd/coin_mvts.h>
#include <lightningd/notification.h>
@ -33,17 +34,26 @@ struct channel_coin_mvt *new_channel_mvt_invoice_hin(const tal_t *ctx,
return new_channel_coin_mvt(ctx, &channel->cid,
hin->payment_hash, NULL,
hin->msat, new_tag_arr(ctx, INVOICE),
true);
true, AMOUNT_MSAT(0));
}
struct channel_coin_mvt *new_channel_mvt_routed_hin(const tal_t *ctx,
struct htlc_in *hin,
struct channel *channel)
{
struct amount_msat fees_collected;
if (!hin->payload)
return NULL;
if (!amount_msat_sub(&fees_collected, hin->msat,
hin->payload->amt_to_forward))
return NULL;
return new_channel_coin_mvt(ctx, &channel->cid,
hin->payment_hash, NULL,
hin->msat, new_tag_arr(ctx, ROUTED),
true);
true, fees_collected);
}
struct channel_coin_mvt *new_channel_mvt_invoice_hout(const tal_t *ctx,
@ -53,15 +63,25 @@ struct channel_coin_mvt *new_channel_mvt_invoice_hout(const tal_t *ctx,
return new_channel_coin_mvt(ctx, &channel->cid,
hout->payment_hash, &hout->partid,
hout->msat, new_tag_arr(ctx, INVOICE),
false);
false, AMOUNT_MSAT(0));
}
struct channel_coin_mvt *new_channel_mvt_routed_hout(const tal_t *ctx,
struct htlc_out *hout,
struct channel *channel)
{
struct amount_msat fees_collected;
if (!hout->in)
return NULL;
if (!amount_msat_sub(&fees_collected, hout->in->msat,
hout->msat))
return NULL;
return new_channel_coin_mvt(ctx, &channel->cid,
hout->payment_hash, NULL,
hout->msat, new_tag_arr(ctx, ROUTED),
false);
false,
fees_collected);
}

View file

@ -474,6 +474,9 @@ static void coin_movement_notification_serialize(struct json_stream *stream,
if (mvt->output_val)
json_add_amount_sat_only(stream, "output_value",
*mvt->output_val);
if (mvt->fees)
json_add_amount_msat_only(stream, "fees",
*mvt->fees);
json_array_start(stream, "tags");
for (size_t i = 0; i < tal_count(mvt->tags); i++)

View file

@ -1544,11 +1544,16 @@ static void remove_htlc_in(struct channel *channel, struct htlc_in *hin)
channel->msat_to_us_max = channel->our_msat;
/* Coins have definitively moved, log a movement */
if (hin->we_filled)
if (hin->we_filled && *hin->we_filled)
mvt = new_channel_mvt_invoice_hin(hin, hin, channel);
else
mvt = new_channel_mvt_routed_hin(hin, hin, channel);
if (!mvt)
log_broken(channel->log,
"Unable to calculate fees collected."
" Not logging an inbound HTLC");
else
notify_channel_mvt(channel->peer->ld, mvt);
}
@ -1597,6 +1602,12 @@ static void remove_htlc_out(struct channel *channel, struct htlc_out *hout)
else
mvt = new_channel_mvt_routed_hout(hout, hout, channel);
if (!mvt)
log_broken(channel->log,
"Unable to calculate fees collected."
" Not logging an outbound HTLC");
else
notify_channel_mvt(channel->peer->ld, mvt);
}

View file

@ -1888,17 +1888,17 @@ def test_coin_movement_notices(node_factory, bitcoind, chainparams):
l1_l2_mvts = [
{'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tags': ['channel_open']},
{'type': 'channel_mvt', 'credit': 100001001, 'debit': 0, 'tags': ['routed']},
{'type': 'channel_mvt', 'credit': 0, 'debit': 50000000, 'tags': ['routed']},
{'type': 'channel_mvt', 'credit': 100000000, 'debit': 0, 'tags': ['invoice']},
{'type': 'channel_mvt', 'credit': 0, 'debit': 50000000, 'tags': ['invoice']},
{'type': 'channel_mvt', 'credit': 100001001, 'debit': 0, 'tags': ['routed'], 'fees': '1001msat'},
{'type': 'channel_mvt', 'credit': 0, 'debit': 50000000, 'tags': ['routed'], 'fees': '501msat'},
{'type': 'channel_mvt', 'credit': 100000000, 'debit': 0, 'tags': ['invoice'], 'fees': '0msat'},
{'type': 'channel_mvt', 'credit': 0, 'debit': 50000000, 'tags': ['invoice'], 'fees': '0msat'},
{'type': 'chain_mvt', 'credit': 0, 'debit': 100001001, 'tags': ['channel_close']},
]
l2_l3_mvts = [
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['channel_open', 'opener']},
{'type': 'channel_mvt', 'credit': 0, 'debit': 100000000, 'tags': ['routed']},
{'type': 'channel_mvt', 'credit': 50000501, 'debit': 0, 'tags': ['routed']},
{'type': 'channel_mvt', 'credit': 0, 'debit': 100000000, 'tags': ['routed'], 'fees': '1001msat'},
{'type': 'channel_mvt', 'credit': 50000501, 'debit': 0, 'tags': ['routed'], 'fees': '501msat'},
{'type': 'chain_mvt', 'credit': 0, 'debit': 950000501, 'tags': ['channel_close']},
]

View file

@ -850,26 +850,26 @@ def test_sign_and_send_psbt(node_factory, bitcoind, chainparams):
l1.rpc.signpsbt(invalid_psbt)
wallet_coin_mvts = [
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tag': 'withdrawal'},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tag': 'withdrawal'},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tag': 'withdrawal'},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tag': 'withdrawal'},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tag': 'withdrawal'},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tag': 'withdrawal'},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tag': 'withdrawal'},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tag': 'withdrawal'},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['deposit']},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['deposit']},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['deposit']},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['deposit']},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['deposit']},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['deposit']},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['deposit']},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['deposit']},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['deposit']},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['deposit']},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['deposit']},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['deposit']},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tags': ['withdrawal']},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tags': ['withdrawal']},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tags': ['withdrawal']},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tags': ['withdrawal']},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tags': ['withdrawal']},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tags': ['withdrawal']},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tags': ['withdrawal']},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tags': ['withdrawal']},
]
check_coin_moves(l1, 'wallet', wallet_coin_mvts, chainparams)

View file

@ -79,6 +79,13 @@ def move_matches(exp, mv):
return False
if mv['tags'] != exp['tags']:
return False
if 'fees' in exp:
if 'fees' not in mv:
return False
if mv['fees'] != exp['fees']:
return False
elif 'fees' in mv:
return False
return True
@ -94,11 +101,12 @@ def check_coin_moves(n, account_id, expected_moves, chainparams):
node_id = n.info['id']
acct_moves = [m for m in moves if m['account_id'] == account_id]
for mv in acct_moves:
print("{{'type': '{}', 'credit': {}, 'debit': {}, 'tags': '{}'}},"
print("{{'type': '{}', 'credit': {}, 'debit': {}, 'tags': '{}' , ['fees'?: '{}']}},"
.format(mv['type'],
Millisatoshi(mv['credit']).millisatoshis,
Millisatoshi(mv['debit']).millisatoshis,
mv['tags']))
mv['tags'],
mv['fees'] if 'fees' in mv else ''))
assert mv['version'] == 2
assert mv['node_id'] == node_id
assert mv['timestamp'] > 0