autoclean: clean up listforwards as well.

And take the opportunity to rename l0 and l1 in the tests to the
more natural l1 l2 (since we add l3).

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell
2022-09-19 10:23:00 +09:30
committed by Christian Decker
parent 3079afb024
commit a15f1be5f8
2 changed files with 155 additions and 55 deletions

View File

@@ -8,6 +8,8 @@
#include <plugins/libplugin.h>
enum subsystem {
SUCCEEDEDFORWARDS,
FAILEDFORWARDS,
SUCCEEDEDPAYS,
FAILEDPAYS,
PAIDINVOICES,
@@ -15,6 +17,8 @@ enum subsystem {
#define NUM_SUBSYSTEM (EXPIREDINVOICES + 1)
};
static const char *subsystem_str[] = {
"succeededforwards",
"failedforwards",
"succeededpays",
"failedpays",
"paidinvoices",
@@ -227,6 +231,73 @@ static struct command_result *listsendpays_done(struct command *cmd,
return set_next_timer(plugin);
}
static struct command_result *listforwards_done(struct command *cmd,
const char *buf,
const jsmntok_t *result,
char *unused)
{
const jsmntok_t *t, *fwds = json_get_member(buf, result, "forwards");
size_t i;
u64 now = time_now().ts.tv_sec;
json_for_each_arr(i, t, fwds) {
const jsmntok_t *status = json_get_member(buf, t, "status");
jsmntok_t time;
enum subsystem subsys;
u64 restime;
if (json_tok_streq(buf, status, "settled")) {
subsys = SUCCEEDEDFORWARDS;
} else if (json_tok_streq(buf, status, "failed")
|| json_tok_streq(buf, status, "local_failed")) {
subsys = FAILEDFORWARDS;
} else
continue;
/* Continue if we don't care. */
if (subsystem_age[subsys] == 0)
continue;
time = *json_get_member(buf, t, "resolved_time");
/* This is a float, so truncate at '.' */
for (int off = time.start; off < time.end; off++) {
if (buf[off] == '.')
time.end = off;
}
if (!json_to_u64(buf, &time, &restime)) {
plugin_err(plugin, "Bad time '%.*s'",
json_tok_full_len(&time),
json_tok_full(buf, &time));
}
if (restime <= now - subsystem_age[subsys]) {
struct out_req *req;
const jsmntok_t *inchan, *inid;
inchan = json_get_member(buf, t, "in_channel");
inid = json_get_member(buf, t, "in_htlc_id");
req = jsonrpc_request_start(plugin, NULL, "delforward",
del_done, del_failed,
int2ptr(subsys));
json_add_tok(req->js, "in_channel", inchan, buf);
json_add_tok(req->js, "in_htlc_id", inid, buf);
json_add_tok(req->js, "status", status, buf);
send_outreq(plugin, req);
plugin_log(plugin, LOG_DBG, "Cleaning up fwd %.*s/%.*s",
json_tok_full_len(inchan),
json_tok_full(buf, inchan),
json_tok_full_len(inid),
json_tok_full(buf, inid));
cleanup_reqs_remaining++;
}
}
if (cleanup_reqs_remaining)
return command_still_pending(cmd);
return set_next_timer(plugin);
}
static void do_clean(void *unused)
{
struct out_req *req = NULL;
@@ -248,6 +319,14 @@ static void do_clean(void *unused)
send_outreq(plugin, req);
}
if (subsystem_age[SUCCEEDEDFORWARDS] != 0
|| subsystem_age[FAILEDFORWARDS] != 0) {
req = jsonrpc_request_start(plugin, NULL, "listforwards",
listforwards_done, cmd_failed,
(char *)"listforwards");
send_outreq(plugin, req);
}
if (!req)
set_next_timer(plugin);
}

View File

@@ -2945,90 +2945,111 @@ def test_commando_badrune(node_factory):
def test_autoclean(node_factory):
l0, l1 = node_factory.line_graph(2, opts={'autoclean-cycle': 10,
'may_reconnect': True})
l1, l2, l3 = node_factory.line_graph(3, opts={'autoclean-cycle': 10,
'may_reconnect': True},
wait_for_announce=True)
assert l1.rpc.autoclean_status('expiredinvoices')['autoclean']['expiredinvoices']['enabled'] is False
l1.rpc.invoice(amount_msat=12300, label='inv1', description='description1', expiry=5)
l1.rpc.invoice(amount_msat=12300, label='inv2', description='description2', expiry=20)
l1.rpc.invoice(amount_msat=12300, label='inv3', description='description3', expiry=20)
inv4 = l1.rpc.invoice(amount_msat=12300, label='inv4', description='description4', expiry=2000)
inv5 = l1.rpc.invoice(amount_msat=12300, label='inv5', description='description5', expiry=2000)
l1.rpc.autoclean(subsystem='expiredinvoices', age=2)
assert l1.rpc.autoclean_status()['autoclean']['expiredinvoices']['enabled'] is True
assert l1.rpc.autoclean_status()['autoclean']['expiredinvoices']['age'] == 2
assert l3.rpc.autoclean_status('expiredinvoices')['autoclean']['expiredinvoices']['enabled'] is False
l3.rpc.invoice(amount_msat=12300, label='inv1', description='description1', expiry=5)
l3.rpc.invoice(amount_msat=12300, label='inv2', description='description2', expiry=20)
l3.rpc.invoice(amount_msat=12300, label='inv3', description='description3', expiry=20)
inv4 = l3.rpc.invoice(amount_msat=12300, label='inv4', description='description4', expiry=2000)
inv5 = l3.rpc.invoice(amount_msat=12300, label='inv5', description='description5', expiry=2000)
l3.rpc.autoclean(subsystem='expiredinvoices', age=2)
assert l3.rpc.autoclean_status()['autoclean']['expiredinvoices']['enabled'] is True
assert l3.rpc.autoclean_status()['autoclean']['expiredinvoices']['age'] == 2
# Both should still be there.
assert l1.rpc.autoclean_status()['autoclean']['expiredinvoices']['cleaned'] == 0
assert len(l1.rpc.listinvoices('inv1')['invoices']) == 1
assert len(l1.rpc.listinvoices('inv2')['invoices']) == 1
assert l1.rpc.listinvoices('inv1')['invoices'][0]['description'] == 'description1'
assert l3.rpc.autoclean_status()['autoclean']['expiredinvoices']['cleaned'] == 0
assert len(l3.rpc.listinvoices('inv1')['invoices']) == 1
assert len(l3.rpc.listinvoices('inv2')['invoices']) == 1
assert l3.rpc.listinvoices('inv1')['invoices'][0]['description'] == 'description1'
# First it expires.
wait_for(lambda: only_one(l1.rpc.listinvoices('inv1')['invoices'])['status'] == 'expired')
wait_for(lambda: only_one(l3.rpc.listinvoices('inv1')['invoices'])['status'] == 'expired')
# Now will get autocleaned
wait_for(lambda: l1.rpc.listinvoices('inv1')['invoices'] == [])
assert l1.rpc.autoclean_status()['autoclean']['expiredinvoices']['cleaned'] == 1
wait_for(lambda: l3.rpc.listinvoices('inv1')['invoices'] == [])
assert l3.rpc.autoclean_status()['autoclean']['expiredinvoices']['cleaned'] == 1
# Keeps settings across restarts
l1.restart()
l3.restart()
assert l1.rpc.autoclean_status()['autoclean']['expiredinvoices']['enabled'] is True
assert l1.rpc.autoclean_status()['autoclean']['expiredinvoices']['age'] == 2
assert l1.rpc.autoclean_status()['autoclean']['expiredinvoices']['cleaned'] == 1
assert l3.rpc.autoclean_status()['autoclean']['expiredinvoices']['enabled'] is True
assert l3.rpc.autoclean_status()['autoclean']['expiredinvoices']['age'] == 2
assert l3.rpc.autoclean_status()['autoclean']['expiredinvoices']['cleaned'] == 1
# Disabling works
l1.rpc.autoclean(subsystem='expiredinvoices', age='never')
assert l1.rpc.autoclean_status()['autoclean']['expiredinvoices']['enabled'] is False
assert 'age' not in l1.rpc.autoclean_status()['autoclean']['expiredinvoices']
l3.rpc.autoclean(subsystem='expiredinvoices', age='never')
assert l3.rpc.autoclean_status()['autoclean']['expiredinvoices']['enabled'] is False
assert 'age' not in l3.rpc.autoclean_status()['autoclean']['expiredinvoices']
# Same with inv2/3
wait_for(lambda: only_one(l1.rpc.listinvoices('inv2')['invoices'])['status'] == 'expired')
wait_for(lambda: only_one(l3.rpc.listinvoices('inv2')['invoices'])['status'] == 'expired')
# Give it time to notice.
time.sleep(15)
assert l1.rpc.listinvoices('inv2')['invoices'] != []
assert l3.rpc.listinvoices('inv2')['invoices'] != []
# Restart keeps it disabled.
l1.restart()
assert l1.rpc.autoclean_status()['autoclean']['expiredinvoices']['enabled'] is False
assert 'age' not in l1.rpc.autoclean_status()['autoclean']['expiredinvoices']
l3.restart()
assert l3.rpc.autoclean_status()['autoclean']['expiredinvoices']['enabled'] is False
assert 'age' not in l3.rpc.autoclean_status()['autoclean']['expiredinvoices']
# Now enable: they will get autocleaned
l1.rpc.autoclean(subsystem='expiredinvoices', age=2)
wait_for(lambda: len(l1.rpc.listinvoices()['invoices']) == 2)
assert l1.rpc.autoclean_status()['autoclean']['expiredinvoices']['cleaned'] == 3
l3.rpc.autoclean(subsystem='expiredinvoices', age=2)
wait_for(lambda: len(l3.rpc.listinvoices()['invoices']) == 2)
assert l3.rpc.autoclean_status()['autoclean']['expiredinvoices']['cleaned'] == 3
# Reconnect, l0 pays invoice, we test paid expiry.
l1.rpc.connect(l0.info['id'], 'localhost', l0.port)
l0.rpc.pay(inv4['bolt11'])
# Reconnect, l1 pays invoice, we test paid expiry.
l2.rpc.connect(l3.info['id'], 'localhost', l3.port)
l1.rpc.pay(inv4['bolt11'])
# We manually delete inv5 so we can have l0 fail a payment.
l1.rpc.delinvoice('inv5', 'unpaid')
# We manually delete inv5 so we can have l1 fail a payment.
l3.rpc.delinvoice('inv5', 'unpaid')
with pytest.raises(RpcError, match='WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS'):
l0.rpc.pay(inv5['bolt11'])
l1.rpc.pay(inv5['bolt11'])
assert l1.rpc.autoclean_status()['autoclean']['paidinvoices']['enabled'] is False
assert l1.rpc.autoclean_status()['autoclean']['paidinvoices']['cleaned'] == 0
l1.rpc.autoclean(subsystem='paidinvoices', age=1)
assert l1.rpc.autoclean_status()['autoclean']['paidinvoices']['enabled'] is True
assert l3.rpc.autoclean_status()['autoclean']['paidinvoices']['enabled'] is False
assert l3.rpc.autoclean_status()['autoclean']['paidinvoices']['cleaned'] == 0
l3.rpc.autoclean(subsystem='paidinvoices', age=1)
assert l3.rpc.autoclean_status()['autoclean']['paidinvoices']['enabled'] is True
wait_for(lambda: l1.rpc.listinvoices()['invoices'] == [])
assert l1.rpc.autoclean_status()['autoclean']['expiredinvoices']['cleaned'] == 3
assert l1.rpc.autoclean_status()['autoclean']['paidinvoices']['cleaned'] == 1
wait_for(lambda: l3.rpc.listinvoices()['invoices'] == [])
assert l3.rpc.autoclean_status()['autoclean']['expiredinvoices']['cleaned'] == 3
assert l3.rpc.autoclean_status()['autoclean']['paidinvoices']['cleaned'] == 1
assert only_one(l0.rpc.listpays(inv5['bolt11'])['pays'])['status'] == 'failed'
assert only_one(l0.rpc.listpays(inv4['bolt11'])['pays'])['status'] == 'complete'
l0.rpc.autoclean(subsystem='failedpays', age=2)
assert only_one(l1.rpc.listpays(inv5['bolt11'])['pays'])['status'] == 'failed'
assert only_one(l1.rpc.listpays(inv4['bolt11'])['pays'])['status'] == 'complete'
l1.rpc.autoclean(subsystem='failedpays', age=2)
wait_for(lambda: l0.rpc.listpays(inv5['bolt11'])['pays'] == [])
assert l0.rpc.autoclean_status()['autoclean']['failedpays']['cleaned'] == 1
assert l0.rpc.autoclean_status()['autoclean']['succeededpays']['cleaned'] == 0
wait_for(lambda: l1.rpc.listpays(inv5['bolt11'])['pays'] == [])
assert l1.rpc.autoclean_status()['autoclean']['failedpays']['cleaned'] == 1
assert l1.rpc.autoclean_status()['autoclean']['succeededpays']['cleaned'] == 0
l0.rpc.autoclean(subsystem='succeededpays', age=2)
wait_for(lambda: l0.rpc.listpays(inv4['bolt11'])['pays'] == [])
assert l0.rpc.listsendpays() == {'payments': []}
l1.rpc.autoclean(subsystem='succeededpays', age=2)
wait_for(lambda: l1.rpc.listpays(inv4['bolt11'])['pays'] == [])
assert l1.rpc.listsendpays() == {'payments': []}
# Now, we should have 1 failed forward, 1 success.
assert len(l2.rpc.listforwards(status='failed')['forwards']) == 1
assert len(l2.rpc.listforwards(status='settled')['forwards']) == 1
assert len(l2.rpc.listforwards()['forwards']) == 2
# Clean failed ones.
l2.rpc.autoclean(subsystem='failedforwards', age=2)
wait_for(lambda: l2.rpc.listforwards(status='failed')['forwards'] == [])
assert len(l2.rpc.listforwards(status='settled')['forwards']) == 1
assert l2.rpc.autoclean_status()['autoclean']['failedforwards']['cleaned'] == 1
assert l2.rpc.autoclean_status()['autoclean']['succeededforwards']['cleaned'] == 0
# Clean succeeded ones
l2.rpc.autoclean(subsystem='succeededforwards', age=2)
wait_for(lambda: l2.rpc.listforwards(status='settled')['forwards'] == [])
assert l2.rpc.listforwards() == {'forwards': []}
assert l2.rpc.autoclean_status()['autoclean']['failedforwards']['cleaned'] == 1
assert l2.rpc.autoclean_status()['autoclean']['succeededforwards']['cleaned'] == 1
def test_block_added_notifications(node_factory, bitcoind):