diff --git a/.github/actions/sync-files/action.yml b/.github/actions/sync-files/action.yml index d8eade875..c1b32f7ad 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 b1de55860..52ebd4726 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/git-utils.sh b/.github/scripts/git-utils.sh index 210ea55da..82f650969 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 diff --git a/.github/scripts/node-sync-params.sh b/.github/scripts/node-sync-params.sh new file mode 100755 index 000000000..8b85e8a6f --- /dev/null +++ b/.github/scripts/node-sync-params.sh @@ -0,0 +1,79 @@ +#!/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 +# 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}" +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_(?!load_test).*}" + +# 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|.*" + +# 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) + selected | cut -d'|' -f1 + ;; + --sources) + selected | cut -d'|' -f3 | 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 000000000..01470fec8 --- /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 000000000..0769bd529 --- /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/scripts/sanitize-config.sh b/.github/scripts/sanitize-config.sh index 116ec6fe6..30b26a0ce 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 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/.github/scripts/version-utils.sh b/.github/scripts/version-utils.sh index d3efb016b..dfaca47db 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 b8feca3c5..e73ddd981 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 release-version rule (newest rc until its stable exists) + run: npm run test:release-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 5e00b8564..5f00ba634 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 @@ -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/asimov.yaml.example - configs/node/bradbury.yaml.example - 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_asimov, config_bradbury, 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_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 - echo "target_path=content/validators/bradbury.yaml" >> $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_(?!load_test).*' }} + 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: | @@ -453,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 @@ -536,20 +495,22 @@ 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 $(echo '${{ needs.prepare.outputs.sync_types }}' | jq -r '.[]'); 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)" ;; + "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/.gitignore b/.gitignore index fb20a9056..ee8e1eaaa 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/api-references/genlayer-node/content.mdx b/content/api-references/genlayer-node/content.mdx index 9d641012c..b57b37eb9 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/content/validators/asimov.yaml b/content/validators/asimov.yaml deleted file mode 100644 index 6aa49ab3f..000000000 --- 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/content/validators/bradbury.protocol-manifest.json b/content/validators/bradbury.protocol-manifest.json new file mode 100644 index 000000000..960807e14 --- /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 7655a3c85..6265477d7 100644 --- a/content/validators/bradbury.yaml +++ b/content/validators/bradbury.yaml @@ -1,4 +1,25 @@ -# Bradbury Phase 1 network consensus configuration +# Bradbury Phase 2 network configuration (GenLayer testnet, chain id 4221). +# +# 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: + 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. + 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/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/changelog/v0.3.3.mdx b/content/validators/changelog/v0.3.3.mdx index 4ade8c245..cb09e621c 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/changelog/v0.6.0-rc.1.mdx b/content/validators/changelog/v0.6.0-rc.1.mdx new file mode 100644 index 000000000..8e5bfa0d7 --- /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/config.yaml b/content/validators/config.yaml index 487236428..b8c424759 100644 --- a/content/validators/config.yaml +++ b/content/validators/config.yaml @@ -1,14 +1,21 @@ # 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 - provider: "TODO: Set your GenLayer Chain ZKSync provider" # Rollup operator name; surfaces as the "provider" label on RPC-driven metrics. Set explicitly per deployment. + 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 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. + # The deployment-specific fields are empty: take them from your network's documentation. + 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 @@ -20,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 @@ -43,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 @@ -91,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 1af0a4676..0015e84ac 100644 --- a/content/validators/docker-compose-monitoring.yaml +++ b/content/validators/docker-compose-monitoring.yaml @@ -1,7 +1,7 @@ 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 @@ -11,26 +11,92 @@ services: - "${WEBDRIVER_PORT:-4444}:4444" restart: unless-stopped + # 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. + # + # 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 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); + # 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. To disable metrics + # instead: set metrics.collectors.webdriver.enabled: false and delete this service. + docker-socket-proxy: + 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 + 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 - ${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 + - ./genvm-module-web-docker.yaml:/app/opt/genvm/${GENVM_VERSION}/config/genvm-module-web.yaml security_opt: - no-new-privileges:true + cap_drop: + - ALL logging: driver: "json-file" options: @@ -39,6 +105,7 @@ services: compress: "true" depends_on: - webdriver-container + - docker-socket-proxy profiles: - node @@ -53,13 +120,35 @@ 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 container_name: genlayer-node-alloy + # 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 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"] 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 @@ -72,17 +161,21 @@ services: 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} + # 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 77c2110a7..41fe121cb 100644 --- a/content/validators/docker-compose.yaml +++ b/content/validators/docker-compose.yaml @@ -1,7 +1,7 @@ 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 @@ -10,24 +10,91 @@ services: ports: - "${WEBDRIVER_PORT:-4444}:4444" restart: unless-stopped + # 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. + # + # 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 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); + # 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. To disable metrics + # instead: set metrics.collectors.webdriver.enabled: false and delete this service. + docker-socket-proxy: + 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 + 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 - ${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 + - ./genvm-module-web-docker.yaml:/app/opt/genvm/${GENVM_VERSION}/config/genvm-module-web.yaml security_opt: - no-new-privileges:true + cap_drop: + - ALL logging: driver: "json-file" options: @@ -36,5 +103,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 000000000..60d972b3b --- /dev/null +++ b/content/validators/env.example @@ -0,0 +1,181 @@ +# 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 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 + +# 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) +# diff --git a/content/validators/greybox-setup-guide.md b/content/validators/greybox-setup-guide.md index d690436f5..08709109e 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/package.json b/package.json index cea35c41d..7a30cbb28 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-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", @@ -17,7 +17,13 @@ "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-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:release-versions": "node scripts/test-release-versions.js" }, "repository": { "type": "git", diff --git a/pages/api-references/genlayer-cli.mdx b/pages/api-references/genlayer-cli.mdx index 47b84f488..c959cda66 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 864772357..15d5a8a2b 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 89a90a41b..1cc89f0b2 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 83b750aa9..0fe17b9a4 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 b75fe6ce5..8db01d2b8 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 325eb3793..149488681 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 1e8b5ad07..387fbddc9 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 3219e7523..a24b0b1a2 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 7f4cf5704..ce8a5014a 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 5d69dbea4..cade25d88 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 c6a55a01c..2d213454c 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 9212e8632..8cc1d8f5b 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 5c5b2e9f8..612ae9db2 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 bddb46573..59f232725 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 1f0dd6a6b..92f8860c5 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 3c8dd58c7..089b479fe 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 e2f29f851..6ed6f709f 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 bc4e6fd8f..221e55be6 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 24e9ff3b7..8247b8066 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 33d335a74..58f1d9bbe 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 015b15185..d12f809eb 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 f863065b9..45cd1f9ce 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 aebb731f2..90a751c32 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 '{ @@ -528,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. @@ -546,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. @@ -573,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. @@ -679,7 +712,8 @@ A `Transaction` object which contains the details of the transaction. The struct "messageFeesBudgetTotal": "0" }, "hasAllocations": false, - "allocations": [] + "allocations": [], + "allocationPolicy": "open" }, "readStateBlockRanges": [ { @@ -826,6 +860,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 +895,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,10 +972,533 @@ 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, `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. +- `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. +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. @@ -1009,6 +1568,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 +1589,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 +1631,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 +1640,10 @@ 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. ## Ethereum Compatibility @@ -1090,20 +1653,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 '{ @@ -1124,7 +1689,7 @@ curl -X POST http://localhost:9151 \ "to": "0x742d35Cc6634C0532925a3b8D4C9db96c4b4d8b6", "data": "0x70a08231000000000000000000000000742d35cc6634c0532925a3b8d4c9db96c4b4d8b6", "type": "read", - "transaction_hash_variant": "latest-nonfinal" + "status": "decided" }], "id": 1 }' @@ -1141,7 +1706,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 '{ @@ -1237,7 +1802,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 +1816,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 +1836,128 @@ 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": "", + "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", "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" } } } ``` **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 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 + - `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`.** +**States** -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`: +| 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 | -```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" } - } -} -``` +Roll-up precedence, most urgent first: `unavailable`, `degraded`, `warning`, `suspect`, `unknown`, `healthy`. -`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. +`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. -**HTTP Status Codes:** +**Only `unavailable` returns 503.** -- `200 OK`: Node is healthy (all checks passing) -- `503 Service Unavailable`: Node is unhealthy (one or more checks failing) +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. -**Notes:** +`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. -- 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 +**Example: the LLM provider is dead — HTTP 200** -**Example Response (Unhealthy):** +```json +{ + "status": "degraded", + "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.", + "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": "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": "unavailable", + "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.", + "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 +2009,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 +2226,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 +2301,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 +2538,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 +2597,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/asimov-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 +2619,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 +2627,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 811f7749f..c82ab8d8a 100644 --- a/pages/api-references/genlayer-node/debug/_meta.json +++ b/pages/api-references/genlayer-node/debug/_meta.json @@ -2,4 +2,4 @@ "gen_dbg_ping": "gen_dbg_ping", "gen_dbg_trie": "gen_dbg_trie", "gen_dbg_traceTransaction": "gen_dbg_traceTransaction" -} \ No newline at end of file +} 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 1500dc4dd..8d6810a66 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 88ee7c444..393a9c18b 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 08ada0fcb..d5b2d5a4b 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 fa217b172..c2715a53c 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 000000000..a9d6abe24 --- /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 dbea8c2d9..88061ac47 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 51527a470..662e8a9ed 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 000000000..ef7972ff9 --- /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 000000000..dab53508a --- /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, `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. +- `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 168bef114..c8c118880 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_getTransactionReceipt.mdx b/pages/api-references/genlayer-node/gen/gen_getTransactionReceipt.mdx index 67177b75b..19ee20d66 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/api-references/genlayer-node/gen/gen_protocolVersion.mdx b/pages/api-references/genlayer-node/gen/gen_protocolVersion.mdx new file mode 100644 index 000000000..0885cc961 --- /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 000000000..330f98e21 --- /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 b316d2d6f..306bd7b27 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 000000000..ab576a660 --- /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 8884e70e7..96f3a7648 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 6b86402d6..8ecf1de03 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,128 @@ 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": "", + "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", "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" } } } ``` **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 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 + - `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`.** +**States** -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`: +| 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 | -```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" } - } -} -``` +Roll-up precedence, most urgent first: `unavailable`, `degraded`, `warning`, `suspect`, `unknown`, `healthy`. -`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. +`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. -**HTTP Status Codes:** +**Only `unavailable` returns 503.** -- `200 OK`: Node is healthy (all checks passing) -- `503 Service Unavailable`: Node is unhealthy (one or more checks failing) +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. -**Notes:** +`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. -- 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 +**Example: the LLM provider is dead — HTTP 200** -**Example Response (Unhealthy):** +```json +{ + "status": "degraded", + "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.", + "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": "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": "unavailable", + "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.", + "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 fd908d57c..2a2bdb5c0 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 07b728c3a..8139b5ba5 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/asimov-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/api-references/genlayer-test.md b/pages/api-references/genlayer-test.md index 618890050..f4d199e85 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 98683deea..1dbeb8f6b 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 495d99bbc..573e80128 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 ac3706636..90d6e220c 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 e8d468daf..07fe0e9b4 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,33 +21,23 @@ 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) | - + ---- - -## Testnet Asimov - -Infrastructure and stress testing. + +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. + -| 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) | +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. --- @@ -104,28 +94,28 @@ 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/) | - + --- ## 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/partners/chutes.mdx b/pages/partners/chutes.mdx index f1afeece1..c198237f1 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/_meta.json b/pages/validators/_meta.json index f04164cd4..878bf65b1 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 9695ec57e..bac7ca4b5 100644 --- a/pages/validators/changelog.mdx +++ b/pages/validators/changelog.mdx @@ -1,5 +1,164 @@ # Changelog +## 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 @@ -560,7 +719,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 c3042eb0a..1e82ac557 100644 --- a/pages/validators/genvm-configuration.mdx +++ b/pages/validators/genvm-configuration.mdx @@ -39,20 +39,20 @@ 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 -All configuration lives at `third_party/genvm/config/`. 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? | |---|---|---| | `genvm-module-llm.yaml` | LLM providers, models, prompt templates, Lua script selection | **Yes** — this is your main configuration file | | `genvm-module-web.yaml` | Web access, webdriver, URL restrictions | Rarely — only for advanced URL policy changes | -| `genvm-manager.yaml` | Concurrency permits, VM pool | Rarely — only if tuning for your hardware | +| `genvm-manager.yaml` | Permit pool (concurrency), VM pool | Rarely — the node sets the permit budget from its own `genvm.permits` (see below) | ## Configuring Your LLM Backends @@ -139,20 +139,35 @@ backends: ### API key environment variables -Set these in your `.env` file before starting the node. You only need to set the keys for providers you're actually using. Keys use `${ENV[VARIABLE_NAME]}` syntax in the YAML. +The YAML reads each key with `${ENV[VARIABLE_NAME]}`. Set only the keys for the providers you use. -| Variable | Provider | -|---|---| -| `OPENROUTERKEY` | OpenRouter | -| `OPENAIKEY` | OpenAI | -| `ANTHROPICKEY` | Anthropic | -| `GEMINIKEY` | Google Gemini | -| `HEURISTKEY` | Heurist | -| `XAIKEY` | X.ai (Grok) | -| `COMPUT3KEY` | Comput3 | -| `IOINTELLIGENCE_API_KEY` | io.net | -| `CHUTES_API_KEY` | Chutes | -| `MORPHEUS_API_KEY` | Morpheus | +Where to set them: + +- **Docker:** in the `.env` file next to `docker-compose.yaml`. The node service loads it. +- **Binary or systemd:** in the node's process environment. Export them in the shell that starts the node, or load the file from the systemd unit (for example with `EnvironmentFile=`). + +Which keys are read depends on the LLM config you run. Two files ship in the GenVM config directory: + +- **Default config:** `genvm-module-llm.yaml` as shipped with GenVM. This is what the node loads unless you change it. +- **Release config:** `genvm-modules-llm-release.yaml`, shipped by the node release next to it. It adds OpenRouter and the partner providers. It takes effect only after you copy it over `genvm-module-llm.yaml` (see step 3 of [Greybox Setup](/validators/greybox-setup)). + +| Variable | Provider | Default config | Release config | +|---|---|---|---| +| `OPENROUTERKEY` | OpenRouter | Not read | Yes | +| `HEURISTKEY` | Heurist | Yes | Yes | +| `COMPUT3KEY` | Comput3 | Not read | Yes | +| `IOINTELLIGENCEKEY` | io.net | Not read | Yes | +| `LIBERTAI_API_KEY` | LibertAI | Not read | Yes | +| `MORPHEUS_API_KEY` | Morpheus | Not read | Yes | +| `OPENAIKEY` | OpenAI | Yes (also the `dev` backend, which points at OpenRouter) | Backend disabled | +| `ANTHROPICKEY` | Anthropic | Yes | Backend disabled | +| `GEMINIKEY` | Google Gemini | Yes | Backend disabled | +| `XAIKEY` | X.ai (Grok) | Yes | Backend disabled | +| `ATOMAKEY` | Atoma | Backend disabled | Backend disabled | + +"Backend disabled" means the backend is in the file with `enabled: false`. Set it to `true` to use the key. + +Chutes has no built-in backend. To use it, add an `openai-compatible` backend with your own key variable (see [Chutes](/partners/chutes)). 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 @@ -226,33 +257,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 @@ -372,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 @@ -384,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/greybox-setup.mdx b/pages/validators/greybox-setup.mdx new file mode 100644 index 000000000..909eb7742 --- /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 1b5df4dbc..f2a869c2e 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,9 +92,9 @@ 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 #testnet-asimov): +- 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 @@ -102,33 +102,34 @@ 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_NAME=asimov-phase5 # 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 ``` -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 @@ -142,13 +143,35 @@ 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 container_name: genlayer-node-alloy + # 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 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"] 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 @@ -161,17 +184,21 @@ METRICS_SCRAPE_INTERVAL=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} + # 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} @@ -394,6 +421,17 @@ 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 + +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 the Alloy container's health: + +```bash copy +docker inspect --format '{{.State.Health.Status}}' genlayer-node-alloy +docker compose logs --since 30m alloy +``` + ## Troubleshooting ### Using the Alloy UI diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index f1204d636..8fd5342a3 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). @@ -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. @@ -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 @@ -113,35 +113,46 @@ 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: +- **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 - 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-asimov -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) -6. **Validator creation** - Submits the staking transaction -7. **Identity setup** - Set your validator's public profile (moniker, website, etc.) +2. **Network selection** - Choose testnet-bradbury +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. + + 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 +162,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 +208,22 @@ 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 +#### GenLayer Chain RPC access + +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: + +- 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="" +``` -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. +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. @@ -210,41 +236,68 @@ 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' | 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 ```sh - v0.5.14 - v0.5.13 - v0.5.12 - v0.5.11 - v0.5.10 + 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.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 + 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" + 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. 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. + +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`. + + 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 + 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: @@ -263,20 +316,29 @@ 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). + +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 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 - provider: "TODO: Set your GenLayer Chain ZKSync provider" # Rollup operator name; surfaces as the "provider" label on RPC-driven metrics. Set explicitly per deployment. + 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 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. + # The deployment-specific fields are empty: take them from your network's documentation. + 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 @@ -288,6 +350,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 @@ -311,10 +385,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 @@ -359,73 +439,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 @@ -455,31 +584,66 @@ 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 +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 -# Asimov Phase 5 network consensus configuration +# Bradbury Phase 2 network configuration (GenLayer testnet, chain id 4221). +# +# 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: + 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. + provider: "conduit" # Rollup operator name; surfaces as the "provider" label on RPC-driven metrics. + +# consensus contracts configuration consensus: - consensusaddress: "0xe66B434bc83805f380509642429eC8e43AE9874a" - genesis: 17326 + 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/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). ``` -##### Testnet Bradbury - -```yaml copy -# Bradbury Phase 1 network consensus configuration -consensus: - consensusaddress: "0x8aCE036C8C3C5D603dB546b031302FCf149648E8" - genesis: 501711 +**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. 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. + +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_GENLAYERCHAINRPCKEY="" + ``` + + 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 -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:** @@ -493,8 +657,9 @@ Any configuration value in `config.yaml` can be overridden using environment var |------------|---------------------| | `rollup.genlayerchainrpcurl` | `GENLAYERNODE_ROLLUP_GENLAYERCHAINRPCURL` | | `rollup.genlayerchainwebsocketurl` | `GENLAYERNODE_ROLLUP_GENLAYERCHAINWEBSOCKETURL` | -| `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` | @@ -502,11 +667,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..." @@ -525,73 +699,57 @@ 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. -#### 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 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 -./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 \ - --setup +read -rsp "Keystore password: " GENLAYERNODE_PASSWORD +export GENLAYERNODE_PASSWORD +printf '\n' ``` -You should see: +#### Option 1: Import from CLI Wizard (Recommended) + +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 -Account imported: - Address: 0xA0b12Fd2f3F7e86fEC458D114A5E7a6f571160a8 - Account setup as a validator +```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 ``` +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 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. +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 -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 \ - --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 \ - --password "your node password" \ - --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 @@ -602,32 +760,29 @@ 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 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 @@ -636,37 +791,43 @@ 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 - ./third_party/genvm/bin/genvm-modules web & ./third_party/genvm/bin/genvm-modules llm & - ``` + 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`. - - 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. - + 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): + + ```sh copy + docker compose up -d docker-socket-proxy + ``` -4. Checking Your Configuration + 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 + + With `GENLAYERNODE_PASSWORD` set (see [Import the Operator Key](#import-the-operator-key)): + + ```sh copy + ./bin/genlayernode run -c $(pwd)/configs/node/config.yaml + ``` -5. Run the node + With WebDriver resource metrics: ```sh copy - ./bin/genlayernode run -c $(pwd)/configs/node/config.yaml --password "your secret password" # The same password you used when creating the account + DOCKER_HOST=tcp://127.0.0.1:2375 ./bin/genlayernode run -c $(pwd)/configs/node/config.yaml ``` @@ -679,7 +840,26 @@ 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`: + +- `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 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 + mkdir -p ./data && sudo chown -R 10001:10001 ./data + ``` 1. Create a `docker-compose.yaml` file with the following content: @@ -687,7 +867,7 @@ 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 @@ -696,24 +876,91 @@ services: ports: - "${WEBDRIVER_PORT:-4444}:4444" restart: unless-stopped + # 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. + # + # 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 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); + # 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. To disable metrics + # instead: set metrics.collectors.webdriver.enabled: false and delete this service. + docker-socket-proxy: + 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 + 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 - ${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 + - ./genvm-module-web-docker.yaml:/app/opt/genvm/${GENVM_VERSION}/config/genvm-module-web.yaml security_opt: - no-new-privileges:true + cap_drop: + - ALL logging: driver: "json-file" options: @@ -722,31 +969,21 @@ services: compress: "true" depends_on: - webdriver-container + - docker-socket-proxy profiles: - node ``` -2. Create `genvm-module-web-docker.yaml` for Docker networking: - -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. +2. Keep `genvm-module-web-docker.yaml` next to your `docker-compose.yaml`. -Create `genvm-module-web-docker.yaml` in the same directory as your `docker-compose.yaml`: + It ships in the release package. It is GenVM's web module config with the webdriver address set to the compose service name: -- 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 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: @@ -756,6 +993,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) @@ -766,10 +1004,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 @@ -781,60 +1022,210 @@ 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 + +# 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) +# ``` -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, 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). -4. Checking Your Configuration +4. Import the operator key + + 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. + + ```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 + ``` -To ensure your node is correctly configured, you can run the following command: + 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: + 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: ```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: +6. Start the services using Docker Compose: ```sh copy -source .env && docker compose --profile node up -d +docker compose --profile node up -d ``` ### Telemetry 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` 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/system-requirements.mdx b/pages/validators/system-requirements.mdx index c8f56def7..e774a0195 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`, with `pip` and `venv` (on Debian/Ubuntu: `python3-pip` and `python3-venv`) - for GenVM setup diff --git a/pages/validators/upgrade.mdx b/pages/validators/upgrade.mdx index 0802f772e..3bd3826b0 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,23 @@ 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 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/` (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 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/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. + ### 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`. diff --git a/scripts/check-node-sync.js b/scripts/check-node-sync.js new file mode 100644 index 000000000..710c62dc9 --- /dev/null +++ b/scripts/check-node-sync.js @@ -0,0 +1,145 @@ +#!/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), +// 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 { mayLeave } = require('./lib/release-versions'); + +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. +// 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' }); + 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); + 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}`) && !mayLeave(file.replace(/\.mdx$/, ''))) { + 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 1b377c485..f576647e0 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 950ecf5c0..eb7b2fbe3 100644 --- a/scripts/generate-changelog.js +++ b/scripts/generate-changelog.js @@ -2,6 +2,10 @@ 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; }; /** * Read all changelog version files and generate the combined content @@ -11,41 +15,19 @@ 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; } - // 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): + // every changelog file the node has, rcs included. + 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 000000000..b82675a00 --- /dev/null +++ b/scripts/lib/release-versions.js @@ -0,0 +1,74 @@ +// 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: 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 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+))?$/; + +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 } = {}) { + return names + .map(parseVersion) + .filter(Boolean) + .filter((v) => testnet || v.pre !== 'testnet') + .sort(compareDesc) + .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. + */ +function mayLeave(name) { + const v = parseVersion(name); + return Boolean(v && v.pre === 'rc'); +} + +module.exports = { parseVersion, compareDesc, publishedVersions, currentLine, mayLeave }; diff --git a/scripts/publish-network-manifests.js b/scripts/publish-network-manifests.js new file mode 100644 index 000000000..1cdbf36f8 --- /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 }; diff --git a/scripts/retrofit/retrofit.js b/scripts/retrofit/retrofit.js index 110fc607f..917095818 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.`; diff --git a/scripts/simulate-node-sync.sh b/scripts/simulate-node-sync.sh new file mode 100755 index 000000000..4ee2494b9 --- /dev/null +++ b/scripts/simulate-node-sync.sh @@ -0,0 +1,55 @@ +#!/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. +# 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. + +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 -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 + 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-release-versions.js b/scripts/test-release-versions.js new file mode 100644 index 000000000..5951be1d4 --- /dev/null +++ b/scripts/test-release-versions.js @@ -0,0 +1,133 @@ +#!/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, currentLine, mayLeave } = 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', 'v0.5.9', 'v0.5.12', 'v0.5.13', 'v0.5.14']; + +const tests = { + '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 #/); + }, + '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.6.0-rc.1', 'v0.5.14']); + const { guide } = guideFor(names); + assert.match(guide, /export version=v0\.6\.0-rc\.2 #/); + // 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'); + }, + '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']; + 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']); + }, + 'an rc of the next version shows above the current stable': () => { + assert.deepStrictEqual(publishedVersions(['v0.6.0', 'v0.6.1-rc.1']), ['v0.6.1-rc.1', 'v0.6.0']); + }, + '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'); + }, + '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 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'); + // 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; + 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/update-alloy-config-in-monitoring.js b/scripts/update-alloy-config-in-monitoring.js index 5cd92433d..497fc54ed 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 33f999eec..93725cea6 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 c10e458e5..09a72b565 100755 --- a/scripts/update-config-in-setup-guide.js +++ b/scripts/update-config-in-setup-guide.js @@ -3,18 +3,20 @@ 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 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'); if (!fs.existsSync(setupGuidePath)) { - console.error(`Setup guide file ${setupGuidePath} does not exist`); + fail(`Setup guide file ${setupGuidePath} does not exist`); return; } @@ -28,31 +30,15 @@ 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'); - } - } - - // 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'); + fail('Could not find main 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```)/; @@ -60,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 655760910..692f644f2 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 000000000..66882572a --- /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 e1b5515e3..413fd920f 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 063d170f1..c4ba56dbd 100644 --- a/scripts/update-setup-guide-versions.js +++ b/scripts/update-setup-guide-versions.js @@ -2,107 +2,79 @@ const fs = require('fs'); const path = require('path'); +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) 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 with the latest versions from changelog + * Update the setup guide's version list, list command and `export version=` line. + * 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() { - const changelogDir = path.join(process.cwd(), 'content/validators/changelog'); - const setupGuidePath = path.join(process.cwd(), 'pages/validators/setup-guide.mdx'); - +function updateSetupGuideVersions({ root = process.cwd() } = {}) { + const changelogDir = path.join(root, 'content/validators/changelog'); + const setupGuidePath = path.join(root, 'pages/validators/setup-guide.mdx'); + if (!fs.existsSync(changelogDir)) { - console.error(`Changelog directory ${changelogDir} does not exist`); - return; - } - - // 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'); + 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')); - // Take only the first 5 versions (matches head -n 5) - const displayVersions = releaseVersions.slice(0, 5); + const names = fs.readdirSync(changelogDir) + .filter((file) => file.endsWith('.mdx')) + .map((file) => file.replace(/\.mdx$/, '')); + // 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; + } - // 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` - ); + // 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, () => LIST_COMMAND_TAIL); } 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 with ${versions.length} published versions, latest: ${latestVersion}`); } -// Run the script if (require.main === module) { updateSetupGuideVersions(); } -module.exports = { updateSetupGuideVersions }; \ No newline at end of file +module.exports = { updateSetupGuideVersions, LIST_COMMAND_TAIL };