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(); +} diff --git a/db/bindings.c b/db/bindings.c index fc95ca52f031..22643e84d745 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,12 +361,23 @@ 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); } +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 cc7707cf5c4f..83679fde8a91 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, @@ -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, 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/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..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`) @@ -208,6 +242,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 +514,4 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:d25af4b0655ebd31db68932c5ea6b532bd134477e42df5d0c7428e4a03fd0335) +[comment]: # ( SHA256STAMP:3eb4e024a1e1a4b40460b48b835354514456558797b8f8ce3c76dcbb9ca79dab) 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/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/Makefile b/lightningd/Makefile index 11dbaaf7a259..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 \ 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/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 new file mode 100644 index 000000000000..44a2269fe184 --- /dev/null +++ b/lightningd/closed_channel.h @@ -0,0 +1,32 @@ +/* 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" +#include +#include +#include +#include + +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/lightningd/peer_control.c b/lightningd/peer_control.c index b9457e72f8de..aaca93ec47bd 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; } @@ -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 58a8715bb478..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(); } @@ -817,6 +820,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 +918,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/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/plugins/sql.c b/plugins/sql.c index e4e61db39c93..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 */ @@ -1114,6 +1131,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) { @@ -1124,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! */ @@ -1134,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 = ","; } @@ -1146,19 +1194,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); @@ -1175,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]; 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 diff --git a/tests/test_plugin.py b/tests/test_plugin.py index dd12a6585eb2..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', @@ -3700,6 +3760,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', @@ -3828,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') diff --git a/wallet/db.c b/wallet/db.c index ea2e2d0672db..538988b1c242 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," @@ -1126,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, @@ -1517,7 +1515,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); @@ -1573,7 +1571,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/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 03029b4c1c2c..085a687309de 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -63,6 +64,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; @@ -209,13 +237,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 +1342,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_scid(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]); - } 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]); - } 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 +1380,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"); @@ -1535,7 +1542,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"), @@ -1553,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; @@ -1930,7 +2031,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); @@ -1974,7 +2075,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); @@ -1997,12 +2098,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); @@ -2119,7 +2220,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)); @@ -2150,7 +2251,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); } @@ -2284,17 +2385,16 @@ 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)); } -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 +2403,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=?")); @@ -2626,12 +2729,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)); @@ -2643,18 +2741,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")) { @@ -2707,12 +2799,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)); @@ -3229,24 +3316,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. */ @@ -3302,11 +3380,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); @@ -3471,21 +3546,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_scid(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; @@ -3541,7 +3608,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); @@ -4379,7 +4446,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); @@ -4439,10 +4506,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); @@ -4571,7 +4638,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); @@ -4581,7 +4648,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); @@ -4615,7 +4682,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! */ @@ -4628,7 +4695,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; @@ -4685,7 +4752,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 { @@ -4695,7 +4762,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); @@ -4718,7 +4785,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 { @@ -4727,7 +4794,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); @@ -4822,7 +4889,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 { @@ -5453,9 +5520,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"); } } diff --git a/wallet/wallet.h b/wallet/wallet.h index 4661e3e3918b..d6427e1a67d4 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 @@ -647,6 +647,16 @@ void wallet_peer_delete(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. *