diff --git a/.github/test-double-vc.sh b/.github/test-double-vc.sh index 06114e40..90ce7cdd 100755 --- a/.github/test-double-vc.sh +++ b/.github/test-double-vc.sh @@ -13,7 +13,8 @@ if ! ETHD_SOURCE_ONLY=1 source ./ethd; then fi set +eEuo pipefail # "ethd" turns these on; this script is deliberately without them -__env_file=.env +__env_file=$(mktemp) +echo "ENV_VERSION=78" > "${__env_file}" __me=ethd __docompose() { echo "STUB compose $*" @@ -43,10 +44,18 @@ check "teku.yml:geth.yml" 0 check "lighthouse-cl-only.yml:teku-vc-only.yml:grafana.yml:grafana-for-vc.yml" 0 check "teku.yml:teku.yml" 0 check "teku-vc-only.yml:grafana.yml:grafana-vc-only.yml" 0 -check "grandine-plugin.yml:caplin.yml:web3signer.yml:vc-traefik.yml:allin1-keyapi-shared.yml" 0 +check "grandine-plugin-cl-only.yml:caplin.yml:web3signer.yml:vc-traefik.yml:allin1-keyapi-shared.yml" 0 check "lighthouse.yml:teku-vc-only.yml" 1 check "nimbus-allin1.yml:reth.yml:vero-vc-only.yml" 1 check "prysm.yml:lodestar.yml:nimbus.yml" 1 +check "grandine.yml:lighthouse-vc-only.yml" 1 +check "grandine-plugin.yml:nethermind.yml:lighthouse-vc-only.yml" 1 + +# Remove after Glamsterdam +# Before 78, grandine-plugin.yml had no VC. "update" has to get past the check to rename it +echo "ENV_VERSION=77" > "${__env_file}" +check "grandine-plugin.yml:nethermind.yml:lighthouse-vc-only.yml" 0 +rm -f "${__env_file}" echo if [[ "${failures}" -gt 0 ]]; then diff --git a/.github/test-ethd-migrate.sh b/.github/test-ethd-migrate.sh index bab0bf19..87d3bb0f 100755 --- a/.github/test-ethd-migrate.sh +++ b/.github/test-ethd-migrate.sh @@ -7,6 +7,8 @@ # They also cover later value migrations, such as the EPBS_BUILD_FACTOR meaning of 100, and the # move from MEV_BOOST and MEV_RELAYS to EPBS_BUILDERS and EPBS_BUILDER_URLS after Glamsterdam. # Before a network's own Glamsterdam, EPBS_BUILDER_URLS is filled in while MEV Boost stays on. +# Compose file renames are covered too: mev-boost.yml and commit-boost-pbs.yml leave a network after +# its Glamsterdam, and grandine-plugin.yml becomes grandine-plugin-cl-only.yml once, at ENV_VERSION 78. # # Run from the root of an eth-docker checkout. It replaces .env, so only run this in CI or in a # scratch checkout, never against a live node. @@ -321,6 +323,60 @@ check_equals "EPBS_BUILDERS stays true" "true" "$(get_value EPBS_BUILDERS)" check_equals "EPBS_BUILDER_URLS is unchanged" "${__urls}" "$(get_raw_value EPBS_BUILDER_URLS)" check_no_output "it does not set builder URLs" "Set ePBS builder URLs" "${output}" +# Remove after Hegotá +echo "== mev-boost.yml and commit-boost-pbs.yml are dropped on Sepolia ==" +fresh_env +set_in_env ENV_VERSION 76 +set_in_env NETWORK sepolia +set_in_env CORE_FILES lighthouse.yml:nethermind.yml:mev-boost.yml +set_in_env CUSTOM_FILES commit-boost-pbs.yml:grafana.yml +output=$(run_update) +check_equals "CORE_FILES loses mev-boost.yml" "lighthouse.yml:nethermind.yml" "$(get_value CORE_FILES)" +check_equals "CUSTOM_FILES loses commit-boost-pbs.yml" "grafana.yml" "$(get_value CUSTOM_FILES)" + +echo "== mev-boost.yml is kept on networks before their Glamsterdam ==" +fresh_env +set_in_env ENV_VERSION 76 +set_in_env NETWORK mainnet +set_in_env CORE_FILES lighthouse.yml:nethermind.yml:mev-boost.yml +output=$(run_update) +check_equals "CORE_FILES keeps mev-boost.yml" "lighthouse.yml:nethermind.yml:mev-boost.yml" "$(get_value CORE_FILES)" + +# Remove after Glamsterdam +echo "== grandine-plugin.yml before ENV_VERSION 78 becomes grandine-plugin-cl-only.yml ==" +fresh_env +set_in_env ENV_VERSION 77 # 26.10.1 shipped 77 with the old grandine-plugin.yml +set_in_env NETWORK mainnet +set_in_env CORE_FILES grandine-plugin.yml:reth.yml:mev-boost.yml +output=$(run_update) +check_equals "grandine-plugin.yml is renamed" "grandine-plugin-cl-only.yml:reth.yml:mev-boost.yml" "$(get_value CORE_FILES)" +set_in_env CORE_FILES grandine-plugin.yml:reth.yml:mev-boost.yml +output=$(run_update) +check_equals "a grandine-plugin.yml set after the migration stays" "grandine-plugin.yml:reth.yml:mev-boost.yml" "$(get_value CORE_FILES)" + +echo "== grandine-plugin.yml at ENV_VERSION 78 is left alone ==" +fresh_env +set_in_env ENV_VERSION 78 +set_in_env CORE_FILES grandine-plugin.yml:reth.yml +output=$(run_update) +check_equals "CORE_FILES keeps grandine-plugin.yml" "grandine-plugin.yml:reth.yml" "$(get_value CORE_FILES)" + +echo "== A pre-54 COMPOSE_FILE with grandine-plugin.yml is split and renamed ==" +fresh_env +set_in_env ENV_VERSION 42 +set_in_env COMPOSE_FILE grandine-plugin.yml:reth.yml +make_pre_54 +output=$(run_update) +check_equals "CORE_FILES has grandine-plugin-cl-only.yml" "grandine-plugin-cl-only.yml:reth.yml" "$(get_value CORE_FILES)" + +echo "== Sepolia drops mev-boost.yml and renames grandine-plugin.yml in one update ==" +fresh_env +set_in_env ENV_VERSION 76 +set_in_env NETWORK sepolia +set_in_env CORE_FILES grandine-plugin.yml:reth.yml:mev-boost.yml +output=$(run_update) +check_equals "both changes are applied" "grandine-plugin-cl-only.yml:reth.yml" "$(get_value CORE_FILES)" + # Leave a pristine .env behind, the way the checkout had it before this ran rm -f .env.new .env.source .env.partial .env.bak.* cp default.env .env diff --git a/.github/workflows/test-client-combos.yml b/.github/workflows/test-client-combos.yml index 1ed6fb4e..73e769e4 100644 --- a/.github/workflows/test-client-combos.yml +++ b/.github/workflows/test-client-combos.yml @@ -75,25 +75,41 @@ jobs: "FEE_RECIPIENT=0xDccf8451070a86183eE70D330C4c43b686E9CF86"], "test_cl": true, "test_el": false, "test_vc": true}, {"labels": ["test-grandine", "test-nimbus-el"], - "env": ["CORE_FILES=grandine-allin1.yml:nimbus-el.yml:mev-boost.yml", - "FEE_RECIPIENT=0xDccf8451070a86183eE70D330C4c43b686E9CF86"], - "test_cl": true, "test_el": true, "test_vc": false}, + "env": ["CORE_FILES=grandine.yml:nimbus-el.yml:mev-boost.yml", + "FEE_RECIPIENT=0xDccf8451070a86183eE70D330C4c43b686E9CF86", + "GRANDINE_DOCKER_TAG=unstable"], + "test_cl": true, "test_el": true, "test_vc": true}, {"labels": ["test-grandine"], - "env": ["CORE_FILES=grandine-cl-only.yml:lighthouse-vc-only.yml:nimbus-el.yml:mev-boost.yml", - "FEE_RECIPIENT=0xDccf8451070a86183eE70D330C4c43b686E9CF86"], + "env": ["CORE_FILES=grandine-cl-only.yml:grandine-vc-only.yml:nimbus-el.yml:mev-boost.yml", + "FEE_RECIPIENT=0xDccf8451070a86183eE70D330C4c43b686E9CF86", + "GRANDINE_DOCKER_TAG=unstable"], "test_cl": true, "test_el": true, "test_vc": true}, + {"labels": ["test-grandine"], + "env": ["CORE_FILES=grandine-allin1.yml:nimbus-el.yml:mev-boost.yml", + "FEE_RECIPIENT=0xDccf8451070a86183eE70D330C4c43b686E9CF86", + "GRANDINE_DOCKER_TAG=unstable"], + "test_cl": true, "test_el": true, "test_vc": false}, {"labels": ["test-grandine-plugin"], - "env": ["CORE_FILES=grandine-plugin.yml:lighthouse-vc-only.yml:nethermind.yml:mev-boost.yml", + "env": ["CORE_FILES=grandine-plugin.yml:nethermind.yml:mev-boost.yml", "FEE_RECIPIENT=0xDccf8451070a86183eE70D330C4c43b686E9CF86", "NM_DOCKER_REPO=sifrai/grandine", - "NM_DOCKER_TAG=unstable-nethermind"], + "NM_DOCKER_TAG=unstable-nethermind", + "GRANDINE_DOCKER_TAG=unstable"], "test_cl": false, "test_el": true, "test_vc": true}, {"labels": ["test-grandine-plugin"], "env": ["CORE_FILES=grandine-plugin-allin1.yml:nethermind.yml:mev-boost.yml", "FEE_RECIPIENT=0xDccf8451070a86183eE70D330C4c43b686E9CF86", "NM_DOCKER_REPO=sifrai/grandine", - "NM_DOCKER_TAG=unstable-nethermind"], + "NM_DOCKER_TAG=unstable-nethermind", + "GRANDINE_DOCKER_TAG=unstable"], "test_cl": false, "test_el": true, "test_vc": false}, + {"labels": ["test-grandine-plugin"], + "env": ["CORE_FILES=grandine-plugin-cl-only.yml:grandine-vc-only.yml:nethermind.yml:mev-boost.yml", + "FEE_RECIPIENT=0xDccf8451070a86183eE70D330C4c43b686E9CF86", + "NM_DOCKER_REPO=sifrai/grandine", + "NM_DOCKER_TAG=unstable-nethermind", + "GRANDINE_DOCKER_TAG=unstable"], + "test_cl": false, "test_el": true, "test_vc": true}, {"labels": ["test-ethrex"], "env": ["CORE_FILES=lighthouse.yml:ethrex.yml:mev-boost.yml", "FEE_RECIPIENT=0xDccf8451070a86183eE70D330C4c43b686E9CF86"], diff --git a/default.env b/default.env index 9c0fd2d7..d7ea0838 100644 --- a/default.env +++ b/default.env @@ -615,4 +615,4 @@ DOCKER_SOCK=/var/run/docker.sock ALLOY_PROJECT_NAME= # Used by ethd update - please do not adjust -ENV_VERSION=77 +ENV_VERSION=78 diff --git a/ethd b/ethd index 808f0404..96c9d5a3 100755 --- a/ethd +++ b/ethd @@ -43,7 +43,7 @@ __during_pg_migrate=0 __env_migrated=0 # Set by __migrate_env, read by update __pg_migrated=0 __keep_targets=1 # Set by update from --refresh-targets, read by __migrate_env -__source_ver=1 # .env version before migration. Set by __migrate_env, read by update and __migrate_alloy_obol +__source_ver=1 # .env version before migration. Set by __migrate_env, read by update, __migrate_alloy_obol and __migrate_compose_files __minty_fresh=0 # 1 when config created a new .env. Set by config, read by its __query_* steps __network_change=0 # Set by __query_network, read by the __query_* steps of config that follow it __handler_ran=0 @@ -2162,7 +2162,7 @@ port-check() { # Beacon API from there. Every other consensus client has its own service. case "${COMPOSE_FILE}" in *caplin.yml*) __cl_service="execution"; cl_client="Caplin";; - *grandine-plugin.yml*|*grandine-plugin-allin1.yml*) __cl_service="execution"; cl_client="Grandine";; + *grandine-plugin.yml*|*grandine-plugin-cl-only.yml*|*grandine-plugin-allin1.yml*) __cl_service="execution"; cl_client="Grandine";; *lighthouse.yml*|*lighthouse-cl-only.yml*) __cl_service="consensus"; cl_client="Lighthouse";; *teku.yml*|*teku-cl-only.yml*|*teku-allin1.yml*) __cl_service="consensus"; cl_client="Teku";; *nimbus-unified.yml*) __cl_service="consensus"; cl_client="Nimbus Unified";; @@ -2825,6 +2825,19 @@ __migrate_compose_files() { ;; esac +# Remove after Hegotá +# Adjust RIGHT after each network's Glamsterdam hardfork + if [[ "${_network}" =~ ^(sepolia)$ ]]; then + _from_yml+=("mev-boost.yml" "commit-boost-pbs.yml") + _to_yml+=("" "") + fi +# Remove after Glamsterdam +# grandine-plugin.yml is renamed, and the new grandine-plugin.yml replaces it. Convert only once + if [[ "${__source_ver}" -lt 78 ]]; then + _from_yml+=("grandine-plugin.yml") + _to_yml+=("grandine-plugin-cl-only.yml") + fi + IFS=":" set -o noglob # Globbing is off @@ -2850,39 +2863,6 @@ __migrate_compose_files() { fi fi done - -# Remove after Hegotá -# Adjust RIGHT after each network's Glamsterdam hardfork - _from_yml=("mev-boost.yml" "commit-boost-pbs.yml") - _to_yml=("" "") - if [[ "${_network}" =~ ^(sepolia)$ ]]; then - IFS=":" - set -o noglob -# Globbing is off -# shellcheck disable=SC2206 - _ymlarray=(${_migrated}) # split+glob with glob disabled, and split using : as delimiter - set +o noglob -# Unset restores default - unset IFS - - _migrated="" - for _n in "${!_ymlarray[@]}"; do - _ymlfile="${_ymlarray[_n]}" - for _index in "${!_from_yml[@]}"; do - if [[ "${_from_yml[_index]}" = "${_ymlfile}" ]]; then - _ymlfile=${_to_yml[_index]} - break - fi - done - if [[ -n "${_ymlfile}" ]]; then - if [[ -z "${_migrated}" ]]; then - _migrated="${_ymlfile}" - else - _migrated="${_migrated}:${_ymlfile}" - fi - fi - done - fi printf -v "${_out_files}" '%s' "${_migrated}" } @@ -4000,7 +3980,7 @@ __migrate_env() { done # Check whether we run a CL or VC, if so nag about FEE_RECIPIENT # It's CL&VC, CL-only, or VC-only - if [[ "${COMPOSE_FILE}" =~ (prysm\.yml|lighthouse\.yml|teku\.yml|nimbus\.yml|lodestar\.yml|-cl-only\.yml|-allin1\.yml|-vc-only\.yml) ]]; then + if __i_haz_cl_or_vc; then # Check for rewards var="FEE_RECIPIENT" __get_value_from_env "${var}" "${__env_file}" "value" @@ -4834,7 +4814,7 @@ resync-consensus() { *nimbus-unified.yml*) cl_volume='wipe-cl-db'; cl_client="Nimbus Unified";; *lodestar.yml*|*lodestar-cl-only.yml*) cl_volume='lsconsensus-data'; cl_client="Lodestar";; *prysm.yml*|*prysm-cl-only.yml*) cl_volume='prysmconsensus-data'; cl_client="Prysm";; - *grandine-plugin.yml*|*grandine-plugin-allin1.yml*) cl_volume='wipe-cl-db'; cl_client="Grandine Plugin";; + *grandine-plugin.yml*|*grandine-plugin-cl-only.yml*|*grandine-plugin-allin1.yml*) cl_volume='wipe-cl-db'; cl_client="Grandine Plugin";; *grandine-allin1.yml*) cl_volume='wipe-cl-db'; cl_client="Grandine";; *grandine.yml*|*grandine-cl-only.yml*) cl_volume='grandineconsensus-data'; cl_client="Grandine";; *ethlambda-cl-only.yml*) cl_volume='ethlambda-consensus-data'; cl_client="ethlambda";; @@ -5696,7 +5676,7 @@ prune-lighthouse() { __prep-keyimport() { local files - if [[ ! "${COMPOSE_FILE}" =~ (prysm\.yml|lighthouse\.yml|teku\.yml|nimbus\.yml|lodestar\.yml|-allin1\.yml|vc-only\.yml) ]]; then + if ! __i_haz_vc; then echo "You do not appear to be running a validator client. Aborting." exit 1 fi @@ -5771,6 +5751,21 @@ __prep-keyimport() { } +# True if COMPOSE_FILE includes a file that defines a validator client, and with it the +# "validator-keys" service that key management runs against. Keep in sync with: +# grep -l '^ validator-keys:' ./*.yml +__i_haz_vc() { + [[ "${COMPOSE_FILE}" =~ ((prysm|lighthouse|teku|nimbus|lodestar|grandine|grandine-plugin)\.yml|-vc-only\.yml|-allin1\.yml) ]] +} + + +# True if COMPOSE_FILE includes a consensus client, a validator client, or both. Used to +# decide whether a fee recipient needs to be set. +__i_haz_cl_or_vc() { + __i_haz_vc || [[ "${COMPOSE_FILE}" =~ (-cl-only\.yml|caplin\.yml|nimbus-unified\.yml) ]] +} + + __i_haz_deposit_cli() { local var local network @@ -6668,11 +6663,13 @@ __query_validator_client() { elif [[ "${arch}" =~ riscv64 ]]; then choices+=("lighthouse-vc-only.yml" "Lighthouse validator client") choices+=("nimbus-vc-only.yml" "Nimbus validator client") + choices+=("grandine-vc-only.yml" "Grandine validator client") else choices+=("teku-vc-only.yml" "Teku validator client") choices+=("lodestar-vc-only.yml" "Lodestar validator client") choices+=("lighthouse-vc-only.yml" "Lighthouse validator client") choices+=("nimbus-vc-only.yml" "Nimbus validator client") + choices+=("grandine-vc-only.yml" "Grandine validator client") fi elif [[ "${arch}" =~ (aarch64|arm64) ]]; then choices+=("lighthouse-vc-only.yml" "Lighthouse validator client") @@ -6683,12 +6680,14 @@ __query_validator_client() { elif [[ "${arch}" =~ riscv64 ]]; then choices+=("nimbus-vc-only.yml" "Nimbus validator client") choices+=("lighthouse-vc-only.yml" "Lighthouse validator client") + choices+=("grandine-vc-only.yml" "Grandine validator client") else choices+=("teku-vc-only.yml" "Teku validator client") choices+=("lighthouse-vc-only.yml" "Lighthouse validator client") choices+=("lodestar-vc-only.yml" "Lodestar validator client") choices+=("nimbus-vc-only.yml" "Nimbus validator client") choices+=("prysm-vc-only.yml" "Prysm validator client") + choices+=("grandine-vc-only.yml" "Grandine validator client") fi num_items=$(( ${#choices[@]} / 2 )) @@ -6798,7 +6797,7 @@ __query_consensus_client() { if [[ "${NETWORK}" != "mainnet" ]]; then choices+=("nimbus-unified.yml" "Nimbus Unified (Nim) - consensus and execution client - beta") fi - choices+=("grandine-allin1.yml" "Grandine (Rust) - consensus with built-in validator client") + choices+=("grandine.yml" "Grandine (Rust) - consensus with built-in validator client") choices+=("teku.yml" "Teku (Java) - consensus and validator client") choices+=("prysm.yml" "Prysm (Go) - consensus and validator client") choices+=("lighthouse.yml" "Lighthouse (Rust) - consensus and validator client") @@ -6821,7 +6820,7 @@ __query_consensus_client() { if [[ "${NETWORK}" != "mainnet" ]]; then choices+=("nimbus-unified.yml" "Nimbus Unified (Nim) - consensus and execution client - beta") fi - choices+=("grandine-allin1.yml" "Grandine (Rust) - consensus with built-in validator client") + choices+=("grandine.yml" "Grandine (Rust) - consensus with built-in validator client") choices+=("teku.yml" "Teku (Java) - consensus and validator client") choices+=("prysm.yml" "Prysm (Go) - consensus and validator client") choices+=("lighthouse.yml" "Lighthouse (Rust) - consensus and validator client") @@ -8424,12 +8423,12 @@ __handle_error() { __check_legacy() { local client - if [[ "${COMPOSE_FILE}" =~ -allin1\.yml && ! "${COMPOSE_FILE}" =~ (grandine-allin1\.yml|grandine-plugin-allin1\.yml) ]]; then # Warn re Grandine once VC + if [[ "${COMPOSE_FILE}" =~ -allin1\.yml ]]; then if [[ "${COMPOSE_FILE}" =~ teku-allin1\.yml ]]; then client="Teku" elif [[ "${COMPOSE_FILE}" =~ nimbus-allin1\.yml ]]; then client="Nimbus" - elif [[ "${COMPOSE_FILE}" =~ (grandine-allin1\.yml|grandine-plugin-allin1\.yml) ]]; then + elif [[ "${COMPOSE_FILE}" =~ grandine-(plugin-)?allin1\.yml ]]; then client="Grandine" else client="Mystery" @@ -9044,14 +9043,21 @@ __check_double_vc() { local vcs=" " local count=0 local files + local env_ver + __get_value_from_env "ENV_VERSION" "${__env_file}" "env_ver" IFS=':' read -r -a files <<< "${COMPOSE_FILE}" for file in "${files[@]}"; do # grafana-vc-only.yml is the pre-77 name of grafana-for-vc.yml, not a VC; a .env keeps it until "update" if [[ "${file}" = "grafana-vc-only.yml" || "${vcs}" = *" ${file} "* ]]; then continue fi - if [[ "${file}" =~ ^(prysm|lighthouse|teku|nimbus|lodestar)\.yml$|-allin1\.yml$|-vc-only\.yml$ ]]; then + # Remove after Glamsterdam + # Before 78, grandine-plugin.yml had no VC; "update" renames it to grandine-plugin-cl-only.yml + if [[ "${file}" = "grandine-plugin.yml" && ( ! "${env_ver}" =~ ^[0-9]+$ || "${env_ver}" -lt 78 ) ]]; then + continue + fi + if [[ "${file}" =~ ^(prysm|lighthouse|teku|nimbus|lodestar|grandine|grandine-plugin)\.yml$|-allin1\.yml$|-vc-only\.yml$ ]]; then vcs+="${file} " count=$(( count + 1 )) fi diff --git a/grandine-cl-only.yml b/grandine-cl-only.yml index d74a3aae..bc78324f 100644 --- a/grandine-cl-only.yml +++ b/grandine-cl-only.yml @@ -66,6 +66,7 @@ services: entrypoint: - docker-entrypoint.sh - grandine + - bn - --disable-upnp - --data-dir - /var/lib/grandine @@ -88,6 +89,7 @@ services: - ${EL_NODE} - --jwt-secret - /var/lib/grandine/ee-secret/jwtsecret + - --track-liveness - --metrics - --metrics-address - 0.0.0.0 diff --git a/grandine-plugin-allin1.yml b/grandine-plugin-allin1.yml index 5c945734..65c66704 100644 --- a/grandine-plugin-allin1.yml +++ b/grandine-plugin-allin1.yml @@ -1,4 +1,4 @@ -# grandine-plugin-allin1.yml to be used with nethermind.yml for Nethermind with Grandine plugin and Grandine VC +# grandine-plugin-allin1.yml to be used with nethermind.yml for Nethermind with Grandine plugin and Grandine built-in VC services: execution: ports: @@ -35,6 +35,14 @@ services: - RUST_LOG=${LOG_LEVEL:-info} volumes: - grandineconsensus-data:/var/lib/grandine + networks: + default: + aliases: + # The CL runs inside execution, so answer to the names of a consensus service + - consensus + - eth2 + # This allows multiple Eth Docker stacks all connected to the same bridge network + - ${CL_ALIAS:-default-consensus} # No metrics scrape at present via labels. # Create an extra scrape target for execution:8008 in prometheus/conf.d diff --git a/grandine-plugin-cl-only.yml b/grandine-plugin-cl-only.yml new file mode 100644 index 00000000..12541364 --- /dev/null +++ b/grandine-plugin-cl-only.yml @@ -0,0 +1,60 @@ +# grandine-plugin-cl-only.yml to be used with nethermind.yml for Nethermind with Grandine plugin +services: + execution: + ports: + # Grandine consensus P2P ports + - "${HOST_IP:-}:${CL_P2P_PORT:-9000}:${CL_P2P_PORT:-9000}/tcp" + - "${HOST_IP:-}:${CL_P2P_PORT:-9000}:${CL_P2P_PORT:-9000}/udp" + environment: + - CL_NODE_TYPE=${CL_NODE_TYPE:-pruned} + - CL_EXTRAS=${CL_EXTRAS:-} + - WEB3SIGNER=${WEB3SIGNER:-false} + - W3S_NODE=${W3S_NODE} + - CHECKPOINT_SYNC_URL=${CHECKPOINT_SYNC_URL} + - JWT_SECRET=${JWT_SECRET} + - MEV_BOOST=${MEV_BOOST} + - EPBS_BUILD_FACTOR=${EPBS_BUILD_FACTOR} + - MEV_NODE=${MEV_NODE} + - BEACON_STATS_API=${BEACON_STATS_API} + - BEACON_STATS_MACHINE=${BEACON_STATS_MACHINE} + - VC_EXTRAS=${VC_EXTRAS:-} + - GRAFFITI=${GRAFFITI:-} + - DEFAULT_GRAFFITI=${DEFAULT_GRAFFITI:-false} + - DOPPELGANGER=${DOPPELGANGER:-false} + - IPV6=${IPV6:-false} + - CL_P2P_PORT=${CL_P2P_PORT:-9000} + - CL_QUIC_PORT=${CL_QUIC_PORT:-9001} + - CL_REST_PORT=${CL_REST_PORT:-5052} + - CL_MAX_PEER_COUNT=${CL_MAX_PEER_COUNT:-} + - FEE_RECIPIENT=${FEE_RECIPIENT:-} + - KEY_API_PORT=${KEY_API_PORT:-} + - EMBEDDED_VC=false + - RUST_LOG=${LOG_LEVEL:-info} + volumes: + - grandineconsensus-data:/var/lib/grandine + networks: + default: + aliases: + # The CL runs inside execution, so answer to the names of a consensus service + - consensus + - eth2 + # This allows multiple Eth Docker stacks all connected to the same bridge network + - ${CL_ALIAS:-default-consensus} +# No metrics scrape at present via labels. +# Create an extra scrape target for execution:8008 in prometheus/conf.d + + wipe-cl-db: + profiles: ["tools"] + restart: "no" + image: alpine:3 + user: "10001" + volumes: + - grandineconsensus-data:/var/lib/grandine + - /etc/localtime:/etc/localtime:ro + entrypoint: ["/bin/sh", "-c"] + command: + - | + rm -rf /var/lib/grandine/${NETWORK}/beacon/* + +volumes: + grandineconsensus-data: diff --git a/grandine-plugin.yml b/grandine-plugin.yml index 781daf97..d54f7401 100644 --- a/grandine-plugin.yml +++ b/grandine-plugin.yml @@ -1,4 +1,23 @@ -# grandine-plugin.yml to be used with nethermind.yml for Nethermind with Grandine plugin +# grandine-plugin.yml to be used with nethermind.yml for Nethermind with Grandine plugin plus Grandine VC +x-logging: &logging + logging: + driver: ${LOG_DRIVER} + options: + max-size: ${LOG_MAX_SIZE} + max-file: ${LOG_MAX_FILE} + tag: ${LOG_TAG} + + +x-build: &gr-build + context: ./grandine + dockerfile: ${GRANDINE_DOCKERFILE:-Dockerfile.binary} + args: + - BUILD_TARGET=${GRANDINE_SRC_BUILD_TARGET:-master} + - SRC_REPO=${GRANDINE_SRC_REPO:-https://github.com/sifraitech/grandine} + - DOCKER_TAG=${GRANDINE_DOCKER_TAG:-latest} + - DOCKER_REPO=${GRANDINE_DOCKER_REPO:-sifrai/grandine} + + services: execution: ports: @@ -32,6 +51,14 @@ services: - RUST_LOG=${LOG_LEVEL:-info} volumes: - grandineconsensus-data:/var/lib/grandine + networks: + default: + aliases: + # The CL runs inside execution, so answer to the names of a consensus service + - consensus + - eth2 + # This allows multiple Eth Docker stacks all connected to the same bridge network + - ${CL_ALIAS:-default-consensus} # No metrics scrape at present via labels. # Create an extra scrape target for execution:8008 in prometheus/conf.d @@ -48,5 +75,101 @@ services: - | rm -rf /var/lib/grandine/${NETWORK}/beacon/* + validator: + restart: "unless-stopped" + build: + <<: *gr-build + image: grandine:local + pull_policy: never + user: gdvalidator + stop_grace_period: 1m + volumes: + - grandinevalidator-data:/var/lib/grandine-vc + - /etc/localtime:/etc/localtime:ro + environment: + - MEV_BOOST=${MEV_BOOST} + - EPBS_BUILDERS=${EPBS_BUILDERS} + - EPBS_BUILDER_URLS=${EPBS_BUILDER_URLS} + - EPBS_BUILD_FACTOR=${EPBS_BUILD_FACTOR} + - EPBS_MIN_BID=${EPBS_MIN_BID} + - VC_EXTRAS=${VC_EXTRAS:-} + - GRAFFITI=${GRAFFITI:-} + - DEFAULT_GRAFFITI=${DEFAULT_GRAFFITI:-false} + - WEB3SIGNER=${WEB3SIGNER:-false} + - W3S_NODE=${W3S_NODE} + - DOPPELGANGER=${DOPPELGANGER:-false} + - ENABLE_DIST_ATTESTATION_AGGR=${ENABLE_DIST_ATTESTATION_AGGR:-false} + - NETWORK=${NETWORK} + - COMPOSE_FILE=${COMPOSE_FILE} + - LOG_LEVEL=${LOG_LEVEL} + - RUST_LOG=${LOG_LEVEL:-info} + networks: + default: + aliases: + # This allows multiple Eth Docker stacks all connected to the same bridge network + - ${VC_ALIAS:-vc} + <<: *logging + entrypoint: + - docker-entrypoint-vc.sh + - grandine + - vc + - --data-dir + - /var/lib/grandine-vc + - --beacon-node-urls + - ${CL_NODE} + - --metrics + - --metrics-address + - 0.0.0.0 + - --metrics-port + - "8009" + - --suggested-fee-recipient + - ${FEE_RECIPIENT} + - --keystore-storage-password-file + - /var/lib/grandine-vc/wallet-password.txt + - --enable-validator-api + - --validator-api-address + - 0.0.0.0 + - --validator-api-port + - ${KEY_API_PORT:-7500} + - --validator-api-allowed-origins=* + labels: + - metrics.scrape=true + - metrics.path=/metrics + - metrics.port=8009 + - metrics.instance=validator + - metrics.network=${NETWORK} + - logs.collect=true + + validator-keys: + profiles: ["tools"] + restart: "no" + build: + context: ./vc-utils + image: vc-utils:local + pull_policy: never + # The API token has 640 permissions. Root copies it, + # then switches to the local user's UID or if not provided, + # 1000. The UID has to be able to write .eth/validator_keys + # for the "keys delete" command. + user: root + volumes: + - grandinevalidator-data:/var/lib/grandine-vc + - ./.eth/validator_keys:/validator_keys + - ./.eth/exit_messages:/exit_messages + - /etc/localtime:/etc/localtime:ro + environment: + - KEYSTORE_PASSWORD=${KEYSTORE_PASSWORD:-} + - KEY_API_PORT=${KEY_API_PORT:-7500} + - WEB3SIGNER=${WEB3SIGNER:-false} + - W3S_NODE=${W3S_NODE} + - CL_NODE=${CL_NODE} + depends_on: + - validator + entrypoint: + - keymanager.sh + - /var/lib/grandine-vc/${NETWORK}/validator/api-token.txt + - validator + volumes: grandineconsensus-data: + grandinevalidator-data: diff --git a/grandine-vc-only.yml b/grandine-vc-only.yml new file mode 100644 index 00000000..d1fbd2d5 --- /dev/null +++ b/grandine-vc-only.yml @@ -0,0 +1,120 @@ +x-logging: &logging + logging: + driver: ${LOG_DRIVER} + options: + max-size: ${LOG_MAX_SIZE} + max-file: ${LOG_MAX_FILE} + tag: ${LOG_TAG} + + +x-build: &gr-build + context: ./grandine + dockerfile: ${GRANDINE_DOCKERFILE:-Dockerfile.binary} + args: + - BUILD_TARGET=${GRANDINE_SRC_BUILD_TARGET:-master} + - SRC_REPO=${GRANDINE_SRC_REPO:-https://github.com/sifraitech/grandine} + - DOCKER_TAG=${GRANDINE_DOCKER_TAG:-latest} + - DOCKER_REPO=${GRANDINE_DOCKER_REPO:-sifrai/grandine} + +services: + validator: + restart: "unless-stopped" + build: + <<: *gr-build + image: grandine:local + pull_policy: never + user: gdvalidator + stop_grace_period: 1m + volumes: + - grandinevalidator-data:/var/lib/grandine-vc + - /etc/localtime:/etc/localtime:ro + environment: + - MEV_BOOST=${MEV_BOOST} + - EPBS_BUILDERS=${EPBS_BUILDERS} + - EPBS_BUILDER_URLS=${EPBS_BUILDER_URLS} + - EPBS_BUILD_FACTOR=${EPBS_BUILD_FACTOR} + - EPBS_MIN_BID=${EPBS_MIN_BID} + - VC_EXTRAS=${VC_EXTRAS:-} + - GRAFFITI=${GRAFFITI:-} + - DEFAULT_GRAFFITI=${DEFAULT_GRAFFITI:-false} + - WEB3SIGNER=${WEB3SIGNER:-false} + - W3S_NODE=${W3S_NODE} + - DOPPELGANGER=${DOPPELGANGER:-false} + - ENABLE_DIST_ATTESTATION_AGGR=${ENABLE_DIST_ATTESTATION_AGGR:-false} + - NETWORK=${NETWORK} + - COMPOSE_FILE=${COMPOSE_FILE} + - LOG_LEVEL=${LOG_LEVEL} + - RUST_LOG=${LOG_LEVEL:-info} + networks: + default: + aliases: + # This allows multiple Eth Docker stacks all connected to the same bridge network + - ${VC_ALIAS:-vc} + <<: *logging + entrypoint: + - docker-entrypoint-vc.sh + - grandine + - vc + - --data-dir + - /var/lib/grandine-vc + - --beacon-node-urls + - ${CL_NODE} + - --metrics + - --metrics-address + - 0.0.0.0 + - --metrics-port + - "8009" + - --suggested-fee-recipient + - ${FEE_RECIPIENT} + - --keystore-storage-password-file + - /var/lib/grandine-vc/wallet-password.txt + - --enable-validator-api + - --validator-api-address + - 0.0.0.0 + - --validator-api-port + - ${KEY_API_PORT:-7500} + - --validator-api-allowed-origins=* + labels: + - metrics.scrape=true + - metrics.path=/metrics + - metrics.port=8009 + - metrics.instance=validator + - metrics.network=${NETWORK} + - logs.collect=true + + validator-keys: + profiles: ["tools"] + restart: "no" + build: + context: ./vc-utils + image: vc-utils:local + pull_policy: never + # The API token has 640 permissions. Root copies it, + # then switches to the local user's UID or if not provided, + # 1000. The UID has to be able to write .eth/validator_keys + # for the "keys delete" command. + user: root + volumes: + - grandinevalidator-data:/var/lib/grandine-vc + - ./.eth/validator_keys:/validator_keys + - ./.eth/exit_messages:/exit_messages + - /etc/localtime:/etc/localtime:ro + environment: + - KEYSTORE_PASSWORD=${KEYSTORE_PASSWORD:-} + - KEY_API_PORT=${KEY_API_PORT:-7500} + - WEB3SIGNER=${WEB3SIGNER:-false} + - W3S_NODE=${W3S_NODE} + - CL_NODE=${CL_NODE} + depends_on: + - validator + entrypoint: + - keymanager.sh + - /var/lib/grandine-vc/${NETWORK}/validator/api-token.txt + - validator + +volumes: + grandinevalidator-data: + +networks: + default: + enable_ipv6: ${IPV6:-false} diff --git a/grandine.yml b/grandine.yml new file mode 100644 index 00000000..04c0678d --- /dev/null +++ b/grandine.yml @@ -0,0 +1,209 @@ +x-logging: &logging + logging: + driver: ${LOG_DRIVER} + options: + max-size: ${LOG_MAX_SIZE} + max-file: ${LOG_MAX_FILE} + tag: ${LOG_TAG} + + +x-build: &gr-build + context: ./grandine + dockerfile: ${GRANDINE_DOCKERFILE:-Dockerfile.binary} + args: + - BUILD_TARGET=${GRANDINE_SRC_BUILD_TARGET:-master} + - SRC_REPO=${GRANDINE_SRC_REPO:-https://github.com/sifraitech/grandine} + - DOCKER_TAG=${GRANDINE_DOCKER_TAG:-latest} + - DOCKER_REPO=${GRANDINE_DOCKER_REPO:-sifrai/grandine} + +services: + consensus: + restart: "unless-stopped" + build: + <<: *gr-build + image: grandine:local + pull_policy: never + user: gdconsensus + stop_grace_period: 1m + volumes: + - grandineconsensus-data:/var/lib/grandine + - /etc/localtime:/etc/localtime:ro + - jwtsecret:/var/lib/grandine/ee-secret + environment: + - CHECKPOINT_SYNC_URL=${CHECKPOINT_SYNC_URL} + - JWT_SECRET=${JWT_SECRET} + - MEV_BOOST=${MEV_BOOST} + - MEV_NODE=${MEV_NODE} + - BEACON_STATS_API=${BEACON_STATS_API} + - BEACON_STATS_MACHINE=${BEACON_STATS_MACHINE} + - CL_EXTRAS=${CL_EXTRAS:-} + - EMBEDDED_VC=false + - VC_EXTRAS= + - WEB3SIGNER=false + - DOPPELGANGER=false + - GRAFFITI= + - DEFAULT_GRAFFITI=true + - NODE_TYPE=${CL_NODE_TYPE:-pruned} + - IPV6=${IPV6:-false} + - CL_P2P_PORT=${CL_P2P_PORT:-9000} + - CL_QUIC_PORT=${CL_QUIC_PORT:-9001} + - NETWORK=${NETWORK} + - COMPOSE_FILE=${COMPOSE_FILE} + - LOG_LEVEL=${LOG_LEVEL} + - RUST_LOG=${LOG_LEVEL:-info} + ports: + - ${HOST_IP:-}:${CL_P2P_PORT:-9000}:${CL_P2P_PORT:-9000}/tcp + - ${HOST_IP:-}:${CL_P2P_PORT:-9000}:${CL_P2P_PORT:-9000}/udp + - ${HOST_IP:-}:${CL_QUIC_PORT:-9001}:${CL_QUIC_PORT:-9001}/udp + networks: + default: + aliases: + - eth2 + # This allows multiple Eth Docker stacks all connected to the same bridge network + - ${CL_ALIAS:-default-consensus} + <<: *logging + entrypoint: + - docker-entrypoint.sh + - grandine + - bn + - --disable-upnp + - --data-dir + - /var/lib/grandine + - --http-address + - 0.0.0.0 + - --http-port + - ${CL_REST_PORT:-5052} + - --http-allowed-origins=* + - --listen-address + - 0.0.0.0 + - --libp2p-port + - ${CL_P2P_PORT:-9000} + - --discovery-port + - ${CL_P2P_PORT:-9000} + - --quic-port + - ${CL_QUIC_PORT:-9001} + - ${CL_MAX_PEER_COUNT:+--target-peers} + - ${CL_MAX_PEER_COUNT:+${CL_MAX_PEER_COUNT}} + - --eth1-rpc-urls + - ${EL_NODE} + - --jwt-secret + - /var/lib/grandine/ee-secret/jwtsecret + - --track-liveness + - --metrics + - --metrics-address + - 0.0.0.0 + - --metrics-port + - "8008" + - --suggested-fee-recipient + - ${FEE_RECIPIENT} + labels: + - metrics.scrape=true + - metrics.path=/metrics + - metrics.port=8008 + - metrics.instance=consensus + - metrics.network=${NETWORK} + - logs.collect=true + + validator: + restart: "unless-stopped" + build: + <<: *gr-build + image: grandine:local + pull_policy: never + user: gdvalidator + stop_grace_period: 1m + volumes: + - grandinevalidator-data:/var/lib/grandine-vc + - /etc/localtime:/etc/localtime:ro + environment: + - MEV_BOOST=${MEV_BOOST} + - EPBS_BUILDERS=${EPBS_BUILDERS} + - EPBS_BUILDER_URLS=${EPBS_BUILDER_URLS} + - EPBS_BUILD_FACTOR=${EPBS_BUILD_FACTOR} + - EPBS_MIN_BID=${EPBS_MIN_BID} + - VC_EXTRAS=${VC_EXTRAS:-} + - GRAFFITI=${GRAFFITI:-} + - DEFAULT_GRAFFITI=${DEFAULT_GRAFFITI:-false} + - WEB3SIGNER=${WEB3SIGNER:-false} + - W3S_NODE=${W3S_NODE} + - DOPPELGANGER=${DOPPELGANGER:-false} + - ENABLE_DIST_ATTESTATION_AGGR=${ENABLE_DIST_ATTESTATION_AGGR:-false} + - NETWORK=${NETWORK} + - COMPOSE_FILE=${COMPOSE_FILE} + - LOG_LEVEL=${LOG_LEVEL} + - RUST_LOG=${LOG_LEVEL:-info} + networks: + default: + aliases: + # This allows multiple Eth Docker stacks all connected to the same bridge network + - ${VC_ALIAS:-vc} + <<: *logging + entrypoint: + - docker-entrypoint-vc.sh + - grandine + - vc + - --data-dir + - /var/lib/grandine-vc + - --beacon-node-urls + - ${CL_NODE} + - --metrics + - --metrics-address + - 0.0.0.0 + - --metrics-port + - "8009" + - --suggested-fee-recipient + - ${FEE_RECIPIENT} + - --keystore-storage-password-file + - /var/lib/grandine-vc/wallet-password.txt + - --enable-validator-api + - --validator-api-address + - 0.0.0.0 + - --validator-api-port + - ${KEY_API_PORT:-7500} + - --validator-api-allowed-origins=* + labels: + - metrics.scrape=true + - metrics.path=/metrics + - metrics.port=8009 + - metrics.instance=validator + - metrics.network=${NETWORK} + - logs.collect=true + + validator-keys: + profiles: ["tools"] + restart: "no" + build: + context: ./vc-utils + image: vc-utils:local + pull_policy: never + # The API token has 640 permissions. Root copies it, + # then switches to the local user's UID or if not provided, + # 1000. The UID has to be able to write .eth/validator_keys + # for the "keys delete" command. + user: root + volumes: + - grandinevalidator-data:/var/lib/grandine-vc + - ./.eth/validator_keys:/validator_keys + - ./.eth/exit_messages:/exit_messages + - /etc/localtime:/etc/localtime:ro + environment: + - KEYSTORE_PASSWORD=${KEYSTORE_PASSWORD:-} + - KEY_API_PORT=${KEY_API_PORT:-7500} + - WEB3SIGNER=${WEB3SIGNER:-false} + - W3S_NODE=${W3S_NODE} + - CL_NODE=${CL_NODE} + depends_on: + - validator + entrypoint: + - keymanager.sh + - /var/lib/grandine-vc/${NETWORK}/validator/api-token.txt + - validator + +volumes: + grandineconsensus-data: + grandinevalidator-data: + jwtsecret: + +networks: + default: + enable_ipv6: ${IPV6:-false} diff --git a/grandine/Dockerfile.binary b/grandine/Dockerfile.binary index 0978d25c..f992b4e3 100644 --- a/grandine/Dockerfile.binary +++ b/grandine/Dockerfile.binary @@ -42,6 +42,27 @@ COPY --chown=${USER}:${USER} ./docker-entrypoint.sh /usr/local/bin/ # Belt and suspenders RUN chmod -R 755 /usr/local/bin/* +ARG USER=gdvalidator +ARG UID=10000 + +# See https://stackoverflow.com/a/55757473/12429735RUN +RUN adduser \ + --disabled-password \ + --gecos "" \ + --home "/nonexistent" \ + --shell "/sbin/nologin" \ + --no-create-home \ + --uid "${UID}" \ + "${USER}" + +# Create data mount point with permissions +RUN mkdir -p /var/lib/grandine-vc && chown ${USER}:${USER} /var/lib/grandine-vc && chmod 700 /var/lib/grandine-vc + +# Cannot assume buildkit, hence no chmod +COPY --chown=${USER}:${USER} ./docker-entrypoint-vc.sh /usr/local/bin/ +# Belt and suspenders +RUN chmod -R 755 /usr/local/bin/* + USER gdconsensus ENTRYPOINT ["grandine"] diff --git a/grandine/Dockerfile.source b/grandine/Dockerfile.source index 4be59413..0e970f2f 100644 --- a/grandine/Dockerfile.source +++ b/grandine/Dockerfile.source @@ -77,6 +77,27 @@ COPY --chown=${USER}:${USER} ./docker-entrypoint.sh /usr/local/bin/ # Belt and suspenders RUN chmod -R 755 /usr/local/bin/* +ARG USER=gdvalidator +ARG UID=10000 + +# See https://stackoverflow.com/a/55757473/12429735RUN +RUN adduser \ + --disabled-password \ + --gecos "" \ + --home "/nonexistent" \ + --shell "/sbin/nologin" \ + --no-create-home \ + --uid "${UID}" \ + "${USER}" + +# Create data mount point with permissions +RUN mkdir -p /var/lib/grandine-vc && chown ${USER}:${USER} /var/lib/grandine-vc && chmod 700 /var/lib/grandine-vc + +# Cannot assume buildkit, hence no chmod +COPY --chown=${USER}:${USER} ./docker-entrypoint-vc.sh /usr/local/bin/ +# Belt and suspenders +RUN chmod -R 755 /usr/local/bin/* + USER gdconsensus ENTRYPOINT ["grandine"] diff --git a/grandine/docker-entrypoint-vc.sh b/grandine/docker-entrypoint-vc.sh new file mode 100755 index 00000000..666cc436 --- /dev/null +++ b/grandine/docker-entrypoint-vc.sh @@ -0,0 +1,200 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +if [[ "$(id -u)" -eq 0 ]]; then + chown -R gdvalidator:gdvalidator /var/lib/grandine-vc + exec gosu gdvalidator docker-entrypoint-vc.sh "$@" +fi + + +__normalize_int() { + local v=$1 + # Strip leading zeros as text. Arithmetic would overflow on the largest boost factors + if [[ "${v}" =~ ^0*([0-9]+)$ ]]; then + v="${BASH_REMATCH[1]}" + fi + printf '%s' "${v}" +} + +__normalize_float() { + local v=$1 + local int_part + local frac_part + + if [[ "${v}" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then + int_part="${v%%.*}" + frac_part="" + if [[ "${v}" == *.* ]]; then + frac_part="${v#*.}" + fi + # Strip leading zeros as text, as in __normalize_int + [[ "${int_part}" =~ ^0*([0-9]+)$ ]] && int_part="${BASH_REMATCH[1]}" + if [[ -n "${frac_part}" ]]; then + v="${int_part}.${frac_part}" + else + v="${int_part}" + fi + fi + printf '%s' "${v}" +} + + +if [[ ! -f /var/lib/grandine-vc/wallet-password.txt ]]; then + echo "Creating password for Grandine key wallet" + head -c 32 /dev/urandom | sha256sum | cut -d' ' -f1 > /var/lib/grandine-vc/wallet-password.txt +fi + +if [[ "${NETWORK}" =~ ^https?:// ]]; then + echo "Custom testnet at ${NETWORK}" + repo=$(awk -F'/tree/' '{print $1}' <<< "${NETWORK}") + branch=$(awk -F'/tree/' '{print $2}' <<< "${NETWORK}" | cut -d'/' -f1) + config_dir=$(awk -F'/tree/' '{print $2}' <<< "${NETWORK}" | cut -d'/' -f2-) + echo "This appears to be the ${repo} repo, branch ${branch} and config directory ${config_dir}." + if [[ ! -d "/var/lib/grandine-vc/testnet/${config_dir}" ]]; then + mkdir -p /var/lib/grandine-vc/testnet + cd /var/lib/grandine-vc/testnet + git init --initial-branch="${branch}" + git remote add origin "${repo}" + git config core.sparseCheckout true + echo "${config_dir}" > .git/info/sparse-checkout + git pull origin "${branch}" + fi + config_dir_path="/var/lib/grandine-vc/testnet/${config_dir}" + __network="--configuration-directory=${config_dir_path} --network=custom" +else + __network="--network=${NETWORK}" +fi + +# Adjust RIGHT after each network's Glamsterdam hardfork +# MEV Boost implies ePBS builders only on networks that haven't forked yet +if [[ "${MEV_BOOST}" = "true" && "${NETWORK}" =~ ^(hoodi|mainnet)$ ]]; then + __mev_active=1 +else + __mev_active=0 + if [[ "${MEV_BOOST}" = "true" ]]; then + echo "MEV_BOOST is true, but MEV Boost is not used on ${NETWORK}. Ignoring it." + fi +fi + +# Check whether we should use ePBS +__epbs="" +if [[ "${__mev_active}" -eq 1 || "${EPBS_BUILDERS}" = "true" ]]; then + if [[ "${__mev_active}" -eq 1 ]]; then + echo "MEV Boost enabled" + __epbs="--use-builder" + if [[ "${EPBS_BUILDERS}" = "false" ]]; then + echo "ePBS builders are meant to be disabled, but MEV Boost is true, which will enable them anyway." + echo "Update Eth Docker again after ${NETWORK}'s Glamsterdam hard fork to fix this." + else + echo "Update Eth Docker again after mainnet Glamsterdam hard fork, expected December 2026, to remove MEV Boost." + fi + fi + if [[ "${EPBS_BUILDERS}" = "true" ]]; then + echo "ePBS builders enabled" + fi + + build_factor="$(__normalize_int "${EPBS_BUILD_FACTOR}")" + if [[ "${build_factor}" = "maxprofit" ]]; then + build_factor=100 # 100 means profit maximization, as in the keymanager API + fi + case "${build_factor}" in + 0|local) + if [[ "${__mev_active}" -eq 1 ]]; then + __epbs="" + echo "Disabled MEV Boost because EPBS_BUILD_FACTOR is ${build_factor}." + echo "WARNING: This conflicts with MEV_BOOST true. Set a factor above 0, or maxprofit or always" + fi + __epbs+=" --default-builder-boost-factor 0" + echo "Build blocks locally, use ePBS builders as fallback. EPBS_BUILD_FACTOR is ${build_factor}." + ;; + always) + __epbs+=" --default-builder-boost-factor 18446744073709551615" + echo "Always prefer ePBS builder blocks, EPBS_BUILD_FACTOR always" + ;; + "") + echo "Use default --default-builder-boost-factor" + ;; + *) + if [[ "${build_factor}" =~ ^[1-9][0-9]{0,19}$ ]]; then + # Compare as text, bash arithmetic cannot hold uint64. Equal length makes this a numeric comparison + # shellcheck disable=SC2071 + if [[ ${#build_factor} -eq 20 && "${build_factor}" > "18446744073709551615" ]]; then + echo "EPBS_BUILD_FACTOR ${build_factor} exceeds the 64-bit maximum, capping it to 18446744073709551615" + build_factor=18446744073709551615 + fi + __epbs+=" --default-builder-boost-factor ${build_factor}" + echo "Enabled ePBS Build Factor of ${build_factor}" + else + echo "WARNING: EPBS_BUILD_FACTOR has an invalid value of \"${build_factor}\"" + fi + ;; + esac + if [[ -n "${EPBS_MIN_BID}" ]]; then + min_bid="$(__normalize_float "${EPBS_MIN_BID}")" + if [[ "${min_bid}" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then + min_bid_gwei=$(awk -v v="${min_bid}" 'BEGIN{printf "%.0f", v * 1000000000}') + __epbs+=" --default-builder-min-bid ${min_bid_gwei}" + else + echo "WARNING: EPBS_MIN_BID has an invalid value of \"${EPBS_MIN_BID}\", ignoring" + fi + fi + # Compose keeps the newlines of a multi-line EPBS_BUILDER_URLS, which would create an empty entry from "url," + builder_urls="${EPBS_BUILDER_URLS//[[:space:]]/}" + if [[ -n "${builder_urls}" ]]; then + __epbs+=" --payload-builder-urls ${builder_urls}" + fi +else + __epbs="--default-builder-boost-factor 0" + echo "Build blocks locally, use ePBS builders as fallback" +fi + +# Check whether we should enable doppelganger protection +if [[ "${DOPPELGANGER}" = "true" ]]; then + __doppel="--detect-doppelgangers" + echo "Doppelganger protection enabled" +else + __doppel="" +fi + +# Distributed attestation aggregation +if [[ "${ENABLE_DIST_ATTESTATION_AGGR}" = "true" ]]; then + echo "WARNING: Grandine VC does not support distributed attestation aggregation for Obol yet. Ignoring ENABLE_DIST_ATTESTATION_AGGR." +fi + +# Web3signer URL +if [[ "${WEB3SIGNER}" = "true" ]]; then + __w3s_url="--web3signer-urls ${W3S_NODE}" + while true; do + if curl -s -m 5 "${W3S_NODE}" &> /dev/null; then + echo "web3signer is up, starting Grandine" + break + else + echo "Waiting for web3signer to be reachable..." + sleep 5 + fi + done +else + __w3s_url="" +fi + +if [[ "${DEFAULT_GRAFFITI}" != "true" ]]; then + __graffiti_args=(--graffiti "${GRAFFITI}") +else + __graffiti_args=() +fi + +# Traces +if [[ "${COMPOSE_FILE}" =~ (grafana\.yml|grafana-rootless\.yml) ]]; then + __trace="--telemetry-metrics-url http://tempo:4317 --telemetry-service-name grandine-vc --telemetry-level ${LOG_LEVEL:-info}" +# These may become default in future. Here so Grandine doesn't murder itself in the meantime + export OTEL_TRACES_SAMPLER=parentbased_traceidratio + export OTEL_TRACES_SAMPLER_ARG=0.01 + export OTEL_EXPORTER_OTLP_INSECURE=true + export OTEL_EXPORTER_OTLP_PROTOCOL=grpc +else + __trace="" +fi + +# Word splitting is desired for the command line parameters +# shellcheck disable=SC2086 +exec "$@" ${__network} ${__w3s_url} "${__graffiti_args[@]}" ${__trace} ${__epbs} ${__doppel} ${VC_EXTRAS} diff --git a/grandine/docker-entrypoint.sh b/grandine/docker-entrypoint.sh index 2e95c387..773d99ee 100755 --- a/grandine/docker-entrypoint.sh +++ b/grandine/docker-entrypoint.sh @@ -33,6 +33,28 @@ __normalize_int() { printf '%s' "${v}" } +__normalize_float() { + local v=$1 + local int_part + local frac_part + + if [[ "${v}" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then + int_part="${v%%.*}" + frac_part="" + if [[ "${v}" == *.* ]]; then + frac_part="${v#*.}" + fi + # Strip leading zeros as text, as in __normalize_int + [[ "${int_part}" =~ ^0*([0-9]+)$ ]] && int_part="${BASH_REMATCH[1]}" + if [[ -n "${frac_part}" ]]; then + v="${int_part}.${frac_part}" + else + v="${int_part}" + fi + fi + printf '%s' "${v}" +} + if [[ -n "${JWT_SECRET}" ]]; then echo -n "${JWT_SECRET}" > /var/lib/grandine/ee-secret/jwtsecret @@ -47,7 +69,7 @@ if [[ -O /var/lib/grandine/ee-secret/jwtsecret ]]; then chmod 666 /var/lib/grandine/ee-secret/jwtsecret fi -if [[ ! -f /var/lib/grandine/wallet-password.txt ]]; then +if [[ "${EMBEDDED_VC}" = "true" && ! -f /var/lib/grandine/wallet-password.txt ]]; then echo "Creating password for Grandine key wallet" head -c 32 /dev/urandom | sha256sum | cut -d' ' -f1 > /var/lib/grandine/wallet-password.txt fi @@ -152,13 +174,11 @@ if [[ "${EMBEDDED_VC}" = "true" ]]; then 0|local) if [[ "${__mev_active}" -eq 1 ]]; then __mev_boost="" - __mev_factor="" echo "Disabled MEV Boost because EPBS_BUILD_FACTOR is ${build_factor}." echo "WARNING: This conflicts with MEV_BOOST true. Set a factor above 0, or maxprofit or always" - else - __mev_factor="--default-builder-boost-factor 0" - echo "Build blocks locally, use ePBS builders as fallback. EPBS_BUILD_FACTOR is ${build_factor}." fi + __mev_factor="--default-builder-boost-factor 0" + echo "Build blocks locally, use ePBS builders as fallback. EPBS_BUILD_FACTOR is ${build_factor}." ;; always) __mev_factor="--default-builder-boost-factor 18446744073709551615" @@ -184,12 +204,19 @@ if [[ "${EMBEDDED_VC}" = "true" ]]; then fi ;; esac - # Adjust once Grandine supports ePBS CLI parameters, to pass them here like Prysm does - # Compose keeps the newlines of a multi-line EPBS_BUILDER_URLS + if [[ -n "${EPBS_MIN_BID}" ]]; then + min_bid="$(__normalize_float "${EPBS_MIN_BID}")" + if [[ "${min_bid}" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then + min_bid_gwei=$(awk -v v="${min_bid}" 'BEGIN{printf "%.0f", v * 1000000000}') + __mev_factor+=" --default-builder-min-bid ${min_bid_gwei}" + else + echo "WARNING: EPBS_MIN_BID has an invalid value of \"${EPBS_MIN_BID}\", ignoring" + fi + fi + # Compose keeps the newlines of a multi-line EPBS_BUILDER_URLS, which would create an empty entry from "url," builder_urls="${EPBS_BUILDER_URLS//[[:space:]]/}" - if [[ -n "${builder_urls}" || -n "${EPBS_MIN_BID}" ]]; then - echo "Grandine does not support ePBS CLI parameters for builder URLs or minimum bid yet. Ignoring EPBS_BUILDER_URLS and EPBS_MIN_BID." - echo "Use \"./ethd keys set-builder\" to configure ePBS builders." + if [[ -n "${builder_urls}" ]]; then + __mev_factor+=" --payload-builder-urls ${builder_urls}" fi else __mev_factor="--default-builder-boost-factor 0" @@ -276,6 +303,9 @@ while true; do fi done +# Belt and suspenders. Already blanked in the compose yaml, but be sure +[[ "${EMBEDDED_VC}" = "true" ]] || VC_EXTRAS="" + # Word splitting is desired for the command line parameters -# shellcheck disable=SC2086 +# shellcheck disable=SC2086,SC2153 exec "$@" ${__network} ${__w3s_url} "${__graffiti_args[@]}" ${__mev_boost} ${__mev_factor} ${__checkpoint_sync} ${__prune} ${__beacon_stats} ${__ipv6} ${__doppel} ${__trace} ${CL_EXTRAS} ${VC_EXTRAS} diff --git a/nethermind/docker-entrypoint.sh b/nethermind/docker-entrypoint.sh index 971060f7..bae2e412 100755 --- a/nethermind/docker-entrypoint.sh +++ b/nethermind/docker-entrypoint.sh @@ -34,6 +34,28 @@ __normalize_int() { printf '%s' "${v}" } +__normalize_float() { + local v=$1 + local int_part + local frac_part + + if [[ "${v}" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then + int_part="${v%%.*}" + frac_part="" + if [[ "${v}" == *.* ]]; then + frac_part="${v#*.}" + fi + # Strip leading zeros as text, as in __normalize_int + [[ "${int_part}" =~ ^0*([0-9]+)$ ]] && int_part="${BASH_REMATCH[1]}" + if [[ -n "${frac_part}" ]]; then + v="${int_part}.${frac_part}" + else + v="${int_part}" + fi + fi + printf '%s' "${v}" +} + if [[ -n "${JWT_SECRET}" ]]; then echo -n "${JWT_SECRET}" > /var/lib/nethermind/ee-secret/jwtsecret @@ -261,7 +283,7 @@ if [[ -n "${__ere}" ]]; then echo "${__ere}" fi -if [[ "${COMPOSE_FILE}" =~ grandine-plugin(-allin1)?\.yml ]]; then +if [[ "${COMPOSE_FILE}" =~ grandine-plugin(-allin1|-cl-only)?\.yml ]]; then if [[ ! -f /var/lib/grandine/wallet-password.txt ]]; then echo "Creating password for Grandine key wallet" head -c 32 /dev/urandom | sha256sum | cut -d' ' -f1 > /var/lib/grandine/wallet-password.txt @@ -372,13 +394,11 @@ if [[ "${COMPOSE_FILE}" =~ grandine-plugin(-allin1)?\.yml ]]; then 0|local) if [[ "${__mev_active}" -eq 1 ]]; then __mev_boost="" - __mev_factor="" echo "Disabled MEV Boost because EPBS_BUILD_FACTOR is ${build_factor}." echo "WARNING: This conflicts with MEV_BOOST true. Set a factor above 0, or maxprofit or always" - else - __mev_factor=" --grandine-default-builder-boost-factor 0" - echo "Build blocks locally, use ePBS builders as fallback. EPBS_BUILD_FACTOR is ${build_factor}." fi + __mev_factor=" --grandine-default-builder-boost-factor 0" + echo "Build blocks locally, use ePBS builders as fallback. EPBS_BUILD_FACTOR is ${build_factor}." ;; always) __mev_factor=" --grandine-default-builder-boost-factor 18446744073709551615" @@ -404,12 +424,19 @@ if [[ "${COMPOSE_FILE}" =~ grandine-plugin(-allin1)?\.yml ]]; then fi ;; esac - # Adjust once Grandine supports ePBS CLI parameters, to pass them here like Prysm does - # Compose keeps the newlines of a multi-line EPBS_BUILDER_URLS + if [[ -n "${EPBS_MIN_BID}" ]]; then + min_bid="$(__normalize_float "${EPBS_MIN_BID}")" + if [[ "${min_bid}" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then + min_bid_gwei=$(awk -v v="${min_bid}" 'BEGIN{printf "%.0f", v * 1000000000}') + __mev_factor+=" --grandine-default-builder-min-bid ${min_bid_gwei}" + else + echo "WARNING: EPBS_MIN_BID has an invalid value of \"${EPBS_MIN_BID}\", ignoring" + fi + fi + # Compose keeps the newlines of a multi-line EPBS_BUILDER_URLS, which would create an empty entry from "url," builder_urls="${EPBS_BUILDER_URLS//[[:space:]]/}" - if [[ -n "${builder_urls}" || -n "${EPBS_MIN_BID}" ]]; then - echo "Grandine does not support ePBS CLI parameters for builder URLs or minimum bid yet. Ignoring EPBS_BUILDER_URLS and EPBS_MIN_BID." - echo "Use \"./ethd keys set-builder\" to configure ePBS builders." + if [[ -n "${builder_urls}" ]]; then + __mev_factor+=" --grandine-payload-builder-urls ${builder_urls}" fi else __mev_factor=" --grandine-default-builder-boost-factor 0" diff --git a/vc-utils/keymanager.sh b/vc-utils/keymanager.sh index 30f21415..29aa49fe 100755 --- a/vc-utils/keymanager.sh +++ b/vc-utils/keymanager.sh @@ -232,6 +232,9 @@ get-grandine-wallet() { if [[ -f /var/lib/grandine/wallet-password.txt ]]; then echo "The password for the Grandine wallet is:" cat /var/lib/grandine/wallet-password.txt + elif [[ -f /var/lib/grandine-vc/wallet-password.txt ]]; then + echo "The password for the Grandine wallet is:" + cat /var/lib/grandine-vc/wallet-password.txt else echo "No stored password found for a Grandine wallet." fi