From 3c9f074a1b04743508aab36a27ea640b2fab25df Mon Sep 17 00:00:00 2001 From: NeoPlays <80448387+NeoPlays@users.noreply.github.com> Date: Fri, 25 Oct 2024 12:03:02 +0200 Subject: [PATCH] ADD: Fetching Validators for DVT Solutions --- .../src/backend/ValidatorAccountManager.js | 84 +++++++++++++++---- .../ethereum-services/CharonService.js | 4 + launcher/src/composables/validators.js | 7 +- 3 files changed, 76 insertions(+), 19 deletions(-) diff --git a/launcher/src/backend/ValidatorAccountManager.js b/launcher/src/backend/ValidatorAccountManager.js index 9d5dbbf0f..e3f03827a 100755 --- a/launcher/src/backend/ValidatorAccountManager.js +++ b/launcher/src/backend/ValidatorAccountManager.js @@ -221,23 +221,38 @@ export class ValidatorAccountManager { this.nodeConnection.taskManager.otherTasksHandler(ref, `Listing Keys`); try { let client = await this.nodeConnection.readServiceConfiguration(serviceID); - const result = await this.keymanagerAPI(client, "GET", "/eth/v1/keystores"); + let data = {}; + if (client.service === "CharonService" || client.service === "SSVNetworkService") { + const keys = await this.getDVTKeys(serviceID); + data.data = keys.map((dv) => { + return { + validating_pubkey: client.service === "CharonService" ? dv.distributed_public_key : "0x" + dv.public_key, + derivation_path: "", + readonly: false, + dvt: true, + }; + }); + //Push successful task + this.nodeConnection.taskManager.otherTasksHandler(ref, `Get Keys`, true, data.data); + } else { + const result = await this.keymanagerAPI(client, "GET", "/eth/v1/keystores"); - //Error handling - if (SSHService.checkExecError(result) && result.stderr) throw SSHService.extractExecError(result); - if (!result.stdout) - throw `ReturnCode: ${result.rc}\nStderr: ${result.stderr}\nStdout: ${result.stdout}\nIs Your Consensus Client Running?`; + //Error handling + if (SSHService.checkExecError(result) && result.stderr) throw SSHService.extractExecError(result); + if (!result.stdout) + throw `ReturnCode: ${result.rc}\nStderr: ${result.stderr}\nStdout: ${result.stdout}\nIs Your Consensus Client Running?`; - const data = JSON.parse(result.stdout); - if (data.data === undefined) { - if (data.code === undefined || data.message === undefined) { - throw "Undexpected Error: " + result; + data = JSON.parse(result.stdout); + if (data.data === undefined) { + if (data.code === undefined || data.message === undefined) { + throw "Undexpected Error: " + result; + } + throw data.code + " " + data.message; } - throw data.code + " " + data.message; - } - //Push successful task - this.nodeConnection.taskManager.otherTasksHandler(ref, `Get Keys`, true, result.stdout); + //Push successful task + this.nodeConnection.taskManager.otherTasksHandler(ref, `Get Keys`, true, result.stdout); + } if (!data.data) data.data = []; this.writeKeys(data.data.map((key) => key.validating_pubkey)); @@ -1068,7 +1083,7 @@ export class ValidatorAccountManager { let charonClient = services.find((service) => service.service === "CharonService"); if (!charonClient) throw "Couldn't find CharonService"; const dataDir = path.posix.join(charonClient.getDataDir(), ".charon"); - this.nodeConnection.sshService.exec(`rm -rf ${dataDir}`); + await this.nodeConnection.sshService.exec(`rm -rf ${dataDir}`); const result = await this.nodeConnection.sshService.uploadDirectorySSH(path.normalize(localPath), dataDir); if (result) { log.info("Obol Backup uploaded from: ", localPath); @@ -1077,4 +1092,45 @@ export class ValidatorAccountManager { log.error("Error uploading Obol Backup: ", err); } } + + async getDVTKeys(serviceID) { + const service = (await this.serviceManager.readServiceConfigurations()).find((s) => s.id === serviceID); + if (!service) throw new Error(`Service with id ${serviceID} not found`); + switch (service.service) { + case "CharonService": { + const result = await this.nodeConnection.sshService.exec(service.getReadClusterLockCommand()); + const clusterLock = JSON.parse(result.stdout); + return clusterLock.distributed_validators; + } + case "SSVNetworkService": { + const ssvConfig = await this.nodeConnection.getSSVTotalConfig(serviceID); + //Get Operator ID + const response = await axios.get( + `https://api.ssv.network/api/v4/${service.network}/operators/public_key/` + ssvConfig.privateKeyFileData.publicKey + ); + if (response.status !== 200 && !response?.data?.data?.id) + throw new Error(`Couldn't get Operator ID from SSV Network ${response.status} ${response.statusText}`); + const operatorID = response.data.data.id; + + //get pagination info + let result = await axios.get( + `https://api.ssv.network/api/v4/${service.network}/validators/in_operator/${operatorID}?page=${1}&perPage=100` + ); + if (result.status !== 200) throw new Error(`Couldn't get Validator Keys from SSV Network ${result.status} ${result.statusText}`); + + //get all pages and concat them + for (let i = 1; i <= result.data.pagination.pages; i++) { + const page = await axios.get( + `https://api.ssv.network/api/v4/${service.network}/validators/in_operator/${operatorID}?page=${i}&perPage=100` + ); + if (page.status !== 200) throw new Error(`Couldn't get Validator Keys from SSV Network ${page.status} ${page.statusText}`); + result.data.validators = result.data.validators.concat(page.data.validators); + } + + return result.data.validators; + } + default: + throw new Error(`Service ${service.service} not supported`); + } + } } diff --git a/launcher/src/backend/ethereum-services/CharonService.js b/launcher/src/backend/ethereum-services/CharonService.js index 5d0af7cab..940d8df2c 100755 --- a/launcher/src/backend/ethereum-services/CharonService.js +++ b/launcher/src/backend/ethereum-services/CharonService.js @@ -114,6 +114,10 @@ export class CharonService extends NodeService { return `ls -1 -a ${this.getDataDir()}/.charon`; } + getReadClusterLockCommand() { + return `cat ${this.getDataDir()}/.charon/cluster-lock.json`; + } + //definitionFile as URL or Path to file (default ".charon/cluster-definition.json" by dkg command) getDKGCommand(definitionFile) { return `docker run -u 0 --name "dkg-container" -d -v "${this.getDataDir()}:/opt/charon" ${this.image + ":" + this.imageVersion} dkg ${ diff --git a/launcher/src/composables/validators.js b/launcher/src/composables/validators.js index c72ade3ed..4c135a72c 100644 --- a/launcher/src/composables/validators.js +++ b/launcher/src/composables/validators.js @@ -10,17 +10,14 @@ export async function useListKeys(forceRefresh) { const stakingStore = useStakingStore(); let keyStats = []; - let clients = serviceStore.installedServices.filter( - (s) => s.category == "validator" && s.service != "CharonService" && s.service != "SSVNetworkService" - ); + let clients = serviceStore.installedServices.filter((s) => s.category == "validator"); if ((clients && clients.length > 0 && nodeManageStore.currentNetwork?.network != "") || forceRefresh) { for (let client of clients) { //if there is already a list of keys () if ((client.config.keys === undefined || client.config.keys.length === 0 || forceRefresh) && client.state === "running") { //refresh validaotr list let result = await ControlService.listValidators(client.config.serviceID); - - if (!client.service.includes("Web3Signer")) { + if (!/Web3Signer|CharonService|SSVNetwork/.test(client.service)) { let resultRemote = await ControlService.listRemoteKeys(client.config.serviceID); let remoteKeys = resultRemote.data ? resultRemote.data.map((e) => {