Created
March 10, 2025 10:20
-
-
Save TPXP/6efd77026263b9bf523fb3f2534f05c5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const axios = require('axios'); | |
// Epoch to data to send to the finalize endpoint | |
const knownCheckpoints = { | |
"117136": { | |
"epoch": "117136", | |
"state_root": "0x6d92a3a81ce8b90ad71c11d7bcd80c551866c0dcf334f843e0234df216d90770", | |
"block_root": "0xe48389dee2da56c7101f0dacc4c5020ec91eb425ff40d16e3b924271eef53edd", | |
}, | |
"117729": { | |
"epoch": "117729", | |
"state_root": "0x1505b771a01bd5991b0ad3d412f26aaefe1bc03ee40369467d5ced9d85c6e243", | |
"block_root": "0x5b1a381ca774dd62ba3a7061d356c2e66d6c19a405d082236ef87c6b522f8111", | |
} | |
}; | |
const hosts = process.env.LIGHTHOUSE_HOSTS.split(";").map(host => { | |
if (!host.startsWith('http')) { | |
host = `http://${host}`; | |
} | |
return host; | |
}) | |
const SLOTS_PER_EPOCH = 32; | |
async function singleHostLogic(host) { | |
try { | |
// Ask the beacon about its known head | |
const lastCheckpoint = await axios.get(`${host}/eth/v1/beacon/headers/head`) | |
.then(r => r.data.data) | |
.then(r => r.header.message.slot) | |
.then(r => Math.floor(r / SLOTS_PER_EPOCH) - 3); | |
console.log(`${host} is at epoch ${lastCheckpoint}`) | |
let lastCheckpointData, tryCheckpoint = lastCheckpoint; | |
while (!lastCheckpointData) { | |
lastCheckpointData = knownCheckpoints[tryCheckpoint]; | |
if (lastCheckpointData) { | |
break; | |
} | |
lastCheckpointData = await axios.get(`${host}/eth/v1/beacon/headers/${tryCheckpoint * SLOTS_PER_EPOCH}`) | |
.then(r => r.data.data) | |
.then(r => r.header.message.slot && r) // Ensure the data is present | |
.then(r => knownCheckpoints[tryCheckpoint] = { | |
epoch: tryCheckpoint, | |
block_root: r.root, | |
state_root: r.header.message.state_root, | |
}) | |
.catch(e => { | |
console.warn(`Could not fetch data for epoch ${tryCheckpoint} from ${host}, trying older: ${e}`); | |
}); | |
// Some epochs don't have a valid checkpoint. In that case try the epoch before | |
tryCheckpoint--; | |
if (lastCheckpoint > tryCheckpoint + 10) { | |
throw new Error("Could not find a working checkpoint in the last 10 epochs. Can't force finalize for now"); | |
} | |
} | |
console.log(`Asking Lighthouse to finalize with data ${JSON.stringify(lastCheckpointData)}`); | |
// Now, send a finalize request | |
await axios.post(`${host}/lighthouse/finalize`, lastCheckpointData) | |
.then(r => r.data) | |
.then(r => console.log(`${host} finalization request result: ${JSON.stringify(r)}`)) | |
await axios.post(`${host}/lighthouse/compaction`) | |
.then(r => r.data) | |
.then(r => console.log(`${host} compaction request result: ${JSON.stringify(r)}`)); | |
} catch (e) { | |
console.error(`Host error for ${host}`, e); | |
} | |
} | |
async function lightHouseLoop() { | |
console.log("Loop start"); | |
await Promise.all(hosts.map(singleHostLogic)); | |
console.log("Loop end"); | |
} | |
const loopInterval = setInterval(lightHouseLoop, 600e3); | |
lightHouseLoop(); | |
process.on('SIGINT', () => { | |
clearInterval(loopInterval); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
apiVersion: apps/v1 | |
kind: StatefulSet | |
metadata: | |
name: tmp-lighthouse-sync-helper | |
spec: | |
selector: | |
matchLabels: | |
app: tmp-lighthouse-sync-helper | |
serviceName: tmp-lighthouse-sync-helper | |
replicas: 1 | |
template: | |
metadata: | |
labels: | |
app: tmp-lighthouse-sync-helper | |
author: kiln-fi | |
details: lighthouse_holesky_sync_helper | |
spec: | |
nodeSelector: | |
k8s.scaleway.com/role: metal | |
containers: | |
- name: tmp-lighthouse-sync-helper | |
image: node:23 | |
command: | |
- /bin/bash | |
- -c | |
- npm i --no-package-lock && node index.js | |
workingDir: /app | |
env: | |
- name: LIGHTHOUSE_HOSTS | |
value: holesky-lighthouse-beacon-rbx-1-0.holesky-lighthouse-beacon-rbx-1:5052;holesky-lighthouse-beacon-rbx-4-0.holesky-lighthouse-beacon-rbx-4:5052;holesky-lighthouse-beacon-rbx-5-0.holesky-lighthouse-beacon-rbx-5:5052;holesky-lighthouse-beacon-sbg-1-0.holesky-lighthouse-beacon-sbg-1:5052 | |
resources: | |
limits: | |
cpu: 500m | |
memory: 128Mi | |
requests: | |
cpu: 250m | |
memory: 128Mi | |
volumeMounts: | |
- name: app | |
mountPath: /app | |
- name: node-modules | |
mountPath: /app/node_modules | |
volumes: | |
- name: app | |
configMap: | |
name: tmp-lighthouse-sync-helper | |
volumeClaimTemplates: | |
- metadata: | |
name: node-modules | |
spec: | |
# storageClassName: k8s-storage | |
accessModes: [ "ReadWriteOnce" ] | |
resources: | |
requests: | |
storage: 1Gi | |
--- | |
apiVersion: v1 | |
kind: ConfigMap | |
metadata: | |
name: tmp-lighthouse-sync-helper | |
data: | |
package.json: | | |
{ | |
"name": "tmp-lighthouse-sync-helper", | |
"version": "1.0.0", | |
"description": "A script forcing Lighthouse to prune its storage, useful to handle holesky pectra forks.", | |
"main": "index.js", | |
"scripts": { | |
"test": "echo \"Error: no test specified\" && exit 1" | |
}, | |
"author": "Kiln.fi", | |
"license": "ISC", | |
"dependencies": { | |
"axios": "^1.8.2" | |
} | |
} | |
index.js: | | |
const axios = require('axios'); | |
// Epoch to data to send to the finalize endpoint | |
const knownCheckpoints = { | |
"117136": { | |
"epoch": "117136", | |
"state_root": "0x6d92a3a81ce8b90ad71c11d7bcd80c551866c0dcf334f843e0234df216d90770", | |
"block_root": "0xe48389dee2da56c7101f0dacc4c5020ec91eb425ff40d16e3b924271eef53edd", | |
}, | |
"117729": { | |
"epoch": "117729", | |
"state_root": "0x1505b771a01bd5991b0ad3d412f26aaefe1bc03ee40369467d5ced9d85c6e243", | |
"block_root": "0x5b1a381ca774dd62ba3a7061d356c2e66d6c19a405d082236ef87c6b522f8111", | |
} | |
}; | |
const hosts = process.env.LIGHTHOUSE_HOSTS.split(";").map(host => { | |
if (!host.startsWith('http')) { | |
host = `http://${host}`; | |
} | |
return host; | |
}) | |
const SLOTS_PER_EPOCH = 32; | |
async function singleHostLogic(host) { | |
try { | |
// Ask the beacon about its known head | |
const lastCheckpoint = await axios.get(`${host}/eth/v1/beacon/headers/head`) | |
.then(r => r.data.data) | |
.then(r => r.header.message.slot) | |
.then(r => Math.floor(r / SLOTS_PER_EPOCH) - 3); | |
console.log(`${host} is at epoch ${lastCheckpoint + 3} - working with epoch ${lastCheckpoint}`) | |
let lastCheckpointData, tryCheckpoint = lastCheckpoint; | |
while (!lastCheckpointData) { | |
lastCheckpointData = knownCheckpoints[tryCheckpoint]; | |
if (lastCheckpointData) { | |
break; | |
} | |
lastCheckpointData = await axios.get(`${host}/eth/v1/beacon/headers/${tryCheckpoint * SLOTS_PER_EPOCH}`) | |
.then(r => r.data.data) | |
.then(r => r.header.message.slot && r) // Ensure the data is present | |
.then(r => knownCheckpoints[tryCheckpoint] = { | |
epoch: tryCheckpoint, | |
block_root: r.root, | |
state_root: r.header.message.state_root, | |
}) | |
.catch(e => { | |
console.warn(`Could not fetch data for epoch ${tryCheckpoint} from ${host}, trying older: ${e}`); | |
}); | |
// Some epochs don't have a valid checkpoint. In that case try the epoch before | |
tryCheckpoint--; | |
if (lastCheckpoint > tryCheckpoint + 10) { | |
throw new Error("Could not find a working checkpoint in the last 10 epochs. Can't force finalize for now"); | |
} | |
} | |
console.log(`Asking Lighthouse to finalize with data ${JSON.stringify(lastCheckpointData)}`); | |
// Now, send a finalize request | |
await axios.post(`${host}/lighthouse/finalize`, lastCheckpointData) | |
.then(r => r.data) | |
.then(r => console.log(`${host} finalization request result: ${JSON.stringify(r)}`)) | |
await axios.post(`${host}/lighthouse/compaction`) | |
.then(r => r.data) | |
.then(r => console.log(`${host} compaction request result: ${JSON.stringify(r)}`)); | |
} catch (e) { | |
console.error(`Host error for ${host}`, e); | |
} | |
} | |
async function lightHouseLoop() { | |
console.log("Loop start"); | |
await Promise.all(hosts.map(singleHostLogic)); | |
console.log("Loop end"); | |
} | |
const loopInterval = setInterval(lightHouseLoop, 600e3); | |
lightHouseLoop(); | |
process.on('SIGINT', () => { | |
clearInterval(loopInterval); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "tmp-lighthouse-sync-helper", | |
"version": "1.0.0", | |
"description": "A script forcing Lighthouse to prune its storage, useful to handle holesky pectra forks.", | |
"main": "index.js", | |
"scripts": { | |
"test": "echo \"Error: no test specified\" && exit 1" | |
}, | |
"author": "Kiln.fi", | |
"license": "ISC", | |
"dependencies": { | |
"axios": "^1.8.2" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment