From e84a1234ab544dc0f0b746e9a5a417fac8985b71 Mon Sep 17 00:00:00 2001 From: ShahanaFarooqui Date: Fri, 8 Nov 2024 22:34:05 -0800 Subject: [PATCH 01/10] gitignore: Ignore chaneld_fakenet --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b7f2de9530f4..dcc908b1d194 100644 --- a/.gitignore +++ b/.gitignore @@ -97,3 +97,4 @@ autogenerate-examples-status.log # Ignore nix outputs result result-[0-9]* +tests/plugins/channeld_fakenet From fea83754d4d573b0a8dd0448f629c415328dae1e Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sun, 10 Nov 2024 13:13:49 +1030 Subject: [PATCH 02/10] onchaind: don't get final_key_idx. onchaind used to make its own txs, but doesn't any more. This parameter is useless. Signed-off-by: Rusty Russell --- lightningd/onchain_control.c | 1 - onchaind/onchaind.c | 4 ---- onchaind/onchaind_wire.csv | 1 - onchaind/test/run-grind_feerate.c | 2 +- 4 files changed, 1 insertion(+), 7 deletions(-) diff --git a/lightningd/onchain_control.c b/lightningd/onchain_control.c index 6c64ad496f3e..826299c79adc 100644 --- a/lightningd/onchain_control.c +++ b/lightningd/onchain_control.c @@ -1798,7 +1798,6 @@ enum watch_result onchaind_funding_spent(struct channel *channel, &our_last_txid, channel->shutdown_scriptpubkey[LOCAL], channel->shutdown_scriptpubkey[REMOTE], - channel->final_key_idx, channel->opener, &channel->local_basepoints, &channel->channel_info.theirbase, diff --git a/onchaind/onchaind.c b/onchaind/onchaind.c index 3d573baffe12..07d3aad67a3f 100644 --- a/onchaind/onchaind.c +++ b/onchaind/onchaind.c @@ -46,9 +46,6 @@ static struct amount_sat dust_limit; /* The CSV delays for each side. */ static u32 to_self_delay[NUM_SIDES]; -/* Where we send money to (our wallet) */ -static u32 our_wallet_index; - /* Their revocation secret (only if they cheated). */ static const struct secret *remote_per_commitment_secret; @@ -3433,7 +3430,6 @@ int main(int argc, char *argv[]) &our_broadcast_txid, &scriptpubkey[LOCAL], &scriptpubkey[REMOTE], - &our_wallet_index, &opener, &basepoints[LOCAL], &basepoints[REMOTE], diff --git a/onchaind/onchaind_wire.csv b/onchaind/onchaind_wire.csv index bc7b3da91557..7ea350f20b62 100644 --- a/onchaind/onchaind_wire.csv +++ b/onchaind/onchaind_wire.csv @@ -27,7 +27,6 @@ msgdata,onchaind_init,local_scriptpubkey_len,u16, msgdata,onchaind_init,local_scriptpubkey,u8,local_scriptpubkey_len msgdata,onchaind_init,remote_scriptpubkey_len,u16, msgdata,onchaind_init,remote_scriptpubkey,u8,remote_scriptpubkey_len -msgdata,onchaind_init,ourwallet_index,u32, # We need these two for commit number obscurer msgdata,onchaind_init,opener,enum side, msgdata,onchaind_init,local_basepoints,basepoints, diff --git a/onchaind/test/run-grind_feerate.c b/onchaind/test/run-grind_feerate.c index 91295e425cd5..2beb01ddbb57 100644 --- a/onchaind/test/run-grind_feerate.c +++ b/onchaind/test/run-grind_feerate.c @@ -56,7 +56,7 @@ bool fromwire_onchaind_dev_memleak(const void *p UNNEEDED) bool fromwire_onchaind_htlcs(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct htlc_stub **htlc UNNEEDED, bool **tell_if_missing UNNEEDED, bool **tell_immediately UNNEEDED) { fprintf(stderr, "fromwire_onchaind_htlcs called!\n"); abort(); } /* Generated stub for fromwire_onchaind_init */ -bool fromwire_onchaind_init(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct shachain *shachain UNNEEDED, const struct chainparams **chainparams UNNEEDED, struct amount_sat *funding_amount_satoshi UNNEEDED, struct amount_msat *our_msat UNNEEDED, struct pubkey *old_remote_per_commitment_point UNNEEDED, struct pubkey *remote_per_commitment_point UNNEEDED, u32 *local_to_self_delay UNNEEDED, u32 *remote_to_self_delay UNNEEDED, struct amount_sat *local_dust_limit_satoshi UNNEEDED, struct bitcoin_txid *our_broadcast_txid UNNEEDED, u8 **local_scriptpubkey UNNEEDED, u8 **remote_scriptpubkey UNNEEDED, u32 *ourwallet_index UNNEEDED, enum side *opener UNNEEDED, struct basepoints *local_basepoints UNNEEDED, struct basepoints *remote_basepoints UNNEEDED, struct tx_parts **tx_parts UNNEEDED, u32 *locktime UNNEEDED, u32 *tx_blockheight UNNEEDED, u32 *reasonable_depth UNNEEDED, struct bitcoin_signature **htlc_signature UNNEEDED, u32 *min_possible_feerate UNNEEDED, u32 *max_possible_feerate UNNEEDED, struct pubkey *local_funding_pubkey UNNEEDED, struct pubkey *remote_funding_pubkey UNNEEDED, u64 *local_static_remotekey_start UNNEEDED, u64 *remote_static_remotekey_start UNNEEDED, bool *option_anchor_outputs UNNEEDED, bool *option_anchors_zero_fee_htlc_tx UNNEEDED, u32 *min_relay_feerate UNNEEDED) +bool fromwire_onchaind_init(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct shachain *shachain UNNEEDED, const struct chainparams **chainparams UNNEEDED, struct amount_sat *funding_amount_satoshi UNNEEDED, struct amount_msat *our_msat UNNEEDED, struct pubkey *old_remote_per_commitment_point UNNEEDED, struct pubkey *remote_per_commitment_point UNNEEDED, u32 *local_to_self_delay UNNEEDED, u32 *remote_to_self_delay UNNEEDED, struct amount_sat *local_dust_limit_satoshi UNNEEDED, struct bitcoin_txid *our_broadcast_txid UNNEEDED, u8 **local_scriptpubkey UNNEEDED, u8 **remote_scriptpubkey UNNEEDED, enum side *opener UNNEEDED, struct basepoints *local_basepoints UNNEEDED, struct basepoints *remote_basepoints UNNEEDED, struct tx_parts **tx_parts UNNEEDED, u32 *locktime UNNEEDED, u32 *tx_blockheight UNNEEDED, u32 *reasonable_depth UNNEEDED, struct bitcoin_signature **htlc_signature UNNEEDED, u32 *min_possible_feerate UNNEEDED, u32 *max_possible_feerate UNNEEDED, struct pubkey *local_funding_pubkey UNNEEDED, struct pubkey *remote_funding_pubkey UNNEEDED, u64 *local_static_remotekey_start UNNEEDED, u64 *remote_static_remotekey_start UNNEEDED, bool *option_anchor_outputs UNNEEDED, bool *option_anchors_zero_fee_htlc_tx UNNEEDED, u32 *min_relay_feerate UNNEEDED) { fprintf(stderr, "fromwire_onchaind_init called!\n"); abort(); } /* Generated stub for fromwire_onchaind_known_preimage */ bool fromwire_onchaind_known_preimage(const void *p UNNEEDED, struct preimage *preimage UNNEEDED) From b81fca40db7790cc2b92f9d4338c03755b179130 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sun, 10 Nov 2024 13:14:27 +1030 Subject: [PATCH 03/10] lightningd: use P2TR for anchor spends. Signed-off-by: Rusty Russell --- lightningd/anchorspend.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lightningd/anchorspend.c b/lightningd/anchorspend.c index 1cc7703a15d7..b54f4b59c3ce 100644 --- a/lightningd/anchorspend.c +++ b/lightningd/anchorspend.c @@ -251,7 +251,7 @@ static struct wally_psbt *anchor_psbt(const tal_t *ctx, bip32_pubkey(ld, &final_key, channel->final_key_idx); psbt_append_output(psbt, - scriptpubkey_p2wpkh(tmpctx, &final_key), + scriptpubkey_p2tr(tmpctx, &final_key), change); return psbt; } From 2b2e60c8988096e56157049f8b928fdcfe4c4848 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sun, 10 Nov 2024 13:34:34 +1030 Subject: [PATCH 04/10] lightningd: use P2TR for onchain tx spends. Both for HTLC txs and the to-self outputs. Signed-off-by: Rusty Russell Changelog-Changed: Wallet: Taproot addresses are used for unilateral-close change addresses. --- lightningd/onchain_control.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lightningd/onchain_control.c b/lightningd/onchain_control.c index 826299c79adc..1142e6eb891a 100644 --- a/lightningd/onchain_control.c +++ b/lightningd/onchain_control.c @@ -882,10 +882,15 @@ static struct bitcoin_tx *onchaind_tx_unsigned(const tal_t *ctx, bitcoin_tx_add_input(tx, &info->out, info->to_self_delay, NULL, info->out_sats, NULL, info->wscript); - /* FIXME should this be p2tr now? */ - bitcoin_tx_add_output( - tx, scriptpubkey_p2wpkh(tmpctx, &final_key), NULL, info->out_sats); - psbt_add_keypath_to_last_output(tx, channel->final_key_idx, &final_wallet_ext_key, false /* is_taproot */); + if (chainparams->is_elements) { + bitcoin_tx_add_output( + tx, scriptpubkey_p2wpkh(tmpctx, &final_key), NULL, info->out_sats); + psbt_add_keypath_to_last_output(tx, channel->final_key_idx, &final_wallet_ext_key, false /* is_taproot */); + } else { + bitcoin_tx_add_output( + tx, scriptpubkey_p2tr(tmpctx, &final_key), NULL, info->out_sats); + psbt_add_keypath_to_last_output(tx, channel->final_key_idx, &final_wallet_ext_key, true /* is_taproot */); + } /* Worst-case sig is 73 bytes */ weight = bitcoin_tx_weight(tx) + 1 + 3 + 73 + 0 + tal_count(info->wscript); @@ -1117,7 +1122,7 @@ static bool consider_onchain_htlc_tx_rebroadcast(struct channel *channel, struct pubkey final_key; bip32_pubkey(ld, &final_key, channel->final_key_idx); psbt_append_output(psbt, - scriptpubkey_p2wpkh(tmpctx, &final_key), + scriptpubkey_p2tr(tmpctx, &final_key), change); } From 7d1d3c36027b11fc08999b5d89dfcea20dbc6ce2 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sun, 10 Nov 2024 13:34:47 +1030 Subject: [PATCH 05/10] wallet: save keytype when issuing new address. Signed-off-by: Rusty Russell --- lightningd/channel_control.c | 6 +++--- lightningd/dual_open_control.c | 10 ++++++++-- lightningd/opening_control.c | 9 ++++++++- wallet/db.c | 4 +++- wallet/reservation.c | 19 ++++++++++++++++-- wallet/wallet.c | 35 +++++++++++++++++++++++++++++++++- wallet/wallet.h | 34 ++++++++++++++++++++++++++++++++- wallet/walletrpc.c | 2 +- wallet/walletrpc.h | 6 ------ 9 files changed, 107 insertions(+), 18 deletions(-) diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c index 8e23a6138abc..e545714a0112 100644 --- a/lightningd/channel_control.c +++ b/lightningd/channel_control.c @@ -1212,9 +1212,9 @@ static void peer_got_shutdown(struct channel *channel, const u8 *msg) u8 *scriptpubkey; struct lightningd *ld = channel->peer->ld; struct bitcoin_outpoint *wrong_funding; - bool anysegwit = feature_negotiated(ld->our_features, - channel->peer->their_features, - OPT_SHUTDOWN_ANYSEGWIT); + bool anysegwit = !chainparams->is_elements && feature_negotiated(ld->our_features, + channel->peer->their_features, + OPT_SHUTDOWN_ANYSEGWIT); bool anchors = feature_negotiated(ld->our_features, channel->peer->their_features, OPT_ANCHOR_OUTPUTS_DEPRECATED) diff --git a/lightningd/dual_open_control.c b/lightningd/dual_open_control.c index 2b504f32f189..129d14209f5e 100644 --- a/lightningd/dual_open_control.c +++ b/lightningd/dual_open_control.c @@ -1383,6 +1383,9 @@ wallet_commit_channel(struct lightningd *ld, { struct amount_msat our_msat, lease_fee_msat; struct channel_inflight *inflight; + bool anysegwit = !chainparams->is_elements && feature_negotiated(channel->peer->ld->our_features, + channel->peer->their_features, + OPT_SHUTDOWN_ANYSEGWIT); bool any_active = peer_any_channel(channel->peer, channel_state_wants_peercomms, NULL); if (!amount_sat_to_msat(&our_msat, our_funding)) { @@ -1395,8 +1398,11 @@ wallet_commit_channel(struct lightningd *ld, return NULL; } - /* Get a key to use for closing outputs from this tx */ - channel->final_key_idx = wallet_get_newindex(ld); + /* Get a key to use for closing outputs from this tx. Prefer + * P2TR, but we'll use BECH32 for older peers (but always P2TR + * for our own onchian spends, if any). */ + + channel->final_key_idx = wallet_get_newindex(ld, anysegwit ? ADDR_P2TR : (ADDR_BECH32|ADDR_P2TR)); if (channel->final_key_idx == -1) { log_broken(channel->log, "Can't get final key index"); return NULL; diff --git a/lightningd/opening_control.c b/lightningd/opening_control.c index d0a2c7e6eed6..50b830cba67f 100644 --- a/lightningd/opening_control.c +++ b/lightningd/opening_control.c @@ -110,6 +110,7 @@ wallet_commit_channel(struct lightningd *ld, struct timeabs timestamp; bool any_active = peer_any_channel(uc->peer, channel_state_wants_peercomms, NULL); struct channel_stats zero_channel_stats; + enum addrtype addrtype; /* We can't have any payments yet */ memset(&zero_channel_stats, 0, sizeof(zero_channel_stats)); @@ -119,8 +120,14 @@ wallet_commit_channel(struct lightningd *ld, */ assert(!(uc->got_offer && uc->fc)); + /* FIXME: P2TR for elements! */ + if (chainparams->is_elements) + addrtype = ADDR_BECH32; + else + addrtype = ADDR_P2TR; + /* Get a key to use for closing outputs from this tx */ - final_key_idx = wallet_get_newindex(ld); + final_key_idx = wallet_get_newindex(ld, addrtype); if (final_key_idx == -1) { log_broken(uc->log, "Can't get final key index"); return NULL; diff --git a/wallet/db.c b/wallet/db.c index 53852e529c56..5b734932e43d 100644 --- a/wallet/db.c +++ b/wallet/db.c @@ -1021,7 +1021,9 @@ static struct migration dbmigrations[] = { {SQL("ALTER TABLE channels ADD remote_htlc_minimum_msat BIGINT DEFAULT NULL;"), NULL}, {SQL("ALTER TABLE channels ADD last_stable_connection BIGINT DEFAULT 0;"), NULL}, {NULL, migrate_initialize_alias_local}, - /* FIXME: Remove now-unused type column from channeltxs */ + {SQL("CREATE TABLE addresses (" + " keyidx BIGINT," + " addrtype INTEGER)"), NULL}, }; /** diff --git a/wallet/reservation.c b/wallet/reservation.c index d1df0dc04d2c..b68155420c87 100644 --- a/wallet/reservation.c +++ b/wallet/reservation.c @@ -366,9 +366,16 @@ static struct command_result *finish_psbt(struct command *cmd, struct pubkey pubkey; s64 keyidx; u8 *b32script; + enum addrtype type; + + /* FIXME: P2TR for elements! */ + if (chainparams->is_elements) + type = ADDR_BECH32; + else + type = ADDR_P2TR; /* Get a change adddress */ - keyidx = wallet_get_newindex(cmd->ld); + keyidx = wallet_get_newindex(cmd->ld, type); if (keyidx < 0) return command_fail(cmd, LIGHTNINGD, "Failed to generate change address." @@ -700,7 +707,15 @@ static struct command_result *json_addpsbtoutput(struct command *cmd, /* Get a change adddress */ if (!b32script) { - keyidx = wallet_get_newindex(cmd->ld); + enum addrtype type; + + /* FIXME: P2TR for elements! */ + if (chainparams->is_elements) + type = ADDR_BECH32; + else + type = ADDR_P2TR; + + keyidx = wallet_get_newindex(cmd->ld, type); if (keyidx < 0) return command_fail(cmd, LIGHTNINGD, "Failed to generate change address." diff --git a/wallet/wallet.c b/wallet/wallet.c index c233424b2562..423440938617 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -836,17 +836,50 @@ bool wallet_can_spend(struct wallet *w, const u8 *script, return false; } -s64 wallet_get_newindex(struct lightningd *ld) +s64 wallet_get_newindex(struct lightningd *ld, enum addrtype addrtype) { + struct db_stmt *stmt; u64 newidx = db_get_intvar(ld->wallet->db, "bip32_max_index", 0) + 1; if (newidx == BIP32_INITIAL_HARDENED_CHILD) return -1; db_set_intvar(ld->wallet->db, "bip32_max_index", newidx); + stmt = db_prepare_v2(ld->wallet->db, + SQL("INSERT INTO addresses (" + " keyidx" + ", addrtype" + ") VALUES (?, ?);")); + db_bind_u64(stmt, newidx); + db_bind_int(stmt, wallet_addrtype_in_db(addrtype)); + db_exec_prepared_v2(take(stmt)); + return newidx; } +enum addrtype wallet_get_addrtype(struct wallet *wallet, u64 idx) +{ + struct db_stmt *stmt; + enum addrtype type; + + stmt = db_prepare_v2(wallet->db, + SQL("SELECT addrtype" + " FROM addresses" + " WHERE keyidx=?")); + db_bind_u64(stmt, idx); + db_query_prepared(stmt); + + /* Unknown means prior to v24.11 */ + if (!db_step(stmt)) { + tal_free(stmt); + return ADDR_P2TR|ADDR_BECH32; + } + + type = wallet_addrtype_in_db(db_col_int(stmt, "addrtype")); + tal_free(stmt); + return type; +} + static void wallet_shachain_init(struct wallet *wallet, struct wallet_shachain *chain) { diff --git a/wallet/wallet.h b/wallet/wallet.h index e556bebd2a89..1c0062fee8ec 100644 --- a/wallet/wallet.h +++ b/wallet/wallet.h @@ -271,6 +271,30 @@ static inline enum channel_state channel_state_in_db(enum channel_state s) fatal("%s: %u is invalid", __func__, s); } +/* /!\ This is a DB ENUM, please do not change the numbering of any + * already defined elements (adding is ok) /!\ */ +enum addrtype { + ADDR_BECH32 = 2, + ADDR_P2TR = 4, + ADDR_ALL = (ADDR_BECH32 + ADDR_P2TR) +}; + +static inline enum addrtype wallet_addrtype_in_db(enum addrtype t) +{ + switch (t) { + case ADDR_BECH32: + BUILD_ASSERT(ADDR_BECH32 == 2); + return t; + case ADDR_P2TR: + BUILD_ASSERT(ADDR_P2TR == 4); + return t; + case ADDR_ALL: + BUILD_ASSERT(ADDR_ALL == 6); + return t; + } + fatal("%s: %u is invalid", __func__, t); +} + /* A database backed shachain struct. The datastructure is * writethrough, reads are performed from an in-memory version, all * writes are passed through to the DB. */ @@ -567,10 +591,18 @@ bool wallet_can_spend(struct wallet *w, /** * wallet_get_newindex - get a new index from the wallet. * @ld: (in) lightning daemon + * @addrtype: (in) addess types we will publish for this * * Returns -1 on error (key exhaustion). */ -s64 wallet_get_newindex(struct lightningd *ld); +s64 wallet_get_newindex(struct lightningd *ld, enum addrtype addrtype); + +/** + * wallet_get_addrtype - get the address types for this key. + * @wallet: (in) wallet + * @keyidx: what address types we've published. + */ +enum addrtype wallet_get_addrtype(struct wallet *w, u64 keyidx); /** * wallet_shachain_add_hash -- wallet wrapper around shachain_add_hash diff --git a/wallet/walletrpc.c b/wallet/walletrpc.c index 08bbff722ef4..78c3e9598cb6 100644 --- a/wallet/walletrpc.c +++ b/wallet/walletrpc.c @@ -119,7 +119,7 @@ bool WARN_UNUSED_RESULT newaddr_inner(struct command *cmd, struct pubkey *pubkey u8 *b32script; u8 *p2tr_script; - keyidx = wallet_get_newindex(cmd->ld); + keyidx = wallet_get_newindex(cmd->ld, addrtype); if (keyidx < 0) { // return command_fail(cmd, LIGHTNINGD, "Keys exhausted "); return false; diff --git a/wallet/walletrpc.h b/wallet/walletrpc.h index 45efff3dcd94..be728cc7b12d 100644 --- a/wallet/walletrpc.h +++ b/wallet/walletrpc.h @@ -2,12 +2,6 @@ #define LIGHTNING_WALLET_WALLETRPC_H #include "config.h" -enum addrtype { - ADDR_BECH32 = 2, - ADDR_P2TR = 4, - ADDR_ALL = (ADDR_BECH32 + ADDR_P2TR) -}; - struct utxo; /* We evaluate reserved timeouts lazily, so use this. */ From af72de6169479248b6621b34a45b89479417fc72 Mon Sep 17 00:00:00 2001 From: ShahanaFarooqui Date: Wed, 13 Nov 2024 17:24:26 -0800 Subject: [PATCH 06/10] wallet: insert addrtype `ADDR_ALL` for issued addresses --- wallet/db.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/wallet/db.c b/wallet/db.c index 5b734932e43d..5b09cd4dfbc5 100644 --- a/wallet/db.c +++ b/wallet/db.c @@ -84,6 +84,8 @@ static void migrate_initialize_forwards_wait_indexes(struct lightningd *ld, struct db *db); static void migrate_initialize_alias_local(struct lightningd *ld, struct db *db); +static void insert_addrtype_to_addresses(struct lightningd *ld, + struct db *db); /* Do not reorder or remove elements from this array, it is used to * migrate existing databases from a previous state, based on the @@ -1024,6 +1026,7 @@ static struct migration dbmigrations[] = { {SQL("CREATE TABLE addresses (" " keyidx BIGINT," " addrtype INTEGER)"), NULL}, + {NULL, insert_addrtype_to_addresses}, }; /** @@ -2000,3 +2003,22 @@ static void migrate_initialize_alias_local(struct lightningd *ld, tal_free(stmt); } } + +/* Insert address type as `ADDR_ALL` for issued addresses */ +static void insert_addrtype_to_addresses(struct lightningd *ld, + struct db *db) +{ + struct db_stmt *stmt; + u64 bip32_max_index = db_get_intvar(db, "bip32_max_index", 0); + for (u64 newidx = 1; newidx <= bip32_max_index; newidx++) { + stmt = db_prepare_v2(db, + SQL("INSERT INTO addresses (" + " keyidx" + ", addrtype" + ") VALUES (?, ?);")); + db_bind_u64(stmt, newidx); + db_bind_int(stmt, wallet_addrtype_in_db(ADDR_ALL)); + db_exec_prepared_v2(stmt); + tal_free(stmt); + } +} From da4b72b35624c6f31a35f47c596924ee300ceb88 Mon Sep 17 00:00:00 2001 From: ShahanaFarooqui Date: Wed, 13 Nov 2024 17:24:50 -0800 Subject: [PATCH 07/10] wallet: list addresses query --- wallet/wallet.c | 22 ++++++++++++++++++++++ wallet/wallet.h | 19 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/wallet/wallet.c b/wallet/wallet.c index 423440938617..21ee7e09c3ec 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -6377,3 +6377,25 @@ struct local_anchor_info *wallet_get_local_anchors(const tal_t *ctx, return anchors; } + +struct issued_address_type *wallet_list_addresses(const tal_t *ctx, struct wallet *wallet, + u64 liststart, const u32 *listlimit) +{ + struct db_stmt *stmt; + struct issued_address_type *addresseslist = tal_arr(ctx, struct issued_address_type, 0); + stmt = db_prepare_v2(wallet->db, SQL("SELECT keyidx, addrtype FROM addresses WHERE keyidx >= ? ORDER BY keyidx LIMIT ?;")); + db_bind_u64(stmt, liststart); + if (listlimit) + db_bind_int(stmt, *listlimit); + else + db_bind_int(stmt, INT_MAX); + db_query_prepared(stmt); + while(db_step(stmt)) { + struct issued_address_type a; + a.keyidx = db_col_u64(stmt, "keyidx"); + a.addrtype = wallet_addrtype_in_db(db_col_int(stmt, "addrtype")); + tal_arr_expand(&addresseslist, a); + } + tal_free(stmt); + return addresseslist; +} diff --git a/wallet/wallet.h b/wallet/wallet.h index 1c0062fee8ec..c6f1bbe98a37 100644 --- a/wallet/wallet.h +++ b/wallet/wallet.h @@ -1798,4 +1798,23 @@ void wallet_remove_local_anchors(struct wallet *w, struct local_anchor_info *wallet_get_local_anchors(const tal_t *ctx, struct wallet *w, u64 channel_id); + +/* Get the addresses addrtype */ +struct issued_address_type { + u64 keyidx; + enum addrtype addrtype; +}; + +/** + * wallet_list_addresses: get the list of addresses with addrtype + * + * @ctx: tal context for returned array + * @wallet: the wallet + * @liststart: first index to return (0 == all). + * @listlimit: limit on number of entries to return (NULL == no limit). + * + * Returns NULL if none, otherwise list of addresses with addrtype. + */ +struct issued_address_type *wallet_list_addresses(const tal_t *ctx, struct wallet *wallet, + u64 liststart, const u32 *listlimit); #endif /* LIGHTNING_WALLET_WALLET_H */ From f2e947532887a20ff65f0891c2bde61d24c8df31 Mon Sep 17 00:00:00 2001 From: ShahanaFarooqui Date: Fri, 8 Nov 2024 22:26:01 -0800 Subject: [PATCH 08/10] rpc: Added listaddresses command Changelog-Added: New rpc `listaddresses` to list issued addresses from the node. --- wallet/walletrpc.c | 87 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/wallet/walletrpc.c b/wallet/walletrpc.c index 78c3e9598cb6..92b5e3dc5ec8 100644 --- a/wallet/walletrpc.c +++ b/wallet/walletrpc.c @@ -177,6 +177,93 @@ static const struct json_command newaddr_command = { }; AUTODATA(json_command, &newaddr_command); +static void json_add_address_details(struct json_stream *response, + const u64 keyidx, + const char *out_p2wpkh, + const char *out_p2tr) +{ + json_object_start(response, NULL); + json_add_u64(response, "keyidx", keyidx); + if (!streq(out_p2wpkh, "")) { + json_add_string(response, "bech32", out_p2wpkh); + } + if (!streq(out_p2tr,"")) { + json_add_string(response, "p2tr", out_p2tr); + } + json_object_end(response); +} + +static struct command_result *json_listaddresses(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) +{ + struct json_stream *response; + struct pubkey pubkey; + const u8 *scriptpubkey; + u64 *liststart; + u32 *listlimit; + char *addr = NULL; + + if (!param(cmd, buffer, params, + p_opt("address", param_bitcoin_address, &scriptpubkey), + p_opt_def("start", param_u64, &liststart, 1), + p_opt("limit", param_u32, &listlimit), + NULL)) + return command_param_failed(); + + addr = encode_scriptpubkey_to_addr(tmpctx, chainparams, scriptpubkey); + + if (*liststart == 0) { + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Starting keyidx is 1; Cannot {start} with 0"); + } + struct issued_address_type *listaddrtypes = wallet_list_addresses(tmpctx, cmd->ld->wallet, *liststart, listlimit); + response = json_stream_success(cmd); + json_array_start(response, "addresses"); + for (size_t i = 0; i < tal_count(listaddrtypes); i++) { + if (listaddrtypes[i].keyidx == BIP32_INITIAL_HARDENED_CHILD){ + break; + } + bip32_pubkey(cmd->ld, &pubkey, listaddrtypes[i].keyidx); + char *out_p2wpkh = ""; + char *out_p2tr = ""; + if (listaddrtypes[i].addrtype == ADDR_BECH32 || listaddrtypes[i].addrtype == ADDR_ALL) { + u8 *redeemscript_p2wpkh; + out_p2wpkh = encode_pubkey_to_addr(cmd, + &pubkey, + ADDR_BECH32, + &redeemscript_p2wpkh); + if (!out_p2wpkh) { + abort(); + } + } + if (listaddrtypes[i].addrtype == ADDR_P2TR || listaddrtypes[i].addrtype == ADDR_ALL) { + out_p2tr = encode_pubkey_to_addr(cmd, + &pubkey, + ADDR_P2TR, + /* out_redeemscript */ NULL); + if (!out_p2tr) { + abort(); + } + } + if (!addr || streq(addr, out_p2wpkh) || streq(addr, out_p2tr)) { + json_add_address_details(response, listaddrtypes[i].keyidx, out_p2wpkh, out_p2tr); + if (addr) { + break; + } + } + } + json_array_end(response); + return command_success(cmd, response); +} + +static const struct json_command listaddresses_command = { + "listaddresses", + json_listaddresses +}; +AUTODATA(json_command, &listaddresses_command); + static struct command_result *json_listaddrs(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, From 5170a83244843fd0b9e497f278091be2ee6eb609 Mon Sep 17 00:00:00 2001 From: ShahanaFarooqui Date: Fri, 8 Nov 2024 22:28:20 -0800 Subject: [PATCH 09/10] doc: Add schema for listaddresses --- contrib/msggen/msggen/schema.json | 128 +++++++++++++++++++++++ doc/Makefile | 1 + doc/index.rst | 1 + doc/schemas/lightning-listaddresses.json | 128 +++++++++++++++++++++++ 4 files changed, 258 insertions(+) create mode 100644 doc/schemas/lightning-listaddresses.json diff --git a/contrib/msggen/msggen/schema.json b/contrib/msggen/msggen/schema.json index d6003d8d9c2f..cff63ed8381b 100644 --- a/contrib/msggen/msggen/schema.json +++ b/contrib/msggen/msggen/schema.json @@ -17245,6 +17245,134 @@ } ] }, + "lightning-listaddresses.json": { + "$schema": "../rpc-schema-draft.json", + "type": "object", + "added": "v24.11", + "rpc": "listaddresses", + "title": "Command to list all addresses issued by the node to date", + "description": [ + "The **listaddresses** RPC command provides a detailed list of all Bitcoin addresses that have been generated and issued by the Core Lightning node up to the current date." + ], + "categories": [ + "readonly" + ], + "request": { + "required": [], + "additionalProperties": false, + "properties": { + "address": { + "type": "string", + "description": [ + "A Bitcoin accepted type, including a bech32, address for lookup in the list of addresses issued to date." + ] + }, + "start": { + "type": "u64", + "description": [ + "Starting key index for listing addresses or searching for a particular address." + ], + "default": 1 + }, + "limit": { + "type": "u32", + "description": [ + "The maximum number of addresses to return or search for." + ], + "default": "Total number of addresses issued" + } + } + }, + "response": { + "required": [ + "addresses" + ], + "additionalProperties": false, + "properties": { + "addresses": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "keyidx" + ], + "properties": { + "keyidx": { + "type": "u64", + "description": [ + "The key index of the address issued." + ] + }, + "bech32": { + "type": "string", + "description": [ + "The bech32 (native segwit) address." + ] + }, + "p2tr": { + "type": "string", + "description": [ + "The taproot address." + ] + } + } + } + } + } + }, + "author": [ + "Shahana Farooqui <> is mainly responsible." + ], + "see_also": [ + "lightning-newaddr(7)", + "lightning-withdraw(7)" + ], + "resources": [ + "Main web site: " + ], + "examples": [ + { + "request": { + "id": "example:listaddresses#1", + "method": "listaddresses", + "params": [ + "bcrt1phtprcvhz842sxe8qw0yryvc0g6n6dw2puweklcpnfye273kjpfhs0d6d2e" + ] + }, + "response": { + "addresses": [ + { + "keyidx": 13, + "p2tr": "bcrt1phtprcvhz842sxe8qw0yryvc0g6n6dw2puweklcpnfye273kjpfhs0d6d2e" + } + ] + } + }, + { + "request": { + "id": "example:listaddresses#2", + "method": "listaddresses", + "params": { + "start": 6, + "limit": 2 + } + }, + "response": { + "addresses": [ + { + "keyidx": 6, + "bech32": "bcrt1qkpw662yvzdy5ttdg8nw4eh4el0uc7m2ythw0h3" + }, + { + "keyidx": 7, + "p2tr": "bcrt1pn45xgkyj54usdu98plm4zgcp4c5jvvfxmyexwcm5kc3gyfsrjmasd6ctsf" + } + ] + } + } + ] + }, "lightning-listchannels.json": { "$schema": "../rpc-schema-draft.json", "type": "object", diff --git a/doc/Makefile b/doc/Makefile index 1f00086bc329..b53fdedc7370 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -74,6 +74,7 @@ GENERATE_MARKDOWN := doc/lightning-addgossip.7 \ doc/lightning-invoice.7 \ doc/lightning-invoicerequest.7 \ doc/lightning-keysend.7 \ + doc/lightning-listaddresses.7 \ doc/lightning-listchannels.7 \ doc/lightning-listclosedchannels.7 \ doc/lightning-listconfigs.7 \ diff --git a/doc/index.rst b/doc/index.rst index 004e4157b24e..f64b6d16140b 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -84,6 +84,7 @@ Core Lightning Documentation lightning-invoice lightning-invoicerequest lightning-keysend + lightning-listaddresses lightning-listchannels lightning-listclosedchannels lightning-listconfigs diff --git a/doc/schemas/lightning-listaddresses.json b/doc/schemas/lightning-listaddresses.json new file mode 100644 index 000000000000..1f5a485ec44a --- /dev/null +++ b/doc/schemas/lightning-listaddresses.json @@ -0,0 +1,128 @@ +{ + "$schema": "../rpc-schema-draft.json", + "type": "object", + "added": "v24.11", + "rpc": "listaddresses", + "title": "Command to list all addresses issued by the node to date", + "description": [ + "The **listaddresses** RPC command provides a detailed list of all Bitcoin addresses that have been generated and issued by the Core Lightning node up to the current date." + ], + "categories": [ + "readonly" + ], + "request": { + "required": [], + "additionalProperties": false, + "properties": { + "address": { + "type": "string", + "description": [ + "A Bitcoin accepted type, including a bech32, address for lookup in the list of addresses issued to date." + ] + }, + "start": { + "type": "u64", + "description": [ + "Starting key index for listing addresses or searching for a particular address." + ], + "default": 1 + }, + "limit": { + "type": "u32", + "description": [ + "The maximum number of addresses to return or search for." + ], + "default": "Total number of addresses issued" + } + } + }, + "response": { + "required": [ + "addresses" + ], + "additionalProperties": false, + "properties": { + "addresses": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "keyidx" + ], + "properties": { + "keyidx": { + "type": "u64", + "description": [ + "The key index of the address issued." + ] + }, + "bech32": { + "type": "string", + "description": [ + "The bech32 (native segwit) address." + ] + }, + "p2tr": { + "type": "string", + "description": [ + "The taproot address." + ] + } + } + } + } + } + }, + "author": [ + "Shahana Farooqui <> is mainly responsible." + ], + "see_also": [ + "lightning-newaddr(7)", + "lightning-withdraw(7)" + ], + "resources": [ + "Main web site: " + ], + "examples": [ + { + "request": { + "id": "example:listaddresses#1", + "method": "listaddresses", + "params": [ + "bcrt1phtprcvhz842sxe8qw0yryvc0g6n6dw2puweklcpnfye273kjpfhs0d6d2e" + ] + }, + "response": { + "addresses": [ + { + "keyidx": 13, + "p2tr": "bcrt1phtprcvhz842sxe8qw0yryvc0g6n6dw2puweklcpnfye273kjpfhs0d6d2e" + } + ] + } + }, + { + "request": { + "id": "example:listaddresses#2", + "method": "listaddresses", + "params": { + "start": 6, + "limit": 2 + } + }, + "response": { + "addresses": [ + { + "keyidx": 6, + "bech32": "bcrt1qkpw662yvzdy5ttdg8nw4eh4el0uc7m2ythw0h3" + }, + { + "keyidx": 7, + "p2tr": "bcrt1pn45xgkyj54usdu98plm4zgcp4c5jvvfxmyexwcm5kc3gyfsrjmasd6ctsf" + } + ] + } + } + ] +} From d46b4d4925b0d570243971cd75558a7ccaed7c7b Mon Sep 17 00:00:00 2001 From: ShahanaFarooqui Date: Fri, 8 Nov 2024 22:31:42 -0800 Subject: [PATCH 10/10] test: listaddresses tests Added a new test for listaddresses rpc. --- tests/test_misc.py | 81 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/tests/test_misc.py b/tests/test_misc.py index a46918d82a2e..57e0571d47dd 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -4325,3 +4325,84 @@ def test_badparam_discretion(node_factory): l1.rpc.check('checkrune', rune='THIS IS NOT ACTUALLY A RUNE') assert err.value.error['message'] == "rune: should be base64 string: invalid token '\"THIS IS NOT ACTUALLY A RUNE\"'" + + +@unittest.skipIf(TEST_NETWORK == 'liquid-regtest', "P2TR not yet supported on Elements") +def test_listaddresses(node_factory): + """Test listaddresses command.""" + l1 = node_factory.get_node() + addr = [] + for i in range(10): + if i % 3 == 0: + addr.append(l1.rpc.newaddr('all')['p2tr']) + elif i % 3 == 1: + addr.append(l1.rpc.newaddr('p2tr')['p2tr']) + else: + addr.append(l1.rpc.newaddr('bech32')['bech32']) + + # Default start and limit (all) + addresses = l1.rpc.listaddresses()["addresses"] + assert len(addresses) == 10 + assert addresses[0]['keyidx'] == 1 + assert addresses[-1]['keyidx'] == 10 + + # Default limit (till end) + addresses = l1.rpc.listaddresses(start=5)["addresses"] + assert len(addresses) == 6 + assert addresses[0]['keyidx'] == 5 + assert addresses[-1]['keyidx'] == 10 + + # Default start + addresses = l1.rpc.listaddresses(limit=5)["addresses"] + assert len(addresses) == 5 + assert addresses[0]['keyidx'] == 1 + assert addresses[-1]['keyidx'] == 5 + + # Start and limit + addresses = l1.rpc.listaddresses(start=5, limit=2)["addresses"] + assert len(addresses) == 2 + assert addresses[0]['keyidx'] == 5 + assert addresses[-1]['keyidx'] == 6 + + # Invalid Address + with pytest.raises(RpcError, match='Could not parse destination address, address should be a valid address') as err: + l1.rpc.listaddresses(address="bcrt1q3p9jh7x0907wc0") + + assert err.value.error['code'] == -1 + assert err.value.error['message'] == "Could not parse destination address, address should be a valid address" + + # Address search, default start=0, default limit is length of the list + addresses = l1.rpc.listaddresses(address=addr[7])["addresses"] + assert len(addresses) == 1 + assert addresses[0]['keyidx'] == 8 + + # Address is between start and limit indices + addresses = l1.rpc.listaddresses(address=addr[4], start=2, limit=4)["addresses"] + assert len(addresses) == 1 + assert addresses[0]['keyidx'] == 5 + + # Address is not between start and limit indices + addresses = l1.rpc.listaddresses(address=addr[9], start=5, limit=4)["addresses"] + assert len(addresses) == 0 + + # Not our Address + addresses = l1.rpc.listaddresses(address="bcrt1q3p9jh7x0mnx8tmx5meapksvcxytlxgwz907wc0")["addresses"] + assert len(addresses) == 0 + + # Check all fields are present in the response + addresses = l1.rpc.listaddresses(address=addr[0])["addresses"] + assert addresses[0]['keyidx'] == 1 + assert addresses[0]['bech32'] == 'bcrt1qq8adjz4u6enf0cjey9j8yt0y490tact93fzgsf' + assert addresses[0]['p2tr'] == 'bcrt1pjaazqg6qgqpv2wxgdpg8hyj49wehrfgajqe2tyuzhcp7p50hachq7tkdxf' + + # start > 10 (issued addresses till now) + addresses = l1.rpc.listaddresses(start=11, limit=2)["addresses"] + assert len(addresses) == 0 + + # limit > bip32_max_index (10) + addresses = l1.rpc.listaddresses(start=8, limit=15)["addresses"] + assert len(addresses) == 3 + + # start and limit from future + addresses = l1.rpc.listaddresses(start=21, limit=5)["addresses"] + assert len(addresses) == 0