-
Notifications
You must be signed in to change notification settings - Fork 23
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
design-doc: supersim #34
Changes from all commits
2ac9cc0
efccce6
78c47f8
d2d17f1
aea9d66
76972c0
7fce2c9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,319 @@ | ||
# Purpose | ||
|
||
The purpose of this document is to introduce the rationale and proposed design for `supersim`, a local multichain development environment. | ||
|
||
# Problem Statement + Context | ||
|
||
As the Superchain becomes interoperable, applications will need to be tested against multiple chains in the Superchain simultaneously. While Anvil enables a single chain local environment, `supersim` enables developers to use their favorite tools (like Anvil) in a multi-chain local environment. | ||
|
||
Interop will usher in a paradigm shift in the way that developers build and deploy their applications, and a way to test their work locally is foundational to their success and thus the success of interop. | ||
|
||
|
||
# High-Level Overview | ||
|
||
Supersim will allow developers to start multiple local Layer 2 chains with one command, and it will simulate the experience of interop via a command line interface. | ||
|
||
# Proposed Solution | ||
|
||
Supersim will be a lightweight tool that simulates an interoperable Superchain environment locally. It will not require a complicated devnet setup and will be run using cli commands with configuration options that fall back to sensible defaults if they are not specified. Predefined state can be passed in at startup or the state can be forked based on specified block height. | ||
|
||
From a developer perspective, supersim will offer a command line interface through which to easily start up multiple local testnets that can be used interoperably via the [Optimism Interop Protocol](https://github.com/ethereum-optimism/specs/tree/main/specs/interop). | ||
|
||
## Usage | ||
|
||
```bash | ||
// brings up two vanilla op chains with each other in the dep set with the same L1 chain | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Besides two L2s, does it also include running the L1 chain itself? Or are there existing different preferred tools to test a L1 <> L2 chain combination in this context? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes it does - supersim's orchestrator spins up an L1 and multiple L2s. |
||
|
||
supersim | ||
``` | ||
|
||
```bash | ||
// brings up all forked chains in the superchain registry, if l1BlockHeight is not specified, uses the latest safe head | ||
|
||
supersim --superchain --l1BlockHeight=423012 | ||
|
||
// can also specify a subset of members in the superchain | ||
|
||
supersim --superchain --networks=base,op-mainnet | ||
``` | ||
|
||
config.toml(optional) | ||
|
||
```yaml | ||
[[networks]] | ||
forkRpcUrl = "https://sepolia.optimism.io" | ||
chainId = 420 | ||
port = 8645 | ||
dependencySet = [] | ||
|
||
[[networks]] | ||
chainId = 99999 | ||
port = 8646 | ||
``` | ||
|
||
## Architecture | ||
|
||
The **orchestrator** is responsible for spinning up the requested number of **anvil instances** and also for spinning up the **op-simulator**. It is also responsible for the interface between the **anvil instances** and the **op-simulator**. | ||
|
||
### Application Flow | ||
|
||
The flow of this tool will differ depending on whether the user opts for **vanilla** mode or **forking** mode. | ||
|
||
#### Vanilla mode | ||
|
||
1. Orchestrator has preset chain configs and starts chains anew. | ||
2. Orchestrator applies genesis state via the genesis-applier. | ||
3. Orchestrator starts op-simulator and offers an interface between anvil instances and op-simulator. | ||
4. Orchestrator monitors status of running local anvil instances. | ||
|
||
#### Forking mode | ||
|
||
1. Orchestrator acquires L1 reference block to get the latest L2 height for forking using l2-fork-state-finder. | ||
2. Orchestrator gets appropriate chain configs from the superchain registry using the chain-config-loader. | ||
3. Orchestrator starts the requested chains at determined L2 block height. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Presumably this means that you'd need RPCs for all chains in your forked interop set, correct? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For forking, yes I don't see a way around that. We were planning to default to public RPCs where possible and allow users to override. |
||
4. Orchestrator applies genesis to each chain using the genesis-applier. | ||
5. Orchestrator starts op-simulator and offers interface between anvil instances and op-simulator. | ||
6. Orchestrator monitors status of running local anvil instances. | ||
|
||
|
||
### Interfaces | ||
|
||
#### Orchestrator interface provides the following to the developer: | ||
|
||
1. Instance status | ||
2. Event log | ||
3. Mode selection | ||
4. Control panel (in cli? Some ui option? both?): buttons/commands to start/stop/restart individual instances | ||
5. Genesis state interface: could be a long term goal to give devs more control over what they do here. Would offer an interface for applying and managing genesis states: | ||
|
||
i. File upload: upload genesis files | ||
ii. Genesis history: list of applied genesis states with timestamps/details | ||
iii. Apply button/command: apply the selected genesis state to the chosen instances. | ||
|
||
6. Configuration loader: interface for loading and managing chain configurations from the chain-config-loader. | ||
|
||
i. Config list: display available configurations from the registry | ||
ii. Download button/command for the selected configuration | ||
iii. Apply button/command | ||
iv. Configuration details: show detailed information about the selected configuration | ||
|
||
#### Op-simulator interface | ||
|
||
Checks instance status as served by orchestrator interface and listens for RPC calls that require op-simulator intervention. The purpose of this interface is to intercept RPC calls that should go to the interface rather than to anvil instances directly. | ||
|
||
This is essentially a proxy between the app's RPC calls and the op-simulator. | ||
|
||
```mermaid | ||
flowchart TD | ||
subgraph Orchestrator Interface | ||
direction LR | ||
A[Instance Status] -->|Displays| A1[Running Instances] | ||
A -->|Displays| A2[Stopped Instances] | ||
|
||
B[Event Log] -->|Shows| B1[Recent Events] | ||
B -->|Shows| B2[System Messages] | ||
|
||
C[Mode Selection] -->|Options| C1[Vanilla Mode] | ||
C -->|Options| C2[Forking Mode] | ||
|
||
subgraph Control Panel | ||
direction TB | ||
D1[CLI Commands] -->|Command Line| D1a[start] | ||
D1 -->|Command Line| D1b[stop] | ||
D1 -->|Command Line| D1c[restart] | ||
|
||
D2[UI Buttons] -->|Graphical Interface| D2a[start] | ||
D2 -->|Graphical Interface| D2b[stop] | ||
D2 -->|Graphical Interface| D2c[restart] | ||
end | ||
end | ||
``` | ||
|
||
```mermaid | ||
flowchart TD | ||
subgraph Op-Simulator Interface | ||
direction LR | ||
|
||
|
||
C -->|any other RPC call| D2[call eth_sendRawTransaction \non Anvil] | ||
|
||
|
||
C[ETH RPC API] -->|eth_sendRawTransaction| C1[Does TX have \n ExecutingMessage?] | ||
C1 -->|no| D2 | ||
|
||
|
||
C1 -->|yes| D1 | ||
|
||
|
||
|
||
D1[Check Interop Message Invariants] -->|If Pass|D2 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clear debug messages, so the user understands how/why their interop executing message is invalid, would be really useful. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes! will add this to be an explicit requirement. |
||
D1 -->|If Fail| D3[Return TX failed] | ||
|
||
|
||
D2 --> B3[Return result] | ||
end | ||
``` | ||
|
||
### Services | ||
|
||
Given the application flow, the following services will be part of supersim: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will this all still be packaged into the same executable? I don't see a user installing and running 6 different tools There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes 100%, the orchestrator spins these up but it is all "supersim." |
||
|
||
1. **Orchestrator**: This is the main focus and interface for developers using supersim. | ||
|
||
* Runs all of the other services in supersim and manages their lifecycle | ||
* Provides an interface to the user | ||
|
||
* Initially a static interface. | ||
* Medium-term, offer an API interface that the orchestrator exposes to make it easier to build new offchain services that hook into the runtime. | ||
|
||
2. **Anvil Instances**: Anvil instances are run by the orchestrator after initial prep work to allow them to mirror the Superchain interop environment. | ||
|
||
* Should be run in `--optimism` mode so the extra fields in transactions are there. | ||
* Orchestrator should also run an L1 instance, though L1 withdrawals won’t initially be supported. | ||
* Eventually supersim can be extended to be used with HardHat and other local simulation tools as well. | ||
|
||
3. **Genesis-applier** : The `genesis-applier` takes genesis files output by the monorepo and applies them to anvil instances. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The monorepo currently outputs Also note that some L2 contracts contain references to the L1, which would need to be updated, to make it work e.g. with different L1 deployments, chain-IDs, etc. Any improvements here, to make templating easier, like not using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ooh the allocs approach is interesting! We have been talking about the best ways to identify what exactly should be used for genesis, and this could be a really helpful option. We'll look into the L1 references in the L2 contracts. Maybe we can help make the changes if it makes sense. I'd like to be able to use the contracts as is rather than have to edit them on our end, if at all possible. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be useful to directly use the JSON allocs files to populate There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @tynes makes sense! we're using in terms of generating the state dump, definitely alligned on trying to use the deploy script as is. basically copied the logic in the python script in the monorepo and added some functionality to handle for multiple l2s https://github.com/ethereum-optimism/supersim/pull/53/files. |
||
|
||
* The genesis files needed will differ depending on whether the user is in vanilla or forked mode, and forked mode is unlikely to need anything other than the interop contracts initially. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could create a small script to install the interop contracts in the short term |
||
* Can be multi-step: multiple genesis files can be applied idempotently, particularly in forking mode (when you fork a network that has bedrock contracts but not interop contracts, for instance). | ||
* Genesis-applier should work as a standalone tool: if a developer is using Anvil outside the context of the orchestrator, they should be able to use genesis-applier to apply genesis state to their instance. In the context of supersim, the `orchestrator` will use the `genesis-applier` to apply the needed genesis state to the anvil instances it is running. | ||
|
||
4. **Chain-config-loader**: Downloads the latest chain config from the superchain registry to allow the orchestrator to apply needed configs to anvil instances. Should be a standalone tool that works with the orchestrator but could be used directly on local testing nodes. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it's part of the other tooling, and written in Go or rust, then it should be relatively straight-forward to just import the superchain-registry as library. Like the op-node does. |
||
|
||
5. **L2-fork-state-finder**: From an L1 reference block, runs an algorithm to identify the latest L2 height derived from the L1 block for each network. | ||
|
||
6. **Op-simulator**: Proxy server in front of anvil instances that simulates op-stack services without the real derivation pipeline. | ||
|
||
* Simulates deposits by listening to the `optimismPortal` and forwarding deposit txs via `eth_sendRawTransaction`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
* Implements `eth_sendRawTransaction` and first applies any necessary out-of-protocol validation such as the interop spec variants. | ||
* Calls `eth_getLog` on other chains to fetch event logs for invariant checks. | ||
* All other rpc calls are proxied by the orchestrator to the anvil instances. | ||
* **Withdrawal pathway to Ethereum is not supported** (at least to start) | ||
|
||
### Sequence Diagram | ||
|
||
```mermaid | ||
sequenceDiagram | ||
participant Developer | ||
participant Orchestrator | ||
participant GenesisApplier | ||
participant AnvilL1 | ||
participant AnvilL2_1 | ||
participant AnvilL2_2 | ||
participant L2ForkStateFinder | ||
participant ChainConfigLoader | ||
participant OpSimulator | ||
|
||
Developer ->> Orchestrator: Start in Vanilla Mode | ||
Orchestrator ->> AnvilL1: Start L1 Instance | ||
Orchestrator ->> AnvilL2_1: Start L2 Instance (L2-1) | ||
Orchestrator ->> AnvilL2_2: Start L2 Instance (L2-2) | ||
Orchestrator ->> GenesisApplier: Apply Genesis State | ||
GenesisApplier ->> AnvilL1: Apply Genesis | ||
GenesisApplier ->> AnvilL2_1: Apply Genesis | ||
GenesisApplier ->> AnvilL2_2: Apply Genesis | ||
Orchestrator ->> OpSimulator: Start OpSimulator | ||
OpSimulator ->> AnvilL1: Interface with L1 | ||
OpSimulator ->> AnvilL2_1: Interface with L2-1 | ||
OpSimulator ->> AnvilL2_2: Interface with L2-2 | ||
Orchestrator ->> Developer: Monitor Anvil Instances | ||
|
||
Developer ->> Orchestrator: Start in Forking Mode | ||
Orchestrator ->> L2ForkStateFinder: Acquire L1 Reference Block | ||
L2ForkStateFinder ->> Orchestrator: Provide L2 Height | ||
Orchestrator ->> ChainConfigLoader: Get Chain Configs | ||
ChainConfigLoader ->> Orchestrator: Provide Chain Configs | ||
Orchestrator ->> AnvilL1: Start L1 Instance | ||
Orchestrator ->> AnvilL2_1: Start L2 Instance at L2 Height | ||
Orchestrator ->> AnvilL2_2: Start L2 Instance at L2 Height | ||
Orchestrator ->> GenesisApplier: Apply Genesis State | ||
GenesisApplier ->> AnvilL1: Apply Genesis | ||
GenesisApplier ->> AnvilL2_1: Apply Genesis | ||
GenesisApplier ->> AnvilL2_2: Apply Genesis | ||
Orchestrator ->> OpSimulator: Start OpSimulator | ||
OpSimulator ->> AnvilL1: Interface with L1 | ||
OpSimulator ->> AnvilL2_1: Interface with L2-1 | ||
OpSimulator ->> AnvilL2_2: Interface with L2-2 | ||
Orchestrator ->> Developer: Monitor Anvil Instances | ||
``` | ||
|
||
### Flow Chart | ||
|
||
```mermaid | ||
flowchart TD | ||
A[Developer] --> B{Mode Selection} | ||
|
||
%% Vanilla Mode Path | ||
B -->|Vanilla Mode| VM1[Orchestrator Starts L1 Anvil Instance] | ||
VM1 --> VM2[Orchestrator Starts L2 Anvil Instances] | ||
VM2 --> CONV1[Orchestrator Applies Genesis State via Genesis Applier] | ||
|
||
%% Forking Mode Path | ||
B -->|Forking Mode| FM1[Orchestrator Acquires L1 Reference Block] | ||
FM1 --> FM2[Orchestrator Determines L2 Block Height] | ||
FM2 --> FM3[Orchestrator Gets Chain Configs from Superchain Registry] | ||
FM3 --> FM4[Orchestrator Starts L1 Anvil Instance] | ||
FM3 --> FM5[Orchestrator Starts L2 Anvil Instances at L2 Height] | ||
FM4 --> CONV1 | ||
FM5 --> CONV1 | ||
|
||
%% Converged Path | ||
CONV1 --> CONV2[Orchestrator Starts Op-Simulator] | ||
CONV2 --> CONV3[Op-Simulator Interfaces with Anvil Instances] | ||
CONV3 --> CONV4[Orchestrator Monitors Anvil Instances] | ||
|
||
%% Grouping for Visual Appeal | ||
subgraph Vanilla Mode Flow | ||
VM1 | ||
VM2 | ||
end | ||
|
||
subgraph Forking Mode Flow | ||
FM1 | ||
FM2 | ||
FM3 | ||
FM4 | ||
FM5 | ||
end | ||
|
||
subgraph Common Flow | ||
CONV1 | ||
CONV2 | ||
CONV3 | ||
CONV4 | ||
end | ||
``` | ||
|
||
## Testing | ||
|
||
In the interest of working in the open we should apply a TDD approach to development. This means we should start off by writing a test script that includes coverage for all the developer actions we intend to support and use this as our main specification. | ||
|
||
### Test Cases | ||
* `test_environment_initialization`: It should verify that multiple Anvil instances can be started and configured correctly. | ||
* `test_genesis_state`: It should pre-deploy all smart contracts needed for testing interoperable optimism chains, when run in vanilla mode. | ||
* `test_genesis_state_fork`: It should update the genesis state for forked chains to ensure all smart contracts needed for testing interoperability are present. | ||
* `test_cross_chain_message_passing`: It should ensure that messages can be sent and received between different L2 chains running locally. | ||
* `test_invariants`: It should check for interop message invariants and fail if they are not met | ||
* `test_timestamp_invariant`: It should fail if the timestamp at the time of inclusion of the executing message is less than the timestamp of the initiating message. | ||
* `test_chainID_invariant`: It should fail if the initiating message is not in the dependency set. | ||
* `test_message_expiry_invariant`: It should fail if the timestamp at the time of inclusion of the executing message is higher than the initiating message timestamp. | ||
* `test_message_relaying`: It should ensure that messages can be “sent” and “received” between different L2 instances. | ||
* `test_initiating_message`: It should log an event when an initiating message from the source chain is created. | ||
* `test_destination_receipt`: It should ensure that destination chain can ingest source chain message`. | ||
* `test_destination_execution`: It should fire an executing message after confirming the source initiating message. | ||
* `test_cross_chain_erc20_asset_transfers`: ensures that erc20’s can be transferred between chains. | ||
* The exact implementation on this is still TBD (see [this doc](https://www.notion.so/oplabs/Mirror-superc20-idea-5fa1d3b139e344bdbb4a86ce5f636b13&sa=D&source=docs&ust=1718736334304945&usg=AOvVaw2qCkqLdAfI4t0tp5ThVOhM)), so this may be out of scope for the initial version. | ||
|
||
## Alternatives | ||
|
||
1. Use the Optimism devnet instead. Make our documentation for how developers can use it better and point them there when they wish to try out interop locally. While this would technically meet the need to test locally, the devnet is cumbersome at this time and this approach would fail to give developers a magical experience when developing optimism applications. | ||
2. Do nothing, and hope application developers cobble together a sufficient solution for their development needs. It is possible that a developer external to OPLabs will build an elegant solution for local testing, but such an approach leaves a lot to chance. By providing tools for developers in our ecosystem, we make it easier for them to contribute and get started in the Superchain ecosystem. | ||
|
||
## Risks and Uncertainties | ||
|
||
1. Will OPLabs have the bandwidth to maintain this tool once it is live? If not, how can we be sure we find support for its maintenance in the ecosystem when it is time to do so? | ||
2. What metrics can we use to assess success/adoption, and how will we ensure continuous feedback from developers who try this tool? | ||
|
||
## Appendix | ||
[PRFAQ](https://docs.google.com/document/d/154K98TwgzaHQMQ--7rvGod-WLDsn8XtFBpgO5eGhXYA/edit#heading=h.bdadv42dvm88) | ||
[Optimism Interop Specs](https://github.com/ethereum-optimism/specs/tree/main/specs/interop) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does it orchestrate each instance? Through Docker or something similar?