diff --git a/CHANGELOG.md b/CHANGELOG.md index e474f5c02067..36bc091524b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). -## [v23.08rc1] - 2023-08-02: "Satoshi's Successor" +## [v23.08rc2] - 2023-08-02: "Satoshi's Successor" This release named by Matt Morehouse. @@ -40,6 +40,7 @@ This release named by Matt Morehouse. - JSON-RPC: `fundpsbt` and `utxopsbt` new parameter `opening_anchor_channel` so lightningd knowns it needs emergency reserve for anchors. ([#6334]) - Config: `min-emergency-msat` setting for (currently experimental!) anchor channels, to keep funds in reserve for forced closes. ([#6334]) - JSON-RPC: `feerates` has new fields `unilateral_anchor_close` to show the feerate used for anchor channels (currently experimental), and `unilateral_close_nonanchor_satoshis`. ([#6334]) + - cln-grpc: Added `staticbackup` support to cln-grpc ([#6507]) ### Changed @@ -100,6 +101,8 @@ Note: You should always set `allow-deprecated-apis=false` to test for changes. - Plugins: reloaded plugins get passed any vars from configuration files. ([#6243]) - JSON-RPC: `listconfigs` `rpc-file-mode` no longer has gratuitous quotes (e.g. "0600" not "\"0600\""). ([#6243]) - JSON-RPC: `listconfigs` `htlc-minimum-msat`, `htlc-maximum-msat` and `max-dust-htlc-exposure-msat` fields are now numbers, not strings. ([#6243]) + - Protocol: We may propose mutual close transaction which has a slightly higher fee than the final commitment tx (depending on the outputs, e.g. two taproot outputs). ([#6547]) + - Protocol: We now close connection with a peer if adding an HTLC times out (which may be a TCP connectivity issue). ([#6520]) ### EXPERIMENTAL @@ -155,7 +158,10 @@ Note: You should always set `allow-deprecated-apis=false` to test for changes. [#6461]: https://github.com/ElementsProject/lightning/pull/6461 [#6466]: https://github.com/ElementsProject/lightning/pull/6466 [#6468]: https://github.com/ElementsProject/lightning/pull/6468 -[v23.08rc1]: https://github.com/ElementsProject/lightning/releases/tag/v23.08rc1 +[#6507]: https://github.com/ElementsProject/lightning/pull/6507 +[#6520]: https://github.com/ElementsProject/lightning/pull/6520 +[#6547]: https://github.com/ElementsProject/lightning/pull/6547 +[v23.08rc2]: https://github.com/ElementsProject/lightning/releases/tag/v23.08rc2 ## [23.05.2] - 2023-06-21: "Austin Texas Agreement(ATXA) III" diff --git a/channeld/channeld.c b/channeld/channeld.c index 1370bde9d423..dcc91079f564 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -443,7 +443,11 @@ static void send_channel_update(struct peer *peer, int disable_flag) * they route through us */ static void send_channel_initial_update(struct peer *peer) { - send_channel_update(peer, 0); + /* If `stfu` is already active then the channel is being mutated quickly + * after creation. These mutations (ie. splice) must announce the + * channel when they finish anyway, so it is safe to skip it here */ + if (!is_stfu_active(peer) && !peer->want_stfu) + send_channel_update(peer, 0); } /** @@ -591,6 +595,12 @@ static void announce_channel(struct peer *peer) send_channel_update(peer, 0); } +static void announce_channel_if_not_stfu(struct peer *peer) +{ + if (!is_stfu_active(peer) && !peer->want_stfu) + announce_channel(peer); +} + /* Returns true if an announcement was sent */ static bool channel_announcement_negotiate(struct peer *peer) { @@ -665,7 +675,7 @@ static bool channel_announcement_negotiate(struct peer *peer) /* Give other nodes time to notice new block. */ notleak(new_reltimer(&peer->timers, peer, time_from_sec(GOSSIP_ANNOUNCE_DELAY(peer->dev_fast_gossip)), - announce_channel, peer)); + announce_channel_if_not_stfu, peer)); } return sent_announcement; @@ -1494,7 +1504,7 @@ static u8 *send_commit_part(struct peer *peer, * send unless negotiated */ if (feature_negotiated(peer->our_features, peer->their_features, - OPT_SPLICE)) { + OPT_EXPERIMENTAL_SPLICE)) { status_debug("send_commit_part(splice: %d, remote_splice: %d)", (int)splice_amnt, (int)remote_splice_amnt); @@ -1722,6 +1732,18 @@ static void send_commit(struct peer *peer) start_commit_timer(peer); } +static void send_commit_if_not_stfu(struct peer *peer) +{ + if (!is_stfu_active(peer) && !peer->want_stfu) { + send_commit(peer); + } + else { + /* Timer now considered expired, you can add a new one. */ + peer->commit_timer = NULL; + start_commit_timer(peer); + } +} + static void start_commit_timer(struct peer *peer) { /* Already armed? */ @@ -1730,7 +1752,7 @@ static void start_commit_timer(struct peer *peer) peer->commit_timer = new_reltimer(&peer->timers, peer, time_from_msec(peer->commit_msec), - send_commit, peer); + send_commit_if_not_stfu, peer); } /* If old_secret is NULL, we don't care, otherwise it is filled in. */ @@ -4592,7 +4614,6 @@ static void peer_reconnect(struct peer *peer, bool dataloss_protect, check_extra_fields; const u8 **premature_msgs = tal_arr(peer, const u8 *, 0); struct inflight *inflight; - bool next_matches_current, next_matches_inflight; struct bitcoin_txid *local_next_funding, *remote_next_funding; struct tlv_channel_reestablish_tlvs *send_tlvs, *recv_tlvs; @@ -4610,12 +4631,12 @@ static void peer_reconnect(struct peer *peer, get_per_commitment_point(peer->next_index[LOCAL] - 1, &my_current_per_commitment_point, NULL); + inflight = last_inflight(peer); + if (peer->experimental_upgrade) { /* Subtle: we free tmpctx below as we loop, so tal off peer */ send_tlvs = tlv_channel_reestablish_tlvs_new(peer); - inflight = last_inflight(peer); - /* If inflight with no sigs on it, send next_funding */ if (inflight && !inflight->last_tx) send_tlvs->next_funding = &inflight->outpoint.txid; @@ -5017,6 +5038,8 @@ static void peer_reconnect(struct peer *peer, remote_next_funding = (recv_tlvs ? recv_tlvs->next_funding : NULL); if (local_next_funding || remote_next_funding) { + bool next_matches_current = false, next_matches_inflight = false; + if (remote_next_funding) { next_matches_current = bitcoin_txid_eq(remote_next_funding, &peer->channel->funding.txid); diff --git a/cli/lightning-cli.c b/cli/lightning-cli.c index 392ad88fc7a8..117f6b19b99c 100644 --- a/cli/lightning-cli.c +++ b/cli/lightning-cli.c @@ -650,7 +650,7 @@ int main(int argc, char *argv[]) jsmntok_t *toks; const jsmntok_t *result, *error, *id; const tal_t *ctx = tal(NULL, char); - char *net_dir, *rpc_filename; + char *config_filename, *base_dir, *net_dir, *rpc_filename; jsmn_parser parser; int parserr; enum format format = DEFAULT_FORMAT; @@ -668,7 +668,8 @@ int main(int argc, char *argv[]) setup_option_allocators(); opt_exitcode = ERROR_USAGE; - minimal_config_opts(ctx, argc, argv, &net_dir, &rpc_filename); + minimal_config_opts(ctx, argc, argv, &config_filename, &base_dir, + &net_dir, &rpc_filename); opt_register_noarg("--help|-h", opt_usage_and_exit, " [...]", "Show this message. Use the command help (without hyphens -- \"lightning-cli help\") to get a list of all RPC commands"); diff --git a/closingd/closingd.c b/closingd/closingd.c index d0dc2a0685fd..9df24ca6d416 100644 --- a/closingd/closingd.c +++ b/closingd/closingd.c @@ -598,8 +598,7 @@ static size_t closing_tx_weight_estimate(u8 *scriptpubkey[NUM_SIDES], static void calc_fee_bounds(size_t expected_weight, u32 min_feerate, u32 desired_feerate, - u32 *max_feerate, - struct amount_sat commitment_fee, + u32 max_feerate, struct amount_sat funding, enum side opener, struct amount_sat *minfee, @@ -620,36 +619,22 @@ static void calc_fee_bounds(size_t expected_weight, if (opener == REMOTE) { *maxfee = funding; - /* This used to appear in BOLT #2: we still set it for non-anchor - * peers who may still enforce it: - * - If the channel does not use `option_anchor_outputs`: - * - MUST set `fee_satoshis` less than or equal to the base fee of - * the final commitment transaction, as calculated in - * [BOLT #3](03-transactions.md#fee-calculation). - */ - } else if (max_feerate) { - *maxfee = amount_tx_fee(*max_feerate, expected_weight); - - status_debug("deriving max fee from rate %u -> %s (not %s)", - *max_feerate, - type_to_string(tmpctx, struct amount_sat, maxfee), - type_to_string(tmpctx, struct amount_sat, &commitment_fee)); - - /* option_anchor_outputs sets commitment_fee to max, so this - * doesn't do anything */ - if (amount_sat_greater(*maxfee, commitment_fee)) { - /* FIXME: would be nice to notify close cmd here! */ - status_unusual("Maximum feerate %u would give fee %s:" - " we must limit it to the final commitment fee %s", - *max_feerate, - type_to_string(tmpctx, struct amount_sat, - maxfee), - type_to_string(tmpctx, struct amount_sat, - &commitment_fee)); - *maxfee = commitment_fee; - } - } else - *maxfee = commitment_fee; + } else { + /* BOLT #2: + * The sending node: + * + * - SHOULD set the initial `fee_satoshis` according to its + * estimate of cost of inclusion in a block. + * + * - SHOULD set `fee_range` according to the minimum and + * maximum fees it is prepared to pay for a close + * transaction. + */ + *maxfee = amount_tx_fee(max_feerate, expected_weight); + status_debug("deriving max fee from rate %u -> %s", + max_feerate, + type_to_string(tmpctx, struct amount_sat, maxfee)); + } /* Can't exceed maxfee. */ if (amount_sat_greater(*minfee, *maxfee)) @@ -868,9 +853,9 @@ int main(int argc, char *argv[]) struct bitcoin_outpoint funding; struct amount_sat funding_sats, out[NUM_SIDES]; struct amount_sat our_dust_limit; - struct amount_sat min_fee_to_accept, commitment_fee, offer[NUM_SIDES], + struct amount_sat min_fee_to_accept, offer[NUM_SIDES], max_fee_to_accept; - u32 min_feerate, initial_feerate, *max_feerate; + u32 min_feerate, initial_feerate, max_feerate; struct feerange feerange; enum side opener; u32 *local_wallet_index; @@ -902,7 +887,6 @@ int main(int argc, char *argv[]) &out[REMOTE], &our_dust_limit, &min_feerate, &initial_feerate, &max_feerate, - &commitment_fee, &local_wallet_index, &local_wallet_ext_key, &scriptpubkey[LOCAL], @@ -929,7 +913,7 @@ int main(int argc, char *argv[]) local_wallet_index, local_wallet_ext_key), min_feerate, initial_feerate, max_feerate, - commitment_fee, funding_sats, opener, + funding_sats, opener, &min_fee_to_accept, &offer[LOCAL], &max_fee_to_accept); /* Write values into tlv for updated closing fee neg */ @@ -1099,7 +1083,6 @@ int main(int argc, char *argv[]) tal_free(wrong_funding); tal_free(our_feerange); tal_free(their_feerange); - tal_free(max_feerate); tal_free(local_wallet_index); tal_free(local_wallet_ext_key); closing_dev_memleak(ctx, scriptpubkey, funding_wscript); diff --git a/closingd/closingd_wire.csv b/closingd/closingd_wire.csv index 29f6eccabbc1..10a5d8cbb1b5 100644 --- a/closingd/closingd_wire.csv +++ b/closingd/closingd_wire.csv @@ -19,8 +19,7 @@ msgdata,closingd_init,remote_sat,amount_sat, msgdata,closingd_init,our_dust_limit,amount_sat, msgdata,closingd_init,min_feerate_perksipa,u32, msgdata,closingd_init,preferred_feerate_perksipa,u32, -msgdata,closingd_init,max_feerate_perksipa,?u32, -msgdata,closingd_init,fee_limit_satoshi,amount_sat, +msgdata,closingd_init,max_feerate_perksipa,u32, msgdata,closingd_init,local_wallet_index,?u32, msgdata,closingd_init,local_wallet_ext_key,?ext_key, msgdata,closingd_init,local_scriptpubkey_len,u16, diff --git a/common/configdir.c b/common/configdir.c index 005cbd9040ea..e932d7e20e12 100644 --- a/common/configdir.c +++ b/common/configdir.c @@ -281,16 +281,18 @@ static struct configvar **gather_cmdline_args(const tal_t *ctx, void minimal_config_opts(const tal_t *ctx, int argc, char *argv[], + char **config_filename, + char **basedir, char **config_netdir, char **rpc_filename) { - char *unused_filename, *unused_basedir; - initial_config_opts(tmpctx, &argc, argv, false, - &unused_filename, - &unused_basedir, + config_filename, + basedir, config_netdir, rpc_filename); + tal_steal(ctx, *config_filename); + tal_steal(ctx, *basedir); tal_steal(ctx, *config_netdir); tal_steal(ctx, *rpc_filename); } diff --git a/common/configdir.h b/common/configdir.h index 283b4ec9cbef..e25207be5edf 100644 --- a/common/configdir.h +++ b/common/configdir.h @@ -15,6 +15,8 @@ void setup_option_allocators(void); /* Minimal config parsing for tools: use opt_early_parse/opt_parse after */ void minimal_config_opts(const tal_t *ctx, int argc, char *argv[], + char **config_filename, + char **basedir, char **config_netdir, char **rpc_filename); diff --git a/common/features.c b/common/features.c index 89c1bd04746d..121f96ee9e38 100644 --- a/common/features.c +++ b/common/features.c @@ -146,6 +146,10 @@ static const struct feature_style feature_styles[] = { .copy_style = { [INIT_FEATURE] = FEATURE_REPRESENT, [NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT, [CHANNEL_FEATURE] = FEATURE_DONT_REPRESENT} }, + { OPT_EXPERIMENTAL_SPLICE, + .copy_style = { [INIT_FEATURE] = FEATURE_REPRESENT, + [NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT, + [CHANNEL_FEATURE] = FEATURE_DONT_REPRESENT} }, }; struct dependency { @@ -491,6 +495,41 @@ const char *feature_name(const tal_t *ctx, size_t f) NULL, NULL, NULL, /* 100/101 */ + NULL, + NULL, + NULL, + NULL, + NULL, /* 110/111 */ + NULL, + NULL, + NULL, + NULL, + NULL, /* 120/121 */ + NULL, + NULL, + NULL, + NULL, + NULL, /* 130/131 */ + NULL, + NULL, + NULL, + NULL, + NULL, /* 140/141 */ + NULL, + NULL, + NULL, + NULL, + NULL, /* 150/151 */ + NULL, + NULL, + NULL, + NULL, + NULL, /* 160/161 */ + "option_experimental_splice", /* https://github.com/lightning/bolts/pull/863 */ + NULL, + NULL, + NULL, + NULL, /* 170/171 */ }; if (f / 2 >= ARRAY_SIZE(fnames) || !fnames[f / 2]) diff --git a/common/features.h b/common/features.h index 31728d1f2758..640fce1248e1 100644 --- a/common/features.h +++ b/common/features.h @@ -136,7 +136,12 @@ struct feature_set *feature_set_dup(const tal_t *ctx, #define OPT_SHUTDOWN_ANYSEGWIT 26 #define OPT_CHANNEL_TYPE 44 #define OPT_PAYMENT_METADATA 48 + +/* BOLT-splice #9: + * | 62/63 | `option_splice` | ... IN ... + */ #define OPT_SPLICE 62 +#define OPT_EXPERIMENTAL_SPLICE 162 /* BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: * | 28/29 | `option_dual_fund` | ... IN9 ... diff --git a/common/gossmap.c b/common/gossmap.c index a1f70eda8f6b..4495574cf824 100644 --- a/common/gossmap.c +++ b/common/gossmap.c @@ -318,9 +318,14 @@ static u32 init_chan_arr(struct gossmap_chan *chan_arr, size_t start) for (i = start; i < tal_count(chan_arr) - 1; i++) { chan_arr[i].cann_off = i + 1; chan_arr[i].plus_scid_off = 0; + /* We don't need to initialize this, *but* on some platforms + * (ppc, arm64) valgrind complains: this is a bitfield shared + * with plus_scid_off */ + chan_arr[i].private = false; } chan_arr[i].cann_off = UINT_MAX; chan_arr[i].plus_scid_off = 0; + chan_arr[i].private = false; return start; } diff --git a/common/pseudorand.c b/common/pseudorand.c index c08e21849e54..79da275b4a12 100644 --- a/common/pseudorand.c +++ b/common/pseudorand.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -56,3 +57,31 @@ const struct siphash_seed *siphash_seed(void) return &siphashseed; } + + +void tal_arr_randomize_(void *arr, size_t elemsize) +{ + /* Easier arith. */ + char *carr = arr; + size_t n = tal_bytelen(arr) / elemsize; + + assert(tal_bytelen(arr) % elemsize == 0); + + /* From Wikipedia's Fischer-Yates shuffle article: + * + * for i from 0 to nāˆ’2 do + * j ā† random integer such that i ā‰¤ j < n + * exchange a[i] and a[j] + */ + if (n < 2) + return; + + for (size_t i = 0; i < n - 1; i++) { + size_t j = i + pseudorand(n - i); + char tmp[elemsize]; + + memcpy(tmp, carr + i * elemsize, elemsize); + memcpy(carr + i * elemsize, carr + j * elemsize, elemsize); + memcpy(carr + j * elemsize, tmp, elemsize); + } +} diff --git a/common/pseudorand.h b/common/pseudorand.h index 65272adda8fa..a34220055560 100644 --- a/common/pseudorand.h +++ b/common/pseudorand.h @@ -2,6 +2,7 @@ #define LIGHTNING_COMMON_PSEUDORAND_H #include "config.h" #include +#include /** * pseudorand - pseudo (guessable!) random number between 0 and max-1. @@ -24,4 +25,9 @@ double pseudorand_double(void); */ const struct siphash_seed *siphash_seed(void); +/* Shuffle a tal array of type type. */ +#define tal_arr_randomize(arr, type) \ + tal_arr_randomize_((arr), sizeof(type) + 0*sizeof(arr == (type *)NULL)) +void tal_arr_randomize_(void *arr, size_t elemsize); + #endif /* LIGHTNING_COMMON_PSEUDORAND_H */ diff --git a/common/route.c b/common/route.c index f88cb0082ac2..64beb206313d 100644 --- a/common/route.c +++ b/common/route.c @@ -13,7 +13,9 @@ bool route_can_carry_even_disabled(const struct gossmap *map, { if (!gossmap_chan_set(c, dir)) return false; - if (!gossmap_chan_capacity(c, dir, amount)) + /* Amount 0 is a special "ignore min" probe case */ + if (!amount_msat_eq(amount, AMOUNT_MSAT(0)) + && !gossmap_chan_capacity(c, dir, amount)) return false; return true; } diff --git a/common/sphinx.c b/common/sphinx.c index d908f622eeb0..4b0fa2e7038a 100644 --- a/common/sphinx.c +++ b/common/sphinx.c @@ -108,9 +108,8 @@ bool sphinx_add_hop_has_length(struct sphinx_path *path, const struct pubkey *pu struct sphinx_hop sp; bigsize_t lenlen, prepended_len; - /* You promised size was prepended! */ - if (tal_bytelen(payload) == 0) - return false; + /* In case length is missing, we'll return false. */ + prepended_len = UINT64_MAX; lenlen = bigsize_get(payload, tal_bytelen(payload), &prepended_len); if (add_overflows_u64(lenlen, prepended_len)) return false; diff --git a/common/test/run-bolt12_decode.c b/common/test/run-bolt12_decode.c index 5bbb6f7a1cc4..e2fa5f0ca7bc 100644 --- a/common/test/run-bolt12_decode.c +++ b/common/test/run-bolt12_decode.c @@ -195,7 +195,7 @@ int main(int argc, char *argv[]) size_t dlen; struct json_escape *esc; - json_to_bool(json, json_get_member(json, t, "valid"), &valid); + assert(json_to_bool(json, json_get_member(json, t, "valid"), &valid)); strtok = json_get_member(json, t, "string"); esc = json_escape_string_(tmpctx, json + strtok->start, strtok->end - strtok->start); diff --git a/common/test/run-bolt12_merkle-json.c b/common/test/run-bolt12_merkle-json.c index b7519fb7f4f7..bcfc9ee6b0bc 100644 --- a/common/test/run-bolt12_merkle-json.c +++ b/common/test/run-bolt12_merkle-json.c @@ -45,6 +45,9 @@ void towire_channel_id(u8 **pptr UNNEEDED, const struct channel_id *channel_id U /* Generated stub for towire_node_id */ void towire_node_id(u8 **pptr UNNEEDED, const struct node_id *id UNNEEDED) { fprintf(stderr, "towire_node_id called!\n"); abort(); } +/* Generated stub for towire_s64 */ +void towire_s64(u8 **pptr UNNEEDED, s64 v UNNEEDED) +{ fprintf(stderr, "towire_s64 called!\n"); abort(); } /* Generated stub for towire_secp256k1_ecdsa_signature */ void towire_secp256k1_ecdsa_signature(u8 **pptr UNNEEDED, const secp256k1_ecdsa_signature *signature UNNEEDED) @@ -67,9 +70,6 @@ void towire_u32(u8 **pptr UNNEEDED, u32 v UNNEEDED) /* 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_s64 */ -void towire_s64(u8 **pptr UNNEEDED, s64 v UNNEEDED) -{ fprintf(stderr, "towire_s64 called!\n"); abort(); } /* Generated stub for towire_u8 */ void towire_u8(u8 **pptr UNNEEDED, u8 v UNNEEDED) { fprintf(stderr, "towire_u8 called!\n"); abort(); } diff --git a/common/test/run-json_remove.c b/common/test/run-json_remove.c index 526611768a79..64e246e00085 100644 --- a/common/test/run-json_remove.c +++ b/common/test/run-json_remove.c @@ -87,9 +87,6 @@ u32 fromwire_u32(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) /* 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_s64 */ -s64 fromwire_s64(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) -{ fprintf(stderr, "fromwire_s64 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(); } @@ -187,9 +184,6 @@ void towire_u32(u8 **pptr UNNEEDED, u32 v UNNEEDED) /* 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_s64 */ -void towire_s64(u8 **pptr UNNEEDED, s64 v UNNEEDED) -{ fprintf(stderr, "towire_s64 called!\n"); abort(); } /* Generated stub for towire_u8 */ void towire_u8(u8 **pptr UNNEEDED, u8 v UNNEEDED) { fprintf(stderr, "towire_u8 called!\n"); abort(); } diff --git a/common/test/run-tal_arr_randomize.c b/common/test/run-tal_arr_randomize.c new file mode 100644 index 000000000000..b539a6d3c475 --- /dev/null +++ b/common/test/run-tal_arr_randomize.c @@ -0,0 +1,191 @@ +#include "config.h" +#include "../pseudorand.c" +#include +#include +#include +#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_div */ +struct amount_sat amount_sat_div(struct amount_sat sat UNNEEDED, u64 div UNNEEDED) +{ fprintf(stderr, "amount_sat_div 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_mul */ +bool amount_sat_mul(struct amount_sat *res UNNEEDED, struct amount_sat sat UNNEEDED, u64 mul UNNEEDED) +{ fprintf(stderr, "amount_sat_mul 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_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_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 bool any_equal(const char **arr, size_t n) +{ + for (size_t i = 0; i < n; i++) { + for (size_t j = i+1; j < n; j++) { + if (arr[i] == arr[j]) + return true; + } + } + return false; +} + +static bool one_of(const char **arr, size_t n, const char *word) +{ + for (size_t i = 0; i < n; i++) { + if (word == arr[i]) + return true; + } + return false; +} + +int main(int argc, char *argv[]) +{ + const char *words[] = { + "hello", + "world", + "this", + "is", + "a", + "pay_test", + "with", + "many", + "elements", + "so", + "the", + "chances", + "of", + "it", + "remaining", + "unshuffled", + "are", + "insignficant", + "and", + "we", + "expect", + "almost", + "every", + "word", + "to", + "be", + "in", + "some", + "different", + "place", + "after", + "shuffling." + }; + + common_setup(argv[0]); + + for (size_t i = 0; i < ARRAY_SIZE(words); i++) { + const char **arr = tal_arr(tmpctx, const char *, i); + memcpy(arr, words, tal_bytelen(arr)); + tal_arr_randomize(arr, const char *); + /* They should all point to distinct words */ + assert(!any_equal(arr, i)); + /* They should point to our expected words. */ + for (size_t j = 0; j < i; j++) + assert(one_of(words, i, arr[j])); + + /* It will actually shuffle! */ + if (i == ARRAY_SIZE(words) - 1) { + size_t num_same = 0; + for (size_t j = 0; j < i; j++) { + printf("%s ", arr[j]); + if (arr[j] == words[j]) + num_same++; + } + assert(num_same < i/2); + printf("\n%zu\n", num_same); + } + } + common_shutdown(); + + return 0; +} diff --git a/contrib/pyln-client/pyln/client/gossmap.py b/contrib/pyln-client/pyln/client/gossmap.py index ad6e681b861d..88641cbba617 100755 --- a/contrib/pyln-client/pyln/client/gossmap.py +++ b/contrib/pyln-client/pyln/client/gossmap.py @@ -74,6 +74,7 @@ class LnFeatureBits(object): OPTION_PROPOSED_UPFRONT_FEE = 56 # IN9 #1052 OPTION_PROPOSED_CLOSING_REJECTED = 60 # IN #1016 OPTION_PROPOSED_SPLICE = 62 # IN #863 + OPTION_PROPOSED_EXPERIMENTAL_SPLICE = 162 # IN #863 def _parse_features(featurebytes): diff --git a/contrib/pyln-testing/pyln/testing/utils.py b/contrib/pyln-testing/pyln/testing/utils.py index f2c880825beb..95083801dd37 100644 --- a/contrib/pyln-testing/pyln/testing/utils.py +++ b/contrib/pyln-testing/pyln/testing/utils.py @@ -1542,7 +1542,8 @@ def get_node(self, node_id=None, options=None, dbfile=None, ) # Regtest estimatefee are unusable, so override. - node.set_feerates(feerates, False) + if feerates is not None: + node.set_feerates(feerates, False) self.nodes.append(node) self.reserved_ports.append(port) diff --git a/devtools/checkchannels.c b/devtools/checkchannels.c index 538912938fee..d70407b14868 100644 --- a/devtools/checkchannels.c +++ b/devtools/checkchannels.c @@ -109,6 +109,7 @@ static void copy_column(void *dst, size_t size, int main(int argc, char *argv[]) { + char *config_filename, *base_dir; char *net_dir, *rpc_filename, *hsmfile, *dbfile; sqlite3 *sql; sqlite3_stmt *stmt; @@ -124,7 +125,8 @@ int main(int argc, char *argv[]) setup_option_allocators(); - minimal_config_opts(top_ctx, argc, argv, &net_dir, &rpc_filename); + minimal_config_opts(top_ctx, argc, argv, &config_filename, &base_dir, + &net_dir, &rpc_filename); opt_register_noarg("-v|--verbose", opt_set_bool, &verbose, "Print everything"); diff --git a/doc/Makefile b/doc/Makefile index 021a2eb2d6af..692203230ba4 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -87,6 +87,8 @@ MANPAGES := doc/lightning-cli.1 \ doc/lightning-preapproveinvoice.7 \ doc/lightning-preapprovekeysend.7 \ doc/lightning-recoverchannel.7 \ + doc/lightning-renepay.7 \ + doc/lightning-renepaystatus.7 \ doc/lightning-reserveinputs.7 \ doc/lightning-sendinvoice.7 \ doc/lightning-sendonion.7 \ diff --git a/doc/developers-guide/plugin-development/hooks.md b/doc/developers-guide/plugin-development/hooks.md index 8ee40495a90a..3e31959bee1d 100644 --- a/doc/developers-guide/plugin-development/hooks.md +++ b/doc/developers-guide/plugin-development/hooks.md @@ -5,7 +5,7 @@ hidden: false createdAt: "2023-02-03T08:57:58.166Z" updatedAt: "2023-02-21T15:08:30.254Z" --- -Hooks allow a plugin to define custom behavior for `lightningd` without having to modify the Core Lightning source code itself. A plugin declares that it'd like to be consulted on what to do next for certain events in the daemon. A hook can then decide how `lightningd` should +Hooks allow a plugin to define custom behavior for `lightningd` without having to modify the Core Lightning source code itself. A plugin declares that it'd like to be consulted on what to do next for certain events in the daemon. A hook can then decide how `lightningd` should react to the given event. When hooks are registered, they can optionally specify "before" and "after" arrays of plugin names, which control what order they will be called in. If a plugin name is unknown, it is ignored, otherwise if the hook calls cannot be ordered to satisfy the specifications of all plugin hooks, the plugin registration will fail. @@ -22,7 +22,7 @@ In `chain`-mode multiple plugins can register for the hook type and they are cal -The remainder of the response is ignored and if there are any more plugins that have registered the hook the next one gets called. If there are no more plugins then the internal handling is resumed as if no hook had been called. Any other result returned by a plugin is considered an exit from the chain. Upon exit no more plugin hooks are called for the current event, and +The remainder of the response is ignored and if there are any more plugins that have registered the hook the next one gets called. If there are no more plugins then the internal handling is resumed as if no hook had been called. Any other result returned by a plugin is considered an exit from the chain. Upon exit no more plugin hooks are called for the current event, and the result is executed. Unless otherwise stated all hooks are `single`-mode. Hooks and notifications are very similar, however there are a few key differences: @@ -102,7 +102,7 @@ The `commitment_revocation` hook is a chained hook, i.e., multiple plugins can r ### `db_write` -This hook is called whenever a change is about to be committed to the database, if you are using a SQLITE3 database (the default). +This hook is called whenever a change is about to be committed to the database, if you are using a SQLITE3 database (the default). This hook will be useless (the `"writes"` field will always be empty) if you are using a PostgreSQL database. It is currently extremely restricted: @@ -132,26 +132,26 @@ This hook is intended for creating continuous backups. The intent is that your b `data_version` is an unsigned 32-bit number that will always increment by 1 each time `db_write` is called. Note that this will wrap around on the limit of 32-bit numbers. -`writes` is an array of strings, each string being a database query that modifies the database. +`writes` is an array of strings, each string being a database query that modifies the database. If the `data_version` above is validated correctly, then you can simply append this to the log of database queries. Your plugin **MUST** validate the `data_version`. It **MUST** keep track of the previous `data_version` it got, and: 1. If the new `data_version` is **_exactly_** one higher than the previous, then this is the ideal case and nothing bad happened and we should save this and continue. -2. If the new `data_version` is **_exactly_** the same value as the previous, then the previous set of queries was not committed. - Your plugin **MAY** overwrite the previous set of queries with the current set, or it **MAY** overwrite its entire backup with a new snapshot of the database and the current `writes` +2. If the new `data_version` is **_exactly_** the same value as the previous, then the previous set of queries was not committed. + Your plugin **MAY** overwrite the previous set of queries with the current set, or it **MAY** overwrite its entire backup with a new snapshot of the database and the current `writes` array (treating this case as if `data_version` were two or more higher than the previous). 3. If the new `data_version` is **_less than_** the previous, your plugin **MUST** halt and catch fire, and have the operator inspect what exactly happend here. 4. Otherwise, some queries were lost and your plugin **SHOULD** recover by creating a new snapshot of the database: copy the database file, back up the given `writes` array, then delete (or atomically `rename` if in a POSIX filesystem) the previous backups of the database and SQL statements, or you **MAY** fail the hook to abort `lightningd`. The "rolling up" of the database could be done periodically as well if the log of SQL statements has grown large. -Any response other than `{"result": "continue"}` will cause lightningd to error without -committing to the database! +Any response other than `{"result": "continue"}` will cause lightningd to error without +committing to the database! This is the expected way to halt and catch fire. -`db_write` is a parallel-chained hook, i.e., multiple plugins can register it, and all of them will be invoked simultaneously without regard for order of registration. -The hook is considered handled if all registered plugins return `{"result": "continue"}`. +`db_write` is a parallel-chained hook, i.e., multiple plugins can register it, and all of them will be invoked simultaneously without regard for order of registration. +The hook is considered handled if all registered plugins return `{"result": "continue"}`. If any plugin returns anything else, `lightningd` will error without committing to the database. ### `invoice_payment` @@ -170,7 +170,7 @@ This hook is called whenever a valid payment for an unpaid invoice has arrived. -The hook is deliberately sparse, since the plugin can use the JSON-RPC `listinvoices` command to get additional details about this invoice. It can return a `failure_message` field as defined for final nodes in [BOLT 4](https://github.com/lightning/bolts/blob/master/04-onion-routing.md#failure-messages), a `result` field with the string +The hook is deliberately sparse, since the plugin can use the JSON-RPC `listinvoices` command to get additional details about this invoice. It can return a `failure_message` field as defined for final nodes in [BOLT 4](https://github.com/lightning/bolts/blob/master/04-onion-routing.md#failure-messages), a `result` field with the string `reject` to fail it with `incorrect_or_unknown_payment_details`, or a `result` field with the string `continue` to accept the payment. ### `openchannel` @@ -208,7 +208,7 @@ e.g. ```json { "result": "continue", - "close_to": "bc1qlq8srqnz64wgklmqvurv7qnr4rvtq2u96hhfg2" + "close_to": "bc1qlq8srqnz64wgklmqvurv7qnr4rvtq2u96hhfg2", "mindepth": 0, "reserve": "1234sat" } @@ -218,12 +218,12 @@ e.g. Note that `close_to` must be a valid address for the current chain, an invalid address will cause the node to exit with an error. -- `mindepth` is the number of confirmations to require before making the channel usable. Notice that setting this to 0 (`zeroconf`) or some other low value might expose you to double-spending issues, so only lower this value from the default if you trust the peer not to +- `mindepth` is the number of confirmations to require before making the channel usable. Notice that setting this to 0 (`zeroconf`) or some other low value might expose you to double-spending issues, so only lower this value from the default if you trust the peer not to double-spend, or you reject incoming payments, including forwards, until the funding is confirmed. - `reserve` is an absolute value for the amount in the channel that the peer must keep on their side. This ensures that they always have something to lose, so only lower this below the 1% of funding amount if you trust the peer. The protocol requires this to be larger than the dust limit, hence it will be adjusted to be the dust limit if the specified value is below. -Note that `openchannel` is a chained hook. Therefore `close_to`, `reserve` will only be +Note that `openchannel` is a chained hook. Therefore `close_to`, `reserve` will only be evaluated for the first plugin that sets it. If more than one plugin tries to set a `close_to` address an error will be logged. ### `openchannel2` @@ -245,7 +245,7 @@ This hook is called whenever a remote peer tries to fund a channel to us using t "feerate_our_min": 253, "to_self_delay": 5, "max_accepted_htlcs": 483, - "channel_flags": 1 + "channel_flags": 1, "locktime": 2453, "channel_max_msat": 16777215000, "requested_lease_msat": 100000000, @@ -259,7 +259,7 @@ This hook is called whenever a remote peer tries to fund a channel to us using t There may be additional fields, such as `shutdown_scriptpubkey`. You can see the definitions of these fields in [BOLT 2's description of the open_channel message](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-open_channel-message). -`requested_lease_msat`, `lease_blockheight_start`, and `node_blockheight` are +`requested_lease_msat`, `lease_blockheight_start`, and `node_blockheight` are only present if the opening peer has requested a funding lease, per `option_will_fund`. The returned result must contain a `result` member which is either the string `reject` or `continue`. If `reject` and there's a member `error_message`, that member is sent to the peer before disconnection. @@ -275,7 +275,7 @@ e.g. ```json { "result": "continue", - "close_to": "bc1qlq8srqnz64wgklmqvurv7qnr4rvtq2u96hhfg2" + "close_to": "bc1qlq8srqnz64wgklmqvurv7qnr4rvtq2u96hhfg2", "psbt": "cHNidP8BADMCAAAAAQ+yBipSVZrrw28Oed52hTw3N7t0HbIyZhFdcZRH3+61AQAAAAD9////AGYAAAAAAQDfAgAAAAABARtaSZufCbC+P+/G23XVaQ8mDwZQFW1vlCsCYhLbmVrpAAAAAAD+////AvJs5ykBAAAAFgAUT6ORgb3CgFsbwSOzNLzF7jQS5s+AhB4AAAAAABepFNi369DMyAJmqX2agouvGHcDKsZkhwJHMEQCIHELIyqrqlwRjyzquEPvqiorzL2hrvdu9EBxsqppeIKiAiBykC6De/PDElnqWw49y2vTqauSJIVBgGtSc+vq5BQd+gEhAg0f8WITWvA8o4grxNKfgdrNDncqreMLeRFiteUlne+GZQAAAAEBIICEHgAAAAAAF6kU2Lfr0MzIAmapfZqCi68YdwMqxmSHAQQWABQB+tkKvNZml+JZIWRyLeSpXr7hZQz8CWxpZ2h0bmluZwEIexhVcpJl8ugM/AlsaWdodG5pbmcCAgABAA==", "our_funding_msat": 39999000 } @@ -302,7 +302,7 @@ This hook is called when we received updates to the funding transaction from the -In return, we expect a `result` indicated to `continue` and an updated `psbt`. +In return, we expect a `result` indicated to `continue` and an updated `psbt`. If we have no updates to contribute, return the passed in PSBT. Once no changes to the PSBT are made on either side, the transaction construction negotiation will end and commitment transactions will be exchanged. #### Expected Return @@ -367,7 +367,7 @@ Similar to `openchannel2`, the `rbf_channel` hook is called when a peer requests "feerate_our_min": 253, "channel_max_msat": 16777215000, "locktime": 2453, - "requested_lease_msat": 100000000, + "requested_lease_msat": 100000000 } } ``` @@ -491,8 +491,8 @@ Instead of `failure_message` the response can contain a hex-encoded `failure_oni `resolve` instructs `lightningd` to claim the HTLC by providing the preimage matching the `payment_hash` presented in the call. Notice that the plugin must ensure that the `payment_key` really matches the `payment_hash` since `lightningd` will not check and the wrong value could result in the channel being closed. -> šŸš§ -> +> šŸš§ +> > `lightningd` will replay the HTLCs for which it doesn't have a final verdict during startup. This means that, if the plugin response wasn't processed before the HTLC was forwarded, failed, or resolved, then the plugin may see the same HTLC again during startup. It is therefore paramount that the plugin is idempotent if it talks to an external system. The `htlc_accepted` hook is a chained hook, i.e., multiple plugins can register it, and they will be called in the order they were registered in until the first plugin return a result that is not `{"result": "continue"}`, after which the event is considered to be handled. After the event has been handled the remaining plugins will be skipped. @@ -590,7 +590,7 @@ The payload for a call follows this format: -This payload would have been sent by the peer with the `node_id` matching `peer_id`, and the message has type `0x1337` and contents `ffffffff`. Notice that the messages are currently limited to odd-numbered types and must not match a type that is handled internally by Core Lightning. These limitations are in place in order to avoid conflicts with the internal state tracking, and avoiding disconnections or channel closures, since odd-numbered message can be +This payload would have been sent by the peer with the `node_id` matching `peer_id`, and the message has type `0x1337` and contents `ffffffff`. Notice that the messages are currently limited to odd-numbered types and must not match a type that is handled internally by Core Lightning. These limitations are in place in order to avoid conflicts with the internal state tracking, and avoiding disconnections or channel closures, since odd-numbered message can be ignored by nodes (see ["it's ok to be odd" in the specification](https://github.com/lightning/bolts/blob/c74a3bbcf890799d343c62cb05fcbcdc952a1cf3/01-messaging.md#lightning-message-format) for details). The plugin must implement the parsing of the message, including the type prefix, since Core Lightning does not know how to parse the message. Because this is a chained hook, the daemon expects the result to be `{'result': 'continue'}`. It will fail if something else is returned. diff --git a/doc/getting-started/getting-started/installation.md b/doc/getting-started/getting-started/installation.md index 30d2cccc3603..6c6e5e673bce 100644 --- a/doc/getting-started/getting-started/installation.md +++ b/doc/getting-started/getting-started/installation.md @@ -310,8 +310,10 @@ Use nix-shell launch a shell with a full Core Lightning dev environment: ```shell $ nix-shell -Q -p gdb sqlite autoconf git clang libtool sqlite autoconf \ -autogen automake libsodium 'python3.withPackages (p: [p.bitcoinlib])' \ -valgrind --run make +autogen automake gmp zlib gettext libsodium poetry 'python3.withPackages (p: [p.bitcoinlib])' \ +valgrind --run "./configure && poetry shell" +$ poetry install +$ make ``` ## To Build on macOS diff --git a/doc/index.rst b/doc/index.rst index ebbb5412fb69..d59d7700cf9f 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -99,6 +99,8 @@ Core Lightning Documentation lightning-preapproveinvoice lightning-preapprovekeysend lightning-recoverchannel + lightning-renepay + lightning-renepaystatus lightning-reserveinputs lightning-sendcustommsg lightning-sendinvoice diff --git a/doc/lightning-getroute.7.md b/doc/lightning-getroute.7.md index 1b59cde18a9a..ff3777f31d0b 100644 --- a/doc/lightning-getroute.7.md +++ b/doc/lightning-getroute.7.md @@ -17,7 +17,9 @@ arrive at *id* with *cltv*-blocks to spare (default 9). *amount\_msat* is in millisatoshi precision; it can be a whole number, or a whole number ending in *msat* or *sat*, or a number with three decimal places ending in *sat*, or a number with 1 to 11 decimal places ending -in *btc*. +in *btc*. The 0 value is special: it ignores any *htlc\_minimum\_msat* +setting on channels, and simply returns a possible route (if any) which +is useful for simple probing. There are two considerations for how good a route is: how low the fees are, and how long your payment will get stuck in a delayed output if a diff --git a/doc/lightning-renepay.7.md b/doc/lightning-renepay.7.md new file mode 100644 index 000000000000..826ae9bbb6f4 --- /dev/null +++ b/doc/lightning-renepay.7.md @@ -0,0 +1,152 @@ +lightning-renepay -- Command for sending a payment to a BOLT11 invoice +====================================================================== + +SYNOPSIS +-------- + +**renepay** *invstring* [*amount\_msat*] [*maxfee*] [*maxdelay*] +[*retry\_for*] [*description*] [*label*] + + +DESCRIPTION +----------- + +**renepay** is a new payment plugin based on Pickhardt-Richter optimization +method for Multi-Path-Payments. This implementation has not been thoroughly +tested and it should be used with caution. + +The **renepay** RPC command attempts to pay the invoice specified +as *invstring*. Currently, **renepay** supports bolt11 invoices only. + +The response will occur when the payment fails or succeeds. Once a +payment has succeeded, calls to **renepay** with the same *invstring* +will not lead to a new payment attempt, but instead it will succeed immediately. + +If the *invstring* does not contain an amount, +*amount\_msat* is required, otherwise if it is specified +it must be *null*. *amount\_msat* is in millisatoshi precision; it can be a +whole number, or a whole number with suffix *msat* or *sat*, or a three +decimal point number with suffix *sat*, or an 1 to 11 decimal point +number suffixed by *btc*. + +*maxfee* limits how much is paid fees and it is measured in millisatoshi +by default, but also in this case the unit can be specified with a suffix: *msat*, *sat* or *btc*. +The default value is 5 sats or 0.5% whichever is higher. + +*maxdelay* overrides the value of `max-locktime-blocks` for this payment. +It serves to limit the locktime of funds in the payment HTLC measured in blocks. + +*retry\_for* measured in seconds (default: 60) specifies how much time it is +allowed for the command to keep retrying the payment. + +*description* is only required for bolt11 invoices which do not +contain a description themselves, but contain a description hash: +in this case *description* is required. +*description* is then checked against the hash inside the invoice +before it will be paid. + +The *label* field is used to attach a label to payments, and is returned +in lightning-listpays(7) and lightning-listsendpays(7). + +When using *lightning-cli*, you may skip optional parameters by using +*null*. Alternatively, use **-k** option to provide parameters by name. + + +OPTIMALITY +---------- + +**renepay** is based on the work by Pickhardt-Richter's +*Optimally Reliable & Cheap Payment Flows on the Lightning Network*. +Which means the payment command will prefer routes that have a higher +probability of success while keeping fees low. + +The algorithm records some partial knowledge of the state of the Network +deduced from the responses obtained after evey payment attempt. +This knowledge is kept through different payment requests, but decays with time +to account for the dynamics of the Network (after 1 hour all previous knowledge +will be erased). +Knowledge from previous payment attempts increases the reliability for +subsequent ones. + +Higher probabilities of success and lower fees cannot generally by optimized at +once. Hence **renepay** combines the two in different amounts seeking solutions +that satisfy *maxfee* bound and a target for 90% probability of success. +*maxfee* is a hard bound, in the sense that the command will never attempt a +payment when the fees exceed that value. While the probability target is not +compulsory (but desirable), i.e. if the best route does not satisfy the +90% probability target it will be tried anyways. + +When *maxfee* and the 90% probability bounds are satified, the algorithm will +optimize the fees to its lowest value. + + +RANDOMIZATION +------------- + +To protect user privacy, the payment algorithm performs *shadow route* +randomization. +Which means the payment algorithm will virtually extend the route +by adding delays and fees along it, making it appear to intermediate nodes +that the route is longer than it actually is. This prevents intermediate +nodes from reliably guessing their distance from the payee. + +Route randomization will never exceed *maxfee* of the payment. +Route randomization and shadow routing will not take routes that would +exceed *maxdelay*. + +RETURN VALUE +------------ + +[comment]: # (GENERATE-FROM-SCHEMA-START) +On success, an object is returned, containing: + +- **payment\_preimage** (secret): the proof of payment: SHA256 of this **payment\_hash** +- **payment\_hash** (hash): the hash of the *payment\_preimage* which will prove payment +- **created\_at** (number): the UNIX timestamp showing when this payment was initiated +- **parts** (u32): how many attempts this took +- **amount\_msat** (msat): amount the recipient received +- **amount\_sent\_msat** (msat): total amount we sent (including fees) +- **status** (string): status of payment (one of "complete", "pending", "failed") +- **destination** (pubkey, optional): the final destination of the payment + +[comment]: # (GENERATE-FROM-SCHEMA-END) + +You can monitor the progress and retries of a payment using the +lightning-renepaystatus(7) command. + +The following error codes may occur: + +-1: Catchall nonspecific error. + +200: Other payment attempts are in progress. + +203: Permanent failure at destination. + +205: Unable to find a route. + +206: Payment routes are too expensive. + +207: Invoice expired. Payment took too long before expiration, or +already expired at the time you initiated payment. + +210: Payment timed out without a payment in progress. + +212: Invoice is invalid. + +AUTHOR +------ + +Eduardo Quintana-Miranda <> is mainly responsible. + +SEE ALSO +-------- + +lightning-renepaystatus(7), lightning-listpays(7), lightning-invoice(7). + +RESOURCES +--------- + +- Main web site: +- Pickhardt R. and Richter S., *Optimally Reliable & Cheap Payment Flows on the Lightning Network* + +[comment]: # ( SHA256STAMP:505a2ea336078020826b5897f2db02d4c4e0e03a9561170458afae008e47e06e) diff --git a/doc/lightning-renepaystatus.7.md b/doc/lightning-renepaystatus.7.md new file mode 100644 index 000000000000..df78aabe7624 --- /dev/null +++ b/doc/lightning-renepaystatus.7.md @@ -0,0 +1,55 @@ +lightning-renepaystatus -- Command for quering the status of previous renepay attempts +====================================================================================== + +SYNOPSIS +-------- + +**renepaystatus** [*invstring*] + + +DESCRIPTION +----------- + +The **renepaystatus** RPC command queries the payment plugin **renepay** +for the status of previous payment attempts. +If *invstring* is specified, the command will return a list of payment attempts +whose invoice matches *invstring*, otherwise all payments with be listed. +This command always succeeds. + +RETURN VALUE +------------ + +[comment]: # (GENERATE-FROM-SCHEMA-START) +On success, an object containing **paystatus** is returned. It is an array of objects, where each object contains: + +- **bolt11** (string): invoice string BOLT11 +- **payment\_hash** (hash): the hash of the *payment\_preimage* which will prove payment +- **created\_at** (number): the UNIX timestamp showing when this payment was initiated +- **groupid** (u32): the id for this payment attempt +- **amount\_msat** (msat): amount the recipient received +- **status** (string): status of payment (one of "complete", "pending", "failed") +- **notes** (array of strings): a list of messages for debugging purposes: + - a message generated by renepay +- **payment\_preimage** (secret, optional): the proof of payment: SHA256 of this **payment\_hash** (for completed payments only) +- **parts** (u32, optional): how many attempts this took +- **amount\_sent\_msat** (msat, optional): total amount we sent including fees (for completed payments only) +- **destination** (pubkey, optional): the final destination of the payment + +[comment]: # (GENERATE-FROM-SCHEMA-END) + +AUTHOR +------ + +Eduardo Quintana-Miranda <> is mainly responsible. + +SEE ALSO +-------- + +lightning-renepay(7), lightning-listpays(7). + +RESOURCES +--------- + +Main web site: + +[comment]: # ( SHA256STAMP:3dfae7499b76853c08d307d8d723933ab680f6827ff079569af97ba2dda03833) diff --git a/doc/schemas/renepay.schema.json b/doc/schemas/renepay.schema.json new file mode 100644 index 000000000000..2700a5e92608 --- /dev/null +++ b/doc/schemas/renepay.schema.json @@ -0,0 +1,54 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "added": "v23.08", + "type": "object", + "additionalProperties": false, + "required": [ + "payment_preimage", + "payment_hash", + "created_at", + "parts", + "amount_msat", + "amount_sent_msat", + "status" + ], + "properties": { + "payment_preimage": { + "type": "secret", + "description": "the proof of payment: SHA256 of this **payment_hash**" + }, + "payment_hash": { + "type": "hash", + "description": "the hash of the *payment_preimage* which will prove payment" + }, + "created_at": { + "type": "number", + "description": "the UNIX timestamp showing when this payment was initiated" + }, + "parts": { + "type": "u32", + "description": "how many attempts this took" + }, + "amount_msat": { + "type": "msat", + "description": "amount the recipient received" + }, + "amount_sent_msat": { + "type": "msat", + "description": "total amount we sent (including fees)" + }, + "status": { + "type": "string", + "enum": [ + "complete", + "pending", + "failed" + ], + "description": "status of payment" + }, + "destination": { + "type": "pubkey", + "description": "the final destination of the payment" + } + } +} diff --git a/doc/schemas/renepaystatus.schema.json b/doc/schemas/renepaystatus.schema.json new file mode 100644 index 000000000000..03da45dc14d8 --- /dev/null +++ b/doc/schemas/renepaystatus.schema.json @@ -0,0 +1,83 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "added": "v23.08", + "type": "object", + "additionalProperties": false, + "required": [ + "paystatus" + ], + "properties": { + "paystatus": { + "type": "array", + "description": "a list of payments attempted by renepay", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "bolt11", + "payment_hash", + "created_at", + "groupid", + "amount_msat", + "status", + "notes" + ], + "properties": { + "bolt11": { + "type": "string", + "description": "invoice string BOLT11" + }, + "payment_preimage": { + "type": "secret", + "description": "the proof of payment: SHA256 of this **payment_hash** (for completed payments only)" + }, + "payment_hash": { + "type": "hash", + "description": "the hash of the *payment_preimage* which will prove payment" + }, + "created_at": { + "type": "number", + "description": "the UNIX timestamp showing when this payment was initiated" + }, + "groupid": { + "type": "u32", + "description": "the id for this payment attempt" + }, + "parts": { + "type": "u32", + "description": "how many attempts this took" + }, + "amount_msat": { + "type": "msat", + "description": "amount the recipient received" + }, + "amount_sent_msat": { + "type": "msat", + "description": "total amount we sent including fees (for completed payments only)" + }, + "status": { + "type": "string", + "enum": [ + "complete", + "pending", + "failed" + ], + "description": "status of payment" + }, + "destination": { + "type": "pubkey", + "description": "the final destination of the payment" + }, + "notes": { + "type": "array", + "description": "a list of messages for debugging purposes", + "items": { + "type": "string", + "description": "a message generated by renepay" + } + } + } + } + } + } +} diff --git a/gossipd/gossip_store.c b/gossipd/gossip_store.c index d16866e8f9ff..248fd81d3242 100644 --- a/gossipd/gossip_store.c +++ b/gossipd/gossip_store.c @@ -926,8 +926,9 @@ u32 gossip_store_load(struct routing_state *rstate, struct gossip_store *gs) if (!routing_add_node_announcement(rstate, take(msg), gs->len, NULL, NULL, spam)) { - bad = "Bad node_announcement"; - goto badmsg; + /* FIXME: This has been reported: routing.c + * has logged, so ignore. */ + break; } stats[2]++; break; diff --git a/gossipd/gossipd.c b/gossipd/gossipd.c index c0b708442064..554a82b2854c 100644 --- a/gossipd/gossipd.c +++ b/gossipd/gossipd.c @@ -419,6 +419,9 @@ static void handle_discovered_ip(struct daemon *daemon, const u8 *msg) maybe_send_own_node_announce(daemon, false); } +/* Statistically, how many peers to we tell about each channel? */ +#define GOSSIP_SPAM_REDUNDANCY 5 + /* BOLT #7: * - if the `gossip_queries` feature is negotiated: * - MUST NOT relay any gossip messages it did not generate itself, @@ -430,15 +433,16 @@ static void handle_discovered_ip(struct daemon *daemon, const u8 *msg) static void dump_our_gossip(struct daemon *daemon, struct peer *peer) { struct node *me; - struct chan_map_iter i; - struct chan *chan; + struct chan_map_iter it; + const struct chan *chan, **chans = tal_arr(tmpctx, const struct chan *, 0); + size_t num_to_send; /* Find ourselves; if no channels, nothing to send */ me = get_node(daemon->rstate, &daemon->id); if (!me) return; - for (chan = first_chan(me, &i); chan; chan = next_chan(me, &i)) { + for (chan = first_chan(me, &it); chan; chan = next_chan(me, &it)) { /* Don't leak private channels, unless it's with you! */ if (!is_chan_public(chan)) { int dir = half_chan_idx(me, chan); @@ -452,6 +456,26 @@ static void dump_our_gossip(struct daemon *daemon, struct peer *peer) continue; } + tal_arr_expand(&chans, chan); + } + + /* Just in case we have many peers and not all are connecting or + * some other corner case, send everything to first few. */ + if (peer_node_id_map_count(daemon->peers) <= GOSSIP_SPAM_REDUNDANCY) + num_to_send = tal_count(chans); + else { + if (tal_count(chans) < GOSSIP_SPAM_REDUNDANCY) + num_to_send = tal_count(chans); + else { + /* Pick victims at random */ + tal_arr_randomize(chans, const struct chan *); + num_to_send = GOSSIP_SPAM_REDUNDANCY; + } + } + + for (size_t i = 0; i < num_to_send; i++) { + chan = chans[i]; + /* Send channel_announce */ queue_peer_from_store(peer, &chan->bcast); diff --git a/gossipd/routing.c b/gossipd/routing.c index fb2a400d2678..bc1993d19dcf 100644 --- a/gossipd/routing.c +++ b/gossipd/routing.c @@ -22,6 +22,11 @@ /* 365.25 * 24 * 60 Groestlcoin */ #define BLOCKS_PER_YEAR 525960 +struct pending_spam_node_announce { + u8 *node_announcement; + u32 index; +}; + struct pending_node_announce { struct routing_state *rstate; struct node_id nodeid; @@ -31,6 +36,8 @@ struct pending_node_announce { u32 index; /* If non-NULL this is peer to credit it with */ struct node_id *source_peer; + /* required for loading gossip store */ + struct pending_spam_node_announce spam; }; /* As per the below BOLT #7 quote, we delay forgetting a channel until 12 @@ -776,6 +783,8 @@ static void catch_node_announcement(const tal_t *ctx, pna->index = 0; pna->refcount = 0; pna->source_peer = NULL; + pna->spam.node_announcement = NULL; + pna->spam.index = 0; pending_node_map_add(rstate->pending_node_map, pna); } pna->refcount++; @@ -805,6 +814,22 @@ static void process_pending_node_announcement(struct routing_state *rstate, /* Never send this again. */ pna->node_announcement = tal_free(pna->node_announcement); } + if (pna->spam.node_announcement) { + SUPERVERBOSE( + "Processing deferred node_announcement for node %s", + type_to_string(pna, struct node_id, nodeid)); + + /* Can fail it timestamp is now too old */ + if (!routing_add_node_announcement(rstate, + pna->spam.node_announcement, + pna->spam.index, + NULL, NULL, + true)) + status_unusual("pending node_announcement %s too old?", + tal_hex(tmpctx, pna->spam.node_announcement)); + /* Never send this again. */ + pna->spam.node_announcement = tal_free(pna->spam.node_announcement); + } /* We don't need to catch any more node_announcements, since we've * accepted the public channel now. But other pending announcements @@ -1821,12 +1846,20 @@ bool routing_add_node_announcement(struct routing_state *rstate, SUPERVERBOSE("Deferring node_announcement for node %s", type_to_string(tmpctx, struct node_id, &node_id)); - pna->timestamp = timestamp; - pna->index = index; - tal_free(pna->node_announcement); - tal_free(pna->source_peer); - pna->node_announcement = tal_dup_talarr(pna, u8, msg); - pna->source_peer = tal_dup_or_null(pna, struct node_id, source_peer); + /* a pending spam node announcement is possible when loading + * from the store */ + if (index && force_spam_flag) { + tal_free(pna->spam.node_announcement); + pna->spam.node_announcement = tal_dup_talarr(pna, u8, msg); + pna->spam.index = index; + } else { + tal_free(pna->node_announcement); + tal_free(pna->source_peer); + pna->node_announcement = tal_dup_talarr(pna, u8, msg); + pna->source_peer = tal_dup_or_null(pna, struct node_id, source_peer); + pna->timestamp = timestamp; + pna->index = index; + } return true; } diff --git a/lightningd/channel.c b/lightningd/channel.c index d830aca527bc..60d1c19cd48e 100644 --- a/lightningd/channel.c +++ b/lightningd/channel.c @@ -176,6 +176,7 @@ new_inflight(struct channel *channel, inflight->lease_amt = lease_amt; inflight->i_am_initiator = i_am_initiator; + inflight->splice_locked_memonly = false; list_add_tail(&channel->inflights, &inflight->list); tal_add_destructor(inflight, destroy_inflight); diff --git a/lightningd/channel.h b/lightningd/channel.h index cb3d6eeb9249..33ad7d9ef38b 100644 --- a/lightningd/channel.h +++ b/lightningd/channel.h @@ -63,6 +63,16 @@ struct channel_inflight { /* Did I initate this splice attempt? */ bool i_am_initiator; + + /* Note: This field is not stored in the database. + * + * After splice_locked, we need a way to stop the chain watchers from + * thinking the old channel was spent. + * + * Leaving the inflight in memory-only with splice_locked true leaves + * moves the responsiblity of cleaning up the inflight to the watcher, + * avoiding any potential race conditions. */ + bool splice_locked_memonly; }; struct open_attempt { diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c index cd59faf9154a..1d03efcad41a 100644 --- a/lightningd/channel_control.c +++ b/lightningd/channel_control.c @@ -645,8 +645,10 @@ static void handle_update_inflight(struct lightningd *ld, struct bitcoin_txid, &txid)); - if (last_tx) - inflight->last_tx = tal_steal(inflight, last_tx); + if (last_tx) { + tal_free(inflight->last_tx); + inflight->last_tx = clone_bitcoin_tx(inflight, last_tx); + } if (last_sig) inflight->last_sig = *last_sig; @@ -825,10 +827,21 @@ static void handle_peer_splice_locked(struct channel *channel, const u8 *msg) /* Remember that we got the lockin */ wallet_channel_save(channel->peer->ld->wallet, channel); - /* Empty out the inflights */ log_debug(channel->log, "lightningd, splice_locked clearing inflights"); + + /* Take out the successful inflight from the list temporarily */ + list_del(&inflight->list); + wallet_channel_clear_inflights(channel->peer->ld->wallet, channel); + /* Put the successful inflight back in as a memory-only object. + * peer_control's funding_spent function will pick this up and clean up + * our inflight. + * + * This prevents any potential race conditions between us and them. */ + inflight->splice_locked_memonly = true; + list_add_tail(&channel->inflights, &inflight->list); + lockin_complete(channel, CHANNELD_AWAITING_SPLICE); } @@ -1394,7 +1407,12 @@ bool peer_start_channeld(struct channel *channel, inflights = tal_arr(tmpctx, struct inflight *, 0); list_for_each(&channel->inflights, inflight, list) { - struct inflight *infcopy = tal(inflights, struct inflight); + struct inflight *infcopy; + + if (inflight->splice_locked_memonly) + continue; + + infcopy = tal(inflights, struct inflight); infcopy->outpoint = inflight->funding->outpoint; infcopy->amnt = inflight->funding->total_funds; @@ -1869,7 +1887,7 @@ static struct command_result *param_channel_for_splice(struct command *cmd, if (!feature_negotiated(cmd->ld->our_features, (*channel)->peer->their_features, - OPT_SPLICE)) + OPT_EXPERIMENTAL_SPLICE)) return command_fail(cmd, SPLICE_NOT_SUPPORTED, "splicing not supported"); diff --git a/lightningd/closing_control.c b/lightningd/closing_control.c index 93d2602fd25e..797a2e48b00a 100644 --- a/lightningd/closing_control.c +++ b/lightningd/closing_control.c @@ -363,9 +363,8 @@ static unsigned closing_msg(struct subd *sd, const u8 *msg, const int *fds UNUSE void peer_start_closingd(struct channel *channel, struct peer_fd *peer_fd) { u8 *initmsg; - u32 min_feerate, feerate, *max_feerate; + u32 min_feerate, feerate, max_feerate; struct amount_msat their_msat; - struct amount_sat feelimit; int hsmfd; struct lightningd *ld = channel->peer->ld; u32 final_commit_feerate; @@ -404,19 +403,15 @@ void peer_start_closingd(struct channel *channel, struct peer_fd *peer_fd) return; } - /* FIXME: This is the old BOLT 2 text, which restricted the closing - * fee to cap at the final commitment fee. We still do this for now. - * + /* BOLT #2: * The sending node: - * - MUST set `fee_satoshis` less than or equal to the base - * fee of the final commitment transaction, as calculated in - * [BOLT #3](03-transactions.md#fee-calculation). + * - SHOULD set the initial `fee_satoshis` according to its estimate of cost of + * inclusion in a block. + * - SHOULD set `fee_range` according to the minimum and maximum fees it is + * prepared to pay for a close transaction. */ final_commit_feerate = get_feerate(channel->fee_states, channel->opener, LOCAL); - feelimit = commit_tx_base_fee(final_commit_feerate, 0, - option_anchor_outputs, - option_anchors_zero_fee_htlc_tx); /* If we can't determine feerate, start at half unilateral feerate. */ feerate = mutual_close_feerate(ld->topology); @@ -426,30 +421,20 @@ void peer_start_closingd(struct channel *channel, struct peer_fd *peer_fd) feerate = get_feerate_floor(ld->topology); } - /* We use a feerate if anchor_outputs, otherwise max fee is set by - * the final unilateral. */ - if (option_anchor_outputs || option_anchors_zero_fee_htlc_tx) { - max_feerate = tal(tmpctx, u32); - /* Aim for reasonable max, but use final if we don't know. */ - *max_feerate = unilateral_feerate(ld->topology, false); - if (!*max_feerate) - *max_feerate = final_commit_feerate; - /* No other limit on fees */ - feelimit = channel->funding_sats; - } else - max_feerate = NULL; + /* Aim for reasonable max, but use final if we don't know. */ + max_feerate = unilateral_feerate(ld->topology, false); + if (!max_feerate) + max_feerate = final_commit_feerate; min_feerate = feerate_min(ld, NULL); /* If they specified feerates in `close`, they apply now! */ if (channel->closing_feerate_range) { min_feerate = channel->closing_feerate_range[0]; - max_feerate = &channel->closing_feerate_range[1]; + max_feerate = channel->closing_feerate_range[1]; } else if (channel->ignore_fee_limits || ld->config.ignore_fee_limits) { - min_feerate = 1; - tal_free(max_feerate); - max_feerate = tal(tmpctx, u32); - *max_feerate = 0xFFFFFFFF; + min_feerate = 253; + max_feerate = 0xFFFFFFFF; } /* BOLT #3: @@ -506,7 +491,6 @@ void peer_start_closingd(struct channel *channel, struct peer_fd *peer_fd) amount_msat_to_sat_round_down(their_msat), channel->our_config.dust_limit, min_feerate, feerate, max_feerate, - feelimit, local_wallet_index, local_wallet_ext_key, channel->shutdown_scriptpubkey[LOCAL], diff --git a/lightningd/options.c b/lightningd/options.c index 5f835e5c20fc..62ffa6bdb4cd 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -1200,7 +1200,7 @@ static char *opt_set_splicing(struct lightningd *ld) OPTIONAL_FEATURE(OPT_QUIESCE)))); feature_set_or(ld->our_features, take(feature_set_for_feature(NULL, - OPTIONAL_FEATURE(OPT_SPLICE)))); + OPTIONAL_FEATURE(OPT_EXPERIMENTAL_SPLICE)))); return NULL; } @@ -1915,7 +1915,7 @@ void add_config_deprecated(struct lightningd *ld, json_add_bool(response, name0, feature_offered(ld->our_features ->bits[INIT_FEATURE], - OPT_SPLICE)); + OPT_EXPERIMENTAL_SPLICE)); } else if (opt->cb == (void *)opt_set_onion_messages) { json_add_bool(response, name0, feature_offered(ld->our_features diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index 8f992ffafa27..e68a79b3d49c 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -1953,18 +1953,26 @@ static enum watch_result funding_spent(struct channel *channel, bitcoin_txid(tx, &txid); - wallet_channeltxs_add(channel->peer->ld->wallet, channel, - WIRE_ONCHAIND_INIT, &txid, 0, block->height); - /* If we're doing a splice, we expect the funding transaction to be * spent, so don't freak out and just keep watching in that case */ list_for_each(&channel->inflights, inflight, list) { if (bitcoin_txid_eq(&txid, &inflight->funding->outpoint.txid)) { + /* splice_locked is a special flag that indicates this + * is a memory-only inflight acting as a race condition + * safeguard. When we see this, it is our responsability + * to clean up this memory-only inflight. */ + if (inflight->splice_locked_memonly) { + tal_free(inflight); + return DELETE_WATCH; + } return KEEP_WATCHING; } } + wallet_channeltxs_add(channel->peer->ld->wallet, channel, + WIRE_ONCHAIND_INIT, &txid, 0, block->height); + return onchaind_funding_spent(channel, tx, block->height); } diff --git a/lightningd/peer_htlcs.c b/lightningd/peer_htlcs.c index abcff1b46acd..301630b01ed3 100644 --- a/lightningd/peer_htlcs.c +++ b/lightningd/peer_htlcs.c @@ -2320,7 +2320,8 @@ void peer_got_commitsig(struct channel *channel, const u8 *msg) i = 0; list_for_each(&channel->inflights, inflight, list) { - i++; + if (!inflight->splice_locked_memonly) + i++; } if (i != tal_count(inflight_commit_sigs)) { channel_internal_error(channel, "Got commitsig with incorrect " @@ -2381,9 +2382,15 @@ void peer_got_commitsig(struct channel *channel, const u8 *msg) /* Now append htlc sigs for inflights */ i = 0; list_for_each(&channel->inflights, inflight, list) { - struct commitsig *commit = inflight_commit_sigs[i]; + struct commitsig *commit; + + if (inflight->splice_locked_memonly) + continue; + + commit = inflight_commit_sigs[i]; - inflight->last_tx = tal_steal(inflight, commit->tx); + tal_free(inflight->last_tx); + inflight->last_tx = clone_bitcoin_tx(inflight, commit->tx); inflight->last_tx->chainparams = chainparams; inflight->last_sig = commit->commit_signature; wallet_inflight_save(ld->wallet, inflight); diff --git a/lightningd/plugin.c b/lightningd/plugin.c index 8734dc6da24e..3555fc1c4575 100644 --- a/lightningd/plugin.c +++ b/lightningd/plugin.c @@ -374,6 +374,8 @@ void plugin_kill(struct plugin *plugin, enum log_level loglevel, log_(plugin->log, loglevel, NULL, loglevel >= LOG_UNUSUAL, "Killing plugin: %s", msg); + /* Unless, maybe, plugin was *really* important? */ + assert(plugin->pid != -1); kill(plugin->pid, SIGKILL); if (plugin->start_cmd) { plugin_cmd_killed(plugin->start_cmd, plugin, msg); @@ -1867,7 +1869,7 @@ bool plugins_send_getmanifest(struct plugins *plugins, const char *cmd_id) } if (plugins->startup) fatal("error starting plugin '%s': %s", p->cmd, err); - plugin_kill(p, LOG_UNUSUAL, "%s", err); + tal_free(p); } return sent; diff --git a/lightningd/plugin_control.c b/lightningd/plugin_control.c index 1d334a4cf6eb..9879f9903d31 100644 --- a/lightningd/plugin_control.c +++ b/lightningd/plugin_control.c @@ -74,13 +74,16 @@ plugin_dynamic_start(struct plugin_command *pcmd, const char *plugin_path, "%s: %s", plugin_path, errno ? strerror(errno) : "already registered"); - /* This will come back via plugin_cmd_killed or plugin_cmd_succeeded */ err = plugin_send_getmanifest(p, pcmd->cmd->id); - if (err) + if (err) { + /* Free plugin with cmd (it owns buffer and params!) */ + tal_steal(pcmd->cmd, p); return command_fail(pcmd->cmd, PLUGIN_ERROR, "%s: %s", plugin_path, err); + } + /* This will come back via plugin_cmd_killed or plugin_cmd_succeeded */ return command_still_pending(pcmd->cmd); } diff --git a/lightningd/runes.c b/lightningd/runes.c index 6eaa5835d6f6..a93d342cedff 100644 --- a/lightningd/runes.c +++ b/lightningd/runes.c @@ -193,43 +193,41 @@ static bool is_unique_id(struct rune_restr **restrs, unsigned int index) return streq(restrs[index]->alterns[0]->fieldname, ""); } +static char *fmt_cond(const tal_t *ctx, + const struct rune_altern *alt, + const char *cond_str) +{ + return tal_fmt(ctx, "%s %s %s", alt->fieldname, cond_str, alt->value); +} + static char *rune_altern_to_english(const tal_t *ctx, const struct rune_altern *alt) { - const char *cond_str; switch (alt->condition) { case RUNE_COND_IF_MISSING: return tal_strcat(ctx, alt->fieldname, " is missing"); case RUNE_COND_EQUAL: - cond_str = "equal to"; - break; + return fmt_cond(ctx, alt, "equal to"); case RUNE_COND_NOT_EQUAL: - cond_str = "unequal to"; - break; + return fmt_cond(ctx, alt, "unequal to"); case RUNE_COND_BEGINS: - cond_str = "starts with"; - break; + return fmt_cond(ctx, alt, "starts with"); case RUNE_COND_ENDS: - cond_str = "ends with"; - break; + return fmt_cond(ctx, alt, "ends with"); case RUNE_COND_CONTAINS: - cond_str = "contains"; - break; + return fmt_cond(ctx, alt, "contains"); case RUNE_COND_INT_LESS: - cond_str = "<"; - break; + return fmt_cond(ctx, alt, "<"); case RUNE_COND_INT_GREATER: - cond_str = ">"; - break; + return fmt_cond(ctx, alt, ">"); case RUNE_COND_LEXO_BEFORE: - cond_str = "sorts before"; - break; + return fmt_cond(ctx, alt, "sorts before"); case RUNE_COND_LEXO_AFTER: - cond_str = "sorts after"; - break; + return fmt_cond(ctx, alt, "sorts after"); case RUNE_COND_COMMENT: return tal_fmt(ctx, "comment: %s %s", alt->fieldname, alt->value); } - return tal_fmt(ctx, "%s %s %s", alt->fieldname, cond_str, alt->value); + + abort(); } static char *json_add_alternative(const tal_t *ctx, diff --git a/lightningd/test/run-invoice-select-inchan.c b/lightningd/test/run-invoice-select-inchan.c index 6e3a298d96e6..cb91683977a1 100644 --- a/lightningd/test/run-invoice-select-inchan.c +++ b/lightningd/test/run-invoice-select-inchan.c @@ -499,6 +499,10 @@ void json_add_num(struct json_stream *result UNNEEDED, const char *fieldname UNN void json_add_preimage(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, const struct preimage *preimage UNNEEDED) { fprintf(stderr, "json_add_preimage called!\n"); abort(); } +/* Generated stub for json_add_s64 */ +void json_add_s64(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, + int64_t value UNNEEDED) +{ fprintf(stderr, "json_add_s64 called!\n"); abort(); } /* Generated stub for json_add_secret */ void json_add_secret(struct json_stream *response UNNEEDED, const char *fieldname UNNEEDED, @@ -544,10 +548,6 @@ void json_add_u32(struct json_stream *result UNNEEDED, const char *fieldname UNN void json_add_u64(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, uint64_t value UNNEEDED) { fprintf(stderr, "json_add_u64 called!\n"); abort(); } -/* Generated stub for json_add_s64 */ -void json_add_s64(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, - int64_t value UNNEEDED) -{ fprintf(stderr, "json_add_s64 called!\n"); abort(); } /* Generated stub for json_add_uncommitted_channel */ void json_add_uncommitted_channel(struct json_stream *response UNNEEDED, const struct uncommitted_channel *uc UNNEEDED, @@ -798,12 +798,6 @@ struct command_result *param_u64(struct command *cmd UNNEEDED, const char *name const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, uint64_t **num UNNEEDED) { fprintf(stderr, "param_u64 called!\n"); abort(); } -/* Generated stub for channel_state_normalish */ -bool channel_state_normalish(const struct channel *channel UNNEEDED) -{ fprintf(stderr, "channel_state_normalish called!\n"); abort(); } -/* Generated stub for channel_state_awaitish */ -bool channel_state_awaitish(const struct channel *channel UNNEEDED) -{ fprintf(stderr, "channel_state_awaitish called!\n"); abort(); } /* Generated stub for peer_any_active_channel */ struct channel *peer_any_active_channel(struct peer *peer UNNEEDED, bool *others UNNEEDED) { fprintf(stderr, "peer_any_active_channel called!\n"); abort(); } diff --git a/plugins/bcli.c b/plugins/bcli.c index be2b92bdc486..6c82c0500d3f 100644 --- a/plugins/bcli.c +++ b/plugins/bcli.c @@ -298,16 +298,16 @@ static void next_bcli(enum bitcoind_prio prio) bcli->pid = pipecmdarr(&in, &bcli->fd, &bcli->fd, cast_const2(char **, bcli->args)); + if (bcli->pid < 0) + plugin_err(bcli->cmd->plugin, "%s exec failed: %s", + bcli->args[0], strerror(errno)); + if (bitcoind->rpcpass) write_all(in, bitcoind->rpcpass, strlen(bitcoind->rpcpass)); close(in); - if (bcli->pid < 0) - plugin_err(bcli->cmd->plugin, "%s exec failed: %s", - bcli->args[0], strerror(errno)); - bcli->start = time_now(); bitcoind->num_requests[prio]++; diff --git a/plugins/libplugin-pay.c b/plugins/libplugin-pay.c index b4ae81d6c13d..bafa2737e05b 100644 --- a/plugins/libplugin-pay.c +++ b/plugins/libplugin-pay.c @@ -2381,7 +2381,9 @@ local_channel_hints_listpeerchannels(struct command *cmd, const char *buffer, /* Filter out local channels if they are * either a) disconnected, or b) not in normal * state. */ - enabled = chans[i]->connected && streq(chans[i]->state, "CHANNELD_NORMAL"); + enabled = chans[i]->connected + && (streq(chans[i]->state, "CHANNELD_NORMAL") + || streq(chans[i]->state, "CHANNELD_AWAITING_SPLICE")); /* Take the configured number of max_htlcs and * subtract any HTLCs that might already be added to @@ -2521,7 +2523,7 @@ static struct route_info **filter_routehints(struct gossmap *map, } distance = dijkstra_distance( - dijkstra(tmpctx, map, entrynode, AMOUNT_MSAT(1), 1, + dijkstra(tmpctx, map, entrynode, AMOUNT_MSAT(0), 1, payment_route_can_carry_even_disabled, route_score_cheaper, p), gossmap_node_idx(map, src)); @@ -2763,12 +2765,12 @@ static void routehint_check_reachable(struct payment *p) if (dst == NULL) d->destination_reachable = false; else if (src != NULL) { - dij = dijkstra(tmpctx, gossmap, dst, AMOUNT_MSAT(1000), + dij = dijkstra(tmpctx, gossmap, dst, AMOUNT_MSAT(0), 10 / 1000000.0, payment_route_can_carry_even_disabled, route_score_cheaper, p); r = route_from_dijkstra(tmpctx, gossmap, dij, src, - AMOUNT_MSAT(1000), 0); + AMOUNT_MSAT(0), 0); /* If there was a route the destination is reachable * without routehints. */ diff --git a/plugins/renepay/pay.c b/plugins/renepay/pay.c index ff11721447df..b3ef3956a476 100644 --- a/plugins/renepay/pay.c +++ b/plugins/renepay/pay.c @@ -29,13 +29,7 @@ #define MAX(a,b) ((a)>(b)? (a) : (b)) #define MIN(a,b) ((a)<(b)? (a) : (b)) -static struct pay_plugin the_pay_plugin; -struct pay_plugin * const pay_plugin = &the_pay_plugin; - -static void timer_kick(struct renepay * renepay); -static struct command_result *try_paying(struct command *cmd, - struct renepay * renepay, - bool first_time); +struct pay_plugin *pay_plugin; void amount_msat_accumulate_(struct amount_msat *dst, struct amount_msat src, @@ -65,25 +59,17 @@ void amount_msat_reduce_(struct amount_msat *dst, #if DEVELOPER static void memleak_mark(struct plugin *p, struct htable *memtable) { - memleak_scan_obj(memtable, pay_plugin->ctx); - memleak_scan_obj(memtable, pay_plugin->gossmap); - memleak_scan_obj(memtable, pay_plugin->chan_extra_map); + memleak_scan_obj(memtable, pay_plugin); memleak_scan_htable(memtable, &pay_plugin->chan_extra_map->raw); } #endif -static void destroy_payflow(struct pay_flow *flow) -{ - remove_htlc_payflow(pay_plugin->chan_extra_map,flow); - payflow_map_del(pay_plugin->payflow_map, flow); -} - static const char *init(struct plugin *p, const char *buf UNUSED, const jsmntok_t *config UNUSED) { size_t num_channel_updates_rejected; - pay_plugin->ctx = notleak_with_children(tal(p,tal_t)); + tal_steal(p, pay_plugin); pay_plugin->plugin = p; pay_plugin->last_time = 0; @@ -101,13 +87,13 @@ static const char *init(struct plugin *p, list_head_init(&pay_plugin->payments); - pay_plugin->chan_extra_map = tal(pay_plugin->ctx,struct chan_extra_map); + pay_plugin->chan_extra_map = tal(pay_plugin,struct chan_extra_map); chan_extra_map_init(pay_plugin->chan_extra_map); - pay_plugin->payflow_map = tal(pay_plugin->ctx,struct payflow_map); + pay_plugin->payflow_map = tal(pay_plugin,struct payflow_map); payflow_map_init(pay_plugin->payflow_map); - pay_plugin->gossmap = gossmap_load(pay_plugin->ctx, + pay_plugin->gossmap = gossmap_load(pay_plugin, GOSSIP_STORE_FILENAME, &num_channel_updates_rejected); @@ -127,68 +113,25 @@ static const char *init(struct plugin *p, return NULL; } - -static void renepay_settimer(struct renepay * renepay) -{ - renepay->rexmit_timer = tal_free(renepay->rexmit_timer); - renepay->rexmit_timer = plugin_timer( - pay_plugin->plugin, - time_from_msec(TIMER_COLLECT_FAILURES_MSEC), - timer_kick, renepay); -} - -/* Happens when timer goes off, but also works to arm timer if nothing to do */ -static void timer_kick(struct renepay * renepay) -{ - struct payment * const p = renepay->payment; - plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); - - switch(p->status) - { - /* Some flows succeeded, we finish the payment. */ - case PAYMENT_SUCCESS: - plugin_log(pay_plugin->plugin,LOG_DBG,"status is PAYMENT_SUCCESS"); - renepay_success(renepay); - break; - - /* Some flows failed, we retry. */ - case PAYMENT_FAIL: - plugin_log(pay_plugin->plugin,LOG_DBG,"status is PAYMENT_FAIL"); - payment_assert_delivering_incomplete(p); - try_paying(renepay->cmd,renepay,/* always try even if prob is low */ true); - break; - - /* Nothing has returned yet, we have to wait. */ - case PAYMENT_PENDING: - plugin_log(pay_plugin->plugin,LOG_DBG,"status is PAYMENT_PENDING"); - payment_assert_delivering_all(p); - renepay_settimer(renepay); - break; - } -} - /* Sometimes we don't know exactly who to blame... */ -static struct command_result *handle_unhandleable_error(struct renepay * renepay, - struct pay_flow *flow, - const char *what) +static struct pf_result *handle_unhandleable_error(struct pay_flow *pf, + const char *what) { - struct payment * const p = renepay->payment; plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); - size_t n = tal_count(flow); + size_t n = tal_count(pf); /* We got a mangled reply. We don't know who to penalize! */ - debug_paynote(p, "%s on route %s", what, flow_path_to_str(tmpctx, flow)); + debug_paynote(pf->payment, "%s on route %s", what, flow_path_to_str(tmpctx, pf)); // TODO(eduardo): does LOG_BROKEN finish the plugin execution? plugin_log(pay_plugin->plugin, LOG_BROKEN, "%s on route %s", - what, flow_path_to_str(tmpctx, flow)); + what, flow_path_to_str(tmpctx, pf)); if (n == 1) { - payflow_fail(flow); - return renepay_fail(renepay, PAY_UNPARSEABLE_ONION, - "Got %s from the destination", what); + /* This is a terminal error. */ + return pay_flow_failed_final(pf, PAY_UNPARSEABLE_ONION, what); } /* FIXME: check chan_extra_map, since we might have succeeded though * this node before? */ @@ -201,18 +144,19 @@ static struct command_result *handle_unhandleable_error(struct renepay * renepay /* Assume it's not the destination */ n = pseudorand(n-1); - tal_arr_expand(&renepay->disabled, flow->path_scids[n]); - debug_paynote(p, "... eliminated %s", + tal_arr_expand(&pf->payment->disabled, pf->path_scids[n]); + debug_paynote(pf->payment, "... eliminated %s", type_to_string(tmpctx, struct short_channel_id, - &flow->path_scids[n])); - return NULL; + &pf->path_scids[n])); + + return pay_flow_failed(pf); } /* We hold onto the flow (and delete the timer) while we're waiting for * gossipd to receive the channel_update we got from the error. */ struct addgossip { struct short_channel_id scid; - struct pay_flow *flow; + struct pay_flow *pf; }; static struct command_result *addgossip_done(struct command *cmd, @@ -221,13 +165,10 @@ static struct command_result *addgossip_done(struct command *cmd, struct addgossip *adg) { plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); - struct renepay * renepay = adg->flow->payment->renepay; - /* Release this: if it's the last flow we'll retry immediately */ - - payflow_fail(adg->flow); - tal_free(adg); - renepay_settimer(renepay); + /* This may free adg (pf is the parent), or otherwise it'll + * happen later. */ + pay_flow_finished_adding_gossip(adg->pf); return command_still_pending(cmd); } @@ -238,38 +179,32 @@ static struct command_result *addgossip_failure(struct command *cmd, struct addgossip *adg) { + struct payment * payment = adg->pf->payment; plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); - struct payment * p = adg->flow->payment; - struct renepay * renepay = p->renepay; - debug_paynote(p, "addgossip failed, removing channel %s (%.*s)", + debug_paynote(payment, "addgossip failed, removing channel %s (%.*s)", type_to_string(tmpctx, struct short_channel_id, &adg->scid), err->end - err->start, buf + err->start); - tal_arr_expand(&renepay->disabled, adg->scid); + tal_arr_expand(&payment->disabled, adg->scid); return addgossip_done(cmd, buf, err, adg); } -static struct command_result *submit_update(struct command *cmd, - struct pay_flow *flow, - const u8 *update, - struct short_channel_id errscid) +static struct pf_result *submit_update(struct pay_flow *pf, + const u8 *update, + struct short_channel_id errscid) { plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); - struct payment * p = flow->payment; - struct renepay * renepay = p->renepay; + struct payment *payment = pf->payment; struct out_req *req; - struct addgossip *adg = tal(cmd, struct addgossip); + struct addgossip *adg = tal(pf, struct addgossip); /* We need to stash scid in case this fails, and we need to hold flow so * we don't get a rexmit before this is complete. */ adg->scid = errscid; - adg->flow = flow; - /* Disable re-xmit until this returns */ - renepay->rexmit_timer - = tal_free(renepay->rexmit_timer); + adg->pf = pf; - debug_paynote(p, "... extracted channel_update, telling gossipd"); + debug_paynote(payment, "... extracted channel_update, telling gossipd"); plugin_log(pay_plugin->plugin, LOG_DBG, "(update = %s)", tal_hex(tmpctx, update)); req = jsonrpc_request_start(pay_plugin->plugin, NULL, "addgossip", @@ -277,7 +212,10 @@ static struct command_result *submit_update(struct command *cmd, addgossip_failure, adg); json_add_hex_talarr(req->js, "message", update); - return send_outreq(pay_plugin->plugin, req); + send_outreq(pay_plugin->plugin, req); + + /* Don't retry until we call pay_flow_finished_adding_gossip! */ + return pay_flow_failed_adding_gossip(pf); } /* Fix up the channel_update to include the type if it doesn't currently have @@ -305,13 +243,11 @@ static u8 *patch_channel_update(const tal_t *ctx, u8 *channel_update TAKES) /* Return NULL if the wrapped onion error message has no channel_update field, * or return the embedded channel_update message otherwise. */ static u8 *channel_update_from_onion_error(const tal_t *ctx, - const char *buf, - const jsmntok_t *onionmsgtok) + const u8 *onion_message) { u8 *channel_update = NULL; struct amount_msat unused_msat; u32 unused32; - u8 *onion_message = json_tok_bin_from_hex(tmpctx, buf, onionmsgtok); /* Identify failcodes that have some channel_update. * @@ -342,7 +278,7 @@ static u8 *channel_update_from_onion_error(const tal_t *ctx, static struct command_result *flow_sent(struct command *cmd, const char *buf, const jsmntok_t *result, - struct pay_flow *flow) + struct pay_flow *pf) { plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); return command_still_pending(cmd); @@ -355,78 +291,72 @@ static struct command_result *flow_sent(struct command *cmd, static struct command_result *flow_sendpay_failed(struct command *cmd, const char *buf, const jsmntok_t *err, - struct pay_flow *flow) + struct pay_flow *pf) { - plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); - - struct payment *p = flow->payment; - debug_assert(p); - struct renepay * renepay = p->renepay; - debug_assert(renepay); + struct payment *payment = pf->payment; + enum jsonrpc_errcode errcode; + const char *msg; - /* This is a fail. */ - payment_fail(p); + plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); - u64 errcode; - const jsmntok_t *msg = json_get_member(buf, err, "message"); + debug_assert(payment); - if (!json_to_u64(buf, json_get_member(buf, err, "code"), &errcode)) - plugin_err(cmd->plugin, "Bad errcode from sendpay: %.*s", + if (json_scan(tmpctx, buf, err, + "{code:%,message:%}", + JSON_SCAN(json_to_jsonrpc_errcode, &errcode), + JSON_SCAN_TAL(tmpctx, json_strdup, &msg))) { + plugin_err(cmd->plugin, "Bad fail from sendpay: %.*s", json_tok_full_len(err), json_tok_full(buf, err)); - + } if (errcode != PAY_TRY_OTHER_ROUTE) plugin_err(cmd->plugin, "Strange error from sendpay: %.*s", json_tok_full_len(err), json_tok_full(buf, err)); - debug_paynote(p, - "sendpay didn't like first hop, eliminated: %.*s", - msg->end - msg->start, buf + msg->start); + debug_paynote(payment, + "sendpay didn't like first hop, eliminated: %s", msg) /* There is no new knowledge from this kind of failure. * We just disable this scid. */ - tal_arr_expand(&renepay->disabled, flow->path_scids[0]); + tal_arr_expand(&payment->disabled, pf->path_scids[0]); - payflow_fail(flow); + pay_flow_failed(pf); return command_still_pending(cmd); } - -static struct command_result * -sendpay_flows(struct command *cmd, - struct renepay * renepay, - struct pay_flow **flows STEALS) +/* Kick off all pay_flows which are in state PAY_FLOW_NOT_STARTED */ +static void sendpay_new_flows(struct payment *p) { - struct payment * const p = renepay->payment; + struct pay_flow *pf; - plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); - debug_paynote(p, "Sending out batch of %zu payments", tal_count(flows)); - - for (size_t i = 0; i < tal_count(flows); i++) { - const size_t path_lengh = tal_count(flows[i]->amounts); - debug_paynote(p, "sendpay flow groupid=%ld, partid=%ld, delivering=%s, probability=%.3lf", - flows[i]->key.groupid, - flows[i]->key.partid, - type_to_string(tmpctx,struct amount_msat, - &flows[i]->amounts[path_lengh-1]), - flows[i]->success_prob); + list_for_each(&p->flows, pf, list) { + if (pf->state != PAY_FLOW_NOT_STARTED) + continue; + + debug_paynote(p, "sendpay flow groupid=%"PRIu64", partid=%"PRIu64", delivering=%s, probability=%.3lf", + pf->key.groupid, + pf->key.partid, + fmt_amount_msat(tmpctx, payflow_delivered(pf)), + pf->success_prob); struct out_req *req; - req = jsonrpc_request_start(cmd->plugin, cmd, "sendpay", + /* FIXME: We don't actually want cmd to own this sendpay, so we use NULL here, + * but we should use a variant which allows us to set json id! */ + req = jsonrpc_request_start(pay_plugin->plugin, NULL, "sendpay", flow_sent, flow_sendpay_failed, - flows[i]); + pf); json_array_start(req->js, "route"); - for (size_t j = 0; j < tal_count(flows[i]->path_nodes); j++) { + for (size_t j = 0; j < tal_count(pf->path_nodes); j++) { json_object_start(req->js, NULL); json_add_node_id(req->js, "id", - &flows[i]->path_nodes[j]); + &pf->path_nodes[j]); json_add_short_channel_id(req->js, "channel", - &flows[i]->path_scids[j]); + &pf->path_scids[j]); json_add_amount_msat(req->js, "amount_msat", - flows[i]->amounts[j]); + pf->amounts[j]); json_add_num(req->js, "direction", - flows[i]->path_dirs[j]); + pf->path_dirs[j]); json_add_u32(req->js, "delay", - flows[i]->cltv_delays[j]); + pf->cltv_delays[j]); json_add_string(req->js,"style","tlv"); json_object_end(req->js); } @@ -437,7 +367,7 @@ sendpay_flows(struct command *cmd, json_add_amount_msat(req->js, "amount_msat", p->amount); - json_add_u64(req->js, "partid", flows[i]->key.partid); + json_add_u64(req->js, "partid", pf->key.partid); json_add_u64(req->js, "groupid", p->groupid); if (p->payment_metadata) @@ -451,81 +381,46 @@ sendpay_flows(struct command *cmd, if (p->description) json_add_string(req->js, "description", p->description); - amount_msat_accumulate(&p->total_sent, flows[i]->amounts[0]); - amount_msat_accumulate(&p->total_delivering, - payflow_delivered(flows[i])); - - /* Flow now owned by all_flows instead of req., in this way we - * can control the destruction occurs before we remove temporary - * channels from chan_extra_map. */ - tal_steal(pay_plugin->ctx,flows[i]); - - /* Let's keep record of this flow. */ - payflow_map_add(pay_plugin->payflow_map,flows[i]); + send_outreq(pay_plugin->plugin, req); - /* record these HTLC along the flow path */ - commit_htlc_payflow(pay_plugin->chan_extra_map,flows[i]); - - /* Remove the HTLC from the chan_extra_map after finish. */ - tal_add_destructor(flows[i], destroy_payflow); - - send_outreq(cmd->plugin, req); + /* Now you're started! */ + pf->state = PAY_FLOW_IN_PROGRESS; } /* Safety check. */ payment_assert_delivering_all(p); - - tal_free(flows); - - /* Get ready to process replies */ - renepay_settimer(renepay); - - return command_still_pending(cmd); } -static struct command_result *try_paying(struct command *cmd, - struct renepay *renepay, - bool first_time) +const char *try_paying(const tal_t *ctx, + struct payment *payment, + enum jsonrpc_errcode *ecode) { - struct payment * const p = renepay->payment; plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); - // TODO(eduardo): does it make sense to have this limit on attempts? - /* I am classifying the flows in attempt cycles. */ - renepay_new_attempt(renepay); - /* We try only MAX_NUM_ATTEMPTS, then we give up. */ - if ( renepay_attempt_count(renepay) > MAX_NUM_ATTEMPTS) - { - return renepay_fail(renepay, PAY_STOPPED_RETRYING, - "Reached maximum number of attempts (%d)", - MAX_NUM_ATTEMPTS); - } - struct amount_msat feebudget, fees_spent, remaining; - if (time_after(time_now(), p->stop_time)) - return renepay_fail(renepay, PAY_STOPPED_RETRYING, "Timed out"); + assert(payment->status == PAYMENT_PENDING); /* Total feebudget */ - if (!amount_msat_sub(&feebudget, p->maxspend, p->amount)) + if (!amount_msat_sub(&feebudget, payment->maxspend, payment->amount)) { plugin_err(pay_plugin->plugin, "%s (line %d) could not substract maxspend=%s and amount=%s.", __PRETTY_FUNCTION__, __LINE__, - type_to_string(tmpctx, struct amount_msat, &p->maxspend), - type_to_string(tmpctx, struct amount_msat, &p->amount)); + type_to_string(tmpctx, struct amount_msat, &payment->maxspend), + type_to_string(tmpctx, struct amount_msat, &payment->amount)); } /* Fees spent so far */ - if (!amount_msat_sub(&fees_spent, p->total_sent, p->total_delivering)) + if (!amount_msat_sub(&fees_spent, payment->total_sent, payment->total_delivering)) { plugin_err(pay_plugin->plugin, "%s (line %d) could not substract total_sent=%s and total_delivering=%s.", __PRETTY_FUNCTION__, __LINE__, - type_to_string(tmpctx, struct amount_msat, &p->total_sent), - type_to_string(tmpctx, struct amount_msat, &p->total_delivering)); + type_to_string(tmpctx, struct amount_msat, &payment->total_sent), + type_to_string(tmpctx, struct amount_msat, &payment->total_delivering)); } /* Remaining fee budget. */ @@ -540,14 +435,14 @@ static struct command_result *try_paying(struct command *cmd, } /* How much are we still trying to send? */ - if (!amount_msat_sub(&remaining, p->amount, p->total_delivering)) + if (!amount_msat_sub(&remaining, payment->amount, payment->total_delivering)) { plugin_err(pay_plugin->plugin, "%s (line %d) could not substract amount=%s and total_delivering=%s.", __PRETTY_FUNCTION__, __LINE__, - type_to_string(tmpctx, struct amount_msat, &p->amount), - type_to_string(tmpctx, struct amount_msat, &p->total_delivering)); + type_to_string(tmpctx, struct amount_msat, &payment->amount), + type_to_string(tmpctx, struct amount_msat, &payment->total_delivering)); } // plugin_log(pay_plugin->plugin,LOG_DBG,fmt_chan_extra_map(tmpctx,pay_plugin->chan_extra_map)); @@ -556,58 +451,68 @@ static struct command_result *try_paying(struct command *cmd, /* We let this return an unlikely path, as it's better to try once * than simply refuse. Plus, models are not truth! */ - struct pay_flow **pay_flows = get_payflows( - renepay, - remaining, feebudget, - - /* would you accept unlikely - * payments? */ - true, - - /* is entire payment? */ - amount_msat_eq(p->total_delivering, AMOUNT_MSAT(0)), - - &err_msg); - - // plugin_log(pay_plugin->plugin,LOG_DBG,"get_payflows produced %s",fmt_payflows(tmpctx,pay_flows)); + gossmap_apply_localmods(pay_plugin->gossmap, payment->local_gossmods); + err_msg = add_payflows(tmpctx, + payment, + remaining, feebudget, + /* is entire payment? */ + amount_msat_eq(payment->total_delivering, AMOUNT_MSAT(0)), + ecode); + gossmap_remove_localmods(pay_plugin->gossmap, payment->local_gossmods); /* MCF cannot find a feasible route, we stop. */ - if (!pay_flows) - { - return renepay_fail(renepay, PAY_ROUTE_NOT_FOUND, - "Failed to find a route, %s", - err_msg); - } + if (err_msg) + return err_msg; + /* Now begin making payments */ + sendpay_new_flows(payment); - return sendpay_flows(cmd, renepay, pay_flows); + return NULL; +} + +static void destroy_cmd_payment_ptr(struct command *cmd, + struct payment *payment) +{ + assert(payment->cmd == cmd); + payment->cmd = NULL; } static struct command_result *listpeerchannels_done( struct command *cmd, const char *buf, const jsmntok_t *result, - struct renepay *renepay) + struct payment *payment) { plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); + const char *errmsg; + enum jsonrpc_errcode ecode; + if (!uncertainty_network_update_from_listpeerchannels( pay_plugin->chan_extra_map, pay_plugin->my_id, - renepay, + payment, buf, result)) - return renepay_fail(renepay,LIGHTNINGD, + return command_fail(cmd, LIGHTNINGD, "listpeerchannels malformed: %.*s", json_tok_full_len(result), json_tok_full(buf, result)); - // So we have all localmods data, now we apply it. Only once per - // payment. // TODO(eduardo): check that there won't be a prob. cost associated with // any gossmap local chan. The same way there aren't fees to pay for my // local channels. - gossmap_apply_localmods(pay_plugin->gossmap,renepay->local_gossmods); - renepay->localmods_applied=true; - return try_paying(cmd, renepay, true); + + /* From now on, we keep a record of the payment, so persist it beyond this cmd. */ + tal_steal(pay_plugin->plugin, payment); + /* When we terminate cmd for any reason, clear it from payment so we don't do it again. */ + assert(cmd == payment->cmd); + tal_add_destructor2(cmd, destroy_cmd_payment_ptr, payment); + + /* This looks for a route, and if OK, fires off the sendpay commands */ + errmsg = try_paying(tmpctx, payment, &ecode); + if (errmsg) + return payment_fail(payment, ecode, "%s", errmsg); + + return command_still_pending(cmd); } @@ -699,26 +604,21 @@ payment_listsendpays_previous( struct command *cmd, const char *buf, const jsmntok_t *result, - struct renepay * renepay) + struct payment * payment) { debug_info("calling %s",__PRETTY_FUNCTION__); - struct payment * p = renepay->payment; size_t i; - const jsmntok_t *t, *arr, *err; + const jsmntok_t *t, *arr; - /* Do we have pending sendpays for the previous attempt? */ - bool pending = false; - /* Group ID of the first pending payment, this will be the one + /* Group ID of the pending payment, this will be the one * who's result gets replayed if we end up suspending. */ - u64 first_pending_group_id = INVALID_ID; - u64 last_pending_group_id = INVALID_ID; - u64 last_pending_partid=0; + u64 pending_group_id = INVALID_ID; + u64 max_pending_partid=0; + u64 max_group_id = 0; struct amount_msat pending_sent = AMOUNT_MSAT(0), pending_msat = AMOUNT_MSAT(0); - /* Did a prior attempt succeed? */ - bool completed = false; /* Metadata for a complete payment, if one exists. */ u32 complete_parts = 0; struct preimage complete_preimage; @@ -726,60 +626,40 @@ payment_listsendpays_previous( complete_msat = AMOUNT_MSAT(0); u32 complete_created_at; - u64 last_group=INVALID_ID; - - err = json_get_member(buf, result, "error"); - if (err) - return command_fail( - cmd, LIGHTNINGD, - "Error retrieving previous pay attempts: %s", - json_strdup(tmpctx, buf, err)); - arr = json_get_member(buf, result, "payments"); if (!arr || arr->type != JSMN_ARRAY) return command_fail( cmd, LIGHTNINGD, - "Unexpected non-array result from listsendpays"); - - /* We need two scans of the payments, the first to identify the groupid - * that have pending sendpays and the second to get the maximum partid - * from that group. */ + "Unexpected non-array result from listsendpays: %.*s", + json_tok_full_len(result), + json_tok_full(buf, result)); - /* We iterate through all prior sendpays, looking for the - * latest group and remembering what its state is. */ json_for_each_arr(i, t, arr) { u64 partid, groupid; struct amount_msat this_msat, this_sent; - - const jsmntok_t *status; + const char *status; // TODO(eduardo): assuming amount_msat is always known. json_scan(tmpctx,buf,t, - "{partid:%" + "{status:%" + ",partid:%" ",groupid:%" ",amount_msat:%" ",amount_sent_msat:%}", + JSON_SCAN_TAL(tmpctx, json_strdup, &status), JSON_SCAN(json_to_u64,&partid), JSON_SCAN(json_to_u64,&groupid), JSON_SCAN(json_to_msat,&this_msat), JSON_SCAN(json_to_msat,&this_sent)); - if(last_group==INVALID_ID) - last_group = groupid; - - last_group = MAX(last_group,groupid); + /* If we decide to create a new group, we base it on max_group_id */ + if (groupid > max_group_id) + max_group_id = 1; /* status could be completed, pending or failed */ - status = json_get_member(buf, t, "status"); - - if(json_tok_streq(buf,status,"failed")) - continue; - - if(json_tok_streq(buf,status,"complete")) - { + if (streq(status, "complete")) { /* Now we know the payment completed. */ - completed = true; if(!amount_msat_add(&complete_msat,complete_msat,this_msat)) debug_err("%s (line %d) msat overflow.", __PRETTY_FUNCTION__,__LINE__); @@ -791,63 +671,21 @@ payment_listsendpays_previous( ",payment_preimage:%}", JSON_SCAN(json_to_u32, &complete_created_at), JSON_SCAN(json_to_preimage, &complete_preimage)); - complete_parts ++; - } - - if(json_tok_streq(buf,status,"pending")) - { - pending = true; // there are parts pending - - if(first_pending_group_id==INVALID_ID || - last_pending_group_id==INVALID_ID) - first_pending_group_id = last_pending_group_id = groupid; - - last_pending_group_id = MAX(last_pending_group_id,groupid); - first_pending_group_id = MIN(first_pending_group_id,groupid); - } - } - - /* We iterate through all prior sendpays, looking for the - * latest pending group. */ - json_for_each_arr(i, t, arr) - { - u64 partid, groupid; - struct amount_msat this_msat, this_sent; - - const jsmntok_t *status; - - // TODO(eduardo): assuming amount_msat is always known. - json_scan(tmpctx,buf,t, - "{partid:%" - ",groupid:%" - ",amount_msat:%" - ",amount_sent_msat:%}", - JSON_SCAN(json_to_u64,&partid), - JSON_SCAN(json_to_u64,&groupid), - JSON_SCAN(json_to_msat,&this_msat), - JSON_SCAN(json_to_msat,&this_sent)); - - /* status could be completed, pending or failed */ - status = json_get_member(buf, t, "status"); - - /* It seems I cannot reuse failed partids for the same groupid, - * therefore let's count them all whatever the status. */ - if(groupid==last_pending_group_id) - last_pending_partid = MAX(last_pending_partid,partid); - - if(groupid == last_pending_group_id && json_tok_streq(buf,status,"pending")) - { - amount_msat_accumulate(&pending_sent,this_sent); - amount_msat_accumulate(&pending_msat,this_msat); - - plugin_log(pay_plugin->plugin,LOG_DBG, - "pending deliver increased by %s", - type_to_string(tmpctx,struct amount_msat,&this_msat)); - } + complete_parts++; + } else if (streq(status, "pending")) { + /* If we have more than one pending group, something went wrong! */ + if (pending_group_id != INVALID_ID + && groupid != pending_group_id) + return command_fail(cmd, PAY_STATUS_UNEXPECTED, + "Multiple pending groups for this payment?"); + pending_group_id = groupid; + if (partid > max_pending_partid) + max_pending_partid = partid; + } else + assert(streq(status, "failed")); } - - if (completed) { + if (complete_parts != 0) { /* There are completed sendpays, we don't need to do anything * but summarize the result. */ struct json_stream *ret = jsonrpc_stream_success(cmd); @@ -855,57 +693,49 @@ payment_listsendpays_previous( json_add_string(ret, "status", "complete"); json_add_amount_msat(ret, "amount_msat", complete_msat); json_add_amount_msat(ret, "amount_sent_msat",complete_sent); - json_add_node_id(ret, "destination", &p->destination); - json_add_sha256(ret, "payment_hash", &p->payment_hash); + json_add_node_id(ret, "destination", &payment->destination); + json_add_sha256(ret, "payment_hash", &payment->payment_hash); json_add_u32(ret, "created_at", complete_created_at); json_add_num(ret, "parts", complete_parts); /* This payment was already completed, we don't keep record of - * it twice. */ - renepay->payment = tal_free(renepay->payment); - + * it twice: payment will be freed with cmd */ return command_finished(cmd, ret); - } else if (pending) { - assert(last_pending_group_id!=INVALID_ID); - assert(first_pending_group_id!=INVALID_ID); - - p->groupid = last_pending_group_id; - renepay->next_partid = last_pending_partid+1; + } else if (pending_group_id != INVALID_ID) { + /* Continue where we left off? */ + payment->groupid = pending_group_id; + payment->next_partid = max_pending_partid+1; - p->total_sent = pending_sent; - p->total_delivering = pending_msat; + payment->total_sent = pending_sent; + payment->total_delivering = pending_msat; plugin_log(pay_plugin->plugin,LOG_DBG, "There are pending sendpays to this invoice. " "groupid = %"PRIu64" " "delivering = %s, " "last_partid = %"PRIu64, - last_pending_group_id, - type_to_string(tmpctx,struct amount_msat,&p->total_delivering), - last_pending_partid); + pending_group_id, + type_to_string(tmpctx,struct amount_msat,&payment->total_delivering), + max_pending_partid); - if( first_pending_group_id != last_pending_group_id) - { - /* At least two pending groups for the same invoice, - * this is weird, we better stop. */ - renepay->payment = tal_free(renepay->payment); - return renepay_fail(renepay, PAY_IN_PROGRESS, - "Payment is pending by some other request."); - } - if(amount_msat_greater_eq(p->total_delivering,p->amount)) + if(amount_msat_greater_eq(payment->total_delivering,payment->amount)) { /* Pending payment already pays the full amount, we * better stop. */ - renepay->payment = tal_free(renepay->payment); - return renepay_fail(renepay, PAY_IN_PROGRESS, + return command_fail(cmd, PAY_IN_PROGRESS, "Payment is pending with full amount already commited"); } }else { /* There are no pending nor completed sendpays, get me the last * sendpay group. */ - p->groupid = (last_group==INVALID_ID ? 1 : (last_group+1)) ; - renepay->next_partid=1; + /* FIXME: use groupid 0 to have sendpay assign an unused groupid, + * as this is theoretically racy against other plugins paying the + * same thing! + * *BUT* that means we have to create one flow first, so we + * can match the others. */ + payment->groupid = max_group_id + 1; + payment->next_partid=1; } @@ -913,7 +743,7 @@ payment_listsendpays_previous( /* Get local capacities... */ req = jsonrpc_request_start(cmd->plugin, cmd, "listpeerchannels", listpeerchannels_done, - listpeerchannels_done, renepay); + listpeerchannels_done, payment); return send_outreq(cmd->plugin, req); } @@ -928,16 +758,19 @@ static struct command_result *json_pay(struct command *cmd, u64 invexpiry; struct amount_msat *msat, *invmsat; struct amount_msat *maxfee; + struct sha256 payment_hash; + struct secret *payment_secret; + const u8 *payment_metadata; + struct node_id destination; u32 *maxdelay; u32 *retryfor; - -#if DEVELOPER u64 *base_fee_penalty; u64 *prob_cost_factor; u64 *riskfactor_millionths; u64 *min_prob_success_millionths; bool *use_shadow; -#endif + u16 final_cltv; + const struct route_info **routes = NULL; if (!param(cmd, buf, params, p_req("invstring", param_invstring, &invstr), @@ -966,95 +799,32 @@ static struct command_result *json_pay(struct command *cmd, NULL)) return command_param_failed(); - /* renepay is bound to the command, if the command finishes renepay is - * freed. */ - struct renepay * renepay = renepay_new(cmd); - tal_add_destructor2(renepay, - renepay_cleanup, - pay_plugin->gossmap); - struct payment * p = renepay->payment; - - p->invstr = tal_steal(p,invstr); - p->description = tal_steal(p,description); - p->label = tal_steal(p,label); - p->local_offer_id = tal_steal(p,local_offer_id); - - - - /* Please renepay try to give me a reliable payment 90% chances of - * success, once you do, then minimize as much as possible those fees. */ - p->min_prob_success = 0.9; - - /* Default delay_feefactor: how much shall we penalize for delay. */ - p->delay_feefactor = 1e-6; - - /* Default prob_cost_factor: how to convert prob. cost to sats. */ - p->prob_cost_factor = 10; - - /* Default base_fee_penalty: how to convert a base fee into a - * proportional fee. */ - p->base_fee_penalty = 10; - -#if DEVELOPER - p->base_fee_penalty = *base_fee_penalty; - base_fee_penalty = tal_free(base_fee_penalty); - - p->prob_cost_factor = *prob_cost_factor; - prob_cost_factor = tal_free(prob_cost_factor); - - p->min_prob_success = *min_prob_success_millionths/1e6; - min_prob_success_millionths = tal_free(min_prob_success_millionths); - - p->delay_feefactor = *riskfactor_millionths/1e6; - riskfactor_millionths = tal_free(riskfactor_millionths); -#endif - - p->maxdelay = *maxdelay; - maxdelay = tal_free(maxdelay); - - /* We inmediately add this payment to the payment list. */ - tal_steal(pay_plugin->ctx,p); - list_add_tail(&pay_plugin->payments, &p->list); - tal_add_destructor(p, destroy_payment); - - plugin_log(pay_plugin->plugin,LOG_DBG,"Starting renepay"); - bool gossmap_changed = gossmap_refresh(pay_plugin->gossmap, NULL); - - if (pay_plugin->gossmap == NULL) - plugin_err(pay_plugin->plugin, "Failed to refresh gossmap: %s", - strerror(errno)); - - p->start_time = time_now(); - p->stop_time = timeabs_add(p->start_time, time_from_sec(*retryfor)); - tal_free(retryfor); - - bool invstr_is_b11=false; - if (!bolt12_has_prefix(p->invstr)) { + /* We might need to parse invstring to get amount */ + if (!bolt12_has_prefix(invstr)) { struct bolt11 *b11; char *fail; b11 = - bolt11_decode(tmpctx, p->invstr, plugin_feature_set(cmd->plugin), - p->description, chainparams, &fail); + bolt11_decode(tmpctx, invstr, plugin_feature_set(cmd->plugin), + description, chainparams, &fail); if (b11 == NULL) - return renepay_fail(renepay, JSONRPC2_INVALID_PARAMS, + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Invalid bolt11: %s", fail); - invstr_is_b11=true; invmsat = b11->msat; invexpiry = b11->timestamp + b11->expiry; - p->destination = b11->receiver_id; - p->payment_hash = b11->payment_hash; - p->payment_secret = - tal_dup_or_null(p, struct secret, b11->payment_secret); + destination = b11->receiver_id; + payment_hash = b11->payment_hash; + payment_secret = + tal_dup_or_null(cmd, struct secret, b11->payment_secret); if (b11->metadata) - p->payment_metadata = tal_dup_talarr(p, u8, b11->metadata); + payment_metadata = tal_dup_talarr(cmd, u8, b11->metadata); else - p->payment_metadata = NULL; + payment_metadata = NULL; - p->final_cltv = b11->min_final_cltv_expiry; + final_cltv = b11->min_final_cltv_expiry; /* Sanity check */ if (feature_offered(b11->features, OPT_VAR_ONION) && !b11->payment_secret) @@ -1070,37 +840,40 @@ static struct command_result *json_pay(struct command *cmd, */ if (!b11->description) { if (!b11->description_hash) { - return renepay_fail(renepay, + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Invalid bolt11: missing description"); } - if (!p->description) - return renepay_fail(renepay, + if (!description) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "bolt11 uses description_hash, but you did not provide description parameter"); } + + routes = cast_const2(const struct route_info **, + b11->routes); } else { // TODO(eduardo): check this, compare with `pay` const struct tlv_invoice *b12; char *fail; - b12 = invoice_decode(tmpctx, p->invstr, strlen(p->invstr), + b12 = invoice_decode(tmpctx, invstr, strlen(invstr), plugin_feature_set(cmd->plugin), chainparams, &fail); if (b12 == NULL) - return renepay_fail(renepay, JSONRPC2_INVALID_PARAMS, + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Invalid bolt12: %s", fail); if (!pay_plugin->exp_offers) - return renepay_fail(renepay, JSONRPC2_INVALID_PARAMS, + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "experimental-offers disabled"); if (!b12->offer_node_id) - return renepay_fail(renepay, JSONRPC2_INVALID_PARAMS, + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "invoice missing offer_node_id"); if (!b12->invoice_payment_hash) - return renepay_fail(renepay, JSONRPC2_INVALID_PARAMS, + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "invoice missing payment_hash"); if (!b12->invoice_created_at) - return renepay_fail(renepay, JSONRPC2_INVALID_PARAMS, + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "invoice missing created_at"); if (b12->invoice_amount) { invmsat = tal(cmd, struct amount_msat); @@ -1108,25 +881,24 @@ static struct command_result *json_pay(struct command *cmd, } else invmsat = NULL; - node_id_from_pubkey(&p->destination, b12->offer_node_id); - p->payment_hash = *b12->invoice_payment_hash; - if (b12->invreq_recurrence_counter && !p->label) - return renepay_fail( - renepay, JSONRPC2_INVALID_PARAMS, + node_id_from_pubkey(&destination, b12->offer_node_id); + payment_hash = *b12->invoice_payment_hash; + if (b12->invreq_recurrence_counter && !label) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "recurring invoice requires a label"); /* FIXME payment_secret should be signature! */ { struct sha256 merkle; - p->payment_secret = tal(p, struct secret); + payment_secret = tal(cmd, struct secret); merkle_tlv(b12->fields, &merkle); - memcpy(p->payment_secret, &merkle, sizeof(merkle)); - BUILD_ASSERT(sizeof(*p->payment_secret) == + memcpy(payment_secret, &merkle, sizeof(merkle)); + BUILD_ASSERT(sizeof(*payment_secret) == sizeof(merkle)); } - p->payment_metadata = NULL; + payment_metadata = NULL; /* FIXME: blinded paths! */ - p->final_cltv = 18; + final_cltv = 18; /* BOLT-offers #12: * - if `relative_expiry` is present: * - MUST reject the invoice if the current time since @@ -1143,49 +915,95 @@ static struct command_result *json_pay(struct command *cmd, invexpiry = *b12->invoice_created_at + BOLT12_DEFAULT_REL_EXPIRY; } - if (node_id_eq(&pay_plugin->my_id, &p->destination)) - return renepay_fail(renepay, JSONRPC2_INVALID_PARAMS, + if (node_id_eq(&pay_plugin->my_id, &destination)) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "This payment is destined for ourselves. " "Self-payments are not supported"); - // set the payment amount if (invmsat) { // amount is written in the invoice if (msat) { - return renepay_fail(renepay, JSONRPC2_INVALID_PARAMS, + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "amount_msat parameter unnecessary"); } - p->amount = *invmsat; - tal_free(invmsat); + msat = invmsat; } else { // amount is not written in the invoice if (!msat) { - return renepay_fail(renepay, JSONRPC2_INVALID_PARAMS, + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "amount_msat parameter required"); } - p->amount = *msat; - tal_free(msat); } /* Default max fee is 5 sats, or 0.5%, whichever is *higher* */ if (!maxfee) { - struct amount_msat fee = amount_msat_div(p->amount, 200); + struct amount_msat fee = amount_msat_div(*msat, 200); if (amount_msat_less(fee, AMOUNT_MSAT(5000))) fee = AMOUNT_MSAT(5000); maxfee = tal_dup(tmpctx, struct amount_msat, &fee); } - if (!amount_msat_add(&p->maxspend, p->amount, *maxfee)) { - return renepay_fail( - renepay, JSONRPC2_INVALID_PARAMS, - "Overflow when computing fee budget, fee far too high."); - } - tal_free(maxfee); - const u64 now_sec = time_now().ts.tv_sec; if (now_sec > invexpiry) - return renepay_fail(renepay, PAY_INVOICE_EXPIRED, "Invoice expired"); + return command_fail(cmd, PAY_INVOICE_EXPIRED, "Invoice expired"); + +#if !DEVELOPER + /* Please renepay try to give me a reliable payment 90% chances of + * success, once you do, then minimize as much as possible those fees. */ + base_fee_penalty = tal(tmpctx, u64); + *base_fee_penalty = 10; + prob_cost_factor = tal(tmpctx, u64); + *prob_cost_factor = 10; + riskfactor_millionths = tal(tmpctx, u64); + *riskfactor_millionths = 1; + min_prob_success_millionths = tal(tmpctx, u64); + *min_prob_success_millionths = 90; + use_shadow = tal(tmpctx, bool); + *use_shadow = 1; +#endif + + /* Payment is allocated off cmd to start, in case we fail cmd + * (e.g. already in progress, already succeeded). Once it's + * actually started, it persists beyond the command, so we + * tal_steal. */ + struct payment *payment = payment_new(cmd, + cmd, + take(invstr), + take(label), + take(description), + take(local_offer_id), + take(payment_secret), + take(payment_metadata), + &destination, + &payment_hash, + *msat, + *maxfee, + *maxdelay, + *retryfor, + final_cltv, + *base_fee_penalty, + *prob_cost_factor, + *riskfactor_millionths, + *min_prob_success_millionths, + use_shadow); + + /* We immediately add this payment to the payment list. */ + list_add_tail(&pay_plugin->payments, &payment->list); + tal_add_destructor(payment, destroy_payment); + + plugin_log(pay_plugin->plugin,LOG_DBG,"Starting renepay"); + bool gossmap_changed = gossmap_refresh(pay_plugin->gossmap, NULL); + + if (pay_plugin->gossmap == NULL) + plugin_err(pay_plugin->plugin, "Failed to refresh gossmap: %s", + strerror(errno)); + + /* Free parameters which would be considered "leaks" by our fussy memleak code */ + tal_free(msat); + tal_free(maxfee); + tal_free(maxdelay); + tal_free(retryfor); /* To construct the uncertainty network we need to perform the following * steps: @@ -1217,8 +1035,7 @@ static struct command_result *json_pay(struct command *cmd, // TODO(eduardo): are there route hints for B12? // Add any extra hidden channel revealed by the routehints to the uncertainty network. - if(invstr_is_b11) - uncertainty_network_add_routehints(pay_plugin->chan_extra_map,renepay); + uncertainty_network_add_routehints(pay_plugin->chan_extra_map, routes, payment); if(!uncertainty_network_check_invariants(pay_plugin->chan_extra_map)) plugin_log(pay_plugin->plugin, @@ -1230,126 +1047,43 @@ static struct command_result *json_pay(struct command *cmd, struct out_req *req = jsonrpc_request_start(cmd->plugin, cmd, "listsendpays", payment_listsendpays_previous, - payment_listsendpays_previous, renepay); + payment_listsendpays_previous, payment); - json_add_sha256(req->js, "payment_hash", &p->payment_hash); + json_add_sha256(req->js, "payment_hash", &payment->payment_hash); return send_outreq(cmd->plugin, req); } -static void handle_sendpay_failure_renepay( - struct command *cmd, - const char *buf, - const jsmntok_t *result, - struct renepay *renepay, - struct pay_flow *flow) +/* Terminates flow */ +static struct pf_result *handle_sendpay_failure_payment(struct pay_flow *pf STEALS, + const char *message, + u32 erridx, + enum onion_wire onionerr, + const u8 *raw) { - debug_assert(renepay); - debug_assert(flow); - struct payment *p = renepay->payment; - debug_assert(p); - - u64 errcode; - if (!json_to_u64(buf, json_get_member(buf, result, "code"), &errcode)) - { - plugin_log(pay_plugin->plugin,LOG_BROKEN, - "Failed to get code from sendpay_failure notification" - ", received json: %.*s", - json_tok_full_len(result), - json_tok_full(buf,result)); - return; - } - const jsmntok_t *msgtok = json_get_member(buf, result, "message"); - const char *message; - if(msgtok) - message = tal_fmt(tmpctx,"%.*s", - msgtok->end - msgtok->start, - buf + msgtok->start); - else - message = "[message missing from sendpay_failure notification]"; - - switch(errcode) - { - case PAY_UNPARSEABLE_ONION: - debug_paynote(p, "Unparsable onion reply on route %s", - flow_path_to_str(tmpctx, flow)); - goto unhandleable; - case PAY_TRY_OTHER_ROUTE: - break; - case PAY_DESTINATION_PERM_FAIL: - renepay_fail(renepay,errcode, - "Got a final failure from destination"); - return; - default: - renepay_fail(renepay,errcode, - "Unexpected errocode from sendpay_failure: %.*s", - json_tok_full_len(result), - json_tok_full(buf,result)); - return; - } - - const jsmntok_t* datatok = json_get_member(buf, result, "data"); - - if(!datatok) - { - plugin_err(pay_plugin->plugin, - "Failed to get data from sendpay_failure notification" - ", received json: %.*s", - json_tok_full_len(result), - json_tok_full(buf,result)); - } - - - /* OK, we expect an onion error reply. */ - u32 erridx; - const jsmntok_t * erridxtok = json_get_member(buf, datatok, "erring_index"); - if (!erridxtok || !json_to_u32(buf, erridxtok, &erridx)) - { - debug_paynote(p, "Missing erring_index reply on route %s", - flow_path_to_str(tmpctx, flow)); - plugin_log(pay_plugin->plugin,LOG_DBG, - "%s (line %d) missing erring_index " - "on request %.*s", - __PRETTY_FUNCTION__,__LINE__, - json_tok_full_len(result), - json_tok_full(buf,result)); - goto unhandleable; - } - struct short_channel_id errscid; - const jsmntok_t *errchantok = json_get_member(buf, datatok, "erring_channel"); - if(!errchantok || !json_to_short_channel_id(buf, errchantok, &errscid)) - { - debug_paynote(p, "Missing erring_channel reply on route %s", - flow_path_to_str(tmpctx, flow)); - goto unhandleable; - } + struct payment *p = pf->payment; + const u8 *update; - if (erridxpath_scids) - && !short_channel_id_eq(&errscid, &flow->path_scids[erridx])) - { + debug_assert(pf); + debug_assert(p); + + /* Final node is usually a hard failure */ + if (erridx == tal_count(pf->path_scids)) { debug_paynote(p, - "erring_index (%d) does not correspond" - "to erring_channel (%s) on route %s", + "onion error %s from final node #%u: %s", + onion_wire_name(onionerr), erridx, - type_to_string(tmpctx,struct short_channel_id,&errscid), - flow_path_to_str(tmpctx,flow)); - goto unhandleable; - } + message); - u32 onionerr; - const jsmntok_t *failcodetok = json_get_member(buf, datatok, "failcode"); - if(!failcodetok || !json_to_u32(buf, failcodetok, &onionerr)) - { - // TODO(eduardo): I wonder which error code should I show the - // user in this case? - renepay_fail(renepay,LIGHTNINGD, - "Failed to get failcode from sendpay_failure notification" - ", received json: %.*s", - json_tok_full_len(result), - json_tok_full(buf,result)); - return; + if (onionerr == WIRE_MPP_TIMEOUT) { + return pay_flow_failed(pf); + } + + debug_paynote(p,"final destination failure"); + return pay_flow_failed_final(pf, PAY_DESTINATION_PERM_FAIL, message); } + errscid = pf->path_scids[erridx]; debug_paynote(p, "onion error %s from node #%u %s: %s", onion_wire_name(onionerr), @@ -1357,11 +1091,7 @@ static void handle_sendpay_failure_renepay( type_to_string(tmpctx, struct short_channel_id, &errscid), message); - const jsmntok_t *rawoniontok = json_get_member(buf, datatok, "raw_message"); - if(!rawoniontok) - goto unhandleable; - - switch ((enum onion_wire)onionerr) { + switch (onionerr) { /* These definitely mean eliminate channel */ case WIRE_PERMANENT_CHANNEL_FAILURE: case WIRE_REQUIRED_CHANNEL_FEATURE_MISSING: @@ -1382,8 +1112,8 @@ static void handle_sendpay_failure_renepay( case WIRE_EXPIRY_TOO_FAR: debug_paynote(p, "we're removing scid %s", type_to_string(tmpctx,struct short_channel_id,&errscid)); - tal_arr_expand(&renepay->disabled, errscid); - return; + tal_arr_expand(&p->disabled, errscid); + return pay_flow_failed(pf); /* These can be fixed (maybe) by applying the included channel_update */ case WIRE_AMOUNT_BELOW_MINIMUM: @@ -1393,353 +1123,220 @@ static void handle_sendpay_failure_renepay( plugin_log(pay_plugin->plugin,LOG_DBG,"sendpay_failure, apply channel_update"); /* FIXME: Check scid! */ // TODO(eduardo): check - const u8 *update = channel_update_from_onion_error(tmpctx, buf, rawoniontok); + update = channel_update_from_onion_error(tmpctx, raw); if (update) - { - submit_update(cmd, flow, update, errscid); - return; - } + return submit_update(pf, update, errscid); debug_paynote(p, "missing an update, so we're removing scid %s", type_to_string(tmpctx,struct short_channel_id,&errscid)); - tal_arr_expand(&renepay->disabled, errscid); - return; + tal_arr_expand(&p->disabled, errscid); + return pay_flow_failed(pf); case WIRE_TEMPORARY_CHANNEL_FAILURE: - case WIRE_MPP_TIMEOUT: - return; + /* These also contain a channel_update, but in this case it's simply + * advisory, not necessary. */ + update = channel_update_from_onion_error(tmpctx, raw); + if (update) + return submit_update(pf, update, errscid); + + return pay_flow_failed(pf); - /* These are from the final distination: fail */ + /* These should only come from the final distination. */ + case WIRE_MPP_TIMEOUT: case WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS: case WIRE_FINAL_INCORRECT_CLTV_EXPIRY: case WIRE_FINAL_INCORRECT_HTLC_AMOUNT: - debug_paynote(p,"final destination failure"); - renepay_fail(renepay,errcode, - "Destination said %s: %s", - onion_wire_name(onionerr), - message); - return; + break; } - debug_assert(erridx<=tal_count(flow->path_nodes)); - - if(erridx == tal_count(flow->path_nodes)) - { - debug_paynote(p,"unkown onion error code %u, fatal", - onionerr); - renepay_fail(renepay,errcode, - "Destination gave unknown error code %u: %s", - onionerr,message); - return; - }else - { - debug_paynote(p,"unkown onion error code %u, removing scid %s", - onionerr, - type_to_string(tmpctx,struct short_channel_id,&errscid)); - tal_arr_expand(&renepay->disabled, errscid); - return; - } - unhandleable: - // TODO(eduardo): check - handle_unhandleable_error(renepay, flow, ""); + debug_paynote(p,"unkown onion error code %u, removing scid %s", + onionerr, + type_to_string(tmpctx,struct short_channel_id,&errscid)); + tal_arr_expand(&p->disabled, errscid); + return pay_flow_failed(pf); } -static void handle_sendpay_failure_flow( - struct command *cmd, - const char *buf, - const jsmntok_t *result, - struct pay_flow *flow) +static void handle_sendpay_failure_flow(struct pay_flow *pf, + const char *msg, + u32 erridx, + u32 onionerr) { - // TODO(eduardo): review with Rusty the level of severity of the - // different cases of error below. - debug_assert(flow); + debug_assert(pf); - struct payment * const p = flow->payment; - payment_fail(p); + struct payment * const p = pf->payment; - u64 errcode; - if (!json_to_u64(buf, json_get_member(buf, result, "code"), &errcode)) - { - plugin_err(pay_plugin->plugin, - "Failed to get code from sendpay_failure notification" - ", received json: %.*s", - json_tok_full_len(result), - json_tok_full(buf,result)); - return; - } - const jsmntok_t *msgtok = json_get_member(buf, result, "message"); - const char *message; - if(msgtok) - message = tal_fmt(tmpctx,"%.*s", - msgtok->end - msgtok->start, - buf + msgtok->start); - else - message = "[message missing from sendpay_failure notification]"; - - if(errcode!=PAY_TRY_OTHER_ROUTE) - return; - - const jsmntok_t* datatok = json_get_member(buf, result, "data"); - if(!datatok) - { - plugin_err(pay_plugin->plugin, - "Failed to get data from sendpay_failure notification" - ", received json: %.*s", - json_tok_full_len(result), - json_tok_full(buf,result)); - } - - /* OK, we expect an onion error reply. */ - u32 erridx; - const jsmntok_t * erridxtok = json_get_member(buf, datatok, "erring_index"); - if (!erridxtok || !json_to_u32(buf, erridxtok, &erridx)) - { - plugin_log(pay_plugin->plugin,LOG_BROKEN, - "Failed to get erring_index from sendpay_failure notification" - ", received json: %.*s", - json_tok_full_len(result), - json_tok_full(buf,result)); - return; - } - - struct short_channel_id errscid; - const jsmntok_t *errchantok = json_get_member(buf, datatok, "erring_channel"); - if(!errchantok || !json_to_short_channel_id(buf, errchantok, &errscid)) - { - plugin_log(pay_plugin->plugin,LOG_BROKEN, - "Failed to get erring_channel from sendpay_failure notification" - ", received json: %.*s", - json_tok_full_len(result), - json_tok_full(buf,result)); - return; - } - - if (erridxpath_scids) - && !short_channel_id_eq(&errscid, &flow->path_scids[erridx])) - { - plugin_err(pay_plugin->plugin, - "Erring channel %u/%zu was %s not %s (path %s)", - erridx, tal_count(flow->path_scids), - type_to_string(tmpctx, - struct short_channel_id, - &errscid), - type_to_string(tmpctx, - struct short_channel_id, - &flow->path_scids[erridx]), - flow_path_to_str(tmpctx, flow)); - return; - } - - - u32 onionerr; - const jsmntok_t *failcodetok = json_get_member(buf, datatok, "failcode"); - if(!failcodetok || !json_to_u32(buf, failcodetok, &onionerr)) - { - plugin_log(pay_plugin->plugin,LOG_BROKEN, - "Failed to get failcode from sendpay_failure notification" - ", received json: %.*s", - json_tok_full_len(result), - json_tok_full(buf,result)); - return; - - } - - plugin_log(pay_plugin->plugin,LOG_UNUSUAL, + plugin_log(pay_plugin->plugin, LOG_UNUSUAL, "onion error %s from node #%u %s: " "%s", onion_wire_name(onionerr), erridx, - type_to_string(tmpctx, struct short_channel_id, &errscid), - message); + erridx == tal_count(pf->path_scids) + ? "final" + : type_to_string(tmpctx, struct short_channel_id, &pf->path_scids[erridx]), + msg); /* we know that all channels before erridx where able to commit to this payment */ uncertainty_network_channel_can_send( pay_plugin->chan_extra_map, - flow, + pf, erridx); - /* Insufficient funds! */ - if((enum onion_wire)onionerr == WIRE_TEMPORARY_CHANNEL_FAILURE) + /* Insufficient funds (not from final, that's weird!) */ + if((enum onion_wire)onionerr == WIRE_TEMPORARY_CHANNEL_FAILURE + && erridx < tal_count(pf->path_scids)) { plugin_log(pay_plugin->plugin,LOG_DBG, "sendpay_failure says insufficient funds!"); chan_extra_cannot_send(p,pay_plugin->chan_extra_map, - flow->path_scids[erridx], - flow->path_dirs[erridx], + pf->path_scids[erridx], + pf->path_dirs[erridx], /* This channel can't send all that was * commited in HTLCs. * Had we removed the commited amount then - * we would have to put here flow->amounts[erridx]. */ + * we would have to put here pf->amounts[erridx]. */ AMOUNT_MSAT(0)); } } -// TODO(eduardo): if I subscribe to a shutdown notification, the plugin takes -// forever to close and eventually it gets killed by force. -// static struct command_result *notification_shutdown(struct command *cmd, -// const char *buf, -// const jsmntok_t *params) -// { -// /* TODO(eduardo): -// * 1. at shutdown the `struct plugin *p` is not freed, -// * 2. `memleak_check` is called before we have the chance to get this -// * notification. */ -// // plugin_log(pay_plugin->plugin,LOG_DBG,"received shutdown notification, freeing data."); -// pay_plugin->ctx = tal_free(pay_plugin->ctx); -// return notification_handled(cmd); -// } -static struct command_result *notification_sendpay_success( - struct command *cmd, - const char *buf, - const jsmntok_t *params) +/* See if this notification is about one of our flows. */ +static struct pay_flow *pay_flow_from_notification(const char *buf, + const jsmntok_t *obj) { - struct pay_flow *flow = NULL; - const jsmntok_t *resulttok = json_get_member(buf,params,"sendpay_success"); - if(!resulttok) - debug_err("Failed to get result from sendpay_success notification" - ", received json: %.*s", - json_tok_full_len(params), - json_tok_full(buf,params)); - - // 1. generate the key of this payflow struct payflow_key key; - key.payment_hash = tal(tmpctx,struct sha256); - - const jsmntok_t *parttok = json_get_member(buf,resulttok,"partid"); - if(!parttok || !json_to_u64(buf,parttok,&key.partid)) - { - // No partid, is this a single-path payment? - key.partid = 0; - // debug_err("Failed to get partid from sendpay_success notification" - // ", received json: %.*s", - // json_tok_full_len(params), - // json_tok_full(buf,params)); - } - const jsmntok_t *grouptok = json_get_member(buf,resulttok,"groupid"); - if(!grouptok || !json_to_u64(buf,grouptok,&key.groupid)) - debug_err("Failed to get groupid from sendpay_success notification" - ", received json: %.*s", - json_tok_full_len(params), - json_tok_full(buf,params)); - - const jsmntok_t *hashtok = json_get_member(buf,resulttok,"payment_hash"); - if(!hashtok || !json_to_sha256(buf,hashtok,key.payment_hash)) - debug_err("Failed to get payment_hash from sendpay_success notification" - ", received json: %.*s", - json_tok_full_len(params), - json_tok_full(buf,params)); - - plugin_log(pay_plugin->plugin,LOG_DBG, - "I received a sendpay_success with key %s", - fmt_payflow_key(tmpctx,&key)); - - // 2. is this payflow recorded in renepay? - flow = payflow_map_get(pay_plugin->payflow_map,key); - if(!flow) - { - plugin_log(pay_plugin->plugin,LOG_DBG, - "sendpay_success does not correspond to a renepay attempt, %s", - fmt_payflow_key(tmpctx,&key)); - goto done; + const char *err; + + /* Single part payment? No partid */ + key.partid = 0; + err = json_scan(tmpctx, buf, obj, "{partid?:%,groupid:%,payment_hash:%}", + JSON_SCAN(json_to_u64, &key.partid), + JSON_SCAN(json_to_u64, &key.groupid), + JSON_SCAN(json_to_sha256, &key.payment_hash)); + if (err) { + plugin_err(pay_plugin->plugin, + "Missing fields (%s) in notification: %.*s", + err, + json_tok_full_len(obj), + json_tok_full(buf, obj)); } - // 3. mark as success - struct payment * const p = flow->payment; - debug_assert(p); - - payment_success(p); - - const jsmntok_t *preimagetok - = json_get_member(buf, resulttok, "payment_preimage"); - struct preimage preimage; - - if (!preimagetok || !json_to_preimage(buf, preimagetok,&preimage)) - debug_err("Failed to get payment_preimage from sendpay_success notification" - ", received json: %.*s", - json_tok_full_len(params), - json_tok_full(buf,params)); + return payflow_map_get(pay_plugin->payflow_map, &key); +} - p->preimage = tal_dup_or_null(p,struct preimage,&preimage); - // 4. update information and release pending HTLCs - uncertainty_network_flow_success(pay_plugin->chan_extra_map,flow); - done: - tal_free(flow); - return notification_handled(cmd); -} -static struct command_result *notification_sendpay_failure( +static struct command_result *notification_sendpay_success( struct command *cmd, const char *buf, const jsmntok_t *params) { - struct pay_flow *flow = NULL; + struct pay_flow *pf; + struct preimage preimage; + const char *err; + const jsmntok_t *sub = json_get_member(buf, params, "sendpay_success"); - const jsmntok_t *resulttok = json_get_member(buf,params,"sendpay_failure"); - if(!resulttok) - debug_err("Failed to get result from sendpay_failure notification" - ", received json: %.*s", - json_tok_full_len(params), - json_tok_full(buf,params)); + pf = pay_flow_from_notification(buf, sub); + if (!pf) + return notification_handled(cmd); - const jsmntok_t *datatok = json_get_member(buf,resulttok,"data"); - if(!datatok) - debug_err("Failed to get data from sendpay_failure notification" - ", received json: %.*s", - json_tok_full_len(params), - json_tok_full(buf,params)); + err = json_scan(tmpctx, buf, sub, "{payment_preimage:%}", + JSON_SCAN(json_to_preimage, &preimage)); + if (err) { + plugin_err(pay_plugin->plugin, + "Bad payment_preimage (%s) in sendpay_success: %.*s", + err, + json_tok_full_len(params), + json_tok_full(buf, params)); + } + // 2. update information + uncertainty_network_flow_success(pay_plugin->chan_extra_map, pf); - // 1. generate the key of this payflow - struct payflow_key key; - key.payment_hash = tal(tmpctx,struct sha256); + // 3. mark as success (frees pf) + pay_flow_succeeded(pf, &preimage); - const jsmntok_t *parttok = json_get_member(buf,datatok,"partid"); - if(!parttok || !json_to_u64(buf,parttok,&key.partid)) - { - // No partid, is this a single-path payment? - key.partid = 0; + return notification_handled(cmd); +} + +/* Dummy return ensures all paths call pay_flow_* to close flow! */ +static struct pf_result *sendpay_failure(struct pay_flow *pf, + enum jsonrpc_errcode errcode, + const char *buf, + const jsmntok_t *sub) +{ + const char *msg, *err; + u32 erridx, onionerr; + const u8 *raw; + + /* Only one code is really actionable */ + switch (errcode) { + case PAY_UNPARSEABLE_ONION: + debug_paynote(pf->payment, "Unparsable onion reply on route %s", + flow_path_to_str(tmpctx, pf)); + return handle_unhandleable_error(pf, "Unparsable onion reply"); + + case PAY_TRY_OTHER_ROUTE: + break; + case PAY_DESTINATION_PERM_FAIL: + break; + default: + return pay_flow_failed_final(pf, + errcode, + "Unexpected errorcode from sendpay_failure"); } - const jsmntok_t *grouptok = json_get_member(buf,datatok,"groupid"); - if(!grouptok || !json_to_u64(buf,grouptok,&key.groupid)) - debug_err("Failed to get groupid from sendpay_failure notification" - ", received json: %.*s", - json_tok_full_len(params), - json_tok_full(buf,params)); - - const jsmntok_t *hashtok = json_get_member(buf,datatok,"payment_hash"); - if(!hashtok || !json_to_sha256(buf,hashtok,key.payment_hash)) - debug_err("Failed to get payment_hash from sendpay_failure notification" - ", received json: %.*s", - json_tok_full_len(params), - json_tok_full(buf,params)); - - plugin_log(pay_plugin->plugin,LOG_DBG, - "I received a sendpay_failure with key %s", - fmt_payflow_key(tmpctx,&key)); - - // 2. is this payflow recorded in renepay? - flow = payflow_map_get(pay_plugin->payflow_map,key); - if(!flow) - { - plugin_log(pay_plugin->plugin,LOG_DBG, - "sendpay_failure does not correspond to a renepay attempt, %s", - fmt_payflow_key(tmpctx,&key)); - goto done; + + /* Extract remaining fields for feedback */ + raw = NULL; + err = json_scan(tmpctx, buf, sub, + "{message:%" + ",data:{erring_index:%" + ",failcode:%" + ",raw_message?:%}}", + JSON_SCAN_TAL(tmpctx, json_strdup, &msg), + JSON_SCAN(json_to_u32, &erridx), + JSON_SCAN(json_to_u32, &onionerr), + JSON_SCAN_TAL(tmpctx, json_tok_bin_from_hex, &raw)); + if (err) + return handle_unhandleable_error(pf, err); + + /* Answer must be sane: but note, erridx can be final node! */ + if (erridx > tal_count(pf->path_scids)) { + plugin_err(pay_plugin->plugin, + "Erring channel %u/%zu in path %s", + erridx, tal_count(pf->path_scids), + flow_path_to_str(tmpctx, pf)); } - // 3. process failure - handle_sendpay_failure_flow(cmd,buf,resulttok,flow); + handle_sendpay_failure_flow(pf, msg, erridx, onionerr); - // there is possibly a pending renepay command for this flow - struct renepay * const renepay = flow->payment->renepay; + return handle_sendpay_failure_payment(pf, msg, erridx, onionerr, raw); +} - if(renepay) - handle_sendpay_failure_renepay(cmd,buf,resulttok,renepay,flow); +static struct command_result *notification_sendpay_failure( + struct command *cmd, + const char *buf, + const jsmntok_t *params) +{ + struct pay_flow *pf; + const char *err; + enum jsonrpc_errcode errcode; + const jsmntok_t *sub = json_get_member(buf, params, "sendpay_failure"); + + pf = pay_flow_from_notification(buf, json_get_member(buf, sub, "data")); + if (!pf) + return notification_handled(cmd); + + err = json_scan(tmpctx, buf, sub, "{code:%}", + JSON_SCAN(json_to_jsonrpc_errcode, &errcode)); + if (err) { + plugin_err(pay_plugin->plugin, + "Bad code (%s) in sendpay_failure: %.*s", + err, + json_tok_full_len(params), + json_tok_full(buf, params)); + } - done: - if(flow) payflow_fail(flow); + sendpay_failure(pf, errcode, buf, sub); return notification_handled(cmd); } @@ -1778,6 +1375,11 @@ static const struct plugin_notification notifications[] = { int main(int argc, char *argv[]) { setup_locale(); + + /* Most gets initialized in init(), but set debug options here. */ + pay_plugin = tal(NULL, struct pay_plugin); + pay_plugin->debug_mcf = pay_plugin->debug_payflow = false; + plugin_main( argv, init, @@ -1796,7 +1398,5 @@ int main(int argc, char *argv[]) flag_option, &pay_plugin->debug_payflow), NULL); - // TODO(eduardo): I think this is actually never executed - tal_free(pay_plugin->ctx); return 0; } diff --git a/plugins/renepay/pay.h b/plugins/renepay/pay.h index 0c7159568f50..6a2c90e89d6c 100644 --- a/plugins/renepay/pay.h +++ b/plugins/renepay/pay.h @@ -69,15 +69,12 @@ struct pay_plugin { /* Per-channel metadata: some persists between payments */ struct chan_extra_map *chan_extra_map; - /* Pending senpays. */ + /* Pending sendpays (to match notifications to). */ struct payflow_map * payflow_map; bool debug_mcf; bool debug_payflow; - /* I'll allocate all global (controlled by pay_plugin) variables tied to - * this tal_t. */ - tal_t *ctx; /* Pending flows have HTLCs (in-flight) liquidity * attached that is reflected in the uncertainty network. * When sendpay_fail or sendpay_success notifications arrive @@ -96,7 +93,7 @@ struct pay_plugin { }; /* Set in init */ -extern struct pay_plugin * const pay_plugin; +extern struct pay_plugin *pay_plugin; /* Accumulate or panic on overflow */ #define amount_msat_accumulate(dst, src) \ @@ -113,4 +110,8 @@ void amount_msat_reduce_(struct amount_msat *dst, const char *dstname, const char *srcname); +/* Returns NULL if OK, otherwise an error msg and sets *ecode */ +const char *try_paying(const tal_t *ctx, + struct payment *payment, + enum jsonrpc_errcode *ecode); #endif /* LIGHTNING_PLUGINS_RENEPAY_PAY_H */ diff --git a/plugins/renepay/pay_flow.c b/plugins/renepay/pay_flow.c index a1fed1076ca7..7454bf626fc8 100644 --- a/plugins/renepay/pay_flow.c +++ b/plugins/renepay/pay_flow.c @@ -35,6 +35,70 @@ #define MAX_SHADOW_LEN 3 +static void remove_htlc_payflow( + struct chan_extra_map *chan_extra_map, + struct pay_flow *pf) +{ + for (size_t i = 0; i < tal_count(pf->path_scids); i++) { + struct chan_extra_half *h = get_chan_extra_half_by_scid( + chan_extra_map, + pf->path_scids[i], + pf->path_dirs[i]); + if(!h) + { + plugin_err(pay_plugin->plugin, + "%s could not resolve chan_extra_half", + __PRETTY_FUNCTION__); + } + if (!amount_msat_sub(&h->htlc_total, h->htlc_total, pf->amounts[i])) + { + plugin_err(pay_plugin->plugin, + "%s could not substract HTLC amounts, " + "half total htlc amount = %s, " + "pf->amounts[%lld] = %s.", + __PRETTY_FUNCTION__, + type_to_string(tmpctx, struct amount_msat, &h->htlc_total), + i, + type_to_string(tmpctx, struct amount_msat, &pf->amounts[i])); + } + if (h->num_htlcs == 0) + { + plugin_err(pay_plugin->plugin, + "%s could not decrease HTLC count.", + __PRETTY_FUNCTION__); + } + h->num_htlcs--; + } +} + +static void commit_htlc_payflow( + struct chan_extra_map *chan_extra_map, + const struct pay_flow *pf) +{ + for (size_t i = 0; i < tal_count(pf->path_scids); i++) { + struct chan_extra_half *h = get_chan_extra_half_by_scid( + chan_extra_map, + pf->path_scids[i], + pf->path_dirs[i]); + if(!h) + { + plugin_err(pay_plugin->plugin, + "%s could not resolve chan_extra_half", + __PRETTY_FUNCTION__); + } + if (!amount_msat_add(&h->htlc_total, h->htlc_total, pf->amounts[i])) + { + plugin_err(pay_plugin->plugin, + "%s could not add HTLC amounts, " + "pf->amounts[%lld] = %s.", + __PRETTY_FUNCTION__, + i, + type_to_string(tmpctx, struct amount_msat, &pf->amounts[i])); + } + h->num_htlcs++; + } +} + /* Returns CLTV, and fills in *shadow_fee, based on extending the path */ static u32 shadow_one_flow(const struct gossmap *gossmap, const struct flow *f, @@ -137,11 +201,10 @@ static u64 flow_delay(const struct flow *flow) /* This enhances f->amounts, and returns per-flow cltvs */ static u32 *shadow_additions(const tal_t *ctx, const struct gossmap *gossmap, - struct renepay *renepay, + struct payment *p, struct flow **flows, bool is_entire_payment) { - struct payment * p = renepay->payment; u32 *final_cltvs; /* Set these up now in case we decide to do nothing */ @@ -199,29 +262,31 @@ static u32 *shadow_additions(const tal_t *ctx, return final_cltvs; } -/* Calculates delays and converts to scids. Frees flows. Caller is responsible - * for removing resultings flows from the chan_extra_map. */ -static struct pay_flow **flows_to_pay_flows(struct payment *payment, - struct gossmap *gossmap, - struct flow **flows STEALS, - const u32 *final_cltvs, - u64 *next_partid) +static void destroy_payment_flow(struct pay_flow *pf) { - struct pay_flow **pay_flows - = tal_arr(payment, struct pay_flow *, tal_count(flows)); + list_del_from(&pf->payment->flows, &pf->list); +} +/* Calculates delays and converts to scids, and links to the payment. + * Frees flows. */ +static void convert_and_attach_flows(struct payment *payment, + struct gossmap *gossmap, + struct flow **flows STEALS, + const u32 *final_cltvs, + u64 *next_partid) +{ for (size_t i = 0; i < tal_count(flows); i++) { struct flow *f = flows[i]; - struct pay_flow *pf = tal(pay_flows, struct pay_flow); + struct pay_flow *pf = tal(payment, struct pay_flow); size_t plen; plen = tal_count(f->path); - pay_flows[i] = pf; pf->payment = payment; + pf->state = PAY_FLOW_NOT_STARTED; pf->key.partid = (*next_partid)++; pf->key.groupid = payment->groupid; - pf->key.payment_hash = &payment->payment_hash; + pf->key.payment_hash = payment->payment_hash; /* Convert gossmap_chan into scids and nodes */ pf->path_scids = tal_arr(pf, struct short_channel_id, plen); @@ -244,11 +309,25 @@ static struct pay_flow **flows_to_pay_flows(struct payment *payment, pf->amounts = tal_steal(pf, f->amounts); pf->path_dirs = tal_steal(pf, f->dirs); pf->success_prob = f->success_prob; - pf->attempt = renepay_current_attempt(payment->renepay); + + /* Payment keeps a list of its flows. */ + list_add(&payment->flows, &pf->list); + + /* Increase totals for payment */ + amount_msat_accumulate(&payment->total_sent, pf->amounts[0]); + amount_msat_accumulate(&payment->total_delivering, + payflow_delivered(pf)); + + /* We keep a global map to identify notifications + * about this flow. */ + payflow_map_add(pay_plugin->payflow_map, pf); + + /* record these HTLC along the flow path */ + commit_htlc_payflow(pay_plugin->chan_extra_map, pf); + + tal_add_destructor(pf, destroy_payment_flow); } tal_free(flows); - - return pay_flows; } static bitmap *make_disabled_bitmap(const tal_t *ctx, @@ -280,12 +359,11 @@ static u64 flows_worst_delay(struct flow **flows) } /* FIXME: If only path has channels marked disabled, we should try... */ -static bool disable_htlc_violations_oneflow(struct renepay * renepay, +static bool disable_htlc_violations_oneflow(struct payment *p, const struct flow *flow, const struct gossmap *gossmap, bitmap *disabled) { - struct payment * p = renepay->payment; bool disabled_some = false; for (size_t i = 0; i < tal_count(flow->path); i++) { @@ -308,7 +386,7 @@ static bool disable_htlc_violations_oneflow(struct renepay * renepay, reason); /* Add this for future searches for this payment. */ - tal_arr_expand(&renepay->disabled, scid); + tal_arr_expand(&p->disabled, scid); /* Add to existing bitmap */ bitmap_set_bit(disabled, gossmap_chan_idx(gossmap, flow->path[i])); @@ -319,7 +397,7 @@ static bool disable_htlc_violations_oneflow(struct renepay * renepay, /* If we can't use one of these flows because we hit limits, we disable that * channel for future searches and return false */ -static bool disable_htlc_violations(struct renepay *renepay, +static bool disable_htlc_violations(struct payment *payment, struct flow **flows, const struct gossmap *gossmap, bitmap *disabled) @@ -328,40 +406,35 @@ static bool disable_htlc_violations(struct renepay *renepay, /* We continue through all of them, to disable many at once. */ for (size_t i = 0; i < tal_count(flows); i++) { - disabled_some |= disable_htlc_violations_oneflow(renepay, flows[i], + disabled_some |= disable_htlc_violations_oneflow(payment, flows[i], gossmap, disabled); } return disabled_some; } -/* Get some payment flows to get this amount to destination, or NULL. */ -struct pay_flow **get_payflows(struct renepay * renepay, - struct amount_msat amount, - struct amount_msat feebudget, - bool unlikely_ok, - bool is_entire_payment, - const char **err_msg) +const char *add_payflows(const tal_t *ctx, + struct payment *p, + struct amount_msat amount, + struct amount_msat feebudget, + bool is_entire_payment, + enum jsonrpc_errcode *ecode) { - *err_msg = tal_fmt(tmpctx,"[no error]"); - - struct payment * p = renepay->payment; bitmap *disabled; - struct pay_flow **pay_flows; const struct gossmap_node *src, *dst; - disabled = make_disabled_bitmap(tmpctx, pay_plugin->gossmap, renepay->disabled); + disabled = make_disabled_bitmap(tmpctx, pay_plugin->gossmap, p->disabled); src = gossmap_find_node(pay_plugin->gossmap, &pay_plugin->my_id); if (!src) { debug_paynote(p, "We don't have any channels?"); - *err_msg = tal_fmt(tmpctx,"We don't have any channels."); - goto fail; + *ecode = PAY_ROUTE_NOT_FOUND; + return tal_fmt(ctx, "We don't have any channels."); } dst = gossmap_find_node(pay_plugin->gossmap, &p->destination); if (!dst) { debug_paynote(p, "No trace of destination in network gossip"); - *err_msg = tal_fmt(tmpctx,"Destination is unreacheable in the network gossip."); - goto fail; + *ecode = PAY_ROUTE_NOT_FOUND; + return tal_fmt(ctx, "Destination is unknown in the network gossip."); } for (;;) { @@ -369,7 +442,7 @@ struct pay_flow **get_payflows(struct renepay * renepay, double prob; struct amount_msat fee; u64 delay; - bool too_unlikely, too_expensive, too_delayed; + bool too_expensive, too_delayed; const u32 *final_cltvs; flows = minflow(tmpctx, pay_plugin->gossmap, src, dst, @@ -385,10 +458,10 @@ struct pay_flow **get_payflows(struct renepay * renepay, "minflow couldn't find a feasible flow for %s", type_to_string(tmpctx,struct amount_msat,&amount)); - *err_msg = tal_fmt(tmpctx, - "minflow couldn't find a feasible flow for %s", - type_to_string(tmpctx,struct amount_msat,&amount)); - goto fail; + *ecode = PAY_ROUTE_NOT_FOUND; + return tal_fmt(ctx, + "minflow couldn't find a feasible flow for %s", + type_to_string(tmpctx,struct amount_msat,&amount)); } /* Are we unhappy? */ @@ -403,29 +476,18 @@ struct pay_flow **get_payflows(struct renepay * renepay, type_to_string(tmpctx,struct amount_msat,&fee), delay); - too_unlikely = (prob < p->min_prob_success); - if (too_unlikely && !unlikely_ok) - { - debug_paynote(p, "Flows too unlikely, P() = %f%%", prob * 100); - *err_msg = tal_fmt(tmpctx, - "Probability is too small, " - "Prob = %f%% (min = %f%%)", - prob*100, - p->min_prob_success*100); - goto fail; - } too_expensive = amount_msat_greater(fee, feebudget); if (too_expensive) { debug_paynote(p, "Flows too expensive, fee = %s (max %s)", type_to_string(tmpctx, struct amount_msat, &fee), type_to_string(tmpctx, struct amount_msat, &feebudget)); - *err_msg = tal_fmt(tmpctx, - "Fee exceeds our fee budget, " - "fee = %s (maxfee = %s)", - type_to_string(tmpctx, struct amount_msat, &fee), - type_to_string(tmpctx, struct amount_msat, &feebudget)); - goto fail; + *ecode = PAY_ROUTE_TOO_EXPENSIVE; + return tal_fmt(ctx, + "Fee exceeds our fee budget, " + "fee = %s (maxfee = %s)", + type_to_string(tmpctx, struct amount_msat, &fee), + type_to_string(tmpctx, struct amount_msat, &feebudget)); } too_delayed = (delay > p->maxdelay); if (too_delayed) { @@ -435,11 +497,11 @@ struct pay_flow **get_payflows(struct renepay * renepay, /* FIXME: What is a sane limit? */ if (p->delay_feefactor > 1000) { debug_paynote(p, "Giving up!"); - *err_msg = tal_fmt(tmpctx, - "CLTV delay exceeds our CLTV budget, " - "delay = %"PRIu64" (maxdelay = %u)", - delay,p->maxdelay); - goto fail; + *ecode = PAY_ROUTE_TOO_EXPENSIVE; + return tal_fmt(ctx, + "CLTV delay exceeds our CLTV budget, " + "delay = %"PRIu64" (maxdelay = %u)", + delay, p->maxdelay); } p->delay_feefactor *= 2; @@ -454,7 +516,7 @@ struct pay_flow **get_payflows(struct renepay * renepay, * to do this inside minflow(), but the diagnostics here * are far better, since we can report min/max which * *actually* made us reconsider. */ - if (disable_htlc_violations(renepay, flows, pay_plugin->gossmap, + if (disable_htlc_violations(p, flows, pay_plugin->gossmap, disabled)) { continue; // retry @@ -463,19 +525,16 @@ struct pay_flow **get_payflows(struct renepay * renepay, /* This can adjust amounts and final cltv for each flow, * to make it look like it's going elsewhere */ final_cltvs = shadow_additions(tmpctx, pay_plugin->gossmap, - renepay, flows, is_entire_payment); + p, flows, is_entire_payment); + /* OK, we are happy with these flows: convert to - * pay_flows to outlive the current gossmap. */ - pay_flows = flows_to_pay_flows(renepay->payment, pay_plugin->gossmap, - flows, final_cltvs, - &renepay->next_partid); - break; + * pay_flows in the current payment, to outlive the + * current gossmap. */ + convert_and_attach_flows(p, pay_plugin->gossmap, + flows, final_cltvs, + &p->next_partid); + return NULL; } - - return pay_flows; - -fail: - return NULL; } const char *flow_path_to_str(const tal_t *ctx, const struct pay_flow *flow) @@ -554,87 +613,76 @@ const char* fmt_payflows(const tal_t *ctx, return json_out_contents(jout,&len); } -void remove_htlc_payflow( - struct chan_extra_map *chan_extra_map, - struct pay_flow *flow) +/* How much does this flow deliver to destination? */ +struct amount_msat payflow_delivered(const struct pay_flow *flow) { - for (size_t i = 0; i < tal_count(flow->path_scids); i++) { - struct chan_extra_half *h = get_chan_extra_half_by_scid( - chan_extra_map, - flow->path_scids[i], - flow->path_dirs[i]); - if(!h) - { - plugin_err(pay_plugin->plugin, - "%s could not resolve chan_extra_half", - __PRETTY_FUNCTION__); - } - if (!amount_msat_sub(&h->htlc_total, h->htlc_total, flow->amounts[i])) - { - plugin_err(pay_plugin->plugin, - "%s could not substract HTLC amounts, " - "half total htlc amount = %s, " - "flow->amounts[%lld] = %s.", - __PRETTY_FUNCTION__, - type_to_string(tmpctx, struct amount_msat, &h->htlc_total), - i, - type_to_string(tmpctx, struct amount_msat, &flow->amounts[i])); - } - if (h->num_htlcs == 0) - { - plugin_err(pay_plugin->plugin, - "%s could not decrease HTLC count.", - __PRETTY_FUNCTION__); - } - h->num_htlcs--; - } + return flow->amounts[tal_count(flow->amounts)-1]; } -void commit_htlc_payflow( - struct chan_extra_map *chan_extra_map, - const struct pay_flow *flow) + +static struct pf_result *pf_resolve(struct pay_flow *pf, + enum pay_flow_state oldstate, + enum pay_flow_state newstate, + bool reconsider) { - for (size_t i = 0; i < tal_count(flow->path_scids); i++) { - struct chan_extra_half *h = get_chan_extra_half_by_scid( - chan_extra_map, - flow->path_scids[i], - flow->path_dirs[i]); - if(!h) - { - plugin_err(pay_plugin->plugin, - "%s could not resolve chan_extra_half", - __PRETTY_FUNCTION__); - } - if (!amount_msat_add(&h->htlc_total, h->htlc_total, flow->amounts[i])) - { - plugin_err(pay_plugin->plugin, - "%s could not add HTLC amounts, " - "flow->amounts[%lld] = %s.", - __PRETTY_FUNCTION__, - i, - type_to_string(tmpctx, struct amount_msat, &flow->amounts[i])); - } - h->num_htlcs++; + assert(pf->state == oldstate); + pf->state = newstate; + + /* If it didn't deliver, remove from totals */ + if (pf->state != PAY_FLOW_SUCCESS) { + amount_msat_reduce(&pf->payment->total_delivering, + payflow_delivered(pf)); + amount_msat_reduce(&pf->payment->total_sent, pf->amounts[0]); } + + /* Subtract HTLC counters from the path */ + remove_htlc_payflow(pay_plugin->chan_extra_map, pf); + /* And remove from the global map: no more notifications about this! */ + payflow_map_del(pay_plugin->payflow_map, pf); + + if (reconsider) + payment_reconsider(pf->payment); + return NULL; } -/* How much does this flow deliver to destination? */ -struct amount_msat payflow_delivered(const struct pay_flow *flow) +/* We've been notified that a pay_flow has failed */ +struct pf_result *pay_flow_failed(struct pay_flow *pf) { - return flow->amounts[tal_count(flow->amounts)-1]; + return pf_resolve(pf, PAY_FLOW_IN_PROGRESS, PAY_FLOW_FAILED, true); } -struct pay_flow* payflow_fail(struct pay_flow *flow) +/* We've been notified that a pay_flow has failed, payment is done. */ +struct pf_result *pay_flow_failed_final(struct pay_flow *pf, + enum jsonrpc_errcode final_error, + const char *final_msg TAKES) { - debug_assert(flow); - struct payment * p = flow->payment; - debug_assert(p); + pf->final_error = final_error; + pf->final_msg = tal_strdup(pf, final_msg); - payment_fail(p); - amount_msat_reduce(&p->total_delivering, payflow_delivered(flow)); - amount_msat_reduce(&p->total_sent, flow->amounts[0]); + return pf_resolve(pf, PAY_FLOW_IN_PROGRESS, PAY_FLOW_FAILED_FINAL, true); +} - /* Release the HTLCs in the uncertainty_network. */ - return tal_free(flow); +/* We've been notified that a pay_flow has failed, adding gossip. */ +struct pf_result *pay_flow_failed_adding_gossip(struct pay_flow *pf) +{ + /* Don't bother reconsidering until addgossip done */ + return pf_resolve(pf, PAY_FLOW_IN_PROGRESS, PAY_FLOW_FAILED_GOSSIP_PENDING, + false); } +/* We've finished adding gossip. */ +struct pf_result *pay_flow_finished_adding_gossip(struct pay_flow *pf) +{ + assert(pf->state == PAY_FLOW_FAILED_GOSSIP_PENDING); + pf->state = PAY_FLOW_FAILED; + payment_reconsider(pf->payment); + return NULL; +} + +/* We've been notified that a pay_flow has succeeded. */ +struct pf_result *pay_flow_succeeded(struct pay_flow *pf, + const struct preimage *preimage) +{ + pf->payment_preimage = tal_dup(pf, struct preimage, preimage); + return pf_resolve(pf, PAY_FLOW_IN_PROGRESS, PAY_FLOW_SUCCESS, true); +} diff --git a/plugins/renepay/pay_flow.h b/plugins/renepay/pay_flow.h index e519e9f7f098..4756ad7a2a4e 100644 --- a/plugins/renepay/pay_flow.h +++ b/plugins/renepay/pay_flow.h @@ -8,20 +8,43 @@ #include #include +/* There are several states a payment can be in */ +enum pay_flow_state { + /* Created, but not sent to sendpay */ + PAY_FLOW_NOT_STARTED, + /* Normally, here */ + PAY_FLOW_IN_PROGRESS, + /* Failed: we've fed the data back to the uncertainly network. */ + PAY_FLOW_FAILED, + /* Failed from the final node, so give up: see ->final_error. */ + PAY_FLOW_FAILED_FINAL, + /* Failed, but still updating gossip. */ + PAY_FLOW_FAILED_GOSSIP_PENDING, + /* Succeeded: see ->payment_preimage. */ + PAY_FLOW_SUCCESS, +}; +#define NUM_PAY_FLOW (PAY_FLOW_SUCCESS + 1) + /* This is like a struct flow, but independent of gossmap, and contains * all we need to actually send the part payment. */ struct pay_flow { + /* Linked from payment->flows */ + struct list_node list; + + enum pay_flow_state state; + /* Iff state == PAY_FLOW_SUCCESS */ + const struct preimage *payment_preimage; + /* Iff state == PAY_FAILED_FINAL */ + enum jsonrpc_errcode final_error; + const char *final_msg; + /* So we can be an independent object for callbacks. */ struct payment * payment; - // TODO(eduardo): remove this, unnecessary - int attempt; - /* Information to link this flow to a unique sendpay. */ struct payflow_key { - // TODO(eduardo): pointer or value? - struct sha256 *payment_hash; + struct sha256 payment_hash; u64 groupid; u64 partid; } key; @@ -39,9 +62,9 @@ struct pay_flow { }; static inline struct payflow_key -payflow_key(struct sha256 *hash, u64 groupid, u64 partid) +payflow_key(const struct sha256 *hash, u64 groupid, u64 partid) { - struct payflow_key k= {hash,groupid,partid}; + struct payflow_key k= {*hash,groupid,partid}; return k; } @@ -53,49 +76,62 @@ static inline const char* fmt_payflow_key( ctx, "key: groupid=%"PRIu64", partid=%"PRIu64", payment_hash=%s", k->groupid,k->partid, - type_to_string(ctx,struct sha256,k->payment_hash)); + type_to_string(ctx,struct sha256,&k->payment_hash)); return str; } -static inline const struct payflow_key +static inline const struct payflow_key * payflow_get_key(const struct pay_flow * pf) { - return pf->key; + return &pf->key; } -static inline size_t payflow_key_hash(const struct payflow_key k) +static inline size_t payflow_key_hash(const struct payflow_key *k) { - return k.payment_hash->u.u32[0] ^ (k.groupid << 32) ^ k.partid; + return k->payment_hash.u.u32[0] ^ (k->groupid << 32) ^ k->partid; } static inline bool payflow_key_equal(const struct pay_flow *pf, - const struct payflow_key k) + const struct payflow_key *k) { - return pf->key.partid==k.partid && pf->key.groupid==k.groupid - && sha256_eq(pf->key.payment_hash,k.payment_hash); + return pf->key.partid==k->partid && pf->key.groupid==k->groupid + && sha256_eq(&pf->key.payment_hash, &k->payment_hash); } HTABLE_DEFINE_TYPE(struct pay_flow, payflow_get_key, payflow_key_hash, payflow_key_equal, payflow_map); - -struct pay_flow **get_payflows(struct renepay * renepay, - struct amount_msat amount, - struct amount_msat feebudget, - bool unlikely_ok, - bool is_entire_payment, - const char **err_msg); - -void commit_htlc_payflow( - struct chan_extra_map *chan_extra_map, - const struct pay_flow *flow); - -void remove_htlc_payflow( - struct chan_extra_map *chan_extra_map, - struct pay_flow *flow); - +/* Add one or more IN_PROGRESS pay_flow to payment. Return NULL if we did, + * otherwise an error message (and sets *ecode). */ +const char *add_payflows(const tal_t *ctx, + struct payment *payment, + struct amount_msat amount, + struct amount_msat feebudget, + bool is_entire_payment, + enum jsonrpc_errcode *ecode); + +/* Each payflow is eventually terminated by one of these. + * + * To make sure you deal with flows, they return a special type. + */ + +/* We've been notified that a pay_flow has failed */ +struct pf_result *pay_flow_failed(struct pay_flow *pf STEALS); +/* We've been notified that a pay_flow has failed, payment is done. */ +struct pf_result *pay_flow_failed_final(struct pay_flow *pf STEALS, + enum jsonrpc_errcode final_error, + const char *final_msg TAKES); +/* We've been notified that a pay_flow has failed, adding gossip. */ +struct pf_result *pay_flow_failed_adding_gossip(struct pay_flow *pf STEALS); +/* We've finished adding gossip. */ +struct pf_result *pay_flow_finished_adding_gossip(struct pay_flow *pf STEALS); +/* We've been notified that a pay_flow has succeeded. */ +struct pf_result *pay_flow_succeeded(struct pay_flow *pf STEALS, + const struct preimage *preimage); + +/* Formatting helpers */ const char *flow_path_to_str(const tal_t *ctx, const struct pay_flow *flow); const char* fmt_payflows(const tal_t *ctx, @@ -104,9 +140,4 @@ const char* fmt_payflows(const tal_t *ctx, /* How much does this flow deliver to destination? */ struct amount_msat payflow_delivered(const struct pay_flow *flow); -/* Removes amounts from payment and frees flow pointer. - * A possible destructor for flow would remove HTLCs from the - * uncertainty_network and remove the flow from any data structure. */ -struct pay_flow* payflow_fail(struct pay_flow *flow); - #endif /* LIGHTNING_PLUGINS_RENEPAY_PAY_FLOW_H */ diff --git a/plugins/renepay/payment.c b/plugins/renepay/payment.c index deff954957d0..26be01e6bf19 100644 --- a/plugins/renepay/payment.c +++ b/plugins/renepay/payment.c @@ -1,76 +1,75 @@ #include "config.h" #include +#include #include +#include #include -struct payment * payment_new(struct renepay * renepay) +struct payment *payment_new(const tal_t *ctx, + struct command *cmd, + const char *invstr TAKES, + const char *label TAKES, + const char *description TAKES, + const struct sha256 *local_offer_id TAKES, + const struct secret *payment_secret TAKES, + const u8 *payment_metadata TAKES, + const struct node_id *destination, + const struct sha256 *payment_hash, + struct amount_msat amount, + struct amount_msat maxfee, + unsigned int maxdelay, + u64 retryfor, + u16 final_cltv, + /* Tweakable in DEVELOPER mode */ + u64 base_fee_penalty, + u64 prob_cost_factor, + u64 riskfactor_millionths, + u64 min_prob_success_millionths, + bool use_shadow) { - struct payment *p = tal(renepay,struct payment); - p->renepay = renepay; + struct payment *p = tal(ctx,struct payment); + p->cmd = cmd; p->paynotes = tal_arr(p, const char *, 0); p->total_sent = AMOUNT_MSAT(0); p->total_delivering = AMOUNT_MSAT(0); - p->invstr=NULL; + p->invstr = tal_strdup(p, invstr); - p->amount = AMOUNT_MSAT(0); - // p->destination= - // p->payment_hash - p->maxspend = AMOUNT_MSAT(0); - p->maxdelay=0; - // p->start_time= - // p->stop_time= + p->amount = amount; + p->destination = *destination; + p->payment_hash = *payment_hash; + if (!amount_msat_add(&p->maxspend, amount, maxfee)) + p->maxspend = AMOUNT_MSAT(UINT64_MAX); + + p->maxdelay = maxdelay; + p->start_time = time_now(); + p->stop_time = timeabs_add(p->start_time, time_from_sec(retryfor)); p->preimage = NULL; - p->payment_secret=NULL; - p->payment_metadata=NULL; + p->payment_secret = tal_dup_or_null(p, struct secret, payment_secret); + p->payment_metadata = tal_dup_talarr(p, u8, payment_metadata); p->status=PAYMENT_PENDING; - p->final_cltv=0; + list_head_init(&p->flows); + p->final_cltv=final_cltv; // p->list= - p->description=NULL; - p->label=NULL; + p->description = tal_strdup_or_null(p, description); + p->label = tal_strdup_or_null(p, label); - p->delay_feefactor=0; - p->base_fee_penalty=0; - p->prob_cost_factor=0; - p->min_prob_success=0; + p->delay_feefactor = riskfactor_millionths / 1e6; + p->base_fee_penalty = base_fee_penalty; + p->prob_cost_factor = prob_cost_factor; + p->min_prob_success = min_prob_success_millionths / 1e6; - p->local_offer_id=NULL; - p->use_shadow=true; + p->local_offer_id = tal_dup_or_null(p, struct sha256, local_offer_id); + p->use_shadow = use_shadow; p->groupid=1; - p->result = NULL; - return p; -} - -struct renepay * renepay_new(struct command *cmd) -{ - struct renepay *renepay = tal(cmd,struct renepay); - - renepay->cmd = cmd; - renepay->payment = payment_new(renepay); - renepay->localmods_applied=false; - renepay->local_gossmods = gossmap_localmods_new(renepay); - renepay->disabled = tal_arr(renepay,struct short_channel_id,0); - renepay->rexmit_timer = NULL; - renepay->next_attempt=1; - renepay->next_partid=1; - renepay->all_flows = tal(renepay,tal_t); - - return renepay; -} - + p->local_gossmods = gossmap_localmods_new(p); + p->disabled = tal_arr(p,struct short_channel_id,0); + p->next_partid=1; + p->progress_deadline = NULL; -void payment_fail(struct payment * p) -{ - /* If the payment already succeeded this function call must correspond - * to an old sendpay. */ - if(p->status == PAYMENT_SUCCESS)return; - p->status=PAYMENT_FAIL; -} -void payment_success(struct payment * p) -{ - p->status=PAYMENT_SUCCESS; + return p; } struct amount_msat payment_sent(const struct payment *p) @@ -109,6 +108,9 @@ void payment_note(struct payment *p, const char *fmt, ...) va_end(ap); tal_arr_expand(&p->paynotes, str); debug_info("%s",str); + + if (p->cmd) + plugin_notify_message(p->cmd, LOG_INFORM, "%s", str); } void payment_assert_delivering_incomplete(const struct payment *p) @@ -132,36 +134,20 @@ void payment_assert_delivering_all(const struct payment *p) } } - -int renepay_current_attempt(const struct renepay * renepay) -{ - return renepay->next_attempt-1; -} -int renepay_attempt_count(const struct renepay * renepay) +static struct command_result *payment_success(struct payment *p) { - return renepay->next_attempt-1; -} -void renepay_new_attempt(struct renepay * renepay) -{ - renepay->payment->status=PAYMENT_PENDING; - renepay->next_attempt++; -} -struct command_result *renepay_success(struct renepay * renepay) -{ - debug_info("calling %s",__PRETTY_FUNCTION__); - struct payment *p = renepay->payment; - - payment_success(p); - payment_assert_delivering_all(p); + /* We only finish command once: its destructor clears this. */ + if (!p->cmd) + return NULL; struct json_stream *response - = jsonrpc_stream_success(renepay->cmd); + = jsonrpc_stream_success(p->cmd); /* Any one succeeding is success. */ json_add_preimage(response, "payment_preimage", p->preimage); json_add_sha256(response, "payment_hash", &p->payment_hash); json_add_timeabs(response, "created_at", p->start_time); - json_add_u32(response, "parts", renepay_parts(renepay)); + json_add_u32(response, "parts", payment_parts(p)); json_add_amount_msat(response, "amount_msat", p->amount); json_add_amount_msat(response, "amount_sent_msat", @@ -169,62 +155,232 @@ struct command_result *renepay_success(struct renepay * renepay) json_add_string(response, "status", "complete"); json_add_node_id(response, "destination", &p->destination); - return command_finished(renepay->cmd, response); + return command_finished(p->cmd, response); } -struct command_result *renepay_fail( - struct renepay * renepay, +struct command_result *payment_fail( + struct payment *payment, enum jsonrpc_errcode code, const char *fmt, ...) { - /* renepay_fail is called after command finished. */ - if(renepay==NULL) - { - return command_still_pending(NULL); - } - payment_fail(renepay->payment); + /* We usually get called because a flow failed, but we + * can also get called because we couldn't route any more + * or some strange error. */ + payment->status = PAYMENT_FAIL; + + /* We only finish command once: its destructor clears this. */ + if (!payment->cmd) + return NULL; va_list args; va_start(args, fmt); char *message = tal_vfmt(tmpctx,fmt,args); va_end(args); - debug_paynote(renepay->payment,"%s",message); + debug_paynote(payment,"%s",message); - return command_fail(renepay->cmd,code,"%s",message); + return command_fail(payment->cmd,code,"%s",message); } -u64 renepay_parts(const struct renepay *renepay) +u64 payment_parts(const struct payment *payment) { - return renepay->next_partid-1; + return payment->next_partid-1; } -/* Either the payment succeeded or failed, we need to cleanup/set the plugin - * into a valid state before the next payment. */ -void renepay_cleanup( - struct renepay * renepay, - struct gossmap * gossmap) +void payment_reconsider(struct payment *payment) { - debug_info("calling %s",__PRETTY_FUNCTION__); - /* Always remove our local mods (routehints) so others can use - * gossmap. We do this only after the payment completes. */ - // TODO(eduardo): it can happen that local_gossmods removed below - // contained a set of channels for which there is information in the - // uncertainty network (chan_extra_map) and that are part of some pending - // payflow (payflow_map). Handle this situation. - if(renepay->localmods_applied) - gossmap_remove_localmods(gossmap, - renepay->local_gossmods); - // TODO(eduardo): I wonder if it is possible to have two instances of - // renepay at the same time. - // 1st problem: dijkstra datastructure is global, this can be fixed, - // 2nd problem: we don't know if gossmap_apply_localmods and gossmap_remove_localmods, - // can handle different local_gossmods applied to the same gossmap. - renepay->localmods_applied=false; - tal_free(renepay->local_gossmods); - - renepay->rexmit_timer = tal_free(renepay->rexmit_timer); - - if(renepay->payment) - renepay->payment->renepay = NULL; + struct pay_flow *i, *next; + bool have_state[NUM_PAY_FLOW] = {false}; + enum jsonrpc_errcode final_error COMPILER_WANTS_INIT("gcc 12.3.0 -O3"), ecode; + const char *final_msg COMPILER_WANTS_INIT("gcc 12.3.0 -O3"); + const char *errmsg; + + plugin_log(pay_plugin->plugin, LOG_DBG, "payment_reconsider"); + + /* Harvest results and free up finished flows */ + list_for_each_safe(&payment->flows, i, next, list) { + plugin_log(pay_plugin->plugin, LOG_DBG, "Flow in state %u", i->state); + have_state[i->state] = true; + + switch (i->state) { + case PAY_FLOW_NOT_STARTED: + /* Can't happen: we start just after we add. */ + plugin_err(pay_plugin->plugin, "flow not started?"); + case PAY_FLOW_IN_PROGRESS: + /* Don't free, it's still going! */ + continue; + case PAY_FLOW_FAILED: + break; + case PAY_FLOW_FAILED_FINAL: + final_error = i->final_error; + final_msg = tal_steal(tmpctx, i->final_msg); + break; + case PAY_FLOW_FAILED_GOSSIP_PENDING: + break; + case PAY_FLOW_SUCCESS: + if (payment->preimage) { + /* This should be impossible without breaking SHA256 */ + if (!preimage_eq(payment->preimage, + i->payment_preimage)) { + plugin_err(pay_plugin->plugin, + "Impossible preimage clash for %s: %s and %s?", + type_to_string(tmpctx, + struct sha256, + &payment->payment_hash), + type_to_string(tmpctx, + struct preimage, + payment->preimage), + type_to_string(tmpctx, + struct preimage, + i->payment_preimage)); + } + } else { + payment->preimage = tal_dup(payment, struct preimage, + i->payment_preimage); + } + break; + } + tal_free(i); + } + + /* First, did one of these succeed? */ + if (have_state[PAY_FLOW_SUCCESS]) { + plugin_log(pay_plugin->plugin, LOG_DBG, "one succeeded!"); + + switch (payment->status) { + case PAYMENT_PENDING: + /* The normal case: one part succeeded, we can succeed immediately */ + payment_success(payment); + payment->status = PAYMENT_SUCCESS; + /* fall thru */ + case PAYMENT_SUCCESS: + /* Since we already succeeded, cmd must be NULL */ + assert(payment->cmd == NULL); + break; + case PAYMENT_FAIL: + /* OK, they told us it failed, but also + * succeeded? It's theoretically possible, + * but someone screwed up. */ + plugin_log(pay_plugin->plugin, LOG_BROKEN, + "Destination %s succeeded payment %s" + " (preimage %s) after previous final failure?", + type_to_string(tmpctx, struct node_id, + &payment->destination), + type_to_string(tmpctx, struct sha256, + &payment->payment_hash), + type_to_string(tmpctx, + struct preimage, + payment->preimage)); + break; + } + + /* We don't need to do anything else. */ + return; + } + + /* One of these returned an error from the destination? */ + if (have_state[PAY_FLOW_FAILED_FINAL]) { + plugin_log(pay_plugin->plugin, LOG_DBG, "one failed final!"); + switch (payment->status) { + case PAYMENT_PENDING: + /* The normal case: we can fail immediately */ + payment_fail(payment, final_error, "%s", final_msg); + /* fall thru */ + case PAYMENT_FAIL: + /* Since we already failed, cmd must be NULL */ + assert(payment->cmd == NULL); + break; + case PAYMENT_SUCCESS: + /* OK, they told us it failed, but also + * succeeded? It's theoretically possible, + * but someone screwed up. */ + plugin_log(pay_plugin->plugin, LOG_BROKEN, + "Destination %s failed payment %s with %u/%s" + " after previous success?", + type_to_string(tmpctx, struct node_id, + &payment->destination), + type_to_string(tmpctx, struct sha256, + &payment->payment_hash), + final_error, final_msg); + break; + } + + /* We don't need to do anything else. */ + return; + } + + /* Now, do we still care about retrying the payment? It could + * have terminated a while ago, and we're just collecting + * outstanding results. */ + switch (payment->status) { + case PAYMENT_PENDING: + break; + case PAYMENT_FAIL: + case PAYMENT_SUCCESS: + assert(!payment->cmd); + plugin_log(pay_plugin->plugin, LOG_DBG, "payment already status %u!", + payment->status); + return; + } + + /* Are we waiting on addgossip? We'll come back later when + * they call pay_flow_finished_adding_gossip. */ + if (have_state[PAY_FLOW_FAILED_GOSSIP_PENDING]) { + plugin_log(pay_plugin->plugin, LOG_DBG, + "%s waiting on addgossip return", + type_to_string(tmpctx, struct sha256, + &payment->payment_hash)); + return; + } + + /* Do we still have pending payment parts? First time, we set + * up a deadline so we don't respond immediately to every + * return: it's better to gather a few failed flows before + * retrying. */ + if (have_state[PAY_FLOW_IN_PROGRESS]) { + struct timemono now = time_mono(); + + /* If we don't have a deadline yet, set it now. */ + if (!payment->progress_deadline) { + payment->progress_deadline = tal(payment, struct timemono); + *payment->progress_deadline = timemono_add(now, + time_from_msec(TIMER_COLLECT_FAILURES_MSEC)); + plugin_log(pay_plugin->plugin, LOG_DBG, "Set deadline"); + } + + /* FIXME: add timemono_before to ccan/time */ + if (time_less_(now.ts, payment->progress_deadline->ts)) { + /* Come back later. */ + /* We don't care that this temporily looks like a leak; we don't even + * care if we end up with multiple outstanding. They just check + * the progress_deadline. */ + plugin_log(pay_plugin->plugin, LOG_DBG, "Setting timer to kick us"); + notleak(plugin_timer(pay_plugin->plugin, + timemono_between(*payment->progress_deadline, now), + payment_reconsider, payment)); + return; + } + } + + /* At this point, we may have some funds to deliver (or we + * could still be waiting). */ + if (amount_msat_greater_eq(payment->total_delivering, payment->amount)) { + plugin_log(pay_plugin->plugin, LOG_DBG, "No more to deliver right now"); + assert(have_state[PAY_FLOW_IN_PROGRESS]); + return; + } + + /* If we had a deadline, reset it */ + payment->progress_deadline = tal_free(payment->progress_deadline); + + /* Before we do that, make sure we're not going over time. */ + if (time_after(time_now(), payment->stop_time)) { + payment_fail(payment, PAY_STOPPED_RETRYING, "Timed out"); + return; + } + + plugin_log(pay_plugin->plugin, LOG_DBG, "Retrying payment"); + errmsg = try_paying(tmpctx, payment, &ecode); + if (errmsg) + payment_fail(payment, ecode, "%s", errmsg); } diff --git a/plugins/renepay/payment.h b/plugins/renepay/payment.h index 96da7a9ce2f5..219b4a2f534a 100644 --- a/plugins/renepay/payment.h +++ b/plugins/renepay/payment.h @@ -8,9 +8,30 @@ enum payment_status { PAYMENT_PENDING, PAYMENT_SUCCESS, PAYMENT_FAIL }; - struct payment { - struct renepay * renepay; + /* Inside pay_plugin->payments list */ + struct list_node list; + + /* Overall, how are we going? */ + enum payment_status status; + + /* The flows we are managing. */ + struct list_head flows; + + /* Deadline for flow status collection. */ + struct timemono *progress_deadline; + + /* The command if still running */ + struct command *cmd; + + /* Localmods to apply to gossip_map for our own use. */ + struct gossmap_localmods *local_gossmods; + + /* Channels we decided to disable for various reasons. */ + struct short_channel_id *disabled; + + /* Used in get_payflows to set ids to each pay_flow. */ + u64 next_partid; /* Chatty description of attempts. */ const char **paynotes; @@ -29,7 +50,6 @@ struct payment { struct node_id destination; struct sha256 payment_hash; - /* Limits on what routes we'll accept. */ struct amount_msat maxspend; @@ -51,14 +71,8 @@ struct payment { /* Payment metadata, if specified by invoice. */ const u8 *payment_metadata; - /* To know if the last attempt failed, succeeded or is it pending. */ - enum payment_status status; - u32 final_cltv; - /* Inside pay_plugin->payments list */ - struct list_node list; - /* Description and labels, if any. */ const char *description, *label; @@ -96,47 +110,31 @@ struct payment { /* Groupid, so listpays() can group them back together */ u64 groupid; - - struct command_result * result; }; -/* Data only kept while the payment is being processed. */ -struct renepay -{ - /* The command, and our owner (needed for timer func) */ - struct command *cmd; - - /* Payment information that will eventually outlive renepay and be - * registered. */ - struct payment * payment; - - /* Localmods to apply to gossip_map for our own use. */ - bool localmods_applied; - struct gossmap_localmods *local_gossmods; - - /* Channels we decided to disable for various reasons. */ - struct short_channel_id *disabled; - - /* Timers. */ - struct plugin_timer *rexmit_timer; - - /* Keep track of the number of attempts. */ - int next_attempt; - /* Used in get_payflows to set ids to each pay_flow. */ - u64 next_partid; - - /* Root to destroy pending flows */ - tal_t *all_flows; -}; -struct payment * payment_new(struct renepay *renepay); -struct renepay * renepay_new(struct command *cmd); -void renepay_cleanup( - struct renepay * renepay, - struct gossmap * gossmap); +struct payment *payment_new(const tal_t *ctx, + struct command *cmd, + const char *invstr TAKES, + const char *label TAKES, + const char *description TAKES, + const struct sha256 *local_offer_id TAKES, + const struct secret *payment_secret TAKES, + const u8 *payment_metadata TAKES, + const struct node_id *destination, + const struct sha256 *payment_hash, + struct amount_msat amount, + struct amount_msat maxfee, + unsigned int maxdelay, + u64 retryfor, + u16 final_cltv, + /* Tweakable in DEVELOPER mode */ + u64 base_fee_penalty, + u64 prob_cost_factor, + u64 riskfactor_millionths, + u64 min_prob_success_millionths, + bool use_shadow); -void payment_fail(struct payment * p); -void payment_success(struct payment * p); struct amount_msat payment_sent(const struct payment *p); struct amount_msat payment_delivered(const struct payment *p); struct amount_msat payment_amount(const struct payment *p); @@ -146,18 +144,14 @@ void payment_note(struct payment *p, const char *fmt, ...); void payment_assert_delivering_incomplete(const struct payment *p); void payment_assert_delivering_all(const struct payment *p); +/* A flow has changed state, or we've hit a timeout: do something! */ +void payment_reconsider(struct payment *p); -int renepay_current_attempt(const struct renepay *renepay); -int renepay_attempt_count(const struct renepay *renepay); -void renepay_new_attempt(struct renepay *renepay); +u64 payment_parts(const struct payment *payment); -struct command_result *renepay_success(struct renepay *renepay); - -struct command_result *renepay_fail( - struct renepay * renepay, +struct command_result *payment_fail( + struct payment *payment, enum jsonrpc_errcode code, const char *fmt, ...); -u64 renepay_parts(const struct renepay *renepay); - #endif /* LIGHTNING_PLUGINS_RENEPAY_PAYMENT_H */ diff --git a/plugins/renepay/test/run-arc.c b/plugins/renepay/test/run-arc.c index 1c1304171935..293520bcdbdc 100644 --- a/plugins/renepay/test/run-arc.c +++ b/plugins/renepay/test/run-arc.c @@ -11,9 +11,6 @@ #include "../mcf.c" -/* update-mocks isn't quiet smart enough for this, so place here */ -struct pay_plugin *const pay_plugin; - /* AUTOGENERATED MOCKS START */ /* Generated stub for flow_complete */ void flow_complete(struct flow *flow UNNEEDED, @@ -43,6 +40,8 @@ s64 linear_fee_cost( double base_fee_penalty UNNEEDED, double delay_feefactor UNNEEDED) { fprintf(stderr, "linear_fee_cost called!\n"); abort(); } +/* Generated stub for pay_plugin */ +struct pay_plugin *pay_plugin; /* AUTOGENERATED MOCKS END */ int main(int argc, char *argv[]) diff --git a/plugins/renepay/test/run-mcf-diamond.c b/plugins/renepay/test/run-mcf-diamond.c index b764d5b0eb91..6436e01c6281 100644 --- a/plugins/renepay/test/run-mcf-diamond.c +++ b/plugins/renepay/test/run-mcf-diamond.c @@ -17,6 +17,16 @@ #include #include +/* AUTOGENERATED MOCKS START */ +/* Generated stub for pay_plugin */ +struct pay_plugin *pay_plugin; +/* Generated stub for try_paying */ +const char *try_paying(const tal_t *ctx UNNEEDED, + struct payment *payment UNNEEDED, + enum jsonrpc_errcode *ecode UNNEEDED) +{ fprintf(stderr, "try_paying called!\n"); abort(); } +/* AUTOGENERATED MOCKS END */ + static u8 empty_map[] = { 0 }; diff --git a/plugins/renepay/test/run-mcf.c b/plugins/renepay/test/run-mcf.c index 723d53d60547..539e973584c6 100644 --- a/plugins/renepay/test/run-mcf.c +++ b/plugins/renepay/test/run-mcf.c @@ -17,6 +17,16 @@ #include #include +/* AUTOGENERATED MOCKS START */ +/* Generated stub for pay_plugin */ +struct pay_plugin *pay_plugin; +/* Generated stub for try_paying */ +const char *try_paying(const tal_t *ctx UNNEEDED, + struct payment *payment UNNEEDED, + enum jsonrpc_errcode *ecode UNNEEDED) +{ fprintf(stderr, "try_paying called!\n"); abort(); } +/* AUTOGENERATED MOCKS END */ + static void swap(int *a, int *b) { int temp = *a; @@ -359,6 +369,9 @@ int main(int argc, char *argv[]) assert(amount_msat_eq(ce->half[1].known_min, AMOUNT_MSAT(0))); assert(amount_msat_eq(ce->half[1].known_max, AMOUNT_MSAT(1000000000))); + /* Clear that */ + remove_completed_flow_set(gossmap, chan_extra_map, flows); + // /* Now try adding a local channel scid */ struct short_channel_id scid13; @@ -379,9 +392,6 @@ int main(int argc, char *argv[]) struct gossmap_chan *local_chan = gossmap_find_chan(gossmap, &scid13); assert(local_chan); - /* Clear that */ - remove_completed_flow_set(gossmap, chan_extra_map, flows); - /* The local chans have no "capacity", so set it manually. */ new_chan_extra(chan_extra_map, scid13, AMOUNT_MSAT(400000000)); diff --git a/plugins/renepay/test/run-payflow_map.c b/plugins/renepay/test/run-payflow_map.c index 9627ef810ecc..1d3c65261133 100644 --- a/plugins/renepay/test/run-payflow_map.c +++ b/plugins/renepay/test/run-payflow_map.c @@ -31,14 +31,14 @@ static void destroy_payflow( } static struct pay_flow* new_payflow( const tal_t *ctx, - struct sha256 * payment_hash, + const struct sha256 * payment_hash, u64 gid, u64 pid) { struct pay_flow *p = tal(ctx,struct pay_flow); p->payment=NULL; - p->key.payment_hash=payment_hash; + p->key.payment_hash = *payment_hash; p->key.groupid = gid; p->key.partid = pid; @@ -61,6 +61,7 @@ static void valgrind_ok1(void) { tal_t *local_ctx = tal(this_ctx,tal_t); + struct payflow_key key; struct pay_flow *p1 = new_payflow(local_ctx, &hash,1,1); @@ -69,17 +70,19 @@ static void valgrind_ok1(void) printf("key1 = %s\n",fmt_payflow_key(local_ctx,&p1->key)); printf("key1 = %s\n",fmt_payflow_key(local_ctx,&p2->key)); - printf("key hash 1 = %zu\n",payflow_key_hash(p1->key)); - printf("key hash 2 = %zu\n",payflow_key_hash(p2->key)); + printf("key hash 1 = %zu\n",payflow_key_hash(&p1->key)); + printf("key hash 2 = %zu\n",payflow_key_hash(&p2->key)); payflow_map_add(map,p1); tal_add_destructor2(p1,destroy_payflow,map); payflow_map_add(map,p2); tal_add_destructor2(p2,destroy_payflow,map); - struct pay_flow *q1 = payflow_map_get(map,payflow_key(&hash,1,1)); - struct pay_flow *q2 = payflow_map_get(map,payflow_key(&hash,2,3)); + key = payflow_key(&hash,1,1); + struct pay_flow *q1 = payflow_map_get(map, &key); + key = payflow_key(&hash,2,3); + struct pay_flow *q2 = payflow_map_get(map, &key); - assert(payflow_key_hash(q1->key)==payflow_key_hash(p1->key)); - assert(payflow_key_hash(q2->key)==payflow_key_hash(p2->key)); + assert(payflow_key_hash(&q1->key)==payflow_key_hash(&p1->key)); + assert(payflow_key_hash(&q2->key)==payflow_key_hash(&p2->key)); tal_free(local_ctx); } diff --git a/plugins/renepay/test/run-testflow.c b/plugins/renepay/test/run-testflow.c index 711d5db78bbd..8b5111b255dc 100644 --- a/plugins/renepay/test/run-testflow.c +++ b/plugins/renepay/test/run-testflow.c @@ -16,6 +16,16 @@ #include "../uncertainty_network.c" #include "../mcf.c" +/* AUTOGENERATED MOCKS START */ +/* Generated stub for pay_plugin */ +struct pay_plugin *pay_plugin; +/* Generated stub for try_paying */ +const char *try_paying(const tal_t *ctx UNNEEDED, + struct payment *payment UNNEEDED, + enum jsonrpc_errcode *ecode UNNEEDED) +{ fprintf(stderr, "try_paying called!\n"); abort(); } +/* AUTOGENERATED MOCKS END */ + static const u8 canned_map[] = { 0x0c, 0x80, 0x00, 0x01, 0xbc, 0x86, 0xe4, 0xbf, 0x95, 0x00, 0x00, 0x00, 0x00, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x42, 0x40, 0x01, 0xb0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, diff --git a/plugins/renepay/uncertainty_network.c b/plugins/renepay/uncertainty_network.c index 410e8561d8fd..22e3d77af25f 100644 --- a/plugins/renepay/uncertainty_network.c +++ b/plugins/renepay/uncertainty_network.c @@ -41,7 +41,7 @@ bool uncertainty_network_check_invariants(struct chan_extra_map *chan_extra_map) static void add_hintchan( struct chan_extra_map *chan_extra_map, - struct renepay * renepay, + struct gossmap_localmods *local_gossmods, const struct node_id *src, const struct node_id *dst, u16 cltv_expiry_delta, @@ -63,9 +63,9 @@ static void add_hintchan( scid, MAX_CAP); /* FIXME: features? */ - gossmap_local_addchan(renepay->local_gossmods, + gossmap_local_addchan(local_gossmods, src, dst, &scid, NULL); - gossmap_local_updatechan(renepay->local_gossmods, + gossmap_local_updatechan(local_gossmods, &scid, /* We assume any HTLC is allowed */ AMOUNT_MSAT(0), MAX_CAP, @@ -84,27 +84,18 @@ static void add_hintchan( /* Add routehints provided by bolt11 */ void uncertainty_network_add_routehints( struct chan_extra_map *chan_extra_map, - struct renepay *renepay) + const struct route_info **routes, + struct payment *p) { - const struct payment *const p = renepay->payment; - struct bolt11 *b11; - char *fail; - - b11 = - bolt11_decode(tmpctx, p->invstr, - plugin_feature_set(renepay->cmd->plugin), - p->description, chainparams, &fail); - if (b11 == NULL) - debug_err("add_routehints: Invalid bolt11: %s", fail); - - for (size_t i = 0; i < tal_count(b11->routes); i++) { + for (size_t i = 0; i < tal_count(routes); i++) { /* Each one, presumably, leads to the destination */ - const struct route_info *r = b11->routes[i]; + const struct route_info *r = routes[i]; const struct node_id *end = & p->destination; for (int j = tal_count(r)-1; j >= 0; j--) { add_hintchan( chan_extra_map, - renepay, &r[j].pubkey, end, + p->local_gossmods, + &r[j].pubkey, end, r[j].cltv_expiry_delta, r[j].short_channel_id, r[j].fee_base_msat, @@ -195,34 +186,34 @@ void uncertainty_network_update( void uncertainty_network_flow_success( struct chan_extra_map *chan_extra_map, - struct pay_flow *flow) + struct pay_flow *pf) { - for (size_t i = 0; i < tal_count(flow->path_scids); i++) + for (size_t i = 0; i < tal_count(pf->path_scids); i++) { chan_extra_sent_success( chan_extra_map, - flow->path_scids[i], - flow->path_dirs[i], - flow->amounts[i]); + pf->path_scids[i], + pf->path_dirs[i], + pf->amounts[i]); } } /* All parts up to erridx succeeded, so we know something about min * capacity! */ void uncertainty_network_channel_can_send( struct chan_extra_map * chan_extra_map, - struct pay_flow *flow, + struct pay_flow *pf, u32 erridx) { for (size_t i = 0; i < erridx; i++) { chan_extra_can_send(chan_extra_map, - flow->path_scids[i], - flow->path_dirs[i], + pf->path_scids[i], + pf->path_dirs[i], /* This channel can send all that was * commited in HTLCs. * Had we removed the commited amount then - * we would have to put here flow->amounts[i]. */ + * we would have to put here pf->amounts[i]. */ AMOUNT_MSAT(0)); } } @@ -232,11 +223,10 @@ void uncertainty_network_channel_can_send( bool uncertainty_network_update_from_listpeerchannels( struct chan_extra_map * chan_extra_map, struct node_id my_id, - struct renepay * renepay, + struct payment *p, const char *buf, const jsmntok_t *toks) { - struct payment * const p = renepay->payment; const jsmntok_t *channels, *channel; size_t i; @@ -270,7 +260,7 @@ bool uncertainty_network_update_from_listpeerchannels( type_to_string(tmpctx, struct short_channel_id, &scid)); - tal_arr_expand(&renepay->disabled, scid); + tal_arr_expand(&p->disabled, scid); continue; } @@ -301,8 +291,9 @@ bool uncertainty_network_update_from_listpeerchannels( goto malformed; /* Don't report opening/closing channels */ - if (!json_tok_streq(buf, statetok, "CHANNELD_NORMAL")) { - tal_arr_expand(&renepay->disabled, scid); + if (!json_tok_streq(buf, statetok, "CHANNELD_NORMAL") + && !json_tok_streq(buf, statetok, "CHANNELD_AWAITING_SPLICE")) { + tal_arr_expand(&p->disabled, scid); continue; } @@ -316,9 +307,9 @@ bool uncertainty_network_update_from_listpeerchannels( scid, capacity); /* FIXME: features? */ - gossmap_local_addchan(renepay->local_gossmods, + gossmap_local_addchan(p->local_gossmods, &src, &dst, &scid, NULL); - gossmap_local_updatechan(renepay->local_gossmods, + gossmap_local_updatechan(p->local_gossmods, &scid, /* TODO(eduardo): does it diff --git a/plugins/renepay/uncertainty_network.h b/plugins/renepay/uncertainty_network.h index 6a8a0bffc9f0..fda20e0249b1 100644 --- a/plugins/renepay/uncertainty_network.h +++ b/plugins/renepay/uncertainty_network.h @@ -14,7 +14,8 @@ bool uncertainty_network_check_invariants(struct chan_extra_map *chan_extra_map) /* Add routehints provided by bolt11 */ void uncertainty_network_add_routehints( struct chan_extra_map *chan_extra_map, - struct renepay *renepay); + const struct route_info **routes, + struct payment *p); /* Mirror the gossmap in the public uncertainty network. * result: Every channel in gossmap must have associated data in chan_extra_map, @@ -40,7 +41,7 @@ void uncertainty_network_channel_can_send( bool uncertainty_network_update_from_listpeerchannels( struct chan_extra_map * chan_extra_map, struct node_id my_id, - struct renepay * renepay, + struct payment *payment, const char *buf, const jsmntok_t *toks); diff --git a/plugins/test/run-route-overlong.c b/plugins/test/run-route-overlong.c index 94d33085273e..f78f0fa561ad 100644 --- a/plugins/test/run-route-overlong.c +++ b/plugins/test/run-route-overlong.c @@ -160,9 +160,6 @@ bool json_to_u32(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, u32 /* Generated stub for json_to_u64 */ bool json_to_u64(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, u64 *num UNNEEDED) { fprintf(stderr, "json_to_u64 called!\n"); abort(); } -/* Generated stub for json_to_s64 */ -bool json_to_s64(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, s64 *num UNNEEDED) -{ fprintf(stderr, "json_to_s64 called!\n"); abort(); } /* Generated stub for json_tok_bin_from_hex */ u8 *json_tok_bin_from_hex(const tal_t *ctx UNNEEDED, const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED) { fprintf(stderr, "json_tok_bin_from_hex called!\n"); abort(); } diff --git a/plugins/topology.c b/plugins/topology.c index 32c2bea4a285..70c3f5e1b14e 100644 --- a/plugins/topology.c +++ b/plugins/topology.c @@ -298,13 +298,14 @@ static struct node_map *local_connected(const tal_t *ctx, json_for_each_arr(i, channel, channels) { struct node_id id; - bool is_connected, normal_chan; - const char *err; + bool is_connected; + const char *err, *state; err = json_scan(tmpctx, buf, channel, - "{peer_id:%,peer_connected:%}", + "{peer_id:%,peer_connected:%,state:%}", JSON_SCAN(json_to_node_id, &id), - JSON_SCAN(json_to_bool, &is_connected)); + JSON_SCAN(json_to_bool, &is_connected), + JSON_SCAN_TAL(tmpctx, json_strdup, &state)); if (err) plugin_err(plugin, "Bad listpeerchannels response (%s): %.*s", err, @@ -314,17 +315,12 @@ static struct node_map *local_connected(const tal_t *ctx, if (!is_connected) continue; - /* Must also have a channel in CHANNELD_NORMAL */ - normal_chan = json_tok_streq(buf, - json_get_member(buf, channel, "state"), - "CHANNELD_NORMAL") - || json_tok_streq(buf, - json_get_member(buf, channel, "state"), - "CHANNELD_AWAITING_SPLICE"); - - if (normal_chan) + /* Must also have a channel in CHANNELD_NORMAL/splice */ + if (streq(state, "CHANNELD_NORMAL") + || streq(state, "CHANNELD_AWAITING_SPLICE")) { node_map_add(connected, tal_dup(connected, struct node_id, &id)); + } } return connected; @@ -566,10 +562,12 @@ static struct command_result *json_listincoming(struct command *cmd, const u8 *peer_features; ourchan = gossmap_nth_chan(gossmap, me, i, &dir); - /* If its half is disabled, ignore. */ - if (!ourchan->half[!dir].enabled) + /* Entirely missing? Ignore. */ + if (ourchan->cupdate_off[!dir] == 0) continue; - + /* We used to ignore if the peer said it was disabled, + * but we have a report of LND telling us our unannounced + * channel is disabled, so we still use them. */ peer = gossmap_nth_node(gossmap, ourchan, !dir); scid = gossmap_chan_scid(gossmap, ourchan); diff --git a/tests/plugins/badinterp.py b/tests/plugins/badinterp.py new file mode 100755 index 000000000000..c69642736b86 --- /dev/null +++ b/tests/plugins/badinterp.py @@ -0,0 +1,15 @@ +#!/some/bad/path/to/python3 +from pyln.client import Plugin +import os +import time + +plugin = Plugin() + + +@plugin.init() +def init(options, configuration, plugin): + plugin.log("slow_init.py initializing {}".format(configuration)) + time.sleep(int(os.getenv('SLOWINIT_TIME', "0"))) + + +plugin.run() diff --git a/tests/test_closing.py b/tests/test_closing.py index 5c60d6ce9f19..9def813691c4 100644 --- a/tests/test_closing.py +++ b/tests/test_closing.py @@ -3354,13 +3354,15 @@ def test_segwit_shutdown_script(node_factory, bitcoind, executor): @pytest.mark.developer("needs to set dev-disconnect") -def test_closing_higherfee(node_factory, bitcoind, executor): - """With anchor outputs we can ask for a *higher* fee than the last commit tx""" +@pytest.mark.parametrize("anchors", [False, True]) +def test_closing_higherfee(node_factory, bitcoind, executor, anchors): + """We can ask for a *higher* fee than the last commit tx""" opts = {'may_reconnect': True, 'dev-no-reconnect': None, - 'experimental-anchors': None, 'feerates': (7500, 7500, 7500, 7500)} + if anchors: + opts['experimental-anchors'] = None # We change the feerate before it starts negotiating close, so it aims # for *higher* than last commit tx. @@ -3379,7 +3381,7 @@ def test_closing_higherfee(node_factory, bitcoind, executor): l1.rpc.connect(l2.info['id'], 'localhost', l2.port) # This causes us to *exceed* previous requirements! - l1.daemon.wait_for_log(r'deriving max fee from rate 30000 -> .*sat \(not 1000000sat\)') + l1.daemon.wait_for_log(r'deriving max fee from rate 30000 -> .*sat') # This will fail because l1 restarted! with pytest.raises(RpcError, match=r'Connection to RPC server lost.'): @@ -3902,3 +3904,16 @@ def test_closing_tx_valid(node_factory, bitcoind): wait_for(lambda: len(bitcoind.rpc.getrawmempool()) == 1) assert only_one(bitcoind.rpc.getrawmempool()) == close['txid'] assert bitcoind.rpc.getrawtransaction(close['txid']) == close['tx'] + + +@pytest.mark.developer("needs dev-no-reconnect") +@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd does not provide feerates on regtest') +def test_closing_minfee(node_factory, bitcoind): + l1, l2 = node_factory.line_graph(2, opts={'feerates': None}) + + l1.rpc.pay(l2.rpc.invoice(10000000, 'test', 'test')['bolt11']) + + wait_for(lambda: only_one(l1.rpc.listpeerchannels()['channels'])['htlcs'] == []) + + txid = l1.rpc.close(l2.info['id'])['txid'] + bitcoind.generate_block(1, wait_for_mempool=txid) diff --git a/tests/test_gossip.py b/tests/test_gossip.py index b394db05c510..9663cd2e7402 100755 --- a/tests/test_gossip.py +++ b/tests/test_gossip.py @@ -2383,3 +2383,96 @@ def test_dump_own_gossip(node_factory): # We should get exactly what we expected. assert expect == [] + + +@pytest.mark.developer("needs --dev-gossip-time") +@unittest.skipIf( + TEST_NETWORK != 'regtest', + "Channel announcement contains genesis hash, receiving node discards on mismatch" +) +def test_read_spam_nannounce(node_factory, bitcoind): + """issue #6531 lead to a node announcement not being deleted from + the gossip_store.""" + """broadcastable and spam node announcements should be loaded properly when + reading the gossip_store - even when they are both pending a channel + update.""" + opts = {'dev-gossip-time': 1691773540} + l1 = node_factory.get_node(start=False, opts=opts) + canned_store = ( + "0c" # Gossip store version byte + "0000" # length flags + "01b0" # length + "d163af25" # checksum + "64d66a3a" # timestamp + # Channel announcement + "010000335733f5942df5d950eb8766dee3a9d6626922844ed6ae7a110dd7e7edc32e3f6f3d9ac5cdea23ce25bb8dbf761fd3d5fc56c05b6856316d12e9d32ca0f08c69ca0306fe716e7b5151317d6440b7373d9fbc646ead48f2163f2b6d511afe6a79c75551c2620fc80974f2f864329d9778a08cdfbc9f2c9c1344c432702c66807cfb4db69b80fae8c33c70143d948b36614d620891bee2df99c86bc62207c17e3b9186214c0ccff2ded5598accc90eb1d5b2f7a83cd7f68d712ea047d8019f343063b0a236356a387146f58fa832ddc13c4714522cbb503f5f3ca8fcec6602be2438ad3f98fa0ceed58fe3a066d385fcacd98c704b2a8e98a3e20bf76b35b736000006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f0000670000010000022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59035d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d029053521d6ea7a52cdd55f733d0fb2d077c0373b0053b5b810d927244061b757302d6063d022691b2490ab454dee73a57c6ff5d308352b461ece69f3c284f2c2412" + # Channel Amount + "0000000a911183f600000000" + "100500000000000f4240" + # broadcastable node announcement (rgb=000001) + "0000009533d9cf8c64d66a44" + "010108f4e25debdd74d0f52b7f1da5dbd82f429d057f48e3d2ed49fcc65cfe3e185c086c1a83d8f3bb15dc0cc852d80390c24cd1fe6d288b91eb55cf98c4a9baf7c0000788a0000a0269a264d66a44022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d5900000153494c454e544152544953542d3536392d67303030643963302d6d6f646465640000" + # Rate-limited node announcment (rgb=000002) + "2000009519aa897a64d66a49" + "0101685f67556cd0a87e04c6d1e9daa4e31dcf14f881d6f1231b1bee8adf6666977b6b9baa497e91d2b6daae726cde69e3faf924d9c95d0ca6b374d6693d4fc0d648000788a0000a0269a264d66a49022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d5900000253494c454e544152544953542d3536392d67303030643963302d6d6f646465640000" + # Channel Update + "0000008a0234021c64d66a61" + "010242ce9d9e79f939399ea1291c04fffcafdfa911246464a4b48c16b7b816dd57b4168562a6c519eb31c37718b68bdfc6345d7c2db663b8b04a3558ce7736c5b61706226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f000067000001000064d66a6101000006000000000000000000000015000003e8000000003b023380" + # Channel Update + "0000008acd55fcb264d66a61" + "01023f5dd1f69f675a71d2a7a34956b26f12c4fe9ee287a8449a3eb7b756c583e3bb1334a8eb7c3e148d0f43e08b95c50017ba62da9a7843fe4850a3cb3c74dc5e2c06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f000067000001000064d66a6101010006000000000000000000000015000003e8000000003b023380" + ) + with open(os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, 'gossip_store'), 'wb') as f: + f.write(bytearray.fromhex(canned_store)) + + bitcoind.generate_block(1) + tx = bitcoind.rpc.createrawtransaction( + [], + [ + # Fundrawtransaction will fill in the first output with the change + {"bcrt1qpd7nwe3jrt07st89uy82nn2xrmqtxyzqpty5ygt6w546lf6n0wcskswjvh": 0.01000000} + ] + ) + tx = bitcoind.rpc.fundrawtransaction(tx, {'changePosition': 0})['hex'] + tx = bitcoind.rpc.signrawtransactionwithwallet(tx)['hex'] + txid = bitcoind.rpc.sendrawtransaction(tx) + wait_for(lambda: txid in bitcoind.rpc.getrawmempool()) + bitcoind.generate_block(6) + l1.start() + # retrieves node info originating from the spam announcement + node_info = l1.rpc.listnodes('022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59') + assert only_one(node_info['nodes'])['color'] == '000002' + + out = subprocess.run(['devtools/gossipwith', + '--initial-sync', + '--timeout-after={}'.format(int(math.sqrt(TIMEOUT) + 1)), + '--hex', + '{}@localhost:{}'.format(l1.info['id'], l1.port)], + check=True, + timeout=TIMEOUT, stdout=subprocess.PIPE).stdout.decode() + + received_broadcastable = False + received_spam = False + for message in out.splitlines(): + gos = subprocess.run(['devtools/decodemsg', message], check=True, + timeout=TIMEOUT, + stdout=subprocess.PIPE).stdout.decode() + + for line in gos.splitlines(): + if 'rgb_color=[000001]' in line: + received_broadcastable = True + if 'rgb_color=[000002]' in line: + received_spam = True + + assert received_broadcastable + assert not received_spam + # Send a new node announcement. It should replace both. + subprocess.run(['devtools/gossipwith', + '--max-messages=0', + '{}@localhost:{}'.format(l1.info['id'], l1.port), + # color=000003 + '0101273fd2c58deb4c3bd98610079657219a5c8291d6a85c3607eae895f25e08babd6e45edd1e62f719b20526ed1c8fc3c7d9e7e3fafa4f24e4cb64872d041a13503000788a0000a0269a264d66a4e022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d5900000353494c454e544152544953542d3536392d67303030643963302d6d6f646465640000'], + check=True, timeout=TIMEOUT) + l1.daemon.wait_for_log('Received node_announcement') + l1.restart() + assert not l1.daemon.is_in_log('BROKEN') diff --git a/tests/test_misc.py b/tests/test_misc.py index 620d2922720f..32edc4ffa94e 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -1195,6 +1195,13 @@ def test_daemon_option(node_factory): assert 'No child process' not in f.read() +def test_cli_no_argument(): + """If no arguments are provided, should display help and exit.""" + out = subprocess.run(['cli/lightning-cli'], stdout=subprocess.PIPE) + assert out.returncode in [0, 2] # returns 2 if lightning-rpc not available + assert "Usage: cli/lightning-cli [...]" in out.stdout.decode() + + @pytest.mark.developer("needs DEVELOPER=1") def test_blockchaintrack(node_factory, bitcoind): """Check that we track the blockchain correctly across reorgs diff --git a/tests/test_pay.py b/tests/test_pay.py index 38d739c63f02..bab6e28e0c28 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -5383,3 +5383,30 @@ def test_listsendpays_crash(node_factory): inv = l1.rpc.invoice(40, "inv", "inv")["bolt11"] l1.rpc.listsendpays('lightning:' + inv) + + +@pytest.mark.developer("updates are delayed without --dev-fast-gossip") +def test_pay_routehint_minhtlc(node_factory, bitcoind): + # l1 -> l2 -> l3 private -> l4 + l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True) + l4 = node_factory.get_node() + + l3.fundchannel(l4, announce_channel=False) + + # l2->l3 required htlc of at least 1sat + scid = only_one(l2.rpc.setchannel(l3.info['id'], htlcmin=1000)['channels'])['short_channel_id'] + + # Make sure l4 knows about l1 + wait_for(lambda: l4.rpc.listnodes(l1.info['id'])['nodes'] != []) + + # And make sure l1 knows that l2->l3 has htlcmin 1000 + wait_for(lambda: l1.rpc.listchannels(scid)['channels'][0]['htlc_minimum_msat'] == Millisatoshi(1000)) + + inv = l4.rpc.invoice(100000, "inv", "inv") + assert only_one(l1.rpc.decodepay(inv['bolt11'])['routes']) + + # You should be able to pay the invoice! + l1.rpc.pay(inv['bolt11']) + + # And you should also be able to getroute (and have it ignore htlc_min/max constraints!) + l1.rpc.getroute(l3.info['id'], amount_msat=0, riskfactor=1) diff --git a/tests/test_plugin.py b/tests/test_plugin.py index c5549edd7d42..63ea0425b712 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -9,7 +9,7 @@ DEPRECATED_APIS, expected_peer_features, expected_node_features, expected_channel_features, account_balance, check_coin_moves, first_channel_id, EXPERIMENTAL_DUAL_FUND, - mine_funding_to_announce + mine_funding_to_announce, VALGRIND ) import ast @@ -4273,3 +4273,20 @@ def test_renepay_not_important(node_factory): # But we don't shut down, and we can restrart. assert [p['name'] for p in l1.rpc.listconfigs()['plugins'] if p['name'] == 'cln-renepay'] == [] l1.rpc.plugin_start(os.path.join(os.getcwd(), 'plugins/cln-renepay')) + + +@unittest.skipIf(VALGRIND, "Valgrind doesn't handle bad #! lines the same") +def test_plugin_nostart(node_factory): + "Should not appear in list if it didn't even start" + + l1 = node_factory.get_node() + with pytest.raises(RpcError, match="badinterp.py: opening pipe: No such file or directory"): + l1.rpc.plugin_start(os.path.join(os.getcwd(), 'tests/plugins/badinterp.py')) + + assert [p['name'] for p in l1.rpc.plugin_list()['plugins'] if 'badinterp' in p['name']] == [] + + +def test_plugin_startdir_lol(node_factory): + """Though we fail to start many of them, we don't crash!""" + l1 = node_factory.get_node(allow_broken_log=True) + l1.rpc.plugin_startdir(os.path.join(os.getcwd(), 'tests/plugins')) diff --git a/tests/test_renepay.py b/tests/test_renepay.py index 53c5afbe0f14..7029f213f3ee 100644 --- a/tests/test_renepay.py +++ b/tests/test_renepay.py @@ -1,10 +1,11 @@ from fixtures import * # noqa: F401,F403 from pyln.client import RpcError, Millisatoshi -from utils import only_one, wait_for, mine_funding_to_announce, sync_blockheight +from utils import only_one, wait_for, mine_funding_to_announce, sync_blockheight, TEST_NETWORK import pytest import random import time import json +import subprocess def test_simple(node_factory): @@ -48,6 +49,8 @@ def test_errors(node_factory, bitcoind): l1, l2, l3, l4, l5, l6 = node_factory.get_nodes(6, opts=opts * 6) send_amount = Millisatoshi('21sat') inv = l6.rpc.invoice(send_amount, 'test_renepay', 'description')['bolt11'] + inv_deleted = l6.rpc.invoice(send_amount, 'test_renepay2', 'description2')['bolt11'] + l6.rpc.delinvoice('test_renepay2', 'unpaid') failmsg = r'We don\'t have any channels' with pytest.raises(RpcError, match=failmsg): @@ -57,7 +60,7 @@ def test_errors(node_factory, bitcoind): node_factory.join_nodes([l1, l3, l5], wait_for_announce=True, fundamount=1000000) - failmsg = r'Destination is unreacheable in the network gossip.' + failmsg = r'Destination is unknown in the network gossip.' with pytest.raises(RpcError, match=failmsg): l1.rpc.call('renepay', {'invstring': inv}) @@ -81,6 +84,14 @@ def test_errors(node_factory, bitcoind): assert details['amount_msat'] == send_amount assert details['destination'] == l6.info['id'] + # Test error from final node. + with pytest.raises(RpcError) as err: + l1.rpc.call('renepay', {'invstring': inv_deleted}) + + PAY_DESTINATION_PERM_FAIL = 203 + assert err.value.error['code'] == PAY_DESTINATION_PERM_FAIL + assert 'WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS' in err.value.error['message'] + @pytest.mark.developer("needs to deactivate shadow routing") @pytest.mark.openchannel('v1') @@ -198,7 +209,7 @@ def test_limits(node_factory): # FIXME: pylightning should define these! # PAY_STOPPED_RETRYING = 210 - PAY_ROUTE_NOT_FOUND = 205 + PAY_ROUTE_TOO_EXPENSIVE = 206 inv = l6.rpc.invoice("any", "any", 'description') @@ -207,7 +218,7 @@ def test_limits(node_factory): with pytest.raises(RpcError, match=failmsg) as err: l1.rpc.call( 'renepay', {'invstring': inv['bolt11'], 'amount_msat': 1000000, 'maxfee': 1}) - assert err.value.error['code'] == PAY_ROUTE_NOT_FOUND + assert err.value.error['code'] == PAY_ROUTE_TOO_EXPENSIVE # TODO(eduardo): which error code shall we use here? # TODO(eduardo): shall we list attempts in renepay? @@ -218,7 +229,7 @@ def test_limits(node_factory): with pytest.raises(RpcError, match=failmsg) as err: l1.rpc.call( 'renepay', {'invstring': inv['bolt11'], 'amount_msat': 1000000, 'maxdelay': 0}) - assert err.value.error['code'] == PAY_ROUTE_NOT_FOUND + assert err.value.error['code'] == PAY_ROUTE_TOO_EXPENSIVE inv2 = l6.rpc.invoice("800000sat", "inv2", 'description') l1.rpc.call( @@ -299,8 +310,19 @@ def test_hardmpp(node_factory): print(json.dumps(l3.rpc.listpeerchannels()), file=f) inv2 = l6.rpc.invoice("1800000sat", "inv2", 'description') - l1.rpc.call( - 'renepay', {'invstring': inv2['bolt11']}) + + out = subprocess.check_output(['cli/lightning-cli', + '--network={}'.format(TEST_NETWORK), + '--lightning-dir={}' + .format(l1.daemon.lightning_dir), + '-k', + 'renepay', 'invstring={}'.format(inv2['bolt11'])]).decode('utf-8') + lines = out.split('\n') + # First comes commentry + assert any([l.startswith('#') for l in lines]) + + # Now comes JSON + json.loads("".join([l for l in lines if not l.startswith('#')])) l1.wait_for_htlcs() invoice = only_one(l6.rpc.listinvoices('inv2')['invoices']) assert isinstance(invoice['amount_received_msat'], Millisatoshi) diff --git a/tests/test_splicing.py b/tests/test_splicing.py index f59017454ee4..959e4d4dc614 100644 --- a/tests/test_splicing.py +++ b/tests/test_splicing.py @@ -2,6 +2,7 @@ from utils import TEST_NETWORK import pytest import unittest +import time @pytest.mark.openchannel('v1') @@ -34,3 +35,7 @@ def test_splice(node_factory, bitcoind): inv = l2.rpc.invoice(10**2, '3', 'no_3') l1.rpc.pay(inv['bolt11']) + + # Check that the splice doesn't generate a unilateral close transaction + time.sleep(5) + assert l1.db_query("SELECT count(*) as c FROM channeltxs;")[0]['c'] == 0 diff --git a/tests/utils.py b/tests/utils.py index 789b7b8026a1..3063b89aff4c 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -44,7 +44,7 @@ def expected_peer_features(wumbo_channels=False, extra=[]): features += [29] if EXPERIMENTAL_SPLICING: features += [35] # option_quiesce - features += [63] # option_splice + features += [163] # option_experimental_splice return hex_bits(features + extra) @@ -60,7 +60,7 @@ def expected_node_features(wumbo_channels=False, extra=[]): features += [29] if EXPERIMENTAL_SPLICING: features += [35] # option_quiesce - features += [63] # option_splice + features += [163] # option_experimental_splice return hex_bits(features + extra) diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c index 7aabbd33a221..4c76dd836041 100644 --- a/wallet/test/run-wallet.c +++ b/wallet/test/run-wallet.c @@ -309,6 +309,10 @@ void json_add_pubkey(struct json_stream *response UNNEEDED, void json_add_s32(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, int32_t value UNNEEDED) { fprintf(stderr, "json_add_s32 called!\n"); abort(); } +/* Generated stub for json_add_s64 */ +void json_add_s64(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, + int64_t value UNNEEDED) +{ fprintf(stderr, "json_add_s64 called!\n"); abort(); } /* Generated stub for json_add_secret */ void json_add_secret(struct json_stream *response UNNEEDED, const char *fieldname UNNEEDED, @@ -354,10 +358,6 @@ void json_add_u32(struct json_stream *result UNNEEDED, const char *fieldname UNN void json_add_u64(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, uint64_t value UNNEEDED) { fprintf(stderr, "json_add_u64 called!\n"); abort(); } -/* Generated stub for json_add_s64 */ -void json_add_s64(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, - int64_t value UNNEEDED) -{ fprintf(stderr, "json_add_s64 called!\n"); abort(); } /* Generated stub for json_add_uncommitted_channel */ void json_add_uncommitted_channel(struct json_stream *response UNNEEDED, const struct uncommitted_channel *uc UNNEEDED, diff --git a/wallet/wallet.c b/wallet/wallet.c index ff9f448992fd..6275f29fc4f2 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -2323,7 +2323,8 @@ void wallet_channel_save(struct wallet *w, struct channel *chan) /* Update the inflights also */ struct channel_inflight *inflight; list_for_each(&chan->inflights, inflight, list) - wallet_inflight_save(w, inflight); + if (!inflight->splice_locked_memonly) + wallet_inflight_save(w, inflight); db_bind_talarr(stmt, last_sent_commit); db_bind_u64(stmt, chan->dbid);