Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ADD: Manage Services #874

Merged
merged 4 commits into from
Nov 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions launcher/src/backend/OneClickInstall.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export class OneClickInstall {
}
}

async createServices(constellation, checkpointURL) {
async createServices(constellation, checkpointURL, relayURL) {
let ports = []
if (constellation.includes('GethService')) {
ports = [
Expand Down Expand Up @@ -155,7 +155,8 @@ export class OneClickInstall {

if (constellation.includes('FlashbotsMevBoostService')) {
//FlashbotsMevBoostService
this.mevboost = FlashbotsMevBoostService.buildByUserInput(this.networkHandler(true))
log.info(relayURL)
this.mevboost = FlashbotsMevBoostService.buildByUserInput(this.networkHandler(true), relayURL)
}

if (constellation.includes('LighthouseBeaconService')) {
Expand Down Expand Up @@ -200,7 +201,6 @@ export class OneClickInstall {
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', 5052, 5052, servicePortProtocol.tcp)
]
this.beaconService = NimbusBeaconService.buildByUserInput(this.networkHandler(false), ports, this.installDir + '/nimbus', [this.executionClient], this.mevboost ? [this.mevboost] : [], checkpointURL)
Expand Down
250 changes: 223 additions & 27 deletions launcher/src/backend/ServiceManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ 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 { ServicePort, servicePortProtocol, changeablePorts } from './ethereum-services/ServicePort'
import { StringUtils } from './StringUtils'
import { ServiceVolume } from './ethereum-services/ServiceVolume'

const log = require('electron-log')

Expand Down Expand Up @@ -171,6 +172,167 @@ export class ServiceManager {
}
}

changePort(service, port){
let portIndex = service.ports.findIndex(p => p.servicePort == changeablePorts[service.service])
if(portIndex != -1){
service.ports[portIndex].destinationPort = port
}
return service
}

async modifyServices(tasks, services){
let modifiedServices = []

tasks.forEach(task => {
let service = services.find(s => s.id === task.service.config.serviceID)
let dependencies = task.data.executionClients.concat(task.data.beaconServices).map(s => services.find(e => e.id === s.config.serviceID))

if(service.service === "FlashbotsMevBoostService"){
service.entrypoint[service.entrypoint.findIndex(e => e === "-relays")+1] = task.data.relays
modifiedServices.push(service)
let dependenciesToRemove = (services.filter(s => s.dependencies.mevboost.map(m => m.id).includes(service.id))).filter(m => !dependencies.map(d => d.id).includes(m.id))
dependenciesToRemove.forEach(dependency => {
modifiedServices.push(this.removeDependencies(dependency,service))
})
}

if(task.data.port){
service = this.changePort(service, task.data.port)
}
let updated = this.addDependencies(service, dependencies)
if(!Array.isArray(updated))
updated = [updated]
updated.forEach(dep => {
let index = modifiedServices.findIndex(s => s.id === dep.id)
if(index != -1){
modifiedServices[index] = dep
}else{
modifiedServices.push(dep)
}
})
})

await Promise.all(modifiedServices.map(async (service) => {
await this.nodeConnection.writeServiceConfiguration(service.buildConfiguration())
}))

}

addDependencies(service, dependencies) {
let command = ""
let filter

switch(service.service.replace(/(Beacon|Validator|Service)/gm, '')){
case "Lighthouse":
if(service.service.includes("Beacon")){
filter = (e) => e.buildExecutionClientEngineRPCHttpEndpointUrl()
command = "--execution-endpoint="
}
if(service.service.includes("Validator")){
filter = (e) => e.buildConsensusClientHttpEndpointUrl()
command = "--beacon-nodes="
}
break;
case "Prysm":
if(service.service.includes("Beacon")){
filter = (e) => e.buildExecutionClientEngineRPCHttpEndpointUrl()
command = "--execution-endpoint="
}
if(service.service.includes("Validator")){
filter = (e) => e.buildConsensusClientEndpoint()
command = "--beacon-rpc-provider="
service.command = this.addCommandConnection(service, command, dependencies, filter)
filter = (e) => e.buildConsensusClientGateway()
command = "--beacon-rpc-gateway-provider="
}
break;
case "Nimbus":
filter = (e) => e.buildExecutionClientEngineRPCWsEndpointUrl()
command = "--web3-url="
break;
case "Teku":
filter = (e) => e.buildExecutionClientEngineRPCHttpEndpointUrl()
command = "--ee-endpoint="
break;
case "FlashbotsMevBoost":
return dependencies.map(client => {
client.command = this.addMevBoostConnection(service, client)
client.dependencies.mevboost.push(service)
return client
})
}
service.command = this.addCommandConnection(service, command, dependencies, filter)

if(service.service.includes("Beacon")){
service.dependencies.executionClients = dependencies
let volumes = dependencies.map(client => new ServiceVolume(client.volumes.find(vol => vol.servicePath === '/engine.jwt').destinationPath, '/engine.jwt'))
service.volumes = service.volumes.filter(v => v.destinationPath.includes(service.id))
service.volumes = service.volumes.concat(volumes)
} else if(service.service.includes("Validator")){
service.dependencies.executionClients = dependencies
}
return service
}

addCommandConnection(service, endpointCommand, dependencies, filter){
let isString = false
let command = service.command
if (typeof command === "string") {
isString = true
command = command.replaceAll(/\n/gm, '').replaceAll(/\s\s+/gm, ' ').split(' ')
}
log.info(command)
let fullCommand = command.find(c => c.includes(endpointCommand))
command = command.filter(c => !c.includes(endpointCommand))
let newProps
if(fullCommand){
newProps = [this.formatCommand(fullCommand, endpointCommand, filter, dependencies)]
}else{
newProps = endpointCommand + dependencies.map(filter).join()
}
if (isString){
return (command.concat(newProps)).join(' ').trim()
}
return command.concat(newProps)
}

addMevBoostConnection(service, dependency){
let command = dependency.command
let isString = false
if (typeof command === "string") {
isString = true
command = command.replaceAll(/\n/gm, '').replaceAll(/\s\s+/gm, ' ').split(' ')
}
let builderCommand = ""
switch(dependency.service){
case'LighthouseBeaconService':
builderCommand = "--builder="
break;
case'PrysmBeaconService':
builderCommand = "--http-mev-relay="
break;
case'NimbusBeaconService':
command.push('--payload-builder=true')
builderCommand = "--payload-builder-url="
break;
case'TekuBeaconService':
builderCommand = "--builder-endpoint="
break;

}
let fullCommand = command.find(c => c.includes(builderCommand))
command = command.filter(c => !c.includes(builderCommand))
if(fullCommand){
fullCommand = this.formatCommand(fullCommand, builderCommand, undefined, [service.buildMevboostEndpointURL()])
} else {
fullCommand = builderCommand + service.buildMevboostEndpointURL()
}

if (isString)
return (command.concat([fullCommand])).join(' ')
return command.concat([fullCommand])
}

removeDependencies(service, serviceToDelete) {
//update command
service.command = this.removeCommandConnection(service.command, serviceToDelete.id)
Expand All @@ -196,22 +358,37 @@ export class ServiceManager {

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
})
return this.formatCommand(c,command,(e) => !e.includes(id))
}).filter(c => c !== undefined)

if (isString)
return (command.concat(newProps)).join(' ')
return command.concat(newProps)
}

formatCommand(fullCommand, command, filter, dependencies) {
let value = fullCommand.replace(command, '')
let quotes = false
if (value.startsWith('"') && value.endsWith('"')) {
quotes = true
value = value.substring(1, value.length - 1)
}
let newValue
if(dependencies && filter){
newValue = dependencies.map(filter).join()
} else if (dependencies){
newValue = value.split(',').concat(dependencies).join()
} else {
newValue = value.split(',').filter(filter).join()
}
if(!newValue){
return undefined
}
if (quotes)
newValue = '"' + newValue + '"'
return command + newValue
}

async deleteService(task, tasks, services) {
let serviceToDelete = services.find(service => service.id === task.service.config.serviceID)
let dependents = []
Expand All @@ -223,7 +400,6 @@ export class ServiceManager {
})
}
})
log.info(dependents)
dependents.forEach(service => {
service = this.removeDependencies(service, serviceToDelete)
this.nodeConnection.writeServiceConfiguration(service.buildConfiguration())
Expand Down Expand Up @@ -264,7 +440,7 @@ export class ServiceManager {
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)
return LighthouseBeaconService.buildByUserInput(args.network, ports, args.installDir + '/lighthouse', args.executionClients, '16', [], args.checkpointURL)

case "LighthouseValidatorService":
ports = [
Expand All @@ -279,7 +455,7 @@ export class ServiceManager {
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)
return PrysmBeaconService.buildByUserInput(args.network, ports, args.installDir + '/prysm', args.executionClients, [], args.checkpointURL)

case "PrysmValidatorService":
ports = [
Expand All @@ -291,19 +467,18 @@ export class ServiceManager {
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)
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)
new ServicePort('127.0.0.1', 5052, 5052, servicePortProtocol.tcp),
new ServicePort('127.0.0.1', args.port ? args.port : 5051, 5051, servicePortProtocol.tcp)
]
return TekuBeaconService.buildByUserInput(args.network, ports, args.installDir + '/teku', args.executionClients, args.checkpointURL)
return TekuBeaconService.buildByUserInput(args.network, ports, args.installDir + '/teku', args.executionClients, [], args.checkpointURL)

case "PrometheusNodeExporterService":
return PrometheusNodeExporterService.buildByUserInput(args.network)
Expand All @@ -321,15 +496,14 @@ export class ServiceManager {
return GrafanaService.buildByUserInput(args.network, ports, args.installDir + '/grafana')

case "FlashbotsMevBoostService":
return FlashbotsMevBoostService.buildByUserInput(args.network)
return FlashbotsMevBoostService.buildByUserInput(args.network, args.relays)
}
}

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`)
Expand Down Expand Up @@ -386,7 +560,26 @@ export class ServiceManager {
})
let PInstalls = tasks.filter(t => t.service.category === "service")
PInstalls.forEach(t => {
if(t.data.beaconServices.length > 0 && t.service.service === "FlashbotsMevBoostService"){
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)
let changed = this.addDependencies(service, t.data.beaconServices)
changed.forEach(dep => {
let index = newServices.findIndex(s => s.id === dep.id)
if(index != -1){
newServices[index] = dep
}else{
newServices.push(dep)
}
})
newServices.push(service)
})

Expand Down Expand Up @@ -429,16 +622,13 @@ export class ServiceManager {
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
}

async modifyServices(tasks) {
async handleServiceChanges(tasks) {
let jobs = tasks.map(t => t.content)
if (jobs.includes("DELETE")) {
let services = await this.readServiceConfigurations()
Expand All @@ -451,10 +641,16 @@ export class ServiceManager {
let after = this.nodeConnection.getTimeStamp()
await this.nodeConnection.restartServices(after - before)
}
} else if (jobs.includes("INSTALL")) {
}
if (jobs.includes("INSTALL")) {
let services = await this.readServiceConfigurations()
let installTasks = tasks.filter(t => t.content === "INSTALL")
await this.addServices(installTasks, services)
}
if (jobs.includes("MODIFY")) {
let services = await this.readServiceConfigurations()
let modifyTasks = tasks.filter(t => t.content === "MODIFY")
await this.modifyServices(modifyTasks, services)
}
}
}
Loading