docs: Add docs on code generation

This commit is contained in:
Christian Decker 2022-12-20 13:05:02 +01:00 committed by Rusty Russell
parent 7153beff28
commit f4efe6c899

View File

@ -0,0 +1,138 @@
# Code Generation
The CLN project has a multitude of interfaces, most of which are
generated from an abstract schema:
- Wire format for peer-to-peer communication: this is the binary
format that is specific by the [LN spec][spec]. It uses the
[generate-wire.py][generate-wire.py] script to parse the (faux) CSV
files that are automatically extrated from the specification and
writes C source code files that are then used internally to encode
and decode messages, as well as provide print functions for the
messages.
- Wire format for inter-daemon communication: CLN follows a
multi-daemon architecture, making communication explicit across
daemons. For this inter-daemon communication we use a slightly
altered message format from the [LN spec][spec]. The changes are 1)
addition of FD passing semantics to allow establishing a new
connection between daemons (communication uses
[socketpair][socketpair]s, so no `connect`), and 2) change the
message length prefix from `u16` to `u32`, allowing for messages
larger than 65Kb. The CSV files are with the respective sub-daemon
and also use [generate-wire.py][generate-wire.py] to generate
encoding, decoding and printing functions.
- We describe the JSON-RPC using [JSON Schema][jschema] in the
[`doc/schemas`][doc-schemas] directory. Each method has a
`.request.json` for the request message, and a `.schema.json` for
the response (the mismatch is historical and will eventually be
addressed). During tests the `pytest` target will verify responses,
however the JSON-RPC methods are _not_ generated (yet?). We do
generate various client stubs for languages, using the
[`msggen`][msggen] tool. More on the generated stubs and utilities
below.
## Man pages
The [manpages][man] are partially generated from the JSON schemas
using the [`fromschema`][fromschema] tool. It reads the request schema
and fills in the manpage between two markers:
```markdown
[comment]: # (GENERATE-FROM-SCHEMA-START)
...
[comment]: # (GENERATE-FROM-SCHEMA-END)
```
!!! note
Some of this functionality overlaps with [`msggen`][msggen] (parsing the Schemas)
and [blockreplace.py][blockreplace.py] (filling in the template). It
is likely that this will eventually be merged.
[blockreplace.py]: https://github.com/ElementsProject/lightning/blob/master/devtools/blockreplace.py
[man]: ../../reference/
[fromschema]: https://github.com/ElementsProject/lightning/blob/master/tools/fromschema.py
## `msggen`
`msggen` is used to generate JSON-RPC client stubs, and converters
between in-memory formats and the JSON format. In addition, by
chaining some of these we can expose a [grpc][grpc] interface that
matches the JSON-RPC interface. This conversion chain is implemented
in the [grpc-plugin][grpc-plugin]
<figure markdown>
```mermaid
graph LR
A[JSON schema];
A --> B[cln-rpc];
B --> B1[Request Structs];
B --> B2[Response Structs];
B --> B3[Method stubs];
A --> C[cln-grpc];
C --> C1[Protobuf File];
C --> C2[In-memory conversion];
C --> C3[Service Implementation];
```
<figcaption>Artifacts generated from the JSON Schemas using `msggen`</figcaption>
</figure>
### `cln-rpc`
We use `msggen` to generate the Rust bindings crate
[`cln-rpc`][cln-rpc]. These bindings contain the stubs for the
JSON-RPC methods, as well as types for the request and response
structs. The [generator code][cln-rpc-gen] maps each abstract JSON-RPC
type to a Rust type, minimizing size (e.g., binary data is
hex-decoded).
The calling pattern follows the `call(req_obj) -> resp_obj` format,
and the individual arguments are not expanded. For more ergonomic
handling of generic requests and responses we also define the
`Request` and `Response` enumerations, so you can hand them to a
generic function without having to resort to dynamic dispatch.
The remainder of the crate implements an async/await JSON-RPC client,
that can deal with the Unix Domain Socket [transport][man:json-rpc]
used by CLN.
### `cln-grpc`
The `cln-grpc` crate is mostly used to provide the primitives to build
the `grpc-plugin`. As mentioned above, the grpc functionality relies on a chain of generated parts:
- First `msggen` is used to generate the [protobuf file][proto],
containing the service definition with the method stubs, and the types
referenced by those stubs.
- Next it generates the `convert.rs` file which is used to convert
the structs for in-memory representation from `cln-rpc` into the
corresponding protobuf structs.
- Finally `msggen` generates the `server.rs` file which can be bound
to a grpc endpoint listening for incoming grpc requests, and it
will convert the request and forward it to the JSON-RPC. Upon
receiving the response it gets converted back into a grpc response
and sent back.
```mermaid
graph LR
A[grpc client] --> B[grpc server] -->|convert.rs| C[cln-rpc] --> D[lightningd];
D --> C -->|convert.rs| B --> A;
```
[proto]: https://github.com/ElementsProject/lightning/blob/master/cln-grpc/proto/node.proto
[man:json-rpc]: ../../lightningd-rpc.7.md
[cln-rpc-gen]: https://github.com/ElementsProject/lightning/blob/master/contrib/msggen/msggen/gen/rust.py
[spec]: https://github.com/lightning/bolts
[generate-wire.py]: https://github.com/ElementsProject/lightning/blob/master/tools/generate-wire.py
[socketpair]: https://man7.org/linux/man-pages/man2/socketpair.2.html
[jschema]: https://json-schema.org/
[doc-schemas]: https://github.com/ElementsProject/lightning/tree/master/doc/schemas
[msggen]: https://github.com/ElementsProject/lightning/tree/master/contrib/msggen
[grpc]: https://grpc.io/
[cln-grpc]: https://docs.rs/cln-grpc/0.1.1/cln_grpc/
[grpc-plugin]: https://github.com/ElementsProject/lightning/tree/master/plugins/grpc-plugin
[cln-rpc]: https://github.com/ElementsProject/lightning/tree/master/cln-rpc