diff --git a/controls/roles/delete-service/tasks/main.yml b/controls/roles/delete-service/tasks/main.yml index c2eecc76b..ce5b5099c 100644 --- a/controls/roles/delete-service/tasks/main.yml +++ b/controls/roles/delete-service/tasks/main.yml @@ -17,11 +17,12 @@ keep_volumes: no become: yes -- name: Remove Docker Image +- name: Remove Docker Image community.docker.docker_image: state: absent name: "{{ service_configuration.image | split(':') | first }}" tag: "{{ service_configuration.image | split(':') | last }}" + force_absent: yes - name: Remove Service Config File ansible.builtin.file: @@ -30,8 +31,9 @@ - name: Remove Service Files ansible.builtin.file: - path: "{{ item | first | regex_search('.*' + service) }}" + path: "{{ service_configuration.volumes | select('search', service) | first | split(':') | first | regex_search('^.*' + service) }}" state: absent - with_items: "{{ service_configuration.volumes | select('search', service) }}" + force: true + when: "{{ service_configuration.volumes | length }}" # EOF diff --git a/controls/roles/update-changes/tasks/main.yml b/controls/roles/update-changes/tasks/main.yml index 29c3a99cc..b9aa8e0b0 100644 --- a/controls/roles/update-changes/tasks/main.yml +++ b/controls/roles/update-changes/tasks/main.yml @@ -1,5 +1,5 @@ --- -- include_tasks: "2.0-rc6/updates-20-rc3.yaml" +- include_tasks: "2.0-rc3/updates-20-rc3.yaml" ignore_errors: yes - include_tasks: "2.0-rc6/updates-20-rc6.yaml" diff --git a/launcher/public/img/icon/manage-node-icons/ADD_PLUGIN.png b/launcher/public/img/icon/manage-node-icons/ADD_PLUGIN.png new file mode 100644 index 000000000..ae237a161 Binary files /dev/null and b/launcher/public/img/icon/manage-node-icons/ADD_PLUGIN.png differ diff --git a/launcher/public/img/icon/manage-node-icons/REMOVE_PLUGIN.png b/launcher/public/img/icon/manage-node-icons/REMOVE_PLUGIN.png new file mode 100644 index 000000000..96d8b682c Binary files /dev/null and b/launcher/public/img/icon/manage-node-icons/REMOVE_PLUGIN.png differ diff --git a/launcher/src/backend/Monitoring.js b/launcher/src/backend/Monitoring.js index af94d72ba..1d09c2b32 100644 --- a/launcher/src/backend/Monitoring.js +++ b/launcher/src/backend/Monitoring.js @@ -59,6 +59,7 @@ export class Monitoring { ports: config.ports, volumes: config.volumes, network: config.network, + dependencies : config.dependencies, }, }; }); diff --git a/launcher/src/backend/ServiceManager.js b/launcher/src/backend/ServiceManager.js index 517b5e3e8..eb565f72b 100644 --- a/launcher/src/backend/ServiceManager.js +++ b/launcher/src/backend/ServiceManager.js @@ -14,6 +14,9 @@ import { PrysmBeaconService } from './ethereum-services/PrysmBeaconService' import { PrysmValidatorService } from './ethereum-services/PrysmValidatorService' import { TekuBeaconService } from './ethereum-services/TekuBeaconService' import { NethermindService } from './ethereum-services/NethermindService' +import { FlashbotsMevBoostService } from './ethereum-services/FlashbotsMevBoostService' +import { ServicePort, servicePortProtocol } from './ethereum-services/ServicePort' +import { StringUtils } from './StringUtils' const log = require('electron-log') @@ -27,7 +30,7 @@ export const serivceState = { } export class ServiceManager { - constructor (nodeConnection) { + constructor(nodeConnection) { this.nodeConnection = nodeConnection } @@ -38,7 +41,7 @@ export class ServiceManager { * @param state a string with the desired state, see serivceState * @returns an object containing a reference to the ansible process output, usable with NodeConnection.playbookStatus */ - manageServiceState (serviceId, state) { + manageServiceState(serviceId, state) { const extraVars = { stereum_role: 'manage-service', stereum_args: { @@ -50,7 +53,7 @@ export class ServiceManager { } } } - return this.nodeConnection.runPlaybook(state.replace("ed","ing Service"), extraVars) + return this.nodeConnection.runPlaybook(state.replace("ed", "ing Service"), extraVars) } /** @@ -58,7 +61,7 @@ export class ServiceManager { * * @returns an array of all service configurations */ - readServiceConfigurations () { + readServiceConfigurations() { return this.nodeConnection.listServicesConfigurations().then(async services => { const serviceConfigurations = new Array() @@ -110,6 +113,8 @@ export class ServiceManager { services.push(PrysmValidatorService.buildByConfiguration(config)) } else if (config.service == 'TekuBeaconService') { services.push(TekuBeaconService.buildByConfiguration(config)) + } else if (config.service == 'FlashbotsMevBoostService') { + services.push(FlashbotsMevBoostService.buildByConfiguration(config)) } } else { log.error('found configuration without service!') @@ -119,17 +124,17 @@ export class ServiceManager { } //retrieve full service out of minimal config services.forEach(service => { - if(service.dependencies.executionClients.length > 0){ + if (service.dependencies.executionClients.length > 0) { service.dependencies.executionClients = service.dependencies.executionClients.map(client => { return services.find(dependency => dependency.id === client.id) }) } - if(service.dependencies.consensusClients.length > 0){ + if (service.dependencies.consensusClients.length > 0) { service.dependencies.consensusClients = service.dependencies.consensusClients.map(client => { return services.find(dependency => dependency.id === client.id) }) } - if(service.dependencies.prometheusNodeExporterClients.length > 0){ + if (service.dependencies.prometheusNodeExporterClients.length > 0) { service.dependencies.prometheusNodeExporterClients = service.dependencies.prometheusNodeExporterClients.map(client => { return services.find(dependency => dependency.id === client.id) }) @@ -142,18 +147,18 @@ export class ServiceManager { }) } - async chooseServiceAction(action, service, data){ + async chooseServiceAction(action, service, data) { switch (action) { case "pruneGeth": - if(service.service === "GethService"){ + if (service.service === "GethService") { let data = service.yaml + "\nisPruning: true" - await this.nodeConnection.writeServiceYAML({id: service.config.serviceID, data: data , service: service.service}) - this.nodeConnection.runPlaybook("Pruning Geth", {stereum_role: 'prune-geth', geth_service: service.config.serviceID}) + await this.nodeConnection.writeServiceYAML({ id: service.config.serviceID, data: data, service: service.service }) + this.nodeConnection.runPlaybook("Pruning Geth", { stereum_role: 'prune-geth', geth_service: service.config.serviceID }) } break; case "reSync": - //initiate resync + //initiate resync break; default: @@ -161,24 +166,290 @@ export class ServiceManager { } } - async deleteService(task){ - this.nodeConnection.runPlaybook("Delete Service", {stereum_role: 'delete-service', service: task.service.config.serviceID}) + removeDependencies(service, serviceToDelete) { + //update command + service.command = this.removeCommandConnection(service.command, serviceToDelete.id) + + //update volumes + service.volumes = service.volumes.filter(v => !v.destinationPath.includes(serviceToDelete.id)) + + //update dependencies arrays + for (const dependency in service.dependencies) { + service.dependencies[dependency] = service.dependencies[dependency].filter(s => s.id != serviceToDelete.id) + } + return service + } + + removeCommandConnection(command, id) { + let isString = false + if (typeof command === "string") { + isString = true + command = command.replaceAll(/\n/gm, '').replaceAll(/\s\s+/gm, ' ').split(' ') + } + let includesID = command.filter(c => c.includes(id)) + command = command.filter(c => !includesID.includes(c)) + + let newProps = includesID.map(c => { + let command = c.match(/.*=/)[0] + let value = c.replace(command, '') + let quotes = false + if (value.startsWith('"') && value.endsWith('"')) { + quotes = true + value = value.substring(1, value.length - 1) + } + let newValue = value.split(',').filter(e => !e.includes(id)).join() + if (quotes) + newValue = '"' + newValue + '"' + return command + newValue + }) + if (isString) + return (command.concat(newProps)).join(' ') + return command.concat(newProps) + } + + async deleteService(task, tasks, services) { + let serviceToDelete = services.find(service => service.id === task.service.config.serviceID) + let dependents = [] + services.forEach(service => { + for (const dependency in service.dependencies) { + service.dependencies[dependency].forEach(s => { + if (s.id === serviceToDelete.id) + dependents.push(service) + }) + } + }) + log.info(dependents) + dependents.forEach(service => { + service = this.removeDependencies(service, serviceToDelete) + this.nodeConnection.writeServiceConfiguration(service.buildConfiguration()) + }) + await this.nodeConnection.runPlaybook("Delete Service", { stereum_role: 'delete-service', service: task.service.config.serviceID }) + } + //args: network, installDir, port, executionClients, checkpointURL, beaconServices + getService(name, args) { + let ports + switch (name) { + case "GethService": + ports = [ + new ServicePort(null, 30303, 30303, servicePortProtocol.tcp), + new ServicePort(null, 30303, 30303, servicePortProtocol.udp), + new ServicePort('127.0.0.1', args.port ? args.port : 8545, 8545, servicePortProtocol.tcp), + ] + return GethService.buildByUserInput(args.network, ports, args.installDir + '/geth') + + case "BesuService": + ports = [ + new ServicePort(null, 30303, 30303, servicePortProtocol.tcp), + new ServicePort(null, 30303, 30303, servicePortProtocol.udp), + new ServicePort('127.0.0.1', args.port ? args.port : 8545, 8545, servicePortProtocol.tcp), + ] + return BesuService.buildByUserInput(args.network, ports, args.installDir + '/besu') + + case "NethermindService": + ports = [ + new ServicePort(null, 30303, 30303, servicePortProtocol.tcp), + new ServicePort(null, 30303, 30303, servicePortProtocol.udp), + new ServicePort('127.0.0.1', args.port ? args.port : 8545, 8545, servicePortProtocol.tcp), + ] + return NethermindService.buildByUserInput(args.network, ports, args.installDir + '/nethermind') + + case "LighthouseBeaconService": + ports = [ + new ServicePort(null, 9000, 9000, servicePortProtocol.tcp), + new ServicePort(null, 9000, 9000, servicePortProtocol.udp), + new ServicePort('127.0.0.1', args.port ? args.port : 5052, 5052, servicePortProtocol.tcp), + ] + return LighthouseBeaconService.buildByUserInput(args.network, ports, args.installDir + '/lighthouse', args.executionClients, '16', args.checkpointURL) + + case "LighthouseValidatorService": + ports = [ + new ServicePort('127.0.0.1', args.port ? args.port : 5062, 5062, servicePortProtocol.tcp), + ] + return LighthouseValidatorService.buildByUserInput(args.network, ports, args.installDir + '/lighthouse', args.beaconServices) + + case "PrysmBeaconService": + ports = [ + new ServicePort(null, 13001, 13001, servicePortProtocol.tcp), + new ServicePort(null, 12001, 12001, servicePortProtocol.udp), + new ServicePort('127.0.0.1', 4000, 4000, servicePortProtocol.tcp), + new ServicePort('127.0.0.1', args.port ? args.port : 3500, 3500, servicePortProtocol.tcp) + ] + return PrysmBeaconService.buildByUserInput(args.network, ports, args.installDir + '/prysm', args.executionClients, args.checkpointURL) + + case "PrysmValidatorService": + ports = [ + new ServicePort('127.0.0.1', args.port ? args.port : 7500, 7500, servicePortProtocol.tcp), + ] + return PrysmValidatorService.buildByUserInput(args.network, ports, args.installDir + '/prysm', args.beaconServices) + + case "NimbusBeaconService": + ports = [ + new ServicePort(null, 9000, 9000, servicePortProtocol.tcp), + new ServicePort(null, 9000, 9000, servicePortProtocol.udp), + new ServicePort('127.0.0.1', 9190, 9190, servicePortProtocol.tcp), + new ServicePort('127.0.0.1', args.port ? args.port : 5052, 5052, servicePortProtocol.tcp) + ] + return NimbusBeaconService.buildByUserInput(args.network, ports, args.installDir + '/nimbus', args.executionClients, args.checkpointURL) + + case "TekuBeaconService": + ports = [ + new ServicePort(null, 9001, 9001, servicePortProtocol.tcp), + new ServicePort(null, 9001, 9001, servicePortProtocol.udp), + new ServicePort('127.0.0.1', 9190, 9190, servicePortProtocol.tcp), + new ServicePort('127.0.0.1', args.port ? args.port : 5052, 5052, servicePortProtocol.tcp) + ] + return TekuBeaconService.buildByUserInput(args.network, ports, args.installDir + '/teku', args.executionClients, args.checkpointURL) + + case "PrometheusNodeExporterService": + return PrometheusNodeExporterService.buildByUserInput(args.network) + + case "PrometheusService": + ports = [ + new ServicePort('127.0.0.1', args.port ? args.port : 9090, 9090, servicePortProtocol.tcp) + ] + return PrometheusService.buildByUserInput(args.network, ports, args.installDir + '/prometheus') + + case "GrafanaService": + ports = [ + new ServicePort('127.0.0.1', args.port ? args.port : 3000, 3000, servicePortProtocol.tcp) + ] + return GrafanaService.buildByUserInput(args.network, ports, args.installDir + '/grafana') + + case "FlashbotsMevBoostService": + return FlashbotsMevBoostService.buildByUserInput(args.network) + } } - async modifyServices(tasks){ - let done = [] - for(let task of tasks){ - switch (task.content) { - case "DELETE": - if(!done.includes(task.service.config.serviceID)){ - await this.deleteService(task) - done.push(task.service.config.serviceID) + async createKeystores(services){ + for(const service of services){ + if(service.service.includes("Nimbus")){ + const valDir = (service.volumes.find(vol => vol.servicePath === '/opt/app/validators')).destinationPath + log.info(valDir) + const token = StringUtils.createRandomString() + await this.nodeConnection.sshService.exec(`mkdir -p ${valDir}`) + await this.nodeConnection.sshService.exec(`echo ${token} > ${valDir}/api-token.txt`) + } else if(service.service.includes("Teku")) { + const dataDir = (service.volumes.find(vol => vol.servicePath === '/opt/app/data')).destinationPath + const password = StringUtils.createRandomString() + await this.nodeConnection.sshService.exec('apt install -y openjdk-8-jre-headless') + await this.nodeConnection.sshService.exec(`mkdir -p ${dataDir}`) + await this.nodeConnection.sshService.exec(`echo ${password} > ${dataDir}/teku_api_password.txt`) + await this.nodeConnection.sshService.exec(`cd ${dataDir} && keytool -genkeypair -keystore teku_api_keystore -storetype PKCS12 -storepass ${password} -keyalg RSA -keysize 2048 -validity 109500 -dname 'CN=localhost, OU=MyCompanyUnit, O=MyCompany, L=MyCity, ST=MyState, C=AU' -ext san=dns:localhost,ip:127.0.0.1`) + } + } + } + + async addServices(tasks, services) { + let newServices = [] + let ELInstalls = tasks.filter(t => t.service.category === "execution") + ELInstalls.forEach(t => { + let service = this.getService(t.service.service, t.data) + t.service.config.serviceID = service.id + newServices.push(service) + }) + let CLInstalls = tasks.filter(t => t.service.category === "consensus") + CLInstalls.forEach(t => { + if(t.data.executionClients.length > 0){ + t.data.executionClients = t.data.executionClients.map(ec => { + let id = ec.config.serviceID + if(id){ + return services.find(s => s.id === id) + } + id = (ELInstalls.find(el => el.service.id === ec.id)).service.config.serviceID + return newServices.find(s => s.id === id) + }) + } + let service = this.getService(t.service.service, t.data) + t.service.config.serviceID = service.id + newServices.push(service) + }) + let VLInstalls = tasks.filter(t => t.service.category === "validator") + VLInstalls.forEach(t => { + if(t.data.beaconServices.length > 0){ + t.data.beaconServices = t.data.beaconServices.map(bc => { + let id = bc.config.serviceID + if(id){ + return services.find(s => s.id === id) + } + id = CLInstalls.find(el => el.service.id === bc.id).service.config.serviceID + return newServices.find(s => s.id === id) + }) + } + let service = this.getService(t.service.service, t.data) + t.service.config.serviceID = service.id + newServices.push(service) + }) + let PInstalls = tasks.filter(t => t.service.category === "service") + PInstalls.forEach(t => { + let service = this.getService(t.service.service, t.data) + newServices.push(service) + }) + + let allPorts = services.map(s => s.ports).flat(1).map(p => p.destinationPort + '/' + p.servicePortProtocol) + let changed + do{ + changed = false + newServices.forEach(service => { + service.ports.forEach(newPort => { + if(allPorts.includes(newPort.destinationPort + '/' + newPort.servicePortProtocol)){ + newPort.destinationPort++ + changed = true } - break; + }) + }) + }while(changed === true) + + await this.createKeystores(newServices.filter(s => s.service.includes("Teku") || s.service.includes("Nimbus"))) + let versions + try{ + versions = await this.nodeConnection.checkUpdates() + }catch(err){ + log.error(`Couldn't fetch versions in OneClickInstallation... + Installing with predefined Versions + ${err.name}: ${err.message} + url: ${err.config.url} + method: ${err.config.method} + headers: ${err.config.headers} + timeout: ${err.config.timeout} + `) + } + newServices.forEach(service => { + if(versions[service.network][service.service]){ + service.imageVersion = versions[service.network][service.service].slice(-1).pop() + }else{ + service.imageVersion = versions["prater"][service.service].slice(-1).pop() + } + }) + + await Promise.all(newServices.map(async (service) => { + await this.nodeConnection.writeServiceConfiguration(service.buildConfiguration()) + })) + + //TODO: write config, + + } + + static uniqueByID(job) { + return (value, index, self) => self.map(t => t.service.config.serviceID).indexOf(value.service.config.serviceID) === index && value.content === job + } - default: - break; + async modifyServices(tasks) { + let jobs = tasks.map(t => t.content) + if (jobs.includes("DELETE")) { + let services = await this.readServiceConfigurations() + let before = this.nodeConnection.getTimeStamp() + try { + await Promise.all(tasks.filter(ServiceManager.uniqueByID("DELETE")).map((task, index, tasks) => { return this.deleteService(task, tasks, services) })) + } catch (err) { + log.error("Deleting Services Failed:", err) + } finally { + let after = this.nodeConnection.getTimeStamp() + await this.nodeConnection.restartServices(after - before) } + } else if (jobs.includes("INSTALL")) { + let services = await this.readServiceConfigurations() + let installTasks = tasks.filter(t => t.content === "INSTALL") + await this.addServices(installTasks, services) } } } \ No newline at end of file diff --git a/launcher/src/backend/ServiceManager.test.js b/launcher/src/backend/ServiceManager.test.js index e75ab17af..81806901f 100644 --- a/launcher/src/backend/ServiceManager.test.js +++ b/launcher/src/backend/ServiceManager.test.js @@ -114,3 +114,105 @@ test('readServiceConfigurations success empty', async () => { expect(serviceConfigs.length).toBe(0) }) + +test('removeConnection String', () => { + const command = `/app/cmd/validator/validator --accept-terms-of-use=true + --beacon-rpc-provider="stereum-42d9f0b4-257f-f71e-10fe-66c342dd4995:4000" + --beacon-rpc-gateway-provider=stereum-42d9f0b4-257f-f71e-10fe-66c342dd4995:3500 + --web --prater=true --datadir=/opt/app/data/db + --wallet-dir=/opt/app/data/wallets + --wallet-password-file=/opt/app/data/passwords/wallet-password + --monitoring-host=0.0.0.0 --grpc-gateway-port=7500 --grpc-gateway-host=0.0.0.0 + --grpc-gateway-corsdomain="*" --monitoring-host=0.0.0.0 + --monitoring-port=8081 + --suggested-fee-recipient=0x0000000000000000000000000000000000000000 + --graffiti-file=/opt/app/graffitis/graffitis.yaml` + const id = '42d9f0b4-257f-f71e-10fe-66c342dd4995' + + const sm = new ServiceManager() + const result = sm.removeCommandConnection(command, id) + + expect(result).not.toMatch(/--beacon-rpc-provider="stereum-42d9f0b4-257f-f71e-10fe-66c342dd4995:4000"/) + expect(result).not.toMatch(/--beacon-rpc-gateway-provider=stereum-42d9f0b4-257f-f71e-10fe-66c342dd4995:3500/) + expect(result).toMatch(/--beacon-rpc-provider=""/) + expect(result).toMatch(/--beacon-rpc-gateway-provider=/) +}) + +test('removeConnection String multiple endpoints', () => { + const command = `/app/cmd/validator/validator --accept-terms-of-use=true + --beacon-rpc-provider="stereum-42d9f0b4-257f-f71e-10fe-66c342dd4995:4000,stereum-foo:3000,stereum-bar:2000" + --beacon-rpc-gateway-provider=stereum-foo:3000,stereum-42d9f0b4-257f-f71e-10fe-66c342dd4995:3500 + --web --prater=true --datadir=/opt/app/data/db + --wallet-dir=/opt/app/data/wallets + --wallet-password-file=/opt/app/data/passwords/wallet-password + --monitoring-host=0.0.0.0 --grpc-gateway-port=7500 --grpc-gateway-host=0.0.0.0 + --grpc-gateway-corsdomain="*" --monitoring-host=0.0.0.0 + --monitoring-port=8081 + --suggested-fee-recipient=0x0000000000000000000000000000000000000000 + --graffiti-file=/opt/app/graffitis/graffitis.yaml` + const id = '42d9f0b4-257f-f71e-10fe-66c342dd4995' + + const sm = new ServiceManager() + const result = sm.removeCommandConnection(command, id) + + expect(result).not.toMatch(/--beacon-rpc-provider="stereum-42d9f0b4-257f-f71e-10fe-66c342dd4995:4000,stereum-foo:3000,stereum-bar:2000"/) + expect(result).not.toMatch(/--beacon-rpc-gateway-provider=stereum-foo:3000,stereum-42d9f0b4-257f-f71e-10fe-66c342dd4995:3500/) + expect(result).toMatch(/--beacon-rpc-provider="stereum-foo:3000,stereum-bar:2000"/) + expect(result).toMatch(/--beacon-rpc-gateway-provider=stereum-foo:3000/) +}) + +test('removeConnection array single endpoint', () => { + const command = [ + '--network=prater', + '--logging=INFO', + '--p2p-enabled=true', + '--p2p-port=9001', + '--validators-keystore-locking-enabled=false', + '--validators-graffiti-file=/opt/app/graffitis/graffitis.yaml', + '--ee-endpoint=http://stereum-9adfdb2e-9f5b-aba4-cfde-f3483d7aac8d:8551', + '--ee-jwt-secret-file=/engine.jwt', + '--validators-proposer-default-fee-recipient=0x0000000000000000000000000000000000000000', + '--data-path=/opt/app/data', + '--data-storage-mode=prune', + '--rest-api-port=5051', + '--rest-api-host-allowlist=*', + '--rest-api-interface=0.0.0.0', + '--rest-api-docs-enabled=true', + '--rest-api-enabled=true', + ] + const id = '9adfdb2e-9f5b-aba4-cfde-f3483d7aac8d' + + const sm = new ServiceManager() + const result = sm.removeCommandConnection(command, id) + + expect(result).not.toContain('--ee-endpoint=http://stereum-9adfdb2e-9f5b-aba4-cfde-f3483d7aac8d:8551') + expect(result).toContain('--ee-endpoint=') +}) + +test('removeConnection array multiple endpoints', () => { + const command = [ + '--network=prater', + '--logging=INFO', + '--p2p-enabled=true', + '--p2p-port=9001', + '--validators-keystore-locking-enabled=false', + '--validators-graffiti-file=/opt/app/graffitis/graffitis.yaml', + '--ee-endpoint="http://stereum-9adfdb2e-9f5b-aba4-cfde-f3483d7aac8d:8551,foo:3000,bar:2000"', + '--ee-jwt-secret-file=/engine.jwt', + '--validators-proposer-default-fee-recipient=0x0000000000000000000000000000000000000000', + '--data-path=/opt/app/data', + '--data-storage-mode=prune', + '--rest-api-port=5051', + '--rest-api-host-allowlist=*', + '--rest-api-interface=0.0.0.0', + '--rest-api-docs-enabled=true', + '--rest-api-enabled=true', + ] + const id = '9adfdb2e-9f5b-aba4-cfde-f3483d7aac8d' + + const sm = new ServiceManager() + const result = sm.removeCommandConnection(command, id) + + expect(result).not.toContain('--ee-endpoint="http://stereum-9adfdb2e-9f5b-aba4-cfde-f3483d7aac8d:8551,foo:3000,bar:2000"') + expect(result).toContain('--ee-endpoint="foo:3000,bar:2000"') +}) diff --git a/launcher/src/backend/ethereum-services/MevboostService.int.js b/launcher/src/backend/ethereum-services/FlashbotsMevBoostService.int.js similarity index 92% rename from launcher/src/backend/ethereum-services/MevboostService.int.js rename to launcher/src/backend/ethereum-services/FlashbotsMevBoostService.int.js index 037c3c2eb..bd6a9e01a 100644 --- a/launcher/src/backend/ethereum-services/MevboostService.int.js +++ b/launcher/src/backend/ethereum-services/FlashbotsMevBoostService.int.js @@ -5,7 +5,7 @@ import { HetznerServer } from '../HetznerServer.js' import { NodeConnection } from '../NodeConnection.js' import { ServicePort, servicePortProtocol } from './ServicePort.js' import { ServiceManager } from '../ServiceManager.js' -import { MevboostService } from './MevboostService.js' +import { FlashbotsMevBoostService } from './FlashbotsMevBoostService.js' const log = require('electron-log') jest.setTimeout(600000) @@ -50,7 +50,7 @@ test('mevboost installation', async () => { await nodeConnection.findStereumSettings() await nodeConnection.prepareStereumNode(nodeConnection.settings.stereum.settings.controls_install_path); - const mevboost = MevboostService.buildByUserInput('goerli', nodeConnection.settings.stereum.settings.controls_install_path + '/mevboost') + const mevboost = FlashbotsMevBoostService.buildByUserInput('goerli', nodeConnection.settings.stereum.settings.controls_install_path + '/mevboost') await nodeConnection.writeServiceConfiguration(mevboost.buildConfiguration()) await serviceManager.manageServiceState(mevboost.id, 'started') diff --git a/launcher/src/backend/ethereum-services/MevboostService.js b/launcher/src/backend/ethereum-services/FlashbotsMevBoostService.js similarity index 85% rename from launcher/src/backend/ethereum-services/MevboostService.js rename to launcher/src/backend/ethereum-services/FlashbotsMevBoostService.js index 2095c9e05..86f656d6d 100644 --- a/launcher/src/backend/ethereum-services/MevboostService.js +++ b/launcher/src/backend/ethereum-services/FlashbotsMevBoostService.js @@ -2,15 +2,15 @@ import { NodeService } from './NodeService' import { ServicePortDefinition } from './SerivcePortDefinition' import { ServiceVolume } from './ServiceVolume' -export class MevboostService extends NodeService { +export class FlashbotsMevBoostService extends NodeService { static buildByUserInput (network) { const image = 'flashbots/mev-boost' - const service = new MevboostService() + const service = new FlashbotsMevBoostService() let relay_link = 'https://0xac6e77dfe25ecd6110b8e780608cce0dab71fdd5ebea22a16c0205200f2f8e2e3ad3b71d3499c54ad14d6c21b41a37ae@boost-relay.flashbots.net' // default for mainnet if ( network === "goerli" ) relay_link = 'https://0xafa4c6985aa049fb79dd37010438cfebeb0f2bd42b115b89dd678dab0670c1de38da0c4e9138c9290a398ecd9a0b3110@builder-relay-goerli.flashbots.net' service.init( - 'MevboostService', + 'FlashbotsMevBoostService', null, // id 1, // configVersion image, // image @@ -32,13 +32,13 @@ export class MevboostService extends NodeService { network // network // executionClients // consensusClients - // MevboostService + // FlashbotsMevBoostService ) return service } static buildByConfiguration (config) { - const service = new MevboostService() + const service = new FlashbotsMevBoostService() service.initByConfig(config) diff --git a/launcher/src/backend/ethereum-services/MevboostService.test.js b/launcher/src/backend/ethereum-services/FlashbotsMevBoostService.test.js similarity index 61% rename from launcher/src/backend/ethereum-services/MevboostService.test.js rename to launcher/src/backend/ethereum-services/FlashbotsMevBoostService.test.js index 43ab822ad..4d15fde1f 100644 --- a/launcher/src/backend/ethereum-services/MevboostService.test.js +++ b/launcher/src/backend/ethereum-services/FlashbotsMevBoostService.test.js @@ -1,22 +1,22 @@ -import { MevboostService } from './MevboostService.js' +import { FlashbotsMevBoostService } from './FlashbotsMevBoostService.js' test('buildConfiguration', () => { - const mbService = MevboostService.buildByUserInput().buildConfiguration() + const mbService = FlashbotsMevBoostService.buildByUserInput().buildConfiguration() expect(mbService.image).toMatch(/flashbots\/mev-boost/) }) test('buildByConfiguration', () => { - const mb = MevboostService.buildByConfiguration({ + const mb = FlashbotsMevBoostService.buildByConfiguration({ id: '124', - service: 'MevboostService', + service: 'FlashbotsMevBoostService', configVersion: 655, image: 'flashbots/mev-boost:v0.8.2', volumes: ['/opt/stereum/foo:/opt/app/data'] }) expect(mb.id).toBe('124') - expect(mb.service).toBe('MevboostService') + expect(mb.service).toBe('FlashbotsMevBoostService') expect(mb.configVersion).toBe(655) expect(mb.image).toBe('flashbots/mev-boost') expect(mb.imageVersion).toBe('v0.8.2') diff --git a/launcher/src/backend/ethereum-services/LighthouseBeaconService.js b/launcher/src/backend/ethereum-services/LighthouseBeaconService.js index d7388e6f5..13a274a8c 100644 --- a/launcher/src/backend/ethereum-services/LighthouseBeaconService.js +++ b/launcher/src/backend/ethereum-services/LighthouseBeaconService.js @@ -7,7 +7,6 @@ export class LighthouseBeaconService extends NodeService { const service = new LighthouseBeaconService() service.setId() const workingDir = service.buildWorkingDir(dir) - const elJWTDir = (executionClients[0].volumes.find(vol => vol.servicePath === '/engine.jwt')).destinationPath const image = 'sigp/lighthouse' @@ -19,8 +18,11 @@ export class LighthouseBeaconService extends NodeService { const volumes = [ new ServiceVolume(workingDir + '/beacon', dataDir), new ServiceVolume(workingDir + '/slasher', slasherDir), - new ServiceVolume(elJWTDir, JWTDir) ] + if(executionClients && executionClients.length > 0){ + const elJWTDir = (executionClients[0].volumes.find(vol => vol.servicePath === '/engine.jwt')).destinationPath + volumes.push(new ServiceVolume(elJWTDir, JWTDir)) + } // eth1 nodes const eth1Nodes = (executionClients.map(client => { return client.buildExecutionClientEngineRPCHttpEndpointUrl() })).join() diff --git a/launcher/src/backend/ethereum-services/NimbusBeaconService.js b/launcher/src/backend/ethereum-services/NimbusBeaconService.js index 46105c2c8..2ff65c732 100644 --- a/launcher/src/backend/ethereum-services/NimbusBeaconService.js +++ b/launcher/src/backend/ethereum-services/NimbusBeaconService.js @@ -7,7 +7,6 @@ export class NimbusBeaconService extends NodeService { const service = new NimbusBeaconService() service.setId() const workingDir = service.buildWorkingDir(dir) - const elJWTDir = (executionClients[0].volumes.find(vol => vol.servicePath === '/engine.jwt')).destinationPath const image = 'statusim/nimbus-eth2' @@ -21,9 +20,13 @@ export class NimbusBeaconService extends NodeService { new ServiceVolume(workingDir + '/beacon', dataDir), new ServiceVolume(workingDir + '/validator/validators', validatorsDir), new ServiceVolume(workingDir + '/validator/secrets', secretsDir), - new ServiceVolume(elJWTDir, JWTDir), ] + if(executionClients && executionClients.length > 0){ + const elJWTDir = (executionClients[0].volumes.find(vol => vol.servicePath === '/engine.jwt')).destinationPath + volumes.push(new ServiceVolume(elJWTDir, JWTDir)) + } + service.init( 'NimbusBeaconService', //service service.id, // id, diff --git a/launcher/src/backend/ethereum-services/NodeService.js b/launcher/src/backend/ethereum-services/NodeService.js index db4f2221c..0a1c8ae6d 100644 --- a/launcher/src/backend/ethereum-services/NodeService.js +++ b/launcher/src/backend/ethereum-services/NodeService.js @@ -37,7 +37,7 @@ export class NodeService { this.imageVersion = imageVersion this.command = (command === undefined || command === null) ? [] : command this.entrypoint = (entrypoint === undefined || entrypoint === null) ? [] : entrypoint - this.env = (env === undefined || env === null) ? { STEREUM_DUMMY: 'foobar' } : env + this.env = (env === undefined || env === null) ? {} : env this.ports = (ports === undefined || ports === null) ? [] : ports this.volumes = (volumes === undefined || volumes === null) ? [] : volumes this.user = (user === undefined || user === null) ? '2000' : user diff --git a/launcher/src/backend/ethereum-services/PrysmBeaconService.js b/launcher/src/backend/ethereum-services/PrysmBeaconService.js index 86fdc2f38..bdd3d912b 100644 --- a/launcher/src/backend/ethereum-services/PrysmBeaconService.js +++ b/launcher/src/backend/ethereum-services/PrysmBeaconService.js @@ -7,7 +7,6 @@ export class PrysmBeaconService extends NodeService { const service = new PrysmBeaconService() service.setId() const workingDir = service.buildWorkingDir(dir) - const elJWTDir = (executionClients[0].volumes.find(vol => vol.servicePath === '/engine.jwt')).destinationPath const image = 'prysmaticlabs/prysm-beacon-chain' @@ -20,9 +19,13 @@ export class PrysmBeaconService extends NodeService { const volumes = [ new ServiceVolume(workingDir + '/beacon', dataDir), new ServiceVolume(workingDir + '/genesis', genesisDir), - new ServiceVolume(elJWTDir, JWTDir) ] + if(executionClients && executionClients.length > 0){ + const elJWTDir = (executionClients[0].volumes.find(vol => vol.servicePath === '/engine.jwt')).destinationPath + volumes.push(new ServiceVolume(elJWTDir, JWTDir)) + } + let genesisFile = ' --genesis-state=/opt/app/genesis/prysm-prater-genesis.ssz' if(network === "mainnet"){ genesisFile = '' diff --git a/launcher/src/backend/ethereum-services/TekuBeaconService.js b/launcher/src/backend/ethereum-services/TekuBeaconService.js index 49e9e4335..27ec18a03 100644 --- a/launcher/src/backend/ethereum-services/TekuBeaconService.js +++ b/launcher/src/backend/ethereum-services/TekuBeaconService.js @@ -7,7 +7,6 @@ export class TekuBeaconService extends NodeService { const service = new TekuBeaconService() service.setId() const workingDir = service.buildWorkingDir(dir) - const elJWTDir = (executionClients[0].volumes.find(vol => vol.servicePath === '/engine.jwt')).destinationPath const image = 'consensys/teku' @@ -19,10 +18,14 @@ export class TekuBeaconService extends NodeService { const volumes = [ new ServiceVolume(workingDir + '/data', dataDir), - new ServiceVolume(elJWTDir, JWTDir), new ServiceVolume(workingDir + '/graffitis', graffitiDir) ] + if(executionClients && executionClients.length > 0){ + const elJWTDir = (executionClients[0].volumes.find(vol => vol.servicePath === '/engine.jwt')).destinationPath + volumes.push(new ServiceVolume(elJWTDir, JWTDir)) + } + service.init( 'TekuBeaconService', // service service.id, // id diff --git a/launcher/src/components/UI/node-header/MainNavbar.vue b/launcher/src/components/UI/node-header/MainNavbar.vue index d4cf872ec..86792e2fe 100644 --- a/launcher/src/components/UI/node-header/MainNavbar.vue +++ b/launcher/src/components/UI/node-header/MainNavbar.vue @@ -56,6 +56,7 @@ export default { }, methods: { refreshServiceStates: async function () { + const allServices = JSON.parse(JSON.stringify(this.allServices)); if (await this.checkConnection()) { if (this.refresh) { let services = await ControlService.refreshServiceInfos(); @@ -77,7 +78,7 @@ export default { s.config.serviceID === service.config.serviceID ); } else { - oldService = this.allServices.find( + oldService = allServices.find( (s) => s.service === service.service ); needForTunnel.push(oldService); @@ -92,10 +93,16 @@ export default { } oldService.state = service.state; if (oldService.name === "Teku" || oldService.name === "Nimbus") { - let vs = this.allServices.find( - (element) => - element.service === oldService.name + "ValidatorService" - ); + let existing = this.installedServices.find(s => s.config.serviceID === oldService.config.serviceID && s.service === oldService.name + "ValidatorService") + let vs + if(existing){ + vs = existing + }else{ + vs = allServices.find( + (element) => + element.service === oldService.name + "ValidatorService" + ); + } vs.config = oldService.config; vs.state = oldService.state; otherServices.push(vs); @@ -103,10 +110,8 @@ export default { return oldService; }); this.installedServices = newServices.concat(otherServices).map((e,i) => {e.id = i;return e}); - let beaconService = this.installedServices.find( - (s) => s.category === "consensus" - ); - if (beaconService && beaconService.config.network === "mainnet") { + let networks = this.installedServices.map(s => s.config.network); + if (networks && networks.includes("mainnet") ) { this.network = "mainnet"; this.currentNetwork = this.networkList.find(item => item.network === "mainnet") } else { @@ -185,6 +190,9 @@ export default { this.isUpdateAvailable = false; if (response && services && services.length > 0) { services.forEach((service) => { + if(!response[service.network][service.service]) + service.network = "prater" + if(response[service.network][service.service]){ if ( service.imageVersion != response[service.network][service.service][ @@ -205,6 +213,7 @@ export default { }); console.log("Service Update Available!"); } + } }); } diff --git a/launcher/src/components/UI/node-manage/AddPanel.vue b/launcher/src/components/UI/node-manage/AddPanel.vue index e8a5b1840..91510ec7e 100644 --- a/launcher/src/components/UI/node-manage/AddPanel.vue +++ b/launcher/src/components/UI/node-manage/AddPanel.vue @@ -31,8 +31,8 @@
- -