From a82c91716df24938e1d13462aad72dc7614d4c56 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 20 Mar 2023 10:44:35 +1030 Subject: [PATCH 01/14] wallet: add comment on db noting that `ON DELETE CASCADE` is never used. We actually have an assertion that there are no channels remaining when we delete peers, so this is confusing! Actually removing the constraint is db-specific and deeply non-trivial. Signed-off-by: Rusty Russell --- wallet/db.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wallet/db.c b/wallet/db.c index ea2e2d0672db..8b1080e9ceba 100644 --- a/wallet/db.c +++ b/wallet/db.c @@ -116,6 +116,8 @@ static struct migration dbmigrations[] = { NULL}, {SQL("CREATE TABLE channels (" " id BIGSERIAL," /* chan->id */ + /* FIXME: We deliberately never delete a peer with channels, so this constraint is + * unnecessary! */ " peer_id BIGINT REFERENCES peers(id) ON DELETE CASCADE," " short_channel_id TEXT," " channel_config_local BIGINT," From 14ebfa87ea9fd86186bfbfeafbe3d31646626f6b Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 20 Mar 2023 10:45:35 +1030 Subject: [PATCH 02/14] wallet: only delete peer from db if it's unused. This relaxes the assertion that it won't be used, and renames the function to be clear. Signed-off-by: Rusty Russell --- lightningd/peer_control.c | 4 ++-- lightningd/test/run-invoice-select-inchan.c | 6 +++--- wallet/wallet.c | 9 ++++++--- wallet/wallet.h | 4 ++-- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index b9457e72f8de..dbc4a63882bd 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -130,7 +130,7 @@ static void delete_peer(struct peer *peer) /* If it only ever existed because of uncommitted channel, it won't * be in the database */ if (peer->dbid != 0) - wallet_peer_delete(peer->ld->wallet, peer->dbid); + wallet_delete_peer_if_unused(peer->ld->wallet, peer->dbid); tal_free(peer); } @@ -142,7 +142,7 @@ void maybe_delete_peer(struct peer *peer) if (peer->uncommitted_channel) { /* This isn't sufficient to keep it in db! */ if (peer->dbid != 0) { - wallet_peer_delete(peer->ld->wallet, peer->dbid); + wallet_delete_peer_if_unused(peer->ld->wallet, peer->dbid); peer_dbid_map_del(peer->ld->peers_by_dbid, peer); peer->dbid = 0; } diff --git a/lightningd/test/run-invoice-select-inchan.c b/lightningd/test/run-invoice-select-inchan.c index 58a8715bb478..ad53b4a6bd95 100644 --- a/lightningd/test/run-invoice-select-inchan.c +++ b/lightningd/test/run-invoice-select-inchan.c @@ -817,6 +817,9 @@ void wallet_channeltxs_add(struct wallet *w UNNEEDED, struct channel *chan UNNEE const int type UNNEEDED, const struct bitcoin_txid *txid UNNEEDED, const u32 input_num UNNEEDED, const u32 blockheight UNNEEDED) { fprintf(stderr, "wallet_channeltxs_add called!\n"); abort(); } +/* Generated stub for wallet_delete_peer_if_unused */ +void wallet_delete_peer_if_unused(struct wallet *w UNNEEDED, u64 peer_dbid UNNEEDED) +{ fprintf(stderr, "wallet_delete_peer_if_unused called!\n"); abort(); } /* Generated stub for wallet_htlcs_load_in_for_channel */ bool wallet_htlcs_load_in_for_channel(struct wallet *wallet UNNEEDED, struct channel *chan UNNEEDED, @@ -912,9 +915,6 @@ char *wallet_offer_find(const tal_t *ctx UNNEEDED, enum offer_status *status) { fprintf(stderr, "wallet_offer_find called!\n"); abort(); } -/* Generated stub for wallet_peer_delete */ -void wallet_peer_delete(struct wallet *w UNNEEDED, u64 peer_dbid UNNEEDED) -{ fprintf(stderr, "wallet_peer_delete called!\n"); abort(); } /* Generated stub for wallet_state_change_get */ struct state_change_entry *wallet_state_change_get(struct wallet *w UNNEEDED, const tal_t *ctx UNNEEDED, diff --git a/wallet/wallet.c b/wallet/wallet.c index 03029b4c1c2c..0fa41c52c7a7 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -2294,7 +2294,7 @@ void wallet_channel_close(struct wallet *w, u64 wallet_id) db_exec_prepared_v2(take(stmt)); } -void wallet_peer_delete(struct wallet *w, u64 peer_dbid) +void wallet_delete_peer_if_unused(struct wallet *w, u64 peer_dbid) { struct db_stmt *stmt; @@ -2303,8 +2303,11 @@ void wallet_peer_delete(struct wallet *w, u64 peer_dbid) db_bind_u64(stmt, 0, peer_dbid); db_query_prepared(stmt); - if (db_step(stmt)) - fatal("We have channels using peer %"PRIu64, peer_dbid); + if (db_step(stmt)) { + db_col_ignore(stmt, "*"); + tal_free(stmt); + return; + } tal_free(stmt); stmt = db_prepare_v2(w->db, SQL("DELETE FROM peers WHERE id=?")); diff --git a/wallet/wallet.h b/wallet/wallet.h index 4661e3e3918b..034e0a4501f5 100644 --- a/wallet/wallet.h +++ b/wallet/wallet.h @@ -632,9 +632,9 @@ struct state_change_entry *wallet_state_change_get(struct wallet *w, u64 channel_id); /** - * wallet_peer_delete -- After no more channels in peer, forget about it + * wallet_delete_peer_if_unused -- After no more channels in peer, forget about it */ -void wallet_peer_delete(struct wallet *w, u64 peer_dbid); +void wallet_delete_peer_if_unused(struct wallet *w, u64 peer_dbid); /** * wallet_init_channels -- Loads active channels into peers From 5f349eae4e13c06f007c8c6a7c04b771edbe22cd Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 20 Mar 2023 10:46:35 +1030 Subject: [PATCH 03/14] wallet: don't clear reference from channel to peers table when we close channel. Signed-off-by: Rusty Russell --- wallet/wallet.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/wallet/wallet.c b/wallet/wallet.c index 0fa41c52c7a7..a4df19354284 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -2284,13 +2284,12 @@ void wallet_channel_close(struct wallet *w, u64 wallet_id) db_bind_u64(stmt, 0, wallet_id); db_exec_prepared_v2(take(stmt)); - /* Set the channel to closed and disassociate with peer */ + /* Set the channel to closed */ stmt = db_prepare_v2(w->db, SQL("UPDATE channels " - "SET state=?, peer_id=? " + "SET state=? " "WHERE channels.id=?")); db_bind_u64(stmt, 0, CLOSED); - db_bind_null(stmt, 1); - db_bind_u64(stmt, 2, wallet_id); + db_bind_u64(stmt, 1, wallet_id); db_exec_prepared_v2(take(stmt)); } From b7ce26e2095cc06c0f56545bb08bfbedb93f4b22 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 20 Mar 2023 10:47:35 +1030 Subject: [PATCH 04/14] db_bind_scid: rename to db_bind_short_channel_id We used to have a text version, so this was named 'scid'. Fix it now. Signed-off-by: Rusty Russell --- db/bindings.c | 8 ++++---- db/bindings.h | 8 ++++---- wallet/db.c | 4 ++-- wallet/wallet.c | 44 ++++++++++++++++++++++---------------------- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/db/bindings.c b/db/bindings.c index fc95ca52f031..e9b8059997b2 100644 --- a/db/bindings.c +++ b/db/bindings.c @@ -159,8 +159,8 @@ void db_bind_pubkey(struct db_stmt *stmt, int pos, const struct pubkey *pk) db_bind_blob(stmt, pos, der, PUBKEY_CMPR_LEN); } -void db_bind_scid(struct db_stmt *stmt, int col, - const struct short_channel_id *id) +void db_bind_short_channel_id(struct db_stmt *stmt, int col, + const struct short_channel_id *id) { db_bind_u64(stmt, col, id->u64); } @@ -361,8 +361,8 @@ void db_col_pubkey(struct db_stmt *stmt, assert(ok); } -void db_col_scid(struct db_stmt *stmt, const char *colname, - struct short_channel_id *dest) +void db_col_short_channel_id(struct db_stmt *stmt, const char *colname, + struct short_channel_id *dest) { dest->u64 = db_col_u64(stmt, colname); } diff --git a/db/bindings.h b/db/bindings.h index cc7707cf5c4f..1e1f0420ed12 100644 --- a/db/bindings.h +++ b/db/bindings.h @@ -37,8 +37,8 @@ void db_bind_node_id(struct db_stmt *stmt, int pos, const struct node_id *ni); void db_bind_node_id_arr(struct db_stmt *stmt, int col, const struct node_id *ids); void db_bind_pubkey(struct db_stmt *stmt, int pos, const struct pubkey *p); -void db_bind_scid(struct db_stmt *stmt, int col, - const struct short_channel_id *id); +void db_bind_short_channel_id(struct db_stmt *stmt, int col, + const struct short_channel_id *id); void db_bind_short_channel_id_arr(struct db_stmt *stmt, int col, const struct short_channel_id *id); void db_bind_signature(struct db_stmt *stmt, int col, @@ -83,8 +83,8 @@ struct node_id *db_col_node_id_arr(const tal_t *ctx, struct db_stmt *stmt, const char *colname); void db_col_pubkey(struct db_stmt *stmt, const char *colname, struct pubkey *p); -void db_col_scid(struct db_stmt *stmt, const char *colname, - struct short_channel_id *dest); +void db_col_short_channel_id(struct db_stmt *stmt, const char *colname, + struct short_channel_id *dest); struct short_channel_id * db_col_short_channel_id_arr(const tal_t *ctx, struct db_stmt *stmt, const char *colname); bool db_col_signature(struct db_stmt *stmt, const char *colname, diff --git a/wallet/db.c b/wallet/db.c index 8b1080e9ceba..0b6fac067b03 100644 --- a/wallet/db.c +++ b/wallet/db.c @@ -1519,7 +1519,7 @@ static void migrate_channels_scids_as_integers(struct lightningd *ld, stmt = db_prepare_v2(db, SQL("UPDATE channels" " SET scid = ?" " WHERE short_channel_id = ?")); - db_bind_scid(stmt, 0, &scid); + db_bind_short_channel_id(stmt, 0, &scid); db_bind_text(stmt, 1, scids[i]); db_exec_prepared_v2(stmt); @@ -1575,7 +1575,7 @@ static void migrate_payments_scids_as_integers(struct lightningd *ld, update_stmt = db_prepare_v2(db, SQL("UPDATE payments SET" " failscid = ?" " WHERE id = ?")); - db_bind_scid(update_stmt, 0, &scid); + db_bind_short_channel_id(update_stmt, 0, &scid); db_bind_u64(update_stmt, 1, db_col_u64(stmt, "id")); db_exec_prepared_v2(update_stmt); tal_free(update_stmt); diff --git a/wallet/wallet.c b/wallet/wallet.c index a4df19354284..a057f4f938da 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -1319,21 +1319,21 @@ static struct channel *wallet_stmt2channel(struct wallet *w, struct db_stmt *stm if (!db_col_is_null(stmt, "scid")) { scid = tal(tmpctx, struct short_channel_id); - db_col_scid(stmt, "scid", scid); + db_col_short_channel_id(stmt, "scid", scid); } else { scid = NULL; } if (!db_col_is_null(stmt, "alias_local")) { alias[LOCAL] = tal(tmpctx, struct short_channel_id); - db_col_scid(stmt, "alias_local", alias[LOCAL]); + db_col_short_channel_id(stmt, "alias_local", alias[LOCAL]); } else { alias[LOCAL] = NULL; } if (!db_col_is_null(stmt, "alias_remote")) { alias[REMOTE] = tal(tmpctx, struct short_channel_id); - db_col_scid(stmt, "alias_remote", alias[REMOTE]); + db_col_short_channel_id(stmt, "alias_remote", alias[REMOTE]); } else { alias[REMOTE] = NULL; } @@ -1930,7 +1930,7 @@ void wallet_channel_save(struct wallet *w, struct channel *chan) " WHERE id=?")); // 46 db_bind_u64(stmt, 0, chan->their_shachain.id); if (chan->scid) - db_bind_scid(stmt, 1, chan->scid); + db_bind_short_channel_id(stmt, 1, chan->scid); else db_bind_null(stmt, 1); @@ -1997,12 +1997,12 @@ void wallet_channel_save(struct wallet *w, struct channel *chan) db_bind_amount_msat(stmt, 43, &chan->htlc_maximum_msat); if (chan->alias[LOCAL] != NULL) - db_bind_scid(stmt, 44, chan->alias[LOCAL]); + db_bind_short_channel_id(stmt, 44, chan->alias[LOCAL]); else db_bind_null(stmt, 44); if (chan->alias[REMOTE] != NULL) - db_bind_scid(stmt, 45, chan->alias[REMOTE]); + db_bind_short_channel_id(stmt, 45, chan->alias[REMOTE]); else db_bind_null(stmt, 45); @@ -3484,7 +3484,7 @@ void wallet_payment_get_failinfo(const tal_t *ctx, *failchannel = NULL; } else { *failchannel = tal(ctx, struct short_channel_id); - db_col_scid(stmt, "failscid", *failchannel); + db_col_short_channel_id(stmt, "failscid", *failchannel); /* For pre-0.6.2 dbs, direction will be 0 */ *faildirection = db_col_int(stmt, "faildirection"); @@ -3543,7 +3543,7 @@ void wallet_payment_set_failinfo(struct wallet *wallet, db_bind_null(stmt, 4); if (failchannel) { - db_bind_scid(stmt, 5, failchannel); + db_bind_short_channel_id(stmt, 5, failchannel); db_bind_int(stmt, 8, faildirection); } else { db_bind_null(stmt, 5); @@ -4381,7 +4381,7 @@ static bool wallet_forwarded_payment_update(struct wallet *w, else db_bind_int(stmt, 5, forward_style_in_db(forward_style)); db_bind_u64(stmt, 6, in->key.id); - db_bind_scid(stmt, 7, channel_scid_or_local_alias(in->key.channel)); + db_bind_short_channel_id(stmt, 7, channel_scid_or_local_alias(in->key.channel)); db_exec_prepared_v2(stmt); changed = db_count_changes(stmt) != 0; tal_free(stmt); @@ -4441,10 +4441,10 @@ void wallet_forwarded_payment_add(struct wallet *w, const struct htlc_in *in, * the sender used to specify the channel, but that's under * control of the remote end. */ assert(in->key.channel->scid != NULL || in->key.channel->alias[LOCAL]); - db_bind_scid(stmt, 2, channel_scid_or_local_alias(in->key.channel)); + db_bind_short_channel_id(stmt, 2, channel_scid_or_local_alias(in->key.channel)); if (scid_out) - db_bind_scid(stmt, 3, scid_out); + db_bind_short_channel_id(stmt, 3, scid_out); else db_bind_null(stmt, 3); db_bind_amount_msat(stmt, 4, &in->msat); @@ -4573,7 +4573,7 @@ const struct forwarding *wallet_forwarded_payments_get(struct wallet *w, if (chan_in) { // specific in_channel db_bind_int(stmt, 2, 0); - db_bind_scid(stmt, 3, chan_in); + db_bind_short_channel_id(stmt, 3, chan_in); } else { // any in_channel db_bind_int(stmt, 2, 1); @@ -4583,7 +4583,7 @@ const struct forwarding *wallet_forwarded_payments_get(struct wallet *w, if (chan_out) { // specific out_channel db_bind_int(stmt, 4, 0); - db_bind_scid(stmt, 5, chan_out); + db_bind_short_channel_id(stmt, 5, chan_out); } else { // any out_channel db_bind_int(stmt, 4, 1); @@ -4617,7 +4617,7 @@ const struct forwarding *wallet_forwarded_payments_get(struct wallet *w, cur->fee = AMOUNT_MSAT(0); } - db_col_scid(stmt, "in_channel_scid", &cur->channel_in); + db_col_short_channel_id(stmt, "in_channel_scid", &cur->channel_in); #ifdef COMPAT_V0121 /* This can happen due to migration! */ @@ -4630,7 +4630,7 @@ const struct forwarding *wallet_forwarded_payments_get(struct wallet *w, #endif if (!db_col_is_null(stmt, "out_channel_scid")) { - db_col_scid(stmt, "out_channel_scid", &cur->channel_out); + db_col_short_channel_id(stmt, "out_channel_scid", &cur->channel_out); } else { assert(cur->status == FORWARD_LOCAL_FAILED); cur->channel_out.u64 = 0; @@ -4687,7 +4687,7 @@ bool wallet_forward_delete(struct wallet *w, " WHERE in_channel_scid = ?" " AND in_htlc_id = ?" " AND state = ?;")); - db_bind_scid(stmt, 0, chan_in); + db_bind_short_channel_id(stmt, 0, chan_in); db_bind_u64(stmt, 1, *htlc_id); db_bind_int(stmt, 2, wallet_forward_status_in_db(FORWARD_SETTLED)); } else { @@ -4697,7 +4697,7 @@ bool wallet_forward_delete(struct wallet *w, " WHERE in_channel_scid = ?" " AND in_htlc_id IS NULL" " AND state = ?;")); - db_bind_scid(stmt, 0, chan_in); + db_bind_short_channel_id(stmt, 0, chan_in); db_bind_int(stmt, 1, wallet_forward_status_in_db(FORWARD_SETTLED)); } db_query_prepared(stmt); @@ -4720,7 +4720,7 @@ bool wallet_forward_delete(struct wallet *w, " WHERE in_channel_scid = ?" " AND in_htlc_id = ?" " AND state = ?")); - db_bind_scid(stmt, 0, chan_in); + db_bind_short_channel_id(stmt, 0, chan_in); db_bind_u64(stmt, 1, *htlc_id); db_bind_int(stmt, 2, wallet_forward_status_in_db(state)); } else { @@ -4729,7 +4729,7 @@ bool wallet_forward_delete(struct wallet *w, " WHERE in_channel_scid = ?" " AND in_htlc_id IS NULL" " AND state = ?")); - db_bind_scid(stmt, 0, chan_in); + db_bind_short_channel_id(stmt, 0, chan_in); db_bind_int(stmt, 1, wallet_forward_status_in_db(state)); } db_exec_prepared_v2(stmt); @@ -4824,7 +4824,7 @@ struct wallet_transaction *wallet_transactions_get(struct wallet *w, const tal_t got_ann: ann->type = db_col_int(stmt, "annotation_type"); if (!db_col_is_null(stmt, "c.scid")) - db_col_scid(stmt, "c.scid", &ann->channel); + db_col_short_channel_id(stmt, "c.scid", &ann->channel); else ann->channel.u64 = 0; } else { @@ -5455,9 +5455,9 @@ struct wallet_htlc_iter *wallet_htlcs_next(struct wallet *w, *scid = iter->scid; else { if (db_col_is_null(iter->stmt, "channels.scid")) - db_col_scid(iter->stmt, "channels.alias_local", scid); + db_col_short_channel_id(iter->stmt, "channels.alias_local", scid); else { - db_col_scid(iter->stmt, "channels.scid", scid); + db_col_short_channel_id(iter->stmt, "channels.scid", scid); db_col_ignore(iter->stmt, "channels.alias_local"); } } From f996aa52ceecd545261ff73b81919d75ab2f7566 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 20 Mar 2023 10:48:35 +1030 Subject: [PATCH 05/14] db_col_optional: wrapper for case where a field is allowed to be NULL. Signed-off-by: Rusty Russell --- db/bindings.c | 11 +++++++++++ db/bindings.h | 14 ++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/db/bindings.c b/db/bindings.c index e9b8059997b2..22643e84d745 100644 --- a/db/bindings.c +++ b/db/bindings.c @@ -367,6 +367,17 @@ void db_col_short_channel_id(struct db_stmt *stmt, const char *colname, dest->u64 = db_col_u64(stmt, colname); } +void *db_col_optional_(tal_t *dst, + struct db_stmt *stmt, const char *colname, + void (*colfn)(struct db_stmt *, const char *, void *)) +{ + if (db_col_is_null(stmt, colname)) + return tal_free(dst); + + colfn(stmt, colname, dst); + return dst; +} + struct short_channel_id * db_col_short_channel_id_arr(const tal_t *ctx, struct db_stmt *stmt, const char *colname) { diff --git a/db/bindings.h b/db/bindings.h index 1e1f0420ed12..83679fde8a91 100644 --- a/db/bindings.h +++ b/db/bindings.h @@ -105,6 +105,20 @@ void *db_col_arr_(const tal_t *ctx, struct db_stmt *stmt, const char *colname, size_t bytes, const char *label, const char *caller); +/* Assumes void db_col_@type(stmt, colname, addr), and struct @type! */ +#define db_col_optional(ctx, stmt, colname, type) \ + ((struct type *)db_col_optional_(tal(ctx, struct type), \ + (stmt), (colname), \ + typesafe_cb_cast(void (*)(struct db_stmt *, const char *, void *), \ + void (*)(struct db_stmt *, const char *, struct type *), \ + db_col_##type))) + +void *WARN_UNUSED_RESULT db_col_optional_(tal_t *dst, + struct db_stmt *stmt, + const char *colname, + void (*colfn)(struct db_stmt *, + const char *, void *)); + /* Some useful default variants */ int db_col_int_or_default(struct db_stmt *stmt, const char *colname, int def); void db_col_amount_msat_or_default(struct db_stmt *stmt, const char *colname, From cf82f8acf49ab7ecea0d3d5fd13ec9a6a16decb8 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 20 Mar 2023 10:48:59 +1030 Subject: [PATCH 06/14] wallet: use db_col_optional. We don't cover three common patterns: 1. Optional integers (db_col_u64 has different form from structs) 2. Optional strings. 3. Optional array fields. But it does neaten and reduce the scope for cut&paste errors in the common "if not-NULL, tal and assign". Signed-off-by: Rusty Russell --- wallet/db.c | 6 +-- wallet/invoices.c | 15 +----- wallet/wallet.c | 117 ++++++++++++---------------------------------- 3 files changed, 33 insertions(+), 105 deletions(-) diff --git a/wallet/db.c b/wallet/db.c index 0b6fac067b03..538988b1c242 100644 --- a/wallet/db.c +++ b/wallet/db.c @@ -1128,11 +1128,7 @@ void fillin_missing_scriptpubkeys(struct lightningd *ld, struct db *db, channel_id = db_col_u64(stmt, "channel_id"); db_col_node_id(stmt, "peer_id", &peer_id); - if (!db_col_is_null(stmt, "commitment_point")) { - commitment_point = tal(stmt, struct pubkey); - db_col_pubkey(stmt, "commitment_point", commitment_point); - } else - commitment_point = NULL; + commitment_point = db_col_optional(stmt, stmt, "commitment_point", pubkey); /* Have to go ask the HSM to derive the pubkey for us */ msg = towire_hsmd_get_output_scriptpubkey(NULL, diff --git a/wallet/invoices.c b/wallet/invoices.c index 2727429a38e9..748267e5f499 100644 --- a/wallet/invoices.c +++ b/wallet/invoices.c @@ -87,13 +87,7 @@ static struct invoice_details *wallet_stmt2invoice_details(const tal_t *ctx, dtl->label = db_col_json_escape(dtl, stmt, "label"); - if (!db_col_is_null(stmt, "msatoshi")) { - dtl->msat = tal(dtl, struct amount_msat); - db_col_amount_msat(stmt, "msatoshi", dtl->msat); - } else { - dtl->msat = NULL; - } - + dtl->msat = db_col_optional(dtl, stmt, "msatoshi", amount_msat); dtl->expiry_time = db_col_u64(stmt, "expiry_time"); if (dtl->state == PAID) { @@ -115,12 +109,7 @@ static struct invoice_details *wallet_stmt2invoice_details(const tal_t *ctx, dtl->description = NULL; dtl->features = db_col_arr(dtl, stmt, "features", u8); - if (!db_col_is_null(stmt, "local_offer_id")) { - dtl->local_offer_id = tal(dtl, struct sha256); - db_col_sha256(stmt, "local_offer_id", - dtl->local_offer_id); - } else - dtl->local_offer_id = NULL; + dtl->local_offer_id = db_col_optional(dtl, stmt, "local_offer_id", sha256); return dtl; } diff --git a/wallet/wallet.c b/wallet/wallet.c index a057f4f938da..f596d4c13acb 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -209,13 +209,10 @@ static struct utxo *wallet_stmt2output(const tal_t *ctx, struct db_stmt *stmt) utxo->close_info = tal(utxo, struct unilateral_close_info); utxo->close_info->channel_id = db_col_u64(stmt, "channel_id"); db_col_node_id(stmt, "peer_id", &utxo->close_info->peer_id); - if (!db_col_is_null(stmt, "commitment_point")) { - utxo->close_info->commitment_point - = tal(utxo->close_info, struct pubkey); - db_col_pubkey(stmt, "commitment_point", - utxo->close_info->commitment_point); - } else - utxo->close_info->commitment_point = NULL; + utxo->close_info->commitment_point + = db_col_optional(utxo->close_info, stmt, + "commitment_point", + pubkey); utxo->close_info->option_anchor_outputs = db_col_int(stmt, "option_anchor_outputs"); utxo->close_info->csv = db_col_int(stmt, "csv_lock"); @@ -1317,26 +1314,11 @@ static struct channel *wallet_stmt2channel(struct wallet *w, struct db_stmt *stm } } - if (!db_col_is_null(stmt, "scid")) { - scid = tal(tmpctx, struct short_channel_id); - db_col_short_channel_id(stmt, "scid", scid); - } else { - scid = NULL; - } - - if (!db_col_is_null(stmt, "alias_local")) { - alias[LOCAL] = tal(tmpctx, struct short_channel_id); - db_col_short_channel_id(stmt, "alias_local", alias[LOCAL]); - } else { - alias[LOCAL] = NULL; - } - - if (!db_col_is_null(stmt, "alias_remote")) { - alias[REMOTE] = tal(tmpctx, struct short_channel_id); - db_col_short_channel_id(stmt, "alias_remote", alias[REMOTE]); - } else { - alias[REMOTE] = NULL; - } + scid = db_col_optional(tmpctx, stmt, "scid", short_channel_id); + alias[LOCAL] = db_col_optional(tmpctx, stmt, "alias_local", + short_channel_id); + alias[REMOTE] = db_col_optional(tmpctx, stmt, "alias_remote", + short_channel_id); ok &= wallet_shachain_load(w, db_col_u64(stmt, "shachain_remote_id"), &wshachain); @@ -1370,12 +1352,9 @@ static struct channel *wallet_stmt2channel(struct wallet *w, struct db_stmt *stm db_col_ignore(stmt, "last_sent_commit_state"); db_col_ignore(stmt, "last_sent_commit_id"); - if (!db_col_is_null(stmt, "future_per_commitment_point")) { - future_per_commitment_point = tal(tmpctx, struct pubkey); - db_col_pubkey(stmt, "future_per_commitment_point", - future_per_commitment_point); - } else - future_per_commitment_point = NULL; + future_per_commitment_point = db_col_optional(tmpctx, stmt, + "future_per_commitment_point", + pubkey); db_col_channel_id(stmt, "full_channel_id", &cid); channel_config_id = db_col_u64(stmt, "channel_config_local"); @@ -2628,12 +2607,7 @@ static bool wallet_stmt2htlc_in(struct channel *channel, db_col_sha256(stmt, "payment_hash", &in->payment_hash); - if (!db_col_is_null(stmt, "payment_key")) { - in->preimage = tal(in, struct preimage); - db_col_preimage(stmt, "payment_key", in->preimage); - } else { - in->preimage = NULL; - } + in->preimage = db_col_optional(in, stmt, "payment_key", preimage); assert(db_col_bytes(stmt, "routing_onion") == sizeof(in->onion_routing_packet)); @@ -2645,18 +2619,12 @@ static bool wallet_stmt2htlc_in(struct channel *channel, else in->failonion = db_col_onionreply(in, stmt, "failuremsg"); in->badonion = db_col_int(stmt, "malformed_onion"); - if (db_col_is_null(stmt, "shared_secret")) { - in->shared_secret = NULL; - } else { - assert(db_col_bytes(stmt, "shared_secret") == sizeof(struct secret)); - in->shared_secret = tal(in, struct secret); - memcpy(in->shared_secret, db_col_blob(stmt, "shared_secret"), - sizeof(struct secret)); + in->shared_secret = db_col_optional(in, stmt, "shared_secret", secret); #ifdef COMPAT_V062 - if (memeqzero(in->shared_secret, sizeof(*in->shared_secret))) - in->shared_secret = tal_free(in->shared_secret); + if (in->shared_secret + && memeqzero(in->shared_secret, sizeof(*in->shared_secret))) + in->shared_secret = tal_free(in->shared_secret); #endif - } #ifdef COMPAT_V072 if (db_col_is_null(stmt, "received_time")) { @@ -2709,12 +2677,7 @@ static bool wallet_stmt2htlc_out(struct wallet *wallet, /* FIXME: save blinding in db !*/ out->blinding = NULL; - if (!db_col_is_null(stmt, "payment_key")) { - out->preimage = tal(out, struct preimage); - db_col_preimage(stmt, "payment_key", out->preimage); - } else { - out->preimage = NULL; - } + out->preimage = db_col_optional(out, stmt, "payment_key", preimage); assert(db_col_bytes(stmt, "routing_onion") == sizeof(out->onion_routing_packet)); @@ -3231,24 +3194,15 @@ static struct wallet_payment *wallet_stmt2payment(const tal_t *ctx, struct wallet_payment *payment = tal(ctx, struct wallet_payment); payment->id = db_col_u64(stmt, "id"); payment->status = db_col_int(stmt, "status"); - - if (!db_col_is_null(stmt, "destination")) { - payment->destination = tal(payment, struct node_id); - db_col_node_id(stmt, "destination", payment->destination); - } else { - payment->destination = NULL; - } - + payment->destination = db_col_optional(payment, stmt, "destination", + node_id); db_col_amount_msat(stmt, "msatoshi", &payment->msatoshi); db_col_sha256(stmt, "payment_hash", &payment->payment_hash); payment->timestamp = db_col_int(stmt, "timestamp"); - if (!db_col_is_null(stmt, "payment_preimage")) { - payment->payment_preimage = tal(payment, struct preimage); - db_col_preimage(stmt, "payment_preimage", - payment->payment_preimage); - } else - payment->payment_preimage = NULL; + payment->payment_preimage = db_col_optional(payment, stmt, + "payment_preimage", + preimage); /* We either used `sendpay` or `sendonion` with the `shared_secrets` * argument. */ @@ -3304,11 +3258,8 @@ static struct wallet_payment *wallet_stmt2payment(const tal_t *ctx, else payment->partid = 0; - if (!db_col_is_null(stmt, "local_invreq_id")) { - payment->local_invreq_id = tal(payment, struct sha256); - db_col_sha256(stmt, "local_invreq_id", payment->local_invreq_id); - } else - payment->local_invreq_id = NULL; + payment->local_invreq_id = db_col_optional(payment, stmt, + "local_invreq_id", sha256); if (!db_col_is_null(stmt, "completed_at")) { payment->completed_at = tal(payment, u32); @@ -3473,21 +3424,13 @@ void wallet_payment_get_failinfo(const tal_t *ctx, *faildestperm = db_col_int(stmt, "faildestperm") != 0; *failindex = db_col_int(stmt, "failindex"); *failcode = (enum onion_wire) db_col_int(stmt, "failcode"); - if (db_col_is_null(stmt, "failnode")) - *failnode = NULL; - else { - *failnode = tal(ctx, struct node_id); - db_col_node_id(stmt, "failnode", *failnode); - } - if (db_col_is_null(stmt, "failscid")) { - db_col_ignore(stmt, "faildirection"); - *failchannel = NULL; - } else { - *failchannel = tal(ctx, struct short_channel_id); - db_col_short_channel_id(stmt, "failscid", *failchannel); - + *failnode = db_col_optional(ctx, stmt, "failnode", node_id); + *failchannel = db_col_optional(ctx, stmt, "failscid", short_channel_id); + if (*failchannel) { /* For pre-0.6.2 dbs, direction will be 0 */ *faildirection = db_col_int(stmt, "faildirection"); + } else { + db_col_ignore(stmt, "faildirection"); } if (db_col_is_null(stmt, "failupdate")) *failupdate = NULL; From 7f8fe7acfbdba267a74bb77db798ec2c5eea1308 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 20 Mar 2023 10:49:05 +1030 Subject: [PATCH 07/14] wallet: make it clear that `enum state_change` is in db. Signed-off-by: Rusty Russell --- lightningd/channel_state.h | 1 + wallet/wallet.c | 35 +++++++++++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/lightningd/channel_state.h b/lightningd/channel_state.h index eead7d32d425..bb871fbb9dec 100644 --- a/lightningd/channel_state.h +++ b/lightningd/channel_state.h @@ -43,6 +43,7 @@ enum channel_state { }; #define CHANNEL_STATE_MAX DUALOPEND_AWAITING_LOCKIN +/* These are in the database, so don't renumber them! */ enum state_change { /* Anything other than the reasons below. Should not happen. */ REASON_UNKNOWN, diff --git a/wallet/wallet.c b/wallet/wallet.c index f596d4c13acb..e7c8912b57f8 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -63,6 +63,33 @@ void db_fatal(const char *fmt, ...) } #endif /* DB_FATAL */ +/* These go in db, so values cannot change (we can't put this into + * lightningd/channel_state.h since it confuses cdump!) */ +static enum state_change state_change_in_db(enum state_change s) +{ + switch (s) { + case REASON_UNKNOWN: + BUILD_ASSERT(REASON_UNKNOWN == 0); + return s; + case REASON_LOCAL: + BUILD_ASSERT(REASON_LOCAL == 1); + return s; + case REASON_USER: + BUILD_ASSERT(REASON_USER == 2); + return s; + case REASON_REMOTE: + BUILD_ASSERT(REASON_REMOTE == 3); + return s; + case REASON_PROTOCOL: + BUILD_ASSERT(REASON_PROTOCOL == 4); + return s; + case REASON_ONCHAIN: + BUILD_ASSERT(REASON_ONCHAIN == 5); + return s; + } + fatal("%s: %u is invalid", __func__, s); +} + static void outpointfilters_init(struct wallet *w) { struct db_stmt *stmt; @@ -1514,7 +1541,7 @@ static struct channel *wallet_stmt2channel(struct wallet *w, struct db_stmt *stm db_col_u64(stmt, "remote_static_remotekey_start"), type, db_col_int(stmt, "closer"), - db_col_int(stmt, "state_change_reason"), + state_change_in_db(db_col_int(stmt, "state_change_reason")), shutdown_wrong_funding, take(height_states), db_col_int(stmt, "lease_expiry"), @@ -1953,7 +1980,7 @@ void wallet_channel_save(struct wallet *w, struct channel *chan) db_bind_int(stmt, 32, channel_has(chan, OPT_ANCHOR_OUTPUTS)); db_bind_talarr(stmt, 33, chan->shutdown_scriptpubkey[LOCAL]); db_bind_int(stmt, 34, chan->closer); - db_bind_int(stmt, 35, chan->state_change_cause); + db_bind_int(stmt, 35, state_change_in_db(chan->state_change_cause)); if (chan->shutdown_wrong_funding) { db_bind_txid(stmt, 36, &chan->shutdown_wrong_funding->txid); db_bind_int(stmt, 37, chan->shutdown_wrong_funding->n); @@ -2098,7 +2125,7 @@ void wallet_state_change_add(struct wallet *w, db_bind_timeabs(stmt, 1, *timestamp); db_bind_int(stmt, 2, old_state); db_bind_int(stmt, 3, new_state); - db_bind_int(stmt, 4, cause); + db_bind_int(stmt, 4, state_change_in_db(cause)); db_bind_text(stmt, 5, message); db_exec_prepared_v2(take(stmt)); @@ -2129,7 +2156,7 @@ struct state_change_entry *wallet_state_change_get(struct wallet *w, tmp.timestamp = db_col_timeabs(stmt, "timestamp"); tmp.old_state = db_col_int(stmt, "old_state"); tmp.new_state = db_col_int(stmt, "new_state"); - tmp.cause = db_col_int(stmt, "cause"); + tmp.cause = state_change_in_db(db_col_int(stmt, "cause")); tmp.message = db_col_strdup(res, stmt, "message"); tal_arr_expand(&res, tmp); } From a7ba0a0a52a238ba06ea7777fadcd625bdd3c036 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 20 Mar 2023 10:49:15 +1030 Subject: [PATCH 08/14] wallet: add accessor for closed channels. This doesn't restore every bit of information we have, but it does contain the important ones. Signed-off-by: Rusty Russell --- lightningd/Makefile | 1 + lightningd/closed_channel.h | 28 +++++++++++ wallet/wallet.c | 95 +++++++++++++++++++++++++++++++++++++ wallet/wallet.h | 10 ++++ 4 files changed, 134 insertions(+) create mode 100644 lightningd/closed_channel.h diff --git a/lightningd/Makefile b/lightningd/Makefile index 11dbaaf7a259..f277ec3b2438 100644 --- a/lightningd/Makefile +++ b/lightningd/Makefile @@ -48,6 +48,7 @@ include wallet/Makefile LIGHTNINGD_HDRS := \ $(LIGHTNINGD_SRC:.c=.h) \ + lightningd/closed_channel.h \ lightningd/channel_state.h \ lightningd/channel_state_names_gen.h \ $(WALLET_HDRS) diff --git a/lightningd/closed_channel.h b/lightningd/closed_channel.h new file mode 100644 index 000000000000..d507f5b84b3e --- /dev/null +++ b/lightningd/closed_channel.h @@ -0,0 +1,28 @@ +/* Not to be confused with live channels in ld->channels */ +#ifndef LIGHTNING_LIGHTNINGD_CLOSED_CHANNEL_H +#define LIGHTNING_LIGHTNINGD_CLOSED_CHANNEL_H +#include "config.h" + +struct closed_channel { + /* This is often deleted on older nodes! */ + struct node_id *peer_id; + struct channel_id cid; + struct short_channel_id *scid; + struct short_channel_id *alias[NUM_SIDES]; + enum side opener, closer; + u8 channel_flags; + u64 next_index[NUM_SIDES], next_htlc_id; + struct bitcoin_outpoint funding; + struct amount_sat funding_sats; + struct amount_msat push; + struct amount_msat our_msat; + /* Statistics for min and max our_msatoshi. */ + struct amount_msat msat_to_us_min; + struct amount_msat msat_to_us_max; + struct bitcoin_tx *last_tx; + const struct channel_type *type; + enum state_change state_change_cause; + bool leased; +}; + +#endif /* LIGHTNING_LIGHTNINGD_CLOSED_CHANNEL_H */ diff --git a/wallet/wallet.c b/wallet/wallet.c index e7c8912b57f8..085a687309de 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -1559,6 +1560,100 @@ static struct channel *wallet_stmt2channel(struct wallet *w, struct db_stmt *stm return chan; } +static struct closed_channel *wallet_stmt2closed_channel(const tal_t *ctx, + struct wallet *w, + struct db_stmt *stmt) +{ + struct closed_channel *cc = tal(ctx, struct closed_channel); + + /* Can be missing in older dbs! */ + cc->peer_id = db_col_optional(cc, stmt, "p.node_id", node_id); + db_col_channel_id(stmt, "full_channel_id", &cc->cid); + cc->scid = db_col_optional(cc, stmt, "scid", short_channel_id); + cc->alias[LOCAL] = db_col_optional(cc, stmt, "alias_local", + short_channel_id); + cc->alias[REMOTE] = db_col_optional(cc, stmt, "alias_remote", + short_channel_id); + cc->opener = db_col_int(stmt, "funder"); + cc->closer = db_col_int(stmt, "closer"); + cc->channel_flags = db_col_int(stmt, "channel_flags"); + cc->next_index[LOCAL] = db_col_u64(stmt, "next_index_local"); + cc->next_index[REMOTE] = db_col_u64(stmt, "next_index_remote"); + cc->next_htlc_id = db_col_u64(stmt, "next_htlc_id"); + db_col_sha256d(stmt, "funding_tx_id", &cc->funding.txid.shad); + cc->funding.n = db_col_int(stmt, "funding_tx_outnum"); + db_col_amount_sat(stmt, "funding_satoshi", &cc->funding_sats); + db_col_amount_msat(stmt, "push_msatoshi", &cc->push); + db_col_amount_msat(stmt, "msatoshi_local", &cc->our_msat); + db_col_amount_msat(stmt, "msatoshi_to_us_min", &cc->msat_to_us_min); + db_col_amount_msat(stmt, "msatoshi_to_us_max", &cc->msat_to_us_max); + /* last_tx is null for stub channels used for recovering funds through + * Static channel backups. */ + if (!db_col_is_null(stmt, "last_tx")) + cc->last_tx = db_col_psbt_to_tx(cc, stmt, "last_tx"); + else + cc->last_tx = NULL; + + if (db_col_int(stmt, "option_anchor_outputs")) { + cc->type = channel_type_anchor_outputs(cc); + db_col_ignore(stmt, "local_static_remotekey_start"); + } else if (db_col_u64(stmt, "local_static_remotekey_start") != 0x7FFFFFFFFFFFFFFFULL) + cc->type = channel_type_static_remotekey(cc); + else + cc->type = channel_type_none(cc); + cc->state_change_cause + = state_change_in_db(db_col_int(stmt, "state_change_reason")); + cc->leased = !db_col_is_null(stmt, "lease_commit_sig"); + + return cc; +} + +struct closed_channel **wallet_load_closed_channels(const tal_t *ctx, + struct wallet *w) +{ + struct db_stmt *stmt; + struct closed_channel **chans = tal_arr(ctx, struct closed_channel *, 0); + + /* We load all channels */ + stmt = db_prepare_v2(w->db, SQL("SELECT " + " p.node_id" + ", full_channel_id" + ", scid" + ", alias_local" + ", alias_remote" + ", funder" + ", closer" + ", channel_flags" + ", next_index_local" + ", next_index_remote" + ", next_htlc_id" + ", funding_tx_id" + ", funding_tx_outnum" + ", funding_satoshi" + ", push_msatoshi" + ", msatoshi_local" + ", msatoshi_to_us_min" + ", msatoshi_to_us_max" + ", last_tx" + ", option_anchor_outputs" + ", local_static_remotekey_start" + ", state_change_reason" + ", lease_commit_sig" + " FROM channels" + " LEFT JOIN peers p ON p.id = peer_id" + " WHERE state = ?;")); + db_bind_int(stmt, 0, CLOSED); + db_query_prepared(stmt); + + while (db_step(stmt)) { + struct closed_channel *cc = wallet_stmt2closed_channel(chans, + w, stmt); + tal_arr_expand(&chans, cc); + } + tal_free(stmt); + return chans; +} + static void set_max_channel_dbid(struct wallet *w) { struct db_stmt *stmt; diff --git a/wallet/wallet.h b/wallet/wallet.h index 034e0a4501f5..d6427e1a67d4 100644 --- a/wallet/wallet.h +++ b/wallet/wallet.h @@ -647,6 +647,16 @@ void wallet_delete_peer_if_unused(struct wallet *w, u64 peer_dbid); */ bool wallet_init_channels(struct wallet *w); +/** + * wallet_load_closed_channels -- Loads dead channels. + * @ctx: context to allocate returned array from + * @w: wallet to load from + * + * These will be all state CLOSED. + */ +struct closed_channel **wallet_load_closed_channels(const tal_t *ctx, + struct wallet *w); + /** * wallet_channel_stats_incr_* - Increase channel statistics. * From 4a48728e72d3bea0218a198756917bd3e72f39ef Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 20 Mar 2023 10:49:27 +1030 Subject: [PATCH 09/14] common: expose routine to map channel_type to feature names. Signed-off-by: Rusty Russell --- common/channel_type.c | 14 ++++ common/channel_type.h | 3 + common/test/run-channel_type.c | 130 +++++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+) create mode 100644 common/test/run-channel_type.c diff --git a/common/channel_type.c b/common/channel_type.c index 3a2574a033ef..920aeb99c207 100644 --- a/common/channel_type.c +++ b/common/channel_type.c @@ -166,3 +166,17 @@ struct channel_type *channel_type_accept(const tal_t *ctx, return NULL; } + +/* Return an array of feature strings indicating channel type. */ +const char **channel_type_name(const tal_t *ctx, const struct channel_type *t) +{ + const char **names = tal_arr(ctx, const char *, 0); + + for (size_t i = 0; i < tal_bytelen(t->features) * CHAR_BIT; i++) { + if (!feature_is_set(t->features, i)) + continue; + tal_arr_expand(&names, + feature_name(names, i) + strlen("option_")); + } + return names; +} diff --git a/common/channel_type.h b/common/channel_type.h index 858dc6471448..2f59df10bc3f 100644 --- a/common/channel_type.h +++ b/common/channel_type.h @@ -35,4 +35,7 @@ struct channel_type *channel_type_accept(const tal_t *ctx, const u8 *t, const struct feature_set *our_features, const u8 *their_features); + +/* Return an array of feature strings indicating channel type. */ +const char **channel_type_name(const tal_t *ctx, const struct channel_type *t); #endif /* LIGHTNING_COMMON_CHANNEL_TYPE_H */ diff --git a/common/test/run-channel_type.c b/common/test/run-channel_type.c new file mode 100644 index 000000000000..24e95686f27e --- /dev/null +++ b/common/test/run-channel_type.c @@ -0,0 +1,130 @@ +#include "config.h" +#include "../channel_type.c" +#include "../features.c" +#include +#include +#include + +/* AUTOGENERATED MOCKS START */ +/* Generated stub for amount_asset_is_main */ +bool amount_asset_is_main(struct amount_asset *asset UNNEEDED) +{ fprintf(stderr, "amount_asset_is_main called!\n"); abort(); } +/* Generated stub for amount_asset_to_sat */ +struct amount_sat amount_asset_to_sat(struct amount_asset *asset UNNEEDED) +{ fprintf(stderr, "amount_asset_to_sat called!\n"); abort(); } +/* Generated stub for amount_sat */ +struct amount_sat amount_sat(u64 satoshis UNNEEDED) +{ fprintf(stderr, "amount_sat called!\n"); abort(); } +/* Generated stub for amount_sat_add */ + bool amount_sat_add(struct amount_sat *val UNNEEDED, + struct amount_sat a UNNEEDED, + struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_add called!\n"); abort(); } +/* Generated stub for amount_sat_eq */ +bool amount_sat_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_eq called!\n"); abort(); } +/* Generated stub for amount_sat_greater_eq */ +bool amount_sat_greater_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_greater_eq called!\n"); abort(); } +/* Generated stub for amount_sat_sub */ + bool amount_sat_sub(struct amount_sat *val UNNEEDED, + struct amount_sat a UNNEEDED, + struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_sub called!\n"); abort(); } +/* Generated stub for amount_sat_to_asset */ +struct amount_asset amount_sat_to_asset(struct amount_sat *sat UNNEEDED, const u8 *asset UNNEEDED) +{ fprintf(stderr, "amount_sat_to_asset called!\n"); abort(); } +/* Generated stub for amount_tx_fee */ +struct amount_sat amount_tx_fee(u32 fee_per_kw UNNEEDED, size_t weight UNNEEDED) +{ fprintf(stderr, "amount_tx_fee called!\n"); abort(); } +/* Generated stub for fromwire */ +const u8 *fromwire(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, void *copy UNNEEDED, size_t n UNNEEDED) +{ fprintf(stderr, "fromwire called!\n"); abort(); } +/* Generated stub for fromwire_bool */ +bool fromwire_bool(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_bool called!\n"); abort(); } +/* Generated stub for fromwire_fail */ +void *fromwire_fail(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_fail called!\n"); abort(); } +/* Generated stub for fromwire_secp256k1_ecdsa_signature */ +void fromwire_secp256k1_ecdsa_signature(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, + secp256k1_ecdsa_signature *signature UNNEEDED) +{ fprintf(stderr, "fromwire_secp256k1_ecdsa_signature called!\n"); abort(); } +/* Generated stub for fromwire_sha256 */ +void fromwire_sha256(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct sha256 *sha256 UNNEEDED) +{ fprintf(stderr, "fromwire_sha256 called!\n"); abort(); } +/* Generated stub for fromwire_tal_arrn */ +u8 *fromwire_tal_arrn(const tal_t *ctx UNNEEDED, + const u8 **cursor UNNEEDED, size_t *max UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "fromwire_tal_arrn called!\n"); abort(); } +/* Generated stub for fromwire_u16 */ +u16 fromwire_u16(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u16 called!\n"); abort(); } +/* Generated stub for fromwire_u32 */ +u32 fromwire_u32(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u32 called!\n"); abort(); } +/* Generated stub for fromwire_u64 */ +u64 fromwire_u64(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u64 called!\n"); abort(); } +/* Generated stub for fromwire_u8 */ +u8 fromwire_u8(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u8 called!\n"); abort(); } +/* Generated stub for fromwire_u8_array */ +void fromwire_u8_array(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, u8 *arr UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "fromwire_u8_array called!\n"); abort(); } +/* Generated stub for towire */ +void towire(u8 **pptr UNNEEDED, const void *data UNNEEDED, size_t len UNNEEDED) +{ fprintf(stderr, "towire called!\n"); abort(); } +/* Generated stub for towire_bool */ +void towire_bool(u8 **pptr UNNEEDED, bool v UNNEEDED) +{ fprintf(stderr, "towire_bool called!\n"); abort(); } +/* Generated stub for towire_secp256k1_ecdsa_signature */ +void towire_secp256k1_ecdsa_signature(u8 **pptr UNNEEDED, + const secp256k1_ecdsa_signature *signature UNNEEDED) +{ fprintf(stderr, "towire_secp256k1_ecdsa_signature called!\n"); abort(); } +/* Generated stub for towire_sha256 */ +void towire_sha256(u8 **pptr UNNEEDED, const struct sha256 *sha256 UNNEEDED) +{ fprintf(stderr, "towire_sha256 called!\n"); abort(); } +/* Generated stub for towire_u16 */ +void towire_u16(u8 **pptr UNNEEDED, u16 v UNNEEDED) +{ fprintf(stderr, "towire_u16 called!\n"); abort(); } +/* Generated stub for towire_u32 */ +void towire_u32(u8 **pptr UNNEEDED, u32 v UNNEEDED) +{ fprintf(stderr, "towire_u32 called!\n"); abort(); } +/* Generated stub for towire_u64 */ +void towire_u64(u8 **pptr UNNEEDED, u64 v UNNEEDED) +{ fprintf(stderr, "towire_u64 called!\n"); abort(); } +/* Generated stub for towire_u8 */ +void towire_u8(u8 **pptr UNNEEDED, u8 v UNNEEDED) +{ fprintf(stderr, "towire_u8 called!\n"); abort(); } +/* Generated stub for towire_u8_array */ +void towire_u8_array(u8 **pptr UNNEEDED, const u8 *arr UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "towire_u8_array called!\n"); abort(); } +/* AUTOGENERATED MOCKS END */ + +static void assert_names_eq(const char **names, const char *expected) +{ + char **expected_names = tal_strsplit(tmpctx, expected, " ", STR_EMPTY_OK); + + assert(tal_count(expected_names) == tal_count(names) + 1); + for (size_t i = 0; i < tal_count(names); i++) + assert(streq(expected_names[i], names[i])); +} + +int main(int argc, char *argv[]) +{ + struct channel_type t; + + common_setup(argv[0]); + + assert_names_eq(channel_type_name(tmpctx, channel_type_none(tmpctx)), ""); + assert_names_eq(channel_type_name(tmpctx, channel_type_static_remotekey(tmpctx)), + "static_remotekey/even"); + assert_names_eq(channel_type_name(tmpctx, channel_type_anchor_outputs(tmpctx)), + "static_remotekey/even anchor_outputs/even"); + + t.features = tal_arr(tmpctx, u8, 0); + set_feature_bit(&t.features, 1000); + assert_names_eq(channel_type_name(tmpctx, &t), "unknown_1000/even"); + common_shutdown(); +} From fcf681ece17a74b0b5a91ac50add6a9abea429f7 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 20 Mar 2023 10:49:42 +1030 Subject: [PATCH 10/14] plugins/sql: handle case of subobject with sub-arrays. i.e. recurse properly in SQL generation. This is about to happen. Signed-off-by: Rusty Russell --- plugins/sql.c | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/plugins/sql.c b/plugins/sql.c index e4e61db39c93..60122b0215be 100644 --- a/plugins/sql.c +++ b/plugins/sql.c @@ -1114,6 +1114,32 @@ static struct command_result *json_listsqlschemas(struct command *cmd, return command_finished(cmd, ret); } +/* Adds a sub_object to this sql statement (and sub-sub etc) */ +static void add_sub_object(char **update_stmt, char **create_stmt, + const char **sep, struct table_desc *sub) +{ + /* sub-arrays are a completely separate table. */ + if (!sub->is_subobject) + return; + + /* sub-objects are folded into this table. */ + for (size_t j = 0; j < tal_count(sub->columns); j++) { + const struct column *subcol = &sub->columns[j]; + + if (subcol->sub) { + add_sub_object(update_stmt, create_stmt, sep, + subcol->sub); + continue; + } + tal_append_fmt(update_stmt, "%s?", *sep); + tal_append_fmt(create_stmt, "%s%s %s", + *sep, + subcol->dbname, + fieldtypemap[subcol->ftype].sqltype); + *sep = ","; + } +} + /* Creates sql statements, initializes table */ static void finish_td(struct plugin *plugin, struct table_desc *td) { @@ -1146,19 +1172,8 @@ static void finish_td(struct plugin *plugin, struct table_desc *td) const struct column *col = &td->columns[i]; if (col->sub) { - /* sub-arrays are a completely separate table. */ - if (!col->sub->is_subobject) - continue; - /* sub-objects are folded into this table. */ - for (size_t j = 0; j < tal_count(col->sub->columns); j++) { - const struct column *subcol = &col->sub->columns[j]; - tal_append_fmt(&td->update_stmt, "%s?", sep); - tal_append_fmt(&create_stmt, "%s%s %s", - sep, - subcol->dbname, - fieldtypemap[subcol->ftype].sqltype); - sep = ","; - } + add_sub_object(&td->update_stmt, &create_stmt, + &sep, col->sub); continue; } tal_append_fmt(&td->update_stmt, "%s?", sep); From 983d20d6c0b8d9f0026c6f8d606c2292f1103b46 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 20 Mar 2023 10:49:47 +1030 Subject: [PATCH 11/14] plugins/sql: recurse correctly into complex objects during processing. We didn't handle the case of an array inside a subobject. But that happens when we add the next commit! Signed-off-by: Rusty Russell --- plugins/sql.c | 61 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/plugins/sql.c b/plugins/sql.c index 60122b0215be..6c0036859acb 100644 --- a/plugins/sql.c +++ b/plugins/sql.c @@ -422,6 +422,39 @@ static struct command_result *process_json_list(struct command *cmd, const u64 *rowid, const struct table_desc *td); +/* Process all subobject columns */ +static struct command_result *process_json_subobjs(struct command *cmd, + const char *buf, + const jsmntok_t *t, + const struct table_desc *td, + u64 this_rowid) +{ + for (size_t i = 0; i < tal_count(td->columns); i++) { + const struct column *col = &td->columns[i]; + struct command_result *ret; + const jsmntok_t *coltok; + + if (!col->sub) + continue; + + coltok = json_get_member(buf, t, col->jsonname); + if (!coltok) + continue; + + /* If it's an array, use process_json_list */ + if (!col->sub->is_subobject) { + ret = process_json_list(cmd, buf, coltok, &this_rowid, + col->sub); + } else { + ret = process_json_subobjs(cmd, buf, coltok, col->sub, + this_rowid); + } + if (ret) + return ret; + } + return NULL; +} + /* Returns NULL on success, otherwise has failed cmd. */ static struct command_result *process_json_obj(struct command *cmd, const char *buf, @@ -569,23 +602,7 @@ static struct command_result *process_json_obj(struct command *cmd, sqlite3_errmsg(db)); } - for (size_t i = 0; i < tal_count(td->columns); i++) { - const struct column *col = &td->columns[i]; - const jsmntok_t *coltok; - struct command_result *ret; - - if (!col->sub || col->sub->is_subobject) - continue; - - coltok = json_get_member(buf, t, col->jsonname); - if (!coltok) - continue; - - ret = process_json_list(cmd, buf, coltok, &this_rowid, col->sub); - if (ret) - return ret; - } - return NULL; + return process_json_subobjs(cmd, buf, t, td, this_rowid); } /* A list, such as in the top-level reply, or for a sub-table */ @@ -1150,7 +1167,8 @@ static void finish_td(struct plugin *plugin, struct table_desc *td) /* subobject are separate at JSON level, folded at db level! */ if (td->is_subobject) - return; + /* But it might have sub-sub objects! */ + goto do_subtables; /* We make an explicit rowid in each table, for subtables to access. This is * becuase the implicit rowid can't be used as a foreign key! */ @@ -1160,10 +1178,14 @@ static void finish_td(struct plugin *plugin, struct table_desc *td) /* If we're a child array, we reference the parent column */ if (td->parent) { + /* But if parent is a subobject, we reference the outer! */ + struct table_desc *parent = td->parent; + while (parent->is_subobject) + parent = parent->parent; tal_append_fmt(&create_stmt, "row INTEGER REFERENCES %s(rowid) ON DELETE CASCADE," " arrindex INTEGER", - td->parent->name); + parent->name); tal_append_fmt(&td->update_stmt, "?,?"); sep = ","; } @@ -1190,6 +1212,7 @@ static void finish_td(struct plugin *plugin, struct table_desc *td) if (err != SQLITE_OK) plugin_err(plugin, "Could not create %s: %s", td->name, errmsg); +do_subtables: /* Now do any children */ for (size_t i = 0; i < tal_count(td->columns); i++) { const struct column *col = &td->columns[i]; From a43ce778a188e9bbd8e7c885b09ad41e4a4cb76d Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 20 Mar 2023 10:49:51 +1030 Subject: [PATCH 12/14] listpeerchannels: add channel_type, both in hex and as array of names. Changelog-Added: JSON-RPC: `listpeerchannels` now has `channel_type` field. Signed-off-by: Rusty Russell --- doc/lightning-listpeerchannels.7.md | 7 +++- doc/lightning-sql.7.md | 10 +++++- doc/schemas/listpeerchannels.schema.json | 39 +++++++++++++++++++++ lightningd/peer_control.c | 24 +++++++++++++ lightningd/peer_control.h | 6 ++++ lightningd/test/run-invoice-select-inchan.c | 3 ++ tests/test_plugin.py | 14 ++++++++ 7 files changed, 101 insertions(+), 2 deletions(-) diff --git a/doc/lightning-listpeerchannels.7.md b/doc/lightning-listpeerchannels.7.md index 1d182704a914..af7d202a0a36 100644 --- a/doc/lightning-listpeerchannels.7.md +++ b/doc/lightning-listpeerchannels.7.md @@ -31,6 +31,11 @@ On success, an object containing **channels** is returned. It is an array of ob - **features** (array of strings): - BOLT #9 features which apply to this channel (one of "option\_static\_remotekey", "option\_anchor\_outputs", "option\_zeroconf") - **scratch\_txid** (txid, optional): The txid we would use if we went onchain now +- **channel\_type** (object, optional): channel\_type as negotiated with peer *(added v23.05)*: + - **bits** (array of u32s): Each bit set in this channel\_type: + - Bit number + - **names** (array of strings): Feature name for each bit set in this channel\_type: + - Name of feature bit (one of "static\_remotekey/even", "anchor\_outputs/even", "anchors\_zero\_fee\_htlc\_tx/even", "scid\_alias/even", "zeroconf/even") - **feerate** (object, optional): Feerates for the current tx: - **perkw** (u32): Feerate per 1000 weight (i.e kSipa) - **perkb** (u32): Feerate per 1000 virtual bytes @@ -189,4 +194,4 @@ Main web site: Lightning RFC site (BOLT \#9): -[comment]: # ( SHA256STAMP:f9919b6967137945cb49392d64a42bd159123b9d3bb83833c5df3bc777065d2e) +[comment]: # ( SHA256STAMP:1e589a9e6eace9134d04693dd00caa03da98dd40c2e65d3c675c9bee06daff7f) diff --git a/doc/lightning-sql.7.md b/doc/lightning-sql.7.md index 1fd2808bc2f2..30cc14fde917 100644 --- a/doc/lightning-sql.7.md +++ b/doc/lightning-sql.7.md @@ -208,6 +208,14 @@ The following tables are currently supported: - `peer_connected` (type `boolean`, sqltype `INTEGER`) - `state` (type `string`, sqltype `TEXT`) - `scratch_txid` (type `txid`, sqltype `BLOB`) + - related table `peerchannels_channel_type_bits`, from JSON object `channel_type` + - `row` (reference to `peerchannels_channel_type.rowid`, sqltype `INTEGER`) + - `arrindex` (index within array, sqltype `INTEGER`) + - `bits` (type `u32`, sqltype `INTEGER`) + - related table `peerchannels_channel_type_names`, from JSON object `channel_type` + - `row` (reference to `peerchannels_channel_type.rowid`, sqltype `INTEGER`) + - `arrindex` (index within array, sqltype `INTEGER`) + - `names` (type `string`, sqltype `TEXT`) - `feerate_perkw` (type `u32`, sqltype `INTEGER`, from JSON object `feerate`) - `feerate_perkb` (type `u32`, sqltype `INTEGER`, from JSON object `feerate`) - `owner` (type `string`, sqltype `TEXT`) @@ -472,4 +480,4 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:d25af4b0655ebd31db68932c5ea6b532bd134477e42df5d0c7428e4a03fd0335) +[comment]: # ( SHA256STAMP:ccc382f01d39253aff5a6c7ccd74400feb2f900f78f492a4c55b0a80e04fe813) diff --git a/doc/schemas/listpeerchannels.schema.json b/doc/schemas/listpeerchannels.schema.json index 496b564d89ed..bd3a2e75f1f7 100644 --- a/doc/schemas/listpeerchannels.schema.json +++ b/doc/schemas/listpeerchannels.schema.json @@ -48,6 +48,41 @@ "type": "txid", "description": "The txid we would use if we went onchain now" }, + "channel_type": { + "type": "object", + "description": "channel_type as negotiated with peer", + "added": "v23.05", + "additionalProperties": false, + "required": [ + "bits", + "names" + ], + "properties": { + "bits": { + "type": "array", + "description": "Each bit set in this channel_type", + "items": { + "type": "u32", + "description": "Bit number" + } + }, + "names": { + "type": "array", + "description": "Feature name for each bit set in this channel_type", + "items": { + "type": "string", + "enum": [ + "static_remotekey/even", + "anchor_outputs/even", + "anchors_zero_fee_htlc_tx/even", + "scid_alias/even", + "zeroconf/even" + ], + "description": "Name of feature bit" + } + } + } + }, "feerate": { "type": "object", "description": "Feerates for the current tx", @@ -569,6 +604,7 @@ "peer_id": {}, "peer_connected": {}, "scratch_txid": {}, + "channel_type": {}, "feerate": {}, "owner": {}, "short_channel_id": {}, @@ -658,6 +694,7 @@ "peer_connected": {}, "alias": {}, "scratch_txid": {}, + "channel_type": {}, "feerate": {}, "owner": {}, "short_channel_id": {}, @@ -746,6 +783,7 @@ "peer_connected": {}, "state": {}, "scratch_txid": {}, + "channel_type": {}, "feerate": {}, "owner": {}, "short_channel_id": {}, @@ -835,6 +873,7 @@ "peer_id": {}, "peer_connected": {}, "scratch_txid": {}, + "channel_type": {}, "feerate": {}, "owner": {}, "alias": {}, diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index dbc4a63882bd..aaca93ec47bd 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -694,6 +694,29 @@ struct amount_msat channel_amount_receivable(const struct channel *channel) return receivable; } +void json_add_channel_type(struct json_stream *response, + const char *fieldname, + const struct channel_type *channel_type) +{ + const char **fnames; + + json_object_start(response, fieldname); + json_array_start(response, "bits"); + for (size_t i = 0; i < tal_bytelen(channel_type->features) * CHAR_BIT; i++) { + if (!feature_is_set(channel_type->features, i)) + continue; + json_add_u64(response, NULL, i); + } + json_array_end(response); + + json_array_start(response, "names"); + fnames = channel_type_name(tmpctx, channel_type); + for (size_t i = 0; i < tal_count(fnames); i++) + json_add_string(response, NULL, fnames[i]); + json_array_end(response); + json_object_end(response); +} + static void json_add_channel(struct lightningd *ld, struct json_stream *response, const char *key, const struct channel *channel, @@ -710,6 +733,7 @@ static void json_add_channel(struct lightningd *ld, if (peer) { json_add_node_id(response, "peer_id", &peer->id); json_add_bool(response, "peer_connected", peer->connected == PEER_CONNECTED); + json_add_channel_type(response, "channel_type", channel->type); } json_add_string(response, "state", channel_state_name(channel)); if (channel->last_tx && !invalid_last_tx(channel->last_tx)) { diff --git a/lightningd/peer_control.h b/lightningd/peer_control.h index a967866bf4a6..5f3044c38470 100644 --- a/lightningd/peer_control.h +++ b/lightningd/peer_control.h @@ -11,6 +11,7 @@ #include #include +struct channel_type; struct peer_fd; struct wally_psbt; @@ -140,6 +141,11 @@ command_find_channel(struct command *cmd, const char *buffer, const jsmntok_t *tok, struct channel **channel); +/* Add channel_type object */ +void json_add_channel_type(struct json_stream *response, + const char *fieldname, + const struct channel_type *channel_type); + /* Ancient (0.7.0 and before) releases could create invalid commitment txs! */ bool invalid_last_tx(const struct bitcoin_tx *tx); diff --git a/lightningd/test/run-invoice-select-inchan.c b/lightningd/test/run-invoice-select-inchan.c index ad53b4a6bd95..45bd50951c79 100644 --- a/lightningd/test/run-invoice-select-inchan.c +++ b/lightningd/test/run-invoice-select-inchan.c @@ -110,6 +110,9 @@ bool channel_tell_depth(struct lightningd *ld UNNEEDED, /* Generated stub for channel_type_has */ bool channel_type_has(const struct channel_type *type UNNEEDED, int feature UNNEEDED) { fprintf(stderr, "channel_type_has called!\n"); abort(); } +/* Generated stub for channel_type_name */ +const char **channel_type_name(const tal_t *ctx UNNEEDED, const struct channel_type *t UNNEEDED) +{ fprintf(stderr, "channel_type_name called!\n"); abort(); } /* Generated stub for channel_unsaved_close_conn */ void channel_unsaved_close_conn(struct channel *channel UNNEEDED, const char *why UNNEEDED) { fprintf(stderr, "channel_unsaved_close_conn called!\n"); abort(); } diff --git a/tests/test_plugin.py b/tests/test_plugin.py index dd12a6585eb2..397b3a6c3f09 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -3700,6 +3700,20 @@ def test_sql(node_factory, bitcoind): 'type': 'string'}, {'name': 'message', 'type': 'string'}]}, + 'peerchannels_channel_type_bits': { + 'columns': [{'name': 'row', + 'type': 'u64'}, + {'name': 'arrindex', + 'type': 'u64'}, + {'name': 'bits', + 'type': 'u64'}]}, + 'peerchannels_channel_type_names': { + 'columns': [{'name': 'row', + 'type': 'u64'}, + {'name': 'arrindex', + 'type': 'u64'}, + {'name': 'names', + 'type': 'string'}]}, 'transactions': { 'indices': [['hash']], 'columns': [{'name': 'hash', From feab2fc82c14ec158a87909a2cb8df8abb0c773d Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 20 Mar 2023 10:51:50 +1030 Subject: [PATCH 13/14] lightningd: add listclosedchannels command. Changelog-Added: JSON-RPC: `listclosedchannels` to show old, dead channels we previously had with peers. Signed-off-by: Rusty Russell --- doc/Makefile | 1 + doc/index.rst | 1 + doc/lightning-listclosedchannels.7.md | 79 ++++++++ doc/schemas/listclosedchannels.request.json | 13 ++ doc/schemas/listclosedchannels.schema.json | 188 ++++++++++++++++++++ lightningd/Makefile | 2 +- lightningd/closed_channel.c | 119 +++++++++++++ lightningd/closed_channel.h | 4 + tests/test_closing.py | 40 ++++- 9 files changed, 444 insertions(+), 3 deletions(-) create mode 100644 doc/lightning-listclosedchannels.7.md create mode 100644 doc/schemas/listclosedchannels.request.json create mode 100644 doc/schemas/listclosedchannels.schema.json create mode 100644 lightningd/closed_channel.c diff --git a/doc/Makefile b/doc/Makefile index 66d40dcb43c1..38c786121e66 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -52,6 +52,7 @@ MANPAGES := doc/lightning-cli.1 \ doc/lightning-invoicerequest.7 \ doc/lightning-keysend.7 \ doc/lightning-listchannels.7 \ + doc/lightning-listclosedchannels.7 \ doc/lightning-listdatastore.7 \ doc/lightning-listforwards.7 \ doc/lightning-listfunds.7 \ diff --git a/doc/index.rst b/doc/index.rst index 428ff82730f9..e4684e4ae151 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -78,6 +78,7 @@ Core Lightning Documentation lightning-invoicerequest lightning-keysend lightning-listchannels + lightning-listclosedchannels lightning-listconfigs lightning-listdatastore lightning-listforwards diff --git a/doc/lightning-listclosedchannels.7.md b/doc/lightning-listclosedchannels.7.md new file mode 100644 index 000000000000..4e193428d928 --- /dev/null +++ b/doc/lightning-listclosedchannels.7.md @@ -0,0 +1,79 @@ +lightning-listclosedchannels -- Get data on our closed historical channels +========================================================================== + +SYNOPSIS +-------- + +**listclosedchannels** \[*id*\] + +DESCRIPTION +----------- + +The **listclosedchannels** RPC command returns data on channels which +are otherwise forgotten (more than 100 blocks after they're completely +resolved onchain). + +If no *id* is supplied, then channel data on all historical channels are given. + +Supplying *id* will filter the results to only match channels to that peer. Note that prior to v23.05, old peers were forgotten. + +RETURN VALUE +------------ + +[comment]: # (GENERATE-FROM-SCHEMA-START) +On success, an object containing **closedchannels** is returned. It is an array of objects, where each object contains: + +- **channel\_id** (hash): The full channel\_id (funding txid Xored with output number) +- **opener** (string): Who initiated the channel (one of "local", "remote") +- **private** (boolean): if False, we will not announce this channel +- **total\_local\_commitments** (u64): Number of commitment transaction we made +- **total\_remote\_commitments** (u64): Number of commitment transaction they made +- **total\_htlcs\_sent** (u64): Number of HTLCs we ever sent +- **funding\_txid** (txid): ID of the funding transaction +- **funding\_outnum** (u32): The 0-based output number of the funding transaction which opens the channel +- **leased** (boolean): Whether this channel was leased from `opener` +- **total\_msat** (msat): total amount in the channel +- **final\_to\_us\_msat** (msat): Our balance in final commitment transaction +- **min\_to\_us\_msat** (msat): Least amount owed to us ever. If the peer were to succesfully steal from us, this is the amount we would still retain. +- **max\_to\_us\_msat** (msat): Most amount owed to us ever. If we were to successfully steal from the peer, this is the amount we could potentially get. +- **close\_cause** (string): What caused the channel to close (one of "unknown", "local", "user", "remote", "protocol", "onchain") +- **peer\_id** (pubkey, optional): Peer public key (can be missing with pre-v23.05 closes!) +- **short\_channel\_id** (short\_channel\_id, optional): The short\_channel\_id +- **alias** (object, optional): + - **local** (short\_channel\_id, optional): An alias assigned by this node to this channel, used for outgoing payments + - **remote** (short\_channel\_id, optional): An alias assigned by the remote node to this channel, usable in routehints and invoices +- **closer** (string, optional): Who initiated the channel close (only present if closing) (one of "local", "remote") +- **channel\_type** (object, optional): channel\_type as negotiated with peer: + - **bits** (array of u32s): Each bit set in this channel\_type: + - Bit number + - **names** (array of strings): Feature name for each bit set in this channel\_type: + - Name of feature bit (one of "static\_remotekey/even", "anchor\_outputs/even", "anchors\_zero\_fee\_htlc\_tx/even", "scid\_alias/even", "zeroconf/even") +- **funding\_fee\_paid\_msat** (msat, optional): How much we paid to lease the channel (iff `leased` is true and `opener` is local) +- **funding\_fee\_rcvd\_msat** (msat, optional): How much they paid to lease the channel (iff `leased` is true and `opener` is remote) +- **funding\_pushed\_msat** (msat, optional): How much `opener` pushed immediate (if non-zero) +- **last\_commitment\_txid** (hash, optional): The final commitment tx's txid (or mutual close, if we accepted it). Not present for some very old, small channels pre-0.7.0. +- **last\_commitment\_fee\_msat** (msat, optional): The fee on `last_commitment_txid` + +[comment]: # (GENERATE-FROM-SCHEMA-END) + +On error the returned object will contain `code` and `message` properties, +with `code` being one of the following: + +- -32602: If the given parameters are wrong. + +AUTHOR +------ + +Rusty Russell <>. + +SEE ALSO +-------- + +lightning-listpeerchannels(7) + +RESOURCES +--------- + +Main web site: Lightning + +[comment]: # ( SHA256STAMP:0c368cb41f46a2124e9b3f0b760494d1f4b9c3b248267f56b887fbf96f26e176) diff --git a/doc/schemas/listclosedchannels.request.json b/doc/schemas/listclosedchannels.request.json new file mode 100644 index 000000000000..2726913be3d1 --- /dev/null +++ b/doc/schemas/listclosedchannels.request.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": [], + "additionalProperties": false, + "added": "v23.05", + "properties": { + "id": { + "type": "pubkey", + "description": "If supplied, limits the channels to just the peer with the given ID, if it exists." + } + } +} diff --git a/doc/schemas/listclosedchannels.schema.json b/doc/schemas/listclosedchannels.schema.json new file mode 100644 index 000000000000..7ee9ae5c4ab8 --- /dev/null +++ b/doc/schemas/listclosedchannels.schema.json @@ -0,0 +1,188 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": false, + "added": "v23.05", + "required": [ + "closedchannels" + ], + "properties": { + "closedchannels": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true, + "required": [ + "channel_id", + "opener", + "private", + "total_msat", + "total_local_commitments", + "total_remote_commitments", + "total_htlcs_sent", + "funding_txid", + "funding_outnum", + "leased", + "final_to_us_msat", + "min_to_us_msat", + "max_to_us_msat", + "close_cause" + ], + "properties": { + "peer_id": { + "type": "pubkey", + "description": "Peer public key (can be missing with pre-v23.05 closes!)" + }, + "channel_id": { + "type": "hash", + "description": "The full channel_id (funding txid Xored with output number)" + }, + "short_channel_id": { + "type": "short_channel_id", + "description": "The short_channel_id" + }, + "alias": { + "type": "object", + "required": [], + "properties": { + "local": { + "type": "short_channel_id", + "description": "An alias assigned by this node to this channel, used for outgoing payments" + }, + "remote": { + "type": "short_channel_id", + "description": "An alias assigned by the remote node to this channel, usable in routehints and invoices" + } + } + }, + "opener": { + "type": "string", + "enum": [ + "local", + "remote" + ], + "description": "Who initiated the channel" + }, + "closer": { + "type": "string", + "enum": [ + "local", + "remote" + ], + "description": "Who initiated the channel close (only present if closing)" + }, + "private": { + "type": "boolean", + "description": "if False, we will not announce this channel" + }, + "channel_type": { + "type": "object", + "description": "channel_type as negotiated with peer", + "additionalProperties": false, + "required": [ + "bits", + "names" + ], + "properties": { + "bits": { + "type": "array", + "description": "Each bit set in this channel_type", + "items": { + "type": "u32", + "description": "Bit number" + } + }, + "names": { + "type": "array", + "description": "Feature name for each bit set in this channel_type", + "items": { + "type": "string", + "enum": [ + "static_remotekey/even", + "anchor_outputs/even", + "anchors_zero_fee_htlc_tx/even", + "scid_alias/even", + "zeroconf/even" + ], + "description": "Name of feature bit" + } + } + } + }, + "total_local_commitments": { + "type": "u64", + "description": "Number of commitment transaction we made" + }, + "total_remote_commitments": { + "type": "u64", + "description": "Number of commitment transaction they made" + }, + "total_htlcs_sent": { + "type": "u64", + "description": "Number of HTLCs we ever sent" + }, + "funding_txid": { + "type": "txid", + "description": "ID of the funding transaction" + }, + "funding_outnum": { + "type": "u32", + "description": "The 0-based output number of the funding transaction which opens the channel" + }, + "leased": { + "type": "boolean", + "description": "Whether this channel was leased from `opener`" + }, + "funding_fee_paid_msat": { + "type": "msat", + "description": "How much we paid to lease the channel (iff `leased` is true and `opener` is local)" + }, + "funding_fee_rcvd_msat": { + "type": "msat", + "description": "How much they paid to lease the channel (iff `leased` is true and `opener` is remote)" + }, + "funding_pushed_msat": { + "type": "msat", + "description": "How much `opener` pushed immediate (if non-zero)" + }, + "total_msat": { + "type": "msat", + "description": "total amount in the channel" + }, + "final_to_us_msat": { + "type": "msat", + "description": "Our balance in final commitment transaction" + }, + "min_to_us_msat": { + "type": "msat", + "description": "Least amount owed to us ever. If the peer were to succesfully steal from us, this is the amount we would still retain." + }, + "max_to_us_msat": { + "type": "msat", + "description": "Most amount owed to us ever. If we were to successfully steal from the peer, this is the amount we could potentially get." + }, + "last_commitment_txid": { + "type": "hash", + "description": "The final commitment tx's txid (or mutual close, if we accepted it). Not present for some very old, small channels pre-0.7.0." + }, + "last_commitment_fee_msat": { + "type": "msat", + "description": "The fee on `last_commitment_txid`" + }, + "close_cause": { + "type": "string", + "enum": [ + "unknown", + "local", + "user", + "remote", + "protocol", + "onchain" + ], + "description": "What caused the channel to close" + } + } + } + } + } +} diff --git a/lightningd/Makefile b/lightningd/Makefile index f277ec3b2438..6cc220dcf305 100644 --- a/lightningd/Makefile +++ b/lightningd/Makefile @@ -8,6 +8,7 @@ LIGHTNINGD_SRC := \ lightningd/closing_control.c \ lightningd/coin_mvts.c \ lightningd/dual_open_control.c \ + lightningd/closed_channel.c \ lightningd/connect_control.c \ lightningd/onion_message.c \ lightningd/feerate.c \ @@ -48,7 +49,6 @@ include wallet/Makefile LIGHTNINGD_HDRS := \ $(LIGHTNINGD_SRC:.c=.h) \ - lightningd/closed_channel.h \ lightningd/channel_state.h \ lightningd/channel_state_names_gen.h \ $(WALLET_HDRS) diff --git a/lightningd/closed_channel.c b/lightningd/closed_channel.c new file mode 100644 index 000000000000..89998165d39e --- /dev/null +++ b/lightningd/closed_channel.c @@ -0,0 +1,119 @@ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void json_add_closed_channel(struct json_stream *response, + const char *fieldname, + const struct closed_channel *channel) +{ + json_object_start(response, fieldname); + if (channel->peer_id) + json_add_node_id(response, "peer_id", channel->peer_id); + json_add_channel_id(response, "channel_id", &channel->cid); + if (channel->scid) + json_add_short_channel_id(response, "short_channel_id", + channel->scid); + if (channel->alias[LOCAL] || channel->alias[REMOTE]) { + json_object_start(response, "alias"); + if (channel->alias[LOCAL]) + json_add_short_channel_id(response, "local", + channel->alias[LOCAL]); + if (channel->alias[REMOTE]) + json_add_short_channel_id(response, "remote", + channel->alias[REMOTE]); + json_object_end(response); + } + json_add_string(response, "opener", + channel->opener == LOCAL ? "local" : "remote"); + if (channel->closer != NUM_SIDES) + json_add_string(response, "closer", channel->closer == LOCAL ? + "local" : "remote"); + + json_add_bool(response, "private", + !(channel->channel_flags & CHANNEL_FLAGS_ANNOUNCE_CHANNEL)); + + json_add_channel_type(response, "channel_type", channel->type); + json_add_u64(response, "total_local_commitments", + channel->next_index[LOCAL] - 1); + json_add_u64(response, "total_remote_commitments", + channel->next_index[REMOTE] - 1); + json_add_u64(response, "total_htlcs_sent", channel->next_htlc_id); + json_add_txid(response, "funding_txid", &channel->funding.txid); + json_add_num(response, "funding_outnum", channel->funding.n); + json_add_bool(response, "leased", channel->leased); + if (channel->leased) { + if (channel->opener == LOCAL) + json_add_amount_msat(response, "funding_fee_paid_msat", + channel->push); + else + json_add_amount_msat(response, "funding_fee_rcvd_msat", + channel->push); + } else if (!amount_msat_eq(channel->push, AMOUNT_MSAT(0))) + json_add_amount_msat(response, "funding_pushed_msat", + channel->push); + + json_add_amount_sat_msat(response, "total_msat", channel->funding_sats); + json_add_amount_msat(response, "final_to_us_msat", channel->our_msat); + json_add_amount_msat(response, "min_to_us_msat", + channel->msat_to_us_min); + json_add_amount_msat(response, "max_to_us_msat", + channel->msat_to_us_max); + if (channel->last_tx && !invalid_last_tx(channel->last_tx)) { + struct bitcoin_txid txid; + bitcoin_txid(channel->last_tx, &txid); + + json_add_txid(response, "last_commitment_txid", &txid); + json_add_amount_sat_msat(response, "last_commitment_fee_msat", + bitcoin_tx_compute_fee(channel->last_tx)); + } + json_add_string(response, "close_cause", + channel_change_state_reason_str(channel->state_change_cause)); + json_object_end(response); +} + +static struct command_result *json_listclosedchannels(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) +{ + struct node_id *peer_id; + struct json_stream *response; + struct closed_channel **chans; + + if (!param(cmd, buffer, params, + p_opt("id", param_node_id, &peer_id), + NULL)) + return command_param_failed(); + + response = json_stream_success(cmd); + json_array_start(response, "closedchannels"); + + chans = wallet_load_closed_channels(cmd, cmd->ld->wallet); + for (size_t i = 0; i < tal_count(chans); i++) { + if (peer_id) { + if (!chans[i]->peer_id) + continue; + if (!node_id_eq(chans[i]->peer_id, peer_id)) + continue; + } + json_add_closed_channel(response, NULL, chans[i]); + } + json_array_end(response); + + return command_success(cmd, response); +} + +static const struct json_command listclosedchannels_command = { + "listclosedchannels", + "network", + json_listclosedchannels, + "Show historical (dead) channels." +}; +AUTODATA(json_command, &listclosedchannels_command); diff --git a/lightningd/closed_channel.h b/lightningd/closed_channel.h index d507f5b84b3e..44a2269fe184 100644 --- a/lightningd/closed_channel.h +++ b/lightningd/closed_channel.h @@ -2,6 +2,10 @@ #ifndef LIGHTNING_LIGHTNINGD_CLOSED_CHANNEL_H #define LIGHTNING_LIGHTNINGD_CLOSED_CHANNEL_H #include "config.h" +#include +#include +#include +#include struct closed_channel { /* This is often deleted on older nodes! */ diff --git a/tests/test_closing.py b/tests/test_closing.py index 462d860857f9..4d5ad0eaadef 100644 --- a/tests/test_closing.py +++ b/tests/test_closing.py @@ -95,14 +95,50 @@ def test_closing_simple(node_factory, bitcoind, chainparams): 'ONCHAIN:All outputs resolved: waiting 90 more blocks before forgetting channel' ]) + # Capture both side's image of channel before it's dead. + l1channel = only_one(l1.rpc.listpeerchannels(l2.info['id'])['channels']) + l2channel = only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels']) + # Make sure both have forgotten about it bitcoind.generate_block(90) - wait_for(lambda: len(l1.rpc.listchannels()['channels']) == 0) - wait_for(lambda: len(l2.rpc.listchannels()['channels']) == 0) + wait_for(lambda: len(l1.rpc.listpeerchannels()['channels']) == 0) + wait_for(lambda: len(l2.rpc.listpeerchannels()['channels']) == 0) # The entry in the channels table should still be there assert l1.db_query("SELECT count(*) as c FROM channels;")[0]['c'] == 1 assert l2.db_query("SELECT count(*) as c FROM channels;")[0]['c'] == 1 + assert l1.db_query("SELECT count(*) as p FROM peers;")[0]['p'] == 1 + assert l2.db_query("SELECT count(*) as p FROM peers;")[0]['p'] == 1 + + # Test listclosedchannels is correct. + l1closedchannel = only_one(l1.rpc.listclosedchannels()['closedchannels']) + l2closedchannel = only_one(l2.rpc.listclosedchannels(l1.info['id'])['closedchannels']) + + # These fields do not appear in listpeerchannels! + l1_only_closed = {'total_local_commitments': 2, + 'total_remote_commitments': 2, + 'total_htlcs_sent': 1, + 'leased': False, + 'close_cause': 'user'} + l2_only_closed = {'total_local_commitments': 2, + 'total_remote_commitments': 2, + 'total_htlcs_sent': 0, + 'leased': False, + 'close_cause': 'remote'} + + # These fields have different names + renamed = {'last_commitment_txid': 'scratch_txid', + 'last_commitment_fee_msat': 'last_tx_fee_msat', + 'final_to_us_msat': 'to_us_msat'} + + for chan, closedchan, onlyclosed in (l1channel, l1closedchannel, l1_only_closed), (l2channel, l2closedchannel, l2_only_closed): + for k, v in closedchan.items(): + if k in renamed: + assert chan[renamed[k]] == v + elif k in onlyclosed: + assert closedchan[k] == onlyclosed[k] + else: + assert chan[k] == v assert account_balance(l1, channel_id) == 0 assert account_balance(l2, channel_id) == 0 From 824b70ef3d85f044712269f493e3accd25725c62 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 20 Mar 2023 10:52:24 +1030 Subject: [PATCH 14/14] plugins/sql: add listclosedchannels Signed-off-by: Rusty Russell Changelog-Added: JSON-RPC: `sql` now includes `listclosedchannels`. --- doc/lightning-sql.7.md | 36 ++++++++++++++++++- plugins/Makefile | 2 +- tests/test_plugin.py | 80 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 113 insertions(+), 5 deletions(-) diff --git a/doc/lightning-sql.7.md b/doc/lightning-sql.7.md index 30cc14fde917..632ac8b53628 100644 --- a/doc/lightning-sql.7.md +++ b/doc/lightning-sql.7.md @@ -136,6 +136,40 @@ The following tables are currently supported: - `htlc_maximum_msat` (type `msat`, sqltype `INTEGER`) - `features` (type `hex`, sqltype `BLOB`) +- `closedchannels` (see lightning-listclosedchannels(7)) + - `peer_id` (type `pubkey`, sqltype `BLOB`) + - `channel_id` (type `hash`, sqltype `BLOB`) + - `short_channel_id` (type `short_channel_id`, sqltype `TEXT`) + - `alias_local` (type `short_channel_id`, sqltype `TEXT`, from JSON object `alias`) + - `alias_remote` (type `short_channel_id`, sqltype `TEXT`, from JSON object `alias`) + - `opener` (type `string`, sqltype `TEXT`) + - `closer` (type `string`, sqltype `TEXT`) + - `private` (type `boolean`, sqltype `INTEGER`) + - related table `closedchannels_channel_type_bits`, from JSON object `channel_type` + - `row` (reference to `closedchannels_channel_type.rowid`, sqltype `INTEGER`) + - `arrindex` (index within array, sqltype `INTEGER`) + - `bits` (type `u32`, sqltype `INTEGER`) + - related table `closedchannels_channel_type_names`, from JSON object `channel_type` + - `row` (reference to `closedchannels_channel_type.rowid`, sqltype `INTEGER`) + - `arrindex` (index within array, sqltype `INTEGER`) + - `names` (type `string`, sqltype `TEXT`) + - `total_local_commitments` (type `u64`, sqltype `INTEGER`) + - `total_remote_commitments` (type `u64`, sqltype `INTEGER`) + - `total_htlcs_sent` (type `u64`, sqltype `INTEGER`) + - `funding_txid` (type `txid`, sqltype `BLOB`) + - `funding_outnum` (type `u32`, sqltype `INTEGER`) + - `leased` (type `boolean`, sqltype `INTEGER`) + - `funding_fee_paid_msat` (type `msat`, sqltype `INTEGER`) + - `funding_fee_rcvd_msat` (type `msat`, sqltype `INTEGER`) + - `funding_pushed_msat` (type `msat`, sqltype `INTEGER`) + - `total_msat` (type `msat`, sqltype `INTEGER`) + - `final_to_us_msat` (type `msat`, sqltype `INTEGER`) + - `min_to_us_msat` (type `msat`, sqltype `INTEGER`) + - `max_to_us_msat` (type `msat`, sqltype `INTEGER`) + - `last_commitment_txid` (type `hash`, sqltype `BLOB`) + - `last_commitment_fee_msat` (type `msat`, sqltype `INTEGER`) + - `close_cause` (type `string`, sqltype `TEXT`) + - `forwards` indexed by `in_channel and in_htlc_id` (see lightning-listforwards(7)) - `in_channel` (type `short_channel_id`, sqltype `TEXT`) - `in_htlc_id` (type `u64`, sqltype `INTEGER`) @@ -480,4 +514,4 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:ccc382f01d39253aff5a6c7ccd74400feb2f900f78f492a4c55b0a80e04fe813) +[comment]: # ( SHA256STAMP:3eb4e024a1e1a4b40460b48b835354514456558797b8f8ce3c76dcbb9ca79dab) diff --git a/plugins/Makefile b/plugins/Makefile index 5047fe341adc..de03189a47b9 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -210,7 +210,7 @@ plugins/fetchinvoice: $(PLUGIN_FETCHINVOICE_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_CO plugins/funder: bitcoin/psbt.o common/psbt_open.o $(PLUGIN_FUNDER_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) # This covers all the low-level list RPCs which return simple arrays -SQL_LISTRPCS := listchannels listforwards listhtlcs listinvoices listnodes listoffers listpeers listpeerchannels listtransactions listsendpays bkpr-listaccountevents bkpr-listincome +SQL_LISTRPCS := listchannels listforwards listhtlcs listinvoices listnodes listoffers listpeers listpeerchannels listclosedchannels listtransactions listsendpays bkpr-listaccountevents bkpr-listincome SQL_LISTRPCS_SCHEMAS := $(foreach l,$(SQL_LISTRPCS),doc/schemas/$l.schema.json) # We squeeze: # descriptions (we don't need) diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 397b3a6c3f09..341666b1a4d9 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -3308,9 +3308,6 @@ def test_sql(node_factory, bitcoind): # Test that we correctly clean up subtables! assert len(l2.rpc.sql("SELECT * from peerchannels_features")['rows']) == len(l2.rpc.sql("SELECT * from peerchannels_features")['rows']) - # This should create a forward through l2 - l1.rpc.pay(l3.rpc.invoice(amount_msat=12300, label='inv1', description='description')['bolt11']) - expected_schemas = { 'channels': { 'indices': [['short_channel_id']], @@ -3346,6 +3343,69 @@ def test_sql(node_factory, bitcoind): 'type': 'msat'}, {'name': 'features', 'type': 'hex'}]}, + 'closedchannels': { + 'columns': [{'name': 'peer_id', + 'type': 'pubkey'}, + {'name': 'channel_id', + 'type': 'hash'}, + {'name': 'short_channel_id', + 'type': 'short_channel_id'}, + {'name': 'alias_local', + 'type': 'short_channel_id'}, + {'name': 'alias_remote', + 'type': 'short_channel_id'}, + {'name': 'opener', + 'type': 'string'}, + {'name': 'closer', + 'type': 'string'}, + {'name': 'private', + 'type': 'boolean'}, + {'name': 'total_local_commitments', + 'type': 'u64'}, + {'name': 'total_remote_commitments', + 'type': 'u64'}, + {'name': 'total_htlcs_sent', + 'type': 'u64'}, + {'name': 'funding_txid', + 'type': 'txid'}, + {'name': 'funding_outnum', + 'type': 'u32'}, + {'name': 'leased', + 'type': 'boolean'}, + {'name': 'funding_fee_paid_msat', + 'type': 'msat'}, + {'name': 'funding_fee_rcvd_msat', + 'type': 'msat'}, + {'name': 'funding_pushed_msat', + 'type': 'msat'}, + {'name': 'total_msat', + 'type': 'msat'}, + {'name': 'final_to_us_msat', + 'type': 'msat'}, + {'name': 'min_to_us_msat', + 'type': 'msat'}, + {'name': 'max_to_us_msat', + 'type': 'msat'}, + {'name': 'last_commitment_txid', + 'type': 'txid'}, + {'name': 'last_commitment_fee_msat', + 'type': 'msat'}, + {'name': 'close_cause', + 'type': 'string'}]}, + 'closedchannels_channel_type_bits': { + 'columns': [{'name': 'row', + 'type': 'u64'}, + {'name': 'arrindex', + 'type': 'u64'}, + {'name': 'bits', + 'type': 'u64'}]}, + 'closedchannels_channel_type_names': { + 'columns': [{'name': 'row', + 'type': 'u64'}, + {'name': 'arrindex', + 'type': 'u64'}, + {'name': 'names', + 'type': 'string'}]}, 'nodes': { 'indices': [['nodeid']], 'columns': [{'name': 'nodeid', @@ -3842,6 +3902,20 @@ def test_sql(node_factory, bitcoind): == sorted(expected_schemas.keys())) assert len(l1.rpc.listsqlschemas()['schemas']) == len(expected_schemas) + # We need one closed channel (but open a new one) + l2.rpc.close(l1.info['id']) + bitcoind.generate_block(1, wait_for_mempool=1) + scid, _ = l1.fundchannel(l2) + # Completely forget old channel + bitcoind.generate_block(99) + wait_for(lambda: len(l2.rpc.listpeerchannels()['channels']) == 2) + + # Make sure l3 sees new channel + wait_for(lambda: len(l3.rpc.listchannels(scid)['channels']) == 2) + + # This should create a forward through l2 + l1.rpc.pay(l3.rpc.invoice(amount_msat=12300, label='inv1', description='description')['bolt11']) + # Very rough checks of other list commands (make sure l2 has one of each) l2.rpc.offer(1, 'desc') l2.rpc.invoice(1, 'label', 'desc')