From 7ff0239f6f61797b91c16e007693d9c9ff388e76 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Thu, 13 Nov 2025 15:21:10 +1030 Subject: [PATCH] lightningd: db migration to clean up any pending payments where theres no htlc. Changelog-Fixed: JSON-RPC: `listpays`/`listsendpays` erroneously left `pending` in xpay are cleaned up. Signed-off-by: Rusty Russell --- tests/test_wallet.py | 1 - wallet/db.c | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/tests/test_wallet.py b/tests/test_wallet.py index f3da6eb47..17cfb9289 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -2469,7 +2469,6 @@ def test_old_htlcs_cleanup(node_factory, bitcoind): assert l1.rpc.listhtlcs() == {'htlcs': []} -@pytest.mark.xfail(strict=True) @unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "Makes use of the sqlite3 db") @unittest.skipIf(TEST_NETWORK != 'regtest', "sqlite3 snapshot is regtest") def test_pending_payments_cleanup(node_factory, bitcoind): diff --git a/wallet/db.c b/wallet/db.c index 61f5c28a5..0024092ba 100644 --- a/wallet/db.c +++ b/wallet/db.c @@ -84,6 +84,8 @@ static void migrate_convert_old_channel_keyidx(struct lightningd *ld, struct db *db); static void migrate_initialize_channel_htlcs_wait_indexes_and_fixup_forwards(struct lightningd *ld, struct db *db); +static void migrate_fail_pending_payments_without_htlcs(struct lightningd *ld, + struct db *db); /* Do not reorder or remove elements from this array, it is used to * migrate existing databases from a previous state, based on the @@ -1098,6 +1100,7 @@ static struct migration dbmigrations[] = { " connect_attempted INTEGER NOT NULL," " PRIMARY KEY (id)" ")"), NULL}, + {NULL, migrate_fail_pending_payments_without_htlcs}, }; /** @@ -2127,3 +2130,25 @@ static void migrate_convert_old_channel_keyidx(struct lightningd *ld, db_bind_int(stmt, channel_state_in_db(CLOSED)); db_exec_prepared_v2(take(stmt)); } + +static void migrate_fail_pending_payments_without_htlcs(struct lightningd *ld, + struct db *db) +{ + /* If channeld died or was offline at the right moment, we + * could register a payment as pending, but then not create an + * HTLC. Clean those up. */ + struct db_stmt *stmt; + + stmt = db_prepare_v2(db, SQL("UPDATE payments AS p" + " SET status = ?" + " WHERE p.status = ?" + " AND NOT EXISTS (" + " SELECT 1" + " FROM channel_htlcs AS h" + " WHERE h.payment_hash = p.payment_hash" + " AND h.groupid = p.groupid" + " AND h.partid = p.partid);")); + db_bind_int(stmt, payment_status_in_db(PAYMENT_FAILED)); + db_bind_int(stmt, payment_status_in_db(PAYMENT_PENDING)); + db_exec_prepared_v2(take(stmt)); +}