pyln-testing: check plugin notifications against any extant notification schemas.
Note that we need a workaround for deprecated APIs where "channel_state_changed" output "null" which violated the schema. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
@@ -3,6 +3,7 @@ from pyln.testing.db import SqliteDbProvider, PostgresDbProvider
|
||||
from pyln.testing.utils import NodeFactory, BitcoinD, ElementsD, env, LightningNode, TEST_DEBUG, TEST_NETWORK
|
||||
from pyln.client import Millisatoshi
|
||||
from typing import Dict
|
||||
from pathlib import Path
|
||||
|
||||
import json
|
||||
import jsonschema # type: ignore
|
||||
@@ -496,6 +497,7 @@ def node_factory(request, directory, test_name, bitcoind, executor, db_provider,
|
||||
map_node_error(nf.nodes, checkBroken, "had BROKEN messages")
|
||||
map_node_error(nf.nodes, lambda n: not n.allow_warning and n.daemon.is_in_log(r' WARNING:'), "had warning messages")
|
||||
map_node_error(nf.nodes, checkReconnect, "had unexpected reconnections")
|
||||
map_node_error(nf.nodes, checkPluginJSON, "had malformed hooks/notifications")
|
||||
|
||||
# On any bad gossip complaints, print out every nodes' gossip_store
|
||||
if map_node_error(nf.nodes, checkBadGossip, "had bad gossip messages"):
|
||||
@@ -617,6 +619,57 @@ def checkBroken(node):
|
||||
return 0
|
||||
|
||||
|
||||
def checkPluginJSON(node):
|
||||
if node.bad_notifications:
|
||||
return 0
|
||||
|
||||
try:
|
||||
notificationfiles = os.listdir('doc/schemas/notification')
|
||||
except FileNotFoundError:
|
||||
notificationfiles = []
|
||||
|
||||
notifications = {}
|
||||
for fname in notificationfiles:
|
||||
if fname.endswith('.json'):
|
||||
base = fname.replace('.json', '')
|
||||
# Request is 0 and Response is 1
|
||||
notifications[base] = _load_schema(os.path.join('doc/schemas/notification', fname))
|
||||
|
||||
# FIXME: add doc/schemas/hook/
|
||||
hooks = {}
|
||||
|
||||
for f in (Path(node.daemon.lightning_dir) / "plugin-io").iterdir():
|
||||
# e.g. hook_in-peer_connected-124567-358
|
||||
io = json.loads(f.read_text())
|
||||
parts = f.name.split('-')
|
||||
if parts[0] == 'hook_in':
|
||||
schema = hooks.get(parts[1])
|
||||
req = io['result']
|
||||
direction = 1
|
||||
elif parts[0] == 'hook_out':
|
||||
schema = hooks.get(parts[1])
|
||||
req = io['params']
|
||||
direction = 0
|
||||
else:
|
||||
assert parts[0] == 'notification_out'
|
||||
schema = notifications.get(parts[1])
|
||||
# The notification is wrapped in an object of its own name.
|
||||
req = io['params'][parts[1]]
|
||||
direction = 1
|
||||
|
||||
# Until v26.09, with channel_state_changed.null_scid, that notification will be non-schema compliant.
|
||||
if f.name.startswith('notification_out-channel_state_changed-') and node.daemon.opts.get('allow-deprecated-apis', True) is True:
|
||||
continue
|
||||
|
||||
if schema is not None:
|
||||
try:
|
||||
schema[direction].validate(req)
|
||||
except jsonschema.exceptions.ValidationError as e:
|
||||
print(f"Failed validating {f}: {e}")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def checkBadReestablish(node):
|
||||
if node.daemon.is_in_log('Bad reestablish'):
|
||||
return 1
|
||||
|
||||
@@ -789,11 +789,13 @@ class LightningNode(object):
|
||||
jsonschemas={},
|
||||
valgrind_plugins=True,
|
||||
executable=None,
|
||||
bad_notifications=False,
|
||||
**kwargs):
|
||||
self.bitcoin = bitcoind
|
||||
self.executor = executor
|
||||
self.may_fail = may_fail
|
||||
self.may_reconnect = may_reconnect
|
||||
self.bad_notifications = bad_notifications
|
||||
self.broken_log = broken_log
|
||||
self.allow_bad_gossip = allow_bad_gossip
|
||||
self.allow_warning = allow_warning
|
||||
@@ -853,6 +855,10 @@ class LightningNode(object):
|
||||
if self.cln_version >= "v24.11":
|
||||
self.daemon.opts.update({"autoconnect-seeker-peers": 0})
|
||||
|
||||
jsondir = Path(lightning_dir) / "plugin-io"
|
||||
jsondir.mkdir()
|
||||
self.daemon.opts['dev-save-plugin-io'] = jsondir
|
||||
|
||||
if options is not None:
|
||||
self.daemon.opts.update(options)
|
||||
dsn = db.get_dsn()
|
||||
@@ -1601,6 +1607,7 @@ class NodeFactory(object):
|
||||
'broken_log',
|
||||
'allow_warning',
|
||||
'may_reconnect',
|
||||
'bad_notifications',
|
||||
'random_hsm',
|
||||
'feerates',
|
||||
'wait_for_bitcoind_sync',
|
||||
|
||||
Reference in New Issue
Block a user