mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-02-20 13:54:36 +01:00
lightningd: add listhtlcs
to list all the HTLCs we know about.
Using `listfowards` for this wrong; expose this directly if people care (and unlike listforwards, which could be deleted, we have to remember these while the channel is still open!). Signed-off-by: Rusty Russell <rusty@rustcorp.com.au> Changelog-Added: JSON-RPC: `listhtlcs` new command to list all known HTLCS.
This commit is contained in:
parent
311807ff1f
commit
7420a7021f
10 changed files with 461 additions and 17 deletions
|
@ -49,6 +49,7 @@ MANPAGES := doc/lightning-cli.1 \
|
|||
doc/lightning-listdatastore.7 \
|
||||
doc/lightning-listforwards.7 \
|
||||
doc/lightning-listfunds.7 \
|
||||
doc/lightning-listhtlcs.7 \
|
||||
doc/lightning-listinvoices.7 \
|
||||
doc/lightning-listoffers.7 \
|
||||
doc/lightning-listpays.7 \
|
||||
|
|
|
@ -76,6 +76,7 @@ Core Lightning Documentation
|
|||
lightning-listdatastore <lightning-listdatastore.7.md>
|
||||
lightning-listforwards <lightning-listforwards.7.md>
|
||||
lightning-listfunds <lightning-listfunds.7.md>
|
||||
lightning-listhtlcs <lightning-listhtlcs.7.md>
|
||||
lightning-listinvoices <lightning-listinvoices.7.md>
|
||||
lightning-listnodes <lightning-listnodes.7.md>
|
||||
lightning-listoffers <lightning-listoffers.7.md>
|
||||
|
|
49
doc/lightning-listhtlcs.7.md
Normal file
49
doc/lightning-listhtlcs.7.md
Normal file
|
@ -0,0 +1,49 @@
|
|||
lightning-listhtlcs -- Command for querying HTLCs
|
||||
=================================================
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
|
||||
**listhtlcs** [*id*]
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
|
||||
The **listhtlcs** RPC command gets all HTLCs (which, generally, we
|
||||
remember for as long as a channel is open, even if they've completed
|
||||
long ago). If given a short channel id (e.g. 1x2x3) or full 64-byte
|
||||
hex channel id, it will only list htlcs for that channel (which
|
||||
must be known).
|
||||
|
||||
RETURN VALUE
|
||||
------------
|
||||
|
||||
[comment]: # (GENERATE-FROM-SCHEMA-START)
|
||||
On success, an object containing **htlcs** is returned. It is an array of objects, where each object contains:
|
||||
|
||||
- **short\_channel\_id** (short\_channel\_id): the channel that contains/contained the HTLC
|
||||
- **id** (u64): the unique, incrementing HTLC id the creator gave this
|
||||
- **expiry** (u32): the block number where this HTLC expires/expired
|
||||
- **amount\_msat** (msat): the value of the HTLC
|
||||
- **direction** (string): out if we offered this to the peer, in if they offered it (one of "out", "in")
|
||||
- **payment\_hash** (hex): payment hash sought by HTLC (always 64 characters)
|
||||
- **state** (string): The first 10 states are for `in`, the next 10 are for `out`. (one of "SENT_ADD_HTLC", "SENT_ADD_COMMIT", "RCVD_ADD_REVOCATION", "RCVD_ADD_ACK_COMMIT", "SENT_ADD_ACK_REVOCATION", "RCVD_REMOVE_HTLC", "RCVD_REMOVE_COMMIT", "SENT_REMOVE_REVOCATION", "SENT_REMOVE_ACK_COMMIT", "RCVD_REMOVE_ACK_REVOCATION", "RCVD_ADD_HTLC", "RCVD_ADD_COMMIT", "SENT_ADD_REVOCATION", "SENT_ADD_ACK_COMMIT", "RCVD_ADD_ACK_REVOCATION", "SENT_REMOVE_HTLC", "SENT_REMOVE_COMMIT", "RCVD_REMOVE_REVOCATION", "RCVD_REMOVE_ACK_COMMIT", "SENT_REMOVE_ACK_REVOCATION")
|
||||
|
||||
[comment]: # (GENERATE-FROM-SCHEMA-END)
|
||||
|
||||
AUTHOR
|
||||
------
|
||||
|
||||
Rusty Russell <<rusty@rustcorp.com.au>> is mainly responsible.
|
||||
|
||||
SEE ALSO
|
||||
--------
|
||||
|
||||
lightning-listforwards(7)
|
||||
|
||||
RESOURCES
|
||||
---------
|
||||
|
||||
Main web site: <https://github.com/ElementsProject/lightning>
|
||||
|
||||
[comment]: # ( SHA256STAMP:6ef16f6e1f54522435130d99f224ca41a38fb3c5bc26886ccdaddc69f1abb946)
|
11
doc/schemas/listhtlcs.request.json
Normal file
11
doc/schemas/listhtlcs.request.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"required": [],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "channel id or short_channel_id"
|
||||
}
|
||||
}
|
||||
}
|
84
doc/schemas/listhtlcs.schema.json
Normal file
84
doc/schemas/listhtlcs.schema.json
Normal file
|
@ -0,0 +1,84 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"htlcs"
|
||||
],
|
||||
"properties": {
|
||||
"htlcs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"short_channel_id",
|
||||
"id",
|
||||
"expiry",
|
||||
"direction",
|
||||
"amount_msat",
|
||||
"payment_hash",
|
||||
"state"
|
||||
],
|
||||
"properties": {
|
||||
"short_channel_id": {
|
||||
"type": "short_channel_id",
|
||||
"description": "the channel that contains/contained the HTLC"
|
||||
},
|
||||
"id": {
|
||||
"type": "u64",
|
||||
"description": "the unique, incrementing HTLC id the creator gave this"
|
||||
},
|
||||
"expiry": {
|
||||
"type": "u32",
|
||||
"description": "the block number where this HTLC expires/expired"
|
||||
},
|
||||
"amount_msat": {
|
||||
"type": "msat",
|
||||
"description": "the value of the HTLC"
|
||||
},
|
||||
"direction": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"out",
|
||||
"in"
|
||||
],
|
||||
"description": "out if we offered this to the peer, in if they offered it"
|
||||
},
|
||||
"payment_hash": {
|
||||
"type": "hex",
|
||||
"description": "payment hash sought by HTLC",
|
||||
"maxLength": 64,
|
||||
"minLength": 64
|
||||
},
|
||||
"state": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"SENT_ADD_HTLC",
|
||||
"SENT_ADD_COMMIT",
|
||||
"RCVD_ADD_REVOCATION",
|
||||
"RCVD_ADD_ACK_COMMIT",
|
||||
"SENT_ADD_ACK_REVOCATION",
|
||||
"RCVD_REMOVE_HTLC",
|
||||
"RCVD_REMOVE_COMMIT",
|
||||
"SENT_REMOVE_REVOCATION",
|
||||
"SENT_REMOVE_ACK_COMMIT",
|
||||
"RCVD_REMOVE_ACK_REVOCATION",
|
||||
"RCVD_ADD_HTLC",
|
||||
"RCVD_ADD_COMMIT",
|
||||
"SENT_ADD_REVOCATION",
|
||||
"SENT_ADD_ACK_COMMIT",
|
||||
"RCVD_ADD_ACK_REVOCATION",
|
||||
"SENT_REMOVE_HTLC",
|
||||
"SENT_REMOVE_COMMIT",
|
||||
"RCVD_REMOVE_REVOCATION",
|
||||
"RCVD_REMOVE_ACK_COMMIT",
|
||||
"SENT_REMOVE_ACK_REVOCATION"
|
||||
],
|
||||
"description": "The first 10 states are for `in`, the next 10 are for `out`."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2894,3 +2894,83 @@ static const struct json_command listforwards_command = {
|
|||
"List all forwarded payments and their information optionally filtering by [status], [in_channel] and [out_channel]"
|
||||
};
|
||||
AUTODATA(json_command, &listforwards_command);
|
||||
|
||||
static struct command_result *param_channel(struct command *cmd,
|
||||
const char *name,
|
||||
const char *buffer,
|
||||
const jsmntok_t *tok,
|
||||
struct channel **chan)
|
||||
{
|
||||
struct channel_id cid;
|
||||
struct short_channel_id scid;
|
||||
|
||||
if (json_tok_channel_id(buffer, tok, &cid)) {
|
||||
*chan = channel_by_cid(cmd->ld, &cid);
|
||||
if (!*chan)
|
||||
return command_fail_badparam(cmd, name, buffer, tok,
|
||||
"unknown channel");
|
||||
return NULL;
|
||||
} else if (json_to_short_channel_id(buffer, tok, &scid)) {
|
||||
*chan = any_channel_by_scid(cmd->ld, &scid, true);
|
||||
if (!*chan)
|
||||
return command_fail_badparam(cmd, name, buffer, tok,
|
||||
"unknown channel");
|
||||
return NULL;
|
||||
}
|
||||
return command_fail_badparam(cmd, name, buffer, tok,
|
||||
"must be channel id or short channel id");
|
||||
}
|
||||
|
||||
static struct command_result *json_listhtlcs(struct command *cmd,
|
||||
const char *buffer,
|
||||
const jsmntok_t *obj UNNEEDED,
|
||||
const jsmntok_t *params)
|
||||
{
|
||||
struct json_stream *response;
|
||||
struct channel *chan;
|
||||
struct wallet_htlc_iter *i;
|
||||
struct short_channel_id scid;
|
||||
u64 htlc_id;
|
||||
int cltv_expiry;
|
||||
enum side owner;
|
||||
struct amount_msat msat;
|
||||
struct sha256 payment_hash;
|
||||
enum htlc_state hstate;
|
||||
|
||||
if (!param(cmd, buffer, params,
|
||||
p_opt("id", param_channel, &chan),
|
||||
NULL))
|
||||
return command_param_failed();
|
||||
|
||||
response = json_stream_success(cmd);
|
||||
json_array_start(response, "htlcs");
|
||||
for (i = wallet_htlcs_first(cmd, cmd->ld->wallet, chan,
|
||||
&scid, &htlc_id, &cltv_expiry, &owner, &msat,
|
||||
&payment_hash, &hstate);
|
||||
i;
|
||||
i = wallet_htlcs_next(cmd->ld->wallet, i,
|
||||
&scid, &htlc_id, &cltv_expiry, &owner, &msat,
|
||||
&payment_hash, &hstate)) {
|
||||
json_object_start(response, NULL);
|
||||
json_add_short_channel_id(response, "short_channel_id", &scid);
|
||||
json_add_u64(response, "id", htlc_id);
|
||||
json_add_u32(response, "expiry", cltv_expiry);
|
||||
json_add_string(response, "direction",
|
||||
owner == LOCAL ? "out": "in");
|
||||
json_add_amount_msat_only(response, "amount_msat", msat);
|
||||
json_add_sha256(response, "payment_hash", &payment_hash);
|
||||
json_add_string(response, "state", htlc_state_name(hstate));
|
||||
json_object_end(response);
|
||||
}
|
||||
json_array_end(response);
|
||||
|
||||
return command_success(cmd, response);
|
||||
}
|
||||
|
||||
static const struct json_command listhtlcs_command = {
|
||||
"listhtlcs",
|
||||
"channels",
|
||||
json_listhtlcs,
|
||||
"List all known HTLCS (optionally, just for [id] (scid or channel id))"
|
||||
};
|
||||
AUTODATA(json_command, &listhtlcs_command);
|
||||
|
|
|
@ -3992,12 +3992,13 @@ def test_multichan(node_factory, executor, bitcoind):
|
|||
# Now fund *second* channel l2->l3 (slightly larger)
|
||||
bitcoind.rpc.sendtoaddress(l2.rpc.newaddr()['bech32'], 0.1)
|
||||
bitcoind.generate_block(1)
|
||||
sync_blockheight(bitcoind, [l2])
|
||||
sync_blockheight(bitcoind, [l1, l2, l3])
|
||||
l2.rpc.fundchannel(l3.info['id'], '0.01001btc')
|
||||
assert(len(only_one(l2.rpc.listpeers(l3.info['id'])['peers'])['channels']) == 2)
|
||||
assert(len(only_one(l3.rpc.listpeers(l2.info['id'])['peers'])['channels']) == 2)
|
||||
|
||||
bitcoind.generate_block(1, wait_for_mempool=1)
|
||||
sync_blockheight(bitcoind, [l1, l2, l3])
|
||||
# Make sure new channel is also CHANNELD_NORMAL
|
||||
wait_for(lambda: [c['state'] for c in only_one(l2.rpc.listpeers(l3.info['id'])['peers'])['channels']] == ["CHANNELD_NORMAL", "CHANNELD_NORMAL"])
|
||||
|
||||
|
@ -4023,9 +4024,9 @@ def test_multichan(node_factory, executor, bitcoind):
|
|||
'delay': 5,
|
||||
'channel': scid23a}]
|
||||
before = only_one(l2.rpc.listpeers(l3.info['id'])['peers'])['channels']
|
||||
inv = l3.rpc.invoice(100000000, "invoice", "invoice")
|
||||
l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret'])
|
||||
l1.rpc.waitsendpay(inv['payment_hash'])
|
||||
inv1 = l3.rpc.invoice(100000000, "invoice", "invoice")
|
||||
l1.rpc.sendpay(route, inv1['payment_hash'], payment_secret=inv1['payment_secret'])
|
||||
l1.rpc.waitsendpay(inv1['payment_hash'])
|
||||
# Wait until HTLCs fully settled
|
||||
wait_for(lambda: [c['htlcs'] for c in only_one(l2.rpc.listpeers(l3.info['id'])['peers'])['channels']] == [[], []])
|
||||
after = only_one(l2.rpc.listpeers(l3.info['id'])['peers'])['channels']
|
||||
|
@ -4049,9 +4050,9 @@ def test_multichan(node_factory, executor, bitcoind):
|
|||
|
||||
before = only_one(l2.rpc.listpeers(l3.info['id'])['peers'])['channels']
|
||||
route[1]['channel'] = scid23b
|
||||
inv = l3.rpc.invoice(100000000, "invoice2", "invoice2")
|
||||
l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret'])
|
||||
l1.rpc.waitsendpay(inv['payment_hash'])
|
||||
inv2 = l3.rpc.invoice(100000000, "invoice2", "invoice2")
|
||||
l1.rpc.sendpay(route, inv2['payment_hash'], payment_secret=inv2['payment_secret'])
|
||||
l1.rpc.waitsendpay(inv2['payment_hash'])
|
||||
# Wait until HTLCs fully settled
|
||||
wait_for(lambda: [c['htlcs'] for c in only_one(l2.rpc.listpeers(l3.info['id'])['peers'])['channels']] == [[], []])
|
||||
after = only_one(l2.rpc.listpeers(l3.info['id'])['peers'])['channels']
|
||||
|
@ -4062,6 +4063,7 @@ def test_multichan(node_factory, executor, bitcoind):
|
|||
|
||||
# Make sure gossip works.
|
||||
bitcoind.generate_block(5)
|
||||
sync_blockheight(bitcoind, [l1, l2, l3])
|
||||
|
||||
wait_for(lambda: len(l1.rpc.listchannels(source=l3.info['id'])['channels']) == 2)
|
||||
|
||||
|
@ -4084,6 +4086,7 @@ def test_multichan(node_factory, executor, bitcoind):
|
|||
|
||||
l2.rpc.close(scid23b)
|
||||
bitcoind.generate_block(1, wait_for_mempool=1)
|
||||
sync_blockheight(bitcoind, [l1, l2, l3])
|
||||
|
||||
# Gossip works as expected.
|
||||
wait_for(lambda: len(l1.rpc.listchannels(source=l3.info['id'])['channels']) == 1)
|
||||
|
@ -4091,9 +4094,9 @@ def test_multichan(node_factory, executor, bitcoind):
|
|||
|
||||
# We can actually pay by *closed* scid (at least until it's completely forgotten)
|
||||
route[1]['channel'] = scid23a
|
||||
inv = l3.rpc.invoice(100000000, "invoice3", "invoice3")
|
||||
l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret'])
|
||||
l1.rpc.waitsendpay(inv['payment_hash'])
|
||||
inv3 = l3.rpc.invoice(100000000, "invoice3", "invoice3")
|
||||
l1.rpc.sendpay(route, inv3['payment_hash'], payment_secret=inv3['payment_secret'])
|
||||
l1.rpc.waitsendpay(inv3['payment_hash'])
|
||||
|
||||
# Restart with multiple channels works.
|
||||
l3.restart()
|
||||
|
@ -4103,8 +4106,48 @@ def test_multichan(node_factory, executor, bitcoind):
|
|||
except RpcError:
|
||||
wait_for(lambda: only_one(l3.rpc.listpeers(l2.info['id'])['peers'])['connected'])
|
||||
|
||||
inv = l3.rpc.invoice(100000000, "invoice4", "invoice4")
|
||||
l1.rpc.pay(inv['bolt11'])
|
||||
inv4 = l3.rpc.invoice(100000000, "invoice4", "invoice4")
|
||||
l1.rpc.pay(inv4['bolt11'])
|
||||
|
||||
# A good place to test listhtlcs!
|
||||
wait_for(lambda: all([h['state'] == 'RCVD_REMOVE_ACK_REVOCATION' for h in l1.rpc.listhtlcs()['htlcs']]))
|
||||
|
||||
l1htlcs = l1.rpc.listhtlcs()['htlcs']
|
||||
assert l1htlcs == l1.rpc.listhtlcs(scid12)['htlcs']
|
||||
assert l1htlcs == [{"short_channel_id": scid12,
|
||||
"id": 0,
|
||||
"expiry": 117,
|
||||
"direction": "out",
|
||||
"amount_msat": Millisatoshi(100001001),
|
||||
"payment_hash": inv1['payment_hash'],
|
||||
"state": "RCVD_REMOVE_ACK_REVOCATION"},
|
||||
{"short_channel_id": scid12,
|
||||
"id": 1,
|
||||
"expiry": 117,
|
||||
"direction": "out",
|
||||
"amount_msat": Millisatoshi(100001001),
|
||||
"payment_hash": inv2['payment_hash'],
|
||||
"state": "RCVD_REMOVE_ACK_REVOCATION"},
|
||||
{"short_channel_id": scid12,
|
||||
"id": 2,
|
||||
"expiry": 123,
|
||||
"direction": "out",
|
||||
"amount_msat": Millisatoshi(100001001),
|
||||
"payment_hash": inv3['payment_hash'],
|
||||
"state": "RCVD_REMOVE_ACK_REVOCATION"},
|
||||
{"short_channel_id": scid12,
|
||||
"id": 3,
|
||||
"expiry": 123,
|
||||
"direction": "out",
|
||||
"amount_msat": Millisatoshi(100001001),
|
||||
"payment_hash": inv4['payment_hash'],
|
||||
"state": "RCVD_REMOVE_ACK_REVOCATION"}]
|
||||
|
||||
# Reverse direction, should match l2's view of channel.
|
||||
for h in l1htlcs:
|
||||
h['direction'] = 'in'
|
||||
h['state'] = 'SENT_REMOVE_ACK_REVOCATION'
|
||||
assert l2.rpc.listhtlcs(scid12)['htlcs'] == l1htlcs
|
||||
|
||||
|
||||
@pytest.mark.developer("dev-no-reconnect required")
|
||||
|
|
|
@ -2,7 +2,7 @@ from bitcoin.rpc import RawProxy
|
|||
from decimal import Decimal
|
||||
from fixtures import * # noqa: F401,F403
|
||||
from fixtures import LightningNode, TEST_NETWORK
|
||||
from pyln.client import RpcError
|
||||
from pyln.client import RpcError, Millisatoshi
|
||||
from threading import Event
|
||||
from pyln.testing.utils import (
|
||||
DEVELOPER, TIMEOUT, VALGRIND, DEPRECATED_APIS, sync_blockheight, only_one,
|
||||
|
@ -2379,15 +2379,15 @@ def test_listfunds(node_factory):
|
|||
assert open_txid in txids
|
||||
|
||||
|
||||
def test_listforwards(node_factory, bitcoind):
|
||||
"""Test listfunds command."""
|
||||
def test_listforwards_and_listhtlcs(node_factory, bitcoind):
|
||||
"""Test listforwards and listhtlcs commands."""
|
||||
l1, l2, l3, l4 = node_factory.get_nodes(4, opts=[{}, {}, {}, {}])
|
||||
|
||||
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
||||
l2.rpc.connect(l3.info['id'], 'localhost', l3.port)
|
||||
l2.rpc.connect(l4.info['id'], 'localhost', l4.port)
|
||||
|
||||
c12, _ = l1.fundchannel(l2, 10**5)
|
||||
c12, c12res = l1.fundchannel(l2, 10**5)
|
||||
c23, _ = l2.fundchannel(l3, 10**5)
|
||||
c24, _ = l2.fundchannel(l4, 10**5)
|
||||
|
||||
|
@ -2406,7 +2406,7 @@ def test_listforwards(node_factory, bitcoind):
|
|||
failed_inv = l3.rpc.invoice(4000, 'failed', 'desc')
|
||||
failed_route = l1.rpc.getroute(l3.info['id'], 4000, 1)['route']
|
||||
|
||||
l2.rpc.close(c23, 1)
|
||||
l2.rpc.close(c23)
|
||||
|
||||
with pytest.raises(RpcError):
|
||||
l1.rpc.sendpay(failed_route, failed_inv['payment_hash'], payment_secret=failed_inv['payment_secret'])
|
||||
|
@ -2447,6 +2447,52 @@ def test_listforwards(node_factory, bitcoind):
|
|||
c24_forwards = l2.rpc.listforwards(out_channel=c24)['forwards']
|
||||
assert len(c24_forwards) == 1
|
||||
|
||||
# listhtlcs on l1 is the same with or without id specifiers
|
||||
c1htlcs = l1.rpc.listhtlcs()['htlcs']
|
||||
assert l1.rpc.listhtlcs(c12)['htlcs'] == c1htlcs
|
||||
assert l1.rpc.listhtlcs(c12res['channel_id'])['htlcs'] == c1htlcs
|
||||
c1htlcs.sort(key=lambda h: h['id'])
|
||||
assert [h['id'] for h in c1htlcs] == [0, 1, 2]
|
||||
assert [h['short_channel_id'] for h in c1htlcs] == [c12] * 3
|
||||
assert [h['amount_msat'] for h in c1htlcs] == [Millisatoshi(1001),
|
||||
Millisatoshi(2001),
|
||||
Millisatoshi(4001)]
|
||||
assert [h['direction'] for h in c1htlcs] == ['out'] * 3
|
||||
assert [h['state'] for h in c1htlcs] == ['RCVD_REMOVE_ACK_REVOCATION'] * 3
|
||||
|
||||
# These should be a mirror!
|
||||
c2c1htlcs = l2.rpc.listhtlcs(c12)['htlcs']
|
||||
for h in c2c1htlcs:
|
||||
assert h['state'] == 'SENT_REMOVE_ACK_REVOCATION'
|
||||
assert h['direction'] == 'in'
|
||||
h['state'] = 'RCVD_REMOVE_ACK_REVOCATION'
|
||||
h['direction'] = 'out'
|
||||
assert c2c1htlcs == c1htlcs
|
||||
|
||||
# One channel at a time should result in all htlcs.
|
||||
allhtlcs = l2.rpc.listhtlcs()['htlcs']
|
||||
parthtlcs = (l2.rpc.listhtlcs(c12)['htlcs']
|
||||
+ l2.rpc.listhtlcs(c23)['htlcs']
|
||||
+ l2.rpc.listhtlcs(c24)['htlcs'])
|
||||
assert len(allhtlcs) == len(parthtlcs)
|
||||
for h in allhtlcs:
|
||||
assert h in parthtlcs
|
||||
|
||||
# Now, close and forget.
|
||||
l2.rpc.close(c24)
|
||||
l2.rpc.close(c12)
|
||||
|
||||
bitcoind.generate_block(100, wait_for_mempool=3)
|
||||
|
||||
# Once channels are gone, htlcs are gone.
|
||||
for n in (l1, l2, l3, l4):
|
||||
# They might reconnect, but still will have no channels
|
||||
wait_for(lambda: all(p['channels'] == [] for p in n.rpc.listpeers()['peers']))
|
||||
assert n.rpc.listhtlcs() == {'htlcs': []}
|
||||
|
||||
# But forwards are not forgotten!
|
||||
assert l2.rpc.listforwards()['forwards'] == all_forwards
|
||||
|
||||
|
||||
@pytest.mark.openchannel('v1')
|
||||
def test_version_reexec(node_factory, bitcoind):
|
||||
|
|
|
@ -5146,3 +5146,96 @@ struct db_stmt *wallet_datastore_next(const tal_t *ctx,
|
|||
|
||||
return stmt;
|
||||
}
|
||||
|
||||
/* We use a different query form if we only care about a single channel. */
|
||||
struct wallet_htlc_iter {
|
||||
struct db_stmt *stmt;
|
||||
/* Non-zero if they specified it */
|
||||
struct short_channel_id scid;
|
||||
};
|
||||
|
||||
struct wallet_htlc_iter *wallet_htlcs_first(const tal_t *ctx,
|
||||
struct wallet *w,
|
||||
const struct channel *chan,
|
||||
struct short_channel_id *scid,
|
||||
u64 *htlc_id,
|
||||
int *cltv_expiry,
|
||||
enum side *owner,
|
||||
struct amount_msat *msat,
|
||||
struct sha256 *payment_hash,
|
||||
enum htlc_state *hstate)
|
||||
{
|
||||
struct wallet_htlc_iter *i = tal(ctx, struct wallet_htlc_iter);
|
||||
|
||||
if (chan) {
|
||||
i->scid = *channel_scid_or_local_alias(chan);
|
||||
assert(i->scid.u64 != 0);
|
||||
assert(chan->dbid != 0);
|
||||
|
||||
i->stmt = db_prepare_v2(w->db,
|
||||
SQL("SELECT h.channel_htlc_id"
|
||||
", h.cltv_expiry"
|
||||
", h.direction"
|
||||
", h.msatoshi"
|
||||
", h.payment_hash"
|
||||
", h.hstate"
|
||||
" FROM channel_htlcs h"
|
||||
" WHERE channel_id = ?"));
|
||||
db_bind_u64(i->stmt, 0, chan->dbid);
|
||||
} else {
|
||||
i->scid.u64 = 0;
|
||||
i->stmt = db_prepare_v2(w->db,
|
||||
SQL("SELECT channels.scid"
|
||||
", channels.alias_local"
|
||||
", h.channel_htlc_id"
|
||||
", h.cltv_expiry"
|
||||
", h.direction"
|
||||
", h.msatoshi"
|
||||
", h.payment_hash"
|
||||
", h.hstate"
|
||||
" FROM channel_htlcs h"
|
||||
" JOIN channels ON channels.id = h.channel_id"));
|
||||
}
|
||||
/* FIXME: db_prepare should take ctx! */
|
||||
tal_steal(i, i->stmt);
|
||||
db_query_prepared(i->stmt);
|
||||
|
||||
return wallet_htlcs_next(w, i,
|
||||
scid, htlc_id, cltv_expiry, owner, msat,
|
||||
payment_hash, hstate);
|
||||
}
|
||||
|
||||
struct wallet_htlc_iter *wallet_htlcs_next(struct wallet *w,
|
||||
struct wallet_htlc_iter *iter,
|
||||
struct short_channel_id *scid,
|
||||
u64 *htlc_id,
|
||||
int *cltv_expiry,
|
||||
enum side *owner,
|
||||
struct amount_msat *msat,
|
||||
struct sha256 *payment_hash,
|
||||
enum htlc_state *hstate)
|
||||
{
|
||||
if (!db_step(iter->stmt))
|
||||
return tal_free(iter);
|
||||
|
||||
if (iter->scid.u64 != 0)
|
||||
*scid = iter->scid;
|
||||
else {
|
||||
if (db_col_is_null(iter->stmt, "channels.scid"))
|
||||
db_col_scid(iter->stmt, "channels.alias_local", scid);
|
||||
else {
|
||||
db_col_scid(iter->stmt, "channels.scid", scid);
|
||||
db_col_ignore(iter->stmt, "channels.alias_local");
|
||||
}
|
||||
}
|
||||
*htlc_id = db_col_u64(iter->stmt, "h.channel_htlc_id");
|
||||
if (db_col_int(iter->stmt, "h.direction") == DIRECTION_INCOMING)
|
||||
*owner = REMOTE;
|
||||
else
|
||||
*owner = LOCAL;
|
||||
db_col_amount_msat(iter->stmt, "h.msatoshi", msat);
|
||||
db_col_sha256(iter->stmt, "h.payment_hash", payment_hash);
|
||||
*cltv_expiry = db_col_int(iter->stmt, "h.cltv_expiry");
|
||||
*hstate = db_col_int(iter->stmt, "h.hstate");
|
||||
return iter;
|
||||
}
|
||||
|
|
|
@ -1632,4 +1632,40 @@ struct db_stmt *wallet_datastore_next(const tal_t *ctx,
|
|||
const u8 **data,
|
||||
u64 *generation);
|
||||
|
||||
/**
|
||||
* Iterate through the htlcs table.
|
||||
* @w: the wallet
|
||||
* @chan: optional channel to filter by
|
||||
*
|
||||
* Returns pointer to hand as @iter to wallet_htlcs_next(), or NULL.
|
||||
* If you choose not to call wallet_htlcs_next() you must free it!
|
||||
*/
|
||||
struct wallet_htlc_iter *wallet_htlcs_first(const tal_t *ctx,
|
||||
struct wallet *w,
|
||||
const struct channel *chan,
|
||||
struct short_channel_id *scid,
|
||||
u64 *htlc_id,
|
||||
int *cltv_expiry,
|
||||
enum side *owner,
|
||||
struct amount_msat *msat,
|
||||
struct sha256 *payment_hash,
|
||||
enum htlc_state *hstate);
|
||||
|
||||
/**
|
||||
* Iterate through the htlcs table.
|
||||
* @w: the wallet
|
||||
* @iter: the previous iter.
|
||||
*
|
||||
* Returns pointer to hand as @iter to wallet_htlcs_next(), or NULL.
|
||||
* If you choose not to call wallet_htlcs_next() you must free it!
|
||||
*/
|
||||
struct wallet_htlc_iter *wallet_htlcs_next(struct wallet *w,
|
||||
struct wallet_htlc_iter *iter,
|
||||
struct short_channel_id *scid,
|
||||
u64 *htlc_id,
|
||||
int *cltv_expiry,
|
||||
enum side *owner,
|
||||
struct amount_msat *msat,
|
||||
struct sha256 *payment_hash,
|
||||
enum htlc_state *hstate);
|
||||
#endif /* LIGHTNING_WALLET_WALLET_H */
|
||||
|
|
Loading…
Add table
Reference in a new issue