Chopin
0x79ef...7c60

Architecture

Client-Side Scripts

There are 3 distinct scripts that run on the client side. The first is very straightforward, it is the script that loads the service worker and web worker. The latter two will be described in detail below.

Service Worker

The Service Worker intercepts all POST, PUT, PATCH, and DELETE requests. It does not intercept GET, OPTIONS, HEAD by default—but can be configured to. The goal is to ensure the sequencer processes all request that mutate state, while letting all state queries pass through directly.

After intercepting the request, its purpose is to do 2 things:

  1. Sign the HTTP request on the client-side. The HTTP requests is serialized and a signature is created with a wallet running on the user's device.
  2. Forward the request to the Web Worker. The Service Worker sends the request to the Web Worker through a Message Channel, which sends it to the sequencer and waits for a response. Ultimately, sending the response back to the user.

Web Worker

The web worker's job is to connect to the sequencer via WebSockets. This is an optional piece of the Chopin stack that helps cut down on infrastructure costs and reduce the number of timeouts experienced by users.

Here is why: All requests in Chopin are executed sequentially, so at times of high traffic, there can be increased latency on HTTP POST/PUT/PATCH/DELETE requests. Since developers usually host their backends on serverless infrastructure, its is costly to keep these requests running for a long-time.

Instead, the Chopin Frontend maintains an open WebSocket connection which is very cheap/free, and only initiates the HTTP request at the time it is authorized to do so.

HTTP Requests

HTTP headers are serialized to a specific JSON format and then signed (see next section). They are also serialized with protobuf when they are stored on the Celestia blockchain.

Several headers are added to HTTP requests.

Authorization

The format of the value is currently "Bearer " + signature. This will likely change to use JWTs soon, but will fundamentally work in the same way.

x-nonce

This is a unique string that can act as an identifier for a given request. It is currently generated using crypto.randomUUID(). Some nonces can simply be any value, like 0x01 however that would be unacceptable in this case, because the nonce should be globally unique. In the future, this assumption will likely be removed.

x-sequencer

This contains the a secret string only known by the sequencer and the full node. When the sequencer adds this to the header, the full node knows to run the request immediately. Otherwise, the incoming request is sent to the sequencer to be added in the queue.

Signatures

Signatures are structured in the EIP-712 format so that wallets are able to present the content in a readable way (rather than just a byte string).

Signing Server

The signing server is the default wallet provider for Chopin-based apps. It utilizes Capsule's pregenerated MPC wallets, and is able to sign payloads via an API. The API spec will be published in the near future.

The signing server is able to elevate the UX even further by signing transactions after they have been executed on the full node.

Users that wish to take full control over their wallet are able to then claim their wallet through Capsule's interface, or export their keys to another wallet—without changing addresses or creating a new account.

Right now this is a completely managed service by Modular Cloud, but an open source version will be made available soon.

Middleware

The middleware executes before the backend. It ensures the following:

  • If a signature is attached, it gets the address and passes it to the backend along with the request.
  • If the signature is invalid, it will reject the request.
  • If there is no signature and the request is not coming from the sequencer, it create an embedded wallet on-the-fly. This is how you get an address upon initially loading the website. The session details are stored in a cookie.
  • If the request comes from the sequencer, it will execute it immediately. However, if it does not, it will forward it to the sequencer and await the response.
  • It injects the client scripts into your app at certain paths, so you can easily host them from your own domain without any extra setup.

Sequencer

The sequencer accepts HTTP and WebSocket connections. It takes incoming requests, runs them through a queue, and executes them sequentially on the backend. It also listens for context from the oracle module.

After the requests have been executed, serialized them—along with their context—into blocks and adds them to Celestia using a PayForBlobs transaction.

Request to a localhost URL work slightly differently. These are useful for development only and will not be added to the blockchain. But also, they are sent back to the Service Worker to be executed client-side (in the correct order), in case the sequencer does not have access to the intended destination.

Right now this is a completely managed service by Modular Cloud, but an open source version will be made available soon.

Oracle Module

The Oracle modules wraps functions that have non-deterministic results such as getting the current time, random numbers, or network requests. By wrapping the call in a function like oraclize(Date.now()) this non-deterministic output is able to be replicated the next time the request is replayed by a validating full node.