Skip to content

Syncoor

Syncoor #3416

Workflow file for this run

name: Syncoor
on:
workflow_dispatch:
inputs:
el-client:
description: 'Comma-separated list of execution layer clients (geth,besu,nethermind,erigon,reth)'
required: false
default: '"geth","reth","nethermind","besu","erigon"'
type: string
cl-client:
description: 'Comma-separated list of consensus layer clients (lighthouse,teku,prysm,nimbus,lodestar,grandine)'
required: false
default: '"lighthouse","teku","prysm","nimbus","lodestar","grandine"'
type: string
run-timeout-minutes:
description: 'Timeout for the sync test (in minutes)'
required: false
default: '1800'
type: string
image:
description: 'Syncoor Docker image'
required: false
default: 'docker.ethquokkaops.io/dh/ethpandaops/syncoor:master'
type: string
git-ref:
description: 'Git reference (branch, tag, or commit) to checkout'
required: false
default: ''
type: string
config:
description: 'Configuration JSON'
required: false
# network: Network to test (e.g., hoodi, mainnet)
# consensus.syncType: checkpoint or genesis
# consensus.nodeType: fullnode or supernode
# consensus.checkpointSyncURL: Checkpoint sync URL (auto-generated based on network if not provided)
default: >-
{
"network": "hoodi",
"execution": {
"images": {
"besu": "hyperledger/besu:25.9.0",
"geth": "ethereum/client-go:v1.16.4",
"erigon": "erigontech/erigon:v3.2.0-rc1",
"nethermind": "nethermind/nethermind:1.34.0",
"reth": "ghcr.io/paradigmxyz/reth:v1.8.2",
"nimbusel": "ethpandaops/nimbus-eth1:master"
}
},
"consensus": {
"images": {
"lighthouse": "sigp/lighthouse:v8.0.0-rc.0",
"teku": "consensys/teku:25.9.3",
"prysm": "gcr.io/offchainlabs/prysm/beacon-chain:v6.1.1",
"nimbus": "statusim/nimbus-eth2:multiarch-v25.9.2",
"lodestar": "chainsafe/lodestar:v1.35.0-rc.1",
"grandine": "sifrai/grandine:2.0.0.rc0"
},
"syncType": "checkpoint",
"nodeType": "fullnode",
"checkpointSyncURL": ""
}
}
type: string
runs-on:
description: 'Runner configuration (JSON). Examples: "ubuntu-latest", {"group": "synctest", "labels": "Disk2TB"}'
required: false
default: '{"group": "synctest", "labels": "Disk2TB"}'
type: string
env:
INSTALL_RCLONE_VERSION: v1.68.2
S3_BUCKET: ethpandaops-syncoor-data
jobs:
sync:
# Timeouts from h to minutes:
# - 4h -> 240m
# - 6h -> 360m
# - 8h -> 480m
# - 12h -> 720m
# - 24h -> 1440m
# - 48h -> 2880m
# - 72h -> 4320m
timeout-minutes: ${{ fromJSON(inputs.run-timeout-minutes || '1800') }}
runs-on: >-
${{ fromJSON(inputs.runs-on || '{"group": "synctest", "labels": "Disk2TB"}') }}
concurrency:
group: >-
${{ github.event_name }}-${{ matrix.network }}-${{ matrix.el-client }}-${{ matrix.cl-client }}-${{ matrix.cl-nodetype }}-${{ matrix.cl-synctype }}
strategy:
fail-fast: false
matrix:
network: >-
${{
fromJSON(format('["{0}"]', fromJSON(inputs.config || '{}').network || 'hoodi'))
}}
el-client: >-
${{
fromJSON(format('[{0}]', inputs.el-client || '
"besu",
"erigon",
"geth",
"nethermind",
"reth"
'))}}
cl-client: >-
${{
fromJSON(format('[{0}]', inputs.cl-client || '
"lighthouse",
"teku",
"prysm",
"nimbus",
"lodestar",
"grandine"
'))}}
cl-nodetype: >-
${{
fromJSON(format('["{0}"]', fromJSON(inputs.config || '{}').consensus.nodeType || 'fullnode'))
}}
cl-synctype: >-
${{
fromJSON(format('["{0}"]', fromJSON(inputs.config || '{}').consensus.syncType || 'checkpoint'))
}}
steps:
- name: Prepare inputs
id: prepare
env:
CONFIG: ${{ inputs.config || '{}' }}
run: |
set -x
# Parse config JSON
if [ "$CONFIG" != '{}' ] && [ -n "$CONFIG" ]; then
# Extract EL images from config
EL_IMAGES=$(echo "$CONFIG" | jq -r '.execution.images // {}')
if [ "$EL_IMAGES" != '{}' ] && [ "$EL_IMAGES" != 'null' ]; then
EL_IMAGE=$(echo "$EL_IMAGES" | jq -r --arg client "${{ matrix.el-client }}" '.[$client] // ""')
echo "el-image=$EL_IMAGE" >> $GITHUB_OUTPUT
else
echo "el-image=" >> $GITHUB_OUTPUT
fi
# Extract CL images from config
CL_IMAGES=$(echo "$CONFIG" | jq -r '.consensus.images // {}')
if [ "$CL_IMAGES" != '{}' ] && [ "$CL_IMAGES" != 'null' ]; then
CL_IMAGE=$(echo "$CL_IMAGES" | jq -r --arg client "${{ matrix.cl-client }}" '.[$client] // ""')
echo "cl-image=$CL_IMAGE" >> $GITHUB_OUTPUT
else
echo "cl-image=" >> $GITHUB_OUTPUT
fi
else
echo "el-image=" >> $GITHUB_OUTPUT
echo "cl-image=" >> $GITHUB_OUTPUT
fi
NETWORK="${{ matrix.network }}"
# Generate checkpoint sync URL based on network or use from config
if [ "$CONFIG" != '{}' ] && [ -n "$CONFIG" ]; then
CHECKPOINT_SYNC_URL=$(echo "$CONFIG" | jq -r '.consensus.checkpointSyncURL // ""')
if [ -z "$CHECKPOINT_SYNC_URL" ] || [ "$CHECKPOINT_SYNC_URL" = "null" ]; then
CHECKPOINT_SYNC_URL="https://checkpoint-sync.${NETWORK}.ethpandaops.io"
fi
else
CHECKPOINT_SYNC_URL="https://checkpoint-sync.${NETWORK}.ethpandaops.io"
fi
echo "checkpoint-sync-url=$CHECKPOINT_SYNC_URL" >> $GITHUB_OUTPUT
# Set checkpoint sync enabled based on cl-synctype
if [ "${{ matrix.cl-synctype }}" = "checkpoint" ]; then
CHECKPOINT_SYNC_ENABLED="true"
else
CHECKPOINT_SYNC_ENABLED="false"
fi
echo "checkpoint-sync-enabled=$CHECKPOINT_SYNC_ENABLED" >> $GITHUB_OUTPUT
# Generate syncoor server address based on network
SERVER_URL="https://syncoor-api.${NETWORK}.ethpandaops.io"
echo "syncoor-server-url=$SERVER_URL" >> $GITHUB_OUTPUT
# Generate S3 path based on network, cl-nodetype, and checkpoint sync
S3_PATH="networks/${NETWORK}"
if [ "${{ matrix.cl-nodetype }}" = "supernode" ]; then
S3_PATH="${S3_PATH}-clsupernode"
fi
if [ "$CHECKPOINT_SYNC_ENABLED" = "false" ]; then
S3_PATH="${S3_PATH}-clcheckpointsyncdisabled"
fi
echo "s3-path=$S3_PATH" >> $GITHUB_OUTPUT
# Calculate timeout (subtract 10 minutes)
TIMEOUT=$(( ${{ fromJSON(inputs.run-timeout-minutes || '1800') }} - 10 ))
echo "run-timeout-minutes=$TIMEOUT" >> $GITHUB_OUTPUT
- name: Run Syncoor Test
uses: ethpandaops/syncoor@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
server: ${{ steps.prepare.outputs.syncoor-server-url }}
server-auth: ${{ secrets.SYNCOOR_SERVER_AUTH }}
log-force-colors: true
client-logs: true
public: true
network: ${{ matrix.network }}
el-client: ${{ matrix.el-client }}
cl-client: ${{ matrix.cl-client }}
el-image: ${{ steps.prepare.outputs.el-image }}
cl-image: ${{ steps.prepare.outputs.cl-image }}
el-extra-args: >-
${{
(matrix.el-client == 'reth') && '["--full"]' ||
(matrix.el-client == 'erigon') && '["--prune.mode=full"]' ||
'[]'
}}
check-interval: 30s
run-timeout: ${{ steps.prepare.outputs.run-timeout-minutes }}m
log-level: info
s3-upload: true
s3-bucket: ${{ env.S3_BUCKET }}
s3-path: ${{ steps.prepare.outputs.s3-path }}
rclone-config: ${{ secrets.SYNCOOR_RCLONE_CONFIG }}
rclone-version: ${{ env.INSTALL_RCLONE_VERSION }}
checkpoint-sync-enabled: ${{ steps.prepare.outputs.checkpoint-sync-enabled }}
checkpoint-sync-url: ${{ steps.prepare.outputs.checkpoint-sync-url }}
supernode: ${{ matrix.cl-nodetype == 'supernode' }}
image: ${{ inputs.image || 'docker.ethquokkaops.io/dh/ethpandaops/syncoor:master' }}
git-ref: ${{ inputs.git-ref }}
- name: Update test results index
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
await github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'syncoor-generate-index.yaml',
ref: context.ref,
inputs: {
's3-path': '${{ steps.prepare.outputs.s3-path }}'
}
});