From 515a0a4322812e3a73c45302c9be6a13e12aed83 Mon Sep 17 00:00:00 2001 From: Alex Metelli Date: Mon, 9 Dec 2024 15:02:58 +0800 Subject: [PATCH] Feat/dockerized application for local testing (#11) * added blocks verification logic tested on single mmr batch * feat(publisher): implement block validity verification using STARK proofs Add core functionality to verify L2 block headers using STARK proofs: - Implement proof generation and verification logic - Add error handling for image_id conversions - Support optional proof generation skipping for testing - Include test binary for verification flow validation This change provides the foundational logic for verifying block header validity and inclusion using zero-knowledge proofs, to be used by other components of the system. * Improve error messages for better clarity and context - Enhanced error descriptions in PublisherError, AccumulatorError, and ValidatorError - Added more detailed context and potential causes for errors - Improved consistency in error message formatting - Added debugging suggestions for critical errors - Included potential causes for EmptyHeaders and InvalidProofsCount errors The improved error messages provide better context for debugging and make it easier to understand and resolve issues when they occur. Each error now includes more specific information about what went wrong and, where applicable, suggestions about potential causes or solutions. * chore: cleaup logs * feat(logging): enhance tracing across proof generation pipeline - Add structured logging with spans for better context tracking - Improve error logging with detailed context and error messages - Add high-level progress tracking for proof generation - Implement consistent logging patterns across MMR state management - Optimize log verbosity to focus on key operational events - Add debug logs for detailed troubleshooting capabilities * Enhance input validation, error handling, and tracing - Added input validation to prevent invalid states and improve robustness. - Improved error handling by providing specific error messages and using error variants. - Enhanced tracing with spans and detailed error messages for better debugging. - Applied changes to ValidatorBuilder, BatchProcessor, MMRStateManager, and ProofGenerator. - Ensured no panics occur from invalid inputs and all error cases are properly handled. * feat(starknet-handler): enhance tracing and logging - Add structured logging with tracing macros across all modules - Implement span instrumentation for key operations - Add debug and info level logs for important state changes - Include meaningful context in log messages - Skip sensitive data in instrumentation - Add logging for: - MMR state operations - Account creation and verification - Provider RPC calls - Hex conversions This improves observability and debugging capabilities across the starknet-handler crate. * fmt * feat(validator): implement onchain MMR root verification - Add verification of MMR roots against onchain state - Extract MMR root verification into separate methods for better modularity: - verify_mmr_roots: orchestrates the verification process - verify_single_mmr_root: handles individual MMR root verification - Create mapping between batch indexes and onchain roots for efficient lookup - Add skip_proof option to bypass verification during testing - Add tracing info for both verification and skip scenarios This change ensures MMR roots match their onchain counterparts, maintaining data integrity between L1 and L2. The skip_proof option facilitates testing and development workflows. * fix(publisher): correct InvalidBlockRange error construction - Update error construction to use struct variant syntax with named fields - Align with AccumulatorError enum definition where InvalidBlockRange expects {start_block, end_block} fields - Fix compiler error in get_block_headers_by_block_range method * fix: improve block integrity verification - Fix hex conversion handling in starknet-handler for U256 values - Update dependencies in common and ethereum crates - Enhance proof generator validation checks - Improve accumulator state handling - Update messaging configuration * refactor: reduce verbosity in light client tracing logs - Simplify log messages to focus on essential information - Remove redundant context from log statements - Maintain critical state information in error scenarios * refactor: remove unused docker images for anvil, ethereum, katana, and starknet * cilppy * ci: add Cairo workflow * cairo ci fix * ci fix * feat(docker): add Dockerfiles for client and server - Add Dockerfile.client for building the client binary - Configure vendored dependencies for offline builds - Set up multi-stage build for minimal runtime image - Add proper file permissions and ownership handling * feat(starknet): add garaga as git submodule * chore(docker): setup local deployment infrastructure WIP: client Dockerfile needs fixing for vendored dependencies * feat: add build-mmr container - Add new Dockerfile.build-mmr for building and running build-mmr binary - Install Foundry and required dependencies in container - Update docker-compose.yml to use new build-mmr container - Add .env.local.docker to gitignore - Configure container to use existing Anvil instance * misc fixes * rolled back starknet contracts to prev state * removed debug logging * feat: add retry mechanism for forge script deployment - Add retry_command function with exponential backoff - Implement retries for forge script to handle Anvil startup delays - Add better error messaging for deployment attempts Fixes issue with deployment failing due to Anvil not being ready * fix(docker): resolve cargo patch resolution in offline mode - Simplify cargo config to handle crates-io and vendored sources - Update patch section to explicitly target crates.io-index - Fix client binary path and volume mount conflicts - Add cargo fetch step for git dependencies Resolves build error with sha2 patch resolution and client binary location * fix(ethereum): handle Anvil provider timeout gracefully - Replace unwrapping provider setup with proper error handling - Use try_on_anvil_with_wallet_and_config to return Result - Add detailed error messages for retry mechanism - Improve error logging for debugging purposes Resolves panic from Anvil timeout errors by implementing proper retry logic and error handling. * refactor(docker): switch relayer to Alpine Linux base image - Replace Ubuntu base image with Alpine Linux 3.19 - Simplify package installation using apk - Remove GPG key management steps - Use Alpine's SSL libraries for runtime dependencies * refactor(docker): align relayer Dockerfile with client pattern - Copy binary to /usr/local/bin instead of working directory - Add executable permissions with chmod - Switch to CMD from ENTRYPOINT - Follow consistent pattern with client Dockerfile * chore(docker): verify relayer service configuration - Confirm command matches Dockerfile CMD instruction - Ensure consistent service definition with other containers - Maintain existing dependency chain * fix(docker): optimize relayer build process - Create minimal workspace with only required crates - Configure static OpenSSL linking with openssl-libs-static - Add necessary build dependencies (gcc, pkgconfig) - Use specific Alpine mirror for reliability - Fix dependency resolution by creating minimal workspace - Optimize multi-stage build process * chore ignore * Clean up gitmodules * Remove garaga from index * Add garaga submodule at commit 5f3b232 * refactor: switch from bash to sh in Docker environment - Modified Dockerfile.relayer to use sh shell - Updated docker-compose.yml to use /bin/sh instead of /bin/bash - Converted run_relayer.sh to use POSIX-compliant shell syntax This change reduces the container size by avoiding the need to install bash and makes the setup more compatible with Alpine Linux's default shell. * format starknet contracts * removed duplicate dep * refactor: split deployment scripts - Split deploy-contracts.sh into separate Ethereum and Starknet deployment scripts - deploy-ethereum.sh handles L1 contract deployment and updates messaging config - deploy-starknet.sh handles L2 contract deployment - Updated docker-compose.yml to run deployments in correct order: * deploy-ethereum runs after anvil * katana runs after ethereum deployment * deploy-starknet runs after katana * other services depend on starknet deployment * fix: json config update in ethereum deployment - Fix anvil.messaging.json not being updated after ethereum deployment - Add proper error handling for json updates - Create temporary files in same directory to avoid permission issues * feat/ Added chain_id to proving logic to handle sepolia test net * removed hardcoded anvil account PK * added submodule to ci to handle garaga dep * added jq to deps * chore updated * fmt * new deployments * feat: add retry mechanism to ethereum deployment script - Add deploy_contracts function with 3 retry attempts - Add 10 second timeout between retries - Improve error handling and feedback messages * fix: adjust mmr-builder restart policy - Change restart policy from 'always' to 'on-failure:3' - Prevent continuous restart loop while maintaining resilience * added source check for mmr update and min update time on store * fix: remove entrypoint from relayer Dockerfile - Remove ENTRYPOINT and CMD directives - Allow docker-compose to control the container entry point - Enable proper logging from run_relayer.sh script * feat: improve relayer service reliability - Add retry mechanism to run_relayer.sh - Configure different retry settings for local/prod environments - Remove restart policy from docker-compose - Update healthcheck to monitor script process - Improve logging and error handling * chore removed redundant logging * refactor: use RELAYER_INTERVAL environment variable - Remove ENV_FILE check from run_relayer.sh - Add RELAYER_INTERVAL environment variable with default 720 minutes - Set 10-minute interval for local development in docker-compose - Simplify configuration management --- .cargo/config.toml | 44 +++++ .dockerignore | 69 +++++++- .github/workflows/cairo.yml | 3 +- .gitignore | 9 + .gitmodules | 3 + Cargo.lock | 96 ++++------- Cargo.toml | 5 +- config/anvil.messaging.json | 16 +- contracts/ethereum/script/LocalTesting.s.sol | 5 +- contracts/starknet/Scarb.lock | 1 - contracts/starknet/garaga | 1 + contracts/starknet/store/src/lib.cairo | 33 ++++ contracts/starknet/verifier/Scarb.toml | 4 +- crates/client/src/client.rs | 14 +- crates/ethereum/Cargo.toml | 1 + crates/ethereum/src/lib.rs | 33 ++-- crates/guest-mmr/Cargo.toml | 2 +- crates/guest-types/src/lib.rs | 20 ++- crates/methods/blocks-validity/src/main.rs | 2 +- crates/methods/mmr-append/src/main.rs | 2 +- crates/publisher/bin/build_mmr.rs | 2 + crates/publisher/bin/update_mmr.rs | 2 + crates/publisher/bin/verify_blocks.rs | 2 + crates/publisher/src/api/operations.rs | 22 ++- crates/publisher/src/core/accumulator.rs | 9 +- crates/publisher/src/core/batch_processor.rs | 13 +- crates/publisher/src/validator/validator.rs | 5 +- crates/starknet-handler/Cargo.toml | 2 +- docker-compose.build.yml | 15 ++ docker-compose.yaml | 2 - docker-compose.yml | 113 +++++++++++++ docker/Dockerfile.anvil | 31 ++++ docker/Dockerfile.build-mmr | 141 ++++++++++++++++ docker/Dockerfile.client | 121 ++++++++++++++ docker/Dockerfile.deploy | 37 +++++ docker/Dockerfile.katana | 31 ++++ docker/Dockerfile.relayer | 60 +++++++ docker/compose/docker-compose.yml | 165 ------------------- docker/images/client/Dockerfile | 61 ------- docker/images/relayer/Dockerfile | 36 ---- scripts/deploy-ethereum.sh | 123 ++++++++++++++ scripts/deploy-starknet.sh | 140 ++++++++++++++++ scripts/deploy.sh | 122 -------------- scripts/run_local_workflow.sh | 69 -------- scripts/run_relayer.sh | 47 +++++- scripts/start.sh | 29 ++++ 46 files changed, 1176 insertions(+), 587 deletions(-) create mode 160000 contracts/starknet/garaga create mode 100644 docker-compose.build.yml delete mode 100644 docker-compose.yaml create mode 100644 docker-compose.yml create mode 100644 docker/Dockerfile.anvil create mode 100644 docker/Dockerfile.build-mmr create mode 100644 docker/Dockerfile.client create mode 100644 docker/Dockerfile.deploy create mode 100644 docker/Dockerfile.katana create mode 100644 docker/Dockerfile.relayer delete mode 100644 docker/compose/docker-compose.yml delete mode 100644 docker/images/client/Dockerfile delete mode 100644 docker/images/relayer/Dockerfile create mode 100755 scripts/deploy-ethereum.sh create mode 100755 scripts/deploy-starknet.sh delete mode 100755 scripts/deploy.sh delete mode 100755 scripts/run_local_workflow.sh create mode 100755 scripts/start.sh diff --git a/.cargo/config.toml b/.cargo/config.toml index e69de29..2821e96 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -0,0 +1,44 @@ +# [source.crates-io] +# replace-with = "vendored-sources" + +# [source."git+https://github.com/ametel01/eth-rlp-verify.git"] +# git = "https://github.com/ametel01/eth-rlp-verify.git" +# replace-with = "vendored-sources" + +# [source."git+https://github.com/ametel01/garaga.git"] +# git = "https://github.com/ametel01/garaga.git" +# replace-with = "vendored-sources" + +# [source."git+https://github.com/ametel01/rust-accumulators.git?branch=feat/sha2-hasher"] +# git = "https://github.com/ametel01/rust-accumulators.git" +# branch = "feat/sha2-hasher" +# replace-with = "vendored-sources" + +# [source."git+https://github.com/lambdaclass/lambdaworks.git"] +# git = "https://github.com/lambdaclass/lambdaworks.git" +# replace-with = "vendored-sources" + +# [source."git+https://github.com/risc0/RustCrypto-hashes.git?tag=sha2-v0.10.8-risczero.0"] +# git = "https://github.com/risc0/RustCrypto-hashes.git" +# tag = "sha2-v0.10.8-risczero.0" +# replace-with = "vendored-sources" + +# [source."git+https://github.com/risc0/risc0-ethereum?tag=v1.1.4"] +# git = "https://github.com/risc0/risc0-ethereum" +# tag = "v1.1.4" +# replace-with = "vendored-sources" + +# [source."git+https://github.com/xJonathanLEI/starknet-rs.git?branch=master"] +# git = "https://github.com/xJonathanLEI/starknet-rs.git" +# branch = "master" +# replace-with = "vendored-sources" + +# [source."git+https://github.com/OpenZeppelin/openzeppelin-contracts.git"] +# git = "https://github.com/OpenZeppelin/openzeppelin-contracts.git" +# replace-with = "vendored-sources" + +# [source.vendored-sources] +# directory = "vendor" + +# [net] +# git-fetch-with-cli = true diff --git a/.dockerignore b/.dockerignore index 399ed94..8a0a86d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,68 @@ +# Build artifacts target/ -Dockerfile -.dockerignore \ No newline at end of file +/target/ +dist/ + +# Docker files +Dockerfile* +.dockerignore +docker/ + +# Version control +.git/ +.gitignore +.github/ + +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo +.DS_Store + +# Debug files +*.log +*.log.* +debug/ + +# Development configs +.env* +*.local +.editorconfig +.eslintrc* +.prettierrc* + +# Documentation +docs/ +*.md +LICENSE + +# Test files +tests/ +*_test.go +*.test +coverage/ + +# Dependencies +node_modules/ + +# Temporary files +tmp/ +temp/ +*.tmp + +# Cargo specific +Cargo.lock # unless it's a binary project +.cargo/ +!vendor/ # Explicitly allow the vendor directory +!vendor/**/target/ # Explicitly allow target directories under vendor + +# Foundry specific +cache/ +out/ +broadcast/ + +# Misc development files +.husky/ +.lint* +.format* diff --git a/.github/workflows/cairo.yml b/.github/workflows/cairo.yml index 22bda50..f4e6212 100644 --- a/.github/workflows/cairo.yml +++ b/.github/workflows/cairo.yml @@ -6,7 +6,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - # - uses: foundry-rs/setup-snfoundry@v3 + with: + submodules: true - uses: software-mansion/setup-scarb@v1 with: scarb-version: "2.8.5" diff --git a/.gitignore b/.gitignore index 012000a..9fa16f1 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,12 @@ scripts/katana/deploy.log scripts/katana/katana.log *.pb + +vendor/ + +.env.local +.env.sepolia +.env.mainnet +docker/rebuild-all.sh + +scripts/deploy-contracts.bash.sh diff --git a/.gitmodules b/.gitmodules index b571bd0..1ce7c90 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,6 @@ path = contracts/ethereum/lib/forge-std url = https://github.com/foundry-rs/forge-std.git shallow = true +[submodule "contracts/starknet/garaga"] + path = contracts/starknet/garaga + url = git@github.com:keep-starknet-strange/garaga.git diff --git a/Cargo.lock b/Cargo.lock index 02d6e24..941ec95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -266,7 +266,7 @@ dependencies = [ "derive_more", "once_cell", "serde", - "sha2 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2", ] [[package]] @@ -284,7 +284,7 @@ dependencies = [ "derive_more", "once_cell", "serde", - "sha2 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2", ] [[package]] @@ -1063,7 +1063,7 @@ dependencies = [ "blake2", "derivative", "digest 0.10.7", - "sha2 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2", ] [[package]] @@ -1722,7 +1722,7 @@ dependencies = [ "alloy-contract 0.6.4", "dotenv", "ruint", - "starknet-crypto 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "starknet-crypto", "thiserror 2.0.4", "tracing-subscriber 0.3.19", ] @@ -2193,7 +2193,7 @@ dependencies = [ "scrypt", "serde", "serde_json", - "sha2 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2", "sha3", "thiserror 1.0.69", "uuid 0.8.2", @@ -2247,6 +2247,7 @@ dependencies = [ "alloy 0.6.4", "common", "tokio", + "tracing", ] [[package]] @@ -2548,8 +2549,8 @@ dependencies = [ "num-bigint", "num-traits", "pyo3", - "sha2 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)", - "starknet-crypto 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2", + "starknet-crypto", "wasm-bindgen", ] @@ -2610,7 +2611,7 @@ dependencies = [ "mmr-utils", "num-bigint", "num-traits", - "sha2 0.10.8 (git+https://github.com/risc0/RustCrypto-hashes.git?tag=sha2-v0.10.8-risczero.0)", + "sha2", "thiserror 2.0.4", "tokio", ] @@ -2688,9 +2689,9 @@ dependencies = [ "hex", "num-bigint", "num-traits", - "sha2 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2", "starknet-core", - "starknet-crypto 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "starknet-crypto", "strum", "strum_macros", "thiserror 2.0.4", @@ -3291,7 +3292,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "once_cell", - "sha2 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2", ] [[package]] @@ -3321,7 +3322,7 @@ checksum = "bbc2a4da0d9e52ccfe6306801a112e81a8fc0c76aa3e4449fefeda7fef72bb34" dependencies = [ "lambdaworks-math 0.10.0", "serde", - "sha2 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2", "sha3", ] @@ -3332,7 +3333,7 @@ source = "git+https://github.com/lambdaclass/lambdaworks.git#fc33967375fd4d99814 dependencies = [ "lambdaworks-math 0.11.0", "serde", - "sha2 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2", "sha3", ] @@ -4138,7 +4139,7 @@ dependencies = [ "serde", "sqlx", "starknet", - "starknet-crypto 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "starknet-crypto", "starknet-handler", "starknet-types-core", "store", @@ -4668,7 +4669,7 @@ dependencies = [ "risc0-core", "risc0-zkvm-platform", "serde", - "sha2 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2", "tracing", ] @@ -4699,7 +4700,7 @@ dependencies = [ "rrs-lib", "semver 1.0.23", "serde", - "sha2 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2", "stability", "tempfile", "tracing", @@ -4988,7 +4989,7 @@ dependencies = [ "hmac", "pbkdf2", "salsa20", - "sha2 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2", ] [[package]] @@ -5167,17 +5168,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.7", -] - [[package]] name = "sha2" version = "0.10.8" @@ -5342,7 +5332,7 @@ dependencies = [ "percent-encoding", "serde", "serde_json", - "sha2 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2", "smallvec", "sqlformat", "thiserror 1.0.69", @@ -5380,7 +5370,7 @@ dependencies = [ "quote", "serde", "serde_json", - "sha2 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2", "sqlx-core", "sqlx-mysql", "sqlx-postgres", @@ -5424,7 +5414,7 @@ dependencies = [ "rsa", "serde", "sha1", - "sha2 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2", "smallvec", "sqlx-core", "stringprep", @@ -5462,7 +5452,7 @@ dependencies = [ "rand", "serde", "serde_json", - "sha2 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2", "smallvec", "sqlx-core", "stringprep", @@ -5519,7 +5509,7 @@ dependencies = [ "starknet-contract", "starknet-core", "starknet-core-derive", - "starknet-crypto 0.7.3 (git+https://github.com/xJonathanLEI/starknet-rs.git?branch=master)", + "starknet-crypto", "starknet-macros", "starknet-providers", "starknet-signers", @@ -5533,7 +5523,7 @@ dependencies = [ "async-trait", "auto_impl", "starknet-core", - "starknet-crypto 0.7.3 (git+https://github.com/xJonathanLEI/starknet-rs.git?branch=master)", + "starknet-crypto", "starknet-providers", "starknet-signers", "thiserror 1.0.69", @@ -5569,7 +5559,7 @@ dependencies = [ "serde_with", "sha3", "starknet-core-derive", - "starknet-crypto 0.7.3 (git+https://github.com/xJonathanLEI/starknet-rs.git?branch=master)", + "starknet-crypto", "starknet-types-core", ] @@ -5583,25 +5573,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "starknet-crypto" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded22ccf4cb9e572ce3f77de6066af53560cd2520d508876c83bb1e6b29d5cbc" -dependencies = [ - "crypto-bigint", - "hex", - "hmac", - "num-bigint", - "num-integer", - "num-traits", - "rfc6979", - "sha2 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)", - "starknet-curve 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "starknet-types-core", - "zeroize", -] - [[package]] name = "starknet-crypto" version = "0.7.3" @@ -5614,21 +5585,12 @@ dependencies = [ "num-integer", "num-traits", "rfc6979", - "sha2 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)", - "starknet-curve 0.5.1 (git+https://github.com/xJonathanLEI/starknet-rs.git?branch=master)", + "sha2", + "starknet-curve", "starknet-types-core", "zeroize", ] -[[package]] -name = "starknet-curve" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcde6bd74269b8161948190ace6cf069ef20ac6e79cd2ba09b320efa7500b6de" -dependencies = [ - "starknet-types-core", -] - [[package]] name = "starknet-curve" version = "0.5.1" @@ -5644,7 +5606,7 @@ dependencies = [ "common", "crypto-bigint", "starknet", - "starknet-crypto 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "starknet-crypto", "thiserror 2.0.4", "tracing", "url", @@ -5691,7 +5653,7 @@ dependencies = [ "getrandom", "rand", "starknet-core", - "starknet-crypto 0.7.3 (git+https://github.com/xJonathanLEI/starknet-rs.git?branch=master)", + "starknet-crypto", "thiserror 1.0.69", ] diff --git a/Cargo.toml b/Cargo.toml index f393603..3c978f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ lto = true eth-rlp-types = { git = "https://github.com/ametel01/eth-rlp-verify.git" } eth-rlp-verify = { git = "https://github.com/ametel01/eth-rlp-verify.git" } starknet = { git = "https://github.com/xJonathanLEI/starknet-rs.git", branch = "master" } +starknet-crypto = { git = "https://github.com/xJonathanLEI/starknet-rs.git", branch = "master" } sqlx = { version = "0.8.2", features = [ "postgres", @@ -35,6 +36,8 @@ thiserror = "2.0.4" tracing = "0.1.40" tokio = "1.41.1" dotenv = "0.15" -starknet-crypto = "0.7.3" clap = "4.5" +[patch.crates-io] +sha2 = { git = "https://github.com/risc0/RustCrypto-hashes.git", tag = "sha2-v0.10.8-risczero.0" } +starknet-crypto = { git = "https://github.com/xJonathanLEI/starknet-rs.git", branch = "master" } \ No newline at end of file diff --git a/config/anvil.messaging.json b/config/anvil.messaging.json index 8b9321f..ebe3f85 100644 --- a/config/anvil.messaging.json +++ b/config/anvil.messaging.json @@ -1,9 +1,9 @@ { - "chain": "ethereum", - "rpc_url": "http://127.0.0.1:8545", - "contract_address": "0xF62eEc897fa5ef36a957702AA4a45B58fE8Fe312", - "sender_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "private_key": "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", - "interval": 2, - "from_block": 21332916 -} \ No newline at end of file + "chain": "ethereum", + "rpc_url": "http://anvil:8545", + "contract_address": "0x50d1bf1Cb2873C8f32AFe3b3AA6c075b341209FE", + "sender_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "private_key": "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "interval": 2, + "from_block": 21340770 +} diff --git a/contracts/ethereum/script/LocalTesting.s.sol b/contracts/ethereum/script/LocalTesting.s.sol index 8b5e950..5362952 100644 --- a/contracts/ethereum/script/LocalTesting.s.sol +++ b/contracts/ethereum/script/LocalTesting.s.sol @@ -7,14 +7,11 @@ import {L1MessageSender} from "../src/L1MessageSender.sol"; import {StarknetMessagingLocal} from "../src/StarknetMessagingLocal.sol"; -// address constant SN_CORE = 0xc662c410C0ECf747543f5bA90660f6ABeBD9C8c4; - contract LocalSetup is Script { function setUp() public {} function run() public{ - // uint256 deployerPrivateKey = vm.envUint("ACCOUNT_PRIVATE_KEY"); - uint256 deployerPrivateKey = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80; + uint256 deployerPrivateKey = vm.envUint("ACCOUNT_PRIVATE_KEY"); string memory json = "local_testing"; diff --git a/contracts/starknet/Scarb.lock b/contracts/starknet/Scarb.lock index ff588ad..e93d860 100644 --- a/contracts/starknet/Scarb.lock +++ b/contracts/starknet/Scarb.lock @@ -11,7 +11,6 @@ dependencies = [ [[package]] name = "garaga" version = "0.14.0" -source = "git+https://github.com/keep-starknet-strange/garaga.git?rev=5f3b232#5f3b23252a04f1714838415e69e318ed8e097c15" [[package]] name = "l1_message_proxy" diff --git a/contracts/starknet/garaga b/contracts/starknet/garaga new file mode 160000 index 0000000..5f3b232 --- /dev/null +++ b/contracts/starknet/garaga @@ -0,0 +1 @@ +Subproject commit 5f3b23252a04f1714838415e69e318ed8e097c15 diff --git a/contracts/starknet/store/src/lib.cairo b/contracts/starknet/store/src/lib.cairo index 8c0c61d..3176bb8 100644 --- a/contracts/starknet/store/src/lib.cairo +++ b/contracts/starknet/store/src/lib.cairo @@ -1,5 +1,10 @@ #[starknet::interface] pub trait IFossilStore { + fn initialize( + ref self: TContractState, + verifier_address: starknet::ContractAddress, + min_update_interval: u64 + ); fn store_latest_blockhash_from_l1(ref self: TContractState, block_number: u64, blockhash: u256); fn update_mmr_state( ref self: TContractState, @@ -34,6 +39,9 @@ mod Store { #[storage] struct Storage { + initialized: bool, + verifier_address: starknet::ContractAddress, + min_update_interval: u64, latest_blockhash_from_l1: (u64, u256), latest_mmr_block: u64, mmr_batches: Map, @@ -61,6 +69,17 @@ mod Store { #[abi(embed_v0)] impl FossilStoreImpl of super::IFossilStore { + fn initialize( + ref self: ContractState, + verifier_address: starknet::ContractAddress, + min_update_interval: u64 + ) { + assert!(!self.initialized.read(), "Contract already initialized"); + self.initialized.write(true); + self.verifier_address.write(verifier_address); + self.min_update_interval.write(min_update_interval); + } + fn store_latest_blockhash_from_l1( ref self: ContractState, block_number: u64, blockhash: u256 ) { @@ -79,6 +98,20 @@ mod Store { leaves_count: u64, mmr_root: u256, ) { + assert!( + starknet::get_caller_address() == self.verifier_address.read(), + "Only Fossil Verifier can update MMR state" + ); + + let min_update_interval = self.min_update_interval.read(); + let actual_update_interval = latest_mmr_block - self.latest_mmr_block.read(); + assert!( + actual_update_interval >= min_update_interval, + "Update interval: {} must be greater than or equal to the minimum update interval: {}", + actual_update_interval, + min_update_interval + ); + let mut curr_state = self.mmr_batches.entry(batch_index); curr_state.leaves_count.write(leaves_count); diff --git a/contracts/starknet/verifier/Scarb.toml b/contracts/starknet/verifier/Scarb.toml index 4692350..9544f16 100644 --- a/contracts/starknet/verifier/Scarb.toml +++ b/contracts/starknet/verifier/Scarb.toml @@ -6,7 +6,7 @@ edition = "2023_11" # See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html [dependencies] -garaga = { git = "https://github.com/keep-starknet-strange/garaga.git", rev = "5f3b232" } +garaga = { path = "../garaga/src" } starknet.workspace = true fossil_store = { path = "../store" } @@ -22,4 +22,4 @@ fmt.workspace = true casm = true casm-add-pythonic-hints = true -[lib] +[lib] \ No newline at end of file diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 09f55c8..4e7a9f1 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -29,12 +29,15 @@ pub enum LightClientError { ConfigError(String), #[error("Polling interval must be greater than zero")] PollingIntervalError, + #[error("Chain ID is not a valid number")] + ChainIdError(#[from] std::num::ParseIntError), } pub struct LightClient { starknet_provider: StarknetProvider, l2_store_addr: Felt, verifier_addr: String, + chain_id: u64, latest_processed_block: u64, starknet_private_key: String, starknet_account_address: String, @@ -54,7 +57,7 @@ impl LightClient { let verifier_addr = get_env_var("FOSSIL_VERIFIER")?; let starknet_private_key = get_env_var("STARKNET_PRIVATE_KEY")?; let starknet_account_address = get_env_var("STARKNET_ACCOUNT_ADDRESS")?; - + let chain_id = get_env_var("CHAIN_ID")?.parse::()?; // Initialize providers let starknet_provider = StarknetProvider::new(&starknet_rpc_url)?; @@ -71,6 +74,7 @@ impl LightClient { starknet_provider, l2_store_addr, verifier_addr, + chain_id, latest_processed_block: 0, starknet_private_key, starknet_account_address, @@ -171,13 +175,6 @@ impl LightClient { .get_latest_mmr_block(&self.l2_store_addr) .await?; - info!( - latest_relayed_block, - latest_mmr_block, - num_blocks = latest_relayed_block - latest_mmr_block, - "State fetched from Starknet" - ); - // Update MMR and verify proofs self.update_mmr(latest_mmr_block, latest_relayed_block) .await?; @@ -207,6 +204,7 @@ impl LightClient { publisher::prove_mmr_update( &self.starknet_provider.rpc_url().to_string(), + self.chain_id, &self.verifier_addr, &self.starknet_private_key, &self.starknet_account_address, diff --git a/crates/ethereum/Cargo.toml b/crates/ethereum/Cargo.toml index b0b123c..da0e9e8 100644 --- a/crates/ethereum/Cargo.toml +++ b/crates/ethereum/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" common = { path = "../common" } tokio = { workspace = true } +tracing = { workspace = true } alloy = { version = "0.6.4", features = ["full", "node-bindings"] } diff --git a/crates/ethereum/src/lib.rs b/crates/ethereum/src/lib.rs index 35f0edb..46c8057 100644 --- a/crates/ethereum/src/lib.rs +++ b/crates/ethereum/src/lib.rs @@ -31,7 +31,13 @@ pub async fn get_finalized_block_hash() -> Result<(u64, String), UtilsError> { let result: Result<(u64, String), UtilsError> = async { let provider = ProviderBuilder::new() .with_recommended_fillers() - .on_anvil_with_wallet_and_config(|anvil| anvil.fork(rpc_url.clone())); + .try_on_anvil_with_wallet_and_config(|anvil| anvil.fork(rpc_url.clone())) + .map_err(|e| { + UtilsError::RetryExhausted( + attempts, + format!("Failed to setup Anvil provider: {}", e), + ) + })?; let contract = BlockHashFetcher::deploy(&provider).await?; let result = contract.getBlockHash().call().await?; @@ -43,16 +49,23 @@ pub async fn get_finalized_block_hash() -> Result<(u64, String), UtilsError> { } .await; - if let Ok(value) = result { - return Ok(value); - } else { - if attempts >= MAX_RETRIES { - return Err(UtilsError::RetryExhausted( - MAX_RETRIES, - "get_finalized_block_hash".to_string(), - )); + match result { + Ok(value) => return Ok(value), + Err(e) => { + if attempts >= MAX_RETRIES { + return Err(UtilsError::RetryExhausted( + MAX_RETRIES, + format!("get_finalized_block_hash failed: {}", e), + )); + } + tracing::error!( + attempts = attempts, + max_retries = MAX_RETRIES, + error = %e.to_string(), + "Attempt failed" + ); + sleep(RETRY_DELAY).await; } - sleep(RETRY_DELAY).await; } } } diff --git a/crates/guest-mmr/Cargo.toml b/crates/guest-mmr/Cargo.toml index 95037f2..d3f76b1 100644 --- a/crates/guest-mmr/Cargo.toml +++ b/crates/guest-mmr/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" guest-types = { path = "../guest-types" } hex = "0.4" -sha2 = { git = 'https://github.com/risc0/RustCrypto-hashes.git', tag = 'sha2-v0.10.8-risczero.0', features = ['compress'] } +sha2 = { git = "https://github.com/risc0/RustCrypto-hashes.git", tag = "sha2-v0.10.8-risczero.0", features = ['compress'] } num-bigint = "0.4.4" num-traits = "0.2.19" thiserror = "2.0.3" diff --git a/crates/guest-types/src/lib.rs b/crates/guest-types/src/lib.rs index d57bc63..83107b7 100644 --- a/crates/guest-types/src/lib.rs +++ b/crates/guest-types/src/lib.rs @@ -102,6 +102,7 @@ impl GuestOutput { // CombinedInput #[derive(Debug, Serialize, Deserialize, Clone)] pub struct CombinedInput { + chain_id: u64, headers: Vec, mmr_input: MMRInput, skip_proof_verification: bool, @@ -109,17 +110,23 @@ pub struct CombinedInput { impl CombinedInput { pub fn new( + chain_id: u64, headers: Vec, mmr_input: MMRInput, skip_proof_verification: bool, ) -> Self { Self { + chain_id, headers, mmr_input, skip_proof_verification, } } + pub fn chain_id(&self) -> u64 { + self.chain_id + } + pub fn headers(&self) -> &Vec { &self.headers } @@ -225,19 +232,30 @@ pub struct GuestProof { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BlocksValidityInput { + chain_id: u64, headers: Vec, mmr_input: MMRInput, proofs: Vec, } impl BlocksValidityInput { - pub fn new(headers: Vec, mmr_input: MMRInput, proofs: Vec) -> Self { + pub fn new( + chain_id: u64, + headers: Vec, + mmr_input: MMRInput, + proofs: Vec, + ) -> Self { Self { + chain_id, headers, mmr_input, proofs, } } + pub fn chain_id(&self) -> u64 { + self.chain_id + } + pub fn headers(&self) -> &Vec { &self.headers } diff --git a/crates/methods/blocks-validity/src/main.rs b/crates/methods/blocks-validity/src/main.rs index b9b8bd0..f9431a9 100644 --- a/crates/methods/blocks-validity/src/main.rs +++ b/crates/methods/blocks-validity/src/main.rs @@ -9,7 +9,7 @@ fn main() { let input: BlocksValidityInput = env::read(); // Verify block headers - if !are_blocks_and_chain_valid(&input.headers()) { + if !are_blocks_and_chain_valid(&input.headers(), input.chain_id()) { env::commit(&false); } // Initialize MMR with previous state diff --git a/crates/methods/mmr-append/src/main.rs b/crates/methods/mmr-append/src/main.rs index 7e31401..8cdae23 100644 --- a/crates/methods/mmr-append/src/main.rs +++ b/crates/methods/mmr-append/src/main.rs @@ -11,7 +11,7 @@ fn main() { let input: CombinedInput = env::read(); // Verify block headers assert!( - are_blocks_and_chain_valid(&input.headers()), + are_blocks_and_chain_valid(&input.headers(), input.chain_id()), "Invalid block headers" ); // Initialize MMR with previous state diff --git a/crates/publisher/bin/build_mmr.rs b/crates/publisher/bin/build_mmr.rs index c4b94f2..a8f66d6 100644 --- a/crates/publisher/bin/build_mmr.rs +++ b/crates/publisher/bin/build_mmr.rs @@ -23,6 +23,7 @@ struct Args { async fn main() -> Result<(), Box> { initialize_logger_and_env()?; + let chain_id = get_env_var("CHAIN_ID")?.parse::()?; let rpc_url = get_env_var("STARKNET_RPC_URL")?; let verifier_address = get_env_var("FOSSIL_VERIFIER")?; let private_key = get_env_var("STARKNET_PRIVATE_KEY")?; @@ -37,6 +38,7 @@ async fn main() -> Result<(), Box> { // Initialize accumulator builder with the batch size let mut builder = AccumulatorBuilder::new( &rpc_url, + chain_id, &verifier_address, &private_key, &account_address, diff --git a/crates/publisher/bin/update_mmr.rs b/crates/publisher/bin/update_mmr.rs index a67f3c5..7cf86c5 100644 --- a/crates/publisher/bin/update_mmr.rs +++ b/crates/publisher/bin/update_mmr.rs @@ -24,6 +24,7 @@ struct Args { async fn main() -> Result<(), Box> { initialize_logger_and_env()?; + let chain_id = get_env_var("CHAIN_ID")?.parse::()?; let rpc_url = get_env_var("STARKNET_RPC_URL")?; let verifier_address = get_env_var("FOSSIL_VERIFIER")?; let private_key = get_env_var("STARKNET_PRIVATE_KEY")?; @@ -36,6 +37,7 @@ async fn main() -> Result<(), Box> { publisher::prove_mmr_update( &rpc_url, + chain_id, &verifier_address, &private_key, &account_address, diff --git a/crates/publisher/bin/verify_blocks.rs b/crates/publisher/bin/verify_blocks.rs index 2f023f9..8529e64 100644 --- a/crates/publisher/bin/verify_blocks.rs +++ b/crates/publisher/bin/verify_blocks.rs @@ -24,6 +24,7 @@ async fn main() -> Result<(), Box> { initialize_logger_and_env()?; let rpc_url = get_env_var("STARKNET_RPC_URL")?; let l2_store_address = get_env_var("FOSSIL_STORE")?; + let chain_id = get_env_var("CHAIN_ID")?.parse::()?; let args = Args::parse(); @@ -37,6 +38,7 @@ async fn main() -> Result<(), Box> { match prove_headers_integrity_and_inclusion( &rpc_url, &l2_store_address, + chain_id, &headers, Some(args.skip_proof), ) diff --git a/crates/publisher/src/api/operations.rs b/crates/publisher/src/api/operations.rs index 26b1b12..3ab6998 100644 --- a/crates/publisher/src/api/operations.rs +++ b/crates/publisher/src/api/operations.rs @@ -6,6 +6,7 @@ const DEFAULT_BATCH_SIZE: u64 = 1024; pub async fn prove_mmr_update( rpc_url: &String, + chain_id: u64, verifier_address: &String, account_private_key: &String, account_address: &String, @@ -16,6 +17,7 @@ pub async fn prove_mmr_update( ) -> Result<(), PublisherError> { let mut builder = AccumulatorBuilder::new( rpc_url, + chain_id, verifier_address, account_private_key, account_address, @@ -46,18 +48,24 @@ pub async fn prove_mmr_update( pub async fn prove_headers_integrity_and_inclusion( rpc_url: &String, l2_store_address: &String, + chain_id: u64, headers: &Vec, skip_proof_verification: Option, ) -> Result, PublisherError> { let skip_proof = skip_proof_verification.unwrap_or(false); - let validator = - ValidatorBuilder::new(rpc_url, l2_store_address, DEFAULT_BATCH_SIZE, skip_proof) - .await - .map_err(|e| { - tracing::error!(error = %e, "Failed to create ValidatorBuilder"); - e - })?; + let validator = ValidatorBuilder::new( + rpc_url, + l2_store_address, + chain_id, + DEFAULT_BATCH_SIZE, + skip_proof, + ) + .await + .map_err(|e| { + tracing::error!(error = %e, "Failed to create ValidatorBuilder"); + e + })?; let result = validator .verify_blocks_integrity_and_inclusion(headers) diff --git a/crates/publisher/src/core/accumulator.rs b/crates/publisher/src/core/accumulator.rs index cbbe5ab..ed719c3 100644 --- a/crates/publisher/src/core/accumulator.rs +++ b/crates/publisher/src/core/accumulator.rs @@ -9,6 +9,7 @@ use tracing::{debug, error, info, warn}; pub struct AccumulatorBuilder<'a> { rpc_url: &'a String, + chain_id: u64, verifier_address: &'a String, account_private_key: &'a String, account_address: &'a String, @@ -20,6 +21,7 @@ pub struct AccumulatorBuilder<'a> { impl<'a> AccumulatorBuilder<'a> { pub async fn new( rpc_url: &'a String, + chain_id: u64, verifier_address: &'a String, account_private_key: &'a String, account_address: &'a String, @@ -56,6 +58,7 @@ impl<'a> AccumulatorBuilder<'a> { Ok(Self { rpc_url, + chain_id, verifier_address, account_private_key, account_address, @@ -105,7 +108,7 @@ impl<'a> AccumulatorBuilder<'a> { let result = self .batch_processor - .process_batch(start_block, current_end) + .process_batch(self.chain_id, start_block, current_end) .await .map_err(|e| { error!( @@ -148,7 +151,7 @@ impl<'a> AccumulatorBuilder<'a> { let start_block = self.batch_processor.calculate_start_block(current_end)?; let batch_result = self .batch_processor - .process_batch(start_block, current_end) + .process_batch(self.chain_id, start_block, current_end) .await?; if let Some(result) = batch_result { @@ -193,7 +196,7 @@ impl<'a> AccumulatorBuilder<'a> { if let Some(result) = self .batch_processor - .process_batch(batch_range.start, batch_range.end) + .process_batch(self.chain_id, batch_range.start, batch_range.end) .await .map_err(|e| { error!( diff --git a/crates/publisher/src/core/batch_processor.rs b/crates/publisher/src/core/batch_processor.rs index d42a43b..3ef7370 100644 --- a/crates/publisher/src/core/batch_processor.rs +++ b/crates/publisher/src/core/batch_processor.rs @@ -6,7 +6,7 @@ use common::get_or_create_db_path; use guest_types::{CombinedInput, GuestOutput, MMRInput}; use mmr::PeaksOptions; use mmr_utils::initialize_mmr; -use tracing::{debug, error, info}; +use tracing::{debug, error, info, warn}; pub struct BatchProcessor { batch_size: u64, @@ -43,6 +43,7 @@ impl BatchProcessor { pub async fn process_batch( &self, + chain_id: u64, start_block: u64, end_block: u64, ) -> Result, AccumulatorError> { @@ -104,7 +105,7 @@ impl BatchProcessor { })?; if headers.is_empty() { - error!( + warn!( "No headers found for block range {} to {}", start_block, adjusted_end_block ); @@ -136,8 +137,12 @@ impl BatchProcessor { new_headers.clone(), ); - let combined_input = - CombinedInput::new(headers.clone(), mmr_input, self.skip_proof_verification); + let combined_input = CombinedInput::new( + chain_id, + headers.clone(), + mmr_input, + self.skip_proof_verification, + ); let proof = self .proof_generator diff --git a/crates/publisher/src/validator/validator.rs b/crates/publisher/src/validator/validator.rs index dbd2c6d..c50bf80 100644 --- a/crates/publisher/src/validator/validator.rs +++ b/crates/publisher/src/validator/validator.rs @@ -16,6 +16,7 @@ use tracing::error; pub struct ValidatorBuilder { rpc_url: String, l2_store_address: Felt, + chain_id: u64, proof_generator: ProofGenerator, batch_size: u64, skip_proof: bool, @@ -25,6 +26,7 @@ impl ValidatorBuilder { pub async fn new( rpc_url: &String, l2_store_address: &String, + chain_id: u64, batch_size: u64, skip_proof: bool, ) -> Result { @@ -40,6 +42,7 @@ impl ValidatorBuilder { Ok(Self { rpc_url: rpc_url.clone(), l2_store_address: Felt::from_hex(l2_store_address)?, + chain_id, proof_generator, batch_size, skip_proof, @@ -160,7 +163,7 @@ impl ValidatorBuilder { let mmr_input = self.prepare_mmr_input(mmr).await?; let blocks_validity_input = - BlocksValidityInput::new(batch_headers, mmr_input, guest_proofs); + BlocksValidityInput::new(self.chain_id, batch_headers, mmr_input, guest_proofs); Ok(self .proof_generator diff --git a/crates/starknet-handler/Cargo.toml b/crates/starknet-handler/Cargo.toml index 1c88c67..488e2d1 100644 --- a/crates/starknet-handler/Cargo.toml +++ b/crates/starknet-handler/Cargo.toml @@ -8,8 +8,8 @@ common = { path = "../common" } thiserror = { workspace = true } starknet = { workspace = true } -starknet-crypto = { workspace = true } tracing = { workspace = true } +starknet-crypto = { workspace = true } crypto-bigint = "0.5.5" url = "2.5.4" \ No newline at end of file diff --git a/docker-compose.build.yml b/docker-compose.build.yml new file mode 100644 index 0000000..0daee6c --- /dev/null +++ b/docker-compose.build.yml @@ -0,0 +1,15 @@ +services: + anvil: + build: + context: . + dockerfile: docker/Dockerfile.anvil + + katana: + build: + context: . + dockerfile: docker/Dockerfile.katana + + deployer: + build: + context: . + dockerfile: docker/Dockerfile.deploy \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index e70ba1a..0000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,2 +0,0 @@ -include: - - docker/compose/docker-compose.yml \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..7c20599 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,113 @@ +services: + anvil: + image: fossil-anvil:latest + networks: + - fossil + ports: + - "8545:8545" + env_file: + - ${ENV_FILE:-.env.local} + healthcheck: + test: ["CMD", "cast", "block-number"] + interval: 5s + timeout: 5s + retries: 5 + + deploy-ethereum: + image: fossil-deploy:latest + networks: + - fossil + env_file: + - ${ENV_FILE:-.env.local} + volumes: + - .:/app + depends_on: + anvil: + condition: service_healthy + command: ["./scripts/deploy-ethereum.sh", "local"] + + katana: + image: fossil-katana:latest + networks: + - fossil + ports: + - "5050:5050" + volumes: + - ./config:/app/config + command: ["katana", "--messaging", "/app/config/anvil.messaging.json", "--disable-fee", "--disable-validate", "--host", "0.0.0.0"] + depends_on: + anvil: + condition: service_healthy + deploy-ethereum: + condition: service_completed_successfully + healthcheck: + test: ["CMD", "katana", "--version"] + interval: 5s + timeout: 5s + retries: 5 + + deploy-starknet: + image: fossil-deploy:latest + networks: + - fossil + env_file: + - ${ENV_FILE:-.env.local} + volumes: + - .:/app + depends_on: + katana: + condition: service_healthy + command: ["./scripts/deploy-starknet.sh", "local"] + + mmr-builder: + image: fossil-build-mmr:latest + networks: + - fossil + env_file: + - ${ENV_FILE:-.env.local} + environment: + - RUST_BACKTRACE=1 + - ANVIL_URL=http://anvil:8545 + depends_on: + deploy-starknet: + condition: service_completed_successfully + restart: on-failure:3 + + client: + image: fossil-client:latest + networks: + - fossil + env_file: + - ${ENV_FILE:-.env.local} + volumes: + - .:/app + command: ["client"] + depends_on: + deploy-starknet: + condition: service_completed_successfully + mmr-builder: + condition: service_completed_successfully + + relayer: + image: fossil-relayer:latest + networks: + - fossil + env_file: + - ${ENV_FILE:-.env.local} + volumes: + - ./scripts:/app/scripts + command: ["/bin/sh", "/app/scripts/run_relayer.sh"] + depends_on: + deploy-starknet: + condition: service_completed_successfully + mmr-builder: + condition: service_completed_successfully + healthcheck: + test: ["CMD-SHELL", "pgrep -f run_relayer.sh"] + interval: 30s + timeout: 10s + retries: 3 + +networks: + fossil: + name: fossil-network \ No newline at end of file diff --git a/docker/Dockerfile.anvil b/docker/Dockerfile.anvil new file mode 100644 index 0000000..103f05f --- /dev/null +++ b/docker/Dockerfile.anvil @@ -0,0 +1,31 @@ +FROM ubuntu:22.04 + +# Avoid prompts from apt +ENV DEBIAN_FRONTEND=noninteractive + +# Install basic dependencies +RUN apt-get update && apt-get install -y \ + curl \ + git \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Install Foundry +RUN curl -L https://foundry.paradigm.xyz | bash +ENV PATH="/root/.foundry/bin:${PATH}" +RUN foundryup + +# Verify installation +RUN forge --version + +# Create entrypoint script +RUN echo '#!/bin/bash\n\ +if [ -z "$ETH_RPC_URL" ]; then\n\ + echo "Error: ETH_RPC_URL environment variable is not set"\n\ + exit 1\n\ +fi\n\ +anvil --fork-url "$ETH_RPC_URL" --block-time 12 --host 0.0.0.0\n\ +' > /entrypoint.sh && chmod +x /entrypoint.sh + +# Default command to run Anvil +ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file diff --git a/docker/Dockerfile.build-mmr b/docker/Dockerfile.build-mmr new file mode 100644 index 0000000..c9e93ea --- /dev/null +++ b/docker/Dockerfile.build-mmr @@ -0,0 +1,141 @@ +# Stage 1: Build the application +FROM rust:latest AS builder + +WORKDIR /app + +# Install Foundry (includes Anvil) +RUN curl -L https://foundry.paradigm.xyz | bash && \ + /root/.foundry/bin/foundryup + +# Add foundry binaries to PATH +ENV PATH="/root/.foundry/bin:$PATH" + +# Set DATABASE_URL as build arg +ARG DATABASE_URL +ENV DATABASE_URL=${DATABASE_URL} + +# Install the RISC Zero toolchain +RUN curl -L https://risczero.com/install | bash && \ + /root/.risc0/bin/rzup install || true && \ + mkdir -p /root/.cargo/bin && \ + (ln -sf /root/.risc0/bin/cargo-risczero /root/.cargo/bin/cargo-risczero || echo "Symlink creation failed, checking directories..." && ls -la /root/.risc0/bin && ls -la /root/.cargo) + +ENV PATH="/root/.risc0/bin:/root/.cargo/bin:$PATH" + +# Step 1: Copy Cargo.toml and Cargo.lock +COPY Cargo.toml Cargo.lock ./ + +# Step 2: Copy the workspace crates first +COPY crates ./crates + +# Step 3: Create necessary directories and copy pre-fetched dependencies +RUN mkdir -p /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823/cc-1.2.2/src/target && \ + mkdir -p /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823/cc-1.2.2/src/windows && \ + mkdir -p /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823/cc-1.2.2/src/parallel + +# Debug the build context +RUN echo "=== Build Context ===" && \ + pwd && \ + ls -la && \ + echo "=== Vendor Directory (if exists) ===" && \ + ls -R vendor || echo "No vendor directory found!" + +# Only proceed with copy if directory exists +RUN mkdir -p /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823/cc-1.2.2/src/{target,windows,parallel} + +# Create the destination directory with correct permissions +RUN mkdir -p /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823 && \ + chmod 755 /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823 + +# Copy the vendor directory with permissions preserved +COPY --chown=root:root vendor/ /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823/ + +# Debug and verify the copy +RUN echo "=== Debug: Directory structure after copy ===" && \ + find /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823 -type d && \ + echo "=== Debug: All files in cc-1.2.2 ===" && \ + find /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823/cc-1.2.2 -type f && \ + echo "=== Debug: Setting final permissions ===" && \ + chmod -R u+r,g+r /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823 + +# Create cargo config for vendored dependencies +RUN mkdir -p /usr/local/cargo/registry && \ + echo '[source.crates-io]\n\ +replace-with = "vendored-sources"\n\ +\n\ +[source."git+https://github.com/risc0/RustCrypto-hashes.git"]\n\ +git = "https://github.com/risc0/RustCrypto-hashes.git"\n\ +tag = "sha2-v0.10.8-risczero.0"\n\ +replace-with = "vendored-sources"\n\ +\n\ +[source."git+https://github.com/xJonathanLEI/starknet-rs.git"]\n\ +git = "https://github.com/xJonathanLEI/starknet-rs.git"\n\ +branch = "master"\n\ +replace-with = "vendored-sources"\n\ +\n\ +[source."git+https://github.com/ametel01/rust-accumulators.git"]\n\ +git = "https://github.com/ametel01/rust-accumulators.git"\n\ +branch = "feat/sha2-hasher"\n\ +replace-with = "vendored-sources"\n\ +\n\ +[source."git+https://github.com/ametel01/eth-rlp-verify.git"]\n\ +git = "https://github.com/ametel01/eth-rlp-verify.git"\n\ +replace-with = "vendored-sources"\n\ +\n\ +[source."git+https://github.com/ametel01/garaga.git"]\n\ +git = "https://github.com/ametel01/garaga.git"\n\ +replace-with = "vendored-sources"\n\ +\n\ +[source."git+https://github.com/risc0/risc0-ethereum.git"]\n\ +git = "https://github.com/risc0/risc0-ethereum"\n\ +tag = "v1.1.4"\n\ +replace-with = "vendored-sources"\n\ +\n\ +[source."git+https://github.com/lambdaclass/lambdaworks.git"]\n\ +git = "https://github.com/lambdaclass/lambdaworks.git"\n\ +replace-with = "vendored-sources"\n\ +\n\ +[source.vendored-sources]\n\ +directory = "/usr/local/cargo/registry/src/github.com-1ecc6299db9ec823"\n\ +\n\ +[net]\n\ +offline = true\n\ +\n\ +[patch.crates-io]\n\ +sha2 = { git = "https://github.com/risc0/RustCrypto-hashes.git", tag = "sha2-v0.10.8-risczero.0" }' > /usr/local/cargo/config.toml + +# Step 4: Prepare sqlx before building +# RUN cargo install sqlx-cli --no-default-features --features native-tls,postgres +# RUN cargo sqlx prepare --database-url="${DATABASE_URL}" -- --package publisher --bin build-mmr + +# Step 5: Build the application +RUN cargo build --release --package publisher --bin build-mmr + +# Stage 2: Create a minimal runtime image +FROM debian:bookworm-slim + +WORKDIR /app + +# Install runtime dependencies and Foundry +RUN apt-get update && apt-get install -y \ + ca-certificates \ + openssl \ + libssl3 \ + curl \ + git \ + && rm -rf /var/lib/apt/lists/* \ + && curl -L https://foundry.paradigm.xyz | bash \ + && /root/.foundry/bin/foundryup + +# Add foundry binaries to PATH +ENV PATH="/root/.foundry/bin:$PATH" + +# Copy the compiled binary from the builder stage +COPY --from=builder /app/target/release/build-mmr . + +# Make the binary executable +RUN chmod +x /app/build-mmr + +# Define the entrypoint +ENTRYPOINT ["/app/build-mmr"] +CMD ["-n", "1"] \ No newline at end of file diff --git a/docker/Dockerfile.client b/docker/Dockerfile.client new file mode 100644 index 0000000..ecaf1bd --- /dev/null +++ b/docker/Dockerfile.client @@ -0,0 +1,121 @@ +# Stage 1: Build the application +FROM rust:latest AS builder + +WORKDIR /app + +# Install the RISC Zero toolchain +RUN curl -L https://risczero.com/install | bash && \ + /root/.risc0/bin/rzup install || true && \ + mkdir -p /root/.cargo/bin && \ + (ln -sf /root/.risc0/bin/cargo-risczero /root/.cargo/bin/cargo-risczero || echo "Symlink creation failed, checking directories..." && ls -la /root/.risc0/bin && ls -la /root/.cargo) + +ENV PATH="/root/.risc0/bin:/root/.cargo/bin:$PATH" + +# Step 1: Copy Cargo.toml and Cargo.lock +COPY Cargo.toml Cargo.lock ./ + +# Step 2: Copy the workspace crates first +COPY crates ./crates + +# Step 3: Create necessary directories and copy pre-fetched dependencies +RUN mkdir -p /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823/cc-1.2.2/src/target && \ + mkdir -p /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823/cc-1.2.2/src/windows && \ + mkdir -p /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823/cc-1.2.2/src/parallel + +# Debug the build context +RUN echo "=== Build Context ===" && \ + pwd && \ + ls -la && \ + echo "=== Vendor Directory (if exists) ===" && \ + ls -R vendor || echo "No vendor directory found!" + +# Only proceed with copy if directory exists +RUN mkdir -p /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823/cc-1.2.2/src/{target,windows,parallel} + +# Create the destination directory with correct permissions +RUN mkdir -p /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823 && \ + chmod 755 /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823 + +# Copy the vendor directory with permissions preserved +COPY --chown=root:root vendor/ /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823/ + +# Debug and verify the copy +RUN echo "=== Debug: Directory structure after copy ===" && \ + find /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823 -type d && \ + echo "=== Debug: All files in cc-1.2.2 ===" && \ + find /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823/cc-1.2.2 -type f && \ + echo "=== Debug: Setting final permissions ===" && \ + chmod -R u+r,g+r /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823 + +# Create cargo config for vendored dependencies +RUN mkdir -p /usr/local/cargo/registry && \ + echo '[source.crates-io]\n\ +replace-with = "vendored-sources"\n\ +\n\ +[source."git+https://github.com/risc0/RustCrypto-hashes.git"]\n\ +git = "https://github.com/risc0/RustCrypto-hashes.git"\n\ +tag = "sha2-v0.10.8-risczero.0"\n\ +replace-with = "vendored-sources"\n\ +\n\ +[source."git+https://github.com/xJonathanLEI/starknet-rs.git"]\n\ +git = "https://github.com/xJonathanLEI/starknet-rs.git"\n\ +branch = "master"\n\ +replace-with = "vendored-sources"\n\ +\n\ +[source."git+https://github.com/ametel01/rust-accumulators.git"]\n\ +git = "https://github.com/ametel01/rust-accumulators.git"\n\ +branch = "feat/sha2-hasher"\n\ +replace-with = "vendored-sources"\n\ +\n\ +[source."git+https://github.com/ametel01/eth-rlp-verify.git"]\n\ +git = "https://github.com/ametel01/eth-rlp-verify.git"\n\ +replace-with = "vendored-sources"\n\ +\n\ +[source."git+https://github.com/ametel01/garaga.git"]\n\ +git = "https://github.com/ametel01/garaga.git"\n\ +replace-with = "vendored-sources"\n\ +\n\ +[source."git+https://github.com/risc0/risc0-ethereum.git"]\n\ +git = "https://github.com/risc0/risc0-ethereum"\n\ +tag = "v1.1.4"\n\ +replace-with = "vendored-sources"\n\ +\n\ +[source."git+https://github.com/lambdaclass/lambdaworks.git"]\n\ +git = "https://github.com/lambdaclass/lambdaworks.git"\n\ +replace-with = "vendored-sources"\n\ +\n\ +[source.vendored-sources]\n\ +directory = "/usr/local/cargo/registry/src/github.com-1ecc6299db9ec823"\n\ +\n\ +[net]\n\ +offline = true\n\ +\n\ +[patch.crates-io]\n\ +sha2 = { git = "https://github.com/risc0/RustCrypto-hashes.git", tag = "sha2-v0.10.8-risczero.0" }' > /usr/local/cargo/config.toml + +# Step 4: Build the application +ARG DATABASE_URL +ENV DATABASE_URL=${DATABASE_URL} +RUN cargo build --release --package client + +# Stage 2: Create a minimal runtime image +FROM debian:bookworm-slim + +# Set up the working directory +WORKDIR /app + +# Install runtime dependencies with OpenSSL 3 +RUN apt-get update && apt-get install -y \ + ca-certificates \ + openssl \ + libssl3 \ + && rm -rf /var/lib/apt/lists/* + +# Copy the compiled binary from the builder stage to /usr/local/bin +COPY --from=builder /app/target/release/client /usr/local/bin/client + +# Make the binary executable +RUN chmod +x /usr/local/bin/client + +# Define the entrypoint +CMD ["client"] diff --git a/docker/Dockerfile.deploy b/docker/Dockerfile.deploy new file mode 100644 index 0000000..5a03452 --- /dev/null +++ b/docker/Dockerfile.deploy @@ -0,0 +1,37 @@ +FROM rust:slim-bookworm + +# Avoid prompts from apt +ENV DEBIAN_FRONTEND=noninteractive + +# Install basic dependencies +RUN apt-get update && apt-get install -y \ + curl \ + git \ + ca-certificates \ + pkg-config \ + libssl-dev \ + jq \ + && rm -rf /var/lib/apt/lists/* + +# Install Foundry +RUN curl -L https://foundry.paradigm.xyz | bash +ENV PATH="/root/.foundry/bin:${PATH}" +RUN foundryup + +# Set up directories and PATH for Scarb +RUN mkdir -p /root/.local/bin +ENV PATH="/root/.local/bin:${PATH}" + +# Install Scarb +RUN curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | sh -s -- -v 2.8.5 + +# Install Starkli +RUN curl https://get.starkli.sh | sh && \ + /root/.starkli/bin/starkliup -v 0.3.5 +ENV PATH="/root/.starkli/bin:${PATH}" + +# Create Starkli directory structure +RUN mkdir -p /root/.starkli/accounts /root/.starkli/keystores + +WORKDIR /app +CMD ["bash"] \ No newline at end of file diff --git a/docker/Dockerfile.katana b/docker/Dockerfile.katana new file mode 100644 index 0000000..f29fb20 --- /dev/null +++ b/docker/Dockerfile.katana @@ -0,0 +1,31 @@ +FROM ubuntu:22.04 + +# Avoid prompts from apt +ENV DEBIAN_FRONTEND=noninteractive + +# Install basic dependencies +RUN apt-get update && apt-get install -y \ + curl \ + git \ + ca-certificates \ + build-essential \ + pkg-config \ + libssl-dev \ + && rm -rf /var/lib/apt/lists/* + +# Install Dojo +RUN curl -L https://install.dojoengine.org | bash +ENV PATH="/root/.dojo/bin:${PATH}" +RUN dojoup -v 1.0.0-alpha.16 + +# Create config directory +RUN mkdir -p /app/config + +# Verify installation +RUN katana --version + +# Expose default Katana port +EXPOSE 5050 + +# Default command to run Katana +CMD ["katana", "--messaging", "/app/config/anvil.messaging.json", "--disable-fee", "--disable-validate", "--host", "0.0.0.0"] \ No newline at end of file diff --git a/docker/Dockerfile.relayer b/docker/Dockerfile.relayer new file mode 100644 index 0000000..9abb0ff --- /dev/null +++ b/docker/Dockerfile.relayer @@ -0,0 +1,60 @@ +# Stage 1: Build the application +FROM rust:alpine AS builder + +# Set up the working directory +WORKDIR /app + +# Install build dependencies including OpenSSL static libraries +RUN apk add --no-cache \ + musl-dev \ + git \ + openssl-dev \ + openssl-libs-static \ + pkgconfig \ + gcc + +# Set environment variables for static linking +ENV OPENSSL_STATIC=1 +ENV OPENSSL_LIB_DIR=/usr/lib +ENV OPENSSL_INCLUDE_DIR=/usr/include/openssl + +# Copy only necessary workspace files +COPY Cargo.toml Cargo.lock ./ +COPY vendor /usr/local/cargo/registry +COPY crates/common ./crates/common +COPY crates/relayer ./crates/relayer + +# Create cargo config for vendored dependencies +RUN mkdir -p /usr/local/cargo/registry && echo $'[source.crates-io]\nreplace-with = "vendored-sources"\n\n[source."git+https://github.com/xJonathanLEI/starknet-rs.git?branch=master"]\ngit = "https://github.com/xJonathanLEI/starknet-rs.git"\nbranch = "master"\nreplace-with = "vendored-sources"\n\n[source.vendored-sources]\ndirectory = "/usr/local/cargo/registry"\n\n[net]\ngit-fetch-with-cli = true' > /usr/local/cargo/config.toml + +# Create a minimal workspace with dependencies +RUN echo $'[workspace]\nmembers = ["crates/common", "crates/relayer"]\nresolver = "2"\n\n\ +[workspace.dependencies]\n\ +dotenv = "0.15.0"\n\ +thiserror = "1.0.50"\n\ +starknet-crypto = "0.7.3"\n\ +tracing = "0.1.40"\n\ +tokio = { version = "1.35.0", features = ["full"] }\n\ +eyre = "0.6.11"' > workspace.toml && \ + mv workspace.toml Cargo.toml + +# Build only relayer +RUN cargo build --release --package relayer + +# Stage 2: Create a minimal runtime image +FROM alpine:latest + +# Update repositories and install SSL certificates +RUN echo "https://mirror.ette.biz/alpine/v3.19/main" > /etc/apk/repositories && \ + echo "https://mirror.ette.biz/alpine/v3.19/community" >> /etc/apk/repositories && \ + apk update && \ + apk add --no-cache ca-certificates openssl + +# Copy the compiled binary from the builder stage to /usr/local/bin +COPY --from=builder /app/target/release/relayer /usr/local/bin/relayer + +# Make the binary executable +RUN chmod +x /usr/local/bin/relayer + +# Remove the ENTRYPOINT and CMD directives +# Let docker-compose handle the command diff --git a/docker/compose/docker-compose.yml b/docker/compose/docker-compose.yml deleted file mode 100644 index 6ed81dd..0000000 --- a/docker/compose/docker-compose.yml +++ /dev/null @@ -1,165 +0,0 @@ -services: - anvil: - build: - context: ../.. - dockerfile: docker/images/anvil/Dockerfile - ports: - - "8545:8545" - env_file: - - ../../.env - environment: - - ETH_RPC_URL=${ETH_RPC_URL} - - FOUNDRY_EVM_VERSION=${FOUNDRY_EVM_VERSION} - healthcheck: - test: ["CMD", - "curl", - "-X", - "POST", - "-H", - "Content-Type: application/json", - "-d", '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}', - "http://localhost:8545" - ] - interval: 10s - retries: 5 - start_period: 10s - timeout: 5s - networks: - fossil_net: - aliases: - - anvil - - katana: - build: - context: ../.. - dockerfile: docker/images/katana/Dockerfile - ports: - - "5050:5050" - volumes: - - ../../config:/app/config - environment: - - STARKNET_RPC_URL=${STARKNET_RPC_URL} - - STARKNET_PRIVATE_KEY=${STARKNET_PRIVATE_KEY} - - STARKNET_ACCOUNT_ADDRESS=${STARKNET_ACCOUNT_ADDRESS} - healthcheck: - test: ["CMD", - "curl", - "-X", - "POST", - "-H", "Content-Type: application/json", - "-d", "{\"jsonrpc\":\"2.0\",\"method\":\"starknet_getBlockWithTxHashes\",\"params\":[\"latest\"],\"id\":1}", - "http://localhost:5050" - ] - interval: 10s - retries: 5 - start_period: 20s - timeout: 5s - depends_on: - anvil: - condition: service_healthy - command: > - katana - --messaging /app/config/anvil.messaging.json - --disable-fee - --disable-validate - --host 0.0.0.0 - networks: - fossil_net: - aliases: - - katana - - ethereum: - build: - context: ../.. - dockerfile: docker/images/ethereum/Dockerfile - env_file: - - ../../.env - environment: - - FOUNDRY_EVM_VERSION=${FOUNDRY_EVM_VERSION} - - ETH_RPC_URL=http://anvil:8545 - - PRIVATE_KEY=${ACCOUNT_PRIVATE_KEY} - volumes: - - type: bind - source: ../../contracts - target: /app/contracts - - type: bind - source: ../../scripts - target: /app/scripts - - type: bind - source: ../../contracts/ethereum/lib - target: /app/lib - - type: bind - source: ../../contracts/ethereum/foundry.toml - target: /app/foundry.toml - depends_on: - anvil: - condition: service_healthy - networks: - fossil_net: - aliases: - - ethereum-deployer - - starknet: - build: - context: ../.. - dockerfile: docker/images/starknet/Dockerfile - env_file: - - ../../.env - environment: - - STARKNET_ACCOUNT=katana-0 - - STARKNET_RPC=http://katana:5050 - - STARKNET_RPC_URL=http://katana:5050 - volumes: - - ../..:/app - depends_on: - katana: - condition: service_healthy - networks: - fossil_net: - aliases: - - starknet-deployer - - client: - build: - context: ../.. - dockerfile: docker/images/client/Dockerfile - platform: linux/amd64 - image: fossil-client:latest - env_file: - - ../../.env - environment: - - STARKNET_RPC_URL=http://katana:5050 - networks: - fossil_net: - aliases: - - light-client - - relayer: - build: - context: ../.. - dockerfile: docker/images/relayer/Dockerfile - image: fossil-relayer:latest - env_file: - - ../../.env - depends_on: - - client - networks: - fossil_net: - aliases: - - relayer - -networks: - fossil_net: - driver: bridge - ipam: - driver: default - config: - - subnet: 172.20.0.0/16 - gateway: 172.20.0.1 - driver_opts: - com.docker.network.bridge.enable_icc: "true" - com.docker.network.bridge.enable_ip_masquerade: "true" - com.docker.network.bridge.host_binding_ipv4: "0.0.0.0" - -volumes: - scarb-cache: diff --git a/docker/images/client/Dockerfile b/docker/images/client/Dockerfile deleted file mode 100644 index bd9b59f..0000000 --- a/docker/images/client/Dockerfile +++ /dev/null @@ -1,61 +0,0 @@ -FROM rust:latest AS builder - -RUN apt-get update && apt-get install -y \ - pkg-config \ - libssl-dev \ - curl \ - git \ - && rm -rf /var/lib/apt/lists/* - -# Configure git for better reliability -RUN git config --global http.postBuffer 524288000 && \ - git config --global http.lowSpeedLimit 1000 && \ - git config --global http.lowSpeedTime 60 && \ - git config --global core.compression 0 && \ - git config --global http.sslVerify false && \ - git config --global submodule.recurse true - -# Create cargo config directory and add git-fetch-with-cli config -RUN mkdir -p /usr/local/cargo/ -COPY < Cargo.toml - -# Copy only the needed crates and Cargo.lock -COPY Cargo.lock ./ -COPY crates/relayer ./crates/relayer -COPY crates/common ./crates/common - -# Build with verbose output -RUN cargo build --release -v && \ - ls -la target/release/relayer && \ - chmod +x target/release/relayer - -# Use a newer Debian version that has OpenSSL 3 -FROM debian:bookworm -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - libssl3 \ - ca-certificates \ - && rm -rf /var/lib/apt/lists/* -COPY --from=builder /app/target/release/relayer /usr/local/bin/relayer - -# Add wrapper script -RUN echo '#!/bin/sh\nwhile true; do\n relayer\n sleep 60\ndone' > /usr/local/bin/run-relayer.sh \ - && chmod +x /usr/local/bin/run-relayer.sh - -CMD ["/usr/local/bin/run-relayer.sh"] diff --git a/scripts/deploy-ethereum.sh b/scripts/deploy-ethereum.sh new file mode 100755 index 0000000..b959a67 --- /dev/null +++ b/scripts/deploy-ethereum.sh @@ -0,0 +1,123 @@ +#!/bin/bash + +# Ensure the script stops on the first error +set -e + +# Check if environment argument is provided +if [ -z "$1" ]; then + echo "Usage: $0 " + echo "Available environments: local, sepolia, mainnet" + exit 1 +fi + +# Validate environment argument +ENV_TYPE="$1" +case "$ENV_TYPE" in +"local" | "sepolia" | "mainnet") + ENV_FILE="/app/.env.$ENV_TYPE" + echo "Using environment: $ENV_TYPE ($ENV_FILE)" + ;; +*) + echo "Invalid environment. Must be one of: local, sepolia, mainnet" + exit 1 + ;; +esac + +# Source the appropriate environment file +source "$ENV_FILE" +export ACCOUNT_PRIVATE_KEY=${ACCOUNT_PRIVATE_KEY} + +ETHEREUM_DIR="/app/contracts/ethereum" +CONFIG_DIR="/app/config" + +# Define colors +GREEN='\033[0;32m' +BLUE='\033[0;34m' +YELLOW='\033[1;33m' +NC='\033[0m' +BOLD='\033[1m' +RED='\033[0;31m' + +# Function to update environment variables +update_env_var() { + local var_name=$1 + local var_value=$2 + + if grep -q "^$var_name=" "$ENV_FILE"; then + echo -e "${BLUE}$var_name already exists, replacing in $ENV_FILE...${NC}" + sed -i "s|^$var_name=.*|$var_name=$var_value|" "$ENV_FILE" + else + echo -e "${BLUE}Appending $var_name to $ENV_FILE...${NC}" + echo "$var_name=$var_value" >>"$ENV_FILE" + fi +} + +# Function to update JSON config +update_json_config() { + local json_file=$1 + local contract_address=$2 + + # Create temp file in the same directory to avoid permission issues + local tmp_file="${json_file}.tmp" + + if ! jq --arg addr "$contract_address" '.contract_address = $addr' "$json_file" > "$tmp_file"; then + echo -e "${RED}Failed to update JSON file${NC}" + rm -f "$tmp_file" + return 1 + fi + + if ! mv "$tmp_file" "$json_file"; then + echo -e "${RED}Failed to replace JSON file${NC}" + rm -f "$tmp_file" + return 1 + fi + + echo -e "${BLUE}Updated contract address in $json_file${NC}" +} + +# Function to deploy with retries +deploy_contracts() { + local max_attempts=3 + local attempt=1 + local wait_time=10 + + while [ $attempt -le $max_attempts ]; do + echo -e "${BLUE}${BOLD}Deploying Ethereum contracts (Attempt $attempt/$max_attempts)...${NC}" + + if forge script script/LocalTesting.s.sol:LocalSetup --broadcast --rpc-url $ANVIL_URL; then + return 0 + fi + + if [ $attempt -lt $max_attempts ]; then + echo -e "${YELLOW}Deployment failed, retrying in ${wait_time}s...${NC}" + sleep $wait_time + fi + + attempt=$((attempt + 1)) + done + + echo -e "${RED}Failed to deploy contracts after $max_attempts attempts${NC}" + return 1 +} + +# Deploy Ethereum contracts +cd "$ETHEREUM_DIR" +deploy_contracts || exit 1 + +# Read values from the JSON file and update env vars +SN_MESSAGING=$(jq -r '.snMessaging_address' logs/local_setup.json) +L1_MESSAGE_SENDER=$(jq -r '.l1MessageSender_address' logs/local_setup.json) + +# Update the environment variables +update_env_var "SN_MESSAGING" "$SN_MESSAGING" +update_env_var "L1_MESSAGE_SENDER" "$L1_MESSAGE_SENDER" + +# Update the anvil.messaging.json config +update_json_config "$CONFIG_DIR/anvil.messaging.json" "$SN_MESSAGING" + +# Source the updated environment variables +source "$ENV_FILE" + +echo -e "${BLUE}Using L1_MESSAGE_SENDER: $L1_MESSAGE_SENDER${NC}" +echo -e "${BLUE}Using SN_MESSAGING: $SN_MESSAGING${NC}" +echo -e "${GREEN}${BOLD}Ethereum deployment completed successfully!${NC}" \ No newline at end of file diff --git a/scripts/deploy-starknet.sh b/scripts/deploy-starknet.sh new file mode 100755 index 0000000..976e23d --- /dev/null +++ b/scripts/deploy-starknet.sh @@ -0,0 +1,140 @@ +#!/bin/bash + +# Ensure the script stops on the first error +set -e + +# Store the original directory (now inside container at /app) +ORIGINAL_DIR="/app" +UPDATE_INTERVAL=40 + +# Update the environment file with new addresses +update_env_var() { + local var_name=$1 + local var_value=$2 + + if grep -q "^$var_name=" "$ENV_FILE"; then + echo -e "${BLUE}$var_name already exists, replacing in $ENV_FILE...${NC}" + sed -i "s|^$var_name=.*|$var_name=$var_value|" "$ENV_FILE" + else + echo -e "${BLUE}Appending $var_name to $ENV_FILE...${NC}" + echo "$var_name=$var_value" >>"$ENV_FILE" + fi +} + +# Check if environment argument is provided +if [ -z "$1" ]; then + echo "Usage: $0 " + echo "Available environments: local, sepolia, mainnet" + exit 1 +fi + +# Validate environment argument +ENV_TYPE="$1" +case "$ENV_TYPE" in +"local" | "sepolia" | "mainnet") + ENV_FILE="$ORIGINAL_DIR/.env.$ENV_TYPE" + echo "Using environment: $ENV_TYPE ($ENV_FILE)" + ;; +*) + echo "Invalid environment. Must be one of: local, sepolia, mainnet" + exit 1 + ;; +esac + +# Check if environment file exists +if [ ! -f "$ENV_FILE" ]; then + echo "Error: Environment file $ENV_FILE not found" + exit 1 +fi + +# Source the appropriate environment file +source "$ENV_FILE" + +STARKNET_DIR="/app/contracts/starknet" + +# Define colors +GREEN='\033[0;32m' +BLUE='\033[0;34m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color +BOLD='\033[1m' +RED='\033[0;31m' + +# Now deploy Starknet contracts +echo -e "\n${BLUE}${BOLD}Building Starknet contracts...${NC}" +cd "$STARKNET_DIR" + +scarb build + +echo -e "\n${BLUE}${BOLD}Deploying Starknet contracts...${NC}" +# Declare and deploy Fossil Store contract +echo -e "\n${YELLOW}Declaring Fossil Store contract...${NC}" +FOSSILSTORE_HASH=$(starkli declare ./target/dev/fossil_store_Store.contract_class.json --compiler-version 2.8.2 -w | grep -o '0x[a-fA-F0-9]\{64\}' | head -1) +echo -e "${GREEN}Class hash declared: ${BOLD}$FOSSILSTORE_HASH${NC}" +echo + +echo -e "${YELLOW}Deploying Fossil Store contract...${NC}" +FOSSILSTORE_ADDRESS=$(starkli deploy $FOSSILSTORE_HASH --salt 1 -w | grep -o '0x[a-fA-F0-9]\{64\}' | head -1) +echo -e "${GREEN}Contract address: ${BOLD}$FOSSILSTORE_ADDRESS${NC}" +echo + +# Declare and deploy Fossil L1MessageProxy contract +echo -e "${YELLOW}Declaring Fossil L1MessageProxy contract...${NC}" +L1MESSAGEPROXY_HASH=$(starkli declare ./target/dev/l1_message_proxy_L1MessageProxy.contract_class.json --compiler-version 2.8.2 -w | grep -o '0x[a-fA-F0-9]\{64\}' | head -1) +echo -e "${GREEN}Class hash declared: ${BOLD}$L1MESSAGEPROXY_HASH${NC}" +echo + +echo -e "${YELLOW}Deploying Fossil L1MessageProxy contract...${NC}" +L1MESSAGEPROXY_ADDRESS=$(starkli deploy $L1MESSAGEPROXY_HASH $L1_MESSAGE_SENDER $FOSSILSTORE_ADDRESS --salt 1 -w | grep -o '0x[a-fA-F0-9]\{64\}' | head -1) +echo -e "${GREEN}Contract address: ${BOLD}$L1MESSAGEPROXY_ADDRESS${NC}" +echo + +# Declare and deploy Universal ECIP contract +echo -e "${YELLOW}Declaring Universal ECIP contract...${NC}" +ECIP_HASH=$(starkli declare ./target/dev/verifier_UniversalECIP.contract_class.json --compiler-version 2.8.2 -w | grep -o '0x[a-fA-F0-9]\{64\}' | head -1) +echo -e "${GREEN}Class hash declared: ${BOLD}$ECIP_HASH${NC}" +echo + +# Declare and deploy Groth16 Verifier contract +echo -e "${YELLOW}Declaring Groth16 Verifier contract...${NC}" +VERIFIER_HASH=$(starkli declare ./target/dev/verifier_Risc0Groth16VerifierBN254.contract_class.json --compiler-version 2.8.2 -w | grep -o '0x[a-fA-F0-9]\{64\}' | head -1) +echo -e "${GREEN}Class hash declared: ${BOLD}$VERIFIER_HASH${NC}" +echo + +echo -e "${YELLOW}Deploying Groth16 Verifier contract...${NC}" +VERIFIER_ADDRESS=$(starkli deploy $VERIFIER_HASH $ECIP_HASH --salt 1 | grep -o '0x[a-fA-F0-9]\{64\}' | head -1) +echo -e "${GREEN}Contract deployed at: ${BOLD}$VERIFIER_ADDRESS${NC}" +echo + +echo -e "${YELLOW}Declaring Fossil Verifier contract...${NC}" +FOSSIL_VERIFIER_HASH=$(starkli declare ./target/dev/verifier_FossilVerifier.contract_class.json --compiler-version 2.8.2 -w | grep -o '0x[a-fA-F0-9]\{64\}' | head -1) +echo -e "${GREEN}Class hash declared: ${BOLD}$FOSSIL_VERIFIER_HASH${NC}" +echo + +echo -e "${YELLOW}Deploying Fossil Verifier contract...${NC}" +FOSSIL_VERIFIER_ADDRESS=$(starkli deploy $FOSSIL_VERIFIER_HASH $VERIFIER_ADDRESS $FOSSILSTORE_ADDRESS --salt 1 -w | grep -o '0x[a-fA-F0-9]\{64\}' | head -1) +echo -e "${GREEN}Contract deployed at: ${BOLD}$FOSSIL_VERIFIER_ADDRESS${NC}" +echo + +echo -e "${YELLOW}Initializing Fossil Store contract...${NC}" +starkli invoke $FOSSILSTORE_ADDRESS initialize $FOSSIL_VERIFIER_ADDRESS $UPDATE_INTERVAL -w +echo -e "${GREEN}Fossil Store contract initialized${NC}" +echo + +echo -e "\n${GREEN}${BOLD}All contracts deployed!${NC}" + +# Update the environment file with the new addresses +update_env_var "L2_MSG_PROXY" "$L1MESSAGEPROXY_ADDRESS" +update_env_var "FOSSIL_STORE" "$FOSSILSTORE_ADDRESS" +update_env_var "STARKNET_VERIFIER" "$VERIFIER_ADDRESS" +update_env_var "FOSSIL_VERIFIER" "$FOSSIL_VERIFIER_ADDRESS" + +# Return to original directory +cd "$ORIGINAL_DIR" + +# Source the updated environment file +source "$ENV_FILE" + +sleep 5 + +echo -e "${GREEN}${BOLD}Environment variables successfully updated in $ENV_FILE${NC}" \ No newline at end of file diff --git a/scripts/deploy.sh b/scripts/deploy.sh deleted file mode 100755 index ccad1c9..0000000 --- a/scripts/deploy.sh +++ /dev/null @@ -1,122 +0,0 @@ -#!/bin/bash - -# Ensure the script stops on the first error -set -e - -source .env - -ETHEREUM_DIR="contracts/ethereum" - -cd $ETHEREUM_DIR && forge script script/LocalTesting.s.sol:LocalSetup --broadcast --rpc-url $ANVIL_URL - -L1_MESSAGE_SENDER=0x364C7188028348566E38D762f6095741c49f492B - -# Function to wait for Katana to be ready -# wait_for_katana() { -# echo "Waiting for Katana to be ready..." -# while ! curl -s -X POST -H "Content-Type: application/json" \ -# -d '{"jsonrpc":"2.0","method":"starknet_chainId","params":[],"id":1}' \ -# http://0.0.0.0:5050 > /dev/null; do -# echo "Katana is not ready yet. Waiting..." -# sleep 5 -# done -# echo "Katana is ready!" -# } - -# # Wait for Katana to be ready -# wait_for_katana - -# # Set absolute paths -STARKNET_DIR="../starknet" - -# Now deploy Starknet contracts -echo "Deploying Starknet contracts..." -cd $STARKNET_DIR - -scarb build - -# Declare and deploy Fossil Store contract -echo "Declaring Fossil Store contract..." -FOSSILSTORE_HASH=$(starkli declare ./target/dev/fossil_store_Store.contract_class.json --compiler-version 2.8.2 -w | grep -o '0x[a-fA-F0-9]\{64\}' | head -1) -echo "Class hash declared: $FOSSILSTORE_HASH" - -echo "Deploying Fossil Store contract..." -FOSSILSTORE_ADDRESS=$(starkli deploy $FOSSILSTORE_HASH --salt 1 -w | grep -o '0x[a-fA-F0-9]\{64\}' | head -1) -echo "Contract address: $FOSSILSTORE_ADDRESS" - -# Declare and deploy Fossil L1MessageProxy contract -echo "Declaring Fossil L1MessageProxy contract..." -L1MESSAGEPROXY_HASH=$(starkli declare ./target/dev/l1_message_proxy_L1MessageProxy.contract_class.json --compiler-version 2.8.2 -w | grep -o '0x[a-fA-F0-9]\{64\}' | head -1) -echo "Class hash declared: $L1MESSAGEPROXY_HASH" - -echo "Deploying Fossil L1MessageProxy contract..." -L1MESSAGEPROXY_ADDRESS=$(starkli deploy $L1MESSAGEPROXY_HASH $L1_MESSAGE_SENDER $FOSSILSTORE_ADDRESS --salt 1 -w | grep -o '0x[a-fA-F0-9]\{64\}' | head -1) -echo "Contract address: $L1MESSAGEPROXY_ADDRESS" - -# Declare and deploy Universal ECIP contract -echo "Declaring Universal ECIP contract..." -ECIP_HASH=$(starkli declare ./target/dev/verifier_UniversalECIP.contract_class.json --compiler-version 2.8.2 -w | grep -o '0x[a-fA-F0-9]\{64\}' | head -1) -echo "Class hash declared: $ECIP_HASH" - -# Declare and deploy Groth16 Verifier contract -echo "Declaring Groth16 Verifier contract..." -VERIFIER_HASH=$(starkli declare ./target/dev/verifier_Risc0Groth16VerifierBN254.contract_class.json --compiler-version 2.8.2 -w | grep -o '0x[a-fA-F0-9]\{64\}' | head -1) -echo "Class hash declared: $VERIFIER_HASH" - -echo "Deploying Groth16 Verifier contract..." -VERIFIER_ADDRESS=$(starkli deploy $VERIFIER_HASH $ECIP_HASH --salt 1 | grep -o '0x[a-fA-F0-9]\{64\}' | head -1) -echo "Contract deployed at: $VERIFIER_ADDRESS" - -echo "Declaring Fossil Verifier contract..." -FOSSIL_VERIFIER_HASH=$(starkli declare ./target/dev/verifier_FossilVerifier.contract_class.json --compiler-version 2.8.2 -w | grep -o '0x[a-fA-F0-9]\{64\}' | head -1) -echo "Class hash declared: $FOSSIL_VERIFIER_HASH" - -echo "Deploying Fossil Verifier contract..." -FOSSIL_VERIFIER_ADDRESS=$(starkli deploy $FOSSIL_VERIFIER_HASH $VERIFIER_ADDRESS $FOSSILSTORE_ADDRESS --salt 1 -w | grep -o '0x[a-fA-F0-9]\{64\}' | head -1) -echo "Contract deployed at: $FOSSIL_VERIFIER_ADDRESS" - -echo "All contracts deployed!" - -# # Fetch the current Ethereum block number using `cast` -# ETH_BLOCK=$(cast block-number) -# echo "Current Ethereum block number: $ETH_BLOCK" - -# # Ensure `ETH_BLOCK` is a valid number before performing arithmetic -# if [[ $ETH_BLOCK =~ ^[0-9]+$ ]]; then -# # Subtract 256 from the current block number -# ETH_BLOCK=$((ETH_BLOCK - 256)) -# echo "Updated Ethereum block number: $ETH_BLOCK" - -# # Run the Starkli command with the updated block number -# starkli invoke $FOSSILSTORE_ADDRESS update_mmr_state $ETH_BLOCK 0x0 0x0 0x0 0x0 -# echo "Updated MMR state on Starknet for testing with block number: $ETH_BLOCK" -# else -# echo "Failed to retrieve a valid block number from 'cast'." -# fi - -# Path to the .env file -ENV_FILE="../../.env" - -# Function to update or append an environment variable in the .env file -update_env_var() { - local var_name=$1 - local var_value=$2 - if grep -q "^$var_name=" "$ENV_FILE"; then - echo "$var_name already exists, replacing..." - sed -i "s|^$var_name=.*|$var_name=$var_value|" "$ENV_FILE" - else - echo "Appending $var_name to $ENV_FILE..." - echo "$var_name=$var_value" >>"$ENV_FILE" - fi -} - -# Update the .env file with the new addresses -update_env_var "L2_MSG_PROXY" "$L1MESSAGEPROXY_ADDRESS" -update_env_var "FOSSIL_STORE" "$FOSSILSTORE_ADDRESS" -update_env_var "STARKNET_VERIFIER" "$VERIFIER_ADDRESS" -update_env_var "FOSSIL_VERIFIER" "$FOSSIL_VERIFIER_ADDRESS" -pwd - -source ../../.env - -echo "Environment variables successfully updated in $ENV_FILE" diff --git a/scripts/run_local_workflow.sh b/scripts/run_local_workflow.sh deleted file mode 100755 index 3234694..0000000 --- a/scripts/run_local_workflow.sh +++ /dev/null @@ -1,69 +0,0 @@ -# Function to wait for a specific log message in a given log file -wait_for_log() { - local log_file=$1 - local search_string=$2 - - echo "Waiting for '$search_string' in $log_file..." - while ! grep -q "$search_string" "$log_file"; do - sleep 1 - done - echo "Found '$search_string' in $log_file." -} - -# Start Terminal 1: Start Anvil Ethereum Devnet -gnome-terminal -- bash -c " -cd config; -source anvil.env; -anvil --fork-url \$ETH_RPC_URL --auto-impersonate --block-time 12 | tee anvil.log; -exec bash" - -# Wait for Anvil to be ready -wait_for_log "config/anvil.log" "Listening on" - -# Start Terminal 2: Deploy L1MessageSender.sol -gnome-terminal -- bash -c " -cd contracts/ethereum; -cp ../../config/anvil.env .env; -source .env; -forge script script/LocalTesting.s.sol:LocalSetup --broadcast --rpc-url \$ANVIL_URL; -exec bash" - -# Wait for the contract deployment to finish -sleep 10 # Adjust as needed - -# Start Terminal 3: Start Katana Starknet Devnet -gnome-terminal -- bash -c " -cd scripts/katana; -source ../../config/katana.env; -katana --messaging ../../config/anvil.messaging.json --disable-fee | tee katana.log; -exec bash" - -# Wait for Katana to be ready -wait_for_log "scripts/katana/katana.log" "RPC server started" - -# Start Terminal 4: Deploy Starknet Contracts -gnome-terminal -- bash -c " -cd scripts/katana; -./deploy.sh | tee deploy.log; -exec bash" - -# Wait for the "Environment variables successfully updated" message -wait_for_log "scripts/katana/deploy.log" "Environment variables successfully updated" - -# Start Terminal 5: Run the Rust Relayer -gnome-terminal -- bash -c " -cp config/anvil.env .env -cd relayer; -cargo run; -exec bash" - -# Wait for the Rust Relayer to start -sleep 5 # Adjust as needed - -# Start Terminal 6: Run the Rust Light Client -gnome-terminal -- bash -c " -cd client; -cargo run; -exec bash" - -echo "Local testing setup complete. All services are running." diff --git a/scripts/run_relayer.sh b/scripts/run_relayer.sh index 041f863..6be3f48 100755 --- a/scripts/run_relayer.sh +++ b/scripts/run_relayer.sh @@ -1,14 +1,45 @@ -#!/bin/bash +#!/bin/sh set -e -cd crates/relayer +# Use RELAYER_INTERVAL from environment, default to 720 if not set +INTERVAL_MINUTES=${RELAYER_INTERVAL:-720} +MAX_RETRIES=3 +RETRY_DELAY=10 # seconds -while true; do - cargo run --release - echo "Waiting 10 minutes before next run..." - for ((i=10; i>0; i--)); do - echo "Next run in $i minutes..." - sleep 60 +run_with_retry() { + attempt=1 + while [ $attempt -le $MAX_RETRIES ]; do + echo "Starting relayer (attempt $attempt/$MAX_RETRIES)..." + if /usr/local/bin/relayer; then + return 0 + fi + + exit_code=$? + echo "Relayer exited with code $exit_code" + + if [ $attempt -lt $MAX_RETRIES ]; then + echo "Retrying in $RETRY_DELAY seconds..." + sleep $RETRY_DELAY + fi + attempt=$((attempt + 1)) done + + echo "Failed to run relayer after $MAX_RETRIES attempts" + return 1 +} + +while true; do + if run_with_retry; then + echo "Relayer completed successfully. Waiting $INTERVAL_MINUTES minutes before next run..." + i=$INTERVAL_MINUTES + while [ $i -gt 0 ]; do + echo "Next run in $i minutes..." + sleep 60 + i=$((i - 1)) + done + else + echo "Relayer failed all retry attempts. Exiting..." + exit 1 + fi done diff --git a/scripts/start.sh b/scripts/start.sh new file mode 100755 index 0000000..2caf374 --- /dev/null +++ b/scripts/start.sh @@ -0,0 +1,29 @@ +#!/bin/bash +set -e + +ENV_FILE=$1 +BUILD=$2 # Optional second parameter to trigger build + +if [ "$ENV_FILE" != ".env.local" ] && [ "$ENV_FILE" != ".env.sepolia" ] && [ "$ENV_FILE" != ".env.mainnet" ]; then + echo "Usage: $0 { .env.local | .env.sepolia | .env.mainnet } [build]" + exit 1 +fi + +export ENV_FILE=$ENV_FILE + +# Clean up any existing containers +docker-compose down + +if [ "$BUILD" == "build" ]; then + # Build images + echo "Building images..." + docker-compose -f docker-compose.yml -f docker-compose.build.yml build +fi + +if [ "$ENV_FILE" == ".env.local" ]; then + # Local development setup + docker-compose up -d +else + # Production setup (sepolia/mainnet) + docker-compose up -d client relayer +fi