-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
23 changed files
with
4,833 additions
and
220 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; | ||
}); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# createApp | ||
|
||
## Usage | ||
|
||
## Returns | ||
|
||
## Parameters |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# App | ||
|
||
## @deroll/app | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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). |
Oops, something went wrong.