What changed, and why it matters
This commit adds a new data type called 'string_map' to the code generator used for converting between Core Lightning's JSON-RPC interface and its gRPC/Rust interfaces. It is a feature addition for the message generator and testing schema validator, not a security fix or vulnerability patch.
No security action required. Treat as normal feature/maintenance commit.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch introduces a ‘string_map’ primitive type across the msggen code generator: it maps to ‘map
Changed components
contrib/msggen/msggen/gen/grpc/convert.pycontrib/msggen/msggen/gen/grpc/proto.pycontrib/msggen/msggen/gen/grpc/unconvert.pycontrib/msggen/msggen/gen/grpc/util.pycontrib/msggen/msggen/gen/rpc/rust.pycontrib/msggen/msggen/model.pycontrib/pyln-testing/pyln/testing/fixtures.pyInspect captured patch +22 / −1
diff --git a/contrib/msggen/msggen/gen/grpc/convert.py b/contrib/msggen/msggen/gen/grpc/convert.py
index 72178b31..3adc7978 100644
--- a/contrib/msggen/msggen/gen/grpc/convert.py
+++ b/contrib/msggen/msggen/gen/grpc/convert.py
@@ -130,6 +130,8 @@ class GrpcConverterGenerator(IGenerator):
"TlvStream?": f"c.{name}.map(|s| s.into())",
"RoutehintList?": f"c.{name}.map(|rl| rl.into())",
"DecodeRoutehintList?": f"c.{name}.map(|drl| drl.into())",
+ "string_map": f"Some(c.{name})",
+ "string_map?": f"c.{name}.unwrap_or(HashMap::new())",
}.get(
typ, f"c.{name}" # default to just assignment
)
@@ -202,6 +204,7 @@ class GrpcConverterGenerator(IGenerator):
use cln_rpc::notifications;
use crate::pb;
use std::str::FromStr;
+ use std::collections::HashMap;
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::Hash;
use cln_rpc::primitives::PublicKey;
diff --git a/contrib/msggen/msggen/gen/grpc/proto.py b/contrib/msggen/msggen/gen/grpc/proto.py
index b0af147e..3f2b981a 100644
--- a/contrib/msggen/msggen/gen/grpc/proto.py
+++ b/contrib/msggen/msggen/gen/grpc/proto.py
@@ -201,7 +201,7 @@ class GrpcGenerator(IGenerator):
if f.omit():
continue
- opt = "optional " if f.optional else ""
+ opt = "optional " if f.optional and not (isinstance(f, PrimitiveField) and f.typename == "string_map") else ""
if isinstance(f, ArrayField):
typename = f.override(
diff --git a/contrib/msggen/msggen/gen/grpc/unconvert.py b/contrib/msggen/msggen/gen/grpc/unconvert.py
index 974139b6..16a7591d 100644
--- a/contrib/msggen/msggen/gen/grpc/unconvert.py
+++ b/contrib/msggen/msggen/gen/grpc/unconvert.py
@@ -131,6 +131,8 @@ class GrpcUnconverterGenerator(GrpcConverterGenerator):
"hash?": f"c.{name}.map(|v| Sha256::from_slice(&v).unwrap())",
"txid": f"hex::encode(&c.{name})",
"TlvStream?": f"c.{name}.map(|s| s.into())",
+ "string_map": f"c.{name}.unwrap()",
+ "string_map?": f"Some(c.{name})",
}.get(
typ, f"c.{name}" # default to just assignment
)
diff --git a/contrib/msggen/msggen/gen/grpc/util.py b/contrib/msggen/msggen/gen/grpc/util.py
index 2682d96b..267691d5 100644
--- a/contrib/msggen/msggen/gen/grpc/util.py
+++ b/contrib/msggen/msggen/gen/grpc/util.py
@@ -35,6 +35,7 @@ typemap = {
"secret": "bytes",
"bip340sig": "string",
"hash": "bytes",
+ "string_map": "map<string, string>",
}
diff --git a/contrib/msggen/msggen/gen/rpc/rust.py b/contrib/msggen/msggen/gen/rpc/rust.py
index 14beeae6..b2fa7625 100644
--- a/contrib/msggen/msggen/gen/rpc/rust.py
+++ b/contrib/msggen/msggen/gen/rpc/rust.py
@@ -46,6 +46,7 @@ typemap = {
"secret": "Secret",
"bip340sig": "String",
"integer": "i64",
+ "string_map": "HashMap<String, String>",
}
header = f"""
@@ -306,6 +307,7 @@ class RustGenerator(IGenerator):
#[allow(unused_imports)]
use serde::{{Deserialize, Serialize}};
use core::fmt::Debug;
+ use std::collections::HashMap;
use super::{IntoRequest, Request, TypedRequest};
"""
)
diff --git a/contrib/msggen/msggen/model.py b/contrib/msggen/msggen/model.py
index 50317abe..21b99d51 100644
--- a/contrib/msggen/msggen/model.py
+++ b/contrib/msggen/msggen/model.py
@@ -432,6 +432,7 @@ class PrimitiveField(Field):
"secret",
"bip340sig",
"hash",
+ "string_map",
]
def __init__(self, typename, path, description, added, deprecated):
diff --git a/contrib/pyln-testing/pyln/testing/fixtures.py b/contrib/pyln-testing/pyln/testing/fixtures.py
index ef55ecd0..5a439677 100644
--- a/contrib/pyln-testing/pyln/testing/fixtures.py
+++ b/contrib/pyln-testing/pyln/testing/fixtures.py
@@ -411,6 +411,17 @@ def _extra_validator(is_request: bool):
return True
return False
+ def is_string_map(checker, instance):
+ """key, value map with strings"""
+ if not checker.is_type(instance, "object"):
+ return False
+ for k, v in instance.items():
+ if not checker.is_type(k, "string"):
+ return False
+ if not checker.is_type(v, "string"):
+ return False
+ return True
+
# "msat" for request can be many forms
if is_request:
is_msat = is_msat_request
@@ -439,6 +450,7 @@ def _extra_validator(is_request: bool):
"outpoint": is_outpoint,
"feerate": is_feerate,
"outputdesc": is_outputdesc,
+ "string_map": is_string_map,
})
return jsonschema.validators.extend(jsonschema.Draft7Validator,
Why this scored 15/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.