From bddd3694fa35df20ba7497b48c8f4ffab2c24168 Mon Sep 17 00:00:00 2001 From: niftynei Date: Tue, 7 Dec 2021 14:09:28 -0600 Subject: [PATCH] coin_mvt: record fees for an outbound htlc If we initialized the payment, the fees are the entire fee-chain (final hop amount - starting hop amount) If it's a payment we routed, the fees are the diff between the inbound htlc and the outbound (net gain by this routing) Added to database so data persists nicely. --- lightningd/coin_mvts.c | 13 ++----------- lightningd/htlc_end.c | 27 ++++++++++++++++++++++++++- lightningd/htlc_end.h | 5 +++++ lightningd/pay.c | 10 +++++++--- lightningd/peer_htlcs.c | 7 +++++-- lightningd/peer_htlcs.h | 1 + tests/test_plugin.py | 14 ++++++++++++-- wallet/db.c | 1 + wallet/wallet.c | 9 +++++++-- 9 files changed, 66 insertions(+), 21 deletions(-) diff --git a/lightningd/coin_mvts.c b/lightningd/coin_mvts.c index a29042cf6..20c2eccac 100644 --- a/lightningd/coin_mvts.c +++ b/lightningd/coin_mvts.c @@ -63,25 +63,16 @@ 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, AMOUNT_MSAT(0)); + false, hout->fees); } 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, - fees_collected); + hout->fees); } diff --git a/lightningd/htlc_end.c b/lightningd/htlc_end.c index a5225f119..d2ad10257 100644 --- a/lightningd/htlc_end.c +++ b/lightningd/htlc_end.c @@ -279,6 +279,7 @@ struct htlc_out *new_htlc_out(const tal_t *ctx, const u8 *onion_routing_packet, const struct pubkey *blinding, bool am_origin, + struct amount_msat final_msat, u64 partid, u64 groupid, struct htlc_in *in) @@ -310,10 +311,34 @@ struct htlc_out *new_htlc_out(const tal_t *ctx, if (am_origin) { hout->partid = partid; hout->groupid = groupid; + + /* Stash the fees (for accounting) */ + if (!amount_msat_sub(&hout->fees, msat, final_msat)) + return corrupt("new_htlc_out", + "overflow subtract %s-%s", + type_to_string(tmpctx, + struct amount_msat, + &msat), + type_to_string(tmpctx, + struct amount_msat, + &final_msat)); + } hout->in = NULL; - if (in) + if (in) { htlc_out_connect_htlc_in(hout, in); + /* Stash the fees (for accounting) */ + if (!amount_msat_sub(&hout->fees, in->msat, msat)) + return corrupt("new_htlc_out", + "overflow subtract %s-%s", + type_to_string(tmpctx, + struct amount_msat, + &in->msat), + type_to_string(tmpctx, + struct amount_msat, + &msat)); + } + return htlc_out_check(hout, "new_htlc_out"); } diff --git a/lightningd/htlc_end.h b/lightningd/htlc_end.h index 91bd6315b..da5f2ce18 100644 --- a/lightningd/htlc_end.h +++ b/lightningd/htlc_end.h @@ -90,6 +90,10 @@ struct htlc_out { /* Is this a locally-generated payment? Implies ->in is NULL. */ bool am_origin; + /* Amount of fees that this out htlc pays (if am_origin); + * otherwise fees collected by routing this out */ + struct amount_msat fees; + /* If am_origin, this is the partid of the payment. */ u64 partid; @@ -168,6 +172,7 @@ struct htlc_out *new_htlc_out(const tal_t *ctx, const u8 *onion_routing_packet, const struct pubkey *blinding, bool am_origin, + struct amount_msat final_msat, u64 partid, u64 groupid, struct htlc_in *in); diff --git a/lightningd/pay.c b/lightningd/pay.c index f4db4fc52..00fdafdf8 100644 --- a/lightningd/pay.c +++ b/lightningd/pay.c @@ -773,6 +773,7 @@ static bool should_use_tlv(enum route_hop_style style) static const u8 *send_onion(const tal_t *ctx, struct lightningd *ld, const struct onionpacket *packet, const struct route_hop *first_hop, + const struct amount_msat final_amount, const struct sha256 *payment_hash, const struct pubkey *blinding, u64 partid, @@ -786,7 +787,8 @@ static const u8 *send_onion(const tal_t *ctx, struct lightningd *ld, base_expiry = get_block_height(ld->topology) + 1; onion = serialize_onionpacket(tmpctx, packet); return send_htlc_out(ctx, channel, first_hop->amount, - base_expiry + first_hop->delay, payment_hash, + base_expiry + first_hop->delay, + final_amount, payment_hash, blinding, partid, groupid, onion, NULL, hout, &dont_care_about_channel_update); } @@ -1047,7 +1049,8 @@ send_payment_core(struct lightningd *ld, return command_failed(cmd, data); } - failmsg = send_onion(tmpctx, ld, packet, first_hop, rhash, NULL, partid, + failmsg = send_onion(tmpctx, ld, packet, first_hop, msat, + rhash, NULL, partid, group, channel, &hout); if (failmsg) { @@ -1211,7 +1214,8 @@ send_payment(struct lightningd *ld, n_hops, type_to_string(tmpctx, struct amount_msat, &msat)); packet = create_onionpacket(tmpctx, path, ROUTING_INFO_SIZE, &path_secrets); return send_payment_core(ld, cmd, rhash, partid, group, &route[0], - msat, total_msat, label, invstring, + msat, total_msat, + label, invstring, packet, &ids[n_hops - 1], ids, channels, path_secrets, local_offer_id); } diff --git a/lightningd/peer_htlcs.c b/lightningd/peer_htlcs.c index 70880689a..8c60ed4e3 100644 --- a/lightningd/peer_htlcs.c +++ b/lightningd/peer_htlcs.c @@ -638,6 +638,7 @@ static void htlc_offer_timeout(struct htlc_out *out) const u8 *send_htlc_out(const tal_t *ctx, struct channel *out, struct amount_msat amount, u32 cltv, + struct amount_msat final_msat, const struct sha256 *payment_hash, const struct pubkey *blinding, u64 partid, @@ -675,6 +676,7 @@ const u8 *send_htlc_out(const tal_t *ctx, *houtp = new_htlc_out(out->owner, out, amount, cltv, payment_hash, onion_routing_packet, blinding, in == NULL, + final_msat, partid, groupid, in); tal_add_destructor(*houtp, destroy_hout_subd_died); @@ -786,7 +788,8 @@ static void forward_htlc(struct htlc_in *hin, } failmsg = send_htlc_out(tmpctx, next, amt_to_forward, - outgoing_cltv_value, &hin->payment_hash, + outgoing_cltv_value, AMOUNT_MSAT(0), + &hin->payment_hash, next_blinding, 0 /* partid */, 0 /* groupid */, next_onion, hin, &hout, &needs_update_appended); if (!failmsg) @@ -1605,7 +1608,7 @@ static void remove_htlc_out(struct channel *channel, struct htlc_out *hout) if (!mvt) log_broken(channel->log, - "Unable to calculate fees collected." + "Unable to calculate fees." " Not logging an outbound HTLC"); else notify_channel_mvt(channel->peer->ld, mvt); diff --git a/lightningd/peer_htlcs.h b/lightningd/peer_htlcs.h index 50618a749..d6c49983e 100644 --- a/lightningd/peer_htlcs.h +++ b/lightningd/peer_htlcs.h @@ -43,6 +43,7 @@ void update_per_commit_point(struct channel *channel, const u8 *send_htlc_out(const tal_t *ctx, struct channel *out, struct amount_msat amount, u32 cltv, + struct amount_msat final_msat, const struct sha256 *payment_hash, const struct pubkey *blinding, u64 partid, diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 58afbdbf5..e59861876 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -1902,10 +1902,18 @@ def test_coin_movement_notices(node_factory, bitcoind, chainparams): {'type': 'chain_mvt', 'credit': 0, 'debit': 950000501, 'tags': ['channel_close']}, ] + l3_l2_mvts = [ + {'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tags': ['channel_open']}, + {'type': 'channel_mvt', 'credit': 100000000, 'debit': 0, 'tags': ['invoice'], 'fees': '0msat'}, + {'type': 'channel_mvt', 'credit': 0, 'debit': 50000501, 'tags': ['invoice'], 'fees': '501msat'}, + {'type': 'chain_mvt', 'credit': 0, 'debit': 49999499, 'tags': ['channel_close']}, + ] + + coin_plugin = os.path.join(os.getcwd(), 'tests/plugins/coin_movements.py') l1, l2, l3 = node_factory.line_graph(3, opts=[ {'may_reconnect': True}, - {'may_reconnect': True, 'plugin': os.path.join(os.getcwd(), 'tests/plugins/coin_movements.py')}, - {'may_reconnect': True}, + {'may_reconnect': True, 'plugin': coin_plugin}, + {'may_reconnect': True, 'plugin': coin_plugin}, ], wait_for_announce=True) bitcoind.generate_block(5) @@ -1977,12 +1985,14 @@ def test_coin_movement_notices(node_factory, bitcoind, chainparams): bitcoind.generate_block(6) sync_blockheight(bitcoind, [l2]) l2.daemon.wait_for_log('{}.*FUNDING_TRANSACTION/FUNDING_OUTPUT->MUTUAL_CLOSE depth'.format(l3.info['id'])) + l3.daemon.wait_for_log('Resolved FUNDING_TRANSACTION/FUNDING_OUTPUT by MUTUAL_CLOSE') # Ending channel balance should be zero assert account_balance(l2, chanid_1) == 0 assert account_balance(l2, chanid_3) == 0 # Verify we recorded all the movements we expect + check_coin_moves(l3, chanid_3, l3_l2_mvts, chainparams) check_coin_moves(l2, chanid_1, l1_l2_mvts, chainparams) check_coin_moves(l2, chanid_3, l2_l3_mvts, chainparams) diff --git a/wallet/db.c b/wallet/db.c index bfbd9a729..21806c2c2 100644 --- a/wallet/db.c +++ b/wallet/db.c @@ -868,6 +868,7 @@ static struct migration dbmigrations[] = { [BUILD_ASSERT_OR_ZERO( 9 == RCVD_REMOVE_ACK_REVOCATION) + BUILD_ASSERT_OR_ZERO(19 == SENT_REMOVE_ACK_REVOCATION)], NULL}, + {SQL("ALTER TABLE channel_htlcs ADD fees_msat BIGINT DEFAULT 0"), NULL}, }; /* Leak tracking. */ diff --git a/wallet/wallet.c b/wallet/wallet.c index 1c4b923b9..62bae46bb 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -2372,8 +2372,9 @@ void wallet_htlc_save_out(struct wallet *wallet, " malformed_onion," " partid," " groupid," + " fees_msat," " min_commit_num" - ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?);")); + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?);")); db_bind_u64(stmt, 0, chan->dbid); db_bind_u64(stmt, 1, out->key.id); @@ -2403,7 +2404,9 @@ void wallet_htlc_save_out(struct wallet *wallet, db_bind_u64(stmt, 10, out->partid); db_bind_u64(stmt, 11, out->groupid); } - db_bind_u64(stmt, 12, min_u64(chan->next_index[LOCAL]-1, + + db_bind_amount_msat(stmt, 12, &out->fees); + db_bind_u64(stmt, 13, min_u64(chan->next_index[LOCAL]-1, chan->next_index[REMOTE]-1)); db_exec_prepared_v2(stmt); @@ -2598,6 +2601,7 @@ static bool wallet_stmt2htlc_out(struct wallet *wallet, out->failmsg = db_col_arr(out, stmt, "localfailmsg", u8); out->in = NULL; + db_col_amount_msat(stmt, "fees_msat", &out->fees); if (!db_col_is_null(stmt, "origin_htlc")) { u64 in_id = db_col_u64(stmt, "origin_htlc"); @@ -2737,6 +2741,7 @@ bool wallet_htlcs_load_out_for_channel(struct wallet *wallet, ", partid" ", localfailmsg" ", groupid" + ", fees_msat" " FROM channel_htlcs" " WHERE direction = ?" " AND channel_id = ?"