Skip to content

Commit

Permalink
keysend: Add maxfee to keysend for consistency with pay. ([Elements…
Browse files Browse the repository at this point in the history
…Project#7227])

Changelog-Added: keysend: Add `maxfee` to keysend for consistency with pay. ([ElementsProject#7227])
  • Loading branch information
s373nZ committed Sep 17, 2024
1 parent 9e8dd15 commit 5fed120
Show file tree
Hide file tree
Showing 11 changed files with 789 additions and 707 deletions.
5 changes: 5 additions & 0 deletions .msggen.json
Original file line number Diff line number Diff line change
Expand Up @@ -1521,6 +1521,7 @@
"KeySend.extratlvs": 9,
"KeySend.label": 3,
"KeySend.maxdelay": 6,
"KeySend.maxfee": 11,
"KeySend.maxfeepercent": 4,
"KeySend.msatoshi": 2,
"KeySend.retry_for": 5,
Expand Down Expand Up @@ -6362,6 +6363,10 @@
"added": "pre-v0.10.1",
"deprecated": null
},
"KeySend.maxfee": {
"added": "v24.11",
"deprecated": null
},
"KeySend.maxfeepercent": {
"added": "pre-v0.10.1",
"deprecated": null
Expand Down
1 change: 1 addition & 0 deletions cln-grpc/proto/node.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions cln-grpc/src/convert.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cln-grpc/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ fn test_keysend() {
}],
}),
extratlvs: None,
maxfee: None
};

