From fc5c4160e6e027d2ac11e9cc8538c16d0a243818 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 13:22:23 +0200 Subject: [PATCH 01/39] docs(validators): drop Asimov from node sync and setup guide The node v0.6 release removes configs/node/asimov.yaml.example and moves Bradbury to the phase 2 deployment, whose example now carries both a rollup and a consensus block. - Stop syncing asimov.yaml (sparse checkout, matrix, params, summary) and remove content/validators/asimov.yaml. A missing source makes sync.sh exit 1, so the matrix entry would fail on every run. - Drop the Asimov block from update-config-in-setup-guide.js. - Setup guide: remove the Asimov network block, point the staking wizard at testnet-bradbury, say to replace whole rollup/consensus sections, and explain consensus.manifestpath (required since v0.6). Co-Authored-By: Claude Opus 5.5 --- .github/workflows/sync-docs-from-node.yml | 12 ++---------- content/validators/asimov.yaml | 4 ---- pages/validators/setup-guide.mdx | 16 ++++------------ scripts/update-config-in-setup-guide.js | 21 ++------------------- 4 files changed, 8 insertions(+), 45 deletions(-) delete mode 100644 content/validators/asimov.yaml diff --git a/.github/workflows/sync-docs-from-node.yml b/.github/workflows/sync-docs-from-node.yml index 5e00b856..747358d9 100644 --- a/.github/workflows/sync-docs-from-node.yml +++ b/.github/workflows/sync-docs-from-node.yml @@ -106,7 +106,6 @@ jobs: docs/api/rpc docs/api/ops configs/node/config.yaml.example - configs/node/asimov.yaml.example configs/node/bradbury.yaml.example release/docker-compose.yaml release/alloy-config.river @@ -129,7 +128,7 @@ jobs: needs: prepare strategy: matrix: - sync_type: [changelog, config, config_asimov, config_bradbury, docker_compose, docker_compose_monitoring, alloy_config, greybox_setup, api_gen, api_debug, api_ops] + sync_type: [changelog, config, config_bradbury, docker_compose, docker_compose_monitoring, alloy_config, greybox_setup, api_gen, api_debug, api_ops] fail-fast: false steps: - name: Checkout documentation repository @@ -165,12 +164,6 @@ jobs: echo "target_path=content/validators/config.yaml" >> $GITHUB_OUTPUT echo "filter_pattern=.*" >> $GITHUB_OUTPUT ;; - "config_asimov") - echo "title=Config File (Asimov)" >> $GITHUB_OUTPUT - echo "source_path=source-repo/configs/node/asimov.yaml.example" >> $GITHUB_OUTPUT - echo "target_path=content/validators/asimov.yaml" >> $GITHUB_OUTPUT - echo "filter_pattern=.*" >> $GITHUB_OUTPUT - ;; "config_bradbury") echo "title=Config File (Bradbury)" >> $GITHUB_OUTPUT echo "source_path=source-repo/configs/node/bradbury.yaml.example" >> $GITHUB_OUTPUT @@ -536,12 +529,11 @@ jobs: echo "" >> $GITHUB_STEP_SUMMARY # Process each sync type report - for sync_type in changelog config config_asimov config_bradbury docker_compose docker_compose_monitoring alloy_config greybox_setup api_gen api_debug api_ops; do + for sync_type in changelog config config_bradbury docker_compose docker_compose_monitoring alloy_config greybox_setup api_gen api_debug api_ops; do # Get proper title case "$sync_type" in "changelog") title="📝 Changelog Sync" ;; "config") title="⚙️ Config File Sync" ;; - "config_asimov") title="⚙️ Config File Sync (Asimov)" ;; "config_bradbury") title="⚙️ Config File Sync (Bradbury)" ;; "docker_compose") title="🐳 Docker Compose Sync" ;; "docker_compose_monitoring") title="🐳 Docker Compose Sync (Monitoring)" ;; diff --git a/content/validators/asimov.yaml b/content/validators/asimov.yaml deleted file mode 100644 index 6aa49ab3..00000000 --- a/content/validators/asimov.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Asimov Phase 5 network consensus configuration -consensus: - consensusaddress: "0xe66B434bc83805f380509642429eC8e43AE9874a" - genesis: 17326 diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index f1204d63..a902ab85 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -131,7 +131,7 @@ genlayer staking wizard The wizard guides you through: 1. **Account setup** - Create or select your owner account -2. **Network selection** - Choose testnet-asimov +2. **Network selection** - Choose testnet-bradbury 3. **Balance verification** - Confirms you have at least 42,000 GEN 4. **Operator setup** - Create and export an operator keystore for your validator server 5. **Stake amount** - Enter how much GEN to stake (minimum 42,000) @@ -263,7 +263,7 @@ This is the main configuration file of your node. Without it, your node won't st The file needs to be located at `configs/node/config.yaml` -You can use the following example configuration. **Note:** For most users, you will only need to modify the `genlayerchainrpcurl`, `genlayerchainwebsocketurl`, and the `consensus` section for your target network (see [Network-Specific Consensus Configuration](#network-specific-consensus-configuration) below). +You can use the following example configuration. **Note:** For most users, you will only need to replace the `rollup` and `consensus` sections with the values for your target network (see [Network-Specific Consensus Configuration](#network-specific-consensus-configuration) below). ```yaml copy # rollup configuration @@ -455,17 +455,9 @@ metrics: #### Network-Specific Consensus Configuration -Set the `consensus` section in your `config.yaml` according to the network you want to join: +Replace the matching sections of your `config.yaml` with the block for the network you want to join. Replace each top-level section as a whole rather than adding a second `rollup:` or `consensus:` key. -##### Testnet Asimov - -```yaml copy -# Asimov Phase 5 network consensus configuration -consensus: - consensusaddress: "0xe66B434bc83805f380509642429eC8e43AE9874a" - genesis: 17326 - -``` +Since v0.6, the node refuses to start unless `consensus.manifestpath` points at the trusted `protocol-manifest.json` for the deployment. The release ships the Bradbury manifest at `configs/node/protocol-manifest.json`, and relative paths resolve against the node root. At startup, the node also refuses a manifest whose registry does not match `consensus.consensusaddress`, or whose chain does not match `consensus.chainid`. ##### Testnet Bradbury diff --git a/scripts/update-config-in-setup-guide.js b/scripts/update-config-in-setup-guide.js index c10e458e..db6abf30 100755 --- a/scripts/update-config-in-setup-guide.js +++ b/scripts/update-config-in-setup-guide.js @@ -5,11 +5,10 @@ const path = require('path'); /** * Update the setup guide with the latest config from content/validators/config.yaml - * and network-specific consensus configs from content/validators/asimov.yaml and bradbury.yaml + * and the network-specific config from content/validators/bradbury.yaml */ function updateConfigInSetupGuide() { const configPath = path.join(process.cwd(), 'content/validators/config.yaml'); - const asimovPath = path.join(process.cwd(), 'content/validators/asimov.yaml'); const bradburyPath = path.join(process.cwd(), 'content/validators/bradbury.yaml'); const setupGuidePath = path.join(process.cwd(), 'pages/validators/setup-guide.mdx'); @@ -36,23 +35,7 @@ function updateConfigInSetupGuide() { } } - // Update Asimov consensus config - if (fs.existsSync(asimovPath)) { - const asimovContent = fs.readFileSync(asimovPath, 'utf8'); - const asimovPattern = /(##### Testnet Asimov\s*```yaml[^\n]*\n)([\s\S]*?)(\n```)/; - - if (asimovPattern.test(setupGuideContent)) { - setupGuideContent = setupGuideContent.replace( - asimovPattern, - `$1${asimovContent}$3` - ); - console.log(`Updated Asimov config block at ${new Date().toISOString()}`); - } else { - console.error('Could not find Asimov config pattern in setup guide'); - } - } - - // Update Bradbury consensus config + // Update Bradbury network config (rollup + consensus) if (fs.existsSync(bradburyPath)) { const bradburyContent = fs.readFileSync(bradburyPath, 'utf8'); const bradburyPattern = /(##### Testnet Bradbury\s*```yaml[^\n]*\n)([\s\S]*?)(\n```)/; From 9dd62fbd734bddf1a6067ae72877fe70a383c36e Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 13:32:53 +0200 Subject: [PATCH 02/39] docs(validators): use shipped Bradbury manifest path and network label The v0.6 release ships configs/node/bradbury.protocol-manifest.json, and bradbury.yaml.example points manifestpath at it. The node names the phase 2 network bradbury-phase2; use it as the monitoring NETWORK_NAME. Co-Authored-By: Claude Opus 5.5 --- pages/validators/monitoring.mdx | 2 +- pages/validators/setup-guide.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/validators/monitoring.mdx b/pages/validators/monitoring.mdx index 1b5df4db..393c3234 100644 --- a/pages/validators/monitoring.mdx +++ b/pages/validators/monitoring.mdx @@ -120,7 +120,7 @@ CENTRAL_LOKI_PASSWORD=your-logs-password # Node identification NODE_ID=validator-001 VALIDATOR_NAME=MyValidator -NETWORK_NAME=asimov-phase5 +NETWORK_NAME=bradbury-phase2 # Usually defaults are fine NODE_METRICS_ENDPOINT=host.docker.internal:9153 diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index a902ab85..8255a839 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -457,7 +457,7 @@ metrics: Replace the matching sections of your `config.yaml` with the block for the network you want to join. Replace each top-level section as a whole rather than adding a second `rollup:` or `consensus:` key. -Since v0.6, the node refuses to start unless `consensus.manifestpath` points at the trusted `protocol-manifest.json` for the deployment. The release ships the Bradbury manifest at `configs/node/protocol-manifest.json`, and relative paths resolve against the node root. At startup, the node also refuses a manifest whose registry does not match `consensus.consensusaddress`, or whose chain does not match `consensus.chainid`. +Since v0.6, the node refuses to start unless `consensus.manifestpath` points at the trusted `protocol-manifest.json` for the deployment. The release ships the Bradbury manifest at `configs/node/bradbury.protocol-manifest.json`, and the Bradbury block below already points `manifestpath` at it. Relative paths resolve against the node root. At startup, the node also refuses a manifest whose registry does not match `consensus.consensusaddress`, or whose chain does not match `consensus.chainid`. ##### Testnet Bradbury From 5286ffd5f833734fa604e6a350f3909e7df2bd26 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 10:18:57 +0200 Subject: [PATCH 03/39] docs(validators): require Python 3.10+ on PATH for GenVM setup The GenVM post-install (setup.py) uses Python 3.10 syntax and creates a venv with pip by default, and the node install calls `python3` from PATH. State the minimum version and the PATH requirement in the setup guide and system requirements. Co-Authored-By: Claude Opus 5.5 --- pages/validators/setup-guide.mdx | 4 +++- pages/validators/system-requirements.mdx | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index 8255a839..74ce77a6 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -21,7 +21,7 @@ For a deeper understanding of how staking works in GenLayer, see the [Staking do | **Disk** | 128 GB SSD/NVMe | | **Network** | 100 Mbps | | **OS** | 64-bit Linux | -| **Software** | Docker, Python 3 (with pip and venv) | +| **Software** | Docker, Python 3.10+ (`python3` on `PATH`, with pip and venv) | GPU not required unless running LLMs locally. See [detailed requirements](/validators/system-requirements) for more info. @@ -243,6 +243,8 @@ One full node can be shared between multiple validators. The optimal validator-t 5. Run Genvm setup + This step needs Python 3.10 or newer, available as `python3` on your `PATH`, with `pip` and `venv` (on Debian/Ubuntu: `python3-pip` and `python3-venv`). Check with `python3 --version`. + ```sh copy python3 ./third_party/genvm/bin/setup.py ``` diff --git a/pages/validators/system-requirements.mdx b/pages/validators/system-requirements.mdx index c8f56def..ee3496d4 100644 --- a/pages/validators/system-requirements.mdx +++ b/pages/validators/system-requirements.mdx @@ -48,4 +48,4 @@ System Requirements for a GenLayer validator node are the **initial** recommende - Operating System - 64-bit Linux (Ubuntu, Debian, CentOS, etc.) - `docker` - for running the WebDriver container -- `python3`, `python3-pip` and `python3-venv` for GenVM setup +- Python 3.10 or newer, available as `python3` on your `PATH`, plus `python3-pip` and `python3-venv` - for GenVM setup From bbe0fb6a8e91ccf9c00fc9f2d440da35b6cded70 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 13:37:18 +0200 Subject: [PATCH 04/39] docs(validators): mark pip/venv package names as Debian/Ubuntu The OS list includes CentOS, where the package names differ. Co-Authored-By: Claude Opus 5.5 --- pages/validators/system-requirements.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/validators/system-requirements.mdx b/pages/validators/system-requirements.mdx index ee3496d4..e774a019 100644 --- a/pages/validators/system-requirements.mdx +++ b/pages/validators/system-requirements.mdx @@ -48,4 +48,4 @@ System Requirements for a GenLayer validator node are the **initial** recommende - Operating System - 64-bit Linux (Ubuntu, Debian, CentOS, etc.) - `docker` - for running the WebDriver container -- Python 3.10 or newer, available as `python3` on your `PATH`, plus `python3-pip` and `python3-venv` - for GenVM setup +- Python 3.10 or newer, available as `python3` on your `PATH`, with `pip` and `venv` (on Debian/Ubuntu: `python3-pip` and `python3-venv`) - for GenVM setup From f31185df2883cf5d3d928ad98a5373e1510bc630 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 13:39:56 +0200 Subject: [PATCH 05/39] docs: remove Testnet Asimov everywhere Asimov is retired with the v0.6 release. Remove it from the networks page and comparison table, network configuration, faucet table, SDK and transaction-kit examples (now testnetBradbury / testnet-bradbury), the genlayer-test and CLI references, node ops snapshot examples, the monitoring prerequisites, and the retrofit prompt. The setup guide's main config example now carries the v0.6 consensus template (empty deployment fields) instead of the Asimov address. Co-Authored-By: Claude Opus 5.5 --- content/validators/config.yaml | 9 ++++-- pages/api-references/genlayer-cli.mdx | 4 +-- .../genlayer-cli/accounts/account/send.mdx | 2 +- .../staking/staking/active-validators.mdx | 2 +- .../staking/staking/banned-validators.mdx | 2 +- .../staking/staking/delegation-info.mdx | 2 +- .../staking/staking/delegator-claim.mdx | 2 +- .../staking/staking/delegator-exit.mdx | 2 +- .../staking/staking/delegator-join.mdx | 2 +- .../staking/staking/epoch-info.mdx | 2 +- .../staking/staking/prime-all.mdx | 2 +- .../staking/quarantined-validators.mdx | 2 +- .../staking/staking/set-identity.mdx | 2 +- .../staking/staking/set-operator.mdx | 2 +- .../staking/staking/validator-claim.mdx | 2 +- .../staking/staking/validator-deposit.mdx | 2 +- .../staking/staking/validator-exit.mdx | 2 +- .../staking/staking/validator-history.mdx | 2 +- .../staking/staking/validator-info.mdx | 2 +- .../staking/staking/validator-join.mdx | 2 +- .../staking/staking/validator-prime.mdx | 2 +- .../staking/staking/validators.mdx | 2 +- pages/api-references/genlayer-js.md | 2 +- pages/api-references/genlayer-node.mdx | 2 +- .../genlayer-node/ops/snapshot.mdx | 2 +- pages/api-references/genlayer-test.md | 6 ++-- .../transaction-kit-integration.mdx | 8 ++--- .../deploying/network-configuration.mdx | 5 ++- .../features/value-transfers.mdx | 5 ++- pages/developers/networks.mdx | 32 ++++--------------- pages/validators/monitoring.mdx | 2 +- pages/validators/setup-guide.mdx | 9 ++++-- scripts/retrofit/retrofit.js | 2 +- 33 files changed, 57 insertions(+), 71 deletions(-) diff --git a/content/validators/config.yaml b/content/validators/config.yaml index 48723642..405ffcf3 100644 --- a/content/validators/config.yaml +++ b/content/validators/config.yaml @@ -5,10 +5,13 @@ rollup: provider: "TODO: Set your GenLayer Chain ZKSync provider" # Rollup operator name; surfaces as the "provider" label on RPC-driven metrics. Set explicitly per deployment. # consensus contracts configuration consensus: - # Testnet - Phase 5 - consensusaddress: "0xe66B434bc83805f380509642429eC8e43AE9874a" # AddressManager Smart Contract Address - genesis: 17326 # (Optional) Genesis block number for this consensus deployment. If not provided, it will be auto-detected by searching for the first log from the ConsensusMain contract. + # Clarke Dev template: the deployment-specific fields are intentionally empty. + consensusaddress: "" # AddressManager contract address for this deployment. + manifestpath: "" # Trusted producer protocol-manifest.json for this deployment. The deploy places it at configs/node/protocol-manifest.json; relative paths resolve against the node root. A mismatch with consensusaddress is refused at startup. + chainid: "" # DECIMAL chain id this deployment was made on (not hex, no leading zeros). Held independently of the manifest so a manifest describing another deployment is caught. + # genesis: 17326 # (Optional) Genesis block number for this consensus deployment. If not provided, it will be auto-detected by searching for the first log from the ConsensusMain contract. # chain_submit_buffer: "30s" # (Optional) Slack reserved at the end of each consensus step to submit the resulting on-chain tx (commit vote, reveal vote, proposed receipt) before the slot closes. Tune to underlying settlement chain latency. Default 30s. + # admissiontimeout: "15s" # (Optional) Bound on the startup deployment admission check. Never unbounded. Default 15s. # data directory datadir: "./data/node" # logging configuration diff --git a/pages/api-references/genlayer-cli.mdx b/pages/api-references/genlayer-cli.mdx index 47b84f48..c959cda6 100644 --- a/pages/api-references/genlayer-cli.mdx +++ b/pages/api-references/genlayer-cli.mdx @@ -358,7 +358,7 @@ EXAMPLES: #### Staking Operations -Manage staking for validators and delegators on testnet-bradbury (or testnet-asimov). Staking is not available on localnet/studio. +Manage staking for validators and delegators on testnet-bradbury. Staking is not available on localnet/studio. ```bash USAGE: @@ -384,7 +384,7 @@ COMMANDS: banned-validators List all banned validators COMMON OPTIONS (all commands): - --network Network to use (localnet, testnet-bradbury, testnet-asimov) + --network Network to use (localnet, testnet-bradbury) --rpc RPC URL override --staking-address
Staking contract address override diff --git a/pages/api-references/genlayer-cli/accounts/account/send.mdx b/pages/api-references/genlayer-cli/accounts/account/send.mdx index 86477235..15d5a8a2 100644 --- a/pages/api-references/genlayer-cli/accounts/account/send.mdx +++ b/pages/api-references/genlayer-cli/accounts/account/send.mdx @@ -18,7 +18,7 @@ Send GEN to an address | Short | Long | Description | Required | Default | | --- | --- | --- | :---: | --- | | | --rpc <rpcUrl> | RPC URL for the network | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | Network to use (localnet, testnet-bradbury) | No | | | | --account <name> | Account to send from | No | | | | --password <password> | Password to unlock account (skips interactive prompt) | No | | | -h | --help | display help for command | No | | diff --git a/pages/api-references/genlayer-cli/staking/staking/active-validators.mdx b/pages/api-references/genlayer-cli/staking/staking/active-validators.mdx index 89a90a41..1cc89f0b 100644 --- a/pages/api-references/genlayer-cli/staking/staking/active-validators.mdx +++ b/pages/api-references/genlayer-cli/staking/staking/active-validators.mdx @@ -12,7 +12,7 @@ List all active validators | Short | Long | Description | Required | Default | | --- | --- | --- | :---: | --- | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | Network to use (localnet, testnet-bradbury) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/pages/api-references/genlayer-cli/staking/staking/banned-validators.mdx b/pages/api-references/genlayer-cli/staking/staking/banned-validators.mdx index 83b750aa..0fe17b9a 100644 --- a/pages/api-references/genlayer-cli/staking/staking/banned-validators.mdx +++ b/pages/api-references/genlayer-cli/staking/staking/banned-validators.mdx @@ -12,7 +12,7 @@ List all banned validators | Short | Long | Description | Required | Default | | --- | --- | --- | :---: | --- | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | Network to use (localnet, testnet-bradbury) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/pages/api-references/genlayer-cli/staking/staking/delegation-info.mdx b/pages/api-references/genlayer-cli/staking/staking/delegation-info.mdx index b75fe6ce..8db01d2b 100644 --- a/pages/api-references/genlayer-cli/staking/staking/delegation-info.mdx +++ b/pages/api-references/genlayer-cli/staking/staking/delegation-info.mdx @@ -19,7 +19,7 @@ Get delegation info for a delegator with a validator | | --validator <address> | Validator address (deprecated, use positional arg) | No | | | | --delegator <address> | Delegator address (defaults to signer) | No | | | | --account <name> | Account to use (for default delegator address) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | Network to use (localnet, testnet-bradbury) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/pages/api-references/genlayer-cli/staking/staking/delegator-claim.mdx b/pages/api-references/genlayer-cli/staking/staking/delegator-claim.mdx index 325eb379..14948868 100644 --- a/pages/api-references/genlayer-cli/staking/staking/delegator-claim.mdx +++ b/pages/api-references/genlayer-cli/staking/staking/delegator-claim.mdx @@ -20,7 +20,7 @@ Claim delegator withdrawals after unbonding period | | --delegator <address> | Delegator address (defaults to signer) | No | | | | --account <name> | Account to use | No | | | | --password <password> | Password to unlock account (skips interactive prompt) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | Network to use (localnet, testnet-bradbury) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/pages/api-references/genlayer-cli/staking/staking/delegator-exit.mdx b/pages/api-references/genlayer-cli/staking/staking/delegator-exit.mdx index 1e8b5ad0..387fbddc 100644 --- a/pages/api-references/genlayer-cli/staking/staking/delegator-exit.mdx +++ b/pages/api-references/genlayer-cli/staking/staking/delegator-exit.mdx @@ -20,7 +20,7 @@ Exit as a delegator by withdrawing shares from a validator | | --shares <shares> | Number of shares to withdraw | No | | | | --account <name> | Account to use | No | | | | --password <password> | Password to unlock account (skips interactive prompt) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | Network to use (localnet, testnet-bradbury) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/pages/api-references/genlayer-cli/staking/staking/delegator-join.mdx b/pages/api-references/genlayer-cli/staking/staking/delegator-join.mdx index 3219e752..a24b0b1a 100644 --- a/pages/api-references/genlayer-cli/staking/staking/delegator-join.mdx +++ b/pages/api-references/genlayer-cli/staking/staking/delegator-join.mdx @@ -20,7 +20,7 @@ Join as a delegator by staking with a validator | | --amount <amount> | Amount to stake (in wei or with 'eth'/'gen' suffix) | No | | | | --account <name> | Account to use | No | | | | --password <password> | Password to unlock account (skips interactive prompt) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | Network to use (localnet, testnet-bradbury) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/pages/api-references/genlayer-cli/staking/staking/epoch-info.mdx b/pages/api-references/genlayer-cli/staking/staking/epoch-info.mdx index 7f4cf570..ce8a5014 100644 --- a/pages/api-references/genlayer-cli/staking/staking/epoch-info.mdx +++ b/pages/api-references/genlayer-cli/staking/staking/epoch-info.mdx @@ -13,7 +13,7 @@ Get current epoch and staking parameters | Short | Long | Description | Required | Default | | --- | --- | --- | :---: | --- | | | --epoch <number> | Show data for specific epoch (current or previous only) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | Network to use (localnet, testnet-bradbury) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/pages/api-references/genlayer-cli/staking/staking/prime-all.mdx b/pages/api-references/genlayer-cli/staking/staking/prime-all.mdx index 5d69dbea..cade25d8 100644 --- a/pages/api-references/genlayer-cli/staking/staking/prime-all.mdx +++ b/pages/api-references/genlayer-cli/staking/staking/prime-all.mdx @@ -14,7 +14,7 @@ Prime all validators that need priming | --- | --- | --- | :---: | --- | | | --account <name> | Account to use (pays gas) | No | | | | --password <password> | Password to unlock account (skips interactive prompt) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | Network to use (localnet, testnet-bradbury) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/pages/api-references/genlayer-cli/staking/staking/quarantined-validators.mdx b/pages/api-references/genlayer-cli/staking/staking/quarantined-validators.mdx index c6a55a01..2d213454 100644 --- a/pages/api-references/genlayer-cli/staking/staking/quarantined-validators.mdx +++ b/pages/api-references/genlayer-cli/staking/staking/quarantined-validators.mdx @@ -12,7 +12,7 @@ List all quarantined validators | Short | Long | Description | Required | Default | | --- | --- | --- | :---: | --- | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | Network to use (localnet, testnet-bradbury) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/pages/api-references/genlayer-cli/staking/staking/set-identity.mdx b/pages/api-references/genlayer-cli/staking/staking/set-identity.mdx index 9212e863..8cc1d8f5 100644 --- a/pages/api-references/genlayer-cli/staking/staking/set-identity.mdx +++ b/pages/api-references/genlayer-cli/staking/staking/set-identity.mdx @@ -28,6 +28,6 @@ Set validator identity metadata (moniker, website, socials, etc.) | | --extra-cid <cid> | Extra data as IPFS CID or hex bytes (0x...) | No | | | | --account <name> | Account to use (must be validator operator) | No | | | | --password <password> | Password to unlock account (skips interactive prompt) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | Network to use (localnet, testnet-bradbury) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | -h | --help | display help for command | No | | diff --git a/pages/api-references/genlayer-cli/staking/staking/set-operator.mdx b/pages/api-references/genlayer-cli/staking/staking/set-operator.mdx index 5c5b2e9f..612ae9db 100644 --- a/pages/api-references/genlayer-cli/staking/staking/set-operator.mdx +++ b/pages/api-references/genlayer-cli/staking/staking/set-operator.mdx @@ -21,6 +21,6 @@ Change the operator address for a validator wallet | | --operator <address> | New operator address (deprecated, use positional arg) | No | | | | --account <name> | Account to use (must be validator owner) | No | | | | --password <password> | Password to unlock account (skips interactive prompt) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | Network to use (localnet, testnet-bradbury) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | -h | --help | display help for command | No | | diff --git a/pages/api-references/genlayer-cli/staking/staking/validator-claim.mdx b/pages/api-references/genlayer-cli/staking/staking/validator-claim.mdx index bddb4657..59f23272 100644 --- a/pages/api-references/genlayer-cli/staking/staking/validator-claim.mdx +++ b/pages/api-references/genlayer-cli/staking/staking/validator-claim.mdx @@ -19,6 +19,6 @@ Claim validator withdrawals after unbonding period | | --validator <address> | Validator wallet contract address (deprecated, use positional arg) | No | | | | --account <name> | Account to use | No | | | | --password <password> | Password to unlock account (skips interactive prompt) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | Network to use (localnet, testnet-bradbury) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | -h | --help | display help for command | No | | diff --git a/pages/api-references/genlayer-cli/staking/staking/validator-deposit.mdx b/pages/api-references/genlayer-cli/staking/staking/validator-deposit.mdx index 1f0dd6a6..92f8860c 100644 --- a/pages/api-references/genlayer-cli/staking/staking/validator-deposit.mdx +++ b/pages/api-references/genlayer-cli/staking/staking/validator-deposit.mdx @@ -20,6 +20,6 @@ Make an additional deposit to a validator wallet | | --amount <amount> | Amount to deposit (in wei or with 'eth'/'gen' suffix) | No | | | | --account <name> | Account to use (must be validator owner) | No | | | | --password <password> | Password to unlock account (skips interactive prompt) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | Network to use (localnet, testnet-bradbury) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | -h | --help | display help for command | No | | diff --git a/pages/api-references/genlayer-cli/staking/staking/validator-exit.mdx b/pages/api-references/genlayer-cli/staking/staking/validator-exit.mdx index 3c8dd58c..089b479f 100644 --- a/pages/api-references/genlayer-cli/staking/staking/validator-exit.mdx +++ b/pages/api-references/genlayer-cli/staking/staking/validator-exit.mdx @@ -20,6 +20,6 @@ Exit as a validator by withdrawing shares | | --shares <shares> | Number of shares to withdraw | No | | | | --account <name> | Account to use (must be validator owner) | No | | | | --password <password> | Password to unlock account (skips interactive prompt) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | Network to use (localnet, testnet-bradbury) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | -h | --help | display help for command | No | | diff --git a/pages/api-references/genlayer-cli/staking/staking/validator-history.mdx b/pages/api-references/genlayer-cli/staking/staking/validator-history.mdx index e2f29f85..6ed6f709 100644 --- a/pages/api-references/genlayer-cli/staking/staking/validator-history.mdx +++ b/pages/api-references/genlayer-cli/staking/staking/validator-history.mdx @@ -23,7 +23,7 @@ Show slash and reward history for a validator (default: last 10 epochs) | | --all | Fetch complete history from genesis (slow) | No | | | | --limit <count> | Maximum number of events to show | No | `50` | | | --account <name> | Account to use (for default validator address) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | Network to use (localnet, testnet-bradbury) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/pages/api-references/genlayer-cli/staking/staking/validator-info.mdx b/pages/api-references/genlayer-cli/staking/staking/validator-info.mdx index bc4e6fd8..221e55be 100644 --- a/pages/api-references/genlayer-cli/staking/staking/validator-info.mdx +++ b/pages/api-references/genlayer-cli/staking/staking/validator-info.mdx @@ -18,7 +18,7 @@ Get information about a validator | --- | --- | --- | :---: | --- | | | --validator <address> | Validator address (deprecated, use positional arg) | No | | | | --account <name> | Account to use (for default validator address) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | Network to use (localnet, testnet-bradbury) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | | --debug | Show raw unfiltered pending deposits/withdrawals | No | | diff --git a/pages/api-references/genlayer-cli/staking/staking/validator-join.mdx b/pages/api-references/genlayer-cli/staking/staking/validator-join.mdx index 24e9ff3b..8247b806 100644 --- a/pages/api-references/genlayer-cli/staking/staking/validator-join.mdx +++ b/pages/api-references/genlayer-cli/staking/staking/validator-join.mdx @@ -16,7 +16,7 @@ Join as a validator by staking tokens | | --operator <address> | Operator address (defaults to signer) | No | | | | --account <name> | Account to use | No | | | | --password <password> | Password to unlock account (skips interactive prompt) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | Network to use (localnet, testnet-bradbury) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/pages/api-references/genlayer-cli/staking/staking/validator-prime.mdx b/pages/api-references/genlayer-cli/staking/staking/validator-prime.mdx index 33d335a7..58f1d9bb 100644 --- a/pages/api-references/genlayer-cli/staking/staking/validator-prime.mdx +++ b/pages/api-references/genlayer-cli/staking/staking/validator-prime.mdx @@ -19,7 +19,7 @@ Prime a validator to prepare their stake record for the next epoch | | --validator <address> | Validator address to prime (deprecated, use positional arg) | No | | | | --account <name> | Account to use | No | | | | --password <password> | Password to unlock account (skips interactive prompt) | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | Network to use (localnet, testnet-bradbury) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/pages/api-references/genlayer-cli/staking/staking/validators.mdx b/pages/api-references/genlayer-cli/staking/staking/validators.mdx index 015b1518..d12f809e 100644 --- a/pages/api-references/genlayer-cli/staking/staking/validators.mdx +++ b/pages/api-references/genlayer-cli/staking/staking/validators.mdx @@ -13,7 +13,7 @@ Show validator set with stake, status, and voting power | Short | Long | Description | Required | Default | | --- | --- | --- | :---: | --- | | | --all | Include banned validators | No | | -| | --network <network> | Network to use (localnet, testnet-asimov) | No | | +| | --network <network> | Network to use (localnet, testnet-bradbury) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | | --staking-address <address> | Staking contract address (overrides chain config) | No | | | -h | --help | display help for command | No | | diff --git a/pages/api-references/genlayer-js.md b/pages/api-references/genlayer-js.md index f863065b..45cd1f9c 100644 --- a/pages/api-references/genlayer-js.md +++ b/pages/api-references/genlayer-js.md @@ -28,7 +28,7 @@ const client = createClient({ }); ``` -Use `testnetBradbury`, `testnetAsimov`, or `studionet` for the corresponding hosted network. The v0.123 preview uses the matching RC's `studioDevnet` definition, which binds chain ID 61997, its RPC, and its consensus deployment together. +Use `testnetBradbury` or `studionet` for the corresponding hosted network. The v0.123 preview uses the matching RC's `studioDevnet` definition, which binds chain ID 61997, its RPC, and its consensus deployment together. ## Read a contract diff --git a/pages/api-references/genlayer-node.mdx b/pages/api-references/genlayer-node.mdx index aebb731f..3ff6d02a 100644 --- a/pages/api-references/genlayer-node.mdx +++ b/pages/api-references/genlayer-node.mdx @@ -1840,7 +1840,7 @@ Content-Disposition: attachment; filename="snapshot.tar.gz" curl -X POST http://127.0.0.1:9153/snapshot \ -H "X-Admin-Auth: $GENLAYERNODE_NODE_OPERATOR_TOKEN" \ -H "Content-Type: application/json" \ - -d '{"destDir": "/var/snapshots/asimov-100"}' + -d '{"destDir": "/var/snapshots/bradbury-100"}' ``` **Example Response — Write-to-Path Mode:** diff --git a/pages/api-references/genlayer-node/ops/snapshot.mdx b/pages/api-references/genlayer-node/ops/snapshot.mdx index 07b728c3..e230ec5a 100644 --- a/pages/api-references/genlayer-node/ops/snapshot.mdx +++ b/pages/api-references/genlayer-node/ops/snapshot.mdx @@ -67,7 +67,7 @@ Content-Disposition: attachment; filename="snapshot.tar.gz" curl -X POST http://127.0.0.1:9153/snapshot \ -H "X-Admin-Auth: $GENLAYERNODE_NODE_OPERATOR_TOKEN" \ -H "Content-Type: application/json" \ - -d '{"destDir": "/var/snapshots/asimov-100"}' + -d '{"destDir": "/var/snapshots/bradbury-100"}' ``` **Example Response — Write-to-Path Mode:** diff --git a/pages/api-references/genlayer-test.md b/pages/api-references/genlayer-test.md index 61889005..f4d199e8 100644 --- a/pages/api-references/genlayer-test.md +++ b/pages/api-references/genlayer-test.md @@ -149,7 +149,7 @@ networks: studionet: # Pre-configured — accounts auto-generated - testnet_asimov: + testnet_bradbury: accounts: - "${ACCOUNT_PRIVATE_KEY_1}" - "${ACCOUNT_PRIVATE_KEY_2}" @@ -163,14 +163,14 @@ environment: .env ``` Key options: -- **Networks**: `localnet` and `studionet` work out of the box. `testnet_asimov` requires account keys. +- **Networks**: `localnet` and `studionet` work out of the box. `testnet_bradbury` requires account keys. - **Paths**: Where your contracts and artifacts live. - **Environment**: `.env` file for private keys. Override via CLI: ```bash -gltest --network testnet_asimov +gltest --network testnet_bradbury gltest --contracts-dir custom/contracts/path gltest --rpc-url http://custom:4000/api gltest --chain-type localnet diff --git a/pages/developers/decentralized-applications/transaction-kit-integration.mdx b/pages/developers/decentralized-applications/transaction-kit-integration.mdx index 98683dee..1dbeb8f6 100644 --- a/pages/developers/decentralized-applications/transaction-kit-integration.mdx +++ b/pages/developers/decentralized-applications/transaction-kit-integration.mdx @@ -27,11 +27,11 @@ The kit depends on an injected [EIP-1193](https://eips.ethereum.org/EIPS/eip-119 ```typescript import { createTransactionKit } from '@genlayer/transaction-kit'; -import { testnetAsimov } from 'genlayer-js/chains'; +import { testnetBradbury } from 'genlayer-js/chains'; import feeProfile from './fee-profile.json'; const kit = createTransactionKit({ - chain: testnetAsimov, + chain: testnetBradbury, provider: window.ethereum, account: userAddress, suggestions: feeProfile, @@ -49,7 +49,7 @@ import '@genlayer/transaction-kit-react/styles.css'; refetchAppState()} /> @@ -71,7 +71,7 @@ Deploys use the same panel shape: ``` diff --git a/pages/developers/intelligent-contracts/deploying/network-configuration.mdx b/pages/developers/intelligent-contracts/deploying/network-configuration.mdx index 495d99bb..573e8012 100644 --- a/pages/developers/intelligent-contracts/deploying/network-configuration.mdx +++ b/pages/developers/intelligent-contracts/deploying/network-configuration.mdx @@ -1,5 +1,5 @@ --- -description: "Choose and configure Localnet, Studionet, the Studio development preview, Asimov, or Bradbury." +description: "Choose and configure Localnet, Studionet, the Studio development preview, or Bradbury." --- import { Callout } from 'nextra-theme-docs' @@ -24,7 +24,6 @@ The stable built-in aliases are: |:--|:--| | `localnet` | Local Studio or GLSim; full control and resets | | `studionet` | Stable hosted Studio at `studio.genlayer.com` | -| `testnet-asimov` | Shared infrastructure and stress testing | | `testnet-bradbury` | Production-like testing with real AI workloads | The v0.123 / Consensus v0.6 release-candidate tooling adds `studio-dev`, backed by the JavaScript SDK's `studioDevnet` chain definition: @@ -70,7 +69,7 @@ Both have browser faucets. Studio-dev is temporary: expect resets and do not dep ## Public testnets -Use Bradbury for production-like contract testing and Asimov for infrastructure-oriented testing: +Use Bradbury for production-like contract testing: ```bash genlayer network set testnet-bradbury diff --git a/pages/developers/intelligent-contracts/features/value-transfers.mdx b/pages/developers/intelligent-contracts/features/value-transfers.mdx index ac370663..90d6e220 100644 --- a/pages/developers/intelligent-contracts/features/value-transfers.mdx +++ b/pages/developers/intelligent-contracts/features/value-transfers.mdx @@ -158,7 +158,6 @@ graph TD | Studio (Localnet) | Built-in faucet — 💧 button in the account selector | | Studionet | Built-in faucet — 💧 button in the account selector | | Studio development preview | Built-in faucet — 💧 button in the account selector | -| Testnet Asimov | [testnet-faucet.genlayer.foundation](https://testnet-faucet.genlayer.foundation/) | | Testnet Bradbury | [testnet-faucet.genlayer.foundation](https://testnet-faucet.genlayer.foundation/) | See [Networks](/developers/networks) for full details on each environment. @@ -169,10 +168,10 @@ Use the `value` parameter in `writeContract()` to send GEN. On a fee-charging de ```typescript import { createClient, createAccount } from 'genlayer-js'; -import { testnetAsimov } from 'genlayer-js/chains'; +import { testnetBradbury } from 'genlayer-js/chains'; const client = createClient({ - chain: testnetAsimov, + chain: testnetBradbury, account: createAccount(), }); diff --git a/pages/developers/networks.mdx b/pages/developers/networks.mdx index e8d468da..b474de59 100644 --- a/pages/developers/networks.mdx +++ b/pages/developers/networks.mdx @@ -33,24 +33,6 @@ Production-like testnet with real AI/LLM workloads. --- -## Testnet Asimov - -Infrastructure and stress testing. - -| Setting | Value | -|:--|:--| -| **GenLayer RPC** | `https://rpc-asimov.genlayer.com` | -| **GenLayer Chain RPC** | `https://rpc.testnet-chain.genlayer.com` | -| **Chain ID** | 4221 | -| **Currency** | GEN | -| **Explorer** | [explorer-asimov.genlayer.com](https://explorer-asimov.genlayer.com) | -| **Chain Explorer** | [explorer.testnet-chain.genlayer.com](https://explorer.testnet-chain.genlayer.com/) | -| **Faucet** | [testnet-faucet.genlayer.foundation](https://testnet-faucet.genlayer.foundation) | - - - ---- - ## Studionet Hosted development environment — no local setup required. @@ -119,13 +101,13 @@ The underlying zkSync Elastic Chain that GenLayer runs on. You typically don't n ## Network Comparison -| | Bradbury | Asimov | Studionet | Studio dev | Localnet | -|---|---|---|---|---|---| -| **Purpose** | Production-like testing | Infrastructure testing | Hosted stable dev | Next-stack RC validation | Local dev | -| **Setup** | None | None | None | Matching RC tooling | Docker or GLSim | -| **Persistence** | Persistent | Persistent | Temporary | Temporary; resets expected | Local only | -| **LLM execution** | Real models | Real models | Real models | Real models | Configurable | -| **Faucet** | [Available](https://testnet-faucet.genlayer.foundation) | [Available](https://testnet-faucet.genlayer.foundation) | Built-in | Built-in | Built-in | +| | Bradbury | Studionet | Studio dev | Localnet | +|---|---|---|---|---| +| **Purpose** | Production-like testing | Hosted stable dev | Next-stack RC validation | Local dev | +| **Setup** | None | None | Matching RC tooling | Docker or GLSim | +| **Persistence** | Persistent | Temporary | Temporary; resets expected | Local only | +| **LLM execution** | Real models | Real models | Real models | Configurable | +| **Faucet** | [Available](https://testnet-faucet.genlayer.foundation) | Built-in | Built-in | Built-in | ## Recommended Flow diff --git a/pages/validators/monitoring.mdx b/pages/validators/monitoring.mdx index 393c3234..cef5a281 100644 --- a/pages/validators/monitoring.mdx +++ b/pages/validators/monitoring.mdx @@ -94,7 +94,7 @@ To contribute your node's metrics and logs to the centralized GenLayer Foundatio **Prerequisites** - Metrics enabled in `config.yaml` (`endpoints.metrics: true` — default in recent versions). - Ops port 9153 exposed in docker-compose (`ports: - "9153:9153"`). -- Credentials from the Foundation team (ask in #testnet-asimov): +- Credentials from the Foundation team: - `CENTRAL_MONITORING_URL` — Prometheus remote write URL - `CENTRAL_LOKI_URL` — Loki push URL - `CENTRAL_MONITORING_USERNAME` / `CENTRAL_MONITORING_PASSWORD` — Metrics (Prometheus) credentials diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index 74ce77a6..304453c1 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -275,10 +275,13 @@ rollup: provider: "TODO: Set your GenLayer Chain ZKSync provider" # Rollup operator name; surfaces as the "provider" label on RPC-driven metrics. Set explicitly per deployment. # consensus contracts configuration consensus: - # Testnet - Phase 5 - consensusaddress: "0xe66B434bc83805f380509642429eC8e43AE9874a" # AddressManager Smart Contract Address - genesis: 17326 # (Optional) Genesis block number for this consensus deployment. If not provided, it will be auto-detected by searching for the first log from the ConsensusMain contract. + # Clarke Dev template: the deployment-specific fields are intentionally empty. + consensusaddress: "" # AddressManager contract address for this deployment. + manifestpath: "" # Trusted producer protocol-manifest.json for this deployment. The deploy places it at configs/node/protocol-manifest.json; relative paths resolve against the node root. A mismatch with consensusaddress is refused at startup. + chainid: "" # DECIMAL chain id this deployment was made on (not hex, no leading zeros). Held independently of the manifest so a manifest describing another deployment is caught. + # genesis: 17326 # (Optional) Genesis block number for this consensus deployment. If not provided, it will be auto-detected by searching for the first log from the ConsensusMain contract. # chain_submit_buffer: "30s" # (Optional) Slack reserved at the end of each consensus step to submit the resulting on-chain tx (commit vote, reveal vote, proposed receipt) before the slot closes. Tune to underlying settlement chain latency. Default 30s. + # admissiontimeout: "15s" # (Optional) Bound on the startup deployment admission check. Never unbounded. Default 15s. # data directory datadir: "./data/node" # logging configuration diff --git a/scripts/retrofit/retrofit.js b/scripts/retrofit/retrofit.js index 110fc607..91709581 100644 --- a/scripts/retrofit/retrofit.js +++ b/scripts/retrofit/retrofit.js @@ -46,7 +46,7 @@ const EXCLUDED_FILES = [ ]; const CANON = `Project context — GenLayer documentation (docs.genlayer.com): -GenLayer is the first Intelligent Blockchain: an L2 (on zkSync Elastic Chain, anchored to Ethereum) whose smart contracts — called Intelligent Contracts, written in Python — can natively access the web, call LLMs, and reach consensus on non-deterministic, subjective outcomes. Consensus mechanism: Optimistic Democracy; validators agree on non-deterministic outputs via the Equivalence Principle. Execution environment: GenVM. Dev tools: GenLayer CLI, GenLayer Studio (browser IDE), GenLayerJS / GenLayerPY SDKs, GenLayer Testing Suite (genlayer-test). Networks: Localnet, Studionet, Testnet Asimov. +GenLayer is the first Intelligent Blockchain: an L2 (on zkSync Elastic Chain, anchored to Ethereum) whose smart contracts — called Intelligent Contracts, written in Python — can natively access the web, call LLMs, and reach consensus on non-deterministic, subjective outcomes. Consensus mechanism: Optimistic Democracy; validators agree on non-deterministic outputs via the Equivalence Principle. Execution environment: GenVM. Dev tools: GenLayer CLI, GenLayer Studio (browser IDE), GenLayerJS / GenLayerPY SDKs, GenLayer Testing Suite (genlayer-test). Networks: Localnet, Studionet, Testnet Bradbury. Canonical terminology — always use exactly these names: "Intelligent Contract" (never "AI contract" or "smart contract 2.0"), "Optimistic Democracy", "Equivalence Principle", "GenVM", "GenLayer Studio". You may read other files in this repository (e.g. public/llms.txt, sibling pages in the same directory) to stay consistent with how concepts are described elsewhere.`; From 918820abe564a7f4628c7e40c9e174ddf25cff10 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 13:41:15 +0200 Subject: [PATCH 06/39] docs(validators): pick up fixed manifestpath comment from node template Mirrors genlayer-node e62e11481: the comment names the shipped configs/node/bradbury.protocol-manifest.json, and the genesis example is the Bradbury phase 2 block (47930). Co-Authored-By: Claude Opus 5.5 --- content/validators/config.yaml | 4 ++-- pages/validators/setup-guide.mdx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/content/validators/config.yaml b/content/validators/config.yaml index 405ffcf3..5179fc5f 100644 --- a/content/validators/config.yaml +++ b/content/validators/config.yaml @@ -7,9 +7,9 @@ rollup: consensus: # Clarke Dev template: the deployment-specific fields are intentionally empty. consensusaddress: "" # AddressManager contract address for this deployment. - manifestpath: "" # Trusted producer protocol-manifest.json for this deployment. The deploy places it at configs/node/protocol-manifest.json; relative paths resolve against the node root. A mismatch with consensusaddress is refused at startup. + manifestpath: "" # Trusted producer protocol manifest for this deployment. Bradbury: configs/node/bradbury.protocol-manifest.json (ships in the release package); a dev-env deploy places its own at configs/node/protocol-manifest.json. Relative paths resolve against the node root. A mismatch with consensusaddress is refused at startup. chainid: "" # DECIMAL chain id this deployment was made on (not hex, no leading zeros). Held independently of the manifest so a manifest describing another deployment is caught. - # genesis: 17326 # (Optional) Genesis block number for this consensus deployment. If not provided, it will be auto-detected by searching for the first log from the ConsensusMain contract. + # genesis: 47930 # (Optional) Genesis block number for this consensus deployment. If not provided, it will be auto-detected by searching for the first log from the ConsensusMain contract. # chain_submit_buffer: "30s" # (Optional) Slack reserved at the end of each consensus step to submit the resulting on-chain tx (commit vote, reveal vote, proposed receipt) before the slot closes. Tune to underlying settlement chain latency. Default 30s. # admissiontimeout: "15s" # (Optional) Bound on the startup deployment admission check. Never unbounded. Default 15s. # data directory diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index 304453c1..532067ce 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -277,9 +277,9 @@ rollup: consensus: # Clarke Dev template: the deployment-specific fields are intentionally empty. consensusaddress: "" # AddressManager contract address for this deployment. - manifestpath: "" # Trusted producer protocol-manifest.json for this deployment. The deploy places it at configs/node/protocol-manifest.json; relative paths resolve against the node root. A mismatch with consensusaddress is refused at startup. + manifestpath: "" # Trusted producer protocol manifest for this deployment. Bradbury: configs/node/bradbury.protocol-manifest.json (ships in the release package); a dev-env deploy places its own at configs/node/protocol-manifest.json. Relative paths resolve against the node root. A mismatch with consensusaddress is refused at startup. chainid: "" # DECIMAL chain id this deployment was made on (not hex, no leading zeros). Held independently of the manifest so a manifest describing another deployment is caught. - # genesis: 17326 # (Optional) Genesis block number for this consensus deployment. If not provided, it will be auto-detected by searching for the first log from the ConsensusMain contract. + # genesis: 47930 # (Optional) Genesis block number for this consensus deployment. If not provided, it will be auto-detected by searching for the first log from the ConsensusMain contract. # chain_submit_buffer: "30s" # (Optional) Slack reserved at the end of each consensus step to submit the resulting on-chain tx (commit vote, reveal vote, proposed receipt) before the slot closes. Tune to underlying settlement chain latency. Default 30s. # admissiontimeout: "15s" # (Optional) Bound on the startup deployment admission check. Never unbounded. Default 15s. # data directory From 8b221b19b2828b19d196db4c007a379032f78eea Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 14:03:30 +0200 Subject: [PATCH 07/39] docs(validators): Bradbury phase 2 config and Conduit WebSocket key step Copy bradbury.yaml from genlayer-node#2262 (2a386216e) so the setup guide shows the phase 2 block, and add a step for the Conduit API key the Bradbury WebSocket URL needs: where to create it, that it goes at the end of the URL (not GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY), and the GENLAYERNODE_ROLLUP_GENLAYERCHAINWEBSOCKETURL form that keeps it out of config.yaml. Co-Authored-By: Claude Opus 5.5 --- content/validators/bradbury.yaml | 26 +++++++++++++++++++--- pages/validators/setup-guide.mdx | 38 +++++++++++++++++++++++++++++--- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/content/validators/bradbury.yaml b/content/validators/bradbury.yaml index 7655a3c8..2ab89ef3 100644 --- a/content/validators/bradbury.yaml +++ b/content/validators/bradbury.yaml @@ -1,4 +1,24 @@ -# Bradbury Phase 1 network consensus configuration +# Bradbury Phase 2 network configuration (GenLayer testnet, chain id 4221). +# +# Put these values into configs/node/config.yaml. They come from the +# genlayer-consensus deployment record deployments/bradbury-phase-2.json and its +# sealed protocol manifest, which is configs/node/bradbury.protocol-manifest.json. + +# rollup configuration +rollup: + genlayerchainrpcurl: "https://rpc-genlayer-chain-testnet-cfn8m3xc6l.t.conduit.xyz/" # GenLayer Chain RPC URL + # Conduit refuses a WebSocket connection without an API key, and takes the + # key at the end of the URL, not in a header. Get a key at conduit.xyz + # (Nodes > Create API key) and replace with it. To keep the + # key out of this file, set the whole URL in the environment as + # GENLAYERNODE_ROLLUP_GENLAYERCHAINWEBSOCKETURL. The node hides the URL path + # when it prints this URL. + genlayerchainwebsocketurl: "wss://rpc-genlayer-chain-testnet-cfn8m3xc6l.t.conduit.xyz/" # GenLayer Chain WebSocket URL + provider: "conduit" # Rollup operator name; surfaces as the "provider" label on RPC-driven metrics. + +# consensus contracts configuration consensus: - consensusaddress: "0x8aCE036C8C3C5D603dB546b031302FCf149648E8" - genesis: 501711 + consensusaddress: "0x3F71AE080BD6270A91dBcc7960d88797828538E0" # AddressManager contract address. The node names the network bradbury-phase2 from it. + chainid: "4221" # DECIMAL chain id of this deployment. + manifestpath: "configs/node/bradbury.protocol-manifest.json" # Trusted protocol manifest of this deployment; a relative path resolves against the node root. + genesis: 47930 # Genesis block number for this consensus deployment (AddressManager deployment block). diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index 532067ce..336edb92 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -467,13 +467,45 @@ Since v0.6, the node refuses to start unless `consensus.manifestpath` points at ##### Testnet Bradbury ```yaml copy -# Bradbury Phase 1 network consensus configuration +# Bradbury Phase 2 network configuration (GenLayer testnet, chain id 4221). +# +# Put these values into configs/node/config.yaml. They come from the +# genlayer-consensus deployment record deployments/bradbury-phase-2.json and its +# sealed protocol manifest, which is configs/node/bradbury.protocol-manifest.json. + +# rollup configuration +rollup: + genlayerchainrpcurl: "https://rpc-genlayer-chain-testnet-cfn8m3xc6l.t.conduit.xyz/" # GenLayer Chain RPC URL + # Conduit refuses a WebSocket connection without an API key, and takes the + # key at the end of the URL, not in a header. Get a key at conduit.xyz + # (Nodes > Create API key) and replace with it. To keep the + # key out of this file, set the whole URL in the environment as + # GENLAYERNODE_ROLLUP_GENLAYERCHAINWEBSOCKETURL. The node hides the URL path + # when it prints this URL. + genlayerchainwebsocketurl: "wss://rpc-genlayer-chain-testnet-cfn8m3xc6l.t.conduit.xyz/" # GenLayer Chain WebSocket URL + provider: "conduit" # Rollup operator name; surfaces as the "provider" label on RPC-driven metrics. + +# consensus contracts configuration consensus: - consensusaddress: "0x8aCE036C8C3C5D603dB546b031302FCf149648E8" - genesis: 501711 + consensusaddress: "0x3F71AE080BD6270A91dBcc7960d88797828538E0" # AddressManager contract address. The node names the network bradbury-phase2 from it. + chainid: "4221" # DECIMAL chain id of this deployment. + manifestpath: "configs/node/bradbury.protocol-manifest.json" # Trusted protocol manifest of this deployment; a relative path resolves against the node root. + genesis: 47930 # Genesis block number for this consensus deployment (AddressManager deployment block). ``` +**WebSocket API key.** The Bradbury HTTPS RPC needs no key, but its WebSocket endpoint (hosted by Conduit) has rejected connections without an API key since August 4, 2026. + +1. Create a key in the Conduit app: open **Nodes** and click **Create API key** (see [Conduit: Get an API key](https://docs.conduit.xyz/rpc-nodes/getting-started/get-api-key)). +2. Replace `` at the end of `genlayerchainwebsocketurl` with your key. Conduit reads the key from the URL, not from a header, so `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY` does not apply to this URL. +3. To keep the key out of `config.yaml`, leave the placeholder in the file and set the whole URL in the node's environment instead: + + ```sh copy + export GENLAYERNODE_ROLLUP_GENLAYERCHAINWEBSOCKETURL="wss://rpc-genlayer-chain-testnet-cfn8m3xc6l.t.conduit.xyz/" + ``` + +The node hides the URL path when it prints this URL, so the key does not appear in logs. + #### Overriding Configuration with Environment Variables Any configuration value in `config.yaml` can be overridden using environment variables with the prefix `GENLAYERNODE_`. From 7161b1d0cbb3a955787dd09d832545eeda0af6c3 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 14:06:38 +0200 Subject: [PATCH 08/39] docs(validators): send the Bradbury Conduit key in the header, not the URL The node sends GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY in the X-AccessKey header on RPC and WebSocket connections (genlayer-node#2224), so the key never goes in a URL. Recopy bradbury.yaml from genlayer-node#2262 (62399e5a9), rewrite the key step, and list the env-only key variable in the override table. Co-Authored-By: Claude Opus 5.5 --- content/validators/bradbury.yaml | 13 ++++++------- pages/validators/setup-guide.mdx | 27 ++++++++++++++------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/content/validators/bradbury.yaml b/content/validators/bradbury.yaml index 2ab89ef3..d0dbac10 100644 --- a/content/validators/bradbury.yaml +++ b/content/validators/bradbury.yaml @@ -7,13 +7,12 @@ # rollup configuration rollup: genlayerchainrpcurl: "https://rpc-genlayer-chain-testnet-cfn8m3xc6l.t.conduit.xyz/" # GenLayer Chain RPC URL - # Conduit refuses a WebSocket connection without an API key, and takes the - # key at the end of the URL, not in a header. Get a key at conduit.xyz - # (Nodes > Create API key) and replace with it. To keep the - # key out of this file, set the whole URL in the environment as - # GENLAYERNODE_ROLLUP_GENLAYERCHAINWEBSOCKETURL. The node hides the URL path - # when it prints this URL. - genlayerchainwebsocketurl: "wss://rpc-genlayer-chain-testnet-cfn8m3xc6l.t.conduit.xyz/" # GenLayer Chain WebSocket URL + genlayerchainwebsocketurl: "wss://rpc-genlayer-chain-testnet-cfn8m3xc6l.t.conduit.xyz/" # GenLayer Chain WebSocket URL + # Conduit refuses a WebSocket connection without an API key. Create a key at + # https://app.conduit.xyz/rpc-keys and set it in the environment as + # GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY, never in these URLs or this file. + # The node sends it in the X-AccessKey header on the RPC and WebSocket + # connections. provider: "conduit" # Rollup operator name; surfaces as the "provider" label on RPC-driven metrics. # consensus contracts configuration diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index 336edb92..e9bdd6eb 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -476,13 +476,12 @@ Since v0.6, the node refuses to start unless `consensus.manifestpath` points at # rollup configuration rollup: genlayerchainrpcurl: "https://rpc-genlayer-chain-testnet-cfn8m3xc6l.t.conduit.xyz/" # GenLayer Chain RPC URL - # Conduit refuses a WebSocket connection without an API key, and takes the - # key at the end of the URL, not in a header. Get a key at conduit.xyz - # (Nodes > Create API key) and replace with it. To keep the - # key out of this file, set the whole URL in the environment as - # GENLAYERNODE_ROLLUP_GENLAYERCHAINWEBSOCKETURL. The node hides the URL path - # when it prints this URL. - genlayerchainwebsocketurl: "wss://rpc-genlayer-chain-testnet-cfn8m3xc6l.t.conduit.xyz/" # GenLayer Chain WebSocket URL + genlayerchainwebsocketurl: "wss://rpc-genlayer-chain-testnet-cfn8m3xc6l.t.conduit.xyz/" # GenLayer Chain WebSocket URL + # Conduit refuses a WebSocket connection without an API key. Create a key at + # https://app.conduit.xyz/rpc-keys and set it in the environment as + # GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY, never in these URLs or this file. + # The node sends it in the X-AccessKey header on the RPC and WebSocket + # connections. provider: "conduit" # Rollup operator name; surfaces as the "provider" label on RPC-driven metrics. # consensus contracts configuration @@ -494,17 +493,18 @@ consensus: ``` -**WebSocket API key.** The Bradbury HTTPS RPC needs no key, but its WebSocket endpoint (hosted by Conduit) has rejected connections without an API key since August 4, 2026. +**WebSocket API key.** The Bradbury HTTPS RPC needs no key, but its WebSocket endpoint (hosted by Conduit) rejects connections without an API key. -1. Create a key in the Conduit app: open **Nodes** and click **Create API key** (see [Conduit: Get an API key](https://docs.conduit.xyz/rpc-nodes/getting-started/get-api-key)). -2. Replace `` at the end of `genlayerchainwebsocketurl` with your key. Conduit reads the key from the URL, not from a header, so `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY` does not apply to this URL. -3. To keep the key out of `config.yaml`, leave the placeholder in the file and set the whole URL in the node's environment instead: +1. Create a key at [app.conduit.xyz/rpc-keys](https://app.conduit.xyz/rpc-keys). +2. Set it in the node's environment as `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY`. Never put it in the RPC or WebSocket URLs, or anywhere in `config.yaml`: ```sh copy - export GENLAYERNODE_ROLLUP_GENLAYERCHAINWEBSOCKETURL="wss://rpc-genlayer-chain-testnet-cfn8m3xc6l.t.conduit.xyz/" + export GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY="" ``` -The node hides the URL path when it prints this URL, so the key does not appear in logs. + With Docker, add the same variable to the `.env` file next to your `docker-compose.yaml`. + +The node sends the key in the `X-AccessKey` header on both the RPC and WebSocket connections. To use a different header name, set `rollup.genlayerchainrpckeyheader`. #### Overriding Configuration with Environment Variables @@ -522,6 +522,7 @@ Any configuration value in `config.yaml` can be overridden using environment var |------------|---------------------| | `rollup.genlayerchainrpcurl` | `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCURL` | | `rollup.genlayerchainwebsocketurl` | `GENLAYERNODE_ROLLUP_GENLAYERCHAINWEBSOCKETURL` | +| (secret, environment only) | `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY` | | `consensus.contractmainaddress` | `GENLAYERNODE_CONSENSUS_CONTRACTMAINADDRESS` | | `consensus.contractdataaddress` | `GENLAYERNODE_CONSENSUS_CONTRACTDATAADDRESS` | | `consensus.genesis` | `GENLAYERNODE_CONSENSUS_GENESIS` | From 1b50a4e9e28ac6caf86eabb972e273575d292cf8 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 14:16:09 +0200 Subject: [PATCH 09/39] docs(validators): point monitoring credentials to #bradbury Co-Authored-By: Claude Opus 5.5 --- pages/validators/monitoring.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/validators/monitoring.mdx b/pages/validators/monitoring.mdx index cef5a281..b60700d5 100644 --- a/pages/validators/monitoring.mdx +++ b/pages/validators/monitoring.mdx @@ -94,7 +94,7 @@ To contribute your node's metrics and logs to the centralized GenLayer Foundatio **Prerequisites** - Metrics enabled in `config.yaml` (`endpoints.metrics: true` — default in recent versions). - Ops port 9153 exposed in docker-compose (`ports: - "9153:9153"`). -- Credentials from the Foundation team: +- Credentials from the Foundation team (ask in #bradbury): - `CENTRAL_MONITORING_URL` — Prometheus remote write URL - `CENTRAL_LOKI_URL` — Loki push URL - `CENTRAL_MONITORING_USERNAME` / `CENTRAL_MONITORING_PASSWORD` — Metrics (Prometheus) credentials From 8e570073bd04b1450045dd2433e70383e5971655 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 15:26:17 +0200 Subject: [PATCH 10/39] docs(validators): serve the Bradbury manifest as a download The node release is network-agnostic since genlayer-node#2262 (6f5bf579f): the tarball and image carry no network files, and every network's manifest is saved at configs/node/protocol-manifest.json. - Sync configs/node/bradbury.protocol-manifest.json to content/validators/ (new config_bradbury_manifest matrix entry). - Build step publishes it at /validators/bradbury.protocol-manifest.json (public copy is gitignored build output, like llms.txt). - Setup guide: download step for the manifest, no more "release ships it" text, NODE_MANIFEST_PATH in the .env example, and the compose mount. bradbury.yaml and the config template block recopied from 6f5bf579f. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/sync-docs-from-node.yml | 12 +- .gitignore | 1 + .../bradbury.protocol-manifest.json | 396 ++++++++++++++++++ content/validators/bradbury.yaml | 9 +- content/validators/config.yaml | 2 +- content/validators/docker-compose.yaml | 2 + package.json | 7 +- pages/validators/setup-guide.mdx | 27 +- scripts/publish-network-manifests.js | 37 ++ 9 files changed, 477 insertions(+), 16 deletions(-) create mode 100644 content/validators/bradbury.protocol-manifest.json create mode 100644 scripts/publish-network-manifests.js diff --git a/.github/workflows/sync-docs-from-node.yml b/.github/workflows/sync-docs-from-node.yml index 747358d9..838f2f0f 100644 --- a/.github/workflows/sync-docs-from-node.yml +++ b/.github/workflows/sync-docs-from-node.yml @@ -107,6 +107,7 @@ jobs: docs/api/ops configs/node/config.yaml.example configs/node/bradbury.yaml.example + configs/node/bradbury.protocol-manifest.json release/docker-compose.yaml release/alloy-config.river release/greybox-setup-guide.md @@ -128,7 +129,7 @@ jobs: needs: prepare strategy: matrix: - sync_type: [changelog, config, config_bradbury, docker_compose, docker_compose_monitoring, alloy_config, greybox_setup, api_gen, api_debug, api_ops] + sync_type: [changelog, config, config_bradbury, config_bradbury_manifest, docker_compose, docker_compose_monitoring, alloy_config, greybox_setup, api_gen, api_debug, api_ops] fail-fast: false steps: - name: Checkout documentation repository @@ -170,6 +171,12 @@ jobs: echo "target_path=content/validators/bradbury.yaml" >> $GITHUB_OUTPUT echo "filter_pattern=.*" >> $GITHUB_OUTPUT ;; + "config_bradbury_manifest") + echo "title=Protocol Manifest (Bradbury)" >> $GITHUB_OUTPUT + echo "source_path=source-repo/configs/node/bradbury.protocol-manifest.json" >> $GITHUB_OUTPUT + echo "target_path=content/validators/bradbury.protocol-manifest.json" >> $GITHUB_OUTPUT + echo "filter_pattern=.*" >> $GITHUB_OUTPUT + ;; "docker_compose") echo "title=Docker Compose File" >> $GITHUB_OUTPUT echo "source_path=source-repo/release/docker-compose.yaml" >> $GITHUB_OUTPUT @@ -529,12 +536,13 @@ jobs: echo "" >> $GITHUB_STEP_SUMMARY # Process each sync type report - for sync_type in changelog config config_bradbury docker_compose docker_compose_monitoring alloy_config greybox_setup api_gen api_debug api_ops; do + for sync_type in changelog config config_bradbury config_bradbury_manifest docker_compose docker_compose_monitoring alloy_config greybox_setup api_gen api_debug api_ops; do # Get proper title case "$sync_type" in "changelog") title="📝 Changelog Sync" ;; "config") title="⚙️ Config File Sync" ;; "config_bradbury") title="⚙️ Config File Sync (Bradbury)" ;; + "config_bradbury_manifest") title="📜 Protocol Manifest Sync (Bradbury)" ;; "docker_compose") title="🐳 Docker Compose Sync" ;; "docker_compose_monitoring") title="🐳 Docker Compose Sync (Monitoring)" ;; "alloy_config") title="📊 Alloy Config Sync" ;; diff --git a/.gitignore b/.gitignore index fb20a905..ee8e1eaa 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ public/llms.txt public/llms-full.txt public/**/llms-full.txt public/**/*.md +public/validators/*.protocol-manifest.json .specstory/ # SpecStory explanation file .specstory/.what-is-this.md diff --git a/content/validators/bradbury.protocol-manifest.json b/content/validators/bradbury.protocol-manifest.json new file mode 100644 index 00000000..960807e1 --- /dev/null +++ b/content/validators/bradbury.protocol-manifest.json @@ -0,0 +1,396 @@ +{ + "version": 1, + "chainId": "4221", + "registry": "0x3F71AE080BD6270A91dBcc7960d88797828538E0", + "entries": [ + { + "key": "Voting", + "addr": "0xB8E01fA102f91ec2E402CfBD23f162e088C2CD3A" + }, + { + "key": "ValidatorPrimeUtils", + "addr": "0x1CDE9c9f3a17cBeB6bCFBE1fd732745309f81576" + }, + { + "key": "VestingBeacon", + "addr": "0x6A5f7bb80b8760B8978e3E2D57a4639111ebba05" + }, + { + "key": "Utils", + "addr": "0x215952B66D801c805e68E2503AfC751cAA84Af7c" + }, + { + "key": "ConsensusMain", + "addr": "0x2E2766cbc3E5e61Ce93bBcdCBb3eAcB82082A019" + }, + { + "key": "SnapshotSelectionKernel", + "addr": "0x84c06bc3d53c5859334BA32d707604750D761196" + }, + { + "key": "GovernanceCouncilElections", + "addr": "0xFF203aBb22412c81Fa04Dfb1F9620790F7ca581c" + }, + { + "key": "VestingFactory", + "addr": "0x82cA5e0e7A9345CF3ED5ee68A7fba41EF2202316" + }, + { + "key": "IdlenessPhase", + "addr": "0xa2f3360c3359e45AD07E889018567BaBB914d3eB" + }, + { + "key": "Rounds", + "addr": "0xe1B234B6959deA2AD0873fc7248a91a53a6185f9" + }, + { + "key": "RandomnessUtils", + "addr": "0xd7DE7Feeed4B6f51D851AAf347CB0895D1275342" + }, + { + "key": "ConsensusData", + "addr": "0xc5FA6f82BEe53598825d6edf7E3e24613082c75B" + }, + { + "key": "IdlenessViews", + "addr": "0x0861c1c7ACAAA2a56c6c49FB13261171DcB10855" + }, + { + "key": "Queues", + "addr": "0xBfE2ece1f2c62Ab3700C94Ed6bB76286bccC8CB3" + }, + { + "key": "RemedySelectionKernel", + "addr": "0x0CB262aD8b7964CC88Ec3C96eb6E1474f16D5bf6" + }, + { + "key": "ValidatorSlashSettlement", + "addr": "0x217530Bf7227CF6BC290Bab5ece5e9375E4fa3Cf" + }, + { + "key": "MessagePaymentsTerminalHelpers", + "addr": "0x7534f102EF52EdC0FCb835eE58e8b15Df757092c" + }, + { + "key": "Staking", + "addr": "0xD8fBa96C0dBB0257028dBc0107060b958e5c7113" + }, + { + "key": "Transactions", + "addr": "0xaCe5e4191Bf408957538Caa9493aBB64F63D92c4" + }, + { + "key": "Messages", + "addr": "0xD14B0489157FE420555F1e5A6AFBA031E978aCE5" + }, + { + "key": "GovernanceVotingPower", + "addr": "0xc42590416fF93dD12a038b601088Ff2629559aBf" + }, + { + "key": "StakingReadModel", + "addr": "0xf1fBdE733A9ceE2C0A44b8f724d944F89aE6D8A4" + }, + { + "key": "SecurityCouncil", + "addr": "0x48713683CdB19ece4f6f9601Ffe64c8862827cFC" + }, + { + "key": "ConsensusFinalizationHelpers", + "addr": "0xa2F211EE0D1CcC372695766a47c18af3C4FE46fC" + }, + { + "key": "ValidatorUtils", + "addr": "0xe13742A806c532972964A25d5A3795FB1702eBc2" + }, + { + "key": "TerminalRoundKernel", + "addr": "0x62d3aaBF65d58D51D32B8781C32ca40174a39a5F" + }, + { + "key": "GhostManager", + "addr": "0x2E2766cbc3E5e61Ce93bBcdCBb3eAcB82082A019" + }, + { + "key": "RoundsCreation", + "addr": "0x35A36496861b11199486121621EEb86E0528630C" + }, + { + "key": "GovernanceClock", + "addr": "0xBDb6b4cAEdF3Bfca166745c2a4C16053351A57a6" + }, + { + "key": "QueueStepUtils", + "addr": "0xb4d264c5f8D51FDfE48fd3b7dbd2F3943eC8c073" + }, + { + "key": "GovernanceL1Bridge", + "addr": "0x94b1907087590241e3B55e6655A7EdaFfEC10300" + }, + { + "key": "Slashing", + "addr": "0x9C30189686B7d6ec5e525eA6f155B57987d2086f" + }, + { + "key": "RevealingPhase", + "addr": "0x88c6D8Ff2BC235eEf758014dd92C2E45D1E5C8F7" + }, + { + "key": "Idleness", + "addr": "0x8f4167edC14Ce4E0640de65cF70DcdE3dfba9FF1" + }, + { + "key": "TransactionFeesManager", + "addr": "0xd122de4CB6704C0FDdbb061275F0171e1119A429" + }, + { + "key": "FeeManager", + "addr": "0x7678587A847712f2A96082AC07883A88D0fc7245" + }, + { + "key": "FinalizationPhase", + "addr": "0x0c746f9c1b5F4cD135cB152fdb10CFd36d6560B5" + }, + { + "key": "GhostBlueprint", + "addr": "0x450d2B06aB240773d1E23e69d0c5d616bEF4Ba2d" + }, + { + "key": "CreationPhase", + "addr": "0x56a1517B263c849Be4a0c861540C14e1d51E6273" + }, + { + "key": "ValidatorWalletBeacon", + "addr": "0xb9F17C1799DdCd26C0e621d65D8Ce0A3191B0495" + }, + { + "key": "VestingBlueprint", + "addr": "0xd52998F07eF136d44B41Dc73c131492c8985e204" + }, + { + "key": "FeeAppealHelpers", + "addr": "0x76F6ECdFb9256BD96bf7Da9Dc724873D8F78429f" + }, + { + "key": "FeeProfileHelpers", + "addr": "0x7EBa83806Cd72ba96A10995840B4Aefc2802F8EF" + }, + { + "key": "RoundsVoting", + "addr": "0xFBA6A1F9a1FB76Cdb1559241A7e0Ddeb4C1DF5a1" + }, + { + "key": "ConsensusManager", + "addr": "0x3F748d4CbDA4FDfe8356690aFD73ac4cFc4eE56B" + }, + { + "key": "RecoveryHelpers", + "addr": "0x2dAE85871C61b8896Ec0485050E66F2Cba4925fB" + }, + { + "key": "MessageFeeAllocations", + "addr": "0x1B1eA007C42C1087d889233Ff7157237e7DAe3A9" + }, + { + "key": "IGenLayerStaking", + "addr": "0xD8fBa96C0dBB0257028dBc0107060b958e5c7113" + }, + { + "key": "ContributionLedger", + "addr": "0x9dC6642157b88da4E05ec2F57a186d5F2C03c106" + }, + { + "key": "ActivationPhase", + "addr": "0x62541A8314b9112407E53B3bc5cf8970906E845c" + }, + { + "key": "ArrayUtils", + "addr": "0x87B8BC807392DeD881b49d544032a3A8ac411613" + }, + { + "key": "Vesting", + "addr": "0xd52998F07eF136d44B41Dc73c131492c8985e204" + }, + { + "key": "ValidatorWalletBlueprint", + "addr": "0xAE308333af9e4aB7418E0CA6f1Fd5fAb8915BAf2" + }, + { + "key": "FeesVerifier", + "addr": "0xE5DaA0860E1a1dc08a219762f96Fe2e5006504b3" + }, + { + "key": "TribunalInitializationManager", + "addr": "0xb80D257061358fF158B6F3e68CE659a3b74443Ff" + }, + { + "key": "GhostFactory", + "addr": "0x901ECAeFc35aed5Dd9967feF4E161d42199aAd9d" + }, + { + "key": "ConsensusDataBigRounds", + "addr": "0xc0aB228Ba46b4825fcd6461ea56d5B732AcA0E3B" + }, + { + "key": "AddressManager", + "addr": "0x3F71AE080BD6270A91dBcc7960d88797828538E0" + }, + { + "key": "CommittingPhase", + "addr": "0xC08fDE7309d590e01FB93E37a808DEf2a8aB6bc2" + }, + { + "key": "Governance", + "addr": "0x14b647BE7358aE0678DB5B60eC8B8fd90e895917" + }, + { + "key": "AppealFundingHelpers", + "addr": "0x6ea7466625A45272AC786bdCbED179C6fBcEc376" + }, + { + "key": "GovernanceGESRegistry", + "addr": "0x9a59348e756f9dD54C58B31ac764fB0130B51e06" + }, + { + "key": "ConsensusMainWithFees", + "addr": "0x2E2766cbc3E5e61Ce93bBcdCBb3eAcB82082A019" + }, + { + "key": "RoundsLight", + "addr": "0xe1B234B6959deA2AD0873fc7248a91a53a6185f9" + }, + { + "key": "ConsensusProposalHelpers", + "addr": "0x06871c41b23D42E940dFD09CA84117d21dC88C8B" + }, + { + "key": "FeeSplitUtils", + "addr": "0x868D93f0f6ee923DD609788842e635ae27A16A0D" + }, + { + "key": "MinimumStakeTransitionManager", + "addr": "0x37f083c99A72e71B9f564B85cAE9eFa26ebA59F8" + }, + { + "key": "GovernanceVoting", + "addr": "0xF4Fbb5C9FEe31cF45ECb37a3578699F8E88A52C3" + }, + { + "key": "NFTMinter", + "addr": "0xEe307BCE38733cD16c15A13FE7e928BBE40e96E1" + }, + { + "key": "ConsensusHelpers", + "addr": "0x9F757AeC38Bd4d217A3F05fC9c715112ca5279f6" + }, + { + "key": "GenManager", + "addr": "0x3F748d4CbDA4FDfe8356690aFD73ac4cFc4eE56B" + }, + { + "key": "DeepthoughtReserve", + "addr": "0x9d8c991Fe71d27589f004c24505f4ce4C6fD3F78" + }, + { + "key": "ValidatorSlashProgress", + "addr": "0x71fD566E1143cf97d3448e641D7A04f1e2C6dE41" + }, + { + "key": "GovernanceClassRegistry", + "addr": "0x172AE46D9eceE2f7c93c2e0226D78266DB77270f" + }, + { + "key": "FeesProcessor", + "addr": "0x040a455CA9c6A8Cdc696F6fE603152692D4CA775" + }, + { + "key": "FeeManagerHelpers", + "addr": "0x4b21cF7CDe9F4a72C618204aAA2509A1c37De2C6" + }, + { + "key": "Gen", + "addr": "0x424242424b68330CcFb022881d25a6acC29852ea" + }, + { + "key": "DelegatorUtils", + "addr": "0xddF2a0491745B238818bcc02CE71aD5145bfd93A" + }, + { + "key": "TransitionEnforcer", + "addr": "0xA30098a857e9246268cf9c8E7F126bDa882289aC" + }, + { + "key": "ValidatorWalletFactory", + "addr": "0x399FCBe6b442903cbA4d3B647B4b30EddF729314" + }, + { + "key": "TransactionManager", + "addr": "0xaCe5e4191Bf408957538Caa9493aBB64F63D92c4" + }, + { + "key": "MessagePaymentsHelpers", + "addr": "0x8101882d8a2Fea752Bf041Aaeb068C3B4FD62B97" + }, + { + "key": "Appeals", + "addr": "0xdc953c3bCf8837987853ca05a78D52fFC3E18651" + }, + { + "key": "ResolutionKernel", + "addr": "0x274663eB1F7E1c23E7192Fd102212F05dbb02e85" + }, + { + "key": "ProposalPhase", + "addr": "0x44aFCc45720c6ab951BabB726C07A651De9aA4b3" + }, + { + "key": "TribunalAppeal", + "addr": "0x7cf2D3Fa7f63F3547C5036dC955ba1a70235682a" + }, + { + "key": "ValidatorSelectionUtils", + "addr": "0x549dC20c5F22D59b0F662Bb5bC800daE0a7b7a2F" + }, + { + "key": "StakingData", + "addr": "0x8951E93ae3E95F44Bc700fd8D145d30f5eeD408E" + }, + { + "key": "IdlenessHelpers", + "addr": "0x894B9b1Fbc238318f10200cbE03eBf6e2ccCa637" + }, + { + "key": "Slash", + "addr": "0x9C30189686B7d6ec5e525eA6f155B57987d2086f" + }, + { + "key": "FeesRecorder", + "addr": "0x8beE3d0b24e07183A840bd0B51041C1f71914797" + }, + { + "key": "MessagePayments", + "addr": "0x5Ac4717C772107c335f2db699e2cDA4ebC192faE" + }, + { + "key": "BatchTransactionHandler", + "addr": "0x318d04EbeB53d121e0a8566a9448ffE84af0D1C4" + }, + { + "key": "StakingInvariant", + "addr": "0xf9F9bed2008EcD50AdbB101eA455CBe42F1b6Be4" + }, + { + "key": "StakingViews", + "addr": "0xf1fBdE733A9ceE2C0A44b8f724d944F89aE6D8A4" + }, + { + "key": "IdlenessCrank", + "addr": "0xea447c0a9656cf5C6983578Da910bb88eF8e0e30" + }, + { + "key": "RoundsStorage", + "addr": "0x4038d4F72C7aAbA5a442cC0dA946012B9E3f4B8a" + } + ], + "commitment": "0x766bc682ffac57a2a540f92c667c1b33ec694deeb7627ca4f41e9759b9dbf524" +} diff --git a/content/validators/bradbury.yaml b/content/validators/bradbury.yaml index d0dbac10..26019fc7 100644 --- a/content/validators/bradbury.yaml +++ b/content/validators/bradbury.yaml @@ -1,8 +1,9 @@ # Bradbury Phase 2 network configuration (GenLayer testnet, chain id 4221). # -# Put these values into configs/node/config.yaml. They come from the -# genlayer-consensus deployment record deployments/bradbury-phase-2.json and its -# sealed protocol manifest, which is configs/node/bradbury.protocol-manifest.json. +# Put these values into configs/node/config.yaml, and save the Bradbury protocol +# manifest (bradbury.protocol-manifest.json) as configs/node/protocol-manifest.json. +# They come from the genlayer-consensus deployment record +# deployments/bradbury-phase-2.json and its sealed protocol manifest. # rollup configuration rollup: @@ -19,5 +20,5 @@ rollup: consensus: consensusaddress: "0x3F71AE080BD6270A91dBcc7960d88797828538E0" # AddressManager contract address. The node names the network bradbury-phase2 from it. chainid: "4221" # DECIMAL chain id of this deployment. - manifestpath: "configs/node/bradbury.protocol-manifest.json" # Trusted protocol manifest of this deployment; a relative path resolves against the node root. + manifestpath: "configs/node/protocol-manifest.json" # Where bradbury.protocol-manifest.json is saved; a relative path resolves against the node root. genesis: 47930 # Genesis block number for this consensus deployment (AddressManager deployment block). diff --git a/content/validators/config.yaml b/content/validators/config.yaml index 5179fc5f..b146f2d8 100644 --- a/content/validators/config.yaml +++ b/content/validators/config.yaml @@ -7,7 +7,7 @@ rollup: consensus: # Clarke Dev template: the deployment-specific fields are intentionally empty. consensusaddress: "" # AddressManager contract address for this deployment. - manifestpath: "" # Trusted producer protocol manifest for this deployment. Bradbury: configs/node/bradbury.protocol-manifest.json (ships in the release package); a dev-env deploy places its own at configs/node/protocol-manifest.json. Relative paths resolve against the node root. A mismatch with consensusaddress is refused at startup. + manifestpath: "configs/node/protocol-manifest.json" # Trusted producer protocol manifest for this deployment: save your network's manifest (from its documentation) at this path; a dev-env deploy places its own here too. Relative paths resolve against the node root. A mismatch with consensusaddress is refused at startup. chainid: "" # DECIMAL chain id this deployment was made on (not hex, no leading zeros). Held independently of the manifest so a manifest describing another deployment is caught. # genesis: 47930 # (Optional) Genesis block number for this consensus deployment. If not provided, it will be auto-detected by searching for the first log from the ConsensusMain contract. # chain_submit_buffer: "30s" # (Optional) Slack reserved at the end of each consensus step to submit the resulting on-chain tx (commit vote, reveal vote, proposed receipt) before the slot closes. Tune to underlying settlement chain latency. Default 30s. diff --git a/content/validators/docker-compose.yaml b/content/validators/docker-compose.yaml index 77c2110a..e4b2452d 100644 --- a/content/validators/docker-compose.yaml +++ b/content/validators/docker-compose.yaml @@ -23,6 +23,8 @@ services: - "${NODE_OPS_PORT:-9153}:9153" volumes: - ${NODE_CONFIG_PATH:-./configs/node/config.yaml}:/app/configs/node/config.yaml:ro + # The trusted protocol manifest of the network; consensus.manifestpath names this path. + - ${NODE_MANIFEST_PATH:-./configs/node/protocol-manifest.json}:/app/configs/node/protocol-manifest.json:ro - ${NODE_DATA_PATH:-./data}:/app/data - ./genvm-module-web-docker.yaml:/app/third_party/genvm/config/genvm-module-web.yaml - /var/run/docker.sock:/var/run/docker.sock:ro # required for webdriver metrics collection diff --git a/package.json b/package.json index cea35c41..01e3ca5d 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,8 @@ "version": "0.0.1", "description": "GenLayer documentation", "scripts": { - "dev": "npm run node-generate-changelog && npm run node-update-setup-guide && npm run node-update-config && npm run node-update-docker-compose && npm run node-update-monitoring-docker-compose && npm run node-update-monitoring-alloy-config && npm run node-update-greybox && npm run node-generate-api-docs && node scripts/generate-full-docs.js && node scripts/check-llm-exports.js && next dev", - "build": "npm run check:protocol-docs && npm run check:v06-release-docs && npm run node-generate-changelog && npm run node-update-setup-guide && npm run node-update-config && npm run node-update-docker-compose && npm run node-update-monitoring-docker-compose && npm run node-update-monitoring-alloy-config && npm run node-update-greybox && npm run node-generate-api-docs && node scripts/generate-full-docs.js && node scripts/check-llm-exports.js && next build", + "dev": "npm run node-generate-changelog && npm run node-update-setup-guide && npm run node-update-config && npm run node-update-docker-compose && npm run node-update-monitoring-docker-compose && npm run node-update-monitoring-alloy-config && npm run node-update-greybox && npm run node-publish-manifests && npm run node-generate-api-docs && node scripts/generate-full-docs.js && node scripts/check-llm-exports.js && next dev", + "build": "npm run check:protocol-docs && npm run check:v06-release-docs && npm run node-generate-changelog && npm run node-update-setup-guide && npm run node-update-config && npm run node-update-docker-compose && npm run node-update-monitoring-docker-compose && npm run node-update-monitoring-alloy-config && npm run node-update-greybox && npm run node-publish-manifests && npm run node-generate-api-docs && node scripts/generate-full-docs.js && node scripts/check-llm-exports.js && next build", "start": "next start", "test:e2e": "playwright test", "generate-sitemap": "node scripts/generate-sitemap-xml.js", @@ -17,7 +17,8 @@ "node-update-docker-compose": "node scripts/update-docker-compose-in-setup-guide.js", "node-update-monitoring-docker-compose": "node scripts/update-alloy-in-monitoring.js", "node-update-monitoring-alloy-config": "node scripts/update-alloy-config-in-monitoring.js", - "node-update-greybox": "node scripts/update-greybox-in-genvm-config.js" + "node-update-greybox": "node scripts/update-greybox-in-genvm-config.js", + "node-publish-manifests": "node scripts/publish-network-manifests.js" }, "repository": { "type": "git", diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index e9bdd6eb..380eaf8f 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -277,7 +277,7 @@ rollup: consensus: # Clarke Dev template: the deployment-specific fields are intentionally empty. consensusaddress: "" # AddressManager contract address for this deployment. - manifestpath: "" # Trusted producer protocol manifest for this deployment. Bradbury: configs/node/bradbury.protocol-manifest.json (ships in the release package); a dev-env deploy places its own at configs/node/protocol-manifest.json. Relative paths resolve against the node root. A mismatch with consensusaddress is refused at startup. + manifestpath: "configs/node/protocol-manifest.json" # Trusted producer protocol manifest for this deployment: save your network's manifest (from its documentation) at this path; a dev-env deploy places its own here too. Relative paths resolve against the node root. A mismatch with consensusaddress is refused at startup. chainid: "" # DECIMAL chain id this deployment was made on (not hex, no leading zeros). Held independently of the manifest so a manifest describing another deployment is caught. # genesis: 47930 # (Optional) Genesis block number for this consensus deployment. If not provided, it will be auto-detected by searching for the first log from the ConsensusMain contract. # chain_submit_buffer: "30s" # (Optional) Slack reserved at the end of each consensus step to submit the resulting on-chain tx (commit vote, reveal vote, proposed receipt) before the slot closes. Tune to underlying settlement chain latency. Default 30s. @@ -462,16 +462,17 @@ metrics: Replace the matching sections of your `config.yaml` with the block for the network you want to join. Replace each top-level section as a whole rather than adding a second `rollup:` or `consensus:` key. -Since v0.6, the node refuses to start unless `consensus.manifestpath` points at the trusted `protocol-manifest.json` for the deployment. The release ships the Bradbury manifest at `configs/node/bradbury.protocol-manifest.json`, and the Bradbury block below already points `manifestpath` at it. Relative paths resolve against the node root. At startup, the node also refuses a manifest whose registry does not match `consensus.consensusaddress`, or whose chain does not match `consensus.chainid`. +Since v0.6, the node refuses to start unless `consensus.manifestpath` points at the trusted protocol manifest of the network. The release package contains no network files: download the manifest for your network from this page and save it at `configs/node/protocol-manifest.json`, the path `manifestpath` names. Relative paths resolve against the node root. At startup, the node also refuses a manifest whose registry does not match `consensus.consensusaddress`, or whose chain does not match `consensus.chainid`. ##### Testnet Bradbury ```yaml copy # Bradbury Phase 2 network configuration (GenLayer testnet, chain id 4221). # -# Put these values into configs/node/config.yaml. They come from the -# genlayer-consensus deployment record deployments/bradbury-phase-2.json and its -# sealed protocol manifest, which is configs/node/bradbury.protocol-manifest.json. +# Put these values into configs/node/config.yaml, and save the Bradbury protocol +# manifest (bradbury.protocol-manifest.json) as configs/node/protocol-manifest.json. +# They come from the genlayer-consensus deployment record +# deployments/bradbury-phase-2.json and its sealed protocol manifest. # rollup configuration rollup: @@ -488,11 +489,20 @@ rollup: consensus: consensusaddress: "0x3F71AE080BD6270A91dBcc7960d88797828538E0" # AddressManager contract address. The node names the network bradbury-phase2 from it. chainid: "4221" # DECIMAL chain id of this deployment. - manifestpath: "configs/node/bradbury.protocol-manifest.json" # Trusted protocol manifest of this deployment; a relative path resolves against the node root. + manifestpath: "configs/node/protocol-manifest.json" # Where bradbury.protocol-manifest.json is saved; a relative path resolves against the node root. genesis: 47930 # Genesis block number for this consensus deployment (AddressManager deployment block). ``` +**Protocol manifest.** Download the Bradbury manifest and save it as `configs/node/protocol-manifest.json`. Run this from the node directory (the extracted release, or the directory with your `docker-compose.yaml`): + +```sh copy +mkdir -p configs/node +curl -fsSL https://docs.genlayer.com/validators/bradbury.protocol-manifest.json -o configs/node/protocol-manifest.json +``` + +The binary reads it from that path. Docker Compose mounts the same file into the container (`NODE_MANIFEST_PATH`, default `./configs/node/protocol-manifest.json`). + **WebSocket API key.** The Bradbury HTTPS RPC needs no key, but its WebSocket endpoint (hosted by Conduit) rejects connections without an API key. 1. Create a key at [app.conduit.xyz/rpc-keys](https://app.conduit.xyz/rpc-keys). @@ -739,6 +749,8 @@ services: - "${NODE_OPS_PORT:-9153}:9153" volumes: - ${NODE_CONFIG_PATH:-./configs/node/config.yaml}:/app/configs/node/config.yaml:ro + # The trusted protocol manifest of the network; consensus.manifestpath names this path. + - ${NODE_MANIFEST_PATH:-./configs/node/protocol-manifest.json}:/app/configs/node/protocol-manifest.json:ro - ${NODE_DATA_PATH:-./data}:/app/data - ./genvm-module-web-docker.yaml:/app/third_party/genvm/config/genvm-module-web.yaml - /var/run/docker.sock:/var/run/docker.sock:ro # required for webdriver metrics collection @@ -804,6 +816,9 @@ NODE_PASSWORD=12345678 # Path to node configuration file NODE_CONFIG_PATH=./configs/node/config.yaml +# Path to the trusted protocol manifest of your network +NODE_MANIFEST_PATH=./configs/node/protocol-manifest.json + # Path to node data directory (for persistence) NODE_DATA_PATH=./data diff --git a/scripts/publish-network-manifests.js b/scripts/publish-network-manifests.js new file mode 100644 index 00000000..1cdbf36f --- /dev/null +++ b/scripts/publish-network-manifests.js @@ -0,0 +1,37 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +/** + * Publish the network protocol manifests synced from genlayer-node as static + * downloads. content/validators/.protocol-manifest.json is served at + * /validators/.protocol-manifest.json. The public copies are build + * output (gitignored). + */ +const MANIFESTS = ['bradbury.protocol-manifest.json']; + +function publishNetworkManifests() { + const sourceDir = path.join(process.cwd(), 'content/validators'); + const targetDir = path.join(process.cwd(), 'public/validators'); + fs.mkdirSync(targetDir, { recursive: true }); + + for (const name of MANIFESTS) { + const source = path.join(sourceDir, name); + if (!fs.existsSync(source)) { + console.error(`Manifest ${source} does not exist`); + process.exitCode = 1; + continue; + } + // Fail the build on a manifest that is not valid JSON. + JSON.parse(fs.readFileSync(source, 'utf8')); + fs.copyFileSync(source, path.join(targetDir, name)); + console.log(`Published ${name} to public/validators/`); + } +} + +if (require.main === module) { + publishNetworkManifests(); +} + +module.exports = { publishNetworkManifests }; From 724ddb7f5427811de8657c251057e751162462a5 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 17:03:16 +0200 Subject: [PATCH 11/39] docs(validators): drop internal deployment name from the config template comment Matches genlayer-node#2267. Co-Authored-By: Claude Opus 5.5 --- content/validators/config.yaml | 2 +- pages/validators/setup-guide.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/content/validators/config.yaml b/content/validators/config.yaml index b146f2d8..7d88fdce 100644 --- a/content/validators/config.yaml +++ b/content/validators/config.yaml @@ -5,7 +5,7 @@ rollup: provider: "TODO: Set your GenLayer Chain ZKSync provider" # Rollup operator name; surfaces as the "provider" label on RPC-driven metrics. Set explicitly per deployment. # consensus contracts configuration consensus: - # Clarke Dev template: the deployment-specific fields are intentionally empty. + # The deployment-specific fields are empty: take them from your network's documentation. consensusaddress: "" # AddressManager contract address for this deployment. manifestpath: "configs/node/protocol-manifest.json" # Trusted producer protocol manifest for this deployment: save your network's manifest (from its documentation) at this path; a dev-env deploy places its own here too. Relative paths resolve against the node root. A mismatch with consensusaddress is refused at startup. chainid: "" # DECIMAL chain id this deployment was made on (not hex, no leading zeros). Held independently of the manifest so a manifest describing another deployment is caught. diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index 380eaf8f..624fff75 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -275,7 +275,7 @@ rollup: provider: "TODO: Set your GenLayer Chain ZKSync provider" # Rollup operator name; surfaces as the "provider" label on RPC-driven metrics. Set explicitly per deployment. # consensus contracts configuration consensus: - # Clarke Dev template: the deployment-specific fields are intentionally empty. + # The deployment-specific fields are empty: take them from your network's documentation. consensusaddress: "" # AddressManager contract address for this deployment. manifestpath: "configs/node/protocol-manifest.json" # Trusted producer protocol manifest for this deployment: save your network's manifest (from its documentation) at this path; a dev-env deploy places its own here too. Relative paths resolve against the node root. A mismatch with consensusaddress is refused at startup. chainid: "" # DECIMAL chain id this deployment was made on (not hex, no leading zeros). Held independently of the manifest so a manifest describing another deployment is caught. From 21dcbc30d355d2c2567531864c8d190bfe15defe Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 17:31:56 +0200 Subject: [PATCH 12/39] ci(node-sync): one sync list, loud generators, and a local sync test - .github/scripts/node-sync-params.sh is the one list of synced node files. The workflow takes its matrix and sparse checkout from it; the composite action and the local run share prepare-sync-source.sh and run-sync-matrix.sh. - New sync type env_example: release/.env.example -> content/validators/env.example, pasted into the setup guide's .env block (update-env-in-setup-guide.js). - Generators exit non-zero when they cannot find their source or block. The greybox generator had been a silent no-op since its heading was renamed. - Generators insert content literally: string replacements turned the compose file's `$$` escapes into `$`, so the guide showed a compose file Docker Compose rejects. - The greybox guide is its own generated page (pages/validators/greybox-setup.mdx). - scripts/check-node-sync.js: generated blocks hold their source verbatim, sanitized files are sanitized, the manifest matches bradbury.yaml, no Asimov, no changelog entry removed vs origin/main. Runs on every PR, in build, and in the sync workflow before it opens a PR. - scripts/simulate-node-sync.sh : the workflow's sync run locally. - versionChannel setting (scripts/setup-guide-versions.config.js, default stable) with tests for both values; the list command sorts by version. - The node snapshot upload keeps release/.env.example and drops .git; the node checkout does not persist credentials. Co-Authored-By: Claude Opus 5.5 --- .github/actions/sync-files/action.yml | 76 +------- .github/scripts/doc-generator.sh | 118 ++++-------- .github/scripts/node-sync-params.sh | 64 +++++++ .github/scripts/prepare-sync-source.sh | 36 ++++ .github/scripts/run-sync-matrix.sh | 36 ++++ .github/workflows/check-node-docs-sync.yml | 21 ++- .github/workflows/sync-docs-from-node.yml | 129 +++++-------- package.json | 11 +- scripts/check-node-sync.js | 139 ++++++++++++++ scripts/generate-api-docs.js | 21 ++- scripts/generate-changelog.js | 5 +- scripts/setup-guide-versions.config.js | 19 ++ scripts/simulate-node-sync.sh | 52 +++++ scripts/test-setup-guide-versions.js | 111 +++++++++++ scripts/update-alloy-config-in-monitoring.js | 11 +- scripts/update-alloy-in-monitoring.js | 15 +- scripts/update-config-in-setup-guide.js | 13 +- .../update-docker-compose-in-setup-guide.js | 13 +- scripts/update-env-in-setup-guide.js | 47 +++++ scripts/update-greybox-in-genvm-config.js | 53 ++---- scripts/update-setup-guide-versions.js | 178 ++++++++++-------- 21 files changed, 772 insertions(+), 396 deletions(-) create mode 100755 .github/scripts/node-sync-params.sh create mode 100755 .github/scripts/prepare-sync-source.sh create mode 100755 .github/scripts/run-sync-matrix.sh create mode 100644 scripts/check-node-sync.js create mode 100644 scripts/setup-guide-versions.config.js create mode 100755 scripts/simulate-node-sync.sh create mode 100644 scripts/test-setup-guide-versions.js create mode 100644 scripts/update-env-in-setup-guide.js diff --git a/.github/actions/sync-files/action.yml b/.github/actions/sync-files/action.yml index d8eade87..c1b32f7a 100644 --- a/.github/actions/sync-files/action.yml +++ b/.github/actions/sync-files/action.yml @@ -37,68 +37,12 @@ outputs: runs: using: 'composite' steps: - - name: Prepare config file - if: inputs.type == 'config' - shell: bash - run: | - # For config type, handle special case: - # Copy config.yaml.example to temp location as config.yaml and sanitize - - if [[ -f "${{ inputs.source_path }}" ]]; then - # Create temp directory - TEMP_DIR=$(mktemp -d) - echo "TEMP_CONFIG_DIR=$TEMP_DIR" >> $GITHUB_ENV - - # Copy and rename config.yaml.example to config.yaml - cp "${{ inputs.source_path }}" "$TEMP_DIR/config.yaml" - - # Sanitize: replace RPC URLs with placeholders and remove dev section - bash .github/scripts/sanitize-config.sh "$TEMP_DIR/config.yaml" - - # Update source path for sync - echo "CONFIG_SOURCE=$TEMP_DIR/config.yaml" >> $GITHUB_ENV - else - echo "Config source file not found: ${{ inputs.source_path }}" - exit 1 - fi - - - name: Prepare docker-compose file - if: inputs.type == 'docker_compose' - shell: bash - run: | - # For docker_compose type, handle special case: - # Copy and sanitize (remove alloy service and volumes) - - if [[ -f "${{ inputs.source_path }}" ]]; then - # Create temp directory - TEMP_DIR=$(mktemp -d) - echo "TEMP_DOCKER_COMPOSE_DIR=$TEMP_DIR" >> $GITHUB_ENV - - # Copy docker-compose.yaml - cp "${{ inputs.source_path }}" "$TEMP_DIR/docker-compose.yaml" - - # Sanitize: remove alloy service and volumes section using yq - bash .github/scripts/sanitize-docker-compose.sh "$TEMP_DIR/docker-compose.yaml" - - # Update source path for sync - echo "DOCKER_COMPOSE_SOURCE=$TEMP_DIR/docker-compose.yaml" >> $GITHUB_ENV - else - echo "Docker compose source file not found: ${{ inputs.source_path }}" - exit 1 - fi - - name: Sync files id: sync shell: bash run: | - # Use prepared source for config/docker_compose types, otherwise use original source - if [[ "${{ inputs.type }}" == "config" ]] && [[ -n "$CONFIG_SOURCE" ]]; then - SOURCE_PATH="$CONFIG_SOURCE" - elif [[ "${{ inputs.type }}" == "docker_compose" ]] && [[ -n "$DOCKER_COMPOSE_SOURCE" ]]; then - SOURCE_PATH="$DOCKER_COMPOSE_SOURCE" - else - SOURCE_PATH="${{ inputs.source_path }}" - fi + # config and docker_compose sync a sanitized copy; every other type syncs as is. + SOURCE_PATH=$(bash .github/scripts/prepare-sync-source.sh "${{ inputs.type }}" "${{ inputs.source_path }}") ${{ github.action_path }}/sync.sh \ "${{ inputs.type }}" \ @@ -107,22 +51,6 @@ runs: "${{ inputs.target_path }}" \ "${{ inputs.filter_pattern }}" \ "${{ inputs.exclude_files }}" - - - name: Cleanup config temp directory - if: inputs.type == 'config' && always() - shell: bash - run: | - if [[ -n "$TEMP_CONFIG_DIR" ]] && [[ -d "$TEMP_CONFIG_DIR" ]]; then - rm -rf "$TEMP_CONFIG_DIR" - fi - - - name: Cleanup docker-compose temp directory - if: inputs.type == 'docker_compose' && always() - shell: bash - run: | - if [[ -n "$TEMP_DOCKER_COMPOSE_DIR" ]] && [[ -d "$TEMP_DOCKER_COMPOSE_DIR" ]]; then - rm -rf "$TEMP_DOCKER_COMPOSE_DIR" - fi - name: Upload artifacts uses: actions/upload-artifact@v4 diff --git a/.github/scripts/doc-generator.sh b/.github/scripts/doc-generator.sh index b1de5586..52ebd472 100755 --- a/.github/scripts/doc-generator.sh +++ b/.github/scripts/doc-generator.sh @@ -1,98 +1,48 @@ #!/bin/bash set -euo pipefail -# Documentation generation wrapper -# Runs all the npm scripts for doc generation with proper error handling +# Runs every generator that turns synced node files into pages. +# The one list of generators: package.json (dev, build), check-node-docs-sync.yml, +# sync-docs-from-node.yml and scripts/simulate-node-sync.sh all call this script. +# Each generator exits non-zero when it cannot find its source or target block. + +GENERATORS=( + node-generate-changelog + node-update-setup-guide + node-update-config + node-update-docker-compose + node-update-env + node-update-monitoring-docker-compose + node-update-monitoring-alloy-config + node-update-greybox + node-publish-manifests + node-generate-api-docs +) run_doc_generation() { echo "Running documentation generation scripts..." - - # Track success - local failed=false - - # Run each generation script - echo "::group::Running node-generate-changelog" - if npm run node-generate-changelog; then - echo "✅ Generated changelog" - else - echo "❌ node-generate-changelog failed" - failed=true - fi - echo "::endgroup::" - - echo "::group::Running node-update-setup-guide" - if npm run node-update-setup-guide; then - echo "✅ Updated setup guide versions" - else - echo "❌ node-update-setup-guide failed" - failed=true - fi - echo "::endgroup::" - - echo "::group::Running node-update-config" - if npm run node-update-config; then - echo "✅ Updated config in setup guide" - else - echo "❌ node-update-config failed" - failed=true - fi - echo "::endgroup::" - - echo "::group::Running node-update-docker-compose" - if npm run node-update-docker-compose; then - echo "✅ Updated docker-compose in setup guide" - else - echo "❌ node-update-docker-compose failed" - failed=true - fi - echo "::endgroup::" - - echo "::group::Running node-update-monitoring-docker-compose" - if npm run node-update-monitoring-docker-compose; then - echo "✅ Updated alloy service in monitoring page" - else - echo "❌ node-update-monitoring-docker-compose failed" - failed=true - fi - echo "::endgroup::" - - echo "::group::Running node-update-monitoring-alloy-config" - if npm run node-update-monitoring-alloy-config; then - echo "✅ Updated alloy config in monitoring page" - else - echo "❌ node-update-monitoring-alloy-config failed" - failed=true - fi - echo "::endgroup::" - - echo "::group::Running node-update-greybox" - if npm run node-update-greybox; then - echo "✅ Updated greybox section in genvm configuration" - else - echo "❌ node-update-greybox failed" - failed=true - fi - echo "::endgroup::" - - echo "::group::Running node-generate-api-docs" - if npm run node-generate-api-docs; then - echo "✅ Generated API documentation" - else - echo "❌ node-generate-api-docs failed" - failed=true - fi - echo "::endgroup::" - - if [[ "$failed" == "true" ]]; then - echo "❌ Some documentation generation scripts failed" + local failed=() + + for script in "${GENERATORS[@]}"; do + echo "::group::Running $script" + if npm run --silent "$script"; then + echo "✅ $script" + else + echo "❌ $script failed" + failed+=("$script") + fi + echo "::endgroup::" + done + + if [[ ${#failed[@]} -gt 0 ]]; then + echo "❌ Failed generators: ${failed[*]}" return 1 fi - + echo "✅ All documentation generation scripts completed successfully" - return 0 } # Run if called directly if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then run_doc_generation -fi \ No newline at end of file +fi diff --git a/.github/scripts/node-sync-params.sh b/.github/scripts/node-sync-params.sh new file mode 100755 index 00000000..41754fdc --- /dev/null +++ b/.github/scripts/node-sync-params.sh @@ -0,0 +1,64 @@ +#!/bin/bash +set -euo pipefail + +# The one list of files synced from genlayer-node. The sync workflow and +# scripts/simulate-node-sync.sh both read it, so a local run copies exactly +# what CI copies. +# +# Usage: +# node-sync-params.sh --list sync types, one per line +# node-sync-params.sh --sources node repo paths to fetch, one per line +# node-sync-params.sh title=, source_path=, target_path=, +# filter_pattern= lines (GITHUB_OUTPUT format) +# +# Environment: +# SOURCE_ROOT where the node snapshot is checked out (default: source-repo) +# CHANGELOG_PATH, API_GEN_PATH, API_DEBUG_PATH, API_OPS_PATH, +# API_GEN_REGEX, API_DEBUG_REGEX: the workflow_dispatch overrides + +SOURCE_ROOT="${SOURCE_ROOT:-source-repo}" +CHANGELOG_PATH="${CHANGELOG_PATH:-docs/changelog}" +API_GEN_PATH="${API_GEN_PATH:-docs/api/rpc}" +API_DEBUG_PATH="${API_DEBUG_PATH:-docs/api/rpc}" +API_OPS_PATH="${API_OPS_PATH:-docs/api/ops}" +API_GEN_REGEX="${API_GEN_REGEX:-gen_(?!dbg_).*}" +API_DEBUG_REGEX="${API_DEBUG_REGEX:-gen_dbg_.*}" + +# type|title|node path|docs target|filter +TABLE="changelog|Changelog|${CHANGELOG_PATH}|content/validators/changelog|.* +config|Config File|configs/node/config.yaml.example|content/validators/config.yaml|.* +config_bradbury|Config File (Bradbury)|configs/node/bradbury.yaml.example|content/validators/bradbury.yaml|.* +config_bradbury_manifest|Protocol Manifest (Bradbury)|configs/node/bradbury.protocol-manifest.json|content/validators/bradbury.protocol-manifest.json|.* +docker_compose|Docker Compose File|release/docker-compose.yaml|content/validators/docker-compose.yaml|.* +docker_compose_monitoring|Docker Compose File (Monitoring)|release/docker-compose.yaml|content/validators/docker-compose-monitoring.yaml|.* +env_example|Environment File (.env.example)|release/.env.example|content/validators/env.example|.* +alloy_config|Alloy Config File|release/alloy-config.river|content/validators/alloy-config.river|.* +greybox_setup|Greybox Setup Guide|release/greybox-setup-guide.md|content/validators/greybox-setup-guide.md|.* +api_gen|API Gen Methods|${API_GEN_PATH}|pages/api-references/genlayer-node/gen|${API_GEN_REGEX} +api_debug|API Debug Methods|${API_DEBUG_PATH}|pages/api-references/genlayer-node/debug|${API_DEBUG_REGEX} +api_ops|API Ops Methods|${API_OPS_PATH}|pages/api-references/genlayer-node/ops|.*" + +case "${1:-}" in + --list) + cut -d'|' -f1 <<< "$TABLE" + ;; + --sources) + cut -d'|' -f3 <<< "$TABLE" | sort -u + ;; + "") + echo "Usage: $0 --list | --sources | " >&2 + exit 1 + ;; + *) + row=$(awk -F'|' -v t="$1" '$1 == t' <<< "$TABLE") + if [[ -z "$row" ]]; then + echo "Unknown sync type: $1" >&2 + exit 1 + fi + IFS='|' read -r _ title src target filter <<< "$row" + echo "title=$title" + echo "source_path=$SOURCE_ROOT/$src" + echo "target_path=$target" + echo "filter_pattern=$filter" + ;; +esac diff --git a/.github/scripts/prepare-sync-source.sh b/.github/scripts/prepare-sync-source.sh new file mode 100755 index 00000000..01470fec --- /dev/null +++ b/.github/scripts/prepare-sync-source.sh @@ -0,0 +1,36 @@ +#!/bin/bash +set -euo pipefail + +# Prints the path to sync from for one sync type. Types whose node file must be +# cleaned before it is published (config, docker_compose) get a sanitized copy in +# a temp directory; every other type syncs the node path as is. +# Used by .github/actions/sync-files and scripts/simulate-node-sync.sh. +# +# Usage: prepare-sync-source.sh + +TYPE="$1" +SOURCE="$2" +SCRIPTS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +if [[ ! -e "$SOURCE" ]]; then + echo "Source not found: $SOURCE" >&2 + exit 1 +fi + +case "$TYPE" in + config) + dir=$(mktemp -d) + cp "$SOURCE" "$dir/config.yaml" + bash "$SCRIPTS/sanitize-config.sh" "$dir/config.yaml" >&2 + echo "$dir/config.yaml" + ;; + docker_compose) + dir=$(mktemp -d) + cp "$SOURCE" "$dir/docker-compose.yaml" + bash "$SCRIPTS/sanitize-docker-compose.sh" "$dir/docker-compose.yaml" >&2 + echo "$dir/docker-compose.yaml" + ;; + *) + echo "$SOURCE" + ;; +esac diff --git a/.github/scripts/run-sync-matrix.sh b/.github/scripts/run-sync-matrix.sh new file mode 100755 index 00000000..0769bd52 --- /dev/null +++ b/.github/scripts/run-sync-matrix.sh @@ -0,0 +1,36 @@ +#!/bin/bash +set -euo pipefail + +# Runs every sync type of .github/scripts/node-sync-params.sh against a node +# snapshot, with the same prepare and sync scripts as the CI matrix. +# Writes into the current directory (the docs repo root). +# +# Usage: run-sync-matrix.sh +# Needs: bash, perl, GNU sed, yq v4. + +SNAPSHOT="$1" +SCRIPTS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SYNC="$SCRIPTS/../actions/sync-files/sync.sh" +export GITHUB_OUTPUT +GITHUB_OUTPUT=$(mktemp) + +failed=0 +for type in $(bash "$SCRIPTS/node-sync-params.sh" --list); do + params=$(SOURCE_ROOT="$SNAPSHOT" bash "$SCRIPTS/node-sync-params.sh" "$type") + title=$(sed -n 's/^title=//p' <<< "$params") + source_path=$(sed -n 's/^source_path=//p' <<< "$params") + target_path=$(sed -n 's/^target_path=//p' <<< "$params") + filter=$(sed -n 's/^filter_pattern=//p' <<< "$params") + + echo "::: $type" + if ! src=$(bash "$SCRIPTS/prepare-sync-source.sh" "$type" "$source_path"); then + failed=1 + continue + fi + bash "$SYNC" "$type" "$title" "$src" "$target_path" "$filter" "README,CHANGELOG,.gitignore,.gitkeep" \ + | grep -E '^(Added|Updated|Deleted):' || true +done + +# sync.sh leaves per-type report artifacts for the CI upload step. +rm -rf artifacts "$GITHUB_OUTPUT" +exit "$failed" diff --git a/.github/workflows/check-node-docs-sync.yml b/.github/workflows/check-node-docs-sync.yml index b8feca3c..69bd61ce 100644 --- a/.github/workflows/check-node-docs-sync.yml +++ b/.github/workflows/check-node-docs-sync.yml @@ -12,6 +12,9 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + # check-node-sync compares the changelog with origin/main. + fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v4 @@ -22,16 +25,16 @@ jobs: - name: Install dependencies run: npm ci + - name: Test the setup guide version channel + run: npm run test:setup-guide-versions + - name: Run documentation sync scripts - run: | - npm run node-generate-changelog - npm run node-update-setup-guide - npm run node-update-config - npm run node-update-docker-compose - npm run node-update-monitoring-docker-compose - npm run node-update-monitoring-alloy-config - npm run node-update-greybox - npm run node-generate-api-docs + # The one generator list (.github/scripts/doc-generator.sh). Fails when a + # generator cannot find its source or its block. + run: npm run node-generate-all + + - name: Check synced content reaches the pages verbatim + run: npm run check:node-sync - name: Check for uncommitted changes run: | diff --git a/.github/workflows/sync-docs-from-node.yml b/.github/workflows/sync-docs-from-node.yml index 838f2f0f..3d15ee26 100644 --- a/.github/workflows/sync-docs-from-node.yml +++ b/.github/workflows/sync-docs-from-node.yml @@ -50,6 +50,7 @@ jobs: environment: Node Sync outputs: version: ${{ steps.final_version.outputs.version }} + sync_types: ${{ steps.sync_list.outputs.sync_types }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -95,32 +96,45 @@ jobs: echo "version=$VERSION" >> $GITHUB_OUTPUT echo "✅ Will sync version: $VERSION" + - name: List synced node paths + id: sync_list + env: + CHANGELOG_PATH: ${{ github.event.inputs.changelog_path || github.event.client_payload.changelog_path || 'docs/changelog' }} + API_GEN_PATH: ${{ github.event.inputs.api_gen_path || github.event.client_payload.api_gen_path || 'docs/api/rpc' }} + API_DEBUG_PATH: ${{ github.event.inputs.api_debug_path || github.event.client_payload.api_debug_path || 'docs/api/rpc' }} + API_OPS_PATH: ${{ github.event.inputs.api_ops_path || github.event.client_payload.api_ops_path || 'docs/api/ops' }} + run: | + # .github/scripts/node-sync-params.sh is the one list of synced files. + { + echo "sources<> "$GITHUB_OUTPUT" + - name: Clone source repository uses: actions/checkout@v4 with: repository: genlayerlabs/genlayer-node token: ${{ steps.app-token.outputs.token }} fetch-depth: 1 - sparse-checkout: | - docs/changelog - docs/api/rpc - docs/api/ops - configs/node/config.yaml.example - configs/node/bradbury.yaml.example - configs/node/bradbury.protocol-manifest.json - release/docker-compose.yaml - release/alloy-config.river - release/greybox-setup-guide.md + sparse-checkout: ${{ steps.sync_list.outputs.sources }} sparse-checkout-cone-mode: false path: source-repo + # Read-only snapshot: do not leave the app token in source-repo/.git/config. + persist-credentials: false ref: ${{ steps.final_version.outputs.version }} - name: Upload source snapshot uses: actions/upload-artifact@v4 with: name: node-source - path: source-repo/ - include-hidden-files: false + # release/.env.example is a dotfile, so hidden files are included. + # .git is excluded: it is not synced content. + path: | + source-repo/ + !source-repo/.git + include-hidden-files: true retention-days: 1 sync-files: @@ -129,7 +143,7 @@ jobs: needs: prepare strategy: matrix: - sync_type: [changelog, config, config_bradbury, config_bradbury_manifest, docker_compose, docker_compose_monitoring, alloy_config, greybox_setup, api_gen, api_debug, api_ops] + sync_type: ${{ fromJSON(needs.prepare.outputs.sync_types) }} fail-fast: false steps: - name: Checkout documentation repository @@ -151,75 +165,14 @@ jobs: - name: Set sync parameters id: set_params - run: | - case "${{ matrix.sync_type }}" in - "changelog") - echo "title=Changelog" >> $GITHUB_OUTPUT - echo "source_path=source-repo/${{ github.event.inputs.changelog_path || github.event.client_payload.changelog_path || 'docs/changelog' }}" >> $GITHUB_OUTPUT - echo "target_path=content/validators/changelog" >> $GITHUB_OUTPUT - echo "filter_pattern=.*" >> $GITHUB_OUTPUT - ;; - "config") - echo "title=Config File" >> $GITHUB_OUTPUT - echo "source_path=source-repo/configs/node/config.yaml.example" >> $GITHUB_OUTPUT - echo "target_path=content/validators/config.yaml" >> $GITHUB_OUTPUT - echo "filter_pattern=.*" >> $GITHUB_OUTPUT - ;; - "config_bradbury") - echo "title=Config File (Bradbury)" >> $GITHUB_OUTPUT - echo "source_path=source-repo/configs/node/bradbury.yaml.example" >> $GITHUB_OUTPUT - echo "target_path=content/validators/bradbury.yaml" >> $GITHUB_OUTPUT - echo "filter_pattern=.*" >> $GITHUB_OUTPUT - ;; - "config_bradbury_manifest") - echo "title=Protocol Manifest (Bradbury)" >> $GITHUB_OUTPUT - echo "source_path=source-repo/configs/node/bradbury.protocol-manifest.json" >> $GITHUB_OUTPUT - echo "target_path=content/validators/bradbury.protocol-manifest.json" >> $GITHUB_OUTPUT - echo "filter_pattern=.*" >> $GITHUB_OUTPUT - ;; - "docker_compose") - echo "title=Docker Compose File" >> $GITHUB_OUTPUT - echo "source_path=source-repo/release/docker-compose.yaml" >> $GITHUB_OUTPUT - echo "target_path=content/validators/docker-compose.yaml" >> $GITHUB_OUTPUT - echo "filter_pattern=.*" >> $GITHUB_OUTPUT - ;; - "docker_compose_monitoring") - echo "title=Docker Compose File (Monitoring)" >> $GITHUB_OUTPUT - echo "source_path=source-repo/release/docker-compose.yaml" >> $GITHUB_OUTPUT - echo "target_path=content/validators/docker-compose-monitoring.yaml" >> $GITHUB_OUTPUT - echo "filter_pattern=.*" >> $GITHUB_OUTPUT - ;; - "alloy_config") - echo "title=Alloy Config File" >> $GITHUB_OUTPUT - echo "source_path=source-repo/release/alloy-config.river" >> $GITHUB_OUTPUT - echo "target_path=content/validators/alloy-config.river" >> $GITHUB_OUTPUT - echo "filter_pattern=.*" >> $GITHUB_OUTPUT - ;; - "greybox_setup") - echo "title=Greybox Setup Guide" >> $GITHUB_OUTPUT - echo "source_path=source-repo/release/greybox-setup-guide.md" >> $GITHUB_OUTPUT - echo "target_path=content/validators/greybox-setup-guide.md" >> $GITHUB_OUTPUT - echo "filter_pattern=.*" >> $GITHUB_OUTPUT - ;; - "api_gen") - echo "title=API Gen Methods" >> $GITHUB_OUTPUT - echo "source_path=source-repo/${{ github.event.inputs.api_gen_path || github.event.client_payload.api_gen_path || 'docs/api/rpc' }}" >> $GITHUB_OUTPUT - echo "target_path=pages/api-references/genlayer-node/gen" >> $GITHUB_OUTPUT - echo "filter_pattern=${{ github.event.inputs.api_gen_regex || github.event.client_payload.api_gen_regex || 'gen_(?!dbg_).*' }}" >> $GITHUB_OUTPUT - ;; - "api_debug") - echo "title=API Debug Methods" >> $GITHUB_OUTPUT - echo "source_path=source-repo/${{ github.event.inputs.api_debug_path || github.event.client_payload.api_debug_path || 'docs/api/rpc' }}" >> $GITHUB_OUTPUT - echo "target_path=pages/api-references/genlayer-node/debug" >> $GITHUB_OUTPUT - echo "filter_pattern=${{ github.event.inputs.api_debug_regex || github.event.client_payload.api_debug_regex || 'gen_dbg_.*' }}" >> $GITHUB_OUTPUT - ;; - "api_ops") - echo "title=API Ops Methods" >> $GITHUB_OUTPUT - echo "source_path=source-repo/${{ github.event.inputs.api_ops_path || github.event.client_payload.api_ops_path || 'docs/api/ops' }}" >> $GITHUB_OUTPUT - echo "target_path=pages/api-references/genlayer-node/ops" >> $GITHUB_OUTPUT - echo "filter_pattern=.*" >> $GITHUB_OUTPUT - ;; - esac + env: + CHANGELOG_PATH: ${{ github.event.inputs.changelog_path || github.event.client_payload.changelog_path || 'docs/changelog' }} + API_GEN_PATH: ${{ github.event.inputs.api_gen_path || github.event.client_payload.api_gen_path || 'docs/api/rpc' }} + API_DEBUG_PATH: ${{ github.event.inputs.api_debug_path || github.event.client_payload.api_debug_path || 'docs/api/rpc' }} + API_OPS_PATH: ${{ github.event.inputs.api_ops_path || github.event.client_payload.api_ops_path || 'docs/api/ops' }} + API_GEN_REGEX: ${{ github.event.inputs.api_gen_regex || github.event.client_payload.api_gen_regex || 'gen_(?!dbg_).*' }} + API_DEBUG_REGEX: ${{ github.event.inputs.api_debug_regex || github.event.client_payload.api_debug_regex || 'gen_dbg_.*' }} + run: bash .github/scripts/node-sync-params.sh "${{ matrix.sync_type }}" >> "$GITHUB_OUTPUT" - name: Sync files using composite action id: sync @@ -302,6 +255,8 @@ jobs: uses: actions/checkout@v4 with: token: ${{ secrets.GITHUB_TOKEN }} + # check-node-sync compares the changelog with origin/main. + fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v4 @@ -332,7 +287,11 @@ jobs: - name: Run documentation generation scripts id: generate run: | - .github/scripts/doc-generator.sh && echo "success=true" >> "$GITHUB_OUTPUT" + .github/scripts/doc-generator.sh + # No PR from a sync that breaks a page: generated blocks must hold + # their synced source verbatim, and no changelog entry may disappear. + npm run check:node-sync + echo "success=true" >> "$GITHUB_OUTPUT" - name: Copy sync reports for final artifact run: | @@ -536,7 +495,7 @@ jobs: echo "" >> $GITHUB_STEP_SUMMARY # Process each sync type report - for sync_type in changelog config config_bradbury config_bradbury_manifest docker_compose docker_compose_monitoring alloy_config greybox_setup api_gen api_debug api_ops; do + for sync_type in $(echo '${{ needs.prepare.outputs.sync_types }}' | jq -r '.[]'); do # Get proper title case "$sync_type" in "changelog") title="📝 Changelog Sync" ;; @@ -545,11 +504,13 @@ jobs: "config_bradbury_manifest") title="📜 Protocol Manifest Sync (Bradbury)" ;; "docker_compose") title="🐳 Docker Compose Sync" ;; "docker_compose_monitoring") title="🐳 Docker Compose Sync (Monitoring)" ;; + "env_example") title="🔑 Environment File Sync (.env.example)" ;; "alloy_config") title="📊 Alloy Config Sync" ;; "greybox_setup") title="🔧 Greybox Setup Guide Sync" ;; "api_gen") title="🔧 API Gen Methods Sync" ;; "api_debug") title="🐛 API Debug Methods Sync" ;; "api_ops") title="📊 API Ops Methods Sync" ;; + *) title="$sync_type" ;; esac echo "### $title" >> $GITHUB_STEP_SUMMARY diff --git a/package.json b/package.json index 01e3ca5d..08a9be6c 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,8 @@ "version": "0.0.1", "description": "GenLayer documentation", "scripts": { - "dev": "npm run node-generate-changelog && npm run node-update-setup-guide && npm run node-update-config && npm run node-update-docker-compose && npm run node-update-monitoring-docker-compose && npm run node-update-monitoring-alloy-config && npm run node-update-greybox && npm run node-publish-manifests && npm run node-generate-api-docs && node scripts/generate-full-docs.js && node scripts/check-llm-exports.js && next dev", - "build": "npm run check:protocol-docs && npm run check:v06-release-docs && npm run node-generate-changelog && npm run node-update-setup-guide && npm run node-update-config && npm run node-update-docker-compose && npm run node-update-monitoring-docker-compose && npm run node-update-monitoring-alloy-config && npm run node-update-greybox && npm run node-publish-manifests && npm run node-generate-api-docs && node scripts/generate-full-docs.js && node scripts/check-llm-exports.js && next build", + "dev": "npm run node-generate-all && node scripts/generate-full-docs.js && node scripts/check-llm-exports.js && next dev", + "build": "npm run check:protocol-docs && npm run check:v06-release-docs && npm run node-generate-all && npm run check:node-sync && node scripts/generate-full-docs.js && node scripts/check-llm-exports.js && next build", "start": "next start", "test:e2e": "playwright test", "generate-sitemap": "node scripts/generate-sitemap-xml.js", @@ -18,7 +18,12 @@ "node-update-monitoring-docker-compose": "node scripts/update-alloy-in-monitoring.js", "node-update-monitoring-alloy-config": "node scripts/update-alloy-config-in-monitoring.js", "node-update-greybox": "node scripts/update-greybox-in-genvm-config.js", - "node-publish-manifests": "node scripts/publish-network-manifests.js" + "node-update-env": "node scripts/update-env-in-setup-guide.js", + "node-publish-manifests": "node scripts/publish-network-manifests.js", + "node-generate-all": "bash .github/scripts/doc-generator.sh", + "node-simulate-sync": "bash scripts/simulate-node-sync.sh", + "check:node-sync": "node scripts/check-node-sync.js", + "test:setup-guide-versions": "node scripts/test-setup-guide-versions.js" }, "repository": { "type": "git", diff --git a/scripts/check-node-sync.js b/scripts/check-node-sync.js new file mode 100644 index 00000000..1d96116a --- /dev/null +++ b/scripts/check-node-sync.js @@ -0,0 +1,139 @@ +#!/usr/bin/env node + +// Checks that the files synced from genlayer-node reach the pages unchanged and +// that the sync kept its rules. Runs in CI on every PR (check-node-docs-sync.yml), +// at the end of scripts/simulate-node-sync.sh, and in the sync workflow. +// +// What it checks: +// - every synced target exists; +// - every generated page block contains its source file verbatim (catches +// generators that drop, duplicate or rewrite content, e.g. `$$` -> `$`); +// - sanitized files are sanitized (no live RPC URLs, no alloy service); +// - the Bradbury manifest is valid JSON and matches bradbury.yaml; +// - no Asimov references in validator and node API pages; +// - no changelog version disappears compared with BASE_REF (default origin/main). + +const fs = require('fs'); +const path = require('path'); +const { execFileSync } = require('child_process'); +const { extractAlloyBlock } = require('./update-alloy-in-monitoring'); + +const root = path.join(__dirname, '..'); +const read = (p) => fs.readFileSync(path.join(root, p), 'utf8'); +const exists = (p) => fs.existsSync(path.join(root, p)); +const failures = []; +const fail = (msg) => failures.push(msg); + +// 1. Every sync target exists. +const types = execFileSync('bash', ['.github/scripts/node-sync-params.sh', '--list'], { cwd: root, encoding: 'utf8' }) + .trim().split('\n'); +for (const type of types) { + const out = execFileSync('bash', ['.github/scripts/node-sync-params.sh', type], { cwd: root, encoding: 'utf8' }); + const target = out.match(/^target_path=(.*)$/m)[1]; + if (!exists(target)) fail(`${type}: synced target ${target} is missing`); +} + +// The workflow must take its matrix and sparse checkout from node-sync-params.sh. +const workflow = read('.github/workflows/sync-docs-from-node.yml'); +if (!/sync_type: \$\{\{ fromJSON\(needs\.prepare\.outputs\.sync_types\) \}\}/.test(workflow)) { + fail('sync-docs-from-node.yml: the matrix must come from node-sync-params.sh (needs.prepare.outputs.sync_types)'); +} +if (!workflow.includes('sparse-checkout: ${{ steps.sync_list.outputs.sources }}')) { + fail('sync-docs-from-node.yml: the sparse checkout must come from node-sync-params.sh --sources'); +} + +// 2. Generated blocks hold their source verbatim. +const blockAfter = (page, anchor, lang) => { + const content = read(page); + const i = content.indexOf(anchor); + if (i < 0) return null; + const open = content.indexOf('```' + lang, i); + if (open < 0) return null; + const start = content.indexOf('\n', open) + 1; + const end = content.indexOf('\n```', start); + return content.slice(start, end); +}; +const sameBlock = (label, page, anchor, lang, expected) => { + const block = blockAfter(page, anchor, lang); + if (block === null) { + fail(`${label}: no \`\`\`${lang} block after "${anchor}" in ${page}`); + } else if (block.trimEnd() !== expected.trimEnd()) { + const a = block.split('\n'); + const b = expected.trimEnd().split('\n'); + const line = a.findIndex((l, n) => l !== b[n]); + fail(`${label}: ${page} differs from its source at block line ${line + 1}:\n page: ${JSON.stringify(a[line])}\n source: ${JSON.stringify(b[line])}`); + } +}; + +const setup = 'pages/validators/setup-guide.mdx'; +const monitoring = 'pages/validators/monitoring.mdx'; +sameBlock('config', setup, 'You can use the following example configuration', 'yaml', read('content/validators/config.yaml')); +sameBlock('config_bradbury', setup, '##### Testnet Bradbury', 'yaml', read('content/validators/bradbury.yaml')); +sameBlock('docker_compose', setup, 'Create a `docker-compose.yaml` file with the following content:', 'yaml', read('content/validators/docker-compose.yaml')); +sameBlock('env_example', setup, 'Set the required environment variables in a `.env` file:', 'env', read('content/validators/env.example')); +sameBlock('alloy_config', monitoring, 'Create or update ./alloy-config.river', 'river', read('content/validators/alloy-config.river')); +const alloyBlock = extractAlloyBlock(read('content/validators/docker-compose-monitoring.yaml')); +if (!alloyBlock) fail('docker_compose_monitoring: no alloy service in content/validators/docker-compose-monitoring.yaml'); +else sameBlock('docker_compose_monitoring', monitoring, 'Add or verify the Alloy service in docker-compose.yaml', 'yaml', alloyBlock); + +const { GENERATED_NOTE } = require('./update-greybox-in-genvm-config'); +if (!exists('pages/validators/greybox-setup.mdx') || + read('pages/validators/greybox-setup.mdx') !== `${GENERATED_NOTE}${read('content/validators/greybox-setup-guide.md').trimEnd()}\n`) { + fail('greybox_setup: pages/validators/greybox-setup.mdx is not the synced greybox-setup-guide.md (run npm run node-update-greybox)'); +} + +// 3. Sanitized files. +const config = read('content/validators/config.yaml'); +for (const key of ['genlayerchainrpcurl', 'genlayerchainwebsocketurl', 'provider']) { + const m = config.match(new RegExp(`^ ${key}: "([^"]*)"`, 'm')); + if (!m || !m[1].startsWith('TODO')) fail(`config: rollup.${key} must be a TODO placeholder, found ${m ? m[1] : 'nothing'}`); +} +if (/^ alloy:/m.test(read('content/validators/docker-compose.yaml'))) { + fail('docker_compose: the alloy service must be removed (sanitize-docker-compose.sh)'); +} + +// 4. Manifest matches the Bradbury block. +try { + const manifest = JSON.parse(read('content/validators/bradbury.protocol-manifest.json')); + const bradbury = read('content/validators/bradbury.yaml'); + const address = (bradbury.match(/consensusaddress: "([^"]+)"/) || [])[1]; + const chainid = (bradbury.match(/chainid: "([^"]+)"/) || [])[1]; + if (!address || manifest.registry.toLowerCase() !== address.toLowerCase()) { + fail(`manifest: registry ${manifest.registry} does not match bradbury.yaml consensusaddress ${address}`); + } + if (!chainid || String(manifest.chainId) !== chainid) { + fail(`manifest: chainId ${manifest.chainId} does not match bradbury.yaml chainid ${chainid}`); + } +} catch (e) { + fail(`manifest: content/validators/bradbury.protocol-manifest.json is not valid JSON (${e.message})`); +} + +// 5. Asimov is retired. +const walk = (dir) => fs.readdirSync(path.join(root, dir), { withFileTypes: true }) + .flatMap((d) => (d.isDirectory() ? walk(path.join(dir, d.name)) : [path.join(dir, d.name)])); +for (const dir of ['content/validators', 'pages/validators', 'pages/api-references/genlayer-node']) { + for (const file of walk(dir)) { + if (/asimov/i.test(read(file))) fail(`asimov: ${file} mentions Asimov`); + } +} + +// 6. No changelog version disappears. +const baseRef = process.env.BASE_REF || 'origin/main'; +try { + const before = execFileSync('git', ['ls-tree', '--name-only', `${baseRef}:content/validators/changelog`], { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }) + .trim().split('\n').filter(Boolean); + for (const file of before) { + if (!exists(`content/validators/changelog/${file}`)) { + fail(`changelog: ${file} is on ${baseRef} but was removed (is it missing in the node source?)`); + } + } +} catch { + console.warn(`check-node-sync: ${baseRef} not available, changelog check skipped`); +} + +if (failures.length) { + console.error(`check-node-sync: ${failures.length} problem(s)`); + for (const f of failures) console.error(` - ${f}`); + process.exit(1); +} +console.log(`check-node-sync: OK (${types.length} sync types, 7 generated blocks verbatim)`); diff --git a/scripts/generate-api-docs.js b/scripts/generate-api-docs.js index 1b377c48..f576647e 100644 --- a/scripts/generate-api-docs.js +++ b/scripts/generate-api-docs.js @@ -3,6 +3,9 @@ const fs = require('fs'); const path = require('path'); +// A generator that cannot find its source or its target block fails the build. +const fail = (msg) => { console.error(msg); process.exitCode = 1; }; + // Configuration constants - can be overridden by environment variables const CONFIG = { API_DIR: process.env.API_DOCS_DIR || 'pages/api-references/genlayer-node', @@ -33,7 +36,7 @@ function updateMetaJson(dirPath) { existingMeta = JSON.parse(fs.readFileSync(metaPath, 'utf8')); existingOrder = Object.keys(existingMeta); } catch (err) { - console.warn(`Failed to parse existing _meta.json: ${err.message}`); + fail(`Failed to parse existing _meta.json: ${err.message}`); } } @@ -77,7 +80,7 @@ function generateApiDocs() { const targetFile = path.join(process.cwd(), CONFIG.TARGET_FILE); if (!fs.existsSync(apiDir)) { - console.error(`API directory ${apiDir} does not exist`); + fail(`API directory ${apiDir} does not exist`); return; } @@ -100,7 +103,7 @@ function generateApiDocs() { } console.log(`Found ${genMethods.length} gen methods`); } else { - console.warn(`Gen methods directory ${genDir} does not exist - skipping`); + fail(`Gen methods directory ${genDir} does not exist - skipping`); } // Read debug methods @@ -122,7 +125,7 @@ function generateApiDocs() { } console.log(`Found ${debugMethods.length} debug methods`); } else { - console.warn(`Debug methods directory ${debugDir} does not exist - skipping`); + fail(`Debug methods directory ${debugDir} does not exist - skipping`); } // Read ops methods @@ -144,14 +147,14 @@ function generateApiDocs() { } console.log(`Found ${opsMethods.length} ops methods`); } else { - console.warn(`Ops methods directory ${opsDir} does not exist - skipping`); + fail(`Ops methods directory ${opsDir} does not exist - skipping`); } // Read the template file const templateFile = path.join(process.cwd(), CONFIG.TEMPLATE_FILE); if (!fs.existsSync(templateFile)) { - console.error(`Template file ${templateFile} does not exist`); + fail(`Template file ${templateFile} does not exist`); return; } @@ -161,9 +164,9 @@ function generateApiDocs() { // Process the template with method content using string replacement // Replace template variables with actual content let apiContent = templateContent - .replace('${genMethods.join(\'\\n\\n\')}', genMethods.join('\n\n')) - .replace('${debugMethods.join(\'\\n\\n\')}', debugMethods.join('\n\n')) - .replace('${opsMethods.join(\'\\n\\n\')}', opsMethods.join('\n\n')); + .replace('${genMethods.join(\'\\n\\n\')}', () => genMethods.join('\n\n')) + .replace('${debugMethods.join(\'\\n\\n\')}', () => debugMethods.join('\n\n')) + .replace('${opsMethods.join(\'\\n\\n\')}', () => opsMethods.join('\n\n')); // Write to the target file fs.writeFileSync(targetFile, apiContent); diff --git a/scripts/generate-changelog.js b/scripts/generate-changelog.js index 950ecf5c..6ca845b9 100644 --- a/scripts/generate-changelog.js +++ b/scripts/generate-changelog.js @@ -3,6 +3,9 @@ const fs = require('fs'); const path = require('path'); +// A generator that cannot find its source or its target block fails the build. +const fail = (msg) => { console.error(msg); process.exitCode = 1; }; + /** * Read all changelog version files and generate the combined content */ @@ -11,7 +14,7 @@ function generateChangelog() { const targetFile = path.join(process.cwd(), 'pages/validators/changelog.mdx'); if (!fs.existsSync(changelogDir)) { - console.error(`Changelog directory ${changelogDir} does not exist`); + fail(`Changelog directory ${changelogDir} does not exist`); return; } diff --git a/scripts/setup-guide-versions.config.js b/scripts/setup-guide-versions.config.js new file mode 100644 index 00000000..b9701381 --- /dev/null +++ b/scripts/setup-guide-versions.config.js @@ -0,0 +1,19 @@ +// Which node versions the setup guide names. Read by scripts/update-setup-guide-versions.js +// (npm run node-update-setup-guide), tested by scripts/test-setup-guide-versions.js. +// +// versionChannel: +// 'stable' The guide names stable releases only: the version list, the +// `export version=` line, and a list command that filters out +// release candidates (`grep -v "rc"`). Default. +// 'all' The guide names the newest version including release candidates +// (for example v0.6.0-rc.2), and the list command keeps them. +// +// Versions come from the changelog files synced from genlayer-node +// (content/validators/changelog/*.mdx). With 'all', a release candidate shows up +// only once the node repo has a changelog file for it (docs/changelog/v0.6.0-rc.2.md). +// +// Flipping the channel is a one-line docs PR; no node release is needed. + +module.exports = { + versionChannel: 'stable', +}; diff --git a/scripts/simulate-node-sync.sh b/scripts/simulate-node-sync.sh new file mode 100755 index 00000000..6396d887 --- /dev/null +++ b/scripts/simulate-node-sync.sh @@ -0,0 +1,52 @@ +#!/bin/bash +set -euo pipefail + +# Local run of the "Sync Documentation from Node Repository" workflow. +# +# Copies every synced node file into this checkout exactly as the workflow does +# (same file list, sanitizers and sync script), runs every doc generator, then +# runs the sync checks. Afterwards `git status` shows what a CI sync of the same +# node commit will change. Commit the result, and a CI run of that commit +# reports "No changes detected". +# +# Usage: scripts/simulate-node-sync.sh +# tag, branch or commit in the node repo (e.g. v0.6.0-rc.2, origin/v0.6-dev) +# Environment: +# NODE_REPO local genlayer-node clone (default: ../genlayer-node); fetch it first. +# +# Needs: git, node, and for the sync step GNU sed + yq v4 + perl. Where those are +# missing (macOS), the sync step runs in a debian:bookworm-slim container. + +REF="${1:?Usage: $0 }" +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +NODE_REPO="${NODE_REPO:-$ROOT/../genlayer-node}" +cd "$ROOT" + +COMMIT=$(git -C "$NODE_REPO" rev-parse --verify "$REF^{commit}") +echo "Node commit: $COMMIT ($REF)" + +SNAPSHOT=$(mktemp -d) +trap 'rm -rf "$SNAPSHOT"' EXIT +# shellcheck disable=SC2046 +git -C "$NODE_REPO" archive "$COMMIT" $(bash .github/scripts/node-sync-params.sh --sources) | tar -x -C "$SNAPSHOT" + +if sed --version >/dev/null 2>&1 && yq --version 2>/dev/null | grep -q 'version v4'; then + bash .github/scripts/run-sync-matrix.sh "$SNAPSHOT" +else + echo "GNU sed or yq v4 not found: running the sync step in a container" + docker run --rm -v "$ROOT:/docs" -v "$SNAPSHOT:/src:ro" -w /docs debian:bookworm-slim bash -c ' + set -e + apt-get -qq update >/dev/null + apt-get -qq install -y wget ca-certificates perl >/dev/null + wget -qO /usr/local/bin/yq "https://github.com/mikefarah/yq/releases/latest/download/yq_linux_$(dpkg --print-architecture)" + chmod +x /usr/local/bin/yq + bash .github/scripts/run-sync-matrix.sh /src + chown -R '"$(id -u):$(id -g)"' content pages' +fi + +bash .github/scripts/doc-generator.sh +node scripts/check-node-sync.js + +echo +echo "Synced from node $COMMIT. Changes:" +git status --short -- content pages diff --git a/scripts/test-setup-guide-versions.js b/scripts/test-setup-guide-versions.js new file mode 100644 index 00000000..6138fc33 --- /dev/null +++ b/scripts/test-setup-guide-versions.js @@ -0,0 +1,111 @@ +#!/usr/bin/env node + +// Tests scripts/update-setup-guide-versions.js on both values of versionChannel +// (scripts/setup-guide-versions.config.js). Run: npm run test:setup-guide-versions + +const assert = require('assert'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const { updateSetupGuideVersions, selectVersions } = require('./update-setup-guide-versions'); +const config = require('./setup-guide-versions.config'); + +const CHANGELOG = [ + 'v0.2.0-testnet005', 'v0.5.9', 'v0.5.12', 'v0.5.13', 'v0.5.14', + 'v0.6.0-rc.1', 'v0.6.0-rc.2', 'v0.5.8-rc.4', +]; + +const GUIDE = `1. list + + \`\`\`sh copy + curl -s "https://example" | sed -n 's/x/y/p' | sort -ru | grep -v "rc" | head -n 5 + \`\`\` + + You should see a list like this + + \`\`\`sh + v0.0.1 + \`\`\` + + Typically, you will want to run the latest version + +2. Download + \`\`\`sh copy + export version=v0.0.1 # set your desired version here + \`\`\` +`; + +function run(channel, changelog = CHANGELOG) { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'setup-guide-versions-')); + fs.mkdirSync(path.join(root, 'content/validators/changelog'), { recursive: true }); + fs.mkdirSync(path.join(root, 'pages/validators'), { recursive: true }); + for (const v of changelog) fs.writeFileSync(path.join(root, 'content/validators/changelog', `${v}.mdx`), ''); + const guidePath = path.join(root, 'pages/validators/setup-guide.mdx'); + fs.writeFileSync(guidePath, GUIDE); + const exitCode = process.exitCode; + updateSetupGuideVersions({ root, channel }); + const failed = process.exitCode === 1 && exitCode !== 1; + process.exitCode = exitCode; + const guide = fs.readFileSync(guidePath, 'utf8'); + fs.rmSync(root, { recursive: true, force: true }); + return { guide, failed }; +} + +const tests = { + 'committed default is stable': () => { + assert.strictEqual(config.versionChannel, 'stable'); + }, + 'stable: newest stable release, numeric order, no rc or testnet': () => { + assert.deepStrictEqual(selectVersions(CHANGELOG, 'stable'), ['v0.5.14', 'v0.5.13', 'v0.5.12', 'v0.5.9']); + const { guide, failed } = run('stable'); + assert.ok(!failed); + assert.match(guide, /export version=v0\.5\.14 #/); + assert.match(guide, /```sh\n v0\.5\.14\n v0\.5\.13\n v0\.5\.12\n v0\.5\.9\n ```/); + assert.match(guide, /\| sort -ruV \| grep -v "rc" \| head -n 5/); + }, + 'all: newest version including rc, rc.2 above rc.1': () => { + assert.deepStrictEqual(selectVersions(CHANGELOG, 'all').slice(0, 3), ['v0.6.0-rc.2', 'v0.6.0-rc.1', 'v0.5.14']); + const { guide, failed } = run('all'); + assert.ok(!failed); + assert.match(guide, /export version=v0\.6\.0-rc\.2 #/); + assert.match(guide, /```sh\n v0\.6\.0-rc\.2\n v0\.6\.0-rc\.1\n v0\.5\.14\n v0\.5\.13\n v0\.5\.12\n ```/); + assert.match(guide, /\| sort -ruV \| head -n 5/); + assert.doesNotMatch(guide, /grep -v "rc"/); + }, + 'all: a stable release sorts above its own rcs': () => { + assert.deepStrictEqual(selectVersions([...CHANGELOG, 'v0.6.0'], 'all').slice(0, 3), ['v0.6.0', 'v0.6.0-rc.2', 'v0.6.0-rc.1']); + }, + 'switching all -> stable restores the stable guide': () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'setup-guide-versions-')); + fs.mkdirSync(path.join(root, 'content/validators/changelog'), { recursive: true }); + fs.mkdirSync(path.join(root, 'pages/validators'), { recursive: true }); + for (const v of CHANGELOG) fs.writeFileSync(path.join(root, 'content/validators/changelog', `${v}.mdx`), ''); + const guidePath = path.join(root, 'pages/validators/setup-guide.mdx'); + fs.writeFileSync(guidePath, GUIDE); + updateSetupGuideVersions({ root, channel: 'all' }); + updateSetupGuideVersions({ root, channel: 'stable' }); + const guide = fs.readFileSync(guidePath, 'utf8'); + fs.rmSync(root, { recursive: true, force: true }); + assert.match(guide, /export version=v0\.5\.14 #/); + assert.match(guide, /\| sort -ruV \| grep -v "rc" \| head -n 5/); + }, + 'an unknown channel fails the generator': () => { + assert.throws(() => selectVersions(CHANGELOG, 'beta'), /versionChannel must be one of stable, all/); + assert.ok(run('beta').failed); + }, + 'stable with only release candidates fails the generator': () => { + assert.ok(run('stable', ['v0.6.0-rc.1']).failed); + }, +}; + +let failures = 0; +for (const [name, fn] of Object.entries(tests)) { + try { + fn(); + console.log(`ok - ${name}`); + } catch (e) { + failures++; + console.error(`not ok - ${name}\n ${e.message}`); + } +} +process.exitCode = failures ? 1 : 0; diff --git a/scripts/update-alloy-config-in-monitoring.js b/scripts/update-alloy-config-in-monitoring.js index 5cd92433..497fc54e 100644 --- a/scripts/update-alloy-config-in-monitoring.js +++ b/scripts/update-alloy-config-in-monitoring.js @@ -3,6 +3,9 @@ const fs = require('fs'); const path = require('path'); +// A generator that cannot find its source or its target block fails the build. +const fail = (msg) => { console.error(msg); process.exitCode = 1; }; + /** * Update the monitoring page with the latest alloy-config.river from the synced content. * Replaces the river code block in monitoring.mdx with the current alloy-config.river file. @@ -13,12 +16,12 @@ function updateAlloyConfigInMonitoring() { const monitoringPath = path.join(projectRoot, 'pages/validators/monitoring.mdx'); if (!fs.existsSync(alloyConfigPath)) { - console.error(`Alloy config file ${alloyConfigPath} does not exist`); + fail(`Alloy config file ${alloyConfigPath} does not exist`); return; } if (!fs.existsSync(monitoringPath)) { - console.error(`Monitoring file ${monitoringPath} does not exist`); + fail(`Monitoring file ${monitoringPath} does not exist`); return; } @@ -32,13 +35,13 @@ function updateAlloyConfigInMonitoring() { if (riverPattern.test(monitoringContent)) { monitoringContent = monitoringContent.replace( riverPattern, - `$1${alloyConfigContent}$3` + (_, before, _old, after) => before + alloyConfigContent + after ); fs.writeFileSync(monitoringPath, monitoringContent); console.log(`Updated monitoring alloy-config.river block at ${new Date().toISOString()}`); } else { - console.error('Could not find alloy-config.river code block pattern in monitoring.mdx'); + fail('Could not find alloy-config.river code block pattern in monitoring.mdx'); } } diff --git a/scripts/update-alloy-in-monitoring.js b/scripts/update-alloy-in-monitoring.js index 33f999ee..93725cea 100644 --- a/scripts/update-alloy-in-monitoring.js +++ b/scripts/update-alloy-in-monitoring.js @@ -3,6 +3,9 @@ const fs = require('fs'); const path = require('path'); +// A generator that cannot find its source or its target block fails the build. +const fail = (msg) => { console.error(msg); process.exitCode = 1; }; + /** * Update the monitoring page with the latest alloy service from the full docker-compose. * Extracts the alloy service definition + volumes section from the full (unsanitized) @@ -14,12 +17,12 @@ function updateAlloyInMonitoring() { const monitoringPath = path.join(projectRoot, 'pages/validators/monitoring.mdx'); if (!fs.existsSync(dockerComposePath)) { - console.error(`Full docker-compose file ${dockerComposePath} does not exist`); + fail(`Full docker-compose file ${dockerComposePath} does not exist`); return; } if (!fs.existsSync(monitoringPath)) { - console.error(`Monitoring file ${monitoringPath} does not exist`); + fail(`Monitoring file ${monitoringPath} does not exist`); return; } @@ -30,7 +33,7 @@ function updateAlloyInMonitoring() { const alloyBlock = extractAlloyBlock(dockerComposeContent); if (!alloyBlock) { - console.error('Could not extract alloy service block from docker-compose'); + fail('Could not extract alloy service block from docker-compose'); return; } @@ -41,13 +44,13 @@ function updateAlloyInMonitoring() { if (alloyPattern.test(monitoringContent)) { monitoringContent = monitoringContent.replace( alloyPattern, - `$1${alloyBlock}$3` + (_, before, _old, after) => before + alloyBlock + after ); fs.writeFileSync(monitoringPath, monitoringContent); console.log(`Updated monitoring alloy service block at ${new Date().toISOString()}`); } else { - console.error('Could not find alloy service YAML block pattern in monitoring.mdx'); + fail('Could not find alloy service YAML block pattern in monitoring.mdx'); } } @@ -144,4 +147,4 @@ if (require.main === module) { updateAlloyInMonitoring(); } -module.exports = { updateAlloyInMonitoring }; +module.exports = { updateAlloyInMonitoring, extractAlloyBlock }; diff --git a/scripts/update-config-in-setup-guide.js b/scripts/update-config-in-setup-guide.js index db6abf30..09a72b56 100755 --- a/scripts/update-config-in-setup-guide.js +++ b/scripts/update-config-in-setup-guide.js @@ -3,6 +3,9 @@ const fs = require('fs'); const path = require('path'); +// A generator that cannot find its source or its target block fails the build. +const fail = (msg) => { console.error(msg); process.exitCode = 1; }; + /** * Update the setup guide with the latest config from content/validators/config.yaml * and the network-specific config from content/validators/bradbury.yaml @@ -13,7 +16,7 @@ function updateConfigInSetupGuide() { const setupGuidePath = path.join(process.cwd(), 'pages/validators/setup-guide.mdx'); if (!fs.existsSync(setupGuidePath)) { - console.error(`Setup guide file ${setupGuidePath} does not exist`); + fail(`Setup guide file ${setupGuidePath} does not exist`); return; } @@ -27,11 +30,11 @@ function updateConfigInSetupGuide() { if (configPattern.test(setupGuideContent)) { setupGuideContent = setupGuideContent.replace( configPattern, - `$1${configContent}$3` + (_, before, _old, after) => before + configContent + after ); console.log(`Updated main config block at ${new Date().toISOString()}`); } else { - console.error('Could not find main config pattern in setup guide'); + fail('Could not find main config pattern in setup guide'); } } @@ -43,11 +46,11 @@ function updateConfigInSetupGuide() { if (bradburyPattern.test(setupGuideContent)) { setupGuideContent = setupGuideContent.replace( bradburyPattern, - `$1${bradburyContent}$3` + (_, before, _old, after) => before + bradburyContent + after ); console.log(`Updated Bradbury config block at ${new Date().toISOString()}`); } else { - console.error('Could not find Bradbury config pattern in setup guide'); + fail('Could not find Bradbury config pattern in setup guide'); } } diff --git a/scripts/update-docker-compose-in-setup-guide.js b/scripts/update-docker-compose-in-setup-guide.js index 65576091..692f644f 100755 --- a/scripts/update-docker-compose-in-setup-guide.js +++ b/scripts/update-docker-compose-in-setup-guide.js @@ -3,6 +3,9 @@ const fs = require('fs'); const path = require('path'); +// A generator that cannot find its source or its target block fails the build. +const fail = (msg) => { console.error(msg); process.exitCode = 1; }; + /** * Update the setup guide with the latest docker-compose from content/validators/docker-compose.yaml */ @@ -12,12 +15,12 @@ function updateDockerComposeInSetupGuide() { const setupGuidePath = path.join(projectRoot, 'pages/validators/setup-guide.mdx'); if (!fs.existsSync(dockerComposePath)) { - console.error(`Docker compose file ${dockerComposePath} does not exist`); + fail(`Docker compose file ${dockerComposePath} does not exist`); return; } if (!fs.existsSync(setupGuidePath)) { - console.error(`Setup guide file ${setupGuidePath} does not exist`); + fail(`Setup guide file ${setupGuidePath} does not exist`); return; } @@ -36,14 +39,14 @@ function updateDockerComposeInSetupGuide() { // Replace the docker-compose content while preserving the surrounding text setupGuideContent = setupGuideContent.replace( dockerComposePattern, - `$1${dockerComposeContent}$3` + (_, before, _old, after) => before + dockerComposeContent + after ); // Write the updated content back fs.writeFileSync(setupGuidePath, setupGuideContent); console.log(`Updated setup guide docker-compose at ${new Date().toISOString()}`); } else { - console.error('Could not find docker-compose pattern in setup guide'); + fail('Could not find docker-compose pattern in setup guide ("Create a `docker-compose.yaml` file with the following content:" followed by a yaml block)'); // Try a more general pattern as fallback // Look for a yaml block that contains typical docker-compose content @@ -70,7 +73,7 @@ function updateDockerComposeInSetupGuide() { } if (!found) { - console.error('Could not find docker-compose YAML block in setup guide'); + fail('Could not find docker-compose YAML block in setup guide'); } } } diff --git a/scripts/update-env-in-setup-guide.js b/scripts/update-env-in-setup-guide.js new file mode 100644 index 00000000..66882572 --- /dev/null +++ b/scripts/update-env-in-setup-guide.js @@ -0,0 +1,47 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +// A generator that cannot find its source or its target block fails the build. +const fail = (msg) => { console.error(msg); process.exitCode = 1; }; + +/** + * Paste content/validators/env.example (synced from genlayer-node + * release/.env.example) into the `.env` block of the setup guide's + * docker-compose section. + */ +function updateEnvInSetupGuide() { + const projectRoot = path.join(__dirname, '..'); + const envPath = path.join(projectRoot, 'content/validators/env.example'); + const setupGuidePath = path.join(projectRoot, 'pages/validators/setup-guide.mdx'); + + if (!fs.existsSync(envPath)) { + fail(`Env example ${envPath} does not exist`); + return; + } + if (!fs.existsSync(setupGuidePath)) { + fail(`Setup guide file ${setupGuidePath} does not exist`); + return; + } + + const envContent = fs.readFileSync(envPath, 'utf8').trimEnd(); + const setupGuideContent = fs.readFileSync(setupGuidePath, 'utf8'); + + // The first ```env block after the step heading. + const pattern = /(Set the required environment variables in a `\.env` file:[\s\S]*?```env[^\n]*\n)([\s\S]*?)(\n```)/; + + if (!pattern.test(setupGuideContent)) { + fail('Could not find the .env block ("Set the required environment variables in a `.env` file:" followed by an env block) in setup guide'); + return; + } + + fs.writeFileSync(setupGuidePath, setupGuideContent.replace(pattern, (_, before, _old, after) => `${before}${envContent}${after}`)); + console.log(`Updated setup guide .env block at ${new Date().toISOString()}`); +} + +if (require.main === module) { + updateEnvInSetupGuide(); +} + +module.exports = { updateEnvInSetupGuide }; diff --git a/scripts/update-greybox-in-genvm-config.js b/scripts/update-greybox-in-genvm-config.js index e1b5515e..413fd920 100644 --- a/scripts/update-greybox-in-genvm-config.js +++ b/scripts/update-greybox-in-genvm-config.js @@ -3,53 +3,40 @@ const fs = require('fs'); const path = require('path'); +// A generator that cannot find its source or its target block fails the build. +const fail = (msg) => { console.error(msg); process.exitCode = 1; }; + +const GENERATED_NOTE = + '{/* Generated from content/validators/greybox-setup-guide.md (genlayer-node release/greybox-setup-guide.md) by scripts/update-greybox-in-genvm-config.js. Do not edit here. */}\n\n'; + /** - * Update the genvm-configuration page with the latest greybox setup guide. - * Replaces everything from "## Greyboxing LLMs" to EOF in genvm-configuration.mdx - * with the content of content/validators/greybox-setup-guide.md (stripping the H1 title). + * Publish the node's greybox setup guide as its own page, + * pages/validators/greybox-setup.mdx. The GenVM Configuration page explains + * greyboxing and links here for the setup steps. The page is rewritten whole, + * so there is no anchor to drift. */ -function updateGreyboxInGenvmConfig() { +function updateGreyboxPage() { const projectRoot = path.join(__dirname, '..'); const greyboxPath = path.join(projectRoot, 'content/validators/greybox-setup-guide.md'); - const genvmConfigPath = path.join(projectRoot, 'pages/validators/genvm-configuration.mdx'); + const pagePath = path.join(projectRoot, 'pages/validators/greybox-setup.mdx'); if (!fs.existsSync(greyboxPath)) { - console.error(`Greybox setup guide ${greyboxPath} does not exist`); + fail(`Greybox setup guide ${greyboxPath} does not exist`); return; } - if (!fs.existsSync(genvmConfigPath)) { - console.error(`GenVM configuration file ${genvmConfigPath} does not exist`); + const content = fs.readFileSync(greyboxPath, 'utf8').trimEnd(); + if (!/^# /.test(content)) { + fail(`Greybox setup guide ${greyboxPath} must start with an H1 title`); return; } - let greyboxContent = fs.readFileSync(greyboxPath, 'utf8').trimEnd(); - let genvmContent = fs.readFileSync(genvmConfigPath, 'utf8'); - - // Strip the H1 title line and any immediately following blank lines from the greybox content - // The H1 is not needed since the MDX already has its own section heading - greyboxContent = greyboxContent.replace(/^# [^\n]*\n\n?/, ''); - - // Replace everything from "## Greyboxing LLMs" (or similar H2 heading about greybox) - // to the end of the file - const greyboxSectionPattern = /## Greybox(?:ing)? LLMs[\s\S]*$/; - - if (greyboxSectionPattern.test(genvmContent)) { - genvmContent = genvmContent.replace( - greyboxSectionPattern, - `## Greyboxing LLMs\n\n${greyboxContent}\n` - ); - - fs.writeFileSync(genvmConfigPath, genvmContent); - console.log(`Updated genvm-configuration greybox section at ${new Date().toISOString()}`); - } else { - console.error('Could not find Greyboxing LLMs section in genvm-configuration.mdx'); - } + fs.writeFileSync(pagePath, `${GENERATED_NOTE}${content}\n`); + console.log(`Updated ${path.relative(projectRoot, pagePath)} at ${new Date().toISOString()}`); } -// Run the script if (require.main === module) { - updateGreyboxInGenvmConfig(); + updateGreyboxPage(); } -module.exports = { updateGreyboxInGenvmConfig }; +module.exports = { updateGreyboxPage, GENERATED_NOTE }; diff --git a/scripts/update-setup-guide-versions.js b/scripts/update-setup-guide-versions.js index 063d170f..640e52b0 100644 --- a/scripts/update-setup-guide-versions.js +++ b/scripts/update-setup-guide-versions.js @@ -3,106 +3,126 @@ const fs = require('fs'); const path = require('path'); +// A generator that cannot find its source or its target block fails the build. +const fail = (msg) => { console.error(msg); process.exitCode = 1; }; + +const CHANNELS = ['stable', 'all']; + +// v0.6.0, v0.6.0-rc.2, v0.2.0-testnet005 +const VERSION_RE = /^v(\d+)\.(\d+)\.(\d+)(?:-(rc)\.(\d+)|-(testnet)(\d+))?$/; + +function parseVersion(v) { + const m = v.match(VERSION_RE); + if (!m) return null; + const pre = m[4] || m[6] || null; + return { + name: v, + core: [Number(m[1]), Number(m[2]), Number(m[3])], + pre, // null for a stable release + preNumber: Number(m[5] || m[7] || 0), + }; +} + +// Newest first. A stable release sorts above its own pre-releases. +function compareDesc(a, b) { + for (let i = 0; i < 3; i++) { + if (a.core[i] !== b.core[i]) return b.core[i] - a.core[i]; + } + if (!a.pre !== !b.pre) return a.pre ? 1 : -1; + return b.preNumber - a.preNumber; +} + /** - * Update the setup guide with the latest versions from changelog + * Select the versions the guide names for a channel. + * Testnet builds (v0.2.0-testnetNNN) are never named. Release candidates are + * named only on the 'all' channel. */ -function updateSetupGuideVersions() { - const changelogDir = path.join(process.cwd(), 'content/validators/changelog'); - const setupGuidePath = path.join(process.cwd(), 'pages/validators/setup-guide.mdx'); - - if (!fs.existsSync(changelogDir)) { - console.error(`Changelog directory ${changelogDir} does not exist`); - return; +function selectVersions(names, channel) { + if (!CHANNELS.includes(channel)) { + throw new Error(`versionChannel must be one of ${CHANNELS.join(', ')}, got ${JSON.stringify(channel)}`); } - - // Read all version files from the changelog directory - const versions = fs.readdirSync(changelogDir) - .filter(file => file.endsWith('.mdx')) - .map(file => file.replace('.mdx', '')) - .sort((a, b) => { - // Sort versions in descending order (newest first) - const parseVersion = (v) => { - const match = v.match(/v(\d+)\.(\d+)\.(\d+)(?:-testnet(\d+))?/); - if (!match) return [0, 0, 0, 0]; - return [ - parseInt(match[1]), - parseInt(match[2]), - parseInt(match[3]), - parseInt(match[4] || '999') // Regular versions come before testnet versions - ]; - }; - - const [majorA, minorA, patchA, testnetA] = parseVersion(a); - const [majorB, minorB, patchB, testnetB] = parseVersion(b); - - if (majorB !== majorA) return majorB - majorA; - if (minorB !== minorA) return minorB - minorA; - if (patchB !== patchA) return patchB - patchA; - return testnetB - testnetA; - }); - - if (versions.length === 0) { - console.error('No version files found in changelog directory'); + return names + .map(parseVersion) + .filter(Boolean) + .filter((v) => v.pre !== 'testnet' && (channel === 'all' || v.pre === null)) + .sort(compareDesc) + .map((v) => v.name); +} + +/** The list command shown in the guide, as the channel would run it. */ +function listCommandTail(channel) { + return channel === 'all' ? '| sort -ruV | head -n 5' : '| sort -ruV | grep -v "rc" | head -n 5'; +} + +/** + * Update the setup guide's version list, list command and `export version=` line. + * @param {{root?: string, channel?: string}} options + */ +function updateSetupGuideVersions({ root = process.cwd(), channel } = {}) { + if (channel === undefined) { + channel = require('./setup-guide-versions.config').versionChannel; + } + const changelogDir = path.join(root, 'content/validators/changelog'); + const setupGuidePath = path.join(root, 'pages/validators/setup-guide.mdx'); + + if (!fs.existsSync(changelogDir)) { + fail(`Changelog directory ${changelogDir} does not exist`); return; } - - // Get the latest version - const latestVersion = versions[0]; - - // Check that the setup guide file exists if (!fs.existsSync(setupGuidePath)) { - console.error(`Setup guide file ${setupGuidePath} does not exist`); + fail(`Setup guide file ${setupGuidePath} does not exist`); return; } - - // Read the setup guide - let setupGuideContent = fs.readFileSync(setupGuidePath, 'utf8'); - - // Update the version list to match the curl command output - // The curl command uses: grep -v "rc" | head -n 5 - // So we filter out rc versions and show only 5 versions - // Filter out testnet and rc versions for the display list (matches grep -v "rc") - const releaseVersions = versions.filter(v => !v.includes('testnet') && !v.includes('rc')); + const names = fs.readdirSync(changelogDir) + .filter((file) => file.endsWith('.mdx')) + .map((file) => file.replace(/\.mdx$/, '')); - // Take only the first 5 versions (matches head -n 5) - const displayVersions = releaseVersions.slice(0, 5); + let versions; + try { + versions = selectVersions(names, channel); + } catch (e) { + fail(e.message); + return; + } + if (versions.length === 0) { + fail(`No ${channel} version files found in changelog directory`); + return; + } - // Build the version list (no indentation, matches actual curl output) - const versionListIndented = displayVersions.map(v => ` ${v}`).join('\n'); + const latestVersion = versions[0]; + let content = fs.readFileSync(setupGuidePath, 'utf8'); - // Replace the version list (between "You should see a list like this" and "Typically you will want") + // The version list between "You should see a list like this" and "Typically you will want". const versionListPattern = /(You should see a list like this\s*\n\s*```sh\n)([\s\S]*?)(\n\s*```\s*\n\s*Typically,? you will want)/; + const versionList = versions.slice(0, 5).map((v) => ` ${v}`).join('\n'); + if (versionListPattern.test(content)) { + content = content.replace(versionListPattern, (_, before, _old, after) => before + versionList + after); + } else { + fail('Could not find version list pattern in setup guide'); + } - if (versionListPattern.test(setupGuideContent)) { - setupGuideContent = setupGuideContent.replace( - versionListPattern, - `$1${versionListIndented}$3` - ); + // The tail of the list command: version sort, rc filter (stable only), first 5. + const listCommandPattern = /\| sort -ruV?(?: \| grep -v "rc")? \| head -n 5/; + if (listCommandPattern.test(content)) { + content = content.replace(listCommandPattern, () => listCommandTail(channel)); } else { - console.warn('Could not find version list pattern in setup guide'); + fail('Could not find the version list command (| sort -ru... | head -n 5) in setup guide'); } - - // Update the export version line - const exportPattern = /export version=v[\d.]+(?:-testnet\d+)?/; - - if (exportPattern.test(setupGuideContent)) { - setupGuideContent = setupGuideContent.replace( - exportPattern, - `export version=${latestVersion}` - ); + + const exportPattern = /export version=v\d+\.\d+\.\d+(?:-rc\.\d+|-testnet\d+)?/; + if (exportPattern.test(content)) { + content = content.replace(exportPattern, () => `export version=${latestVersion}`); } else { - console.warn('Could not find export version pattern in setup guide'); + fail('Could not find export version pattern in setup guide'); } - - // Write the updated content back - fs.writeFileSync(setupGuidePath, setupGuideContent); - console.log(`Updated setup guide with ${versions.length} versions, latest: ${latestVersion}`); + + fs.writeFileSync(setupGuidePath, content); + console.log(`Updated setup guide (${channel} channel) with ${versions.length} versions, latest: ${latestVersion}`); } -// Run the script if (require.main === module) { updateSetupGuideVersions(); } -module.exports = { updateSetupGuideVersions }; \ No newline at end of file +module.exports = { updateSetupGuideVersions, selectVersions, listCommandTail, CHANNELS }; From cb5e6c0c739552356b042bb2a5b521615a24ef70 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 17:31:56 +0200 Subject: [PATCH 13/39] docs(validators): sync node v0.6-dev (genlayer-node#2267 at ed771d161) Result of `scripts/simulate-node-sync.sh ed771d161`: config, compose, monitoring compose, .env example, alloy config, greybox guide, changelog and node API pages. gen_getTransactionReceipt.mdx is held back at the previous version: the node copy gives LeaderRevealing as status 14 (the node code has 13) and drops vote 5 DeterministicViolation, and fails check-v06-release-docs. Fix requested in genlayer-node#2267. Hand-written text now matching the synced files: - Docker: genvm-module-web-docker.yaml ships in the release and is mounted at /app/opt/genvm/current; .env needs NODE_PASSWORD (no default); doctor runs through `docker compose run`. - Binary and GenVM config paths: opt/genvm/ instead of third_party/genvm. - GenVM Configuration links to the generated Greybox Setup page instead of a copy of its steps. Co-Authored-By: Claude Opus 5.5 --- content/validators/alloy-config.river | 59 + content/validators/changelog/v0.3.3.mdx | 2 +- content/validators/config.yaml | 193 +++- .../validators/docker-compose-monitoring.yaml | 188 ++- content/validators/docker-compose.yaml | 108 +- content/validators/env.example | 190 +++ content/validators/greybox-setup-guide.md | 54 +- pages/api-references/genlayer-node.mdx | 1024 +++++++++++++++-- .../genlayer-node/debug/_meta.json | 5 +- .../genlayer-node/debug/gen_dbg_load_test.mdx | 50 + .../debug/gen_dbg_traceTransaction.mdx | 13 +- .../genlayer-node/debug/gen_dbg_trie.mdx | 1 + .../genlayer-node/gen/_meta.json | 8 +- .../genlayer-node/gen/gen_call.mdx | 23 +- .../genlayer-node/gen/gen_estimateGas.mdx | 53 + .../genlayer-node/gen/gen_getContractCode.mdx | 17 +- .../gen/gen_getContractState.mdx | 22 +- .../genlayer-node/gen/gen_getLogs.mdx | 105 ++ .../gen/gen_getTransactionData.mdx | 132 +++ .../gen/gen_getTransactionLifecycle.mdx | 2 + .../genlayer-node/gen/gen_protocolVersion.mdx | 53 + .../genlayer-node/gen/gen_subscribe.mdx | 99 ++ .../genlayer-node/gen/gen_syncing.mdx | 13 + .../genlayer-node/gen/gen_unsubscribe.mdx | 52 + .../genlayer-node/ops/balance.mdx | 2 +- .../genlayer-node/ops/health.mdx | 197 ++-- .../genlayer-node/ops/metrics.mdx | 151 +++ .../genlayer-node/ops/snapshot.mdx | 32 +- pages/validators/_meta.json | 1 + pages/validators/changelog.mdx | 2 +- pages/validators/genvm-configuration.mdx | 30 +- pages/validators/greybox-setup.mdx | 195 ++++ pages/validators/monitoring.mdx | 134 ++- pages/validators/setup-guide.mdx | 516 +++++++-- 34 files changed, 3254 insertions(+), 472 deletions(-) create mode 100644 content/validators/env.example create mode 100644 pages/api-references/genlayer-node/debug/gen_dbg_load_test.mdx create mode 100644 pages/api-references/genlayer-node/gen/gen_estimateGas.mdx create mode 100644 pages/api-references/genlayer-node/gen/gen_getLogs.mdx create mode 100644 pages/api-references/genlayer-node/gen/gen_getTransactionData.mdx create mode 100644 pages/api-references/genlayer-node/gen/gen_protocolVersion.mdx create mode 100644 pages/api-references/genlayer-node/gen/gen_subscribe.mdx create mode 100644 pages/api-references/genlayer-node/gen/gen_unsubscribe.mdx create mode 100644 pages/validators/greybox-setup.mdx diff --git a/content/validators/alloy-config.river b/content/validators/alloy-config.river index db983230..405c203b 100644 --- a/content/validators/alloy-config.river +++ b/content/validators/alloy-config.river @@ -55,6 +55,65 @@ loki.source.file "genlayer" { } // Write logs to central Loki instance +// Read opted-in WebDriver/Alloy recovery events through the existing read-only +// Docker proxy. Containers log their own restart attempts before exiting. +discovery.docker "health_recovery" { + host = "http://docker-socket-proxy:2375" + refresh_interval = "5s" + + filter { + name = "label" + values = [ + "com.genlayer.health-recovery.logs=true", + ] + } +} + +discovery.relabel "health_recovery" { + targets = discovery.docker.health_recovery.targets + + rule { + source_labels = ["__meta_docker_container_label_com_docker_compose_project"] + target_label = "compose_project" + } + + rule { + source_labels = ["__meta_docker_container_label_com_genlayer_network"] + target_label = "network" + } + + rule { + source_labels = ["network"] + regex = "" + target_label = "network" + replacement = "unknown" + } +} + +loki.source.docker "health_recovery" { + // Docker's TCP transport keeps the log stream open. With an HTTP(S) host, + // Alloy bounds the entire stream by refresh_interval and repeatedly reopens it. + host = "tcp://docker-socket-proxy:2375" + targets = discovery.relabel.health_recovery.output + refresh_interval = "5s" + labels = { + job = "genlayer-node", + component = "health-recovery", + instance = sys.env("NODE_ID"), + validator_name = sys.env("VALIDATOR_NAME"), + } + forward_to = [loki.process.health_recovery.receiver] +} + +// Collect only restart events, not Alloy's own operational log stream. +loki.process "health_recovery" { + stage.match { + selector = "{component=\"health-recovery\"} != \"found to be unhealthy - Restarting container now\"" + action = "drop" + } + forward_to = [loki.write.central.receiver] +} + loki.write "central" { endpoint { url = sys.env("CENTRAL_LOKI_URL") diff --git a/content/validators/changelog/v0.3.3.mdx b/content/validators/changelog/v0.3.3.mdx index 4ade8c24..cb09e621 100644 --- a/content/validators/changelog/v0.3.3.mdx +++ b/content/validators/changelog/v0.3.3.mdx @@ -2,4 +2,4 @@ ### New features -- Added [Comput3](/validators/setup-guide#genvm-configuration) as a supported LLM provider for validators +- Added [Comput3](https://docs.genlayer.com/validators/setup-guide#genvm-configuration) as a supported LLM provider for validators diff --git a/content/validators/config.yaml b/content/validators/config.yaml index 7d88fdce..3177305f 100644 --- a/content/validators/config.yaml +++ b/content/validators/config.yaml @@ -2,6 +2,10 @@ rollup: genlayerchainrpcurl: "TODO: Set your GenLayer Chain ZKSync HTTP RPC URL here" # GenLayer Chain RPC URL genlayerchainwebsocketurl: "TODO: Set your GenLayer Chain ZKSync WebSocket RPC URL here" # GenLayer Chain WebSocket URL + # An API key of the RPC provider never goes in these URLs or in this file: + # set it in the environment as GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY. + # The node sends it in this header (default X-AccessKey): + # genlayerchainrpckeyheader: "X-AccessKey" provider: "TODO: Set your GenLayer Chain ZKSync provider" # Rollup operator name; surfaces as the "provider" label on RPC-driven metrics. Set explicitly per deployment. # consensus contracts configuration consensus: @@ -23,6 +27,18 @@ logging: file: # enabled: set to `true` to save logs to a folder enabled: true + # The console and this file carry different levels on purpose: INFO to the + # terminal, DEBUG here. That split is why the file sink exists — INFO is what + # an operator watches live and has to stay readable, DEBUG is the complete + # record kept for investigation. Set this to INFO and the two collapse: the + # file becomes a copy of the console and stops being a record. + # + # It has to be on permanently. An incident is over by the time anyone could + # raise the level, and the detail cannot be reconstructed after the fact. + # + # What it accepts: every debug line lands on disk, including RPC and GenVM + # payloads chosen by the caller. The level bounds nothing — + # maxsize/maxage/maxbackups below are what limit the volume. level: "DEBUG" # folder: path to the folder where to store logs. Relative paths go under `datadir`. folder: logs @@ -46,10 +62,16 @@ node: mode: "validator" # Address of the ValidatorWallet contract (required for validator mode) validatorWalletAddress: "" - # Address of the operator that owns the ValidatorWallet + # Address of the operator that performs consensus duties for the ValidatorWallet operatorAddress: "" admin: port: 9155 + # snapshot_dir: bounding directory for POST /snapshot write-to-path mode. + # A caller-supplied destDir must resolve inside this directory, else the + # request is rejected with 400. Relative paths go under `datadir`; an + # absolute path is used as-is (e.g. to target a dedicated disk). + # Default: snapshots (i.e. /snapshots) + # snapshot_dir: snapshots rpc: # host: 0.0.0.0 # bind address for the HTTP listener (default: all interfaces) port: 9151 # RPC server port @@ -94,73 +116,122 @@ node: # - "::1" # IPv6 loopback (localhost on macOS) # - "10.0.0.0/8" endpoints: - # Group-level configuration (enables/disables all methods in a group). - # When a group is enabled, ALL methods in that group are available. - # Rate limits (req/s per IP) apply by default even without config: + # RPC method groups, keyed directly under `endpoints`. Each group toggles a + # family of methods and may nest per-method overrides and rate limits. + # Default rate limits (req/s per IP) apply even when unset: # genlayer: 20, genlayer_debug: 2, ethereum: 50, zksync: 50 - # gen_call: 2, gen_getContractSchema: 2 (method-level overrides) - groups: - genlayer: true # gen_* methods (default ratelimit: 20 req/s per IP) - genlayer_debug: true # gen_dbg_* methods (default ratelimit: 2 req/s per IP) - ethereum: true # eth_* proxy methods (default ratelimit: 50 req/s per IP) - # includes: eth_blockNumber, eth_getBlockByNumber, eth_getBlockByHash, - # eth_sendRawTransaction, eth_getTransactionReceipt, etc. - zksync: true # zks_* proxy methods (default ratelimit: 50 req/s per IP) - # includes: zks_getTransaction, zks_getBlockDetails, etc. - # Object format with custom rate limit (overrides defaults): - # groups: - # genlayer: - # enabled: true - # ratelimit: 100 # custom: 100 req/s per IP instead of default 20 - # - # Method-level configuration (optional, overrides group settings). - # Methods inherit their group's enabled state by default. - # Use this section to: - # - Disable specific methods in an enabled group - # - Enable specific methods in a disabled group - # - Set per-method rate limits - # - # Example: enable all eth_* except eth_sendRawTransaction: - # groups: - # ethereum: true - # methods: - # eth_sendRawTransaction: false - # - # Example: disable all eth_* except eth_sendRawTransaction: - # groups: - # ethereum: false - # methods: - # eth_sendRawTransaction: true - # methods: - # gen_call: true # default ratelimit: 2 req/s per IP (GenVM-bound) - # gen_getContractSchema: true # default ratelimit: 2 req/s per IP (GenVM-bound) - # eth_blockNumber: true - # eth_sendRawTransaction: true - # zks_getTransaction: true - # Object format with custom rate limit: - # methods: - # gen_call: - # enabled: true - # ratelimit: 5 # custom: 5 req/s per IP instead of default 2 - # gen_getContractSchema: - # enabled: true - # ratelimit: 0 # explicitly disable rate limiting for this method + # gen_call: 2, gen_getContractSchema: 2 (method-level defaults) + genlayer: + enabled: true # gen_* methods + # gen_dbg_* methods (trie dump, transaction trace). OFF by default: these + # replay a transaction through GenVM and return internal execution output, + # so they are debug tooling, not a public API. Enable deliberately, and + # only on an RPC bind that untrusted callers cannot reach. Matches the + # node's own code default and the go-ethereum debug-namespace convention. + genlayer_debug: + enabled: false # gen_dbg_* methods + ethereum: + enabled: true # eth_* proxy methods (eth_blockNumber, eth_call, ...) + zksync: + enabled: false # zks_* proxy methods (zks_getBlockDetails, ...) + # + # A group with no overrides may use the bool shorthand (`ethereum: true`). + # + # Custom per-group rate limit + per-method overrides (object form): + # genlayer: + # enabled: true + # ratelimit: 100 # 100 req/s per IP instead of the default 20 + # methods: + # gen_call: + # enabled: true + # ratelimit: 5 # 5 req/s per IP instead of the default 2 + # gen_getContractSchema: + # enabled: false # disable a single method within an enabled group + # + # Proxy allowlist (opt-in): disable a proxy group, then re-enable only a + # vetted set under its `methods`. Every other eth_*/zks_* is then rejected + # with -32601 (method not found) and not forwarded upstream. Leave the + # groups enabled (the default) to forward all eth_*/zks_* (passthrough). + # ethereum: + # enabled: false + # methods: + # eth_getBalance: true + # eth_getTransactionCount: true + # eth_getCode: true + # eth_getStorageAt: true + # eth_call: true + # eth_estimateGas: true + # eth_gasPrice: true + # eth_blockNumber: true + # eth_getBlockByHash: true + # eth_getBlockByNumber: true + # eth_getTransactionByHash: true + # eth_getTransactionReceipt: true + # eth_getLogs: true + # eth_chainId: true + # eth_syncing: true + # eth_accounts: true + # eth_sendRawTransaction: true # write — enable only if clients submit raw txs + # zksync: + # enabled: false # zks_* left fully disabled (no vetted client use) ops: - port: 9153 # Metrics port + port: 9153 # Ops server port (/health, /metrics, /balance, /snapshot) endpoints: - metrics: true # Enable metrics endpoint - health: true # Enable health endpoint - balance: true # Enable balance endpoint - dev: - disableSubscription: false # Useful for testing with zksync-anvil + # Compatibility metadata. The current Ops server registers /metrics, + # /health, /balance, and /snapshot regardless of these values. `doctor` + # consults `health` to decide whether to run its HTTP health probe. + metrics: true + health: true + # Execution-trace store and debug-trace reply bounds. All optional; the + # values below are the built-in defaults. Uncomment to tune at runtime — no + # rebuild needed. + # trace: + # retention_blocks: 1000000 # keep traces this many blocks behind the tip (0 disables retention) + # max_trace_bytes: 33554432 # 32 MB: a single trace larger than this is never stored + # # Per-field cap on the public gen_dbg_traceTransaction reply. Each free-form + # # field (return_data, stdout, stderr, genvm_log) carries contract-emitted + # # output, so its size is contract-controlled. Over the cap, the field is + # # truncated and the cut is marked in-band. 0 takes the default. + # max_debug_field_bytes: 262144 # 256 KB per field # genvm configuration genvm: - root_dir: ./third_party/genvm + root_dir: ./opt/genvm/v0.6.0-rc1 start_manager: true # if true node will start genvm manager itself manager_url: http://127.0.0.1:3999 - permits: 8 # Leave empty for autodiscovery, put a number for updating genvm permits on startup - sync_permits: 64 # Optional: permits used while !isSynced (bulk replay). Leave empty to reuse the steady-state `permits` value. - sync_wait_timeout: "20s" # Max time to wait for GenVM to sync to a requested block before returning an error + # Permits are charged PER EXECUTION by the genvm manager, and one + # nondeterministic execution costs 8 of them (permits.nondet). This value is + # therefore a permit budget, not an execution count: 32 admits 4 concurrent + # nondet executions, which is what the previous `8` bought when a run cost 2 + # permits. Leaving it EMPTY is preferable — autodiscovery sizes it from the + # host and already speaks this unit. + permits: 32 # Leave empty for autodiscovery, put a number for updating genvm permits on startup + # Same unit as `permits` above, but a sync execution costs 4 rather than 8, + # so 256 admits the 64 concurrent replays the previous `64` bought at cost 1. + sync_permits: 256 # Optional: permits used while significantly behind (bulk replay). Leave empty to reuse the steady-state `permits` value. + # sync_permits_lag_threshold: blocks-behind above which the node runs the replay + # budget (sync_permits) instead of the live budget (permits); at or below it the + # node is treated as caught up. Keyed on the actual sync lag, not the health + # gradient. Uncomment to tune; default 1000. + # sync_permits_lag_threshold: 1000 + # sync_wait_timeout bounds the gen_* READ path — not GenVM execution: a read + # (gen_call / gen_getContractState / gen_getContractCode / + # gen_dbg_traceTransaction) waits up to this long for the node to sync the + # requested block and index the record, then returns a not-synced error. + sync_wait_timeout: "20s" + # sync_wait_poll_interval is the re-check cadence those same reads use while they + # poll for an artifact that lands just after its block is synced, bounded overall + # by sync_wait_timeout. Uncomment to tune; default 100ms. An absent key and a + # non-positive value both mean "use the default": 0s would otherwise be a wait + # that never ticks. + # sync_wait_poll_interval: "100ms" + replay: + parallel_enabled: false # Opt in to bounded parallel replay of independent accepted transactions in the same block + workers: 4 # Maximum concurrent replay executions + consensus_reserve_permits: 2 # Kept free for live validator duties unless a duty is waiting on replayed state + replay_floor_permits: 1 # Minimum replay capacity while replay work is queued + max_journal_bytes: 268435456 # 256 MiB block-wide cap; overflow falls back to serial replay + reorg_history_blocks: 4096 # Number of hash-identified replay checkpoints retained for exact canonical rollback + max_reorg_journal_bytes: 268435456 # 256 MiB inverse-journal cap per block; overflow aborts replay and keeps duties disabled # Advanced configuration merkleforest: maxdepth: 16 diff --git a/content/validators/docker-compose-monitoring.yaml b/content/validators/docker-compose-monitoring.yaml index 1af0a467..f6ea71f4 100644 --- a/content/validators/docker-compose-monitoring.yaml +++ b/content/validators/docker-compose-monitoring.yaml @@ -1,36 +1,136 @@ services: webdriver-container: container_name: genlayer-node-webdriver - image: yeagerai/genlayer-genvm-webdriver:0.0.11 + image: yeagerai/genlayer-genvm-webdriver:0.0.12 shm_size: 2gb security_opt: - no-new-privileges:true environment: PORT: 4444 + HEALTHCHECK_AUTO_RESTART: "${HEALTHCHECK_AUTO_RESTART:-true}" ports: - "${WEBDRIVER_PORT:-4444}:4444" restart: unless-stopped + labels: + com.genlayer.health-recovery.logs: "true" + com.genlayer.network: "${NETWORK_NAME:-unknown}" + healthcheck: + test: + - CMD-SHELL + - | + set -eu + boot=$$(awk '{print $$22}' /proc/1/stat) + if curl -fsS --max-time 10 "http://localhost:$${PORT:-4444}/healthcheck?mode=text&url=https%3A%2F%2Ftest-server.genlayer.com%2Fstatic%2Fgenvm%2Fhello.html"; then + printf '%s 0\n' "$$boot" > /tmp/webdriver-health-failures + exit 0 + fi + age=$$(awk -v ticks="$$boot" -v hz="$$(getconf CLK_TCK)" '{print int($$1-ticks/hz)}' /proc/uptime) + [ "$$age" -ge 10 ] || exit 1 + read -r previous failures 2>/dev/null < /tmp/webdriver-health-failures || failures=0 + [ "$${previous:-}" = "$$boot" ] || failures=0 + failures=$$((failures + 1)) + printf '%s %s\n' "$$boot" "$$failures" > /tmp/webdriver-health-failures + if [ "$$failures" -ge 4 ] && [ "$${HEALTHCHECK_AUTO_RESTART:-true}" != false ]; then + printf 'Container webdriver (%s) found to be unhealthy - Restarting container now\n' "$$(cat /etc/hostname)" > /proc/1/fd/1 + children=$$(cat /proc/1/task/1/children) + for child in $$children; do kill -KILL "$$child"; done + fi + exit 1 + interval: 30s + timeout: 12s + retries: 4 + start_period: 10s + + # docker-socket-proxy — read-only Docker API gateway for the node's WebDriver + # resource stats and Alloy's recovery-log reader. Runs with the `node` OR + # `monitoring` profile, so it comes up alongside + # a node container AND alongside a monitoring/telemetry bring-up — the latter is + # how a binary/systemd node (node not containerized) gets its webdriver metrics. + # + # The node and Alloy use this read-only gateway instead of the host socket. + # Reading container stats requires the Docker API. Previously the node reached + # the host /var/run/docker.sock directly (bind-mounted into the container, or via + # docker-group membership for a binary node) — but that grants the FULL Docker + # API as root (a `:ro` flag does NOT restrict API calls), so a node compromise + # could escalate to host root. This proxy REPLACES the node's socket access, + # restricted to read-only container endpoints (CONTAINERS=1; + # POST=0 => it returns 403 for create / exec / image operations). + # Health checks stop only their own service process; recovery has no Docker API access. + # - Published ONLY on 127.0.0.1:2375 (host loopback), never 0.0.0.0: 2375 is + # the unauthenticated Docker API, so it must not leave the host. A container + # node reaches it over the compose network (tcp://docker-socket-proxy:2375); + # a binary/systemd node reaches it on the host (tcp://127.0.0.1:2375). + # - Standard, widely-audited image (tecnativa/docker-socket-proxy). + # + # The node points DOCKER_HOST at this proxy and NEVER falls back to the host + # socket: if DOCKER_HOST is unset the node skips webdriver metrics entirely + # (non-fatal). Note those are the webdriver container's CPU/memory/traffic series + # (genlayer_node_*{component="webdriver"}) that feed validator resource dashboards — + # so keep DOCKER_HOST set to keep those panels populated. Disabling metrics + # permits removing the node's DOCKER_HOST; Alloy still needs this gateway. + docker-socket-proxy: + image: tecnativa/docker-socket-proxy:v0.5.0 + container_name: genlayer-node-docker-proxy + environment: + CONTAINERS: 1 # GET /containers/{json,inspect,stats} — read-only via POST=0 + VERSION: 1 # required by the Docker client's API-version negotiation + NETWORKS: 1 # read-only network metadata required by Alloy Docker discovery + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + ports: + # Loopback ONLY (never 0.0.0.0) — 2375 is the unauthenticated Docker API and + # must not be exposed beyond the host. Lets a binary/systemd node reach the + # proxy at tcp://127.0.0.1:2375; container nodes use the service name instead. + - "127.0.0.1:2375:2375" + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:2375/version"] + interval: 10s + timeout: 3s + retries: 5 + start_period: 5s + security_opt: + - no-new-privileges:true + restart: unless-stopped + profiles: + - node + - monitoring genlayer-node: image: yeagerai/genlayer-node:${NODE_VERSION:-latest} - entrypoint: [ - "sh", "-c", "/app/bin/genlayernode run --password ${NODE_PASSWORD:-12345678}", - ] + # The keystore password is NOT on this command line. It reaches the node as + # GENLAYERNODE_PASSWORD (environment below), which the run command reads in + # place of --password. A command-line argument is visible in process + # listings on the host; an environment variable is not. + entrypoint: ["/app/bin/genlayernode", "run"] container_name: genlayer-node restart: unless-stopped env_file: - path: ./.env required: false + environment: + # ":?" makes Compose refuse to start when NODE_PASSWORD is unset or empty, + # before any container runs. There is no fallback: this is the password + # that encrypts the validator's signing key, and a default shared by every + # operator who forgot to set one is a key anyone can open. The release + # binary carries no default either — it is compiled out of production. + GENLAYERNODE_PASSWORD: "${NODE_PASSWORD:?NODE_PASSWORD is not set. It is the keystore password that unlocks the validator key. Set it in .env; see README section 4.}" + # WebDriver metrics collector reaches Docker via the read-only proxy + # (see docker-socket-proxy above), never the host socket directly. + DOCKER_HOST: tcp://docker-socket-proxy:2375 ports: - "${NODE_RPC_PORT:-9151}:9151" - "${NODE_OPS_PORT:-9153}:9153" volumes: - ${NODE_CONFIG_PATH:-./configs/node/config.yaml}:/app/configs/node/config.yaml:ro + # The trusted protocol manifest of the network; consensus.manifestpath names this path. + - ${NODE_MANIFEST_PATH:-./configs/node/protocol-manifest.json}:/app/configs/node/protocol-manifest.json:ro - ${NODE_DATA_PATH:-./data}:/app/data - - ./genvm-module-web-docker.yaml:/app/third_party/genvm/config/genvm-module-web.yaml - - /var/run/docker.sock:/var/run/docker.sock:ro # required for webdriver metrics collection + # opt/genvm/current links to the image's GenVM tree, so no version variable is needed. + - ./genvm-module-web-docker.yaml:/app/opt/genvm/current/config/genvm-module-web.yaml security_opt: - no-new-privileges:true + cap_drop: + - ALL logging: driver: "json-file" options: @@ -39,6 +139,7 @@ services: compress: "true" depends_on: - webdriver-container + - docker-socket-proxy profiles: - node @@ -53,36 +154,85 @@ services: # Set SCRAPE_TARGETS_JSON in .env # docker compose --profile monitoring up -d alloy: - image: grafana/alloy:v1.16.1 + image: grafana/alloy:v1.20.1 + init: true container_name: genlayer-node-alloy + labels: + com.genlayer.health-recovery.logs: "true" + com.genlayer.network: "${NETWORK_NAME:-unknown}" + depends_on: + - docker-socket-proxy + # The guard runs before Alloy: with the monitoring profile up but the + # endpoints or credentials unset, the container stops with a message + # naming what to set, instead of half-running. Half-running is worse than + # stopping here in both directions: pushing to a shared endpoint with a + # shared default credential pollutes it for everyone, and pushing to an + # unreachable one leaves telemetry unavailable and can grow the WAL. The + # healthcheck preserves that buffer. The $$ keeps compose from + # interpolating; the shell only tests emptiness — no value ever lands on + # a command line. + entrypoint: ["/bin/sh", "-c"] command: - - run - - /etc/alloy/config.river - - --server.http.listen-addr=0.0.0.0:12345 - - --storage.path=/var/lib/alloy/data + - >- + missing=""; + [ -n "$$CENTRAL_MONITORING_URL" ] || missing="$$missing CENTRAL_MONITORING_URL"; + [ -n "$$CENTRAL_MONITORING_USERNAME" ] || missing="$$missing CENTRAL_MONITORING_USERNAME"; + [ -n "$$CENTRAL_MONITORING_PASSWORD" ] || missing="$$missing CENTRAL_MONITORING_PASSWORD"; + [ -n "$$CENTRAL_LOKI_URL" ] || missing="$$missing CENTRAL_LOKI_URL"; + [ -n "$$CENTRAL_LOKI_USERNAME" ] || missing="$$missing CENTRAL_LOKI_USERNAME"; + [ -n "$$CENTRAL_LOKI_PASSWORD" ] || missing="$$missing CENTRAL_LOKI_PASSWORD"; + if [ -n "$$missing" ]; then + echo "ERROR: not set:$$missing" >&2; + echo "Monitoring pushes to an endpoint YOU configure — set the CENTRAL_* values in .env (see .env.example for a localhost example)." >&2; + exit 1; + fi; + exec /bin/alloy run /etc/alloy/config.river + --server.http.listen-addr=0.0.0.0:12345 + --storage.path=/var/lib/alloy/data volumes: - ./alloy-config.river:/etc/alloy/config.river:ro - ./alloy-healthcheck.sh:/etc/alloy/healthcheck.sh:ro - ${NODE_LOGS_PATH:-./data/node/logs}:/var/log/genlayer:ro - alloy_data:/var/lib/alloy healthcheck: - test: ["CMD", "sh", "/etc/alloy/healthcheck.sh"] + test: + - CMD-SHELL + - | + set -eu + sh /etc/alloy/healthcheck.sh && exit 0 + [ "$${HEALTHCHECK_AUTO_RESTART:-true}" != false ] || exit 1 + [ "$$(cat /proc/1/comm)" = docker-init ] || exit 1 + boot=$$(awk '{print $$22}' /proc/1/stat) + age=$$(awk -v ticks="$$boot" -v hz="$$(getconf CLK_TCK)" '{print int($$1-ticks/hz)}' /proc/uptime) + [ "$$age" -ge 120 ] || exit 1 + children=$$(cat /proc/1/task/1/children) + printf 'Container alloy (%s) found to be unhealthy - Restarting container now\n' "$$(cat /etc/hostname)" > /proc/1/fd/1 + kill -TERM 1 + sleep 5 + # A stopped service cannot handle TERM; init's child can still receive KILL. + for child in $$children; do kill -KILL "$$child"; done + exit 1 interval: 120s timeout: 15s retries: 1 start_period: 120s environment: - # Central monitoring endpoints - - CENTRAL_LOKI_URL=${CENTRAL_LOKI_URL:-https://logs-prod-042.grafana.net/loki/api/v1/push} - - CENTRAL_MONITORING_URL=${CENTRAL_MONITORING_URL:-https://prometheus-prod-66-prod-us-east-3.grafana.net/api/prom/push} + - HEALTHCHECK_AUTO_RESTART=${HEALTHCHECK_AUTO_RESTART:-true} + # Central monitoring endpoints and credentials: NO defaults, on purpose. + # An endpoint may be public knowledge; a credential never is, and a + # default one ships the same key to everyone holding this file. The + # entrypoint guard above turns "unset" into a clear stop instead of a + # silent push to the wrong place. + - CENTRAL_LOKI_URL=${CENTRAL_LOKI_URL:-} + - CENTRAL_MONITORING_URL=${CENTRAL_MONITORING_URL:-} # Metrics (Prometheus) authentication - - CENTRAL_MONITORING_USERNAME=${CENTRAL_MONITORING_USERNAME:-telemetric} - - CENTRAL_MONITORING_PASSWORD=${CENTRAL_MONITORING_PASSWORD:-12345678} + - CENTRAL_MONITORING_USERNAME=${CENTRAL_MONITORING_USERNAME:-} + - CENTRAL_MONITORING_PASSWORD=${CENTRAL_MONITORING_PASSWORD:-} # Logs (Loki) authentication - - CENTRAL_LOKI_USERNAME=${CENTRAL_LOKI_USERNAME:-telemetric} - - CENTRAL_LOKI_PASSWORD=${CENTRAL_LOKI_PASSWORD:-12345678} + - CENTRAL_LOKI_USERNAME=${CENTRAL_LOKI_USERNAME:-} + - CENTRAL_LOKI_PASSWORD=${CENTRAL_LOKI_PASSWORD:-} # Single node configuration - NODE_ID=${NODE_ID:-validator-001} diff --git a/content/validators/docker-compose.yaml b/content/validators/docker-compose.yaml index e4b2452d..acfad56f 100644 --- a/content/validators/docker-compose.yaml +++ b/content/validators/docker-compose.yaml @@ -1,23 +1,120 @@ services: webdriver-container: container_name: genlayer-node-webdriver - image: yeagerai/genlayer-genvm-webdriver:0.0.11 + image: yeagerai/genlayer-genvm-webdriver:0.0.12 shm_size: 2gb security_opt: - no-new-privileges:true environment: PORT: 4444 + HEALTHCHECK_AUTO_RESTART: "${HEALTHCHECK_AUTO_RESTART:-true}" ports: - "${WEBDRIVER_PORT:-4444}:4444" restart: unless-stopped + labels: + com.genlayer.health-recovery.logs: "true" + com.genlayer.network: "${NETWORK_NAME:-unknown}" + healthcheck: + test: + - CMD-SHELL + - | + set -eu + boot=$$(awk '{print $$22}' /proc/1/stat) + if curl -fsS --max-time 10 "http://localhost:$${PORT:-4444}/healthcheck?mode=text&url=https%3A%2F%2Ftest-server.genlayer.com%2Fstatic%2Fgenvm%2Fhello.html"; then + printf '%s 0\n' "$$boot" > /tmp/webdriver-health-failures + exit 0 + fi + age=$$(awk -v ticks="$$boot" -v hz="$$(getconf CLK_TCK)" '{print int($$1-ticks/hz)}' /proc/uptime) + [ "$$age" -ge 10 ] || exit 1 + read -r previous failures 2>/dev/null < /tmp/webdriver-health-failures || failures=0 + [ "$${previous:-}" = "$$boot" ] || failures=0 + failures=$$((failures + 1)) + printf '%s %s\n' "$$boot" "$$failures" > /tmp/webdriver-health-failures + if [ "$$failures" -ge 4 ] && [ "$${HEALTHCHECK_AUTO_RESTART:-true}" != false ]; then + printf 'Container webdriver (%s) found to be unhealthy - Restarting container now\n' "$$(cat /etc/hostname)" > /proc/1/fd/1 + children=$$(cat /proc/1/task/1/children) + for child in $$children; do kill -KILL "$$child"; done + fi + exit 1 + interval: 30s + timeout: 12s + retries: 4 + start_period: 10s + # docker-socket-proxy — read-only Docker API gateway for the node's WebDriver + # resource stats and Alloy's recovery-log reader. Runs with the `node` OR + # `monitoring` profile, so it comes up alongside + # a node container AND alongside a monitoring/telemetry bring-up — the latter is + # how a binary/systemd node (node not containerized) gets its webdriver metrics. + # + # The node and Alloy use this read-only gateway instead of the host socket. + # Reading container stats requires the Docker API. Previously the node reached + # the host /var/run/docker.sock directly (bind-mounted into the container, or via + # docker-group membership for a binary node) — but that grants the FULL Docker + # API as root (a `:ro` flag does NOT restrict API calls), so a node compromise + # could escalate to host root. This proxy REPLACES the node's socket access, + # restricted to read-only container endpoints (CONTAINERS=1; + # POST=0 => it returns 403 for create / exec / image operations). + # Health checks stop only their own service process; recovery has no Docker API access. + # - Published ONLY on 127.0.0.1:2375 (host loopback), never 0.0.0.0: 2375 is + # the unauthenticated Docker API, so it must not leave the host. A container + # node reaches it over the compose network (tcp://docker-socket-proxy:2375); + # a binary/systemd node reaches it on the host (tcp://127.0.0.1:2375). + # - Standard, widely-audited image (tecnativa/docker-socket-proxy). + # + # The node points DOCKER_HOST at this proxy and NEVER falls back to the host + # socket: if DOCKER_HOST is unset the node skips webdriver metrics entirely + # (non-fatal). Note those are the webdriver container's CPU/memory/traffic series + # (genlayer_node_*{component="webdriver"}) that feed validator resource dashboards — + # so keep DOCKER_HOST set to keep those panels populated. Disabling metrics + # permits removing the node's DOCKER_HOST; Alloy still needs this gateway. + docker-socket-proxy: + image: tecnativa/docker-socket-proxy:v0.5.0 + container_name: genlayer-node-docker-proxy + environment: + CONTAINERS: 1 # GET /containers/{json,inspect,stats} — read-only via POST=0 + VERSION: 1 # required by the Docker client's API-version negotiation + NETWORKS: 1 # read-only network metadata required by Alloy Docker discovery + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + ports: + # Loopback ONLY (never 0.0.0.0) — 2375 is the unauthenticated Docker API and + # must not be exposed beyond the host. Lets a binary/systemd node reach the + # proxy at tcp://127.0.0.1:2375; container nodes use the service name instead. + - "127.0.0.1:2375:2375" + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:2375/version"] + interval: 10s + timeout: 3s + retries: 5 + start_period: 5s + security_opt: + - no-new-privileges:true + restart: unless-stopped + profiles: + - node + - monitoring genlayer-node: image: yeagerai/genlayer-node:${NODE_VERSION:-latest} - entrypoint: ["sh", "-c", "/app/bin/genlayernode run --password ${NODE_PASSWORD:-12345678}"] + # The keystore password is NOT on this command line. It reaches the node as + # GENLAYERNODE_PASSWORD (environment below), which the run command reads in + # place of --password. A command-line argument is visible in process + # listings on the host; an environment variable is not. + entrypoint: ["/app/bin/genlayernode", "run"] container_name: genlayer-node restart: unless-stopped env_file: - path: ./.env required: false + environment: + # ":?" makes Compose refuse to start when NODE_PASSWORD is unset or empty, + # before any container runs. There is no fallback: this is the password + # that encrypts the validator's signing key, and a default shared by every + # operator who forgot to set one is a key anyone can open. The release + # binary carries no default either — it is compiled out of production. + GENLAYERNODE_PASSWORD: "${NODE_PASSWORD:?NODE_PASSWORD is not set. It is the keystore password that unlocks the validator key. Set it in .env; see README section 4.}" + # WebDriver metrics collector reaches Docker via the read-only proxy + # (see docker-socket-proxy above), never the host socket directly. + DOCKER_HOST: tcp://docker-socket-proxy:2375 ports: - "${NODE_RPC_PORT:-9151}:9151" - "${NODE_OPS_PORT:-9153}:9153" @@ -26,10 +123,12 @@ services: # The trusted protocol manifest of the network; consensus.manifestpath names this path. - ${NODE_MANIFEST_PATH:-./configs/node/protocol-manifest.json}:/app/configs/node/protocol-manifest.json:ro - ${NODE_DATA_PATH:-./data}:/app/data - - ./genvm-module-web-docker.yaml:/app/third_party/genvm/config/genvm-module-web.yaml - - /var/run/docker.sock:/var/run/docker.sock:ro # required for webdriver metrics collection + # opt/genvm/current links to the image's GenVM tree, so no version variable is needed. + - ./genvm-module-web-docker.yaml:/app/opt/genvm/current/config/genvm-module-web.yaml security_opt: - no-new-privileges:true + cap_drop: + - ALL logging: driver: "json-file" options: @@ -38,5 +137,6 @@ services: compress: "true" depends_on: - webdriver-container + - docker-socket-proxy profiles: - node diff --git a/content/validators/env.example b/content/validators/env.example new file mode 100644 index 00000000..67e1f05a --- /dev/null +++ b/content/validators/env.example @@ -0,0 +1,190 @@ +# GenLayer Node Release Configuration +# Copy this file to .env and configure for your deployment + +# ============================================================================= +# WebDriver Configuration (required for GenVM) +# ============================================================================= +WEBDRIVER_PORT=4444 + +# ============================================================================= +# Node Service Configuration (optional - use node profile) +# ============================================================================= +# Docker image version +NODE_VERSION=latest + +# Keystore password (unlocks the pre-imported validator wallet). Required — the +# stack refuses to start without it. Choose your own; never reuse an example +# value. It reaches the node as an environment variable, not a command-line +# argument. See README section 4 for reading it in without echo. +NODE_PASSWORD= + +# Path to node configuration file +NODE_CONFIG_PATH=./configs/node/config.yaml + +# Path to the trusted protocol manifest of your network +NODE_MANIFEST_PATH=./configs/node/protocol-manifest.json + +# Path to node data directory (for persistence) +NODE_DATA_PATH=./data + +# Port mappings +NODE_RPC_PORT=9151 +NODE_OPS_PORT=9153 + +# LLM API Keys (required for GenVM LLM module) +OPENROUTERKEY= +HEURISTKEY= +ANTHROPICKEY= +XAIKEY= +GEMINIKEY= +ATOMAKEY= + +# Optional logging level override +GENLAYERNODE_LOGGING_LEVEL=INFO + +# API key of the GenLayer Chain RPC provider, for both the RPC and the +# WebSocket endpoint of the node config. Optional: leave it empty when the +# endpoints need no key. Put the key only here, never in the endpoint URLs of +# the config: a key in a URL ends up in logs and error texts. +GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY= + +# Operator bearer token for the ops server's POST /snapshot route. When +# unset, the route is default-deny (every caller gets 401). Set this to +# enable operator-driven snapshot tooling from outside the node container. +# Generate with: openssl rand -hex 32 +GENLAYERNODE_NODE_OPERATOR_TOKEN= + +# ============================================================================= +# Monitoring Configuration (optional - use monitoring profile) +# ============================================================================= + +# Where Alloy pushes metrics and logs. All six values are REQUIRED when the +# monitoring profile is up — the alloy container stops with a clear message +# when any is missing. There are no defaults on purpose: an endpoint default +# sends your data to somebody else's tenant, and a credential default gives +# everybody the same key. +# +# Examples: +# Your own local stack: +# CENTRAL_MONITORING_URL=http://localhost:9090/api/v1/write +# CENTRAL_LOKI_URL=http://localhost:3100/loki/api/v1/push +# A Grafana Cloud tenant (values from your Grafana Cloud stack page): +# CENTRAL_MONITORING_URL=https:///api/prom/push +# CENTRAL_LOKI_URL=https:///loki/api/v1/push +# +# A receiver without authentication still needs the username/password set — +# any placeholder value satisfies the start check and is ignored by the +# receiver. +CENTRAL_MONITORING_URL= +CENTRAL_LOKI_URL= + +# Metrics (Prometheus) credentials +CENTRAL_MONITORING_USERNAME= +CENTRAL_MONITORING_PASSWORD= +# Logs (Loki) credentials +CENTRAL_LOKI_USERNAME= +CENTRAL_LOKI_PASSWORD= + +# ============================================================================= +# Single Node Configuration +# ============================================================================= +# Use these variables for monitoring a single node + +# Node identification +NODE_ID=validator-001 +VALIDATOR_NAME=MyValidator +# Network label for recovery logs and alert routing; use the deployed network. +# Leave empty for an unknown label, rather than guessing a production network. +NETWORK_NAME= + +# Node metrics endpoint (optional) +# Endpoint where the node exposes its Prometheus metrics +# Default: host.docker.internal:9153 +NODE_METRICS_ENDPOINT=host.docker.internal:9153 + +# ============================================================================= +# Multi-Node Configuration +# ============================================================================= +# Alternative to single node config: provide a JSON array of targets +# When SCRAPE_TARGETS_JSON is set, it overrides NODE_ID, VALIDATOR_NAME, and NODE_METRICS_ENDPOINT +# +# Format: JSON array of target objects with required fields: +# - __address__: "host:port" of the metrics endpoint +# - instance: unique identifier (e.g., node address) +# - validator_name: human-readable name +# +# Example for multiple nodes: +# SCRAPE_TARGETS_JSON=[{"__address__":"host.docker.internal:9250","instance":"0x...","validator_name":"node-1"},{"__address__":"host.docker.internal:9251","instance":"0x...","validator_name":"node-2"}] +# +# To generate for local nodes, use: ./generate-dev-targets.sh +# SCRAPE_TARGETS_JSON= + +# ============================================================================= +# Log Collection Configuration +# ============================================================================= +# Local log directory (on host machine) +# Path where GenLayer node(s) write their log files +# Single node: ./data/node/logs +# Multi-node: ./data/node/default (parent directory containing all node subdirs) +NODE_LOGS_PATH=./data/node/logs + +# Log file pattern (inside container, mapped from NODE_LOGS_PATH) +# Single node: /var/log/genlayer/node.log +# Multi-node: /var/log/genlayer/*/logs/node.log +# Default: /var/log/genlayer/node*.log (matches both patterns) +LOG_FILE_PATTERN=/var/log/genlayer/node*.log + +# Metrics scraping intervals (optional) +# How often to scrape metrics from the node +# Default: 15s for node metrics, 10s timeout +METRICS_SCRAPE_INTERVAL=15s +METRICS_SCRAPE_TIMEOUT=10s + +# Alloy internal health monitoring interval (optional) +# How often Alloy checks its own internal health metrics +# These metrics are not forwarded, only used for local debugging +# Default: 60s +ALLOY_SELF_MONITORING_INTERVAL=60s + +# Loki log batching configuration (optional) +# Controls how logs are batched before sending to central Loki +# Default: 1MiB batch size, 1s wait time +LOKI_BATCH_SIZE=1MiB +LOKI_BATCH_WAIT=1s + +# ============================================================================= +# Deployment Instructions +# ============================================================================= +# +# 1. Basic deployment (WebDriver only): +# docker compose up -d +# +# 2. Node deployment (WebDriver + Node): +# Configure node settings above, then: +# docker compose --profile node up -d +# +# 3. Node with monitoring (WebDriver + Node + Alloy): +# Configure node and monitoring settings, then: +# docker compose --profile node --profile monitoring up -d +# +# 4. Monitoring only (WebDriver + Alloy): +# Configure monitoring settings, then: +# docker compose --profile monitoring up -d +# +# 5. Check status: +# docker compose ps +# docker compose logs genlayer-node +# docker compose logs alloy +# +# ============================================================================= +# Configuration Notes +# ============================================================================= +# +# - Grafana Alloy handles both metrics and logs in a single service +# - To customize monitoring, edit alloy-config.river +# - To filter metrics (reduce bandwidth), uncomment the filtering rule in alloy-config.river +# - Ensure firewall allows outbound connections to monitoring server (port 8180) +# + +# Keep health checks but disable automatic container-local recycling when false. +HEALTHCHECK_AUTO_RESTART=true diff --git a/content/validators/greybox-setup-guide.md b/content/validators/greybox-setup-guide.md index d690436f..08709109 100644 --- a/content/validators/greybox-setup-guide.md +++ b/content/validators/greybox-setup-guide.md @@ -1,10 +1,14 @@ # Greybox LLM Strategy — Validator Setup Guide -Switch your GenLayer node from random LLM provider selection to deterministic ordered fallback via OpenRouter. +Switch your GenLayer node from the default auto-generated provider chain to a curated, operator-controlled fallback chain via OpenRouter. ## What is Greybox? -By default, the node picks a random LLM provider for each call. With **greybox**, the node uses a fixed priority chain configured via `meta.greybox` fields in the YAML config. +The default policy (`genvm-llm-default.lua`) is already deterministic: it auto-builds a single chain over every enabled model, ordered by provider `meta.priority` **descending**, and falls through to the next candidate on any failure. What it is not is *curated* — it takes whatever models are enabled, in one undifferentiated chain. + +With **greybox**, the operator picks the models and their order by hand, in two independent chains (text and image), configured via `meta.greybox` fields on each model in the YAML. Note the two knobs run in opposite directions: `meta.priority` is highest-first, `meta.greybox` is lowest-first. + +Greyboxing is deliberately per-validator — validators are expected to diverge in provider choice, so there is no single "correct" chain to converge on. **Default text chain:** deepseek-v3.2 → qwen3-235b → claude-haiku-4.5 → kimi-k2 → glm-5 → llama-3.3 (heurist) → llama-3.3 (ionet) @@ -44,30 +48,30 @@ The tarball ships with a unified config that includes all backends (openrouter, ```bash VERSION=$(readlink /opt/genlayer-node/bin | sed 's|/bin||; s|.*/||') -cp /opt/genlayer-node/${VERSION}/third_party/genvm/config/genvm-modules-llm-release.yaml \ - /opt/genlayer-node/${VERSION}/third_party/genvm/config/genvm-module-llm.yaml +cp /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-modules-llm-release.yaml \ + /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml ``` ### 4. Switch to greybox strategy ```bash sed -i 's/genvm-llm-default\.lua/genvm-llm-greybox.lua/' \ - /opt/genlayer-node/${VERSION}/third_party/genvm/config/genvm-module-llm.yaml + /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml ``` ### 5. Verify the config ```bash # Check lua script path -grep lua_script_path /opt/genlayer-node/${VERSION}/third_party/genvm/config/genvm-module-llm.yaml +grep lua_script_path /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml # Expected: lua_script_path: ${exeDir}/../config/genvm-llm-greybox.lua # Check openrouter is present -grep -A2 'openrouter:' /opt/genlayer-node/${VERSION}/third_party/genvm/config/genvm-module-llm.yaml +grep -A2 'openrouter:' /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml # Expected: enabled: true # Check the greybox Lua file exists -ls -la /opt/genlayer-node/${VERSION}/third_party/genvm/config/genvm-llm-greybox.lua +ls -la /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-llm-greybox.lua ``` ### 6. Start the node @@ -92,13 +96,13 @@ greybox: success provider: openrouter model: deepseek/deepseek-v3.2 is_ ## Switching Back to Default -To revert to random provider selection: +To revert to the default generated chain: ```bash sudo systemctl stop genlayer-node sed -i 's/genvm-llm-greybox\.lua/genvm-llm-default.lua/' \ - /opt/genlayer-node/${VERSION}/third_party/genvm/config/genvm-module-llm.yaml + /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml sudo systemctl start genlayer-node ``` @@ -177,23 +181,13 @@ After editing the YAML, restart the LLM module (see "Updating Greybox on a Runni - Run `task node:doctor` — the "LLM Providers" section probes each enabled backend and reports which ones actually answer, rather than only whether a key variable is set - Fallback providers (heurist, ionet) also need valid keys if you want fallback to work -## No usable LLM backend: the node starts DEGRADED - -A node with no working LLM backend no longer refuses to start. It boots, syncs and -stays in the validator set, but it votes **Timeout** on every transaction that -needs an LLM, logs a loud alert naming the broken module, and reports -`genvm_modules.llm.state = "degraded"` on ops `/health` while `status` stays -`"up"`. That is better than the old behaviour — the node used to exit about 75 -seconds after start and then be banned for idleness — but it is **not** a working -validator: Timeout votes are penalized. Treat it as a failure to fix, not a state -to run in. The same applies to the web module when the webdriver is unreachable. - -Recovery is automatic once a provider answers again — the node learns from its own -transaction traffic and needs no restart. The **one exception** is a key added to -`.env`: the node reads its environment at exec, so a running process cannot pick -that up. Restart it. Editing `genvm-module-llm.yaml` does not require a restart. - -**Configure more than one provider.** A backend only enters the fallback chain if -its key is set, so a node set up with OpenRouter alone has no fallback: when that -provider goes down or is decommissioned, the chain exhausts immediately. `doctor` -warns when only one backend is usable. +**Configure more than one provider.** A backend only enters the fallback chain +if its key is set, so a node set up with OpenRouter alone has no fallback: when +that provider goes down or is decommissioned, the chain exhausts immediately. +The node stays up and keeps voting, but it votes Timeout on every transaction +needing an LLM, and Timeout votes are penalized. `doctor` warns when only one +backend is usable. + +Note that a key added to `.env` needs a node restart to take effect — a running +process cannot pick up environment changes. Editing `genvm-module-llm.yaml` +does not. diff --git a/pages/api-references/genlayer-node.mdx b/pages/api-references/genlayer-node.mdx index 3ff6d02a..1f3bb132 100644 --- a/pages/api-references/genlayer-node.mdx +++ b/pages/api-references/genlayer-node.mdx @@ -18,16 +18,16 @@ Executes a new message call immediately without creating a transaction on the bl - `data` (string, required): The encoded function call data - `type` (string, required): The type of call (`"read"`, `"write"`, or `"deploy"`) - `blockNumber` (string, optional): The block number to query at (hex-encoded). If not provided, uses latest block - - `status` (string, optional): The transaction status filter (`"finalized"` or `"accepted"`). If not provided, uses accepted state + - `status` (string, optional): The transaction status filter (`"finalized"` or `"decided"`). If not provided, uses decided state - `value` (string, optional): The value to send with the call (hex-encoded) - - `leader_results` (array of strings or null, optional): When provided, GenVM runs in validator mode (executing `validator_fn` instead of `leader_fn` in `run_nondet` calls). Each element is a hex-encoded equivalence output from a previous leader execution. When omitted or null, GenVM runs in leader mode + - `leader_public_data` (string or null, optional): Hex-encoded data returned by a previous leader execution. Its encoding is defined by the GenVM specification. When provided, GenVM runs in validator mode; when omitted or null, GenVM runs in leader mode **Returns:** A structured JSON object containing execution results and debug information **Response Fields:** - `data` (string): Hex-encoded return value from contract execution -- `eqOutputs` (array of strings): Equivalence outputs from GenVM execution, each as a hex-encoded string. These are deterministic outputs used for consensus validation. Populated in leader mode; empty in validator mode +- `leaderPublicData` (string): Hex-encoded leader data whose encoding is defined by the GenVM specification. Populated in leader mode; empty in validator mode - `status` (object): Execution status - `code` (number): Status code (0=success, 1=user error, 2=vm error, 3=internal error) - `message` (string): Human-readable status message @@ -42,9 +42,10 @@ Executes a new message call immediately without creating a transaction on the bl - `recipient` (string): Recipient address - `value` (string): Value in hex format - `data` (string): Message data as hex-encoded string - - `onAcceptance` (boolean): Whether message is sent on acceptance + - `onAcceptance` (boolean): Whether message is sent on acceptance. Always `false` for an external message (`messageType` 0): external messages on acceptance are not supported yet - `saltNonce` (string): Salt nonce in hex format -- `nondetDisagreementCallNo` (number or null): When running in validator mode (with `leader_results`), indicates which `run_nondet` call the validator disagreed on. Null when there is no disagreement or when running in leader mode +- `nondetDisagreementCallNo` (number or null): When running in validator mode (with `leader_public_data`), indicates which `run_nondet` call the validator disagreed on. Null when there is no disagreement or when running in leader mode +- `syncedBlock` (string): Hex-encoded block number that GenVM has synced to. Indicates data freshness — responses are based on state up to this block **Example - Deploy Call:** @@ -72,7 +73,7 @@ Executes a new message call immediately without creating a transaction on the bl "jsonrpc": "2.0", "result": { "data": "00", - "eqOutputs": [], + "leaderPublicData": "0e076e645f6f75747305", "status": { "code": 0, "message": "success" @@ -122,7 +123,7 @@ Executes a new message call immediately without creating a transaction on the bl } ``` -**Example - Read Call (Latest Accepted State):** +**Example - Read Call (Latest Decided State):** ```json { @@ -155,7 +156,7 @@ Executes a new message call immediately without creating a transaction on the bl "from": "0xf6A60752C24Cd3BFcAf8d387a5EB62d3746F44EE", "to": "0xCe5712E63fd5475288aB1B7c0a368B9417357b81", "blockNumber": "0x151ec5", - "status": "accepted" + "status": "decided" } ], "id": 1 @@ -191,7 +192,7 @@ When a contract emits internal messages (e.g., a router contract forwarding call "jsonrpc": "2.0", "result": { "data": "00", - "eqOutputs": [], + "leaderPublicData": "0e076e645f6f75747305", "status": { "code": 0, "message": "success" @@ -235,8 +236,8 @@ When a contract emits internal messages (e.g., a router contract forwarding call **Notes:** - The `blockNumber` parameter can be either hex-encoded with a `0x` prefix (e.g., `"0x151ec5"`) or decimal (e.g., `"1384133"`) -- Valid `status` values are `"accepted"` (default) and `"finalized"` -- When both parameters are omitted, the call executes against the latest accepted state +- Valid `status` values are `"decided"` (default) and `"finalized"` +- When both parameters are omitted, the call executes against the latest decided state - For `read` and `write` type calls, if the contract doesn't exist at the specified block/status combination, an error is returned with code `-32001` (Resource not found) and message `"contract not found at address 0x..."` - For `deploy` type calls, the `status` and `blockNumber` parameters are not used for contract state validation since no contract exists yet @@ -327,11 +328,19 @@ Retrieves the current state of a GenLayer intelligent contract at a specific blo - `request` (object, required): The contract state request parameters - `address` (string, required): The contract address to query - `blockNumber` (string, optional): The block number to query at (hex-encoded). If not provided, uses latest block - - `status` (string, optional): The transaction status filter (`"finalized"` or `"accepted"`). If not provided, uses accepted state + - `status` (string, optional): The transaction status filter (`"finalized"` or `"decided"`). If not provided, uses decided state **Returns:** Hex-encoded contract state data -**Example Request - Latest Accepted State:** +**Limits:** + +| Limit | Default | Config key | +|---|---|---| +| Contract bytes read per call | 16 MB | `node.rpc.contract_read_max_bytes` | + +This method reads the contract's whole storage, so its cost grows with the contract. The limit stops the read once the contract exceeds it and returns an error, rather than returning part of the state as if it were all of it. Operators may reduce it; `0` or less disables it. + +**Example Request - Latest Decided State:** ```json { @@ -356,7 +365,7 @@ Retrieves the current state of a GenLayer intelligent contract at a specific blo { "address": "0x73ca5a2b51edf506ceaf110a41780ec51294d89f", "blockNumber": "0x151ec5", - "status": "accepted" + "status": "decided" } ], "id": 1 @@ -376,7 +385,7 @@ Retrieves the current state of a GenLayer intelligent contract at a specific blo **Notes:** - The `blockNumber` parameter should be hex-encoded with a `0x` prefix -- Valid `status` values are `"accepted"` (default) and `"finalized"` +- Valid `status` values are `"decided"` (default) and `"finalized"` - The returned state is hex-encoded binary data with a `0x` prefix - Empty contract state returns `"0x"` - If the contract doesn't exist at the specified block/status, an error will be returned @@ -384,7 +393,7 @@ Retrieves the current state of a GenLayer intelligent contract at a specific blo **cURL Example:** ```bash -# Get latest accepted state +# Get latest decided state curl -X POST http://localhost:9151 \ -H "Content-Type: application/json" \ -d '{ @@ -396,7 +405,7 @@ curl -X POST http://localhost:9151 \ "id": 1 }' -# Get state at specific block with accepted status +# Get state at specific block with decided status curl -X POST http://localhost:9151 \ -H "Content-Type: application/json" \ -d '{ @@ -405,7 +414,7 @@ curl -X POST http://localhost:9151 \ "params": [{ "address": "0x73ca5a2b51edf506ceaf110a41780ec51294d89f", "blockNumber": "0x151ec5", - "status": "accepted" + "status": "decided" }], "id": 1 }' @@ -422,11 +431,19 @@ Retrieves the code of a deployed GenLayer intelligent contract at a specific blo - `request` (object, required): The contract code request parameters - `address` (string, required): The contract address to query - `blockNumber` (string, optional): The block number to query at (hex-encoded). If not provided, uses latest block - - `status` (string, optional): The transaction status filter (`"finalized"` or `"accepted"`). If not provided, uses accepted state + - `status` (string, optional): The transaction status filter (`"finalized"` or `"decided"`). If not provided, uses decided state **Returns:** Base64-encoded contract code -**Example Request - Latest Accepted State:** +**Limits:** + +| Limit | Default | Config key | +|---|---|---| +| Contract bytes read per call | 16 MB | `node.rpc.contract_read_max_bytes` | + +The node reads the contract's code slot directly, so the cost of a call is proportional to the code it returns rather than to everything the contract stores. The limit is a backstop above that: a contract whose declared code length exceeds it is refused before the bytes are read. Operators may reduce it; `0` or less disables it. + +**Example Request - Latest Decided State:** ```json { @@ -471,15 +488,16 @@ Retrieves the code of a deployed GenLayer intelligent contract at a specific blo **Notes:** - The `blockNumber` parameter should be hex-encoded with a `0x` prefix -- Valid `status` values are `"accepted"` (default) and `"finalized"` +- Valid `status` values are `"decided"` (default) and `"finalized"` - The returned code is base64-encoded - If the contract doesn't exist at the specified block/status, an error will be returned - If the address does not have code (not a contract), an error will be returned +- If the contract's code exceeds `node.rpc.contract_read_max_bytes`, the call is refused rather than truncated, so a partial answer is never returned as a whole one **cURL Example:** ```bash -# Get latest accepted code +# Get latest decided code curl -X POST http://localhost:9151 \ -H "Content-Type: application/json" \ -d '{ @@ -826,6 +844,7 @@ Returns one advanced protocol lifecycle snapshot for a transaction. This endpoin | `resolutionSourceCode` | uint8 | Resolution source code | | `decisionId` | string or null | Decimal decision identity used by decision-scoped protocol commands | | `decisionActive` | boolean | Whether `decisionId` still identifies the active decision | +| `executionGeneration` | string or null | Current execution generation as a decimal string from the same snapshot; independent of retained decision provenance. Null when unavailable in a synthetic response. | | `evaluatedAt` | integer | Unix timestamp used for the projection | **Example Request:** @@ -860,6 +879,7 @@ Returns one advanced protocol lifecycle snapshot for a transaction. This endpoin "resolutionSourceCode": 6, "decisionId": "42", "decisionActive": true, + "executionGeneration": "3", "evaluatedAt": 1787868000 }, "id": 1 @@ -936,6 +956,519 @@ Returns the GenVM sync status, indicating how far the node's local GenVM state i - Use `blocksBehind == 0` to determine if the node is fully synced - When the node is catching up, `gen_call` requests without an explicit `blockNumber` will serve data at the `syncedBlock` rather than waiting for full sync +**cURL Example:** + +```bash +curl -X POST http://localhost:9151 \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "gen_syncing", + "params": [], + "id": 1 + }' +``` + +### gen_estimateGas + +Reserved for estimating gas for a GenLayer transaction. **Not implemented** — the method is registered and reachable, but every call returns an error. + +**Method:** `gen_estimateGas` + +**Parameters:** None currently read — any params supplied are ignored because the method returns before parsing them + +**Returns:** Nothing. The call always fails with JSON-RPC error code `-32004` + +**Example Request:** + +```json +{ + "jsonrpc": "2.0", + "method": "gen_estimateGas", + "params": [], + "id": 1 +} +``` + +**Example Response:** + +```json +{ + "jsonrpc": "2.0", + "error": { + "code": -32004, + "message": "method not implemented: gen_estimateGas" + }, + "id": 1 +} +``` + +**Notes:** + +- The method is registered in the `genlayer` endpoint group, so it is reachable and rate-limited like any other method — it just cannot succeed +- The error code is `-32004` ("method not supported"), not `-32601` ("method not found"): the node recognises the method and is declining to serve it, rather than failing to route it +- Disabling the `genlayer` group, or this method specifically via `endpoints.methods`, changes the response to the same `-32004` code with a "method not supported" message +- Do not use gas estimation to size a transaction budget today; set fees explicitly instead + +**cURL Example:** + +```bash +curl -X POST http://localhost:9151 \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "gen_estimateGas", + "params": [], + "id": 1 + }' +``` + +### gen_getLogs + +Queries GenLayer contract-emitted events, mirroring `eth_getLogs` semantics under the `gen_` namespace. Events are produced by intelligent contracts (`gl.chain.Event` subclasses calling `.emit()`), persisted by the node while syncing accepted blocks, and indexed by contract address and topics. + +**Method:** `gen_getLogs` + +**Parameters:** + +- `filter` (object, required): + - `fromBlock` (`string`, optional): First block of the range (inclusive), as a hex (`0x…`), decimal string, or `"latest"`. Block numbers are rollup (L2) block numbers — the same numbering `gen_syncing` reports. Defaults to the latest GenVM-synced block. + - `toBlock` (`string`, optional): Last block of the range (inclusive). Same format and default as `fromBlock`. + - `address` (`string | string[]`, optional): Restrict to events emitted by this contract address (or any of these addresses). Empty matches all addresses. + - `topics` (`array`, optional): Positional topic filters, at most 4 entries. Each position is `null` (wildcard), a 32-byte hex topic, or an array of topics (OR within the position). Positions match `eth_getLogs` semantics: an event matches when every constrained position matches; an event with fewer topics than a constrained position does not match. + - `blockHash` (`string`, optional): 32-byte rollup block hash selecting exactly one block (EIP-234). Mutually exclusive with `fromBlock`/`toBlock`. The node resolves the hash through the rollup RPC; an unknown hash returns error `-32001`. + - `status` (`string[]`, optional): Lifecycle stages to return, any of `"accepted"`, `"finalized"`, `"cancelled"`. Filters on the emitting transaction's **current** stage, not on the stage it held at `blockNumber`. Defaults to `["accepted", "finalized"]` — every event whose emitting round still stands. An unknown value returns error `-32602`. + +**Returns:** + +An array of log objects, ordered by block number, then transaction, then per-transaction event index: + +- `address` (`string`): Address of the emitting contract. +- `topics` (`string[]`): The emitted topics, each a 32-byte hex string. `topics[0]` is the event signature topic: `keccak256` of the UTF-8 string `EventName(sortedIndexedFieldNames)` — GenLayer hashes the **sorted indexed field names without types**, unlike Solidity's typed signature. Remaining topics are the indexed field values (calldata-encoded, right-padded to 32 bytes, or keccak-hashed when longer). +- `data` (`string`): Hex of the event payload. **Deviation from eth:** this is the GenVM **calldata** encoding of the event's keyword-argument map, not ABI encoding — decode it with a GenLayer SDK calldata codec. +- `blockNumber` (`string`): Rollup block number (hex) of the block whose sync recorded the event. +- `transactionHash` (`string`): 32-byte GenLayer transaction ID. +- `logIndex` (`string`): **Deviation from eth:** index of the event **within its transaction** (hex), not within the block. +- `round` (`number`): GenLayer extra — the consensus round of the executed result that emitted the event. +- `status` (`string`): GenLayer extra — the emitting transaction's current lifecycle stage: `"accepted"`, `"finalized"`, or `"cancelled"`. **Not** the `gen_getTransactionStatus` vocabulary: that method's `"Canceled"` means a transaction cancelled before execution, whereas a `"cancelled"` log belongs to a transaction whose acceptance an appeal overturned and which is re-executed on a later round. +- `removed` (`boolean`): Carries eth's meaning — the log is no longer part of the canonical set. `true` exactly when `status` is `"cancelled"`. + +`blockHash` and `transactionIndex` are omitted (not indexed node-side). + +**Semantics and limitations:** + +- GenLayer events follow their transaction's lifecycle: they become emitted when the transaction is **ACCEPTED**, become **FINALIZED** with it, and can be **CANCELLED** by a successful appeal. Events from **successful, accepted** executions are recorded at acceptance time (a reverted transaction emits no logs, matching EVM semantics). +- **A log belongs to the block that emitted it.** `blockNumber` is always the acceptance block and `fromBlock`/`toBlock` select on it, so a log's block is stable while its `status` moves. Each log is returned exactly once per query, whatever combination of statuses is requested — including when a transaction is accepted and finalized within the same block. +- **`status` is current, not as-of.** The same query run twice can report a different `status` for a log (accepted → finalized) or stop returning it (accepted → cancelled), the same way `gen_getTransactionStatus` reports current state. +- **Default excludes cancelled logs.** A query with no `status` returns `["accepted", "finalized"]`. Events whose round was overturned on appeal are reachable with `status: ["cancelled"]`, where they carry `removed: true`. +- **Retention.** Operators bound the store with `node.events.retention_blocks` (default 1,000,000 blocks; `0` disables). Queries below the retention watermark return no logs. +- **Deviation from eth:** an event emitted inside a write-enabled sandbox (`allow_write_ops=True`) whose failure the contract catches still surfaces on the successful transaction — GenVM sandboxes are permission boundaries, not transactional ones, so caught-failure branch events are part of the transaction's log set (EVM drops reverted-sub-call logs). +- Coverage requires the transaction to have executed on an executor that reports events in its result (all executors bundled with the currently pinned GenVM manager do); a future pin bundling a pre-2025-12 executor would not surface events for transactions it executes. +- Results reflect **synced** state: a transaction accepted moments ago becomes queryable once the sync loop writes its block (same trailing semantics as `gen_getContractState` at `latest`). + +**Limits:** + +| Limit | Default | Config key | +|---|---|---| +| Block range per query | 10,000 blocks | `node.rpc.getlogs_max_block_range` | +| Logs per response | 10,000 entries | `node.rpc.getlogs_max_results` | +| Event retention | 1,000,000 blocks | `node.events.retention_blocks` | + +Both are node-configurable (operators may reduce them); exceeding either returns error `-32005` (`query exceeds limits: …` — distinct from HTTP rate limiting, which uses the same code). The method carries a default rate limit of 2 requests per second per IP, also configurable. + +**Errors:** + +- `-32602`: malformed filter (invalid hex, more than 4 topic positions, non-32-byte topic, `blockHash` combined with a block range, `fromBlock` greater than `toBlock`, unknown `status` value). +- `-32005`: block range or result count over the configured cap. +- `-32001`: `blockHash` unknown to the rollup. +- `-32000`: internal failure (details are logged, not returned). + +**Example Request:** + +```json +{ + "jsonrpc": "2.0", + "method": "gen_getLogs", + "params": [ + { + "fromBlock": "0x14e300", + "toBlock": "0x14e400", + "address": "0x73ca5a2b51edf506ceaf110a41780ec51294d89f", + "topics": [ + "0x9f6dbb5f0f942152b8b254e69610e1c65e33a1bb61bd8f4bd88f0ec7bbe15c2f", + null + ] + } + ], + "id": 1 +} +``` + +**Example Response:** + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "address": "0x73ca5a2b51edf506ceaf110a41780ec51294d89f", + "topics": [ + "0x9f6dbb5f0f942152b8b254e69610e1c65e33a1bb61bd8f4bd88f0ec7bbe15c2f", + "0x2a00000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x261674696d657374616d70014996d2c96e", + "blockNumber": "0x14e309", + "transactionHash": "0x635060dd514082096d18c8eb64682cc6a944f9ce1ae6982febf7a71e9f656f49", + "logIndex": "0x0", + "round": 0, + "status": "finalized", + "removed": false + } + ] +} +``` + +### gen_getTransactionData + +Retrieves the on-chain transaction data for one round together with the contract execution outputs (`returnData`, `leaderPublicData`) recovered from the node's local trace store. If the node no longer holds the trace (restart, pruning), the on-chain fields are still returned with `returnData: null` — the transaction is never replayed. + +**Method:** `gen_getTransactionData` + +**Parameters:** + +- `request` (object, required): + - `txId` (`string`, required): The 32-byte transaction hash as a hexadecimal string with a `0x` prefix. + - `round` (`uint8`, optional): The consensus round whose execution outputs to return. Defaults to the latest round. + +**Returns:** + +An object with the on-chain transaction fields for the selected round plus the locally stored execution outputs: + +- `id` (`string`): The transaction ID as a hex string. +- `sender` (`address`): The address of the sender. +- `recipient` (`address`): The destination address of the transaction. +- `activator` (`address`): The address of the validator that activated the transaction. +- `status` (`uint8`): The current status of the transaction. +- `statusName` (`string`): The ABI transaction status name for `status` (`Uninitialized`, `Pending`, `Proposing`, `Committing`, `Revealing`, `Accepted`, `Undetermined`, `Finalized`, `Canceled`, `AppealRevealing`, `AppealCommitting`, `ValidatorsTimeout`, `LeaderTimeout`, or `LeaderRevealing`). +- `result` (`uint8`): The consensus result of the transaction (`0` Idle, `1` MajorityAgree, `2` MajorityDisagree, `3` MajorityTimeout, `4` DeterministicViolation, `5` NoMajority). +- `resultName` (`string`): The ABI result name for `result`. +- `txExecutionResult` (`uint8`): The leader execution vote result (`0` NotVoted, `1` FinishedWithReturn, `2` FinishedWithError, `3` Timeout, `4` NondetDisagree). +- `txExecutionResultName` (`string`): The ABI vote type name for `txExecutionResult`. +- `txCallData` (`string`): The transaction call data as a hex string. +- `txExecutionHash` (`string`): The execution hash of the transaction as a hex string. +- `eqBlocksOutputs` (`string`): The equivalent blocks outputs as a hex string. +- `timestamps` (`object`): Object containing various timestamps: + - `Created` (`uint256`): When the transaction was created. + - `Pending` (`uint256`): When the transaction became pending. + - `Activated` (`uint256`): When the transaction was activated. + - `Proposed` (`uint256`): When the transaction was proposed. + - `Committed` (`uint256`): When the transaction was committed. + - `LastVote` (`uint256`): When the last vote was cast. + - `AppealSubmitted` (`uint256`): When an appeal was submitted. + - `LeaderRevealed` (`uint256`): When the leader was revealed. +- `round` (`uint8`): The round the execution outputs belong to (the requested round, or the latest round when omitted). +- `returnData` (`string|null`): The contract's return data as a `0x`-prefixed hex string, from the node's locally stored execution trace. `null` when the node holds no trace for the round — clients must handle `null` explicitly. +- `leaderPublicData` (`string`): Leader data as a `0x`-prefixed hex string from the stored trace. Its encoding is defined by the GenVM specification. `0x` when no trace is available. + +**Example Request:** + +```json +{ + "jsonrpc": "2.0", + "method": "gen_getTransactionData", + "params": [ + { + "txId": "0x635060dd514082096d18c8eb64682cc6a944f9ce1ae6982febf7a71e9f656f49" + } + ], + "id": 1 +} +``` + +**Example Response:** + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "id": "0x635060dd514082096d18c8eb64682cc6a944f9ce1ae6982febf7a71e9f656f49", + "sender": "0x5b70759760a85d92bf972b67ee8558e8e8da87d4", + "recipient": "0x73ca5a2b51edf506ceaf110a41780ec51294d89f", + "activator": "0xf27b07d36ff5b9dcd37fd3a09811d6456dd4483a", + "status": 7, + "statusName": "Finalized", + "result": 1, + "resultName": "MajorityAgree", + "txExecutionResult": 1, + "txExecutionResultName": "FinishedWithReturn", + "txCallData": "f5b31604617267730da4017570646174656420746573742073746f72616765066d6574686f64747570646174655f73746f7261676500", + "txExecutionHash": "0x76744dc491b1725eb2ba08cf516855c14aac310e36711368c04442fd39d61853", + "eqBlocksOutputs": "c0", + "timestamps": { + "Created": 1750774777, + "Pending": 1750774777, + "Activated": 1750774781, + "Proposed": 1750774786, + "Committed": 1750774791, + "LastVote": 1750774795, + "AppealSubmitted": 0, + "LeaderRevealed": 0 + }, + "round": 0, + "returnData": "0x00", + "leaderPublicData": "0x0e076e645f6f7574730d1b01aabb" + } +} +``` + +**Notes:** + +- Multi-round appeal data (`roundData`) and fee accounting stay on `gen_getTransactionReceipt`; debug output (stdout, stderr, genvm_log) and trace replay stay on `gen_dbg_traceTransaction`. +- `returnData` and `leaderPublicData` come from the node's local trace store only — a node that never executed the transaction (or pruned the trace) serves `returnData: null` and `leaderPublicData: "0x"` without replaying. +- **Retention.** `returnData` and `leaderPublicData` come from the trace store, which operators bound with `node.trace.retention_blocks` (default 1,000,000 blocks; `0` disables). Past the watermark this endpoint answers from chain data with `returnData: null`, exactly as it does for a transaction the node never executed. +- The trace is persisted asynchronously after a transaction is accepted, so a query issued immediately after acceptance can still return `returnData: null`. Clients that need the execution output should poll until it appears (or fall back to `gen_dbg_traceTransaction`, which replays on demand). +- An explicit `round` beyond the rounds the transaction has returns a `-32001` error. +- A malformed `txId` (not a 32-byte hex string) returns `-32602`; a transaction not found on-chain returns `-32001`. +- This endpoint is in the `genlayer` group and is enabled by default. + +**cURL Example:** + +```bash +# Latest round +curl -X POST http://localhost:9151 \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "gen_getTransactionData", + "params": [{ + "txId": "0x635060dd514082096d18c8eb64682cc6a944f9ce1ae6982febf7a71e9f656f49" + }], + "id": 1 + }' + +# Specific round +curl -X POST http://localhost:9151 \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "gen_getTransactionData", + "params": [{ + "txId": "0x635060dd514082096d18c8eb64682cc6a944f9ce1ae6982febf7a71e9f656f49", + "round": 1 + }], + "id": 1 + }' +``` + +### gen_protocolVersion + +Reserved for reporting the GenLayer protocol version. **Not implemented** — the method is registered and reachable, but every call returns an error. + +**Method:** `gen_protocolVersion` + +**Parameters:** None currently read — any params supplied are ignored because the method returns before parsing them + +**Returns:** Nothing. The call always fails with JSON-RPC error code `-32004` + +**Example Request:** + +```json +{ + "jsonrpc": "2.0", + "method": "gen_protocolVersion", + "params": [], + "id": 1 +} +``` + +**Example Response:** + +```json +{ + "jsonrpc": "2.0", + "error": { + "code": -32004, + "message": "method not implemented: gen_protocolVersion" + }, + "id": 1 +} +``` + +**Notes:** + +- The method is registered in the `genlayer` endpoint group, so it is reachable and rate-limited like any other method — it just cannot succeed +- The error code is `-32004` ("method not supported"), not `-32601` ("method not found"): the node recognises the method and is declining to serve it, rather than failing to route it +- To identify what a node is running, use the ops server's `GET /health`, which reports node version and sync state +- `eth_protocolVersion` is unaffected — it is proxied to the GenLayer chain like any other `eth_*` method + +**cURL Example:** + +```bash +curl -X POST http://localhost:9151 \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "gen_protocolVersion", + "params": [], + "id": 1 + }' +``` + +### gen_subscribe + +Opens a subscription to a stream of GenLayer consensus events over the node's inbound WebSocket endpoint. Notifications are pushed as they occur, removing the need to poll `gen_getTransactionStatus`. + +**Method:** `gen_subscribe` + +**Transport:** WebSocket only. This method is not available over HTTP — an HTTP POST cannot receive push notifications. See the WebSocket endpoint section for the URL. + +**Parameters:** + +- `stream` (string, required): The event stream to subscribe to. One of `transactions`, `leadership`, `epochs`, `newHeads`. + +**Returns:** A subscription id (UUID v4 string) used to correlate notifications and to cancel via `gen_unsubscribe` + +**Streams:** + +| Stream | Event kinds delivered | +|--------|-----------------------| +| `transactions` | `NewTransaction`, `TransactionActivated`, `TransactionReceiptProposed`, `VoteCommitted`, `VoteRevealed`, `TransactionAccepted`, `TransactionFinalized`, `TransactionUndetermined` | +| `leadership` | `TransactionLeaderRotated`, `TransactionLeaderTimeout`, `TransactionLeaderChanged`, `TransactionLeaderRevealed`, `LeaderIdlenessProcessed`, `AppealStarted` | +| `epochs` | `EpochAdvance`, `EpochFinalize`, `TimeoutsSet`, `SetEpochMinDuration` | +| `newHeads` | `NewHeader` | + +**Notification Fields:** + +| Field | Type | Description | +|-------|------|-------------| +| `kind` | string | The event name, matching the consensus contract's ABI event (e.g. `TransactionAccepted`) | +| `event` | object | The decoded event payload. `bytes32` values are 0x-prefixed hex; field names are camelCase | +| `event.raw` | object | The originating chain log (block hash, transaction hash, topics), for reconciling against `gen_getLogs` | + +**Example Request:** + +```json +{ + "jsonrpc": "2.0", + "method": "gen_subscribe", + "params": [ + "transactions" + ], + "id": 1 +} +``` + +**Example Response:** + +```json +{ + "jsonrpc": "2.0", + "result": "b3f1c2de-9a44-4c1e-8f77-2b0a5d6e1c33", + "id": 1 +} +``` + +**Example Notification:** + +```json +{ + "jsonrpc": "2.0", + "method": "gen_subscription", + "params": { + "subscription": "b3f1c2de-9a44-4c1e-8f77-2b0a5d6e1c33", + "result": { + "kind": "TransactionAccepted", + "event": { + "txId": "0x9c1f3a7b8d2e4f5061728394a5b6c7d8e9f0a1b2c3d4e5f60718293a4b5c6d7e", + "raw": { + "address": "0x1234567890abcdef1234567890abcdef12345678", + "blockNumber": "0x151ec5", + "transactionHash": "0x4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c" + } + } + } + } +} +``` + +**Notes:** + +- The subscription id is a UUID v4 generated by this node; it is never an upstream chain subscription id +- Streams are coarse by design: one subscription covers a whole lifecycle, so following a transaction end to end costs one subscription rather than one per event kind +- The `kind` values are the consensus contracts' ABI event names, which are stable across node releases. They are deliberately not the node's internal topic names +- Every payload is derived from on-chain logs and contains no node-private data. The node's own GenVM sync position is not exposed here; use `gen_syncing` +- `newHeads` is served for convenience and duplicates `eth_subscribe("newHeads")`, which is fed from the same upstream. Latency-sensitive clients should prefer `eth_subscribe` +- Notifications are live only — there is no historical replay or backfill. Reconcile any gap with `gen_getLogs` +- Server-side filtering (for example by `txId`) is not supported; subscribe to the stream and filter client-side +- Subscribing to an unknown stream returns error code `-32602` with the list of supported streams +- Exceeding `max_subs_per_conn` returns error code `-32000` ("subscription limit reached") +- This method belongs to the `genlayer` endpoint group; disabling that group disables it on the WebSocket transport too +- Standard clients can consume this with their generic subscription API, since notifications use the conventional `gen_subscription` envelope. In go-ethereum: `client.Subscribe(ctx, "gen", ch, "transactions")` + +**wscat Example:** + +```bash +wscat -c ws://localhost:9151/ws +> {"jsonrpc":"2.0","method":"gen_subscribe","params":["transactions"],"id":1} +< {"jsonrpc":"2.0","result":"b3f1c2de-9a44-4c1e-8f77-2b0a5d6e1c33","id":1} +< {"jsonrpc":"2.0","method":"gen_subscription","params":{"subscription":"b3f1c2de-9a44-4c1e-8f77-2b0a5d6e1c33","result":{"kind":"NewTransaction","event":{"txId":"0x9c1f..."}}}} +``` + +### gen_unsubscribe + +Cancels a subscription previously opened with `gen_subscribe`. The node stops sending notifications for that subscription and releases the resources backing it. + +**Method:** `gen_unsubscribe` + +**Transport:** WebSocket only, on the same connection that opened the subscription. + +**Parameters:** + +- `subscriptionId` (string, required): The id returned by `gen_subscribe` + +**Returns:** Boolean — `true` when a matching subscription was cancelled, `false` when the id is unknown on this connection + +**Example Request:** + +```json +{ + "jsonrpc": "2.0", + "method": "gen_unsubscribe", + "params": [ + "b3f1c2de-9a44-4c1e-8f77-2b0a5d6e1c33" + ], + "id": 2 +} +``` + +**Example Response:** + +```json +{ + "jsonrpc": "2.0", + "result": true, + "id": 2 +} +``` + +**Notes:** + +- An unknown subscription id is not an error: the response is `result: false`, matching the `eth_unsubscribe` convention +- Subscriptions are scoped to the connection that created them. One connection cannot cancel another's subscription, and an id from a closed connection always returns `false` +- Explicit unsubscription is optional for cleanup: closing the WebSocket cancels every subscription on that connection automatically +- A notification already in flight when the cancellation is processed may still arrive; treat the `true` response as "no further events will be produced", not as a barrier +- A missing or non-string parameter returns error code `-32602` + +**wscat Example:** + +```bash +wscat -c ws://localhost:9151/ws +> {"jsonrpc":"2.0","method":"gen_unsubscribe","params":["b3f1c2de-9a44-4c1e-8f77-2b0a5d6e1c33"],"id":2} +< {"jsonrpc":"2.0","result":true,"id":2} +``` + ## Debug Methods These methods are available for debugging and testing purposes during development. @@ -1009,6 +1542,7 @@ Get trie information for debugging node internal DB state. "result": "graph TD\nnode0@{label: \"br seq=1\"}\nnode1@{label: \"ext seq=1 0x00000000000000000000000000000000000000000000000000000000000000000000000\"}\nnode1 --\u003e data2@{label: \"seq=1\n[21 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]\"}\nnode0 --\u003e|0| node1\nnode3@{label: \"ext seq=1 0x72d46c3ada9f897c74d349bbfe0e450c798167c9f580f8daf85def57e96c3ea00000000\"}\nnode3 --\u003e data4@{label: \"seq=1\n[105 110 105 116 105 97 108 32 115 116 111 114 97 103 101 32 118 97 108 117 101 0 0 0 0 0 0 0 0 0 0 0]\"}\nnode0 --\u003e|3| node3\n", "id": 1 } +``` ### gen_dbg_traceTransaction @@ -1029,14 +1563,15 @@ Retrieves the full execution trace for a transaction, including the return data, | Field | Type | Description | |-------|------|-------------| | `transaction_id` | string | Transaction hash | -| `result_code` | number | 0=success, 1=user error, 2=VM error | +| `result_code` | number | 0=success, 1=user error, 2=VM error, 3=internal error | | `return_data` | string | Hex-encoded contract return data | | `stdout` | string | Standard output captured during execution | | `stderr` | string | Standard error captured during execution | | `genvm_log` | array | Detailed GenVM execution log entries | | `storage_proof` | string | Hex-encoded storage proof hash | | `run_time` | string | Execution duration | -| `eq_outputs` | array | Hex-encoded equivalence principle outputs | +| `leader_public_data` | string | Hex-encoded leader data; its encoding is defined by the GenVM specification | +| `stored_at` | string (optional) | ISO 8601 timestamp of when the trace was stored; omitted when not available | **Example:** @@ -1070,7 +1605,7 @@ Retrieves the full execution trace for a transaction, including the return data, ], "storage_proof": "0x99ff0d9125e1fc9531a11262e15aeb2c60509a078c4cc4c64cefdfb06ff68647", "run_time": "0s", - "eq_outputs": [] + "leader_public_data": "0x0e076e645f6f75747305" }, "id": 1 } @@ -1079,8 +1614,61 @@ Retrieves the full execution trace for a transaction, including the return data, **Notes:** - If the trace isn't stored locally (e.g., the node restarted or data was pruned), the endpoint will replay the transaction automatically. -- This endpoint is in the `genlayer_debug` group and must be enabled in the node configuration. -- Use `result_code` to determine execution outcome: 0 = contract returned successfully, 1 = contract raised a user error, 2 = GenVM encountered an error. +- **Retention.** Operators bound the trace store with `node.trace.retention_blocks` (default 1,000,000 blocks; `0` disables). A trace below the retention watermark is gone, so this endpoint falls back to a live replay — reconstructed from the latest on-chain view, and not byte-identical to the stored artifact. A single trace larger than `node.trace.max_trace_bytes` (default 32 MB) is never stored, which produces the same fallback. +- **Field bound.** The free-form execution-output fields (`return_data`, `stdout`, `stderr`, `genvm_log`) carry whatever the replayed contract emitted, so their size is contract-controlled. Each is capped at `node.trace.max_debug_field_bytes` (default 256 KB; `0` takes the default). When a field is cut, the truncation is marked in-band — a trailing `...[truncated: N of M bytes omitted from gen_dbg trace]` on a string field, or a final `{"truncated": "..."}` entry on `genvm_log` — so the reply stays honest rather than silently short. +- This endpoint is in the `genlayer_debug` group, which is **disabled by default**. Enable it deliberately, and only on an RPC bind that untrusted callers cannot reach: it replays a transaction through GenVM and returns internal execution output, so it is debug tooling, not a public API. +- Use `result_code` to determine execution outcome: 0 = contract returned successfully, 1 = contract raised a user error, 2 = GenVM encountered an error, 3 = internal error. + +### gen_dbg_load_test + +Debug hook reserved for driving a node-side load test. The endpoint is wired end to end, but the service behind it is currently a no-op stub: the call succeeds and returns `null` without performing any work. + +**Method:** `gen_dbg_load_test` + +**Parameters:** None. Any params supplied are accepted and discarded — the handler does not read them + +**Returns:** `null` + +**Example Request:** + +```json +{ + "jsonrpc": "2.0", + "method": "gen_dbg_load_test", + "params": [], + "id": 1 +} +``` + +**Example Response:** + +```json +{ + "jsonrpc": "2.0", + "result": null, + "id": 1 +} +``` + +**Notes:** + +- A `null` result means "call accepted, nothing performed" — it is not an error and does not indicate a load test ran +- Because the handler ignores its params, this method cannot currently be tuned (operation count, test type, or duration). Do not build tooling that depends on such parameters +- Belongs to the `genlayer_debug` group, which **fails closed**: it is disabled unless an operator explicitly enables it, since the debug methods are reachable unauthenticated on the default `0.0.0.0` RPC bind +- When the group is disabled, the response is JSON-RPC error `-32004` ("method not supported") + +**cURL Example:** + +```bash +curl -X POST http://localhost:9151 \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "gen_dbg_load_test", + "params": [], + "id": 1 + }' +``` ## Ethereum Compatibility @@ -1237,7 +1825,7 @@ curl -X GET http://localhost:9153/balance ```json { - "message": "Balance endpoint not available in rollup mode" + "message": "Balance endpoint not available in full mode" } ``` @@ -1251,13 +1839,15 @@ curl -X GET http://localhost:9153/balance \ ### health -Returns the health status of the GenLayer node along with version information and the status of individual health checks. +Returns the node's operational status and the typed condition catalog that explains it. **Method:** `GET /health` **Parameters:** None -**Returns:** JSON object containing the overall health status, version information, and individual check results +**Returns:** JSON object containing the overall status, network/version information, validator identity (validator mode), and the full condition catalog + +**HTTP status:** `503` when `status` is `unavailable`; `200` for every other state. **Example Request:** @@ -1269,101 +1859,150 @@ curl -X GET http://localhost:9153/health ```json { - "status": "up", - "node_version": "v1.2.3", - "protocol_version": "consensus-v2.1", - "genvm_modules": { - "llm": { - "state": "healthy", - "timestamp": "2024-01-15T10:30:45Z", - "last_ok": "2024-01-15T10:30:45Z" - }, - "web": { - "state": "healthy", - "timestamp": "2024-01-15T10:30:45Z", - "last_ok": "2024-01-15T10:30:45Z" - } - }, - "checks": { - "test-check": { - "status": "up", - "timestamp": "2024-01-15T10:30:45Z" - } + "status": "healthy", + "network": "bradbury-phase2", + "node_version": "v0.6.0", + "protocol_version": "0.3.0", + "genvm_version": "v0.0.18", + "validator_address": "0x1234567890abcdef1234567890abcdef12345678", + "validator_moniker": "my-validator", + "conditions": { + "chain.rpc": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "chain.websocket": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "chain.sync": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:02:14Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "consensus.ban": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "consensus.stake": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "consensus.operator_funds": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "consensus.prime": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:04:41Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "genvm.llm": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:31Z", "updated_at": "2026-09-01T10:29:56Z", "last_ok": "2026-09-01T10:29:56Z" }, + "genvm.web": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:31Z", "updated_at": "2026-09-01T10:29:56Z", "last_ok": "2026-09-01T10:29:56Z" }, + "node.disk": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "node.snapshot_age": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "webdriver.render": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" } } } ``` **Response Fields:** -- `status` (string): Overall health status - "up" if all checks pass, "down" if any check fails -- `node_version` (string): The version of the GenLayer node software -- `protocol_version` (string): The consensus protocol version -- `genvm_modules` (object): Per-module GenVM health — whether this node can currently do LLM and web work. Reported for `llm` and `web`. - - `state` (string): `healthy`, `suspect`, `degraded`, or `unknown`. `suspect` means one failure has been observed and is not yet treated as confirmed; `unknown` means the module has not been exercised yet. - - `timestamp` (string, optional): ISO 8601 time the verdict was last updated. Absent until the module is first observed. - - `last_ok` (string, optional): ISO 8601 time the module last answered successfully. Absent if it never has. - - `detail` (string, optional): the module's own error text. Present only while not healthy. -- `checks` (object, optional): Map of individual health check results - - `[check-name]` (object): Results for a specific health check - - `status` (string): Check status - "up" or "down" - - `timestamp` (string): ISO 8601 timestamp of when the check was performed - - `error` (string, optional): Error message if check failed +- `status` (string): the most urgent state across the catalog — `unknown`, `healthy`, `suspect`, `warning`, `degraded`, or `unavailable` +- `network` (string): The network the node is connected to +- `node_version`, `protocol_version`, `genvm_version` (string): build versions +- `validator_address` (string, validator mode only): The validator's address +- `validator_moniker` (string, validator mode only): The validator's on-chain moniker; reported alongside the address even when empty +- `conditions` (object): the full catalog. Every entry is always present — this is not a list of what is broken. + - `state` (string): current observed health; use this field to determine whether the capability is working. + - `occurrences` (number): accepted failed observations since process start + - `consecutive_failures` (number): current failure streak; zero after recovery + - `since`, `updated_at`, `last_ok` (string, optional): ISO 8601 lifecycle timestamps + - `reason` (string, present only when not healthy): stable, low-cardinality cause code + - `impact` (string, present only when not healthy): what the fault does to this node + - `action` (string, present only when not healthy): what the operator should do + - `details` (object, present only when not healthy): typed fields explicitly allowlisted by the catalog entry. Raw provider payloads, prompts, credentials, URLs, and stack traces are rejected. -**GenVM module health does not affect `status`.** +`severity` is not returned by this endpoint. Alert priority belongs to the alerting policy; clients that previously read `conditions.*.severity` should use `state` for current health. -A node with a broken module is still booted, synced and voting — it votes Timeout on the transactions that need that module, which is the designed degraded behaviour rather than an outage. Failing the endpoint would pull a working validator out of load balancers and page an operator about a node behaving as intended. So `genvm_modules` is reported alongside `checks` rather than as one of them, and `status` stays `up`: +**States** -```json -{ - "status": "up", - "genvm_modules": { - "llm": { - "state": "degraded", - "timestamp": "2024-01-15T10:30:45Z", - "last_ok": "2024-01-15T04:12:03Z", - "detail": "provider unreachable — voting Timeout on transactions that need an LLM" - }, - "web": { "state": "healthy", "timestamp": "2024-01-15T10:30:45Z", "last_ok": "2024-01-15T10:30:45Z" } - } -} -``` +| State | Meaning | Alerted | +|---|---|---| +| `unknown` | The node never observed this condition. | no | +| `healthy` | The last observation succeeded. | no | +| `suspect` | One failure, not yet confirmed. It may clear by itself. | no | +| `warning` | The capability works, and it is confirmed sub-optimal. | yes | +| `degraded` | The failure is confirmed. The capability does not work. | yes | +| `unavailable` | The node cannot do its duty at all. | yes | -`timestamp` and `last_ok` answer different questions and are both reported. `timestamp` is when the verdict was last updated; `last_ok` is when the module last worked. They match while healthy, and the gap between them while degraded shows how long the outage has run. `timestamp` matters more here than under `checks`: those entries are re-evaluated on every request, whereas module state is folded from real transaction traffic and is not polled while healthy — so on a quiet validator a `healthy` verdict can rest on an older observation. +Roll-up precedence, most urgent first: `unavailable`, `degraded`, `warning`, `suspect`, `unknown`, `healthy`. +`unknown` is not on that ladder. It is only ever the state a condition starts in, so it means "not observed yet", never "faulty". A GenVM module rests there until a transaction needs one; `node.snapshot_age` rests there on a node that takes no snapshots. Letting either gate the endpoint would report `unknown` for a node where nothing is wrong. -**HTTP Status Codes:** +If **no** condition has been observed at all, `status` is `unknown` — nothing observed is not the same as nothing wrong. -- `200 OK`: Node is healthy (all checks passing) -- `503 Service Unavailable`: Node is unhealthy (one or more checks failing) +**Only `unavailable` returns 503.** -**Notes:** +A condition can never exceed the liveness cap its catalog entry declares. The GenVM conditions are capped at `degraded`, so a dead LLM provider returns **200**: that node is still booted, synced and voting — it votes Timeout on the transactions it cannot execute, which is the designed behaviour rather than an outage. Failing the endpoint would pull a working validator out of a load balancer. + +`consensus.ban`, `consensus.stake`, `chain.rpc`, `chain.websocket` and `chain.sync` are capped at `unavailable`, because each of them stops the node doing its job. + +**WebDriver render check** + +`webdriver.render` runs the same fresh render probe as `doctor`, using +`webdriver_host` from the configured GenVM's `genvm-module-web.yaml`. It requires +HTTP 200, `Resulting-Status: 200`, and exactly `Hello world!` from the diagnostic +page, within 10 seconds. It bypasses WebDriver's cached `/healthcheck` response. +A failure reports `degraded` with reason `webdriver_check_failed` and safe +`failure`, `http_status` and `page_status` details. The endpoint still returns +HTTP 200: a browser outage does not stop the node syncing and voting. -- The endpoint has a 5-second timeout for all health checks -- The `checks` field is only included when health checks are configured -- Version fields will show "unset" if version information is not available -- Individual health checks are executed concurrently with results aggregated in the response +Allow time for the ten-second render deadline when setting a client timeout. +`task devtools:nodes:health` defaults to 15 seconds per request so a browser +timeout can return its diagnosis instead of appearing as an unreachable node. -**Example Response (Unhealthy):** +`genvm.web` continues reporting the broader web module's boot and transaction +results. A successful direct browser check does not clear a web-module failure. + +For `genvm.start_manager: false`, the manager owns the browser configuration. +The node leaves `webdriver.render` unknown instead of probing an unused local +root. Doctor likewise reports the browser as not checked; run it on the node +that owns the manager configuration. Observed `genvm.web` results still apply. + +**Example: the LLM provider is dead — HTTP 200** ```json { - "status": "down", - "node_version": "v1.2.3", - "protocol_version": "consensus-v2.1", - "checks": { - "database": { - "status": "down", - "error": "connection timeout", - "timestamp": "2024-01-15T10:30:45Z" - }, - "consensus": { - "status": "up", - "timestamp": "2024-01-15T10:30:45Z" + "status": "degraded", + "conditions": { + "genvm.llm": { + "state": "degraded", + "reason": "llm_request_failed", + "impact": "This validator cannot execute a transaction that needs an LLM. It votes Timeout, and the consensus contracts may penalize a Timeout vote.", + "action": "Check the affected LLM provider service and payment or billing status, restore access, or configure a working fallback. If credentials are changed through .env, restart the node.", + "occurrences": 2, + "consecutive_failures": 2, + "since": "2026-09-01T10:30:45Z", + "updated_at": "2026-09-01T10:31:12Z", + "last_ok": "2026-09-01T04:12:03Z" } } } ``` +**Example: the validator is banned — HTTP 503** + +```json +{ + "status": "unavailable", + "conditions": { + "consensus.ban": { + "state": "unavailable", + "reason": "validator_banned", + "impact": "The consensus contracts do not select this validator for a round. The node earns nothing.", + "action": "Wait for the stated epoch. If the ban is permanent, restake with a new validator.", + "details": { "until_epoch": "4182", "permanent": "false" }, + "occurrences": 1, + "consecutive_failures": 1, + "since": "2026-09-01T09:14:02Z", + "updated_at": "2026-09-01T10:31:12Z", + "last_ok": "2026-09-01T09:13:52Z" + } + } +} +``` + +**`chain.sync` — 503 only when the node stops syncing** + +Depth is not a fault. A node syncing from genesis is millions of blocks behind and perfectly healthy. What matters is whether the stored synced block advances. + +| Reason | State | HTTP | +|---|---|---| +| `sync_lagging` | `warning` | 200 | +| `sync_catching_up` | `degraded` | 200 | +| `sync_stalled` | `unavailable` | **503** | +| `sync_feed_frozen` | `unavailable` | **503** | + +The node never skips a duty because it is behind. It votes Timeout only on a transaction that reads state it has not synced; a new contract deploy still runs. + **cURL Example with Headers:** ```bash @@ -1415,6 +2054,17 @@ The node collects metrics for three main components: - **GenVM** - The GenVM modules (LLM and Web) - **WebDriver** - The WebDriver container +**Operator Condition Metrics** + +- `genlayer_node_operator_condition_state{condition,state}`: one-hot gauge for the current catalog state. All state series are prewarmed at process start. +- `genlayer_node_operator_condition_occurrences_total{condition,reason}`: accepted failed observations by stable reason. +- `genlayer_node_operator_condition_transitions_total{condition,from,to}`: lifecycle transitions, including recovery. +- `genlayer_node_operator_condition_last_success_timestamp_seconds{condition}`: Unix timestamp of the last successful observation, or zero before one occurs. + +`condition`, `state`, `reason`, `from`, and `to` come from the fixed startup catalog; upstream error text is never used as a label. + +`occurrences_total` counts differently per condition kind, so the counter stays comparable: an *observed* condition (folded from duty traffic) counts every failed observation, while a *polled* condition counts only entry into a failing state. Counting every failed poll would scale the counter with the poll interval rather than the fault — a three-day ban at a ten-second poll would read as roughly 26,000 events. + **CPU Metrics** **genlayer_node_cpu_usage_percent** @@ -1621,6 +2271,15 @@ Node synchronization status (1 = synced, 0 = not synced). **Labels:** - `node`: Node address (hex format) +**genlayer_node_prime_blocked_insufficient_funds** + +Whether a mandatory validator prime is currently blocked by an unfunded operator wallet (1 = blocked, 0 = not blocked). While blocked the validator cannot pay for consensus duty transactions and is flagged idle each round — fund the operator wallet to recover. + +**Type:** Gauge + +**Labels:** +- `node`: Node address (hex format) + **genlayer_node_uptime_seconds** Node uptime in seconds. @@ -1687,6 +2346,137 @@ Total number of validator reveal votes. **Labels:** - `node`: Node address (hex format) +**Gas Metrics (duty transactions)** + +Emitted inline from the shared consensus gas estimator and transactor, so they cover every duty method (`finalizeTransaction`, `commitVote`, `revealVote`, `proposeReceiptWithFees`, `topUpAndSubmitAppeal`, `activateTransaction`, `validatorPrime`, `epochAdvance`, ...). All are true counters. + +**genlayer_node_gas_estimation_total** + +Outcome of every duty gas estimation. + +**Type:** Counter + +**Labels:** +- `method`: Contract method name +- `outcome`: One of `success` (real RPC estimate), `fallback_block_limit` (estimation failed; submitted at full block gas limit), `fallback_cache` / `fallback_cache_scaled` (estimation failed; reused gas from the shared calldata-length-keyed cache, possibly learned from a different method) + +**genlayer_node_gas_estimation_capped_total** + +Estimation succeeded but the safety-multiplied estimate (or per-call reserve) exceeded the block gas limit and was silently capped — the safety margin is gone while everything still works. + +**Type:** Counter + +**Labels:** +- `method`: Contract method name + +**genlayer_node_gas_shortfall_total** + +Confirmed on-chain out-of-gas: the transaction landed but ran out of gas mid-execution. Self-heals — the node retries with a larger gas limit — so a single occurrence is not actionable; a **sustained rate** signals systematic under-estimation (treat as a warning, not a page). + +**Type:** Counter + +**Labels:** +- `method`: Contract method name +- `reason`: One of `typed_shortfall` (`InsufficientGasForInternalMessageCreation` revert with exact gas requirement), `out_of_gas` (explicit `OutOfGas` revert), `heuristic` (undecoded revert that consumed gas above the exhaustion threshold) + +**genlayer_node_gas_block_limit_exhausted_total** + +Gas shortfall while the gas limit was already capped at the block limit: the work cannot fit in one block and retrying cannot help. Non-self-healing — **any occurrence is actionable** (page-worthy). + +**Type:** Counter + +**Labels:** +- `method`: Contract method name + +**genlayer_node_gas_spent_wei_total** + +Cumulative wei actually billed for gas, from each mined duty receipt (`gasUsed × effectiveGasPrice`). Reverted attempts are billed too and are accounted per attempt; submissions that never land cost nothing. The `reverted` slice is the exact cost of failed work, and `sum(genlayer_node_gas_spent_wei_total)` is the operator's total billed duty gas. + +**Type:** Counter + +**Labels:** +- `method`: Contract method name +- `outcome`: One of `success`, `reverted` +- `price_source`: `effective` (receipt's billed price) or `fee_cap` (submitted fee cap used as an upper bound when the RPC omits `effectiveGasPrice`) + +**Consensus Submission Metrics** + +**genlayer_node_consensus_submit_total** + +Terminal outcome of every duty submission. + +**Type:** Counter + +**Labels:** +- `method`: Contract method name +- `outcome`: One of `success` (landed and executed), `reverted` (landed, contract rejected — gas still billed), `failed` (never landed: RPC/nonce/funds/authorization) + +**genlayer_node_consensus_revert_total** + +Decoded revert causes, one increment per observed revert (attempt-level). + +**Type:** Counter + +**Labels:** +- `method`: Contract method name +- `error`: Decoded custom error from the fee/gas allowlist (`InsufficientFeesForRound`, `InsufficientFees`, `FailedFeeTransfer`, `FeeSettlementNotCompleted`, `MaxPriceExceeded`, `ExecutionBudgetExceeded`, `MessageBudgetExceeded`, `BudgetTooLow`, `RollupBudgetBelowFloor`, `InsufficientBondCustody`, `InvalidAppealBond`, `AppealBondTooLow`, `InsufficientGasForInternalMessageCreation`, `OutOfGas`); anything else collapses to `other` + +**Fee Flow Metrics (v0.6 ConsensusMainWithFees)** + +Note: there are no dedicated appeal-bond metrics. No node runtime path submits appeals — appellants are external clients funding bonds from their own wallets. If a validator appeal duty ever ships, the method-labeled gas/submission metrics above cover it under `method="topUpAndSubmitAppeal"` with no code change. + +**genlayer_node_fees_operator_insufficient_funds_total** + +Duty submissions rejected because the operator account cannot cover value + gas. Complements the `genlayer_node_prime_blocked_insufficient_funds` gauge by leaving a trace of dips that recover between scrapes. A sustained increase means the operator wallet needs funding (page-worthy). + +**Type:** Counter + +**genlayer_node_fees_settlement_events_total** + +Fee-settlement events decoded from this node's own mined finalize receipts. Finalize runs under a staggered lease, so each finalize is observed by exactly one node — aggregate fleet-wide with `sum by (network)`. Use the `severity` label to alert on failures without enumerating event names: `genlayer_node_fees_settlement_events_total{severity="alarm"}` covers all three failure modes and any future one. + +**Type:** Counter + +**Labels:** +- `event`: One of `UnifiedFeesDistributed` (fees distributed at settlement), `FeesRefunded` (surplus returned to the sender), `FeeSettlementFailed` (finalize succeeded but settlement failed — validators unpaid until `retryFeeSettlement`), `CommitFinalFeesFailed` (tx finalized without fee settlement), `ValueWithdrawalFailed` (a best-effort transfer — refund/appealer payout — failed; funds stay custodied pending recovery) +- `severity`: `alarm` for the three failure events (unpaid or stuck-funds modes needing operator action) or `info` for the normal `UnifiedFeesDistributed` / `FeesRefunded` happy path + +**genlayer_node_fees_distributed_wei_total** + +Total wei moved at fee settlement across **all** recipients — committee payouts **plus** sender/contributor refunds **plus** appealer rewards — summed from `UnifiedFeesDistributed` amounts in this node's finalize receipts. It **excludes** amounts retained in staking custody (`amountToStaking`). This is a network **"value settled"** liveness signal, **not** per-node earnings — for a validator's own income use `genlayer_node_fees_earned_wei_total`. + +**Type:** Counter + +**genlayer_node_fees_earned_wei_total** + +This node's validator earnings credited at prime, read from the `ValidatorPrime` event when a prime is effective. This is the accurate **per-node** income (unlike `fees_distributed_wei_total`, which is whole-committee plus refunds). Earnings settle into stake per epoch, so this series lags settlement by up to one prime interval. Pair `type="fee_rewards"` with `genlayer_node_gas_spent_wei_total` for operator net P&L (`earned − gas_spent`). + +**Type:** Counter + +**Labels:** +- `type`: One of `fee_rewards` (fee-flow income), `validator_rewards`, `delegator_rewards` (staking-reward components) + +**genlayer_node_fees_penalties_wei_total** + +This node's fee penalties deducted at prime, read from the same `ValidatorPrime` event. Kept separate from `fees_earned_wei_total` so earnings stay gross income; subtract this for net. + +**Type:** Counter + +**genlayer_node_external_message_delivery_total** + +External messages this node's own delivery calls (`flushExternalMessages`, `flushInternalMessages`, `finalizeTransaction`) resolved or paused, read from their mined receipts. A paused message was not called and not charged; the node retries it with a backoff (30 s doubling to 10 min) until the chain reports it issued. A `paused_*` series that keeps rising for one network means a message nobody can deliver. + +**Type:** Counter + +**Labels:** +- `outcome`: `delivered` (ran and succeeded), `call_failed` (ran and failed; final), `paused_gas` (no room to forward its gas envelope), `paused_value` (its value was not yet available), `cancelled_unfunded` (its open-bucket envelope was never funded; value refunded), `cancelled_ceiling` (its gas limit is above the current ceiling; value refunded) + +**genlayer_node_external_message_reimbursed_wei_total** + +Wei the chain reimbursed this node for running external messages: gas used times the transaction's locked gas price. Pair it with `gas_spent_wei_total` for the net cost of delivery. + +**Type:** Counter + **Go Runtime Metrics** The endpoint also exposes standard Go runtime metrics including: @@ -1793,7 +2583,19 @@ The token is the value of `GENLAYERNODE_NODE_OPERATOR_TOKEN` on the node process **Parameters:** -- `destDir` (string, optional): Absolute path on the node's filesystem. When set, switches to write-to-path mode; when omitted or empty, the snapshot is streamed back as `application/gzip`. +- `scope` (string, optional): Which stores the archive carries — `all` (default), `genvm` or `ops`. An absent scope means `all`, so a caller written before the store split keeps its behaviour. An unrecognised value is rejected with `400`. + + | Scope | Contents | Portable between nodes | + |---|---|---| + | `all` | both stores, operation store first | **No** | + | `genvm` | chain-derived history only | **Yes** | + | `ops` | this node's consensus record only | **No** | + + Only `genvm` may be given to another operator. `all` and `ops` carry the vote salts this node generated and the nonce ownership of its operator key; restoring either onto a different node would make that node act on votes it never cast and nonce slots it never signed. The node refuses to start against an operation store stamped with another operator, but that guard runs at startup, not at copy time — treat a non-portable archive accordingly. + + Every archive carries a `snapshot.json` manifest recording the format version, the scopes inside, the rollup block the GenVM store was captured at, and — for `all` and `ops` — the operator address that produced it. + +- `destDir` (string, optional): Absolute path on the node's filesystem, which must resolve to a location inside the configured snapshot directory (`node.admin.snapshot_dir`, default `/snapshots`). When set, switches to write-to-path mode; when omitted or empty, the snapshot is streamed back as `application/gzip`. Paths outside the snapshot directory are rejected with `400`. **Returns:** - Stream mode (`destDir` omitted): gzipped tar archive in the response body, `Content-Type: application/gzip`. @@ -1840,7 +2642,16 @@ Content-Disposition: attachment; filename="snapshot.tar.gz" curl -X POST http://127.0.0.1:9153/snapshot \ -H "X-Admin-Auth: $GENLAYERNODE_NODE_OPERATOR_TOKEN" \ -H "Content-Type: application/json" \ - -d '{"destDir": "/var/snapshots/bradbury-100"}' + -d '{"destDir": "/var/lib/genlayer/data/node/snapshots/bradbury-100"}' + +Take only the portable half, to hand to another operator: + +```bash +curl -sS -X POST http://localhost:9153/snapshot \ + -H "Authorization: Bearer $OPERATOR_TOKEN" \ + -H 'Content-Type: application/json' \ + -d '{"scope": "genvm"}' -o genvm-snapshot.tar.gz +``` ``` **Example Response — Write-to-Path Mode:** @@ -1853,7 +2664,7 @@ HTTP/1.1 204 No Content - `200 OK`: Stream mode succeeded; body carries the gzipped tar archive. - `204 No Content`: Write-to-path mode succeeded; snapshot is on disk at `destDir`. -- `400 Bad Request`: `destDir` is not absolute, or already exists. +- `400 Bad Request`: `destDir` is not absolute, resolves outside the configured snapshot directory, or already exists. - `401 Unauthorized`: Missing, invalid, or empty operator token (also returned when `GENLAYERNODE_NODE_OPERATOR_TOKEN` is unset on the node). - `500 Internal Server Error`: Snapshot creation failed (e.g., disk full, DB checkpoint error). @@ -1861,8 +2672,9 @@ HTTP/1.1 204 No Content - A snapshot may be several GB. In stream mode the connection stays open for the duration of the snapshot — configure reverse proxies / load balancers with adequate read timeouts and disable response buffering. - In stream mode, a dropped connection leaves no on-disk artifact on the node; retry the request from scratch. -- In write-to-path mode, `destDir` must be an absolute path that does not yet exist; the node creates it. Path-traversal hardening beyond these checks is a separate (Sev: Medium) follow-up — treat the operator token as a credential that can write under the node user's filesystem permissions. -- **Docker deploys**: in write-to-path mode, `destDir` is resolved inside the node container's filesystem, not on the host. To make the resulting directory reachable from the host (or from another container), use a volume mount that maps `destDir` identically on both sides, or stick with stream mode. Stream mode is unaffected — the response body is sent over HTTP regardless of where the node runs. +- In write-to-path mode, `destDir` must be an absolute path that does not yet exist and that resolves (after `filepath.Clean`) to a location inside the configured snapshot directory; the node creates it. Paths that escape the directory via `..` or point elsewhere on the filesystem are rejected with `400`. This bounds where an operator-token holder can write a checkpoint — still treat the operator token as a credential that can write under the node user's filesystem permissions within that directory. +- **Snapshot directory** (`node.admin.snapshot_dir`): bounds write-to-path mode. A relative value resolves under `datadir` (so the default `snapshots` is `/snapshots`); an absolute value is used as-is, letting a node target a dedicated disk. The node creates the directory with `0700` permissions on first use if it does not exist. +- **Docker deploys**: in write-to-path mode, `destDir` (and the snapshot directory) is resolved inside the node container's filesystem, not on the host. The default `/snapshots` falls under whatever volume the deployment mounts for `datadir`, so it is host-reachable wherever that volume maps. To use a different location, set `snapshot_dir` to a path that is itself volume-mapped identically on both sides, or stick with stream mode. Stream mode is unaffected — the response body is sent over HTTP regardless of where the node runs. - The snapshot includes the full chain database (SSTables, MANIFEST, WAL) at the moment of capture. Treat the artifact as sensitive — anyone holding it can reconstruct the node's state. - Token comparison is constant-time and length-padded so neither the value nor the length of the secret can be inferred from response timing. - Rotation: update `GENLAYERNODE_NODE_OPERATOR_TOKEN` and restart the node. Tokens are read once at startup and not hot-reloaded. diff --git a/pages/api-references/genlayer-node/debug/_meta.json b/pages/api-references/genlayer-node/debug/_meta.json index 811f7749..a6bcfa17 100644 --- a/pages/api-references/genlayer-node/debug/_meta.json +++ b/pages/api-references/genlayer-node/debug/_meta.json @@ -1,5 +1,6 @@ { "gen_dbg_ping": "gen_dbg_ping", "gen_dbg_trie": "gen_dbg_trie", - "gen_dbg_traceTransaction": "gen_dbg_traceTransaction" -} \ No newline at end of file + "gen_dbg_traceTransaction": "gen_dbg_traceTransaction", + "gen_dbg_load_test": "gen_dbg_load_test" +} diff --git a/pages/api-references/genlayer-node/debug/gen_dbg_load_test.mdx b/pages/api-references/genlayer-node/debug/gen_dbg_load_test.mdx new file mode 100644 index 00000000..d8609ec5 --- /dev/null +++ b/pages/api-references/genlayer-node/debug/gen_dbg_load_test.mdx @@ -0,0 +1,50 @@ +### gen_dbg_load_test + +Debug hook reserved for driving a node-side load test. The endpoint is wired end to end, but the service behind it is currently a no-op stub: the call succeeds and returns `null` without performing any work. + +**Method:** `gen_dbg_load_test` + +**Parameters:** None. Any params supplied are accepted and discarded — the handler does not read them + +**Returns:** `null` + +**Example Request:** + +```json +{ + "jsonrpc": "2.0", + "method": "gen_dbg_load_test", + "params": [], + "id": 1 +} +``` + +**Example Response:** + +```json +{ + "jsonrpc": "2.0", + "result": null, + "id": 1 +} +``` + +**Notes:** + +- A `null` result means "call accepted, nothing performed" — it is not an error and does not indicate a load test ran +- Because the handler ignores its params, this method cannot currently be tuned (operation count, test type, or duration). Do not build tooling that depends on such parameters +- Belongs to the `genlayer_debug` group, which **fails closed**: it is disabled unless an operator explicitly enables it, since the debug methods are reachable unauthenticated on the default `0.0.0.0` RPC bind +- When the group is disabled, the response is JSON-RPC error `-32004` ("method not supported") + +**cURL Example:** + +```bash +curl -X POST http://localhost:9151 \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "gen_dbg_load_test", + "params": [], + "id": 1 + }' +``` diff --git a/pages/api-references/genlayer-node/debug/gen_dbg_traceTransaction.mdx b/pages/api-references/genlayer-node/debug/gen_dbg_traceTransaction.mdx index 1500dc4d..8d6810a6 100644 --- a/pages/api-references/genlayer-node/debug/gen_dbg_traceTransaction.mdx +++ b/pages/api-references/genlayer-node/debug/gen_dbg_traceTransaction.mdx @@ -17,14 +17,15 @@ Retrieves the full execution trace for a transaction, including the return data, | Field | Type | Description | |-------|------|-------------| | `transaction_id` | string | Transaction hash | -| `result_code` | number | 0=success, 1=user error, 2=VM error | +| `result_code` | number | 0=success, 1=user error, 2=VM error, 3=internal error | | `return_data` | string | Hex-encoded contract return data | | `stdout` | string | Standard output captured during execution | | `stderr` | string | Standard error captured during execution | | `genvm_log` | array | Detailed GenVM execution log entries | | `storage_proof` | string | Hex-encoded storage proof hash | | `run_time` | string | Execution duration | -| `eq_outputs` | array | Hex-encoded equivalence principle outputs | +| `leader_public_data` | string | Hex-encoded leader data; its encoding is defined by the GenVM specification | +| `stored_at` | string (optional) | ISO 8601 timestamp of when the trace was stored; omitted when not available | **Example:** @@ -58,7 +59,7 @@ Retrieves the full execution trace for a transaction, including the return data, ], "storage_proof": "0x99ff0d9125e1fc9531a11262e15aeb2c60509a078c4cc4c64cefdfb06ff68647", "run_time": "0s", - "eq_outputs": [] + "leader_public_data": "0x0e076e645f6f75747305" }, "id": 1 } @@ -67,5 +68,7 @@ Retrieves the full execution trace for a transaction, including the return data, **Notes:** - If the trace isn't stored locally (e.g., the node restarted or data was pruned), the endpoint will replay the transaction automatically. -- This endpoint is in the `genlayer_debug` group and must be enabled in the node configuration. -- Use `result_code` to determine execution outcome: 0 = contract returned successfully, 1 = contract raised a user error, 2 = GenVM encountered an error. +- **Retention.** Operators bound the trace store with `node.trace.retention_blocks` (default 1,000,000 blocks; `0` disables). A trace below the retention watermark is gone, so this endpoint falls back to a live replay — reconstructed from the latest on-chain view, and not byte-identical to the stored artifact. A single trace larger than `node.trace.max_trace_bytes` (default 32 MB) is never stored, which produces the same fallback. +- **Field bound.** The free-form execution-output fields (`return_data`, `stdout`, `stderr`, `genvm_log`) carry whatever the replayed contract emitted, so their size is contract-controlled. Each is capped at `node.trace.max_debug_field_bytes` (default 256 KB; `0` takes the default). When a field is cut, the truncation is marked in-band — a trailing `...[truncated: N of M bytes omitted from gen_dbg trace]` on a string field, or a final `{"truncated": "..."}` entry on `genvm_log` — so the reply stays honest rather than silently short. +- This endpoint is in the `genlayer_debug` group, which is **disabled by default**. Enable it deliberately, and only on an RPC bind that untrusted callers cannot reach: it replays a transaction through GenVM and returns internal execution output, so it is debug tooling, not a public API. +- Use `result_code` to determine execution outcome: 0 = contract returned successfully, 1 = contract raised a user error, 2 = GenVM encountered an error, 3 = internal error. diff --git a/pages/api-references/genlayer-node/debug/gen_dbg_trie.mdx b/pages/api-references/genlayer-node/debug/gen_dbg_trie.mdx index 88ee7c44..393a9c18 100644 --- a/pages/api-references/genlayer-node/debug/gen_dbg_trie.mdx +++ b/pages/api-references/genlayer-node/debug/gen_dbg_trie.mdx @@ -36,3 +36,4 @@ Get trie information for debugging node internal DB state. "result": "graph TD\nnode0@{label: \"br seq=1\"}\nnode1@{label: \"ext seq=1 0x00000000000000000000000000000000000000000000000000000000000000000000000\"}\nnode1 --\u003e data2@{label: \"seq=1\n[21 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]\"}\nnode0 --\u003e|0| node1\nnode3@{label: \"ext seq=1 0x72d46c3ada9f897c74d349bbfe0e450c798167c9f580f8daf85def57e96c3ea00000000\"}\nnode3 --\u003e data4@{label: \"seq=1\n[105 110 105 116 105 97 108 32 115 116 111 114 97 103 101 32 118 97 108 117 101 0 0 0 0 0 0 0 0 0 0 0]\"}\nnode0 --\u003e|3| node3\n", "id": 1 } +``` diff --git a/pages/api-references/genlayer-node/gen/_meta.json b/pages/api-references/genlayer-node/gen/_meta.json index 08ada0fc..d5b2d5a4 100644 --- a/pages/api-references/genlayer-node/gen/_meta.json +++ b/pages/api-references/genlayer-node/gen/_meta.json @@ -6,5 +6,11 @@ "gen_getTransactionReceipt": "gen_getTransactionReceipt", "gen_getTransactionStatus": "gen_getTransactionStatus", "gen_getTransactionLifecycle": "gen_getTransactionLifecycle", - "gen_syncing": "gen_syncing" + "gen_syncing": "gen_syncing", + "gen_estimateGas": "gen_estimateGas", + "gen_getLogs": "gen_getLogs", + "gen_getTransactionData": "gen_getTransactionData", + "gen_protocolVersion": "gen_protocolVersion", + "gen_subscribe": "gen_subscribe", + "gen_unsubscribe": "gen_unsubscribe" } diff --git a/pages/api-references/genlayer-node/gen/gen_call.mdx b/pages/api-references/genlayer-node/gen/gen_call.mdx index fa217b17..c2715a53 100644 --- a/pages/api-references/genlayer-node/gen/gen_call.mdx +++ b/pages/api-references/genlayer-node/gen/gen_call.mdx @@ -12,16 +12,16 @@ Executes a new message call immediately without creating a transaction on the bl - `data` (string, required): The encoded function call data - `type` (string, required): The type of call (`"read"`, `"write"`, or `"deploy"`) - `blockNumber` (string, optional): The block number to query at (hex-encoded). If not provided, uses latest block - - `status` (string, optional): The transaction status filter (`"finalized"` or `"accepted"`). If not provided, uses accepted state + - `status` (string, optional): The transaction status filter (`"finalized"` or `"decided"`). If not provided, uses decided state - `value` (string, optional): The value to send with the call (hex-encoded) - - `leader_results` (array of strings or null, optional): When provided, GenVM runs in validator mode (executing `validator_fn` instead of `leader_fn` in `run_nondet` calls). Each element is a hex-encoded equivalence output from a previous leader execution. When omitted or null, GenVM runs in leader mode + - `leader_public_data` (string or null, optional): Hex-encoded data returned by a previous leader execution. Its encoding is defined by the GenVM specification. When provided, GenVM runs in validator mode; when omitted or null, GenVM runs in leader mode **Returns:** A structured JSON object containing execution results and debug information **Response Fields:** - `data` (string): Hex-encoded return value from contract execution -- `eqOutputs` (array of strings): Equivalence outputs from GenVM execution, each as a hex-encoded string. These are deterministic outputs used for consensus validation. Populated in leader mode; empty in validator mode +- `leaderPublicData` (string): Hex-encoded leader data whose encoding is defined by the GenVM specification. Populated in leader mode; empty in validator mode - `status` (object): Execution status - `code` (number): Status code (0=success, 1=user error, 2=vm error, 3=internal error) - `message` (string): Human-readable status message @@ -36,9 +36,10 @@ Executes a new message call immediately without creating a transaction on the bl - `recipient` (string): Recipient address - `value` (string): Value in hex format - `data` (string): Message data as hex-encoded string - - `onAcceptance` (boolean): Whether message is sent on acceptance + - `onAcceptance` (boolean): Whether message is sent on acceptance. Always `false` for an external message (`messageType` 0): external messages on acceptance are not supported yet - `saltNonce` (string): Salt nonce in hex format -- `nondetDisagreementCallNo` (number or null): When running in validator mode (with `leader_results`), indicates which `run_nondet` call the validator disagreed on. Null when there is no disagreement or when running in leader mode +- `nondetDisagreementCallNo` (number or null): When running in validator mode (with `leader_public_data`), indicates which `run_nondet` call the validator disagreed on. Null when there is no disagreement or when running in leader mode +- `syncedBlock` (string): Hex-encoded block number that GenVM has synced to. Indicates data freshness — responses are based on state up to this block **Example - Deploy Call:** @@ -66,7 +67,7 @@ Executes a new message call immediately without creating a transaction on the bl "jsonrpc": "2.0", "result": { "data": "00", - "eqOutputs": [], + "leaderPublicData": "0e076e645f6f75747305", "status": { "code": 0, "message": "success" @@ -116,7 +117,7 @@ Executes a new message call immediately without creating a transaction on the bl } ``` -**Example - Read Call (Latest Accepted State):** +**Example - Read Call (Latest Decided State):** ```json { @@ -149,7 +150,7 @@ Executes a new message call immediately without creating a transaction on the bl "from": "0xf6A60752C24Cd3BFcAf8d387a5EB62d3746F44EE", "to": "0xCe5712E63fd5475288aB1B7c0a368B9417357b81", "blockNumber": "0x151ec5", - "status": "accepted" + "status": "decided" } ], "id": 1 @@ -185,7 +186,7 @@ When a contract emits internal messages (e.g., a router contract forwarding call "jsonrpc": "2.0", "result": { "data": "00", - "eqOutputs": [], + "leaderPublicData": "0e076e645f6f75747305", "status": { "code": 0, "message": "success" @@ -229,7 +230,7 @@ When a contract emits internal messages (e.g., a router contract forwarding call **Notes:** - The `blockNumber` parameter can be either hex-encoded with a `0x` prefix (e.g., `"0x151ec5"`) or decimal (e.g., `"1384133"`) -- Valid `status` values are `"accepted"` (default) and `"finalized"` -- When both parameters are omitted, the call executes against the latest accepted state +- Valid `status` values are `"decided"` (default) and `"finalized"` +- When both parameters are omitted, the call executes against the latest decided state - For `read` and `write` type calls, if the contract doesn't exist at the specified block/status combination, an error is returned with code `-32001` (Resource not found) and message `"contract not found at address 0x..."` - For `deploy` type calls, the `status` and `blockNumber` parameters are not used for contract state validation since no contract exists yet diff --git a/pages/api-references/genlayer-node/gen/gen_estimateGas.mdx b/pages/api-references/genlayer-node/gen/gen_estimateGas.mdx new file mode 100644 index 00000000..a9d6abe2 --- /dev/null +++ b/pages/api-references/genlayer-node/gen/gen_estimateGas.mdx @@ -0,0 +1,53 @@ +### gen_estimateGas + +Reserved for estimating gas for a GenLayer transaction. **Not implemented** — the method is registered and reachable, but every call returns an error. + +**Method:** `gen_estimateGas` + +**Parameters:** None currently read — any params supplied are ignored because the method returns before parsing them + +**Returns:** Nothing. The call always fails with JSON-RPC error code `-32004` + +**Example Request:** + +```json +{ + "jsonrpc": "2.0", + "method": "gen_estimateGas", + "params": [], + "id": 1 +} +``` + +**Example Response:** + +```json +{ + "jsonrpc": "2.0", + "error": { + "code": -32004, + "message": "method not implemented: gen_estimateGas" + }, + "id": 1 +} +``` + +**Notes:** + +- The method is registered in the `genlayer` endpoint group, so it is reachable and rate-limited like any other method — it just cannot succeed +- The error code is `-32004` ("method not supported"), not `-32601` ("method not found"): the node recognises the method and is declining to serve it, rather than failing to route it +- Disabling the `genlayer` group, or this method specifically via `endpoints.methods`, changes the response to the same `-32004` code with a "method not supported" message +- Do not use gas estimation to size a transaction budget today; set fees explicitly instead + +**cURL Example:** + +```bash +curl -X POST http://localhost:9151 \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "gen_estimateGas", + "params": [], + "id": 1 + }' +``` diff --git a/pages/api-references/genlayer-node/gen/gen_getContractCode.mdx b/pages/api-references/genlayer-node/gen/gen_getContractCode.mdx index dbea8c2d..88061ac4 100644 --- a/pages/api-references/genlayer-node/gen/gen_getContractCode.mdx +++ b/pages/api-references/genlayer-node/gen/gen_getContractCode.mdx @@ -9,11 +9,19 @@ Retrieves the code of a deployed GenLayer intelligent contract at a specific blo - `request` (object, required): The contract code request parameters - `address` (string, required): The contract address to query - `blockNumber` (string, optional): The block number to query at (hex-encoded). If not provided, uses latest block - - `status` (string, optional): The transaction status filter (`"finalized"` or `"accepted"`). If not provided, uses accepted state + - `status` (string, optional): The transaction status filter (`"finalized"` or `"decided"`). If not provided, uses decided state **Returns:** Base64-encoded contract code -**Example Request - Latest Accepted State:** +**Limits:** + +| Limit | Default | Config key | +|---|---|---| +| Contract bytes read per call | 16 MB | `node.rpc.contract_read_max_bytes` | + +The node reads the contract's code slot directly, so the cost of a call is proportional to the code it returns rather than to everything the contract stores. The limit is a backstop above that: a contract whose declared code length exceeds it is refused before the bytes are read. Operators may reduce it; `0` or less disables it. + +**Example Request - Latest Decided State:** ```json { @@ -58,15 +66,16 @@ Retrieves the code of a deployed GenLayer intelligent contract at a specific blo **Notes:** - The `blockNumber` parameter should be hex-encoded with a `0x` prefix -- Valid `status` values are `"accepted"` (default) and `"finalized"` +- Valid `status` values are `"decided"` (default) and `"finalized"` - The returned code is base64-encoded - If the contract doesn't exist at the specified block/status, an error will be returned - If the address does not have code (not a contract), an error will be returned +- If the contract's code exceeds `node.rpc.contract_read_max_bytes`, the call is refused rather than truncated, so a partial answer is never returned as a whole one **cURL Example:** ```bash -# Get latest accepted code +# Get latest decided code curl -X POST http://localhost:9151 \ -H "Content-Type: application/json" \ -d '{ diff --git a/pages/api-references/genlayer-node/gen/gen_getContractState.mdx b/pages/api-references/genlayer-node/gen/gen_getContractState.mdx index 51527a47..662e8a9e 100644 --- a/pages/api-references/genlayer-node/gen/gen_getContractState.mdx +++ b/pages/api-references/genlayer-node/gen/gen_getContractState.mdx @@ -9,11 +9,19 @@ Retrieves the current state of a GenLayer intelligent contract at a specific blo - `request` (object, required): The contract state request parameters - `address` (string, required): The contract address to query - `blockNumber` (string, optional): The block number to query at (hex-encoded). If not provided, uses latest block - - `status` (string, optional): The transaction status filter (`"finalized"` or `"accepted"`). If not provided, uses accepted state + - `status` (string, optional): The transaction status filter (`"finalized"` or `"decided"`). If not provided, uses decided state **Returns:** Hex-encoded contract state data -**Example Request - Latest Accepted State:** +**Limits:** + +| Limit | Default | Config key | +|---|---|---| +| Contract bytes read per call | 16 MB | `node.rpc.contract_read_max_bytes` | + +This method reads the contract's whole storage, so its cost grows with the contract. The limit stops the read once the contract exceeds it and returns an error, rather than returning part of the state as if it were all of it. Operators may reduce it; `0` or less disables it. + +**Example Request - Latest Decided State:** ```json { @@ -38,7 +46,7 @@ Retrieves the current state of a GenLayer intelligent contract at a specific blo { "address": "0x73ca5a2b51edf506ceaf110a41780ec51294d89f", "blockNumber": "0x151ec5", - "status": "accepted" + "status": "decided" } ], "id": 1 @@ -58,7 +66,7 @@ Retrieves the current state of a GenLayer intelligent contract at a specific blo **Notes:** - The `blockNumber` parameter should be hex-encoded with a `0x` prefix -- Valid `status` values are `"accepted"` (default) and `"finalized"` +- Valid `status` values are `"decided"` (default) and `"finalized"` - The returned state is hex-encoded binary data with a `0x` prefix - Empty contract state returns `"0x"` - If the contract doesn't exist at the specified block/status, an error will be returned @@ -66,7 +74,7 @@ Retrieves the current state of a GenLayer intelligent contract at a specific blo **cURL Example:** ```bash -# Get latest accepted state +# Get latest decided state curl -X POST http://localhost:9151 \ -H "Content-Type: application/json" \ -d '{ @@ -78,7 +86,7 @@ curl -X POST http://localhost:9151 \ "id": 1 }' -# Get state at specific block with accepted status +# Get state at specific block with decided status curl -X POST http://localhost:9151 \ -H "Content-Type: application/json" \ -d '{ @@ -87,7 +95,7 @@ curl -X POST http://localhost:9151 \ "params": [{ "address": "0x73ca5a2b51edf506ceaf110a41780ec51294d89f", "blockNumber": "0x151ec5", - "status": "accepted" + "status": "decided" }], "id": 1 }' diff --git a/pages/api-references/genlayer-node/gen/gen_getLogs.mdx b/pages/api-references/genlayer-node/gen/gen_getLogs.mdx new file mode 100644 index 00000000..ef7972ff --- /dev/null +++ b/pages/api-references/genlayer-node/gen/gen_getLogs.mdx @@ -0,0 +1,105 @@ +### gen_getLogs + +Queries GenLayer contract-emitted events, mirroring `eth_getLogs` semantics under the `gen_` namespace. Events are produced by intelligent contracts (`gl.chain.Event` subclasses calling `.emit()`), persisted by the node while syncing accepted blocks, and indexed by contract address and topics. + +**Method:** `gen_getLogs` + +**Parameters:** + +- `filter` (object, required): + - `fromBlock` (`string`, optional): First block of the range (inclusive), as a hex (`0x…`), decimal string, or `"latest"`. Block numbers are rollup (L2) block numbers — the same numbering `gen_syncing` reports. Defaults to the latest GenVM-synced block. + - `toBlock` (`string`, optional): Last block of the range (inclusive). Same format and default as `fromBlock`. + - `address` (`string | string[]`, optional): Restrict to events emitted by this contract address (or any of these addresses). Empty matches all addresses. + - `topics` (`array`, optional): Positional topic filters, at most 4 entries. Each position is `null` (wildcard), a 32-byte hex topic, or an array of topics (OR within the position). Positions match `eth_getLogs` semantics: an event matches when every constrained position matches; an event with fewer topics than a constrained position does not match. + - `blockHash` (`string`, optional): 32-byte rollup block hash selecting exactly one block (EIP-234). Mutually exclusive with `fromBlock`/`toBlock`. The node resolves the hash through the rollup RPC; an unknown hash returns error `-32001`. + - `status` (`string[]`, optional): Lifecycle stages to return, any of `"accepted"`, `"finalized"`, `"cancelled"`. Filters on the emitting transaction's **current** stage, not on the stage it held at `blockNumber`. Defaults to `["accepted", "finalized"]` — every event whose emitting round still stands. An unknown value returns error `-32602`. + +**Returns:** + +An array of log objects, ordered by block number, then transaction, then per-transaction event index: + +- `address` (`string`): Address of the emitting contract. +- `topics` (`string[]`): The emitted topics, each a 32-byte hex string. `topics[0]` is the event signature topic: `keccak256` of the UTF-8 string `EventName(sortedIndexedFieldNames)` — GenLayer hashes the **sorted indexed field names without types**, unlike Solidity's typed signature. Remaining topics are the indexed field values (calldata-encoded, right-padded to 32 bytes, or keccak-hashed when longer). +- `data` (`string`): Hex of the event payload. **Deviation from eth:** this is the GenVM **calldata** encoding of the event's keyword-argument map, not ABI encoding — decode it with a GenLayer SDK calldata codec. +- `blockNumber` (`string`): Rollup block number (hex) of the block whose sync recorded the event. +- `transactionHash` (`string`): 32-byte GenLayer transaction ID. +- `logIndex` (`string`): **Deviation from eth:** index of the event **within its transaction** (hex), not within the block. +- `round` (`number`): GenLayer extra — the consensus round of the executed result that emitted the event. +- `status` (`string`): GenLayer extra — the emitting transaction's current lifecycle stage: `"accepted"`, `"finalized"`, or `"cancelled"`. **Not** the `gen_getTransactionStatus` vocabulary: that method's `"Canceled"` means a transaction cancelled before execution, whereas a `"cancelled"` log belongs to a transaction whose acceptance an appeal overturned and which is re-executed on a later round. +- `removed` (`boolean`): Carries eth's meaning — the log is no longer part of the canonical set. `true` exactly when `status` is `"cancelled"`. + +`blockHash` and `transactionIndex` are omitted (not indexed node-side). + +**Semantics and limitations:** + +- GenLayer events follow their transaction's lifecycle: they become emitted when the transaction is **ACCEPTED**, become **FINALIZED** with it, and can be **CANCELLED** by a successful appeal. Events from **successful, accepted** executions are recorded at acceptance time (a reverted transaction emits no logs, matching EVM semantics). +- **A log belongs to the block that emitted it.** `blockNumber` is always the acceptance block and `fromBlock`/`toBlock` select on it, so a log's block is stable while its `status` moves. Each log is returned exactly once per query, whatever combination of statuses is requested — including when a transaction is accepted and finalized within the same block. +- **`status` is current, not as-of.** The same query run twice can report a different `status` for a log (accepted → finalized) or stop returning it (accepted → cancelled), the same way `gen_getTransactionStatus` reports current state. +- **Default excludes cancelled logs.** A query with no `status` returns `["accepted", "finalized"]`. Events whose round was overturned on appeal are reachable with `status: ["cancelled"]`, where they carry `removed: true`. +- **Retention.** Operators bound the store with `node.events.retention_blocks` (default 1,000,000 blocks; `0` disables). Queries below the retention watermark return no logs. +- **Deviation from eth:** an event emitted inside a write-enabled sandbox (`allow_write_ops=True`) whose failure the contract catches still surfaces on the successful transaction — GenVM sandboxes are permission boundaries, not transactional ones, so caught-failure branch events are part of the transaction's log set (EVM drops reverted-sub-call logs). +- Coverage requires the transaction to have executed on an executor that reports events in its result (all executors bundled with the currently pinned GenVM manager do); a future pin bundling a pre-2025-12 executor would not surface events for transactions it executes. +- Results reflect **synced** state: a transaction accepted moments ago becomes queryable once the sync loop writes its block (same trailing semantics as `gen_getContractState` at `latest`). + +**Limits:** + +| Limit | Default | Config key | +|---|---|---| +| Block range per query | 10,000 blocks | `node.rpc.getlogs_max_block_range` | +| Logs per response | 10,000 entries | `node.rpc.getlogs_max_results` | +| Event retention | 1,000,000 blocks | `node.events.retention_blocks` | + +Both are node-configurable (operators may reduce them); exceeding either returns error `-32005` (`query exceeds limits: …` — distinct from HTTP rate limiting, which uses the same code). The method carries a default rate limit of 2 requests per second per IP, also configurable. + +**Errors:** + +- `-32602`: malformed filter (invalid hex, more than 4 topic positions, non-32-byte topic, `blockHash` combined with a block range, `fromBlock` greater than `toBlock`, unknown `status` value). +- `-32005`: block range or result count over the configured cap. +- `-32001`: `blockHash` unknown to the rollup. +- `-32000`: internal failure (details are logged, not returned). + +**Example Request:** + +```json +{ + "jsonrpc": "2.0", + "method": "gen_getLogs", + "params": [ + { + "fromBlock": "0x14e300", + "toBlock": "0x14e400", + "address": "0x73ca5a2b51edf506ceaf110a41780ec51294d89f", + "topics": [ + "0x9f6dbb5f0f942152b8b254e69610e1c65e33a1bb61bd8f4bd88f0ec7bbe15c2f", + null + ] + } + ], + "id": 1 +} +``` + +**Example Response:** + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "address": "0x73ca5a2b51edf506ceaf110a41780ec51294d89f", + "topics": [ + "0x9f6dbb5f0f942152b8b254e69610e1c65e33a1bb61bd8f4bd88f0ec7bbe15c2f", + "0x2a00000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x261674696d657374616d70014996d2c96e", + "blockNumber": "0x14e309", + "transactionHash": "0x635060dd514082096d18c8eb64682cc6a944f9ce1ae6982febf7a71e9f656f49", + "logIndex": "0x0", + "round": 0, + "status": "finalized", + "removed": false + } + ] +} +``` diff --git a/pages/api-references/genlayer-node/gen/gen_getTransactionData.mdx b/pages/api-references/genlayer-node/gen/gen_getTransactionData.mdx new file mode 100644 index 00000000..b9686e11 --- /dev/null +++ b/pages/api-references/genlayer-node/gen/gen_getTransactionData.mdx @@ -0,0 +1,132 @@ +### gen_getTransactionData + +Retrieves the on-chain transaction data for one round together with the contract execution outputs (`returnData`, `leaderPublicData`) recovered from the node's local trace store. If the node no longer holds the trace (restart, pruning), the on-chain fields are still returned with `returnData: null` — the transaction is never replayed. + +**Method:** `gen_getTransactionData` + +**Parameters:** + +- `request` (object, required): + - `txId` (`string`, required): The 32-byte transaction hash as a hexadecimal string with a `0x` prefix. + - `round` (`uint8`, optional): The consensus round whose execution outputs to return. Defaults to the latest round. + +**Returns:** + +An object with the on-chain transaction fields for the selected round plus the locally stored execution outputs: + +- `id` (`string`): The transaction ID as a hex string. +- `sender` (`address`): The address of the sender. +- `recipient` (`address`): The destination address of the transaction. +- `activator` (`address`): The address of the validator that activated the transaction. +- `status` (`uint8`): The current status of the transaction. +- `statusName` (`string`): The ABI transaction status name for `status` (`Uninitialized`, `Pending`, `Proposing`, `Committing`, `Revealing`, `Accepted`, `Undetermined`, `Finalized`, `Canceled`, `AppealRevealing`, `AppealCommitting`, `ValidatorsTimeout`, `LeaderTimeout`, or `LeaderRevealing`). +- `result` (`uint8`): The consensus result of the transaction (`0` Idle, `1` MajorityAgree, `2` MajorityDisagree, `3` MajorityTimeout, `4` DeterministicViolation, `5` NoMajority). +- `resultName` (`string`): The ABI result name for `result`. +- `txExecutionResult` (`uint8`): The leader execution vote result (`0` NotVoted, `1` FinishedWithReturn, `2` FinishedWithError, `3` Timeout, `4` NondetDisagree). +- `txExecutionResultName` (`string`): The ABI vote type name for `txExecutionResult`. +- `txCallData` (`string`): The transaction call data as a hex string. +- `txExecutionHash` (`string`): The execution hash of the transaction as a hex string. +- `eqBlocksOutputs` (`string`): The equivalent blocks outputs as a hex string. +- `timestamps` (`object`): Object containing various timestamps: + - `Created` (`uint256`): When the transaction was created. + - `Pending` (`uint256`): When the transaction became pending. + - `Activated` (`uint256`): When the transaction was activated. + - `Proposed` (`uint256`): When the transaction was proposed. + - `Committed` (`uint256`): When the transaction was committed. + - `LastVote` (`uint256`): When the last vote was cast. + - `AppealSubmitted` (`uint256`): When an appeal was submitted. + - `LeaderRevealed` (`uint256`): When the leader was revealed. +- `round` (`uint8`): The round the execution outputs belong to (the requested round, or the latest round when omitted). +- `returnData` (`string|null`): The contract's return data as a `0x`-prefixed hex string, from the node's locally stored execution trace. `null` when the node holds no trace for the round — clients must handle `null` explicitly. +- `leaderPublicData` (`string`): Leader data as a `0x`-prefixed hex string from the stored trace. Its encoding is defined by the GenVM specification. `0x` when no trace is available. + +**Example Request:** + +```json +{ + "jsonrpc": "2.0", + "method": "gen_getTransactionData", + "params": [ + { + "txId": "0x635060dd514082096d18c8eb64682cc6a944f9ce1ae6982febf7a71e9f656f49" + } + ], + "id": 1 +} +``` + +**Example Response:** + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "id": "0x635060dd514082096d18c8eb64682cc6a944f9ce1ae6982febf7a71e9f656f49", + "sender": "0x5b70759760a85d92bf972b67ee8558e8e8da87d4", + "recipient": "0x73ca5a2b51edf506ceaf110a41780ec51294d89f", + "activator": "0xf27b07d36ff5b9dcd37fd3a09811d6456dd4483a", + "status": 7, + "statusName": "Finalized", + "result": 1, + "resultName": "MajorityAgree", + "txExecutionResult": 1, + "txExecutionResultName": "FinishedWithReturn", + "txCallData": "f5b31604617267730da4017570646174656420746573742073746f72616765066d6574686f64747570646174655f73746f7261676500", + "txExecutionHash": "0x76744dc491b1725eb2ba08cf516855c14aac310e36711368c04442fd39d61853", + "eqBlocksOutputs": "c0", + "timestamps": { + "Created": 1750774777, + "Pending": 1750774777, + "Activated": 1750774781, + "Proposed": 1750774786, + "Committed": 1750774791, + "LastVote": 1750774795, + "AppealSubmitted": 0, + "LeaderRevealed": 0 + }, + "round": 0, + "returnData": "0x00", + "leaderPublicData": "0x0e076e645f6f7574730d1b01aabb" + } +} +``` + +**Notes:** + +- Multi-round appeal data (`roundData`) and fee accounting stay on `gen_getTransactionReceipt`; debug output (stdout, stderr, genvm_log) and trace replay stay on `gen_dbg_traceTransaction`. +- `returnData` and `leaderPublicData` come from the node's local trace store only — a node that never executed the transaction (or pruned the trace) serves `returnData: null` and `leaderPublicData: "0x"` without replaying. +- **Retention.** `returnData` and `leaderPublicData` come from the trace store, which operators bound with `node.trace.retention_blocks` (default 1,000,000 blocks; `0` disables). Past the watermark this endpoint answers from chain data with `returnData: null`, exactly as it does for a transaction the node never executed. +- The trace is persisted asynchronously after a transaction is accepted, so a query issued immediately after acceptance can still return `returnData: null`. Clients that need the execution output should poll until it appears (or fall back to `gen_dbg_traceTransaction`, which replays on demand). +- An explicit `round` beyond the rounds the transaction has returns a `-32001` error. +- A malformed `txId` (not a 32-byte hex string) returns `-32602`; a transaction not found on-chain returns `-32001`. +- This endpoint is in the `genlayer` group and is enabled by default. + +**cURL Example:** + +```bash +# Latest round +curl -X POST http://localhost:9151 \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "gen_getTransactionData", + "params": [{ + "txId": "0x635060dd514082096d18c8eb64682cc6a944f9ce1ae6982febf7a71e9f656f49" + }], + "id": 1 + }' + +# Specific round +curl -X POST http://localhost:9151 \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "gen_getTransactionData", + "params": [{ + "txId": "0x635060dd514082096d18c8eb64682cc6a944f9ce1ae6982febf7a71e9f656f49", + "round": 1 + }], + "id": 1 + }' +``` diff --git a/pages/api-references/genlayer-node/gen/gen_getTransactionLifecycle.mdx b/pages/api-references/genlayer-node/gen/gen_getTransactionLifecycle.mdx index 168bef11..c8c11888 100644 --- a/pages/api-references/genlayer-node/gen/gen_getTransactionLifecycle.mdx +++ b/pages/api-references/genlayer-node/gen/gen_getTransactionLifecycle.mdx @@ -26,6 +26,7 @@ Returns one advanced protocol lifecycle snapshot for a transaction. This endpoin | `resolutionSourceCode` | uint8 | Resolution source code | | `decisionId` | string or null | Decimal decision identity used by decision-scoped protocol commands | | `decisionActive` | boolean | Whether `decisionId` still identifies the active decision | +| `executionGeneration` | string or null | Current execution generation as a decimal string from the same snapshot; independent of retained decision provenance. Null when unavailable in a synthetic response. | | `evaluatedAt` | integer | Unix timestamp used for the projection | **Example Request:** @@ -60,6 +61,7 @@ Returns one advanced protocol lifecycle snapshot for a transaction. This endpoin "resolutionSourceCode": 6, "decisionId": "42", "decisionActive": true, + "executionGeneration": "3", "evaluatedAt": 1787868000 }, "id": 1 diff --git a/pages/api-references/genlayer-node/gen/gen_protocolVersion.mdx b/pages/api-references/genlayer-node/gen/gen_protocolVersion.mdx new file mode 100644 index 00000000..0885cc96 --- /dev/null +++ b/pages/api-references/genlayer-node/gen/gen_protocolVersion.mdx @@ -0,0 +1,53 @@ +### gen_protocolVersion + +Reserved for reporting the GenLayer protocol version. **Not implemented** — the method is registered and reachable, but every call returns an error. + +**Method:** `gen_protocolVersion` + +**Parameters:** None currently read — any params supplied are ignored because the method returns before parsing them + +**Returns:** Nothing. The call always fails with JSON-RPC error code `-32004` + +**Example Request:** + +```json +{ + "jsonrpc": "2.0", + "method": "gen_protocolVersion", + "params": [], + "id": 1 +} +``` + +**Example Response:** + +```json +{ + "jsonrpc": "2.0", + "error": { + "code": -32004, + "message": "method not implemented: gen_protocolVersion" + }, + "id": 1 +} +``` + +**Notes:** + +- The method is registered in the `genlayer` endpoint group, so it is reachable and rate-limited like any other method — it just cannot succeed +- The error code is `-32004` ("method not supported"), not `-32601` ("method not found"): the node recognises the method and is declining to serve it, rather than failing to route it +- To identify what a node is running, use the ops server's `GET /health`, which reports node version and sync state +- `eth_protocolVersion` is unaffected — it is proxied to the GenLayer chain like any other `eth_*` method + +**cURL Example:** + +```bash +curl -X POST http://localhost:9151 \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "gen_protocolVersion", + "params": [], + "id": 1 + }' +``` diff --git a/pages/api-references/genlayer-node/gen/gen_subscribe.mdx b/pages/api-references/genlayer-node/gen/gen_subscribe.mdx new file mode 100644 index 00000000..330f98e2 --- /dev/null +++ b/pages/api-references/genlayer-node/gen/gen_subscribe.mdx @@ -0,0 +1,99 @@ +### gen_subscribe + +Opens a subscription to a stream of GenLayer consensus events over the node's inbound WebSocket endpoint. Notifications are pushed as they occur, removing the need to poll `gen_getTransactionStatus`. + +**Method:** `gen_subscribe` + +**Transport:** WebSocket only. This method is not available over HTTP — an HTTP POST cannot receive push notifications. See the WebSocket endpoint section for the URL. + +**Parameters:** + +- `stream` (string, required): The event stream to subscribe to. One of `transactions`, `leadership`, `epochs`, `newHeads`. + +**Returns:** A subscription id (UUID v4 string) used to correlate notifications and to cancel via `gen_unsubscribe` + +**Streams:** + +| Stream | Event kinds delivered | +|--------|-----------------------| +| `transactions` | `NewTransaction`, `TransactionActivated`, `TransactionReceiptProposed`, `VoteCommitted`, `VoteRevealed`, `TransactionAccepted`, `TransactionFinalized`, `TransactionUndetermined` | +| `leadership` | `TransactionLeaderRotated`, `TransactionLeaderTimeout`, `TransactionLeaderChanged`, `TransactionLeaderRevealed`, `LeaderIdlenessProcessed`, `AppealStarted` | +| `epochs` | `EpochAdvance`, `EpochFinalize`, `TimeoutsSet`, `SetEpochMinDuration` | +| `newHeads` | `NewHeader` | + +**Notification Fields:** + +| Field | Type | Description | +|-------|------|-------------| +| `kind` | string | The event name, matching the consensus contract's ABI event (e.g. `TransactionAccepted`) | +| `event` | object | The decoded event payload. `bytes32` values are 0x-prefixed hex; field names are camelCase | +| `event.raw` | object | The originating chain log (block hash, transaction hash, topics), for reconciling against `gen_getLogs` | + +**Example Request:** + +```json +{ + "jsonrpc": "2.0", + "method": "gen_subscribe", + "params": [ + "transactions" + ], + "id": 1 +} +``` + +**Example Response:** + +```json +{ + "jsonrpc": "2.0", + "result": "b3f1c2de-9a44-4c1e-8f77-2b0a5d6e1c33", + "id": 1 +} +``` + +**Example Notification:** + +```json +{ + "jsonrpc": "2.0", + "method": "gen_subscription", + "params": { + "subscription": "b3f1c2de-9a44-4c1e-8f77-2b0a5d6e1c33", + "result": { + "kind": "TransactionAccepted", + "event": { + "txId": "0x9c1f3a7b8d2e4f5061728394a5b6c7d8e9f0a1b2c3d4e5f60718293a4b5c6d7e", + "raw": { + "address": "0x1234567890abcdef1234567890abcdef12345678", + "blockNumber": "0x151ec5", + "transactionHash": "0x4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c" + } + } + } + } +} +``` + +**Notes:** + +- The subscription id is a UUID v4 generated by this node; it is never an upstream chain subscription id +- Streams are coarse by design: one subscription covers a whole lifecycle, so following a transaction end to end costs one subscription rather than one per event kind +- The `kind` values are the consensus contracts' ABI event names, which are stable across node releases. They are deliberately not the node's internal topic names +- Every payload is derived from on-chain logs and contains no node-private data. The node's own GenVM sync position is not exposed here; use `gen_syncing` +- `newHeads` is served for convenience and duplicates `eth_subscribe("newHeads")`, which is fed from the same upstream. Latency-sensitive clients should prefer `eth_subscribe` +- Notifications are live only — there is no historical replay or backfill. Reconcile any gap with `gen_getLogs` +- Server-side filtering (for example by `txId`) is not supported; subscribe to the stream and filter client-side +- Subscribing to an unknown stream returns error code `-32602` with the list of supported streams +- Exceeding `max_subs_per_conn` returns error code `-32000` ("subscription limit reached") +- This method belongs to the `genlayer` endpoint group; disabling that group disables it on the WebSocket transport too +- Standard clients can consume this with their generic subscription API, since notifications use the conventional `gen_subscription` envelope. In go-ethereum: `client.Subscribe(ctx, "gen", ch, "transactions")` + +**wscat Example:** + +```bash +wscat -c ws://localhost:9151/ws +> {"jsonrpc":"2.0","method":"gen_subscribe","params":["transactions"],"id":1} +< {"jsonrpc":"2.0","result":"b3f1c2de-9a44-4c1e-8f77-2b0a5d6e1c33","id":1} +< {"jsonrpc":"2.0","method":"gen_subscription","params":{"subscription":"b3f1c2de-9a44-4c1e-8f77-2b0a5d6e1c33","result":{"kind":"NewTransaction","event":{"txId":"0x9c1f..."}}}} +``` diff --git a/pages/api-references/genlayer-node/gen/gen_syncing.mdx b/pages/api-references/genlayer-node/gen/gen_syncing.mdx index b316d2d6..306bd7b2 100644 --- a/pages/api-references/genlayer-node/gen/gen_syncing.mdx +++ b/pages/api-references/genlayer-node/gen/gen_syncing.mdx @@ -60,3 +60,16 @@ Returns the GenVM sync status, indicating how far the node's local GenVM state i - This endpoint always returns an object (never `false` like `eth_syncing`) - Use `blocksBehind == 0` to determine if the node is fully synced - When the node is catching up, `gen_call` requests without an explicit `blockNumber` will serve data at the `syncedBlock` rather than waiting for full sync + +**cURL Example:** + +```bash +curl -X POST http://localhost:9151 \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "gen_syncing", + "params": [], + "id": 1 + }' +``` diff --git a/pages/api-references/genlayer-node/gen/gen_unsubscribe.mdx b/pages/api-references/genlayer-node/gen/gen_unsubscribe.mdx new file mode 100644 index 00000000..ab576a66 --- /dev/null +++ b/pages/api-references/genlayer-node/gen/gen_unsubscribe.mdx @@ -0,0 +1,52 @@ +### gen_unsubscribe + +Cancels a subscription previously opened with `gen_subscribe`. The node stops sending notifications for that subscription and releases the resources backing it. + +**Method:** `gen_unsubscribe` + +**Transport:** WebSocket only, on the same connection that opened the subscription. + +**Parameters:** + +- `subscriptionId` (string, required): The id returned by `gen_subscribe` + +**Returns:** Boolean — `true` when a matching subscription was cancelled, `false` when the id is unknown on this connection + +**Example Request:** + +```json +{ + "jsonrpc": "2.0", + "method": "gen_unsubscribe", + "params": [ + "b3f1c2de-9a44-4c1e-8f77-2b0a5d6e1c33" + ], + "id": 2 +} +``` + +**Example Response:** + +```json +{ + "jsonrpc": "2.0", + "result": true, + "id": 2 +} +``` + +**Notes:** + +- An unknown subscription id is not an error: the response is `result: false`, matching the `eth_unsubscribe` convention +- Subscriptions are scoped to the connection that created them. One connection cannot cancel another's subscription, and an id from a closed connection always returns `false` +- Explicit unsubscription is optional for cleanup: closing the WebSocket cancels every subscription on that connection automatically +- A notification already in flight when the cancellation is processed may still arrive; treat the `true` response as "no further events will be produced", not as a barrier +- A missing or non-string parameter returns error code `-32602` + +**wscat Example:** + +```bash +wscat -c ws://localhost:9151/ws +> {"jsonrpc":"2.0","method":"gen_unsubscribe","params":["b3f1c2de-9a44-4c1e-8f77-2b0a5d6e1c33"],"id":2} +< {"jsonrpc":"2.0","result":true,"id":2} +``` diff --git a/pages/api-references/genlayer-node/ops/balance.mdx b/pages/api-references/genlayer-node/ops/balance.mdx index 8884e70e..96f3a764 100644 --- a/pages/api-references/genlayer-node/ops/balance.mdx +++ b/pages/api-references/genlayer-node/ops/balance.mdx @@ -64,7 +64,7 @@ curl -X GET http://localhost:9153/balance ```json { - "message": "Balance endpoint not available in rollup mode" + "message": "Balance endpoint not available in full mode" } ``` diff --git a/pages/api-references/genlayer-node/ops/health.mdx b/pages/api-references/genlayer-node/ops/health.mdx index 6b86402d..fdd2fe13 100644 --- a/pages/api-references/genlayer-node/ops/health.mdx +++ b/pages/api-references/genlayer-node/ops/health.mdx @@ -1,12 +1,14 @@ ### health -Returns the health status of the GenLayer node along with version information and the status of individual health checks. +Returns the node's operational status and the typed condition catalog that explains it. **Method:** `GET /health` **Parameters:** None -**Returns:** JSON object containing the overall health status, version information, and individual check results +**Returns:** JSON object containing the overall status, network/version information, validator identity (validator mode), and the full condition catalog + +**HTTP status:** `503` when `status` is `unavailable`; `200` for every other state. **Example Request:** @@ -18,101 +20,150 @@ curl -X GET http://localhost:9153/health ```json { - "status": "up", - "node_version": "v1.2.3", - "protocol_version": "consensus-v2.1", - "genvm_modules": { - "llm": { - "state": "healthy", - "timestamp": "2024-01-15T10:30:45Z", - "last_ok": "2024-01-15T10:30:45Z" - }, - "web": { - "state": "healthy", - "timestamp": "2024-01-15T10:30:45Z", - "last_ok": "2024-01-15T10:30:45Z" - } - }, - "checks": { - "test-check": { - "status": "up", - "timestamp": "2024-01-15T10:30:45Z" - } + "status": "healthy", + "network": "bradbury-phase2", + "node_version": "v0.6.0", + "protocol_version": "0.3.0", + "genvm_version": "v0.0.18", + "validator_address": "0x1234567890abcdef1234567890abcdef12345678", + "validator_moniker": "my-validator", + "conditions": { + "chain.rpc": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "chain.websocket": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "chain.sync": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:02:14Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "consensus.ban": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "consensus.stake": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "consensus.operator_funds": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "consensus.prime": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:04:41Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "genvm.llm": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:31Z", "updated_at": "2026-09-01T10:29:56Z", "last_ok": "2026-09-01T10:29:56Z" }, + "genvm.web": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:31Z", "updated_at": "2026-09-01T10:29:56Z", "last_ok": "2026-09-01T10:29:56Z" }, + "node.disk": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "node.snapshot_age": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "webdriver.render": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" } } } ``` **Response Fields:** -- `status` (string): Overall health status - "up" if all checks pass, "down" if any check fails -- `node_version` (string): The version of the GenLayer node software -- `protocol_version` (string): The consensus protocol version -- `genvm_modules` (object): Per-module GenVM health — whether this node can currently do LLM and web work. Reported for `llm` and `web`. - - `state` (string): `healthy`, `suspect`, `degraded`, or `unknown`. `suspect` means one failure has been observed and is not yet treated as confirmed; `unknown` means the module has not been exercised yet. - - `timestamp` (string, optional): ISO 8601 time the verdict was last updated. Absent until the module is first observed. - - `last_ok` (string, optional): ISO 8601 time the module last answered successfully. Absent if it never has. - - `detail` (string, optional): the module's own error text. Present only while not healthy. -- `checks` (object, optional): Map of individual health check results - - `[check-name]` (object): Results for a specific health check - - `status` (string): Check status - "up" or "down" - - `timestamp` (string): ISO 8601 timestamp of when the check was performed - - `error` (string, optional): Error message if check failed +- `status` (string): the most urgent state across the catalog — `unknown`, `healthy`, `suspect`, `warning`, `degraded`, or `unavailable` +- `network` (string): The network the node is connected to +- `node_version`, `protocol_version`, `genvm_version` (string): build versions +- `validator_address` (string, validator mode only): The validator's address +- `validator_moniker` (string, validator mode only): The validator's on-chain moniker; reported alongside the address even when empty +- `conditions` (object): the full catalog. Every entry is always present — this is not a list of what is broken. + - `state` (string): current observed health; use this field to determine whether the capability is working. + - `occurrences` (number): accepted failed observations since process start + - `consecutive_failures` (number): current failure streak; zero after recovery + - `since`, `updated_at`, `last_ok` (string, optional): ISO 8601 lifecycle timestamps + - `reason` (string, present only when not healthy): stable, low-cardinality cause code + - `impact` (string, present only when not healthy): what the fault does to this node + - `action` (string, present only when not healthy): what the operator should do + - `details` (object, present only when not healthy): typed fields explicitly allowlisted by the catalog entry. Raw provider payloads, prompts, credentials, URLs, and stack traces are rejected. -**GenVM module health does not affect `status`.** +`severity` is not returned by this endpoint. Alert priority belongs to the alerting policy; clients that previously read `conditions.*.severity` should use `state` for current health. -A node with a broken module is still booted, synced and voting — it votes Timeout on the transactions that need that module, which is the designed degraded behaviour rather than an outage. Failing the endpoint would pull a working validator out of load balancers and page an operator about a node behaving as intended. So `genvm_modules` is reported alongside `checks` rather than as one of them, and `status` stays `up`: +**States** -```json -{ - "status": "up", - "genvm_modules": { - "llm": { - "state": "degraded", - "timestamp": "2024-01-15T10:30:45Z", - "last_ok": "2024-01-15T04:12:03Z", - "detail": "provider unreachable — voting Timeout on transactions that need an LLM" - }, - "web": { "state": "healthy", "timestamp": "2024-01-15T10:30:45Z", "last_ok": "2024-01-15T10:30:45Z" } - } -} -``` +| State | Meaning | Alerted | +|---|---|---| +| `unknown` | The node never observed this condition. | no | +| `healthy` | The last observation succeeded. | no | +| `suspect` | One failure, not yet confirmed. It may clear by itself. | no | +| `warning` | The capability works, and it is confirmed sub-optimal. | yes | +| `degraded` | The failure is confirmed. The capability does not work. | yes | +| `unavailable` | The node cannot do its duty at all. | yes | + +Roll-up precedence, most urgent first: `unavailable`, `degraded`, `warning`, `suspect`, `unknown`, `healthy`. + +`unknown` is not on that ladder. It is only ever the state a condition starts in, so it means "not observed yet", never "faulty". A GenVM module rests there until a transaction needs one; `node.snapshot_age` rests there on a node that takes no snapshots. Letting either gate the endpoint would report `unknown` for a node where nothing is wrong. + +If **no** condition has been observed at all, `status` is `unknown` — nothing observed is not the same as nothing wrong. + +**Only `unavailable` returns 503.** + +A condition can never exceed the liveness cap its catalog entry declares. The GenVM conditions are capped at `degraded`, so a dead LLM provider returns **200**: that node is still booted, synced and voting — it votes Timeout on the transactions it cannot execute, which is the designed behaviour rather than an outage. Failing the endpoint would pull a working validator out of a load balancer. -`timestamp` and `last_ok` answer different questions and are both reported. `timestamp` is when the verdict was last updated; `last_ok` is when the module last worked. They match while healthy, and the gap between them while degraded shows how long the outage has run. `timestamp` matters more here than under `checks`: those entries are re-evaluated on every request, whereas module state is folded from real transaction traffic and is not polled while healthy — so on a quiet validator a `healthy` verdict can rest on an older observation. +`consensus.ban`, `consensus.stake`, `chain.rpc`, `chain.websocket` and `chain.sync` are capped at `unavailable`, because each of them stops the node doing its job. +**WebDriver render check** -**HTTP Status Codes:** +`webdriver.render` runs the same fresh render probe as `doctor`, using +`webdriver_host` from the configured GenVM's `genvm-module-web.yaml`. It requires +HTTP 200, `Resulting-Status: 200`, and exactly `Hello world!` from the diagnostic +page, within 10 seconds. It bypasses WebDriver's cached `/healthcheck` response. +A failure reports `degraded` with reason `webdriver_check_failed` and safe +`failure`, `http_status` and `page_status` details. The endpoint still returns +HTTP 200: a browser outage does not stop the node syncing and voting. -- `200 OK`: Node is healthy (all checks passing) -- `503 Service Unavailable`: Node is unhealthy (one or more checks failing) +Allow time for the ten-second render deadline when setting a client timeout. +`task devtools:nodes:health` defaults to 15 seconds per request so a browser +timeout can return its diagnosis instead of appearing as an unreachable node. -**Notes:** +`genvm.web` continues reporting the broader web module's boot and transaction +results. A successful direct browser check does not clear a web-module failure. -- The endpoint has a 5-second timeout for all health checks -- The `checks` field is only included when health checks are configured -- Version fields will show "unset" if version information is not available -- Individual health checks are executed concurrently with results aggregated in the response +For `genvm.start_manager: false`, the manager owns the browser configuration. +The node leaves `webdriver.render` unknown instead of probing an unused local +root. Doctor likewise reports the browser as not checked; run it on the node +that owns the manager configuration. Observed `genvm.web` results still apply. -**Example Response (Unhealthy):** +**Example: the LLM provider is dead — HTTP 200** ```json { - "status": "down", - "node_version": "v1.2.3", - "protocol_version": "consensus-v2.1", - "checks": { - "database": { - "status": "down", - "error": "connection timeout", - "timestamp": "2024-01-15T10:30:45Z" - }, - "consensus": { - "status": "up", - "timestamp": "2024-01-15T10:30:45Z" + "status": "degraded", + "conditions": { + "genvm.llm": { + "state": "degraded", + "reason": "llm_request_failed", + "impact": "This validator cannot execute a transaction that needs an LLM. It votes Timeout, and the consensus contracts may penalize a Timeout vote.", + "action": "Check the affected LLM provider service and payment or billing status, restore access, or configure a working fallback. If credentials are changed through .env, restart the node.", + "occurrences": 2, + "consecutive_failures": 2, + "since": "2026-09-01T10:30:45Z", + "updated_at": "2026-09-01T10:31:12Z", + "last_ok": "2026-09-01T04:12:03Z" } } } ``` +**Example: the validator is banned — HTTP 503** + +```json +{ + "status": "unavailable", + "conditions": { + "consensus.ban": { + "state": "unavailable", + "reason": "validator_banned", + "impact": "The consensus contracts do not select this validator for a round. The node earns nothing.", + "action": "Wait for the stated epoch. If the ban is permanent, restake with a new validator.", + "details": { "until_epoch": "4182", "permanent": "false" }, + "occurrences": 1, + "consecutive_failures": 1, + "since": "2026-09-01T09:14:02Z", + "updated_at": "2026-09-01T10:31:12Z", + "last_ok": "2026-09-01T09:13:52Z" + } + } +} +``` + +**`chain.sync` — 503 only when the node stops syncing** + +Depth is not a fault. A node syncing from genesis is millions of blocks behind and perfectly healthy. What matters is whether the stored synced block advances. + +| Reason | State | HTTP | +|---|---|---| +| `sync_lagging` | `warning` | 200 | +| `sync_catching_up` | `degraded` | 200 | +| `sync_stalled` | `unavailable` | **503** | +| `sync_feed_frozen` | `unavailable` | **503** | + +The node never skips a duty because it is behind. It votes Timeout only on a transaction that reads state it has not synced; a new contract deploy still runs. + **cURL Example with Headers:** ```bash diff --git a/pages/api-references/genlayer-node/ops/metrics.mdx b/pages/api-references/genlayer-node/ops/metrics.mdx index fd908d57..2a2bdb5c 100644 --- a/pages/api-references/genlayer-node/ops/metrics.mdx +++ b/pages/api-references/genlayer-node/ops/metrics.mdx @@ -41,6 +41,17 @@ The node collects metrics for three main components: - **GenVM** - The GenVM modules (LLM and Web) - **WebDriver** - The WebDriver container +**Operator Condition Metrics** + +- `genlayer_node_operator_condition_state{condition,state}`: one-hot gauge for the current catalog state. All state series are prewarmed at process start. +- `genlayer_node_operator_condition_occurrences_total{condition,reason}`: accepted failed observations by stable reason. +- `genlayer_node_operator_condition_transitions_total{condition,from,to}`: lifecycle transitions, including recovery. +- `genlayer_node_operator_condition_last_success_timestamp_seconds{condition}`: Unix timestamp of the last successful observation, or zero before one occurs. + +`condition`, `state`, `reason`, `from`, and `to` come from the fixed startup catalog; upstream error text is never used as a label. + +`occurrences_total` counts differently per condition kind, so the counter stays comparable: an *observed* condition (folded from duty traffic) counts every failed observation, while a *polled* condition counts only entry into a failing state. Counting every failed poll would scale the counter with the poll interval rather than the fault — a three-day ban at a ten-second poll would read as roughly 26,000 events. + **CPU Metrics** **genlayer_node_cpu_usage_percent** @@ -247,6 +258,15 @@ Node synchronization status (1 = synced, 0 = not synced). **Labels:** - `node`: Node address (hex format) +**genlayer_node_prime_blocked_insufficient_funds** + +Whether a mandatory validator prime is currently blocked by an unfunded operator wallet (1 = blocked, 0 = not blocked). While blocked the validator cannot pay for consensus duty transactions and is flagged idle each round — fund the operator wallet to recover. + +**Type:** Gauge + +**Labels:** +- `node`: Node address (hex format) + **genlayer_node_uptime_seconds** Node uptime in seconds. @@ -313,6 +333,137 @@ Total number of validator reveal votes. **Labels:** - `node`: Node address (hex format) +**Gas Metrics (duty transactions)** + +Emitted inline from the shared consensus gas estimator and transactor, so they cover every duty method (`finalizeTransaction`, `commitVote`, `revealVote`, `proposeReceiptWithFees`, `topUpAndSubmitAppeal`, `activateTransaction`, `validatorPrime`, `epochAdvance`, ...). All are true counters. + +**genlayer_node_gas_estimation_total** + +Outcome of every duty gas estimation. + +**Type:** Counter + +**Labels:** +- `method`: Contract method name +- `outcome`: One of `success` (real RPC estimate), `fallback_block_limit` (estimation failed; submitted at full block gas limit), `fallback_cache` / `fallback_cache_scaled` (estimation failed; reused gas from the shared calldata-length-keyed cache, possibly learned from a different method) + +**genlayer_node_gas_estimation_capped_total** + +Estimation succeeded but the safety-multiplied estimate (or per-call reserve) exceeded the block gas limit and was silently capped — the safety margin is gone while everything still works. + +**Type:** Counter + +**Labels:** +- `method`: Contract method name + +**genlayer_node_gas_shortfall_total** + +Confirmed on-chain out-of-gas: the transaction landed but ran out of gas mid-execution. Self-heals — the node retries with a larger gas limit — so a single occurrence is not actionable; a **sustained rate** signals systematic under-estimation (treat as a warning, not a page). + +**Type:** Counter + +**Labels:** +- `method`: Contract method name +- `reason`: One of `typed_shortfall` (`InsufficientGasForInternalMessageCreation` revert with exact gas requirement), `out_of_gas` (explicit `OutOfGas` revert), `heuristic` (undecoded revert that consumed gas above the exhaustion threshold) + +**genlayer_node_gas_block_limit_exhausted_total** + +Gas shortfall while the gas limit was already capped at the block limit: the work cannot fit in one block and retrying cannot help. Non-self-healing — **any occurrence is actionable** (page-worthy). + +**Type:** Counter + +**Labels:** +- `method`: Contract method name + +**genlayer_node_gas_spent_wei_total** + +Cumulative wei actually billed for gas, from each mined duty receipt (`gasUsed × effectiveGasPrice`). Reverted attempts are billed too and are accounted per attempt; submissions that never land cost nothing. The `reverted` slice is the exact cost of failed work, and `sum(genlayer_node_gas_spent_wei_total)` is the operator's total billed duty gas. + +**Type:** Counter + +**Labels:** +- `method`: Contract method name +- `outcome`: One of `success`, `reverted` +- `price_source`: `effective` (receipt's billed price) or `fee_cap` (submitted fee cap used as an upper bound when the RPC omits `effectiveGasPrice`) + +**Consensus Submission Metrics** + +**genlayer_node_consensus_submit_total** + +Terminal outcome of every duty submission. + +**Type:** Counter + +**Labels:** +- `method`: Contract method name +- `outcome`: One of `success` (landed and executed), `reverted` (landed, contract rejected — gas still billed), `failed` (never landed: RPC/nonce/funds/authorization) + +**genlayer_node_consensus_revert_total** + +Decoded revert causes, one increment per observed revert (attempt-level). + +**Type:** Counter + +**Labels:** +- `method`: Contract method name +- `error`: Decoded custom error from the fee/gas allowlist (`InsufficientFeesForRound`, `InsufficientFees`, `FailedFeeTransfer`, `FeeSettlementNotCompleted`, `MaxPriceExceeded`, `ExecutionBudgetExceeded`, `MessageBudgetExceeded`, `BudgetTooLow`, `RollupBudgetBelowFloor`, `InsufficientBondCustody`, `InvalidAppealBond`, `AppealBondTooLow`, `InsufficientGasForInternalMessageCreation`, `OutOfGas`); anything else collapses to `other` + +**Fee Flow Metrics (v0.6 ConsensusMainWithFees)** + +Note: there are no dedicated appeal-bond metrics. No node runtime path submits appeals — appellants are external clients funding bonds from their own wallets. If a validator appeal duty ever ships, the method-labeled gas/submission metrics above cover it under `method="topUpAndSubmitAppeal"` with no code change. + +**genlayer_node_fees_operator_insufficient_funds_total** + +Duty submissions rejected because the operator account cannot cover value + gas. Complements the `genlayer_node_prime_blocked_insufficient_funds` gauge by leaving a trace of dips that recover between scrapes. A sustained increase means the operator wallet needs funding (page-worthy). + +**Type:** Counter + +**genlayer_node_fees_settlement_events_total** + +Fee-settlement events decoded from this node's own mined finalize receipts. Finalize runs under a staggered lease, so each finalize is observed by exactly one node — aggregate fleet-wide with `sum by (network)`. Use the `severity` label to alert on failures without enumerating event names: `genlayer_node_fees_settlement_events_total{severity="alarm"}` covers all three failure modes and any future one. + +**Type:** Counter + +**Labels:** +- `event`: One of `UnifiedFeesDistributed` (fees distributed at settlement), `FeesRefunded` (surplus returned to the sender), `FeeSettlementFailed` (finalize succeeded but settlement failed — validators unpaid until `retryFeeSettlement`), `CommitFinalFeesFailed` (tx finalized without fee settlement), `ValueWithdrawalFailed` (a best-effort transfer — refund/appealer payout — failed; funds stay custodied pending recovery) +- `severity`: `alarm` for the three failure events (unpaid or stuck-funds modes needing operator action) or `info` for the normal `UnifiedFeesDistributed` / `FeesRefunded` happy path + +**genlayer_node_fees_distributed_wei_total** + +Total wei moved at fee settlement across **all** recipients — committee payouts **plus** sender/contributor refunds **plus** appealer rewards — summed from `UnifiedFeesDistributed` amounts in this node's finalize receipts. It **excludes** amounts retained in staking custody (`amountToStaking`). This is a network **"value settled"** liveness signal, **not** per-node earnings — for a validator's own income use `genlayer_node_fees_earned_wei_total`. + +**Type:** Counter + +**genlayer_node_fees_earned_wei_total** + +This node's validator earnings credited at prime, read from the `ValidatorPrime` event when a prime is effective. This is the accurate **per-node** income (unlike `fees_distributed_wei_total`, which is whole-committee plus refunds). Earnings settle into stake per epoch, so this series lags settlement by up to one prime interval. Pair `type="fee_rewards"` with `genlayer_node_gas_spent_wei_total` for operator net P&L (`earned − gas_spent`). + +**Type:** Counter + +**Labels:** +- `type`: One of `fee_rewards` (fee-flow income), `validator_rewards`, `delegator_rewards` (staking-reward components) + +**genlayer_node_fees_penalties_wei_total** + +This node's fee penalties deducted at prime, read from the same `ValidatorPrime` event. Kept separate from `fees_earned_wei_total` so earnings stay gross income; subtract this for net. + +**Type:** Counter + +**genlayer_node_external_message_delivery_total** + +External messages this node's own delivery calls (`flushExternalMessages`, `flushInternalMessages`, `finalizeTransaction`) resolved or paused, read from their mined receipts. A paused message was not called and not charged; the node retries it with a backoff (30 s doubling to 10 min) until the chain reports it issued. A `paused_*` series that keeps rising for one network means a message nobody can deliver. + +**Type:** Counter + +**Labels:** +- `outcome`: `delivered` (ran and succeeded), `call_failed` (ran and failed; final), `paused_gas` (no room to forward its gas envelope), `paused_value` (its value was not yet available), `cancelled_unfunded` (its open-bucket envelope was never funded; value refunded), `cancelled_ceiling` (its gas limit is above the current ceiling; value refunded) + +**genlayer_node_external_message_reimbursed_wei_total** + +Wei the chain reimbursed this node for running external messages: gas used times the transaction's locked gas price. Pair it with `gas_spent_wei_total` for the net cost of delivery. + +**Type:** Counter + **Go Runtime Metrics** The endpoint also exposes standard Go runtime metrics including: diff --git a/pages/api-references/genlayer-node/ops/snapshot.mdx b/pages/api-references/genlayer-node/ops/snapshot.mdx index e230ec5a..8139b5ba 100644 --- a/pages/api-references/genlayer-node/ops/snapshot.mdx +++ b/pages/api-references/genlayer-node/ops/snapshot.mdx @@ -20,7 +20,19 @@ The token is the value of `GENLAYERNODE_NODE_OPERATOR_TOKEN` on the node process **Parameters:** -- `destDir` (string, optional): Absolute path on the node's filesystem. When set, switches to write-to-path mode; when omitted or empty, the snapshot is streamed back as `application/gzip`. +- `scope` (string, optional): Which stores the archive carries — `all` (default), `genvm` or `ops`. An absent scope means `all`, so a caller written before the store split keeps its behaviour. An unrecognised value is rejected with `400`. + + | Scope | Contents | Portable between nodes | + |---|---|---| + | `all` | both stores, operation store first | **No** | + | `genvm` | chain-derived history only | **Yes** | + | `ops` | this node's consensus record only | **No** | + + Only `genvm` may be given to another operator. `all` and `ops` carry the vote salts this node generated and the nonce ownership of its operator key; restoring either onto a different node would make that node act on votes it never cast and nonce slots it never signed. The node refuses to start against an operation store stamped with another operator, but that guard runs at startup, not at copy time — treat a non-portable archive accordingly. + + Every archive carries a `snapshot.json` manifest recording the format version, the scopes inside, the rollup block the GenVM store was captured at, and — for `all` and `ops` — the operator address that produced it. + +- `destDir` (string, optional): Absolute path on the node's filesystem, which must resolve to a location inside the configured snapshot directory (`node.admin.snapshot_dir`, default `/snapshots`). When set, switches to write-to-path mode; when omitted or empty, the snapshot is streamed back as `application/gzip`. Paths outside the snapshot directory are rejected with `400`. **Returns:** - Stream mode (`destDir` omitted): gzipped tar archive in the response body, `Content-Type: application/gzip`. @@ -67,7 +79,16 @@ Content-Disposition: attachment; filename="snapshot.tar.gz" curl -X POST http://127.0.0.1:9153/snapshot \ -H "X-Admin-Auth: $GENLAYERNODE_NODE_OPERATOR_TOKEN" \ -H "Content-Type: application/json" \ - -d '{"destDir": "/var/snapshots/bradbury-100"}' + -d '{"destDir": "/var/lib/genlayer/data/node/snapshots/bradbury-100"}' + +Take only the portable half, to hand to another operator: + +```bash +curl -sS -X POST http://localhost:9153/snapshot \ + -H "Authorization: Bearer $OPERATOR_TOKEN" \ + -H 'Content-Type: application/json' \ + -d '{"scope": "genvm"}' -o genvm-snapshot.tar.gz +``` ``` **Example Response — Write-to-Path Mode:** @@ -80,7 +101,7 @@ HTTP/1.1 204 No Content - `200 OK`: Stream mode succeeded; body carries the gzipped tar archive. - `204 No Content`: Write-to-path mode succeeded; snapshot is on disk at `destDir`. -- `400 Bad Request`: `destDir` is not absolute, or already exists. +- `400 Bad Request`: `destDir` is not absolute, resolves outside the configured snapshot directory, or already exists. - `401 Unauthorized`: Missing, invalid, or empty operator token (also returned when `GENLAYERNODE_NODE_OPERATOR_TOKEN` is unset on the node). - `500 Internal Server Error`: Snapshot creation failed (e.g., disk full, DB checkpoint error). @@ -88,8 +109,9 @@ HTTP/1.1 204 No Content - A snapshot may be several GB. In stream mode the connection stays open for the duration of the snapshot — configure reverse proxies / load balancers with adequate read timeouts and disable response buffering. - In stream mode, a dropped connection leaves no on-disk artifact on the node; retry the request from scratch. -- In write-to-path mode, `destDir` must be an absolute path that does not yet exist; the node creates it. Path-traversal hardening beyond these checks is a separate (Sev: Medium) follow-up — treat the operator token as a credential that can write under the node user's filesystem permissions. -- **Docker deploys**: in write-to-path mode, `destDir` is resolved inside the node container's filesystem, not on the host. To make the resulting directory reachable from the host (or from another container), use a volume mount that maps `destDir` identically on both sides, or stick with stream mode. Stream mode is unaffected — the response body is sent over HTTP regardless of where the node runs. +- In write-to-path mode, `destDir` must be an absolute path that does not yet exist and that resolves (after `filepath.Clean`) to a location inside the configured snapshot directory; the node creates it. Paths that escape the directory via `..` or point elsewhere on the filesystem are rejected with `400`. This bounds where an operator-token holder can write a checkpoint — still treat the operator token as a credential that can write under the node user's filesystem permissions within that directory. +- **Snapshot directory** (`node.admin.snapshot_dir`): bounds write-to-path mode. A relative value resolves under `datadir` (so the default `snapshots` is `/snapshots`); an absolute value is used as-is, letting a node target a dedicated disk. The node creates the directory with `0700` permissions on first use if it does not exist. +- **Docker deploys**: in write-to-path mode, `destDir` (and the snapshot directory) is resolved inside the node container's filesystem, not on the host. The default `/snapshots` falls under whatever volume the deployment mounts for `datadir`, so it is host-reachable wherever that volume maps. To use a different location, set `snapshot_dir` to a path that is itself volume-mapped identically on both sides, or stick with stream mode. Stream mode is unaffected — the response body is sent over HTTP regardless of where the node runs. - The snapshot includes the full chain database (SSTables, MANIFEST, WAL) at the moment of capture. Treat the artifact as sensitive — anyone holding it can reconstruct the node's state. - Token comparison is constant-time and length-padded so neither the value nor the length of the secret can be inferred from response timing. - Rotation: update `GENLAYERNODE_NODE_OPERATOR_TOKEN` and restart the node. Tokens are read once at startup and not hot-reloaded. diff --git a/pages/validators/_meta.json b/pages/validators/_meta.json index f04164cd..878bf65b 100644 --- a/pages/validators/_meta.json +++ b/pages/validators/_meta.json @@ -4,6 +4,7 @@ "network-keeper-roles": "Network Keeper Roles", "system-requirements": "System Requirements", "genvm-configuration": "GenVM Configuration", + "greybox-setup": "Greybox Setup", "upgrade": "Upgrade Guide", "changelog": "Changelog" } diff --git a/pages/validators/changelog.mdx b/pages/validators/changelog.mdx index 9695ec57..fe05e979 100644 --- a/pages/validators/changelog.mdx +++ b/pages/validators/changelog.mdx @@ -560,7 +560,7 @@ ### New features -- Added [Comput3](/validators/setup-guide#genvm-configuration) as a supported LLM provider for validators +- Added [Comput3](https://docs.genlayer.com/validators/setup-guide#genvm-configuration) as a supported LLM provider for validators ## v0.3.2 diff --git a/pages/validators/genvm-configuration.mdx b/pages/validators/genvm-configuration.mdx index c3042eb0..47c9c469 100644 --- a/pages/validators/genvm-configuration.mdx +++ b/pages/validators/genvm-configuration.mdx @@ -46,7 +46,7 @@ Repeat for each GenVM instance if running multiple. For a full restart: `sudo sy ## Configuration Files -All configuration lives at `third_party/genvm/config/`. Three files: +All configuration lives in the GenVM tree of the release, at `opt/genvm//config/` (in the Docker image: `/app/opt/genvm/current/config/`). Three files: | File | What it controls | Should you modify? | |---|---|---| @@ -226,33 +226,7 @@ models: greybox: { text: 3, image: 3 } # both chains, lower priority ``` -**To enable:** - -```bash -sudo systemctl stop genlayer-node - -# Set your OpenRouter key (or whichever provider you're using) -echo "OPENROUTERKEY=sk-or-v1-your-key-here" >> /opt/genlayer-node/.env - -# Apply the release config with greybox metadata -VERSION=$(readlink /opt/genlayer-node/bin | sed 's|/bin||; s|.*/||') -cp /opt/genlayer-node/${VERSION}/third_party/genvm/config/genvm-modules-llm-release.yaml \ - /opt/genlayer-node/${VERSION}/third_party/genvm/config/genvm-module-llm.yaml - -# Switch to greybox script -sed -i 's/genvm-llm-default\.lua/genvm-llm-greybox.lua/' \ - /opt/genlayer-node/${VERSION}/third_party/genvm/config/genvm-module-llm.yaml - -sudo systemctl start genlayer-node -``` - -**Verify:** -```bash -sudo journalctl -u genlayer-node --no-hostname | grep "greybox" -# Expected: greybox: success provider: openrouter model: deepseek/deepseek-v3.2 -``` - -**To revert:** change `genvm-llm-greybox.lua` back to `genvm-llm-default.lua` and restart. +**To enable, verify, switch back, or reload it on a running node:** follow the [Greybox Setup](/validators/greybox-setup) guide. It is the node release's own guide, synced from the node repository. #### Writing a custom strategy diff --git a/pages/validators/greybox-setup.mdx b/pages/validators/greybox-setup.mdx new file mode 100644 index 00000000..909eb774 --- /dev/null +++ b/pages/validators/greybox-setup.mdx @@ -0,0 +1,195 @@ +{/* Generated from content/validators/greybox-setup-guide.md (genlayer-node release/greybox-setup-guide.md) by scripts/update-greybox-in-genvm-config.js. Do not edit here. */} + +# Greybox LLM Strategy — Validator Setup Guide + +Switch your GenLayer node from the default auto-generated provider chain to a curated, operator-controlled fallback chain via OpenRouter. + +## What is Greybox? + +The default policy (`genvm-llm-default.lua`) is already deterministic: it auto-builds a single chain over every enabled model, ordered by provider `meta.priority` **descending**, and falls through to the next candidate on any failure. What it is not is *curated* — it takes whatever models are enabled, in one undifferentiated chain. + +With **greybox**, the operator picks the models and their order by hand, in two independent chains (text and image), configured via `meta.greybox` fields on each model in the YAML. Note the two knobs run in opposite directions: `meta.priority` is highest-first, `meta.greybox` is lowest-first. + +Greyboxing is deliberately per-validator — validators are expected to diverge in provider choice, so there is no single "correct" chain to converge on. + +**Default text chain:** deepseek-v3.2 → qwen3-235b → claude-haiku-4.5 → kimi-k2 → glm-5 → llama-3.3 (heurist) → llama-3.3 (ionet) + +**Default image chain:** gpt-5.1-mini → gemini-3-flash → claude-haiku-4.5 + +Chain order is determined by the `meta.greybox` priority numbers on each model in the YAML. Lower number = higher priority. You can change the order by editing these numbers — no Lua changes needed. + +OpenRouter is the primary aggregator. If it fails, the node falls back to direct provider APIs (heurist, ionet). + +## Prerequisites + +- GenLayer node **v0.5.7+** (tarball must include `genvm-modules-llm-release.yaml` and `genvm-llm-greybox.lua`) +- An **OpenRouter API key** — get one at https://openrouter.ai/keys + +## Step-by-Step Setup + +### 1. Stop the node + +```bash +sudo systemctl stop genlayer-node +``` + +### 2. Add OpenRouter API key to .env + +```bash +# Find your active .env +ENV_FILE="/opt/genlayer-node/.env" + +# Add the key (or edit the file manually) +echo "OPENROUTERKEY=sk-or-v1-your-key-here" >> ${ENV_FILE} +``` + +### 3. Apply the release LLM config + +The tarball ships with a unified config that includes all backends (openrouter, morpheus, heurist, ionet, etc.). + +```bash +VERSION=$(readlink /opt/genlayer-node/bin | sed 's|/bin||; s|.*/||') + +cp /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-modules-llm-release.yaml \ + /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml +``` + +### 4. Switch to greybox strategy + +```bash +sed -i 's/genvm-llm-default\.lua/genvm-llm-greybox.lua/' \ + /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml +``` + +### 5. Verify the config + +```bash +# Check lua script path +grep lua_script_path /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml +# Expected: lua_script_path: ${exeDir}/../config/genvm-llm-greybox.lua + +# Check openrouter is present +grep -A2 'openrouter:' /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml +# Expected: enabled: true + +# Check the greybox Lua file exists +ls -la /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-llm-greybox.lua +``` + +### 6. Start the node + +```bash +sudo systemctl start genlayer-node +``` + +### 7. Verify greybox is active + +Wait for an LLM transaction to be processed, then check the logs: + +```bash +sudo journalctl -u genlayer-node --no-hostname | grep "greybox" +``` + +You should see entries like: +``` +greybox: using text chain count: 5 +greybox: success provider: openrouter model: deepseek/deepseek-v3.2 is_primary: true +``` + +## Switching Back to Default + +To revert to the default generated chain: + +```bash +sudo systemctl stop genlayer-node + +sed -i 's/genvm-llm-greybox\.lua/genvm-llm-default.lua/' \ + /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml + +sudo systemctl start genlayer-node +``` + +## Updating Greybox on a Running Node (No Full Restart) + +If you need to update the Lua script or model config without stopping the whole node, +you can restart just the LLM module on each GenVM instance: + +```bash +# Find the GenVM manager port (check your config or active ports) +PORT=3999 + +# Stop the LLM module +curl -X POST "http://127.0.0.1:${PORT}/module/stop" \ + -H 'Content-Type: application/json' \ + -d '{"module_type": "Llm"}' + +# Start the LLM module (reloads Lua script and config) +curl -X POST "http://127.0.0.1:${PORT}/module/start" \ + -H 'Content-Type: application/json' \ + -d '{"module_type": "Llm", "config": null}' +``` + +Repeat for each GenVM instance port. There is no atomic restart — each instance +restarts independently. + +## Customizing the Chain Order + +The greybox Lua script reads chain membership and priority from `meta.greybox` on each model in the YAML config. Example: + +```yaml +models: + deepseek/deepseek-v3.2: + supports_json: true + meta: + greybox: { text: 1 } # text chain, priority 1 (primary) + openai/gpt-5.1-mini: + supports_json: true + supports_image: true + meta: + greybox: { image: 1 } # image chain, priority 1 + anthropic/claude-haiku-4.5: + supports_json: true + supports_image: true + meta: + greybox: { text: 3, image: 3 } # both chains +``` + +**Change model order:** Edit the priority numbers. Lower number = tried first. + +**Add a model to the chain:** Add `meta: { greybox: { text: N } }` to any model in any enabled backend. + +**Remove a model from the chain:** Remove its `meta.greybox` field. + +**Disable an entire provider:** Remove its API key from `.env` — all its models drop out automatically. + +The YAML config **must** have `meta.greybox` fields on at least some models. If none are found, the LLM module will fail to start with an error. + +After editing the YAML, restart the LLM module (see "Updating Greybox on a Running Node" above) or restart the node. + +## Troubleshooting + +**"module_failed_to_start" error:** +- Check that `genvm-llm-greybox.lua` exists in the config directory +- Check that `OPENROUTERKEY` is set in `.env` and not empty +- Check that the openrouter backend shows `enabled: true` in the YAML + +**No "greybox:" entries in logs:** +- The greybox Lua only logs when an LLM call happens. Run a transaction that uses an intelligent contract with LLM calls. +- Verify `lua_script_path` points to `genvm-llm-greybox.lua` (not `default`) + +**All models exhausted error:** +- OpenRouter may be down or your key is invalid +- Check your key at https://openrouter.ai/settings/keys +- Run `task node:doctor` — the "LLM Providers" section probes each enabled backend and reports which ones actually answer, rather than only whether a key variable is set +- Fallback providers (heurist, ionet) also need valid keys if you want fallback to work + +**Configure more than one provider.** A backend only enters the fallback chain +if its key is set, so a node set up with OpenRouter alone has no fallback: when +that provider goes down or is decommissioned, the chain exhausts immediately. +The node stays up and keeps voting, but it votes Timeout on every transaction +needing an LLM, and Timeout votes are penalized. `doctor` warns when only one +backend is usable. + +Note that a key added to `.env` needs a node restart to take effect — a running +process cannot pick up environment changes. Editing `genvm-module-llm.yaml` +does not. diff --git a/pages/validators/monitoring.mdx b/pages/validators/monitoring.mdx index b60700d5..7e4345c4 100644 --- a/pages/validators/monitoring.mdx +++ b/pages/validators/monitoring.mdx @@ -142,36 +142,85 @@ METRICS_SCRAPE_INTERVAL=15s # Set SCRAPE_TARGETS_JSON in .env # docker compose --profile monitoring up -d alloy: - image: grafana/alloy:v1.16.1 + image: grafana/alloy:v1.20.1 + init: true container_name: genlayer-node-alloy + labels: + com.genlayer.health-recovery.logs: "true" + com.genlayer.network: "${NETWORK_NAME:-unknown}" + depends_on: + - docker-socket-proxy + # The guard runs before Alloy: with the monitoring profile up but the + # endpoints or credentials unset, the container stops with a message + # naming what to set, instead of half-running. Half-running is worse than + # stopping here in both directions: pushing to a shared endpoint with a + # shared default credential pollutes it for everyone, and pushing to an + # unreachable one leaves telemetry unavailable and can grow the WAL. The + # healthcheck preserves that buffer. The $$ keeps compose from + # interpolating; the shell only tests emptiness — no value ever lands on + # a command line. + entrypoint: ["/bin/sh", "-c"] command: - - run - - /etc/alloy/config.river - - --server.http.listen-addr=0.0.0.0:12345 - - --storage.path=/var/lib/alloy/data + - >- + missing=""; + [ -n "$$CENTRAL_MONITORING_URL" ] || missing="$$missing CENTRAL_MONITORING_URL"; + [ -n "$$CENTRAL_MONITORING_USERNAME" ] || missing="$$missing CENTRAL_MONITORING_USERNAME"; + [ -n "$$CENTRAL_MONITORING_PASSWORD" ] || missing="$$missing CENTRAL_MONITORING_PASSWORD"; + [ -n "$$CENTRAL_LOKI_URL" ] || missing="$$missing CENTRAL_LOKI_URL"; + [ -n "$$CENTRAL_LOKI_USERNAME" ] || missing="$$missing CENTRAL_LOKI_USERNAME"; + [ -n "$$CENTRAL_LOKI_PASSWORD" ] || missing="$$missing CENTRAL_LOKI_PASSWORD"; + if [ -n "$$missing" ]; then + echo "ERROR: not set:$$missing" >&2; + echo "Monitoring pushes to an endpoint YOU configure — set the CENTRAL_* values in .env (see .env.example for a localhost example)." >&2; + exit 1; + fi; + exec /bin/alloy run /etc/alloy/config.river + --server.http.listen-addr=0.0.0.0:12345 + --storage.path=/var/lib/alloy/data volumes: - ./alloy-config.river:/etc/alloy/config.river:ro - ./alloy-healthcheck.sh:/etc/alloy/healthcheck.sh:ro - ${NODE_LOGS_PATH:-./data/node/logs}:/var/log/genlayer:ro - alloy_data:/var/lib/alloy healthcheck: - test: ["CMD", "sh", "/etc/alloy/healthcheck.sh"] + test: + - CMD-SHELL + - | + set -eu + sh /etc/alloy/healthcheck.sh && exit 0 + [ "$${HEALTHCHECK_AUTO_RESTART:-true}" != false ] || exit 1 + [ "$$(cat /proc/1/comm)" = docker-init ] || exit 1 + boot=$$(awk '{print $$22}' /proc/1/stat) + age=$$(awk -v ticks="$$boot" -v hz="$$(getconf CLK_TCK)" '{print int($$1-ticks/hz)}' /proc/uptime) + [ "$$age" -ge 120 ] || exit 1 + children=$$(cat /proc/1/task/1/children) + printf 'Container alloy (%s) found to be unhealthy - Restarting container now\n' "$$(cat /etc/hostname)" > /proc/1/fd/1 + kill -TERM 1 + sleep 5 + # A stopped service cannot handle TERM; init's child can still receive KILL. + for child in $$children; do kill -KILL "$$child"; done + exit 1 interval: 120s timeout: 15s retries: 1 start_period: 120s environment: - # Central monitoring endpoints - - CENTRAL_LOKI_URL=${CENTRAL_LOKI_URL:-https://logs-prod-042.grafana.net/loki/api/v1/push} - - CENTRAL_MONITORING_URL=${CENTRAL_MONITORING_URL:-https://prometheus-prod-66-prod-us-east-3.grafana.net/api/prom/push} + - HEALTHCHECK_AUTO_RESTART=${HEALTHCHECK_AUTO_RESTART:-true} + # Central monitoring endpoints and credentials: NO defaults, on purpose. + # An endpoint may be public knowledge; a credential never is, and a + # default one ships the same key to everyone holding this file. The + # entrypoint guard above turns "unset" into a clear stop instead of a + # silent push to the wrong place. + - CENTRAL_LOKI_URL=${CENTRAL_LOKI_URL:-} + - CENTRAL_MONITORING_URL=${CENTRAL_MONITORING_URL:-} # Metrics (Prometheus) authentication - - CENTRAL_MONITORING_USERNAME=${CENTRAL_MONITORING_USERNAME:-telemetric} - - CENTRAL_MONITORING_PASSWORD=${CENTRAL_MONITORING_PASSWORD:-12345678} + - CENTRAL_MONITORING_USERNAME=${CENTRAL_MONITORING_USERNAME:-} + - CENTRAL_MONITORING_PASSWORD=${CENTRAL_MONITORING_PASSWORD:-} # Logs (Loki) authentication - - CENTRAL_LOKI_USERNAME=${CENTRAL_LOKI_USERNAME:-telemetric} - - CENTRAL_LOKI_PASSWORD=${CENTRAL_LOKI_PASSWORD:-12345678} + - CENTRAL_LOKI_USERNAME=${CENTRAL_LOKI_USERNAME:-} + - CENTRAL_LOKI_PASSWORD=${CENTRAL_LOKI_PASSWORD:-} # Single node configuration - NODE_ID=${NODE_ID:-validator-001} @@ -265,6 +314,65 @@ loki.source.file "genlayer" { } // Write logs to central Loki instance +// Read opted-in WebDriver/Alloy recovery events through the existing read-only +// Docker proxy. Containers log their own restart attempts before exiting. +discovery.docker "health_recovery" { + host = "http://docker-socket-proxy:2375" + refresh_interval = "5s" + + filter { + name = "label" + values = [ + "com.genlayer.health-recovery.logs=true", + ] + } +} + +discovery.relabel "health_recovery" { + targets = discovery.docker.health_recovery.targets + + rule { + source_labels = ["__meta_docker_container_label_com_docker_compose_project"] + target_label = "compose_project" + } + + rule { + source_labels = ["__meta_docker_container_label_com_genlayer_network"] + target_label = "network" + } + + rule { + source_labels = ["network"] + regex = "" + target_label = "network" + replacement = "unknown" + } +} + +loki.source.docker "health_recovery" { + // Docker's TCP transport keeps the log stream open. With an HTTP(S) host, + // Alloy bounds the entire stream by refresh_interval and repeatedly reopens it. + host = "tcp://docker-socket-proxy:2375" + targets = discovery.relabel.health_recovery.output + refresh_interval = "5s" + labels = { + job = "genlayer-node", + component = "health-recovery", + instance = sys.env("NODE_ID"), + validator_name = sys.env("VALIDATOR_NAME"), + } + forward_to = [loki.process.health_recovery.receiver] +} + +// Collect only restart events, not Alloy's own operational log stream. +loki.process "health_recovery" { + stage.match { + selector = "{component=\"health-recovery\"} != \"found to be unhealthy - Restarting container now\"" + action = "drop" + } + forward_to = [loki.write.central.receiver] +} + loki.write "central" { endpoint { url = sys.env("CENTRAL_LOKI_URL") diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index 624fff75..d3c942f3 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -210,7 +210,7 @@ One full node can be shared between multiple validators. The optimal validator-t You can use this command to list available versions: ```sh copy - curl -s "https://storage.googleapis.com/storage/v1/b/gh-af/o?prefix=genlayer-node/bin/amd64" | grep -o '"name": *"[^"]*"' | sed -n 's/.*\/\(v[^/]*\)\/.*/\1/p' | sort -ru | grep -v "rc" | head -n 5 + curl -s "https://storage.googleapis.com/storage/v1/b/gh-af/o?prefix=genlayer-node/bin/amd64" | grep -o '"name": *"[^"]*"' | sed -n 's/.*\/\(v[^/]*\)\/.*/\1/p' | sort -ruV | grep -v "rc" | head -n 5 ``` You should see a list like this @@ -245,8 +245,11 @@ One full node can be shared between multiple validators. The optimal validator-t This step needs Python 3.10 or newer, available as `python3` on your `PATH`, with `pip` and `venv` (on Debian/Ubuntu: `python3-pip` and `python3-venv`). Check with `python3 --version`. + Run it in the final install location: it writes this directory's absolute path into the GenVM binaries. If you move the installation later, run it again there. + ```sh copy - python3 ./third_party/genvm/bin/setup.py + export GENVM_VERSION=$(ls opt/genvm) # the GenVM version this release ships + python3 ./opt/genvm/${GENVM_VERSION}/bin/setup.py ``` It does following: @@ -272,6 +275,10 @@ You can use the following example configuration. **Note:** For most users, you w rollup: genlayerchainrpcurl: "TODO: Set your GenLayer Chain ZKSync HTTP RPC URL here" # GenLayer Chain RPC URL genlayerchainwebsocketurl: "TODO: Set your GenLayer Chain ZKSync WebSocket RPC URL here" # GenLayer Chain WebSocket URL + # An API key of the RPC provider never goes in these URLs or in this file: + # set it in the environment as GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY. + # The node sends it in this header (default X-AccessKey): + # genlayerchainrpckeyheader: "X-AccessKey" provider: "TODO: Set your GenLayer Chain ZKSync provider" # Rollup operator name; surfaces as the "provider" label on RPC-driven metrics. Set explicitly per deployment. # consensus contracts configuration consensus: @@ -293,6 +300,18 @@ logging: file: # enabled: set to `true` to save logs to a folder enabled: true + # The console and this file carry different levels on purpose: INFO to the + # terminal, DEBUG here. That split is why the file sink exists — INFO is what + # an operator watches live and has to stay readable, DEBUG is the complete + # record kept for investigation. Set this to INFO and the two collapse: the + # file becomes a copy of the console and stops being a record. + # + # It has to be on permanently. An incident is over by the time anyone could + # raise the level, and the detail cannot be reconstructed after the fact. + # + # What it accepts: every debug line lands on disk, including RPC and GenVM + # payloads chosen by the caller. The level bounds nothing — + # maxsize/maxage/maxbackups below are what limit the volume. level: "DEBUG" # folder: path to the folder where to store logs. Relative paths go under `datadir`. folder: logs @@ -316,10 +335,16 @@ node: mode: "validator" # Address of the ValidatorWallet contract (required for validator mode) validatorWalletAddress: "" - # Address of the operator that owns the ValidatorWallet + # Address of the operator that performs consensus duties for the ValidatorWallet operatorAddress: "" admin: port: 9155 + # snapshot_dir: bounding directory for POST /snapshot write-to-path mode. + # A caller-supplied destDir must resolve inside this directory, else the + # request is rejected with 400. Relative paths go under `datadir`; an + # absolute path is used as-is (e.g. to target a dedicated disk). + # Default: snapshots (i.e. /snapshots) + # snapshot_dir: snapshots rpc: # host: 0.0.0.0 # bind address for the HTTP listener (default: all interfaces) port: 9151 # RPC server port @@ -364,73 +389,122 @@ node: # - "::1" # IPv6 loopback (localhost on macOS) # - "10.0.0.0/8" endpoints: - # Group-level configuration (enables/disables all methods in a group). - # When a group is enabled, ALL methods in that group are available. - # Rate limits (req/s per IP) apply by default even without config: + # RPC method groups, keyed directly under `endpoints`. Each group toggles a + # family of methods and may nest per-method overrides and rate limits. + # Default rate limits (req/s per IP) apply even when unset: # genlayer: 20, genlayer_debug: 2, ethereum: 50, zksync: 50 - # gen_call: 2, gen_getContractSchema: 2 (method-level overrides) - groups: - genlayer: true # gen_* methods (default ratelimit: 20 req/s per IP) - genlayer_debug: true # gen_dbg_* methods (default ratelimit: 2 req/s per IP) - ethereum: true # eth_* proxy methods (default ratelimit: 50 req/s per IP) - # includes: eth_blockNumber, eth_getBlockByNumber, eth_getBlockByHash, - # eth_sendRawTransaction, eth_getTransactionReceipt, etc. - zksync: true # zks_* proxy methods (default ratelimit: 50 req/s per IP) - # includes: zks_getTransaction, zks_getBlockDetails, etc. - # Object format with custom rate limit (overrides defaults): - # groups: - # genlayer: - # enabled: true - # ratelimit: 100 # custom: 100 req/s per IP instead of default 20 - # - # Method-level configuration (optional, overrides group settings). - # Methods inherit their group's enabled state by default. - # Use this section to: - # - Disable specific methods in an enabled group - # - Enable specific methods in a disabled group - # - Set per-method rate limits - # - # Example: enable all eth_* except eth_sendRawTransaction: - # groups: - # ethereum: true - # methods: - # eth_sendRawTransaction: false - # - # Example: disable all eth_* except eth_sendRawTransaction: - # groups: - # ethereum: false - # methods: - # eth_sendRawTransaction: true - # methods: - # gen_call: true # default ratelimit: 2 req/s per IP (GenVM-bound) - # gen_getContractSchema: true # default ratelimit: 2 req/s per IP (GenVM-bound) - # eth_blockNumber: true - # eth_sendRawTransaction: true - # zks_getTransaction: true - # Object format with custom rate limit: - # methods: - # gen_call: - # enabled: true - # ratelimit: 5 # custom: 5 req/s per IP instead of default 2 - # gen_getContractSchema: - # enabled: true - # ratelimit: 0 # explicitly disable rate limiting for this method + # gen_call: 2, gen_getContractSchema: 2 (method-level defaults) + genlayer: + enabled: true # gen_* methods + # gen_dbg_* methods (trie dump, transaction trace). OFF by default: these + # replay a transaction through GenVM and return internal execution output, + # so they are debug tooling, not a public API. Enable deliberately, and + # only on an RPC bind that untrusted callers cannot reach. Matches the + # node's own code default and the go-ethereum debug-namespace convention. + genlayer_debug: + enabled: false # gen_dbg_* methods + ethereum: + enabled: true # eth_* proxy methods (eth_blockNumber, eth_call, ...) + zksync: + enabled: false # zks_* proxy methods (zks_getBlockDetails, ...) + # + # A group with no overrides may use the bool shorthand (`ethereum: true`). + # + # Custom per-group rate limit + per-method overrides (object form): + # genlayer: + # enabled: true + # ratelimit: 100 # 100 req/s per IP instead of the default 20 + # methods: + # gen_call: + # enabled: true + # ratelimit: 5 # 5 req/s per IP instead of the default 2 + # gen_getContractSchema: + # enabled: false # disable a single method within an enabled group + # + # Proxy allowlist (opt-in): disable a proxy group, then re-enable only a + # vetted set under its `methods`. Every other eth_*/zks_* is then rejected + # with -32601 (method not found) and not forwarded upstream. Leave the + # groups enabled (the default) to forward all eth_*/zks_* (passthrough). + # ethereum: + # enabled: false + # methods: + # eth_getBalance: true + # eth_getTransactionCount: true + # eth_getCode: true + # eth_getStorageAt: true + # eth_call: true + # eth_estimateGas: true + # eth_gasPrice: true + # eth_blockNumber: true + # eth_getBlockByHash: true + # eth_getBlockByNumber: true + # eth_getTransactionByHash: true + # eth_getTransactionReceipt: true + # eth_getLogs: true + # eth_chainId: true + # eth_syncing: true + # eth_accounts: true + # eth_sendRawTransaction: true # write — enable only if clients submit raw txs + # zksync: + # enabled: false # zks_* left fully disabled (no vetted client use) ops: - port: 9153 # Metrics port + port: 9153 # Ops server port (/health, /metrics, /balance, /snapshot) endpoints: - metrics: true # Enable metrics endpoint - health: true # Enable health endpoint - balance: true # Enable balance endpoint - dev: - disableSubscription: false # Useful for testing with zksync-anvil + # Compatibility metadata. The current Ops server registers /metrics, + # /health, /balance, and /snapshot regardless of these values. `doctor` + # consults `health` to decide whether to run its HTTP health probe. + metrics: true + health: true + # Execution-trace store and debug-trace reply bounds. All optional; the + # values below are the built-in defaults. Uncomment to tune at runtime — no + # rebuild needed. + # trace: + # retention_blocks: 1000000 # keep traces this many blocks behind the tip (0 disables retention) + # max_trace_bytes: 33554432 # 32 MB: a single trace larger than this is never stored + # # Per-field cap on the public gen_dbg_traceTransaction reply. Each free-form + # # field (return_data, stdout, stderr, genvm_log) carries contract-emitted + # # output, so its size is contract-controlled. Over the cap, the field is + # # truncated and the cut is marked in-band. 0 takes the default. + # max_debug_field_bytes: 262144 # 256 KB per field # genvm configuration genvm: - root_dir: ./third_party/genvm + root_dir: ./opt/genvm/v0.6.0-rc1 start_manager: true # if true node will start genvm manager itself manager_url: http://127.0.0.1:3999 - permits: 8 # Leave empty for autodiscovery, put a number for updating genvm permits on startup - sync_permits: 64 # Optional: permits used while !isSynced (bulk replay). Leave empty to reuse the steady-state `permits` value. - sync_wait_timeout: "20s" # Max time to wait for GenVM to sync to a requested block before returning an error + # Permits are charged PER EXECUTION by the genvm manager, and one + # nondeterministic execution costs 8 of them (permits.nondet). This value is + # therefore a permit budget, not an execution count: 32 admits 4 concurrent + # nondet executions, which is what the previous `8` bought when a run cost 2 + # permits. Leaving it EMPTY is preferable — autodiscovery sizes it from the + # host and already speaks this unit. + permits: 32 # Leave empty for autodiscovery, put a number for updating genvm permits on startup + # Same unit as `permits` above, but a sync execution costs 4 rather than 8, + # so 256 admits the 64 concurrent replays the previous `64` bought at cost 1. + sync_permits: 256 # Optional: permits used while significantly behind (bulk replay). Leave empty to reuse the steady-state `permits` value. + # sync_permits_lag_threshold: blocks-behind above which the node runs the replay + # budget (sync_permits) instead of the live budget (permits); at or below it the + # node is treated as caught up. Keyed on the actual sync lag, not the health + # gradient. Uncomment to tune; default 1000. + # sync_permits_lag_threshold: 1000 + # sync_wait_timeout bounds the gen_* READ path — not GenVM execution: a read + # (gen_call / gen_getContractState / gen_getContractCode / + # gen_dbg_traceTransaction) waits up to this long for the node to sync the + # requested block and index the record, then returns a not-synced error. + sync_wait_timeout: "20s" + # sync_wait_poll_interval is the re-check cadence those same reads use while they + # poll for an artifact that lands just after its block is synced, bounded overall + # by sync_wait_timeout. Uncomment to tune; default 100ms. An absent key and a + # non-positive value both mean "use the default": 0s would otherwise be a wait + # that never ticks. + # sync_wait_poll_interval: "100ms" + replay: + parallel_enabled: false # Opt in to bounded parallel replay of independent accepted transactions in the same block + workers: 4 # Maximum concurrent replay executions + consensus_reserve_permits: 2 # Kept free for live validator duties unless a duty is waiting on replayed state + replay_floor_permits: 1 # Minimum replay capacity while replay work is queued + max_journal_bytes: 268435456 # 256 MiB block-wide cap; overflow falls back to serial replay + reorg_history_blocks: 4096 # Number of hash-identified replay checkpoints retained for exact canonical rollback + max_reorg_journal_bytes: 268435456 # 256 MiB inverse-journal cap per block; overflow aborts replay and keeps duties disabled # Advanced configuration merkleforest: maxdepth: 16 @@ -680,7 +754,7 @@ Once you have configured everything, you are ready to start the node. - To start them automatically in node configuration set `genvm.manage_modules` to `true` - To start them manually run ```bash copy - ./third_party/genvm/bin/genvm-modules web & ./third_party/genvm/bin/genvm-modules llm & + ./opt/genvm/${GENVM_VERSION}/bin/genvm-modules web & ./opt/genvm/${GENVM_VERSION}/bin/genvm-modules llm & ``` @@ -727,23 +801,120 @@ You can also run the GenLayer node using Docker and Docker Compose. services: webdriver-container: container_name: genlayer-node-webdriver - image: yeagerai/genlayer-genvm-webdriver:0.0.11 + image: yeagerai/genlayer-genvm-webdriver:0.0.12 shm_size: 2gb security_opt: - no-new-privileges:true environment: PORT: 4444 + HEALTHCHECK_AUTO_RESTART: "${HEALTHCHECK_AUTO_RESTART:-true}" ports: - "${WEBDRIVER_PORT:-4444}:4444" restart: unless-stopped + labels: + com.genlayer.health-recovery.logs: "true" + com.genlayer.network: "${NETWORK_NAME:-unknown}" + healthcheck: + test: + - CMD-SHELL + - | + set -eu + boot=$$(awk '{print $$22}' /proc/1/stat) + if curl -fsS --max-time 10 "http://localhost:$${PORT:-4444}/healthcheck?mode=text&url=https%3A%2F%2Ftest-server.genlayer.com%2Fstatic%2Fgenvm%2Fhello.html"; then + printf '%s 0\n' "$$boot" > /tmp/webdriver-health-failures + exit 0 + fi + age=$$(awk -v ticks="$$boot" -v hz="$$(getconf CLK_TCK)" '{print int($$1-ticks/hz)}' /proc/uptime) + [ "$$age" -ge 10 ] || exit 1 + read -r previous failures 2>/dev/null < /tmp/webdriver-health-failures || failures=0 + [ "$${previous:-}" = "$$boot" ] || failures=0 + failures=$$((failures + 1)) + printf '%s %s\n' "$$boot" "$$failures" > /tmp/webdriver-health-failures + if [ "$$failures" -ge 4 ] && [ "$${HEALTHCHECK_AUTO_RESTART:-true}" != false ]; then + printf 'Container webdriver (%s) found to be unhealthy - Restarting container now\n' "$$(cat /etc/hostname)" > /proc/1/fd/1 + children=$$(cat /proc/1/task/1/children) + for child in $$children; do kill -KILL "$$child"; done + fi + exit 1 + interval: 30s + timeout: 12s + retries: 4 + start_period: 10s + # docker-socket-proxy — read-only Docker API gateway for the node's WebDriver + # resource stats and Alloy's recovery-log reader. Runs with the `node` OR + # `monitoring` profile, so it comes up alongside + # a node container AND alongside a monitoring/telemetry bring-up — the latter is + # how a binary/systemd node (node not containerized) gets its webdriver metrics. + # + # The node and Alloy use this read-only gateway instead of the host socket. + # Reading container stats requires the Docker API. Previously the node reached + # the host /var/run/docker.sock directly (bind-mounted into the container, or via + # docker-group membership for a binary node) — but that grants the FULL Docker + # API as root (a `:ro` flag does NOT restrict API calls), so a node compromise + # could escalate to host root. This proxy REPLACES the node's socket access, + # restricted to read-only container endpoints (CONTAINERS=1; + # POST=0 => it returns 403 for create / exec / image operations). + # Health checks stop only their own service process; recovery has no Docker API access. + # - Published ONLY on 127.0.0.1:2375 (host loopback), never 0.0.0.0: 2375 is + # the unauthenticated Docker API, so it must not leave the host. A container + # node reaches it over the compose network (tcp://docker-socket-proxy:2375); + # a binary/systemd node reaches it on the host (tcp://127.0.0.1:2375). + # - Standard, widely-audited image (tecnativa/docker-socket-proxy). + # + # The node points DOCKER_HOST at this proxy and NEVER falls back to the host + # socket: if DOCKER_HOST is unset the node skips webdriver metrics entirely + # (non-fatal). Note those are the webdriver container's CPU/memory/traffic series + # (genlayer_node_*{component="webdriver"}) that feed validator resource dashboards — + # so keep DOCKER_HOST set to keep those panels populated. Disabling metrics + # permits removing the node's DOCKER_HOST; Alloy still needs this gateway. + docker-socket-proxy: + image: tecnativa/docker-socket-proxy:v0.5.0 + container_name: genlayer-node-docker-proxy + environment: + CONTAINERS: 1 # GET /containers/{json,inspect,stats} — read-only via POST=0 + VERSION: 1 # required by the Docker client's API-version negotiation + NETWORKS: 1 # read-only network metadata required by Alloy Docker discovery + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + ports: + # Loopback ONLY (never 0.0.0.0) — 2375 is the unauthenticated Docker API and + # must not be exposed beyond the host. Lets a binary/systemd node reach the + # proxy at tcp://127.0.0.1:2375; container nodes use the service name instead. + - "127.0.0.1:2375:2375" + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:2375/version"] + interval: 10s + timeout: 3s + retries: 5 + start_period: 5s + security_opt: + - no-new-privileges:true + restart: unless-stopped + profiles: + - node + - monitoring genlayer-node: image: yeagerai/genlayer-node:${NODE_VERSION:-latest} - entrypoint: ["sh", "-c", "/app/bin/genlayernode run --password ${NODE_PASSWORD:-12345678}"] + # The keystore password is NOT on this command line. It reaches the node as + # GENLAYERNODE_PASSWORD (environment below), which the run command reads in + # place of --password. A command-line argument is visible in process + # listings on the host; an environment variable is not. + entrypoint: ["/app/bin/genlayernode", "run"] container_name: genlayer-node restart: unless-stopped env_file: - path: ./.env required: false + environment: + # ":?" makes Compose refuse to start when NODE_PASSWORD is unset or empty, + # before any container runs. There is no fallback: this is the password + # that encrypts the validator's signing key, and a default shared by every + # operator who forgot to set one is a key anyone can open. The release + # binary carries no default either — it is compiled out of production. + GENLAYERNODE_PASSWORD: "${NODE_PASSWORD:?NODE_PASSWORD is not set. It is the keystore password that unlocks the validator key. Set it in .env; see README section 4.}" + # WebDriver metrics collector reaches Docker via the read-only proxy + # (see docker-socket-proxy above), never the host socket directly. + DOCKER_HOST: tcp://docker-socket-proxy:2375 ports: - "${NODE_RPC_PORT:-9151}:9151" - "${NODE_OPS_PORT:-9153}:9153" @@ -752,10 +923,12 @@ services: # The trusted protocol manifest of the network; consensus.manifestpath names this path. - ${NODE_MANIFEST_PATH:-./configs/node/protocol-manifest.json}:/app/configs/node/protocol-manifest.json:ro - ${NODE_DATA_PATH:-./data}:/app/data - - ./genvm-module-web-docker.yaml:/app/third_party/genvm/config/genvm-module-web.yaml - - /var/run/docker.sock:/var/run/docker.sock:ro # required for webdriver metrics collection + # opt/genvm/current links to the image's GenVM tree, so no version variable is needed. + - ./genvm-module-web-docker.yaml:/app/opt/genvm/current/config/genvm-module-web.yaml security_opt: - no-new-privileges:true + cap_drop: + - ALL logging: driver: "json-file" options: @@ -764,31 +937,21 @@ services: compress: "true" depends_on: - webdriver-container + - docker-socket-proxy profiles: - node ``` -2. Create `genvm-module-web-docker.yaml` for Docker networking: +2. Keep `genvm-module-web-docker.yaml` next to your `docker-compose.yaml`. -The extracted tarball includes `third_party/genvm/config/genvm-module-web.yaml` configured for `localhost`, which works when running -the binary directly. For docker-compose, you need a version that points to the webdriver service name. + It ships in the release package. It is GenVM's web module config with the webdriver address set to the compose service name: -Create `genvm-module-web-docker.yaml` in the same directory as your `docker-compose.yaml`: - -- Copy the contents of `third_party/genvm/config/genvm-module-web.yaml` into `genvm-module-web-docker.yaml`. -- Modify the `webdriver_host` configuration to use the service name `webdriver-container` instead of `localhost`: - -```yaml copy -webdriver_host: http://webdriver-container:4444 -``` - -This allows the node container to reach the webdriver container using Docker's internal networking. - -The key difference: -- **Binary execution**: uses `localhost:4444` (webdriver runs on same machine) -- **Docker-compose**: uses `webdriver-container:4444` (container service name) + ```yaml + webdriver_host: http://webdriver-container:4444 + ``` + The compose file mounts it over the image's own copy at `/app/opt/genvm/current/config/genvm-module-web.yaml`, so no version variable is needed. The binary keeps using `opt/genvm//config/genvm-module-web.yaml`, which points at `localhost:4444`. 3. Set the required environment variables in a `.env` file: @@ -798,6 +961,7 @@ The key difference: ```env copy # GenLayer Node Release Configuration +# Copy this file to .env and configure for your deployment # ============================================================================= # WebDriver Configuration (required for GenVM) @@ -808,10 +972,13 @@ WEBDRIVER_PORT=4444 # Node Service Configuration (optional - use node profile) # ============================================================================= # Docker image version -NODE_VERSION=v0.4.0 +NODE_VERSION=latest -# Keystore password (used to unlock the pre-imported wallet) -NODE_PASSWORD=12345678 +# Keystore password (unlocks the pre-imported validator wallet). Required — the +# stack refuses to start without it. Choose your own; never reuse an example +# value. It reaches the node as an environment variable, not a command-line +# argument. See README section 4 for reading it in without echo. +NODE_PASSWORD= # Path to node configuration file NODE_CONFIG_PATH=./configs/node/config.yaml @@ -826,35 +993,184 @@ NODE_DATA_PATH=./data NODE_RPC_PORT=9151 NODE_OPS_PORT=9153 -# LLM API Key (required for GenVM LLM module) +# LLM API Keys (required for GenVM LLM module) +OPENROUTERKEY= HEURISTKEY= -COMPUT3KEY= -IOINTELLIGENCE_API_KEY= ANTHROPICKEY= XAIKEY= GEMINIKEY= ATOMAKEY= -CHUTES_API_KEY= -MORPHEUS_API_KEY= + +# Optional logging level override +GENLAYERNODE_LOGGING_LEVEL=INFO + +# API key of the GenLayer Chain RPC provider, for both the RPC and the +# WebSocket endpoint of the node config. Optional: leave it empty when the +# endpoints need no key. Put the key only here, never in the endpoint URLs of +# the config: a key in a URL ends up in logs and error texts. +GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY= + +# Operator bearer token for the ops server's POST /snapshot route. When +# unset, the route is default-deny (every caller gets 401). Set this to +# enable operator-driven snapshot tooling from outside the node container. +# Generate with: openssl rand -hex 32 +GENLAYERNODE_NODE_OPERATOR_TOKEN= + +# ============================================================================= +# Monitoring Configuration (optional - use monitoring profile) +# ============================================================================= + +# Where Alloy pushes metrics and logs. All six values are REQUIRED when the +# monitoring profile is up — the alloy container stops with a clear message +# when any is missing. There are no defaults on purpose: an endpoint default +# sends your data to somebody else's tenant, and a credential default gives +# everybody the same key. +# +# Examples: +# Your own local stack: +# CENTRAL_MONITORING_URL=http://localhost:9090/api/v1/write +# CENTRAL_LOKI_URL=http://localhost:3100/loki/api/v1/push +# A Grafana Cloud tenant (values from your Grafana Cloud stack page): +# CENTRAL_MONITORING_URL=https:///api/prom/push +# CENTRAL_LOKI_URL=https:///loki/api/v1/push +# +# A receiver without authentication still needs the username/password set — +# any placeholder value satisfies the start check and is ignored by the +# receiver. +CENTRAL_MONITORING_URL= +CENTRAL_LOKI_URL= + +# Metrics (Prometheus) credentials +CENTRAL_MONITORING_USERNAME= +CENTRAL_MONITORING_PASSWORD= +# Logs (Loki) credentials +CENTRAL_LOKI_USERNAME= +CENTRAL_LOKI_PASSWORD= + +# ============================================================================= +# Single Node Configuration +# ============================================================================= +# Use these variables for monitoring a single node + +# Node identification +NODE_ID=validator-001 +VALIDATOR_NAME=MyValidator +# Network label for recovery logs and alert routing; use the deployed network. +# Leave empty for an unknown label, rather than guessing a production network. +NETWORK_NAME= + +# Node metrics endpoint (optional) +# Endpoint where the node exposes its Prometheus metrics +# Default: host.docker.internal:9153 +NODE_METRICS_ENDPOINT=host.docker.internal:9153 + +# ============================================================================= +# Multi-Node Configuration +# ============================================================================= +# Alternative to single node config: provide a JSON array of targets +# When SCRAPE_TARGETS_JSON is set, it overrides NODE_ID, VALIDATOR_NAME, and NODE_METRICS_ENDPOINT +# +# Format: JSON array of target objects with required fields: +# - __address__: "host:port" of the metrics endpoint +# - instance: unique identifier (e.g., node address) +# - validator_name: human-readable name +# +# Example for multiple nodes: +# SCRAPE_TARGETS_JSON=[{"__address__":"host.docker.internal:9250","instance":"0x...","validator_name":"node-1"},{"__address__":"host.docker.internal:9251","instance":"0x...","validator_name":"node-2"}] +# +# To generate for local nodes, use: ./generate-dev-targets.sh +# SCRAPE_TARGETS_JSON= + +# ============================================================================= +# Log Collection Configuration +# ============================================================================= +# Local log directory (on host machine) +# Path where GenLayer node(s) write their log files +# Single node: ./data/node/logs +# Multi-node: ./data/node/default (parent directory containing all node subdirs) +NODE_LOGS_PATH=./data/node/logs + +# Log file pattern (inside container, mapped from NODE_LOGS_PATH) +# Single node: /var/log/genlayer/node.log +# Multi-node: /var/log/genlayer/*/logs/node.log +# Default: /var/log/genlayer/node*.log (matches both patterns) +LOG_FILE_PATTERN=/var/log/genlayer/node*.log + +# Metrics scraping intervals (optional) +# How often to scrape metrics from the node +# Default: 15s for node metrics, 10s timeout +METRICS_SCRAPE_INTERVAL=15s +METRICS_SCRAPE_TIMEOUT=10s + +# Alloy internal health monitoring interval (optional) +# How often Alloy checks its own internal health metrics +# These metrics are not forwarded, only used for local debugging +# Default: 60s +ALLOY_SELF_MONITORING_INTERVAL=60s + +# Loki log batching configuration (optional) +# Controls how logs are batched before sending to central Loki +# Default: 1MiB batch size, 1s wait time +LOKI_BATCH_SIZE=1MiB +LOKI_BATCH_WAIT=1s + +# ============================================================================= +# Deployment Instructions +# ============================================================================= +# +# 1. Basic deployment (WebDriver only): +# docker compose up -d +# +# 2. Node deployment (WebDriver + Node): +# Configure node settings above, then: +# docker compose --profile node up -d +# +# 3. Node with monitoring (WebDriver + Node + Alloy): +# Configure node and monitoring settings, then: +# docker compose --profile node --profile monitoring up -d +# +# 4. Monitoring only (WebDriver + Alloy): +# Configure monitoring settings, then: +# docker compose --profile monitoring up -d +# +# 5. Check status: +# docker compose ps +# docker compose logs genlayer-node +# docker compose logs alloy +# +# ============================================================================= +# Configuration Notes +# ============================================================================= +# +# - Grafana Alloy handles both metrics and logs in a single service +# - To customize monitoring, edit alloy-config.river +# - To filter metrics (reduce bandwidth), uncomment the filtering rule in alloy-config.river +# - Ensure firewall allows outbound connections to monitoring server (port 8180) +# + +# Keep health checks but disable automatic container-local recycling when false. +HEALTHCHECK_AUTO_RESTART=true ``` -or simply use the provided `docker-compose.yaml`, `.env.example` and the `genvm-module-web-docker.yaml` from the extracted tarball. +This is the release package's `.env.example`: copy it to `.env` next to `docker-compose.yaml`. The release package also ships `docker-compose.yaml` and `genvm-module-web-docker.yaml`. Set at least: + +- `NODE_PASSWORD`: the keystore password. It is required: Docker Compose refuses to start without it, and there is no default. +- `NODE_VERSION`: pin the version you run. `latest` is the newest stable release. +- the API key of at least one LLM provider. +- `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY`, when your chain RPC provider needs a key (the Bradbury WebSocket does). 4. Checking Your Configuration -To ensure your node is correctly configured, you can run the following command: +Before the first start, run `doctor` in the node container, with the same config, manifest, environment and network as the node: ```sh copy -source .env && docker run --rm --env-file ./.env \ - -v ${NODE_CONFIG_PATH:-./configs/node/config.yaml}:/app/configs/node/config.yaml \ - yeagerai/genlayer-node:${NODE_VERSION:-v0.4.0} \ - ./bin/genlayernode doctor +docker compose --profile node run --rm --entrypoint /app/bin/genlayernode genlayer-node doctor ``` 5. Start the services using Docker Compose: ```sh copy -source .env && docker compose --profile node up -d +docker compose --profile node up -d ``` ### Telemetry From 3a083a094810f4f2db18822667886ea71ae1a2e4 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 17:41:27 +0200 Subject: [PATCH 14/39] docs(validators): fix hand-written v0.6 instructions that contradict the node Checked against genlayer-node ed771d161 (and genvm-manager v0.6.0-rc10). Setup guide: - Verify the download (cosign + SHA256SUMS, v0.6.0 and later), install.sh. - Extract with --strip-components=1: the tarball has one top-level directory, and the old backticked tar/cd commands never reached bin/. - GenVM under opt/genvm/; check genvm.root_dir after a manual extract. - GENLAYERNODE_PASSWORD instead of --password; real account import output. - Env overrides: v0.6 consensus keys, environment-only variables, and the rule that only keys present in the file are picked up. - genvm.start_manager instead of the removed genvm.manage_modules. - LLM key names (IOINTELLIGENCEKEY; release-config-only keys marked). - v0.6 doctor checks; WebDriver metrics through docker-socket-proxy and DOCKER_HOST. - Docker prerequisites: config, manifest, chown 10001 ./data. - Telemetry points to the monitoring guide instead of a second .env copy. GenVM configuration: default policy is a meta.priority chain, hot-reload restarts Llm, key table per config file, permits as a budget, troubleshooting. Monitoring: use the release compose as is (alloy needs docker-socket-proxy), no hard-coded push URLs, ops endpoints always registered, /snapshot token, container health checks. Upgrade: what to delete for a clean resync in v0.6; v0.5 -> v0.6 notes. Node API overview: zks_ prefix, gen_call status parameter, debug and zksync groups off by default, eth_sendRawTransaction. Chutes: GenVM does not read CHUTES_API_KEY; how to add it as a backend. Co-Authored-By: Claude Opus 5.5 --- .../api-references/genlayer-node/content.mdx | 22 ++- pages/api-references/genlayer-node.mdx | 22 ++- pages/partners/chutes.mdx | 27 +++ pages/validators/genvm-configuration.mdx | 90 ++++++--- pages/validators/monitoring.mdx | 49 +++-- pages/validators/setup-guide.mdx | 171 +++++++++++------- pages/validators/upgrade.mdx | 16 +- 7 files changed, 278 insertions(+), 119 deletions(-) diff --git a/content/api-references/genlayer-node/content.mdx b/content/api-references/genlayer-node/content.mdx index 9d641012..b57b37eb 100644 --- a/content/api-references/genlayer-node/content.mdx +++ b/content/api-references/genlayer-node/content.mdx @@ -10,6 +10,16 @@ ${genMethods.join('\n\n')} These methods are available for debugging and testing purposes during development. +Debug methods are off by default. To enable them, set `genlayer_debug.enabled: true` under `node.rpc.endpoints` in `config.yaml`. Enable them only on an RPC port that untrusted callers cannot reach: + +```yaml +node: + rpc: + endpoints: + genlayer_debug: + enabled: true +``` + ${debugMethods.join('\n\n')} ## Ethereum Compatibility @@ -20,20 +30,22 @@ The GenLayer Node also supports Ethereum-compatible methods that are proxied to - `eth_blockNumber` - `eth_getBalance` -- `eth_sendTransaction` +- `eth_sendRawTransaction` - `eth_call` - And other standard Ethereum JSON-RPC methods ## zkSync Compatibility -[zkSync-compatible](https://docs.zksync.io/zksync-protocol/api/zks-rpc) methods are also supported and proxied to the underlying infrastructure. These methods are prefixed with `zksync_`. +[zkSync-compatible](https://docs.zksync.io/zksync-protocol/api/zks-rpc) methods are also supported and proxied to the underlying infrastructure. These methods are prefixed with `zks_`. + +The default `config.yaml` turns the `zksync` group off (`node.rpc.endpoints.zksync.enabled: false`). Set it to `true` to forward `zks_*` calls. ## Usage Examples ### cURL ```bash -# Test connectivity +# Test connectivity (debug method, needs genlayer_debug enabled) curl -X POST http://localhost:9151 \ -H "Content-Type: application/json" \ -d '{ @@ -54,7 +66,7 @@ curl -X POST http://localhost:9151 \ "to": "0x742d35Cc6634C0532925a3b8D4C9db96c4b4d8b6", "data": "0x70a08231000000000000000000000000742d35cc6634c0532925a3b8d4c9db96c4b4d8b6", "type": "read", - "transaction_hash_variant": "latest-nonfinal" + "status": "decided" }], "id": 1 }' @@ -71,7 +83,7 @@ curl -X POST http://localhost:9151 \ "id": 1 }' -# Get debug trie information +# Get debug trie information (debug method, needs genlayer_debug enabled) curl -X POST http://localhost:9151 \ -H "Content-Type: application/json" \ -d '{ diff --git a/pages/api-references/genlayer-node.mdx b/pages/api-references/genlayer-node.mdx index 1f3bb132..409af926 100644 --- a/pages/api-references/genlayer-node.mdx +++ b/pages/api-references/genlayer-node.mdx @@ -1473,6 +1473,16 @@ wscat -c ws://localhost:9151/ws These methods are available for debugging and testing purposes during development. +Debug methods are off by default. To enable them, set `genlayer_debug.enabled: true` under `node.rpc.endpoints` in `config.yaml`. Enable them only on an RPC port that untrusted callers cannot reach: + +```yaml +node: + rpc: + endpoints: + genlayer_debug: + enabled: true +``` + ### gen_dbg_ping Simple connectivity test method that returns a "pong" response. @@ -1678,20 +1688,22 @@ The GenLayer Node also supports Ethereum-compatible methods that are proxied to - `eth_blockNumber` - `eth_getBalance` -- `eth_sendTransaction` +- `eth_sendRawTransaction` - `eth_call` - And other standard Ethereum JSON-RPC methods ## zkSync Compatibility -[zkSync-compatible](https://docs.zksync.io/zksync-protocol/api/zks-rpc) methods are also supported and proxied to the underlying infrastructure. These methods are prefixed with `zksync_`. +[zkSync-compatible](https://docs.zksync.io/zksync-protocol/api/zks-rpc) methods are also supported and proxied to the underlying infrastructure. These methods are prefixed with `zks_`. + +The default `config.yaml` turns the `zksync` group off (`node.rpc.endpoints.zksync.enabled: false`). Set it to `true` to forward `zks_*` calls. ## Usage Examples ### cURL ```bash -# Test connectivity +# Test connectivity (debug method, needs genlayer_debug enabled) curl -X POST http://localhost:9151 \ -H "Content-Type: application/json" \ -d '{ @@ -1712,7 +1724,7 @@ curl -X POST http://localhost:9151 \ "to": "0x742d35Cc6634C0532925a3b8D4C9db96c4b4d8b6", "data": "0x70a08231000000000000000000000000742d35cc6634c0532925a3b8d4c9db96c4b4d8b6", "type": "read", - "transaction_hash_variant": "latest-nonfinal" + "status": "decided" }], "id": 1 }' @@ -1729,7 +1741,7 @@ curl -X POST http://localhost:9151 \ "id": 1 }' -# Get debug trie information +# Get debug trie information (debug method, needs genlayer_debug enabled) curl -X POST http://localhost:9151 \ -H "Content-Type: application/json" \ -d '{ diff --git a/pages/partners/chutes.mdx b/pages/partners/chutes.mdx index f1afeece..c198237f 100644 --- a/pages/partners/chutes.mdx +++ b/pages/partners/chutes.mdx @@ -169,6 +169,33 @@ For validator deployments: export CHUTES_API_KEY=your_api_key_here ``` +This variable is for your own API calls, such as the curl examples above. GenVM does not read it out of the box: Chutes is not a built-in backend in the GenVM LLM configs that ship with the node. + +--- + +## Use Chutes as a GenVM LLM Backend + +To let your validator's GenVM send prompts to Chutes, add it as an `openai-compatible` backend in `genvm-module-llm.yaml`, reading the key from the variable above: + +```yaml +backends: + chutes: + enabled: true + host: https://llm.chutes.ai # GenVM adds /v1/chat/completions + provider: openai-compatible + key: ${ENV[CHUTES_API_KEY]} + models: + model_team/model_name: # a model from https://llm.chutes.ai/v1/models + supports_json: true # set both flags to match the model + supports_image: false +``` + +Then: +- Put `CHUTES_API_KEY` in the node's environment (with Docker, in `.env`) and restart the node. A running node does not see new environment variables. +- Run `./bin/genlayernode doctor` to check that the backend answers. + +See [GenVM Configuration](/validators/genvm-configuration) for the backend format and how the node orders backends. + --- ## Next Steps diff --git a/pages/validators/genvm-configuration.mdx b/pages/validators/genvm-configuration.mdx index 47c9c469..c13e76ce 100644 --- a/pages/validators/genvm-configuration.mdx +++ b/pages/validators/genvm-configuration.mdx @@ -39,10 +39,10 @@ curl -X POST "http://127.0.0.1:${PORT}/module/stop" \ curl -X POST "http://127.0.0.1:${PORT}/module/start" \ -H 'Content-Type: application/json' \ - -d '{"module_type": "Web", "config": null}' + -d '{"module_type": "Llm", "config": null}' ``` -Repeat for each GenVM instance if running multiple. For a full restart: `sudo systemctl restart genlayer-node`. +Repeat for each GenVM instance if running multiple. A module reload picks up YAML and Lua changes only. A new or changed API key in `.env` needs a full node restart, because a running process cannot see environment changes. For a full restart: `sudo systemctl restart genlayer-node`. ## Configuration Files @@ -52,7 +52,7 @@ All configuration lives in the GenVM tree of the release, at `opt/genvm/ Enabled backends without a configured API key will log warnings. Either set the key or disable the backend with `enabled: false`. @@ -194,13 +209,29 @@ Greyboxing encompasses several layers of configuration, all implemented in the L GenVM ships with two Lua scripts. Switch between them by changing `lua_script_path` in `genvm-module-llm.yaml`. -#### Default: Random selection +#### Default: Priority chain **Script:** `genvm-llm-default.lua` -Iterates through all enabled backends that match the required capabilities (JSON support, image support) and tries them in arbitrary order. Falls back to the next provider on overload errors (429, 503, 408, 504, 529). +Builds one chain over every enabled model, in a fixed order: + +1. Providers by `meta.priority`, highest first. A provider without it counts as `0`. Ties go by provider name, in reverse alphabetical order. +2. Within a provider, models by name, in alphabetical order. + +For each prompt, models without a needed capability (`supports_json`, `supports_image`) are skipped and the rest are tried in order. Any failure moves on to the next model, not only overload errors. If every candidate fails, the prompt fails with `NO_PROVIDER_FOR_PROMPT`. -This is the simplest strategy — works out of the box with no tuning. +Set the order with `meta.priority` on the backend: + +```yaml +backends: + my-provider: + meta: + priority: 10 # higher is tried first +``` + +`meta.priority` runs in the opposite direction to the `meta.greybox` numbers of the ordered fallback chain below: for priority, higher goes first; for greybox, lower goes first. + +This strategy works out of the box with no tuning. It is not curated: it uses every enabled model in one chain. #### Ordered fallback chain @@ -346,10 +377,17 @@ The Lua script (`genvm-web-default.lua`) implements two functions: ## Advanced: GenVM Manager -The manager (`genvm-manager.yaml`) controls concurrency. The main setting is `permits` — max concurrent GenVM executions. +The manager (`genvm-manager.yaml`) controls concurrency through a pool of permits. The node sets that pool on startup from `genvm.permits` in its `config.yaml`: + +```yaml +genvm: + permits: 32 # Leave empty for autodiscovery +``` + +`permits` is a permit budget, not an execution count. Each execution costs permits, and one nondeterministic execution costs 8, so `32` admits 4 nondeterministic executions at a time. Leaving it empty is preferable: autodiscovery sizes the budget from the host. `sync_permits` uses the same unit; a sync execution costs 4. - On macOS (native), auto-detection is not supported. Set `permits` manually (e.g., `32`). + On macOS (native), auto-detection is not supported: GenVM falls back to a fixed default. Set `genvm.permits` manually (e.g., `32`). ## Troubleshooting @@ -358,6 +396,12 @@ The manager (`genvm-manager.yaml`) controls concurrency. The main setting is `pe **"module_failed_to_start":** Check that the Lua script exists at the path in `lua_script_path`, API keys are set, and YAML syntax is valid. -**"NO_PROVIDER_FOR_PROMPT":** No enabled backend matches the request's capabilities. Ensure at least one has `supports_json: true` (and `supports_image: true` for image prompts). +**"NO_PROVIDER_FOR_PROMPT":** No model could answer the prompt. Either no enabled model has the capability it needs (ensure at least one has `supports_json: true`, and `supports_image: true` for image prompts), or every candidate failed. + +**All models exhausted:** Every candidate failed. Check API key validity and provider status pages. + +**Check which backends actually answer:** Run `./bin/genlayernode doctor`. Its LLM check probes each enabled backend live, rather than only checking that a key variable is set. + +**Configure more than one provider.** A backend only enters the chain if its key is set. With a single provider there is no fallback: when it is down or out of credit, the chain is exhausted at once. The node keeps running, but it votes Timeout on every transaction that needs an LLM, and Timeout votes are penalized. `doctor` warns when only one backend is usable. -**All models exhausted:** All providers returned overload errors. Check API key validity and provider status pages. Add more backends as fallback. +**Restart vs. reload:** A key added or changed in `.env` needs a node restart. Edits to `genvm-module-llm.yaml` or the Lua script need only an LLM module reload (see [Hot-Reload](#hot-reload)). diff --git a/pages/validators/monitoring.mdx b/pages/validators/monitoring.mdx index 7e4345c4..3c1da34f 100644 --- a/pages/validators/monitoring.mdx +++ b/pages/validators/monitoring.mdx @@ -9,16 +9,16 @@ Monitoring and telemetry for GenLayer validators means collecting validator perf ## Accessing Metrics -The metrics endpoint is exposed on the operations port (default: 9153) configured in your `config.yaml`: +The metrics endpoint is served by the ops server, on the port set in your `config.yaml` (default: 9153): ```yaml node: ops: - port: 9153 # Metrics port - endpoints: - metrics: true # Enable metrics endpoint + port: 9153 # Ops server port (/health, /metrics, /balance, /snapshot) ``` +The ops server always registers `/metrics`, `/health`, `/balance` and `/snapshot`. The `endpoints.metrics` and `endpoints.health` keys in the config template do not turn them on or off; they are kept for compatibility, and `doctor` reads `health` to decide whether to run its HTTP health probe. + Once your node is running, you can access the metrics at: ``` http://localhost:9153/metrics @@ -64,7 +64,7 @@ curl -s http://localhost:9153/metrics | grep genlayer_node_ ``` - The metrics endpoint also provides `/health` and `/balance` endpoints on the same port for additional monitoring capabilities. + The ops port also serves `/health` and `/balance` for additional monitoring, and `POST /snapshot` for operator snapshots. `/snapshot` is default-deny: every caller gets 401 unless `GENLAYERNODE_NODE_OPERATOR_TOKEN` is set in the node's environment, and callers must send that token as a bearer token. ## Monitoring Best Practices @@ -92,7 +92,7 @@ To contribute your node's metrics and logs to the centralized GenLayer Foundatio - Setup takes 10–15 minutes once credentials are provided. **Prerequisites** -- Metrics enabled in `config.yaml` (`endpoints.metrics: true` — default in recent versions). +- The node's ops server running (it always serves `/metrics`; no config switch is needed). - Ops port 9153 exposed in docker-compose (`ports: - "9153:9153"`). - Credentials from the Foundation team (ask in #bradbury): - `CENTRAL_MONITORING_URL` — Prometheus remote write URL @@ -102,33 +102,38 @@ To contribute your node's metrics and logs to the centralized GenLayer Foundatio **Steps** -1. **Create or update .env** (next to your docker-compose.yaml): +1. **Create or update .env** (next to your docker-compose.yaml). Fill in the values the Foundation team gave you. All six `CENTRAL_*` values are required: Alloy stops at start-up and names the missing ones if any is empty. ```env -# Central monitoring server endpoints for GenLayer Foundation -CENTRAL_MONITORING_URL=https://prometheus-prod-66-prod-us-east-3.grafana.net/api/prom/push -CENTRAL_LOKI_URL=https://logs-prod-042.grafana.net/loki/api/v1/push +# Central monitoring endpoints, from the Foundation team (ask in #bradbury) +CENTRAL_MONITORING_URL= +CENTRAL_LOKI_URL= # Authentication for central monitoring # Metrics (Prometheus) credentials -CENTRAL_MONITORING_USERNAME=your-metrics-username -CENTRAL_MONITORING_PASSWORD=your-metrics-password +CENTRAL_MONITORING_USERNAME= +CENTRAL_MONITORING_PASSWORD= # Logs (Loki) credentials -CENTRAL_LOKI_USERNAME=your-logs-username -CENTRAL_LOKI_PASSWORD=your-logs-password +CENTRAL_LOKI_USERNAME= +CENTRAL_LOKI_PASSWORD= # Node identification NODE_ID=validator-001 VALIDATOR_NAME=MyValidator +# Network label for recovery logs and alert routing NETWORK_NAME=bradbury-phase2 # Usually defaults are fine NODE_METRICS_ENDPOINT=host.docker.internal:9153 LOG_FILE_PATTERN=/var/log/genlayer/node*.log METRICS_SCRAPE_INTERVAL=15s +# Host directory where the node writes its log files (mounted into Alloy) +NODE_LOGS_PATH=./data/node/logs +# false keeps the container health checks but turns off automatic restarts +HEALTHCHECK_AUTO_RESTART=true ``` -2. **Add or verify the Alloy service in docker-compose.yaml** (copy if missing): +2. **Add or verify the Alloy service in docker-compose.yaml.** Use the docker-compose.yaml from the release package as it is. It already defines the alloy service under the monitoring profile, together with the docker-socket-proxy service that alloy depends on. Alloy also mounts alloy-healthcheck.sh from the same package. Copying only the alloy block into another compose file does not work: it will not start without docker-socket-proxy and alloy-healthcheck.sh. The block is shown here for reference: ```yaml # Grafana Alloy for both logs and metrics forwarding @@ -502,6 +507,20 @@ Look for messages indicating successful batch sending (no error codes like 401, `instance="${NODE_ID}"` or `validator_name="${VALIDATOR_NAME}"` (example: `genlayer_node_uptime_seconds{instance="0xYourID"}`). +## Container Health Checks + +The release docker-compose.yaml gives the webdriver and Alloy containers health checks. When a check keeps failing, the container stops its own process and Docker restarts it (`restart: unless-stopped`). A container you stop by hand stays stopped. Each restart attempt is written to the container's logs; with monitoring on, Alloy forwards these lines with `component="health-recovery"`. + +To keep the health checks but turn off the automatic restarts, set `HEALTHCHECK_AUTO_RESTART=false` in `.env` and recreate the services. + +Check container health and recovery events: + +```bash copy +docker inspect --format '{{.State.Health.Status}}' genlayer-node-webdriver +docker inspect --format '{{.State.Health.Status}}' genlayer-node-alloy +docker compose logs --since 30m webdriver-container alloy +``` + ## Troubleshooting ### Using the Alloy UI diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index d3c942f3..327bd55a 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -230,18 +230,40 @@ One full node can be shared between multiple validators. The optimal validator-t export version=v0.5.14 # set your desired version here wget https://storage.googleapis.com/gh-af/genlayer-node/bin/amd64/${version}/genlayer-node-linux-amd64-${version}.tar.gz ``` -3. Extract the node software +3. Verify the download (v0.6.0 and later) + + Each release from v0.6.0 on publishes a signed checksum file next to the tarball. Verify it before you extract: extraction feeds the GenVM setup script, so a tampered tarball must be caught here. You need [cosign](https://docs.sigstore.dev/cosign/system_config/installation/). + ```sh copy - mkdir -p ${version} - tar -xzvf `genlayer-node-linux-amd64-${version}.tar.gz` -C `./${version}` + base=https://storage.googleapis.com/gh-af/genlayer-node/bin/amd64/${version} + wget ${base}/genlayer-node_${version}_SHA256SUMS ${base}/genlayer-node_${version}_SHA256SUMS.sigstore.json + + # 1. The checksum file was produced by GenLayer's release workflow. + cosign verify-blob \ + --bundle "genlayer-node_${version}_SHA256SUMS.sigstore.json" \ + --certificate-identity-regexp '^https://github\.com/genlayerlabs/genlayer-node/\.github/workflows/release-package\.yml@' \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com \ + "genlayer-node_${version}_SHA256SUMS" + + # 2. The tarball matches the checksum file. + sha256sum -c "genlayer-node_${version}_SHA256SUMS" --ignore-missing ``` -4. Change the directory + + If either command fails, do not extract: delete the download and fetch it again. Releases before v0.6.0 have no checksum file. + + The same releases also publish `install.sh`, covered by the same checksum file. After verifying, `./install.sh --install-dir /opt/genlayer-node/${version} --tarball genlayer-node-linux-amd64-${version}.tar.gz` replaces steps 4 and 5, and also points `genvm.root_dir` at the GenVM version it unpacked. + +4. Extract the node software + + The tarball has one top-level directory, `genlayer-node-linux-amd64/`; `--strip-components=1` puts its contents straight into `./${version}`. ```sh copy - cd `./${version}` + mkdir -p ${version} + tar -xzf genlayer-node-linux-amd64-${version}.tar.gz -C ./${version} --strip-components=1 + cd ./${version} ``` -5. Run Genvm setup +5. Run the GenVM setup This step needs Python 3.10 or newer, available as `python3` on your `PATH`, with `pip` and `venv` (on Debian/Ubuntu: `python3-pip` and `python3-venv`). Check with `python3 --version`. @@ -270,6 +292,8 @@ The file needs to be located at `configs/node/config.yaml` You can use the following example configuration. **Note:** For most users, you will only need to replace the `rollup` and `consensus` sections with the values for your target network (see [Network-Specific Consensus Configuration](#network-specific-consensus-configuration) below). +Also check that `genvm.root_dir` names the GenVM directory the release ships: run `ls opt/genvm` in the install directory. `install.sh` sets it for you; after a manual extract, set it yourself. + ```yaml copy # rollup configuration rollup: @@ -592,7 +616,7 @@ The node sends the key in the `X-AccessKey` header on both the RPC and WebSocket #### Overriding Configuration with Environment Variables -Any configuration value in `config.yaml` can be overridden using environment variables with the prefix `GENLAYERNODE_`. +Any configuration value in `config.yaml` can be overridden using environment variables with the prefix `GENLAYERNODE_`. Only keys that exist in the file are picked up: a key that is commented out, such as `consensus.genesis` in the template, must be uncommented before its variable has an effect. **Pattern:** @@ -606,9 +630,9 @@ Any configuration value in `config.yaml` can be overridden using environment var |------------|---------------------| | `rollup.genlayerchainrpcurl` | `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCURL` | | `rollup.genlayerchainwebsocketurl` | `GENLAYERNODE_ROLLUP_GENLAYERCHAINWEBSOCKETURL` | -| (secret, environment only) | `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY` | -| `consensus.contractmainaddress` | `GENLAYERNODE_CONSENSUS_CONTRACTMAINADDRESS` | -| `consensus.contractdataaddress` | `GENLAYERNODE_CONSENSUS_CONTRACTDATAADDRESS` | +| `consensus.consensusaddress` | `GENLAYERNODE_CONSENSUS_CONSENSUSADDRESS` | +| `consensus.chainid` | `GENLAYERNODE_CONSENSUS_CHAINID` | +| `consensus.manifestpath` | `GENLAYERNODE_CONSENSUS_MANIFESTPATH` | | `consensus.genesis` | `GENLAYERNODE_CONSENSUS_GENESIS` | | `node.mode` | `GENLAYERNODE_NODE_MODE` | | `node.validatorWalletAddress` | `GENLAYERNODE_NODE_VALIDATORWALLETADDRESS` | @@ -616,11 +640,20 @@ Any configuration value in `config.yaml` can be overridden using environment var | `node.rpc.port` | `GENLAYERNODE_NODE_RPC_PORT` | | `logging.level` | `GENLAYERNODE_LOGGING_LEVEL` | +These have no key in `config.yaml` and are read from the environment only: + +| Variable | Purpose | +|----------|---------| +| `GENLAYERNODE_PASSWORD` | Keystore password. Use it instead of `--password`, which is visible in process listings and shell history. | +| `GENLAYERNODE_CONFIG_PATH` | Path to the config file (default `configs/node/config.yaml`). | +| `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY` | API key of the chain RPC provider. Never put it in the endpoint URLs. | +| `GENLAYERNODE_NODE_OPERATOR_TOKEN` | Bearer token for the ops server's `POST /snapshot` route. Without it the route refuses every caller. Generate one with `openssl rand -hex 32`. | + **Usage example:** ```sh copy # Override the RPC port -export GENLAYERNODE_CONSENSUS_CONTRACTMAINADDRESS="0x..." +export GENLAYERNODE_NODE_RPC_PORT="9151" # Set validator wallet address export GENLAYERNODE_NODE_VALIDATORWALLETADDRESS="0x..." @@ -639,13 +672,20 @@ See the [GenVM Configuration](/validators/genvm-configuration) page for detailed The operator key is used by your node to sign blocks and perform validator duties. +The account commands read the node's keystore password from `GENLAYERNODE_PASSWORD`. Set it once per shell, without echoing it. Do not pass it with `--password`: command-line arguments can be visible in process listings and shell history. + +```sh copy +read -rsp "Keystore password: " GENLAYERNODE_PASSWORD +export GENLAYERNODE_PASSWORD +printf '\n' +``` + #### Option 1: Import from CLI Wizard (Recommended) If you used `genlayer staking wizard`, it exported an operator keystore file. Transfer this file to your validator server and import it: ```sh copy ./bin/genlayernode account import \ - --password "your node password" \ --passphrase "password you set when exporting from wizard" \ --path "/path/to/operator-keystore.json" \ -c $(pwd)/configs/node/config.yaml \ @@ -655,9 +695,9 @@ If you used `genlayer staking wizard`, it exported an operator keystore file. Tr You should see: ```sh -Account imported: - Address: 0xA0b12Fd2f3F7e86fEC458D114A5E7a6f571160a8 - Account setup as a validator +Operator account imported: + Operator address: 0xA0b12Fd2f3F7e86fEC458D114A5E7a6f571160a8 + Operator address has been set in config.yaml ``` #### Option 2: Generate New Operator Key @@ -665,7 +705,7 @@ Account imported: You can also generate a new operator key directly on the server: ```sh copy -./bin/genlayernode account new -c $(pwd)/configs/node/config.yaml --setup --password "your secret password" +./bin/genlayernode account new -c $(pwd)/configs/node/config.yaml --setup ``` Then use this address when running the wizard on your local machine. @@ -676,7 +716,6 @@ To restore from a backup (e.g., after migrating to a new server): ```sh copy ./bin/genlayernode account import \ - --password "your node password" \ --passphrase "your backup encryption passphrase" \ --path "/path/to/your/secure/backup.key" \ -c $(pwd)/configs/node/config.yaml \ @@ -693,7 +732,6 @@ After setting up your operator key, back it up securely: ```sh copy ./bin/genlayernode account export \ - --password "your node password" \ --address "your operator address" \ --passphrase "your backup encryption passphrase" \ --path "/path/to/your/secure/backup.key" \ @@ -723,25 +761,22 @@ Once you have configured everything, you are ready to start the node. 1. Set the LLM Provider API Key - Set the appropriate environment variable for your chosen LLM provider. See the [GenVM Configuration](/validators/genvm-configuration) page for details on LLM providers and obtaining API credits. + Set the environment variable of at least one LLM provider. See the [GenVM Configuration](/validators/genvm-configuration) page for details on LLM providers and obtaining API credits. ```sh copy - # For Heurist + # Read by the default GenVM LLM config export HEURISTKEY='your_heurist_api_key' + export ANTHROPICKEY='your_anthropic_api_key' + export OPENAIKEY='your_openai_api_key' + export XAIKEY='your_xai_api_key' + export GEMINIKEY='your_gemini_api_key' - # For Comput3 + # Read only by the release LLM config (genvm-modules-llm-release.yaml, used by greyboxing) + export OPENROUTERKEY='your_openrouter_api_key' export COMPUT3KEY='your_comput3_api_key' - - # For io.net - export IOINTELLIGENCE_API_KEY='your_ionet_api_key' - - # For Chutes - export CHUTES_API_KEY='your_chutes_api_key' - - # For Morpheus + export IOINTELLIGENCEKEY='your_ionet_api_key' export MORPHEUS_API_KEY='your_morpheus_api_key' - - # For other providers, use the appropriate environment variable name + export LIBERTAI_API_KEY='your_libertai_api_key' ``` 2. Run the WebDriver container @@ -750,37 +785,41 @@ Once you have configured everything, you are ready to start the node. docker compose up -d # Starts the WebDriver needed by the GenVM web module ``` -3. (Optional) Run two services (modules) in background (this is a crucial step for running _Intelligent_ contracts). This can be done automatically or manually. - - To start them automatically in node configuration set `genvm.manage_modules` to `true` - - To start them manually run - ```bash copy - ./opt/genvm/${GENVM_VERSION}/bin/genvm-modules web & ./opt/genvm/${GENVM_VERSION}/bin/genvm-modules llm & - ``` + The node starts the GenVM manager and its modules itself (`genvm.start_manager: true` in the config template). - - Note: If you are using the default configuration, `genvm.manage_modules` is - set to `true` by default, meaning the node will manage these modules - automatically. - + **WebDriver resource metrics (optional).** To report the WebDriver container's CPU, memory and traffic as `genlayer_node_*{component="webdriver"}` series, also start the read-only Docker API proxy, and give only the node process `DOCKER_HOST=tcp://127.0.0.1:2375` (see step 4): -4. Checking Your Configuration + ```sh copy + docker compose up -d docker-socket-proxy + ``` + + Run the node as a user that is not in the `docker` group, and do not `export DOCKER_HOST` in a shell you also use for `docker` commands: they would reach the read-only proxy and fail. Without `DOCKER_HOST` the node skips these metrics. - To ensure your node is correctly configured, you can run the following command: +3. Checking Your Configuration + + Run `doctor` before the first start, from the install directory, with the same environment the node will run with: ```sh copy ./bin/genlayernode doctor ``` - The `doctor` command now includes comprehensive GenVM diagnostics integration to validate: - - Consensus contract configuration and accessibility - - GenVM module connectivity and health status - - LLM provider configuration and API connectivity - - Network configuration and ZKSync node accessibility + It checks, in order: + - GenLayer Chain connectivity + - the consensus deployment: it reads the on-chain contracts and checks them against the trusted protocol manifest at `consensus.manifestpath` + - the GenVM configuration: binaries, module config, the LLM provider keys and a live call to each enabled provider, and the WebDriver + +4. Run the node -5. Run the node + With `GENLAYERNODE_PASSWORD` set (see [Import the Operator Key](#import-the-operator-key)): ```sh copy - ./bin/genlayernode run -c $(pwd)/configs/node/config.yaml --password "your secret password" # The same password you used when creating the account + ./bin/genlayernode run -c $(pwd)/configs/node/config.yaml + ``` + + With WebDriver resource metrics: + + ```sh copy + DOCKER_HOST=tcp://127.0.0.1:2375 ./bin/genlayernode run -c $(pwd)/configs/node/config.yaml ``` @@ -795,6 +834,16 @@ Once you have configured everything, you are ready to start the node. You can also run the GenLayer node using Docker and Docker Compose. +Before the first start, in the directory with your `docker-compose.yaml`: + +- `configs/node/config.yaml` holds your network's `rollup` and `consensus` values (see [Configuration](#configuration)). +- `configs/node/protocol-manifest.json` is your network's trusted manifest (see [Network-Specific Consensus Configuration](#network-specific-consensus-configuration)). +- The node container runs as uid `10001`, and the bind-mounted `./data` keeps its host owner. Let that user write to it once: + + ```sh copy + mkdir -p ./data && sudo chown -R 10001:10001 ./data + ``` + 1. Create a `docker-compose.yaml` file with the following content: ```yaml copy @@ -1177,25 +1226,7 @@ docker compose --profile node up -d For a quick setup using Docker Compose: -1. **Configure your `.env` file** with the required variables for monitoring: - - ```env copy - # Central monitoring server endpoints for GenLayer Foundation - CENTRAL_MONITORING_URL=https://prometheus-prod-66-prod-us-east-3.grafana.net/api/prom/push - CENTRAL_LOKI_URL=https://logs-prod-042.grafana.net/loki/api/v1/push - - # Authentication for central monitoring - # Metrics (Prometheus) credentials - CENTRAL_MONITORING_USERNAME=your-metrics-username - CENTRAL_MONITORING_PASSWORD=your-metrics-password - # Logs (Loki) credentials - CENTRAL_LOKI_USERNAME=your-logs-username - CENTRAL_LOKI_PASSWORD=your-logs-password - - # Node identification - NODE_ID=validator-001 - VALIDATOR_NAME=MyValidator - ``` +1. **Set the monitoring variables in your `.env` file.** The `.env` example above lists them: the six `CENTRAL_*` endpoint and credential values, `NODE_ID`, `VALIDATOR_NAME` and `NETWORK_NAME`. All six `CENTRAL_*` values are required: Alloy stops with a message when one is missing. Get the endpoints and credentials from the Foundation team, and see the [Monitoring Guide](/validators/monitoring) for the values to use. 2. **Run docker compose**: ```bash copy diff --git a/pages/validators/upgrade.mdx b/pages/validators/upgrade.mdx index 0802f772..76d6b79f 100644 --- a/pages/validators/upgrade.mdx +++ b/pages/validators/upgrade.mdx @@ -43,7 +43,7 @@ Breaking changes involve a new consensus deployment. The old database is incompa 3. **Download and extract into a fresh folder** — See [download instructions](/validators/setup-guide#download-the-node-software). 4. **Configure from scratch** — Create `config.yaml` and `.env` using the new example files as a base. **Do not copy your old config** — the schema may have changed. Refer to the [configuration section](/validators/setup-guide#configuration) for the current format. 5. **Import your operator key** — Copy your backed-up keystore into `data/node/keystore/`, or re-import using the CLI. See [restoring your operator key](/validators/setup-guide#restoring-your-operator-key). -6. **Do NOT copy the old database** — Delete `data/node/genlayer.db` if you accidentally copied the full `data/` folder. The node will create a fresh database and sync from genesis. +6. **Do NOT copy the old database** — If you copied the full `data/` folder, delete everything in `data/node/` except `keystore/`: the stores `genvm.db` and `ops.db`, the Merkle state in `merkle/`, and the pre-v0.6 `genlayer.db` if present. If you set custom `merkleforest` or `merkletree` paths, delete those too. The node creates fresh stores and syncs from genesis. 7. **Validate** — Run `./bin/genlayernode doctor` and ensure all checks pass. 8. **Stop the old node, start the new one.** @@ -51,6 +51,20 @@ Breaking changes involve a new consensus deployment. The old database is incompa ## Migration Notes +### v0.5.x to v0.6.0 (Breaking) + +- Bradbury Phase 2 is a new consensus deployment with a new `consensusaddress`. Follow the [breaking upgrade](#breaking-change-upgrade) steps. +- The node needs the trusted protocol manifest of the network. Set `consensus.manifestpath` (default `configs/node/protocol-manifest.json`) and `consensus.chainid`. The release package has no network files: take the manifest and values from [Network-Specific Consensus Configuration](/validators/setup-guide#network-specific-consensus-configuration). +- GenVM moved from `third_party/genvm` to `opt/genvm/`. Update `genvm.root_dir` and run `python3 ./opt/genvm//bin/setup.py` (see [download instructions](/validators/setup-guide#download-the-node-software)). +- The Bradbury RPC WebSocket needs an API key. Set it in the environment as `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY`; it has no `config.yaml` key. See [Testnet Bradbury](/validators/setup-guide#testnet-bradbury). +- The `genlayer_debug` (`gen_dbg_*`) and `zksync` (`zks_*`) RPC groups are off in the default `config.yaml` (`node.rpc.endpoints`). +- The data store is split into `genvm.db` and `ops.db`. On startup the node renames an old `genlayer.db` to `genvm.db`; it refuses to start if both exist. +- Docker: `NODE_PASSWORD` is required and has no default. It reaches the node as `GENLAYERNODE_PASSWORD`, not as `--password`. See [Running the Node using docker-compose](/validators/setup-guide#running-the-node-using-docker-compose). +- Docker: the node container runs as uid `10001`. Make `./data` writable by it once: `sudo chown -R 10001:10001 ./data`. +- Docker: the node no longer mounts `/var/run/docker.sock`. It reaches Docker through the read-only `docker-socket-proxy` service. +- Monitoring: the `CENTRAL_*` URLs and credentials have no defaults. Alloy stops at startup if any is unset. See [Monitoring](/validators/monitoring). +- `genlayernode run --upgrade` (zero-downtime hand-off) is not usable yet: the admin routes that drive it deny every caller in v0.6. Do not start a node with it. + ### v0.4.x to v0.5.0 (Breaking) - The two consensus contract addresses (`contractmainaddress` and `contractdataaddress`) have been replaced by a single `consensusaddress` field. Do not copy your old consensus config — use the new example `config.yaml`. From cc2a2ea5662b03c25e005448b8a652dedfda1322 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 17:45:16 +0200 Subject: [PATCH 15/39] docs(validators): sync node #2267 at 3924a3036, use opt/genvm/current Result of `scripts/simulate-node-sync.sh 3924a3036`. The receipt page is no longer held back: the node doc now has LeaderRevealing = 13 and vote 5 DeterministicViolation. Compose uses the image entrypoint (GenVM precompile runs again), .env.example lists all 11 LLM keys, the greybox guide uses NODE_DIR and ./bin/genlayernode doctor. The release tarball ships opt/genvm/current and its config.yaml uses it, so the hand-written steps drop GENVM_VERSION and use opt/genvm/current. Co-Authored-By: Claude Opus 5.5 --- content/validators/config.yaml | 2 +- .../validators/docker-compose-monitoring.yaml | 5 +-- content/validators/docker-compose.yaml | 5 +-- content/validators/env.example | 16 ++++++---- content/validators/greybox-setup-guide.md | 22 +++++++------ pages/api-references/genlayer-node.mdx | 30 +++++++++++++---- .../gen/gen_getTransactionData.mdx | 2 +- .../gen/gen_getTransactionReceipt.mdx | 28 ++++++++++++---- pages/validators/genvm-configuration.mdx | 2 +- pages/validators/greybox-setup.mdx | 22 +++++++------ pages/validators/setup-guide.mdx | 32 +++++++++++-------- pages/validators/upgrade.mdx | 2 +- 12 files changed, 109 insertions(+), 59 deletions(-) diff --git a/content/validators/config.yaml b/content/validators/config.yaml index 3177305f..ad1ac602 100644 --- a/content/validators/config.yaml +++ b/content/validators/config.yaml @@ -195,7 +195,7 @@ node: # max_debug_field_bytes: 262144 # 256 KB per field # genvm configuration genvm: - root_dir: ./opt/genvm/v0.6.0-rc1 + root_dir: ./opt/genvm/v0.6.0-rc10 start_manager: true # if true node will start genvm manager itself manager_url: http://127.0.0.1:3999 # Permits are charged PER EXECUTION by the genvm manager, and one diff --git a/content/validators/docker-compose-monitoring.yaml b/content/validators/docker-compose-monitoring.yaml index f6ea71f4..1da8802e 100644 --- a/content/validators/docker-compose-monitoring.yaml +++ b/content/validators/docker-compose-monitoring.yaml @@ -97,11 +97,12 @@ services: genlayer-node: image: yeagerai/genlayer-node:${NODE_VERSION:-latest} - # The keystore password is NOT on this command line. It reaches the node as + # No entrypoint here: the image's own entrypoint precompiles GenVM for the + # host CPU on the first start, then runs `genlayernode run`. The keystore + # password is NOT on a command line. It reaches the node as # GENLAYERNODE_PASSWORD (environment below), which the run command reads in # place of --password. A command-line argument is visible in process # listings on the host; an environment variable is not. - entrypoint: ["/app/bin/genlayernode", "run"] container_name: genlayer-node restart: unless-stopped env_file: diff --git a/content/validators/docker-compose.yaml b/content/validators/docker-compose.yaml index acfad56f..0a122d54 100644 --- a/content/validators/docker-compose.yaml +++ b/content/validators/docker-compose.yaml @@ -95,11 +95,12 @@ services: - monitoring genlayer-node: image: yeagerai/genlayer-node:${NODE_VERSION:-latest} - # The keystore password is NOT on this command line. It reaches the node as + # No entrypoint here: the image's own entrypoint precompiles GenVM for the + # host CPU on the first start, then runs `genlayernode run`. The keystore + # password is NOT on a command line. It reaches the node as # GENLAYERNODE_PASSWORD (environment below), which the run command reads in # place of --password. A command-line argument is visible in process # listings on the host; an environment variable is not. - entrypoint: ["/app/bin/genlayernode", "run"] container_name: genlayer-node restart: unless-stopped env_file: diff --git a/content/validators/env.example b/content/validators/env.example index 67e1f05a..22d12372 100644 --- a/content/validators/env.example +++ b/content/validators/env.example @@ -38,6 +38,11 @@ ANTHROPICKEY= XAIKEY= GEMINIKEY= ATOMAKEY= +COMPUT3KEY= +IOINTELLIGENCEKEY= +LIBERTAI_API_KEY= +MORPHEUS_API_KEY= +OPENAIKEY= # Optional logging level override GENLAYERNODE_LOGGING_LEVEL=INFO @@ -116,7 +121,6 @@ NODE_METRICS_ENDPOINT=host.docker.internal:9153 # Example for multiple nodes: # SCRAPE_TARGETS_JSON=[{"__address__":"host.docker.internal:9250","instance":"0x...","validator_name":"node-1"},{"__address__":"host.docker.internal:9251","instance":"0x...","validator_name":"node-2"}] # -# To generate for local nodes, use: ./generate-dev-targets.sh # SCRAPE_TARGETS_JSON= # ============================================================================= @@ -136,8 +140,8 @@ LOG_FILE_PATTERN=/var/log/genlayer/node*.log # Metrics scraping intervals (optional) # How often to scrape metrics from the node -# Default: 15s for node metrics, 10s timeout -METRICS_SCRAPE_INTERVAL=15s +# Default: 60s for node metrics, 10s timeout +METRICS_SCRAPE_INTERVAL=60s METRICS_SCRAPE_TIMEOUT=10s # Alloy internal health monitoring interval (optional) @@ -148,9 +152,9 @@ ALLOY_SELF_MONITORING_INTERVAL=60s # Loki log batching configuration (optional) # Controls how logs are batched before sending to central Loki -# Default: 1MiB batch size, 1s wait time +# Default: 1MiB batch size, 60s wait time LOKI_BATCH_SIZE=1MiB -LOKI_BATCH_WAIT=1s +LOKI_BATCH_WAIT=60s # ============================================================================= # Deployment Instructions @@ -183,7 +187,7 @@ LOKI_BATCH_WAIT=1s # - Grafana Alloy handles both metrics and logs in a single service # - To customize monitoring, edit alloy-config.river # - To filter metrics (reduce bandwidth), uncomment the filtering rule in alloy-config.river -# - Ensure firewall allows outbound connections to monitoring server (port 8180) +# - Ensure the firewall allows outbound HTTPS to CENTRAL_MONITORING_URL and CENTRAL_LOKI_URL # # Keep health checks but disable automatic container-local recycling when false. diff --git a/content/validators/greybox-setup-guide.md b/content/validators/greybox-setup-guide.md index 08709109..58fbb563 100644 --- a/content/validators/greybox-setup-guide.md +++ b/content/validators/greybox-setup-guide.md @@ -45,33 +45,37 @@ echo "OPENROUTERKEY=sk-or-v1-your-key-here" >> ${ENV_FILE} The tarball ships with a unified config that includes all backends (openrouter, morpheus, heurist, ionet, etc.). +`NODE_DIR` is the directory of your node release (where you extracted the +tarball). On a host with the `/opt/genlayer-node/bin` symlink layout, it is +`/opt/genlayer-node/`. + ```bash -VERSION=$(readlink /opt/genlayer-node/bin | sed 's|/bin||; s|.*/||') +NODE_DIR=/path/to/your/node/release -cp /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-modules-llm-release.yaml \ - /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml +cp ${NODE_DIR}/opt/genvm/current/config/genvm-modules-llm-release.yaml \ + ${NODE_DIR}/opt/genvm/current/config/genvm-module-llm.yaml ``` ### 4. Switch to greybox strategy ```bash sed -i 's/genvm-llm-default\.lua/genvm-llm-greybox.lua/' \ - /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml + ${NODE_DIR}/opt/genvm/current/config/genvm-module-llm.yaml ``` ### 5. Verify the config ```bash # Check lua script path -grep lua_script_path /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml +grep lua_script_path ${NODE_DIR}/opt/genvm/current/config/genvm-module-llm.yaml # Expected: lua_script_path: ${exeDir}/../config/genvm-llm-greybox.lua # Check openrouter is present -grep -A2 'openrouter:' /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml +grep -A2 'openrouter:' ${NODE_DIR}/opt/genvm/current/config/genvm-module-llm.yaml # Expected: enabled: true # Check the greybox Lua file exists -ls -la /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-llm-greybox.lua +ls -la ${NODE_DIR}/opt/genvm/current/config/genvm-llm-greybox.lua ``` ### 6. Start the node @@ -102,7 +106,7 @@ To revert to the default generated chain: sudo systemctl stop genlayer-node sed -i 's/genvm-llm-greybox\.lua/genvm-llm-default.lua/' \ - /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml + ${NODE_DIR}/opt/genvm/current/config/genvm-module-llm.yaml sudo systemctl start genlayer-node ``` @@ -178,7 +182,7 @@ After editing the YAML, restart the LLM module (see "Updating Greybox on a Runni **All models exhausted error:** - OpenRouter may be down or your key is invalid - Check your key at https://openrouter.ai/settings/keys -- Run `task node:doctor` — the "LLM Providers" section probes each enabled backend and reports which ones actually answer, rather than only whether a key variable is set +- Run `./bin/genlayernode doctor` from `${NODE_DIR}` — the "LLM Providers" section probes each enabled backend and reports which ones actually answer, rather than only whether a key variable is set - Fallback providers (heurist, ionet) also need valid keys if you want fallback to work **Configure more than one provider.** A backend only enters the fallback chain diff --git a/pages/api-references/genlayer-node.mdx b/pages/api-references/genlayer-node.mdx index 409af926..1b52a25e 100644 --- a/pages/api-references/genlayer-node.mdx +++ b/pages/api-references/genlayer-node.mdx @@ -546,7 +546,11 @@ A `Transaction` object which contains the details of the transaction. The struct - `activator` (`address`): The address of the validator that activated the transaction. - `status` (`uint8`): The current status of the transaction. - `statusName` (`string`): The ABI transaction status name for `status` (`Uninitialized`, `Pending`, `Proposing`, `Committing`, `Revealing`, `Accepted`, `Undetermined`, `Finalized`, `Canceled`, `AppealRevealing`, `AppealCommitting`, `ValidatorsTimeout`, `LeaderTimeout`, or `LeaderRevealing`). -- `previousStatus` (`null`): **Always null.** The contract dropped this field from `ITransactions.Transaction` (26 → 25 tuple components), so the node has nothing to report. It is `null` rather than `0` because `0` decodes as `Uninitialized`, which would be a wrong answer rather than an absent one. The field is removed in a v2 receipt. +- `previousStatus` (`null`): **Always null.** The contract dropped this field + from `ITransactions.Transaction` (26 → 25 tuple components), so the node has + nothing to report. It is `null` rather than `0` because `0` decodes as + `Uninitialized`, which would be a wrong answer rather than an absent one. The + field is removed in a v2 receipt. - `txSlot` (`uint256`): The transaction slot number. - `initialRotations` (`uint256`): The initial number of rotations. - `numOfInitialValidators` (`uint256`): The number of initial validators. @@ -564,7 +568,7 @@ A `Transaction` object which contains the details of the transaction. The struct - `AppealSubmitted` (`uint256`): When an appeal was submitted. - `LeaderRevealed` (`uint256`): When the leader was revealed. - `result` (`uint8`): The result of the transaction execution. -- `txExecutionResult` (`uint8`): The execution vote result (`0` NotVoted, `1` FinishedWithReturn, `2` FinishedWithError, `3` Timeout, `4` NondetDisagree, `5` DeterministicViolation). +- `txExecutionResult` (`uint8`): The leader execution vote result (`0` NotVoted, `1` FinishedWithReturn, `2` FinishedWithError, `3` Timeout, `4` NondetDisagree, `5` DeterministicViolation). - `txExecutionResultName` (`string`): The ABI vote type name for `txExecutionResult`. - `epoch` (`uint256`): The epoch the transaction belongs to. - `resultHash` (`string`): The transaction result hash as a hex string. @@ -591,16 +595,27 @@ A `Transaction` object which contains the details of the transaction. The struct - `storageFeeUsed` (`string`): Storage fee charged on-chain for the transaction, equal to the storage pages it consumed multiplied by the locked `storageUnitPrice`. - `messageFeesConsumed` (`string`) - `messageFeesBudgetTotal` (`string`) - - `hasAllocations` (`boolean`): `true` when the transaction locked a pinned per-message fee allocation tree at submission; `false` for an open bucket (global message-fee bucket) or fee-less transactions. - - `allocations` (`array`): The pinned per-message fee allocation tree, one entry per locked allocation (empty for an open bucket). Budget comes from the on-chain allocation tree; `consumed` is read from the separate `allocationConsumed(txId, key)` mapping, keyed by `keccak256(abi.encode(messageType, recipient, callKey))`. Each entry: + - `hasAllocations` (`boolean`): `true` when the chain pinned the transaction's message allocations: a pinned per-message fee allocation tree, or a closed transaction, whose allowed set is empty. `false` for an open bucket (global message-fee bucket) or fee-less transactions. It is also `false` when the tree cannot be read, so that a receipt query still answers; the node logs the read failure. + - `allocations` (`array`): The pinned per-message fee allocation tree, one entry per locked allocation, internal and external (empty for an open bucket and for a closed transaction). Budget comes from the on-chain allocation tree; `consumed` is read from the separate `allocationConsumed(txId, key)` mapping, keyed by `keccak256(abi.encode(messageType, recipient, callKey))`. Each entry: - `messageType` (`number`): `0` = external, `1` = internal. - `onAcceptance` (`boolean`): Emission phase the allocation is pinned to (`true` = on acceptance, `false` = on finalization). - `parentIndex` (`string`): Index of the parent allocation in the flat tree, or the root sentinel (`2^256 - 1`) for a root-layer allocation. - - `recipient` (`string`): Target contract address the allocation funds, or the zero address for a recipient wildcard. + - `recipient` (`string`): Target contract address the allocation funds. There is no recipient wildcard: the zero address is a concrete recipient. - `callKey` (`string`): 32-byte call key (hex) the allocation matches, or the `keccak256("")` call-key wildcard. - `budget` (`string`): Maximum message-fee budget locked for this allocation. - `consumed` (`string`): Amount consumed against this allocation so far. - `feeParams` (`string`): ABI-encoded per-message fee params (hex), decoded per `messageType`. + - `allocationPolicy` (`string`, optional): How the chain classifies the transaction's message allocations. It is read from the chain's own policy, not derived from `hasAllocations` and `allocations`; read it to tell a closed transaction from the other two. + + | `allocationPolicy` | `hasAllocations` | `allocations` | Meaning | + | --- | --- | --- | --- | + | `"open"` | `false` | `[]` | Nothing is pinned. Messages draw from the open bucket. | + | `"pinned"` | `true` | internal and external nodes | A message must match one of the listed allocations. | + | `"closed"` | `true` | `[]` | Allocations are pinned to an empty allowed set. The transaction runs and can fund a message from its own contract balance; the chain rejects every other message it emits. | + + The field is omitted when the node could not read the allocation tree or the policy, or when the chain returns a policy this node version does not know; the node logs it. A receipt never states a policy it did not read. + + The values are read at the chain head. The chain deletes the allocation marker when it refunds a transaction's fee budget, so such a transaction reads `"open"` (and `hasAllocations: false`) whatever it was before. That applies to a cancelled transaction, to one that expired by idleness, and to one finalized through recovery; ordinary finalization keeps the policy. For those transactions `"open"` does not show that the transaction ran as an open bucket. - `readStateBlockRanges` (`array`): Array of block range objects, each containing: - `ActivationBlock` (`uint256`): The activation block number. - `ProcessingBlock` (`uint256`): The processing block number. @@ -697,7 +712,8 @@ A `Transaction` object which contains the details of the transaction. The struct "messageFeesBudgetTotal": "0" }, "hasAllocations": false, - "allocations": [] + "allocations": [], + "allocationPolicy": "open" }, "readStateBlockRanges": [ { @@ -1153,7 +1169,7 @@ An object with the on-chain transaction fields for the selected round plus the l - `statusName` (`string`): The ABI transaction status name for `status` (`Uninitialized`, `Pending`, `Proposing`, `Committing`, `Revealing`, `Accepted`, `Undetermined`, `Finalized`, `Canceled`, `AppealRevealing`, `AppealCommitting`, `ValidatorsTimeout`, `LeaderTimeout`, or `LeaderRevealing`). - `result` (`uint8`): The consensus result of the transaction (`0` Idle, `1` MajorityAgree, `2` MajorityDisagree, `3` MajorityTimeout, `4` DeterministicViolation, `5` NoMajority). - `resultName` (`string`): The ABI result name for `result`. -- `txExecutionResult` (`uint8`): The leader execution vote result (`0` NotVoted, `1` FinishedWithReturn, `2` FinishedWithError, `3` Timeout, `4` NondetDisagree). +- `txExecutionResult` (`uint8`): The leader execution vote result (`0` NotVoted, `1` FinishedWithReturn, `2` FinishedWithError, `3` Timeout, `4` NondetDisagree, `5` DeterministicViolation). - `txExecutionResultName` (`string`): The ABI vote type name for `txExecutionResult`. - `txCallData` (`string`): The transaction call data as a hex string. - `txExecutionHash` (`string`): The execution hash of the transaction as a hex string. diff --git a/pages/api-references/genlayer-node/gen/gen_getTransactionData.mdx b/pages/api-references/genlayer-node/gen/gen_getTransactionData.mdx index b9686e11..dab53508 100644 --- a/pages/api-references/genlayer-node/gen/gen_getTransactionData.mdx +++ b/pages/api-references/genlayer-node/gen/gen_getTransactionData.mdx @@ -22,7 +22,7 @@ An object with the on-chain transaction fields for the selected round plus the l - `statusName` (`string`): The ABI transaction status name for `status` (`Uninitialized`, `Pending`, `Proposing`, `Committing`, `Revealing`, `Accepted`, `Undetermined`, `Finalized`, `Canceled`, `AppealRevealing`, `AppealCommitting`, `ValidatorsTimeout`, `LeaderTimeout`, or `LeaderRevealing`). - `result` (`uint8`): The consensus result of the transaction (`0` Idle, `1` MajorityAgree, `2` MajorityDisagree, `3` MajorityTimeout, `4` DeterministicViolation, `5` NoMajority). - `resultName` (`string`): The ABI result name for `result`. -- `txExecutionResult` (`uint8`): The leader execution vote result (`0` NotVoted, `1` FinishedWithReturn, `2` FinishedWithError, `3` Timeout, `4` NondetDisagree). +- `txExecutionResult` (`uint8`): The leader execution vote result (`0` NotVoted, `1` FinishedWithReturn, `2` FinishedWithError, `3` Timeout, `4` NondetDisagree, `5` DeterministicViolation). - `txExecutionResultName` (`string`): The ABI vote type name for `txExecutionResult`. - `txCallData` (`string`): The transaction call data as a hex string. - `txExecutionHash` (`string`): The execution hash of the transaction as a hex string. diff --git a/pages/api-references/genlayer-node/gen/gen_getTransactionReceipt.mdx b/pages/api-references/genlayer-node/gen/gen_getTransactionReceipt.mdx index 67177b75..19ee20d6 100644 --- a/pages/api-references/genlayer-node/gen/gen_getTransactionReceipt.mdx +++ b/pages/api-references/genlayer-node/gen/gen_getTransactionReceipt.mdx @@ -20,7 +20,11 @@ A `Transaction` object which contains the details of the transaction. The struct - `activator` (`address`): The address of the validator that activated the transaction. - `status` (`uint8`): The current status of the transaction. - `statusName` (`string`): The ABI transaction status name for `status` (`Uninitialized`, `Pending`, `Proposing`, `Committing`, `Revealing`, `Accepted`, `Undetermined`, `Finalized`, `Canceled`, `AppealRevealing`, `AppealCommitting`, `ValidatorsTimeout`, `LeaderTimeout`, or `LeaderRevealing`). -- `previousStatus` (`null`): **Always null.** The contract dropped this field from `ITransactions.Transaction` (26 → 25 tuple components), so the node has nothing to report. It is `null` rather than `0` because `0` decodes as `Uninitialized`, which would be a wrong answer rather than an absent one. The field is removed in a v2 receipt. +- `previousStatus` (`null`): **Always null.** The contract dropped this field + from `ITransactions.Transaction` (26 → 25 tuple components), so the node has + nothing to report. It is `null` rather than `0` because `0` decodes as + `Uninitialized`, which would be a wrong answer rather than an absent one. The + field is removed in a v2 receipt. - `txSlot` (`uint256`): The transaction slot number. - `initialRotations` (`uint256`): The initial number of rotations. - `numOfInitialValidators` (`uint256`): The number of initial validators. @@ -38,7 +42,7 @@ A `Transaction` object which contains the details of the transaction. The struct - `AppealSubmitted` (`uint256`): When an appeal was submitted. - `LeaderRevealed` (`uint256`): When the leader was revealed. - `result` (`uint8`): The result of the transaction execution. -- `txExecutionResult` (`uint8`): The execution vote result (`0` NotVoted, `1` FinishedWithReturn, `2` FinishedWithError, `3` Timeout, `4` NondetDisagree, `5` DeterministicViolation). +- `txExecutionResult` (`uint8`): The leader execution vote result (`0` NotVoted, `1` FinishedWithReturn, `2` FinishedWithError, `3` Timeout, `4` NondetDisagree, `5` DeterministicViolation). - `txExecutionResultName` (`string`): The ABI vote type name for `txExecutionResult`. - `epoch` (`uint256`): The epoch the transaction belongs to. - `resultHash` (`string`): The transaction result hash as a hex string. @@ -65,16 +69,27 @@ A `Transaction` object which contains the details of the transaction. The struct - `storageFeeUsed` (`string`): Storage fee charged on-chain for the transaction, equal to the storage pages it consumed multiplied by the locked `storageUnitPrice`. - `messageFeesConsumed` (`string`) - `messageFeesBudgetTotal` (`string`) - - `hasAllocations` (`boolean`): `true` when the transaction locked a pinned per-message fee allocation tree at submission; `false` for an open bucket (global message-fee bucket) or fee-less transactions. - - `allocations` (`array`): The pinned per-message fee allocation tree, one entry per locked allocation (empty for an open bucket). Budget comes from the on-chain allocation tree; `consumed` is read from the separate `allocationConsumed(txId, key)` mapping, keyed by `keccak256(abi.encode(messageType, recipient, callKey))`. Each entry: + - `hasAllocations` (`boolean`): `true` when the chain pinned the transaction's message allocations: a pinned per-message fee allocation tree, or a closed transaction, whose allowed set is empty. `false` for an open bucket (global message-fee bucket) or fee-less transactions. It is also `false` when the tree cannot be read, so that a receipt query still answers; the node logs the read failure. + - `allocations` (`array`): The pinned per-message fee allocation tree, one entry per locked allocation, internal and external (empty for an open bucket and for a closed transaction). Budget comes from the on-chain allocation tree; `consumed` is read from the separate `allocationConsumed(txId, key)` mapping, keyed by `keccak256(abi.encode(messageType, recipient, callKey))`. Each entry: - `messageType` (`number`): `0` = external, `1` = internal. - `onAcceptance` (`boolean`): Emission phase the allocation is pinned to (`true` = on acceptance, `false` = on finalization). - `parentIndex` (`string`): Index of the parent allocation in the flat tree, or the root sentinel (`2^256 - 1`) for a root-layer allocation. - - `recipient` (`string`): Target contract address the allocation funds, or the zero address for a recipient wildcard. + - `recipient` (`string`): Target contract address the allocation funds. There is no recipient wildcard: the zero address is a concrete recipient. - `callKey` (`string`): 32-byte call key (hex) the allocation matches, or the `keccak256("")` call-key wildcard. - `budget` (`string`): Maximum message-fee budget locked for this allocation. - `consumed` (`string`): Amount consumed against this allocation so far. - `feeParams` (`string`): ABI-encoded per-message fee params (hex), decoded per `messageType`. + - `allocationPolicy` (`string`, optional): How the chain classifies the transaction's message allocations. It is read from the chain's own policy, not derived from `hasAllocations` and `allocations`; read it to tell a closed transaction from the other two. + + | `allocationPolicy` | `hasAllocations` | `allocations` | Meaning | + | --- | --- | --- | --- | + | `"open"` | `false` | `[]` | Nothing is pinned. Messages draw from the open bucket. | + | `"pinned"` | `true` | internal and external nodes | A message must match one of the listed allocations. | + | `"closed"` | `true` | `[]` | Allocations are pinned to an empty allowed set. The transaction runs and can fund a message from its own contract balance; the chain rejects every other message it emits. | + + The field is omitted when the node could not read the allocation tree or the policy, or when the chain returns a policy this node version does not know; the node logs it. A receipt never states a policy it did not read. + + The values are read at the chain head. The chain deletes the allocation marker when it refunds a transaction's fee budget, so such a transaction reads `"open"` (and `hasAllocations: false`) whatever it was before. That applies to a cancelled transaction, to one that expired by idleness, and to one finalized through recovery; ordinary finalization keeps the policy. For those transactions `"open"` does not show that the transaction ran as an open bucket. - `readStateBlockRanges` (`array`): Array of block range objects, each containing: - `ActivationBlock` (`uint256`): The activation block number. - `ProcessingBlock` (`uint256`): The processing block number. @@ -171,7 +186,8 @@ A `Transaction` object which contains the details of the transaction. The struct "messageFeesBudgetTotal": "0" }, "hasAllocations": false, - "allocations": [] + "allocations": [], + "allocationPolicy": "open" }, "readStateBlockRanges": [ { diff --git a/pages/validators/genvm-configuration.mdx b/pages/validators/genvm-configuration.mdx index c13e76ce..32d1980a 100644 --- a/pages/validators/genvm-configuration.mdx +++ b/pages/validators/genvm-configuration.mdx @@ -46,7 +46,7 @@ Repeat for each GenVM instance if running multiple. A module reload picks up YAM ## Configuration Files -All configuration lives in the GenVM tree of the release, at `opt/genvm//config/` (in the Docker image: `/app/opt/genvm/current/config/`). Three files: +All configuration lives in the GenVM tree of the release, at `opt/genvm/current/config/` (in the Docker image: `/app/opt/genvm/current/config/`). `opt/genvm/current` links to the GenVM version the release ships. Three files: | File | What it controls | Should you modify? | |---|---|---| diff --git a/pages/validators/greybox-setup.mdx b/pages/validators/greybox-setup.mdx index 909eb774..35afcff1 100644 --- a/pages/validators/greybox-setup.mdx +++ b/pages/validators/greybox-setup.mdx @@ -47,33 +47,37 @@ echo "OPENROUTERKEY=sk-or-v1-your-key-here" >> ${ENV_FILE} The tarball ships with a unified config that includes all backends (openrouter, morpheus, heurist, ionet, etc.). +`NODE_DIR` is the directory of your node release (where you extracted the +tarball). On a host with the `/opt/genlayer-node/bin` symlink layout, it is +`/opt/genlayer-node/`. + ```bash -VERSION=$(readlink /opt/genlayer-node/bin | sed 's|/bin||; s|.*/||') +NODE_DIR=/path/to/your/node/release -cp /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-modules-llm-release.yaml \ - /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml +cp ${NODE_DIR}/opt/genvm/current/config/genvm-modules-llm-release.yaml \ + ${NODE_DIR}/opt/genvm/current/config/genvm-module-llm.yaml ``` ### 4. Switch to greybox strategy ```bash sed -i 's/genvm-llm-default\.lua/genvm-llm-greybox.lua/' \ - /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml + ${NODE_DIR}/opt/genvm/current/config/genvm-module-llm.yaml ``` ### 5. Verify the config ```bash # Check lua script path -grep lua_script_path /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml +grep lua_script_path ${NODE_DIR}/opt/genvm/current/config/genvm-module-llm.yaml # Expected: lua_script_path: ${exeDir}/../config/genvm-llm-greybox.lua # Check openrouter is present -grep -A2 'openrouter:' /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml +grep -A2 'openrouter:' ${NODE_DIR}/opt/genvm/current/config/genvm-module-llm.yaml # Expected: enabled: true # Check the greybox Lua file exists -ls -la /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-llm-greybox.lua +ls -la ${NODE_DIR}/opt/genvm/current/config/genvm-llm-greybox.lua ``` ### 6. Start the node @@ -104,7 +108,7 @@ To revert to the default generated chain: sudo systemctl stop genlayer-node sed -i 's/genvm-llm-greybox\.lua/genvm-llm-default.lua/' \ - /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml + ${NODE_DIR}/opt/genvm/current/config/genvm-module-llm.yaml sudo systemctl start genlayer-node ``` @@ -180,7 +184,7 @@ After editing the YAML, restart the LLM module (see "Updating Greybox on a Runni **All models exhausted error:** - OpenRouter may be down or your key is invalid - Check your key at https://openrouter.ai/settings/keys -- Run `task node:doctor` — the "LLM Providers" section probes each enabled backend and reports which ones actually answer, rather than only whether a key variable is set +- Run `./bin/genlayernode doctor` from `${NODE_DIR}` — the "LLM Providers" section probes each enabled backend and reports which ones actually answer, rather than only whether a key variable is set - Fallback providers (heurist, ionet) also need valid keys if you want fallback to work **Configure more than one provider.** A backend only enters the fallback chain diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index 327bd55a..0b55ce31 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -251,7 +251,7 @@ One full node can be shared between multiple validators. The optimal validator-t If either command fails, do not extract: delete the download and fetch it again. Releases before v0.6.0 have no checksum file. - The same releases also publish `install.sh`, covered by the same checksum file. After verifying, `./install.sh --install-dir /opt/genlayer-node/${version} --tarball genlayer-node-linux-amd64-${version}.tar.gz` replaces steps 4 and 5, and also points `genvm.root_dir` at the GenVM version it unpacked. + The same releases also publish `install.sh`, covered by the same checksum file. After verifying, `./install.sh --install-dir /opt/genlayer-node/${version} --tarball genlayer-node-linux-amd64-${version}.tar.gz` replaces steps 4 and 5. 4. Extract the node software @@ -270,8 +270,7 @@ One full node can be shared between multiple validators. The optimal validator-t Run it in the final install location: it writes this directory's absolute path into the GenVM binaries. If you move the installation later, run it again there. ```sh copy - export GENVM_VERSION=$(ls opt/genvm) # the GenVM version this release ships - python3 ./opt/genvm/${GENVM_VERSION}/bin/setup.py + python3 ./opt/genvm/current/bin/setup.py ``` It does following: @@ -292,7 +291,7 @@ The file needs to be located at `configs/node/config.yaml` You can use the following example configuration. **Note:** For most users, you will only need to replace the `rollup` and `consensus` sections with the values for your target network (see [Network-Specific Consensus Configuration](#network-specific-consensus-configuration) below). -Also check that `genvm.root_dir` names the GenVM directory the release ships: run `ls opt/genvm` in the install directory. `install.sh` sets it for you; after a manual extract, set it yourself. +The `config.yaml` in the release package sets `genvm.root_dir: ./opt/genvm/current`, a link to the GenVM version the release ships. Keep that value: the example below names the versioned directory, which the package also contains. ```yaml copy # rollup configuration @@ -492,7 +491,7 @@ node: # max_debug_field_bytes: 262144 # 256 KB per field # genvm configuration genvm: - root_dir: ./opt/genvm/v0.6.0-rc1 + root_dir: ./opt/genvm/v0.6.0-rc10 start_manager: true # if true node will start genvm manager itself manager_url: http://127.0.0.1:3999 # Permits are charged PER EXECUTION by the genvm manager, and one @@ -944,11 +943,12 @@ services: - monitoring genlayer-node: image: yeagerai/genlayer-node:${NODE_VERSION:-latest} - # The keystore password is NOT on this command line. It reaches the node as + # No entrypoint here: the image's own entrypoint precompiles GenVM for the + # host CPU on the first start, then runs `genlayernode run`. The keystore + # password is NOT on a command line. It reaches the node as # GENLAYERNODE_PASSWORD (environment below), which the run command reads in # place of --password. A command-line argument is visible in process # listings on the host; an environment variable is not. - entrypoint: ["/app/bin/genlayernode", "run"] container_name: genlayer-node restart: unless-stopped env_file: @@ -1000,7 +1000,7 @@ services: webdriver_host: http://webdriver-container:4444 ``` - The compose file mounts it over the image's own copy at `/app/opt/genvm/current/config/genvm-module-web.yaml`, so no version variable is needed. The binary keeps using `opt/genvm//config/genvm-module-web.yaml`, which points at `localhost:4444`. + The compose file mounts it over the image's own copy at `/app/opt/genvm/current/config/genvm-module-web.yaml`, so no version variable is needed. The binary keeps using `opt/genvm/current/config/genvm-module-web.yaml`, which points at `localhost:4444`. 3. Set the required environment variables in a `.env` file: @@ -1049,6 +1049,11 @@ ANTHROPICKEY= XAIKEY= GEMINIKEY= ATOMAKEY= +COMPUT3KEY= +IOINTELLIGENCEKEY= +LIBERTAI_API_KEY= +MORPHEUS_API_KEY= +OPENAIKEY= # Optional logging level override GENLAYERNODE_LOGGING_LEVEL=INFO @@ -1127,7 +1132,6 @@ NODE_METRICS_ENDPOINT=host.docker.internal:9153 # Example for multiple nodes: # SCRAPE_TARGETS_JSON=[{"__address__":"host.docker.internal:9250","instance":"0x...","validator_name":"node-1"},{"__address__":"host.docker.internal:9251","instance":"0x...","validator_name":"node-2"}] # -# To generate for local nodes, use: ./generate-dev-targets.sh # SCRAPE_TARGETS_JSON= # ============================================================================= @@ -1147,8 +1151,8 @@ LOG_FILE_PATTERN=/var/log/genlayer/node*.log # Metrics scraping intervals (optional) # How often to scrape metrics from the node -# Default: 15s for node metrics, 10s timeout -METRICS_SCRAPE_INTERVAL=15s +# Default: 60s for node metrics, 10s timeout +METRICS_SCRAPE_INTERVAL=60s METRICS_SCRAPE_TIMEOUT=10s # Alloy internal health monitoring interval (optional) @@ -1159,9 +1163,9 @@ ALLOY_SELF_MONITORING_INTERVAL=60s # Loki log batching configuration (optional) # Controls how logs are batched before sending to central Loki -# Default: 1MiB batch size, 1s wait time +# Default: 1MiB batch size, 60s wait time LOKI_BATCH_SIZE=1MiB -LOKI_BATCH_WAIT=1s +LOKI_BATCH_WAIT=60s # ============================================================================= # Deployment Instructions @@ -1194,7 +1198,7 @@ LOKI_BATCH_WAIT=1s # - Grafana Alloy handles both metrics and logs in a single service # - To customize monitoring, edit alloy-config.river # - To filter metrics (reduce bandwidth), uncomment the filtering rule in alloy-config.river -# - Ensure firewall allows outbound connections to monitoring server (port 8180) +# - Ensure the firewall allows outbound HTTPS to CENTRAL_MONITORING_URL and CENTRAL_LOKI_URL # # Keep health checks but disable automatic container-local recycling when false. diff --git a/pages/validators/upgrade.mdx b/pages/validators/upgrade.mdx index 76d6b79f..689e45b9 100644 --- a/pages/validators/upgrade.mdx +++ b/pages/validators/upgrade.mdx @@ -55,7 +55,7 @@ Breaking changes involve a new consensus deployment. The old database is incompa - Bradbury Phase 2 is a new consensus deployment with a new `consensusaddress`. Follow the [breaking upgrade](#breaking-change-upgrade) steps. - The node needs the trusted protocol manifest of the network. Set `consensus.manifestpath` (default `configs/node/protocol-manifest.json`) and `consensus.chainid`. The release package has no network files: take the manifest and values from [Network-Specific Consensus Configuration](/validators/setup-guide#network-specific-consensus-configuration). -- GenVM moved from `third_party/genvm` to `opt/genvm/`. Update `genvm.root_dir` and run `python3 ./opt/genvm//bin/setup.py` (see [download instructions](/validators/setup-guide#download-the-node-software)). +- GenVM moved from `third_party/genvm` to `opt/genvm/`, with the link `opt/genvm/current` to it. Set `genvm.root_dir: ./opt/genvm/current` and run `python3 ./opt/genvm/current/bin/setup.py` (see [download instructions](/validators/setup-guide#download-the-node-software)). - The Bradbury RPC WebSocket needs an API key. Set it in the environment as `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY`; it has no `config.yaml` key. See [Testnet Bradbury](/validators/setup-guide#testnet-bradbury). - The `genlayer_debug` (`gen_dbg_*`) and `zksync` (`zks_*`) RPC groups are off in the default `config.yaml` (`node.rpc.endpoints`). - The data store is split into `genvm.db` and `ops.db`. On startup the node renames an old `genlayer.db` to `genvm.db`; it refuses to start if both exist. From ebc04a90f7a63a7978363e2c76c7be6bdc4cb2b3 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 17:47:24 +0200 Subject: [PATCH 16/39] docs(api): keep the gen_dbg_load_test stub out of the docs The debug filter default is now gen_dbg_(?!load_test).*, matching genlayer-node#2267 (81d88213f, release-sync-docs.yml). The node RPC is an unimplemented stub. The synced page is removed by the sync itself (`scripts/simulate-node-sync.sh 81d88213f`). Co-Authored-By: Claude Opus 5.5 --- .github/scripts/node-sync-params.sh | 2 +- .github/workflows/sync-docs-from-node.yml | 8 +-- pages/api-references/genlayer-node.mdx | 51 ------------------- .../genlayer-node/debug/_meta.json | 3 +- .../genlayer-node/debug/gen_dbg_load_test.mdx | 50 ------------------ 5 files changed, 6 insertions(+), 108 deletions(-) delete mode 100644 pages/api-references/genlayer-node/debug/gen_dbg_load_test.mdx diff --git a/.github/scripts/node-sync-params.sh b/.github/scripts/node-sync-params.sh index 41754fdc..197f5f6c 100755 --- a/.github/scripts/node-sync-params.sh +++ b/.github/scripts/node-sync-params.sh @@ -22,7 +22,7 @@ API_GEN_PATH="${API_GEN_PATH:-docs/api/rpc}" API_DEBUG_PATH="${API_DEBUG_PATH:-docs/api/rpc}" API_OPS_PATH="${API_OPS_PATH:-docs/api/ops}" API_GEN_REGEX="${API_GEN_REGEX:-gen_(?!dbg_).*}" -API_DEBUG_REGEX="${API_DEBUG_REGEX:-gen_dbg_.*}" +API_DEBUG_REGEX="${API_DEBUG_REGEX:-gen_dbg_(?!load_test).*}" # type|title|node path|docs target|filter TABLE="changelog|Changelog|${CHANGELOG_PATH}|content/validators/changelog|.* diff --git a/.github/workflows/sync-docs-from-node.yml b/.github/workflows/sync-docs-from-node.yml index 3d15ee26..5f00ba63 100644 --- a/.github/workflows/sync-docs-from-node.yml +++ b/.github/workflows/sync-docs-from-node.yml @@ -26,9 +26,9 @@ on: required: false default: 'gen_(?!dbg_).*' api_debug_regex: - description: 'Regex pattern to filter API debug files (e.g., "gen_dbg_.*")' + description: 'Regex pattern to filter API debug files (e.g., "gen_dbg_(?!load_test).*"; gen_dbg_load_test is a stub and stays out of the docs)' required: false - default: 'gen_dbg_.*' + default: 'gen_dbg_(?!load_test).*' api_ops_path: description: 'Path to API ops files in source repo' required: false @@ -171,7 +171,7 @@ jobs: API_DEBUG_PATH: ${{ github.event.inputs.api_debug_path || github.event.client_payload.api_debug_path || 'docs/api/rpc' }} API_OPS_PATH: ${{ github.event.inputs.api_ops_path || github.event.client_payload.api_ops_path || 'docs/api/ops' }} API_GEN_REGEX: ${{ github.event.inputs.api_gen_regex || github.event.client_payload.api_gen_regex || 'gen_(?!dbg_).*' }} - API_DEBUG_REGEX: ${{ github.event.inputs.api_debug_regex || github.event.client_payload.api_debug_regex || 'gen_dbg_.*' }} + API_DEBUG_REGEX: ${{ github.event.inputs.api_debug_regex || github.event.client_payload.api_debug_regex || 'gen_dbg_(?!load_test).*' }} run: bash .github/scripts/node-sync-params.sh "${{ matrix.sync_type }}" >> "$GITHUB_OUTPUT" - name: Sync files using composite action @@ -412,7 +412,7 @@ jobs: - **Source Repository**: \`genlayerlabs/genlayer-node\` - **Version**: \`${{ needs.prepare.outputs.version }}\` - **API Gen Filter**: \`${{ github.event.inputs.api_gen_regex != '' && github.event.inputs.api_gen_regex || (github.event.client_payload.api_gen_regex != '' && github.event.client_payload.api_gen_regex || 'gen_(?!dbg_).*') }}\` - - **API Debug Filter**: \`${{ github.event.inputs.api_debug_regex != '' && github.event.inputs.api_debug_regex || (github.event.client_payload.api_debug_regex != '' && github.event.client_payload.api_debug_regex || 'gen_dbg_.*') }}\` + - **API Debug Filter**: \`${{ github.event.inputs.api_debug_regex != '' && github.event.inputs.api_debug_regex || (github.event.client_payload.api_debug_regex != '' && github.event.client_payload.api_debug_regex || 'gen_dbg_(?!load_test).*') }}\` - **Total Files Changed**: ${{ needs.aggregate-results.outputs.total_changes }} - Added: ${{ needs.aggregate-results.outputs.total_added }} files - Updated: ${{ needs.aggregate-results.outputs.total_updated }} files diff --git a/pages/api-references/genlayer-node.mdx b/pages/api-references/genlayer-node.mdx index 1b52a25e..a37ea68e 100644 --- a/pages/api-references/genlayer-node.mdx +++ b/pages/api-references/genlayer-node.mdx @@ -1645,57 +1645,6 @@ Retrieves the full execution trace for a transaction, including the return data, - This endpoint is in the `genlayer_debug` group, which is **disabled by default**. Enable it deliberately, and only on an RPC bind that untrusted callers cannot reach: it replays a transaction through GenVM and returns internal execution output, so it is debug tooling, not a public API. - Use `result_code` to determine execution outcome: 0 = contract returned successfully, 1 = contract raised a user error, 2 = GenVM encountered an error, 3 = internal error. -### gen_dbg_load_test - -Debug hook reserved for driving a node-side load test. The endpoint is wired end to end, but the service behind it is currently a no-op stub: the call succeeds and returns `null` without performing any work. - -**Method:** `gen_dbg_load_test` - -**Parameters:** None. Any params supplied are accepted and discarded — the handler does not read them - -**Returns:** `null` - -**Example Request:** - -```json -{ - "jsonrpc": "2.0", - "method": "gen_dbg_load_test", - "params": [], - "id": 1 -} -``` - -**Example Response:** - -```json -{ - "jsonrpc": "2.0", - "result": null, - "id": 1 -} -``` - -**Notes:** - -- A `null` result means "call accepted, nothing performed" — it is not an error and does not indicate a load test ran -- Because the handler ignores its params, this method cannot currently be tuned (operation count, test type, or duration). Do not build tooling that depends on such parameters -- Belongs to the `genlayer_debug` group, which **fails closed**: it is disabled unless an operator explicitly enables it, since the debug methods are reachable unauthenticated on the default `0.0.0.0` RPC bind -- When the group is disabled, the response is JSON-RPC error `-32004` ("method not supported") - -**cURL Example:** - -```bash -curl -X POST http://localhost:9151 \ - -H "Content-Type: application/json" \ - -d '{ - "jsonrpc": "2.0", - "method": "gen_dbg_load_test", - "params": [], - "id": 1 - }' -``` - ## Ethereum Compatibility The GenLayer Node also supports Ethereum-compatible methods that are proxied to the underlying infrastructure. These methods follow the standard [Ethereum JSON-RPC specification](https://ethereum.org/en/developers/docs/apis/json-rpc/) and are prefixed with `eth_`. diff --git a/pages/api-references/genlayer-node/debug/_meta.json b/pages/api-references/genlayer-node/debug/_meta.json index a6bcfa17..c82ab8d8 100644 --- a/pages/api-references/genlayer-node/debug/_meta.json +++ b/pages/api-references/genlayer-node/debug/_meta.json @@ -1,6 +1,5 @@ { "gen_dbg_ping": "gen_dbg_ping", "gen_dbg_trie": "gen_dbg_trie", - "gen_dbg_traceTransaction": "gen_dbg_traceTransaction", - "gen_dbg_load_test": "gen_dbg_load_test" + "gen_dbg_traceTransaction": "gen_dbg_traceTransaction" } diff --git a/pages/api-references/genlayer-node/debug/gen_dbg_load_test.mdx b/pages/api-references/genlayer-node/debug/gen_dbg_load_test.mdx deleted file mode 100644 index d8609ec5..00000000 --- a/pages/api-references/genlayer-node/debug/gen_dbg_load_test.mdx +++ /dev/null @@ -1,50 +0,0 @@ -### gen_dbg_load_test - -Debug hook reserved for driving a node-side load test. The endpoint is wired end to end, but the service behind it is currently a no-op stub: the call succeeds and returns `null` without performing any work. - -**Method:** `gen_dbg_load_test` - -**Parameters:** None. Any params supplied are accepted and discarded — the handler does not read them - -**Returns:** `null` - -**Example Request:** - -```json -{ - "jsonrpc": "2.0", - "method": "gen_dbg_load_test", - "params": [], - "id": 1 -} -``` - -**Example Response:** - -```json -{ - "jsonrpc": "2.0", - "result": null, - "id": 1 -} -``` - -**Notes:** - -- A `null` result means "call accepted, nothing performed" — it is not an error and does not indicate a load test ran -- Because the handler ignores its params, this method cannot currently be tuned (operation count, test type, or duration). Do not build tooling that depends on such parameters -- Belongs to the `genlayer_debug` group, which **fails closed**: it is disabled unless an operator explicitly enables it, since the debug methods are reachable unauthenticated on the default `0.0.0.0` RPC bind -- When the group is disabled, the response is JSON-RPC error `-32004` ("method not supported") - -**cURL Example:** - -```bash -curl -X POST http://localhost:9151 \ - -H "Content-Type: application/json" \ - -d '{ - "jsonrpc": "2.0", - "method": "gen_dbg_load_test", - "params": [], - "id": 1 - }' -``` From ce52f250e07c1229a51dbe61d3c99c99356662aa Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 18:03:52 +0200 Subject: [PATCH 17/39] docs(validators): publish the newest rc until its stable release exists One rule instead of the stable/all setting (scripts/lib/release-versions.js): a release candidate is published while its version has no stable release, and only the newest rc of that version. Once v0.6.0 exists, every v0.6.0-rc.N disappears. The node keeps one changelog file per rc; the docs choose what to show. - Setup guide: the version list, `export version=` and the list command follow the rule. The command now lists rcs, sorts v0.6.0 above its rcs (sed ~rc + sort -V) and drops superseded rcs (awk): against the bucket it showed v0.5.14-rc.1 and v0.5.13-rc.1 otherwise. - Changelog page: the same rule (testnet builds kept). - check-node-sync: an rc may leave the changelog once it is superseded. - version-utils.sh: `latest` detection counts rc tags under the same rule. - Tests: scripts/test-release-versions.js (npm run test:release-versions), in PR CI. - Upgrade guide: Bradbury Phase 2 needs a new validator join; do not use `run --upgrade` in v0.6 (the node waits forever). - Setup guide: importing the operator key for a Docker node (tested by genlayer-node-3 in a #2267 image). Co-Authored-By: Claude Opus 5.5 --- .github/scripts/version-utils.sh | 5 +- .github/workflows/check-node-docs-sync.yml | 4 +- package.json | 2 +- pages/validators/setup-guide.mdx | 29 ++++- pages/validators/upgrade.mdx | 4 +- scripts/check-node-sync.js | 8 +- scripts/generate-changelog.js | 39 ++----- scripts/lib/release-versions.js | 73 +++++++++++++ scripts/setup-guide-versions.config.js | 19 ---- scripts/test-release-versions.js | 121 +++++++++++++++++++++ scripts/test-setup-guide-versions.js | 111 ------------------- scripts/update-setup-guide-versions.js | 82 +++----------- 12 files changed, 259 insertions(+), 238 deletions(-) create mode 100644 scripts/lib/release-versions.js delete mode 100644 scripts/setup-guide-versions.config.js create mode 100644 scripts/test-release-versions.js delete mode 100644 scripts/test-setup-guide-versions.js diff --git a/.github/scripts/version-utils.sh b/.github/scripts/version-utils.sh index d3efb016..dfaca47d 100755 --- a/.github/scripts/version-utils.sh +++ b/.github/scripts/version-utils.sh @@ -24,9 +24,10 @@ detect_latest_version() { # Fetch all tags git fetch --tags - # Get latest stable version tag + # Latest release tag: a release candidate counts until its stable release + # exists (scripts/lib/release-versions.js). The sed pair sorts v0.6.0-rc.N below v0.6.0. local latest_tag - latest_tag=$(git tag -l | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n1) + latest_tag=$(git tag -l | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?$' | sed 's/-rc/~rc/' | sort -V | sed 's/~rc/-rc/' | tail -n1) # Cleanup cd / diff --git a/.github/workflows/check-node-docs-sync.yml b/.github/workflows/check-node-docs-sync.yml index 69bd61ce..e73ddd98 100644 --- a/.github/workflows/check-node-docs-sync.yml +++ b/.github/workflows/check-node-docs-sync.yml @@ -25,8 +25,8 @@ jobs: - name: Install dependencies run: npm ci - - name: Test the setup guide version channel - run: npm run test:setup-guide-versions + - name: Test the release-version rule (newest rc until its stable exists) + run: npm run test:release-versions - name: Run documentation sync scripts # The one generator list (.github/scripts/doc-generator.sh). Fails when a diff --git a/package.json b/package.json index 08a9be6c..7a30cbb2 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "node-generate-all": "bash .github/scripts/doc-generator.sh", "node-simulate-sync": "bash scripts/simulate-node-sync.sh", "check:node-sync": "node scripts/check-node-sync.js", - "test:setup-guide-versions": "node scripts/test-setup-guide-versions.js" + "test:release-versions": "node scripts/test-release-versions.js" }, "repository": { "type": "git", diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index 0b55ce31..aba596a8 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -210,7 +210,7 @@ One full node can be shared between multiple validators. The optimal validator-t You can use this command to list available versions: ```sh copy - curl -s "https://storage.googleapis.com/storage/v1/b/gh-af/o?prefix=genlayer-node/bin/amd64" | grep -o '"name": *"[^"]*"' | sed -n 's/.*\/\(v[^/]*\)\/.*/\1/p' | sort -ruV | grep -v "rc" | head -n 5 + curl -s "https://storage.googleapis.com/storage/v1/b/gh-af/o?prefix=genlayer-node/bin/amd64" | grep -o '"name": *"[^"]*"' | sed -n 's/.*\/\(v[^/]*\)\/.*/\1/p' | sed 's/-rc/~rc/' | sort -ruV | sed 's/~rc/-rc/' | awk '{c=$0; sub(/-rc\..*/,"",c)} /-rc\./{if ((c in s) || (c in r)) next; r[c]=1} !/-rc\./{s[c]=1} {print}' | head -n 5 ``` You should see a list like this @@ -1212,7 +1212,30 @@ This is the release package's `.env.example`: copy it to `.env` next to `docker- - the API key of at least one LLM provider. - `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY`, when your chain RPC provider needs a key (the Bradbury WebSocket does). -4. Checking Your Configuration +4. Import the operator key + + `config.yaml` is mounted read-only, so `account import --setup` cannot write the operator address from inside the container. Import the key into the data directory, then add the address to the config yourself: + + ```sh copy + # 1. Put the key file in the data directory (mounted writable at /app/data) + mkdir -p ./data/import && cp /path/to/operator-keystore.json ./data/import/ + + # 2. Import it. The keystore password comes from NODE_PASSWORD in .env. + docker compose --profile node run --rm --entrypoint /app/bin/genlayernode genlayer-node \ + account import --path /app/data/import/operator-keystore.json --passphrase "password you set when exporting from wizard" + + # 3. Remove the key file + rm ./data/import/operator-keystore.json && rmdir ./data/import + ``` + + On Linux, run step 2 after the `chown` above. For a raw private key, use `--private-key ` instead of `--path` and `--passphrase`. The command prints the operator address: add it to `configs/node/config.yaml` on the host: + + ```yaml + node: + operatorAddress: "0x..." + ``` + +5. Checking Your Configuration Before the first start, run `doctor` in the node container, with the same config, manifest, environment and network as the node: @@ -1220,7 +1243,7 @@ Before the first start, run `doctor` in the node container, with the same config docker compose --profile node run --rm --entrypoint /app/bin/genlayernode genlayer-node doctor ``` -5. Start the services using Docker Compose: +6. Start the services using Docker Compose: ```sh copy docker compose --profile node up -d diff --git a/pages/validators/upgrade.mdx b/pages/validators/upgrade.mdx index 689e45b9..ca2e9372 100644 --- a/pages/validators/upgrade.mdx +++ b/pages/validators/upgrade.mdx @@ -53,7 +53,7 @@ Breaking changes involve a new consensus deployment. The old database is incompa ### v0.5.x to v0.6.0 (Breaking) -- Bradbury Phase 2 is a new consensus deployment with a new `consensusaddress`. Follow the [breaking upgrade](#breaking-change-upgrade) steps. +- Bradbury Phase 2 is a new deployment on a new chain, with a new `consensusaddress` and a new staking contract. Phase 1 validators are not carried over: join again as a validator, with stake, on Phase 2. Follow the [breaking upgrade](#breaking-change-upgrade) steps. - The node needs the trusted protocol manifest of the network. Set `consensus.manifestpath` (default `configs/node/protocol-manifest.json`) and `consensus.chainid`. The release package has no network files: take the manifest and values from [Network-Specific Consensus Configuration](/validators/setup-guide#network-specific-consensus-configuration). - GenVM moved from `third_party/genvm` to `opt/genvm/`, with the link `opt/genvm/current` to it. Set `genvm.root_dir: ./opt/genvm/current` and run `python3 ./opt/genvm/current/bin/setup.py` (see [download instructions](/validators/setup-guide#download-the-node-software)). - The Bradbury RPC WebSocket needs an API key. Set it in the environment as `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY`; it has no `config.yaml` key. See [Testnet Bradbury](/validators/setup-guide#testnet-bradbury). @@ -63,7 +63,7 @@ Breaking changes involve a new consensus deployment. The old database is incompa - Docker: the node container runs as uid `10001`. Make `./data` writable by it once: `sudo chown -R 10001:10001 ./data`. - Docker: the node no longer mounts `/var/run/docker.sock`. It reaches Docker through the read-only `docker-socket-proxy` service. - Monitoring: the `CENTRAL_*` URLs and credentials have no defaults. Alloy stops at startup if any is unset. See [Monitoring](/validators/monitoring). -- `genlayernode run --upgrade` (zero-downtime hand-off) is not usable yet: the admin routes that drive it deny every caller in v0.6. Do not start a node with it. +- Do not use `genlayernode run --upgrade` in v0.6: the call that resumes the node is not available yet, so the node waits forever. ### v0.4.x to v0.5.0 (Breaking) diff --git a/scripts/check-node-sync.js b/scripts/check-node-sync.js index 1d96116a..7c4cb7b4 100644 --- a/scripts/check-node-sync.js +++ b/scripts/check-node-sync.js @@ -11,12 +11,14 @@ // - sanitized files are sanitized (no live RPC URLs, no alloy service); // - the Bradbury manifest is valid JSON and matches bradbury.yaml; // - no Asimov references in validator and node API pages; -// - no changelog version disappears compared with BASE_REF (default origin/main). +// - no changelog version disappears compared with BASE_REF (default origin/main), +// except a release candidate superseded by its stable release or a newer rc. const fs = require('fs'); const path = require('path'); const { execFileSync } = require('child_process'); const { extractAlloyBlock } = require('./update-alloy-in-monitoring'); +const { isSuperseded } = require('./lib/release-versions'); const root = path.join(__dirname, '..'); const read = (p) => fs.readFileSync(path.join(root, p), 'utf8'); @@ -122,8 +124,10 @@ const baseRef = process.env.BASE_REF || 'origin/main'; try { const before = execFileSync('git', ['ls-tree', '--name-only', `${baseRef}:content/validators/changelog`], { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }) .trim().split('\n').filter(Boolean); + const now = fs.readdirSync(path.join(root, 'content/validators/changelog')) + .filter((f) => f.endsWith('.mdx')).map((f) => f.replace(/\.mdx$/, '')); for (const file of before) { - if (!exists(`content/validators/changelog/${file}`)) { + if (!exists(`content/validators/changelog/${file}`) && !isSuperseded(file.replace(/\.mdx$/, ''), now)) { fail(`changelog: ${file} is on ${baseRef} but was removed (is it missing in the node source?)`); } } diff --git a/scripts/generate-changelog.js b/scripts/generate-changelog.js index 6ca845b9..5c5b797e 100644 --- a/scripts/generate-changelog.js +++ b/scripts/generate-changelog.js @@ -2,6 +2,7 @@ const fs = require('fs'); const path = require('path'); +const { publishedVersions, parseVersion } = require('./lib/release-versions'); // A generator that cannot find its source or its target block fails the build. const fail = (msg) => { console.error(msg); process.exitCode = 1; }; @@ -18,37 +19,15 @@ function generateChangelog() { return; } - // Read all .mdx files from the changelog directory - const files = fs.readdirSync(changelogDir) + // The versions the docs publish, newest first (scripts/lib/release-versions.js): + // a release candidate shows until its stable release exists. + const names = fs.readdirSync(changelogDir) .filter(file => file.endsWith('.mdx')) - .sort((a, b) => { - // Sort versions in descending order (newest first) - // Handle both v0.x.x and v0.x.x-testnetXXX formats - const versionA = a.replace('.mdx', ''); - const versionB = b.replace('.mdx', ''); - - // Extract version parts for comparison - const parseVersion = (v) => { - const match = v.match(/v(\d+)\.(\d+)\.(\d+)(?:-testnet(\d+))?/); - if (!match) return [0, 0, 0, 0]; - return [ - parseInt(match[1]), - parseInt(match[2]), - parseInt(match[3]), - parseInt(match[4] || '999') // Regular versions come before testnet versions - ]; - }; - - const [majorA, minorA, patchA, testnetA] = parseVersion(versionA); - const [majorB, minorB, patchB, testnetB] = parseVersion(versionB); - - // Compare versions (descending order) - if (majorB !== majorA) return majorB - majorA; - if (minorB !== minorA) return minorB - minorA; - if (patchB !== patchA) return patchB - patchA; - return testnetB - testnetA; - }); - + .map(file => file.replace(/\.mdx$/, '')); + const files = publishedVersions(names, { testnet: true }).map(v => `${v}.mdx`); + const unknown = names.filter(n => !files.includes(`${n}.mdx`) && !parseVersion(n)); + if (unknown.length) fail(`Changelog files with an unknown version name: ${unknown.join(', ')}`); + // Read the content of each file const sections = []; for (const file of files) { diff --git a/scripts/lib/release-versions.js b/scripts/lib/release-versions.js new file mode 100644 index 00000000..d69ba5f8 --- /dev/null +++ b/scripts/lib/release-versions.js @@ -0,0 +1,73 @@ +// Which node versions the docs publish. One rule, used by the setup guide +// (update-setup-guide-versions.js), the changelog page (generate-changelog.js) +// and the sync check (check-node-sync.js). Tested by scripts/test-release-versions.js. +// +// The rule: +// - A stable release (v0.6.0) is always published. +// - A release candidate (v0.6.0-rc.2) is published while its version has no +// stable release yet, and only the newest rc of that version. It acts as the +// current release until the stable one exists. +// - Once v0.6.0 exists, every v0.6.0-rc.N disappears from the docs. +// - Testnet builds (v0.2.0-testnetNNN) are kept in the changelog page only. +// +// Versions come from the changelog files synced from genlayer-node +// (content/validators/changelog/.mdx). The node keeps one file per rc +// and never deletes them: the docs choose what to show. + +// v0.6.0, v0.6.0-rc.2, v0.2.0-testnet005 +const VERSION_RE = /^v(\d+)\.(\d+)\.(\d+)(?:-(rc)\.(\d+)|-(testnet)(\d+))?$/; + +function parseVersion(name) { + const m = name.match(VERSION_RE); + if (!m) return null; + return { + name, + core: `${m[1]}.${m[2]}.${m[3]}`, + parts: [Number(m[1]), Number(m[2]), Number(m[3])], + pre: m[4] || m[6] || null, // null for a stable release + preNumber: Number(m[5] || m[7] || 0), + }; +} + +// Newest first. A stable release sorts above its own pre-releases. +function compareDesc(a, b) { + for (let i = 0; i < 3; i++) { + if (a.parts[i] !== b.parts[i]) return b.parts[i] - a.parts[i]; + } + if (!a.pre !== !b.pre) return a.pre ? 1 : -1; + return b.preNumber - a.preNumber; +} + +/** + * The versions the docs publish, newest first. + * @param {string[]} names version names (changelog file names without .mdx) + * @param {{testnet?: boolean}} options testnet: keep testnet builds (changelog page) + */ +function publishedVersions(names, { testnet = false } = {}) { + const versions = names.map(parseVersion).filter(Boolean).sort(compareDesc); + const stable = new Set(versions.filter((v) => v.pre === null).map((v) => v.core)); + const newestRc = new Map(); + for (const v of versions) { + if (v.pre === 'rc' && !newestRc.has(v.core)) newestRc.set(v.core, v.name); + } + return versions + .filter((v) => { + if (v.pre === 'testnet') return testnet; + if (v.pre === 'rc') return !stable.has(v.core) && newestRc.get(v.core) === v.name; + return true; + }) + .map((v) => v.name); +} + +/** + * True when a changelog version may leave the docs: an rc is superseded once + * its stable release or a newer rc of the same version exists. + */ +function isSuperseded(name, names) { + const v = parseVersion(name); + if (!v || v.pre !== 'rc') return false; + return names.map(parseVersion).filter(Boolean) + .some((o) => o.core === v.core && (o.pre === null || (o.pre === 'rc' && o.preNumber > v.preNumber))); +} + +module.exports = { parseVersion, compareDesc, publishedVersions, isSuperseded }; diff --git a/scripts/setup-guide-versions.config.js b/scripts/setup-guide-versions.config.js deleted file mode 100644 index b9701381..00000000 --- a/scripts/setup-guide-versions.config.js +++ /dev/null @@ -1,19 +0,0 @@ -// Which node versions the setup guide names. Read by scripts/update-setup-guide-versions.js -// (npm run node-update-setup-guide), tested by scripts/test-setup-guide-versions.js. -// -// versionChannel: -// 'stable' The guide names stable releases only: the version list, the -// `export version=` line, and a list command that filters out -// release candidates (`grep -v "rc"`). Default. -// 'all' The guide names the newest version including release candidates -// (for example v0.6.0-rc.2), and the list command keeps them. -// -// Versions come from the changelog files synced from genlayer-node -// (content/validators/changelog/*.mdx). With 'all', a release candidate shows up -// only once the node repo has a changelog file for it (docs/changelog/v0.6.0-rc.2.md). -// -// Flipping the channel is a one-line docs PR; no node release is needed. - -module.exports = { - versionChannel: 'stable', -}; diff --git a/scripts/test-release-versions.js b/scripts/test-release-versions.js new file mode 100644 index 00000000..53ef8652 --- /dev/null +++ b/scripts/test-release-versions.js @@ -0,0 +1,121 @@ +#!/usr/bin/env node + +// Tests the release-version rule (scripts/lib/release-versions.js) and the +// setup guide generator that applies it. Run: npm run test:release-versions + +const assert = require('assert'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const { execFileSync } = require('child_process'); +const { publishedVersions, isSuperseded } = require('./lib/release-versions'); +const { updateSetupGuideVersions, LIST_COMMAND_TAIL } = require('./update-setup-guide-versions'); + +const GUIDE = `1. list + + \`\`\`sh copy + curl -s "https://example" | sed -n 's/x/y/p' | sort -ru | grep -v "rc" | head -n 5 + \`\`\` + + You should see a list like this + + \`\`\`sh + v0.0.1 + \`\`\` + + Typically, you will want to run the latest version + +2. Download + \`\`\`sh copy + export version=v0.0.1 # set your desired version here + \`\`\` +`; + +function guideFor(changelog, guide = GUIDE) { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'release-versions-')); + fs.mkdirSync(path.join(root, 'content/validators/changelog'), { recursive: true }); + fs.mkdirSync(path.join(root, 'pages/validators'), { recursive: true }); + for (const v of changelog) fs.writeFileSync(path.join(root, 'content/validators/changelog', `${v}.mdx`), ''); + const guidePath = path.join(root, 'pages/validators/setup-guide.mdx'); + fs.writeFileSync(guidePath, guide); + const before = process.exitCode; + updateSetupGuideVersions({ root }); + const failed = process.exitCode === 1 && before !== 1; + process.exitCode = before; + const out = fs.readFileSync(guidePath, 'utf8'); + fs.rmSync(root, { recursive: true, force: true }); + return { guide: out, failed }; +} + +const STABLE = ['v0.2.0-testnet005', 'v0.5.8-rc.4', 'v0.5.8', 'v0.5.9', 'v0.5.12', 'v0.5.13', 'v0.5.14']; + +const tests = { + 'only stable releases: newest stable, numeric order, old rcs and testnets hidden': () => { + assert.deepStrictEqual(publishedVersions(STABLE), ['v0.5.14', 'v0.5.13', 'v0.5.12', 'v0.5.9', 'v0.5.8']); + const { guide, failed } = guideFor(STABLE); + assert.ok(!failed); + assert.match(guide, /export version=v0\.5\.14 #/); + }, + 'during an rc: the newest rc is the current release, older rcs hidden': () => { + const names = [...STABLE, 'v0.6.0-rc.1', 'v0.6.0-rc.2']; + assert.deepStrictEqual(publishedVersions(names).slice(0, 3), ['v0.6.0-rc.2', 'v0.5.14', 'v0.5.13']); + const { guide } = guideFor(names); + assert.match(guide, /export version=v0\.6\.0-rc\.2 #/); + assert.match(guide, /```sh\n v0\.6\.0-rc\.2\n v0\.5\.14\n v0\.5\.13\n v0\.5\.12\n v0\.5\.9\n ```/); + }, + 'rc.10 is newer than rc.9': () => { + assert.strictEqual(publishedVersions(['v0.6.0-rc.9', 'v0.6.0-rc.10'])[0], 'v0.6.0-rc.10'); + }, + 'after the stable release: every rc of that version disappears': () => { + const names = [...STABLE, 'v0.6.0-rc.1', 'v0.6.0-rc.2', 'v0.6.0']; + const published = publishedVersions(names); + assert.strictEqual(published[0], 'v0.6.0'); + assert.ok(!published.some((v) => v.startsWith('v0.6.0-rc'))); + assert.match(guideFor(names).guide, /export version=v0\.6\.0 #/); + }, + 'an rc of the next version shows above the current stable': () => { + const names = ['v0.6.0', 'v0.6.1-rc.1']; + assert.deepStrictEqual(publishedVersions(names), ['v0.6.1-rc.1', 'v0.6.0']); + }, + 'the changelog page keeps testnet builds but hides superseded rcs': () => { + assert.deepStrictEqual(publishedVersions(['v0.2.0-testnet005', 'v0.5.8-rc.4', 'v0.5.8'], { testnet: true }), ['v0.5.8', 'v0.2.0-testnet005']); + }, + 'superseded: an rc may leave once its stable or a newer rc exists, a stable never': () => { + assert.ok(isSuperseded('v0.6.0-rc.1', ['v0.6.0-rc.2'])); + assert.ok(isSuperseded('v0.6.0-rc.2', ['v0.6.0'])); + assert.ok(!isSuperseded('v0.6.0-rc.2', ['v0.6.0-rc.1', 'v0.5.14'])); + assert.ok(!isSuperseded('v0.5.14', ['v0.6.0'])); + }, + 'the list command applies the same rule in shell': () => { + const guide = guideFor(STABLE).guide; + assert.ok(guide.includes(LIST_COMMAND_TAIL)); + assert.doesNotMatch(guide, /grep -v "rc"/); + const tail = LIST_COMMAND_TAIL.replace(/^\| /, ''); + const run = (list) => execFileSync('sh', ['-c', `printf '${list.join('\\n')}\\n' | ${tail}`], { encoding: 'utf8' }).trim().split('\n'); + // The shell command applies the same rule as publishedVersions. + const during = ['v0.5.9', 'v0.5.14-rc.1', 'v0.5.14', 'v0.6.0-rc.1', 'v0.6.0-rc.2']; + assert.deepStrictEqual(run(during), publishedVersions(during)); + const after = [...during, 'v0.6.0', 'v0.6.1-rc.1']; + assert.deepStrictEqual(run(after), publishedVersions(after)); + assert.deepStrictEqual(run(after), ['v0.6.1-rc.1', 'v0.6.0', 'v0.5.14', 'v0.5.9']); + }, + 'the generator is idempotent': () => { + const once = guideFor([...STABLE, 'v0.6.0-rc.2']).guide; + assert.strictEqual(guideFor([...STABLE, 'v0.6.0-rc.2'], once).guide, once); + }, + 'no versions fails the generator': () => { + assert.ok(guideFor([]).failed); + }, +}; + +let failures = 0; +for (const [name, fn] of Object.entries(tests)) { + try { + fn(); + console.log(`ok - ${name}`); + } catch (e) { + failures++; + console.error(`not ok - ${name}\n ${e.message}`); + } +} +process.exitCode = failures ? 1 : 0; diff --git a/scripts/test-setup-guide-versions.js b/scripts/test-setup-guide-versions.js deleted file mode 100644 index 6138fc33..00000000 --- a/scripts/test-setup-guide-versions.js +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/env node - -// Tests scripts/update-setup-guide-versions.js on both values of versionChannel -// (scripts/setup-guide-versions.config.js). Run: npm run test:setup-guide-versions - -const assert = require('assert'); -const fs = require('fs'); -const os = require('os'); -const path = require('path'); -const { updateSetupGuideVersions, selectVersions } = require('./update-setup-guide-versions'); -const config = require('./setup-guide-versions.config'); - -const CHANGELOG = [ - 'v0.2.0-testnet005', 'v0.5.9', 'v0.5.12', 'v0.5.13', 'v0.5.14', - 'v0.6.0-rc.1', 'v0.6.0-rc.2', 'v0.5.8-rc.4', -]; - -const GUIDE = `1. list - - \`\`\`sh copy - curl -s "https://example" | sed -n 's/x/y/p' | sort -ru | grep -v "rc" | head -n 5 - \`\`\` - - You should see a list like this - - \`\`\`sh - v0.0.1 - \`\`\` - - Typically, you will want to run the latest version - -2. Download - \`\`\`sh copy - export version=v0.0.1 # set your desired version here - \`\`\` -`; - -function run(channel, changelog = CHANGELOG) { - const root = fs.mkdtempSync(path.join(os.tmpdir(), 'setup-guide-versions-')); - fs.mkdirSync(path.join(root, 'content/validators/changelog'), { recursive: true }); - fs.mkdirSync(path.join(root, 'pages/validators'), { recursive: true }); - for (const v of changelog) fs.writeFileSync(path.join(root, 'content/validators/changelog', `${v}.mdx`), ''); - const guidePath = path.join(root, 'pages/validators/setup-guide.mdx'); - fs.writeFileSync(guidePath, GUIDE); - const exitCode = process.exitCode; - updateSetupGuideVersions({ root, channel }); - const failed = process.exitCode === 1 && exitCode !== 1; - process.exitCode = exitCode; - const guide = fs.readFileSync(guidePath, 'utf8'); - fs.rmSync(root, { recursive: true, force: true }); - return { guide, failed }; -} - -const tests = { - 'committed default is stable': () => { - assert.strictEqual(config.versionChannel, 'stable'); - }, - 'stable: newest stable release, numeric order, no rc or testnet': () => { - assert.deepStrictEqual(selectVersions(CHANGELOG, 'stable'), ['v0.5.14', 'v0.5.13', 'v0.5.12', 'v0.5.9']); - const { guide, failed } = run('stable'); - assert.ok(!failed); - assert.match(guide, /export version=v0\.5\.14 #/); - assert.match(guide, /```sh\n v0\.5\.14\n v0\.5\.13\n v0\.5\.12\n v0\.5\.9\n ```/); - assert.match(guide, /\| sort -ruV \| grep -v "rc" \| head -n 5/); - }, - 'all: newest version including rc, rc.2 above rc.1': () => { - assert.deepStrictEqual(selectVersions(CHANGELOG, 'all').slice(0, 3), ['v0.6.0-rc.2', 'v0.6.0-rc.1', 'v0.5.14']); - const { guide, failed } = run('all'); - assert.ok(!failed); - assert.match(guide, /export version=v0\.6\.0-rc\.2 #/); - assert.match(guide, /```sh\n v0\.6\.0-rc\.2\n v0\.6\.0-rc\.1\n v0\.5\.14\n v0\.5\.13\n v0\.5\.12\n ```/); - assert.match(guide, /\| sort -ruV \| head -n 5/); - assert.doesNotMatch(guide, /grep -v "rc"/); - }, - 'all: a stable release sorts above its own rcs': () => { - assert.deepStrictEqual(selectVersions([...CHANGELOG, 'v0.6.0'], 'all').slice(0, 3), ['v0.6.0', 'v0.6.0-rc.2', 'v0.6.0-rc.1']); - }, - 'switching all -> stable restores the stable guide': () => { - const root = fs.mkdtempSync(path.join(os.tmpdir(), 'setup-guide-versions-')); - fs.mkdirSync(path.join(root, 'content/validators/changelog'), { recursive: true }); - fs.mkdirSync(path.join(root, 'pages/validators'), { recursive: true }); - for (const v of CHANGELOG) fs.writeFileSync(path.join(root, 'content/validators/changelog', `${v}.mdx`), ''); - const guidePath = path.join(root, 'pages/validators/setup-guide.mdx'); - fs.writeFileSync(guidePath, GUIDE); - updateSetupGuideVersions({ root, channel: 'all' }); - updateSetupGuideVersions({ root, channel: 'stable' }); - const guide = fs.readFileSync(guidePath, 'utf8'); - fs.rmSync(root, { recursive: true, force: true }); - assert.match(guide, /export version=v0\.5\.14 #/); - assert.match(guide, /\| sort -ruV \| grep -v "rc" \| head -n 5/); - }, - 'an unknown channel fails the generator': () => { - assert.throws(() => selectVersions(CHANGELOG, 'beta'), /versionChannel must be one of stable, all/); - assert.ok(run('beta').failed); - }, - 'stable with only release candidates fails the generator': () => { - assert.ok(run('stable', ['v0.6.0-rc.1']).failed); - }, -}; - -let failures = 0; -for (const [name, fn] of Object.entries(tests)) { - try { - fn(); - console.log(`ok - ${name}`); - } catch (e) { - failures++; - console.error(`not ok - ${name}\n ${e.message}`); - } -} -process.exitCode = failures ? 1 : 0; diff --git a/scripts/update-setup-guide-versions.js b/scripts/update-setup-guide-versions.js index 640e52b0..719e022b 100644 --- a/scripts/update-setup-guide-versions.js +++ b/scripts/update-setup-guide-versions.js @@ -2,66 +2,23 @@ const fs = require('fs'); const path = require('path'); +const { publishedVersions } = require('./lib/release-versions'); // A generator that cannot find its source or its target block fails the build. const fail = (msg) => { console.error(msg); process.exitCode = 1; }; -const CHANNELS = ['stable', 'all']; - -// v0.6.0, v0.6.0-rc.2, v0.2.0-testnet005 -const VERSION_RE = /^v(\d+)\.(\d+)\.(\d+)(?:-(rc)\.(\d+)|-(testnet)(\d+))?$/; - -function parseVersion(v) { - const m = v.match(VERSION_RE); - if (!m) return null; - const pre = m[4] || m[6] || null; - return { - name: v, - core: [Number(m[1]), Number(m[2]), Number(m[3])], - pre, // null for a stable release - preNumber: Number(m[5] || m[7] || 0), - }; -} - -// Newest first. A stable release sorts above its own pre-releases. -function compareDesc(a, b) { - for (let i = 0; i < 3; i++) { - if (a.core[i] !== b.core[i]) return b.core[i] - a.core[i]; - } - if (!a.pre !== !b.pre) return a.pre ? 1 : -1; - return b.preNumber - a.preNumber; -} - -/** - * Select the versions the guide names for a channel. - * Testnet builds (v0.2.0-testnetNNN) are never named. Release candidates are - * named only on the 'all' channel. - */ -function selectVersions(names, channel) { - if (!CHANNELS.includes(channel)) { - throw new Error(`versionChannel must be one of ${CHANNELS.join(', ')}, got ${JSON.stringify(channel)}`); - } - return names - .map(parseVersion) - .filter(Boolean) - .filter((v) => v.pre !== 'testnet' && (channel === 'all' || v.pre === null)) - .sort(compareDesc) - .map((v) => v.name); -} - -/** The list command shown in the guide, as the channel would run it. */ -function listCommandTail(channel) { - return channel === 'all' ? '| sort -ruV | head -n 5' : '| sort -ruV | grep -v "rc" | head -n 5'; -} +// The tail of the guide's list command, the same rule in shell: the sed pair +// makes the version sort put v0.6.0-rc.N below v0.6.0; awk drops an rc once its +// stable release exists and keeps only the newest rc of an unreleased version. +const LIST_COMMAND_TAIL = `| sed 's/-rc/~rc/' | sort -ruV | sed 's/~rc/-rc/' | awk '{c=$0; sub(/-rc\\..*/,"",c)} /-rc\\./{if ((c in s) || (c in r)) next; r[c]=1} !/-rc\\./{s[c]=1} {print}' | head -n 5`; /** * Update the setup guide's version list, list command and `export version=` line. - * @param {{root?: string, channel?: string}} options + * The guide names the newest published version (scripts/lib/release-versions.js): + * the newest release candidate while its stable release does not exist yet. + * @param {{root?: string}} options */ -function updateSetupGuideVersions({ root = process.cwd(), channel } = {}) { - if (channel === undefined) { - channel = require('./setup-guide-versions.config').versionChannel; - } +function updateSetupGuideVersions({ root = process.cwd() } = {}) { const changelogDir = path.join(root, 'content/validators/changelog'); const setupGuidePath = path.join(root, 'pages/validators/setup-guide.mdx'); @@ -77,16 +34,9 @@ function updateSetupGuideVersions({ root = process.cwd(), channel } = {}) { const names = fs.readdirSync(changelogDir) .filter((file) => file.endsWith('.mdx')) .map((file) => file.replace(/\.mdx$/, '')); - - let versions; - try { - versions = selectVersions(names, channel); - } catch (e) { - fail(e.message); - return; - } + const versions = publishedVersions(names); if (versions.length === 0) { - fail(`No ${channel} version files found in changelog directory`); + fail('No version files found in changelog directory'); return; } @@ -102,10 +52,10 @@ function updateSetupGuideVersions({ root = process.cwd(), channel } = {}) { fail('Could not find version list pattern in setup guide'); } - // The tail of the list command: version sort, rc filter (stable only), first 5. - const listCommandPattern = /\| sort -ruV?(?: \| grep -v "rc")? \| head -n 5/; + // Everything after the sed that extracts the version names. + const listCommandPattern = /\| (?:sed 's\/-rc\/~rc\/' \| )?sort -ruV?[^\n]*?\| head -n 5/; if (listCommandPattern.test(content)) { - content = content.replace(listCommandPattern, () => listCommandTail(channel)); + content = content.replace(listCommandPattern, () => LIST_COMMAND_TAIL); } else { fail('Could not find the version list command (| sort -ru... | head -n 5) in setup guide'); } @@ -118,11 +68,11 @@ function updateSetupGuideVersions({ root = process.cwd(), channel } = {}) { } fs.writeFileSync(setupGuidePath, content); - console.log(`Updated setup guide (${channel} channel) with ${versions.length} versions, latest: ${latestVersion}`); + console.log(`Updated setup guide with ${versions.length} published versions, latest: ${latestVersion}`); } if (require.main === module) { updateSetupGuideVersions(); } -module.exports = { updateSetupGuideVersions, selectVersions, listCommandTail, CHANNELS }; +module.exports = { updateSetupGuideVersions, LIST_COMMAND_TAIL }; From 4502a4c927e8224fbea011d375f87ee9dd6ceae7 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 18:05:36 +0200 Subject: [PATCH 18/39] docs(validators): rc Docker images are linux/amd64 only Co-Authored-By: Claude Opus 5.5 --- pages/validators/setup-guide.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index aba596a8..9fb40440 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -831,7 +831,7 @@ Once you have configured everything, you are ready to start the node. #### Running the Node using docker-compose -You can also run the GenLayer node using Docker and Docker Compose. +You can also run the GenLayer node using Docker and Docker Compose. Release candidate images (`v0.6.0-rc.N`) are published for `linux/amd64` only; stable images also cover `linux/arm64`. Before the first start, in the directory with your `docker-compose.yaml`: From 0adfda7b9f72c54211442f15e57701f0387be107 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 19:23:22 +0200 Subject: [PATCH 19/39] docs(validators): changelog page shows every rc of an unreleased version Each rc changelog lists only what changed since the previous rc (v0.6.0-rc.1 holds the v0.6 release notes), so hiding older rcs would drop most of the notes. The setup guide still names only the newest rc. An rc entry may leave the docs only once its stable release exists. Co-Authored-By: Claude Opus 5.5 --- scripts/generate-changelog.js | 2 +- scripts/lib/release-versions.js | 18 ++++++++++-------- scripts/test-release-versions.js | 11 +++++++---- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/scripts/generate-changelog.js b/scripts/generate-changelog.js index 5c5b797e..0d0ea5ec 100644 --- a/scripts/generate-changelog.js +++ b/scripts/generate-changelog.js @@ -24,7 +24,7 @@ function generateChangelog() { const names = fs.readdirSync(changelogDir) .filter(file => file.endsWith('.mdx')) .map(file => file.replace(/\.mdx$/, '')); - const files = publishedVersions(names, { testnet: true }).map(v => `${v}.mdx`); + const files = publishedVersions(names, { testnet: true, allRcs: true }).map(v => `${v}.mdx`); const unknown = names.filter(n => !files.includes(`${n}.mdx`) && !parseVersion(n)); if (unknown.length) fail(`Changelog files with an unknown version name: ${unknown.join(', ')}`); diff --git a/scripts/lib/release-versions.js b/scripts/lib/release-versions.js index d69ba5f8..0429f4f2 100644 --- a/scripts/lib/release-versions.js +++ b/scripts/lib/release-versions.js @@ -5,8 +5,10 @@ // The rule: // - A stable release (v0.6.0) is always published. // - A release candidate (v0.6.0-rc.2) is published while its version has no -// stable release yet, and only the newest rc of that version. It acts as the -// current release until the stable one exists. +// stable release yet. It acts as the current release until the stable one exists: +// - the setup guide names only the newest rc of that version; +// - the changelog page shows every rc of that version, because each rc's +// changelog lists only what changed since the previous rc. // - Once v0.6.0 exists, every v0.6.0-rc.N disappears from the docs. // - Testnet builds (v0.2.0-testnetNNN) are kept in the changelog page only. // @@ -41,9 +43,10 @@ function compareDesc(a, b) { /** * The versions the docs publish, newest first. * @param {string[]} names version names (changelog file names without .mdx) - * @param {{testnet?: boolean}} options testnet: keep testnet builds (changelog page) + * @param {{testnet?: boolean, allRcs?: boolean}} options for the changelog page: + * testnet keeps testnet builds, allRcs keeps every rc of an unreleased version */ -function publishedVersions(names, { testnet = false } = {}) { +function publishedVersions(names, { testnet = false, allRcs = false } = {}) { const versions = names.map(parseVersion).filter(Boolean).sort(compareDesc); const stable = new Set(versions.filter((v) => v.pre === null).map((v) => v.core)); const newestRc = new Map(); @@ -53,7 +56,7 @@ function publishedVersions(names, { testnet = false } = {}) { return versions .filter((v) => { if (v.pre === 'testnet') return testnet; - if (v.pre === 'rc') return !stable.has(v.core) && newestRc.get(v.core) === v.name; + if (v.pre === 'rc') return !stable.has(v.core) && (allRcs || newestRc.get(v.core) === v.name); return true; }) .map((v) => v.name); @@ -61,13 +64,12 @@ function publishedVersions(names, { testnet = false } = {}) { /** * True when a changelog version may leave the docs: an rc is superseded once - * its stable release or a newer rc of the same version exists. + * the stable release of its version exists. */ function isSuperseded(name, names) { const v = parseVersion(name); if (!v || v.pre !== 'rc') return false; - return names.map(parseVersion).filter(Boolean) - .some((o) => o.core === v.core && (o.pre === null || (o.pre === 'rc' && o.preNumber > v.preNumber))); + return names.map(parseVersion).filter(Boolean).some((o) => o.core === v.core && o.pre === null); } module.exports = { parseVersion, compareDesc, publishedVersions, isSuperseded }; diff --git a/scripts/test-release-versions.js b/scripts/test-release-versions.js index 53ef8652..d4774e6c 100644 --- a/scripts/test-release-versions.js +++ b/scripts/test-release-versions.js @@ -77,11 +77,14 @@ const tests = { const names = ['v0.6.0', 'v0.6.1-rc.1']; assert.deepStrictEqual(publishedVersions(names), ['v0.6.1-rc.1', 'v0.6.0']); }, - 'the changelog page keeps testnet builds but hides superseded rcs': () => { - assert.deepStrictEqual(publishedVersions(['v0.2.0-testnet005', 'v0.5.8-rc.4', 'v0.5.8'], { testnet: true }), ['v0.5.8', 'v0.2.0-testnet005']); + 'the changelog page keeps testnet builds and every rc of an unreleased version': () => { + const opts = { testnet: true, allRcs: true }; + assert.deepStrictEqual(publishedVersions(['v0.2.0-testnet005', 'v0.5.8-rc.4', 'v0.5.8'], opts), ['v0.5.8', 'v0.2.0-testnet005']); + assert.deepStrictEqual(publishedVersions(['v0.5.14', 'v0.6.0-rc.1', 'v0.6.0-rc.2'], opts), ['v0.6.0-rc.2', 'v0.6.0-rc.1', 'v0.5.14']); + assert.deepStrictEqual(publishedVersions(['v0.5.14', 'v0.6.0-rc.1', 'v0.6.0-rc.2', 'v0.6.0'], opts), ['v0.6.0', 'v0.5.14']); }, - 'superseded: an rc may leave once its stable or a newer rc exists, a stable never': () => { - assert.ok(isSuperseded('v0.6.0-rc.1', ['v0.6.0-rc.2'])); + 'superseded: an rc may leave once its stable exists, a stable never': () => { + assert.ok(!isSuperseded('v0.6.0-rc.1', ['v0.6.0-rc.2'])); assert.ok(isSuperseded('v0.6.0-rc.2', ['v0.6.0'])); assert.ok(!isSuperseded('v0.6.0-rc.2', ['v0.6.0-rc.1', 'v0.5.14'])); assert.ok(!isSuperseded('v0.5.14', ['v0.6.0'])); From c3373a7dfa6831b67eb7ad905250ba7fe6baa3d5 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 19:23:22 +0200 Subject: [PATCH 20/39] docs(validators): sync node v0.6-dev at ec51b80c6 (genlayer-node#2267 merged) Result of `scripts/simulate-node-sync.sh ec51b80c6`: adds the v0.6.0-rc.1 and v0.6.0-rc.2 changelogs. The setup guide now names v0.6.0-rc.2. Co-Authored-By: Claude Opus 5.5 --- content/validators/changelog/v0.6.0-rc.1.mdx | 158 ++++++++++++++++ content/validators/changelog/v0.6.0-rc.2.mdx | 25 +++ pages/validators/changelog.mdx | 185 +++++++++++++++++++ pages/validators/setup-guide.mdx | 4 +- 4 files changed, 370 insertions(+), 2 deletions(-) create mode 100644 content/validators/changelog/v0.6.0-rc.1.mdx create mode 100644 content/validators/changelog/v0.6.0-rc.2.mdx diff --git a/content/validators/changelog/v0.6.0-rc.1.mdx b/content/validators/changelog/v0.6.0-rc.1.mdx new file mode 100644 index 00000000..8e5bfa0d --- /dev/null +++ b/content/validators/changelog/v0.6.0-rc.1.mdx @@ -0,0 +1,158 @@ +## v0.6.0-rc.1 + +v0.6 is a breaking release. It runs only against the v0.6 consensus contracts (release tag `v0.6-rc-1`), and all validators upgrade together: receipt hashes, storage-fee votes, spendable contract balance and child budgets change. + +### New features + +- Check at start-up and in `doctor` that the consensus deployment matches the trusted protocol manifest of the network, and refuse a mismatch; new required `consensus.manifestpath` and `consensus.chainid`, optional `consensus.admissiontimeout` (default 15s) +- Serve as a tribunal juror: re-run the accused execution, commit and reveal the tribunal vote, and after a restart reveal a committed vote and vote in tribunals that opened while the node was down; log an error when this validator is the accused leader +- After a tribunal verdict, settle its penalty pages and its quarantine cause +- As leader, propose receipts that emit messages through the v0.6 message envelope +- Deliver the messages that accepted and finalized transactions still owe, in turns with the other validators that voted +- Retry an external message that the chain paused, with a backoff from 30 s up to 10 min +- Execute transactions under the v0.6 fee model: fee values pinned at activation, storage and receipt budgets, message fee limits and fee allocations; the leader proposes the storage fee the execution used +- When the chain rejects a leader receipt as over a fee or size limit, propose a leader timeout instead, so the leader gets no idleness strike +- Vote `DeterministicViolation`, instead of not voting, when the leader receipt is missing data or cannot be used, or reports a storage fee the execution did not use +- Prove activations and leader proposals with an ECVRF proof made with the operator key; a validator whose signer cannot make ECVRF proofs does not start +- After a restart, resume the duties this node accepted but did not finish: activate, propose, commit, reveal, appeal vote, message delivery and tribunal vote +- Deliver consensus events to the duty handlers at least once; new `node.streamer` settings `ack_wait_seconds` (30), `max_ack_pending` (256) and `buffer_size` (2048) +- Refuse to start when the transaction layout of the consensus contracts differs from the node +- Bind `ops.db` to `node.operatorAddress` on the first start, and refuse to start when it belongs to another operator (for example a copied data directory) +- Add opt-in operational keys for the duties that do not need the operator key (`consensus.operational_keys`: `enabled`, `pool_size` 1, `low_watermark` 0.05, `target_balance` 0.25, `interval` 5m); the node derives them from the operator key and tops them up from the operator account +- Add opt-in parallel replay of independent transactions (`genvm.replay.parallel_enabled`, default off; `workers` 4, `consensus_reserve_permits` 2, `replay_floor_permits` 1, `max_journal_bytes` 256 MiB) +- Roll back replayed state exactly on chain reorgs (`genvm.replay.reorg_history_blocks`, default 4096; `genvm.replay.max_reorg_journal_bytes`, default 256 MiB) +- Add the `gen_getLogs` event query endpoint, with a `status` filter (logs of appeal-cancelled rounds need `status: ["cancelled"]`) and the limits `node.rpc.getlogs_max_block_range` and `node.rpc.getlogs_max_results` (10,000 each) +- Add `gen_subscribe` and `gen_unsubscribe` on the RPC WebSocket, with the streams `transactions`, `leadership`, `epochs` and `newHeads` +- Add the `gen_getTransactionLifecycle` endpoint: projected status, resolution action, decision id and `executionGeneration` +- Add the `gen_getTransactionData` endpoint: the on-chain data of a transaction round, plus its `returnData` and `leaderPublicData` from the local trace store (`null` once pruned) +- Add a `fees` object to `gen_getTransactionReceipt`: deposit, distribution, locked prices, consumed counters (including `storageFeeUsed`), `allocations` and `allocationPolicy` +- Add events store retention (`node.events.retention_blocks`, default 1,000,000) +- Add trace store limits: `node.trace.retention_blocks` (1,000,000), `max_trace_bytes` (32 MB) and `max_debug_field_bytes` (256 KB per `gen_dbg_traceTransaction` field) +- Add the `node.rpc.contract_read_max_bytes` limit (16 MB) for `gen_getContractCode` and `gen_getContractState` +- Add `genvm.sync_permits_lag_threshold` (default 1000) to choose between the replay and live GenVM permits +- Add `rollup.rpc_response_header_timeout` (default 30s), `node.rpc.request_timeout` (default 180s) and `consensus.head_pin_freshness` (default 30s) +- Add `node.rpc.gen_call_max_debug_bytes` (default 1 MB) and `node.rpc.gen_call_max_debug_elements` (default 10,000) to limit `gen_call` debug output +- Add an opt-in allowlist for proxied `eth_*` and `zks_*` methods: disable the group and enable only the methods listed under it +- Add a `scope` (`all` default, `genvm`, `ops`) and a `snapshot.json` manifest to `POST /snapshot` +- Report the operator balance in `/health` and `doctor`: warn below `node.operatorBalanceWarnWei` (default 0.1 GEN), fail below `node.operatorBalanceCriticalWei` (default 0.01 GEN) +- Add a Validator Participation section to `doctor`: ban, self-stake, operator balance, sync and primed state +- Show `validator_address` and `validator_moniker` in `/health`, and report the health conditions in `doctor` and the logs +- Read the chain RPC API key from `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY` and send it in the `rollup.genlayerchainrpckeyheader` header (default `X-AccessKey`) +- Publish signed checksums (`genlayer-node__SHA256SUMS` with a Sigstore bundle) and an `install.sh` script with each release +- Add gas, duty and fee metrics: `genlayer_node_gas_*`, `genlayer_node_consensus_submit_total`, `genlayer_node_consensus_revert_total`, `genlayer_node_operator_misconfigured_total`, `genlayer_node_fees_operator_insufficient_funds_total`, `genlayer_node_fees_settlement_events_total`, `genlayer_node_fees_distributed_wei_total`, `genlayer_node_fees_earned_wei_total` and `genlayer_node_fees_penalties_wei_total` +- Add metrics `genlayer_node_operator_condition_*`, `genlayer_node_external_message_delivery_total`, `genlayer_node_external_message_reimbursed_wei_total`, `genlayer_node_sync_consecutive_failures`, `genlayer_node_sync_stuck_block`, `genlayer_node_prime_blocked_insufficient_funds`, `genlayer_node_transact_keyed_replacements_total`, `genlayer_node_event_dispatch_*_total`, `genlayer_node_replay_*`, `genlayer_node_head_partition_*_total` and `genlayer_node_pubsub_messages_dropped_total` + +### Misc + +- Ship GenVM v0.6.0-rc10; the node uses the new GenVM manager protocol, so an older GenVM manager does not work (this matters only with `genvm.start_manager: false`) +- The GenVM runtime moves from `third_party/genvm` to `opt/genvm/`: set `genvm.root_dir: ./opt/genvm/v0.6.0-rc10` (`install.sh` sets it for you) +- Refuse to start when the config file is missing, is not a regular file, or has an empty required field: `datadir`, both rollup URLs, `consensus.consensusaddress`, `consensus.manifestpath`, `consensus.chainid`, and `genvm.root_dir` (or `genvm.manager_url` when `start_manager: false`) +- Warn about unknown config keys (`Ignoring unknown config keys`) +- RPC groups move from `node.rpc.endpoints.groups.` to `node.rpc.endpoints.`, and method overrides to `node.rpc.endpoints..methods.`; the node ignores the old form without an error and uses the defaults, so move your settings +- The `gen_dbg_*` methods are off unless `node.rpc.endpoints.genlayer_debug.enabled: true`; the example config also turns off the `zks_*` proxy +- Return `-32601` instead of `-32004` for a denied `eth_*` or `zks_*` method; a disabled `gen_*` method still returns `-32004` +- Replace the `/health` body: `status` is one of `unknown`, `healthy`, `suspect`, `warning`, `degraded` and `unavailable`, explained by a `conditions` catalog; `checks` and `genvm_modules` are removed +- `/health` returns HTTP 503 only for `unavailable`, so a node that catches up or is not primed yet now returns 200; a self-stake below the minimum or a critical operator balance sets `consensus.stake` or `consensus.operator_funds` to `unavailable` +- `gen_getTransactionStatus` returns the stored status only and ignores `timestamp` (use `gen_getTransactionLifecycle` for the projected status); `ReadyToFinalize` (11) is removed, so `ValidatorsTimeout`, `LeaderTimeout` and `LeaderRevealing` are now 11, 12 and 13 +- `previousStatus` in `gen_getTransactionReceipt` is always `null` +- The `status` parameter of `gen_call`, `gen_getContractState` and `gen_getContractCode` takes `decided` instead of `accepted` (default `decided`); `accepted` returns `-32602` +- `genvm.permits` and `genvm.sync_permits` are now permit budgets (a nondeterministic run costs 8, a sync run 4): leave them empty or multiply your old values by 4 (8 → 32, 64 → 256) +- Split the database into `genvm.db` (chain data) and `ops.db` (this node's own records); `/genlayer.db` is renamed to `genvm.db` at start-up, and start-up is refused when both exist +- `node.genlayerdb` is replaced by `node.genvmdb` and `node.opsdb`; move a custom database path by hand +- Prove VRF duties with the operator key registered before the transaction was created; after an operator key rotation, keep the old operator account in the keystore, with the same password, until its transactions are done +- Run the Docker image as the non-root user uid 10001; run `sudo chown -R 10001:10001 ./data` once before the first start +- Read WebDriver container stats only through `DOCKER_HOST`: the release compose adds a read-only Docker socket proxy and no longer mounts `docker.sock` into the node; a binary install sets `DOCKER_HOST`, else the collector is skipped +- The release compose file requires `NODE_PASSWORD` (the default `12345678` is removed) and gives it to the node as `GENLAYERNODE_PASSWORD`, not on the command line +- The release monitoring profile has no default `CENTRAL_*` endpoints or credentials: set all six in `.env` +- Clamp `consensus.chain_submit_buffer` at start-up so at least 5 s of the shortest on-chain phase window stays for execution, and log a warning when it does +- The `genlayer_node_disk_*` metrics report `directory="genvm.db"` and `directory="ops.db"` in place of `directory="genlayer.db"`; update dashboards and alerts +- Add a `reason` label (`http_unavailable`, `block_lag`, `rate_limited`) to `genlayer_rollup_rpc_ws_fallback_total` +- Signed GenVM web requests send the validator wallet address in the `genlayer-node-address` header, instead of the operator address +- Show contract revert errors with their argument names in logs +- Show module paths, not build-machine paths, in log `caller` fields and stack traces +- Update the release image base to Alpine 3.24.2, Grafana Alloy to v1.20.1 and the WebDriver image to 0.0.12, and add `tecnativa/docker-socket-proxy:0.3.0` + +### Bug fixes + +- Retry a chain HTTP request once after a dropped connection, a 502/503/504 or a rate limit; the transport does not retry timeouts and transaction sends +- Fall back to the WebSocket RPC when the HTTP RPC is down or does not answer in time, also for transaction submission and at start-up +- Report the RPC error, not a connection-closed error, when the WebSocket server closes right after it replies +- Keep syncing when the HTTP RPC lags the WebSocket head, instead of exiting after repeated `block not found` +- Keep every nonce owned until the chain settles it: a stuck nonce no longer blocks all later transactions until a restart, recorded transactions are sent again after a restart, and a nonce is freed or reused only on chain evidence +- Make the self-cancellation of a stuck nonce affordable on a small wallet, and outbid the previous cancellation when it is retried +- Finalize, idleness, epoch-advance and message-delivery transactions no longer cancel pending commit and reveal transactions +- Keep commit and reveal transactions on time when older pending transactions block them, and cancel transactions whose deadline passed or whose fee bumps are used up +- No longer mark a transaction rejected, and reuse its nonce, when the RPC does not show it as pending within 10 s +- Read the receipt before treating a timed-out transaction as failed, so a mined transaction is not sent again +- Fix a node crash (`concurrent map writes`) when several transactions fail at the same time, for example during parallel finalizations +- Keep committed votes and their salts across restarts and replayed events, so the reveal is not missed +- Retries no longer regenerate a commit vote or a proposed receipt, so a validator or leader whose first transaction already mined can still reveal +- A leader rotation delivered twice (for example at restart) no longer sends a second, reverting propose transaction +- A GenVM manager outage or restart during a duty no longer turns the transaction into a leader timeout; the duty waits for the manager within its phase +- Stop retrying a transaction that the operator wallet cannot pay for; when priming is blocked, log one error that names the operator wallet, and resume once it is funded +- Duty watchers no longer sleep through their turn when the chain clock runs ahead of the node clock +- Fail a transaction whose calldata cannot be decoded with an error receipt, instead of voting Timeout +- Stop a transient database read error or a missed sync event from making a synced node vote Timeout +- Vote Timeout instead of crashing when a node behind sync is missing transaction data +- Give intelligent contracts the real chain ID instead of `1` +- Set the contract `origin_address` to the transaction submitter instead of the activating validator +- Read EVM contract views for intelligent contracts at the activation block, so all validators see the same value +- Stop sending an extra transaction before each duty; read time-sensitive contract views at the pending block instead +- Drive pending tribunals to their end during epoch finalization, so an epoch no longer stays unfinalized behind them +- Re-run transactions that an appeal sends back for recomputation, from a clean round +- Stagger finalization: one validator at a time polls and submits, also for the predecessor drain and the epoch sweep, so validators stop reverting with `already finalized` +- Stop sending a duplicate finalize when the last reveal and the decision event arrive together +- Stop resubmitting a finalize that reverts permanently +- Epoch recovery sweep: force-process only stuck transactions, and bound each attempt, so a transaction that cannot finalize no longer stops epoch finalization +- Retry the epoch advance during each validator's turn, so epochs advance at their normal pace again +- Stop a validator from skipping an epoch's prime after a stale epoch read or a failed retry loop +- Stop paying for prime transactions that the contract ignores: after 5 in a row with no effect, only re-check until the next epoch +- Re-fetch blocks that a chain reorg replaced, so their consensus events are no longer lost +- Stop dropping consensus events that the log bloom check wrongly treated as false positives, and never move the sync cursor backwards (a synced node looked behind and lost its duties) +- Stop sync with an error that names the transaction, instead of silently skipping an accepted transaction that cannot be decoded +- Sync contract state for transactions accepted without a `TransactionAccepted` event, so contract reads stop failing with `pebble: not found` +- Do not restore contract state from an execution that an appeal replaced in the same block +- Fetch at most 16 transactions at a time during sync, so a node that catches up no longer floods its RPC provider +- Retry instead of rescanning from block 0 when the first-sync genesis lookup fails +- Wait for indexed data in `gen_call`, `gen_getContractState`, `gen_getContractCode` and `gen_dbg_traceTransaction` instead of failing while the node catches up (new `genvm.sync_wait_poll_interval`, default `100ms`) +- Stop crashes on empty or out-of-range data from the chain, and on malformed revert data +- Reject malformed GenVM storage changes instead of crashing the node +- Stop a crash from an oversized map-key length in contract calldata, and reject calldata with non-canonical length headers +- Fail the transaction instead of crashing on missing or malformed numbers in GenVM output and emitted messages +- Fix a GenVM cache race that could crash the node during sync, and data races on per-transaction and epoch state +- Fix a slow memory leak of finalized and canceled transactions +- Exit with a non-zero status after a panic, so Docker or systemd restarts the node +- Report the real error, not `context canceled`, when parallel work fails +- Stop logging `Can not process rotate leader` errors when the proposal of the replacement leader carries the rotation +- Allow full-mode nodes to start without a password +- Honor `node.rpc.host`; the RPC listener bound to all interfaces +- Raise the open-file limit to 65536 (or to the hard limit) at start-up, so GenVM stops failing with `Too many open files` +- Make `doctor` report a GenVM install that was moved after `setup.py` ran +- Omit `result` from JSON-RPC error responses +- Accept `gen_dbg_trie` transaction IDs with or without `0x`, and stop `gen_syncing` from reporting a huge `blocksBehind` when the synced block is ahead of the latest one + +### Removed + +- Remove the `node.environment` field and the development-only `node.dev.disableSubscription` switch; old configs still load, and the node logs the ignored keys +- Remove the `doctor --network` flag; `doctor` checks the deployment against the protocol manifest instead + +### Security + +- Keep the chain RPC API key out of logs, error responses, `doctor` output and the config dump; RPC URLs show only scheme and host +- Stop passing the `GENLAYERNODE_ROLLUP_*` variables (chain URLs and RPC key) and the operator token to GenVM processes +- Mask the operator token, the GenVM token and the chain RPC key in the config dump, and log RPC request params and results only at DEBUG +- Stop logging the node admin token in the GenVM manager request logs of the node +- Warn at start-up when `genvm.manager_url` is not a loopback address, because the requests carry the admin token +- Write `account new --export` and `account export` files straight to the target with owner-only permissions; the command fails if the file exists +- A validator refuses to start when its operator account cannot be resolved, instead of signing with a throwaway key +- Bound memory use: cap GenVM host-call frames at 64 MiB and the size of GenVM manager responses, and limit the concurrent GenVM runs that public RPC (`gen_call`, `gen_dbg_traceTransaction`) starts +- Stop a malformed leader payload from crashing validators +- Return generic messages for internal errors from the admin, ops and RPC APIs, and log the detail; a failed chain read in `gen_call` names only the read that failed +- Restrict the `destDir` of `POST /snapshot` to `node.admin.snapshot_dir` (default `/snapshots`); other paths return `400` +- Reduce the `rpc_url` label of `genlayer_node_info` to scheme and host +- Limit the `namespace` label of `genlayer_node_ws_inbound_subscriptions_active` to known namespaces, so clients cannot create unbounded metric series +- Cache `/balance` for 5 s, so public requests cannot multiply chain RPC calls +- Stop failures of proxied public `eth_*` and `zks_*` requests from opening the chain RPC circuit breaker that guards validator duties +- Drop all Linux capabilities from the node container in the release compose file +- Build with a newer Go release that includes the standard-library security fixes +- Update `golang.org/x/net` to v0.60.0 and `golang.org/x/crypto` to v0.57.0 diff --git a/content/validators/changelog/v0.6.0-rc.2.mdx b/content/validators/changelog/v0.6.0-rc.2.mdx new file mode 100644 index 00000000..a91abe12 --- /dev/null +++ b/content/validators/changelog/v0.6.0-rc.2.mdx @@ -0,0 +1,25 @@ +## v0.6.0-rc.2 + +### New features + +- Check WebDriver rendering in `/health` and `doctor` with the same fresh render of a test page (10 s limit); a failure sets the `webdriver.render` condition to `degraded` with reason `webdriver_check_failed` +- The release compose file restarts a WebDriver that stops rendering (after 4 failed health checks) and an Alloy that stays unhealthy; set `HEALTHCHECK_AUTO_RESTART=false` to keep the health checks and turn off the restarts +- The release monitoring profile sends the restart events of WebDriver and Alloy to central monitoring, with the `NETWORK_NAME` label from `.env` + +### Misc + +- `/health` no longer returns `severity` in `conditions`; use `state` for the current health +- The release compose file mounts the protocol manifest from `NODE_MANIFEST_PATH` (default `./configs/node/protocol-manifest.json`) at the path that `consensus.manifestpath` names +- The release package links `opt/genvm/current` to its GenVM tree, and its `config.yaml` sets `genvm.root_dir: ./opt/genvm/current` +- The example `.env` lists all 11 LLM provider keys of the release LLM config, and sets the monitoring defaults `METRICS_SCRAPE_INTERVAL=60s` and `LOKI_BATCH_WAIT=60s` +- The release README shows how to run `doctor` under Docker +- Update `tecnativa/docker-socket-proxy` to v0.5.0; the proxy also gives Alloy read-only network data +- Update `github.com/ethereum/go-ethereum` to v1.17.8, `labstack/echo` to v4.16.0, `golang.org/x/crypto` to v0.58.0 and `golang.org/x/net` to v0.61.0 + +### Bug fixes + +- Fix web access from contracts under the release compose file: the GenVM web config is mounted at the path GenVM reads, so GenVM uses the WebDriver container instead of `localhost` +- The release compose file no longer replaces the image entrypoint, so the first start precompiles GenVM for the host CPU (the first start takes longer) +- The example config sets `genvm.root_dir` to the shipped GenVM (`./opt/genvm/v0.6.0-rc10`), not `v0.6.0-rc1` +- Update the GenVM web config of the release to the schema and keys of the shipped GenVM +- Correct the API reference: `LeaderRevealing` is status `13`, and the vote values include `5` (`DeterministicViolation`) diff --git a/pages/validators/changelog.mdx b/pages/validators/changelog.mdx index fe05e979..7ae50e9e 100644 --- a/pages/validators/changelog.mdx +++ b/pages/validators/changelog.mdx @@ -1,5 +1,190 @@ # Changelog +## v0.6.0-rc.2 + +### New features + +- Check WebDriver rendering in `/health` and `doctor` with the same fresh render of a test page (10 s limit); a failure sets the `webdriver.render` condition to `degraded` with reason `webdriver_check_failed` +- The release compose file restarts a WebDriver that stops rendering (after 4 failed health checks) and an Alloy that stays unhealthy; set `HEALTHCHECK_AUTO_RESTART=false` to keep the health checks and turn off the restarts +- The release monitoring profile sends the restart events of WebDriver and Alloy to central monitoring, with the `NETWORK_NAME` label from `.env` + +### Misc + +- `/health` no longer returns `severity` in `conditions`; use `state` for the current health +- The release compose file mounts the protocol manifest from `NODE_MANIFEST_PATH` (default `./configs/node/protocol-manifest.json`) at the path that `consensus.manifestpath` names +- The release package links `opt/genvm/current` to its GenVM tree, and its `config.yaml` sets `genvm.root_dir: ./opt/genvm/current` +- The example `.env` lists all 11 LLM provider keys of the release LLM config, and sets the monitoring defaults `METRICS_SCRAPE_INTERVAL=60s` and `LOKI_BATCH_WAIT=60s` +- The release README shows how to run `doctor` under Docker +- Update `tecnativa/docker-socket-proxy` to v0.5.0; the proxy also gives Alloy read-only network data +- Update `github.com/ethereum/go-ethereum` to v1.17.8, `labstack/echo` to v4.16.0, `golang.org/x/crypto` to v0.58.0 and `golang.org/x/net` to v0.61.0 + +### Bug fixes + +- Fix web access from contracts under the release compose file: the GenVM web config is mounted at the path GenVM reads, so GenVM uses the WebDriver container instead of `localhost` +- The release compose file no longer replaces the image entrypoint, so the first start precompiles GenVM for the host CPU (the first start takes longer) +- The example config sets `genvm.root_dir` to the shipped GenVM (`./opt/genvm/v0.6.0-rc10`), not `v0.6.0-rc1` +- Update the GenVM web config of the release to the schema and keys of the shipped GenVM +- Correct the API reference: `LeaderRevealing` is status `13`, and the vote values include `5` (`DeterministicViolation`) + +## v0.6.0-rc.1 + +v0.6 is a breaking release. It runs only against the v0.6 consensus contracts (release tag `v0.6-rc-1`), and all validators upgrade together: receipt hashes, storage-fee votes, spendable contract balance and child budgets change. + +### New features + +- Check at start-up and in `doctor` that the consensus deployment matches the trusted protocol manifest of the network, and refuse a mismatch; new required `consensus.manifestpath` and `consensus.chainid`, optional `consensus.admissiontimeout` (default 15s) +- Serve as a tribunal juror: re-run the accused execution, commit and reveal the tribunal vote, and after a restart reveal a committed vote and vote in tribunals that opened while the node was down; log an error when this validator is the accused leader +- After a tribunal verdict, settle its penalty pages and its quarantine cause +- As leader, propose receipts that emit messages through the v0.6 message envelope +- Deliver the messages that accepted and finalized transactions still owe, in turns with the other validators that voted +- Retry an external message that the chain paused, with a backoff from 30 s up to 10 min +- Execute transactions under the v0.6 fee model: fee values pinned at activation, storage and receipt budgets, message fee limits and fee allocations; the leader proposes the storage fee the execution used +- When the chain rejects a leader receipt as over a fee or size limit, propose a leader timeout instead, so the leader gets no idleness strike +- Vote `DeterministicViolation`, instead of not voting, when the leader receipt is missing data or cannot be used, or reports a storage fee the execution did not use +- Prove activations and leader proposals with an ECVRF proof made with the operator key; a validator whose signer cannot make ECVRF proofs does not start +- After a restart, resume the duties this node accepted but did not finish: activate, propose, commit, reveal, appeal vote, message delivery and tribunal vote +- Deliver consensus events to the duty handlers at least once; new `node.streamer` settings `ack_wait_seconds` (30), `max_ack_pending` (256) and `buffer_size` (2048) +- Refuse to start when the transaction layout of the consensus contracts differs from the node +- Bind `ops.db` to `node.operatorAddress` on the first start, and refuse to start when it belongs to another operator (for example a copied data directory) +- Add opt-in operational keys for the duties that do not need the operator key (`consensus.operational_keys`: `enabled`, `pool_size` 1, `low_watermark` 0.05, `target_balance` 0.25, `interval` 5m); the node derives them from the operator key and tops them up from the operator account +- Add opt-in parallel replay of independent transactions (`genvm.replay.parallel_enabled`, default off; `workers` 4, `consensus_reserve_permits` 2, `replay_floor_permits` 1, `max_journal_bytes` 256 MiB) +- Roll back replayed state exactly on chain reorgs (`genvm.replay.reorg_history_blocks`, default 4096; `genvm.replay.max_reorg_journal_bytes`, default 256 MiB) +- Add the `gen_getLogs` event query endpoint, with a `status` filter (logs of appeal-cancelled rounds need `status: ["cancelled"]`) and the limits `node.rpc.getlogs_max_block_range` and `node.rpc.getlogs_max_results` (10,000 each) +- Add `gen_subscribe` and `gen_unsubscribe` on the RPC WebSocket, with the streams `transactions`, `leadership`, `epochs` and `newHeads` +- Add the `gen_getTransactionLifecycle` endpoint: projected status, resolution action, decision id and `executionGeneration` +- Add the `gen_getTransactionData` endpoint: the on-chain data of a transaction round, plus its `returnData` and `leaderPublicData` from the local trace store (`null` once pruned) +- Add a `fees` object to `gen_getTransactionReceipt`: deposit, distribution, locked prices, consumed counters (including `storageFeeUsed`), `allocations` and `allocationPolicy` +- Add events store retention (`node.events.retention_blocks`, default 1,000,000) +- Add trace store limits: `node.trace.retention_blocks` (1,000,000), `max_trace_bytes` (32 MB) and `max_debug_field_bytes` (256 KB per `gen_dbg_traceTransaction` field) +- Add the `node.rpc.contract_read_max_bytes` limit (16 MB) for `gen_getContractCode` and `gen_getContractState` +- Add `genvm.sync_permits_lag_threshold` (default 1000) to choose between the replay and live GenVM permits +- Add `rollup.rpc_response_header_timeout` (default 30s), `node.rpc.request_timeout` (default 180s) and `consensus.head_pin_freshness` (default 30s) +- Add `node.rpc.gen_call_max_debug_bytes` (default 1 MB) and `node.rpc.gen_call_max_debug_elements` (default 10,000) to limit `gen_call` debug output +- Add an opt-in allowlist for proxied `eth_*` and `zks_*` methods: disable the group and enable only the methods listed under it +- Add a `scope` (`all` default, `genvm`, `ops`) and a `snapshot.json` manifest to `POST /snapshot` +- Report the operator balance in `/health` and `doctor`: warn below `node.operatorBalanceWarnWei` (default 0.1 GEN), fail below `node.operatorBalanceCriticalWei` (default 0.01 GEN) +- Add a Validator Participation section to `doctor`: ban, self-stake, operator balance, sync and primed state +- Show `validator_address` and `validator_moniker` in `/health`, and report the health conditions in `doctor` and the logs +- Read the chain RPC API key from `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY` and send it in the `rollup.genlayerchainrpckeyheader` header (default `X-AccessKey`) +- Publish signed checksums (`genlayer-node__SHA256SUMS` with a Sigstore bundle) and an `install.sh` script with each release +- Add gas, duty and fee metrics: `genlayer_node_gas_*`, `genlayer_node_consensus_submit_total`, `genlayer_node_consensus_revert_total`, `genlayer_node_operator_misconfigured_total`, `genlayer_node_fees_operator_insufficient_funds_total`, `genlayer_node_fees_settlement_events_total`, `genlayer_node_fees_distributed_wei_total`, `genlayer_node_fees_earned_wei_total` and `genlayer_node_fees_penalties_wei_total` +- Add metrics `genlayer_node_operator_condition_*`, `genlayer_node_external_message_delivery_total`, `genlayer_node_external_message_reimbursed_wei_total`, `genlayer_node_sync_consecutive_failures`, `genlayer_node_sync_stuck_block`, `genlayer_node_prime_blocked_insufficient_funds`, `genlayer_node_transact_keyed_replacements_total`, `genlayer_node_event_dispatch_*_total`, `genlayer_node_replay_*`, `genlayer_node_head_partition_*_total` and `genlayer_node_pubsub_messages_dropped_total` + +### Misc + +- Ship GenVM v0.6.0-rc10; the node uses the new GenVM manager protocol, so an older GenVM manager does not work (this matters only with `genvm.start_manager: false`) +- The GenVM runtime moves from `third_party/genvm` to `opt/genvm/`: set `genvm.root_dir: ./opt/genvm/v0.6.0-rc10` (`install.sh` sets it for you) +- Refuse to start when the config file is missing, is not a regular file, or has an empty required field: `datadir`, both rollup URLs, `consensus.consensusaddress`, `consensus.manifestpath`, `consensus.chainid`, and `genvm.root_dir` (or `genvm.manager_url` when `start_manager: false`) +- Warn about unknown config keys (`Ignoring unknown config keys`) +- RPC groups move from `node.rpc.endpoints.groups.` to `node.rpc.endpoints.`, and method overrides to `node.rpc.endpoints..methods.`; the node ignores the old form without an error and uses the defaults, so move your settings +- The `gen_dbg_*` methods are off unless `node.rpc.endpoints.genlayer_debug.enabled: true`; the example config also turns off the `zks_*` proxy +- Return `-32601` instead of `-32004` for a denied `eth_*` or `zks_*` method; a disabled `gen_*` method still returns `-32004` +- Replace the `/health` body: `status` is one of `unknown`, `healthy`, `suspect`, `warning`, `degraded` and `unavailable`, explained by a `conditions` catalog; `checks` and `genvm_modules` are removed +- `/health` returns HTTP 503 only for `unavailable`, so a node that catches up or is not primed yet now returns 200; a self-stake below the minimum or a critical operator balance sets `consensus.stake` or `consensus.operator_funds` to `unavailable` +- `gen_getTransactionStatus` returns the stored status only and ignores `timestamp` (use `gen_getTransactionLifecycle` for the projected status); `ReadyToFinalize` (11) is removed, so `ValidatorsTimeout`, `LeaderTimeout` and `LeaderRevealing` are now 11, 12 and 13 +- `previousStatus` in `gen_getTransactionReceipt` is always `null` +- The `status` parameter of `gen_call`, `gen_getContractState` and `gen_getContractCode` takes `decided` instead of `accepted` (default `decided`); `accepted` returns `-32602` +- `genvm.permits` and `genvm.sync_permits` are now permit budgets (a nondeterministic run costs 8, a sync run 4): leave them empty or multiply your old values by 4 (8 → 32, 64 → 256) +- Split the database into `genvm.db` (chain data) and `ops.db` (this node's own records); `/genlayer.db` is renamed to `genvm.db` at start-up, and start-up is refused when both exist +- `node.genlayerdb` is replaced by `node.genvmdb` and `node.opsdb`; move a custom database path by hand +- Prove VRF duties with the operator key registered before the transaction was created; after an operator key rotation, keep the old operator account in the keystore, with the same password, until its transactions are done +- Run the Docker image as the non-root user uid 10001; run `sudo chown -R 10001:10001 ./data` once before the first start +- Read WebDriver container stats only through `DOCKER_HOST`: the release compose adds a read-only Docker socket proxy and no longer mounts `docker.sock` into the node; a binary install sets `DOCKER_HOST`, else the collector is skipped +- The release compose file requires `NODE_PASSWORD` (the default `12345678` is removed) and gives it to the node as `GENLAYERNODE_PASSWORD`, not on the command line +- The release monitoring profile has no default `CENTRAL_*` endpoints or credentials: set all six in `.env` +- Clamp `consensus.chain_submit_buffer` at start-up so at least 5 s of the shortest on-chain phase window stays for execution, and log a warning when it does +- The `genlayer_node_disk_*` metrics report `directory="genvm.db"` and `directory="ops.db"` in place of `directory="genlayer.db"`; update dashboards and alerts +- Add a `reason` label (`http_unavailable`, `block_lag`, `rate_limited`) to `genlayer_rollup_rpc_ws_fallback_total` +- Signed GenVM web requests send the validator wallet address in the `genlayer-node-address` header, instead of the operator address +- Show contract revert errors with their argument names in logs +- Show module paths, not build-machine paths, in log `caller` fields and stack traces +- Update the release image base to Alpine 3.24.2, Grafana Alloy to v1.20.1 and the WebDriver image to 0.0.12, and add `tecnativa/docker-socket-proxy:0.3.0` + +### Bug fixes + +- Retry a chain HTTP request once after a dropped connection, a 502/503/504 or a rate limit; the transport does not retry timeouts and transaction sends +- Fall back to the WebSocket RPC when the HTTP RPC is down or does not answer in time, also for transaction submission and at start-up +- Report the RPC error, not a connection-closed error, when the WebSocket server closes right after it replies +- Keep syncing when the HTTP RPC lags the WebSocket head, instead of exiting after repeated `block not found` +- Keep every nonce owned until the chain settles it: a stuck nonce no longer blocks all later transactions until a restart, recorded transactions are sent again after a restart, and a nonce is freed or reused only on chain evidence +- Make the self-cancellation of a stuck nonce affordable on a small wallet, and outbid the previous cancellation when it is retried +- Finalize, idleness, epoch-advance and message-delivery transactions no longer cancel pending commit and reveal transactions +- Keep commit and reveal transactions on time when older pending transactions block them, and cancel transactions whose deadline passed or whose fee bumps are used up +- No longer mark a transaction rejected, and reuse its nonce, when the RPC does not show it as pending within 10 s +- Read the receipt before treating a timed-out transaction as failed, so a mined transaction is not sent again +- Fix a node crash (`concurrent map writes`) when several transactions fail at the same time, for example during parallel finalizations +- Keep committed votes and their salts across restarts and replayed events, so the reveal is not missed +- Retries no longer regenerate a commit vote or a proposed receipt, so a validator or leader whose first transaction already mined can still reveal +- A leader rotation delivered twice (for example at restart) no longer sends a second, reverting propose transaction +- A GenVM manager outage or restart during a duty no longer turns the transaction into a leader timeout; the duty waits for the manager within its phase +- Stop retrying a transaction that the operator wallet cannot pay for; when priming is blocked, log one error that names the operator wallet, and resume once it is funded +- Duty watchers no longer sleep through their turn when the chain clock runs ahead of the node clock +- Fail a transaction whose calldata cannot be decoded with an error receipt, instead of voting Timeout +- Stop a transient database read error or a missed sync event from making a synced node vote Timeout +- Vote Timeout instead of crashing when a node behind sync is missing transaction data +- Give intelligent contracts the real chain ID instead of `1` +- Set the contract `origin_address` to the transaction submitter instead of the activating validator +- Read EVM contract views for intelligent contracts at the activation block, so all validators see the same value +- Stop sending an extra transaction before each duty; read time-sensitive contract views at the pending block instead +- Drive pending tribunals to their end during epoch finalization, so an epoch no longer stays unfinalized behind them +- Re-run transactions that an appeal sends back for recomputation, from a clean round +- Stagger finalization: one validator at a time polls and submits, also for the predecessor drain and the epoch sweep, so validators stop reverting with `already finalized` +- Stop sending a duplicate finalize when the last reveal and the decision event arrive together +- Stop resubmitting a finalize that reverts permanently +- Epoch recovery sweep: force-process only stuck transactions, and bound each attempt, so a transaction that cannot finalize no longer stops epoch finalization +- Retry the epoch advance during each validator's turn, so epochs advance at their normal pace again +- Stop a validator from skipping an epoch's prime after a stale epoch read or a failed retry loop +- Stop paying for prime transactions that the contract ignores: after 5 in a row with no effect, only re-check until the next epoch +- Re-fetch blocks that a chain reorg replaced, so their consensus events are no longer lost +- Stop dropping consensus events that the log bloom check wrongly treated as false positives, and never move the sync cursor backwards (a synced node looked behind and lost its duties) +- Stop sync with an error that names the transaction, instead of silently skipping an accepted transaction that cannot be decoded +- Sync contract state for transactions accepted without a `TransactionAccepted` event, so contract reads stop failing with `pebble: not found` +- Do not restore contract state from an execution that an appeal replaced in the same block +- Fetch at most 16 transactions at a time during sync, so a node that catches up no longer floods its RPC provider +- Retry instead of rescanning from block 0 when the first-sync genesis lookup fails +- Wait for indexed data in `gen_call`, `gen_getContractState`, `gen_getContractCode` and `gen_dbg_traceTransaction` instead of failing while the node catches up (new `genvm.sync_wait_poll_interval`, default `100ms`) +- Stop crashes on empty or out-of-range data from the chain, and on malformed revert data +- Reject malformed GenVM storage changes instead of crashing the node +- Stop a crash from an oversized map-key length in contract calldata, and reject calldata with non-canonical length headers +- Fail the transaction instead of crashing on missing or malformed numbers in GenVM output and emitted messages +- Fix a GenVM cache race that could crash the node during sync, and data races on per-transaction and epoch state +- Fix a slow memory leak of finalized and canceled transactions +- Exit with a non-zero status after a panic, so Docker or systemd restarts the node +- Report the real error, not `context canceled`, when parallel work fails +- Stop logging `Can not process rotate leader` errors when the proposal of the replacement leader carries the rotation +- Allow full-mode nodes to start without a password +- Honor `node.rpc.host`; the RPC listener bound to all interfaces +- Raise the open-file limit to 65536 (or to the hard limit) at start-up, so GenVM stops failing with `Too many open files` +- Make `doctor` report a GenVM install that was moved after `setup.py` ran +- Omit `result` from JSON-RPC error responses +- Accept `gen_dbg_trie` transaction IDs with or without `0x`, and stop `gen_syncing` from reporting a huge `blocksBehind` when the synced block is ahead of the latest one + +### Removed + +- Remove the `node.environment` field and the development-only `node.dev.disableSubscription` switch; old configs still load, and the node logs the ignored keys +- Remove the `doctor --network` flag; `doctor` checks the deployment against the protocol manifest instead + +### Security + +- Keep the chain RPC API key out of logs, error responses, `doctor` output and the config dump; RPC URLs show only scheme and host +- Stop passing the `GENLAYERNODE_ROLLUP_*` variables (chain URLs and RPC key) and the operator token to GenVM processes +- Mask the operator token, the GenVM token and the chain RPC key in the config dump, and log RPC request params and results only at DEBUG +- Stop logging the node admin token in the GenVM manager request logs of the node +- Warn at start-up when `genvm.manager_url` is not a loopback address, because the requests carry the admin token +- Write `account new --export` and `account export` files straight to the target with owner-only permissions; the command fails if the file exists +- A validator refuses to start when its operator account cannot be resolved, instead of signing with a throwaway key +- Bound memory use: cap GenVM host-call frames at 64 MiB and the size of GenVM manager responses, and limit the concurrent GenVM runs that public RPC (`gen_call`, `gen_dbg_traceTransaction`) starts +- Stop a malformed leader payload from crashing validators +- Return generic messages for internal errors from the admin, ops and RPC APIs, and log the detail; a failed chain read in `gen_call` names only the read that failed +- Restrict the `destDir` of `POST /snapshot` to `node.admin.snapshot_dir` (default `/snapshots`); other paths return `400` +- Reduce the `rpc_url` label of `genlayer_node_info` to scheme and host +- Limit the `namespace` label of `genlayer_node_ws_inbound_subscriptions_active` to known namespaces, so clients cannot create unbounded metric series +- Cache `/balance` for 5 s, so public requests cannot multiply chain RPC calls +- Stop failures of proxied public `eth_*` and `zks_*` requests from opening the chain RPC circuit breaker that guards validator duties +- Drop all Linux capabilities from the node container in the release compose file +- Build with a newer Go release that includes the standard-library security fixes +- Update `golang.org/x/net` to v0.60.0 and `golang.org/x/crypto` to v0.57.0 + ## v0.5.14 ### Bug fixes diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index 9fb40440..1ee5ea36 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -216,18 +216,18 @@ One full node can be shared between multiple validators. The optimal validator-t You should see a list like this ```sh + v0.6.0-rc.2 v0.5.14 v0.5.13 v0.5.12 v0.5.11 - v0.5.10 ``` Typically, you will want to run the latest version 2. Download the packaged application ```sh copy - export version=v0.5.14 # set your desired version here + export version=v0.6.0-rc.2 # set your desired version here wget https://storage.googleapis.com/gh-af/genlayer-node/bin/amd64/${version}/genlayer-node-linux-amd64-${version}.tar.gz ``` 3. Verify the download (v0.6.0 and later) From 2543f74bda4542e39374515611d6e96ba6593d21 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 19:28:11 +0200 Subject: [PATCH 21/39] ci(node-sync): a sync with no doc changes reports no changes instead of failing check_for_changes looked at the whole checkout, so the downloaded artifact directory (temp-final/) always counted as a change; the commit step then found nothing to commit and the job failed. Both now use the same synced paths (content/validators, pages/api-references, pages/validators). Seen in run 39293694234, whose synced content matched #705 exactly. Co-Authored-By: Claude Opus 5.5 --- .github/scripts/git-utils.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/scripts/git-utils.sh b/.github/scripts/git-utils.sh index 210ea55d..82f65096 100755 --- a/.github/scripts/git-utils.sh +++ b/.github/scripts/git-utils.sh @@ -5,6 +5,10 @@ set -euo pipefail # Extracted from the complex git operations in the workflow # Create sync branch with proper naming +# The docs paths a node sync may change: what check_for_changes looks at and +# what commit_and_push_changes commits. +SYNC_PATHS=(content/validators pages/api-references pages/validators) + create_sync_branch() { local version="$1" @@ -44,7 +48,7 @@ commit_and_push_changes() { echo "Committing changes..." # Add relevant directories (including deletions) - git add --all content/validators pages/api-references pages/validators + git add --all "${SYNC_PATHS[@]}" # Check what's staged echo "📋 Files staged for commit:" @@ -71,7 +75,9 @@ EOF # Check for any changes check_for_changes() { - if [[ -n "$(git status --porcelain)" ]]; then + # Only the synced paths count: the job also downloads its artifacts + # (temp-final/) into the checkout, and those are never committed. + if [[ -n "$(git status --porcelain -- "${SYNC_PATHS[@]}")" ]]; then echo "has_changes=true" >> "$GITHUB_OUTPUT" echo "✅ Changes detected" return 0 From 60017f8fe55f0f771b5a911ae2fcb381a5277c62 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 19:41:45 +0200 Subject: [PATCH 22/39] docs(validators): publish every version the node has a changelog for The setup guide's version list and the changelog page now list the same versions: every changelog file synced from the node, release candidates included. The node decides when an rc leaves the docs by removing its changelog file; the sync check allows an rc file to leave, never a stable one. The guide names the newest version (a stable release sorts above its rcs). The list command keeps the rcs of the version in progress and drops rc builds of versions that are already stable, which have no changelog file. Co-Authored-By: Claude Opus 5.5 --- pages/validators/setup-guide.mdx | 4 +- scripts/check-node-sync.js | 6 +-- scripts/generate-changelog.js | 4 +- scripts/lib/release-versions.js | 55 ++++++++++---------------- scripts/test-release-versions.js | 55 ++++++++++++-------------- scripts/update-setup-guide-versions.js | 12 +++--- 6 files changed, 60 insertions(+), 76 deletions(-) diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index 1ee5ea36..4b31f815 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -210,17 +210,17 @@ One full node can be shared between multiple validators. The optimal validator-t You can use this command to list available versions: ```sh copy - curl -s "https://storage.googleapis.com/storage/v1/b/gh-af/o?prefix=genlayer-node/bin/amd64" | grep -o '"name": *"[^"]*"' | sed -n 's/.*\/\(v[^/]*\)\/.*/\1/p' | sed 's/-rc/~rc/' | sort -ruV | sed 's/~rc/-rc/' | awk '{c=$0; sub(/-rc\..*/,"",c)} /-rc\./{if ((c in s) || (c in r)) next; r[c]=1} !/-rc\./{s[c]=1} {print}' | head -n 5 + curl -s "https://storage.googleapis.com/storage/v1/b/gh-af/o?prefix=genlayer-node/bin/amd64" | grep -o '"name": *"[^"]*"' | sed -n 's/.*\/\(v[^/]*\)\/.*/\1/p' | sed 's/-rc/~rc/' | sort -ruV | sed 's/~rc/-rc/' | awk '{c=$0; sub(/-rc\..*/,"",c)} /-rc\./{if (c in s) next} !/-rc\./{s[c]=1} {print}' | head -n 5 ``` You should see a list like this ```sh v0.6.0-rc.2 + v0.6.0-rc.1 v0.5.14 v0.5.13 v0.5.12 - v0.5.11 ``` Typically, you will want to run the latest version diff --git a/scripts/check-node-sync.js b/scripts/check-node-sync.js index 7c4cb7b4..d47e6441 100644 --- a/scripts/check-node-sync.js +++ b/scripts/check-node-sync.js @@ -12,13 +12,13 @@ // - the Bradbury manifest is valid JSON and matches bradbury.yaml; // - no Asimov references in validator and node API pages; // - no changelog version disappears compared with BASE_REF (default origin/main), -// except a release candidate superseded by its stable release or a newer rc. +// except a release candidate: the node removes rc changelog files on purpose. const fs = require('fs'); const path = require('path'); const { execFileSync } = require('child_process'); const { extractAlloyBlock } = require('./update-alloy-in-monitoring'); -const { isSuperseded } = require('./lib/release-versions'); +const { mayLeave } = require('./lib/release-versions'); const root = path.join(__dirname, '..'); const read = (p) => fs.readFileSync(path.join(root, p), 'utf8'); @@ -127,7 +127,7 @@ try { const now = fs.readdirSync(path.join(root, 'content/validators/changelog')) .filter((f) => f.endsWith('.mdx')).map((f) => f.replace(/\.mdx$/, '')); for (const file of before) { - if (!exists(`content/validators/changelog/${file}`) && !isSuperseded(file.replace(/\.mdx$/, ''), now)) { + if (!exists(`content/validators/changelog/${file}`) && !mayLeave(file.replace(/\.mdx$/, ''))) { fail(`changelog: ${file} is on ${baseRef} but was removed (is it missing in the node source?)`); } } diff --git a/scripts/generate-changelog.js b/scripts/generate-changelog.js index 0d0ea5ec..eb7b2fbe 100644 --- a/scripts/generate-changelog.js +++ b/scripts/generate-changelog.js @@ -20,11 +20,11 @@ function generateChangelog() { } // The versions the docs publish, newest first (scripts/lib/release-versions.js): - // a release candidate shows until its stable release exists. + // every changelog file the node has, rcs included. const names = fs.readdirSync(changelogDir) .filter(file => file.endsWith('.mdx')) .map(file => file.replace(/\.mdx$/, '')); - const files = publishedVersions(names, { testnet: true, allRcs: true }).map(v => `${v}.mdx`); + const files = publishedVersions(names, { testnet: true }).map(v => `${v}.mdx`); const unknown = names.filter(n => !files.includes(`${n}.mdx`) && !parseVersion(n)); if (unknown.length) fail(`Changelog files with an unknown version name: ${unknown.join(', ')}`); diff --git a/scripts/lib/release-versions.js b/scripts/lib/release-versions.js index 0429f4f2..fcd944cb 100644 --- a/scripts/lib/release-versions.js +++ b/scripts/lib/release-versions.js @@ -2,19 +2,15 @@ // (update-setup-guide-versions.js), the changelog page (generate-changelog.js) // and the sync check (check-node-sync.js). Tested by scripts/test-release-versions.js. // -// The rule: -// - A stable release (v0.6.0) is always published. -// - A release candidate (v0.6.0-rc.2) is published while its version has no -// stable release yet. It acts as the current release until the stable one exists: -// - the setup guide names only the newest rc of that version; -// - the changelog page shows every rc of that version, because each rc's -// changelog lists only what changed since the previous rc. -// - Once v0.6.0 exists, every v0.6.0-rc.N disappears from the docs. -// - Testnet builds (v0.2.0-testnetNNN) are kept in the changelog page only. -// -// Versions come from the changelog files synced from genlayer-node -// (content/validators/changelog/.mdx). The node keeps one file per rc -// and never deletes them: the docs choose what to show. +// The rule: the docs publish every version that has a changelog file in the +// node repo (content/validators/changelog/.mdx, synced from +// genlayer-node docs/changelog/). The node decides what is published: +// - each release candidate gets its own file (v0.6.0-rc.1, v0.6.0-rc.2, ...); +// - the node removes the rc files when it decides to (for example when v0.6.0 +// is released), and the next sync removes them from the docs. +// The setup guide's version list and the changelog page list the same +// versions. The guide names the newest one; a stable release sorts above its +// own rcs. Testnet builds (v0.2.0-testnetNNN) are kept in the changelog page only. // v0.6.0, v0.6.0-rc.2, v0.2.0-testnet005 const VERSION_RE = /^v(\d+)\.(\d+)\.(\d+)(?:-(rc)\.(\d+)|-(testnet)(\d+))?$/; @@ -43,33 +39,24 @@ function compareDesc(a, b) { /** * The versions the docs publish, newest first. * @param {string[]} names version names (changelog file names without .mdx) - * @param {{testnet?: boolean, allRcs?: boolean}} options for the changelog page: - * testnet keeps testnet builds, allRcs keeps every rc of an unreleased version + * @param {{testnet?: boolean}} options testnet: keep testnet builds (changelog page) */ -function publishedVersions(names, { testnet = false, allRcs = false } = {}) { - const versions = names.map(parseVersion).filter(Boolean).sort(compareDesc); - const stable = new Set(versions.filter((v) => v.pre === null).map((v) => v.core)); - const newestRc = new Map(); - for (const v of versions) { - if (v.pre === 'rc' && !newestRc.has(v.core)) newestRc.set(v.core, v.name); - } - return versions - .filter((v) => { - if (v.pre === 'testnet') return testnet; - if (v.pre === 'rc') return !stable.has(v.core) && (allRcs || newestRc.get(v.core) === v.name); - return true; - }) +function publishedVersions(names, { testnet = false } = {}) { + return names + .map(parseVersion) + .filter(Boolean) + .filter((v) => testnet || v.pre !== 'testnet') + .sort(compareDesc) .map((v) => v.name); } /** - * True when a changelog version may leave the docs: an rc is superseded once - * the stable release of its version exists. + * True when a changelog version may leave the docs without failing the sync + * check: the node removes rc files on purpose; a stable release never leaves. */ -function isSuperseded(name, names) { +function mayLeave(name) { const v = parseVersion(name); - if (!v || v.pre !== 'rc') return false; - return names.map(parseVersion).filter(Boolean).some((o) => o.core === v.core && o.pre === null); + return Boolean(v && v.pre === 'rc'); } -module.exports = { parseVersion, compareDesc, publishedVersions, isSuperseded }; +module.exports = { parseVersion, compareDesc, publishedVersions, mayLeave }; diff --git a/scripts/test-release-versions.js b/scripts/test-release-versions.js index d4774e6c..96d0353b 100644 --- a/scripts/test-release-versions.js +++ b/scripts/test-release-versions.js @@ -8,7 +8,7 @@ const fs = require('fs'); const os = require('os'); const path = require('path'); const { execFileSync } = require('child_process'); -const { publishedVersions, isSuperseded } = require('./lib/release-versions'); +const { publishedVersions, mayLeave } = require('./lib/release-versions'); const { updateSetupGuideVersions, LIST_COMMAND_TAIL } = require('./update-setup-guide-versions'); const GUIDE = `1. list @@ -47,60 +47,57 @@ function guideFor(changelog, guide = GUIDE) { return { guide: out, failed }; } -const STABLE = ['v0.2.0-testnet005', 'v0.5.8-rc.4', 'v0.5.8', 'v0.5.9', 'v0.5.12', 'v0.5.13', 'v0.5.14']; +const STABLE = ['v0.2.0-testnet005', 'v0.5.8', 'v0.5.9', 'v0.5.12', 'v0.5.13', 'v0.5.14']; const tests = { - 'only stable releases: newest stable, numeric order, old rcs and testnets hidden': () => { + 'stable releases: numeric order, testnet builds hidden from the guide': () => { assert.deepStrictEqual(publishedVersions(STABLE), ['v0.5.14', 'v0.5.13', 'v0.5.12', 'v0.5.9', 'v0.5.8']); const { guide, failed } = guideFor(STABLE); assert.ok(!failed); assert.match(guide, /export version=v0\.5\.14 #/); }, - 'during an rc: the newest rc is the current release, older rcs hidden': () => { + 'every rc with a changelog file is listed; the newest is named': () => { const names = [...STABLE, 'v0.6.0-rc.1', 'v0.6.0-rc.2']; - assert.deepStrictEqual(publishedVersions(names).slice(0, 3), ['v0.6.0-rc.2', 'v0.5.14', 'v0.5.13']); + assert.deepStrictEqual(publishedVersions(names).slice(0, 3), ['v0.6.0-rc.2', 'v0.6.0-rc.1', 'v0.5.14']); const { guide } = guideFor(names); assert.match(guide, /export version=v0\.6\.0-rc\.2 #/); - assert.match(guide, /```sh\n v0\.6\.0-rc\.2\n v0\.5\.14\n v0\.5\.13\n v0\.5\.12\n v0\.5\.9\n ```/); + assert.match(guide, /```sh\n v0\.6\.0-rc\.2\n v0\.6\.0-rc\.1\n v0\.5\.14\n v0\.5\.13\n v0\.5\.12\n ```/); }, 'rc.10 is newer than rc.9': () => { assert.strictEqual(publishedVersions(['v0.6.0-rc.9', 'v0.6.0-rc.10'])[0], 'v0.6.0-rc.10'); }, - 'after the stable release: every rc of that version disappears': () => { + 'a stable release sorts above its own rcs and becomes the named version': () => { const names = [...STABLE, 'v0.6.0-rc.1', 'v0.6.0-rc.2', 'v0.6.0']; - const published = publishedVersions(names); - assert.strictEqual(published[0], 'v0.6.0'); - assert.ok(!published.some((v) => v.startsWith('v0.6.0-rc'))); + assert.deepStrictEqual(publishedVersions(names).slice(0, 3), ['v0.6.0', 'v0.6.0-rc.2', 'v0.6.0-rc.1']); assert.match(guideFor(names).guide, /export version=v0\.6\.0 #/); }, + 'the node removes rc files, the docs follow': () => { + assert.deepStrictEqual(publishedVersions([...STABLE, 'v0.6.0']).slice(0, 2), ['v0.6.0', 'v0.5.14']); + }, 'an rc of the next version shows above the current stable': () => { - const names = ['v0.6.0', 'v0.6.1-rc.1']; - assert.deepStrictEqual(publishedVersions(names), ['v0.6.1-rc.1', 'v0.6.0']); + assert.deepStrictEqual(publishedVersions(['v0.6.0', 'v0.6.1-rc.1']), ['v0.6.1-rc.1', 'v0.6.0']); }, - 'the changelog page keeps testnet builds and every rc of an unreleased version': () => { - const opts = { testnet: true, allRcs: true }; - assert.deepStrictEqual(publishedVersions(['v0.2.0-testnet005', 'v0.5.8-rc.4', 'v0.5.8'], opts), ['v0.5.8', 'v0.2.0-testnet005']); - assert.deepStrictEqual(publishedVersions(['v0.5.14', 'v0.6.0-rc.1', 'v0.6.0-rc.2'], opts), ['v0.6.0-rc.2', 'v0.6.0-rc.1', 'v0.5.14']); - assert.deepStrictEqual(publishedVersions(['v0.5.14', 'v0.6.0-rc.1', 'v0.6.0-rc.2', 'v0.6.0'], opts), ['v0.6.0', 'v0.5.14']); + 'the changelog page lists the same versions plus testnet builds': () => { + const names = [...STABLE, 'v0.6.0-rc.1', 'v0.6.0-rc.2']; + const page = publishedVersions(names, { testnet: true }); + assert.deepStrictEqual(page.filter((v) => !v.includes('testnet')), publishedVersions(names)); + assert.strictEqual(page[page.length - 1], 'v0.2.0-testnet005'); }, - 'superseded: an rc may leave once its stable exists, a stable never': () => { - assert.ok(!isSuperseded('v0.6.0-rc.1', ['v0.6.0-rc.2'])); - assert.ok(isSuperseded('v0.6.0-rc.2', ['v0.6.0'])); - assert.ok(!isSuperseded('v0.6.0-rc.2', ['v0.6.0-rc.1', 'v0.5.14'])); - assert.ok(!isSuperseded('v0.5.14', ['v0.6.0'])); + 'sync check: an rc file may leave, a stable one never': () => { + assert.ok(mayLeave('v0.6.0-rc.1')); + assert.ok(!mayLeave('v0.5.14')); + assert.ok(!mayLeave('v0.2.0-testnet005')); }, - 'the list command applies the same rule in shell': () => { + 'the list command keeps rcs of the version in progress, drops rc builds of stable versions': () => { const guide = guideFor(STABLE).guide; assert.ok(guide.includes(LIST_COMMAND_TAIL)); assert.doesNotMatch(guide, /grep -v "rc"/); const tail = LIST_COMMAND_TAIL.replace(/^\| /, ''); const run = (list) => execFileSync('sh', ['-c', `printf '${list.join('\\n')}\\n' | ${tail}`], { encoding: 'utf8' }).trim().split('\n'); - // The shell command applies the same rule as publishedVersions. - const during = ['v0.5.9', 'v0.5.14-rc.1', 'v0.5.14', 'v0.6.0-rc.1', 'v0.6.0-rc.2']; - assert.deepStrictEqual(run(during), publishedVersions(during)); - const after = [...during, 'v0.6.0', 'v0.6.1-rc.1']; - assert.deepStrictEqual(run(after), publishedVersions(after)); - assert.deepStrictEqual(run(after), ['v0.6.1-rc.1', 'v0.6.0', 'v0.5.14', 'v0.5.9']); + // Bucket during the rc: old rc builds of stable versions are dropped. + assert.deepStrictEqual(run(['v0.5.9', 'v0.5.14-rc.1', 'v0.5.14', 'v0.6.0-rc.1', 'v0.6.0-rc.2']), ['v0.6.0-rc.2', 'v0.6.0-rc.1', 'v0.5.14', 'v0.5.9']); + // After v0.6.0: its rc builds drop too, an rc of the next version shows. + assert.deepStrictEqual(run(['v0.5.14', 'v0.6.0-rc.1', 'v0.6.0-rc.2', 'v0.6.0', 'v0.6.1-rc.1']), ['v0.6.1-rc.1', 'v0.6.0', 'v0.5.14']); }, 'the generator is idempotent': () => { const once = guideFor([...STABLE, 'v0.6.0-rc.2']).guide; diff --git a/scripts/update-setup-guide-versions.js b/scripts/update-setup-guide-versions.js index 719e022b..ecfa7c77 100644 --- a/scripts/update-setup-guide-versions.js +++ b/scripts/update-setup-guide-versions.js @@ -7,15 +7,15 @@ const { publishedVersions } = require('./lib/release-versions'); // A generator that cannot find its source or its target block fails the build. const fail = (msg) => { console.error(msg); process.exitCode = 1; }; -// The tail of the guide's list command, the same rule in shell: the sed pair -// makes the version sort put v0.6.0-rc.N below v0.6.0; awk drops an rc once its -// stable release exists and keeps only the newest rc of an unreleased version. -const LIST_COMMAND_TAIL = `| sed 's/-rc/~rc/' | sort -ruV | sed 's/~rc/-rc/' | awk '{c=$0; sub(/-rc\\..*/,"",c)} /-rc\\./{if ((c in s) || (c in r)) next; r[c]=1} !/-rc\\./{s[c]=1} {print}' | head -n 5`; +// The tail of the guide's list command: the sed pair makes the version sort +// put v0.6.0-rc.N below v0.6.0; awk drops the rc builds of versions that are +// already stable (the bucket keeps them, the changelog does not list them). +const LIST_COMMAND_TAIL = `| sed 's/-rc/~rc/' | sort -ruV | sed 's/~rc/-rc/' | awk '{c=$0; sub(/-rc\\..*/,"",c)} /-rc\\./{if (c in s) next} !/-rc\\./{s[c]=1} {print}' | head -n 5`; /** * Update the setup guide's version list, list command and `export version=` line. - * The guide names the newest published version (scripts/lib/release-versions.js): - * the newest release candidate while its stable release does not exist yet. + * The guide lists the versions the node has changelog files for + * (scripts/lib/release-versions.js) and names the newest one. * @param {{root?: string}} options */ function updateSetupGuideVersions({ root = process.cwd() } = {}) { From f6100ce8181408f18b900de11ac6bd28faf71536 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 19:44:58 +0200 Subject: [PATCH 23/39] docs(validators): the setup guide lists only the current release line During v0.6.0-rc.N the version list and the list command show v0.6.x only; v0.5 cannot run Bradbury phase 2. The changelog page keeps every version. Co-Authored-By: Claude Opus 5.5 --- pages/validators/setup-guide.mdx | 5 +---- scripts/lib/release-versions.js | 20 ++++++++++++++++---- scripts/test-release-versions.js | 26 +++++++++++++++++++------- scripts/update-setup-guide-versions.js | 14 ++++++++------ 4 files changed, 44 insertions(+), 21 deletions(-) diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index 4b31f815..3fc46edc 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -210,7 +210,7 @@ One full node can be shared between multiple validators. The optimal validator-t You can use this command to list available versions: ```sh copy - curl -s "https://storage.googleapis.com/storage/v1/b/gh-af/o?prefix=genlayer-node/bin/amd64" | grep -o '"name": *"[^"]*"' | sed -n 's/.*\/\(v[^/]*\)\/.*/\1/p' | sed 's/-rc/~rc/' | sort -ruV | sed 's/~rc/-rc/' | awk '{c=$0; sub(/-rc\..*/,"",c)} /-rc\./{if (c in s) next} !/-rc\./{s[c]=1} {print}' | head -n 5 + curl -s "https://storage.googleapis.com/storage/v1/b/gh-af/o?prefix=genlayer-node/bin/amd64" | grep -o '"name": *"[^"]*"' | sed -n 's/.*\/\(v[^/]*\)\/.*/\1/p' | sed 's/-rc/~rc/' | sort -ruV | sed 's/~rc/-rc/' | awk '{c=$0; sub(/-rc\..*/,"",c)} /-rc\./{if (c in s) next} !/-rc\./{s[c]=1} {split(c,p,"."); l=p[1]"."p[2]} !m{m=l} l==m{print}' | head -n 5 ``` You should see a list like this @@ -218,9 +218,6 @@ One full node can be shared between multiple validators. The optimal validator-t ```sh v0.6.0-rc.2 v0.6.0-rc.1 - v0.5.14 - v0.5.13 - v0.5.12 ``` Typically, you will want to run the latest version diff --git a/scripts/lib/release-versions.js b/scripts/lib/release-versions.js index fcd944cb..b82675a0 100644 --- a/scripts/lib/release-versions.js +++ b/scripts/lib/release-versions.js @@ -8,9 +8,10 @@ // - each release candidate gets its own file (v0.6.0-rc.1, v0.6.0-rc.2, ...); // - the node removes the rc files when it decides to (for example when v0.6.0 // is released), and the next sync removes them from the docs. -// The setup guide's version list and the changelog page list the same -// versions. The guide names the newest one; a stable release sorts above its -// own rcs. Testnet builds (v0.2.0-testnetNNN) are kept in the changelog page only. +// The changelog page lists them all (plus testnet builds). The setup guide +// lists only the current release line, the major.minor of the newest version +// (during v0.6.0-rc.N that is v0.6.x), and names the newest one; a stable +// release sorts above its own rcs. // v0.6.0, v0.6.0-rc.2, v0.2.0-testnet005 const VERSION_RE = /^v(\d+)\.(\d+)\.(\d+)(?:-(rc)\.(\d+)|-(testnet)(\d+))?$/; @@ -50,6 +51,17 @@ function publishedVersions(names, { testnet = false } = {}) { .map((v) => v.name); } +/** + * The current release line: the published versions with the same major.minor + * as the newest one. Input and output are newest first. + */ +function currentLine(versions) { + if (versions.length === 0) return []; + const line = (name) => parseVersion(name).parts.slice(0, 2).join('.'); + const current = line(versions[0]); + return versions.filter((v) => line(v) === current); +} + /** * True when a changelog version may leave the docs without failing the sync * check: the node removes rc files on purpose; a stable release never leaves. @@ -59,4 +71,4 @@ function mayLeave(name) { return Boolean(v && v.pre === 'rc'); } -module.exports = { parseVersion, compareDesc, publishedVersions, mayLeave }; +module.exports = { parseVersion, compareDesc, publishedVersions, currentLine, mayLeave }; diff --git a/scripts/test-release-versions.js b/scripts/test-release-versions.js index 96d0353b..5951be1d 100644 --- a/scripts/test-release-versions.js +++ b/scripts/test-release-versions.js @@ -8,7 +8,7 @@ const fs = require('fs'); const os = require('os'); const path = require('path'); const { execFileSync } = require('child_process'); -const { publishedVersions, mayLeave } = require('./lib/release-versions'); +const { publishedVersions, currentLine, mayLeave } = require('./lib/release-versions'); const { updateSetupGuideVersions, LIST_COMMAND_TAIL } = require('./update-setup-guide-versions'); const GUIDE = `1. list @@ -61,7 +61,8 @@ const tests = { assert.deepStrictEqual(publishedVersions(names).slice(0, 3), ['v0.6.0-rc.2', 'v0.6.0-rc.1', 'v0.5.14']); const { guide } = guideFor(names); assert.match(guide, /export version=v0\.6\.0-rc\.2 #/); - assert.match(guide, /```sh\n v0\.6\.0-rc\.2\n v0\.6\.0-rc\.1\n v0\.5\.14\n v0\.5\.13\n v0\.5\.12\n ```/); + // The guide lists only the current release line (v0.6.x). + assert.match(guide, /```sh\n v0\.6\.0-rc\.2\n v0\.6\.0-rc\.1\n ```/); }, 'rc.10 is newer than rc.9': () => { assert.strictEqual(publishedVersions(['v0.6.0-rc.9', 'v0.6.0-rc.10'])[0], 'v0.6.0-rc.10'); @@ -71,6 +72,12 @@ const tests = { assert.deepStrictEqual(publishedVersions(names).slice(0, 3), ['v0.6.0', 'v0.6.0-rc.2', 'v0.6.0-rc.1']); assert.match(guideFor(names).guide, /export version=v0\.6\.0 #/); }, + 'the guide lists the current release line only': () => { + assert.deepStrictEqual(currentLine(publishedVersions([...STABLE, 'v0.6.0-rc.1', 'v0.6.0'])), ['v0.6.0', 'v0.6.0-rc.1']); + assert.deepStrictEqual(currentLine(publishedVersions(STABLE)), ['v0.5.14', 'v0.5.13', 'v0.5.12', 'v0.5.9', 'v0.5.8']); + assert.deepStrictEqual(currentLine(publishedVersions(['v0.6.1', 'v0.7.0-rc.1'])), ['v0.7.0-rc.1']); + assert.deepStrictEqual(currentLine([]), []); + }, 'the node removes rc files, the docs follow': () => { assert.deepStrictEqual(publishedVersions([...STABLE, 'v0.6.0']).slice(0, 2), ['v0.6.0', 'v0.5.14']); }, @@ -88,16 +95,21 @@ const tests = { assert.ok(!mayLeave('v0.5.14')); assert.ok(!mayLeave('v0.2.0-testnet005')); }, - 'the list command keeps rcs of the version in progress, drops rc builds of stable versions': () => { + 'the list command keeps the current line, drops rc builds of stable versions': () => { const guide = guideFor(STABLE).guide; assert.ok(guide.includes(LIST_COMMAND_TAIL)); assert.doesNotMatch(guide, /grep -v "rc"/); const tail = LIST_COMMAND_TAIL.replace(/^\| /, ''); const run = (list) => execFileSync('sh', ['-c', `printf '${list.join('\\n')}\\n' | ${tail}`], { encoding: 'utf8' }).trim().split('\n'); - // Bucket during the rc: old rc builds of stable versions are dropped. - assert.deepStrictEqual(run(['v0.5.9', 'v0.5.14-rc.1', 'v0.5.14', 'v0.6.0-rc.1', 'v0.6.0-rc.2']), ['v0.6.0-rc.2', 'v0.6.0-rc.1', 'v0.5.14', 'v0.5.9']); - // After v0.6.0: its rc builds drop too, an rc of the next version shows. - assert.deepStrictEqual(run(['v0.5.14', 'v0.6.0-rc.1', 'v0.6.0-rc.2', 'v0.6.0', 'v0.6.1-rc.1']), ['v0.6.1-rc.1', 'v0.6.0', 'v0.5.14']); + // The shell command lists what the guide lists. + const same = (names) => assert.deepStrictEqual(run(names), currentLine(publishedVersions(names))); + // Bucket during the rc: v0.6.x only; rc builds of stable versions dropped. + assert.deepStrictEqual(run(['v0.5.9', 'v0.5.14-rc.1', 'v0.5.14', 'v0.6.0-rc.1', 'v0.6.0-rc.2']), ['v0.6.0-rc.2', 'v0.6.0-rc.1']); + // After v0.6.0 (node removed the rc files): v0.6.x stable only. + same(['v0.5.14', 'v0.6.0']); + assert.deepStrictEqual(run(['v0.5.14', 'v0.6.0-rc.1', 'v0.6.0-rc.2', 'v0.6.0']), ['v0.6.0']); + // Only v0.5: the v0.5 line. + same(['v0.5.9', 'v0.5.12', 'v0.5.14']); }, 'the generator is idempotent': () => { const once = guideFor([...STABLE, 'v0.6.0-rc.2']).guide; diff --git a/scripts/update-setup-guide-versions.js b/scripts/update-setup-guide-versions.js index ecfa7c77..c4ba56db 100644 --- a/scripts/update-setup-guide-versions.js +++ b/scripts/update-setup-guide-versions.js @@ -2,20 +2,21 @@ const fs = require('fs'); const path = require('path'); -const { publishedVersions } = require('./lib/release-versions'); +const { publishedVersions, currentLine } = require('./lib/release-versions'); // A generator that cannot find its source or its target block fails the build. const fail = (msg) => { console.error(msg); process.exitCode = 1; }; // The tail of the guide's list command: the sed pair makes the version sort // put v0.6.0-rc.N below v0.6.0; awk drops the rc builds of versions that are -// already stable (the bucket keeps them, the changelog does not list them). -const LIST_COMMAND_TAIL = `| sed 's/-rc/~rc/' | sort -ruV | sed 's/~rc/-rc/' | awk '{c=$0; sub(/-rc\\..*/,"",c)} /-rc\\./{if (c in s) next} !/-rc\\./{s[c]=1} {print}' | head -n 5`; +// already stable (the bucket keeps them, the changelog does not list them) and +// keeps only the current release line (the major.minor of the newest version). +const LIST_COMMAND_TAIL = `| sed 's/-rc/~rc/' | sort -ruV | sed 's/~rc/-rc/' | awk '{c=$0; sub(/-rc\\..*/,"",c)} /-rc\\./{if (c in s) next} !/-rc\\./{s[c]=1} {split(c,p,"."); l=p[1]"."p[2]} !m{m=l} l==m{print}' | head -n 5`; /** * Update the setup guide's version list, list command and `export version=` line. - * The guide lists the versions the node has changelog files for - * (scripts/lib/release-versions.js) and names the newest one. + * The guide lists the current release line of the versions the node has + * changelog files for (scripts/lib/release-versions.js) and names the newest one. * @param {{root?: string}} options */ function updateSetupGuideVersions({ root = process.cwd() } = {}) { @@ -34,7 +35,8 @@ function updateSetupGuideVersions({ root = process.cwd() } = {}) { const names = fs.readdirSync(changelogDir) .filter((file) => file.endsWith('.mdx')) .map((file) => file.replace(/\.mdx$/, '')); - const versions = publishedVersions(names); + // The current release line only: during v0.6.0-rc.N the guide lists v0.6.x. + const versions = currentLine(publishedVersions(names)); if (versions.length === 0) { fail('No version files found in changelog directory'); return; From ac162bb09f7e5a2448788fb5175a9356ce1aa057 Mon Sep 17 00:00:00 2001 From: jmlago Date: Mon, 19 Oct 2026 19:28:24 +0100 Subject: [PATCH 24/39] docs: correct Phase 2 validator prerequisites and network checks --- content/validators/config.yaml | 6 +-- pages/developers/networks.mdx | 30 ++++++++++----- pages/validators/setup-guide.mdx | 66 ++++++++++++++++++++------------ 3 files changed, 66 insertions(+), 36 deletions(-) diff --git a/content/validators/config.yaml b/content/validators/config.yaml index ad1ac602..65cc93bc 100644 --- a/content/validators/config.yaml +++ b/content/validators/config.yaml @@ -1,12 +1,12 @@ # rollup configuration rollup: - genlayerchainrpcurl: "TODO: Set your GenLayer Chain ZKSync HTTP RPC URL here" # GenLayer Chain RPC URL - genlayerchainwebsocketurl: "TODO: Set your GenLayer Chain ZKSync WebSocket RPC URL here" # GenLayer Chain WebSocket URL + genlayerchainrpcurl: "TODO: Set your GenLayer Chain Orbit HTTP RPC URL here" # GenLayer Chain RPC URL + genlayerchainwebsocketurl: "TODO: Set your GenLayer Chain Orbit WebSocket RPC URL here" # GenLayer Chain WebSocket URL # An API key of the RPC provider never goes in these URLs or in this file: # set it in the environment as GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY. # The node sends it in this header (default X-AccessKey): # genlayerchainrpckeyheader: "X-AccessKey" - provider: "TODO: Set your GenLayer Chain ZKSync provider" # Rollup operator name; surfaces as the "provider" label on RPC-driven metrics. Set explicitly per deployment. + provider: "TODO: Set your GenLayer Chain Orbit provider" # Rollup operator name; surfaces as the "provider" label on RPC-driven metrics. Set explicitly per deployment. # consensus contracts configuration consensus: # The deployment-specific fields are empty: take them from your network's documentation. diff --git a/pages/developers/networks.mdx b/pages/developers/networks.mdx index b474de59..544dd925 100644 --- a/pages/developers/networks.mdx +++ b/pages/developers/networks.mdx @@ -7,11 +7,11 @@ import AddToWallet from '../../components/AddToWallet' # Networks -GenLayer operates as two layers: the **GenLayer RPC** handles intelligent contract operations (`gen_*` methods), while the **GenLayer Chain** is the underlying L2 (zkSync Elastic Chain) that handles standard Ethereum operations (`eth_*` methods). The GenLayer RPC also passes through all `eth_*` and `zks_*` calls to the underlying chain, so you can use either endpoint for standard wallet operations. +GenLayer operates as two layers: the **GenLayer RPC** handles intelligent contract operations (`gen_*` methods), while the **GenLayer Chain** is the underlying Arbitrum Orbit L2 that handles standard Ethereum operations (`eth_*` methods). The GenLayer RPC also passes through supported `eth_*` calls to the underlying chain, so you can use either endpoint for standard wallet operations. For the full RPC reference, see [GenLayer Node API](/api-references/genlayer-node). -Your wallet connects to the **GenLayer RPC** — it handles both intelligent contract calls and standard Ethereum methods. +For transfers and staking, connect to the **GenLayer Chain RPC**. Intelligent-contract methods such as `gen_call` require a **GenLayer node RPC**; the Conduit chain RPC does not provide them. Use a Phase 2-compatible GenLayer node endpoint for SDK intelligent-contract operations. --- @@ -21,15 +21,27 @@ Production-like testnet with real AI/LLM workloads. | Setting | Value | |:--|:--| -| **GenLayer RPC** | `https://rpc-bradbury.genlayer.com` | +| **GenLayer RPC** | Use the Phase 2 node RPC published in the release announcement, or your own qualified node | | **GenLayer Chain RPC** | `https://rpc.testnet-chain.genlayer.com` | | **Chain ID** | 4221 | | **Currency** | GEN | -| **Explorer** | [explorer-bradbury.genlayer.com](https://explorer-bradbury.genlayer.com) | -| **Chain Explorer** | [explorer.testnet-chain.genlayer.com](https://explorer.testnet-chain.genlayer.com/) | +| **Validator / staking UI** | [stakegen.com](https://stakegen.com/explore/) | +| **Chain Explorer** | [scan.testnet-chain.genlayer.com](https://scan.testnet-chain.genlayer.com/) | | **Faucet** | [testnet-faucet.genlayer.foundation](https://testnet-faucet.genlayer.foundation) | - + + + +Bradbury Phase 2 reuses chain ID **4221**. Verify block zero has hash `0x17d89764d4f4f963158fc4c5e8f2cb670c2837740b35f37081c12860c3df97e4`, and the AddressManager is `0x3F71AE080BD6270A91dBcc7960d88797828538E0`. An old RPC can return the right chain ID but lead to a different deployment. If a public domain returns a different genesis, stop and have its routing corrected. + + +Direct provider endpoints for Phase 2 qualification are: + +- HTTP: `https://rpc-genlayer-chain-testnet-cfn8m3xc6l.t.conduit.xyz` +- WebSocket: `wss://rpc-genlayer-chain-testnet-cfn8m3xc6l.t.conduit.xyz` (assigned API key required) +- Chain explorer: `https://explorer-genlayer-chain-testnet-cfn8m3xc6l.t.conduit.xyz/` + +Public domains above are the intended release endpoints. Check their readiness and the faucet's target network before transferring funds; do not assume old balances or registrations carry over. --- @@ -86,16 +98,16 @@ Local development with full control. Requires [GenLayer Studio](/developers/inte ## GenLayer Chain (L2) -The underlying zkSync Elastic Chain that GenLayer runs on. You typically don't need to interact with this directly — the GenLayer RPC passes through all `eth_*` calls. But if you need direct L2 access (e.g., token transfers, contract debugging at the EVM level), you can add this chain to your wallet. +The underlying Arbitrum Orbit L2 for Bradbury Phase 2. You typically don't need to interact with this directly — the GenLayer RPC passes through supported `eth_*` calls. But if you need direct L2 access (e.g., token transfers, contract debugging at the EVM level), you can add this chain to your wallet. | Setting | Value | |:--|:--| | **RPC** | `https://rpc.testnet-chain.genlayer.com` | | **Chain ID** | 4221 | | **Currency** | GEN | -| **Explorer** | [explorer.testnet-chain.genlayer.com](https://explorer.testnet-chain.genlayer.com/) | +| **Explorer** | [scan.testnet-chain.genlayer.com](https://scan.testnet-chain.genlayer.com/) | - + --- diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index 3fc46edc..d2aaa647 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -8,7 +8,7 @@ import { Callout } from "nextra-theme-docs"; Running a GenLayer Validator node ensures the security and reliability of the GenLayer network. Below are the requirements for running a GenLayer validator. -Validators require both a staking deposit and a validator priming transaction to enter the set. +Registration creates a validator identity; it does not by itself make the node an active validator. Check the deployed self-stake minimum, activation epoch and priming requirements separately. During epoch 0, a registered and healthy node may correctly be waiting for activation. For a deeper understanding of how staking works in GenLayer, see the [Staking documentation](/understand-genlayer-protocol/core-concepts/optimistic-democracy/staking). @@ -114,27 +114,32 @@ The wizard outputs all three addresses at the end. Save them - you'll need the V ### Prerequisites - **Node.js** (v18 or higher) -- **GenLayer CLI** - Install the latest version: +- **GenLayer CLI** - Install an exact, qualified **v0.40** release that bundles the **v2 SDK with Bradbury Phase 2 addresses**. The default npm tag can still select an older release: ```bash copy - npm install -g genlayer + # Set CLI_VERSION to the exact version in the Bradbury Phase 2 release announcement. + : "${CLI_VERSION:?Set the qualified Bradbury Phase 2 CLI version first}" + npm install -g "genlayer@${CLI_VERSION}" + genlayer --version ``` -- **GEN tokens** - You need at least **42,000 GEN** for the minimum self-stake requirement +- **GEN tokens** - Read the deployed self-stake minimum with `genlayer staking epoch-info --network testnet-bradbury`, and keep additional GEN for gas. **42,000 GEN is a protocol default, not a guaranteed current network setting.** Delegation does not replace required self-stake. + +During the public DNS cutover, first verify the network identity described on the [networks page](/developers/networks). If you have an assigned provider endpoint, add `--rpc "$BRADBURY_RPC_URL"` to each CLI command below. Do not continue against an endpoint returning the previous network genesis. ### Using the Validator Wizard The easiest way to create your validator wallet is using the interactive wizard: ```bash copy -genlayer staking wizard +genlayer staking wizard --network testnet-bradbury ``` The wizard guides you through: 1. **Account setup** - Create or select your owner account 2. **Network selection** - Choose testnet-bradbury -3. **Balance verification** - Confirms you have at least 42,000 GEN +3. **Balance verification** - Checks your balance against the deployed minimum and gas requirement 4. **Operator setup** - Create and export an operator keystore for your validator server -5. **Stake amount** - Enter how much GEN to stake (minimum 42,000) +5. **Stake amount** - Enter how much GEN to self-stake using the current on-chain minimum 6. **Validator creation** - Submits the staking transaction 7. **Identity setup** - Set your validator's public profile (moniker, website, etc.) @@ -142,6 +147,10 @@ The wizard guides you through: **Save Your Validator Wallet Address!** After the wizard completes, note down your **Validator Wallet** address. You'll need this for your node configuration. + + Phase 2 registration requires the operator public key and a proof of possession bound to the owner, chain and validator-wallet factory. Use an operator account available to the qualified CLI so it can sign the proof. An external operator address alone is not sufficient; do not use the old `validatorJoin(address)` flow. + + #### Operator Keystore Export The wizard creates and exports an operator keystore file for you to transfer to your validator server. If your server is compromised, your staked funds (controlled by owner) remain safe. @@ -151,26 +160,28 @@ The wizard creates and exports an operator keystore file for you to transfer to After completing the wizard, verify your status: ```bash copy -genlayer staking validator-info --validator 0xYourValidatorWallet... +genlayer staking validator-info 0xYourValidatorWallet... --network testnet-bradbury +# Includes registered validators that are not active yet: +genlayer staking validators --network testnet-bradbury ``` ### Managing Your Validator ```bash copy # Add more stake -genlayer staking validator-deposit --amount 1000gen +genlayer staking validator-deposit 0xYourValidatorWallet... --amount 1000gen --network testnet-bradbury # Check active validators -genlayer staking active-validators +genlayer staking active-validators --network testnet-bradbury # Exit (initiates 7-epoch unbonding) -genlayer staking validator-exit --shares 100 +genlayer staking validator-exit 0xYourValidatorWallet... --shares 100 --network testnet-bradbury # Claim after unbonding period -genlayer staking validator-claim +genlayer staking validator-claim 0xYourValidatorWallet... --network testnet-bradbury # Update identity -genlayer staking set-identity --validator 0x... --moniker "New Name" +genlayer staking set-identity 0x... --moniker "New Name" --network testnet-bradbury ``` --- @@ -195,9 +206,11 @@ Possible options: See the [GenVM Configuration](/validators/genvm-configuration) page for partner credits, provider setup, and the greybox fallback strategy. -#### ZKSync Full Node for the GenLayer Chain +#### Orbit RPC access for the GenLayer Chain -Each validator needs access to a [ZKSync Full Node](https://docs.zksync.io/zksync-era/tooling/external-node) connected to the GenLayer chain. Validators use this node to read the chain state and submit transactions. +Bradbury Phase 2 runs on an **Arbitrum Orbit L2**. Each validator needs HTTP and WebSocket access to that network to read state, send transactions and subscribe to chain updates. An old zkSync endpoint is incompatible even when it returns the same chain ID. + +Obtain the assigned endpoint, API key and quota from the Foundation's designated RPC operator. The direct Conduit WebSocket requires a key; anonymous HTTP access does not prove that WebSocket subscriptions will work. Set the key privately in `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY`; the node sends it as `X-AccessKey`. Never place it in a URL or a shared config file. One full node can be shared between multiple validators. The optimal validator-to-node ratio is currently under evaluation. @@ -220,12 +233,17 @@ One full node can be shared between multiple validators. The optimal validator-t v0.6.0-rc.1 ``` - Typically, you will want to run the latest version + Typically, you will want to run the latest version **approved for Bradbury Phase 2**, not an arbitrary `latest` tag. The list above is a documentation example generated from changelog entries; an entry can precede artifact publication. Confirm the exact version in the release announcement and check all download URLs before installation. Do not fall back to an older release when a target artifact returns 404. 2. Download the packaged application ```sh copy - export version=v0.6.0-rc.2 # set your desired version here - wget https://storage.googleapis.com/gh-af/genlayer-node/bin/amd64/${version}/genlayer-node-linux-amd64-${version}.tar.gz + export version=v0.6.0-rc.2 # example target; confirm it is published and approved before use + base=https://storage.googleapis.com/gh-af/genlayer-node/bin/amd64/${version} + curl -fI "${base}/genlayer-node-linux-amd64-${version}.tar.gz" + curl -fI "${base}/genlayer-node_${version}_SHA256SUMS" + curl -fI "${base}/genlayer-node_${version}_SHA256SUMS.sigstore.json" + # Stop if any check fails; only download after all three succeed. + wget "${base}/genlayer-node-linux-amd64-${version}.tar.gz" ``` 3. Verify the download (v0.6.0 and later) @@ -293,13 +311,13 @@ The `config.yaml` in the release package sets `genvm.root_dir: ./opt/genvm/curre ```yaml copy # rollup configuration rollup: - genlayerchainrpcurl: "TODO: Set your GenLayer Chain ZKSync HTTP RPC URL here" # GenLayer Chain RPC URL - genlayerchainwebsocketurl: "TODO: Set your GenLayer Chain ZKSync WebSocket RPC URL here" # GenLayer Chain WebSocket URL + genlayerchainrpcurl: "TODO: Set your GenLayer Chain Orbit HTTP RPC URL here" # GenLayer Chain RPC URL + genlayerchainwebsocketurl: "TODO: Set your GenLayer Chain Orbit WebSocket RPC URL here" # GenLayer Chain WebSocket URL # An API key of the RPC provider never goes in these URLs or in this file: # set it in the environment as GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY. # The node sends it in this header (default X-AccessKey): # genlayerchainrpckeyheader: "X-AccessKey" - provider: "TODO: Set your GenLayer Chain ZKSync provider" # Rollup operator name; surfaces as the "provider" label on RPC-driven metrics. Set explicitly per deployment. + provider: "TODO: Set your GenLayer Chain Orbit provider" # Rollup operator name; surfaces as the "provider" label on RPC-driven metrics. Set explicitly per deployment. # consensus contracts configuration consensus: # The deployment-specific fields are empty: take them from your network's documentation. @@ -704,7 +722,7 @@ You can also generate a new operator key directly on the server: ./bin/genlayernode account new -c $(pwd)/configs/node/config.yaml --setup ``` -Then use this address when running the wizard on your local machine. +For Phase 2 registration, the CLI also needs that operator account to sign its proof of possession. Securely export/import the operator keystore into the owner-side CLI and select it in the wizard; providing the address alone is insufficient. Never transfer the owner key to the server. #### Restoring Your Operator Key @@ -750,7 +768,7 @@ Once you have configured everything, you are ready to start the node. 1. **Operator key** imported into the node (see "Import the Operator Key" above) 2. **Validator wallet address** - obtained after joining as validator via `genlayer staking wizard` or `validator-join` - Without both configured, your node will run as a full node instead of a validator. + Set `node.mode: validator` explicitly and require doctor to pass. Do not assume an incomplete validator configuration will safely become a full node. #### Running the Node using the binary @@ -1205,7 +1223,7 @@ HEALTHCHECK_AUTO_RESTART=true This is the release package's `.env.example`: copy it to `.env` next to `docker-compose.yaml`. The release package also ships `docker-compose.yaml` and `genvm-module-web-docker.yaml`. Set at least: - `NODE_PASSWORD`: the keystore password. It is required: Docker Compose refuses to start without it, and there is no default. -- `NODE_VERSION`: pin the version you run. `latest` is the newest stable release. +- `NODE_VERSION`: pin the exact version approved for Bradbury Phase 2. The `latest` image can belong to an older release family. - the API key of at least one LLM provider. - `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY`, when your chain RPC provider needs a key (the Bradbury WebSocket does). From 7101a109a3a5f6c49049a173b17b63f01868184d Mon Sep 17 00:00:00 2001 From: jmlago Date: Mon, 19 Oct 2026 19:31:22 +0100 Subject: [PATCH 25/39] docs: preserve Orbit labels in future node config syncs --- .github/scripts/sanitize-config.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/scripts/sanitize-config.sh b/.github/scripts/sanitize-config.sh index 116ec6fe..2cbe22df 100644 --- a/.github/scripts/sanitize-config.sh +++ b/.github/scripts/sanitize-config.sh @@ -22,9 +22,9 @@ echo "Sanitizing config file: $CONFIG_FILE" # Use yq to: # Replace RPC URLs / provider with TODO placeholders yq -i ' - .rollup.genlayerchainrpcurl = "TODO: Set your GenLayer Chain ZKSync HTTP RPC URL here" | - .rollup.genlayerchainwebsocketurl = "TODO: Set your GenLayer Chain ZKSync WebSocket RPC URL here" | - .rollup.provider = "TODO: Set your GenLayer Chain ZKSync provider" + .rollup.genlayerchainrpcurl = "TODO: Set your GenLayer Chain Orbit HTTP RPC URL here" | + .rollup.genlayerchainwebsocketurl = "TODO: Set your GenLayer Chain Orbit WebSocket RPC URL here" | + .rollup.provider = "TODO: Set your GenLayer Chain Orbit provider" ' "$CONFIG_FILE" echo "Config sanitization completed" From ee4bc867e4eebad6e5edc8cea7081b56238461e2 Mon Sep 17 00:00:00 2001 From: jmlago Date: Mon, 19 Oct 2026 19:42:16 +0100 Subject: [PATCH 26/39] docs: document Conduit key creation and registry discovery --- pages/developers/networks.mdx | 2 ++ pages/validators/setup-guide.mdx | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pages/developers/networks.mdx b/pages/developers/networks.mdx index 544dd925..253c2af0 100644 --- a/pages/developers/networks.mdx +++ b/pages/developers/networks.mdx @@ -41,6 +41,8 @@ Direct provider endpoints for Phase 2 qualification are: - WebSocket: `wss://rpc-genlayer-chain-testnet-cfn8m3xc6l.t.conduit.xyz` (assigned API key required) - Chain explorer: `https://explorer-genlayer-chain-testnet-cfn8m3xc6l.t.conduit.xyz/` +Create a Conduit key at [app.conduit.xyz/rpc-keys](https://app.conduit.xyz/rpc-keys) and configure it as `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY` in the node environment (or the `.env` next to `docker-compose.yaml`). Never add it to URLs or `config.yaml`. Resolve Staking and ConsensusMain through the AddressManager instead of treating their initial addresses as permanent network identity. + Public domains above are the intended release endpoints. Check their readiness and the faucet's target network before transferring funds; do not assume old balances or registrations carry over. --- diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index d2aaa647..38fd8c9b 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -210,7 +210,13 @@ See the [GenVM Configuration](/validators/genvm-configuration) page for partner Bradbury Phase 2 runs on an **Arbitrum Orbit L2**. Each validator needs HTTP and WebSocket access to that network to read state, send transactions and subscribe to chain updates. An old zkSync endpoint is incompatible even when it returns the same chain ID. -Obtain the assigned endpoint, API key and quota from the Foundation's designated RPC operator. The direct Conduit WebSocket requires a key; anonymous HTTP access does not prove that WebSocket subscriptions will work. Set the key privately in `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY`; the node sends it as `X-AccessKey`. Never place it in a URL or a shared config file. +Create a key at [app.conduit.xyz/rpc-keys](https://app.conduit.xyz/rpc-keys). The Conduit WebSocket requires a key; anonymous HTTP access does not prove that WebSocket subscriptions will work. Set it in the node's environment: + +```sh copy +export GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY="" +``` + +With Docker, add the same variable to the private `.env` file next to `docker-compose.yaml`. The node sends it as `X-AccessKey`. Never put it in the RPC or WebSocket URLs, or anywhere in `config.yaml`. One full node can be shared between multiple validators. The optimal validator-to-node ratio is currently under evaluation. From bd94a00fb7f0656394487c5f29cf1e7ab7543d36 Mon Sep 17 00:00:00 2001 From: jmlago Date: Mon, 19 Oct 2026 19:57:15 +0100 Subject: [PATCH 27/39] docs(validators): require Node 20 for the v0.40 CLI --- pages/validators/setup-guide.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index 38fd8c9b..bd72b4d0 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -113,7 +113,7 @@ The wizard outputs all three addresses at the end. Save them - you'll need the V ### Prerequisites -- **Node.js** (v18 or higher) +- **Node.js** (v20 or higher; Node.js 22 for a fresh installation) - **GenLayer CLI** - Install an exact, qualified **v0.40** release that bundles the **v2 SDK with Bradbury Phase 2 addresses**. The default npm tag can still select an older release: ```bash copy # Set CLI_VERSION to the exact version in the Bradbury Phase 2 release announcement. From a6ff38bf0fd1e0133909b118c31a6b3d66792ad3 Mon Sep 17 00:00:00 2001 From: jmlago Date: Mon, 19 Oct 2026 19:59:55 +0100 Subject: [PATCH 28/39] docs(validators): install encrypted operator keystores without argv secrets --- pages/validators/setup-guide.mdx | 89 ++++++++++++-------------------- 1 file changed, 34 insertions(+), 55 deletions(-) diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index bd72b4d0..a67a5ddf 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -702,23 +702,23 @@ printf '\n' #### Option 1: Import from CLI Wizard (Recommended) -If you used `genlayer staking wizard`, it exported an operator keystore file. Transfer this file to your validator server and import it: +The wizard exports an encrypted Web3/geth-compatible operator keystore. Transfer +only that file to the validator server. With the default `datadir: ./data/node` +and `node.keystoredir: keystore`, install it as the node service user: -```sh copy -./bin/genlayernode account import \ - --passphrase "password you set when exporting from wizard" \ - --path "/path/to/operator-keystore.json" \ - -c $(pwd)/configs/node/config.yaml \ - --setup +```bash copy +install -d -m 700 ./data/node/keystore +test ! -e ./data/node/keystore/operator.json || exit 1 +install -m 600 /path/to/operator-keystore.json ./data/node/keystore/operator.json ``` -You should see: - -```sh -Operator account imported: - Operator address: 0xA0b12Fd2f3F7e86fEC458D114A5E7a6f571160a8 - Operator address has been set in config.yaml -``` +Set `GENLAYERNODE_PASSWORD` privately to the **export passphrase**. This preserves +the file's encryption; it does not re-encrypt it with a different password. Adjust +the destination if your configured data or keystore path differs. Set the public +`node.operatorAddress` and `node.validatorWalletAddress` in your config and run +doctor to verify unlocking and registration. The current `account import` command +requires an export passphrase argument; it does not prompt for it. The encrypted +file installation above avoids putting that secret in process arguments. #### Option 2: Generate New Operator Key @@ -732,38 +732,17 @@ For Phase 2 registration, the CLI also needs that operator account to sign its p #### Restoring Your Operator Key -To restore from a backup (e.g., after migrating to a new server): - -```sh copy -./bin/genlayernode account import \ - --passphrase "your backup encryption passphrase" \ - --path "/path/to/your/secure/backup.key" \ - -c $(pwd)/configs/node/config.yaml \ - --setup -``` - - - Always verify that your imported key works by checking the operator address matches what's configured for your validator. - +Stop the old node before transferring its operator to another server. Restore the +encrypted keystore using the procedure above, retain its passphrase privately and +verify the public address against registration before startup. Never run both +nodes with the same operator simultaneously. #### Backing Up Your Operator Key -After setting up your operator key, back it up securely: - -```sh copy -./bin/genlayernode account export \ - --address "your operator address" \ - --passphrase "your backup encryption passphrase" \ - --path "/path/to/your/secure/backup.key" \ - -c $(pwd)/configs/node/config.yaml -``` - - - **Important: Back up your operator key!** Losing access means you'll need to set up a new operator and update your validator configuration. Store the backup securely. - - -To print the private key from your backup file, use the `--print` flag. **Keep this private key secure and never share it.** - +Back up the encrypted operator keystore, public config and trusted manifest using +your approved secure backup location. Keep the passphrase separately. Preserve +owner-only permissions and test recovery before discarding the source. Do not +print or export the raw private key as part of routine installation. ### Running the node @@ -1235,21 +1214,21 @@ This is the release package's `.env.example`: copy it to `.env` next to `docker- 4. Import the operator key - `config.yaml` is mounted read-only, so `account import --setup` cannot write the operator address from inside the container. Import the key into the data directory, then add the address to the config yourself: - - ```sh copy - # 1. Put the key file in the data directory (mounted writable at /app/data) - mkdir -p ./data/import && cp /path/to/operator-keystore.json ./data/import/ + Install the encrypted CLI operator export into the node's keystore directory. + The example uses the default data mount and `datadir: ./data/node`. Adapt it if + you changed `NODE_DATA_PATH` or the configured keystore path. - # 2. Import it. The keystore password comes from NODE_PASSWORD in .env. - docker compose --profile node run --rm --entrypoint /app/bin/genlayernode genlayer-node \ - account import --path /app/data/import/operator-keystore.json --passphrase "password you set when exporting from wizard" - - # 3. Remove the key file - rm ./data/import/operator-keystore.json && rmdir ./data/import + ```bash copy + sudo install -d -m 700 -o 10001 -g 10001 ./data/node/keystore + test ! -e ./data/node/keystore/operator.json || exit 1 + sudo install -m 600 -o 10001 -g 10001 /path/to/operator-keystore.json \ + ./data/node/keystore/operator.json ``` - On Linux, run step 2 after the `chown` above. For a raw private key, use `--private-key ` instead of `--path` and `--passphrase`. The command prints the operator address: add it to `configs/node/config.yaml` on the host: + Set `NODE_PASSWORD` in the private `.env` to the export's passphrase. This keeps + the original encryption and avoids passing a password or private key in process + arguments. `config.yaml` is mounted read-only: set the public registered + operator address in the host configuration, then verify it with doctor: ```yaml node: From 27cd02682e2b582f881d3d88830870f83df64a7d Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 22:06:50 +0200 Subject: [PATCH 29/39] docs(validators): NODE_PASSWORD is needed for a validator, not for every stack Sync of genlayer-node#2271 at 9fc2b862a (not merged yet): the compose file passes NODE_PASSWORD without a :? guard, because Compose interpolates every service and the guard stopped the WebDriver-only stack and a full node. The node refuses validator mode without a password. Setup and upgrade guides now say so instead of "Compose refuses to start without it". Co-Authored-By: Claude Opus 5.5 --- content/validators/changelog/v0.6.0-rc.2.mdx | 2 ++ .../validators/docker-compose-monitoring.yaml | 15 ++++++++----- content/validators/docker-compose.yaml | 15 ++++++++----- content/validators/env.example | 5 +++-- pages/validators/changelog.mdx | 2 ++ pages/validators/setup-guide.mdx | 22 +++++++++++-------- pages/validators/upgrade.mdx | 2 +- 7 files changed, 39 insertions(+), 24 deletions(-) diff --git a/content/validators/changelog/v0.6.0-rc.2.mdx b/content/validators/changelog/v0.6.0-rc.2.mdx index a91abe12..2e27df4f 100644 --- a/content/validators/changelog/v0.6.0-rc.2.mdx +++ b/content/validators/changelog/v0.6.0-rc.2.mdx @@ -18,6 +18,8 @@ ### Bug fixes +- The release compose file no longer requires `NODE_PASSWORD` for every service: `docker compose up -d webdriver-container` (the binary install) and a full node work with it empty, and a validator still refuses to start without it + - Fix web access from contracts under the release compose file: the GenVM web config is mounted at the path GenVM reads, so GenVM uses the WebDriver container instead of `localhost` - The release compose file no longer replaces the image entrypoint, so the first start precompiles GenVM for the host CPU (the first start takes longer) - The example config sets `genvm.root_dir` to the shipped GenVM (`./opt/genvm/v0.6.0-rc10`), not `v0.6.0-rc1` diff --git a/content/validators/docker-compose-monitoring.yaml b/content/validators/docker-compose-monitoring.yaml index 1da8802e..c7c3c03b 100644 --- a/content/validators/docker-compose-monitoring.yaml +++ b/content/validators/docker-compose-monitoring.yaml @@ -109,12 +109,15 @@ services: - path: ./.env required: false environment: - # ":?" makes Compose refuse to start when NODE_PASSWORD is unset or empty, - # before any container runs. There is no fallback: this is the password - # that encrypts the validator's signing key, and a default shared by every - # operator who forgot to set one is a key anyone can open. The release - # binary carries no default either — it is compiled out of production. - GENLAYERNODE_PASSWORD: "${NODE_PASSWORD:?NODE_PASSWORD is not set. It is the keystore password that unlocks the validator key. Set it in .env; see README section 4.}" + # No default: this is the password that encrypts the validator's signing + # key, and a default shared by every operator who forgot to set one is a + # key anyone can open (the release binary carries none either). The node + # itself refuses validator mode with an empty password ("password is + # required for validator mode"); a full node needs none. Not a ":?" guard: + # Compose interpolates every service, also under another profile, so a + # guard here would stop `docker compose up -d webdriver-container` (the + # binary install path) and a full node, which never read it. + GENLAYERNODE_PASSWORD: "${NODE_PASSWORD:-}" # WebDriver metrics collector reaches Docker via the read-only proxy # (see docker-socket-proxy above), never the host socket directly. DOCKER_HOST: tcp://docker-socket-proxy:2375 diff --git a/content/validators/docker-compose.yaml b/content/validators/docker-compose.yaml index 0a122d54..2c8037f2 100644 --- a/content/validators/docker-compose.yaml +++ b/content/validators/docker-compose.yaml @@ -107,12 +107,15 @@ services: - path: ./.env required: false environment: - # ":?" makes Compose refuse to start when NODE_PASSWORD is unset or empty, - # before any container runs. There is no fallback: this is the password - # that encrypts the validator's signing key, and a default shared by every - # operator who forgot to set one is a key anyone can open. The release - # binary carries no default either — it is compiled out of production. - GENLAYERNODE_PASSWORD: "${NODE_PASSWORD:?NODE_PASSWORD is not set. It is the keystore password that unlocks the validator key. Set it in .env; see README section 4.}" + # No default: this is the password that encrypts the validator's signing + # key, and a default shared by every operator who forgot to set one is a + # key anyone can open (the release binary carries none either). The node + # itself refuses validator mode with an empty password ("password is + # required for validator mode"); a full node needs none. Not a ":?" guard: + # Compose interpolates every service, also under another profile, so a + # guard here would stop `docker compose up -d webdriver-container` (the + # binary install path) and a full node, which never read it. + GENLAYERNODE_PASSWORD: "${NODE_PASSWORD:-}" # WebDriver metrics collector reaches Docker via the read-only proxy # (see docker-socket-proxy above), never the host socket directly. DOCKER_HOST: tcp://docker-socket-proxy:2375 diff --git a/content/validators/env.example b/content/validators/env.example index 22d12372..c65d5345 100644 --- a/content/validators/env.example +++ b/content/validators/env.example @@ -12,8 +12,9 @@ WEBDRIVER_PORT=4444 # Docker image version NODE_VERSION=latest -# Keystore password (unlocks the pre-imported validator wallet). Required — the -# stack refuses to start without it. Choose your own; never reuse an example +# Keystore password (unlocks the pre-imported validator wallet). Required for a +# validator: the node refuses validator mode without it. A full node and the +# WebDriver-only stack need none. Choose your own; never reuse an example # value. It reaches the node as an environment variable, not a command-line # argument. See README section 4 for reading it in without echo. NODE_PASSWORD= diff --git a/pages/validators/changelog.mdx b/pages/validators/changelog.mdx index 7ae50e9e..04f7fd39 100644 --- a/pages/validators/changelog.mdx +++ b/pages/validators/changelog.mdx @@ -20,6 +20,8 @@ ### Bug fixes +- The release compose file no longer requires `NODE_PASSWORD` for every service: `docker compose up -d webdriver-container` (the binary install) and a full node work with it empty, and a validator still refuses to start without it + - Fix web access from contracts under the release compose file: the GenVM web config is mounted at the path GenVM reads, so GenVM uses the WebDriver container instead of `localhost` - The release compose file no longer replaces the image entrypoint, so the first start precompiles GenVM for the host CPU (the first start takes longer) - The example config sets `genvm.root_dir` to the shipped GenVM (`./opt/genvm/v0.6.0-rc10`), not `v0.6.0-rc1` diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index a67a5ddf..12beb74f 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -955,12 +955,15 @@ services: - path: ./.env required: false environment: - # ":?" makes Compose refuse to start when NODE_PASSWORD is unset or empty, - # before any container runs. There is no fallback: this is the password - # that encrypts the validator's signing key, and a default shared by every - # operator who forgot to set one is a key anyone can open. The release - # binary carries no default either — it is compiled out of production. - GENLAYERNODE_PASSWORD: "${NODE_PASSWORD:?NODE_PASSWORD is not set. It is the keystore password that unlocks the validator key. Set it in .env; see README section 4.}" + # No default: this is the password that encrypts the validator's signing + # key, and a default shared by every operator who forgot to set one is a + # key anyone can open (the release binary carries none either). The node + # itself refuses validator mode with an empty password ("password is + # required for validator mode"); a full node needs none. Not a ":?" guard: + # Compose interpolates every service, also under another profile, so a + # guard here would stop `docker compose up -d webdriver-container` (the + # binary install path) and a full node, which never read it. + GENLAYERNODE_PASSWORD: "${NODE_PASSWORD:-}" # WebDriver metrics collector reaches Docker via the read-only proxy # (see docker-socket-proxy above), never the host socket directly. DOCKER_HOST: tcp://docker-socket-proxy:2375 @@ -1023,8 +1026,9 @@ WEBDRIVER_PORT=4444 # Docker image version NODE_VERSION=latest -# Keystore password (unlocks the pre-imported validator wallet). Required — the -# stack refuses to start without it. Choose your own; never reuse an example +# Keystore password (unlocks the pre-imported validator wallet). Required for a +# validator: the node refuses validator mode without it. A full node and the +# WebDriver-only stack need none. Choose your own; never reuse an example # value. It reaches the node as an environment variable, not a command-line # argument. See README section 4 for reading it in without echo. NODE_PASSWORD= @@ -1207,7 +1211,7 @@ HEALTHCHECK_AUTO_RESTART=true This is the release package's `.env.example`: copy it to `.env` next to `docker-compose.yaml`. The release package also ships `docker-compose.yaml` and `genvm-module-web-docker.yaml`. Set at least: -- `NODE_PASSWORD`: the keystore password. It is required: Docker Compose refuses to start without it, and there is no default. +- `NODE_PASSWORD`: the keystore password. A validator needs it: the node refuses validator mode without a password, and there is no default. A full node and the WebDriver-only stack need none. - `NODE_VERSION`: pin the exact version approved for Bradbury Phase 2. The `latest` image can belong to an older release family. - the API key of at least one LLM provider. - `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY`, when your chain RPC provider needs a key (the Bradbury WebSocket does). diff --git a/pages/validators/upgrade.mdx b/pages/validators/upgrade.mdx index ca2e9372..ebf2b1c2 100644 --- a/pages/validators/upgrade.mdx +++ b/pages/validators/upgrade.mdx @@ -59,7 +59,7 @@ Breaking changes involve a new consensus deployment. The old database is incompa - The Bradbury RPC WebSocket needs an API key. Set it in the environment as `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY`; it has no `config.yaml` key. See [Testnet Bradbury](/validators/setup-guide#testnet-bradbury). - The `genlayer_debug` (`gen_dbg_*`) and `zksync` (`zks_*`) RPC groups are off in the default `config.yaml` (`node.rpc.endpoints`). - The data store is split into `genvm.db` and `ops.db`. On startup the node renames an old `genlayer.db` to `genvm.db`; it refuses to start if both exist. -- Docker: `NODE_PASSWORD` is required and has no default. It reaches the node as `GENLAYERNODE_PASSWORD`, not as `--password`. See [Running the Node using docker-compose](/validators/setup-guide#running-the-node-using-docker-compose). +- Docker: `NODE_PASSWORD` has no default, and a validator needs it: the node refuses validator mode without a password. It reaches the node as `GENLAYERNODE_PASSWORD`, not as `--password`. See [Running the Node using docker-compose](/validators/setup-guide#running-the-node-using-docker-compose). - Docker: the node container runs as uid `10001`. Make `./data` writable by it once: `sudo chown -R 10001:10001 ./data`. - Docker: the node no longer mounts `/var/run/docker.sock`. It reaches Docker through the read-only `docker-socket-proxy` service. - Monitoring: the `CENTRAL_*` URLs and credentials have no defaults. Alloy stops at startup if any is unset. See [Monitoring](/validators/monitoring). From 9973979f03ef8a07c9a2e015c390c9c9b1998bbd Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 22:29:33 +0200 Subject: [PATCH 30/39] docs(validators): sync genlayer-node#2271 at c26437fb9 The compose file names its project genlayer-node, so a Docker update from a new version directory replaces the old containers; the upgrade guide's v0.6 notes say so. rc.2 changelog: two more lines. Co-Authored-By: Claude Opus 5.5 --- content/validators/changelog/v0.6.0-rc.2.mdx | 2 ++ content/validators/docker-compose-monitoring.yaml | 6 ++++++ content/validators/docker-compose.yaml | 5 +++++ pages/validators/changelog.mdx | 2 ++ pages/validators/setup-guide.mdx | 5 +++++ pages/validators/upgrade.mdx | 1 + 6 files changed, 21 insertions(+) diff --git a/content/validators/changelog/v0.6.0-rc.2.mdx b/content/validators/changelog/v0.6.0-rc.2.mdx index 2e27df4f..1ee62da0 100644 --- a/content/validators/changelog/v0.6.0-rc.2.mdx +++ b/content/validators/changelog/v0.6.0-rc.2.mdx @@ -19,6 +19,8 @@ ### Bug fixes - The release compose file no longer requires `NODE_PASSWORD` for every service: `docker compose up -d webdriver-container` (the binary install) and a full node work with it empty, and a validator still refuses to start without it +- The release compose file has a fixed project name (`genlayer-node`), so starting the stack from a new release directory replaces the old one's containers instead of failing with `container name ... is already in use` +- `install.sh` keeps `genvm.root_dir: ./opt/genvm/current` in a package that has that link, as the docs say - Fix web access from contracts under the release compose file: the GenVM web config is mounted at the path GenVM reads, so GenVM uses the WebDriver container instead of `localhost` - The release compose file no longer replaces the image entrypoint, so the first start precompiles GenVM for the host CPU (the first start takes longer) diff --git a/content/validators/docker-compose-monitoring.yaml b/content/validators/docker-compose-monitoring.yaml index c7c3c03b..89ed4db9 100644 --- a/content/validators/docker-compose-monitoring.yaml +++ b/content/validators/docker-compose-monitoring.yaml @@ -1,3 +1,9 @@ +# A fixed project name: the container names below are fixed too, so a stack +# started from a new release directory must be the SAME Compose project, or +# Docker refuses with "container name ... is already in use". With it, `up -d` +# from the new version's directory recreates the old version's containers. +name: genlayer-node + services: webdriver-container: container_name: genlayer-node-webdriver diff --git a/content/validators/docker-compose.yaml b/content/validators/docker-compose.yaml index 2c8037f2..6f2f2690 100644 --- a/content/validators/docker-compose.yaml +++ b/content/validators/docker-compose.yaml @@ -1,3 +1,8 @@ +# A fixed project name: the container names below are fixed too, so a stack +# started from a new release directory must be the SAME Compose project, or +# Docker refuses with "container name ... is already in use". With it, `up -d` +# from the new version's directory recreates the old version's containers. +name: genlayer-node services: webdriver-container: container_name: genlayer-node-webdriver diff --git a/pages/validators/changelog.mdx b/pages/validators/changelog.mdx index 04f7fd39..ece6e429 100644 --- a/pages/validators/changelog.mdx +++ b/pages/validators/changelog.mdx @@ -21,6 +21,8 @@ ### Bug fixes - The release compose file no longer requires `NODE_PASSWORD` for every service: `docker compose up -d webdriver-container` (the binary install) and a full node work with it empty, and a validator still refuses to start without it +- The release compose file has a fixed project name (`genlayer-node`), so starting the stack from a new release directory replaces the old one's containers instead of failing with `container name ... is already in use` +- `install.sh` keeps `genvm.root_dir: ./opt/genvm/current` in a package that has that link, as the docs say - Fix web access from contracts under the release compose file: the GenVM web config is mounted at the path GenVM reads, so GenVM uses the WebDriver container instead of `localhost` - The release compose file no longer replaces the image entrypoint, so the first start precompiles GenVM for the host CPU (the first start takes longer) diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index 12beb74f..3a57e992 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -846,6 +846,11 @@ Before the first start, in the directory with your `docker-compose.yaml`: 1. Create a `docker-compose.yaml` file with the following content: ```yaml copy +# A fixed project name: the container names below are fixed too, so a stack +# started from a new release directory must be the SAME Compose project, or +# Docker refuses with "container name ... is already in use". With it, `up -d` +# from the new version's directory recreates the old version's containers. +name: genlayer-node services: webdriver-container: container_name: genlayer-node-webdriver diff --git a/pages/validators/upgrade.mdx b/pages/validators/upgrade.mdx index ebf2b1c2..3292b7d7 100644 --- a/pages/validators/upgrade.mdx +++ b/pages/validators/upgrade.mdx @@ -62,6 +62,7 @@ Breaking changes involve a new consensus deployment. The old database is incompa - Docker: `NODE_PASSWORD` has no default, and a validator needs it: the node refuses validator mode without a password. It reaches the node as `GENLAYERNODE_PASSWORD`, not as `--password`. See [Running the Node using docker-compose](/validators/setup-guide#running-the-node-using-docker-compose). - Docker: the node container runs as uid `10001`. Make `./data` writable by it once: `sudo chown -R 10001:10001 ./data`. - Docker: the node no longer mounts `/var/run/docker.sock`. It reaches Docker through the read-only `docker-socket-proxy` service. +- Docker: the compose project is named `genlayer-node`, so `docker compose up` from a new version's directory replaces the containers of the previous version instead of failing with "container name ... already in use". - Monitoring: the `CENTRAL_*` URLs and credentials have no defaults. Alloy stops at startup if any is unset. See [Monitoring](/validators/monitoring). - Do not use `genlayernode run --upgrade` in v0.6: the call that resumes the node is not available yet, so the node waits forever. From 7dc549a17d5cc1f36fad012108e4b763b0824891 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 22:31:46 +0200 Subject: [PATCH 31/39] docs(validators): list the staking wizard's nine steps The v0.40 wizard (genlayer-cli fix/bradbury-phase-2-v040, also in v0.40.0-rc.3) adds a funding-source step (wallet or vesting contract) and a closing summary; identity setup can be skipped with --skip-identity. Reported by the genlayer-js session. Co-Authored-By: Claude Opus 5.5 --- pages/validators/setup-guide.mdx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index 3a57e992..0dc6c9fa 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -137,11 +137,13 @@ The wizard guides you through: 1. **Account setup** - Create or select your owner account 2. **Network selection** - Choose testnet-bradbury -3. **Balance verification** - Checks your balance against the deployed minimum and gas requirement -4. **Operator setup** - Create and export an operator keystore for your validator server -5. **Stake amount** - Enter how much GEN to self-stake using the current on-chain minimum -6. **Validator creation** - Submits the staking transaction -7. **Identity setup** - Set your validator's public profile (moniker, website, etc.) +3. **Funding source** - Stake from your wallet or from a vesting contract +4. **Balance verification** - Checks your balance against the deployed minimum and gas requirement +5. **Operator setup** - Create and export an operator keystore for your validator server +6. **Stake amount** - Enter how much GEN to self-stake using the current on-chain minimum +7. **Validator creation** - Submits the staking transaction +8. **Identity setup** - Set your validator's public profile (moniker, website, etc.); skip it with `--skip-identity` +9. **Summary** - Shows the owner, operator and validator wallet addresses **Save Your Validator Wallet Address!** After the wizard completes, note down your **Validator Wallet** address. You'll need this for your node configuration. From 045c9cec28dfa770bd30ec1887bba93a9d70b90a Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 22:50:23 +0200 Subject: [PATCH 32/39] docs(validators): v0.6 upgrade: move Docker GenVM config mounts to opt/genvm/current A config mounted at /app/third_party/genvm/config/ in v0.5 (for example a customized LLM config for greyboxing) is ignored without an error in v0.6, which reads /app/opt/genvm/current/config/. Co-Authored-By: Claude Opus 5.5 --- pages/validators/upgrade.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/pages/validators/upgrade.mdx b/pages/validators/upgrade.mdx index 3292b7d7..a173377f 100644 --- a/pages/validators/upgrade.mdx +++ b/pages/validators/upgrade.mdx @@ -62,6 +62,7 @@ Breaking changes involve a new consensus deployment. The old database is incompa - Docker: `NODE_PASSWORD` has no default, and a validator needs it: the node refuses validator mode without a password. It reaches the node as `GENLAYERNODE_PASSWORD`, not as `--password`. See [Running the Node using docker-compose](/validators/setup-guide#running-the-node-using-docker-compose). - Docker: the node container runs as uid `10001`. Make `./data` writable by it once: `sudo chown -R 10001:10001 ./data`. - Docker: the node no longer mounts `/var/run/docker.sock`. It reaches Docker through the read-only `docker-socket-proxy` service. +- Docker: if you mount your own GenVM config files (for example a customized LLM config for greyboxing, in a `docker-compose.override.yml`), move every mount from `/app/third_party/genvm/config/` to `/app/opt/genvm/current/config/`. v0.6 no longer reads the old path, so a mount left there is ignored without an error and the image's default config loads. - Docker: the compose project is named `genlayer-node`, so `docker compose up` from a new version's directory replaces the containers of the previous version instead of failing with "container name ... already in use". - Monitoring: the `CENTRAL_*` URLs and credentials have no defaults. Alloy stops at startup if any is unset. See [Monitoring](/validators/monitoring). - Do not use `genlayernode run --upgrade` in v0.6: the call that resumes the node is not available yet, so the node waits forever. From 04c387fe9a1410480ddbe6d84ad9e11bd93e7142 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 22:51:22 +0200 Subject: [PATCH 33/39] docs(validators): sync genlayer-node#2272 at 62454eac8 (rc.2 changelog) Co-Authored-By: Claude Opus 5.5 --- content/validators/changelog/v0.6.0-rc.2.mdx | 1 + pages/validators/changelog.mdx | 1 + 2 files changed, 2 insertions(+) diff --git a/content/validators/changelog/v0.6.0-rc.2.mdx b/content/validators/changelog/v0.6.0-rc.2.mdx index 1ee62da0..2e0bc7ad 100644 --- a/content/validators/changelog/v0.6.0-rc.2.mdx +++ b/content/validators/changelog/v0.6.0-rc.2.mdx @@ -8,6 +8,7 @@ ### Misc +- Docker: mount custom GenVM configs (for example `genvm-module-llm.yaml` for OpenRouter/greybox) at `/app/opt/genvm/current/config/`; v0.6 no longer reads `/app/third_party/genvm/config/`, so a v0.5 `docker-compose.override.yml` mount there is ignored and the image default loads - `/health` no longer returns `severity` in `conditions`; use `state` for the current health - The release compose file mounts the protocol manifest from `NODE_MANIFEST_PATH` (default `./configs/node/protocol-manifest.json`) at the path that `consensus.manifestpath` names - The release package links `opt/genvm/current` to its GenVM tree, and its `config.yaml` sets `genvm.root_dir: ./opt/genvm/current` diff --git a/pages/validators/changelog.mdx b/pages/validators/changelog.mdx index ece6e429..b4125910 100644 --- a/pages/validators/changelog.mdx +++ b/pages/validators/changelog.mdx @@ -10,6 +10,7 @@ ### Misc +- Docker: mount custom GenVM configs (for example `genvm-module-llm.yaml` for OpenRouter/greybox) at `/app/opt/genvm/current/config/`; v0.6 no longer reads `/app/third_party/genvm/config/`, so a v0.5 `docker-compose.override.yml` mount there is ignored and the image default loads - `/health` no longer returns `severity` in `conditions`; use `state` for the current health - The release compose file mounts the protocol manifest from `NODE_MANIFEST_PATH` (default `./configs/node/protocol-manifest.json`) at the path that `consensus.manifestpath` names - The release package links `opt/genvm/current` to its GenVM tree, and its `config.yaml` sets `genvm.root_dir: ./opt/genvm/current` From 6e331db9bd6d0eefa5b44b3e2a1fa22822981bf3 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Mon, 19 Oct 2026 23:29:22 +0200 Subject: [PATCH 34/39] docs(validators): AI-assisted setup matches the genlayernode skill (skills#45) The skill checks Docker, Python and cosign on the server and installs what is missing after confirmation; Node.js is only needed where the GenLayer CLI runs. It also sets up full nodes. Co-Authored-By: Claude Opus 5.5 --- pages/validators/setup-guide.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index 0dc6c9fa..37c9eaad 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -41,11 +41,11 @@ We've released an interactive AI wizard that guides you through the entire valid #### What it does - Walks you through all setup steps interactively -- Checks prerequisites automatically (architecture, RAM, Docker, Node.js, Python) +- Checks prerequisites automatically (architecture, RAM, Docker, Python, cosign) and installs what is missing after you confirm; Node.js is needed only on the machine that runs the GenLayer CLI - Detects common issues before they cause problems (port conflicts, disk space, wrong architecture) - Generates configurations based on your answers - Supports multiple deployment options: local machine, remote SSH, GCP, AWS, Azure -- Handles both new validators (staking wizard) and existing validators +- Handles full nodes, new validators (staking wizard) and existing validators - Includes minimal-downtime update procedure for upgrades #### How to use it From 0305d1fa45408f3e6e254c95388253bb42121b54 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Tue, 20 Oct 2026 00:02:49 +0200 Subject: [PATCH 35/39] docs(validators): publish up to v0.6.0-rc.1; rc.2 comes later Removes the v0.6.0-rc.2 changelog entry, so the setup guide and the changelog page list and name v0.6.0-rc.1. The rc.2 entry waits on branch docs/v0.6.0-rc.2 until rc.2 is published. Co-Authored-By: Claude Opus 5.5 --- content/validators/changelog/v0.6.0-rc.2.mdx | 30 ------------------- pages/validators/changelog.mdx | 31 -------------------- pages/validators/setup-guide.mdx | 3 +- 3 files changed, 1 insertion(+), 63 deletions(-) delete mode 100644 content/validators/changelog/v0.6.0-rc.2.mdx diff --git a/content/validators/changelog/v0.6.0-rc.2.mdx b/content/validators/changelog/v0.6.0-rc.2.mdx deleted file mode 100644 index 2e0bc7ad..00000000 --- a/content/validators/changelog/v0.6.0-rc.2.mdx +++ /dev/null @@ -1,30 +0,0 @@ -## v0.6.0-rc.2 - -### New features - -- Check WebDriver rendering in `/health` and `doctor` with the same fresh render of a test page (10 s limit); a failure sets the `webdriver.render` condition to `degraded` with reason `webdriver_check_failed` -- The release compose file restarts a WebDriver that stops rendering (after 4 failed health checks) and an Alloy that stays unhealthy; set `HEALTHCHECK_AUTO_RESTART=false` to keep the health checks and turn off the restarts -- The release monitoring profile sends the restart events of WebDriver and Alloy to central monitoring, with the `NETWORK_NAME` label from `.env` - -### Misc - -- Docker: mount custom GenVM configs (for example `genvm-module-llm.yaml` for OpenRouter/greybox) at `/app/opt/genvm/current/config/`; v0.6 no longer reads `/app/third_party/genvm/config/`, so a v0.5 `docker-compose.override.yml` mount there is ignored and the image default loads -- `/health` no longer returns `severity` in `conditions`; use `state` for the current health -- The release compose file mounts the protocol manifest from `NODE_MANIFEST_PATH` (default `./configs/node/protocol-manifest.json`) at the path that `consensus.manifestpath` names -- The release package links `opt/genvm/current` to its GenVM tree, and its `config.yaml` sets `genvm.root_dir: ./opt/genvm/current` -- The example `.env` lists all 11 LLM provider keys of the release LLM config, and sets the monitoring defaults `METRICS_SCRAPE_INTERVAL=60s` and `LOKI_BATCH_WAIT=60s` -- The release README shows how to run `doctor` under Docker -- Update `tecnativa/docker-socket-proxy` to v0.5.0; the proxy also gives Alloy read-only network data -- Update `github.com/ethereum/go-ethereum` to v1.17.8, `labstack/echo` to v4.16.0, `golang.org/x/crypto` to v0.58.0 and `golang.org/x/net` to v0.61.0 - -### Bug fixes - -- The release compose file no longer requires `NODE_PASSWORD` for every service: `docker compose up -d webdriver-container` (the binary install) and a full node work with it empty, and a validator still refuses to start without it -- The release compose file has a fixed project name (`genlayer-node`), so starting the stack from a new release directory replaces the old one's containers instead of failing with `container name ... is already in use` -- `install.sh` keeps `genvm.root_dir: ./opt/genvm/current` in a package that has that link, as the docs say - -- Fix web access from contracts under the release compose file: the GenVM web config is mounted at the path GenVM reads, so GenVM uses the WebDriver container instead of `localhost` -- The release compose file no longer replaces the image entrypoint, so the first start precompiles GenVM for the host CPU (the first start takes longer) -- The example config sets `genvm.root_dir` to the shipped GenVM (`./opt/genvm/v0.6.0-rc10`), not `v0.6.0-rc1` -- Update the GenVM web config of the release to the schema and keys of the shipped GenVM -- Correct the API reference: `LeaderRevealing` is status `13`, and the vote values include `5` (`DeterministicViolation`) diff --git a/pages/validators/changelog.mdx b/pages/validators/changelog.mdx index b4125910..bac7ca4b 100644 --- a/pages/validators/changelog.mdx +++ b/pages/validators/changelog.mdx @@ -1,36 +1,5 @@ # Changelog -## v0.6.0-rc.2 - -### New features - -- Check WebDriver rendering in `/health` and `doctor` with the same fresh render of a test page (10 s limit); a failure sets the `webdriver.render` condition to `degraded` with reason `webdriver_check_failed` -- The release compose file restarts a WebDriver that stops rendering (after 4 failed health checks) and an Alloy that stays unhealthy; set `HEALTHCHECK_AUTO_RESTART=false` to keep the health checks and turn off the restarts -- The release monitoring profile sends the restart events of WebDriver and Alloy to central monitoring, with the `NETWORK_NAME` label from `.env` - -### Misc - -- Docker: mount custom GenVM configs (for example `genvm-module-llm.yaml` for OpenRouter/greybox) at `/app/opt/genvm/current/config/`; v0.6 no longer reads `/app/third_party/genvm/config/`, so a v0.5 `docker-compose.override.yml` mount there is ignored and the image default loads -- `/health` no longer returns `severity` in `conditions`; use `state` for the current health -- The release compose file mounts the protocol manifest from `NODE_MANIFEST_PATH` (default `./configs/node/protocol-manifest.json`) at the path that `consensus.manifestpath` names -- The release package links `opt/genvm/current` to its GenVM tree, and its `config.yaml` sets `genvm.root_dir: ./opt/genvm/current` -- The example `.env` lists all 11 LLM provider keys of the release LLM config, and sets the monitoring defaults `METRICS_SCRAPE_INTERVAL=60s` and `LOKI_BATCH_WAIT=60s` -- The release README shows how to run `doctor` under Docker -- Update `tecnativa/docker-socket-proxy` to v0.5.0; the proxy also gives Alloy read-only network data -- Update `github.com/ethereum/go-ethereum` to v1.17.8, `labstack/echo` to v4.16.0, `golang.org/x/crypto` to v0.58.0 and `golang.org/x/net` to v0.61.0 - -### Bug fixes - -- The release compose file no longer requires `NODE_PASSWORD` for every service: `docker compose up -d webdriver-container` (the binary install) and a full node work with it empty, and a validator still refuses to start without it -- The release compose file has a fixed project name (`genlayer-node`), so starting the stack from a new release directory replaces the old one's containers instead of failing with `container name ... is already in use` -- `install.sh` keeps `genvm.root_dir: ./opt/genvm/current` in a package that has that link, as the docs say - -- Fix web access from contracts under the release compose file: the GenVM web config is mounted at the path GenVM reads, so GenVM uses the WebDriver container instead of `localhost` -- The release compose file no longer replaces the image entrypoint, so the first start precompiles GenVM for the host CPU (the first start takes longer) -- The example config sets `genvm.root_dir` to the shipped GenVM (`./opt/genvm/v0.6.0-rc10`), not `v0.6.0-rc1` -- Update the GenVM web config of the release to the schema and keys of the shipped GenVM -- Correct the API reference: `LeaderRevealing` is status `13`, and the vote values include `5` (`DeterministicViolation`) - ## v0.6.0-rc.1 v0.6 is a breaking release. It runs only against the v0.6 consensus contracts (release tag `v0.6-rc-1`), and all validators upgrade together: receipt hashes, storage-fee votes, spendable contract balance and child budgets change. diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index 37c9eaad..16e739fc 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -237,7 +237,6 @@ One full node can be shared between multiple validators. The optimal validator-t You should see a list like this ```sh - v0.6.0-rc.2 v0.6.0-rc.1 ``` @@ -245,7 +244,7 @@ One full node can be shared between multiple validators. The optimal validator-t 2. Download the packaged application ```sh copy - export version=v0.6.0-rc.2 # example target; confirm it is published and approved before use + export version=v0.6.0-rc.1 # example target; confirm it is published and approved before use base=https://storage.googleapis.com/gh-af/genlayer-node/bin/amd64/${version} curl -fI "${base}/genlayer-node-linux-amd64-${version}.tar.gz" curl -fI "${base}/genlayer-node_${version}_SHA256SUMS" From 8a1ab1ea03f06f6c01f46ea336333502a0d4ce70 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Tue, 20 Oct 2026 00:32:45 +0200 Subject: [PATCH 36/39] ci(node-sync): SYNC_TYPES runs a subset of sync types locally For pinning the files a release shipped to its tag while the rest come from a newer ref. The container gets SYNC_TYPES; check-node-sync still checks every type. Co-Authored-By: Claude Opus 5.5 --- .github/scripts/node-sync-params.sh | 19 +++++++++++++++++-- scripts/check-node-sync.js | 4 +++- scripts/simulate-node-sync.sh | 5 ++++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/.github/scripts/node-sync-params.sh b/.github/scripts/node-sync-params.sh index 197f5f6c..8b85e8a6 100755 --- a/.github/scripts/node-sync-params.sh +++ b/.github/scripts/node-sync-params.sh @@ -15,6 +15,9 @@ set -euo pipefail # SOURCE_ROOT where the node snapshot is checked out (default: source-repo) # CHANGELOG_PATH, API_GEN_PATH, API_DEBUG_PATH, API_OPS_PATH, # API_GEN_REGEX, API_DEBUG_REGEX: the workflow_dispatch overrides +# SYNC_TYPES optional space-separated subset of sync types for --list +# and --sources (local runs only, e.g. to pin some files to +# an older node tag); default: all types SOURCE_ROOT="${SOURCE_ROOT:-source-repo}" CHANGELOG_PATH="${CHANGELOG_PATH:-docs/changelog}" @@ -38,12 +41,24 @@ api_gen|API Gen Methods|${API_GEN_PATH}|pages/api-references/genlayer-node/gen|$ api_debug|API Debug Methods|${API_DEBUG_PATH}|pages/api-references/genlayer-node/debug|${API_DEBUG_REGEX} api_ops|API Ops Methods|${API_OPS_PATH}|pages/api-references/genlayer-node/ops|.*" +# Rows of the selected types (all rows when SYNC_TYPES is unset). +selected() { + if [[ -z "${SYNC_TYPES:-}" ]]; then + printf '%s\n' "$TABLE" + return + fi + local t + for t in $SYNC_TYPES; do + awk -F'|' -v t="$t" '$1 == t' <<< "$TABLE" | grep . || { echo "Unknown sync type: $t" >&2; exit 1; } + done +} + case "${1:-}" in --list) - cut -d'|' -f1 <<< "$TABLE" + selected | cut -d'|' -f1 ;; --sources) - cut -d'|' -f3 <<< "$TABLE" | sort -u + selected | cut -d'|' -f3 | sort -u ;; "") echo "Usage: $0 --list | --sources | " >&2 diff --git a/scripts/check-node-sync.js b/scripts/check-node-sync.js index d47e6441..710c62dc 100644 --- a/scripts/check-node-sync.js +++ b/scripts/check-node-sync.js @@ -27,7 +27,9 @@ const failures = []; const fail = (msg) => failures.push(msg); // 1. Every sync target exists. -const types = execFileSync('bash', ['.github/scripts/node-sync-params.sh', '--list'], { cwd: root, encoding: 'utf8' }) +// Always every type, whatever SYNC_TYPES a local run used. +const allTypes = { ...process.env, SYNC_TYPES: '' }; +const types = execFileSync('bash', ['.github/scripts/node-sync-params.sh', '--list'], { cwd: root, encoding: 'utf8', env: allTypes }) .trim().split('\n'); for (const type of types) { const out = execFileSync('bash', ['.github/scripts/node-sync-params.sh', type], { cwd: root, encoding: 'utf8' }); diff --git a/scripts/simulate-node-sync.sh b/scripts/simulate-node-sync.sh index 6396d887..4ee2494b 100755 --- a/scripts/simulate-node-sync.sh +++ b/scripts/simulate-node-sync.sh @@ -13,6 +13,9 @@ set -euo pipefail # tag, branch or commit in the node repo (e.g. v0.6.0-rc.2, origin/v0.6-dev) # Environment: # NODE_REPO local genlayer-node clone (default: ../genlayer-node); fetch it first. +# SYNC_TYPES optional subset of sync types (node-sync-params.sh --list) to take +# from , e.g. to pin the files a release shipped: +# SYNC_TYPES="config docker_compose" scripts/simulate-node-sync.sh v0.6.0-rc.1 # # Needs: git, node, and for the sync step GNU sed + yq v4 + perl. Where those are # missing (macOS), the sync step runs in a debian:bookworm-slim container. @@ -34,7 +37,7 @@ if sed --version >/dev/null 2>&1 && yq --version 2>/dev/null | grep -q 'version bash .github/scripts/run-sync-matrix.sh "$SNAPSHOT" else echo "GNU sed or yq v4 not found: running the sync step in a container" - docker run --rm -v "$ROOT:/docs" -v "$SNAPSHOT:/src:ro" -w /docs debian:bookworm-slim bash -c ' + docker run --rm -e SYNC_TYPES -v "$ROOT:/docs" -v "$SNAPSHOT:/src:ro" -w /docs debian:bookworm-slim bash -c ' set -e apt-get -qq update >/dev/null apt-get -qq install -y wget ca-certificates perl >/dev/null From c4a09c85efe8b1209642e46817c97cd0f1c772eb Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Tue, 20 Oct 2026 00:33:59 +0200 Subject: [PATCH 37/39] docs: GenLayer Chain RPC endpoints instead of Conduit/Orbit ones Bradbury Phase 2: https://rpc.testnet-chain.genlayer.com and wss://rpc.testnet-chain.genlayer.com (same chain: genesis 0x17d8...97e4; the WebSocket needs an API key). The config placeholders no longer say Orbit. The Bradbury config block is synced from the node and follows once genlayer-node changes bradbury.yaml.example. Co-Authored-By: Claude Opus 5.5 --- .github/scripts/sanitize-config.sh | 6 +++--- content/validators/config.yaml | 6 +++--- pages/developers/networks.mdx | 8 +------- pages/validators/setup-guide.mdx | 19 ++++++++++++------- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/.github/scripts/sanitize-config.sh b/.github/scripts/sanitize-config.sh index 2cbe22df..30b26a0c 100644 --- a/.github/scripts/sanitize-config.sh +++ b/.github/scripts/sanitize-config.sh @@ -22,9 +22,9 @@ echo "Sanitizing config file: $CONFIG_FILE" # Use yq to: # Replace RPC URLs / provider with TODO placeholders yq -i ' - .rollup.genlayerchainrpcurl = "TODO: Set your GenLayer Chain Orbit HTTP RPC URL here" | - .rollup.genlayerchainwebsocketurl = "TODO: Set your GenLayer Chain Orbit WebSocket RPC URL here" | - .rollup.provider = "TODO: Set your GenLayer Chain Orbit provider" + .rollup.genlayerchainrpcurl = "TODO: Set your GenLayer Chain HTTP RPC URL here" | + .rollup.genlayerchainwebsocketurl = "TODO: Set your GenLayer Chain WebSocket RPC URL here" | + .rollup.provider = "TODO: Set your GenLayer Chain provider" ' "$CONFIG_FILE" echo "Config sanitization completed" diff --git a/content/validators/config.yaml b/content/validators/config.yaml index 65cc93bc..0ff0edec 100644 --- a/content/validators/config.yaml +++ b/content/validators/config.yaml @@ -1,12 +1,12 @@ # rollup configuration rollup: - genlayerchainrpcurl: "TODO: Set your GenLayer Chain Orbit HTTP RPC URL here" # GenLayer Chain RPC URL - genlayerchainwebsocketurl: "TODO: Set your GenLayer Chain Orbit WebSocket RPC URL here" # GenLayer Chain WebSocket URL + genlayerchainrpcurl: "TODO: Set your GenLayer Chain HTTP RPC URL here" # GenLayer Chain RPC URL + genlayerchainwebsocketurl: "TODO: Set your GenLayer Chain WebSocket RPC URL here" # GenLayer Chain WebSocket URL # An API key of the RPC provider never goes in these URLs or in this file: # set it in the environment as GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY. # The node sends it in this header (default X-AccessKey): # genlayerchainrpckeyheader: "X-AccessKey" - provider: "TODO: Set your GenLayer Chain Orbit provider" # Rollup operator name; surfaces as the "provider" label on RPC-driven metrics. Set explicitly per deployment. + provider: "TODO: Set your GenLayer Chain provider" # Rollup operator name; surfaces as the "provider" label on RPC-driven metrics. Set explicitly per deployment. # consensus contracts configuration consensus: # The deployment-specific fields are empty: take them from your network's documentation. diff --git a/pages/developers/networks.mdx b/pages/developers/networks.mdx index 253c2af0..07fe0e9b 100644 --- a/pages/developers/networks.mdx +++ b/pages/developers/networks.mdx @@ -35,13 +35,7 @@ Production-like testnet with real AI/LLM workloads. Bradbury Phase 2 reuses chain ID **4221**. Verify block zero has hash `0x17d89764d4f4f963158fc4c5e8f2cb670c2837740b35f37081c12860c3df97e4`, and the AddressManager is `0x3F71AE080BD6270A91dBcc7960d88797828538E0`. An old RPC can return the right chain ID but lead to a different deployment. If a public domain returns a different genesis, stop and have its routing corrected. -Direct provider endpoints for Phase 2 qualification are: - -- HTTP: `https://rpc-genlayer-chain-testnet-cfn8m3xc6l.t.conduit.xyz` -- WebSocket: `wss://rpc-genlayer-chain-testnet-cfn8m3xc6l.t.conduit.xyz` (assigned API key required) -- Chain explorer: `https://explorer-genlayer-chain-testnet-cfn8m3xc6l.t.conduit.xyz/` - -Create a Conduit key at [app.conduit.xyz/rpc-keys](https://app.conduit.xyz/rpc-keys) and configure it as `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY` in the node environment (or the `.env` next to `docker-compose.yaml`). Never add it to URLs or `config.yaml`. Resolve Staking and ConsensusMain through the AddressManager instead of treating their initial addresses as permanent network identity. +The GenLayer Chain WebSocket is `wss://rpc.testnet-chain.genlayer.com` and requires an API key. Create a key at [app.conduit.xyz/rpc-keys](https://app.conduit.xyz/rpc-keys) and configure it as `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY` in the node environment (or the `.env` next to `docker-compose.yaml`). Never add it to URLs or `config.yaml`. Resolve Staking and ConsensusMain through the AddressManager instead of treating their initial addresses as permanent network identity. Public domains above are the intended release endpoints. Check their readiness and the faucet's target network before transferring funds; do not assume old balances or registrations carry over. diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index 16e739fc..e8453536 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -208,11 +208,16 @@ Possible options: See the [GenVM Configuration](/validators/genvm-configuration) page for partner credits, provider setup, and the greybox fallback strategy. -#### Orbit RPC access for the GenLayer Chain +#### GenLayer Chain RPC access -Bradbury Phase 2 runs on an **Arbitrum Orbit L2**. Each validator needs HTTP and WebSocket access to that network to read state, send transactions and subscribe to chain updates. An old zkSync endpoint is incompatible even when it returns the same chain ID. +Each validator needs HTTP and WebSocket access to the GenLayer Chain to read state, send transactions and subscribe to chain updates. For Bradbury Phase 2: -Create a key at [app.conduit.xyz/rpc-keys](https://app.conduit.xyz/rpc-keys). The Conduit WebSocket requires a key; anonymous HTTP access does not prove that WebSocket subscriptions will work. Set it in the node's environment: +- HTTP: `https://rpc.testnet-chain.genlayer.com` +- WebSocket: `wss://rpc.testnet-chain.genlayer.com` (API key required) + +An endpoint of the previous Bradbury chain is incompatible even when it returns the same chain ID. + +Create a key at [app.conduit.xyz/rpc-keys](https://app.conduit.xyz/rpc-keys). The WebSocket requires a key; anonymous HTTP access does not prove that WebSocket subscriptions will work. Set it in the node's environment: ```sh copy export GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY="" @@ -318,13 +323,13 @@ The `config.yaml` in the release package sets `genvm.root_dir: ./opt/genvm/curre ```yaml copy # rollup configuration rollup: - genlayerchainrpcurl: "TODO: Set your GenLayer Chain Orbit HTTP RPC URL here" # GenLayer Chain RPC URL - genlayerchainwebsocketurl: "TODO: Set your GenLayer Chain Orbit WebSocket RPC URL here" # GenLayer Chain WebSocket URL + genlayerchainrpcurl: "TODO: Set your GenLayer Chain HTTP RPC URL here" # GenLayer Chain RPC URL + genlayerchainwebsocketurl: "TODO: Set your GenLayer Chain WebSocket RPC URL here" # GenLayer Chain WebSocket URL # An API key of the RPC provider never goes in these URLs or in this file: # set it in the environment as GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY. # The node sends it in this header (default X-AccessKey): # genlayerchainrpckeyheader: "X-AccessKey" - provider: "TODO: Set your GenLayer Chain Orbit provider" # Rollup operator name; surfaces as the "provider" label on RPC-driven metrics. Set explicitly per deployment. + provider: "TODO: Set your GenLayer Chain provider" # Rollup operator name; surfaces as the "provider" label on RPC-driven metrics. Set explicitly per deployment. # consensus contracts configuration consensus: # The deployment-specific fields are empty: take them from your network's documentation. @@ -622,7 +627,7 @@ curl -fsSL https://docs.genlayer.com/validators/bradbury.protocol-manifest.json The binary reads it from that path. Docker Compose mounts the same file into the container (`NODE_MANIFEST_PATH`, default `./configs/node/protocol-manifest.json`). -**WebSocket API key.** The Bradbury HTTPS RPC needs no key, but its WebSocket endpoint (hosted by Conduit) rejects connections without an API key. +**WebSocket API key.** The Bradbury HTTPS RPC needs no key, but its WebSocket endpoint rejects connections without an API key. 1. Create a key at [app.conduit.xyz/rpc-keys](https://app.conduit.xyz/rpc-keys). 2. Set it in the node's environment as `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY`. Never put it in the RPC or WebSocket URLs, or anywhere in `config.yaml`: From 9b3b13b68dfd5278c6aee258e6c4a17726657799 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Tue, 20 Oct 2026 00:35:24 +0200 Subject: [PATCH 38/39] docs(validators): Bradbury block uses the GenLayer RPC host (genlayer-node#2274) Synced bradbury.yaml from aa05854d0 (not merged yet): rpc/wss rpc.testnet-chain.genlayer.com, a DNS name of the Conduit endpoint. API keys still come from app.conduit.xyz. Co-Authored-By: Claude Opus 5.5 --- content/validators/bradbury.yaml | 9 +++++---- pages/validators/setup-guide.mdx | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/content/validators/bradbury.yaml b/content/validators/bradbury.yaml index 26019fc7..6265477d 100644 --- a/content/validators/bradbury.yaml +++ b/content/validators/bradbury.yaml @@ -7,10 +7,11 @@ # rollup configuration rollup: - genlayerchainrpcurl: "https://rpc-genlayer-chain-testnet-cfn8m3xc6l.t.conduit.xyz/" # GenLayer Chain RPC URL - genlayerchainwebsocketurl: "wss://rpc-genlayer-chain-testnet-cfn8m3xc6l.t.conduit.xyz/" # GenLayer Chain WebSocket URL - # Conduit refuses a WebSocket connection without an API key. Create a key at - # https://app.conduit.xyz/rpc-keys and set it in the environment as + genlayerchainrpcurl: "https://rpc.testnet-chain.genlayer.com/" # GenLayer Chain RPC URL + genlayerchainwebsocketurl: "wss://rpc.testnet-chain.genlayer.com/" # GenLayer Chain WebSocket URL + # The WebSocket refuses a connection without an API key. The chain is + # operated by Conduit: create a key at https://app.conduit.xyz/rpc-keys and + # set it in the environment as # GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY, never in these URLs or this file. # The node sends it in the X-AccessKey header on the RPC and WebSocket # connections. diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index e8453536..83e5bafa 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -600,10 +600,11 @@ Since v0.6, the node refuses to start unless `consensus.manifestpath` points at # rollup configuration rollup: - genlayerchainrpcurl: "https://rpc-genlayer-chain-testnet-cfn8m3xc6l.t.conduit.xyz/" # GenLayer Chain RPC URL - genlayerchainwebsocketurl: "wss://rpc-genlayer-chain-testnet-cfn8m3xc6l.t.conduit.xyz/" # GenLayer Chain WebSocket URL - # Conduit refuses a WebSocket connection without an API key. Create a key at - # https://app.conduit.xyz/rpc-keys and set it in the environment as + genlayerchainrpcurl: "https://rpc.testnet-chain.genlayer.com/" # GenLayer Chain RPC URL + genlayerchainwebsocketurl: "wss://rpc.testnet-chain.genlayer.com/" # GenLayer Chain WebSocket URL + # The WebSocket refuses a connection without an API key. The chain is + # operated by Conduit: create a key at https://app.conduit.xyz/rpc-keys and + # set it in the environment as # GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY, never in these URLs or this file. # The node sends it in the X-AccessKey header on the RPC and WebSocket # connections. From d96c3050503760d969eb5d90eea6e5ca0eece767 Mon Sep 17 00:00:00 2001 From: Darien Hernandez Date: Tue, 20 Oct 2026 00:37:39 +0200 Subject: [PATCH 39/39] docs(validators): document the published node v0.6.0-rc.1 Darien's decision: this PR documents v0.6.0-rc.1, the published release. The rc.2-only parts live on branch docs/v0.6.0-rc.2, which reverts this commit. Pinned to tag v0.6.0-rc.1, as shipped (SYNC_TYPES="config docker_compose docker_compose_monitoring env_example alloy_config greybox_setup api_ops" scripts/simulate-node-sync.sh v0.6.0-rc.1): config template, compose, monitoring compose, .env.example, alloy config, greybox guide, ops API pages. Two edits to those copies: Asimov example values in the ops pages (network "" as rc.1 reports it on Bradbury, bradbury-100) and the internal deployment name in the config comment. Kept from v0.6-dev: Bradbury block, manifest, RPC API pages (doc fixes only since rc.1), changelog without rc.2. Hand-written text for rc.1 behaviour (verified on a fresh VM by the claude-code-skills session): - GenVM at opt/genvm/v0.6.0-rc10 (no opt/genvm/current); the template's root_dir says v0.6.0-rc1, install.sh writes the right value. - Every docker compose command fails while NODE_PASSWORD is empty, also the WebDriver of a binary node and a full node. - No fixed compose project name: COMPOSE_PROJECT_NAME=genlayer-node or -p. - The compose file mounts neither the manifest nor the LLM config: docker-compose.override.yml; GENVM_VERSION=v0.6.0-rc10 for the web config. - No NETWORK_NAME, no HEALTHCHECK_AUTO_RESTART, no WebDriver health check; only Alloy has one. Co-Authored-By: Claude Opus 5.5 --- content/validators/alloy-config.river | 59 ------- content/validators/config.yaml | 6 +- .../validators/docker-compose-monitoring.yaml | 113 +++---------- content/validators/docker-compose.yaml | 83 +++------- content/validators/env.example | 30 +--- content/validators/greybox-setup-guide.md | 22 ++- pages/api-references/genlayer-node.mdx | 54 ++----- .../genlayer-node/ops/health.mdx | 54 ++----- pages/validators/genvm-configuration.mdx | 2 +- pages/validators/greybox-setup.mdx | 22 ++- pages/validators/monitoring.mdx | 101 +----------- pages/validators/setup-guide.mdx | 148 ++++++------------ pages/validators/upgrade.mdx | 9 +- 13 files changed, 168 insertions(+), 535 deletions(-) diff --git a/content/validators/alloy-config.river b/content/validators/alloy-config.river index 405c203b..db983230 100644 --- a/content/validators/alloy-config.river +++ b/content/validators/alloy-config.river @@ -55,65 +55,6 @@ loki.source.file "genlayer" { } // Write logs to central Loki instance -// Read opted-in WebDriver/Alloy recovery events through the existing read-only -// Docker proxy. Containers log their own restart attempts before exiting. -discovery.docker "health_recovery" { - host = "http://docker-socket-proxy:2375" - refresh_interval = "5s" - - filter { - name = "label" - values = [ - "com.genlayer.health-recovery.logs=true", - ] - } -} - -discovery.relabel "health_recovery" { - targets = discovery.docker.health_recovery.targets - - rule { - source_labels = ["__meta_docker_container_label_com_docker_compose_project"] - target_label = "compose_project" - } - - rule { - source_labels = ["__meta_docker_container_label_com_genlayer_network"] - target_label = "network" - } - - rule { - source_labels = ["network"] - regex = "" - target_label = "network" - replacement = "unknown" - } -} - -loki.source.docker "health_recovery" { - // Docker's TCP transport keeps the log stream open. With an HTTP(S) host, - // Alloy bounds the entire stream by refresh_interval and repeatedly reopens it. - host = "tcp://docker-socket-proxy:2375" - targets = discovery.relabel.health_recovery.output - refresh_interval = "5s" - labels = { - job = "genlayer-node", - component = "health-recovery", - instance = sys.env("NODE_ID"), - validator_name = sys.env("VALIDATOR_NAME"), - } - forward_to = [loki.process.health_recovery.receiver] -} - -// Collect only restart events, not Alloy's own operational log stream. -loki.process "health_recovery" { - stage.match { - selector = "{component=\"health-recovery\"} != \"found to be unhealthy - Restarting container now\"" - action = "drop" - } - forward_to = [loki.write.central.receiver] -} - loki.write "central" { endpoint { url = sys.env("CENTRAL_LOKI_URL") diff --git a/content/validators/config.yaml b/content/validators/config.yaml index 0ff0edec..b8c42475 100644 --- a/content/validators/config.yaml +++ b/content/validators/config.yaml @@ -11,9 +11,9 @@ rollup: consensus: # The deployment-specific fields are empty: take them from your network's documentation. consensusaddress: "" # AddressManager contract address for this deployment. - manifestpath: "configs/node/protocol-manifest.json" # Trusted producer protocol manifest for this deployment: save your network's manifest (from its documentation) at this path; a dev-env deploy places its own here too. Relative paths resolve against the node root. A mismatch with consensusaddress is refused at startup. + manifestpath: "" # Trusted producer protocol-manifest.json for this deployment. The deploy places it at configs/node/protocol-manifest.json; relative paths resolve against the node root. A mismatch with consensusaddress is refused at startup. chainid: "" # DECIMAL chain id this deployment was made on (not hex, no leading zeros). Held independently of the manifest so a manifest describing another deployment is caught. - # genesis: 47930 # (Optional) Genesis block number for this consensus deployment. If not provided, it will be auto-detected by searching for the first log from the ConsensusMain contract. + # genesis: 17326 # (Optional) Genesis block number for this consensus deployment. If not provided, it will be auto-detected by searching for the first log from the ConsensusMain contract. # chain_submit_buffer: "30s" # (Optional) Slack reserved at the end of each consensus step to submit the resulting on-chain tx (commit vote, reveal vote, proposed receipt) before the slot closes. Tune to underlying settlement chain latency. Default 30s. # admissiontimeout: "15s" # (Optional) Bound on the startup deployment admission check. Never unbounded. Default 15s. # data directory @@ -195,7 +195,7 @@ node: # max_debug_field_bytes: 262144 # 256 KB per field # genvm configuration genvm: - root_dir: ./opt/genvm/v0.6.0-rc10 + root_dir: ./opt/genvm/v0.6.0-rc1 start_manager: true # if true node will start genvm manager itself manager_url: http://127.0.0.1:3999 # Permits are charged PER EXECUTION by the genvm manager, and one diff --git a/content/validators/docker-compose-monitoring.yaml b/content/validators/docker-compose-monitoring.yaml index 89ed4db9..0015e84a 100644 --- a/content/validators/docker-compose-monitoring.yaml +++ b/content/validators/docker-compose-monitoring.yaml @@ -1,9 +1,3 @@ -# A fixed project name: the container names below are fixed too, so a stack -# started from a new release directory must be the SAME Compose project, or -# Docker refuses with "container name ... is already in use". With it, `up -d` -# from the new version's directory recreates the old version's containers. -name: genlayer-node - services: webdriver-container: container_name: genlayer-node-webdriver @@ -13,55 +7,25 @@ services: - no-new-privileges:true environment: PORT: 4444 - HEALTHCHECK_AUTO_RESTART: "${HEALTHCHECK_AUTO_RESTART:-true}" ports: - "${WEBDRIVER_PORT:-4444}:4444" restart: unless-stopped - labels: - com.genlayer.health-recovery.logs: "true" - com.genlayer.network: "${NETWORK_NAME:-unknown}" - healthcheck: - test: - - CMD-SHELL - - | - set -eu - boot=$$(awk '{print $$22}' /proc/1/stat) - if curl -fsS --max-time 10 "http://localhost:$${PORT:-4444}/healthcheck?mode=text&url=https%3A%2F%2Ftest-server.genlayer.com%2Fstatic%2Fgenvm%2Fhello.html"; then - printf '%s 0\n' "$$boot" > /tmp/webdriver-health-failures - exit 0 - fi - age=$$(awk -v ticks="$$boot" -v hz="$$(getconf CLK_TCK)" '{print int($$1-ticks/hz)}' /proc/uptime) - [ "$$age" -ge 10 ] || exit 1 - read -r previous failures 2>/dev/null < /tmp/webdriver-health-failures || failures=0 - [ "$${previous:-}" = "$$boot" ] || failures=0 - failures=$$((failures + 1)) - printf '%s %s\n' "$$boot" "$$failures" > /tmp/webdriver-health-failures - if [ "$$failures" -ge 4 ] && [ "$${HEALTHCHECK_AUTO_RESTART:-true}" != false ]; then - printf 'Container webdriver (%s) found to be unhealthy - Restarting container now\n' "$$(cat /etc/hostname)" > /proc/1/fd/1 - children=$$(cat /proc/1/task/1/children) - for child in $$children; do kill -KILL "$$child"; done - fi - exit 1 - interval: 30s - timeout: 12s - retries: 4 - start_period: 10s - # docker-socket-proxy — read-only Docker API gateway for the node's WebDriver - # resource stats and Alloy's recovery-log reader. Runs with the `node` OR - # `monitoring` profile, so it comes up alongside + # docker-socket-proxy — hardened, read-only Docker API gateway used ONLY so the + # node can read the WebDriver container's resource stats (CPU/memory/traffic) for + # metrics. Runs with the `node` OR `monitoring` profile, so it comes up alongside # a node container AND alongside a monitoring/telemetry bring-up — the latter is # how a binary/systemd node (node not containerized) gets its webdriver metrics. # - # The node and Alloy use this read-only gateway instead of the host socket. + # WHY THIS EXISTS — and why it is NOT a new exposure or a "leak": # Reading container stats requires the Docker API. Previously the node reached # the host /var/run/docker.sock directly (bind-mounted into the container, or via # docker-group membership for a binary node) — but that grants the FULL Docker # API as root (a `:ro` flag does NOT restrict API calls), so a node compromise - # could escalate to host root. This proxy REPLACES the node's socket access, - # restricted to read-only container endpoints (CONTAINERS=1; - # POST=0 => it returns 403 for create / exec / image operations). - # Health checks stop only their own service process; recovery has no Docker API access. + # could escalate to host root. This proxy REPLACES that: it is the only thing + # touching the socket, restricted to read-only container endpoints (CONTAINERS=1; + # POST=0 => it returns 403 for create / exec / images / everything else). Net + # exposure goes DOWN, not up. # - Published ONLY on 127.0.0.1:2375 (host loopback), never 0.0.0.0: 2375 is # the unauthenticated Docker API, so it must not leave the host. A container # node reaches it over the compose network (tcp://docker-socket-proxy:2375); @@ -72,15 +36,14 @@ services: # socket: if DOCKER_HOST is unset the node skips webdriver metrics entirely # (non-fatal). Note those are the webdriver container's CPU/memory/traffic series # (genlayer_node_*{component="webdriver"}) that feed validator resource dashboards — - # so keep DOCKER_HOST set to keep those panels populated. Disabling metrics - # permits removing the node's DOCKER_HOST; Alloy still needs this gateway. + # so keep DOCKER_HOST set to keep those panels populated. To disable metrics + # instead: set metrics.collectors.webdriver.enabled: false and delete this service. docker-socket-proxy: - image: tecnativa/docker-socket-proxy:v0.5.0 + image: tecnativa/docker-socket-proxy:0.3.0 container_name: genlayer-node-docker-proxy environment: CONTAINERS: 1 # GET /containers/{json,inspect,stats} — read-only via POST=0 VERSION: 1 # required by the Docker client's API-version negotiation - NETWORKS: 1 # read-only network metadata required by Alloy Docker discovery volumes: - /var/run/docker.sock:/var/run/docker.sock:ro ports: @@ -103,27 +66,23 @@ services: genlayer-node: image: yeagerai/genlayer-node:${NODE_VERSION:-latest} - # No entrypoint here: the image's own entrypoint precompiles GenVM for the - # host CPU on the first start, then runs `genlayernode run`. The keystore - # password is NOT on a command line. It reaches the node as + # The keystore password is NOT on this command line. It reaches the node as # GENLAYERNODE_PASSWORD (environment below), which the run command reads in # place of --password. A command-line argument is visible in process # listings on the host; an environment variable is not. + entrypoint: ["/app/bin/genlayernode", "run"] container_name: genlayer-node restart: unless-stopped env_file: - path: ./.env required: false environment: - # No default: this is the password that encrypts the validator's signing - # key, and a default shared by every operator who forgot to set one is a - # key anyone can open (the release binary carries none either). The node - # itself refuses validator mode with an empty password ("password is - # required for validator mode"); a full node needs none. Not a ":?" guard: - # Compose interpolates every service, also under another profile, so a - # guard here would stop `docker compose up -d webdriver-container` (the - # binary install path) and a full node, which never read it. - GENLAYERNODE_PASSWORD: "${NODE_PASSWORD:-}" + # ":?" makes Compose refuse to start when NODE_PASSWORD is unset or empty, + # before any container runs. There is no fallback: this is the password + # that encrypts the validator's signing key, and a default shared by every + # operator who forgot to set one is a key anyone can open. The release + # binary carries no default either — it is compiled out of production. + GENLAYERNODE_PASSWORD: "${NODE_PASSWORD:?NODE_PASSWORD is not set. It is the keystore password that unlocks the validator key. Set it in .env; see README section 4.}" # WebDriver metrics collector reaches Docker via the read-only proxy # (see docker-socket-proxy above), never the host socket directly. DOCKER_HOST: tcp://docker-socket-proxy:2375 @@ -132,11 +91,8 @@ services: - "${NODE_OPS_PORT:-9153}:9153" volumes: - ${NODE_CONFIG_PATH:-./configs/node/config.yaml}:/app/configs/node/config.yaml:ro - # The trusted protocol manifest of the network; consensus.manifestpath names this path. - - ${NODE_MANIFEST_PATH:-./configs/node/protocol-manifest.json}:/app/configs/node/protocol-manifest.json:ro - ${NODE_DATA_PATH:-./data}:/app/data - # opt/genvm/current links to the image's GenVM tree, so no version variable is needed. - - ./genvm-module-web-docker.yaml:/app/opt/genvm/current/config/genvm-module-web.yaml + - ./genvm-module-web-docker.yaml:/app/opt/genvm/${GENVM_VERSION}/config/genvm-module-web.yaml security_opt: - no-new-privileges:true cap_drop: @@ -165,20 +121,14 @@ services: # docker compose --profile monitoring up -d alloy: image: grafana/alloy:v1.20.1 - init: true container_name: genlayer-node-alloy - labels: - com.genlayer.health-recovery.logs: "true" - com.genlayer.network: "${NETWORK_NAME:-unknown}" - depends_on: - - docker-socket-proxy # The guard runs before Alloy: with the monitoring profile up but the # endpoints or credentials unset, the container stops with a message # naming what to set, instead of half-running. Half-running is worse than # stopping here in both directions: pushing to a shared endpoint with a # shared default credential pollutes it for everyone, and pushing to an - # unreachable one leaves telemetry unavailable and can grow the WAL. The - # healthcheck preserves that buffer. The $$ keeps compose from + # unreachable one grows the WAL until the healthcheck below reads the + # growth as a stall and deletes it. The $$ keeps compose from # interpolating; the shell only tests emptiness — no value ever lands on # a command line. entrypoint: ["/bin/sh", "-c"] @@ -205,29 +155,12 @@ services: - ${NODE_LOGS_PATH:-./data/node/logs}:/var/log/genlayer:ro - alloy_data:/var/lib/alloy healthcheck: - test: - - CMD-SHELL - - | - set -eu - sh /etc/alloy/healthcheck.sh && exit 0 - [ "$${HEALTHCHECK_AUTO_RESTART:-true}" != false ] || exit 1 - [ "$$(cat /proc/1/comm)" = docker-init ] || exit 1 - boot=$$(awk '{print $$22}' /proc/1/stat) - age=$$(awk -v ticks="$$boot" -v hz="$$(getconf CLK_TCK)" '{print int($$1-ticks/hz)}' /proc/uptime) - [ "$$age" -ge 120 ] || exit 1 - children=$$(cat /proc/1/task/1/children) - printf 'Container alloy (%s) found to be unhealthy - Restarting container now\n' "$$(cat /etc/hostname)" > /proc/1/fd/1 - kill -TERM 1 - sleep 5 - # A stopped service cannot handle TERM; init's child can still receive KILL. - for child in $$children; do kill -KILL "$$child"; done - exit 1 + test: ["CMD", "sh", "/etc/alloy/healthcheck.sh"] interval: 120s timeout: 15s retries: 1 start_period: 120s environment: - - HEALTHCHECK_AUTO_RESTART=${HEALTHCHECK_AUTO_RESTART:-true} # Central monitoring endpoints and credentials: NO defaults, on purpose. # An endpoint may be public knowledge; a credential never is, and a # default one ships the same key to everyone holding this file. The diff --git a/content/validators/docker-compose.yaml b/content/validators/docker-compose.yaml index 6f2f2690..41fe121c 100644 --- a/content/validators/docker-compose.yaml +++ b/content/validators/docker-compose.yaml @@ -1,8 +1,3 @@ -# A fixed project name: the container names below are fixed too, so a stack -# started from a new release directory must be the SAME Compose project, or -# Docker refuses with "container name ... is already in use". With it, `up -d` -# from the new version's directory recreates the old version's containers. -name: genlayer-node services: webdriver-container: container_name: genlayer-node-webdriver @@ -12,54 +7,24 @@ services: - no-new-privileges:true environment: PORT: 4444 - HEALTHCHECK_AUTO_RESTART: "${HEALTHCHECK_AUTO_RESTART:-true}" ports: - "${WEBDRIVER_PORT:-4444}:4444" restart: unless-stopped - labels: - com.genlayer.health-recovery.logs: "true" - com.genlayer.network: "${NETWORK_NAME:-unknown}" - healthcheck: - test: - - CMD-SHELL - - | - set -eu - boot=$$(awk '{print $$22}' /proc/1/stat) - if curl -fsS --max-time 10 "http://localhost:$${PORT:-4444}/healthcheck?mode=text&url=https%3A%2F%2Ftest-server.genlayer.com%2Fstatic%2Fgenvm%2Fhello.html"; then - printf '%s 0\n' "$$boot" > /tmp/webdriver-health-failures - exit 0 - fi - age=$$(awk -v ticks="$$boot" -v hz="$$(getconf CLK_TCK)" '{print int($$1-ticks/hz)}' /proc/uptime) - [ "$$age" -ge 10 ] || exit 1 - read -r previous failures 2>/dev/null < /tmp/webdriver-health-failures || failures=0 - [ "$${previous:-}" = "$$boot" ] || failures=0 - failures=$$((failures + 1)) - printf '%s %s\n' "$$boot" "$$failures" > /tmp/webdriver-health-failures - if [ "$$failures" -ge 4 ] && [ "$${HEALTHCHECK_AUTO_RESTART:-true}" != false ]; then - printf 'Container webdriver (%s) found to be unhealthy - Restarting container now\n' "$$(cat /etc/hostname)" > /proc/1/fd/1 - children=$$(cat /proc/1/task/1/children) - for child in $$children; do kill -KILL "$$child"; done - fi - exit 1 - interval: 30s - timeout: 12s - retries: 4 - start_period: 10s - # docker-socket-proxy — read-only Docker API gateway for the node's WebDriver - # resource stats and Alloy's recovery-log reader. Runs with the `node` OR - # `monitoring` profile, so it comes up alongside + # docker-socket-proxy — hardened, read-only Docker API gateway used ONLY so the + # node can read the WebDriver container's resource stats (CPU/memory/traffic) for + # metrics. Runs with the `node` OR `monitoring` profile, so it comes up alongside # a node container AND alongside a monitoring/telemetry bring-up — the latter is # how a binary/systemd node (node not containerized) gets its webdriver metrics. # - # The node and Alloy use this read-only gateway instead of the host socket. + # WHY THIS EXISTS — and why it is NOT a new exposure or a "leak": # Reading container stats requires the Docker API. Previously the node reached # the host /var/run/docker.sock directly (bind-mounted into the container, or via # docker-group membership for a binary node) — but that grants the FULL Docker # API as root (a `:ro` flag does NOT restrict API calls), so a node compromise - # could escalate to host root. This proxy REPLACES the node's socket access, - # restricted to read-only container endpoints (CONTAINERS=1; - # POST=0 => it returns 403 for create / exec / image operations). - # Health checks stop only their own service process; recovery has no Docker API access. + # could escalate to host root. This proxy REPLACES that: it is the only thing + # touching the socket, restricted to read-only container endpoints (CONTAINERS=1; + # POST=0 => it returns 403 for create / exec / images / everything else). Net + # exposure goes DOWN, not up. # - Published ONLY on 127.0.0.1:2375 (host loopback), never 0.0.0.0: 2375 is # the unauthenticated Docker API, so it must not leave the host. A container # node reaches it over the compose network (tcp://docker-socket-proxy:2375); @@ -70,15 +35,14 @@ services: # socket: if DOCKER_HOST is unset the node skips webdriver metrics entirely # (non-fatal). Note those are the webdriver container's CPU/memory/traffic series # (genlayer_node_*{component="webdriver"}) that feed validator resource dashboards — - # so keep DOCKER_HOST set to keep those panels populated. Disabling metrics - # permits removing the node's DOCKER_HOST; Alloy still needs this gateway. + # so keep DOCKER_HOST set to keep those panels populated. To disable metrics + # instead: set metrics.collectors.webdriver.enabled: false and delete this service. docker-socket-proxy: - image: tecnativa/docker-socket-proxy:v0.5.0 + image: tecnativa/docker-socket-proxy:0.3.0 container_name: genlayer-node-docker-proxy environment: CONTAINERS: 1 # GET /containers/{json,inspect,stats} — read-only via POST=0 VERSION: 1 # required by the Docker client's API-version negotiation - NETWORKS: 1 # read-only network metadata required by Alloy Docker discovery volumes: - /var/run/docker.sock:/var/run/docker.sock:ro ports: @@ -100,27 +64,23 @@ services: - monitoring genlayer-node: image: yeagerai/genlayer-node:${NODE_VERSION:-latest} - # No entrypoint here: the image's own entrypoint precompiles GenVM for the - # host CPU on the first start, then runs `genlayernode run`. The keystore - # password is NOT on a command line. It reaches the node as + # The keystore password is NOT on this command line. It reaches the node as # GENLAYERNODE_PASSWORD (environment below), which the run command reads in # place of --password. A command-line argument is visible in process # listings on the host; an environment variable is not. + entrypoint: ["/app/bin/genlayernode", "run"] container_name: genlayer-node restart: unless-stopped env_file: - path: ./.env required: false environment: - # No default: this is the password that encrypts the validator's signing - # key, and a default shared by every operator who forgot to set one is a - # key anyone can open (the release binary carries none either). The node - # itself refuses validator mode with an empty password ("password is - # required for validator mode"); a full node needs none. Not a ":?" guard: - # Compose interpolates every service, also under another profile, so a - # guard here would stop `docker compose up -d webdriver-container` (the - # binary install path) and a full node, which never read it. - GENLAYERNODE_PASSWORD: "${NODE_PASSWORD:-}" + # ":?" makes Compose refuse to start when NODE_PASSWORD is unset or empty, + # before any container runs. There is no fallback: this is the password + # that encrypts the validator's signing key, and a default shared by every + # operator who forgot to set one is a key anyone can open. The release + # binary carries no default either — it is compiled out of production. + GENLAYERNODE_PASSWORD: "${NODE_PASSWORD:?NODE_PASSWORD is not set. It is the keystore password that unlocks the validator key. Set it in .env; see README section 4.}" # WebDriver metrics collector reaches Docker via the read-only proxy # (see docker-socket-proxy above), never the host socket directly. DOCKER_HOST: tcp://docker-socket-proxy:2375 @@ -129,11 +89,8 @@ services: - "${NODE_OPS_PORT:-9153}:9153" volumes: - ${NODE_CONFIG_PATH:-./configs/node/config.yaml}:/app/configs/node/config.yaml:ro - # The trusted protocol manifest of the network; consensus.manifestpath names this path. - - ${NODE_MANIFEST_PATH:-./configs/node/protocol-manifest.json}:/app/configs/node/protocol-manifest.json:ro - ${NODE_DATA_PATH:-./data}:/app/data - # opt/genvm/current links to the image's GenVM tree, so no version variable is needed. - - ./genvm-module-web-docker.yaml:/app/opt/genvm/current/config/genvm-module-web.yaml + - ./genvm-module-web-docker.yaml:/app/opt/genvm/${GENVM_VERSION}/config/genvm-module-web.yaml security_opt: - no-new-privileges:true cap_drop: diff --git a/content/validators/env.example b/content/validators/env.example index c65d5345..60d972b3 100644 --- a/content/validators/env.example +++ b/content/validators/env.example @@ -12,9 +12,8 @@ WEBDRIVER_PORT=4444 # Docker image version NODE_VERSION=latest -# Keystore password (unlocks the pre-imported validator wallet). Required for a -# validator: the node refuses validator mode without it. A full node and the -# WebDriver-only stack need none. Choose your own; never reuse an example +# Keystore password (unlocks the pre-imported validator wallet). Required — the +# stack refuses to start without it. Choose your own; never reuse an example # value. It reaches the node as an environment variable, not a command-line # argument. See README section 4 for reading it in without echo. NODE_PASSWORD= @@ -22,9 +21,6 @@ NODE_PASSWORD= # Path to node configuration file NODE_CONFIG_PATH=./configs/node/config.yaml -# Path to the trusted protocol manifest of your network -NODE_MANIFEST_PATH=./configs/node/protocol-manifest.json - # Path to node data directory (for persistence) NODE_DATA_PATH=./data @@ -39,11 +35,6 @@ ANTHROPICKEY= XAIKEY= GEMINIKEY= ATOMAKEY= -COMPUT3KEY= -IOINTELLIGENCEKEY= -LIBERTAI_API_KEY= -MORPHEUS_API_KEY= -OPENAIKEY= # Optional logging level override GENLAYERNODE_LOGGING_LEVEL=INFO @@ -99,9 +90,6 @@ CENTRAL_LOKI_PASSWORD= # Node identification NODE_ID=validator-001 VALIDATOR_NAME=MyValidator -# Network label for recovery logs and alert routing; use the deployed network. -# Leave empty for an unknown label, rather than guessing a production network. -NETWORK_NAME= # Node metrics endpoint (optional) # Endpoint where the node exposes its Prometheus metrics @@ -122,6 +110,7 @@ NODE_METRICS_ENDPOINT=host.docker.internal:9153 # Example for multiple nodes: # SCRAPE_TARGETS_JSON=[{"__address__":"host.docker.internal:9250","instance":"0x...","validator_name":"node-1"},{"__address__":"host.docker.internal:9251","instance":"0x...","validator_name":"node-2"}] # +# To generate for local nodes, use: ./generate-dev-targets.sh # SCRAPE_TARGETS_JSON= # ============================================================================= @@ -141,8 +130,8 @@ LOG_FILE_PATTERN=/var/log/genlayer/node*.log # Metrics scraping intervals (optional) # How often to scrape metrics from the node -# Default: 60s for node metrics, 10s timeout -METRICS_SCRAPE_INTERVAL=60s +# Default: 15s for node metrics, 10s timeout +METRICS_SCRAPE_INTERVAL=15s METRICS_SCRAPE_TIMEOUT=10s # Alloy internal health monitoring interval (optional) @@ -153,9 +142,9 @@ ALLOY_SELF_MONITORING_INTERVAL=60s # Loki log batching configuration (optional) # Controls how logs are batched before sending to central Loki -# Default: 1MiB batch size, 60s wait time +# Default: 1MiB batch size, 1s wait time LOKI_BATCH_SIZE=1MiB -LOKI_BATCH_WAIT=60s +LOKI_BATCH_WAIT=1s # ============================================================================= # Deployment Instructions @@ -188,8 +177,5 @@ LOKI_BATCH_WAIT=60s # - Grafana Alloy handles both metrics and logs in a single service # - To customize monitoring, edit alloy-config.river # - To filter metrics (reduce bandwidth), uncomment the filtering rule in alloy-config.river -# - Ensure the firewall allows outbound HTTPS to CENTRAL_MONITORING_URL and CENTRAL_LOKI_URL +# - Ensure firewall allows outbound connections to monitoring server (port 8180) # - -# Keep health checks but disable automatic container-local recycling when false. -HEALTHCHECK_AUTO_RESTART=true diff --git a/content/validators/greybox-setup-guide.md b/content/validators/greybox-setup-guide.md index 58fbb563..08709109 100644 --- a/content/validators/greybox-setup-guide.md +++ b/content/validators/greybox-setup-guide.md @@ -45,37 +45,33 @@ echo "OPENROUTERKEY=sk-or-v1-your-key-here" >> ${ENV_FILE} The tarball ships with a unified config that includes all backends (openrouter, morpheus, heurist, ionet, etc.). -`NODE_DIR` is the directory of your node release (where you extracted the -tarball). On a host with the `/opt/genlayer-node/bin` symlink layout, it is -`/opt/genlayer-node/`. - ```bash -NODE_DIR=/path/to/your/node/release +VERSION=$(readlink /opt/genlayer-node/bin | sed 's|/bin||; s|.*/||') -cp ${NODE_DIR}/opt/genvm/current/config/genvm-modules-llm-release.yaml \ - ${NODE_DIR}/opt/genvm/current/config/genvm-module-llm.yaml +cp /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-modules-llm-release.yaml \ + /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml ``` ### 4. Switch to greybox strategy ```bash sed -i 's/genvm-llm-default\.lua/genvm-llm-greybox.lua/' \ - ${NODE_DIR}/opt/genvm/current/config/genvm-module-llm.yaml + /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml ``` ### 5. Verify the config ```bash # Check lua script path -grep lua_script_path ${NODE_DIR}/opt/genvm/current/config/genvm-module-llm.yaml +grep lua_script_path /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml # Expected: lua_script_path: ${exeDir}/../config/genvm-llm-greybox.lua # Check openrouter is present -grep -A2 'openrouter:' ${NODE_DIR}/opt/genvm/current/config/genvm-module-llm.yaml +grep -A2 'openrouter:' /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml # Expected: enabled: true # Check the greybox Lua file exists -ls -la ${NODE_DIR}/opt/genvm/current/config/genvm-llm-greybox.lua +ls -la /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-llm-greybox.lua ``` ### 6. Start the node @@ -106,7 +102,7 @@ To revert to the default generated chain: sudo systemctl stop genlayer-node sed -i 's/genvm-llm-greybox\.lua/genvm-llm-default.lua/' \ - ${NODE_DIR}/opt/genvm/current/config/genvm-module-llm.yaml + /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml sudo systemctl start genlayer-node ``` @@ -182,7 +178,7 @@ After editing the YAML, restart the LLM module (see "Updating Greybox on a Runni **All models exhausted error:** - OpenRouter may be down or your key is invalid - Check your key at https://openrouter.ai/settings/keys -- Run `./bin/genlayernode doctor` from `${NODE_DIR}` — the "LLM Providers" section probes each enabled backend and reports which ones actually answer, rather than only whether a key variable is set +- Run `task node:doctor` — the "LLM Providers" section probes each enabled backend and reports which ones actually answer, rather than only whether a key variable is set - Fallback providers (heurist, ionet) also need valid keys if you want fallback to work **Configure more than one provider.** A backend only enters the fallback chain diff --git a/pages/api-references/genlayer-node.mdx b/pages/api-references/genlayer-node.mdx index a37ea68e..90a751c3 100644 --- a/pages/api-references/genlayer-node.mdx +++ b/pages/api-references/genlayer-node.mdx @@ -1837,25 +1837,24 @@ curl -X GET http://localhost:9153/health ```json { "status": "healthy", - "network": "bradbury-phase2", + "network": "", "node_version": "v0.6.0", "protocol_version": "0.3.0", "genvm_version": "v0.0.18", "validator_address": "0x1234567890abcdef1234567890abcdef12345678", "validator_moniker": "my-validator", "conditions": { - "chain.rpc": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, - "chain.websocket": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, - "chain.sync": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:02:14Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, - "consensus.ban": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, - "consensus.stake": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, - "consensus.operator_funds": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, - "consensus.prime": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:04:41Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, - "genvm.llm": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:31Z", "updated_at": "2026-09-01T10:29:56Z", "last_ok": "2026-09-01T10:29:56Z" }, - "genvm.web": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:31Z", "updated_at": "2026-09-01T10:29:56Z", "last_ok": "2026-09-01T10:29:56Z" }, - "node.disk": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, - "node.snapshot_age": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, - "webdriver.render": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" } + "chain.rpc": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "chain.websocket": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "chain.sync": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:02:14Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "consensus.ban": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "consensus.stake": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "consensus.operator_funds": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "consensus.prime": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:04:41Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "genvm.llm": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:31Z", "updated_at": "2026-09-01T10:29:56Z", "last_ok": "2026-09-01T10:29:56Z" }, + "genvm.web": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:31Z", "updated_at": "2026-09-01T10:29:56Z", "last_ok": "2026-09-01T10:29:56Z" }, + "node.disk": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "node.snapshot_age": { "state": "healthy", "severity": "info", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" } } } ``` @@ -1868,7 +1867,8 @@ curl -X GET http://localhost:9153/health - `validator_address` (string, validator mode only): The validator's address - `validator_moniker` (string, validator mode only): The validator's on-chain moniker; reported alongside the address even when empty - `conditions` (object): the full catalog. Every entry is always present — this is not a list of what is broken. - - `state` (string): current observed health; use this field to determine whether the capability is working. + - `state` (string): current state + - `severity` (string): the catalog's fixed class — `info`, `warning`, or `critical`. Not the state: a `critical` condition can be `healthy`. - `occurrences` (number): accepted failed observations since process start - `consecutive_failures` (number): current failure streak; zero after recovery - `since`, `updated_at`, `last_ok` (string, optional): ISO 8601 lifecycle timestamps @@ -1877,8 +1877,6 @@ curl -X GET http://localhost:9153/health - `action` (string, present only when not healthy): what the operator should do - `details` (object, present only when not healthy): typed fields explicitly allowlisted by the catalog entry. Raw provider payloads, prompts, credentials, URLs, and stack traces are rejected. -`severity` is not returned by this endpoint. Alert priority belongs to the alerting policy; clients that previously read `conditions.*.severity` should use `state` for current health. - **States** | State | Meaning | Alerted | @@ -1902,28 +1900,6 @@ A condition can never exceed the liveness cap its catalog entry declares. The Ge `consensus.ban`, `consensus.stake`, `chain.rpc`, `chain.websocket` and `chain.sync` are capped at `unavailable`, because each of them stops the node doing its job. -**WebDriver render check** - -`webdriver.render` runs the same fresh render probe as `doctor`, using -`webdriver_host` from the configured GenVM's `genvm-module-web.yaml`. It requires -HTTP 200, `Resulting-Status: 200`, and exactly `Hello world!` from the diagnostic -page, within 10 seconds. It bypasses WebDriver's cached `/healthcheck` response. -A failure reports `degraded` with reason `webdriver_check_failed` and safe -`failure`, `http_status` and `page_status` details. The endpoint still returns -HTTP 200: a browser outage does not stop the node syncing and voting. - -Allow time for the ten-second render deadline when setting a client timeout. -`task devtools:nodes:health` defaults to 15 seconds per request so a browser -timeout can return its diagnosis instead of appearing as an unreachable node. - -`genvm.web` continues reporting the broader web module's boot and transaction -results. A successful direct browser check does not clear a web-module failure. - -For `genvm.start_manager: false`, the manager owns the browser configuration. -The node leaves `webdriver.render` unknown instead of probing an unused local -root. Doctor likewise reports the browser as not checked; run it on the node -that owns the manager configuration. Observed `genvm.web` results still apply. - **Example: the LLM provider is dead — HTTP 200** ```json @@ -1932,6 +1908,7 @@ that owns the manager configuration. Observed `genvm.web` results still apply. "conditions": { "genvm.llm": { "state": "degraded", + "severity": "critical", "reason": "llm_request_failed", "impact": "This validator cannot execute a transaction that needs an LLM. It votes Timeout, and the consensus contracts may penalize a Timeout vote.", "action": "Check the affected LLM provider service and payment or billing status, restore access, or configure a working fallback. If credentials are changed through .env, restart the node.", @@ -1953,6 +1930,7 @@ that owns the manager configuration. Observed `genvm.web` results still apply. "conditions": { "consensus.ban": { "state": "unavailable", + "severity": "critical", "reason": "validator_banned", "impact": "The consensus contracts do not select this validator for a round. The node earns nothing.", "action": "Wait for the stated epoch. If the ban is permanent, restake with a new validator.", diff --git a/pages/api-references/genlayer-node/ops/health.mdx b/pages/api-references/genlayer-node/ops/health.mdx index fdd2fe13..8ecf1de0 100644 --- a/pages/api-references/genlayer-node/ops/health.mdx +++ b/pages/api-references/genlayer-node/ops/health.mdx @@ -21,25 +21,24 @@ curl -X GET http://localhost:9153/health ```json { "status": "healthy", - "network": "bradbury-phase2", + "network": "", "node_version": "v0.6.0", "protocol_version": "0.3.0", "genvm_version": "v0.0.18", "validator_address": "0x1234567890abcdef1234567890abcdef12345678", "validator_moniker": "my-validator", "conditions": { - "chain.rpc": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, - "chain.websocket": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, - "chain.sync": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:02:14Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, - "consensus.ban": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, - "consensus.stake": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, - "consensus.operator_funds": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, - "consensus.prime": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:04:41Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, - "genvm.llm": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:31Z", "updated_at": "2026-09-01T10:29:56Z", "last_ok": "2026-09-01T10:29:56Z" }, - "genvm.web": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:31Z", "updated_at": "2026-09-01T10:29:56Z", "last_ok": "2026-09-01T10:29:56Z" }, - "node.disk": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, - "node.snapshot_age": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, - "webdriver.render": { "state": "healthy", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" } + "chain.rpc": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "chain.websocket": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "chain.sync": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:02:14Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "consensus.ban": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "consensus.stake": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "consensus.operator_funds": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "consensus.prime": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:04:41Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "genvm.llm": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:31Z", "updated_at": "2026-09-01T10:29:56Z", "last_ok": "2026-09-01T10:29:56Z" }, + "genvm.web": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:31Z", "updated_at": "2026-09-01T10:29:56Z", "last_ok": "2026-09-01T10:29:56Z" }, + "node.disk": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }, + "node.snapshot_age": { "state": "healthy", "severity": "info", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" } } } ``` @@ -52,7 +51,8 @@ curl -X GET http://localhost:9153/health - `validator_address` (string, validator mode only): The validator's address - `validator_moniker` (string, validator mode only): The validator's on-chain moniker; reported alongside the address even when empty - `conditions` (object): the full catalog. Every entry is always present — this is not a list of what is broken. - - `state` (string): current observed health; use this field to determine whether the capability is working. + - `state` (string): current state + - `severity` (string): the catalog's fixed class — `info`, `warning`, or `critical`. Not the state: a `critical` condition can be `healthy`. - `occurrences` (number): accepted failed observations since process start - `consecutive_failures` (number): current failure streak; zero after recovery - `since`, `updated_at`, `last_ok` (string, optional): ISO 8601 lifecycle timestamps @@ -61,8 +61,6 @@ curl -X GET http://localhost:9153/health - `action` (string, present only when not healthy): what the operator should do - `details` (object, present only when not healthy): typed fields explicitly allowlisted by the catalog entry. Raw provider payloads, prompts, credentials, URLs, and stack traces are rejected. -`severity` is not returned by this endpoint. Alert priority belongs to the alerting policy; clients that previously read `conditions.*.severity` should use `state` for current health. - **States** | State | Meaning | Alerted | @@ -86,28 +84,6 @@ A condition can never exceed the liveness cap its catalog entry declares. The Ge `consensus.ban`, `consensus.stake`, `chain.rpc`, `chain.websocket` and `chain.sync` are capped at `unavailable`, because each of them stops the node doing its job. -**WebDriver render check** - -`webdriver.render` runs the same fresh render probe as `doctor`, using -`webdriver_host` from the configured GenVM's `genvm-module-web.yaml`. It requires -HTTP 200, `Resulting-Status: 200`, and exactly `Hello world!` from the diagnostic -page, within 10 seconds. It bypasses WebDriver's cached `/healthcheck` response. -A failure reports `degraded` with reason `webdriver_check_failed` and safe -`failure`, `http_status` and `page_status` details. The endpoint still returns -HTTP 200: a browser outage does not stop the node syncing and voting. - -Allow time for the ten-second render deadline when setting a client timeout. -`task devtools:nodes:health` defaults to 15 seconds per request so a browser -timeout can return its diagnosis instead of appearing as an unreachable node. - -`genvm.web` continues reporting the broader web module's boot and transaction -results. A successful direct browser check does not clear a web-module failure. - -For `genvm.start_manager: false`, the manager owns the browser configuration. -The node leaves `webdriver.render` unknown instead of probing an unused local -root. Doctor likewise reports the browser as not checked; run it on the node -that owns the manager configuration. Observed `genvm.web` results still apply. - **Example: the LLM provider is dead — HTTP 200** ```json @@ -116,6 +92,7 @@ that owns the manager configuration. Observed `genvm.web` results still apply. "conditions": { "genvm.llm": { "state": "degraded", + "severity": "critical", "reason": "llm_request_failed", "impact": "This validator cannot execute a transaction that needs an LLM. It votes Timeout, and the consensus contracts may penalize a Timeout vote.", "action": "Check the affected LLM provider service and payment or billing status, restore access, or configure a working fallback. If credentials are changed through .env, restart the node.", @@ -137,6 +114,7 @@ that owns the manager configuration. Observed `genvm.web` results still apply. "conditions": { "consensus.ban": { "state": "unavailable", + "severity": "critical", "reason": "validator_banned", "impact": "The consensus contracts do not select this validator for a round. The node earns nothing.", "action": "Wait for the stated epoch. If the ban is permanent, restake with a new validator.", diff --git a/pages/validators/genvm-configuration.mdx b/pages/validators/genvm-configuration.mdx index 32d1980a..1e82ac55 100644 --- a/pages/validators/genvm-configuration.mdx +++ b/pages/validators/genvm-configuration.mdx @@ -46,7 +46,7 @@ Repeat for each GenVM instance if running multiple. A module reload picks up YAM ## Configuration Files -All configuration lives in the GenVM tree of the release, at `opt/genvm/current/config/` (in the Docker image: `/app/opt/genvm/current/config/`). `opt/genvm/current` links to the GenVM version the release ships. Three files: +All configuration lives in the GenVM tree of the release, at `opt/genvm//config/`. v0.6.0-rc.1 ships GenVM v0.6.0-rc10: `opt/genvm/v0.6.0-rc10/config/` (in the Docker image: `/app/opt/genvm/v0.6.0-rc10/config/`). Three files: | File | What it controls | Should you modify? | |---|---|---| diff --git a/pages/validators/greybox-setup.mdx b/pages/validators/greybox-setup.mdx index 35afcff1..909eb774 100644 --- a/pages/validators/greybox-setup.mdx +++ b/pages/validators/greybox-setup.mdx @@ -47,37 +47,33 @@ echo "OPENROUTERKEY=sk-or-v1-your-key-here" >> ${ENV_FILE} The tarball ships with a unified config that includes all backends (openrouter, morpheus, heurist, ionet, etc.). -`NODE_DIR` is the directory of your node release (where you extracted the -tarball). On a host with the `/opt/genlayer-node/bin` symlink layout, it is -`/opt/genlayer-node/`. - ```bash -NODE_DIR=/path/to/your/node/release +VERSION=$(readlink /opt/genlayer-node/bin | sed 's|/bin||; s|.*/||') -cp ${NODE_DIR}/opt/genvm/current/config/genvm-modules-llm-release.yaml \ - ${NODE_DIR}/opt/genvm/current/config/genvm-module-llm.yaml +cp /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-modules-llm-release.yaml \ + /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml ``` ### 4. Switch to greybox strategy ```bash sed -i 's/genvm-llm-default\.lua/genvm-llm-greybox.lua/' \ - ${NODE_DIR}/opt/genvm/current/config/genvm-module-llm.yaml + /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml ``` ### 5. Verify the config ```bash # Check lua script path -grep lua_script_path ${NODE_DIR}/opt/genvm/current/config/genvm-module-llm.yaml +grep lua_script_path /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml # Expected: lua_script_path: ${exeDir}/../config/genvm-llm-greybox.lua # Check openrouter is present -grep -A2 'openrouter:' ${NODE_DIR}/opt/genvm/current/config/genvm-module-llm.yaml +grep -A2 'openrouter:' /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml # Expected: enabled: true # Check the greybox Lua file exists -ls -la ${NODE_DIR}/opt/genvm/current/config/genvm-llm-greybox.lua +ls -la /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-llm-greybox.lua ``` ### 6. Start the node @@ -108,7 +104,7 @@ To revert to the default generated chain: sudo systemctl stop genlayer-node sed -i 's/genvm-llm-greybox\.lua/genvm-llm-default.lua/' \ - ${NODE_DIR}/opt/genvm/current/config/genvm-module-llm.yaml + /opt/genlayer-node/${VERSION}/opt/genvm/${GENVM_VERSION}/config/genvm-module-llm.yaml sudo systemctl start genlayer-node ``` @@ -184,7 +180,7 @@ After editing the YAML, restart the LLM module (see "Updating Greybox on a Runni **All models exhausted error:** - OpenRouter may be down or your key is invalid - Check your key at https://openrouter.ai/settings/keys -- Run `./bin/genlayernode doctor` from `${NODE_DIR}` — the "LLM Providers" section probes each enabled backend and reports which ones actually answer, rather than only whether a key variable is set +- Run `task node:doctor` — the "LLM Providers" section probes each enabled backend and reports which ones actually answer, rather than only whether a key variable is set - Fallback providers (heurist, ionet) also need valid keys if you want fallback to work **Configure more than one provider.** A backend only enters the fallback chain diff --git a/pages/validators/monitoring.mdx b/pages/validators/monitoring.mdx index 3c1da34f..f2a869c2 100644 --- a/pages/validators/monitoring.mdx +++ b/pages/validators/monitoring.mdx @@ -120,8 +120,6 @@ CENTRAL_LOKI_PASSWORD= # Node identification NODE_ID=validator-001 VALIDATOR_NAME=MyValidator -# Network label for recovery logs and alert routing -NETWORK_NAME=bradbury-phase2 # Usually defaults are fine NODE_METRICS_ENDPOINT=host.docker.internal:9153 @@ -129,8 +127,6 @@ LOG_FILE_PATTERN=/var/log/genlayer/node*.log METRICS_SCRAPE_INTERVAL=15s # Host directory where the node writes its log files (mounted into Alloy) NODE_LOGS_PATH=./data/node/logs -# false keeps the container health checks but turns off automatic restarts -HEALTHCHECK_AUTO_RESTART=true ``` 2. **Add or verify the Alloy service in docker-compose.yaml.** Use the docker-compose.yaml from the release package as it is. It already defines the alloy service under the monitoring profile, together with the docker-socket-proxy service that alloy depends on. Alloy also mounts alloy-healthcheck.sh from the same package. Copying only the alloy block into another compose file does not work: it will not start without docker-socket-proxy and alloy-healthcheck.sh. The block is shown here for reference: @@ -148,20 +144,14 @@ HEALTHCHECK_AUTO_RESTART=true # docker compose --profile monitoring up -d alloy: image: grafana/alloy:v1.20.1 - init: true container_name: genlayer-node-alloy - labels: - com.genlayer.health-recovery.logs: "true" - com.genlayer.network: "${NETWORK_NAME:-unknown}" - depends_on: - - docker-socket-proxy # The guard runs before Alloy: with the monitoring profile up but the # endpoints or credentials unset, the container stops with a message # naming what to set, instead of half-running. Half-running is worse than # stopping here in both directions: pushing to a shared endpoint with a # shared default credential pollutes it for everyone, and pushing to an - # unreachable one leaves telemetry unavailable and can grow the WAL. The - # healthcheck preserves that buffer. The $$ keeps compose from + # unreachable one grows the WAL until the healthcheck below reads the + # growth as a stall and deletes it. The $$ keeps compose from # interpolating; the shell only tests emptiness — no value ever lands on # a command line. entrypoint: ["/bin/sh", "-c"] @@ -188,29 +178,12 @@ HEALTHCHECK_AUTO_RESTART=true - ${NODE_LOGS_PATH:-./data/node/logs}:/var/log/genlayer:ro - alloy_data:/var/lib/alloy healthcheck: - test: - - CMD-SHELL - - | - set -eu - sh /etc/alloy/healthcheck.sh && exit 0 - [ "$${HEALTHCHECK_AUTO_RESTART:-true}" != false ] || exit 1 - [ "$$(cat /proc/1/comm)" = docker-init ] || exit 1 - boot=$$(awk '{print $$22}' /proc/1/stat) - age=$$(awk -v ticks="$$boot" -v hz="$$(getconf CLK_TCK)" '{print int($$1-ticks/hz)}' /proc/uptime) - [ "$$age" -ge 120 ] || exit 1 - children=$$(cat /proc/1/task/1/children) - printf 'Container alloy (%s) found to be unhealthy - Restarting container now\n' "$$(cat /etc/hostname)" > /proc/1/fd/1 - kill -TERM 1 - sleep 5 - # A stopped service cannot handle TERM; init's child can still receive KILL. - for child in $$children; do kill -KILL "$$child"; done - exit 1 + test: ["CMD", "sh", "/etc/alloy/healthcheck.sh"] interval: 120s timeout: 15s retries: 1 start_period: 120s environment: - - HEALTHCHECK_AUTO_RESTART=${HEALTHCHECK_AUTO_RESTART:-true} # Central monitoring endpoints and credentials: NO defaults, on purpose. # An endpoint may be public knowledge; a credential never is, and a # default one ships the same key to everyone holding this file. The @@ -319,65 +292,6 @@ loki.source.file "genlayer" { } // Write logs to central Loki instance -// Read opted-in WebDriver/Alloy recovery events through the existing read-only -// Docker proxy. Containers log their own restart attempts before exiting. -discovery.docker "health_recovery" { - host = "http://docker-socket-proxy:2375" - refresh_interval = "5s" - - filter { - name = "label" - values = [ - "com.genlayer.health-recovery.logs=true", - ] - } -} - -discovery.relabel "health_recovery" { - targets = discovery.docker.health_recovery.targets - - rule { - source_labels = ["__meta_docker_container_label_com_docker_compose_project"] - target_label = "compose_project" - } - - rule { - source_labels = ["__meta_docker_container_label_com_genlayer_network"] - target_label = "network" - } - - rule { - source_labels = ["network"] - regex = "" - target_label = "network" - replacement = "unknown" - } -} - -loki.source.docker "health_recovery" { - // Docker's TCP transport keeps the log stream open. With an HTTP(S) host, - // Alloy bounds the entire stream by refresh_interval and repeatedly reopens it. - host = "tcp://docker-socket-proxy:2375" - targets = discovery.relabel.health_recovery.output - refresh_interval = "5s" - labels = { - job = "genlayer-node", - component = "health-recovery", - instance = sys.env("NODE_ID"), - validator_name = sys.env("VALIDATOR_NAME"), - } - forward_to = [loki.process.health_recovery.receiver] -} - -// Collect only restart events, not Alloy's own operational log stream. -loki.process "health_recovery" { - stage.match { - selector = "{component=\"health-recovery\"} != \"found to be unhealthy - Restarting container now\"" - action = "drop" - } - forward_to = [loki.write.central.receiver] -} - loki.write "central" { endpoint { url = sys.env("CENTRAL_LOKI_URL") @@ -509,16 +423,13 @@ Look for messages indicating successful batch sending (no error codes like 401, ## Container Health Checks -The release docker-compose.yaml gives the webdriver and Alloy containers health checks. When a check keeps failing, the container stops its own process and Docker restarts it (`restart: unless-stopped`). A container you stop by hand stays stopped. Each restart attempt is written to the container's logs; with monitoring on, Alloy forwards these lines with `component="health-recovery"`. - -To keep the health checks but turn off the automatic restarts, set `HEALTHCHECK_AUTO_RESTART=false` in `.env` and recreate the services. +In v0.6.0-rc.1 the release docker-compose.yaml gives the Alloy container a health check (`alloy-healthcheck.sh`, every 120 seconds). It detects a stalled push to the central server: when Alloy's write-ahead log keeps growing, the check clears it and marks the container unhealthy, and Docker restarts Alloy (`restart: unless-stopped`). The WebDriver container has no health check in this release. -Check container health and recovery events: +Check the Alloy container's health: ```bash copy -docker inspect --format '{{.State.Health.Status}}' genlayer-node-webdriver docker inspect --format '{{.State.Health.Status}}' genlayer-node-alloy -docker compose logs --since 30m webdriver-container alloy +docker compose logs --since 30m alloy ``` ## Troubleshooting diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index 83e5bafa..8fd5342a 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -297,7 +297,7 @@ One full node can be shared between multiple validators. The optimal validator-t Run it in the final install location: it writes this directory's absolute path into the GenVM binaries. If you move the installation later, run it again there. ```sh copy - python3 ./opt/genvm/current/bin/setup.py + python3 ./opt/genvm/v0.6.0-rc10/bin/setup.py # v0.6.0-rc.1 ships GenVM v0.6.0-rc10: check with `ls opt/genvm` ``` It does following: @@ -318,7 +318,7 @@ The file needs to be located at `configs/node/config.yaml` You can use the following example configuration. **Note:** For most users, you will only need to replace the `rollup` and `consensus` sections with the values for your target network (see [Network-Specific Consensus Configuration](#network-specific-consensus-configuration) below). -The `config.yaml` in the release package sets `genvm.root_dir: ./opt/genvm/current`, a link to the GenVM version the release ships. Keep that value: the example below names the versioned directory, which the package also contains. +Set `genvm.root_dir` to the GenVM directory the release ships. v0.6.0-rc.1 ships `opt/genvm/v0.6.0-rc10`, but its config template says `./opt/genvm/v0.6.0-rc1`: set `genvm.root_dir: ./opt/genvm/v0.6.0-rc10` (check with `ls opt/genvm`). `install.sh` writes the right value for you. ```yaml copy # rollup configuration @@ -334,9 +334,9 @@ rollup: consensus: # The deployment-specific fields are empty: take them from your network's documentation. consensusaddress: "" # AddressManager contract address for this deployment. - manifestpath: "configs/node/protocol-manifest.json" # Trusted producer protocol manifest for this deployment: save your network's manifest (from its documentation) at this path; a dev-env deploy places its own here too. Relative paths resolve against the node root. A mismatch with consensusaddress is refused at startup. + manifestpath: "" # Trusted producer protocol-manifest.json for this deployment. The deploy places it at configs/node/protocol-manifest.json; relative paths resolve against the node root. A mismatch with consensusaddress is refused at startup. chainid: "" # DECIMAL chain id this deployment was made on (not hex, no leading zeros). Held independently of the manifest so a manifest describing another deployment is caught. - # genesis: 47930 # (Optional) Genesis block number for this consensus deployment. If not provided, it will be auto-detected by searching for the first log from the ConsensusMain contract. + # genesis: 17326 # (Optional) Genesis block number for this consensus deployment. If not provided, it will be auto-detected by searching for the first log from the ConsensusMain contract. # chain_submit_buffer: "30s" # (Optional) Slack reserved at the end of each consensus step to submit the resulting on-chain tx (commit vote, reveal vote, proposed receipt) before the slot closes. Tune to underlying settlement chain latency. Default 30s. # admissiontimeout: "15s" # (Optional) Bound on the startup deployment admission check. Never unbounded. Default 15s. # data directory @@ -518,7 +518,7 @@ node: # max_debug_field_bytes: 262144 # 256 KB per field # genvm configuration genvm: - root_dir: ./opt/genvm/v0.6.0-rc10 + root_dir: ./opt/genvm/v0.6.0-rc1 start_manager: true # if true node will start genvm manager itself manager_url: http://127.0.0.1:3999 # Permits are charged PER EXECUTION by the genvm manager, and one @@ -626,7 +626,7 @@ mkdir -p configs/node curl -fsSL https://docs.genlayer.com/validators/bradbury.protocol-manifest.json -o configs/node/protocol-manifest.json ``` -The binary reads it from that path. Docker Compose mounts the same file into the container (`NODE_MANIFEST_PATH`, default `./configs/node/protocol-manifest.json`). +The binary reads it from that path. With Docker, mount it into the container yourself: the v0.6.0-rc.1 compose file does not (see [Running the Node using docker-compose](#running-the-node-using-docker-compose)). **WebSocket API key.** The Bradbury HTTPS RPC needs no key, but its WebSocket endpoint rejects connections without an API key. @@ -791,6 +791,8 @@ Once you have configured everything, you are ready to start the node. docker compose up -d # Starts the WebDriver needed by the GenVM web module ``` + In v0.6.0-rc.1 every `docker compose` command fails while `NODE_PASSWORD` is empty, also for the WebDriver alone. For a binary node, set any non-empty value in the `.env` next to `docker-compose.yaml`. + The node starts the GenVM manager and its modules itself (`genvm.start_manager: true` in the config template). **WebDriver resource metrics (optional).** To report the WebDriver container's CPU, memory and traffic as `genlayer_node_*{component="webdriver"}` series, also start the read-only Docker API proxy, and give only the node process `DOCKER_HOST=tcp://127.0.0.1:2375` (see step 4): @@ -843,7 +845,16 @@ You can also run the GenLayer node using Docker and Docker Compose. Release cand Before the first start, in the directory with your `docker-compose.yaml`: - `configs/node/config.yaml` holds your network's `rollup` and `consensus` values (see [Configuration](#configuration)). -- `configs/node/protocol-manifest.json` is your network's trusted manifest (see [Network-Specific Consensus Configuration](#network-specific-consensus-configuration)). +- `configs/node/protocol-manifest.json` is your network's trusted manifest (see [Network-Specific Consensus Configuration](#network-specific-consensus-configuration)). The v0.6.0-rc.1 compose file does not mount it: add a `docker-compose.override.yml` next to `docker-compose.yaml`, which Docker Compose reads automatically: + + ```yaml copy + services: + genlayer-node: + volumes: + - ./configs/node/protocol-manifest.json:/app/configs/node/protocol-manifest.json:ro + # Only if you customize the LLM config (for example for greyboxing): + # - ./genvm-module-llm.yaml:/app/opt/genvm/v0.6.0-rc10/config/genvm-module-llm.yaml:ro + ``` - The node container runs as uid `10001`, and the bind-mounted `./data` keeps its host owner. Let that user write to it once: ```sh copy @@ -853,11 +864,6 @@ Before the first start, in the directory with your `docker-compose.yaml`: 1. Create a `docker-compose.yaml` file with the following content: ```yaml copy -# A fixed project name: the container names below are fixed too, so a stack -# started from a new release directory must be the SAME Compose project, or -# Docker refuses with "container name ... is already in use". With it, `up -d` -# from the new version's directory recreates the old version's containers. -name: genlayer-node services: webdriver-container: container_name: genlayer-node-webdriver @@ -867,54 +873,24 @@ services: - no-new-privileges:true environment: PORT: 4444 - HEALTHCHECK_AUTO_RESTART: "${HEALTHCHECK_AUTO_RESTART:-true}" ports: - "${WEBDRIVER_PORT:-4444}:4444" restart: unless-stopped - labels: - com.genlayer.health-recovery.logs: "true" - com.genlayer.network: "${NETWORK_NAME:-unknown}" - healthcheck: - test: - - CMD-SHELL - - | - set -eu - boot=$$(awk '{print $$22}' /proc/1/stat) - if curl -fsS --max-time 10 "http://localhost:$${PORT:-4444}/healthcheck?mode=text&url=https%3A%2F%2Ftest-server.genlayer.com%2Fstatic%2Fgenvm%2Fhello.html"; then - printf '%s 0\n' "$$boot" > /tmp/webdriver-health-failures - exit 0 - fi - age=$$(awk -v ticks="$$boot" -v hz="$$(getconf CLK_TCK)" '{print int($$1-ticks/hz)}' /proc/uptime) - [ "$$age" -ge 10 ] || exit 1 - read -r previous failures 2>/dev/null < /tmp/webdriver-health-failures || failures=0 - [ "$${previous:-}" = "$$boot" ] || failures=0 - failures=$$((failures + 1)) - printf '%s %s\n' "$$boot" "$$failures" > /tmp/webdriver-health-failures - if [ "$$failures" -ge 4 ] && [ "$${HEALTHCHECK_AUTO_RESTART:-true}" != false ]; then - printf 'Container webdriver (%s) found to be unhealthy - Restarting container now\n' "$$(cat /etc/hostname)" > /proc/1/fd/1 - children=$$(cat /proc/1/task/1/children) - for child in $$children; do kill -KILL "$$child"; done - fi - exit 1 - interval: 30s - timeout: 12s - retries: 4 - start_period: 10s - # docker-socket-proxy — read-only Docker API gateway for the node's WebDriver - # resource stats and Alloy's recovery-log reader. Runs with the `node` OR - # `monitoring` profile, so it comes up alongside + # docker-socket-proxy — hardened, read-only Docker API gateway used ONLY so the + # node can read the WebDriver container's resource stats (CPU/memory/traffic) for + # metrics. Runs with the `node` OR `monitoring` profile, so it comes up alongside # a node container AND alongside a monitoring/telemetry bring-up — the latter is # how a binary/systemd node (node not containerized) gets its webdriver metrics. # - # The node and Alloy use this read-only gateway instead of the host socket. + # WHY THIS EXISTS — and why it is NOT a new exposure or a "leak": # Reading container stats requires the Docker API. Previously the node reached # the host /var/run/docker.sock directly (bind-mounted into the container, or via # docker-group membership for a binary node) — but that grants the FULL Docker # API as root (a `:ro` flag does NOT restrict API calls), so a node compromise - # could escalate to host root. This proxy REPLACES the node's socket access, - # restricted to read-only container endpoints (CONTAINERS=1; - # POST=0 => it returns 403 for create / exec / image operations). - # Health checks stop only their own service process; recovery has no Docker API access. + # could escalate to host root. This proxy REPLACES that: it is the only thing + # touching the socket, restricted to read-only container endpoints (CONTAINERS=1; + # POST=0 => it returns 403 for create / exec / images / everything else). Net + # exposure goes DOWN, not up. # - Published ONLY on 127.0.0.1:2375 (host loopback), never 0.0.0.0: 2375 is # the unauthenticated Docker API, so it must not leave the host. A container # node reaches it over the compose network (tcp://docker-socket-proxy:2375); @@ -925,15 +901,14 @@ services: # socket: if DOCKER_HOST is unset the node skips webdriver metrics entirely # (non-fatal). Note those are the webdriver container's CPU/memory/traffic series # (genlayer_node_*{component="webdriver"}) that feed validator resource dashboards — - # so keep DOCKER_HOST set to keep those panels populated. Disabling metrics - # permits removing the node's DOCKER_HOST; Alloy still needs this gateway. + # so keep DOCKER_HOST set to keep those panels populated. To disable metrics + # instead: set metrics.collectors.webdriver.enabled: false and delete this service. docker-socket-proxy: - image: tecnativa/docker-socket-proxy:v0.5.0 + image: tecnativa/docker-socket-proxy:0.3.0 container_name: genlayer-node-docker-proxy environment: CONTAINERS: 1 # GET /containers/{json,inspect,stats} — read-only via POST=0 VERSION: 1 # required by the Docker client's API-version negotiation - NETWORKS: 1 # read-only network metadata required by Alloy Docker discovery volumes: - /var/run/docker.sock:/var/run/docker.sock:ro ports: @@ -955,27 +930,23 @@ services: - monitoring genlayer-node: image: yeagerai/genlayer-node:${NODE_VERSION:-latest} - # No entrypoint here: the image's own entrypoint precompiles GenVM for the - # host CPU on the first start, then runs `genlayernode run`. The keystore - # password is NOT on a command line. It reaches the node as + # The keystore password is NOT on this command line. It reaches the node as # GENLAYERNODE_PASSWORD (environment below), which the run command reads in # place of --password. A command-line argument is visible in process # listings on the host; an environment variable is not. + entrypoint: ["/app/bin/genlayernode", "run"] container_name: genlayer-node restart: unless-stopped env_file: - path: ./.env required: false environment: - # No default: this is the password that encrypts the validator's signing - # key, and a default shared by every operator who forgot to set one is a - # key anyone can open (the release binary carries none either). The node - # itself refuses validator mode with an empty password ("password is - # required for validator mode"); a full node needs none. Not a ":?" guard: - # Compose interpolates every service, also under another profile, so a - # guard here would stop `docker compose up -d webdriver-container` (the - # binary install path) and a full node, which never read it. - GENLAYERNODE_PASSWORD: "${NODE_PASSWORD:-}" + # ":?" makes Compose refuse to start when NODE_PASSWORD is unset or empty, + # before any container runs. There is no fallback: this is the password + # that encrypts the validator's signing key, and a default shared by every + # operator who forgot to set one is a key anyone can open. The release + # binary carries no default either — it is compiled out of production. + GENLAYERNODE_PASSWORD: "${NODE_PASSWORD:?NODE_PASSWORD is not set. It is the keystore password that unlocks the validator key. Set it in .env; see README section 4.}" # WebDriver metrics collector reaches Docker via the read-only proxy # (see docker-socket-proxy above), never the host socket directly. DOCKER_HOST: tcp://docker-socket-proxy:2375 @@ -984,11 +955,8 @@ services: - "${NODE_OPS_PORT:-9153}:9153" volumes: - ${NODE_CONFIG_PATH:-./configs/node/config.yaml}:/app/configs/node/config.yaml:ro - # The trusted protocol manifest of the network; consensus.manifestpath names this path. - - ${NODE_MANIFEST_PATH:-./configs/node/protocol-manifest.json}:/app/configs/node/protocol-manifest.json:ro - ${NODE_DATA_PATH:-./data}:/app/data - # opt/genvm/current links to the image's GenVM tree, so no version variable is needed. - - ./genvm-module-web-docker.yaml:/app/opt/genvm/current/config/genvm-module-web.yaml + - ./genvm-module-web-docker.yaml:/app/opt/genvm/${GENVM_VERSION}/config/genvm-module-web.yaml security_opt: - no-new-privileges:true cap_drop: @@ -1015,7 +983,7 @@ services: webdriver_host: http://webdriver-container:4444 ``` - The compose file mounts it over the image's own copy at `/app/opt/genvm/current/config/genvm-module-web.yaml`, so no version variable is needed. The binary keeps using `opt/genvm/current/config/genvm-module-web.yaml`, which points at `localhost:4444`. + The v0.6.0-rc.1 compose file mounts it over the image's own copy at `/app/opt/genvm/${GENVM_VERSION}/config/genvm-module-web.yaml`. Set `GENVM_VERSION=v0.6.0-rc10` in `.env`: without it the file lands in the wrong place and GenVM cannot reach the WebDriver. The binary keeps using `opt/genvm/v0.6.0-rc10/config/genvm-module-web.yaml`, which points at `localhost:4444`. 3. Set the required environment variables in a `.env` file: @@ -1038,9 +1006,8 @@ WEBDRIVER_PORT=4444 # Docker image version NODE_VERSION=latest -# Keystore password (unlocks the pre-imported validator wallet). Required for a -# validator: the node refuses validator mode without it. A full node and the -# WebDriver-only stack need none. Choose your own; never reuse an example +# Keystore password (unlocks the pre-imported validator wallet). Required — the +# stack refuses to start without it. Choose your own; never reuse an example # value. It reaches the node as an environment variable, not a command-line # argument. See README section 4 for reading it in without echo. NODE_PASSWORD= @@ -1048,9 +1015,6 @@ NODE_PASSWORD= # Path to node configuration file NODE_CONFIG_PATH=./configs/node/config.yaml -# Path to the trusted protocol manifest of your network -NODE_MANIFEST_PATH=./configs/node/protocol-manifest.json - # Path to node data directory (for persistence) NODE_DATA_PATH=./data @@ -1065,11 +1029,6 @@ ANTHROPICKEY= XAIKEY= GEMINIKEY= ATOMAKEY= -COMPUT3KEY= -IOINTELLIGENCEKEY= -LIBERTAI_API_KEY= -MORPHEUS_API_KEY= -OPENAIKEY= # Optional logging level override GENLAYERNODE_LOGGING_LEVEL=INFO @@ -1125,9 +1084,6 @@ CENTRAL_LOKI_PASSWORD= # Node identification NODE_ID=validator-001 VALIDATOR_NAME=MyValidator -# Network label for recovery logs and alert routing; use the deployed network. -# Leave empty for an unknown label, rather than guessing a production network. -NETWORK_NAME= # Node metrics endpoint (optional) # Endpoint where the node exposes its Prometheus metrics @@ -1148,6 +1104,7 @@ NODE_METRICS_ENDPOINT=host.docker.internal:9153 # Example for multiple nodes: # SCRAPE_TARGETS_JSON=[{"__address__":"host.docker.internal:9250","instance":"0x...","validator_name":"node-1"},{"__address__":"host.docker.internal:9251","instance":"0x...","validator_name":"node-2"}] # +# To generate for local nodes, use: ./generate-dev-targets.sh # SCRAPE_TARGETS_JSON= # ============================================================================= @@ -1167,8 +1124,8 @@ LOG_FILE_PATTERN=/var/log/genlayer/node*.log # Metrics scraping intervals (optional) # How often to scrape metrics from the node -# Default: 60s for node metrics, 10s timeout -METRICS_SCRAPE_INTERVAL=60s +# Default: 15s for node metrics, 10s timeout +METRICS_SCRAPE_INTERVAL=15s METRICS_SCRAPE_TIMEOUT=10s # Alloy internal health monitoring interval (optional) @@ -1179,9 +1136,9 @@ ALLOY_SELF_MONITORING_INTERVAL=60s # Loki log batching configuration (optional) # Controls how logs are batched before sending to central Loki -# Default: 1MiB batch size, 60s wait time +# Default: 1MiB batch size, 1s wait time LOKI_BATCH_SIZE=1MiB -LOKI_BATCH_WAIT=60s +LOKI_BATCH_WAIT=1s # ============================================================================= # Deployment Instructions @@ -1214,17 +1171,16 @@ LOKI_BATCH_WAIT=60s # - Grafana Alloy handles both metrics and logs in a single service # - To customize monitoring, edit alloy-config.river # - To filter metrics (reduce bandwidth), uncomment the filtering rule in alloy-config.river -# - Ensure the firewall allows outbound HTTPS to CENTRAL_MONITORING_URL and CENTRAL_LOKI_URL +# - Ensure firewall allows outbound connections to monitoring server (port 8180) # - -# Keep health checks but disable automatic container-local recycling when false. -HEALTHCHECK_AUTO_RESTART=true ``` This is the release package's `.env.example`: copy it to `.env` next to `docker-compose.yaml`. The release package also ships `docker-compose.yaml` and `genvm-module-web-docker.yaml`. Set at least: -- `NODE_PASSWORD`: the keystore password. A validator needs it: the node refuses validator mode without a password, and there is no default. A full node and the WebDriver-only stack need none. -- `NODE_VERSION`: pin the exact version approved for Bradbury Phase 2. The `latest` image can belong to an older release family. +- `NODE_PASSWORD`: the keystore password, with no default. In v0.6.0-rc.1 every `docker compose` command fails while it is empty, also for a full node or the WebDriver alone. A validator sets the operator key's password (the export passphrase); a full node sets any non-empty value. +- `GENVM_VERSION=v0.6.0-rc10`: the GenVM version v0.6.0-rc.1 ships. The compose file uses it to mount `genvm-module-web-docker.yaml`; `.env.example` does not list it. +- `COMPOSE_PROJECT_NAME=genlayer-node`: the v0.6.0-rc.1 compose file has no fixed project name, so Compose names the project after the directory. With a fixed name, a new version run from a new directory replaces the old containers instead of failing with "container name ... already in use". Passing `-p genlayer-node` to every `docker compose` command does the same. +- `NODE_VERSION`: pin the exact version approved for Bradbury Phase 2, today `NODE_VERSION=v0.6.0-rc.1`. The `latest` image can belong to an older release family. - the API key of at least one LLM provider. - `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY`, when your chain RPC provider needs a key (the Bradbury WebSocket does). @@ -1269,7 +1225,7 @@ docker compose --profile node up -d For a quick setup using Docker Compose: -1. **Set the monitoring variables in your `.env` file.** The `.env` example above lists them: the six `CENTRAL_*` endpoint and credential values, `NODE_ID`, `VALIDATOR_NAME` and `NETWORK_NAME`. All six `CENTRAL_*` values are required: Alloy stops with a message when one is missing. Get the endpoints and credentials from the Foundation team, and see the [Monitoring Guide](/validators/monitoring) for the values to use. +1. **Set the monitoring variables in your `.env` file.** The `.env` example above lists them: the six `CENTRAL_*` endpoint and credential values, `NODE_ID` and `VALIDATOR_NAME`. All six `CENTRAL_*` values are required: Alloy stops with a message when one is missing. Get the endpoints and credentials from the Foundation team, and see the [Monitoring Guide](/validators/monitoring) for the values to use. 2. **Run docker compose**: ```bash copy diff --git a/pages/validators/upgrade.mdx b/pages/validators/upgrade.mdx index a173377f..3bd3826b 100644 --- a/pages/validators/upgrade.mdx +++ b/pages/validators/upgrade.mdx @@ -55,15 +55,16 @@ Breaking changes involve a new consensus deployment. The old database is incompa - Bradbury Phase 2 is a new deployment on a new chain, with a new `consensusaddress` and a new staking contract. Phase 1 validators are not carried over: join again as a validator, with stake, on Phase 2. Follow the [breaking upgrade](#breaking-change-upgrade) steps. - The node needs the trusted protocol manifest of the network. Set `consensus.manifestpath` (default `configs/node/protocol-manifest.json`) and `consensus.chainid`. The release package has no network files: take the manifest and values from [Network-Specific Consensus Configuration](/validators/setup-guide#network-specific-consensus-configuration). -- GenVM moved from `third_party/genvm` to `opt/genvm/`, with the link `opt/genvm/current` to it. Set `genvm.root_dir: ./opt/genvm/current` and run `python3 ./opt/genvm/current/bin/setup.py` (see [download instructions](/validators/setup-guide#download-the-node-software)). +- GenVM moved from `third_party/genvm` to `opt/genvm/` (v0.6.0-rc.1: `opt/genvm/v0.6.0-rc10`). Set `genvm.root_dir: ./opt/genvm/v0.6.0-rc10` and run `python3 ./opt/genvm/v0.6.0-rc10/bin/setup.py` (see [download instructions](/validators/setup-guide#download-the-node-software)). - The Bradbury RPC WebSocket needs an API key. Set it in the environment as `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCKEY`; it has no `config.yaml` key. See [Testnet Bradbury](/validators/setup-guide#testnet-bradbury). - The `genlayer_debug` (`gen_dbg_*`) and `zksync` (`zks_*`) RPC groups are off in the default `config.yaml` (`node.rpc.endpoints`). - The data store is split into `genvm.db` and `ops.db`. On startup the node renames an old `genlayer.db` to `genvm.db`; it refuses to start if both exist. -- Docker: `NODE_PASSWORD` has no default, and a validator needs it: the node refuses validator mode without a password. It reaches the node as `GENLAYERNODE_PASSWORD`, not as `--password`. See [Running the Node using docker-compose](/validators/setup-guide#running-the-node-using-docker-compose). +- Docker: `NODE_PASSWORD` has no default, and in v0.6.0-rc.1 every `docker compose` command fails while it is empty. It reaches the node as `GENLAYERNODE_PASSWORD`, not as `--password`. See [Running the Node using docker-compose](/validators/setup-guide#running-the-node-using-docker-compose). +- Docker: the v0.6.0-rc.1 compose file does not mount the protocol manifest. Mount it in a `docker-compose.override.yml` (see [Running the Node using docker-compose](/validators/setup-guide#running-the-node-using-docker-compose)). - Docker: the node container runs as uid `10001`. Make `./data` writable by it once: `sudo chown -R 10001:10001 ./data`. - Docker: the node no longer mounts `/var/run/docker.sock`. It reaches Docker through the read-only `docker-socket-proxy` service. -- Docker: if you mount your own GenVM config files (for example a customized LLM config for greyboxing, in a `docker-compose.override.yml`), move every mount from `/app/third_party/genvm/config/` to `/app/opt/genvm/current/config/`. v0.6 no longer reads the old path, so a mount left there is ignored without an error and the image's default config loads. -- Docker: the compose project is named `genlayer-node`, so `docker compose up` from a new version's directory replaces the containers of the previous version instead of failing with "container name ... already in use". +- Docker: if you mount your own GenVM config files (for example a customized LLM config for greyboxing, in a `docker-compose.override.yml`), move every mount from `/app/third_party/genvm/config/` to `/app/opt/genvm/v0.6.0-rc10/config/` (v0.6.0-rc.1). v0.6 no longer reads the old path, so a mount left there is ignored without an error and the image's default config loads. +- Docker: the v0.6.0-rc.1 compose file has no fixed project name. Set `COMPOSE_PROJECT_NAME=genlayer-node` in `.env` (or pass `-p genlayer-node`), so `docker compose up` from a new version's directory replaces the containers of the previous version instead of failing with "container name ... already in use". - Monitoring: the `CENTRAL_*` URLs and credentials have no defaults. Alloy stops at startup if any is unset. See [Monitoring](/validators/monitoring). - Do not use `genlayernode run --upgrade` in v0.6: the call that resumes the node is not available yet, so the node waits forever.