mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-03-04 03:03:51 +01:00
cln-grpc: Add result conversion generator to msggen
This takes the Rust bindings and converts them into the generated protobuf bindings: > JSON-RPC -> Rust bindings -> grpc bindings -> protobuf
This commit is contained in:
parent
4c105d2424
commit
8d3871d791
4 changed files with 276 additions and 2 deletions
|
@ -2,7 +2,9 @@ cln-grpc-wrongdir:
|
|||
$(MAKE) -C .. cln-grpc-all
|
||||
|
||||
CLN_GRPC_EXAMPLES :=
|
||||
CLN_GRPC_GENALL = cln-grpc/proto/node.proto
|
||||
CLN_GRPC_GENALL = cln-grpc/proto/node.proto \
|
||||
cln-grpc/src/convert.rs
|
||||
|
||||
DEFAULT_TARGETS += $(CLN_GRPC_EXAMPLES) $(CLN_GRPC_GENALL)
|
||||
|
||||
$(CLN_GRPC_GENALL): $(JSON_SCHEMA)
|
||||
|
|
170
cln-grpc/src/convert.rs
Normal file
170
cln-grpc/src/convert.rs
Normal file
|
@ -0,0 +1,170 @@
|
|||
|
||||
// This file was automatically derived from the JSON-RPC schemas in
|
||||
// `doc/schemas`. Do not edit this file manually as it would get
|
||||
// overwritten.
|
||||
|
||||
use std::convert::From;
|
||||
#[allow(unused_imports)]
|
||||
use cln_rpc::model::{responses,requests};
|
||||
use crate::pb;
|
||||
|
||||
#[allow(unused_variables)]
|
||||
impl From<&responses::GetinfoAddress> for pb::GetinfoAddress {
|
||||
fn from(c: &responses::GetinfoAddress) -> Self {
|
||||
Self {
|
||||
item_type: c.item_type as i32,
|
||||
port: c.port.into(),
|
||||
address: c.address.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
impl From<&responses::GetinfoBinding> for pb::GetinfoBinding {
|
||||
fn from(c: &responses::GetinfoBinding) -> Self {
|
||||
Self {
|
||||
item_type: c.item_type as i32,
|
||||
address: c.address.clone(),
|
||||
port: c.port.map(|v| v.into()),
|
||||
socket: c.socket.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
impl From<&responses::GetinfoResponse> for pb::GetinfoResponse {
|
||||
fn from(c: &responses::GetinfoResponse) -> Self {
|
||||
Self {
|
||||
id: hex::decode(&c.id).unwrap(),
|
||||
alias: c.alias.clone(),
|
||||
color: hex::decode(&c.color).unwrap(),
|
||||
num_peers: c.num_peers.clone(),
|
||||
num_pending_channels: c.num_pending_channels.clone(),
|
||||
num_active_channels: c.num_active_channels.clone(),
|
||||
num_inactive_channels: c.num_inactive_channels.clone(),
|
||||
version: c.version.clone(),
|
||||
lightning_dir: c.lightning_dir.clone(),
|
||||
blockheight: c.blockheight.clone(),
|
||||
network: c.network.clone(),
|
||||
fees_collected_msat: Some(c.fees_collected_msat.into()),
|
||||
address: c.address.iter().map(|s| s.into()).collect(),
|
||||
binding: c.binding.iter().map(|s| s.into()).collect(),
|
||||
warning_bitcoind_sync: c.warning_bitcoind_sync.clone(),
|
||||
warning_lightningd_sync: c.warning_lightningd_sync.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
impl From<&responses::ListfundsOutputs> for pb::ListfundsOutputs {
|
||||
fn from(c: &responses::ListfundsOutputs) -> Self {
|
||||
Self {
|
||||
txid: hex::decode(&c.txid).unwrap(),
|
||||
output: c.output.clone(),
|
||||
amount_msat: Some(c.amount_msat.into()),
|
||||
scriptpubkey: hex::decode(&c.scriptpubkey).unwrap(),
|
||||
address: c.address.clone(),
|
||||
redeemscript: c.redeemscript.as_ref().map(|v| hex::decode(&v).unwrap()),
|
||||
status: c.status as i32,
|
||||
blockheight: c.blockheight.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
impl From<&responses::ListfundsChannels> for pb::ListfundsChannels {
|
||||
fn from(c: &responses::ListfundsChannels) -> Self {
|
||||
Self {
|
||||
peer_id: hex::decode(&c.peer_id).unwrap(),
|
||||
our_amount_msat: Some(c.our_amount_msat.into()),
|
||||
amount_msat: Some(c.amount_msat.into()),
|
||||
funding_txid: hex::decode(&c.funding_txid).unwrap(),
|
||||
funding_output: c.funding_output.clone(),
|
||||
connected: c.connected.clone(),
|
||||
state: c.state as i32,
|
||||
short_channel_id: c.short_channel_id.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
impl From<&responses::ListfundsResponse> for pb::ListfundsResponse {
|
||||
fn from(c: &responses::ListfundsResponse) -> Self {
|
||||
Self {
|
||||
outputs: c.outputs.iter().map(|s| s.into()).collect(),
|
||||
channels: c.channels.iter().map(|s| s.into()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
impl From<&responses::ListchannelsChannels> for pb::ListchannelsChannels {
|
||||
fn from(c: &responses::ListchannelsChannels) -> Self {
|
||||
Self {
|
||||
source: hex::decode(&c.source).unwrap(),
|
||||
destination: hex::decode(&c.destination).unwrap(),
|
||||
public: c.public.clone(),
|
||||
amount_msat: Some(c.amount_msat.into()),
|
||||
message_flags: c.message_flags.into(),
|
||||
channel_flags: c.channel_flags.into(),
|
||||
active: c.active.clone(),
|
||||
last_update: c.last_update.clone(),
|
||||
base_fee_millisatoshi: c.base_fee_millisatoshi.clone(),
|
||||
fee_per_millionth: c.fee_per_millionth.clone(),
|
||||
delay: c.delay.clone(),
|
||||
htlc_minimum_msat: Some(c.htlc_minimum_msat.into()),
|
||||
htlc_maximum_msat: c.htlc_maximum_msat.map(|f| f.into()),
|
||||
features: hex::decode(&c.features).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
impl From<&responses::ListchannelsResponse> for pb::ListchannelsResponse {
|
||||
fn from(c: &responses::ListchannelsResponse) -> Self {
|
||||
Self {
|
||||
channels: c.channels.iter().map(|s| s.into()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
impl From<&responses::AddgossipResponse> for pb::AddgossipResponse {
|
||||
fn from(c: &responses::AddgossipResponse) -> Self {
|
||||
Self {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
impl From<&responses::AutocleaninvoiceResponse> for pb::AutocleaninvoiceResponse {
|
||||
fn from(c: &responses::AutocleaninvoiceResponse) -> Self {
|
||||
Self {
|
||||
enabled: c.enabled.clone(),
|
||||
expired_by: c.expired_by.clone(),
|
||||
cycle_seconds: c.cycle_seconds.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
impl From<&responses::CheckmessageResponse> for pb::CheckmessageResponse {
|
||||
fn from(c: &responses::CheckmessageResponse) -> Self {
|
||||
Self {
|
||||
verified: c.verified.clone(),
|
||||
pubkey: c.pubkey.as_ref().map(|v| hex::decode(&v).unwrap()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
impl From<&responses::CloseResponse> for pb::CloseResponse {
|
||||
fn from(c: &responses::CloseResponse) -> Self {
|
||||
Self {
|
||||
item_type: c.item_type as i32,
|
||||
tx: c.tx.as_ref().map(|v| hex::decode(&v).unwrap()),
|
||||
txid: c.txid.as_ref().map(|v| hex::decode(&v).unwrap()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
from msggen.model import Method, CompositeField, Service
|
||||
from msggen.grpc import GrpcGenerator
|
||||
from msggen.grpc import GrpcGenerator, GrpcConverterGenerator
|
||||
from msggen.rust import RustGenerator
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
@ -132,6 +132,10 @@ def gengrpc(service):
|
|||
fname = repo_root() / "cln-grpc" / "proto" / "node.proto"
|
||||
dest = open(fname, "w")
|
||||
GrpcGenerator(dest).generate(service)
|
||||
|
||||
fname = repo_root() / "cln-grpc" / "src" / "convert.rs"
|
||||
dest = open(fname, "w")
|
||||
GrpcConverterGenerator(dest).generate(service)
|
||||
def genrustjsonrpc(service):
|
||||
fname = repo_root() / "cln-rpc" / "src" / "model.rs"
|
||||
dest = open(fname, "w")
|
||||
|
|
|
@ -151,3 +151,101 @@ class GrpcGenerator:
|
|||
|
||||
for message in [f for f in fields if isinstance(f, CompositeField)]:
|
||||
self.generate_message(message)
|
||||
|
||||
|
||||
class GrpcConverterGenerator:
|
||||
def __init__(self, dest: TextIO):
|
||||
self.dest = dest
|
||||
self.logger = logging.getLogger("msggen.grpc.GrpcConversionGenerator")
|
||||
|
||||
def generate_array(self, prefix, field: ArrayField):
|
||||
if isinstance(field.itemtype, CompositeField):
|
||||
self.generate_composite(prefix, field.itemtype)
|
||||
|
||||
def generate_composite(self, prefix, field: CompositeField):
|
||||
"""Generates the conversions from JSON-RPC to GRPC.
|
||||
"""
|
||||
# First pass: generate any sub-fields before we generate the
|
||||
# top-level field itself.
|
||||
for f in field.fields:
|
||||
if isinstance(f, ArrayField):
|
||||
self.generate_array(prefix, f)
|
||||
|
||||
# And now we can convert the current field:
|
||||
self.write(f"""\
|
||||
#[allow(unused_variables)]
|
||||
impl From<&{prefix}::{field.typename}> for pb::{field.typename} {{
|
||||
fn from(c: &{prefix}::{field.typename}) -> Self {{
|
||||
Self {{
|
||||
""")
|
||||
|
||||
for f in field.fields:
|
||||
name = f.normalized()
|
||||
if isinstance(f, ArrayField):
|
||||
self.write(f"{name}: c.{name}.iter().map(|s| s.into()).collect(),\n", numindent=3)
|
||||
|
||||
elif isinstance(f, EnumField):
|
||||
self.write(f"{name}: c.{name} as i32,\n", numindent=3)
|
||||
|
||||
elif isinstance(f, PrimitiveField):
|
||||
typ = f.typename + ("?" if not f.required else "")
|
||||
# We may need to reduce or increase the size of some
|
||||
# types, or have some conversion such as
|
||||
# hex-decoding. Also includes the `Some()` that grpc
|
||||
# requires for non-native types.
|
||||
rhs = {
|
||||
'u8': f'c.{name}.into()',
|
||||
'u16': f'c.{name}.into()',
|
||||
'u16?': f'c.{name}.map(|v| v.into())',
|
||||
'msat': f'Some(c.{name}.into())',
|
||||
'msat?': f'c.{name}.map(|f| f.into())',
|
||||
'pubkey': f'hex::decode(&c.{name}).unwrap()',
|
||||
'pubkey?': f'c.{name}.as_ref().map(|v| hex::decode(&v).unwrap())',
|
||||
'hex': f'hex::decode(&c.{name}).unwrap()',
|
||||
'hex?': f'c.{name}.as_ref().map(|v| hex::decode(&v).unwrap())',
|
||||
'txid': f'hex::decode(&c.{name}).unwrap()',
|
||||
'txid?': f'c.{name}.as_ref().map(|v| hex::decode(&v).unwrap())',
|
||||
}.get(
|
||||
typ,
|
||||
f'c.{name}.clone()' # default to just assignment
|
||||
)
|
||||
self.write(f"{name}: {rhs},\n", numindent=3)
|
||||
|
||||
self.write(f"""\
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
|
||||
""")
|
||||
|
||||
def generate_requests(self, service):
|
||||
for meth in service.methods:
|
||||
req = meth.request
|
||||
self.generate_composite("requests", req)
|
||||
|
||||
def generate_responses(self, service):
|
||||
for meth in service.methods:
|
||||
res = meth.response
|
||||
self.generate_composite("responses", res)
|
||||
|
||||
def generate(self, service: Service) -> None:
|
||||
self.write("""
|
||||
// This file was automatically derived from the JSON-RPC schemas in
|
||||
// `doc/schemas`. Do not edit this file manually as it would get
|
||||
// overwritten.
|
||||
|
||||
use std::convert::From;
|
||||
#[allow(unused_imports)]
|
||||
use cln_rpc::model::{responses,requests};
|
||||
use crate::pb;
|
||||
|
||||
""")
|
||||
|
||||
self.generate_responses(service)
|
||||
|
||||
def write(self, text: str, numindent: int = 0) -> None:
|
||||
raw = dedent(text)
|
||||
if numindent > 0:
|
||||
raw = indent(text, " " * numindent)
|
||||
|
||||
self.dest.write(raw)
|
||||
|
|
Loading…
Add table
Reference in a new issue