diff --git a/.gitignore b/.gitignore index f22a3ae7c..5c051e5b0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ .DS_Store build/deployments **/.idea/ +build/anvil/anvil_state/state.json +build/anvil/deployments/** diff --git a/build/anvil/anvil-base/Dockerfile b/build/anvil/anvil-base/Dockerfile new file mode 100644 index 000000000..8e24430bb --- /dev/null +++ b/build/anvil/anvil-base/Dockerfile @@ -0,0 +1,22 @@ +# syntax=docker.io/docker/dockerfile:1.4 +FROM debian:bookworm-20230814-slim + +# install curl and jq (for healthcheck support) +RUN < "$TMPFILE" + +echo "Loading state into ${RPC}" +curl -sL -H 'Content-Type: application/json' -d @"$TMPFILE" "$RPC_URL" | jq -r .result diff --git a/build/anvil/devnet/Dockerfile b/build/anvil/devnet/Dockerfile new file mode 100644 index 000000000..7f03e86c0 --- /dev/null +++ b/build/anvil/devnet/Dockerfile @@ -0,0 +1,10 @@ +ARG NODE_ANVIL_VERSION=devel +FROM cartesi/anvil:${NODE_ANVIL_VERSION} + +WORKDIR /usr/share/cartesi +COPY ./entrypoint.sh / +COPY ./anvil_state/state.json . +COPY ./deployments/localhost.json . + +ENTRYPOINT ["/entrypoint.sh"] +CMD ["anvil", "--load-state", "/usr/share/cartesi/state.json"] diff --git a/build/anvil/devnet/docker-bake.hcl b/build/anvil/devnet/docker-bake.hcl new file mode 100644 index 000000000..d71acab38 --- /dev/null +++ b/build/anvil/devnet/docker-bake.hcl @@ -0,0 +1,6 @@ +target "docker-metadata-action" {} +target "docker-platforms" {} + +target "default" { + inherits = ["docker-metadata-action", "docker-platforms"] +} diff --git a/build/anvil/devnet/docker-bake.override.hcl b/build/anvil/devnet/docker-bake.override.hcl new file mode 100644 index 000000000..8564e9d7e --- /dev/null +++ b/build/anvil/devnet/docker-bake.override.hcl @@ -0,0 +1,6 @@ +target "default" { + tags = ["cartesi/anvil_devnet:devel"] + args = { + NODE_ANVIL_VERSION = "devel" + } +} diff --git a/build/anvil/devnet/docker-bake.platforms.hcl b/build/anvil/devnet/docker-bake.platforms.hcl new file mode 100644 index 000000000..40168da77 --- /dev/null +++ b/build/anvil/devnet/docker-bake.platforms.hcl @@ -0,0 +1,6 @@ +target "docker-platforms" { + platforms = [ + "linux/amd64", + "linux/arm64" + ] +} diff --git a/build/anvil/devnet/entrypoint.sh b/build/anvil/devnet/entrypoint.sh new file mode 100755 index 000000000..c058b16cb --- /dev/null +++ b/build/anvil/devnet/entrypoint.sh @@ -0,0 +1,3 @@ +#!/bin/sh +jq -r '.contracts | to_entries | .[] | "\(.value.address) \(.key)"' < /usr/share/cartesi/localhost.json +exec "$@" diff --git a/build/anvil/docker-compose.yml b/build/anvil/docker-compose.yml new file mode 100644 index 000000000..650a7722c --- /dev/null +++ b/build/anvil/docker-compose.yml @@ -0,0 +1,57 @@ +version: "3.9" + +services: + + anvil: + build: + context: ./anvil-base + dockerfile: ./Dockerfile + tags: + - "cartesi/anvil:devel" + command: + [ + "anvil", + "--host", + "0.0.0.0", + "--dump-state", + "anvil_state.json" + ] + healthcheck: + test: ["CMD", "eth_isready"] + interval: 10s + timeout: 1s + retries: 5 + ports: + - "8545:8545" + volumes: + - ./devnet/anvil_state:/anvil_state.json + + hardhat: + image: cartesi/rollups-hardhat:1.0.0 + environment: + - RPC_URL=http://anvil:8545 + command: + [ + "deploy", + "--network", + "localhost", + "--export", + "/opt/cartesi/share/deployments/localhost.json" + ] + init: true + depends_on: + anvil: + condition: service_healthy + healthcheck: + test: + [ + "CMD", + "test", + "-f", + "/opt/cartesi/share/deployments/localhost.json" + ] + interval: 30s + timeout: 30s + retries: 5 + volumes: + - ./devnet/deployments:/opt/cartesi/share/deployments diff --git a/build/anvil/hardhat.config.ts b/build/anvil/hardhat.config.ts new file mode 100644 index 000000000..b4efd6a61 --- /dev/null +++ b/build/anvil/hardhat.config.ts @@ -0,0 +1,138 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +import path from "path"; +import { HardhatUserConfig } from "hardhat/config"; +import { HttpNetworkUserConfig } from "hardhat/types"; +import { getSingletonFactoryInfo } from "@safe-global/safe-singleton-factory"; + +import "@nomiclabs/hardhat-ethers"; +import "@nomicfoundation/hardhat-verify"; +import "@typechain/hardhat"; +import "hardhat-deploy"; +import "hardhat-gas-reporter"; + +import { + Chain, + arbitrum, + arbitrumGoerli, + mainnet, + optimism, + optimismGoerli, + sepolia, +} from "viem/chains"; + +// read MNEMONIC from env variable +let mnemonic = process.env.MNEMONIC; + +const ppath = (packageName: string, pathname: string) => { + return path.join( + path.dirname(require.resolve(`${packageName}/package.json`)), + pathname, + ); +}; + +const networkConfig = (chain: Chain): HttpNetworkUserConfig => { + let url = process.env.RPC_URL || chain.rpcUrls.public.http.at(0); + + // support for infura and alchemy URLs through env variables + if (process.env.INFURA_ID && chain.rpcUrls.infura?.http) { + url = `${chain.rpcUrls.infura.http}/${process.env.INFURA_ID}`; + } else if (process.env.ALCHEMY_ID && chain.rpcUrls.alchemy?.http) { + url = `${chain.rpcUrls.alchemy.http}/${process.env.ALCHEMY_ID}`; + } + + return { + chainId: chain.id, + url, + accounts: mnemonic ? { mnemonic } : undefined, + }; +}; + +const config: HardhatUserConfig = { + networks: { + hardhat: mnemonic ? { accounts: { mnemonic } } : {}, + localhost: { + url: process.env.RPC_URL || "http://192.168.0.192:8545", + accounts: mnemonic ? { mnemonic } : undefined, + }, + arbitrum: networkConfig(arbitrum), + arbitrum_goerli: networkConfig(arbitrumGoerli), + mainnet: networkConfig(mainnet), + sepolia: networkConfig(sepolia), + optimism: networkConfig(optimism), + optimism_goerli: networkConfig(optimismGoerli), + }, + solidity: { + version: "0.8.19", + settings: { + optimizer: { + enabled: true, + }, + }, + }, + paths: { + artifacts: "artifacts", + deploy: "deploy", + deployments: "deployments", + }, + deterministicDeployment: (network: string) => { + // networks will use another deterministic deployment proxy + // https://github.com/safe-global/safe-singleton-factory + const chainId = parseInt(network); + const info = getSingletonFactoryInfo(chainId); + if (info) { + return { + factory: info.address, + deployer: info.signerAddress, + funding: ( + BigInt(info.gasPrice) * BigInt(info.gasLimit) + ).toString(), + signedTx: info.transaction, + }; + } else { + console.warn( + `unsupported deterministic deployment for network ${network}`, + ); + return undefined; + } + }, + typechain: { + outDir: "src/types", + target: "ethers-v5", + }, + etherscan: { + apiKey: process.env.ETHERSCAN_API_KEY, + }, + external: { + contracts: [ + { + artifacts: ppath("@cartesi/util", "/export/artifacts"), + deploy: ppath("@cartesi/util", "/dist/deploy"), + }, + ], + deployments: { + localhost: ["deployments/localhost"], + arbitrum: [ppath("@cartesi/util", "/deployments/arbitrum")], + arbitrum_goerli: [ + ppath("@cartesi/util", "/deployments/arbitrum_goerli"), + ], + mainnet: [ppath("@cartesi/util", "/deployments/mainnet")], + optimism: [ppath("@cartesi/util", "/deployments/optimism")], + optimism_goerli: [ + ppath("@cartesi/util", "/deployments/optimism_goerli"), + ], + sepolia: [ppath("@cartesi/util", "/deployments/sepolia")], + }, + }, + namedAccounts: { + deployer: { + default: 0, + }, + }, + gasReporter: { + enabled: process.env.REPORT_GAS ? true : false, + }, +}; + +export default config; \ No newline at end of file diff --git a/build/docker-compose.yml b/build/docker-compose.yml index a8ca443e5..9b86c2cc2 100644 --- a/build/docker-compose.yml +++ b/build/docker-compose.yml @@ -1,78 +1,199 @@ version: "3.9" -name: rollups-node +x-credentials: + &postgres-config + POSTGRES_HOSTNAME: database + POSTGRES_PORT: "5432" + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + POSTGRES_DB: postgres + services: - rollups-node: + + anvil: + build: + context: anvil/devnet + tags: + - "cartesi/anvil_devnet:devel" + command: [ + "anvil", + "--load-state", + "/usr/share/cartesi/state.json", + "--block-time", + "5" + ] + healthcheck: + test: ["CMD", "eth_isready"] + interval: 10s + timeout: 1s + retries: 5 + environment: + ANVIL_IP_ADDR: 0.0.0.0 + ports: + - "8545:8545" + volumes: + - blockchain-data:/usr/share/cartesi + + dispatcher: build: context: .. dockerfile: build/Dockerfile tags: - "cartesi/rollups-node:devel" - entrypoint: ["cartesi-rollups-node", "validator"] - ports: - - "4000:4000" + command: cartesi-rollups-dispatcher + restart: always depends_on: - hardhat: + anvil: condition: service_healthy deployer: condition: service_completed_successfully - hardhat_set_interval: + state_server: + condition: service_healthy + redis: + condition: service_healthy + environment: + RUST_LOG: info + RD_DAPP_DEPLOYMENT_FILE: /deployments/localhost/dapp.json + RD_ROLLUPS_DEPLOYMENT_FILE: /opt/cartesi/share/deployments/localhost.json + RD_EPOCH_DURATION: 86400 + SC_GRPC_ENDPOINT: http://state_server:50051 + SC_DEFAULT_CONFIRMATIONS: 1 + TX_PROVIDER_HTTP_ENDPOINT: http://anvil:8545 + AUTH_MNEMONIC: "test test test test test test test test test test test junk" + CHAIN_ID: 31337 + TX_CHAIN_IS_LEGACY: ${TX_LEGACY:-false} + TX_DEFAULT_CONFIRMATIONS: 2 + REDIS_ENDPOINT: redis://redis:6379 + volumes: + - blockchain-data:/opt/cartesi/share/deployments:ro + - ./deployments:/deployments:ro + + state_server: + build: + context: .. + dockerfile: build/Dockerfile + tags: + - "cartesi/rollups-node:devel" + command: cartesi-rollups-state-server + restart: always + healthcheck: + test: ["CMD-SHELL","bash -c 'echo \"\" > /dev/tcp/127.0.0.1/50051;'"] + interval: 10s + timeout: 5s + retries: 5 + depends_on: + anvil: + condition: service_healthy + environment: + RUST_LOG: info + SF_GENESIS_BLOCK: 0x1 + SF_SAFETY_MARGIN: 1 + BH_HTTP_ENDPOINT: http://anvil:8545 + BH_WS_ENDPOINT: ws://anvil:8545 + BH_BLOCK_TIMEOUT: 8 + + advance_runner: + build: + context: .. + dockerfile: build/Dockerfile + tags: + - "cartesi/rollups-node:devel" + command: cartesi-rollups-advance-runner + restart: always + healthcheck: + test: [ "CMD", "curl", "--fail", "localhost:8080/healthz" ] + interval: 10s + timeout: 5s + retries: 5 + depends_on: + redis: + condition: service_healthy + server_manager: + condition: service_healthy + deployer: condition: service_completed_successfully + volumes: + - ./deployments:/deployments:ro + - machine:/var/opt/cartesi/machine-snapshots + environment: + RUST_LOG: info + SERVER_MANAGER_ENDPOINT: http://server_manager:5001 + PROVIDER_HTTP_ENDPOINT: http://anvil:8545 + SESSION_ID: default_rollups_id + REDIS_ENDPOINT: redis://redis:6379 + CHAIN_ID: 31337 + DAPP_CONTRACT_ADDRESS_FILE: /deployments/localhost/dapp.json + SNAPSHOT_DIR: /var/opt/cartesi/machine-snapshots + SNAPSHOT_LATEST: /var/opt/cartesi/machine-snapshots/latest + + + inspect_server: + build: + context: .. + dockerfile: build/Dockerfile + tags: + - "cartesi/rollups-node:devel" + command: cartesi-rollups-inspect-server + restart: always + ports: + - "5005:5005" + depends_on: server_manager: condition: service_healthy + environment: + RUST_LOG: info + INSPECT_SERVER_ADDRESS: 0.0.0.0:5005 + SERVER_MANAGER_ADDRESS: server_manager:5001 + SESSION_ID: default_rollups_id + + indexer: + build: + context: .. + dockerfile: build/Dockerfile + tags: + - "cartesi/rollups-node:devel" + command: cartesi-rollups-indexer + restart: always + depends_on: database: condition: service_healthy redis: condition: service_healthy + deployer: + condition: service_completed_successfully environment: - RUST_LOG: info - CARTESI_LOG_LEVEL: info POSTGRES_ENDPOINT: postgres://postgres:password@database:5432/postgres - - #GraphQL Server - GRAPHQL_HEALTHCHECK_PORT: 8082 - GRAPHQL_HOST: "0.0.0.0" - GRAPHQL_PORT: "4000" - - #Indexer - INDEXER_HEALTHCHECK_PORT: 8083 - DAPP_CONTRACT_ADDRESS_FILE: /deployments/localhost/dapp.json + RUST_LOG: info REDIS_ENDPOINT: redis://redis:6379 CHAIN_ID: 31337 - + DAPP_CONTRACT_ADDRESS_FILE: /deployments/localhost/dapp.json volumes: - - machine:/var/opt/cartesi/machine-snapshots - - blockchain-data:/opt/cartesi/share/deployments:ro - ./deployments:/deployments:ro - hardhat: - image: cartesi/rollups-hardhat:1.0.0 - command: - [ - "node", - "--network", - "hardhat", - "--export", - "/opt/cartesi/share/deployments/localhost.json", - ] - init: true + graphql_server: + build: + context: .. + dockerfile: build/Dockerfile + tags: + - "cartesi/rollups-node:devel" + command: cartesi-rollups-graphql-server ports: - - "8545:8545" - healthcheck: - test: - ["CMD", "test", "-f", "/opt/cartesi/share/deployments/localhost.json"] - interval: 30s - timeout: 30s - retries: 5 - volumes: - - blockchain-data:/opt/cartesi/share/deployments - - ./deployments:/app/rollups/deployments + - "4000:4000" + depends_on: + database: + condition: service_healthy + environment: + RUST_LOG: info + GRAPHQL_HOST: "0.0.0.0" + GRAPHQL_PORT: "4000" + POSTGRES_ENDPOINT: postgres://postgres:password@database:5432/postgres + deployer: image: cartesi/rollups-cli:1.0.0 restart: on-failure depends_on: - hardhat: + anvil: condition: service_healthy server_manager: condition: service_healthy @@ -80,7 +201,7 @@ services: [ "create", "--rpc", - "http://hardhat:8545", + "http://anvil:8545", "--deploymentFile", "/opt/cartesi/share/deployments/localhost.json", "--mnemonic", @@ -88,42 +209,13 @@ services: "--templateHashFile", "/var/opt/cartesi/machine-snapshots/0_0/hash", "--outputFile", - "/deployments/localhost/dapp.json", + "/deployments/localhost/dapp.json" ] volumes: - blockchain-data:/opt/cartesi/share/deployments:ro - machine:/var/opt/cartesi/machine-snapshots:ro - ./deployments:/deployments - - hardhat_stop_automine: - image: curlimages/curl:7.84.0 - restart: on-failure - depends_on: - hardhat: - condition: service_healthy - deployer: - condition: service_completed_successfully - command: - [ - "--data", - '{"id":1337,"jsonrpc":"2.0","method":"evm_setAutomine","params":[false]}', - "http://hardhat:8545", - ] - - hardhat_set_interval: - image: curlimages/curl:7.84.0 - restart: on-failure - depends_on: - hardhat: - condition: service_healthy - hardhat_stop_automine: - condition: service_completed_successfully - command: - [ - "--data", - '{"id":1337,"jsonrpc":"2.0","method":"evm_setIntervalMining","params":[5000]}', - "http://hardhat:8545", - ] + server_manager: build: