Skip to content

Commit 8ba9e19

Browse files
committed
Fix BOLT11 annotation loss after restart
Fixes #6978 where BOLT11 invoice annotations were lost after lightning node restart due to broken linkage between HTLCs and payment records. Add payment_id column to channel_htlcs table with proper foreign key relationship to maintain HTLC-payment linkage through restarts.
1 parent 2e2a085 commit 8ba9e19

File tree

3 files changed

+79
-2
lines changed

3 files changed

+79
-2
lines changed

tests/test_pay.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7048,3 +7048,27 @@ def test_htlc_tlv_crash(node_factory):
70487048

70497049
l1.rpc.waitsendpay(inv1['payment_hash'], TIMEOUT)
70507050
l1.rpc.waitsendpay(inv2['payment_hash'], TIMEOUT)
7051+
7052+
7053+
@pytest.mark.openchannel('v1')
7054+
@pytest.mark.openchannel('v2')
7055+
def test_bolt11_annotation_after_restart(node_factory):
7056+
"""Test bolt11 field persists in listpays after restart. Fixes #6978"""
7057+
l1, l2 = node_factory.line_graph(2)
7058+
7059+
inv = l2.rpc.invoice(100000, 'test_bolt11_persist', 'Test BOLT11 persistence')['bolt11']
7060+
l1.dev_pay(inv, dev_use_shadow=False)
7061+
7062+
pays_before = l1.rpc.listpays()['pays']
7063+
payment_before = [p for p in pays_before if 'bolt11' in p and p['bolt11'] == inv][0]
7064+
assert payment_before['status'] == 'complete'
7065+
assert payment_before['bolt11'] == inv
7066+
payment_hash = payment_before['payment_hash']
7067+
7068+
l1.restart()
7069+
7070+
pays_after = l1.rpc.listpays()['pays']
7071+
payment_after = [p for p in pays_after if 'bolt11' in p and p['bolt11'] == inv][0]
7072+
assert payment_after['payment_hash'] == payment_hash
7073+
assert payment_after['status'] == 'complete'
7074+
assert payment_after['bolt11'] == inv

wallet/db.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ static void migrate_our_funding(struct lightningd *ld, struct db *db);
3535

3636
static void migrate_last_tx_to_psbt(struct lightningd *ld, struct db *db);
3737

38+
static void migrate_add_payment_id_to_htlcs(struct lightningd *ld, struct db *db);
39+
3840
static void
3941
migrate_inflight_last_tx_to_psbt(struct lightningd *ld, struct db *db);
4042

@@ -1092,6 +1094,8 @@ static struct migration dbmigrations[] = {
10921094
")"), NULL},
10931095
/* We do a lookup before each append, to avoid duplicates */
10941096
{SQL("CREATE INDEX chain_moves_utxo_idx ON chain_moves (utxo)"), NULL},
1097+
/* Add payment_id column to channel_htlcs */
1098+
{NULL, migrate_add_payment_id_to_htlcs},
10951099
{NULL, migrate_from_account_db},
10961100
};
10971101

@@ -2080,6 +2084,31 @@ static void migrate_initialize_alias_local(struct lightningd *ld,
20802084
}
20812085
}
20822086

2087+
/* Add payment_id column to channel_htlcs */
2088+
static void migrate_add_payment_id_to_htlcs(struct lightningd *ld, struct db *db)
2089+
{
2090+
struct db_stmt *stmt;
2091+
2092+
stmt = db_prepare_v2(db, SQL("ALTER TABLE channel_htlcs ADD COLUMN payment_id BIGINT"));
2093+
db_exec_prepared_v2(stmt);
2094+
tal_free(stmt);
2095+
2096+
stmt = db_prepare_v2(db, SQL("UPDATE channel_htlcs "
2097+
"SET payment_id = ("
2098+
" SELECT p.id FROM payments p "
2099+
" WHERE p.payment_hash = channel_htlcs.payment_hash "
2100+
" AND p.partid = channel_htlcs.partid "
2101+
" AND p.groupid = channel_htlcs.groupid "
2102+
" LIMIT 1"
2103+
")"));
2104+
db_exec_prepared_v2(stmt);
2105+
tal_free(stmt);
2106+
2107+
stmt = db_prepare_v2(db, SQL("CREATE INDEX channel_htlcs_payment_id_idx ON channel_htlcs (payment_id)"));
2108+
db_exec_prepared_v2(stmt);
2109+
tal_free(stmt);
2110+
}
2111+
20832112
/* Insert address type as `ADDR_ALL` for issued addresses */
20842113
static void insert_addrtype_to_addresses(struct lightningd *ld,
20852114
struct db *db)

wallet/wallet.c

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3316,11 +3316,29 @@ void wallet_htlc_save_out(struct wallet *wallet,
33163316
struct htlc_out *out)
33173317
{
33183318
struct db_stmt *stmt;
3319+
u64 payment_id = 0;
33193320

33203321
/* We absolutely need the incoming HTLC to be persisted before
33213322
* we can persist it's dependent */
33223323
assert(out->in == NULL || out->in->dbid != 0);
33233324

3325+
/* Get payment_id if this is an outgoing payment */
3326+
if (out->am_origin) {
3327+
struct db_stmt *payment_stmt = db_prepare_v2(wallet->db,
3328+
SQL("SELECT id FROM payments "
3329+
"WHERE payment_hash = ? AND partid = ? AND groupid = ? "
3330+
"LIMIT 1"));
3331+
db_bind_sha256(payment_stmt, &out->payment_hash);
3332+
db_bind_u64(payment_stmt, out->partid);
3333+
db_bind_u64(payment_stmt, out->groupid);
3334+
db_query_prepared(payment_stmt);
3335+
3336+
if (db_step(payment_stmt)) {
3337+
payment_id = db_col_u64(payment_stmt, "id");
3338+
}
3339+
tal_free(payment_stmt);
3340+
}
3341+
33243342
stmt = db_prepare_v2(
33253343
wallet->db,
33263344
SQL("INSERT INTO channel_htlcs ("
@@ -3339,8 +3357,9 @@ void wallet_htlc_save_out(struct wallet *wallet,
33393357
" partid,"
33403358
" groupid,"
33413359
" fees_msat,"
3342-
" min_commit_num"
3343-
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?);"));
3360+
" min_commit_num,"
3361+
" payment_id"
3362+
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, ?);"));
33443363

33453364
out->dbid = htlcs_index_created(wallet->ld,
33463365
out->key.id,
@@ -3384,6 +3403,11 @@ void wallet_htlc_save_out(struct wallet *wallet,
33843403
db_bind_u64(stmt, min_u64(chan->next_index[LOCAL]-1,
33853404
chan->next_index[REMOTE]-1));
33863405

3406+
if (payment_id > 0)
3407+
db_bind_u64(stmt, payment_id);
3408+
else
3409+
db_bind_null(stmt);
3410+
33873411
db_exec_prepared_v2(take(stmt));
33883412
}
33893413

0 commit comments

Comments
 (0)