libplugin: correctly wrap notifications we send in the notification name.
What changed, and why it matters
This commit fixes a notification formatting inconsistency in Core Lightning's plugin system. Plugin-generated notifications (channel_hint_update, pay_failure, pay_success) now wrap their data inside an object named after the notification, matching how core notifications already work. The old unwrapped format is kept as a deprecated option for backward compatibility. This is primarily an API consistency and compatibility fix, not a security vulnerability fix.
No immediate security action required. Operators and plugin authors should update their notification handlers to expect the new wrapped format and prepare for removal of the deprecated raw fields in v26.09.
Security signals we found
API consistency fix for JSON-RPC notification format
Backward-compatible deprecation of old notification field layout
No memory corruption, authentication bypass, or cryptographic weakness evident
No explicit security relevance stated by vendor
Evidence from the diff
The change updates libplugin’s notification helpers so that plugin-emitted notifications wrap payload fields in a same-named object (e.g., {‘pay_success’: {…}}). It introduces plugin_notification_start_obs/_end_obs and notification_deprecated_out_ok to support both the new wrapped format and the legacy unwrapped/’payload’ format when allow-deprecated-apis is enabled. The affected notifications are channel_hint_update, pay_failure, and pay_success. Tests are updated to expect the new format and to verify deprecated-mode behavior.
Changed components
plugins/libplugin.cplugins/libplugin.hplugins/libplugin-pay.cplugins/channel_hint.ctests/test_plugin.pytests/test_xpay.pyInspect captured patch +229 / −114
diff --git a/doc/developers-guide/deprecated-features.md b/doc/developers-guide/deprecated-features.md
index 3193abd4..6769e840 100644
--- a/doc/developers-guide/deprecated-features.md
+++ b/doc/developers-guide/deprecated-features.md
@@ -23,6 +23,7 @@ hidden: false
| coin_movement.txid | Notification Field | v25.09 | v26.09 | Use `spending_txid` instead |
| channel_state_changed.null_scid | Notification Field | v25.09 | v26.09 | In channel_state_changed notification, `short_channel_id` will be missing instead of `null` |
| notification.payload | Notification Field | v25.09 | v26.09 | Notifications from plugins used to have fields in `payload` sub-object, now they are not (just like normal notifications) |
+| pay_notifications.raw_fields | Field | v25.09 | v26.09 | `channel_hint_update`, `pay_failure` and `pay_success` notifications now wrap members in an object of the same name |
Inevitably there are features which need to change: either to be generalized, or removed when they can no longer be supported.
diff --git a/plugins/channel_hint.c b/plugins/channel_hint.c
index af545759..1db37791 100644
--- a/plugins/channel_hint.c
+++ b/plugins/channel_hint.c
@@ -191,7 +191,7 @@ struct channel_hint *channel_hint_from_json(const tal_t *ctx,
payload = json_get_member(buffer, toks, "payload");
/* Modern API includes fields directly */
if (!payload) {
- jhint = json_get_member(buffer, toks, "channel_hint");
+ jhint = json_get_member(buffer, toks, "channel_hint_update");
} else {
jhint = json_get_member(buffer, payload, "channel_hint");
}
diff --git a/plugins/libplugin-pay.c b/plugins/libplugin-pay.c
index 0fe5472a..ec2bcb7a 100644
--- a/plugins/libplugin-pay.c
+++ b/plugins/libplugin-pay.c
@@ -383,15 +383,32 @@ void payment_start(struct payment *p)
* We share the channel_hints across payments, and across plugins, in order
* to maximize the context they have when performing payments.
*/
+static void channel_hint_notify_core(struct plugin *plugin,
+ struct json_stream *js,
+ const char *fieldname,
+ const struct channel_hint *hint)
+{
+ /* The timestamp used to decay the observation over time. */
+ channel_hint_to_json(fieldname, hint, js);
+}
+
static void channel_hint_notify(struct plugin *plugin,
const struct channel_hint *hint)
{
struct json_stream *js =
- plugin_notification_start(plugin, "channel_hint_update");
+ plugin_notification_start_obs(plugin, "channel_hint_update");
- /* The timestamp used to decay the observation over time. */
- channel_hint_to_json("channel_hint", hint, js);
- plugin_notification_end(plugin, js);
+ /* Fake up the old "payload" style, *and* the old unwrapped style */
+ if (notification_deprecated_out_ok(plugin, "notification", "payload",
+ "v25.09", "v26.09")) {
+ json_add_string(js, "origin", "pay");
+ json_object_start(js, "payload");
+ channel_hint_notify_core(plugin, js, "channel_hint", hint);
+ json_object_end(js);
+ }
+
+ channel_hint_notify_core(plugin, js, "channel_hint_update", hint);
+ plugin_notification_end_obs(plugin, js);
}
static void channel_hints_update(struct payment *p,
@@ -2077,12 +2094,11 @@ static void payment_json_add_attempts(struct json_stream *s,
json_array_end(s);
}
-static void payment_notify_failure(struct payment *p, const char *error_message)
+static void payment_failure_notify_core(struct payment *p,
+ struct json_stream *n,
+ const char *error_message)
{
struct payment *root = payment_root(p);
- struct json_stream *n;
-
- n = plugin_notification_start(p->plugin, "pay_failure");
json_add_sha256(n, "payment_hash", p->payment_hash);
if (root->invstring != NULL)
json_add_string(n, "bolt11", root->invstring);
@@ -2090,8 +2106,26 @@ static void payment_notify_failure(struct payment *p, const char *error_message)
json_object_start(n, "error");
json_add_string(n, "message", error_message);
json_object_end(n); /* .error */
+}
+
+static void payment_notify_failure(struct payment *p, const char *error_message)
+{
+ struct json_stream *n;
- plugin_notification_end(p->plugin, n);
+ n = plugin_notification_start_obs(p->plugin, "pay_failure");
+ /* Fake up the old "payload" style, *and* the old unwrapped style */
+ if (notification_deprecated_out_ok(p->plugin, "notification", "payload",
+ "v25.09", "v26.09")) {
+ json_add_string(n, "origin", "pay");
+ json_object_start(n, "payload");
+ payment_failure_notify_core(p, n, error_message);
+ json_object_end(n);
+ }
+
+ json_object_start(n, "pay_failure");
+ payment_failure_notify_core(p, n, error_message);
+ json_object_end(n);
+ plugin_notification_end_obs(p->plugin, n);
}
/* Code shared by selfpay fast-path: populate JSON output for successful
@@ -2126,11 +2160,24 @@ void json_add_payment_success(struct json_stream *js,
json_add_preimage(js, "payment_preimage", preimage);
json_add_string(js, "status", "complete");
- n = plugin_notification_start(p->plugin, "pay_success");
+ n = plugin_notification_start_obs(p->plugin, "pay_success");
+ /* Fake up the old "payload" style, *and* the old unwrapped style */
+ if (notification_deprecated_out_ok(p->plugin, "notification", "payload",
+ "v25.09", "v26.09")) {
+ json_add_string(n, "origin", "pay");
+ json_object_start(n, "payload");
+ json_add_sha256(n, "payment_hash", p->payment_hash);
+ if (root->invstring != NULL)
+ json_add_string(n, "bolt11", root->invstring);
+ json_object_end(n);
+ }
+
+ json_object_start(n, "pay_success");
json_add_sha256(n, "payment_hash", p->payment_hash);
if (root->invstring != NULL)
json_add_string(n, "bolt11", root->invstring);
- plugin_notification_end(p->plugin, n);
+ json_object_end(n);
+ plugin_notification_end_obs(p->plugin, n);
}
/* This function is called whenever a payment ends up in a final state, or all
diff --git a/plugins/libplugin.c b/plugins/libplugin.c
index 614b5d3d..dbe58d2c 100644
--- a/plugins/libplugin.c
+++ b/plugins/libplugin.c
@@ -239,6 +239,19 @@ bool command_deprecated_out_ok(struct command *cmd,
NULL, NULL);
}
+bool notification_deprecated_out_ok(struct plugin *plugin,
+ const char *method,
+ const char *fieldname,
+ const char *depr_start,
+ const char *depr_end)
+{
+ return deprecated_ok(plugin->deprecated_ok,
+ tal_fmt(tmpctx, "%s.%s", method, fieldname),
+ depr_start, depr_end,
+ plugin->beglist,
+ NULL, NULL);
+}
+
static void ld_send(struct plugin *plugin, struct json_stream *stream)
{
struct jstream *jstr = tal(plugin, struct jstream);
@@ -1859,8 +1872,8 @@ void plugin_gossmap_logcb(struct plugin *plugin,
va_end(ap);
}
-struct json_stream *plugin_notification_start(const tal_t *ctx,
- const char *method)
+struct json_stream *plugin_notification_start_obs(const tal_t *ctx,
+ const char *method)
{
struct json_stream *js = new_json_stream(ctx, NULL, NULL);
@@ -1872,11 +1885,26 @@ struct json_stream *plugin_notification_start(const tal_t *ctx,
return js;
}
+void plugin_notification_end_obs(struct plugin *plugin,
+ struct json_stream *stream STEALS)
+{
+ json_object_end(stream);
+ jsonrpc_finish_and_send(plugin, stream);
+}
+
+struct json_stream *plugin_notification_start(const tal_t *ctx,
+ const char *method)
+{
+ struct json_stream *js = plugin_notification_start_obs(ctx, method);
+ json_object_start(js, method);
+ return js;
+}
+
void plugin_notification_end(struct plugin *plugin,
struct json_stream *stream STEALS)
{
json_object_end(stream);
- jsonrpc_finish_and_send(plugin, stream);
+ plugin_notification_end_obs(plugin, stream);
}
struct json_stream *plugin_notify_start(struct command *cmd, const char *method)
diff --git a/plugins/libplugin.h b/plugins/libplugin.h
index 2cc781da..80261ab2 100644
--- a/plugins/libplugin.h
+++ b/plugins/libplugin.h
@@ -381,6 +381,13 @@ bool command_deprecated_in_named_ok(struct command *cmd,
const char *depr_start,
const char *depr_end);
+/* Should we include this field in the notification? */
+bool notification_deprecated_out_ok(struct plugin *plugin,
+ const char *method,
+ const char *fieldname,
+ const char *depr_start,
+ const char *depr_end);
+
/* Call this on fatal error. */
void NORETURN PRINTF_FMT(2,3) plugin_err(struct plugin *p, const char *fmt, ...);
@@ -539,6 +546,12 @@ struct json_stream *plugin_notification_start(const tal_t *ctx,
void plugin_notification_end(struct plugin *plugin,
struct json_stream *stream STEALS);
+/* Obsolete versions: do not use for new code! */
+struct json_stream *plugin_notification_start_obs(const tal_t *ctx,
+ const char *method);
+void plugin_notification_end_obs(struct plugin *plugin,
+ struct json_stream *stream TAKES);
+
/* Convenience wrapper for notify "message" */
void plugin_notify_message(struct command *cmd,
enum log_level level,
diff --git a/plugins/test/run-route-calc.c b/plugins/test/run-route-calc.c
index 73e56ce3..caca1ff6 100644
--- a/plugins/test/run-route-calc.c
+++ b/plugins/test/run-route-calc.c
@@ -284,6 +284,13 @@ void memleak_add_helper_(const tal_t *p UNNEEDED, void (*cb)(struct htable *memt
/* Generated stub for memleak_scan_htable */
void memleak_scan_htable(struct htable *memtable UNNEEDED, const struct htable *ht UNNEEDED)
{ fprintf(stderr, "memleak_scan_htable called!\n"); abort(); }
+/* Generated stub for notification_deprecated_out_ok */
+bool notification_deprecated_out_ok(struct plugin *plugin UNNEEDED,
+ const char *method UNNEEDED,
+ const char *fieldname UNNEEDED,
+ const char *depr_start UNNEEDED,
+ const char *depr_end UNNEEDED)
+{ fprintf(stderr, "notification_deprecated_out_ok called!\n"); abort(); }
/* Generated stub for notleak_ */
void *notleak_(void *ptr UNNEEDED, bool plus_children UNNEEDED)
{ fprintf(stderr, "notleak_ called!\n"); abort(); }
@@ -299,14 +306,14 @@ void plugin_gossmap_logcb(struct plugin *plugin UNNEEDED,
/* Generated stub for plugin_log */
void plugin_log(struct plugin *p UNNEEDED, enum log_level l UNNEEDED, const char *fmt UNNEEDED, ...)
{ fprintf(stderr, "plugin_log called!\n"); abort(); }
-/* Generated stub for plugin_notification_end */
-void plugin_notification_end(struct plugin *plugin UNNEEDED,
- struct json_stream *stream STEALS UNNEEDED)
-{ fprintf(stderr, "plugin_notification_end called!\n"); abort(); }
-/* Generated stub for plugin_notification_start */
-struct json_stream *plugin_notification_start(const tal_t *ctx UNNEEDED,
- const char *method UNNEEDED)
-{ fprintf(stderr, "plugin_notification_start called!\n"); abort(); }
+/* Generated stub for plugin_notification_end_obs */
+void plugin_notification_end_obs(struct plugin *plugin UNNEEDED,
+ struct json_stream *stream TAKES UNNEEDED)
+{ fprintf(stderr, "plugin_notification_end_obs called!\n"); abort(); }
+/* Generated stub for plugin_notification_start_obs */
+struct json_stream *plugin_notification_start_obs(const tal_t *ctx UNNEEDED,
+ const char *method UNNEEDED)
+{ fprintf(stderr, "plugin_notification_start_obs called!\n"); abort(); }
/* Generated stub for plugin_notify_message */
void plugin_notify_message(struct command *cmd UNNEEDED,
enum log_level level UNNEEDED,
diff --git a/plugins/test/run-route-overlong.c b/plugins/test/run-route-overlong.c
index ad0a63ec..e5d5e69c 100644
--- a/plugins/test/run-route-overlong.c
+++ b/plugins/test/run-route-overlong.c
@@ -281,6 +281,13 @@ void memleak_add_helper_(const tal_t *p UNNEEDED, void (*cb)(struct htable *memt
/* Generated stub for memleak_scan_htable */
void memleak_scan_htable(struct htable *memtable UNNEEDED, const struct htable *ht UNNEEDED)
{ fprintf(stderr, "memleak_scan_htable called!\n"); abort(); }
+/* Generated stub for notification_deprecated_out_ok */
+bool notification_deprecated_out_ok(struct plugin *plugin UNNEEDED,
+ const char *method UNNEEDED,
+ const char *fieldname UNNEEDED,
+ const char *depr_start UNNEEDED,
+ const char *depr_end UNNEEDED)
+{ fprintf(stderr, "notification_deprecated_out_ok called!\n"); abort(); }
/* Generated stub for notleak_ */
void *notleak_(void *ptr UNNEEDED, bool plus_children UNNEEDED)
{ fprintf(stderr, "notleak_ called!\n"); abort(); }
@@ -296,14 +303,14 @@ void plugin_gossmap_logcb(struct plugin *plugin UNNEEDED,
/* Generated stub for plugin_log */
void plugin_log(struct plugin *p UNNEEDED, enum log_level l UNNEEDED, const char *fmt UNNEEDED, ...)
{ fprintf(stderr, "plugin_log called!\n"); abort(); }
-/* Generated stub for plugin_notification_end */
-void plugin_notification_end(struct plugin *plugin UNNEEDED,
- struct json_stream *stream STEALS UNNEEDED)
-{ fprintf(stderr, "plugin_notification_end called!\n"); abort(); }
-/* Generated stub for plugin_notification_start */
-struct json_stream *plugin_notification_start(const tal_t *ctx UNNEEDED,
- const char *method UNNEEDED)
-{ fprintf(stderr, "plugin_notification_start called!\n"); abort(); }
+/* Generated stub for plugin_notification_end_obs */
+void plugin_notification_end_obs(struct plugin *plugin UNNEEDED,
+ struct json_stream *stream TAKES UNNEEDED)
+{ fprintf(stderr, "plugin_notification_end_obs called!\n"); abort(); }
+/* Generated stub for plugin_notification_start_obs */
+struct json_stream *plugin_notification_start_obs(const tal_t *ctx UNNEEDED,
+ const char *method UNNEEDED)
+{ fprintf(stderr, "plugin_notification_start_obs called!\n"); abort(); }
/* Generated stub for plugin_notify_message */
void plugin_notify_message(struct command *cmd UNNEEDED,
enum log_level level UNNEEDED,
diff --git a/tests/plugins/custom_notifications.py b/tests/plugins/custom_notifications.py
index c57a386c..1a3d92f1 100755
--- a/tests/plugins/custom_notifications.py
+++ b/tests/plugins/custom_notifications.py
@@ -25,11 +25,11 @@ def faulty_emit(plugin):
@plugin.subscribe("pay_success")
-def on_pay_success(origin, payment_hash, **kwargs):
+def on_pay_success(origin, pay_success, **kwargs):
plugin.log(
"Got a pay_success notification from plugin {} for payment_hash {}".format(
origin,
- payment_hash
+ pay_success['payment_hash']
)
)
diff --git a/tests/test_plugin.py b/tests/test_plugin.py
index 706bda88..2ac9da76 100644
--- a/tests/test_plugin.py
+++ b/tests/test_plugin.py
@@ -4374,10 +4374,12 @@ def test_peer_storage(node_factory, bitcoind):
assert not l2.daemon.is_in_log(r'PeerStorageFailed')
-@pytest.mark.xfail(strict=True)
-def test_pay_plugin_notifications(node_factory, bitcoind, chainparams):
+@pytest.mark.parametrize("deprecated", [False, True])
+def test_pay_plugin_notifications(node_factory, bitcoind, chainparams, deprecated):
plugin = os.path.join(os.getcwd(), 'tests/plugins/all_notifications.py')
opts = {"plugin": plugin}
+ if deprecated:
+ opts['allow-deprecated-apis'] = True
l1, l2, l3 = node_factory.line_graph(3, opts=[opts, {}, {}],
wait_for_announce=True)
@@ -4403,15 +4405,17 @@ def test_pay_plugin_notifications(node_factory, bitcoind, chainparams):
dict_str = line.split("notification channel_hint_update: ", 1)[1]
data = zero_timestamps(ast.literal_eval(dict_str))
- # pyln-client's plugin.py duplicated payload into same name as update.
channel_hint_update_core = {'scid': first_scid(l1, l2) + '/1',
'estimated_capacity_msat': 964719000 if chainparams['elements'] else 978718000,
'total_capacity_msat': 1000000000,
'timestamp': 0,
'enabled': True}
channel_hint_update = {'origin': 'pay',
- 'payload': {'channel_hint': channel_hint_update_core},
- 'channel_hint_update': {'channel_hint': channel_hint_update_core}}
+ 'channel_hint_update': channel_hint_update_core}
+ if deprecated:
+ # pyln-client's plugin.py duplicated payload into same name as update.
+ channel_hint_update['payload'] = {'channel_hint': channel_hint_update_core}
+
assert data == channel_hint_update
# It gets a success notification
@@ -4420,10 +4424,11 @@ def test_pay_plugin_notifications(node_factory, bitcoind, chainparams):
data = ast.literal_eval(dict_str)
success_core = {'payment_hash': inv1['payment_hash'],
'bolt11': inv1['bolt11']}
- # Includes deprecated and modern. pyln-client plugin.py copies fields as necessary.
success = {'origin': 'pay',
- 'payload': success_core,
'pay_success': success_core}
+ if deprecated:
+ # pyln-client's plugin.py duplicated payload into same name as update.
+ success['payload'] = success_core
assert data == success
inv2 = l3.rpc.invoice(10000, "second", "desc")
@@ -4435,10 +4440,11 @@ def test_pay_plugin_notifications(node_factory, bitcoind, chainparams):
dict_str = line.split("notification pay_failure: ", 1)[1]
data = ast.literal_eval(dict_str)
failure_core = {'payment_hash': inv2['payment_hash'], 'bolt11': inv2['bolt11'], 'error': {'message': 'failed: WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS (reply from remote)'}}
- # Includes deprecated and modern.
failure = {'origin': 'pay',
- 'payload': failure_core,
'pay_failure': failure_core}
+ if deprecated:
+ # pyln-client's plugin.py duplicated payload into same name as update.
+ failure['payload'] = failure_core
assert data == failure
diff --git a/tests/test_xpay.py b/tests/test_xpay.py
index f9c95a30..8539fdd2 100644
--- a/tests/test_xpay.py
+++ b/tests/test_xpay.py
@@ -863,31 +863,33 @@ def test_attempt_notifications(node_factory):
line = l1.daemon.wait_for_log("plugin-custom_notifications.py: Got pay_part_start: ")
dict_str = line.split("Got pay_part_start: ", 1)[1]
data = zero_fields(ast.literal_eval(dict_str), ['groupid'])
- expected = {'payment_hash': inv1['payment_hash'],
- 'groupid': 0,
- 'partid': 1,
- 'total_payment_msat': 5000000,
- 'attempt_msat': 5000000,
- 'hops': [{'next_node': l2.info['id'],
- 'short_channel_id': scid12,
- 'direction': scid12_dir,
- 'channel_in_msat': 5000051,
- 'channel_out_msat': 5000051},
- {'next_node': l3.info['id'],
- 'short_channel_id': scid23,
- 'direction': scid23_dir,
- 'channel_in_msat': 5000051,
- 'channel_out_msat': 5000000}]}
+ expected = {'pay_part_start':
+ {'payment_hash': inv1['payment_hash'],
+ 'groupid': 0,
+ 'partid': 1,
+ 'total_payment_msat': 5000000,
+ 'attempt_msat': 5000000,
+ 'hops': [{'next_node': l2.info['id'],
+ 'short_channel_id': scid12,
+ 'direction': scid12_dir,
+ 'channel_in_msat': 5000051,
+ 'channel_out_msat': 5000051},
+ {'next_node': l3.info['id'],
+ 'short_channel_id': scid23,
+ 'direction': scid23_dir,
+ 'channel_in_msat': 5000051,
+ 'channel_out_msat': 5000000}]}}
assert data == expected
line = l1.daemon.wait_for_log("plugin-custom_notifications.py: Got pay_part_end: ")
dict_str = line.split("Got pay_part_end: ", 1)[1]
data = zero_fields(ast.literal_eval(dict_str), ('duration', 'groupid'))
- expected = {'payment_hash': inv1['payment_hash'],
- 'status': 'success',
- 'duration': 0,
- 'groupid': 0,
- 'partid': 1}
+ expected = {'pay_part_end':
+ {'payment_hash': inv1['payment_hash'],
+ 'status': 'success',
+ 'duration': 0,
+ 'groupid': 0,
+ 'partid': 1}}
assert data == expected
inv2 = l3.rpc.invoice(10000000, 'test_attempt_notifications2', 'test_attempt_notifications2')
@@ -900,35 +902,37 @@ def test_attempt_notifications(node_factory):
line = l1.daemon.wait_for_log("plugin-custom_notifications.py: Got pay_part_start: ")
dict_str = line.split("Got pay_part_start: ", 1)[1]
data = zero_fields(ast.literal_eval(dict_str), ['groupid'])
- expected = {'payment_hash': inv2['payment_hash'],
- 'groupid': 0,
- 'partid': 1,
- 'total_payment_msat': 10000000,
- 'attempt_msat': 10000000,
- 'hops': [{'next_node': l2.info['id'],
- 'short_channel_id': scid12,
- 'direction': scid12_dir,
- 'channel_in_msat': 10000101,
- 'channel_out_msat': 10000101},
- {'next_node': l3.info['id'],
- 'short_channel_id': scid23,
- 'direction': scid23_dir,
- 'channel_in_msat': 10000101,
- 'channel_out_msat': 10000000}]}
+ expected = {'pay_part_start':
+ {'payment_hash': inv2['payment_hash'],
+ 'groupid': 0,
+ 'partid': 1,
+ 'total_payment_msat': 10000000,
+ 'attempt_msat': 10000000,
+ 'hops': [{'next_node': l2.info['id'],
+ 'short_channel_id': scid12,
+ 'direction': scid12_dir,
+ 'channel_in_msat': 10000101,
+ 'channel_out_msat': 10000101},
+ {'next_node': l3.info['id'],
+ 'short_channel_id': scid23,
+ 'direction': scid23_dir,
+ 'channel_in_msat': 10000101,
+ 'channel_out_msat': 10000000}]}}
assert data == expected
line = l1.daemon.wait_for_log("plugin-custom_notifications.py: Got pay_part_end: ")
dict_str = line.split("Got pay_part_end: ", 1)[1]
data = zero_fields(ast.literal_eval(dict_str), ('duration', 'groupid'))
- expected = {'payment_hash': inv2['payment_hash'],
- 'status': 'failure',
- 'duration': 0,
- 'groupid': 0,
- 'partid': 1,
- 'failed_msg': '400f00000000009896800000006c',
- 'failed_node_id': l3.info['id'],
- 'error_code': 16399,
- 'error_message': 'incorrect_or_unknown_payment_details'}
+ expected = {'pay_part_end':
+ {'payment_hash': inv2['payment_hash'],
+ 'status': 'failure',
+ 'duration': 0,
+ 'groupid': 0,
+ 'partid': 1,
+ 'failed_msg': '400f00000000009896800000006c',
+ 'failed_node_id': l3.info['id'],
+ 'error_code': 16399,
+ 'error_message': 'incorrect_or_unknown_payment_details'}}
assert data == expected
# Intermediary node failure
@@ -939,36 +943,38 @@ def test_attempt_notifications(node_factory):
line = l1.daemon.wait_for_log("plugin-custom_notifications.py: Got pay_part_start: ")
dict_str = line.split("Got pay_part_start: ", 1)[1]
data = zero_fields(ast.literal_eval(dict_str), ['groupid'])
- expected = {'payment_hash': inv2['payment_hash'],
- 'groupid': 0,
- 'partid': 1,
- 'total_payment_msat': 10000000,
- 'attempt_msat': 10000000,
- 'hops': [{'next_node': l2.info['id'],
- 'short_channel_id': scid12,
- 'direction': scid12_dir,
- 'channel_in_msat': 10000101,
- 'channel_out_msat': 10000101},
- {'next_node': l3.info['id'],
- 'short_channel_id': scid23,
- 'direction': scid23_dir,
- 'channel_in_msat': 10000101,
- 'channel_out_msat': 10000000}]}
+ expected = {'pay_part_start':
+ {'payment_hash': inv2['payment_hash'],
+ 'groupid': 0,
+ 'partid': 1,
+ 'total_payment_msat': 10000000,
+ 'attempt_msat': 10000000,
+ 'hops': [{'next_node': l2.info['id'],
+ 'short_channel_id': scid12,
+ 'direction': scid12_dir,
+ 'channel_in_msat': 10000101,
+ 'channel_out_msat': 10000101},
+ {'next_node': l3.info['id'],
+ 'short_channel_id': scid23,
+ 'direction': scid23_dir,
+ 'channel_in_msat': 10000101,
+ 'channel_out_msat': 10000000}]}}
assert data == expected
line = l1.daemon.wait_for_log("plugin-custom_notifications.py: Got pay_part_end: ")
dict_str = line.split("Got pay_part_end: ", 1)[1]
data = zero_fields(ast.literal_eval(dict_str), ('duration', 'groupid', 'failed_msg'))
- expected = {'payment_hash': inv2['payment_hash'],
- 'status': 'failure',
- 'duration': 0,
- 'groupid': 0,
- 'partid': 1,
- # This includes the channel update: just zero it out
- 'failed_msg': 0,
- 'failed_direction': 0,
- 'failed_node_id': l2.info['id'],
- 'failed_short_channel_id': scid23,
- 'error_code': 4103,
- 'error_message': 'temporary_channel_failure'}
+ expected = {'pay_part_end':
+ {'payment_hash': inv2['payment_hash'],
+ 'status': 'failure',
+ 'duration': 0,
+ 'groupid': 0,
+ 'partid': 1,
+ # This includes the channel update: just zero it out
+ 'failed_msg': 0,
+ 'failed_direction': 0,
+ 'failed_node_id': l2.info['id'],
+ 'failed_short_channel_id': scid23,
+ 'error_code': 4103,
+ 'error_message': 'temporary_channel_failure'}}
assert data == expected
diff --git a/wallet/test/run-db.c b/wallet/test/run-db.c
index a75d099e..b5eca59a 100644
--- a/wallet/test/run-db.c
+++ b/wallet/test/run-db.c
@@ -190,7 +190,7 @@ struct channel *new_channel(struct peer *peer UNNEEDED, u64 dbid UNNEEDED,
struct amount_sat our_funds UNNEEDED,
bool remote_channel_ready UNNEEDED,
/* NULL or stolen */
- struct short_channel_id *scid STEALS UNNEEDED,
+ struct short_channel_id *scid TAKES UNNEEDED,
struct short_channel_id *old_scids TAKES UNNEEDED,
struct short_channel_id alias_local UNNEEDED,
struct short_channel_id *alias_remote STEALS UNNEEDED,
Why this scored 26/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.