Skip to content

Instantly share code, notes, and snippets.

@insipx
Last active November 30, 2023 19:25
Show Gist options
  • Save insipx/0835567a92e1bf0df1b2b24297c084e4 to your computer and use it in GitHub Desktop.
Save insipx/0835567a92e1bf0df1b2b24297c084e4 to your computer and use it in GitHub Desktop.

Modular Gateway


  • Problem:
    • Need to build Resolver, Inbox and L2 Node services but provide a consistent API for libXMTP that is quickly iterable on both ends (Gateways and libxmtp)
  • Need:
    • Easy to create more than one gateway

L2 Node probably Optimism

  • Uses: JSONRPC to access on-chain state

Inbox

  • Uses: TBA - EthersRS uses JSON RPC to access chain state, although it is abstracted away

Resolver

  • Uses: TBA - Needs to use EthersRS for inbox contract too

libgateway -> client wrapper around Inbox, Resolver, and L2 Node Interfaces

Each service wants to define it's own endpoints, but libXMTP will need to use all these services regardless. So no matter what we do, we end up building some kind of API library to interact with the services

  • make an API client library crate to combine services and provide a consistent interface for libXMTP

libXMTP and everything currently use ProtoBufs

  • Is there any reason NOT to use protobufs?
    • JSONRPC is standard across the blockchain world
    • JSONRPC is easily composable and understandable; can be queried from the commandline without extra steps
    • JSONRPC is used by L2 node, Eth, etc, so we're already importing it in our prototype/demo examples, and require it for blockchain interaction anyway
    • JSONRPC is supported well in rust via jsonrpsee
    • Better Rust WASM Support in JSONRPC (simple HTTP or WebSockets Requests)
  • For Protobufs
    • Protobufs define a standard to adhere to, already used in libxmtp
    • one impl to rule them all, and very well supported and battle tested (Google)

As far as gateway modularization goes, i think it depends on a few directions we want to go. There are a some things that can be agreed upon:

- libXMTP needs one interface/client to access everything defined in these gateways, as it does today with Waku via gRPC.
    - implies the creation of an api_client within libxmtp or as a crate regardless  -- the 'Frontend'
- Each gateway defines custom functionality that needs to be queried somehow by the client 
- We need JSONRPC no matter what, as the native transport of Ethereum/Optimism/L2 chains, and already used under-the-hood in ethers

I think there's a few ways to about this,

  • Make all functionality defined by resolver, postal_service a library, one monolithic crate defining a standard server API libxmtp client interacts with.
    • Pros:
      • Endpoint configuration happens in one place
    • Cons:
      • Multiple PRs to change or add a feature (not unlike what we have with gRPC today), but with the added complexity of a PR
  • Each gateway defines endpoints separately, combine implementations into one Server, or start a server per-service
    • Pros:
      • still monolithic server from view of client
      • able to iterate within each gateway faster
      • Services which are less coupled
    • Cons:
      • No one standard view of our d14n API, discoverability is worse
      • could become confusing

Problem


solution

JsonRPC Modular Gateway Design

DID Registry Service Definition Example

file: rpc-definitions/did.rs OR didethresolver repository

use jsonrpsee::*;
    [rpc(server, client, namespace = "did")]
    pub trait DIDRegistry {
        [method(name = "resolveDid")]
        async fn resolveDid(&self, publicKey: String) -> ResultDidDocument, Error;
    }

file: rpc-impls/did.rs OR didethresolver repository

Service Implementation Example

use jsonrpsee::*;
use didethresolver::resolve_did;

pub struct DidRegistryMethods;
impl DidRegistryServer for DidRegistryMethods {

    async fn resolveDid(&self, publicKey: String) -> ResultDidDocument, Error {
        resolve_did(publicKey)
    }
}

Postal Service Definition Example

file: rpc-definitions/postal_service.rs OR postal_service repository

    [rpc(server, client, namespace = "postalService")]
    pub trait PostalService {
        #[method(name = "sendMessage")]
        async fn send_message(&self, message: String) -> Result(), Error;
    }

Postal Service Implementation Example

file: rpc-impls/postal_service.rs OR postal_service repository

use postal_service::send_message;

pub struct PostalServiceMethods;
impl PostalServiceServer for PostalServiceMethods {
    async fn send_message(&self, message: String) -> Result(), Error {
        send_message(message);
    }
}

Server Definition Example

in own separate crate, or within each gateway crate as needed or until complexity requires otherwise

use jsonrpsee::[RpcModule, Server](RpcModule, Server);
use crate::rpc_impls::*;

// could automatically enumerate methods on the server at runtime, depending on configuration.
pub async fn run_server_both() {
    let rpc = RpcModule::new()
    rpc.merge(DidRegistryMethods);
    rpc.merge(PostalServiceMethods);
    let server = Server::builder().build("127.0.0.1:8000").await?;

    let addr = server.local_addr()?;
    let handle = server.start(rpc);
}

pub async fn run_did() {
    let rpc = RpcModule::new();
    rpc.merge(DidRegistryMethods);
    let server = Server::builder().build("127.0.0.1:8000").await?;

    let addr = server.local_addr()?;
    let handle = server.start(rpc);
}

pub async fn run_postal_service() {
    let rpc = RpcModule::new();
    rpc.merge(PostalServiceMethods);
    let server = Server::builder().build("127.0.0.1:8000").await?;

    let addr = server.local_addr()?;
    let handle = server.start(rpc);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment