Skip to content

Commit 959e713

Browse files
committed
wallet: don't delete old htlcs when we forget a channel, do it on startup.
For old channels, this can take a while, and it stops everything. But we are only doing this to save space; it's not a *functional* necessity. A quick and dirty test with 50,000 htlcs shows the htlc deletion took 450msec. I tried adding an index, and changing it to set hstate to HTLC_STATE_INVALID instead of deleting entries, but it still took about 350ms. Whereas the "COUNT(*)" only took 1.7msec, so it's worth keeping. Reported-by: @michael1011 Signed-off-by: Rusty Russell <[email protected]> Changelog-Changed: lightningd: we defer deletion of old htlcs on channel close, to avoid pausing for a long time (we clean them on startup) Fixes: #7962
1 parent ce05941 commit 959e713

File tree

5 files changed

+44
-6
lines changed

5 files changed

+44
-6
lines changed

lightningd/lightningd.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1371,6 +1371,9 @@ int main(int argc, char *argv[])
13711371
trace_span_end(ld->topology);
13721372

13731373
db_begin_transaction(ld->wallet->db);
1374+
trace_span_start("delete_old_htlcs", ld->wallet);
1375+
wallet_delete_old_htlcs(ld->wallet);
1376+
trace_span_end(ld->wallet);
13741377

13751378
/*~ Pull peers, channels and HTLCs from db. Needs to happen after the
13761379
* topology is initialized since some decisions rely on being able to

lightningd/test/run-find_my_abspath.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,9 @@ void waitblockheight_notify_new_block(struct lightningd *ld UNNEEDED)
286286
/* Generated stub for wallet_begin_old_close_rescan */
287287
void wallet_begin_old_close_rescan(struct lightningd *ld UNNEEDED)
288288
{ fprintf(stderr, "wallet_begin_old_close_rescan called!\n"); abort(); }
289+
/* Generated stub for wallet_delete_old_htlcs */
290+
void wallet_delete_old_htlcs(struct wallet *w UNNEEDED)
291+
{ fprintf(stderr, "wallet_delete_old_htlcs called!\n"); abort(); }
289292
/* Generated stub for wallet_new */
290293
struct wallet *wallet_new(struct lightningd *ld UNNEEDED, struct timers *timers UNNEEDED)
291294
{ fprintf(stderr, "wallet_new called!\n"); abort(); }

tests/test_misc.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3461,6 +3461,9 @@ def test_listforwards_and_listhtlcs(node_factory, bitcoind):
34613461
l2.rpc.delforward(in_channel=c12, in_htlc_id=2, status='local_failed')
34623462
assert l2.rpc.listforwards() == {'forwards': []}
34633463

3464+
l2.restart()
3465+
assert l2.rpc.wait('htlcs', 'deleted', 0)['deleted'] == 5
3466+
34643467

34653468
def test_listforwards_wait(node_factory, executor):
34663469
l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True)

wallet/wallet.c

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2957,6 +2957,21 @@ void wallet_channel_insert(struct wallet *w, struct channel *chan)
29572957
wallet_channel_save(w, chan);
29582958
}
29592959

2960+
void wallet_delete_old_htlcs(struct wallet *w)
2961+
{
2962+
struct db_stmt *stmt;
2963+
2964+
/* Delete htlcs for closed channels */
2965+
stmt = db_prepare_v2(w->db, SQL("DELETE FROM channel_htlcs"
2966+
" WHERE id IN ("
2967+
" SELECT ch.id"
2968+
" FROM channel_htlcs AS ch"
2969+
" JOIN channels AS c ON c.id = ch.channel_id"
2970+
" WHERE c.state = ?);"));
2971+
db_bind_int(stmt, channel_state_in_db(CLOSED));
2972+
db_exec_prepared_v2(take(stmt));
2973+
}
2974+
29602975
void wallet_channel_close(struct wallet *w,
29612976
const struct channel *chan)
29622977
{
@@ -2967,15 +2982,21 @@ void wallet_channel_close(struct wallet *w,
29672982
* dbs to recover. */
29682983
struct db_stmt *stmt;
29692984
u64 new_move_id;
2985+
u64 htlcs;
29702986

2971-
/* Delete entries from `channel_htlcs` */
2972-
stmt = db_prepare_v2(w->db, SQL("DELETE FROM channel_htlcs "
2987+
/* The channel_htlcs table is quite large, and deleting it can take a
2988+
* while. So we do that on next restart by calling
2989+
* wallet_delete_old_htlcs. But update delete count in case anyone
2990+
* is watching. */
2991+
stmt = db_prepare_v2(w->db, SQL("SELECT COUNT(*) FROM channel_htlcs "
29732992
"WHERE channel_id=?"));
29742993
db_bind_u64(stmt, chan->dbid);
2975-
db_exec_prepared_v2(stmt);
2976-
/* FIXME: We don't actually tell them what was deleted! */
2977-
if (db_count_changes(stmt) != 0)
2978-
htlcs_index_deleted(w->ld, chan, db_count_changes(stmt));
2994+
db_query_prepared(stmt);
2995+
db_step(stmt);
2996+
2997+
htlcs = db_col_u64(stmt, "COUNT(*)");
2998+
if (htlcs != 0)
2999+
htlcs_index_deleted(w->ld, chan, htlcs);
29793000
tal_free(stmt);
29803001

29813002
/* Delete entries from `htlc_sigs` */

wallet/wallet.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,14 @@ void wallet_state_change_add(struct wallet *w,
725725
*/
726726
void wallet_delete_peer_if_unused(struct wallet *w, u64 peer_dbid);
727727

728+
/**
729+
* wallet_delete_old_htlcs -- delete htlcs associated with CLOSED channels.
730+
*
731+
* We do this at startup, instead of when we finally CLOSED a channel, to
732+
* avoid a significant pause.
733+
*/
734+
void wallet_delete_old_htlcs(struct wallet *w);
735+
728736
/**
729737
* wallet_init_channels -- Loads active channels into peers
730738
* and inits the dbid counter for next channel.

0 commit comments

Comments
 (0)