let u: cln_rpc::model::requests::KeysendRequest = g.into();
Expand Down
2 changes: 2 additions & 0 deletions cln-rpc/src/model.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 17 additions & 10 deletions contrib/msggen/msggen/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -15048,6 +15048,13 @@
"description": [
"Dictionary of additional fields to insert into the final tlv. The format is 'fieldnumber': 'hexstring'."
]
},
"maxfee": {
"added": "v24.11",
"type": "msat",
"description": [
"*maxfee* overrides both *maxfeepercent* and *exemptfee* defaults (and if you specify *maxfee* you cannot specify either of those), and creates an absolute limit on what fee we will pay. This allows you to implement your own heuristics rather than the primitive ones used here."
]
}
}
},
Expand Down Expand Up @@ -15180,12 +15187,12 @@
},
"response": {
"destination": "035d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d",
"payment_hash": "80ff407792947a23f193f9a1968e9a437b071364ae3159f83631335c9a453c1b",
"created_at": 1722303677.1300898,
"payment_hash": "bf2806c76e67fbc07bccd878a2c583664f97ba49eebb707939bacaa9d6813858",
"created_at": 1726169170.7366238,
"parts": 1,
"amount_msat": 10000,
"amount_sent_msat": 10001,
"payment_preimage": "0d802c9c611bae611d51afa8ddf396df8ba4e0580a2eccfd1120da97e70482a0",
"payment_preimage": "8e9f92f7f4928fde1469b199680aa09ca2690fa4e1c0b21ea60fd87e962ecce4",
"status": "complete"
}
},
Expand All @@ -15204,12 +15211,12 @@
},
"response": {
"destination": "0382ce59ebf18be7d84677c2e35f23294b9992ceca95491fcf8a56c6cb2d9de199",
"payment_hash": "3b80a3028343b16f8ab7261343eae40ff73ba833b0b7d4dcbfd42a3078dc322b",
"created_at": 1722303679.3164163,
"payment_hash": "06f861fa426d342ca144785243c44093654ff8dfc7cec7db346286c20010cffc",
"created_at": 1726169172.886992,
"parts": 1,
"amount_msat": 10000000,
"amount_sent_msat": 10000202,
"payment_preimage": "f76d6b7ef362f33e25eb5571e616f6e539a2c77caf0afa4227d1351546823664",
"payment_preimage": "9416f0889f0dfe6316e047aee01a5451e29753a51b10ddda83fe6b8ccad2eb72",
"status": "complete"
}
},
Expand All @@ -15223,7 +15230,7 @@
"routehints": [
[
{
"scid": "6250403x3681116x19863",
"scid": "6974878x14919575x17443",
"id": "022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59",
"feebase": "1msat",
"feeprop": 10,
Expand All @@ -15235,12 +15242,12 @@
},
"response": {
"destination": "035d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d",
"payment_hash": "0458c01fdd3aa0b9829002390301f92083e78dc27bb293bc3e7caee5d4ed7259",
"created_at": 1722303682.5805738,
"payment_hash": "c9e6b85aafb27fbaceb3f0eca6d0940d6e1d93a4be2d6da981aed9503fcec92c",
"created_at": 1726169175.977477,
"parts": 2,
"amount_msat": 10000,
"amount_sent_msat": 10001,
"payment_preimage": "4dad6dcf625f650a35a8199fbda18ea4f6717cdfadb40e6bed2bf5f96a4742b0",
"payment_preimage": "1cd3749122f3a1b8c1cbd9f4585ec820ec4e1b8f2aa3514a4dd9deda170bdf5d",
"status": "complete"
}
}
Expand Down
1,348 changes: 674 additions & 674 deletions contrib/pyln-grpc-proto/pyln/grpc/node_pb2.py

Large diffs are not rendered by default.

27 changes: 17 additions & 10 deletions doc/schemas/lightning-keysend.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@
"description": [
"Dictionary of additional fields to insert into the final tlv. The format is 'fieldnumber': 'hexstring'."
]
},
"maxfee": {
"added": "v24.11",
"type": "msat",
"description": [
"*maxfee* overrides both *maxfeepercent* and *exemptfee* defaults (and if you specify *maxfee* you cannot specify either of those), and creates an absolute limit on what fee we will pay. This allows you to implement your own heuristics rather than the primitive ones used here."
]
}
}
},
Expand Down Expand Up @@ -235,12 +242,12 @@
},
"response": {
"destination": "035d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d",
"payment_hash": "80ff407792947a23f193f9a1968e9a437b071364ae3159f83631335c9a453c1b",
"created_at": 1722303677.1300898,
"payment_hash": "bf2806c76e67fbc07bccd878a2c583664f97ba49eebb707939bacaa9d6813858",
"created_at": 1726169170.7366238,
"parts": 1,
"amount_msat": 10000,
"amount_sent_msat": 10001,
"payment_preimage": "0d802c9c611bae611d51afa8ddf396df8ba4e0580a2eccfd1120da97e70482a0",
"payment_preimage": "8e9f92f7f4928fde1469b199680aa09ca2690fa4e1c0b21ea60fd87e962ecce4",
"status": "complete"
}
},
Expand All @@ -259,12 +266,12 @@
},
"response": {
"destination": "0382ce59ebf18be7d84677c2e35f23294b9992ceca95491fcf8a56c6cb2d9de199",
"payment_hash": "3b80a3028343b16f8ab7261343eae40ff73ba833b0b7d4dcbfd42a3078dc322b",
"created_at": 1722303679.3164163,
"payment_hash": "06f861fa426d342ca144785243c44093654ff8dfc7cec7db346286c20010cffc",
"created_at": 1726169172.886992,
"parts": 1,
"amount_msat": 10000000,
"amount_sent_msat": 10000202,
"payment_preimage": "f76d6b7ef362f33e25eb5571e616f6e539a2c77caf0afa4227d1351546823664",
"payment_preimage": "9416f0889f0dfe6316e047aee01a5451e29753a51b10ddda83fe6b8ccad2eb72",
"status": "complete"
}
},
Expand All @@ -278,7 +285,7 @@
"routehints": [
[
{
"scid": "6250403x3681116x19863",
"scid": "6974878x14919575x17443",
"id": "022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59",
"feebase": "1msat",
"feeprop": 10,
Expand All @@ -290,12 +297,12 @@
},
"response": {
"destination": "035d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d",
"payment_hash": "0458c01fdd3aa0b9829002390301f92083e78dc27bb293bc3e7caee5d4ed7259",
"created_at": 1722303682.5805738,
"payment_hash": "c9e6b85aafb27fbaceb3f0eca6d0940d6e1d93a4be2d6da981aed9503fcec92c",
"created_at": 1726169175.977477,
"parts": 2,
"amount_msat": 10000,
"amount_sent_msat": 10001,
"payment_preimage": "4dad6dcf625f650a35a8199fbda18ea4f6717cdfadb40e6bed2bf5f96a4742b0",
"payment_preimage": "1cd3749122f3a1b8c1cbd9f4585ec820ec4e1b8f2aa3514a4dd9deda170bdf5d",
"status": "complete"
}
}
Expand Down
44 changes: 31 additions & 13 deletions plugins/keysend.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ static u64 *accepted_extra_tlvs;
* ================
*
* The keysend modifier adds the payment preimage to the TLV payload. This
* enables the recipient to accept the payment despite it not correspondin to
* enables the recipient to accept the payment despite it not corresponding to
* an invoice that the recipient created. Keysend does not provide any proof
* or payment, but does not require an out-of-band communication round to get
* an invoice first.
Expand Down Expand Up @@ -215,7 +215,7 @@ static struct command_result *json_keysend(struct command *cmd, const char *buf,
{
struct payment *p;
const char *label;
struct amount_msat *exemptfee, *msat;
struct amount_msat *exemptfee, *msat, *maxfee;
struct node_id *destination;
u64 *maxfee_pct_millionths;
u32 *maxdelay;
Expand All @@ -229,14 +229,15 @@ static struct command_result *json_keysend(struct command *cmd, const char *buf,
p_req("destination", param_node_id, &destination),
p_req("amount_msat", param_msat, &msat),
p_opt("label", param_string, &label),
p_opt_def("maxfeepercent", param_millionths,
&maxfee_pct_millionths, 500000),
p_opt("maxfeepercent", param_millionths,
&maxfee_pct_millionths),
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)),
p_opt("exemptfee", param_msat, &exemptfee),
p_opt("extratlvs", param_extra_tlvs, &extra_fields),
p_opt("routehints", param_routehint_array, &hints),
p_opt("maxfee", param_msat, &maxfee),
p_opt_dev("dev_use_shadow", param_bool, &dev_use_shadow, true),
NULL))
return command_param_failed();
Expand Down Expand Up @@ -272,19 +273,36 @@ static struct command_result *json_keysend(struct command *cmd, const char *buf,
"We are the destination. Keysend cannot be used to send funds to yourself");
}

