Skip to content

[draft] docs(api): graphql reference update #247

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: release-v2
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
id: delegate-call-vouchers
title: Delegate Call Vouchers
---

:::danger Security Considerations
Delegate Call Vouchers are a powerful feature that should be used with extreme caution. Incorrect implementation can lead to serious security vulnerabilities as the target contract's code has full access to the Application contract's storage and funds.
:::

Delegate Call Vouchers are an extension of vouchers that enables advanced smart contract interactions through the [`DELEGATECALL](https://www.evm.codes/?fork=cancun#f4) opcode.

Unlike regular vouchers, delegate call vouchers allow dApps to separate their execution logic from their storage context. When using delegate calls, the Application contract always maintains the storage, context, and funds (both ETH and tokens), while the target contract provides only the execution logic. This separation enables more flexible and reusable smart contract patterns while keeping all state changes and assets within the Application contract.

When a delegate call voucher is executed through the Application contract, the code at the target address is executed with the following characteristics:

- All storage operations occur in the Application contract's storage space
- All funds (ETH and tokens) remain in and are managed by the Application contract
- The msg.sender and msg.value from the original transaction are preserved
- The execution logic comes from the target contract, but operates on the Application contract's state and funds

This mechanism, where the Application contract maintains the state and funds while borrowing logic from other contracts, enables powerful patterns such as:

- **Paid Vouchers**: Vouchers that provide payment to the user who executes them.

- **Future Vouchers**: Vouchers that are time-locked and can only be executed after a specific timestamp.

- **Expirable Vouchers**: Vouchers that have an expiration timestamp, after which they can no longer be executed.

- **Targeted Vouchers**: Vouchers that are restricted to execution by specific addresses or a list of authorized addresses.

- **Atomic Vouchers**: A sequence of message calls that must be executed in order, ensuring atomicity of the operations.

- **Re-executable Vouchers**: Vouchers that can be executed multiple times, unlike standard vouchers which can only be executed once.

- **Ordered Vouchers**: Vouchers that must be executed in a specific sequence. For example, voucher A can only be executed after voucher B has been executed.

The [`Application`](../contracts/application.md) contract handles the execution of delegate call vouchers through its [`executeOutput()`](../../contracts/application/#executeoutput) function, which validates and processes the delegate call operation on the blockchain.

## Implementation Considerations

When implementing delegate call vouchers, consider the following:

1. **Storage Layout**: Since all storage operations happen in the Application contract, the storage layout of the target contract must be compatible with the Application contract's layout to prevent unintended storage collisions.

2. **Security**: Since delegate calls execute code in the context of the Application contract, careful validation of the target contract and its code is essential to prevent malicious modifications to the Application's state.

3. **State Management**: All state changes occur in the Application contract's storage, making it the single source of truth for the application's state.

:::note create a delegate call voucher
[Refer to the documentation here](../../development/asset-handling.md) for implementing delegate call vouchers in your dApp.
:::

## Execution Context

In a delegate call voucher execution:

- The Application contract provides the execution context and storage
- The target contract provides only the logic to be executed
- All storage operations affect the Application contract's state
- msg.sender and msg.value from the original transaction are preserved

This architecture, where the Application contract maintains all state while being able to execute logic from other contracts, makes delegate call vouchers particularly useful for customizable logics while keeping all application state centralized in the Application contract.

## Epoch Configuration

An epoch refers to a specific period during which a batch of updates is processed off-chain, and upon agreement by validators, the finalized state is recorded on-chain.

Epoch Length is the number of blocks that make up an epoch. It determines how long each epoch lasts in terms of block counts. For instance, if an epoch length is set to 7200 blocks, the epoch will end once 7200 blocks have been processed. This length directly influences how frequently updates are finalized and recorded on the blockchain.

Delegate call vouchers, like regular vouchers, are executed on the blockchain upon the closure of the corresponding epoch. This ensures that all state changes and logic executions are properly validated and recorded in the blockchain.

You can manually set the epoch length to facilitate quicker execution of delegate call vouchers during development.

:::note epoch duration
[Refer to the documentation here](../../development/cli-commands.md/#run) to manually configure epoch length during development.
:::
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
---
id: exception
title: Exception
---

The `/exception` endpoint is used to register an exception when the dApp cannot proceed with request processing. This should be the last method called by the dApp backend while processing a request.

When an exception occurs during request processing, the dApp backend should:
1. Call the `/exception` endpoint with a payload describing the error
2. Not make any further API calls after registering the exception
3. Exit the processing loop

The Rollup HTTP Server will:
- Skip the input with the reason [`EXCEPTION`](../graphql/enums/completion-status.md)
- Forward the exception message
- Return status code 200

The exception payload should be a hex-encoded string starting with '0x' followed by pairs of hexadecimal numbers.

Let's see how a Cartesi dApp's backend handles exceptions:

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

<Tabs>
<TabItem value="JavaScript" label="JavaScript" default>
<pre><code>

```javascript
async function handle_advance(data) {
console.log("Received advance request data " + JSON.stringify(data));

try {
// Process the request
// ...
} catch (error) {
// Register exception and exit
await fetch(rollup_server + "/exception", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
payload: "0x" + Buffer.from(error.message).toString("hex"),
}),
});
process.exit(1);
}

