From a5ade0d0acc260689010f6df10e70c5af6919618 Mon Sep 17 00:00:00 2001 From: fakedev9999 Date: Fri, 14 Feb 2025 12:13:44 -0800 Subject: [PATCH 1/5] feat(fault_proof): implement proposer --- Cargo.lock | 28 ++ Cargo.toml | 6 +- book/SUMMARY.md | 2 + book/fault_proofs/deploy.md | 122 ++++++ book/fault_proofs/proposer.md | 165 ++++++++ contracts/script/fp/DeployOPSuccinctFDG.s.sol | 125 ++++++ .../{ => validity}/DeployMockVerifier.s.sol | 0 .../OPSuccinctDGFDeployer.s.sol | 6 +- .../{ => validity}/OPSuccinctDeployer.s.sol | 4 +- .../OPSuccinctParameterUpdater.s.sol | 4 +- .../{ => validity}/OPSuccinctUpgrader.s.sol | 4 +- .../src/fp/OPSuccinctFaultDisputeGame.sol | 14 +- .../test/fp/OPSuccinctFaultDisputeGame.t.sol | 36 +- contracts/test/validity/Upgrade.t.sol | 2 +- contracts/utils/MockOptimismPortal2.sol | 43 +++ fault_proof/Cargo.toml | 45 +++ fault_proof/bin/proposer.rs | 356 ++++++++++++++++++ fault_proof/src/config.rs | 75 ++++ fault_proof/src/contract.rs | 113 ++++++ fault_proof/src/lib.rs | 250 ++++++++++++ fault_proof/src/utils.rs | 20 + proposer/succinct/Dockerfile | 1 + 22 files changed, 1368 insertions(+), 53 deletions(-) create mode 100644 book/fault_proofs/deploy.md create mode 100644 book/fault_proofs/proposer.md create mode 100644 contracts/script/fp/DeployOPSuccinctFDG.s.sol rename contracts/script/{ => validity}/DeployMockVerifier.s.sol (100%) rename contracts/script/{ => validity}/OPSuccinctDGFDeployer.s.sol (90%) rename contracts/script/{ => validity}/OPSuccinctDeployer.s.sol (91%) rename contracts/script/{ => validity}/OPSuccinctParameterUpdater.s.sol (96%) rename contracts/script/{ => validity}/OPSuccinctUpgrader.s.sol (91%) create mode 100644 contracts/utils/MockOptimismPortal2.sol create mode 100644 fault_proof/Cargo.toml create mode 100644 fault_proof/bin/proposer.rs create mode 100644 fault_proof/src/config.rs create mode 100644 fault_proof/src/contract.rs create mode 100644 fault_proof/src/lib.rs create mode 100644 fault_proof/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index d45802d9..02ba6076 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4121,6 +4121,34 @@ dependencies = [ "tokio", ] +[[package]] +name = "op-succinct-fp" +version = "0.1.0" +dependencies = [ + "alloy-contract", + "alloy-eips", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types", + "alloy-signer-local", + "alloy-sol-macro", + "alloy-sol-types", + "alloy-transport", + "alloy-transport-http", + "anyhow", + "async-trait", + "clap", + "dotenv", + "log", + "op-alloy-network", + "op-alloy-rpc-types", + "serde_json", + "tokio", + "tracing", + "tracing-subscriber", +] + [[package]] name = "op-succinct-host-utils" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 18092d78..d3947d98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [workspace] -members = ["utils/*", "programs/*", "scripts/*", "proposer/succinct"] +members = ["utils/*", "programs/*", "scripts/*", "proposer/succinct", "fault_proof"] resolver = "2" [workspace.package] license = "MIT" edition = "2021" -authors = ["ratankaliani", "zachobront"] +authors = ["ratankaliani", "zachobront", "fakedev9999"] homepage = "https://succinctlabs.github.io/op-succinct/" repository = "https://github.com/succinctlabs/op-succinct" @@ -62,6 +62,7 @@ op-succinct-build-utils = { path = "utils/build" } op-succinct-proposer = { path = "proposer/succinct" } # Alloy (Network) +alloy-network = { version = "0.11.0", default-features = false } alloy-signer-local = { version = "0.11.0", default-features = false } alloy-provider = { version = "0.11.0", default-features = false } alloy-transport = { version = "0.11.0", default-features = false } @@ -85,6 +86,7 @@ alloy-primitives = { version = "0.8.19", default-features = false, features = [ "sha3-keccak", ] } alloy-sol-types = { version = "0.8.19", default-features = false } +alloy-sol-macro = { version = "0.8.19", default-features = false } # OP Alloy op-alloy-consensus = { version = "0.10.0", default-features = false } diff --git a/book/SUMMARY.md b/book/SUMMARY.md index db457f33..d95914dd 100644 --- a/book/SUMMARY.md +++ b/book/SUMMARY.md @@ -23,6 +23,8 @@ - [Update Contract Parameters](./contracts/update-parameters.md) - [Modifications to Original `L2OutputOracle`](./contracts/modifications.md) - [Fault Proofs](./fault_proofs/fault_proof_architecture.md) + - [Deploy FP Contracts](./fault_proofs/deploy.md) + - [How to run the FP Proposer](./fault_proofs/proposer.md) - [Experimental](./experimental/intro.md) - [OptimismPortalV2](./experimental/optimism-portal-v2.md) - [FAQ](./faq.md) diff --git a/book/fault_proofs/deploy.md b/book/fault_proofs/deploy.md new file mode 100644 index 00000000..9bbc33b4 --- /dev/null +++ b/book/fault_proofs/deploy.md @@ -0,0 +1,122 @@ +# Deploying OP Succinct Fault Dispute Game + +This guide explains how to deploy the OP Succinct Fault Dispute Game contracts using the `DeployOPSuccinctFDG.s.sol` script. + +## Overview + +The deployment script performs the following actions: +1. Deploys the `DisputeGameFactory` implementation and proxy. +2. Deploys the `AnchorStateRegistry` implementation and proxy. +3. Deploys a mock `OptimismPortal2` for testing. +4. Deploys the `AccessManager` and configures it for permissionless games. +5. Deploys either a mock SP1 verifier for testing or uses a provided verifier address. +6. Deploys the `OPSuccinctFaultDisputeGame` implementation. +7. Configures the factory with initial bond and game implementation. + +## Prerequisites + +- [Foundry](https://book.getfoundry.sh/getting-started/installation) installed. +- Access to an Ethereum node (local or network). +- Environment variables properly configured. + +## Configuration + +Create a `.env` file in the contracts directory with the following variables: + +### Required Environment Variables + +| Variable | Description | Example | +|----------|-------------|---------| +| `GAME_TYPE` | Unique identifier for the game type (uint32). | `42` | +| `DISPUTE_GAME_FINALITY_DELAY_SECONDS` | Delay before finalizing dispute games. | `604800` for 7 days | +| `MAX_CHALLENGE_DURATION` | Maximum duration for challenges in seconds. | `604800` for 7 days | +| `MAX_PROVE_DURATION` | Maximum duration for proving in seconds. | `86400` for 1 day | +| `PROOF_REWARD` | Reward for successful proofs (optional). | `0.01 ether` | + +### SP1 Verifier Configuration +Choose one of the following: + +| Variable | Description | Example | +|----------|-------------|---------| +| `USE_SP1_MOCK_VERIFIER` | Set to true to deploy a mock verifier for testing. | `true` | +| `VERIFIER_ADDRESS` | Address of the SP1 verifier for production. | `0x...` | +| `ROLLUP_CONFIG_HASH` | Hash of the rollup configuration (if not using mock). | `0x...` | +| `AGGREGATION_VKEY` | Verification key for aggregation (if not using mock). | `0x...` | +| `RANGE_VKEY_COMMITMENT` | Commitment to range verification key (if not using mock). | `0x...` | + +## Deployment + +1. Install dependencies: + ```bash + forge install + ``` + +2. Change directory to contracts: + ```bash + cd contracts + ``` + +3. Build the contracts: + ```bash + forge build + ``` + +4. Run the deployment script: + ```bash + forge script script/fp/DeployOPSuccinctFDG.s.sol --broadcast --rpc-url --private-key + ``` + +## Contract Parameters + +The deployment script deploys the contract with the following parameters: + +- **Initial Bond**: 0.01 ETH. +- **Proof Reward**: 0.01 ETH (configurable via `PROOF_REWARD`). +- **Starting Anchor Root**: Genesis configuration with block number 0. +- **Access Control**: Permissionless (address(0) can propose and challenge). + +## Post-Deployment + +After deployment, the script will output the addresses of: +- Factory Proxy. +- Game Implementation. +- SP1 Verifier. +- Portal2. +- Anchor State Registry. +- Access Manager. + +Save these addresses for future reference and configuration of other components. + +## Security Considerations + +- The deployer address will be set as the factory owner. +- Initial parameters are set for testing - adjust for production. +- The mock SP1 verifier (`USE_SP1_MOCK_VERIFIER=true`) should ONLY be used for testing. +- For production deployments: + - Provide a valid `VERIFIER_ADDRESS`. + - Configure proper `ROLLUP_CONFIG_HASH`, `AGGREGATION_VKEY`, and `RANGE_VKEY_COMMITMENT`. + - Review and adjust finality delay and duration parameters. + - Consider access control settings. + +## Troubleshooting + +Common issues and solutions: + +1. **Compilation Errors**: + - Ensure Foundry is up to date (run `foundryup`). + - Run `forge clean && forge build`. + +2. **Deployment Failures**: + - Check RPC connection. + - Verify sufficient ETH balance. + - Confirm environment variables are set correctly. + +## Next Steps + +After deployment: + +1. Update the proposer configuration with the factory address. +2. Configure the challenger with the game parameters. +3. Test the deployment with a sample game. +4. Monitor initial games for correct behavior. +5. For production: Replace mock OptimismPortal2 with the real implementation. diff --git a/book/fault_proofs/proposer.md b/book/fault_proofs/proposer.md new file mode 100644 index 00000000..91a4afef --- /dev/null +++ b/book/fault_proofs/proposer.md @@ -0,0 +1,165 @@ +# Fault Proof Proposer + +The fault proof proposer is a component responsible for creating and managing OP-Succinct fault dispute games on the L1 chain. It continuously monitors the L2 chain and creates new dispute games at regular intervals to ensure the validity of L2 state transitions. + +## Prerequisites + +Before running the proposer, ensure you have: + +1. Rust toolchain installed (latest stable version) +2. Access to L1 and L2 network nodes +3. The DisputeGameFactory contract deployed (See [Deploy](./deploy.md)) +4. Sufficient ETH balance for: + - Transaction fees + - Game bonds (configurable in the factory) +5. Required environment variables properly configured (see [Configuration](#configuration)) + +## Overview + +The proposer performs several key functions: + +1. **Game Creation**: Creates new dispute games for L2 blocks at configurable intervals +2. **Game Resolution**: Optionally resolves unchallenged games after their deadline passes +3. **Chain Monitoring**: Continuously monitors the L2 chain's safe head and creates proposals accordingly +4. **Fast Finality Mode**: Optionally enables fast finality by including proofs with proposals + +## Configuration + +The proposer is configured through various environment variables. Create a `.env.proposer` file in the fault_proof directory: + +### Required Environment Variables + +| Variable | Description | +|----------|-------------| +| `L1_RPC` | L1 RPC endpoint URL | +| `L2_RPC` | L2 RPC endpoint URL | +| `FACTORY_ADDRESS` | Address of the DisputeGameFactory contract | +| `GAME_TYPE` | Type identifier for the dispute game | +| `PRIVATE_KEY` | Private key for transaction signing | + +### Optional Environment Variables + +| Variable | Description | Default Value | +|----------|-------------|---------------| +| `PROPOSAL_INTERVAL_IN_BLOCKS` | Number of L2 blocks between proposals | `1000` | +| `FETCH_INTERVAL` | Polling interval in seconds | `30` | +| `ENABLE_GAME_RESOLUTION` | Whether to enable automatic game resolution | `false` | +| `MAX_GAMES_TO_CHECK_FOR_RESOLUTION` | Maximum number of games to check for resolution | `100` | +| `FAST_FINALITY_MODE` | Enable fast finality with proofs | `false` | + +```env +# Required Configuration +L1_RPC= # L1 RPC endpoint URL +L2_RPC= # L2 RPC endpoint URL +FACTORY_ADDRESS= # Address of the DisputeGameFactory contract (obtained from deployment) +GAME_TYPE= # Type identifier for the dispute game (must match factory configuration) +PRIVATE_KEY= # Private key for transaction signing + +# Optional Configuration +PROPOSAL_INTERVAL_IN_BLOCKS=1000 # Number of L2 blocks between proposals +FETCH_INTERVAL=30 # Polling interval in seconds +ENABLE_GAME_RESOLUTION=false # Whether to enable automatic game resolution +MAX_GAMES_TO_CHECK_FOR_RESOLUTION=100 # Maximum number of games to check for resolution +FAST_FINALITY_MODE=false # Enable fast finality mode with proofs +``` + +### Configuration Steps + +1. Deploy the DisputeGameFactory contract following the [deployment guide](./deploy.md) +2. Copy the factory address from the deployment output +3. Create `.env` file with the above configuration +4. Ensure your account has sufficient ETH for bonds and gas + +## Running + +To run the proposer: + ```bash + cargo run --bin proposer + ``` + +The proposer will run indefinitely, creating new games and optionally resolving them based on the configuration. + +## Features + +### Game Creation +- Creates new dispute games at configurable block intervals. +- Computes L2 output roots for game proposals. +- Ensures proper game sequencing with parent-child relationships. +- Handles bond requirements for game creation. +- Supports fast finality mode with proofs. + +### Game Resolution +When enabled (`ENABLE_GAME_RESOLUTION=true`), the proposer: +- Monitors unchallenged games +- Resolves games after their challenge period expires +- Respects parent-child game relationships in resolution +- Only resolves games whose parent games are already resolved + +### Chain Monitoring +- Monitors the L2 chain's finalized (safe) head +- Creates proposals for new blocks as they become available +- Maintains proper spacing between proposals based on configuration +- Tracks the latest valid proposal for proper sequencing + +### Fast Finality Mode +When enabled (`FAST_FINALITY_MODE=true`), the proposer: +- Includes proofs with proposals for faster finality +- Adds additional data to game creation transactions +- Enables immediate validation of proposals + +## Logging + +The proposer uses the `tracing` crate for logging with a default level of INFO. You can adjust the log level by setting the `RUST_LOG` environment variable: + +```bash +RUST_LOG=debug cargo run --bin proposer +``` + +## Error Handling + +The proposer includes robust error handling for: +- RPC connection issues +- Transaction failures +- Contract interaction errors +- Invalid configurations + +Errors are logged with appropriate context to aid in debugging. + +## Architecture + +The proposer is built around the `OPSuccinctProposer` struct which manages: +- Configuration state. +- Wallet management for transactions. +- Game creation and resolution logic. +- Chain monitoring and interval management. + +Key components: +- `ProposerConfig`: Handles environment-based configuration. +- `handle_game_creation`: Main function for proposing new games that: + - Monitors the L2 chain's safe head. + - Determines appropriate block numbers for proposals. + - Creates new games with proper parent-child relationships. +- `handle_game_resolution`: Main function for resolving games that: + - Checks if resolution is enabled. + - Manages resolution of unchallenged games. + - Respects parent-child relationships. +- `run`: Main loop that: + - Runs at configurable intervals. + - Handles both game creation and resolution. + - Provides error isolation between creation and resolution tasks. + +### Helper Functions +- `create_game`: Creates individual games with proper bonding. +- `try_resolve_unchallenged_game`: Attempts to resolve a single game. +- `should_attempt_resolution`: Determines if games can be resolved based on parent status. +- `resolve_unchallenged_games`: Manages batch resolution of games. + +## Development + +When developing or modifying the proposer: +1. Ensure all environment variables are properly set. +2. Test with a local L1/L2 setup first. +3. Monitor logs for proper operation. +4. Test game creation and resolution separately. +5. Verify proper handling of edge cases (network issues, invalid responses, etc.). +6. Test both normal and fast finality modes. diff --git a/contracts/script/fp/DeployOPSuccinctFDG.s.sol b/contracts/script/fp/DeployOPSuccinctFDG.s.sol new file mode 100644 index 00000000..d8c8a88c --- /dev/null +++ b/contracts/script/fp/DeployOPSuccinctFDG.s.sol @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Libraries +import {Script} from "forge-std/Script.sol"; +import {console} from "forge-std/console.sol"; +import {Claim, GameType, Hash, OutputRoot, Duration} from "src/dispute/lib/Types.sol"; + +// Interfaces +import {IDisputeGame} from "interfaces/dispute/IDisputeGame.sol"; +import {IDisputeGameFactory} from "interfaces/dispute/IDisputeGameFactory.sol"; +import {ISP1Verifier} from "@sp1-contracts/src/ISP1Verifier.sol"; +import {IAnchorStateRegistry} from "interfaces/dispute/IAnchorStateRegistry.sol"; +import {ISuperchainConfig} from "interfaces/L1/ISuperchainConfig.sol"; +import {IOptimismPortal2} from "interfaces/L1/IOptimismPortal2.sol"; + +// Contracts +import {AnchorStateRegistry} from "src/dispute/AnchorStateRegistry.sol"; +import {AccessManager} from "../../src/fp/AccessManager.sol"; +import {SuperchainConfig} from "src/L1/SuperchainConfig.sol"; +import {DisputeGameFactory} from "src/dispute/DisputeGameFactory.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {OPSuccinctFaultDisputeGame} from "src/fp/OPSuccinctFaultDisputeGame.sol"; +import {SP1MockVerifier} from "@sp1-contracts/src/SP1MockVerifier.sol"; + +// Utils +import {MockOptimismPortal2} from "../../utils/MockOptimismPortal2.sol"; + +contract DeployOPSuccinctDG is Script { + function run() public { + vm.startBroadcast(); + + // Deploy factory proxy. + ERC1967Proxy factoryProxy = new ERC1967Proxy( + address(new DisputeGameFactory()), + abi.encodeWithSelector(DisputeGameFactory.initialize.selector, msg.sender) + ); + DisputeGameFactory factory = DisputeGameFactory(address(factoryProxy)); + + GameType gameType = GameType.wrap(uint32(vm.envUint("GAME_TYPE"))); + + // TODO(fakedev9999): Use real OptimismPortal2. + MockOptimismPortal2 portal = + new MockOptimismPortal2(gameType, vm.envUint("DISPUTE_GAME_FINALITY_DELAY_SECONDS")); + console.log("OptimismPortal2:", address(portal)); + + OutputRoot memory startingAnchorRoot = OutputRoot({root: Hash.wrap(keccak256("genesis")), l2BlockNumber: 0}); + + // Deploy the anchor state registry proxy. + ERC1967Proxy registryProxy = new ERC1967Proxy( + address(new AnchorStateRegistry()), + abi.encodeCall( + AnchorStateRegistry.initialize, + ( + ISuperchainConfig(address(new SuperchainConfig())), + IDisputeGameFactory(address(factory)), + IOptimismPortal2(payable(address(portal))), + startingAnchorRoot + ) + ) + ); + + AnchorStateRegistry registry = AnchorStateRegistry(address(registryProxy)); + console.log("Anchor state registry:", address(registry)); + // Deploy the access manager contract. + AccessManager accessManager = new AccessManager(); + console.log("Access manager:", address(accessManager)); + + // Set to permissionless games. + // TODO(fakedev9999): Allow custom config with env vars. + accessManager.setProposer(address(0), true); + accessManager.setChallenger(address(0), true); + + // Config values dependent on the `USE_SP1_MOCK_VERIFIER` flag. + address sp1VerifierAddress; + bytes32 rollupConfigHash; + bytes32 aggregationVkey; + bytes32 rangeVkeyCommitment; + + // Get or deploy SP1 verifier based on environment variable. + if (vm.envOr("USE_SP1_MOCK_VERIFIER", false)) { + // Deploy mock verifier for testing. + SP1MockVerifier sp1Verifier = new SP1MockVerifier(); + sp1VerifierAddress = address(sp1Verifier); + console.log("Using SP1 Mock Verifier:", address(sp1Verifier)); + + rollupConfigHash = bytes32(0); + aggregationVkey = bytes32(0); + rangeVkeyCommitment = bytes32(0); + } else { + // Use provided verifier address for production. + sp1VerifierAddress = vm.envAddress("VERIFIER_ADDRESS"); + console.log("Using SP1 Verifier Gateway:", sp1VerifierAddress); + + rollupConfigHash = vm.envBytes32("ROLLUP_CONFIG_HASH"); + aggregationVkey = vm.envBytes32("AGGREGATION_VKEY"); + rangeVkeyCommitment = vm.envBytes32("RANGE_VKEY_COMMITMENT"); + } + + OPSuccinctFaultDisputeGame gameImpl = new OPSuccinctFaultDisputeGame( + Duration.wrap(uint64(vm.envUint("MAX_CHALLENGE_DURATION"))), + Duration.wrap(uint64(vm.envUint("MAX_PROVE_DURATION"))), + IDisputeGameFactory(address(factory)), + ISP1Verifier(sp1VerifierAddress), + rollupConfigHash, + aggregationVkey, + rangeVkeyCommitment, + vm.envOr("PROOF_REWARD", uint256(0.01 ether)), + IAnchorStateRegistry(address(registry)), + accessManager + ); + + // Set initial bond and implementation in factory. + uint256 initialBond = vm.envOr("INITIAL_BOND", uint256(0.01 ether)); + factory.setInitBond(gameType, initialBond); + factory.setImplementation(gameType, IDisputeGame(address(gameImpl))); + + vm.stopBroadcast(); + + // Log deployed addresses. + console.log("Factory Proxy:", address(factoryProxy)); + console.log("Game Implementation:", address(gameImpl)); + console.log("SP1 Verifier:", sp1VerifierAddress); + } +} diff --git a/contracts/script/DeployMockVerifier.s.sol b/contracts/script/validity/DeployMockVerifier.s.sol similarity index 100% rename from contracts/script/DeployMockVerifier.s.sol rename to contracts/script/validity/DeployMockVerifier.s.sol diff --git a/contracts/script/OPSuccinctDGFDeployer.s.sol b/contracts/script/validity/OPSuccinctDGFDeployer.s.sol similarity index 90% rename from contracts/script/OPSuccinctDGFDeployer.s.sol rename to contracts/script/validity/OPSuccinctDGFDeployer.s.sol index 22217020..fd0a6c32 100644 --- a/contracts/script/OPSuccinctDGFDeployer.s.sol +++ b/contracts/script/validity/OPSuccinctDGFDeployer.s.sol @@ -2,10 +2,10 @@ pragma solidity ^0.8.15; import {Script} from "forge-std/Script.sol"; -import {OPSuccinctL2OutputOracle} from "../src/validity/OPSuccinctL2OutputOracle.sol"; -import {OPSuccinctDisputeGame} from "../src/validity/OPSuccinctDisputeGame.sol"; +import {OPSuccinctL2OutputOracle} from "../../src/validity/OPSuccinctL2OutputOracle.sol"; +import {OPSuccinctDisputeGame} from "../../src/validity/OPSuccinctDisputeGame.sol"; import {DisputeGameFactory} from "src/dispute/DisputeGameFactory.sol"; -import {Utils} from "../test/helpers/Utils.sol"; +import {Utils} from "../../test/helpers/Utils.sol"; import {Proxy} from "@optimism/src/universal/Proxy.sol"; import {console} from "forge-std/console.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; diff --git a/contracts/script/OPSuccinctDeployer.s.sol b/contracts/script/validity/OPSuccinctDeployer.s.sol similarity index 91% rename from contracts/script/OPSuccinctDeployer.s.sol rename to contracts/script/validity/OPSuccinctDeployer.s.sol index b79bc767..4a75e302 100644 --- a/contracts/script/OPSuccinctDeployer.s.sol +++ b/contracts/script/validity/OPSuccinctDeployer.s.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.15; import {Script} from "forge-std/Script.sol"; -import {OPSuccinctL2OutputOracle} from "../src/validity/OPSuccinctL2OutputOracle.sol"; -import {Utils} from "../test/helpers/Utils.sol"; +import {OPSuccinctL2OutputOracle} from "../../src/validity/OPSuccinctL2OutputOracle.sol"; +import {Utils} from "../../test/helpers/Utils.sol"; import {Proxy} from "@optimism/src/universal/Proxy.sol"; import {console} from "forge-std/console.sol"; diff --git a/contracts/script/OPSuccinctParameterUpdater.s.sol b/contracts/script/validity/OPSuccinctParameterUpdater.s.sol similarity index 96% rename from contracts/script/OPSuccinctParameterUpdater.s.sol rename to contracts/script/validity/OPSuccinctParameterUpdater.s.sol index 60aece0c..b9c7500c 100644 --- a/contracts/script/OPSuccinctParameterUpdater.s.sol +++ b/contracts/script/validity/OPSuccinctParameterUpdater.s.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.15; import {Script} from "forge-std/Script.sol"; -import {OPSuccinctL2OutputOracle} from "../src/validity/OPSuccinctL2OutputOracle.sol"; -import {Utils} from "../test/helpers/Utils.sol"; +import {OPSuccinctL2OutputOracle} from "../../src/validity/OPSuccinctL2OutputOracle.sol"; +import {Utils} from "../../test/helpers/Utils.sol"; import {Proxy} from "@optimism/src/universal/Proxy.sol"; import {console} from "forge-std/console.sol"; diff --git a/contracts/script/OPSuccinctUpgrader.s.sol b/contracts/script/validity/OPSuccinctUpgrader.s.sol similarity index 91% rename from contracts/script/OPSuccinctUpgrader.s.sol rename to contracts/script/validity/OPSuccinctUpgrader.s.sol index 8c49400f..efe4cc30 100644 --- a/contracts/script/OPSuccinctUpgrader.s.sol +++ b/contracts/script/validity/OPSuccinctUpgrader.s.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.15; import {Script} from "forge-std/Script.sol"; -import {OPSuccinctL2OutputOracle} from "../src/validity/OPSuccinctL2OutputOracle.sol"; -import {Utils} from "../test/helpers/Utils.sol"; +import {OPSuccinctL2OutputOracle} from "../../src/validity/OPSuccinctL2OutputOracle.sol"; +import {Utils} from "../../test/helpers/Utils.sol"; import {Proxy} from "@optimism/src/universal/Proxy.sol"; import {console} from "forge-std/console.sol"; diff --git a/contracts/src/fp/OPSuccinctFaultDisputeGame.sol b/contracts/src/fp/OPSuccinctFaultDisputeGame.sol index a460e837..6690f8e8 100644 --- a/contracts/src/fp/OPSuccinctFaultDisputeGame.sol +++ b/contracts/src/fp/OPSuccinctFaultDisputeGame.sol @@ -52,13 +52,13 @@ contract OPSuccinctFaultDisputeGame is Clone, ISemver, IDisputeGame { //////////////////////////////////////////////////////////////// enum ProposalStatus { - // The initial state of a new proposal + // The initial state of a new proposal. Unchallenged, - // A proposal that has been challenged but not yet proven + // A proposal that has been challenged but not yet proven. Challenged, - // An unchallenged proposal that has been proven valid with a verified proof + // An unchallenged proposal that has been proven valid with a verified proof. UnchallengedAndValidProofProvided, - // A challenged proposal that has been proven valid with a verified proof + // A challenged proposal that has been proven valid with a verified proof. ChallengedAndValidProofProvided, // The final state after resolution, either GameStatus.CHALLENGER_WINS or GameStatus.DEFENDER_WINS. Resolved @@ -136,13 +136,13 @@ contract OPSuccinctFaultDisputeGame is Clone, ISemver, IDisputeGame { /// @custom:semver 1.0.0 string public constant version = "1.0.0"; - /// @notice The starting timestamp of the game + /// @notice The starting timestamp of the game. Timestamp public createdAt; /// @notice The timestamp of the game's global resolution. Timestamp public resolvedAt; - /// @notice Returns the current status of the game. + /// @notice The current status of the game. GameStatus public status; /// @notice Flag for the `initialize` function to prevent re-initialization. @@ -302,7 +302,7 @@ contract OPSuccinctFaultDisputeGame is Clone, ISemver, IDisputeGame { GameType.unwrap(ANCHOR_STATE_REGISTRY.respectedGameType()) == GameType.unwrap(GAME_TYPE); } - /// @notice The l2BlockNumber of the disputed output root in the `L2OutputOracle`. + /// @notice The L2 block number for which this game is proposing an output root. function l2BlockNumber() public pure returns (uint256 l2BlockNumber_) { l2BlockNumber_ = _getArgUint256(0x54); } diff --git a/contracts/test/fp/OPSuccinctFaultDisputeGame.t.sol b/contracts/test/fp/OPSuccinctFaultDisputeGame.t.sol index 1bb6c19c..08e8c312 100644 --- a/contracts/test/fp/OPSuccinctFaultDisputeGame.t.sol +++ b/contracts/test/fp/OPSuccinctFaultDisputeGame.t.sol @@ -36,40 +36,8 @@ import {ISuperchainConfig} from "interfaces/L1/ISuperchainConfig.sol"; import {IOptimismPortal2} from "interfaces/L1/IOptimismPortal2.sol"; import {IAnchorStateRegistry} from "interfaces/dispute/IAnchorStateRegistry.sol"; -contract MockOptimismPortal2 { - /// @notice The delay between when a dispute game is resolved and when a withdrawal proven against it may be - /// finalized. - uint256 internal immutable DISPUTE_GAME_FINALITY_DELAY_SECONDS; - - /// @notice A mapping of dispute game addresses to whether or not they are blacklisted. - mapping(IDisputeGame => bool) public disputeGameBlacklist; - - /// @notice The game type that the OptimismPortal consults for output proposals. - GameType public respectedGameType; - - /// @notice The timestamp at which the respected game type was last updated. - uint64 public respectedGameTypeUpdatedAt; - - constructor(GameType _initialRespectedGameType, uint256 _disputeGameFinalityDelaySeconds) { - respectedGameType = _initialRespectedGameType; - respectedGameTypeUpdatedAt = uint64(block.timestamp); - - DISPUTE_GAME_FINALITY_DELAY_SECONDS = _disputeGameFinalityDelaySeconds; - } - - function blacklistDisputeGame(IDisputeGame _disputeGame) external { - disputeGameBlacklist[_disputeGame] = true; - } - - function setRespectedGameType(GameType _gameType) external { - respectedGameType = _gameType; - respectedGameTypeUpdatedAt = uint64(block.timestamp); - } - - function disputeGameFinalityDelaySeconds() public view returns (uint256) { - return DISPUTE_GAME_FINALITY_DELAY_SECONDS; - } -} +// Utils +import {MockOptimismPortal2} from "../../utils/MockOptimismPortal2.sol"; contract OPSuccinctFaultDisputeGameTest is Test { // Event definitions matching those in OPSuccinctFaultDisputeGame. diff --git a/contracts/test/validity/Upgrade.t.sol b/contracts/test/validity/Upgrade.t.sol index 6ec6a51a..eba9d233 100644 --- a/contracts/test/validity/Upgrade.t.sol +++ b/contracts/test/validity/Upgrade.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.15; import {Test, console} from "forge-std/Test.sol"; -import {OPSuccinctUpgrader} from "../../script/OPSuccinctUpgrader.s.sol"; +import {OPSuccinctUpgrader} from "../../script/validity/OPSuccinctUpgrader.s.sol"; import {OPSuccinctL2OutputOracle} from "../../src/validity/OPSuccinctL2OutputOracle.sol"; import {Proxy} from "@optimism/src/universal/Proxy.sol"; import {Utils} from "../helpers/Utils.sol"; diff --git a/contracts/utils/MockOptimismPortal2.sol b/contracts/utils/MockOptimismPortal2.sol new file mode 100644 index 00000000..b35526c7 --- /dev/null +++ b/contracts/utils/MockOptimismPortal2.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Libraries +import {GameType} from "src/dispute/lib/Types.sol"; + +// Interfaces +import {IDisputeGame} from "interfaces/dispute/IDisputeGame.sol"; + +contract MockOptimismPortal2 { + /// @notice The delay between when a dispute game is resolved and when a withdrawal proven against it may be + /// finalized. + uint256 internal immutable DISPUTE_GAME_FINALITY_DELAY_SECONDS; + + /// @notice A mapping of dispute game addresses to whether or not they are blacklisted. + mapping(IDisputeGame => bool) public disputeGameBlacklist; + + /// @notice The game type that the OptimismPortal consults for output proposals. + GameType public respectedGameType; + + /// @notice The timestamp at which the respected game type was last updated. + uint64 public respectedGameTypeUpdatedAt; + + constructor(GameType _initialRespectedGameType, uint256 _disputeGameFinalityDelaySeconds) { + respectedGameType = _initialRespectedGameType; + respectedGameTypeUpdatedAt = uint64(block.timestamp); + + DISPUTE_GAME_FINALITY_DELAY_SECONDS = _disputeGameFinalityDelaySeconds; + } + + function blacklistDisputeGame(IDisputeGame _disputeGame) external { + disputeGameBlacklist[_disputeGame] = true; + } + + function setRespectedGameType(GameType _gameType) external { + respectedGameType = _gameType; + respectedGameTypeUpdatedAt = uint64(block.timestamp); + } + + function disputeGameFinalityDelaySeconds() public view returns (uint256) { + return DISPUTE_GAME_FINALITY_DELAY_SECONDS; + } +} diff --git a/fault_proof/Cargo.toml b/fault_proof/Cargo.toml new file mode 100644 index 00000000..4da48aa3 --- /dev/null +++ b/fault_proof/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "op-succinct-fp" +version = "0.1.0" +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true + +[lib] +name = "fault_proof" +path = "src/lib.rs" + +[[bin]] +name = "proposer" +path = "bin/proposer.rs" + +[dependencies] +# alloy +alloy-contract.workspace = true +alloy-eips.workspace = true +alloy-network.workspace = true +alloy-primitives.workspace = true +alloy-provider.workspace = true +alloy-rpc-types.workspace = true +alloy-signer-local.workspace = true +alloy-sol-macro.workspace = true +alloy-sol-types.workspace = true +alloy-transport.workspace = true +alloy-transport-http.workspace = true + +# op-alloy +op-alloy-network.workspace = true +op-alloy-rpc-types.workspace = true + +# general +anyhow.workspace = true +async-trait.workspace = true +clap = { workspace = true, features = ["derive"] } +dotenv.workspace = true +log.workspace = true +tokio.workspace = true +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +serde_json.workspace = true +tracing.workspace = true diff --git a/fault_proof/bin/proposer.rs b/fault_proof/bin/proposer.rs new file mode 100644 index 00000000..8ee97ae8 --- /dev/null +++ b/fault_proof/bin/proposer.rs @@ -0,0 +1,356 @@ +use std::{env, time::Duration}; + +use alloy_eips::BlockNumberOrTag; +use alloy_network::Ethereum; +use alloy_primitives::{Address, U256}; +use alloy_provider::{fillers::TxFiller, Provider, ProviderBuilder}; +use alloy_signer_local::PrivateKeySigner; +use alloy_sol_types::SolValue; +use alloy_transport_http::reqwest::Url; +use anyhow::Result; +use clap::Parser; +use op_alloy_network::EthereumWallet; +use tokio::time; + +use fault_proof::{ + config::ProposerConfig, + contract::{ + DisputeGameFactory, DisputeGameFactory::DisputeGameFactoryInstance, GameStatus, + OPSuccinctFaultDisputeGame, ProposalStatus, + }, + utils::setup_logging, + FactoryTrait, L1Provider, L1ProviderWithWallet, L2Provider, L2ProviderTrait, +}; + +#[derive(Parser)] +struct Args { + #[clap(long, default_value = ".env.proposer")] + env_file: String, +} + +struct OPSuccinctProposer +where + F: TxFiller + Send + Sync, + P: Provider + Clone + Send + Sync, +{ + config: ProposerConfig, + l1_provider: L1Provider, + l2_provider: L2Provider, + l1_provider_with_wallet: L1ProviderWithWallet, + factory: DisputeGameFactoryInstance<(), L1ProviderWithWallet>, + init_bond: U256, +} + +impl OPSuccinctProposer +where + F: TxFiller + Send + Sync, + P: Provider + Clone + Send + Sync, +{ + /// Creates a new challenger instance with the provided L1 provider with wallet and factory contract instance. + pub async fn new( + l1_provider_with_wallet: L1ProviderWithWallet, + factory: DisputeGameFactoryInstance<(), L1ProviderWithWallet>, + ) -> Result { + let config = ProposerConfig::from_env()?; + + Ok(Self { + config: config.clone(), + l1_provider: ProviderBuilder::default().on_http(config.l1_rpc.clone()), + l2_provider: ProviderBuilder::default().on_http(config.l2_rpc), + l1_provider_with_wallet: l1_provider_with_wallet.clone(), + factory: factory.clone(), + init_bond: factory.fetch_init_bond(config.game_type).await?, + }) + } + + /// Creates a new game with the given parameters. + /// + /// `l2_block_number`: the L2 block number we are proposing the output root for. + /// `parent_game_index`: the index of the parent game. + async fn create_game(&self, l2_block_number: U256, parent_game_index: u32) -> Result<()> { + let extra_data = if self.config.fast_finality_mode { + tracing::info!("Creating game with fast finality mode"); + + let proof = vec![]; + <(U256, u32, bool, Vec)>::abi_encode_packed(&( + l2_block_number, + parent_game_index, + self.config.fast_finality_mode, + proof, + )) + } else { + tracing::info!("Creating game with non fast finality mode"); + + <(U256, u32)>::abi_encode_packed(&(l2_block_number, parent_game_index)) + }; + + let receipt = self + .factory + .create( + self.config.game_type, + self.l2_provider + .compute_output_root_at_block(l2_block_number) + .await?, + extra_data.into(), + ) + .value(self.init_bond) + .send() + .await? + .get_receipt() + .await?; + + let game_address = receipt.inner.logs()[0].address(); + + tracing::info!( + "New game {:?} created with tx {:?}", + game_address, + receipt.transaction_hash + ); + + Ok(()) + } + + /// Determines if we should attempt resolution or not. The `oldest_game_index` is configured + /// to be `latest_game_index`` - `max_games_to_check_for_resolution`. + /// + /// If the oldest game has no parent (i.e., it's a first game), we always attempt resolution. + /// For other games, we only attempt resolution if the parent game is not in progress. + /// + /// NOTE(fakedev9999): Needs to be updated considering more complex cases where there are + /// multiple branches of games. + async fn should_attempt_resolution(&self, oldest_game_index: U256) -> Result<(bool, Address)> { + let oldest_game_address = self + .factory + .fetch_game_address_by_index(oldest_game_index) + .await?; + let oldest_game = + OPSuccinctFaultDisputeGame::new(oldest_game_address, self.l1_provider.clone()); + let parent_game_index = oldest_game.claimData().call().await?.claimData_.parentIndex; + + // Always attempt resolution for first games (those with parent_game_index == u32::MAX) + // For other games, only attempt if the oldest game's parent game is resolved + if parent_game_index == u32::MAX { + Ok((true, oldest_game_address)) + } else { + let parent_game_address = self + .factory + .fetch_game_address_by_index(U256::from(parent_game_index)) + .await?; + let parent_game = + OPSuccinctFaultDisputeGame::new(parent_game_address, self.l1_provider.clone()); + + Ok(( + parent_game.status().call().await?.status_ != GameStatus::IN_PROGRESS, + oldest_game_address, + )) + } + } + + /// Attempts to resolve an unchallenged game. + /// + /// This function checks if the game is in progress and unchallenged, and if so, attempts to resolve it. + async fn try_resolve_unchallenged_game(&self, index: U256) -> Result<()> { + let game_address = self.factory.fetch_game_address_by_index(index).await?; + let game = OPSuccinctFaultDisputeGame::new(game_address, self.l1_provider.clone()); + if game.status().call().await?.status_ != GameStatus::IN_PROGRESS { + tracing::info!( + "Game {:?} at index {:?} is not in progress, not attempting resolution", + game_address, + index + ); + return Ok(()); + } + + let claim_data = game.claimData().call().await?.claimData_; + if claim_data.status != ProposalStatus::Unchallenged { + tracing::info!( + "Game {:?} at index {:?} is not unchallenged, not attempting resolution", + game_address, + index + ); + return Ok(()); + } + + let current_timestamp = self + .l2_provider + .get_l2_block_by_number(BlockNumberOrTag::Latest) + .await? + .header + .timestamp; + let deadline = U256::from(claim_data.deadline).to::(); + if deadline >= current_timestamp { + tracing::info!( + "Game {:?} at index {:?} deadline {:?} has not passed, not attempting resolution", + game_address, + index, + deadline + ); + return Ok(()); + } + + let contract = + OPSuccinctFaultDisputeGame::new(game_address, self.l1_provider_with_wallet.clone()); + let receipt = contract.resolve().send().await?.get_receipt().await?; + tracing::info!( + "Successfully resolved unchallenged game {:?} at index {:?} with tx {:?}", + game_address, + index, + receipt.transaction_hash + ); + Ok(()) + } + + /// Attempts to resolve all unchallenged games, up to `max_games_to_check_for_resolution`. + async fn resolve_unchallenged_games(&self) -> Result<()> { + // Find latest game index, return early if no games exist. + let Some(latest_game_index) = self.factory.fetch_latest_game_index().await? else { + tracing::info!("No games exist, skipping resolution"); + return Ok(()); + }; + + // If the oldest game's parent game is not resolved, we'll not attempt resolution. + // Except for the game without a parent, which are first games. + let oldest_game_index = latest_game_index + .saturating_sub(U256::from(self.config.max_games_to_check_for_resolution)); + let games_to_check = + latest_game_index.min(U256::from(self.config.max_games_to_check_for_resolution)); + + let (should_attempt_resolution, game_address) = + self.should_attempt_resolution(oldest_game_index).await?; + + if should_attempt_resolution { + for i in 0..games_to_check.to::() { + let index = oldest_game_index + U256::from(i); + self.try_resolve_unchallenged_game(index).await?; + } + } else { + tracing::info!( + "Oldest game {:?} at index {:?} is not resolved, not attempting resolution", + game_address, + oldest_game_index + ); + } + + Ok(()) + } + + /// Handles the creation of a new game if conditions are met. + async fn handle_game_creation(&self) -> Result<()> { + let _span = tracing::info_span!("[[Proposing]]").entered(); + + // Get the safe L2 head block number + let safe_l2_head_block_number = self + .l2_provider + .get_l2_block_by_number(BlockNumberOrTag::Safe) + .await? + .header + .number; + tracing::debug!("Safe L2 head block number: {:?}", safe_l2_head_block_number); + + // Get the latest valid proposal. + let latest_valid_proposal = self + .factory + .get_latest_valid_proposal(self.l2_provider.clone()) + .await?; + + // Determine next block number and parent game index. + // + // Two cases based on the result of `get_latest_valid_proposal`: + // 1. With existing valid proposal: + // - Block number = latest valid proposal's block + proposal interval. + // - Parent = latest valid game's index. + // + // 2. Without valid proposal (first game or all existing games being faulty): + // - Block number = anchor L2 block number + proposal interval. + // - Parent = u32::MAX (special value indicating no parent). + let (next_l2_block_number_for_proposal, parent_game_index) = match latest_valid_proposal { + Some((latest_block, latest_game_idx)) => ( + latest_block + U256::from(self.config.proposal_interval_in_blocks), + latest_game_idx.to::(), + ), + None => { + let anchor_l2_block_number = self + .factory + .get_anchor_l2_block_number(self.config.game_type) + .await?; + tracing::info!("Anchor L2 block number: {:?}", anchor_l2_block_number); + ( + anchor_l2_block_number + .checked_add(U256::from(self.config.proposal_interval_in_blocks)) + .unwrap(), + u32::MAX, + ) + } + }; + + // There's always a new game to propose, as the chain is always moving forward from the genesis block set for the game type. + // Only create a new game if the safe L2 head block number is greater than the next L2 block number for proposal. + if U256::from(safe_l2_head_block_number) > next_l2_block_number_for_proposal { + self.create_game(next_l2_block_number_for_proposal, parent_game_index) + .await?; + } + + Ok(()) + } + + /// Handles the resolution of all eligible unchallenged games. + async fn handle_game_resolution(&self) -> Result<()> { + // Only resolve games if the config is enabled. + if !self.config.enable_game_resolution { + return Ok(()); + } + + let _span = tracing::info_span!("[[Resolving]]").entered(); + self.resolve_unchallenged_games().await + } + + /// Runs the proposer indefinitely. + async fn run(&self) -> Result<()> { + tracing::info!("OP Succinct Proposer running..."); + let mut interval = time::interval(Duration::from_secs(self.config.fetch_interval)); + + loop { + interval.tick().await; + + if let Err(e) = self.handle_game_creation().await { + tracing::warn!("Failed to handle game creation: {:?}", e); + } + + if let Err(e) = self.handle_game_resolution().await { + tracing::warn!("Failed to handle game resolution: {:?}", e); + } + } + } +} + +#[tokio::main] +async fn main() { + setup_logging(); + + let args = Args::parse(); + dotenv::from_filename(args.env_file).ok(); + + let wallet = EthereumWallet::from( + env::var("PRIVATE_KEY") + .expect("PRIVATE_KEY must be set") + .parse::() + .unwrap(), + ); + + let l1_provider_with_wallet = ProviderBuilder::new() + .wallet(wallet.clone()) + .on_http(env::var("L1_RPC").unwrap().parse::().unwrap()); + + let factory = DisputeGameFactory::new( + env::var("FACTORY_ADDRESS") + .expect("FACTORY_ADDRESS must be set") + .parse::
() + .unwrap(), + l1_provider_with_wallet.clone(), + ); + + let proposer = OPSuccinctProposer::new(l1_provider_with_wallet, factory) + .await + .unwrap(); + proposer.run().await.expect("Runs in an infinite loop"); +} diff --git a/fault_proof/src/config.rs b/fault_proof/src/config.rs new file mode 100644 index 00000000..ab135492 --- /dev/null +++ b/fault_proof/src/config.rs @@ -0,0 +1,75 @@ +use std::env; + +use alloy_primitives::Address; +use alloy_transport_http::reqwest::Url; +use anyhow::Result; + +#[derive(Debug, Clone)] +pub struct ProposerConfig { + /// The L1 RPC URL. + pub l1_rpc: Url, + + /// The L2 RPC URL. + pub l2_rpc: Url, + + /// The address of the factory contract. + pub factory_address: Address, + + /// The interval in blocks between proposing new games. + pub proposal_interval_in_blocks: u64, + + /// The interval in seconds between checking for new proposals and game resolution. + /// During each interval, the proposer: + /// 1. Checks the safe L2 head block number + /// 2. Gets the latest valid proposal + /// 3. Creates a new game if conditions are met + /// 4. Optionally attempts to resolve unchallenged games + pub fetch_interval: u64, + + /// The type of game to propose. + pub game_type: u32, + + /// Whether to enable game resolution. + /// When game resolution is not enabled, the proposer will only propose new games. + pub enable_game_resolution: bool, + + /// The number of games to check for resolution. + /// When game resolution is enabled, the proposer will attempt to resolve games that are + /// unchallenged up to `max_games_to_check_for_resolution` games behind the latest game. + pub max_games_to_check_for_resolution: u64, + + /// Whether to enable fast finality mode. + /// When fast finality mode is enabled, the proposer will propose games with a proof that + /// the proposal is valid. + pub fast_finality_mode: bool, +} + +impl ProposerConfig { + pub fn from_env() -> Result { + dotenv::from_filename(".env.proposer").ok(); + + Ok(Self { + l1_rpc: env::var("L1_RPC")?.parse().expect("L1_RPC not set"), + l2_rpc: env::var("L2_RPC")?.parse().expect("L2_RPC not set"), + factory_address: env::var("FACTORY_ADDRESS")? + .parse() + .expect("FACTORY_ADDRESS not set"), + proposal_interval_in_blocks: env::var("PROPOSAL_INTERVAL_IN_BLOCKS") + .unwrap_or("1000".to_string()) + .parse()?, + fetch_interval: env::var("FETCH_INTERVAL") + .unwrap_or("30".to_string()) + .parse()?, + game_type: env::var("GAME_TYPE").expect("GAME_TYPE not set").parse()?, + enable_game_resolution: env::var("ENABLE_GAME_RESOLUTION") + .unwrap_or("false".to_string()) + .parse()?, + max_games_to_check_for_resolution: env::var("MAX_GAMES_TO_CHECK_FOR_RESOLUTION") + .unwrap_or("100".to_string()) + .parse()?, + fast_finality_mode: env::var("FAST_FINALITY_MODE") + .unwrap_or("false".to_string()) + .parse()?, + }) + } +} diff --git a/fault_proof/src/contract.rs b/fault_proof/src/contract.rs new file mode 100644 index 00000000..8e7c1907 --- /dev/null +++ b/fault_proof/src/contract.rs @@ -0,0 +1,113 @@ +use alloy_sol_macro::sol; + +sol! { + type GameType is uint32; + type Claim is bytes32; + type Timestamp is uint64; + type Hash is bytes32; + + #[sol(rpc)] + #[derive(Debug)] + contract DisputeGameFactory { + /// @notice `gameImpls` is a mapping that maps `GameType`s to their respective + /// `IDisputeGame` implementations. + mapping(GameType => IDisputeGame) public gameImpls; + + /// @notice Returns the required bonds for initializing a dispute game of the given type. + mapping(GameType => uint256) public initBonds; + + /// @notice The total number of dispute games created by this factory. + function gameCount() external view returns (uint256 gameCount_); + + /// @notice `gameAtIndex` returns the dispute game contract address and its creation timestamp + /// at the given index. Each created dispute game increments the underlying index. + function gameAtIndex(uint256 _index) external view returns (GameType gameType, Timestamp timestamp, IDisputeGame proxy); + + /// @notice Creates a new DisputeGame proxy contract. + function create(GameType gameType, Claim rootClaim, bytes extraData) external; + } + + #[allow(missing_docs)] + #[sol(rpc)] + interface IDisputeGame {} + + #[sol(rpc)] + contract OPSuccinctFaultDisputeGame { + /// @notice The L2 block number for which this game is proposing an output root. + function l2BlockNumber() public pure returns (uint256 l2BlockNumber_); + + /// @notice Getter for the root claim. + function rootClaim() public pure returns (Claim rootClaim_); + + /// @notice Getter for the status of the game. + function status() public view returns (GameStatus status_); + + /// @notice Getter for the claim data. + function claimData() public view returns (ClaimData memory claimData_); + + /// @notice Resolves the game after the clock expires. + /// `DEFENDER_WINS` when no one has challenged the proposer's claim and `MAX_CHALLENGE_DURATION` has passed + /// or there is a challenge but the prover has provided a valid proof within the `MAX_PROVE_DURATION`. + /// `CHALLENGER_WINS` when the proposer's claim has been challenged, but the proposer has not proven + /// its claim within the `MAX_PROVE_DURATION`. + function resolve() external returns (GameStatus status_); + + /// @notice Returns the anchor state registry contract. + function anchorStateRegistry() external view returns (IAnchorStateRegistry registry_); + } + + #[allow(missing_docs)] + #[sol(rpc)] + interface IAnchorStateRegistry {} + + #[allow(missing_docs)] + #[sol(rpc)] + contract AnchorStateRegistry { + /// @notice Returns the current anchor root. + function getAnchorRoot() public view returns (Hash, uint256); + } + + #[derive(Debug, PartialEq)] + /// @notice The current status of the dispute game. + enum GameStatus { + // The game is currently in progress, and has not been resolved. + IN_PROGRESS, + // The game has concluded, and the `rootClaim` was challenged successfully. + CHALLENGER_WINS, + // The game has concluded, and the `rootClaim` could not be contested. + DEFENDER_WINS + } + + #[derive(Debug, PartialEq)] + enum ProposalStatus { + // The initial state of a new proposal. + Unchallenged, + // A proposal that has been challenged but not yet proven. + Challenged, + // An unchallenged proposal that has been proven valid with a verified proof. + UnchallengedAndValidProofProvided, + // A challenged proposal that has been proven valid with a verified proof. + ChallengedAndValidProofProvided, + // The final state after resolution, either GameStatus.CHALLENGER_WINS or GameStatus.DEFENDER_WINS. + Resolved + } + + #[derive(Debug)] + /// @notice The `ClaimData` struct represents the data associated with a Claim. + struct ClaimData { + uint32 parentIndex; + address counteredBy; + address prover; + Claim claim; + ProposalStatus status; + Timestamp deadline; + } + + /// @notice The `L2Output` struct represents the L2 output. + struct L2Output { + uint64 zero; + bytes32 l2_state_root; + bytes32 l2_storage_hash; + bytes32 l2_claim_hash; + } +} diff --git a/fault_proof/src/lib.rs b/fault_proof/src/lib.rs new file mode 100644 index 00000000..7f193f67 --- /dev/null +++ b/fault_proof/src/lib.rs @@ -0,0 +1,250 @@ +pub mod config; +pub mod contract; +pub mod utils; + +use alloy_eips::BlockNumberOrTag; +use alloy_network::Ethereum; +use alloy_primitives::{address, keccak256, Address, FixedBytes, B256, U256}; +use alloy_provider::{ + fillers::{FillProvider, TxFiller}, + Provider, RootProvider, +}; +use alloy_rpc_types::eth::Block; +use alloy_sol_types::SolValue; +use anyhow::{bail, Result}; +use async_trait::async_trait; +use op_alloy_network::{primitives::BlockTransactionsKind, Optimism}; +use op_alloy_rpc_types::Transaction; + +use crate::contract::{ + AnchorStateRegistry, DisputeGameFactory::DisputeGameFactoryInstance, L2Output, + OPSuccinctFaultDisputeGame, +}; + +pub type L1Provider = RootProvider; +pub type L2Provider = RootProvider; +pub type L2NodeProvider = RootProvider; +pub type L1ProviderWithWallet = FillProvider; + +#[async_trait] +pub trait L2ProviderTrait { + /// Get the L2 block by number. + async fn get_l2_block_by_number( + &self, + block_number: BlockNumberOrTag, + ) -> Result>; + + /// Get the L2 storage root for an address at a given block number. + async fn get_l2_storage_root( + &self, + address: Address, + block_number: BlockNumberOrTag, + ) -> Result; + + /// Compute the output root at a given L2 block number. + async fn compute_output_root_at_block(&self, l2_block_number: U256) -> Result>; +} + +#[async_trait] +impl L2ProviderTrait for L2Provider { + /// Get the L2 block by number. + async fn get_l2_block_by_number( + &self, + block_number: BlockNumberOrTag, + ) -> Result> { + let block = self + .get_block_by_number(block_number, BlockTransactionsKind::Hashes) + .await?; + if let Some(block) = block { + Ok(block) + } else { + bail!("Failed to get L2 block by number"); + } + } + + /// Get the L2 storage root for an address at a given block number. + async fn get_l2_storage_root( + &self, + address: Address, + block_number: BlockNumberOrTag, + ) -> Result { + let storage_root = self + .get_proof(address, Vec::new()) + .block_id(block_number.into()) + .await? + .storage_hash; + Ok(storage_root) + } + + /// Compute the output root at a given L2 block number. + /// + /// Local implementation is used because the RPC method `optimism_outputAtBlock` can fail for older + /// blocks if the L2 node isn't fully synced or has pruned historical state data. + /// + /// Common error: "missing trie node ... state is not available". + async fn compute_output_root_at_block(&self, l2_block_number: U256) -> Result> { + let l2_block = self + .get_l2_block_by_number(BlockNumberOrTag::Number(l2_block_number.to::())) + .await?; + let l2_state_root = l2_block.header.state_root; + let l2_claim_hash = l2_block.header.hash; + let l2_storage_root = self + .get_l2_storage_root( + address!("0x4200000000000000000000000000000000000016"), + BlockNumberOrTag::Number(l2_block_number.to::()), + ) + .await?; + + let l2_claim_encoded = L2Output { + zero: 0, + l2_state_root: l2_state_root.0.into(), + l2_storage_hash: l2_storage_root.0.into(), + l2_claim_hash: l2_claim_hash.0.into(), + }; + let l2_output_root = keccak256(l2_claim_encoded.abi_encode()); + Ok(l2_output_root) + } +} + +#[async_trait] +pub trait FactoryTrait { + /// Fetches the bond required to create a game. + async fn fetch_init_bond(&self, game_type: u32) -> Result; + + /// Fetches the latest game index. + async fn fetch_latest_game_index(&self) -> Result>; + + /// Fetches the game address by index. + async fn fetch_game_address_by_index(&self, game_index: U256) -> Result
; + + /// Get the latest valid proposal. + /// + /// This function checks from the latest game to the earliest game, returning the latest valid proposal. + async fn get_latest_valid_proposal( + &self, + l2_provider: L2Provider, + ) -> Result>; + + /// Get the anchor L2 block number. + /// + /// This function returns the L2 block number of the anchor game for a given game type. + async fn get_anchor_l2_block_number(&self, game_type: u32) -> Result; +} + +#[async_trait] +impl FactoryTrait for DisputeGameFactoryInstance<(), L1ProviderWithWallet> +where + F: TxFiller, + P: Provider + Clone, + L1ProviderWithWallet: alloy_contract::private::Provider<(), Ethereum>, +{ + /// Fetches the bond required to create a game. + async fn fetch_init_bond(&self, game_type: u32) -> Result { + let init_bond = self.initBonds(game_type).call().await?; + Ok(init_bond._0) + } + + /// Fetches the latest game index. + async fn fetch_latest_game_index(&self) -> Result> { + let game_count = self.gameCount().call().await?; + + if game_count.gameCount_ == U256::ZERO { + tracing::info!("No games exist yet"); + return Ok(None); + } + + let latest_game_index = game_count.gameCount_ - U256::from(1); + tracing::info!("Latest game index: {:?}", latest_game_index); + + Ok(Some(latest_game_index)) + } + + /// Fetches the game address by index. + async fn fetch_game_address_by_index(&self, game_index: U256) -> Result
{ + let game = self.gameAtIndex(game_index).call().await?; + Ok(game.proxy) + } + + /// Get the latest valid proposal. + /// + /// This function checks from the latest game to the earliest game, returning the latest valid proposal. + async fn get_latest_valid_proposal( + &self, + l2_provider: L2Provider, + ) -> Result> { + // Get latest game index, return None if no games exist. + let Some(mut game_index) = self.fetch_latest_game_index().await? else { + tracing::info!("No games exist yet"); + return Ok(None); + }; + + let mut block_number; + + // Loop through games in reverse order (latest to earliest) to find the most recent valid game + loop { + // Get the game contract for the current index. + let game_address = self.fetch_game_address_by_index(game_index).await?; + let game = OPSuccinctFaultDisputeGame::new(game_address, self.provider()); + + // Get the L2 block number the game is proposing output for. + block_number = game.l2BlockNumber().call().await?.l2BlockNumber_; + tracing::debug!( + "Checking if game {:?} at block {:?} is valid", + game_address, + block_number + ); + + // Get the output root the game is proposing. + let game_claim = game.rootClaim().call().await?.rootClaim_; + + // Compute the actual output root at the L2 block number. + let output_root = l2_provider + .compute_output_root_at_block(block_number) + .await?; + + // If the output root matches the game claim, we've found the latest valid proposal. + if output_root == game_claim { + break; + } + + // If the output root doesn't match the game claim, we need to find earlier games. + tracing::info!( + "Output root {:?} is not same as game claim {:?}", + output_root, + game_claim + ); + + // If we've reached index 0 (the earliest game) and still haven't found a valid proposal. + // Return `None` as no valid proposals were found. + if game_index == U256::ZERO { + tracing::info!("No valid proposals found after checking all games"); + return Ok(None); + } + + // Decrement the game index to check the previous game. + game_index -= U256::from(1); + } + + tracing::info!( + "Latest valid proposal at game index {:?} with l2 block number: {:?}", + game_index, + block_number + ); + + Ok(Some((block_number, game_index))) + } + + /// Get the anchor L2 block number. + /// + /// This function returns the L2 block number of the anchor game for a given game type. + async fn get_anchor_l2_block_number(&self, game_type: u32) -> Result { + let game_impl_address = self.gameImpls(game_type).call().await?._0; + let game_impl = OPSuccinctFaultDisputeGame::new(game_impl_address, self.provider()); + let anchor_state_registry = AnchorStateRegistry::new( + game_impl.anchorStateRegistry().call().await?.registry_, + self.provider(), + ); + let anchor_l2_block_number = anchor_state_registry.getAnchorRoot().call().await?._1; + Ok(anchor_l2_block_number) + } +} diff --git a/fault_proof/src/utils.rs b/fault_proof/src/utils.rs new file mode 100644 index 00000000..564dff0d --- /dev/null +++ b/fault_proof/src/utils.rs @@ -0,0 +1,20 @@ +use tracing_subscriber::{fmt, EnvFilter}; + +pub fn setup_logging() { + let format = fmt::format() + .with_level(true) + .with_target(false) + .with_thread_ids(false) + .with_thread_names(false) + .with_file(false) + .with_line_number(false) + .with_ansi(true); + + // Initialize logging using RUST_LOG environment variable, defaulting to INFO level + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::try_from_env("RUST_LOG").unwrap_or_else(|_| { + EnvFilter::from_default_env().add_directive(tracing::Level::INFO.into()) + })) + .event_format(format) + .init(); +} diff --git a/proposer/succinct/Dockerfile b/proposer/succinct/Dockerfile index 8aa47d9c..bd943ce4 100644 --- a/proposer/succinct/Dockerfile +++ b/proposer/succinct/Dockerfile @@ -31,6 +31,7 @@ RUN curl -L https://sp1.succinct.xyz | bash && \ # Copy only what's needed for the build COPY Cargo.toml Cargo.lock ./ COPY proposer/succinct ./proposer/succinct +COPY fault_proof ./fault_proof COPY elf ./elf COPY utils ./utils COPY programs ./programs From b4c9d394fcf8d13338a334f44e519e18dbd44641 Mon Sep 17 00:00:00 2001 From: fakedev9999 Date: Sun, 16 Feb 2025 18:12:09 -0800 Subject: [PATCH 2/5] fix: fix deploy script to set reward/bond in wei --- book/fault_proofs/deploy.md | 5 ++--- contracts/script/fp/DeployOPSuccinctFDG.s.sol | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/book/fault_proofs/deploy.md b/book/fault_proofs/deploy.md index 9bbc33b4..0845e892 100644 --- a/book/fault_proofs/deploy.md +++ b/book/fault_proofs/deploy.md @@ -31,7 +31,6 @@ Create a `.env` file in the contracts directory with the following variables: | `DISPUTE_GAME_FINALITY_DELAY_SECONDS` | Delay before finalizing dispute games. | `604800` for 7 days | | `MAX_CHALLENGE_DURATION` | Maximum duration for challenges in seconds. | `604800` for 7 days | | `MAX_PROVE_DURATION` | Maximum duration for proving in seconds. | `86400` for 1 day | -| `PROOF_REWARD` | Reward for successful proofs (optional). | `0.01 ether` | ### SP1 Verifier Configuration Choose one of the following: @@ -70,8 +69,8 @@ Choose one of the following: The deployment script deploys the contract with the following parameters: -- **Initial Bond**: 0.01 ETH. -- **Proof Reward**: 0.01 ETH (configurable via `PROOF_REWARD`). +- **Initial Bond**: 0.01 ETH by default (configurable via `INITIAL_BOND` in wei, so 10000000000000000 wei for 0.01 ETH). +- **Proof Reward**: 0.01 ETH by default (configurable via `PROOF_REWARD` in wei, so 10000000000000000 wei for 0.01 ETH). - **Starting Anchor Root**: Genesis configuration with block number 0. - **Access Control**: Permissionless (address(0) can propose and challenge). diff --git a/contracts/script/fp/DeployOPSuccinctFDG.s.sol b/contracts/script/fp/DeployOPSuccinctFDG.s.sol index d8c8a88c..c8e4f034 100644 --- a/contracts/script/fp/DeployOPSuccinctFDG.s.sol +++ b/contracts/script/fp/DeployOPSuccinctFDG.s.sol @@ -111,8 +111,7 @@ contract DeployOPSuccinctDG is Script { ); // Set initial bond and implementation in factory. - uint256 initialBond = vm.envOr("INITIAL_BOND", uint256(0.01 ether)); - factory.setInitBond(gameType, initialBond); + factory.setInitBond(gameType, vm.envOr("INITIAL_BOND", uint256(0.01 ether))); factory.setImplementation(gameType, IDisputeGame(address(gameImpl))); vm.stopBroadcast(); From 071f5a547d37486d93a7972585526826a9fd2b81 Mon Sep 17 00:00:00 2001 From: fakedev9999 Date: Mon, 17 Feb 2025 21:21:42 -0800 Subject: [PATCH 3/5] docs: add quick start guide --- book/SUMMARY.md | 1 + book/fault_proofs/quick_start.md | 81 +++++++++++++++++++ contracts/script/fp/DeployOPSuccinctFDG.s.sol | 2 +- fault_proof/src/config.rs | 2 +- 4 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 book/fault_proofs/quick_start.md diff --git a/book/SUMMARY.md b/book/SUMMARY.md index d95914dd..ca4ec514 100644 --- a/book/SUMMARY.md +++ b/book/SUMMARY.md @@ -23,6 +23,7 @@ - [Update Contract Parameters](./contracts/update-parameters.md) - [Modifications to Original `L2OutputOracle`](./contracts/modifications.md) - [Fault Proofs](./fault_proofs/fault_proof_architecture.md) + - [Quick Start](./fault_proofs/quick_start.md) - [Deploy FP Contracts](./fault_proofs/deploy.md) - [How to run the FP Proposer](./fault_proofs/proposer.md) - [Experimental](./experimental/intro.md) diff --git a/book/fault_proofs/quick_start.md b/book/fault_proofs/quick_start.md new file mode 100644 index 00000000..5440a262 --- /dev/null +++ b/book/fault_proofs/quick_start.md @@ -0,0 +1,81 @@ +# Quick Start Guide: OP Succinct Fault Dispute Game + +This guide provides the fastest path to try out OP Succinct fault dispute games by deploying contracts and running a proposer to create games. + +## Prerequisites + +- [Foundry](https://book.getfoundry.sh/getting-started/installation) +- [Rust](https://www.rust-lang.org/tools/install) +- L1 and L2 RPC endpoints +- ETH on L1 for deployment and bonds + +## Step 1: Deploy Contracts + +1. Clone and setup the repository: +```bash +git clone https://github.com/succinctlabs/op-succinct.git +cd op-succinct +forge install +``` + +2. Create a `.env` file in the contracts directory: +```env +# example .env file + +# Required - set these values +GAME_TYPE=42 +DISPUTE_GAME_FINALITY_DELAY_SECONDS=604800 +MAX_CHALLENGE_DURATION=604800 +MAX_PROVE_DURATION=86400 + +# For testing, use mock verifier +USE_SP1_MOCK_VERIFIER=true +``` + +3. Deploy contracts: +```bash +cd contracts +forge script script/fp/DeployOPSuccinctFDG.s.sol --broadcast --rpc-url --private-key +``` + +Save the output addresses, particularly the `FACTORY_ADDRESS` output as "Factory Proxy: 0x..." + +## Step 2: Run the Proposer + +1. Create a `.env.proposer` file in the fault_proof directory: +```env +# Required Configuration +L1_RPC= +L2_RPC= +FACTORY_ADDRESS= +GAME_TYPE=42 +PRIVATE_KEY= +``` + +2. Run the proposer: +```bash +cargo run --bin proposer +``` + +## Step 3: Monitor Games + +1. The proposer will automatically create new games at regular intervals (every 1800 blocks with the default config) +2. You can view created games on a block explorer using the factory address and the game address in the proposer logs + +## Next Steps + +Once you've seen the basic flow: +1. Try `ENABLE_GAME_RESOLUTION=true` to automatically resolve unchallenged games +2. Try enabling `FAST_FINALITY_MODE=true` to include proofs on game creation and get faster finality +3. Try removing the `USE_SP1_MOCK_VERIFIER=true` flag and use [Succinct Prover Network](https://docs.succinct.xyz/docs/sp1/generating-proofs/prover-network) with [deployed SP1 Verifiers](https://docs.succinct.xyz/docs/sp1/verification/onchain/contract-addresses) for production + +## Troubleshooting + +Common issues: +- **Deployment fails**: Check RPC connection and ETH balance +- **Proposer won't start**: Verify environment variables and addresses +- **Games not creating**: Check proposer logs for errors and L1,L2 RPC endpoints + +For detailed configuration and advanced features, see: +- [Full Deployment Guide](./deploy.md) +- [Proposer Documentation](./proposer.md) diff --git a/contracts/script/fp/DeployOPSuccinctFDG.s.sol b/contracts/script/fp/DeployOPSuccinctFDG.s.sol index c8e4f034..f2d9a041 100644 --- a/contracts/script/fp/DeployOPSuccinctFDG.s.sol +++ b/contracts/script/fp/DeployOPSuccinctFDG.s.sol @@ -26,7 +26,7 @@ import {SP1MockVerifier} from "@sp1-contracts/src/SP1MockVerifier.sol"; // Utils import {MockOptimismPortal2} from "../../utils/MockOptimismPortal2.sol"; -contract DeployOPSuccinctDG is Script { +contract DeployOPSuccinctFDG is Script { function run() public { vm.startBroadcast(); diff --git a/fault_proof/src/config.rs b/fault_proof/src/config.rs index ab135492..434e382e 100644 --- a/fault_proof/src/config.rs +++ b/fault_proof/src/config.rs @@ -55,7 +55,7 @@ impl ProposerConfig { .parse() .expect("FACTORY_ADDRESS not set"), proposal_interval_in_blocks: env::var("PROPOSAL_INTERVAL_IN_BLOCKS") - .unwrap_or("1000".to_string()) + .unwrap_or("1800".to_string()) .parse()?, fetch_interval: env::var("FETCH_INTERVAL") .unwrap_or("30".to_string()) From 3f67cbd371cc204e4c27882c81f7ed3ff55607ca Mon Sep 17 00:00:00 2001 From: fakedev9999 Date: Wed, 19 Feb 2025 15:32:21 -0800 Subject: [PATCH 4/5] cleanup --- book/fault_proofs/deploy.md | 15 +++++++++------ fault_proof/src/lib.rs | 1 - 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/book/fault_proofs/deploy.md b/book/fault_proofs/deploy.md index 0845e892..b14d20f8 100644 --- a/book/fault_proofs/deploy.md +++ b/book/fault_proofs/deploy.md @@ -33,15 +33,18 @@ Create a `.env` file in the contracts directory with the following variables: | `MAX_PROVE_DURATION` | Maximum duration for proving in seconds. | `86400` for 1 day | ### SP1 Verifier Configuration -Choose one of the following: +For testing, set: +```bash +USE_SP1_MOCK_VERIFIER=true +``` +For production, set all of these: | Variable | Description | Example | |----------|-------------|---------| -| `USE_SP1_MOCK_VERIFIER` | Set to true to deploy a mock verifier for testing. | `true` | -| `VERIFIER_ADDRESS` | Address of the SP1 verifier for production. | `0x...` | -| `ROLLUP_CONFIG_HASH` | Hash of the rollup configuration (if not using mock). | `0x...` | -| `AGGREGATION_VKEY` | Verification key for aggregation (if not using mock). | `0x...` | -| `RANGE_VKEY_COMMITMENT` | Commitment to range verification key (if not using mock). | `0x...` | +| `VERIFIER_ADDRESS` | Address of the SP1 verifier ([see contract addresses](https://docs.succinct.xyz/docs/sp1/verification/onchain/contract-addresses)) | `0x...` | +| `ROLLUP_CONFIG_HASH` | Hash of the rollup configuration | `0x...` | +| `AGGREGATION_VKEY` | Verification key for aggregation | `0x...` | +| `RANGE_VKEY_COMMITMENT` | Commitment to range verification key | `0x...` | ## Deployment diff --git a/fault_proof/src/lib.rs b/fault_proof/src/lib.rs index 7f193f67..0e96d496 100644 --- a/fault_proof/src/lib.rs +++ b/fault_proof/src/lib.rs @@ -136,7 +136,6 @@ impl FactoryTrait for DisputeGameFactoryInstance<(), L1ProviderWithW where F: TxFiller, P: Provider + Clone, - L1ProviderWithWallet: alloy_contract::private::Provider<(), Ethereum>, { /// Fetches the bond required to create a game. async fn fetch_init_bond(&self, game_type: u32) -> Result { From 44ef872daf445f0982291382d5377a290f685f1c Mon Sep 17 00:00:00 2001 From: fakedev9999 Date: Wed, 19 Feb 2025 18:17:26 -0800 Subject: [PATCH 5/5] fix --- book/fault_proofs/proposer.md | 14 +++----------- book/fault_proofs/quick_start.md | 8 +------- fault_proof/bin/proposer.rs | 26 +++++++++----------------- fault_proof/src/config.rs | 10 +--------- 4 files changed, 14 insertions(+), 44 deletions(-) diff --git a/book/fault_proofs/proposer.md b/book/fault_proofs/proposer.md index 91a4afef..c973c767 100644 --- a/book/fault_proofs/proposer.md +++ b/book/fault_proofs/proposer.md @@ -41,11 +41,10 @@ The proposer is configured through various environment variables. Create a `.env | Variable | Description | Default Value | |----------|-------------|---------------| -| `PROPOSAL_INTERVAL_IN_BLOCKS` | Number of L2 blocks between proposals | `1000` | +| `PROPOSAL_INTERVAL_IN_BLOCKS` | Number of L2 blocks between proposals | `1800` | | `FETCH_INTERVAL` | Polling interval in seconds | `30` | -| `ENABLE_GAME_RESOLUTION` | Whether to enable automatic game resolution | `false` | +| `ENABLE_GAME_RESOLUTION` | Whether to enable automatic game resolution | `true` | | `MAX_GAMES_TO_CHECK_FOR_RESOLUTION` | Maximum number of games to check for resolution | `100` | -| `FAST_FINALITY_MODE` | Enable fast finality with proofs | `false` | ```env # Required Configuration @@ -56,11 +55,10 @@ GAME_TYPE= # Type identifier for the dispute game (must match fact PRIVATE_KEY= # Private key for transaction signing # Optional Configuration -PROPOSAL_INTERVAL_IN_BLOCKS=1000 # Number of L2 blocks between proposals +PROPOSAL_INTERVAL_IN_BLOCKS=1800 # Number of L2 blocks between proposals FETCH_INTERVAL=30 # Polling interval in seconds ENABLE_GAME_RESOLUTION=false # Whether to enable automatic game resolution MAX_GAMES_TO_CHECK_FOR_RESOLUTION=100 # Maximum number of games to check for resolution -FAST_FINALITY_MODE=false # Enable fast finality mode with proofs ``` ### Configuration Steps @@ -101,12 +99,6 @@ When enabled (`ENABLE_GAME_RESOLUTION=true`), the proposer: - Maintains proper spacing between proposals based on configuration - Tracks the latest valid proposal for proper sequencing -### Fast Finality Mode -When enabled (`FAST_FINALITY_MODE=true`), the proposer: -- Includes proofs with proposals for faster finality -- Adds additional data to game creation transactions -- Enables immediate validation of proposals - ## Logging The proposer uses the `tracing` crate for logging with a default level of INFO. You can adjust the log level by setting the `RUST_LOG` environment variable: diff --git a/book/fault_proofs/quick_start.md b/book/fault_proofs/quick_start.md index 5440a262..ef4049f6 100644 --- a/book/fault_proofs/quick_start.md +++ b/book/fault_proofs/quick_start.md @@ -61,13 +61,7 @@ cargo run --bin proposer 1. The proposer will automatically create new games at regular intervals (every 1800 blocks with the default config) 2. You can view created games on a block explorer using the factory address and the game address in the proposer logs - -## Next Steps - -Once you've seen the basic flow: -1. Try `ENABLE_GAME_RESOLUTION=true` to automatically resolve unchallenged games -2. Try enabling `FAST_FINALITY_MODE=true` to include proofs on game creation and get faster finality -3. Try removing the `USE_SP1_MOCK_VERIFIER=true` flag and use [Succinct Prover Network](https://docs.succinct.xyz/docs/sp1/generating-proofs/prover-network) with [deployed SP1 Verifiers](https://docs.succinct.xyz/docs/sp1/verification/onchain/contract-addresses) for production +3. The proposer will also attempt to resolve unchallenged games after the challenge period expires ## Troubleshooting diff --git a/fault_proof/bin/proposer.rs b/fault_proof/bin/proposer.rs index 8ee97ae8..feab8a8c 100644 --- a/fault_proof/bin/proposer.rs +++ b/fault_proof/bin/proposer.rs @@ -68,21 +68,13 @@ where /// `l2_block_number`: the L2 block number we are proposing the output root for. /// `parent_game_index`: the index of the parent game. async fn create_game(&self, l2_block_number: U256, parent_game_index: u32) -> Result<()> { - let extra_data = if self.config.fast_finality_mode { - tracing::info!("Creating game with fast finality mode"); - - let proof = vec![]; - <(U256, u32, bool, Vec)>::abi_encode_packed(&( - l2_block_number, - parent_game_index, - self.config.fast_finality_mode, - proof, - )) - } else { - tracing::info!("Creating game with non fast finality mode"); + tracing::info!( + "Creating game at L2 block number: {:?}, with parent game index: {:?}", + l2_block_number, + parent_game_index + ); - <(U256, u32)>::abi_encode_packed(&(l2_block_number, parent_game_index)) - }; + let extra_data = <(U256, u32)>::abi_encode_packed(&(l2_block_number, parent_game_index)); let receipt = self .factory @@ -99,10 +91,10 @@ where .get_receipt() .await?; - let game_address = receipt.inner.logs()[0].address(); - + let game_address = + Address::from_slice(&receipt.inner.logs()[0].inner.data.topics()[1][12..]); tracing::info!( - "New game {:?} created with tx {:?}", + "\x1b[1mNew game at address {:?} created with tx {:?}\x1b[0m", game_address, receipt.transaction_hash ); diff --git a/fault_proof/src/config.rs b/fault_proof/src/config.rs index 434e382e..a128edeb 100644 --- a/fault_proof/src/config.rs +++ b/fault_proof/src/config.rs @@ -37,11 +37,6 @@ pub struct ProposerConfig { /// When game resolution is enabled, the proposer will attempt to resolve games that are /// unchallenged up to `max_games_to_check_for_resolution` games behind the latest game. pub max_games_to_check_for_resolution: u64, - - /// Whether to enable fast finality mode. - /// When fast finality mode is enabled, the proposer will propose games with a proof that - /// the proposal is valid. - pub fast_finality_mode: bool, } impl ProposerConfig { @@ -62,14 +57,11 @@ impl ProposerConfig { .parse()?, game_type: env::var("GAME_TYPE").expect("GAME_TYPE not set").parse()?, enable_game_resolution: env::var("ENABLE_GAME_RESOLUTION") - .unwrap_or("false".to_string()) + .unwrap_or("true".to_string()) .parse()?, max_games_to_check_for_resolution: env::var("MAX_GAMES_TO_CHECK_FOR_RESOLUTION") .unwrap_or("100".to_string()) .parse()?, - fast_finality_mode: env::var("FAST_FINALITY_MODE") - .unwrap_or("false".to_string()) - .parse()?, }) } }