diff --git a/doc/Makefile b/doc/Makefile index 8c02044a5ed3..c3d44c3e70b7 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -16,6 +16,7 @@ MANPAGES := doc/lightning-cli.1 \ doc/lightning-decodepay.7 \ doc/lightning-delexpiredinvoice.7 \ doc/lightning-delinvoice.7 \ + doc/lightning-delpay.7 \ doc/lightning-dev-sendcustommsg.7 \ doc/lightning-disconnect.7 \ doc/lightning-feerates.7 \ diff --git a/doc/index.rst b/doc/index.rst index 95c286885a98..69e34e6f3dde 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -38,6 +38,7 @@ c-lightning Documentation lightning-decodepay lightning-delexpiredinvoice lightning-delinvoice + lightning-delpay lightning-dev-sendcustommsg lightning-disconnect lightning-feerates diff --git a/doc/lightning-delpay.7 b/doc/lightning-delpay.7 new file mode 100644 index 000000000000..6867a17bde8c --- /dev/null +++ b/doc/lightning-delpay.7 @@ -0,0 +1,63 @@ +.TH "LIGHTNING-DELPAY" "7" "" "" "lightning-delpay" +.SH NAME +lightning-delpay - Command for removing a completed payment +.SH SYNOPSIS + +\fBdelpay\fR [payment_hash] [status] (is not specify the status is complete) + +.SH DESCRIPTION + +The \fBdelpay\fR RPC command removes a payment as given in \fBlistsendpays\fR or \fBlistpays\fR with a complete or failed +status\. However, the command doesn't permit to remove the pending payment\. + +.SH EXAMPLE JSON REQUEST +.nf +.RS +{ + "id": 82, + "method": "delpay", + "params": { + "payment_hash": "4fa2f1b001067ec06d7f95b8695b8acd9ef04c1b4d1110e3b94e1fa0687bb1e0", + "status": "complete" + } +} +.RE + +.fi +.SH RETURN VALUE + +On success, the command will return a payment object, such as the listpays\. + + +On failure, an error is returned and any payment is deleted\. If the lightning process fails before responding, the +caller should use \fBlightning-listsentpays\fR(7) or \fBlightning-listpays\fR(7) to query whether this payment was deleted or not\. + + +The following error codes may occur: + +.RS +.IP \[bu] +-32602: Some parameter missed or some parameter is malformed; +.IP \[bu] +-1: Payment with wrong status +.IP \[bu] +-1: Payment not found\. + +.RE + +- + +.SH AUTHOR + +Vincenzo Palazzo \fI is mainly responsible\. + +.SH SEE ALSO + +\fBlightning-listpays\fR(7), \fBlightning-listsendpays\fR(7), +\fBlightning-pay\fR(7), \fBlightning-paystatus\fR(7), +\\fBlightning-keysend\\fR(7) + +.SH RESOURCES + +Main web site: \fIhttps://github.com/ElementsProject/lightning\fR + diff --git a/doc/lightning-delpay.7.md b/doc/lightning-delpay.7.md new file mode 100644 index 000000000000..c4be21a163b2 --- /dev/null +++ b/doc/lightning-delpay.7.md @@ -0,0 +1,58 @@ +lightning-delpay -- Command for removing a completed payment +============================================================ + +SYNOPSIS +-------- + +**delpay** *payment_hash* \[status\] (is not specify the status is complete) + +DESCRIPTION +----------- + +The **delpay** RPC command removes a payment as given in **listsendpays** or **listpays** with a complete or failed +status. However, the command doesn't permit to remove the pending payment. + +EXAMPLE JSON REQUEST +------------ +```json +{ + "id": 82, + "method": "delpay", + "params": { + "payment_hash": "4fa2f1b001067ec06d7f95b8695b8acd9ef04c1b4d1110e3b94e1fa0687bb1e0", + "status": "complete" + } +} +``` + +RETURN VALUE +------------ + +On success, the command will return a payment object, such as the listpays. + +On failure, an error is returned and any payment is deleted. If the lightning process fails before responding, the +caller should use lightning-listsentpays(7) or lightning-listpays(7) to query whether this payment was deleted or not. + +The following error codes may occur: + +- -32602: Some parameter missed or some parameter is malformed; +- -1: Payment with wrong status +- -1: Payment not found. + +- +AUTHOR +------ + +Vincenzo Palazzo <> is mainly responsible. + +SEE ALSO +-------- + +lightning-listpays(7), lightning-listsendpays(7), +lightning-pay(7), lightning-paystatus(7), +~~lightning-keysend(7)~~ + +RESOURCES +--------- + +Main web site: diff --git a/lightningd/pay.c b/lightningd/pay.c index f9dc40f5715e..9614155c73c0 100644 --- a/lightningd/pay.c +++ b/lightningd/pay.c @@ -42,6 +42,31 @@ struct sendpay_command { struct command *cmd; }; +static enum wallet_payment_status string_to_payment_status(const char *status_str) +{ + if (streq(status_str, "complete")) { + return PAYMENT_COMPLETE; + } else if (streq(status_str, "failed")) { + return PAYMENT_FAILED; + } else if (streq(status_str, "pending")) { + return PAYMENT_PENDING; + } + return -1; +} + +static const char *payment_status_to_string(const enum wallet_payment_status status) +{ + switch (status) { + case PAYMENT_COMPLETE: + return "complete"; + case PAYMENT_FAILED: + return "failed"; + default: + return "pending"; + } +} + + static void destroy_sendpay_command(struct sendpay_command *pc) { list_del(&pc->list); @@ -1496,6 +1521,74 @@ static const struct json_command listsendpays_command = { }; AUTODATA(json_command, &listsendpays_command); +static struct command_result *json_delpay(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) +{ + struct json_stream *response; + const struct wallet_payment *payment; + const char *status_str; + enum wallet_payment_status status; + struct sha256 *payment_hash; + u64 *partid; + + if (!param(cmd, buffer, params, + p_req("payment_hash", param_sha256, &payment_hash), + p_opt("status", param_string, &status_str), + p_opt_def("partid", param_u64, &partid, 0), + NULL)) + return command_param_failed(); + + if (!payment_hash) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "missing required parameter: payment_hash"); + + + status = status_str == NULL ? PAYMENT_COMPLETE : string_to_payment_status(status_str); + + if (status == -1) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "status insert %s wrong", status_str); + + if (status == PAYMENT_PENDING) + return command_fail(cmd, LIGHTNINGD, "invalid status: %s", + payment_status_to_string(status)); + + payment = wallet_payment_by_hash(cmd, cmd->ld->wallet, payment_hash, *partid); + + //FIXME(vicenzopalazzo): set the correct error about payment not found + if (!payment) + return command_fail(cmd, LIGHTNINGD, + "unknown payment with payment_hash: %s and status: %s", + type_to_string(tmpctx, struct sha256, payment_hash), + payment_status_to_string(status)); + + //FIXME(vicenzopalazzo) set the correct error about status + if (payment->status != status) + return command_fail(cmd, LIGHTNINGD, + "payment status is %s not %s", + payment_status_to_string(payment->status), + payment_status_to_string(status)); + + wallet_payment_delete(cmd->ld->wallet, payment_hash, payment->partid); + + response = json_stream_success(cmd); + json_object_start(response, "payment"); + json_add_payment_fields(response, payment); + json_object_end(response); + + return command_success(cmd, response); +} + +static const struct json_command delpay_command = { + "delpay", + "payment", + json_delpay, + "Delete payment with {payment_hash} and {status}, (if no is complete)", +}; +AUTODATA(json_command, &delpay_command); + static struct command_result *json_createonion(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, diff --git a/tests/test_pay.py b/tests/test_pay.py index 8d22f86589b4..dfe625e29849 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -3221,3 +3221,58 @@ def test_bolt11_null_after_pay(node_factory, bitcoind): pays = l2.rpc.listpays()["pays"] assert(pays[0]["bolt11"] == invl1) assert('amount_msat' in pays[0] and pays[0]['amount_msat'] == amt) + + +def test_delpay_argument_invalid(node_factory): + """ + This test includes all possible combination of input error inside the + delpay command. + TODO(vincenzopalazzo) more sanity test when the payment exists. + """ + + l1 = node_factory.get_node() + + with pytest.raises(RpcError): + l1.rpc.delpay() + + # invoice unpayed + payment_hash = l1.rpc.invoice(10**5, "no_payed", "Invoice unpayed")["payment_hash"] + with pytest.raises(RpcError): + l1.rpc.delpay(payment_hash) + + # payment unpayed with wrong status (pending status is a illegal imput) + with pytest.raises(RpcError): + l1.rpc.delpay(payment_hash, "pending") + + with pytest.raises(RpcError): + l1.rpc.delpay(payment_hash, "invalid_status") + + +def test_delpay(node_factory, bitcoind): + """ + This unit test try to catch some error inside the command + delpay when it receives the correct input from the user + """ + + l1, l2 = node_factory.get_nodes(2) + + amount_sat = 10 ** 6 + + # create l2->l1 channel. + l2.fundwallet(amount_sat * 5) + l1.rpc.connect(l2.info['id'], 'localhost', l2.port) + l2.rpc.fundchannel(l1.info['id'], amount_sat * 3) + + # Let the channel confirm. + bitcoind.generate_block(6) + sync_blockheight(bitcoind, [l1, l2]) + + invl1 = l1.rpc.invoice(Millisatoshi(amount_sat * 2 * 1000), "j", "j") + l2.rpc.pay(invl1["bolt11"]) + + before_del_pay = l2.rpc.listpays() + + l2.rpc.delpay(invl1["payment_hash"]) + + after_del_pay = l2.rpc.listpays()["pays"] + assert len(after_del_pay) == (len(before_del_pay) - 1)