diff --git a/plugins/pay.c b/plugins/pay.c index 9dfedfc25..23a9170c5 100644 --- a/plugins/pay.c +++ b/plugins/pay.c @@ -24,63 +24,8 @@ static unsigned int maxdelay_default; static bool exp_offers; static bool disablempp = false; -static LIST_HEAD(pay_status); - static LIST_HEAD(payments); -struct pay_attempt { - /* What we changed when starting this attempt. */ - const char *why; - /* Time we started & finished attempt */ - struct timeabs start, end; - /* Route hint we were using (if any) */ - struct route_info *routehint; - /* Channels we excluded when doing route lookup. */ - const char **excludes; - /* Route we got (NULL == route lookup fail). */ - const char *route; - /* Did we actually try to send a payment? */ - bool sendpay; - /* The failure result (NULL on success) */ - struct json_out *failure; - /* The non-failure result (NULL on failure) */ - const char *result; - /* The blockheight at which the payment attempt was - * started. */ - u32 start_block; -}; - -struct pay_status { - /* Destination, as text */ - const char *dest; - - /* We're in 'pay_status' global list. */ - struct list_node list; - - /* Description user provided (if any) */ - const char *label; - /* Amount they wanted to pay. */ - struct amount_msat msat; - /* CLTV delay required by destination. */ - u32 final_cltv; - /* Bolt11/bolt12 invoice. */ - const char *invstring; - - /* What we did about routehints (if anything) */ - const char *routehint_modifications; - - /* Details of shadow route we chose (if any) */ - char *shadow; - - /* Details of initial exclusions (if any) */ - const char *exclusions; - - /* Array of payment attempts. */ - struct pay_attempt *attempts; - - struct sha256 payment_hash; -}; - struct pay_command { /* Global state */ struct plugin *plugin; @@ -141,1257 +86,6 @@ struct pay_command { const char *shadow_dest; }; -static struct pay_attempt *current_attempt(struct pay_command *pc) -{ - return &pc->ps->attempts[tal_count(pc->ps->attempts)-1]; -} - -/* Helper to copy JSON object directly into a json_out */ -static void json_out_add_raw_len(struct json_out *jout, - const char *fieldname, - const char *jsonstr, size_t len) -{ - char *p; - - p = json_out_member_direct(jout, fieldname, len); - memcpy(p, jsonstr, len); -} - -static struct json_out *failed_start(struct pay_command *pc) -{ - struct pay_attempt *attempt = current_attempt(pc); - - attempt->end = time_now(); - attempt->failure = json_out_new(pc->ps->attempts); - json_out_start(attempt->failure, NULL, '{'); - return attempt->failure; -} - -static void failed_end(struct json_out *jout) -{ - json_out_end(jout, '}'); - json_out_finished(jout); -} - -/* Copy field and member to output, if it exists: return member */ -static const jsmntok_t *copy_member(struct json_out *ret, - const char *buf, const jsmntok_t *obj, - const char *membername) -{ - const jsmntok_t *m = json_get_member(buf, obj, membername); - if (!m) - return NULL; - - /* FIXME: The fact it is a string is probably the wrong thing - * to handle: if it *is* a string we should probably copy - * the quote marks, but json_tok_full/json_tok_full_len - * specifically remove those. - * It works *now* because it is only used in "code" and - * "data": "code" is always numeric, and "data" is usually - * a JSON object/key-value table, but pure stromgs will - * probably result in invalid JSON. - */ - /* Literal copy: it's already JSON escaped, and may be a string. */ - json_out_add_raw_len(ret, membername, - json_tok_full(buf, m), json_tok_full_len(m)); - return m; -} - -/* Copy (and modify) error object. */ -static void attempt_failed_tok(struct pay_command *pc, const char *method, - const char *buf, const jsmntok_t *errtok) -{ - const jsmntok_t *msg = json_get_member(buf, errtok, "message"); - struct json_out *failed = failed_start(pc); - - /* Every JSON error response has code and error. */ - copy_member(failed, buf, errtok, "code"); - json_out_add(failed, "message", true, - "Call to %s: %.*s", - method, msg->end - msg->start, - buf + msg->start); - copy_member(failed, buf, errtok, "data"); - failed_end(failed); -} - -static struct command_result *start_pay_attempt(struct command *cmd, - struct pay_command *pc, - const char *fmt, ...); - -/* Is this (erring) channel within the routehint itself? */ -static bool node_or_channel_in_routehint(struct plugin *plugin, - const struct route_info *routehint, - const char *idstr, size_t idlen) -{ - struct node_id nodeid; - struct short_channel_id scid; - bool node_err = true; - - if (!node_id_from_hexstr(idstr, idlen, &nodeid)) { - if (!short_channel_id_from_str(idstr, idlen, &scid)) - plugin_err(plugin, "bad erring_node or erring_channel '%.*s'", - (int)idlen, idstr); - else - node_err = false; - } - - for (size_t i = 0; i < tal_count(routehint); i++) { - if (node_err) { - if (node_id_eq(&nodeid, &routehint[i].pubkey)) - return true; - } else { - if (short_channel_id_eq(&scid, &routehint[i].short_channel_id)) - return true; - } - } - return false; -} - -/* Count times we actually tried to pay, not where route lookup failed or - * we disliked route for being too expensive, etc. */ -static size_t count_sendpays(const struct pay_attempt *attempts) -{ - size_t n = 0; - - for (size_t i = 0; i < tal_count(attempts); i++) - n += attempts[i].sendpay; - - return n; -} - -static struct command_result *waitsendpay_expired(struct command *cmd, - struct pay_command *pc) -{ - char *errmsg; - struct json_stream *data; - size_t num_attempts = count_sendpays(pc->ps->attempts); - - errmsg = tal_fmt(pc, "Gave up after %zu attempt%s: see paystatus", - num_attempts, num_attempts == 1 ? "" : "s"); - data = jsonrpc_stream_fail(cmd, PAY_STOPPED_RETRYING, errmsg); - json_object_start(data, "data"); - json_array_start(data, "attempts"); - for (size_t i = 0; i < tal_count(pc->ps->attempts); i++) { - json_object_start(data, NULL); - if (pc->ps->attempts[i].route) - json_add_jsonstr(data, "route", - pc->ps->attempts[i].route); - json_out_add_splice(data->jout, "failure", - pc->ps->attempts[i].failure); - json_object_end(data); - } - json_array_end(data); - json_object_end(data); - return command_finished(cmd, data); -} - -static bool routehint_excluded(struct plugin *plugin, - const struct route_info *routehint, - const char **excludes) -{ - /* Note that we ignore direction here: in theory, we could have - * found that one direction of a channel is unavailable, but they - * are suggesting we use it the other way. Very unlikely though! */ - for (size_t i = 0; i < tal_count(excludes); i++) - if (node_or_channel_in_routehint(plugin, - routehint, - excludes[i], - strlen(excludes[i]))) - return true; - return false; -} - -static struct command_result *next_routehint(struct command *cmd, - struct pay_command *pc) -{ - size_t num_attempts = count_sendpays(pc->ps->attempts); - - while (tal_count(pc->routehints) > 0) { - if (!routehint_excluded(pc->plugin, pc->routehints[0], - pc->excludes)) { - pc->current_routehint = pc->routehints[0]; - tal_arr_remove(&pc->routehints, 0); - return start_pay_attempt(cmd, pc, "Trying route hint"); - } - tal_free(pc->routehints[0]); - tal_arr_remove(&pc->routehints, 0); - } - - /* No (more) routehints; we're out of routes. */ - /* If we eliminated one because it was too pricy, return that. */ - if (pc->expensive_route) - return command_fail(cmd, PAY_ROUTE_TOO_EXPENSIVE, - "%s", pc->expensive_route); - - if (num_attempts > 0) - return command_fail(cmd, PAY_STOPPED_RETRYING, - "Ran out of routes to try after" - " %zu attempt%s: see paystatus", - num_attempts, num_attempts == 1 ? "" : "s"); - - return command_fail(cmd, PAY_ROUTE_NOT_FOUND, - "Could not find a route"); -} - -static struct command_result * -waitblockheight_done(struct command *cmd, - const char *buf UNUSED, - const jsmntok_t *result UNUSED, - struct pay_command *pc) -{ - return start_pay_attempt(cmd, pc, - "Retried due to blockheight " - "disagreement with payee"); -} -static struct command_result * -waitblockheight_error(struct command *cmd, - const char *buf UNUSED, - const jsmntok_t *error UNUSED, - struct pay_command *pc) -{ - if (time_after(time_now(), pc->stoptime)) - return waitsendpay_expired(cmd, pc); - else - /* Ehhh just retry it. */ - return waitblockheight_done(cmd, buf, error, pc); -} - -static struct command_result * -execute_waitblockheight(struct command *cmd, - u32 blockheight, - struct pay_command *pc) -{ - struct out_req *req; - struct timeabs now = time_now(); - struct timerel remaining; - - if (time_after(now, pc->stoptime)) - return waitsendpay_expired(cmd, pc); - - remaining = time_between(pc->stoptime, now); - - req = jsonrpc_request_start(cmd->plugin, cmd, "waitblockheight", - &waitblockheight_done, - &waitblockheight_error, - pc); - json_add_u32(req->js, "blockheight", blockheight); - json_add_u32(req->js, "timeout", time_to_sec(remaining)); - - return send_outreq(cmd->plugin, req); -} - -/* Gets the remote height from a - * WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS - * failure. - * Return 0 if unable to find such a height. - */ -static u32 -get_remote_block_height(const char *buf, const jsmntok_t *error) -{ - const u8 *raw_message; - size_t raw_message_len; - u16 type; - - /* Is there even a raw_message? */ - if (json_scan(tmpctx, buf, error, "{data:{raw_message:%}}", - JSON_SCAN_TAL(tmpctx, json_tok_bin_from_hex, - &raw_message)) != NULL) - return 0; - - /* BOLT #4: - * - * 1. type: PERM|15 (`incorrect_or_unknown_payment_details`) - * 2. data: - * * [`u64`:`htlc_msat`] - * * [`u32`:`height`] - * - */ - raw_message_len = tal_count(raw_message); - - type = fromwire_u16(&raw_message, &raw_message_len); /* type */ - if (type != WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS) - return 0; - - (void) fromwire_u64(&raw_message, &raw_message_len); /* htlc_msat */ - - return fromwire_u32(&raw_message, &raw_message_len); /* height */ -} - -static struct command_result *waitsendpay_error(struct command *cmd, - const char *buf, - const jsmntok_t *error, - struct pay_command *pc) -{ - struct pay_attempt *attempt = current_attempt(pc); - errcode_t code; - int failcode; - const char *err; - - attempt_failed_tok(pc, "waitsendpay", buf, error); - - err = json_scan(tmpctx, buf, error, "{code:%}", - JSON_SCAN(json_to_errcode, &code)); - if (err) - plugin_err(cmd->plugin, "waitsendpay error %s? '%.*s'", - err, - json_tok_full_len(error), json_tok_full(buf, error)); - - if (code != PAY_UNPARSEABLE_ONION) { - err = json_scan(tmpctx, buf, error, "{data:{failcode:%}}", - JSON_SCAN(json_to_int, &failcode)); - if (err) - plugin_err(cmd->plugin, "waitsendpay failcode error %s '%.*s'", - err, - json_tok_full_len(error), json_tok_full(buf, error)); - } - - /* Special case for WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS. - * - * One possible trigger for this failure is that the receiver - * thinks the final timeout it gets is too near the future. - * - * For the most part, we respect the indicated `final_cltv` - * in the invoice, and our shadow routing feature also tends - * to give more timing budget to the receiver than the - * `final_cltv`. - * - * However, there is an edge case possible on real networks: - * - * * We send out a payment respecting the `final_cltv` of - * the receiver. - * * Miners mine a new block while the payment is in transit. - * * By the time the payment reaches the receiver, the - * payment violates the `final_cltv` because the receiver - * is now using a different basis blockheight. - * - * This is a transient error. - * Unfortunately, WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS - * is marked with the PERM bit. - * This means that we would give up on this since `waitsendpay` - * would return PAY_DESTINATION_PERM_FAIL instead of - * PAY_TRY_OTHER_ROUTE. - * Thus the `pay` plugin would not retry this case. - * - * Thus, we need to add this special-case checking here, where - * the blockheight when we started the pay attempt was not - * the same as what the payee reports. - * - * In the past this particular failure had its own failure code, - * equivalent to 17. - * In case the receiver is a really old software, we also - * special-case it here. - */ - if ((code != PAY_UNPARSEABLE_ONION) && - ((failcode == 17) || - ((failcode == WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS) && - (attempt->start_block < get_remote_block_height(buf, error))))) { - u32 target_blockheight; - - if (failcode == 17) - target_blockheight = attempt->start_block + 1; - else - target_blockheight = get_remote_block_height(buf, error); - - return execute_waitblockheight(cmd, target_blockheight, - pc); - } - - /* FIXME: Handle PAY_UNPARSEABLE_ONION! */ - - /* Many error codes are final. */ - if (code != PAY_TRY_OTHER_ROUTE) { - return forward_error(cmd, buf, error, pc); - } - - if (time_after(time_now(), pc->stoptime)) { - return waitsendpay_expired(cmd, pc); - } - - if (failcode & NODE) { - struct node_id id; - const char *idstr, *err; - - err = json_scan(tmpctx, buf, error, "{data:{erring_node:%}}", - JSON_SCAN(json_to_node_id, &id)); - if (err) - plugin_err(cmd->plugin, "waitsendpay error %s '%.*s'", - err, - json_tok_full_len(error), - json_tok_full(buf, error)); - - /* FIXME: Keep as node_id, don't use strings. */ - idstr = node_id_to_hexstr(tmpctx, &id); - - /* If failure is in routehint part, try next one */ - if (node_or_channel_in_routehint(pc->plugin, pc->current_routehint, - idstr, strlen(idstr))) - return next_routehint(cmd, pc); - - /* Otherwise, add erring channel to exclusion list. */ - tal_arr_expand(&pc->excludes, tal_steal(pc->excludes, idstr)); - } else { - struct short_channel_id scid; - u32 dir; - const char *scidstr, *err; - - err = json_scan(tmpctx, buf, error, - "{data:{erring_channel:%,erring_direction:%}}", - JSON_SCAN(json_to_short_channel_id, &scid), - JSON_SCAN(json_to_number, &dir)); - if (err) - plugin_err(cmd->plugin, "waitsendpay error %s '%.*s'", - err, - json_tok_full_len(error), - json_tok_full(buf, error)); - - scidstr = short_channel_id_to_str(tmpctx, &scid); - /* If failure is in routehint part, try next one */ - if (node_or_channel_in_routehint(pc->plugin, pc->current_routehint, - scidstr, strlen(scidstr))) - return next_routehint(cmd, pc); - - /* Otherwise, add erring channel to exclusion list. */ - tal_arr_expand(&pc->excludes, - tal_fmt(pc->excludes, "%s/%u", scidstr, dir)); - } - - /* Try again. */ - return start_pay_attempt(cmd, pc, "Excluded %s %s", - failcode & NODE ? "node" : "channel", - pc->excludes[tal_count(pc->excludes)-1]); -} - -static struct command_result *waitsendpay_done(struct command *cmd, - const char *buf, - const jsmntok_t *result, - struct pay_command *pc) -{ - struct pay_attempt *attempt = current_attempt(pc); - - attempt->result = json_strdup(pc->ps->attempts, buf, result); - attempt->end = time_now(); - - return forward_result(cmd, buf, result, pc); -} - -static struct command_result *sendpay_done(struct command *cmd, - const char *buf, - const jsmntok_t *result, - struct pay_command *pc) -{ - struct out_req *req = jsonrpc_request_start(cmd->plugin, cmd, - "waitsendpay", - waitsendpay_done, - waitsendpay_error, pc); - json_add_string(req->js, "payment_hash", pc->payment_hash); - - return send_outreq(cmd->plugin, req); -} - -/* Calculate how many millisatoshi we need at the start of this route - * to get msatoshi to the end. */ -static bool route_msatoshi(struct amount_msat *total, - const struct amount_msat msat, - const struct route_info *route, size_t num_route) -{ - *total = msat; - for (ssize_t i = num_route - 1; i >= 0; i--) { - if (!amount_msat_add_fee(total, - route[i].fee_base_msat, - route[i].fee_proportional_millionths)) - return false; - } - return true; -} - -/* Calculate cltv we need at the start of this route to get cltv at the end. */ -static u32 route_cltv(u32 cltv, - const struct route_info *route, size_t num_route) -{ - for (size_t i = 0; i < num_route; i++) - cltv += route[i].cltv_expiry_delta; - return cltv; -} - -/* The pubkey to use is the destination of this routehint. */ -static const char *route_pubkey(const tal_t *ctx, - const struct pay_command *pc, - const struct route_info *routehint, - size_t n) -{ - if (n == tal_count(routehint)) - return pc->dest; - return type_to_string(ctx, struct node_id, &routehint[n].pubkey); -} - -static const char *join_routehint(const tal_t *ctx, - const char *buf, - const jsmntok_t *route, - const struct pay_command *pc, - const struct route_info *routehint) -{ - char *ret; - - /* Truncate closing ] from route */ - ret = tal_strndup(ctx, buf + route->start, route->end - route->start - 1); - for (size_t i = 0; i < tal_count(routehint); i++) { - /* amount to be received by *destination* */ - struct amount_msat dest_amount; - - if (!route_msatoshi(&dest_amount, pc->msat, - routehint + i + 1, - tal_count(routehint) - i - 1)) - return tal_free(ret); - - tal_append_fmt(&ret, ", {" - " \"id\": \"%s\"," - " \"channel\": \"%s\"," - " \"msatoshi\": \"%s\"," - " \"delay\": %u }", - /* pubkey of *destination* */ - route_pubkey(tmpctx, pc, routehint, i + 1), - type_to_string(tmpctx, struct short_channel_id, - &routehint[i].short_channel_id), - type_to_string(tmpctx, struct amount_msat, - &dest_amount), - /* cltv for *destination* */ - route_cltv(pc->final_cltv, routehint + i + 1, - tal_count(routehint) - i - 1)); - } - /* Put ] back */ - tal_append_fmt(&ret, "]"); - return ret; -} - -static struct command_result *sendpay_error(struct command *cmd, - const char *buf, - const jsmntok_t *error, - struct pay_command *pc) -{ - attempt_failed_tok(pc, "sendpay", buf, error); - - return forward_error(cmd, buf, error, pc); -} - -static const jsmntok_t *find_worst_channel(const char *buf, - const jsmntok_t *route, - const char *fieldname) -{ - u64 prev, worstval = 0; - const jsmntok_t *worst = NULL, *t, *t_prev = NULL; - size_t i; - - json_for_each_arr(i, t, route) { - u64 val; - - json_to_u64(buf, json_get_member(buf, t, fieldname), &val); - - /* For the first hop, now we can't know if it's the worst. - * Just store the info and continue. */ - if (!i) { - prev = val; - t_prev = t; - continue; - } - - if (worst == NULL || prev - val > worstval) { - worst = t_prev; - worstval = prev - val; - } - prev = val; - t_prev = t; - } - - return worst; -} - -/* Can't exclude if it's in routehint itself. */ -static bool maybe_exclude(struct pay_command *pc, - const char *buf, const jsmntok_t *route) -{ - const jsmntok_t *scid, *dir; - - if (!route) - return false; - - scid = json_get_member(buf, route, "channel"); - - if (node_or_channel_in_routehint(pc->plugin, - pc->current_routehint, - buf + scid->start, - scid->end - scid->start)) - return false; - - dir = json_get_member(buf, route, "direction"); - tal_arr_expand(&pc->excludes, - tal_fmt(pc->excludes, "%.*s/%c", - scid->end - scid->start, - buf + scid->start, - buf[dir->start])); - return true; -} - -static struct command_result *getroute_done(struct command *cmd, - const char *buf, - const jsmntok_t *result, - struct pay_command *pc) -{ - struct pay_attempt *attempt = current_attempt(pc); - const jsmntok_t *t = json_get_member(buf, result, "route"); - struct amount_msat fee; - struct amount_msat max_fee; - u32 delay; - struct out_req *req; - const char *err; - - if (!t) - plugin_err(cmd->plugin, "getroute gave no 'route'? '%.*s'", - result->end - result->start, buf); - - if (pc->current_routehint) { - attempt->route = join_routehint(pc->ps->attempts, buf, t, - pc, pc->current_routehint); - if (!attempt->route) { - struct json_out *failed = failed_start(pc); - json_out_add(failed, "message", true, - "Joining routehint gave absurd fee"); - failed_end(failed); - return next_routehint(cmd, pc); - } - } else - attempt->route = json_strdup(pc->ps->attempts, buf, t); - - err = json_scan(tmpctx, buf, t, "[0:{msatoshi:%,delay:%}]", - JSON_SCAN(json_to_msat, &fee), - JSON_SCAN(json_to_number, &delay)); - if (err) - plugin_err(cmd->plugin, "getroute %s? %.*s", - err, - json_tok_full_len(result), json_tok_full(buf, result)); - - if (pc->maxfee_pct_millionths / 100 > UINT32_MAX) - plugin_err(cmd->plugin, "max fee percent too large: %lf", - pc->maxfee_pct_millionths / 1000000.0); - - if (!amount_msat_fee(&max_fee, pc->msat, 0, - (u32)(pc->maxfee_pct_millionths / 100))) - plugin_err( - cmd->plugin, "max fee too large: %s * %lf%%", - type_to_string(tmpctx, struct amount_msat, &pc->msat), - pc->maxfee_pct_millionths / 1000000.0); - - if (amount_msat_greater(fee, pc->exemptfee) && - amount_msat_greater(fee, max_fee)) { - const jsmntok_t *charger; - struct json_out *failed; - char *feemsg; - - feemsg = tal_fmt(pc, "Route wanted fee of %s", - type_to_string(tmpctx, struct amount_msat, - &fee)); - failed = failed_start(pc); - json_out_addstr(failed, "message", feemsg); - failed_end(failed); - - /* Remember this if we eliminating this causes us to have no - * routes at all! */ - if (!pc->expensive_route) - pc->expensive_route = feemsg; - else - tal_free(feemsg); - - /* Try excluding most fee-charging channel (unless it's in - * routeboost). */ - charger = find_worst_channel(buf, t, "msatoshi"); - if (maybe_exclude(pc, buf, charger)) { - return start_pay_attempt(cmd, pc, - "Excluded expensive channel %s", - pc->excludes[tal_count(pc->excludes)-1]); - } - - return next_routehint(cmd, pc); - } - - if (delay > pc->maxdelay) { - const jsmntok_t *delayer; - struct json_out *failed; - char *feemsg; - - feemsg = tal_fmt(pc, "Route wanted delay of %u blocks", delay); - failed = failed_start(pc); - json_out_addstr(failed, "message", feemsg); - failed_end(failed); - - /* Remember this if we eliminating this causes us to have no - * routes at all! */ - if (!pc->expensive_route) - pc->expensive_route = feemsg; - else - tal_free(failed); - - delayer = find_worst_channel(buf, t, "delay"); - - /* Try excluding most delaying channel (unless it's in - * routeboost). */ - if (maybe_exclude(pc, buf, delayer)) { - return start_pay_attempt(cmd, pc, - "Excluded delaying channel %s", - pc->excludes[tal_count(pc->excludes)-1]); - } - - return next_routehint(cmd, pc); - } - - attempt->sendpay = true; - req = jsonrpc_request_start(cmd->plugin, cmd, "sendpay", - sendpay_done, sendpay_error, pc); - json_add_jsonstr(req->js, "route", attempt->route); - json_add_string(req->js, "payment_hash", pc->payment_hash); - json_add_string(req->js, "bolt11", pc->ps->invstring); - if (pc->label) - json_add_string(req->js, "label", pc->label); - if (pc->payment_secret) - json_add_string(req->js, "payment_secret", pc->payment_secret); - if (pc->payment_metadata) - json_add_string(req->js, "payment_metadata", pc->payment_metadata); - - return send_outreq(cmd->plugin, req); -} - -static struct command_result *getroute_error(struct command *cmd, - const char *buf, - const jsmntok_t *error, - struct pay_command *pc) -{ - errcode_t code; - const jsmntok_t *codetok; - - attempt_failed_tok(pc, "getroute", buf, error); - - codetok = json_get_member(buf, error, "code"); - if (!json_to_errcode(buf, codetok, &code)) - plugin_err(cmd->plugin, "getroute error gave no 'code'? '%.*s'", - error->end - error->start, buf + error->start); - - /* Strange errors from getroute should be forwarded. */ - if (code != PAY_ROUTE_NOT_FOUND) - return forward_error(cmd, buf, error, pc); - - return next_routehint(cmd, pc); -} - -/* Deep copy of excludes array. */ -static const char **dup_excludes(const tal_t *ctx, const char **excludes) -{ - const char **ret = tal_dup_talarr(ctx, const char *, excludes); - for (size_t i = 0; i < tal_count(ret); i++) - ret[i] = tal_strdup(ret, excludes[i]); - return ret; -} - -/* Get a route from the lightningd. */ -static struct command_result *execute_getroute(struct command *cmd, - struct pay_command *pc) -{ - struct pay_attempt *attempt = current_attempt(pc); - - u32 max_hops = ROUTING_MAX_HOPS; - struct amount_msat msat; - const char *dest; - u32 cltv; - struct out_req *req; - - /* routehint set below. */ - - /* If we have a routehint, try that first; we need to do extra - * checks that it meets our criteria though. */ - if (pc->current_routehint) { - attempt->routehint = tal_steal(pc->ps, pc->current_routehint); - if (!route_msatoshi(&msat, pc->msat, - attempt->routehint, - tal_count(attempt->routehint))) { - struct json_out *failed; - - failed = failed_start(pc); - json_out_addstr(failed, "message", - "Routehint absurd fee"); - failed_end(failed); - return next_routehint(cmd, pc); - } - dest = type_to_string(tmpctx, struct node_id, - &attempt->routehint[0].pubkey); - max_hops -= tal_count(attempt->routehint); - cltv = route_cltv(pc->final_cltv, - attempt->routehint, - tal_count(attempt->routehint)); - } else { - msat = pc->msat; - dest = pc->dest; - cltv = pc->final_cltv; - attempt->routehint = NULL; - } - - /* OK, ask for route to destination */ - req = jsonrpc_request_start(cmd->plugin, cmd, "getroute", - getroute_done, getroute_error, pc); - json_add_string(req->js, "id", dest); - json_add_string(req->js, "msatoshi", - type_to_string(tmpctx, struct amount_msat, &msat)); - json_add_u32(req->js, "cltv", cltv); - json_add_u32(req->js, "maxhops", max_hops); - json_add_member(req->js, "riskfactor", false, "%lf", - pc->riskfactor_millionths / 1000000.0); - if (tal_count(pc->excludes) != 0) { - json_array_start(req->js, "exclude"); - for (size_t i = 0; i < tal_count(pc->excludes); i++) - json_add_string(req->js, NULL, pc->excludes[i]); - json_array_end(req->js); - } - - return send_outreq(cmd->plugin, req); -} - -static struct command_result * -getstartblockheight_done(struct command *cmd, - const char *buf, - const jsmntok_t *result, - struct pay_command *pc) -{ - const jsmntok_t *blockheight_tok; - u32 blockheight; - - blockheight_tok = json_get_member(buf, result, "blockheight"); - if (!blockheight_tok) - plugin_err(cmd->plugin, "getstartblockheight: " - "getinfo gave no 'blockheight'? '%.*s'", - result->end - result->start, buf); - - if (!json_to_u32(buf, blockheight_tok, &blockheight)) - plugin_err(cmd->plugin, "getstartblockheight: " - "getinfo gave non-unsigned-32-bit 'blockheight'? '%.*s'", - result->end - result->start, buf); - - current_attempt(pc)->start_block = blockheight; - - return execute_getroute(cmd, pc); -} - -static struct command_result * -getstartblockheight_error(struct command *cmd, - const char *buf, - const jsmntok_t *error, - struct pay_command *pc) -{ - /* Should never happen. */ - plugin_err(cmd->plugin, "getstartblockheight: getinfo failed!? '%.*s'", - error->end - error->start, buf); -} - -static struct command_result * -execute_getstartblockheight(struct command *cmd, - struct pay_command *pc) -{ - struct out_req *req = jsonrpc_request_start(cmd->plugin, cmd, "getinfo", - &getstartblockheight_done, - &getstartblockheight_error, - pc); - return send_outreq(cmd->plugin, req); -} - -static struct command_result *start_pay_attempt(struct command *cmd, - struct pay_command *pc, - const char *fmt, ...) -{ - struct pay_attempt *attempt; - va_list ap; - size_t n; - - n = tal_count(pc->ps->attempts); - tal_resize(&pc->ps->attempts, n+1); - attempt = &pc->ps->attempts[n]; - - va_start(ap, fmt); - attempt->start = time_now(); - /* Mark it unfinished */ - attempt->end.ts.tv_sec = -1; - attempt->excludes = dup_excludes(pc->ps, pc->excludes); - attempt->route = NULL; - attempt->failure = NULL; - attempt->result = NULL; - attempt->sendpay = false; - attempt->why = tal_vfmt(pc->ps, fmt, ap); - va_end(ap); - - return execute_getstartblockheight(cmd, pc); -} - -/* BOLT #7: - * - * If a route is computed by simply routing to the intended recipient and - * summing the `cltv_expiry_delta`s, then it's possible for intermediate nodes - * to guess their position in the route. Knowing the CLTV of the HTLC, the - * surrounding network topology, and the `cltv_expiry_delta`s gives an - * attacker a way to guess the intended recipient. Therefore, it's highly - * desirable to add a random offset to the CLTV that the intended recipient - * will receive, which bumps all CLTVs along the route. - * - * In order to create a plausible offset, the origin node MAY start a limited - * random walk on the graph, starting from the intended recipient and summing - * the `cltv_expiry_delta`s, and use the resulting sum as the offset. This - * effectively creates a _shadow route extension_ to the actual route and - * provides better protection against this attack vector than simply picking a - * random offset would. - */ -static struct command_result *shadow_route(struct command *cmd, - struct pay_command *pc); - -static struct command_result *add_shadow_route(struct command *cmd, - const char *buf, - const jsmntok_t *result, - struct pay_command *pc) -{ - /* Use reservoir sampling across the capable channels. */ - const jsmntok_t *channels = json_get_member(buf, result, "channels"); - const jsmntok_t *chan, *best = NULL; - size_t i; - u64 sample = 0; - struct route_info *route = tal_arr(NULL, struct route_info, 1); - struct amount_msat fees, maxfees; - /* Don't go above this. Note how we use the initial amount to get the percentage - * of the fees, or it would increase with the addition of new shadow routes. */ - if (!amount_msat_fee(&maxfees, pc->initial_msat, 0, pc->maxfee_pct_millionths)) - plugin_err(cmd->plugin, "Overflow when computing maxfees for " - "shadow routes."); - - json_for_each_arr(i, chan, channels) { - u64 v = pseudorand(UINT64_MAX); - - if (!best || v > sample) { - struct amount_sat sat; - - json_to_sat(buf, json_get_member(buf, chan, "satoshis"), &sat); - if (amount_msat_greater_sat(pc->msat, sat)) - continue; - - /* Don't use if total would exceed 1/4 of our time allowance. */ - json_to_u16(buf, json_get_member(buf, chan, "delay"), - &route[0].cltv_expiry_delta); - if ((pc->final_cltv + route[0].cltv_expiry_delta) * 4 > pc->maxdelay) - continue; - - json_to_number(buf, json_get_member(buf, chan, "base_fee_millisatoshi"), - &route[0].fee_base_msat); - json_to_number(buf, json_get_member(buf, chan, "fee_per_millionth"), - &route[0].fee_proportional_millionths); - - if (!amount_msat_fee(&fees, pc->initial_msat, route[0].fee_base_msat, - route[0].fee_proportional_millionths) - || amount_msat_greater_eq(fees, maxfees)) - continue; - - best = chan; - sample = v; - } - } - - if (!best) { - tal_append_fmt(&pc->ps->shadow, - "No suitable channels found to %s. ", - pc->shadow_dest); - return start_pay_attempt(cmd, pc, "Initial attempt"); - } - - pc->final_cltv += route[0].cltv_expiry_delta; - pc->shadow_dest = json_strdup(pc, buf, - json_get_member(buf, best, "destination")); - route_msatoshi(&pc->msat, pc->msat, route, 1); - tal_append_fmt(&pc->ps->shadow, - "Added %u cltv delay, %u base fee, and %u ppm fee " - "for shadow to %s.", - route[0].cltv_expiry_delta, route[0].fee_base_msat, - route[0].fee_proportional_millionths, - pc->shadow_dest); - tal_free(route); - - return shadow_route(cmd, pc); -} - -static struct command_result *shadow_route(struct command *cmd, - struct pay_command *pc) -{ - struct out_req *req; - -#if DEVELOPER - if (!pc->use_shadow) - return start_pay_attempt(cmd, pc, "Initial attempt"); -#endif - if (pseudorand(2) == 0) - return start_pay_attempt(cmd, pc, "Initial attempt"); - - req = jsonrpc_request_start(cmd->plugin, cmd, "listchannels", - add_shadow_route, forward_error, pc); - json_add_string(req->js, "source", pc->shadow_dest); - return send_outreq(cmd->plugin, req); -} - -/* gossipd doesn't know much about the current state of channels; here we - * manually exclude peers which are disconnected and channels which lack - * current capacity (it will eliminate those without total capacity). */ -static struct command_result *listpeers_done(struct command *cmd, - const char *buf, - const jsmntok_t *result, - struct pay_command *pc) -{ - const jsmntok_t *peers, *peer; - size_t i; - char *mods = tal_strdup(tmpctx, ""); - - peers = json_get_member(buf, result, "peers"); - if (!peers) - plugin_err(cmd->plugin, "listpeers gave no 'peers'? '%.*s'", - result->end - result->start, buf); - - json_for_each_arr(i, peer, peers) { - const jsmntok_t *chans, *chan; - bool connected; - size_t j; - - json_to_bool(buf, json_get_member(buf, peer, "connected"), - &connected); - chans = json_get_member(buf, peer, "channels"); - json_for_each_arr(j, chan, chans) { - const jsmntok_t *state, *scid, *dir; - struct amount_msat spendable; - - /* gossipd will only consider things in state NORMAL - * anyway; we don't need to exclude others. */ - state = json_get_member(buf, chan, "state"); - if (!json_tok_streq(buf, state, "CHANNELD_NORMAL")) - continue; - - json_to_msat(buf, - json_get_member(buf, chan, - "spendable_msatoshi"), - &spendable); - - if (connected - && amount_msat_greater_eq(spendable, pc->msat)) - continue; - - /* Exclude this disconnected or low-capacity channel */ - scid = json_get_member(buf, chan, "short_channel_id"); - dir = json_get_member(buf, chan, "direction"); - tal_arr_expand(&pc->excludes, - tal_fmt(pc->excludes, "%.*s/%c", - scid->end - scid->start, - buf + scid->start, - buf[dir->start])); - - tal_append_fmt(&mods, - "Excluded channel %s (%s, %s). ", - pc->excludes[tal_count(pc->excludes)-1], - type_to_string(tmpctx, struct amount_msat, - &spendable), - connected ? "connected" : "disconnected"); - } - } - - if (!streq(mods, "")) - pc->ps->exclusions = tal_steal(pc->ps, mods); - - pc->ps->shadow = tal_strdup(pc->ps, ""); - return shadow_route(cmd, pc); -} - -/* Trim route to this length by taking from the *front* of route - * (end points to destination, so we need that bit!) */ -static void trim_route(struct route_info **route, size_t n) -{ - size_t remove = tal_count(*route) - n; - memmove(*route, *route + remove, sizeof(**route) * n); - tal_resize(route, n); -} - -/* Make sure routehints are reasonable length, and (since we assume we - * can append), not directly to us. Note: untrusted data! */ -static struct route_info **filter_routehints(struct pay_command *pc, - struct route_info **hints) -{ - char *mods = tal_strdup(tmpctx, ""); - - for (size_t i = 0; i < tal_count(hints); i++) { - /* Trim any routehint > 10 hops */ - size_t max_hops = ROUTING_MAX_HOPS / 2; - if (tal_count(hints[i]) > max_hops) { - tal_append_fmt(&mods, - "Trimmed routehint %zu (%zu hops) to %zu. ", - i, tal_count(hints[i]), max_hops); - trim_route(&hints[i], max_hops); - } - - /* If we are first hop, trim. */ - if (tal_count(hints[i]) > 0 - && node_id_eq(&hints[i][0].pubkey, &my_id)) { - tal_append_fmt(&mods, - "Removed ourselves from routehint %zu. ", - i); - trim_route(&hints[i], tal_count(hints[i])-1); - } - - /* If route is empty, remove altogether. */ - if (tal_count(hints[i]) == 0) { - tal_append_fmt(&mods, - "Removed empty routehint %zu. ", i); - tal_arr_remove(&hints, i); - i--; - } - } - - if (!streq(mods, "")) - pc->ps->routehint_modifications = tal_steal(pc->ps, mods); - - return tal_steal(pc, hints); -} - -static struct pay_status *add_pay_status(struct pay_command *pc, - const char *invstring STEALS) -{ - struct pay_status *ps = tal(NULL, struct pay_status); - - /* The pay_status outlives the pc, so it simply takes field ownership */ - ps->dest = tal_steal(ps, pc->dest); - ps->label = tal_steal(ps, pc->label); - ps->msat = pc->msat; - ps->final_cltv = pc->final_cltv; - ps->invstring = tal_steal(ps, invstring); - ps->routehint_modifications = NULL; - ps->shadow = NULL; - ps->exclusions = NULL; - ps->attempts = tal_arr(ps, struct pay_attempt, 0); - hex_decode(pc->payment_hash, strlen(pc->payment_hash), - &ps->payment_hash, sizeof(ps->payment_hash)); - - list_add_tail(&pay_status, &ps->list); - return ps; -} - -#ifndef COMPAT_V090 -UNUSED -#endif -static struct command_result *json_pay(struct command *cmd, - const char *buf, - const jsmntok_t *params) -{ - struct amount_msat *msat; - struct bolt11 *b11; - const char *b11str; - char *fail; - u64 *riskfactor_millionths; - unsigned int *retryfor; - struct pay_command *pc = tal(cmd, struct pay_command); - u64 *maxfee_pct_millionths; - unsigned int *maxdelay; - struct amount_msat *exemptfee; - struct out_req *req; -#if DEVELOPER - bool *use_shadow; -#endif - - if (!param(cmd, buf, params, p_req("bolt11", param_string, &b11str), - p_opt("msatoshi", param_msat, &msat), - p_opt("label", param_string, &pc->label), - p_opt_def("riskfactor", param_millionths, - &riskfactor_millionths, 10000000), - p_opt_def("maxfeepercent", param_millionths, - &maxfee_pct_millionths, 500000), - p_opt_def("retry_for", param_number, &retryfor, 60), - p_opt_def("maxdelay", param_number, &maxdelay, - maxdelay_default), - p_opt_def("exemptfee", param_msat, &exemptfee, - AMOUNT_MSAT(5000)), -#if DEVELOPER - p_opt_def("use_shadow", param_bool, &use_shadow, true), -#endif - NULL)) - return command_param_failed(); - - b11 = bolt11_decode(cmd, b11str, plugin_feature_set(cmd->plugin), - NULL, chainparams, &fail); - if (!b11) { - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "Invalid bolt11: %s", fail); - } - - if (!b11->chain) { - return command_fail(cmd, PAY_ROUTE_NOT_FOUND, "Invoice is for an unknown network"); - } - - if (b11->chain != chainparams) { - return command_fail(cmd, PAY_ROUTE_NOT_FOUND, "Invoice is for another network %s", b11->chain->network_name); - } - - if (time_now().ts.tv_sec > b11->timestamp + b11->expiry) { - return command_fail(cmd, PAY_INVOICE_EXPIRED, "Invoice expired"); - } - - if (b11->msat) { - if (msat) { - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "msatoshi parameter unnecessary"); - } - pc->msat = pc->initial_msat = *b11->msat; - } else { - if (!msat) { - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "msatoshi parameter required"); - } - pc->msat = pc->initial_msat = *msat; - } - - /* Sanity check */ - if (feature_offered(b11->features, OPT_VAR_ONION) - && !b11->payment_secret) { - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "Invalid bolt11:" - " sets feature var_onion with no secret"); - } - - pc->maxfee_pct_millionths = *maxfee_pct_millionths; - pc->maxdelay = *maxdelay; - pc->exemptfee = *exemptfee; - pc->riskfactor_millionths = *riskfactor_millionths; - pc->final_cltv = b11->min_final_cltv_expiry; - pc->dest = type_to_string(cmd, struct node_id, &b11->receiver_id); - pc->shadow_dest = tal_strdup(pc, pc->dest); - pc->payment_hash = type_to_string(pc, struct sha256, - &b11->payment_hash); - pc->stoptime = timeabs_add(time_now(), time_from_sec(*retryfor)); - pc->excludes = tal_arr(cmd, const char *, 0); - pc->ps = add_pay_status(pc, b11str); - if (b11->payment_secret) - pc->payment_secret = tal_hexstr(pc, b11->payment_secret, - sizeof(*b11->payment_secret)); - else - pc->payment_secret = NULL; - if (b11->metadata) - pc->payment_metadata = tal_hex(pc, b11->metadata); - else - pc->payment_metadata = NULL; - - /* We try first without using routehint */ - pc->current_routehint = NULL; - pc->routehints = filter_routehints(pc, b11->routes); - pc->expensive_route = NULL; -#if DEVELOPER - pc->use_shadow = *use_shadow; -#endif - - /* Get capacities of local channels (no parameters) */ - req = jsonrpc_request_start(cmd->plugin, cmd, "listpeers", - listpeers_done, forward_error, pc); - return send_outreq(cmd->plugin, req); -} - /* FIXME: Add this to ccan/time? */ #define UTC_TIMELEN (sizeof("YYYY-mm-ddTHH:MM:SS.nnnZ")) static void utc_timestring(const struct timeabs *time, char str[UTC_TIMELEN]) @@ -1410,66 +104,6 @@ static void utc_timestring(const struct timeabs *time, char str[UTC_TIMELEN]) (int) time->ts.tv_nsec / 1000000); } -static void add_attempt(struct json_stream *ret, - const struct pay_status *ps, - const struct pay_attempt *attempt) -{ - char timestr[UTC_TIMELEN]; - - utc_timestring(&attempt->start, timestr); - - json_object_start(ret, NULL); - json_add_string(ret, "strategy", attempt->why); - json_add_string(ret, "start_time", timestr); - json_add_u64(ret, "age_in_seconds", - time_to_sec(time_between(time_now(), attempt->start))); - if (attempt->result || attempt->failure) { - utc_timestring(&attempt->end, timestr); - json_add_string(ret, "end_time", timestr); - json_add_u64(ret, "duration_in_seconds", - time_to_sec(time_between(attempt->end, - attempt->start))); - } - if (tal_count(attempt->routehint)) { - json_array_start(ret, "routehint"); - for (size_t i = 0; i < tal_count(attempt->routehint); i++) { - json_object_start(ret, NULL); - json_add_string(ret, "id", - type_to_string(tmpctx, struct node_id, - &attempt->routehint[i].pubkey)); - json_add_string(ret, "channel", - type_to_string(tmpctx, - struct short_channel_id, - &attempt->routehint[i].short_channel_id)); - json_add_u64(ret, "fee_base_msat", - attempt->routehint[i].fee_base_msat); - json_add_u64(ret, "fee_proportional_millionths", - attempt->routehint[i].fee_proportional_millionths); - json_add_u64(ret, "cltv_expiry_delta", - attempt->routehint[i].cltv_expiry_delta); - json_object_end(ret); - } - json_array_end(ret); - } - if (tal_count(attempt->excludes)) { - json_array_start(ret, "excluded_nodes_or_channels"); - for (size_t i = 0; i < tal_count(attempt->excludes); i++) - json_add_string(ret, NULL, attempt->excludes[i]); - json_array_end(ret); - } - - if (attempt->route) - json_add_jsonstr(ret, "route", attempt->route); - - if (attempt->failure) - json_out_add_splice(ret->jout, "failure", attempt->failure); - - if (attempt->result) - json_add_member(ret, "success", true, "%s", attempt->result); - - json_object_end(ret); -} - static void json_add_sendpay_result(struct json_stream *s, const struct payment_result *r) { if (r->code != 0) { @@ -1558,7 +192,6 @@ static struct command_result *json_paystatus(struct command *cmd, const char *buf, const jsmntok_t *params) { - struct pay_status *ps; const char *invstring; struct json_stream *ret; struct payment *p; @@ -1572,38 +205,6 @@ static struct command_result *json_paystatus(struct command *cmd, ret = jsonrpc_stream_success(cmd); json_array_start(ret, "pay"); - /* FIXME: Index by bolt11 string! */ - /* TODO(cdecker) Remove once we migrated to `pay` with modifiers. */ - list_for_each(&pay_status, ps, list) { - if (invstring && !streq(invstring, ps->invstring)) - continue; - - json_object_start(ret, NULL); - json_add_invstring(ret, ps->invstring); - json_add_u64(ret, "msatoshi", - ps->msat.millisatoshis); /* Raw: JSON */ - json_add_string(ret, "amount_msat", - type_to_string(tmpctx, struct amount_msat, - &ps->msat)); - json_add_string(ret, "destination", ps->dest); - if (ps->label) - json_add_string(ret, "label", ps->label); - if (ps->routehint_modifications) - json_add_string(ret, "routehint_modifications", - ps->routehint_modifications); - if (ps->shadow && !streq(ps->shadow, "")) - json_add_string(ret, "shadow", ps->shadow); - if (ps->exclusions) - json_add_string(ret, "local_exclusions", ps->exclusions); - - /* If it's in listpeers right now, this can be 0 */ - json_array_start(ret, "attempts"); - for (size_t i = 0; i < tal_count(ps->attempts); i++) - add_attempt(ret, ps, &ps->attempts[i]); - json_array_end(ret); - json_object_end(ret); - } - list_for_each(&payments, p, list) { assert(p->parent == NULL); if (invstring && !streq(invstring, p->invstring)) @@ -1637,20 +238,11 @@ static struct command_result *json_paystatus(struct command *cmd, static bool attempt_ongoing(const struct sha256 *payment_hash) { - struct pay_status *ps; struct payment *root; - struct pay_attempt *attempt; struct payment_tree_result res; enum payment_step diff, final_states = PAYMENT_STEP_FAILED | PAYMENT_STEP_SUCCESS; - list_for_each(&pay_status, ps, list) { - if (!sha256_eq(payment_hash, &ps->payment_hash)) - continue; - attempt = &ps->attempts[tal_count(ps->attempts)-1]; - return attempt->result == NULL && attempt->failure == NULL; - } - list_for_each(&payments, root, list) { if (!sha256_eq(payment_hash, root->payment_hash)) continue; @@ -2323,9 +915,9 @@ static void destroy_payment(struct payment *p) list_del(&p->list); } -static struct command_result *json_paymod(struct command *cmd, - const char *buf, - const jsmntok_t *params) +static struct command_result *json_pay(struct command *cmd, + const char *buf, + const jsmntok_t *params) { struct payment *p; const char *b11str; @@ -2548,15 +1140,6 @@ static struct command_result *json_paymod(struct command *cmd, } static const struct plugin_command commands[] = { -#ifdef COMPAT_V090 - { - "legacypay", - "payment", - "Send payment specified by {bolt11} with {amount}", - "Try to send a payment, retrying {retry_for} seconds before giving up", - json_pay - }, -#endif { "paystatus", "payment", @@ -2575,7 +1158,7 @@ static const struct plugin_command commands[] = { "payment", "Send payment specified by {bolt11}", "Attempt to pay the {bolt11} invoice.", - json_paymod + json_pay }, }; diff --git a/tests/test_pay.py b/tests/test_pay.py index 985e8f181..ac68c7b40 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -7,7 +7,7 @@ from pyln.proto.onion import TlvPayload from pyln.testing.utils import EXPERIMENTAL_DUAL_FUND, FUNDAMOUNT from utils import ( DEVELOPER, wait_for, only_one, sync_blockheight, TIMEOUT, - EXPERIMENTAL_FEATURES, env, VALGRIND, mine_funding_to_announce + EXPERIMENTAL_FEATURES, VALGRIND, mine_funding_to_announce ) import copy import os @@ -90,7 +90,7 @@ def test_pay_amounts(node_factory): @pytest.mark.developer("needs to deactivate shadow routing") -def test_pay_limits(node_factory, compat): +def test_pay_limits(node_factory): """Test that we enforce fee max percentage and max delay""" l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True) @@ -346,7 +346,7 @@ def test_pay_error_update_fees(node_factory): @pytest.mark.developer("needs to deactivate shadow routing") -def test_pay_optional_args(node_factory, compat): +def test_pay_optional_args(node_factory): l1, l2 = node_factory.line_graph(2) inv1 = l2.rpc.invoice(123000, 'test_pay', 'desc')['bolt11'] @@ -1779,7 +1779,7 @@ def test_pay_retry(node_factory, bitcoind, executor, chainparams): @pytest.mark.developer("needs DEVELOPER=1 otherwise gossip takes 5 minutes!") @pytest.mark.slow_test -def test_pay_routeboost(node_factory, bitcoind, compat): +def test_pay_routeboost(node_factory, bitcoind): """Make sure we can use routeboost information. """ # l1->l2->l3--private-->l4 l1, l2 = node_factory.line_graph(2, announce_channels=True, wait_for_announce=True) @@ -3448,7 +3448,7 @@ def test_sendpay_blinding(node_factory): l1.rpc.waitsendpay(inv['payment_hash']) -def test_excluded_adjacent_routehint(node_factory, bitcoind, compat): +def test_excluded_adjacent_routehint(node_factory, bitcoind): """Test case where we try have a routehint which leads to an adjacent node, but the result exceeds our maxfee; we crashed trying to find what part of the path was most expensive in that case @@ -3615,7 +3615,7 @@ def test_invalid_onion_channel_update(node_factory): @pytest.mark.developer("Requires use_shadow") -def test_pay_exemptfee(node_factory, compat): +def test_pay_exemptfee(node_factory): """Tiny payment, huge fee l1 -> l2 -> l3 @@ -3972,46 +3972,6 @@ def test_listpay_result_with_paymod(node_factory, bitcoind): assert 'destination' in l2.rpc.listpays()['pays'][0] -@unittest.skipIf(env('COMPAT') != 1, "legacypay requires COMPAT=1") -def test_listpays_ongoing_attempt(node_factory, bitcoind, executor): - """Test to reproduce issue #3915. - - The issue is that the bolt11 string is not initialized if the root payment - was split (no attempt with the bolt11 annotation ever hit `lightningd`, - hence we cannot filter by that. In addition keysends never have a bolt11 - string, so we need to switch to payment_hash comparisons anyway. - """ - plugin = os.path.join(os.path.dirname(__file__), 'plugins', 'hold_htlcs.py') - l1, l2, l3 = node_factory.line_graph(3, opts=[{}, {}, {'plugin': plugin}], - wait_for_announce=True) - - f = executor.submit(l1.rpc.keysend, l3.info['id'], 100) - l3.daemon.wait_for_log(r'Holding onto an incoming htlc') - l1.rpc.listpays() - f.result() - - inv = l2.rpc.invoice(10**6, 'legacy', 'desc')['bolt11'] - l1.rpc.legacypay(inv) - l1.rpc.listpays() - - # Produce loads of parts to increase probability of hitting the issue, - # should result in 100 splits at least - inv = l3.rpc.invoice(10**9, 'mpp invoice', 'desc')['bolt11'] - - # Start the payment, it'll get stuck for 10 seconds at l3 - executor.submit(l1.rpc.pay, inv) - l1.daemon.wait_for_log(r'Split into [0-9]+ sub-payments due to initial size') - l3.daemon.wait_for_log(r'Holding onto an incoming htlc') - - # While that is going on, check in with `listpays` to see if aggregation - # is working. - l1.rpc.listpays() - - # Now restart and see if we still can aggregate things correctly. - l1.restart() - l1.rpc.listpays() - - def test_listsendpays_and_listpays_order(node_factory): """listsendpays should be in increasing id order, listpays in created_at""" l1, l2 = node_factory.line_graph(2)