diff --git a/.env.example b/.env.example index aa7f1438c..c9b9dbd5f 100644 --- a/.env.example +++ b/.env.example @@ -3,6 +3,15 @@ LOG_LEVEL=debug CONFIG_DIR=./config CONFIG_FILE_NAME=config.json + +# AWS dev credentials. There are 2 users each with their own +# kms key access: +# relayer-kms-signer with AWS_ACCESS_KEY_ID=AKIAWA.... +# relayer-kms-signer-dev with AWS_ACCESS_KEY_ID=AKIAWA.... + +AWS_ACCESS_KEY_ID=[AKIAWA....] +AWS_SECRET_ACCESS_KEY= + WEBHOOK_SIGNING_KEY= API_KEY= RATE_LIMIT_REQUESTS_PER_SECOND=100 @@ -13,6 +22,10 @@ REDIS_URL=redis://localhost:6379 # REDIS_READER_URL=redis://localhost:6380 REDIS_CONNECTION_TIMEOUT_MS=10000 REDIS_KEY_PREFIX=oz-relayer +# Required when REPOSITORY_STORAGE_TYPE=redis (application fails to start if missing) +# Generate with: openssl rand -base64 32 +# Alternative: cargo run --example generate_encryption_key +STORAGE_ENCRYPTION_KEY= # Queue backend selection # Supported: redis, sqs diff --git a/.env.railway.production.example b/.env.railway.production.example new file mode 100644 index 000000000..98eaac188 --- /dev/null +++ b/.env.railway.production.example @@ -0,0 +1,25 @@ +# Railway production environment template +# Configure these in your Railway relayer service variables. + +# Load production config file (no file copy needed) +CONFIG_DIR=./config +CONFIG_FILE_NAME=config.production.json + +# Redis storage (redis-prod is the recommended Railway Redis service name) +REPOSITORY_STORAGE_TYPE=redis +REDIS_URL=${{redis-prod.REDIS_URL}} +REDIS_KEY_PREFIX=oz-relayer-prod +REDIS_CONNECTION_TIMEOUT_MS=10000 + +# Enable if running multiple relayer instances +DISTRIBUTED_MODE=true + +# Required application secrets +API_KEY= +WEBHOOK_SIGNING_KEY= +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +# Required for Redis repository storage: +# Generate with: openssl rand -base64 32 +# Alternative: cargo run --example generate_encryption_key +STORAGE_ENCRYPTION_KEY= diff --git a/Dockerfile.development b/Dockerfile.development index a1fdf50ab..022703da4 100644 --- a/Dockerfile.development +++ b/Dockerfile.development @@ -19,10 +19,7 @@ ARG CARGO_FEATURES="" # Copy COPY . . -RUN --mount=type=cache,target=/usr/local/cargo/registry \ - --mount=type=cache,target=/usr/local/cargo/git \ - --mount=type=cache,target=/usr/app/target \ - if [ -n "$CARGO_FEATURES" ]; then \ +RUN if [ -n "$CARGO_FEATURES" ]; then \ cargo install --root /usr/app --path . --debug --locked --features "$CARGO_FEATURES"; \ else \ cargo install --root /usr/app --path . --debug --locked; \ @@ -34,6 +31,7 @@ FROM cgr.dev/chainguard/wolfi-base WORKDIR /app COPY --from=base --chown=nonroot:nonroot /usr/app/bin/openzeppelin-relayer /app/openzeppelin-relayer +COPY --from=base --chown=nonroot:nonroot /usr/app/config /app/config COPY --from=base /usr/lib/libssl.so.3 /usr/lib/libssl.so.3 COPY --from=base /usr/lib/libcrypto.so.3 /usr/lib/libcrypto.so.3 diff --git a/Dockerfile.production b/Dockerfile.production index 7f88f92fb..18c393daf 100644 --- a/Dockerfile.production +++ b/Dockerfile.production @@ -15,10 +15,7 @@ WORKDIR /usr/app ARG CARGO_FEATURES="" COPY . . -RUN --mount=type=cache,target=/usr/local/cargo/registry \ - --mount=type=cache,target=/usr/local/cargo/git \ - --mount=type=cache,target=/usr/app/target \ - if [ -n "$CARGO_FEATURES" ]; then \ +RUN if [ -n "$CARGO_FEATURES" ]; then \ cargo install --root /usr/app --path . --locked --features "$CARGO_FEATURES"; \ else \ cargo install --root /usr/app --path . --locked; \ @@ -29,6 +26,7 @@ FROM cgr.dev/chainguard/wolfi-base WORKDIR /app COPY --from=base --chown=nonroot:nonroot /usr/app/bin/openzeppelin-relayer /app/openzeppelin-relayer +COPY --from=base --chown=nonroot:nonroot /usr/app/config /app/config COPY --from=base /usr/lib/libssl.so.3 /usr/lib/libssl.so.3 COPY --from=base /usr/lib/libcrypto.so.3 /usr/lib/libcrypto.so.3 diff --git a/README.md b/README.md index 30f33ac47..c9780b9d4 100644 --- a/README.md +++ b/README.md @@ -1,649 +1,142 @@ # OpenZeppelin Relayer -[![codecov](https://codecov.io/gh/OpenZeppelin/openzeppelin-relayer/graph/badge.svg?token=HKHIQNSJ6H)](https://codecov.io/gh/OpenZeppelin/openzeppelin-relayer) -[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/OpenZeppelin/openzeppelin-relayer/badge)](https://api.securityscorecards.dev/projects/github.com/OpenZeppelin/openzeppelin-relayer) -[![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0) -[![CLA Assistant](https://github.com/OpenZeppelin/openzeppelin-relayer/actions/workflows/cla.yml/badge.svg)](https://github.com/OpenZeppelin/openzeppelin-relayer/actions/workflows/cla.yml) -[![CI](https://github.com/OpenZeppelin/openzeppelin-relayer/actions/workflows/ci.yaml/badge.svg)](https://github.com/OpenZeppelin/openzeppelin-relayer/actions/workflows/ci.yaml) -[![Release Workflow](https://github.com/OpenZeppelin/openzeppelin-relayer/actions/workflows/release-please.yml/badge.svg)](https://github.com/OpenZeppelin/openzeppelin-relayer/actions/workflows/release-please.yml) - This relayer service enables interaction with blockchain networks through transaction submissions. It offers multi-chain support and an extensible architecture for adding new chains. [User Docs](https://docs.openzeppelin.com/relayer/) | [Quickstart](https://docs.openzeppelin.com/relayer/quickstart) -## Features - -- **Multi-Chain Support**: Interact with multiple blockchain networks, including Solana and EVM-based chains. -- **Transaction Relaying**: Submit transactions to supported blockchain networks efficiently. -- **Transaction Signing**: Securely sign transactions using configurable key management. -- **Transaction Fee Estimation**: Estimate transaction fees for better cost management. -- **Solana Gasless Transactions**: Support for gasless transactions on Solana, enabling users to interact without transaction fees. -- **Transaction Nonce Management**: Handle nonce management to ensure transaction order. -- **Transaction Status Monitoring**: Track the status of submitted transactions. -- **SDK Integration**: Easily interact with the relayer through our companion JavaScript/TypeScript SDK. -- **Extensible Architecture**: Easily add support for new blockchain networks. -- **Configurable Network Policies**: Define and enforce network-specific policies for transaction processing. -- **Metrics and Observability**: Monitor application performance using Prometheus and Grafana. -- **Docker Support**: Deploy the relayer using Docker for both development and production environments. -- **Relayer Plugins**: Extend the relayer functionality through TypeScript functions. - -## Supported networks - -- Solana -- EVM -- Stellar - -> For details about current development status and upcoming features, check our [Project Roadmap](https://docs.openzeppelin.com/relayer/roadmap). - -## For users - -### Installation - -View the [Installation](https://docs.openzeppelin.com/relayer#getting_started) documentation for detailed information. For a quicker introduction, check out the [Quickstart](https://docs.openzeppelin.com/relayer/quickstart) guide. - -### Usage - -View the [Usage](https://docs.openzeppelin.com/relayer#running_the_relayer) documentation for more information. - -### Examples - -The repository includes several ready-to-use examples to help you get started with different configurations: - -| Example | Description | -| ------------------------------------------------------------------------------------ | -------------------------------------------------------- | -| [`basic-example`](./examples/basic-example/) | Simple setup with Redis | -| [`redis-storage`](./examples/redis-storage/) | Simple setup with Redis for storage | -| [`redis-tls`](./examples/redis-tls/) | Redis with TLS encrypted connections | -| [`basic-example-logging`](./examples/basic-example-logging/) | Configuration with file-based logging | -| [`basic-example-metrics`](./examples/basic-example-metrics/) | Setup with Prometheus and Grafana metrics | -| [`vault-secret-signer`](./examples/vault-secret-signer/) | Using HashiCorp Vault for key management | -| [`vault-transit-signer`](./examples/vault-transit-signer/) | Using Vault Transit for secure signing | -| [`evm-turnkey-signer`](./examples/evm-turnkey-signer/) | Using Turnkey Signer for EVM secure signing | -| [`solana-turnkey-signer`](./examples/solana-turnkey-signer/) | Using Turnkey Signer for Solana secure signing | -| [`solana-google-cloud-kms-signer`](./examples/solana-google-cloud-kms-signer/) | Using Google Cloud KMS Signer for Solana secure signing | -| [`stellar-gcp-kms-signer`](./examples/stellar-gcp-kms-signer/) | Using Google Cloud KMS Signer for Stellar secure signing | -| [`evm-cdp-signer`](./examples/evm-cdp-signer/) | Using CDP Signer for EVM secure signing | -| [`network-configuration-config-file`](./examples/network-configuration-config-file/) | Using Custom network configuration via config file | -| [`network-configuration-json-file`](./examples/network-configuration-json-file/) | Using Custom network configuration via json file | -| [`aws-sqs-queue-storage`](./examples/aws-sqs-queue-storage/) | Local SQS queue backend setup using LocalStack | -| [`x402-facilitator-plugin`](./examples/x402-facilitator-plugin/) | x402 Facilitator plugin | - -Each example includes: - -- A README with step-by-step instructions -- Docker Compose configuration -- Required configuration files - -## For Developers - -### Technical Overview - -The OpenZeppelin Relayer is built using Actix-web and provides HTTP endpoints for transaction submission, in-memory repository implementations, and configurable network policies. - -The following diagram illustrates the architecture of the relayer service, highlighting key components and their interactions. - -```mermaid -%%{init: { - 'theme': 'base', - 'themeVariables': { - 'background': '#ffffff', - 'mainBkg': '#ffffff', - 'primaryBorderColor': '#cccccc' - } -}}%% -flowchart TB - subgraph "Clients" - client[API/SDK] - end - - subgraph "OpenZeppelin Relayer" - subgraph "API Layer" - api[API Routes & Controllers] - middleware[Middleware] - plugins[Relayer Plugins] - end - - subgraph "Domain Layer" - domain[Domain Logic] - relayer[Relayer Services] - policies[Policy Enforcement] - end - - subgraph "Infrastructure" - repositories[Repositories] - jobs[Job Queue System] - signer[Signer Services] - provider[Network Providers] - end - - subgraph "Services Layer" - transaction[Transaction Services] - vault[Vault Services] - webhook[Webhook Notifications] - monitoring[Monitoring & Metrics] - end - - subgraph "Configuration" - config_files[Config Files] - env_vars[Environment Variables] - end - end - - subgraph "External Systems" - blockchain[Blockchain Networks] - redis[Redis] - vault_ext[HashiCorp Vault] - metrics[Prometheus/Grafana] - notification[Notification Services] - end - - %% Client connections - client -- "HTTP Requests" --> api - - %% API Layer connections - api -- "Processes requests" --> middleware - middleware -- "Validates & routes" --> domain - middleware -- "Invokes" --> plugins - - %% Domain Layer connections - domain -- "Uses" --> relayer - domain -- "Enforces" --> policies - relayer -- "Processes" --> transaction - plugins -- "Uses" --> relayer - - %% Services Layer connections - transaction -- "Signs with" --> signer - transaction -- "Connects via" --> provider - transaction -- "Queues jobs" --> jobs - webhook -- "Notifies" --> notification - monitoring -- "Collects" --> metrics - signer -- "May use" --> vault - - %% Infrastructure connections - repositories -- "Stores data" --> redis - jobs -- "Processes async" --> redis - vault -- "Secrets management" --> vault_ext - provider -- "Interacts with" --> blockchain - - %% Configuration connections - config_files -- "Configures" --> domain - env_vars -- "Configures" --> domain - - %% Styling - classDef apiClass fill:#f9f,stroke:#333,stroke-width:2px - classDef domainClass fill:#bbf,stroke:#333,stroke-width:2px - classDef infraClass fill:#bfb,stroke:#333,stroke-width:2px - classDef serviceClass fill:#fbf,stroke:#333,stroke-width:2px - classDef configClass fill:#fbb,stroke:#333,stroke-width:2px - classDef externalClass fill:#ddd,stroke:#333,stroke-width:1px - - class api,middleware,plugins apiClass - class domain,relayer,policies domainClass - class repositories,jobs,signer,provider infraClass - class transaction,vault,webhook,monitoring serviceClass - class config_files,env_vars configClass - class blockchain,redis,vault_ext,metrics,notification externalClass -``` - -### Project Structure - -The project follows a standard Rust project layout: - -```sh -openzeppelin-relayer/ -├── src/ -│ ├── api/ # Route and controllers logic -│ ├── bootstrap/ # Service initialization logic -│ ├── config/ # Configuration logic -│ ├── constants/ # Constant values used in the system -│ ├── domain/ # Domain logic -│ ├── jobs/ # Asynchronous processing logic (queueing) -│ ├── logging/ # Logs File rotation logic -│ ├── metrics/ # Metrics logic -│ ├── models/ # Data structures and types -│ ├── repositories/ # Configuration storage -│ ├── services/ # Services logic -│ ├── plugins/ # Relayer plugins -│ └── utils/ # Helper functions -│ -├── config/ # Configuration files -├── tests/ # Integration tests -├── docs/ # Documentation -├── scripts/ # Utility scripts -├── examples/ # Configuration examples -├── helpers/ # Rust helper scripts -└── ... other root files (Cargo.toml, README.md, etc.) -``` - -### Prerequisites - -- Docker -- Rust -- Redis -- [Sodium](https://doc.libsodium.org/) -- [Node.js + Typescript + ts-node](https://nodejs.org/) (v20+) for plugins. - -### Setup - -To get started, clone the repository: - -```sh -git clone https://github.com/openzeppelin/openzeppelin-relayer -cd openzeppelin-relayer -``` - -Run the following commands to install pre-commit hooks: - -- Install pre-commit hooks: - - ```bash - pip install pre-commit - pre-commit install --install-hooks -t commit-msg -t pre-commit -t pre-push - ``` - - > :warning: If you encounter issues with pip, consider using [pipx](https://pipx.pypa.io/stable/installation/) for a global installation. - -- Install the toolchain: - - ```sh - rustup component add rustfmt - ``` - -### Install Sodium - -- Install stable libsodium version from [here](https://download.libsodium.org/libsodium/releases/). -- Follow steps to install libsodium from the [libsodium installation guide](https://doc.libsodium.org/installation). - - > Note (Debian/Ubuntu): If you're compiling libsodium from source, install build-essential first. +## Pre-requisites - ```bash - sudo apt-get update && sudo apt-get install -y build-essential - ``` +- Docker installed on your machine +- [Sodium](https://doc.libsodium.org/). See [install sodium section](https://github.com/OpenZeppelin/openzeppelin-relayer?tab=readme-ov-file#install-sodium) for more information. +- `.env` file with the required environment variables & `config/config.json` file with the required configuration. See how to set it up in [config files section](https://github.com/OpenZeppelin/openzeppelin-relayer?tab=readme-ov-file#config-files) for more information. +- Create signers and add them to the `config/config.json` file. See how to set it up in [signers section](https://github.com/OpenZeppelin/openzeppelin-relayer?tab=readme-ov-file#creating-a-signer) for more information. +- Configure webook url in `config/config.json` file. See how to set it up in [webhook section](https://github.com/OpenZeppelin/openzeppelin-relayer?tab=readme-ov-file#configure-webhook-url) for more information. +- Configure webhook signing key in `config/config.json` file. See how to set it up in [webhook section](https://github.com/OpenZeppelin/openzeppelin-relayer?tab=readme-ov-file#configure-webhook-signing-key) for more information. +- Configure Api key in `config/config.json` file. See how to set it up in [api key section](https://github.com/OpenZeppelin/openzeppelin-relayer?tab=readme-ov-file#configure-api-key) for more information. +- Redis server running. See how to set it up in [redis section](https://github.com/OpenZeppelin/openzeppelin-relayer?tab=readme-ov-file#starting-redis-manually-without-docker-compose) for more information. -### Install Node.js +> ⚠️ Redis is automatically started when using docker compose. If you are not using docker compose, you need to create a dedicated network and start redis manually. -- Install Node.js from [here](https://nodejs.org/). -- Install Typescript and ts-node: +## Running Docker locally - ```bash - npm install -g typescript ts-node - ``` - -### Run Tests - -To run tests, use the following commands: - -```bash -cargo test -cargo test properties -cargo test integration -``` - -> :warning: Debian/Ubuntu: If you encounter OpenSSL build errors, install the required packages: - -```bash -sudo apt-get update && sudo apt-get install -y pkg-config libssl-dev -``` - -#### Run tests against Redis - -1. You can start a Redis instance using the following command: +### 1. Setup env vars ```bash -docker run -d \ - --name redis \ - -p 6379:6379 \ - redis:latest -``` - -2. Then remove the `#[ignore = "Requires active Redis instance"]` attribute from the tests you want to run. - -3. Run the tests using single thread to avoid race conditions within suites: - -````bash -cargo test your_test_regex -- --test-threads=1 - - -### Config files - -Create `config/config.json` file. You can use `config/config.example.json` as a starting point: - -```sh -cp config/config.example.json config/config.json -```` - -Refer to the [Configuration References](https://docs.openzeppelin.com/relayer#configuration_references) section for a complete list of configuration options. - -Create `.env` with correct values according to your needs from `.env.example` file as a starting point: - -```sh cp .env.example .env +uuidgen -> generates UUID +openssl rand -base64 32 -> generates STORAGE_ENCRYPTION_KEY ``` -### Queue backend configuration (Redis or SQS) - -The relayer supports two queue backends: +Create a unique uuid for WEBHOOK_SIGNING_KEY & API_KEY and set them in .env +Set STORAGE_ENCRYPTION_KEY in `.env` when using Redis repository storage (`REPOSITORY_STORAGE_TYPE=redis`). -- `redis` (default): uses Apalis + Redis queues -- `sqs`: uses AWS SQS workers/cron and minimizes Apalis queue usage +Set the correct AWS_ACCESS_KEY_ID & AWS_SECRET_ACCESS_KEY depending on user / relayer environment requires. + +AWS user `relayer-kms-signer-dev` + - has access to `origin-relayer-development-evm` + - with a public address: `0xca00ab46d0e009985c84c41e2f712c31102ff967` -Set in `.env`: - -```bash -QUEUE_BACKEND=redis -# or -# QUEUE_BACKEND=sqs -``` +AWS user `relayer-kms-signer` + - has access to `origin-relayer-production-evm` + - with a public address: `[todo....]` -When using SQS: +### 2. Cofigure the correct config file ```bash -QUEUE_BACKEND=sqs -AWS_REGION=us-east-1 -AWS_ACCOUNT_ID=123456789012 -# Optional: "auto" (default), "standard", or "fifo" -# SQS_QUEUE_TYPE=auto -# Optional alternative to AWS_ACCOUNT_ID: -# SQS_QUEUE_URL_PREFIX=https://sqs.us-east-1.amazonaws.com/123456789012/relayer- -``` - -By default (`SQS_QUEUE_TYPE=auto`), the relayer auto-detects whether queues are standard or FIFO at startup. - -Use distributed mode for multi-instance deployments so scheduled workers use Redis-based distributed locks and avoid duplicate execution: +# for development relayer +cp config/config.development.json config/config.json -```bash -DISTRIBUTED_MODE=true +# for production relayer +cp config/config.production.json config/config.json ``` -For single-instance local development, keep: +### 3. Start the service ```bash -DISTRIBUTED_MODE=false -``` - -> **Note**: After the service is running, all configuration components (relayers, signers, notifications) can also be managed via REST API endpoints for runtime changes. See the [Configuration Guide](https://docs.openzeppelin.com/relayer/configuration) for details on API-based configuration management. - -### Creating a Signer - -To create a new signer keystore, use the provided key generation tool: - -```sh -cargo run --example create_key -- \ - --password DEFINE_YOUR_PASSWORD \ - --output-dir config/keys \ - --filename local-signer.json +docker compose up ``` -Then update the `KEYSTORE_PASSPHRASE` field in your `.env` file with the password you used in the key creation example. - -The tool supports the following options: - -- `--password`: Required. Must contain at least: - - 12 characters - - One uppercase letter - - One lowercase letter - - One number - - One special character -- `--output-dir`: Directory for the keystore file (creates if not exists) -- `--filename`: Optional. Uses timestamp-based name if not provided -- `--force`: Optional. Allows overwriting existing files - -Example with all options: - -```sh -cargo run --example create_key -- \ - --password "YourSecurePassword123!" \ - --output-dir config/keys \ - --filename local-signer.json \ - --force -``` - -### Configure Webhook URL - -`/config/config.json` file is partially pre-configured. You need to specify the webhook URL that will receive updates from the relayer service. - -For simplicity, visit [Webhook.site](https://webhook.site), copy your unique URL, and then update the notifications[0].url field in `config/config.json` with this value. +### 4. Access the service -### Configure Webhook Signing Key - -To sign webhook notification payloads, populate the `WEBHOOK_SIGNING_KEY` entry in the `.env` file. - -For development purposes, you can generate the signing key using: +Once the container is running, you can access the service at `http://localhost:8080`. ```bash -cargo run --example generate_uuid +API_KEY=[set the api key] curl -s http://localhost:8080/api/v1/relayers \ + -H "Authorization: Bearer $API_KEY" | jq ``` -> Note: Alternatively, you can use any online UUID generator. - -Copy the generated UUID and update the `WEBHOOK_SIGNING_KEY` entry in the `.env` file. +## Railway production deployment (with Redis) -### Configure API Key +Use Railway source builds with `Dockerfile.production`. A separate GitHub workflow for image builds is not required. +This Railway service is configured to auto-deploy from GitHub on pushes to the `production` branch (each push triggers a Docker build + deployment). -Generate an API key signing key for development purposes using: +Reference template: `.env.railway.production.example`. -```bash -cargo run --example generate_uuid -# or run this command to generate a UUID -# uuidgen -``` +### 1. Provision Redis in Railway -> Note: Alternatively, you can use any online UUID generator. +1. Add a Redis service from Railway templates. +2. Rename it clearly, for example: `redis-prod`. +3. Keep Redis internal/private (disable public TCP proxy unless you explicitly need external access). -Copy the generated UUID and update the `API_KEY` entry in the `.env` file. +### 2. Configure relayer service variables -### Starting Redis manually (without docker compose) +Set these in the Railway service running this repo: -You can start Redis in one of two ways: - -A. _Expose to Host Only_ - -Use this if only your host machine needs direct access to Redis (e.g., for local testing with redis-cli). - -```bash -docker run -d \ - --name redis \ - -p 6379:6379 \ - redis:latest +```env +CONFIG_DIR=./config +CONFIG_FILE_NAME=config.production.json +REPOSITORY_STORAGE_TYPE=redis +REDIS_URL=${{redis-prod.REDIS_URL}} +REDIS_KEY_PREFIX=oz-relayer-prod +REDIS_CONNECTION_TIMEOUT_MS=10000 ``` -`-p 6379:6379` binds the container port to your localhost on the same port. +Required application secrets (already needed by the relayer): -B. _Connect with Other Containers via Custom Network_ - -Use this if relayer container need to talk to Redis. - -```sh -docker run -d \ - --name redis \ - --network relayer-net \ - redis:latest -``` - -`--network relayer-net` attaches Redis to the network you created in step 1. - -> Note: Make sure to create a dedicated network for the relayer and Redis containers to communicate. You can create a network using the following command `docker network create relayer-net`. - -## Configure a plugin - -In order to create and run plugins please follow the [Plugins README](./plugins/README.md) file instructions. - -## Running the relayer locally - -Install dependencies: - -```sh -cargo build +```env +API_KEY= +WEBHOOK_SIGNING_KEY= +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +STORAGE_ENCRYPTION_KEY= ``` -Run relayer: - -```sh -cargo run -``` - -## Test the Relayer - -The service is available at `http://localhost:8080/api/v1` +Generate the encryption key with one of: ```bash -curl -X GET http://localhost:8080/api/v1/relayers \ - -H "Content-Type: application/json" \ - -H "AUTHORIZATION: Bearer YOUR_API_KEY" -``` - -### Running services with docker compose - -If you use `docker-compose` over `docker compose` please read [Compose V1 vs Compose V2](#compose-v1-vs-compose-v2) section. - -Based on your `.env` file, docker compose may or may not start the metrics server ( within relayer app container), prometheus and grafana. - -> Note: If you want to start the metrics server, prometheus and grafana, make sure to set `METRICS_ENABLED=true` in your `.env` file. - -If you want to start the services using [make](./Makefile.toml) target, you can use the following command to start the services: - -```sh -cargo make docker-compose-up -``` - -> Note: By default docker compose command uses Dockerfile.development to build the image. If you want to use Dockerfile.production, you can set: `DOCKERFILE=Dockerfile.production` before running `cargo make docker-compose-up`. - -We have a [make](./Makefile.toml) target to start the services with docker compose with metrics profile based on your `.env` file. For metrics server you will need to make sure `METRICS_ENABLED=true` is set in your `.env` file. If you want to start the services directly using docker compose, you can use the following command: - -```sh -# without metrics profile ( METRICS_ENABLED=false by default ) -# will only start the relayer app container and redis container -docker compose up -d -# or with metrics profile ( METRICS_ENABLED=true in .env file ) -# docker compose --profile metrics up -d -``` - -Make sure the containers are running without any restarts/issues: - -```sh -docker ps -a -``` - -To stop the services, run the following command: - -```sh -cargo make docker-compose-down +openssl rand -base64 32 # or -# using docker compose without make target -# without metrics profile -# docker compose down -# or with metrics profile -# docker compose --profile metrics down +cargo run --example generate_encryption_key ``` -To check the logs of the services/containers, run the following command: +Recommended for production reliability: -```sh -docker compose logs -f +```env +DISTRIBUTED_MODE=true ``` -## Compose V1 vs Compose V2 - -- If you use `docker-compose` command, it will use Compose V1 by default which is deprecated. We recommend using `docker compose` command. -- You can read more about the differences between Compose V1 and Compose V2 [here](https://docs.docker.com/compose/intro/history/). -- You can also check out the issue [here](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/64). - -## Documentation - -- All the documentation is under `docs/` directory. - -- You can directly make changes to the specific files and raise a PR on this repo as well as on [docs](https://github.com/OpenZeppelin/docs) repo for the content that is modified. +### 3. Deploy and validate -- To generate technical rust documentation locally, run the following command - - ```sh - cargo make rust-docs - ``` - -- Rust docs will be generated in `docs/build/site/openzeppelin_relayer/` directory. - -## Observability - -- Currently we support logs and metrics ( uses prometheus and grafana) for the relayer server. - -### Logs - -- For logs, our app defaults to writing logs to stdout/console. You can also configure it to write logs to a file path by setting `LOG_MODE` to `file`. See [docker compose file](./docker-compose.yaml) for more details. - -### Metrics - -- Metrics server is started on port `8081` by default, which collects the metrics from the relayer server. - - - Exposes list of metrics on the `/metrics` endpoint. - - > Note: By default, we don't map this port to the host machine. If you want to access the metrics server from the host machine, you can update the `docker-compose.yaml` file. - - - Exposes `/debug/metrics/scrape` endpoint for prometheus to scrape metrics. - -- To view prometheus metrics in a UI, you can use `http://localhost:9090` on your browser. - -- To view grafana dashboard, you can use `http://localhost:3000` on your browser. - -## Contributing - -We welcome contributions from the community! Here's how you can get involved: - -1. Fork the repository -2. Create your feature branch -3. Commit your changes -4. Push to the branch -5. Create a Pull Request - -If you are looking for a good place to start, find a good first issue [here](https://github.com/openzeppelin/openzeppelin-relayer/issues?q=is%3Aissue%20is%3Aopen%20label%3Agood-first-issue). - -You can open an issue for a [bug report](https://github.com/openzeppelin/openzeppelin-relayer/issues/new?assignees=&labels=T-bug%2CS-needs-triage&projects=&template=bug.yml), [feature request](https://github.com/openzeppelin/openzeppelin-relayer/issues/new?assignees=&labels=T-feature%2CS-needs-triage&projects=&template=feature.yml), or [documentation request](https://github.com/openzeppelin/openzeppelin-relayer/issues/new?assignees=&labels=T-documentation%2CS-needs-triage&projects=&template=docs.yml). - -You can find more details in our [Contributing](CONTRIBUTING.md) guide. - -Please read our [Code of Conduct](CODE_OF_CONDUCT.md) and check the [Security Policy](SECURITY.md) for reporting vulnerabilities. - -## License - -This project is licensed under the GNU Affero General Public License v3.0 - see the [LICENSE](LICENSE) file for details. - -## Security - -For security concerns, please refer to our [Security Policy](SECURITY.md). - -### Custom RPC URL Security - -The relayer includes built-in protection against Server-Side Request Forgery (SSRF) attacks when using custom RPC URLs. You can configure the following security features via environment variables: - -- **`RPC_ALLOWED_HOSTS`**: Comma-separated list of allowed RPC hostnames/IPs. If non-empty, only URLs with these hosts are permitted. - - - Example: `RPC_ALLOWED_HOSTS=eth-mainnet.g.alchemy.com,mainnet.infura.io` - -- **`RPC_BLOCK_PRIVATE_IPS`**: Block private IP addresses (RFC 1918, loopback, link-local). Set to `true` to prevent RPC URLs from targeting private networks. - - Example: `RPC_BLOCK_PRIVATE_IPS=true` - - Default: `false` (for backwards compatibility) - -**Note:** Cloud metadata endpoints (`169.254.169.254`, `fd00:ec2::254`) are **always blocked** to prevent credential theft, regardless of configuration. - -**Recommended Production Configuration:** +1. Trigger a Railway deployment. +2. Confirm logs do not contain `REDIS_URL must be set` or Redis connection errors. +3. Verify health endpoint: ```bash -RPC_BLOCK_PRIVATE_IPS=true -RPC_ALLOWED_HOSTS=eth-mainnet.g.alchemy.com,mainnet.infura.io,eth.llamarpc.com +curl -s https:///api/v1/health ``` -See [`.env.example`](.env.example) for more configuration examples. - -## Get Help +4. Verify relayers are loaded from `config.production.json`: -If you have any questions, first see if the answer to your question can be found in the [User Documentation](https://docs.openzeppelin.com/relayer/). - -If the answer is not there: - -- Join the [Telegram](https://t.me/openzeppelin_tg/2) to get help, or -- Open an issue with [the bug](https://github.com/openzeppelin/openzeppelin-relayer/issues/new?assignees=&labels=T-bug%2CS-needs-triage&projects=&template=bug.yml) +```bash +curl -s https:///api/v1/relayers \ + -H "Authorization: Bearer " | jq +``` -We encourage you to reach out with any questions or feedback. +Expected: both `mainnet` and `base` relayers are present. -## Maintainers +### 4. Backups and restore runbook -See [CODEOWNERS](CODEOWNERS) file for the list of project maintainers. +1. Enable Redis backups/snapshots in Railway. +2. During incident recovery, restore the latest healthy snapshot on Redis. +3. Redeploy the relayer service to re-establish clean connections. +4. Re-run the health and relayer validation calls above. diff --git a/config/config.development.json b/config/config.development.json new file mode 100644 index 000000000..a5ba7ec15 --- /dev/null +++ b/config/config.development.json @@ -0,0 +1,39 @@ +{ + "relayers": [ + { + "id": "hoodi-example", + "name": "Hoodi Relayer", + "network": "hoodi", + "paused": false, + "notification_id": "notification-example", + "signer_id": "relayer-kms-signer-dev", + "network_type": "evm", + "policies": { + "min_balance": 0 + } + } + ], + "notifications": [ + { + "id": "notification-example", + "type": "webhook", + "url": "https://example.com/webhook", + "signing_key": { + "type": "env", + "value": "WEBHOOK_SIGNING_KEY" + } + } + ], + "signers": [ + { + "id": "relayer-kms-signer-dev", + "type": "aws_kms", + "config": { + "region": "us-east-1", + "key_id": "arn:aws:kms:us-east-1:412463071885:key/mrk-248128595151466bb7f7b9a56501a98f" + } + } + ], + "networks": "./config/networks", + "plugins": [] +} diff --git a/config/config.production.json b/config/config.production.json new file mode 100644 index 000000000..70fe45b7f --- /dev/null +++ b/config/config.production.json @@ -0,0 +1,51 @@ +{ + "relayers": [ + { + "id": "mainnet-relayer", + "name": "Mainnet Relayer", + "network": "mainnet", + "paused": false, + "notification_id": "notification-example", + "signer_id": "relayer-kms-signer", + "network_type": "evm", + "policies": { + "min_balance": 0 + } + }, + { + "id": "base-relayer", + "name": "Base Relayer", + "network": "base", + "paused": false, + "notification_id": "notification-example", + "signer_id": "relayer-kms-signer", + "network_type": "evm", + "policies": { + "min_balance": 0 + } + } + ], + "notifications": [ + { + "id": "notification-example", + "type": "webhook", + "url": "https://example.com/webhook", + "signing_key": { + "type": "env", + "value": "WEBHOOK_SIGNING_KEY" + } + } + ], + "signers": [ + { + "id": "relayer-kms-signer", + "type": "aws_kms", + "config": { + "region": "us-east-1", + "key_id": "arn:aws:kms:us-east-1:412463071885:key/mrk-248128595151466bb7f7b9a56501a98f" + } + } + ], + "networks": "./config/networks", + "plugins": [] +} diff --git a/config/networks/ethereum.json b/config/networks/ethereum.json index ac3e286b8..8b39786ae 100644 --- a/config/networks/ethereum.json +++ b/config/networks/ethereum.json @@ -60,6 +60,22 @@ "tags": [ "deprecated" ] + }, + { + "from": "mainnet", + "type": "evm", + "network": "hoodi", + "chain_id": 560048, + "explorer_urls": [ + "https://api-hoodi.etherscan.io/api", + "https://hoodi.etherscan.io" + ], + "is_testnet": true, + "required_confirmations": 6, + "rpc_urls": [ + "https://rpc.hoodi.ethpandaops.io" + ], + "tags": [] } ] } diff --git a/docker-compose.yaml b/docker-compose.yaml index a1ce9daec..2581ba659 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -10,8 +10,9 @@ services: - 8080:8080/tcp secrets: - api_key - - keystore_passphrase - webhook_signing_key + - aws_access_key_id + - aws_secret_access_key environment: APP_PORT: ${APP_PORT:-8080} METRICS_PORT: ${METRICS_PORT:-8081} @@ -20,7 +21,8 @@ services: RATE_LIMIT_BURST: ${RATE_LIMIT_BURST:-30} METRICS_ENABLED: ${METRICS_ENABLED:-false} WEBHOOK_SIGNING_KEY: ${WEBHOOK_SIGNING_KEY:-/dev/null} - KEYSTORE_PASSPHRASE: ${KEYSTORE_PASSPHRASE:-/dev/null} + AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} + AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} API_KEY: ${API_KEY} # Options: trace, debug, info, warn, error # Default: info @@ -68,8 +70,6 @@ services: - REDIS_ADDR=redis://redis:6379 security_opt: - no-new-privileges - profiles: - - metrics depends_on: - redis networks: @@ -88,24 +88,6 @@ services: volumes: - ./cmd/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml restart: on-failure:5 - profiles: - - metrics - grafana: - image: grafana/grafana:11.5.1 - security_opt: - - no-new-privileges - ports: - - 3000:3000/tcp - networks: - - metrics-network - - relayer-network - volumes: - - ./cmd/prometheus/grafana.ini:/etc/grafana/grafana.ini - - ./cmd/prometheus/datasources:/etc/grafana/provisioning/datasources - - ./cmd/prometheus/dashboards:/etc/grafana/provisioning/dashboards - restart: on-failure:5 - profiles: - - metrics networks: metrics-network: internal: true @@ -119,5 +101,7 @@ secrets: environment: API_KEY webhook_signing_key: environment: WEBHOOK_SIGNING_KEY - keystore_passphrase: - environment: KEYSTORE_PASSPHRASE + aws_access_key_id: + environment: AWS_ACCESS_KEY_ID + aws_secret_access_key: + environment: AWS_SECRET_ACCESS_KEY diff --git a/docs/README.md b/docs/README.md index ed97eb2b4..5f3648441 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,5 +1,10 @@ # Generate Documentation +## Deployment note + +- The Railway production service is configured with GitHub auto-deploy on the `production` branch. +- A push to `production` automatically triggers Docker build and deployment. + - To generate rust documentation locally, run the following command - In separate terminal from root of the repo run: