Skip to content

e2e-nitro-celestia

e2e-nitro-celestia #7

name: e2e-nitro-celestia
on:
workflow_dispatch:
inputs:
runner_label:
description: Self-hosted runner label
required: true
default: gh-solutions
testnode_repo:
description: Repo for OffchainLabs testnode
required: true
default: OffchainLabs/nitro-testnode
testnode_ref:
description: Ref/branch/tag for testnode (release recommended)
required: true
default: release
celestia_network:
description: Celestia p2p network for the light node
required: true
default: mocha-4
das_image:
description: Celestia DA server image tag
required: true
default: ghcr.io/celestiaorg/nitro-das-celestia:v0.5.3-mocha
enforce_da_roundtrip:
description: Fail if DA store/fetch can't be verified (true/false)
required: true
default: "true"
concurrency:
group: e2e-nitro-celestia-${{ github.ref }}
cancel-in-progress: true
jobs:
e2e:
runs-on: ${{ inputs.runner_label }}
timeout-minutes: 45
env:
WORKDIR: ${{ github.workspace }}/.e2e
COMPOSE_PROJECT_NAME: ndc-${{ github.run_id }}
TESTNODE_REPO: ${{ inputs.testnode_repo }}
TESTNODE_REF: ${{ inputs.testnode_ref }}
CELESTIA_NET: ${{ inputs.celestia_network }}
DAS_IMAGE: ${{ inputs.das_image }}
ENFORCE_DA_ROUNDTRIP: ${{ inputs.enforce_da_roundtrip }}
steps:
- name: Checkout nitro-das-celestia (this repo)
uses: actions/checkout@v4
- name: Prep folders
run: |
mkdir -p "$WORKDIR"/{logs,artifacts}
- name: Docker info (pre)
run: |
docker info || true
docker system df || true
# ---------- 1) Bring up Nitro devnet via OffchainLabs testnode ----------
- name: Start Nitro + Celestia + DA concurrently
shell: bash
run: |
set -euo pipefail
# Kick off Nitro testnode (init + start) in background
(
cd "$WORKDIR/nitro-testnode"
./test-node.bash --init
./test-node.bash --start
docker compose ps
) &
# Start Celestia light node (RPC open, no auth) in background
(
docker rm -f celestia-light 2>/dev/null || true
docker run -d --name celestia-light --restart unless-stopped \
-p 26658:26658 \
ghcr.io/celestiaorg/celestia-node:latest \
celestia light start --rpc.skip-auth --p2p.network "${CELESTIA_NET}"
) &
# Start DA server wired to the light node in background
(
docker rm -f celestia-da 2>/dev/null || true
# Ensure Linux can reach the host as host.docker.internal
HGI=$(docker info --format '{{.HostGateway}}' 2>/dev/null || echo "")
ADD_HOST_ARG=""
if [ -n "$HGI" ]; then
ADD_HOST_ARG="--add-host=host.docker.internal:${HGI}"
fi
docker run -d --name celestia-da --restart unless-stopped \
-p 9875:9875 \
$ADD_HOST_ARG \
"${DAS_IMAGE}" \
--enable-rpc --rpc-addr 0.0.0.0 --rpc-port 9875 \
--celestia.rpc http://host.docker.internal:26658
) &
# Wait for all background startups before running health checks
wait
# ---------- Health gates ----------
- name: Wait for Celestia RPC (26658)
run: |
set -e
tries=90
until curl -sf http://localhost:26658/health >/dev/null 2>&1 || \
curl -sf http://localhost:26658 | head -c 1 >/dev/null 2>&1; do
((tries--)) || { echo "Celestia RPC not up"; exit 1; }
sleep 5
done
- name: Wait for DA server (9875)
run: |
set -e
tries=90
until curl -sf http://localhost:9875/health >/dev/null 2>&1 || \
curl -sf http://localhost:9875 | head -c 1 >/dev/null 2>&1; do
((tries--)) || { echo "DA server not up"; exit 1; }
sleep 5
done
- name: Wait for L2 RPC (8547)
run: |
set -e
tries=120
until curl -sf -H 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
http://localhost:8547 > /dev/null; do
((tries--)) || { echo "L2 RPC not up"; exit 1; }
sleep 5
done
- name: Assert L2 progresses
run: |
set -e
getbn() { curl -s -H 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
http://localhost:8547 | jq -r .result; }
b1=$(getbn); sleep 8; b2=$(getbn)
echo "BlockNumber t0=$b1 t1=$b2"
[ "$b1" != "null" ] && [ "$b2" != "null" ] || { echo "null block number"; exit 1; }
[ $((b2)) -gt $((b1)) ] || { echo "L2 not progressing"; exit 1; }
# ---------- Heavier E2E: DA roundtrip + L2 tx ----------
- name: DA store & (best effort) fetch
run: |
set -euo pipefail
PAYLOAD=$(printf 'hello-celestia-%s' "${GITHUB_RUN_ID}" | xxd -p -c 256)
OK=0
# Try known endpoints in order; capture first success
try_store () {
URL="$1"; BODY="$2"; DESC="$3"
echo "Trying store via ${DESC} (${URL})"
set +e
RESP=$(curl -sS -X POST "$URL" -H "Content-Type: application/json" --data "$BODY")
RC=$?
set -e
if [ $RC -eq 0 ] && [ -n "$RESP" ]; then
echo "$RESP" > "$WORKDIR/artifacts/store.json"
CMT=$(jq -r '(.commitment // .data.commitment // .result // empty)' "$WORKDIR/artifacts/store.json")
if [ -n "$CMT" ] && [ "$CMT" != "null" ]; then
echo "Got commitment: $CMT"
echo "$CMT" > "$WORKDIR/artifacts/commitment.txt"
return 0
fi
fi
return 1
}
# Candidate API shapes (update to match your server if needed)
try_store "http://localhost:9875/store" \
"{\"data\":\"0x${PAYLOAD}\"}" \
"/store (data hex)"
if [ $? -ne 0 ]; then
try_store "http://localhost:9875/v1/store" \
"{\"data\":\"0x${PAYLOAD}\"}" \
"/v1/store (data hex)"
fi
if [ -f "$WORKDIR/artifacts/commitment.txt" ]; then
OK=1
fi
if [ "$OK" -ne 1 ] && [ "${ENFORCE_DA_ROUNDTRIP}" = "true" ]; then
echo "Failed to store payload via DA server with known endpoints."
exit 1
fi
# Best-effort fetch
if [ -f "$WORKDIR/artifacts/commitment.txt" ]; then
CMT=$(cat "$WORKDIR/artifacts/commitment.txt")
set +e
curl -sS "http://localhost:9875/get?commitment=${CMT}" \
| tee "$WORKDIR/artifacts/get.json" >/dev/null
curl -sS "http://localhost:9875/v1/get?commitment=${CMT}" \
| tee -a "$WORKDIR/artifacts/get.json" >/dev/null
set -e
fi
- name: L2 tx smoke (send to self)
working-directory: ${{ env.WORKDIR }}
run: |
set -euo pipefail
cat > send.json <<'EOF'
{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[{"from":"0xe2148eE53c0755215Df69b2616E552154EdC584f","to":"0xe2148eE53c0755215Df69b2616E552154EdC584f","value":"0x38d7ea4c68000"}],"id":7}
EOF
curl -sS -H 'Content-Type: application/json' --data @send.json http://localhost:8547 | tee artifacts/send-tx.json
sleep 4
curl -sS -H 'Content-Type: application/json' --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":9}' http://localhost:8547 | tee artifacts/bn-after.json
# ---------- Logs & cleanup ----------
- name: Snapshot docker state & logs
if: always()
run: |
set -euo pipefail
# testnode compose context (if present)
(cd "$WORKDIR/nitro-testnode" && docker compose ps | tee "$WORKDIR/logs/compose-ps.txt") || true
for c in $(docker ps -a --format '{{.Names}}'); do
docker logs --tail=2000 "$c" > "$WORKDIR/logs/${c}.log" 2>&1 || true
done
- name: Upload artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: e2e-artifacts-${{ github.run_id }}
path: |
${{ env.WORKDIR }}/logs
${{ env.WORKDIR }}/artifacts
- name: Stop stacks
if: always()
run: |
set -e
(cd "$WORKDIR/nitro-testnode" && docker compose down -v) || true
docker rm -f celestia-da celestia-light 2>/dev/null || true
- name: Docker prune (post)
if: always()
run: |
docker system prune -af || true
docker volume prune -f || true