EPF4: UPDATE 6 Got very busy with my other project this week, but i was able to dig into [ephemery native retention scripts](https://github.com/ephemery-testnet/ephemery-scripts/blob/master/retention.sh) and understand how ephemery reset works manually and rewrote it in typescript. Typescript is the language of choice for Lodestar. I have written the purpose of this experimental code [here](https://github.com/atkinsonholly/lodestar/pull/1), so i will just jump into my own impression of how to modify it within Lodestar here: ``` import * as fs from 'fs'; import * as path from 'path'; import * as util from 'util'; import { exec } from 'child_process'; import axios from 'axios'; import * as tar from 'tar'; const execPromise = util.promisify(exec); const genesisRepository = 'ephemery-testnet/ephemery-genesis'; const testnetDir = '/home/ethereum/testnet'; const clDataDir = '/home/ethereum/data-lodestar'; const clPort = 5052; const genesisResetInterval = 86400; // 1 day in seconds ``` **notes** * most of the constants and modules here are already present in lodestar, which is a good thing. i still want to know how directories are created though! ### START CLIENTS ``` async function startClients() { console.log('Start clients'); await execPromise('sudo systemctl start beacon-chain'); await execPromise('sudo systemctl start validator'); } ``` **notes** * this is not really neccessary, in the sense that lodestar with ephemery currently starts with `./lodestar beacon --network ephemery`. however this is done manually from the terminal by the user. * is there any implementation code in lodestar in which the client is restarted after a process? If yes, it can be repurposed here. ### STOP CLIENTS ``` async function stopClients() { console.log('Stop clients'); await execPromise('sudo systemctl stop beacon-chain'); await execPromise('sudo systemctl stop validator'); } ``` **notes:** * same as above. * stopping the client is probably not semantically correct because the client will still be implementing the following processes below after it has 'stopped' * what's being stopped actually are the transactions :). ### CLEAR DIRECTORIES ``` async function clearDataDirs() { fs.rmdirSync(path.join(clDataDir, 'beacon'), { recursive: true }); fs.unlinkSync(path.join(clDataDir, 'validators/slashing_protection.sqlite')); } ``` **notes** * this refers to the management of the directories automatically created in `.ethereum`. the directory being cleared here is lodestar's which will contain beacon configurations(?) and validator list(?). * something worth noting here is that the consensus client directories are not recreated as they are done in the original retention scripts for the execution client. i think this is so because once the client is restarted again after the reset, lodestar will reinitialize this directory automatically? * this process is indicated in the `setupGenesis` function below: ### SETUP GENESIS ``` async function setupGenesis() { console.log('Setup Genesis'); await execPromise(`~/lodestar/bin/lodestar init --datadir ${clDataDir} ${path.join(testnetDir, 'genesis.json')}`); } ``` **notes** * this function initializes lodestar while creating the neccessary directories. * however, in the original retention scripts there is no genesis setup function for the consensus clients, i wonder why it's not available ? * is it because it's already created by the execution client? * this function also indicates that the ephemery directory(`testnetDir`) will be within lodestar's directory(`clDataDir`) ### GET GITHUB RELEASE ``` async function getGithubRelease(repository: string) { try { const response = await axios.get(`https://api.github.com/repos/${repository}/releases/latest`); return response.data.tag_name; } catch (error) { console.error('Error fetching GitHub release:', error); throw error; } } ``` **notes** * this is basically grabbing the latest release of ephemery (i need to confirm it this is already being done automatically on lodestar) ### DOWNLOAD GENESIS RELEASE ``` async function downloadGenesisRelease(genesisRelease: string) { console.log('Download Genesis Release'); const testnetDirExists = fs.existsSync(testnetDir); if (testnetDirExists) { fs.rmdirSync(testnetDir); } fs.mkdirSync(testnetDir, { recursive: true }); const response = await axios.get(`https://github.com/${genesisRepository}/releases/download/${genesisRelease}/testnet-all.tar.gz`, { responseType: 'stream' }); response.data.pipe(tar.x({ C: testnetDir })); await new Promise((resolve, reject) => { response.data.on('end', resolve); response.data.on('error', reject); }); } ``` **notes:** * if there is a new github release, this function downloads it. more file management here; this release will be downloaded into the `.ethereum` folder as well. * lodestar like every other client after initialization has files within the `.ethereum`. so i suspect something similar to this will be available already. ### RESET TESTNET ``` async function resetTestnet(genesisRelease: string) { await stopClients(); clearDataDirs(); await downloadGenesisRelease(genesisRelease); await setupGenesis(); await startClients(); } ``` **notes** * this function encompasses all the previous functions and implements them sequentially one after the other. * (i think) most of these functions already exist in some form on lodestar and i will just need to modify them as neccessary. * thus, this function will likely end up importing them into the [ephemery package within lodestar](https://github.com/atkinsonholly/lodestar/tree/unstable/packages/ephemery) where the reset can happen. ### CHECK TESTNET ``` async function checkTestnet() { const currentTime = Math.floor(Date.now() / 1000); const genesisTimeResponse = await axios.get(`http://localhost:${clPort}/eth/v1/beacon/genesis`); const genesisTime = genesisTimeResponse.data.genesis_time; if (!genesisTime || genesisTime <= 0) { console.error('Could not get genesis time from beacon node'); return; } const retentionVarsPath = path.join(testnetDir, 'retention.vars'); if (!fs.existsSync(retentionVarsPath)) { console.error('Could not find retention.vars'); return; } const { GENESIS_RESET_INTERVAL, ITERATION_RELEASE, CHAIN_ID } = require(retentionVarsPath); const testnetTimeout = genesisTime + GENESIS_RESET_INTERVAL - 300; console.log(`Genesis timeout: ${testnetTimeout - currentTime} sec`); if (testnetTimeout <= currentTime) { const latestGenesisRelease = await getGithubRelease(genesisRepository); if (!ITERATION_RELEASE) { process.env.ITERATION_RELEASE = CHAIN_ID; } if (latestGenesisRelease === ITERATION_RELEASE) { console.error(`Could not find new genesis release (release: ${latestGenesisRelease})`); return; } await resetTestnet(latestGenesisRelease); } } ``` **notes** * this function does the same thing as the previous in that it will be importing the previous functions (including the reset function). * this function is checking if it's time to reset the testnet so as to proceed towards downloading a new genesis. ### MAIN ``` async function main() { const genesisJsonPath = path.join(testnetDir, 'genesis.json'); if (!fs.existsSync(genesisJsonPath)) { const latestGenesisRelease = await getGithubRelease(genesisRepository); await resetTestnet(latestGenesisRelease); } else { await checkTestnet(); } } ``` **notes** * this function will end up being an index file in keeping with lodestar best practice of setting up files in directories using typescript. * this suggests that the code files within ephemery/reset directory will look somewhat like this: * `checktestnet.ts` * `reset.ts` * `index.ts` Where the `index.ts` imports all the aforementioned functions to implement ephemery reset automatically.