Skip to content

Commit be1f8c3

Browse files
authored
Merge branch 'master' into fix-flake-test_no_delay-9218
2 parents 45c2aa1 + 1e4fdd2 commit be1f8c3

34 files changed

Lines changed: 810 additions & 734 deletions

.github/workflows/repro.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ jobs:
9292
uses: dawidd6/action-send-mail@v17
9393
with:
9494
server_address: smtp.gmail.com
95-
server_port: 587
95+
server_port: 465
9696
secure: true
9797
username: ${{ secrets.EMAIL_USERNAME }}
9898
password: ${{ secrets.EMAIL_PASSWORD }}

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

DEV.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
- Build with `--configure --enable-debugbuild` and `uv run make`. Much faster with `uv run make RUST=0`.
2+
- Env vars for tests: `RUST=0` (if RUST plugins were not built), `VALGRIND=0` (much faster), `TIMEOUT=10` to speed failures (or 100 if VALGRIND=1).
3+
- *gen.[ch] files are generated by the Makefile.
4+
- ccan/ is imported from ccan repo: PRs should go there, then `make update-ccan`.
5+
- Insert BOLT quotes (current version is checked out in .tmp.lightningrfc/) in source when dealing with protocol work, for `make check-source-bolt`. This ensures we keep up with spec changes. `...` means skip some, but not over a section boundary. `...` at the start means "continues directly from previous quote".
6+
- `make check-source` does formatting checks.
7+
- assert-based unit tests can "#include ../file.c", OR add objects to Makefile targets: `make update-mocks` will regenerate mocks for functions from link errors.
8+
- Commits should be reviewable, bisectable, and include tests. Pattern: one commit adds a python test with @pytest.mark.xfail(strict=True), next commit fixes the problem and removes that line.
9+
- Commits which fix crashes or bug MUST quote the bug for later searches: do not rely on being able to find the bug report in future!
10+
- Commits which create signficant user (not developer!) visible changes should have Changelog-(Added|Deprecated|Changed|Fixed|EXPERIMENTAL) for assembling CHANGELOG.md at release time.
11+
- Deprecations must use the deprecation infrastructure, and append to the table in doc/developers-guide/deprecated-features.md
12+
- Adding a new JSON-RPC command requires the most careful design: start with doc/schemas/NAME.json and add doc/NAME.json to MARKDOWNPAGES in doc/Makefile. `make doc-all` will regen the rest.
13+
- In pytest integration tests, name nodes l1, l2, etc in creation order. This matches the log prefixes they use (`lightningd-1` etc) and on test failure the logs and other ephemera will in /tmp/ltests-*/TESTNAME*/lightning-1/.
14+
- In pytest, never assert that a command raises an exception without specifying *exactly what* it raises! Use `with pytest.raises(RpcError, match='xxx'):`
15+
- tal_bytelen / tal_count of NULL are defined to be zero, and you may assume this.

contrib/pyln-testing/pyln/testing/fixtures.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -193,13 +193,7 @@ def bitcoind(request, directory, teardown_checks):
193193

194194
yield bitcoind
195195

196-
try:
197-
bitcoind.stop()
198-
except Exception:
199-
bitcoind.proc.kill()
200-
bitcoind.proc.wait()
201-
202-
bitcoind.cleanup_files()
196+
bitcoind.kill()
203197

204198

205199
class TeardownErrors(object):
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/usr/bin/env python3
2+
"""Generic inline plugin shim: bridges lightningd stdio <-> inline-plugin.sock in cwd.
3+
Used by inline_plugin() in pyln/testing/utils.py."""
4+
import os
5+
import socket
6+
import sys
7+
import threading
8+
9+
10+
def _stdin_to_sock(conn):
11+
while chunk := sys.stdin.buffer.read1(4096):
12+
conn.sendall(chunk)
13+
# Stdin closed means lightningd is done with us: exit immediately so the
14+
# OS closes the socket and the serve thread can accept the next connection.
15+
os._exit(0)
16+
17+
18+
conn = socket.socket(socket.AF_UNIX)
19+
conn.connect('inline-plugin.sock')
20+
21+
threading.Thread(target=_stdin_to_sock, args=(conn,), daemon=True).start()
22+
23+
while chunk := conn.recv(4096):
24+
sys.stdout.buffer.write(chunk)
25+
sys.stdout.buffer.flush()

