Skip to content

Commit

Permalink
More documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
tuler committed Aug 20, 2024
1 parent 4c85308 commit 0ee8b8a
Show file tree
Hide file tree
Showing 23 changed files with 4,833 additions and 220 deletions.
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,11 @@ const app = createApp({

// Handle input encoded in hex
app.addAdvanceHandler(async ({ payload }) => {
const hexString = payload.replace(/^0x/, "");
const buffer = Buffer.from(hexString, "hex");
// read payload as string
const str = hexToString(payload);

// Convert the buffer to a UTF-8 string
const utf8String = buffer.toString("utf8");
console.log(utf8String);
return Promise.resolve("accept");
// create a notice with the string in uppercase
await app.createNotice({ payload: stringToHex(str.toUpperCase()) });
});

// Start the application
Expand All @@ -71,6 +69,8 @@ prompt-1 | Anvil running at http://localhost:8545
prompt-1 | GraphQL running at http://localhost:8080/graphql
prompt-1 | Inspect running at http://localhost:8080/inspect/
prompt-1 | Explorer running at http://localhost:8080/explorer/
prompt-1 | Bundler running at http://localhost:8080/bundler/rpc
prompt-1 | Paymaster running at http://localhost:8080/paymaster/
prompt-1 | Press Ctrl+C to stop the node
```

Expand Down Expand Up @@ -113,6 +113,8 @@ prompt-1 | Anvil running at http://localhost:8545
prompt-1 | GraphQL running at http://localhost:8080/graphql
prompt-1 | Inspect running at http://localhost:8080/inspect/
prompt-1 | Explorer running at http://localhost:8080/explorer/
prompt-1 | Bundler running at http://localhost:8080/bundler/rpc
prompt-1 | Paymaster running at http://localhost:8080/paymaster/
prompt-1 | Press Ctrl+C to stop the node
validator-1 | [INFO rollup_http_server::http_service] Received new request of type ADVANCE
validator-1 | [INFO actix_web::middleware::logger] 127.0.0.1 "POST /finish HTTP/1.1" 200 224 "-" "undici" 0.000960
Expand All @@ -125,7 +127,7 @@ Now you're ready to start building your Cartesi application with cartesi and der

### Requirements

- Corepack (with pnpm) or pnpm v8 (8.7.1 recommended)
- Corepack (with pnpm) or pnpm v9 (9.7.1 recommended)
- Node 20 or greater (LTS)

### Installation
Expand All @@ -148,7 +150,7 @@ pnpm run build

## How to contribute

TODO
Try to follow deroll conventions and patterns, open a ticket to discuss ideas, or open a PR.

## License

Expand Down
3 changes: 0 additions & 3 deletions apps/docs/docs/pages/getting-started.mdx

This file was deleted.

11 changes: 10 additions & 1 deletion apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "@deroll/docs",
"version": "0.0.0",
"type": "module",
"private": true,
"scripts": {
"dev": "vocs dev",
"build": "vocs build",
Expand All @@ -11,7 +12,15 @@
"@types/react": "latest",
"react": "latest",
"react-dom": "latest",
"typescript": "latest",
"vocs": "latest"
},
"devDependencies": {
"@deroll/app": "workspace:*",
"@deroll/core": "workspace:*",
"@deroll/router": "workspace:*",
"@deroll/wallet": "workspace:*",
"@types/node": "^22.4.1",
"typescript": "^5.5.4",
"viem": "^2.19.8"
}
}
93 changes: 93 additions & 0 deletions apps/docs/pages/advance-handlers.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Advance handlers

## Overview

Advance handlers are async function callbacks that receive the `metadata` and `payload` of an input, and must return either `"accept"` or `"reject"`.
The following example defines a request handler that accepts all requests, but just logs the input metadata.

```ts twoslash
import { createApp } from "@deroll/app";
import { AdvanceRequestHandler } from "@deroll/core";

const handler: AdvanceRequestHandler = async ({ metadata, payload }) => {
console.log(metadata);
// @noErrors
return "
// ^|
}

// create application
const app = createApp({ url: "http://127.0.0.1:5004" });
app.addAdvanceHandler(handler);
```

## Handlers Execution

The application created with `createApp` holds a list of advance handlers that are executed in the order they were added.

By default, when a handler returns `"accept"` the execution chain stops.

```ts twoslash
import { createApp } from "@deroll/app";

// create application
const app = createApp({ url: "http://127.0.0.1:5004" });

// ---cut---
app.addAdvanceHandler(async (data) => {
console.log(data);
return "accept";
});

app.addAdvanceHandler(async (data) => {
// this code is never executed, because the first handler always returns "accept" // [!code hl]
return "accept";
});
```

If a handler returns `"reject"` or **raises an exception**, the next handler in the list is executed.

```ts twoslash
import { createApp } from "@deroll/app";

// create application
const app = createApp({ url: "http://127.0.0.1:5004" });

// ---cut---
app.addAdvanceHandler(async (data) => {
console.log(data);
return "reject";
});

app.addAdvanceHandler(async (data) => {
// this code will execute // [!code hl]
return "accept";
});
```

If no handler returns `"accept"` the input is rejected and the machine is reverted to the initial state by the Cartesi node.

## Broadcast Mode

If the application is created with the `broadcastAdvanceRequests` option set to `true`, then all handlers are executed regardless the return value of each handler.

```ts twoslash
import { createApp } from "@deroll/app";

// create application
const app = createApp({
url: "http://127.0.0.1:5004",
broadcastAdvanceRequests: true
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
});

app.addAdvanceHandler(async (data) => {
console.log(data);
return "accept";
});

app.addAdvanceHandler(async (data) => {
// this code executes, even when the first handler returns "accept" // [!code hl]
return "accept";
});
```
7 changes: 7 additions & 0 deletions apps/docs/pages/app/create-app.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# createApp

## Usage

## Returns

## Parameters
4 changes: 4 additions & 0 deletions apps/docs/pages/app/overview.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# App

## @deroll/app

36 changes: 36 additions & 0 deletions apps/docs/pages/application.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Application

A deroll `App` is responsible for the main loop of the Cartesi application.
It fetches advance requests (inputs) sent to the application, handling them to [AdvanceHandler's](./advance-handlers), and inspect requests to [InspectHandler's](./inspect-handlers).

The source of these requests is usually the rollups http server that runs inside the Cartesi Machine, which fetches requests from the outside using Cartesi I/O primitives.

You can construct an `App` that communicates with the rollups http server using the [createApp](./app/create-app) function.
You must specify the URL of the http server, which by default is set in the application Dockerfile as the environment variable `ROLLUP_HTTP_SERVER_URL=http://127.0.0.1:5004`.

```ts twoslash
/// <reference types="node" />
// ---cut---
import { createApp } from "@deroll/app";

// create application
const app = createApp({ url: "http://127.0.0.1:5004" }); // [!code focus]
```

You then call the `app.start()` method. That will enter in a continuous loop, fetching requests and handling them to the application request handlers.

```ts twoslash
/// <reference types="node" />
// ---cut---
import { createApp } from "@deroll/app";

// create application
const app = createApp({ url: "http://127.0.0.1:5004" });

// start app // [!code ++]
app.start().catch((e) => process.exit(1)); // [!code ++]
```

In the example above we are catching exceptions and exiting the application. Keep in mind that exiting a Cartesi rollups application means that the rollups is terminated forever.

To do anything useful the application must implement [AdvanceHandler's](./advance-handlers) and [InspectHandler's](./inspect-handlers), which is the subject of the following sections.
99 changes: 99 additions & 0 deletions apps/docs/pages/data-encoding.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Data Encoding

Data processed by a Cartesi application, inputs and outpus, contains a generic binary payload, which when needed by text transport methods (like JSON-RPC) is serialized as a hex-encoded string.
The encoding and semantics of those payloads are defined by the application itself, which can use string encodings, like UTF-8, JSON structures, or binary encodings like [ABI](https://docs.soliditylang.org/en/latest/abi-spec.html) or [Protobuf](https://protobuf.dev/).

## String encoding

There are several data description formats that use UTF-8 strings as the base format. Those include JSON, XML, and others.

You can use the viem [hexToString](https://viem.sh/docs/utilities/fromHex#hextostring) and [stringToHex](https://viem.sh/docs/utilities/toHex#stringtohex) utility methods to handle conversion between string and hex-encoded binary strings.

The following example takes a JavaScript object and converts it to a hex-encoded string to be sent as input payload.

```ts twoslash
import { stringToHex } from "viem";
const payload = stringToHex(JSON.stringify({ key: "value" }));
// 0x7b226b6579223a2276616c7565227d
```

If you send that payload as input to your Cartesi application you can decode it back to a JavaScript object using the following code:

```ts twoslash
import { hexToString } from "viem";
const obj = JSON.parse(hexToString("0x7b226b6579223a2276616c7565227d"));
// { key: 'value' }
```

## ABI encoding

ABI is also a popular encoding choice for binary data, especially because of its extensive use in Solidity, which doesn't have good string handling capabilities.

Cartesi portals and relays smart contracts use ABI encoding, and you will most likely need to learn how to decode ABI encoded data anyway.

Data can be encoding by specifying the types of the data and the data itself. The following example shows how to encode a string, a uint and a bool value using the [encodeAbiParameters](https://viem.sh/docs/abi/encodeAbiParameters#encodeabiparameters) utility function.

```ts twoslash
import { encodeAbiParameters, parseAbiParameters } from 'viem'

const encodedData = encodeAbiParameters(
parseAbiParameters('string x, uint y, bool z'),
['wagmi', 420n, true]
)
// 0x000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001a4000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000057761676d69000000000000000000000000000000000000000000000000000000
```

You can also use the [encodeFunctionData](https://viem.sh/docs/contract/encodeFunctionData#encodefunctiondata) utility function to encode a "function name" plus its parameters, and defined an "Application ABI" that you can use in an [advance handler](./advance-handlers).

Your application can define an API like the one below:

```ts twoslash
import { parseAbi } from "viem";

// define application ABI
const abi = parseAbi([
"function attackDragon(uint256 dragonId, string weapon)",
"function drinkPotion()",
]);
```

Notice how the `abi` is properly typed. Then on an advance handler you can decode some payload and do a type-checked switch case like in the full example below.

```ts twoslash
/// <reference types="node" />
// ---cut---
import { createApp } from "@deroll/app";
import { decodeFunctionData, parseAbi } from "viem";

// create application
const app = createApp({ url: "http://127.0.0.1:5004" });

// define application ABI
const abi = parseAbi([
"function attackDragon(uint256 dragonId, string weapon)",
"function drinkPotion()",
]);

// handle input encoded as ABI function call
app.addAdvanceHandler(async ({ payload }) => {
const { functionName, args } = decodeFunctionData({ abi, data: payload });

switch (functionName) {
// ^?
case "attackDragon":

// see how `args` is properly typed thanks to TypeScript inferring
const [dragonId, weapon] = args;
// ^?
console.log(`attacking dragon ${dragonId} with ${weapon}...`);
return "accept";

case "drinkPotion":
console.log(`drinking potion...`);
return "accept";
}
});

// start app
app.start().catch((e) => process.exit(1));
```
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { HomePage } from 'vocs/components'
<HomePage.InstallPackage name="@deroll/app" type="init" />
<HomePage.Description>deroll is a set of libraries that helps the development of Cartesi applications, both on the backend and on the frontend.</HomePage.Description>
<HomePage.Buttons>
<HomePage.Button href="/getting-started" variant="accent">Get started</HomePage.Button>
<HomePage.Button href="/quick-start" variant="accent">Get started</HomePage.Button>
<HomePage.Button href="https://github.com/tuler/deroll">GitHub</HomePage.Button>
</HomePage.Buttons>
</HomePage.Root>
45 changes: 45 additions & 0 deletions apps/docs/pages/inspect-handlers.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Inspect handlers

## Overview

Inspect handlers are async function callbacks that receive the `payload` of an inspect request, and has no return value.
The following example defines an inspect handler that logs the payload, decode it as a string and add a report with the string uppercase value.

```ts twoslash
import { createApp } from "@deroll/app";
import { InspectRequestHandler } from "@deroll/core";
import { stringToHex, hexToString } from "viem";

const handler: InspectRequestHandler = async ({ payload }) => {
console.log(payload);
const str = hexToString(payload);
await app.createReport({ payload: stringToHex(str.toUpperCase()) });
}

// create application
const app = createApp({ url: "http://127.0.0.1:5004" });
app.addInspectHandler(handler);
```

Reports are the only kind of output that inspect handlers should create.
The state of the Cartesi machine as a whole is not modified on an inspect request, so you should only use this kind of request handler to return application state (and not modify the state).

Another useful use case of inspects is to simulate the outcome of an input, like doing some kind of validation logic, and returning the expected result.

Inspect requests are made directly to the Cartesi node through an HTTP GET request to the `/inspect` endpoint.
The following `curl` command creates an inspect request to the local node executed by `cartesi run`, and calls the above handler.

```shell
$ curl http://localhost:8080/inspect/tuler
{"status":"Accepted","exception_payload":null,"reports":[{"payload":"0x54554c4552"}],"processed_input_count":0}
```

Let's use some command line utilities, `jq` and `xxd`, to decode the report payload returned:

```shell
$ curl -slL http://localhost:8080/inspect/tuler | jq -r '.reports[0].payload' | xxd -r -p
TULER
```

We'll leave as an exercise to the reader to use the `fetch` API, and the payload conversion APIs to implement inspect requests using TypeScript.
More details about inspect requests check the [official documentation](https://docs.cartesi.io/cartesi-rollups/1.5/development/send-requests/#advance-and-inspect-requests).
Loading

0 comments on commit 0ee8b8a

Please sign in to comment.