Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EXPERIMENTAL: MPP send and receive support (lowlevel) #3309

Merged
merged 22 commits into from
Dec 12, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
178892b
db: add partid, total_msat fields to payment entries.
rustyrussell Dec 11, 2019
ad4ed97
db: add partid field to htlc_out.
rustyrussell Dec 11, 2019
2a03434
htlcs: remove origin_htlc_id from htlc_out.
rustyrussell Dec 11, 2019
c654478
lightningd: share more code between sendpay and sendonion.
rustyrussell Dec 11, 2019
04a46f0
lightningd: change amount-in-flight check to be more nuanced.
rustyrussell Dec 11, 2019
c61227b
sendpay/sendonion: add optional partid arg, finesse msatoshi argument.
rustyrussell Dec 11, 2019
15fa972
configure: make partid payments only available with EXPERIMENTAL_FEAT…
rustyrussell Dec 11, 2019
3bc4636
waitsendpay: add partid arg.
rustyrussell Dec 11, 2019
94d3897
pytest: Add tests to make sure received onion is as expected.
rustyrussell Dec 11, 2019
2e4416e
doc: update experimental bolt version quotes.
rustyrussell Dec 11, 2019
d94ae31
lightningd: cleanup redundant args from handle_localpay
rustyrussell Dec 11, 2019
3c6e33a
lightningd: split invoice check into separate function.
rustyrussell Dec 11, 2019
555b217
lightningd: implement htlc sets.
rustyrussell Dec 11, 2019
73bf9e0
lightningd: wrap htlc replay in a database transaction.
rustyrussell Dec 11, 2019
1839483
lightningd: sew in htlc set.
rustyrussell Dec 11, 2019
8cee375
plugins: listpays ignores pre-0.7.0 or manual sendpay payments w/ no …
rustyrussell Dec 11, 2019
84a2753
plugins: listpays will now consolidate multi-part payments.
rustyrussell Dec 11, 2019
c6bbb41
common: offer option_basic_mpp for EXPERIMENTAL_FEATURES.
rustyrussell Dec 11, 2019
cbfc84f
pytest: add more multi-part-payment tests.
rustyrussell Dec 11, 2019
2b4ca09
lightningd: require payment_secret for MPP.
rustyrussell Dec 11, 2019
207ae69
lightningd: fix spurious "more than twice final" error.
rustyrussell Dec 12, 2019
e6edb76
lightningd: fix failure message in waitsendpay with multi-part payments.
rustyrussell Dec 12, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 90 additions & 24 deletions lightningd/pay.c
Original file line number Diff line number Diff line change
Expand Up @@ -733,42 +733,108 @@ send_payment_core(struct lightningd *ld,
struct short_channel_id *route_channels TAKES,
struct secret *path_secrets)
{
struct wallet_payment *payment;
const struct wallet_payment **payments, *old_payment = NULL;
struct channel *channel;
enum onion_type failcode;
struct htlc_out *hout;
struct routing_failure *fail;
struct amount_msat msat_already_pending = AMOUNT_MSAT(0);

/* Now, do we already have one or more payments? */
payments = wallet_payment_list(tmpctx, ld->wallet, rhash);
for (size_t i = 0; i < tal_count(payments); i++) {
log_debug(ld->log, "Payment %zu/%zu: %s %s",
i, tal_count(payments),
type_to_string(tmpctx, struct amount_msat,
&payments[i]->msatoshi),
payments[i]->status == PAYMENT_COMPLETE ? "COMPLETE"
: payments[i]->status == PAYMENT_PENDING ? "PENDING"
: "FAILED");

switch (payments[i]->status) {
case PAYMENT_COMPLETE:
if (payments[i]->partid != partid)
continue;

/* Now, do we already have a payment? */
payment = wallet_payment_by_hash(tmpctx, ld->wallet, rhash, partid);
if (payment) {
/* FIXME: We should really do something smarter here! */
if (payment->status == PAYMENT_PENDING) {
log_debug(ld->log, "send_payment: previous still in progress");
return json_sendpay_in_progress(cmd, payment);
}
if (payment->status == PAYMENT_COMPLETE) {
log_debug(ld->log, "send_payment: previous succeeded");
/* Must match successful payment parameters. */
if (!amount_msat_eq(payment->msatoshi, msat)) {
if (!amount_msat_eq(payments[i]->msatoshi, msat)) {
return command_fail(cmd, PAY_RHASH_ALREADY_USED,
"Already succeeded "
"with amount %s",
type_to_string(tmpctx,
struct amount_msat,
&payment->msatoshi));
&payments[i]->msatoshi));
}
if (payment->destination && destination
&& !node_id_eq(payment->destination, destination)) {
if (payments[i]->destination && destination
&& !node_id_eq(payments[i]->destination,
destination)) {
return command_fail(cmd, PAY_RHASH_ALREADY_USED,
"Already succeeded to %s",
type_to_string(tmpctx,
struct node_id,
payment->destination));
payments[i]->destination));
}
return sendpay_success(cmd, payment);
}
log_debug(ld->log, "send_payment: found previous, retrying");
return sendpay_success(cmd, payments[i]);

case PAYMENT_PENDING:
/* Can't mix non-parallel and parallel payments! */
if (!payments[i]->partid != !partid) {
return command_fail(cmd, PAY_IN_PROGRESS,
"Already have %s payment in progress",
payments[i]->partid ? "parallel" : "non-parallel");
}
if (payments[i]->partid == partid)
return json_sendpay_in_progress(cmd, payments[i]);
/* You shouldn't change your mind about amount being
* sent, since we'll use it in onion! */
else if (!amount_msat_eq(payments[i]->total_msat,
total_msat))
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"msatoshi was previously %s, now %s",
type_to_string(tmpctx,
struct amount_msat,
&payments[i]->total_msat),
type_to_string(tmpctx,
struct amount_msat,
&total_msat));


if (!amount_msat_add(&msat_already_pending,
msat_already_pending,
payments[i]->msatoshi)) {
return command_fail(cmd, LIGHTNINGD,
"Internal amount overflow!"
" %s + %s in %zu/%zu",
type_to_string(tmpctx,
struct amount_msat,
&msat_already_pending),
type_to_string(tmpctx,
struct amount_msat,
&payments[i]->msatoshi),
i, tal_count(payments));
}
break;

case PAYMENT_FAILED:
if (payments[i]->partid == partid)
old_payment = payments[i];
}
}