return "accept";
}
```

</code></pre>
</TabItem>

<TabItem value="Python" label="Python" default>
<pre><code>

```python
def handle_advance(data):
logger.info(f"Received advance request data {data}")

try:
# Process the request
# ...
except Exception as e:
# Register exception and exit
response = requests.post(
rollup_server + "/exception",
json={"payload": "0x" + e.message.encode("utf-8").hex()},
)
logger.info(
f"Received exception status {response.status_code} body {response.content}"
)
sys.exit(1)

return "accept"
```

</code></pre>
</TabItem>

</Tabs>

## Notes

- This endpoint should only be called when the dApp cannot proceed with request processing
- After calling this endpoint, the dApp should not make any further API calls
- The exception payload should be a hex-encoded string starting with '0x'
- An empty payload is represented by the string '0x'
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
---
id: finish
title: Finish
---

The `/finish` endpoint is used to indicate that any previous processing has been completed and the backend is ready to handle the next request. The subsequent request is returned as the call's response.

The dApp backend should call the `/finish` endpoint to start processing rollup requests. The Rollup HTTP Server returns the next rollup request in the response body.

The possible values for the `request_type` field are:
- `'advance_state'` - For requests that modify the dApp state
- `'inspect_state'` - For read-only queries about the dApp state

For advance-state requests, the input data contains:
- The advance-state metadata (including the account address that submitted the input)
- The payload

For inspect-state requests, the input data contains only the payload.

After processing a rollup request, the dApp backend should call the `/finish` endpoint again. For advance-state requests, depending on the processing result, it should set the `status` field to either `'accept'` or `'reject'`. The status field is ignored for inspect-state requests.

If an advance-state request is rejected:
- Any vouchers and notices generated during processing are discarded
- Reports are not discarded
- The state is reverted to its previous condition

During a finish call, the next rollup request might not be immediately available. In this case:
- The Rollup HTTP Server returns status code 202
- When receiving status 202, the dApp backend should retry the finish call with the same arguments

Let's see how a Cartesi dApp's backend processes requests using the finish endpoint:

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

<Tabs>
<TabItem value="JavaScript" label="JavaScript" default>
<pre><code>

```javascript
const rollup_server = process.env.ROLLUP_HTTP_SERVER_URL;
console.log("HTTP rollup_server url is " + rollup_server);

async function handle_advance(data) {
console.log("Received advance request data " + JSON.stringify(data));
return "accept";
}

async function handle_inspect(data) {
console.log("Received inspect request data " + JSON.stringify(data));
return "accept";
}

var handlers = {
advance_state: handle_advance,
inspect_state: handle_inspect,
};

var finish = { status: "accept" };

(async () => {
while (true) {
const finish_req = await fetch(rollup_server + "/finish", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(finish),
});

console.log("Received finish status " + finish_req.status);

if (finish_req.status == 202) {
console.log("No pending rollup request, trying again");
} else {
const rollup_req = await finish_req.json();
var handler = handlers[rollup_req["request_type"]];
finish["status"] = await handler(rollup_req["data"]);
}
}
})();
```

</code></pre>
</TabItem>

<TabItem value="Python" label="Python" default>
<pre><code>

```python
from os import environ
import logging
import requests

logging.basicConfig(level="INFO")
logger = logging.getLogger(__name__)

rollup_server = environ["ROLLUP_HTTP_SERVER_URL"]
logger.info(f"HTTP rollup_server url is {rollup_server}")

def handle_advance(data):
logger.info(f"Received advance request data {data}")
return "accept"

def handle_inspect(data):
logger.info(f"Received inspect request data {data}")
return "accept"

handlers = {
"advance_state": handle_advance,
"inspect_state": handle_inspect,
}

finish = {"status": "accept"}

