diff --git a/doc/lightning-delpay.7.md b/doc/lightning-delpay.7.md index 021787d7b..cc57d4d7d 100644 --- a/doc/lightning-delpay.7.md +++ b/doc/lightning-delpay.7.md @@ -9,7 +9,7 @@ SYNOPSIS DESCRIPTION ----------- -The **delpay** RPC command deletes a payment with the given `payment_hash` if its status is either `complete` or `failed`. Deleting a `pending` payment is an error. If *partid* and *groupid* are not specified, all payment parts are deleted. +The **delpay** RPC command deletes a payment with the given `payment_hash` if its status is either `complete` or `failed`. Deleting a `pending` payment is an error. If *partid* and *groupid* are not specified, all payment parts with matchin status are deleted. - *payment\_hash*: The unique identifier of a payment. - *status*: Expected status of the payment. diff --git a/lightningd/pay.c b/lightningd/pay.c index 921f46ed5..0f0953246 100644 --- a/lightningd/pay.c +++ b/lightningd/pay.c @@ -1654,6 +1654,7 @@ static struct command_result *json_delpay(struct command *cmd, const jsmntok_t *obj UNNEEDED, const jsmntok_t *params) { + const enum wallet_payment_status *found_status = NULL; struct json_stream *response; const struct wallet_payment **payments; enum wallet_payment_status *status; @@ -1686,21 +1687,26 @@ static struct command_result *json_delpay(struct command *cmd, if (partid && payments[i]->partid != *partid) continue; - found = true; - if (payments[i]->status != *status) { - return command_fail(cmd, PAY_STATUS_UNEXPECTED, "Payment with hash %s has %s status but it should be %s", - type_to_string(tmpctx, struct sha256, payment_hash), - payment_status_to_string(payments[i]->status), - payment_status_to_string(*status)); + if (payments[i]->status == *status) { + found = true; + break; } + + found_status = &payments[i]->status; } if (!found) { + if (found_status) + return command_fail(cmd, PAY_NO_SUCH_PAYMENT, "Payment with hash %s has %s status but it different from the one provided %s", + type_to_string(tmpctx, struct sha256, payment_hash), + payment_status_to_string(*found_status), + payment_status_to_string(*status)); + return command_fail(cmd, PAY_NO_SUCH_PAYMENT, "No payment for that payment_hash with that partid and groupid"); } - wallet_payment_delete(cmd->ld->wallet, payment_hash, groupid, partid); + wallet_payment_delete(cmd->ld->wallet, payment_hash, groupid, partid, status); response = json_stream_success(cmd); json_array_start(response, "payments"); @@ -1709,6 +1715,8 @@ static struct command_result *json_delpay(struct command *cmd, continue; if (partid && payments[i]->partid != *partid) continue; + if (payments[i]->status != *status) + continue; json_object_start(response, NULL); json_add_payment_fields(response, payments[i]); json_object_end(response); diff --git a/tests/test_pay.py b/tests/test_pay.py index 6cca937b8..4cab55bb1 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -4096,6 +4096,29 @@ def test_delpay_payment_split(node_factory, bitcoind): assert len(l1.rpc.listpays()['pays']) == 0 +@pytest.mark.developer("needs dev-no-reconnect, dev-routes to force failover") +def test_delpay_mixed_status(node_factory, bitcoind): + """ + One failure, one success; we only want to delete the failed one! + """ + l1, l2, l3 = node_factory.line_graph(3, fundamount=10**5, + wait_for_announce=True) + # Expensive route! + l4 = node_factory.get_node(options={'fee-per-satoshi': 1000, + 'fee-base': 2000}) + node_factory.join_nodes([l1, l4, l3], wait_for_announce=True) + + # Don't give a hint, so l1 chooses cheapest. + inv = l3.dev_invoice(10**5, 'lbl', 'desc', dev_routes=[]) + l3.rpc.disconnect(l2.info['id'], force=True) + l1.rpc.pay(inv['bolt11']) + + assert len(l1.rpc.listsendpays()['payments']) == 2 + delpay_result = l1.rpc.delpay(inv['payment_hash'], 'failed')['payments'] + assert len(delpay_result) == 1 + assert len(l1.rpc.listsendpays()['payments']) == 1 + + def test_listpay_result_with_paymod(node_factory, bitcoind): """ The object of this test is to verify the correct behavior diff --git a/wallet/wallet.c b/wallet/wallet.c index 05bf340b0..65b02ad58 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -3294,24 +3294,30 @@ u64 wallet_payment_get_groupid(struct wallet *wallet, void wallet_payment_delete(struct wallet *wallet, const struct sha256 *payment_hash, - const u64 *groupid, - const u64 *partid) + const u64 *groupid, const u64 *partid, + const enum wallet_payment_status *status) { struct db_stmt *stmt; + + assert(status); if (groupid) { assert(partid); stmt = db_prepare_v2(wallet->db, SQL("DELETE FROM payments" " WHERE payment_hash = ?" " AND groupid = ?" - " AND partid = ?")); + " AND partid = ?" + " AND status = ?")); db_bind_u64(stmt, 1, *groupid); db_bind_u64(stmt, 2, *partid); + db_bind_u64(stmt, 3, *status); } else { assert(!partid); stmt = db_prepare_v2(wallet->db, SQL("DELETE FROM payments" - " WHERE payment_hash = ?")); + " WHERE payment_hash = ?" + " AND status = ?")); + db_bind_u64(stmt, 1, *status); } db_bind_sha256(stmt, 0, payment_hash); db_exec_prepared_v2(take(stmt)); diff --git a/wallet/wallet.h b/wallet/wallet.h index ec7bfc6a2..b6c9bb408 100644 --- a/wallet/wallet.h +++ b/wallet/wallet.h @@ -1106,8 +1106,8 @@ void wallet_payment_store(struct wallet *wallet, */ void wallet_payment_delete(struct wallet *wallet, const struct sha256 *payment_hash, - const u64 *groupid, - const u64 *partid); + const u64 *groupid, const u64 *partid, + const enum wallet_payment_status *status); /** * wallet_local_htlc_out_delete - Remove a local outgoing failed HTLC