/* BOLT-3a09bc54f8443c4757b47541a5310aff6377ee21 #4:
*
* - MUST NOT send another HTLC if the total `amount_msat` of the HTLC
* set is already greater or equal to `total_msat`.
*/
/* We don't do this for single 0-value payments (sendonion does this) */
if (!amount_msat_eq(total_msat, AMOUNT_MSAT(0))
&& amount_msat_greater_eq(msat_already_pending, total_msat)) {
return command_fail(cmd, PAY_IN_PROGRESS,
"Already have %s of %s payments in progress",
type_to_string(tmpctx, struct amount_msat,
&msat_already_pending),
type_to_string(tmpctx, struct amount_msat,
&total_msat));
}

channel = active_channel_by_id(ld, &first_hop->nodeid, NULL);
Expand All @@ -795,7 +861,7 @@ send_payment_core(struct lightningd *ld,
&first_hop->channel_id,
&channel->peer->id);

return sendpay_fail(cmd, payment, PAY_TRY_OTHER_ROUTE,
return sendpay_fail(cmd, old_payment, PAY_TRY_OTHER_ROUTE,
NULL, fail, "First peer not ready");
}

Expand All @@ -804,14 +870,14 @@ send_payment_core(struct lightningd *ld,
* a possibility, and we end up in handle_missing_htlc_output->
* onchain_failed_our_htlc->payment_failed with no payment.
*/
if (payment) {
wallet_payment_delete(ld->wallet, rhash, payment->partid);
if (old_payment) {
wallet_payment_delete(ld->wallet, rhash, partid);
wallet_local_htlc_out_delete(ld->wallet, channel, rhash,
payment->partid);
partid);
}

/* If hout fails, payment should be freed too. */
payment = tal(hout, struct wallet_payment);
struct wallet_payment *payment = tal(hout, struct wallet_payment);
payment->id = 0;
payment->payment_hash = *rhash;
payment->partid = partid;
Expand Down
4 changes: 2 additions & 2 deletions tests/test_pay.py
Original file line number Diff line number Diff line change
Expand Up @@ -532,12 +532,12 @@ def check_balances():
wait_for(check_balances)

# Repeat will "succeed", but won't actually send anything (duplicate)
assert not l1.daemon.is_in_log('... succeeded')
assert not l1.daemon.is_in_log('Payment 0/1: .* COMPLETE')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For non-experimental builds this doesn't test anything. I'll just also apply #3335 😉

details = l1.rpc.sendpay([routestep], rhash)
assert details['status'] == "complete"
preimage2 = details['payment_preimage']
assert preimage == preimage2
l1.daemon.wait_for_log('... succeeded')
l1.daemon.wait_for_log('Payment 0/1: .* COMPLETE')
assert only_one(l2.rpc.listinvoices('testpayment2')['invoices'])['status'] == 'paid'
assert only_one(l2.rpc.listinvoices('testpayment2')['invoices'])['msatoshi_received'] == rs['msatoshi']

Expand Down