Migrating to permissionless fault proofs on OP Stack
This guide provides a high-level overview for chain operators looking to transition their OP Stack from permissioned to permissionless fault proofs. It's designed to be accessible for technical decision makers while providing sufficient detail for implementation teams.
Overview
The OP Stack architecture uses Fault Proofs to ensure the validity of withdrawals from L2 to L1. Transitioning from permissioned to permissionless proofs represents a significant security upgrade, allowing any participant to propose and challenge state output roots. Permissioned games previously relied on a single trusted validator, this is typically the proposer which is configured in the PermissionedDisputeGame, and is usually the network's only sequencer.
This migration involves several key components:
- Configuring security-critical dispute monitoring services
- Deploying and configuring smart contracts using op-deployer
- Testing the new system before activation
- Setting the respected game type to permissionless fault proofs, specifically using the
FaultDisputeGame
(opens in a new tab)
Prerequisites
Before beginning this transition, your chain should:
- Be running a standard OP Stack implementation
- Use contracts version v2.0.0 (opens in a new tab).
- Be operating with the required infrastructure services including
op-challenger
andop-dispute-mon
.
Migration steps
The process of migrating from permissioned to permissionless fault proofs involves four main phases: configuring off-chain dispute components, deploying the necessary smart contracts, testing the system thoroughly, and finally switching the chain to use permissionless proofs. Each step builds on the previous one to ensure a smooth and secure transition.
Let's begin with configuring the dispute components that will interact with the new permissionless game.
1. Configure the dispute components
The op-challenger
and op-dispute-mon
services are critical security components that participate in the dispute game process to challenge invalid proposals and monitor active games.
Upgrade to the latest op-challenger
Upgrade to the latest release (opens in a new tab), which contains important improvements to simplify the upgrade process.
We recommend using the official Docker images for reliability and ease of deployment:
# Pull a specific stable release version
docker pull us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger:<latest_version>
Then run the image, for example:
# Run with all required flags
docker run -d --name op-challenger \
-e OP_CHALLENGER_TRACE_TYPE=permissioned,cannon \
-e OP_CHALLENGER_PRESTATES_URL=<YOUR_PRESTATES_URL> \
-e OP_CHALLENGER_L1_ETH_RPC=<YOUR_L1_RPC_URL> \
-e OP_CHALLENGER_GAME_FACTORY_ADDRESS=<YOUR_DISPUTE_GAME_FACTORY> \
-e OP_CHALLENGER_PRIVATE_KEY=<YOUR_PRIVATE_KEY_OR_USE_WALLET_SIGNER> \
-e OP_CHALLENGER_NETWORK=<YOUR_NETWORK> \
-v /path/to/local/prestates:/prestates \
us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger:latest
# Replace all placeholder values with your actual configuration
Replace <YOUR_PRESTATES_URL>
with your actual prestates URL.
If your deployment requires building from source, you can alternatively use:
git clone https://github.com/ethereum-optimism/optimism -b op-challenger/v1.3.3 --recurse-submodules
cd optimism
make op-challenger
Update network configuration
Configure op-challenger
to load your chain configuration.
Even if your chain is not included in the superchain-registry, you can specify a custom configuration:
# For chains in the registry
--network <chain-name>
# For chains not in the registry, provide a path to your rollup configuration
- `rollup.json` - Your rollup configuration
- `genesis-l2.json` - Your L2 genesis file
Enable cannon trace type
Configure op-challenger
to support both permissioned and permissionless games by setting:
--trace-type permissioned,cannon
Or by setting the environment variable:
OP_CHALLENGER_TRACE_TYPE=permissioned,cannon
Configure prestates access
Replace the --cannon-prestate
flag with --prestates-url
, which points to a source containing all required prestates:
--prestates-url <URL_TO_PRESTATES_DIRECTORY>
The URL can use http
, https
, or file
protocols. Each prestate should be named as <PRESTATE_HASH>.json
or <PRESTATE_HASH>.bin.gz
.
Building required prestates for chains not in the Superchain Registry
You'll need to deploy two new dispute game contracts with the new absolute prestate:
FaultDisputeGame
PermissionedDisputeGame
The initial prestate used for permissioned games doesn't include the necessary chain configuration for the Fault Proof System. The assumption is that the chain operator, the single permissioned actor, will not challenge their own games. So the absolute prestate on the initial PermissionedDisputeGame
will never be used.
When deploying a new chain, you must first deploy the L1 contracts and then generate the chain genesis file and rollup configuration files.
These are inputs to the creation of the absolute prestate and this circular dependency is the reason chains cannot be deployed directly to the permissionless Fault Proof System.
For chains not in the Superchain Registry, you need to build custom prestates with your chain's configuration:
The prestate will typically be generated at:
op-program/bin/prestate.json
(for older versions)op-program/bin/prestate.bin.gz
(for intermediate versions)op-program/bin/prestate-mt64.bin.gz
(for chains upgraded to Cannon MT64, starting from upgrade 14)
Post-upgrade 14, chains are expected to use prestate-mt64.bin.gz
due to the Fault Proof VM contract upgrade to cannon-mt64
.
The older prestate.bin.gz
will eventually be deprecated but is temporarily retained until all chains complete the upgrade.
Ensure sufficient funds for bonds
Bonds are required for both permissioned and permissionless games. However, with permissioned games, you typically don't post claims regularly, making bond requirements less noticeable. In contrast, the challenger in permissionless games will frequently need to post bonds with each claim it makes. Therefore, ensure your challenger has sufficient funds available.
As a general guideline:
- Maintain a minimum balance of 50 ETH
- Have access to a large pool of ETH for potential attack scenarios
- Implement monitoring to ensure sufficient funds are always available
Set up op-dispute-mon
Ensure op-dispute-mon
is properly configured by following the steps in the documentation.
2. Deploy and configure smart contracts using OPCM
This section requires privileged actions by the ProxyAdminOwner
and the Guardian
role.
Understanding ProxyAdmin Owner and Guardian roles
This migration requires actions by privileged roles in your system:
- The ProxyAdmin Owner has the authority to upgrade proxy contracts.
- The Guardian has emergency powers like pausing withdrawals and changing the respected game type.
For detailed information about privileged roles and their security implications, refer to the privileged roles documentation.
Adding the PermissionlessDisputeGame to a chain
Not all chains support the permissionless dispute game by default.
To enable it, you must call the addGameType()
function on the OPChainManager
(OPCM) contract.
This function orchestrates all the necessary steps to register the new dispute game type for your chain.
This method will:
- Deploy the
FaultDisputeGame
contract - Setup the
DelayedWethProxy
for the new game - Reinitialize the
AnchorStateRegistry
to add the new game type.
See a high‐level implementation from this docs.
3. Testing off-chain agents
After you've set the permissionless FaultDisputeContract
implementations on the DisputeGameFactory
and before you set the respected game type to it; you can test your off-chain services in the cold path.
Test defending valid proposals
Create a valid proposal using the permissionless game type 0
:
-
Ensure the proposal is from a block at or before the
safe
head:cast block --rpc-url <OP_GETH_ENDPOINT> safe
-
Get a valid output root:
cast rpc --rpc-url <OP_NODE_ENDPOINT> optimism_outputAtBlock \ $(cast 2h <BLOCK_NUMBER>) | jq -r .outputRoot
-
Create a test game:
./op-challenger/bin/op-challenger create-game \ --l1-eth-rpc=<L1_RPC_ENDPOINT> \ --game-factory-address <DISPUTE_GAME_FACTORY_ADDR> \ --l2-block-num <BLOCK_NUMBER> \ --output-root <OUTPUT_ROOT> \ <SIGNER_OPTIONS>
-
Verify:
op-challenger
logs a message showing the game is in progressop-challenger
doesn't post a counter claim (as this is a valid proposal)dispute-mon
includes the new game withstatus="agree_defender_ahead"
Test countering invalid claims
Post an invalid counter claim to the valid proposal created above:
./op-challenger/bin/op-challenger move \
--l1-eth-rpc <L1_RPC_ENDPOINT> \
--game-address <GAME_ADDR> \
--attack \
--parent-index 0 \
--claim 0x0000000000000000000000000000000000000000000000000000000000000000 \
<SIGNER_OPTIONS>
Verify that op-challenger
posts a counter-claim to the invalid claim. You can view claims using:
./op-challenger/bin/op-challenger list-claims \
--l1-eth-rpc <L1_RPC_ENDPOINT> \
--game-address <GAME_ADDR>
There should be 3 claims in the game after this test.
Switch to permissionless proofs
After completing all previous steps and verifying their successful operation, you need to update the respectedGameType
in the OptimismPortal
. This requires execution through the appropriate privileged role (typically the Guardian).
You have two main options for executing this step:
Option 1: Execute using a multisig
If your privileged role (such as the Guardian) is controlled by a multisig or DAO governance system, use the provided JSON payload (input.json
):
Create an input.json
file with the transaction to update the respectedGameType
:
{
"chainId": "<YOUR_L1_CHAIN_ID>",
"metadata": {
"name": "Deputy Guardian - Enable Permissionless Dispute Game",
"description": "This task updates the `respectedGameType` in the `OptimismPortal` to `CANNON`, enabling users to permissionlessly propose outputs as well as for anyone to participate in the dispute of these proposals. This action requires all in-progress withdrawals to be re-proven against a new `FaultDisputeGame` that was created after this update occurs."
},
"transactions": [
{
"metadata": {
"name": "Update `respectedGameType` in the `OptimismPortal`",
"description": "Updates the `respectedGameType` to `CANNON` in the `OptimismPortal`, enabling permissionless proposals and challenging."
},
"to": "<SUPERCHAIN_CONFIG_MANAGER_ADDRESS>",
"value": "0x0",
"data": "0xa1155ed9000000000000000000000000<OPTIMISM_PORTAL_ADDRESS>0000000000000000000000000000000000000000000000000000000000000000",
"contractMethod": {
"type": "function",
"name": "setRespectedGameType",
"inputs": [
{
"name": "_portal",
"type": "address"
},
{
"name": "_gameType",
"type": "uint32"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
"contractInputsValues": {
"_gameType": "0",
"_portal": "<OPTIMISM_PORTAL_ADDRESS>"
}
}
]
}
- Submit this JSON payload through your interface (e.g., Safe transaction builder).
- Simulate this transaction using Tenderly before execution to ensure the expected state changes:
- The
respectedGameType
in theOptimismPortal
should change from1
(PERMISSIONED) to0
(CANNON).
- The
Option 2: Direct execution via cast
(Forge CLI)
Alternatively, if you're executing directly from a single-privileged wallet or want quicker execution, use the following cast
command template:
- First, encode the transaction calldata using
cast calldata
:
CALLDATA=$(cast calldata "setRespectedGameType(address,uint32)" <OPTIMISM_PORTAL_ADDRESS> 0)
- Send the transaction:
# Execute the transaction
cast send --rpc-url <YOUR_RPC_URL> --private-key <YOUR_PRIVATE_KEY> $OPCM_ADDRESS $CALLDATA
- After execution, verify the storage slot change using:
# Check storage slot 0x3b in OptimismPortal
cast storage <OPTIMISM_PORTAL_ADDRESS> 0x3b
# The last byte should change from 0x01 to 0x00
Post-execution configuration:
After updating, configure op-proposer
to create proposals using the permissionless CANNON
game type:
Via command-line argument:
--game-type 0
Or via environment variable:
OP_PROPOSER_GAME_TYPE=0
This action requires all in-progress withdrawals to be re-proven against a new FaultDisputeGame
created after this update occurs.
Next steps
- For more detail on deploying new dispute games with OPCM, see the docs.