if (!amount_msat_fee(&p->constraints.fee_budget, p->our_amount, 0,
*maxfee_pct_millionths / 100)) {
return command_fail(
cmd, JSONRPC2_INVALID_PARAMS,
"Overflow when computing fee budget, fee rate too high.");
}

p->constraints.cltv_budget = *maxdelay;

payment_mod_keysend_get_data(p)->extra_tlvs =
tal_steal(p, extra_fields);

payment_mod_exemptfee_get_data(p)->amount = *exemptfee;
if (maxfee) {
if (maxfee_pct_millionths || exemptfee) {
return command_fail(
cmd, JSONRPC2_INVALID_PARAMS,
"If you specify maxfee, cannot specify maxfeepercent or exemptfee.");
}
p->constraints.fee_budget = *maxfee;
payment_mod_exemptfee_get_data(p)->amount = AMOUNT_MSAT(0);
} else {
u64 maxppm;

if (maxfee_pct_millionths)
maxppm = *maxfee_pct_millionths / 100;
else
maxppm = 500000 / 100;
if (!amount_msat_fee(&p->constraints.fee_budget, p->our_amount, 0,
maxppm)) {
return command_fail(
cmd, JSONRPC2_INVALID_PARAMS,
"Overflow when computing fee budget, fee rate too high.");
}
payment_mod_exemptfee_get_data(p)->amount
= exemptfee ? *exemptfee : AMOUNT_MSAT(5000);
}

payment_mod_shadowroute_get_data(p)->use_shadow = *dev_use_shadow;
p->label = tal_steal(p, label);

Expand Down
25 changes: 25 additions & 0 deletions tests/test_pay.py
Original file line number Diff line number Diff line change
Expand Up @@ -3655,6 +3655,31 @@ def test_keysend_routehint(node_factory):
assert(inv['amount_received_msat'] >= Millisatoshi(amt))


def test_keysend_maxfee(node_factory):
l1, l2, l3 = node_factory.line_graph(
3,
wait_for_announce=True,
opts=[{}, {'fee-base': 50, 'fee-per-satoshi': 0}, {}]
)

# We should fail because maxfee and exemptfee cannot be set simultaneously.
with pytest.raises(RpcError):
l1.rpc.call("keysend", payload={'destination': l3.info['id'], 'amount_msat': 1, 'maxfee': 1, 'exemptfee': 5000})

# We should fail because maxfee and maxfeepercent cannot be set simultaneously.
with pytest.raises(RpcError):
l1.rpc.call("keysend", payload={'destination': l3.info['id'], 'amount_msat': 1, 'maxfee': 1, 'maxfeepercent': 0.0001})

# We should fail because 50msat base fee on l2 exceeds maxfee of 1msat.
with pytest.raises(RpcError):
l1.rpc.call("keysend", payload={'destination': l3.info['id'], 'amount_msat': 1, 'maxfee': 1})
assert len(l3.rpc.listinvoices()['invoices']) == 0

# Perform a normal keysend with maxfee.
l1.rpc.call("keysend", payload={'destination': l3.info['id'], 'amount_msat': 1, 'maxfee': 50})
assert len(l3.rpc.listinvoices()['invoices']) == 1


def test_invalid_onion_channel_update(node_factory):
'''
Some onion failures "should" send a `channel_update`.
Expand Down
14 changes: 14 additions & 0 deletions tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,20 @@ def test_pay_plugin(node_factory):
assert only_one(l1.rpc.help('pay')['help'])['command'] == msg


def test_keysend_plugin(node_factory):
l1, l2 = node_factory.line_graph(2)

with pytest.raises(RpcError, match=r'missing required parameter'):
l1.rpc.call('keysend')

# Make sure usage messages are present.
msg = 'keysend destination amount_msat [label] [maxfeepercent] [retry_for] '\
'[maxdelay] [exemptfee] [extratlvs] [routehints] [maxfee]'
# We run with --developer:
msg += ' [dev_use_shadow]'
assert only_one(l1.rpc.help('keysend')['help'])['command'] == msg


def test_plugin_connected_hook_chaining(node_factory):
""" l1 uses the logger_a, reject and logger_b plugin.
Expand Down

0 comments on commit 5fed120

Please sign in to comment.