contrib/pyln-testing/pyln/testing/utils.py

Lines changed: 123 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
from pyln.client import LightningRpc
1212
from pyln.client import Millisatoshi
1313
from pyln.client import NodeVersion
14+
from pyln.client import Plugin
1415

1516
import ephemeral_port_reserve # type: ignore
17+
import tempfile
1618
import json
1719
import logging
1820
import lzma
@@ -22,13 +24,15 @@
2224
import random
2325
import re
2426
import shutil
27+
import socket
2528
import sqlite3
2629
import string
2730
import struct
2831
import subprocess
2932
import sys
3033
import threading
3134
import time
35+
import types
3236
import warnings
3337

3438
BITCOIND_CONFIG = {
@@ -78,6 +82,8 @@ def env(name, default=None):
7882
VALGRIND = env("VALGRIND") == "1"
7983
TEST_NETWORK = env("TEST_NETWORK", 'regtest')
8084
TEST_DEBUG = env("TEST_DEBUG", "0") == "1"
85+
86+
INLINE_PLUGIN_PATH = os.path.join(os.path.dirname(__file__), 'inline-plugin.py')
8187
SLOW_MACHINE = env("SLOW_MACHINE", "0") == "1"
8288
DEPRECATED_APIS = env("DEPRECATED_APIS", "0") == "1"
8389
TIMEOUT = int(env("TIMEOUT", 180 if SLOW_MACHINE else 60))
@@ -166,25 +172,46 @@ def get_tx_p2wsh_outnum(bitcoind, tx, amount):
166172
return None
167173

168174

169-
unused_port_lock = threading.Lock()
170-
unused_port_set = set()
175+
_PORT_LOCK_DIR = Path(tempfile.gettempdir()) / "pyln-testing-ports"
176+
_PORT_LOCK_DIR.mkdir(exist_ok=True)
171177

172178

173179
def reserve_unused_port():
174180
"""Get an unused port: avoids handing out the same port unless it's been
175181
returned"""
176-
with unused_port_lock:
177-
while True:
178-
port = ephemeral_port_reserve.reserve()
179-
if port not in unused_port_set:
180-
break
181-
unused_port_set.add(port)
182+
while True:
183+
port = ephemeral_port_reserve.reserve()
182184

183-
return port
185+
lock_path = _PORT_LOCK_DIR / f"{port}.lock"
186+
try:
187+
fd = os.open(str(lock_path), os.O_CREAT | os.O_EXCL | os.O_WRONLY)
188+
os.write(fd, str(os.getpid()).encode())
189+
os.close(fd)
190+
return port
191+
except FileExistsError:
192+
continue
184193

185194

186195
def drop_unused_port(port):
187-
unused_port_set.remove(port)
196+
if port:
197+
lock_path = _PORT_LOCK_DIR / f"{port}.lock"
198+
lock_path.unlink(missing_ok=True)
199+
200+
201+
def cleanup_stale_port_locks():
202+
"""Remove lockfiles whose owning process no longer exists."""
203+
try:
204+
for lock_path in _PORT_LOCK_DIR.glob("*.lock"):
205+
try:
206+
pid = int(lock_path.read_text())
207+
try:
208+
os.kill(pid, 0) # signal 0 = existence check, no actual signal
209+
except ProcessLookupError:
210+
lock_path.unlink(missing_ok=True)
211+
except (ValueError, PermissionError, FileNotFoundError):
212+
pass
213+
except Exception:
214+
pass # best-effort, never crash the test run over cleanup
188215

189216

190217
class TailableProc(object):
@@ -285,6 +312,8 @@ def kill(self):
285312

286313
def cleanup_files(self):
287314
"""Ensure files are closed."""
315+
cleanup_stale_port_locks()
316+
288317
for f in ["stdout_write", "stderr_write", "stdout_read", "stderr_read"]:
289318
try:
290319
getattr(self, f).close()
@@ -454,10 +483,7 @@ def __init__(self, bitcoin_dir="/tmp/bitcoind-test", rpcport=None):
454483
TailableProc.__init__(self, bitcoin_dir, verbose=False)
455484

456485
if rpcport is None:
457-
self.reserved_rpcport = reserve_unused_port()
458-
rpcport = self.reserved_rpcport
459-
else:
460-
self.reserved_rpcport = None
486+
rpcport = reserve_unused_port()
461487

462488
self.bitcoin_dir = bitcoin_dir
463489
self.rpcport = rpcport
@@ -494,9 +520,15 @@ def __init__(self, bitcoin_dir="/tmp/bitcoind-test", rpcport=None):
494520
self.rpc = SimpleBitcoinProxy(btc_conf_file=self.conf_file)
495521
self.proxies = []
496522

497-
def __del__(self):
498-
if self.reserved_rpcport is not None:
499-
drop_unused_port(self.reserved_rpcport)
523+
def kill(self):
524+
try:
525+
self.stop()
526+
except Exception:
527+
self.proc.kill()
528+
self.proc.wait()
529+
530+
self.cleanup_files()
531+
drop_unused_port(self.rpcport)
500532

501533
def start(self, wallet_file=None):
502534
TailableProc.start(self)
@@ -1069,6 +1101,11 @@ def grpc(self):
10691101
creds,
10701102
options=(('grpc.ssl_target_name_override', 'cln'),)
10711103
)
1104+
1105+
# Force the connect+handshake to finish now, instead of lazily on
1106+
# the first RPC the caller happens to make.
1107+
grpc.channel_ready_future(channel).result(timeout=10)
1108+
10721109
from pyln import grpc as clnpb
10731110
return clnpb.NodeStub(channel)
10741111

