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, u64 *part_id,
struct amount_msat amount, struct amount_msat amount,
enum mvt_tag *tags STEALS, 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); 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->credit = AMOUNT_MSAT(0);
} }
mvt->fees = fees;
return mvt; 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, return new_channel_coin_mvt(ctx, cid, empty_hash,
NULL, amount, 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, 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 = tal(mvt, struct amount_sat);
*mvt->output_val = chain_mvt->output_val; *mvt->output_val = chain_mvt->output_val;
mvt->fees = NULL;
mvt->timestamp = timestamp; mvt->timestamp = timestamp;
mvt->blockheight = chain_mvt->blockheight; 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->credit = chan_mvt->credit;
mvt->debit = chan_mvt->debit; mvt->debit = chan_mvt->debit;
mvt->output_val = NULL; mvt->output_val = NULL;
mvt->fees = tal(mvt, struct amount_msat);
*mvt->fees = chan_mvt->fees;
mvt->timestamp = timestamp; mvt->timestamp = timestamp;
/* channel movements don't have a blockheight */ /* channel movements don't have a blockheight */
mvt->blockheight = 0; mvt->blockheight = 0;

View File

@@ -58,6 +58,8 @@ struct channel_coin_mvt {
struct amount_msat credit; struct amount_msat credit;
struct amount_msat debit; struct amount_msat debit;
/* Fees collected (or paid) on this mvt */
struct amount_msat fees;
}; };
struct chain_coin_mvt { struct chain_coin_mvt {
@@ -114,6 +116,9 @@ struct coin_mvt {
* our credit/debit amount, eg channel opens */ * our credit/debit amount, eg channel opens */
struct amount_sat *output_val; struct amount_sat *output_val;
/* Amount of fees collected/paid by channel mvt */
struct amount_msat *fees;
u32 timestamp; u32 timestamp;
u32 blockheight; u32 blockheight;
@@ -133,7 +138,8 @@ struct channel_coin_mvt *new_channel_coin_mvt(const tal_t *ctx,
u64 *part_id, u64 *part_id,
struct amount_msat amount, struct amount_msat amount,
enum mvt_tag *tags STEALS, 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, struct chain_coin_mvt *new_onchaind_withdraw(const tal_t *ctx,
const struct bitcoin_outpoint *outpoint, 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) "part_id": 0, // (`channel_mvt` type only, mandatory)
"credit":"2000000000msat", "credit":"2000000000msat",
"debit":"0msat", "debit":"0msat",
"output_value": "2000000000msat", // ('chain_mvt' only)
"fees": "382msat", // ('channel_mvt' only, optional)
"tags": ["deposit"], "tags": ["deposit"],
"blockheight":102, // (May be null) "blockheight":102, // (May be null)
"timestamp":1585948198, "timestamp":1585948198,
@@ -747,6 +749,16 @@ multiple times. `channel_mvt` only
`credit` and `debit` are millisatoshi denominated amounts of the fund movement. A `credit` and `debit` are millisatoshi denominated amounts of the fund movement. A
'credit' is funds deposited into an account; a `debit` is funds withdrawn. '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: `tag` is a movement descriptor. Current tags are as follows:
- `deposit`: funds deposited - `deposit`: funds deposited

View File

@@ -1,4 +1,5 @@
#include "config.h" #include "config.h"
#include <common/onion.h>
#include <lightningd/channel.h> #include <lightningd/channel.h>
#include <lightningd/coin_mvts.h> #include <lightningd/coin_mvts.h>
#include <lightningd/notification.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, return new_channel_coin_mvt(ctx, &channel->cid,
hin->payment_hash, NULL, hin->payment_hash, NULL,
hin->msat, new_tag_arr(ctx, INVOICE), 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 channel_coin_mvt *new_channel_mvt_routed_hin(const tal_t *ctx,
struct htlc_in *hin, struct htlc_in *hin,
struct channel *channel) 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, return new_channel_coin_mvt(ctx, &channel->cid,
hin->payment_hash, NULL, hin->payment_hash, NULL,
hin->msat, new_tag_arr(ctx, ROUTED), hin->msat, new_tag_arr(ctx, ROUTED),
true); true, fees_collected);
} }
struct channel_coin_mvt *new_channel_mvt_invoice_hout(const tal_t *ctx, 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, return new_channel_coin_mvt(ctx, &channel->cid,
hout->payment_hash, &hout->partid, hout->payment_hash, &hout->partid,
hout->msat, new_tag_arr(ctx, INVOICE), 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 channel_coin_mvt *new_channel_mvt_routed_hout(const tal_t *ctx,
struct htlc_out *hout, struct htlc_out *hout,
struct channel *channel) 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, return new_channel_coin_mvt(ctx, &channel->cid,
hout->payment_hash, NULL, hout->payment_hash, NULL,
hout->msat, new_tag_arr(ctx, ROUTED), 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) if (mvt->output_val)
json_add_amount_sat_only(stream, "output_value", json_add_amount_sat_only(stream, "output_value",
*mvt->output_val); *mvt->output_val);
if (mvt->fees)
json_add_amount_msat_only(stream, "fees",
*mvt->fees);
json_array_start(stream, "tags"); json_array_start(stream, "tags");
for (size_t i = 0; i < tal_count(mvt->tags); i++) for (size_t i = 0; i < tal_count(mvt->tags); i++)

View File

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

View File

@@ -1888,17 +1888,17 @@ def test_coin_movement_notices(node_factory, bitcoind, chainparams):
l1_l2_mvts = [ l1_l2_mvts = [
{'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tags': ['channel_open']}, {'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tags': ['channel_open']},
{'type': 'channel_mvt', 'credit': 100001001, 'debit': 0, 'tags': ['routed']}, {'type': 'channel_mvt', 'credit': 100001001, 'debit': 0, 'tags': ['routed'], 'fees': '1001msat'},
{'type': 'channel_mvt', 'credit': 0, 'debit': 50000000, 'tags': ['routed']}, {'type': 'channel_mvt', 'credit': 0, 'debit': 50000000, 'tags': ['routed'], 'fees': '501msat'},
{'type': 'channel_mvt', 'credit': 100000000, 'debit': 0, 'tags': ['invoice']}, {'type': 'channel_mvt', 'credit': 100000000, 'debit': 0, 'tags': ['invoice'], 'fees': '0msat'},
{'type': 'channel_mvt', 'credit': 0, 'debit': 50000000, 'tags': ['invoice']}, {'type': 'channel_mvt', 'credit': 0, 'debit': 50000000, 'tags': ['invoice'], 'fees': '0msat'},
{'type': 'chain_mvt', 'credit': 0, 'debit': 100001001, 'tags': ['channel_close']}, {'type': 'chain_mvt', 'credit': 0, 'debit': 100001001, 'tags': ['channel_close']},
] ]
l2_l3_mvts = [ l2_l3_mvts = [
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['channel_open', 'opener']}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['channel_open', 'opener']},
{'type': 'channel_mvt', 'credit': 0, 'debit': 100000000, 'tags': ['routed']}, {'type': 'channel_mvt', 'credit': 0, 'debit': 100000000, 'tags': ['routed'], 'fees': '1001msat'},
{'type': 'channel_mvt', 'credit': 50000501, 'debit': 0, 'tags': ['routed']}, {'type': 'channel_mvt', 'credit': 50000501, 'debit': 0, 'tags': ['routed'], 'fees': '501msat'},
{'type': 'chain_mvt', 'credit': 0, 'debit': 950000501, 'tags': ['channel_close']}, {'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) l1.rpc.signpsbt(invalid_psbt)
wallet_coin_mvts = [ wallet_coin_mvts = [
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['deposit']},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['deposit']},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['deposit']},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['deposit']},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['deposit']},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['deposit']},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['deposit']},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['deposit']},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['deposit']},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['deposit']},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['deposit']},
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tags': ['deposit']},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tag': 'withdrawal'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tags': ['withdrawal']},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tag': 'withdrawal'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tags': ['withdrawal']},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tag': 'withdrawal'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tags': ['withdrawal']},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tag': 'withdrawal'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tags': ['withdrawal']},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tag': 'withdrawal'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tags': ['withdrawal']},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tag': 'withdrawal'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tags': ['withdrawal']},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tag': 'withdrawal'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tags': ['withdrawal']},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tag': 'withdrawal'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tags': ['withdrawal']},
] ]
check_coin_moves(l1, 'wallet', wallet_coin_mvts, chainparams) check_coin_moves(l1, 'wallet', wallet_coin_mvts, chainparams)

View File

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