Skip to content

Commit 2fe4879

Browse files
committed
lightningd: implement listnetworkevents.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
1 parent 72ca305 commit 2fe4879

File tree

6 files changed

+407
-0
lines changed

6 files changed

+407
-0
lines changed

contrib/msggen/msggen/schema.json

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23476,6 +23476,122 @@
2347623476
}
2347723477
]
2347823478
},
23479+
"listnetworkevents.json": {
23480+
"$schema": "../rpc-schema-draft.json",
23481+
"type": "object",
23482+
"rpc": "listnetworkevents",
23483+
"title": "Command for querying network events",
23484+
"added": "v25.12",
23485+
"description": [
23486+
"The **listnetworkevents** RPC command retrieves the log of connections, disconnections and pings on the network. This can be analyzed to evaluate node reliability and latency."
23487+
],
23488+
"categories": [
23489+
"readonly"
23490+
],
23491+
"request": {
23492+
"required": [],
23493+
"additionalProperties": false,
23494+
"properties": {
23495+
"id": {
23496+
"type": "string",
23497+
"description": [
23498+
"A node id: if set, only network events for this peer are returned"
23499+
]
23500+
},
23501+
"index": {
23502+
"type": "string",
23503+
"enum": [
23504+
"created"
23505+
],
23506+
"description": [
23507+
"This controls the ordering of results."
23508+
],
23509+
"default": "`created`"
23510+
},
23511+
"start": {
23512+
"type": "u64",
23513+
"description": [
23514+
"If `index` is specified, `start` may be specified to start from that value, which is generally returned from lightning-wait(7)."
23515+
]
23516+
},
23517+
"limit": {
23518+
"type": "u32",
23519+
"description": [
23520+
"If `index` is specified, `limit` can be used to specify the maximum number of entries to return."
23521+
]
23522+
}
23523+
}
23524+
},
23525+
"response": {
23526+
"required": [
23527+
"networkevents"
23528+
],
23529+
"additionalProperties": false,
23530+
"properties": {
23531+
"networkevents": {
23532+
"type": "array",
23533+
"items": {
23534+
"type": "object",
23535+
"additionalProperties": false,
23536+
"required": [
23537+
"created_index",
23538+
"timestamp",
23539+
"peer_id",
23540+
"type"
23541+
],
23542+
"properties": {
23543+
"created_index": {
23544+
"type": "u64",
23545+
"description": [
23546+
"1-based index indicating order this network event was created in."
23547+
]
23548+
},
23549+
"timestamp": {
23550+
"type": "u64",
23551+
"description": [
23552+
"Time this event was recorded, in seconds since January 1 1970 UTC"
23553+
]
23554+
},
23555+
"peer_id": {
23556+
"type": "pubkey",
23557+
"description": [
23558+
"The node the network connection was talking to."
23559+
]
23560+
},
23561+
"type": {
23562+
"type": "string",
23563+
"description": [
23564+
"The type of event (currently `ping`, `connect`, `connect_fail` or `disconnect`)"
23565+
]
23566+
},
23567+
"reason": {
23568+
"type": "string",
23569+
"description": [
23570+
"The cause of the event (if known)"
23571+
]
23572+
},
23573+
"duration_nsec": {
23574+
"type": "u64",
23575+
"description": [
23576+
"The time taken (for ping, the latency, for connect / connect_fail, the time taken to get a result, for disconnect, the time we were connected)"
23577+
]
23578+
}
23579+
}
23580+
}
23581+
}
23582+
}
23583+
},
23584+
"author": [
23585+
"Rusty Russell <<rusty@rustcorp.com.au>> is mainly responsible."
23586+
],
23587+
"see_also": [
23588+
"lightning-autoclean(7)",
23589+
"lightning-listpeers(7)"
23590+
],
23591+
"resources": [
23592+
"Main web site: <https://github.com/ElementsProject/lightning>"
23593+
]
23594+
},
2347923595
"listnodes.json": {
2348023596
"$schema": "../rpc-schema-draft.json",
2348123597
"type": "object",

doc/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ MARKDOWNPAGES := doc/addgossip.7 \
8888
doc/listhtlcs.7 \
8989
doc/listinvoicerequests.7 \
9090
doc/listinvoices.7 \
91+
doc/listnetworkevents.7 \
9192
doc/listnodes.7 \
9293
doc/listoffers.7 \
9394
doc/listpays.7 \

doc/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ Core Lightning Documentation
102102
listhtlcs <listhtlcs.7.md>
103103
listinvoicerequests <listinvoicerequests.7.md>
104104
listinvoices <listinvoices.7.md>
105+
listnetworkevents <listnetworkevents.7.md>
105106
listnodes <listnodes.7.md>
106107
listoffers <listoffers.7.md>
107108
listpays <listpays.7.md>

doc/schemas/listnetworkevents.json

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
{
2+
"$schema": "../rpc-schema-draft.json",
3+
"type": "object",
4+
"rpc": "listnetworkevents",
5+
"title": "Command for querying network events",
6+
"added": "v25.12",
7+
"description": [
8+
"The **listnetworkevents** RPC command retrieves the log of connections, disconnections and pings on the network. This can be analyzed to evaluate node reliability and latency."
9+
],
10+
"categories": [
11+
"readonly"
12+
],
13+
"request": {
14+
"required": [],
15+
"additionalProperties": false,
16+
"properties": {
17+
"id": {
18+
"type": "string",
19+
"description": [
20+
"A node id: if set, only network events for this peer are returned"
21+
]
22+
},
23+
"index": {
24+
"type": "string",
25+
"enum": [
26+
"created"
27+
],
28+
"description": [
29+
"This controls the ordering of results."
30+
],
31+
"default": "`created`"
32+
},
33+
"start": {
34+
"type": "u64",
35+
"description": [
36+
"If `index` is specified, `start` may be specified to start from that value, which is generally returned from lightning-wait(7)."
37+
]
38+
},
39+
"limit": {
40+
"type": "u32",
41+
"description": [
42+
"If `index` is specified, `limit` can be used to specify the maximum number of entries to return."
43+
]
44+
}
45+
}
46+
},
47+
"response": {
48+
"required": [
49+
"networkevents"
50+
],
51+
"additionalProperties": false,
52+
"properties": {
53+
"networkevents": {
54+
"type": "array",
55+
"items": {
56+
"type": "object",
57+
"additionalProperties": false,
58+
"required": [
59+
"created_index",
60+
"timestamp",
61+
"peer_id",
62+
"type"
63+
],
64+
"properties": {
65+
"created_index": {
66+
"type": "u64",
67+
"description": [
68+
"1-based index indicating order this network event was created in."
69+
]
70+
},
71+
"timestamp": {
72+
"type": "u64",
73+
"description": [
74+
"Time this event was recorded, in seconds since January 1 1970 UTC"
75+
]
76+
},
77+
"peer_id": {
78+
"type": "pubkey",
79+
"description": [
80+
"The node the network connection was talking to."
81+
]
82+
},
83+
"type": {
84+
"type": "string",
85+
"description": [
86+
"The type of event (currently `ping`, `connect`, `connect_fail` or `disconnect`)"
87+
]
88+
},
89+
"reason": {
90+
"type": "string",
91+
"description": [
92+
"The cause of the event (if known)"
93+
]
94+
},
95+
"duration_nsec": {
96+
"type": "u64",
97+
"description": [
98+
"The time taken (for ping, the latency, for connect / connect_fail, the time taken to get a result, for disconnect, the time we were connected)"
99+
]
100+
}
101+
}
102+
}
103+
}
104+
}
105+
},
106+
"author": [
107+
"Rusty Russell <<rusty@rustcorp.com.au>> is mainly responsible."
108+
],
109+
"see_also": [
110+
"lightning-autoclean(7)",
111+
"lightning-listpeers(7)"
112+
],
113+
"resources": [
114+
"Main web site: <https://github.com/ElementsProject/lightning>"
115+
]
116+
}

tests/test_connection.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4882,3 +4882,117 @@ def test_listpeerchannels_by_scid(node_factory):
48824882

48834883
with pytest.raises(RpcError, match="Cannot specify both short_channel_id and id"):
48844884
l2.rpc.listpeerchannels(peer_id=l1.info['id'], short_channel_id='1x2x3')
4885+
4886+
4887+
def test_networkevents(node_factory, executor):
4888+
l1, l2 = node_factory.get_nodes(2, opts={'log-level': 'io'})
4889+
4890+
before = time.time()
4891+
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
4892+
after = time.time()
4893+
4894+
nevents = l1.rpc.listnetworkevents()['networkevents']
4895+
# Filtering by id gives same results
4896+
assert nevents == l1.rpc.listnetworkevents(l2.info['id'])['networkevents']
4897+
assert nevents == l1.rpc.listnetworkevents(id=l2.info['id'])['networkevents']
4898+
4899+
# Another id gives no events
4900+
assert l1.rpc.listnetworkevents(l1.info['id']) == {'networkevents': []}
4901+
4902+
# These can only be estimated, will vary each time
4903+
assert int(before) <= only_one(nevents)['timestamp'] <= after
4904+
del only_one(nevents)['timestamp']
4905+
4906+
assert only_one(nevents)['duration_nsec'] <= int((after - before) * 1_000_000_000)
4907+
del only_one(nevents)['duration_nsec']
4908+
4909+
assert only_one(nevents)['reason'].startswith('connect command "')
4910+
del only_one(nevents)['reason']
4911+
4912+
assert nevents == [{'created_index': 1,
4913+
'peer_id': l2.info['id'],
4914+
'type': 'connect'}]
4915+
4916+
# We will eventually get a ping event.
4917+
res = l1.rpc.wait('networkevents', 'created', 2)
4918+
assert res == {'subsystem': 'networkevents',
4919+
'created': 2,
4920+
'networkevents': {'created_index': 2,
4921+
'type': 'ping',
4922+
'peer_id': l2.info['id']}}
4923+
4924+
nevents = l1.rpc.listnetworkevents(start=2)['networkevents']
4925+
del only_one(nevents)['duration_nsec']
4926+
del only_one(nevents)['timestamp']
4927+
4928+
assert nevents == [{'created_index': 2,
4929+
'peer_id': l2.info['id'],
4930+
'type': 'ping'}]
4931+
4932+
# Finally, disconnect event.
4933+
fut = executor.submit(l1.rpc.wait, 'networkevents', 'created', 3)
4934+
time.sleep(1)
4935+
l2.rpc.disconnect(l1.info['id'])
4936+
after = time.time()
4937+
4938+
res = fut.result(TIMEOUT)
4939+
assert res == {'subsystem': 'networkevents',
4940+
'created': 3,
4941+
'networkevents': {'created_index': 3,
4942+
'type': 'disconnect',
4943+
'peer_id': l2.info['id']}}
4944+
4945+
nevents = l1.rpc.listnetworkevents(None, 'created', 3)['networkevents']
4946+
del only_one(nevents)['timestamp']
4947+
assert only_one(nevents)['duration_nsec'] < (after - before) * 1_000_000_000
4948+
del only_one(nevents)['duration_nsec']
4949+
4950+
assert nevents == [{'created_index': 3,
4951+
'peer_id': l2.info['id'],
4952+
'type': 'disconnect'}]
4953+
4954+
# Failed connects
4955+
with pytest.raises(RpcError, match="Cryptographic handshake: peer closed connection"):
4956+
l1.rpc.connect(l2.info['id'], 'localhost', l1.port)
4957+
nevents = l1.rpc.listnetworkevents(start=4)['networkevents']
4958+
del only_one(nevents)['timestamp']
4959+
del only_one(nevents)['duration_nsec']
4960+
4961+
assert nevents == [{'created_index': 4,
4962+
'peer_id': l2.info['id'],
4963+
'type': 'connect_fail',
4964+
'reason': f'All addresses failed: 127.0.0.1:{l1.port}: Cryptographic handshake: peer closed connection (wrong key?). '}]
4965+
4966+
# Connect failed because no listener
4967+
with pytest.raises(RpcError, match="Connection establishment: Connection refused."):
4968+
l1.rpc.connect(l2.info['id'], 'localhost', 1)
4969+
nevents = l1.rpc.listnetworkevents(start=5)['networkevents']
4970+
del only_one(nevents)['timestamp']
4971+
del only_one(nevents)['duration_nsec']
4972+
4973+
assert nevents == [{'created_index': 5,
4974+
'peer_id': l2.info['id'],
4975+
'type': 'connect_fail',
4976+
'reason': f'All addresses failed: 127.0.0.1:1: Connection establishment: Connection refused. '}]
4977+
4978+
# Connect failed because unreachable
4979+
with pytest.raises(RpcError, match="Connection establishment: Connection timed out."):
4980+
l1.rpc.connect(l2.info['id'], '1.1.1.1', 8081)
4981+
nevents = l1.rpc.listnetworkevents(start=6)['networkevents']
4982+
del only_one(nevents)['timestamp']
4983+
del only_one(nevents)['duration_nsec']
4984+
4985+
assert nevents == [{'created_index': 6,
4986+
'peer_id': l2.info['id'],
4987+
'type': 'connect_fail',
4988+
'reason': f'All addresses failed: 1.1.1.1:8081: Connection establishment: Connection timed out. '}]
4989+
4990+
# Connect in
4991+
l2.rpc.connect(l1.info['id'], 'localhost', l1.port)
4992+
nevents = l1.rpc.listnetworkevents(start=7)['networkevents']
4993+
del only_one(nevents)['timestamp']
4994+
4995+
# No duration, no reason.
4996+
assert nevents == [{'created_index': 7,
4997+
'peer_id': l2.info['id'],
4998+
'type': 'connect'}]

0 commit comments

Comments
 (0)