@@ -1786,7 +1823,8 @@ def get_nodes(self, num_nodes, opts=None):
17861823
def get_node(self, node_id=None, options=None, dbfile=None,
17871824
bkpr_dbfile=None, feerates=(15000, 11000, 7500, 3750),
17881825
start=True, wait_for_bitcoind_sync=True, may_fail=False,
1789-
expect_fail=False, cleandir=True, gossip_store_file=None, unused_grpc_port=True, **kwargs):
1826+
expect_fail=False, cleandir=True, gossip_store_file=None, unused_grpc_port=True,
1827+
inline_plugin=None, **kwargs):
17901828
node_id = self.get_node_id() if not node_id else node_id
17911829
port = reserve_unused_port()
17921830
grpc_port = self.get_unused_port() if unused_grpc_port else None
@@ -1830,6 +1868,11 @@ def get_node(self, node_id=None, options=None, dbfile=None,
18301868
shutil.copy(gossip_store_file, os.path.join(node.daemon.lightning_dir, TEST_NETWORK,
18311869
'gossip_store'))
18321870

1871+
if inline_plugin is not None:
1872+
if 'plugin' not in node.daemon.opts:
1873+
node.daemon.opts['plugin'] = INLINE_PLUGIN_PATH
1874+
_inline_plugin(node, inline_plugin)
1875+
18331876
if start:
18341877
try:
18351878
node.start(wait_for_bitcoind_sync)
@@ -1944,3 +1987,65 @@ def killall(self, expected_successes):
19441987
drop_unused_port(p)
19451988

19461989
return not unexpected_fail, err_msgs
1990+
1991+
1992+
def _inline_plugin(node, setup_fn):
1993+
"""Set up an inline plugin serve thread for a not-yet-started node.
1994+
1995+
Normally called via get_node(inline_plugin=setup_fn). The plugin's cwd
1996+
(set by lightningd) is node.daemon.lightning_dir/TEST_NETWORK/, which is
1997+
where the shim looks for inline-plugin.sock.
1998+
1999+
Example::
2000+
2001+
def setup(plugin):
2002+
@plugin.method('greet')
2003+
def greet(name, plugin):
2004+
return {'message': f'hello {name}'}
2005+
2006+
l1 = node_factory.get_node(inline_plugin=setup)
2007+
assert l1.rpc.greet('world') == {'message': 'hello world'}
2008+
"""
2009+
sock_path = os.path.join(node.daemon.lightning_dir, TEST_NETWORK, 'inline-plugin.sock')
2010+
srv = socket.socket(socket.AF_UNIX)
2011+
srv.bind(sock_path)
2012+
srv.listen(1)
2013+
2014+
plugin = Plugin(autopatch=False)
2015+
setup_fn(plugin)
2016+
2017+
def serve():
2018+
while True:
2019+
conn, _ = srv.accept()
2020+
2021+
class _SockWriter:
2022+
def write(self, data):
2023+
try:
2024+
conn.sendall(data)
2025+
except OSError:
2026+
pass
2027+
2028+
def flush(self):
2029+
pass
2030+
2031+
writer = _SockWriter()
2032+
plugin.stdout = types.SimpleNamespace(buffer=writer, flush=writer.flush)
2033+
2034+
partial = b""
2035+
while True:
2036+
try:
2037+
chunk = conn.recv(4096)
2038+
except OSError:
2039+
break
2040+
if not chunk:
2041+
break
2042+
partial += chunk
2043+
msgs = partial.split(b'\n\n')
2044+
if len(msgs) < 2:
2045+
continue
2046+
try:
2047+
partial = plugin._multi_dispatch(msgs)
2048+
except Exception:
2049+
break
2050+
2051+
threading.Thread(target=serve, daemon=True).start()

doc/contribute-to-core-lightning/release-checklist.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ Here's a checklist for the release process.
4646

4747
1. Update CHANGELOG.md by changing rc(N-1) to rcN. Update the changelog list with information from newly merged PRs also.
4848
2. Update the package versions: `uv run make update-versions NEW_VERSION=v<VERSION>rcN`
49-
3. Add a PR with the rcN.
49+
3. Add a PR with the rcN, and merge it.
5050
4. Tag it `git pull && git tag -s v<VERSION>rcN && git push origin v<VERSION>rcN`.
5151
5. Pushing the tag automatically starts the "Release 🚀" CI job, creating a draft pre-release and uploading reproducible builds with their `SHA256SUMS` files signed by the project key.
5252
6. Set up the reproducible build environment by running the script `contrib/cl-repro.sh` to generate the necessary builder images.

doc/contribute-to-core-lightning/security-policy.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ We have a 3 month release cycle, and the last two versions are supported.
1212

1313
## Reporting a Vulnerability
1414

15-
To report security vulnerabilities, please send an email to one of the following addresses:
16-
- `rusty@rustcorp.com.au`
15+
To report security vulnerabilities, please send an email to:
1716
- `security@blockstream.com`
1817

1918
Note: These email addresses are exclusively for vulnerability reporting.

plugins/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,4 @@ tracing = { version = "^0.1", features = ["async-await", "log"] }
3535
[dev-dependencies]
3636
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
3737
cln-grpc = { workspace = true }
38+
cln-rpc = { workspace = true }

plugins/askrene/askrene.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,11 @@ static struct command_result *json_getroutes(struct command *cmd,
943943
maxdelay_allowed);
944944
}
945945

946+
if (node_id_eq(source, dest)) {
947+
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
948+
"source and destination must be different");
949+
}
950+
946951
if (command_check_only(cmd))
947952
return command_check_done(cmd);
948953

0 commit comments

Comments
 (0)