while True:
logger.info("Sending finish")
response = requests.post(rollup_server + "/finish", json=finish)
logger.info(f"Received finish status {response.status_code}")
if response.status_code == 202:
logger.info("No pending rollup request, trying again")
else:
rollup_request = response.json()
data = rollup_request["data"]
handler = handlers[rollup_request["request_type"]]
finish["status"] = handler(rollup_request["data"])
```

</code></pre>
</TabItem>

</Tabs>

## Notes

- The `/finish` endpoint must be called after processing each request to indicate readiness for the next one
- For advance state requests, the status field determines whether the request is accepted or rejected
- For inspect state requests, the status field is ignored
- If an advance state request is rejected:
- Any vouchers and notices generated during processing are discarded
- Reports are not discarded
- The state is reverted to its previous condition
- During a finish call, the next rollup request might not be immediately available:
- The Rollup HTTP Server returns status code 202
- When receiving status 202, the dApp backend should retry the finish call with the same arguments
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,16 @@ id: introduction
title: Introduction
---

The backend of a Cartesi dApp retrieves a new request as follows:
The backend of a Cartesi dApp processes requests in the following manner:

- Finish — Communicates that any previous processing has been completed and that the backend is ready to handle the subsequent request. This following request is returned as the call's response and can be of the following types:
- **Finish**Called via [`/finish`](./finish.md), indicates that any previous processing has been completed and the backend is ready to handle the next request. The subsequent request is returned as the call's response and can be of the following types:

- **Advance** — Provides input to be processed by the backend to advance the Cartesi Machine state. When processing an `Advance` request, the backend can call the methods `/voucher`, `/notice`, and `/report`. For such requests, the input data contains the payload and metadata, such as the account address that submitted the input.
- **Advance** — Provides input to be processed by the backend to advance the Cartesi Machine state. When processing an Advance request, the backend can call the [`/voucher`](./vouchers.md), [`/delegate-call-voucher`](./delegate-call-vouchers.md), [`/notice`](./notices.md), and [`/report`](./reports.md) endpoints. For such requests, the input data contains both the payload and metadata, including the account address that submitted the input.

- **Inspect** — This function submits a query about the application's current state. When running inside a Cartesi Machine, this operation is guaranteed to leave the state unchanged since the machine is reverted to its exact previous condition after processing. For Inspect requests, the input data has only a payload.

:::caution Inspect requests
Inspect requests are best suited for non-production use, such as debugging and testing. They may not function reliably in production environments, potentially leading to errors or disruptions.
:::
- **Inspect** — Submits a query about the application's current state. When running inside a Cartesi Machine, this operation is guaranteed to leave the state unchanged, as the machine reverts to its exact previous condition after processing. For Inspect requests, the input data contains only a payload, and the backend can only call the [`/report`](./reports.md) endpoint.

- **Exception** — Called by the backend when it encounters an unrecoverable error during request processing. This signals to the Rollup HTTP Server that the current request processing failed and should be terminated. See [`/exception`](./exception.md) for more details.

## Advance and Inspect

Here is a simple boilerplate application that handles Advance and Inspect requests:
Expand Down Expand Up @@ -122,31 +120,34 @@ while True:

</Tabs>

An **Advance** request involves sending input data to the base layer via JSON-RPC so they can reach the dApp backend to change the application's state.
An **Advance** request involves sending input data to the base layer via JSON-RPC, allowing it to reach the dApp backend to change the application's state.

![img](../../../../static/img/v1.3/advance.jpg)

In the dApp architecture, here is how an advance request plays out.
Here is how an advance request works in the dApp architecture:

- Step 1: Send an input to the [`addInput(address, bytes)`](../contracts/input-box.md#addinput) function of the InputBox smart contract.

- Step 2: The HTTP Rollups Server reads the data and gives it to the Cartesi machine for processing.
- Step 2: The HTTP Rollups Server reads the data and sends it to the Cartesi Machine for processing.

- Step 3: After the computation, the machine state is updated, and the results are returned to the rollup server.
- Step 3: After computation, the machine state is updated, and the results are returned to the rollup server.

An **Inspect** request involves making an external HTTP API call to the rollups server to read the dApp state without changing it.
An **Inspect** request involves making an external HTTP API call to the rollups server to read the dApp state without modifying it.

![img](../../../../static/img/v1.3/inspect.jpg)

You can make a simple inspect call from your frontend client to retrieve reports.

To perform an Inspect call, use an HTTP GET request to `<address of the node>/inspect/<request path>`. For example:
To perform an Inspect call, send an HTTP POST request to `<address of the node>/inspect/<application name>` with a payload in the request body. For example:

```shell
curl http://localhost:8080/inspect/mypath
curl -X POST http://localhost:8080/inspect/<application name> \
-H "Content-Type: application/json" \
-d '{"payload": "0xdeadbeef"}'
```

Once the call's response is received, the payload is extracted from the response data, allowing the backend code to examine it and produce outputs as **reports**.
The payload should be a hex-encoded string starting with '0x' followed by pairs of hexadecimal numbers.

After receiving the call's response, the payload is extracted from the response data, allowing the backend code to examine it and produce outputs as **reports**.

The direct output types for **Advance** requests are [vouchers](./vouchers.md), [notices](./notices.md), and [reports](./reports.md), while **Inspect** requests generate only [reports](./reports.md).
The direct output types for **Advance** requests are [vouchers](./vouchers.md), [delegate call vouchers](./delegate-call-vouchers.md), [notices](./notices.md), and [reports](./reports.md), while **Inspect** requests generate only [reports](./reports.md).
Loading