Skip to content

Commit

Permalink
Prepare dappmanager for multi-service nimbus client (#2014)
Browse files Browse the repository at this point in the history
* Remove logic related to beacon-validator service

* Clean getEthConsClientApiUrl

* Remove unused arg

* Remove unneeded boolean

* Remove consensus settings code duplication

* Use new user settings

* Add TODOs

* Force recreate on network set

* Ensure nimbus connection

* Clean staker user settings

* Fix import

* Fix root networks

* Fix error on disconnecting restarting container

* Fix docker container disconnect

* Remove recreate containers function
  • Loading branch information
dappnodedev authored Sep 9, 2024
1 parent 0cd19c5 commit 081c80c
Show file tree
Hide file tree
Showing 11 changed files with 203 additions and 272 deletions.
11 changes: 6 additions & 5 deletions packages/daemons/src/ethMultiClient/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as db from "@dappnode/db";
import { eventBus } from "@dappnode/eventbus";
import { params } from "@dappnode/params";
import { runAtMostEvery, runOnlyOneSequentially, getConsensusUserSettings } from "@dappnode/utils";
import { runAtMostEvery, runOnlyOneSequentially, getDefaultConsensusUserSettings } from "@dappnode/utils";
import { logs } from "@dappnode/logger";
import {
EthClientRemote,
Expand Down Expand Up @@ -70,10 +70,11 @@ export async function runEthClientInstaller(
if (isConsensusClientMainnet(target))
await packageInstall(dappnodeInstaller, {
name: target,
userSettings: getConsensusUserSettings({
dnpName: target,
network: Network.Mainnet
})
userSettings: {
[target]: getDefaultConsensusUserSettings({
network: Network.Mainnet
})
}
});
else await packageInstall(dappnodeInstaller, { name: target });
} catch (e) {
Expand Down
42 changes: 41 additions & 1 deletion packages/dappmanager/src/calls/packageInstall.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Routes } from "@dappnode/types";
import { Network, Routes } from "@dappnode/types";
import { packageInstall as pkgInstall } from "@dappnode/installer";
import { dappnodeInstaller } from "../index.js";
import { Consensus } from "@dappnode/stakers";
import { logs } from "@dappnode/logger";

/**
* Installs a DAppNode Package.
Expand All @@ -26,4 +28,42 @@ export async function packageInstall({
userSettings,
options
});

ensureNimbusConnection(reqName);
}

/**
* Nimbus package will be migrated from a monoservice to a multiservice package.
* beacon-validator will be split into beacon-chain and validator services.
*
* This function ensures both services are properly connected to the staker network
* after installing the new version.
*
* TODO: Remove this once all Nimbus packages are multiservice
*/
function ensureNimbusConnection(dnpName: string): void {
if (!dnpName.includes("nimbus")) {
logs.debug("Not a Nimbus package, skipping network reconnection");
}

logs.info("Ensuring Nimbus services are connected to the staker network");

const consensus: Consensus = new Consensus(dappnodeInstaller);

const nimbusNetwork: Record<string, Network> = {
"nimbus.dnp.dappnode.eth": Network.Mainnet,
"nimbus-prater.dnp.dappnode.eth": Network.Prater,
"nimbus-gnosis.dnp.dappnode.eth": Network.Gnosis,
"nimbus-holesky.dnp.dappnode.eth": Network.Holesky
};

const network = nimbusNetwork[dnpName];

if (!network) {
logs.error("Could not determine the network for the Nimbus package");
return;
}

// Not awaited
consensus.persistSelectedConsensusIfInstalled(network);
}
43 changes: 23 additions & 20 deletions packages/installer/src/ethClient/apiUrl.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { buildNetworkAlias, getBeaconServiceName } from "@dappnode/utils";
import { buildNetworkAlias } from "@dappnode/utils";
import { listPackageNoThrow } from "@dappnode/dockerapi";

/**
Expand Down Expand Up @@ -27,27 +27,30 @@ export function getEthExecClientApiUrl(dnpName: string, port = 8545): string {
* @param dnpName
*/
export async function getEthConsClientApiUrl(dnpName: string): Promise<string> {
let port = 3500;
let domain = "";
const defaultPort = 3500;
const defaultServiceName = "beacon-chain";

// TODO: Use beacon-chain.<network>.dncore.dappnode

const dnp = await listPackageNoThrow({ dnpName });
if (dnp && typeof dnp.chain === "object" && dnp.chain.portNumber && dnp.chain.serviceName) {
port = dnp.chain.portNumber;
domain = buildNetworkAlias({
dnpName: dnpName,
serviceName: dnp.chain.serviceName,
isMainOrMonoservice: false
});
} else {
// Lighthouse, Teku and Prysm use 3500
// Nimbus uses 4500 because it is a monoservice and the validator API is using that port
if (dnpName.includes("nimbus")) {
port = 4500;
}
domain = buildNetworkAlias({
dnpName: dnpName,
serviceName: getBeaconServiceName(dnpName),

if (!dnp || typeof dnp.chain !== "object") {
const domain = buildNetworkAlias({
dnpName,
serviceName: defaultServiceName,
isMainOrMonoservice: false
});

return `http://${domain}:${defaultPort}`;
}
return `http://${domain}:${port}`;

const { chain: { portNumber = defaultPort, serviceName = defaultServiceName } = {} } = dnp;

const domain = buildNetworkAlias({
dnpName,
serviceName,
isMainOrMonoservice: false
});

return `http://${domain}:${portNumber}`;
}
12 changes: 7 additions & 5 deletions packages/installer/src/ethClient/ethereumClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Eth2ClientTarget, EthClientRemote, InstalledPackageDetailData } from "@
import * as db from "@dappnode/db";
import { eventBus } from "@dappnode/eventbus";
import { logs } from "@dappnode/logger";
import { getConsensusUserSettings } from "@dappnode/utils";
import { getDefaultConsensusUserSettings } from "@dappnode/utils";
import { packageInstall, packageGet, packageRemove } from "../calls/index.js";
import { ComposeFileEditor, parseServiceNetworks } from "@dappnode/dockercompose";
import { params } from "@dappnode/params";
Expand Down Expand Up @@ -317,10 +317,12 @@ export class EthereumClient {
});
if (!consClientPkg) {
// Get default cons client user settings and install cons client
const userSettings = getConsensusUserSettings({
dnpName: consClient,
network: Network.Mainnet
});
const userSettings = {
[consClient]: getDefaultConsensusUserSettings({
network: Network.Mainnet
})
};

await packageInstall(dappnodeInstaller, {
name: consClient,
userSettings
Expand Down
142 changes: 40 additions & 102 deletions packages/stakers/src/consensus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
import { StakerComponent } from "./stakerComponent.js";
import { DappnodeInstaller } from "@dappnode/installer";
import * as db from "@dappnode/db";
import { listPackageNoThrow } from "@dappnode/dockerapi";
import { params } from "@dappnode/params";
import { getDefaultConsensusUserSettings } from "@dappnode/utils";

// TODO: move ethereumClient logic here

Expand All @@ -30,13 +30,6 @@ export class Consensus extends StakerComponent {
[Network.Holesky]: db.consensusClientHolesky,
[Network.Lukso]: db.consensusClientLukso
};
protected static readonly DefaultCheckpointSync: Record<Network, string> = {
[Network.Mainnet]: "https://checkpoint-sync.dappnode.io",
[Network.Prater]: "https://checkpoint-sync-prater.dappnode.io",
[Network.Gnosis]: "https://checkpoint-sync-gnosis.dappnode.io",
[Network.Holesky]: "https://checkpoint-sync-holesky.dappnode.io",
[Network.Lukso]: "https://checkpoints.mainnet.lukso.network"
};
protected static readonly CompatibleConsensus: Record<Network, { dnpName: string; minVersion: string }[]> = {
[Network.Mainnet]: [
{ dnpName: ConsensusClientMainnet.Prysm, minVersion: "3.0.4" },
Expand Down Expand Up @@ -85,16 +78,19 @@ export class Consensus extends StakerComponent {
async persistSelectedConsensusIfInstalled(network: Network): Promise<void> {
const currentConsensusDnpName = this.DbHandlers[network].get();
if (currentConsensusDnpName) {
const isInstalled = Boolean(await listPackageNoThrow({ dnpName: currentConsensusDnpName }));
const isInstalled = await this.isPackageInstalled(currentConsensusDnpName);

if (!isInstalled) {
// update status in db
this.DbHandlers[network].set(undefined);
return;
}

const userSettings = await this.getUserSettings(network, currentConsensusDnpName);

await this.persistSelectedIfInstalled({
dnpName: currentConsensusDnpName,
userSettings: this.getUserSettings(currentConsensusDnpName, isInstalled, network)
userSettings
});
await this.DbHandlers[network].set(currentConsensusDnpName);
}
Expand All @@ -103,115 +99,57 @@ export class Consensus extends StakerComponent {
async setNewConsensus(network: Network, newConsensusDnpName: string | null) {
const prevConsClientDnpName = this.DbHandlers[network].get();

const userSettings = await this.getUserSettings(network, newConsensusDnpName);

await super.setNew({
newStakerDnpName: newConsensusDnpName,
dockerNetworkName: params.DOCKER_STAKER_NETWORKS[network],
compatibleClients: Consensus.CompatibleConsensus[network],
userSettings: newConsensusDnpName
? this.getUserSettings(
newConsensusDnpName,
!(await listPackageNoThrow({ dnpName: newConsensusDnpName })),
network
)
: {},
userSettings,
prevClient: prevConsClientDnpName
});
// persist on db
if (newConsensusDnpName !== prevConsClientDnpName) await this.DbHandlers[network].set(newConsensusDnpName);
}

private getUserSettings(newConsensusDnpName: string, shouldSetEnvironment: boolean, network: Network): UserSettings {
const validatorServiceName = this.getValidatorServiceName(newConsensusDnpName);
const beaconServiceName = this.getBeaconServiceName(newConsensusDnpName);
const defaultDappnodeGraffiti = "validating_from_DAppNode";
const defaultFeeRecipient = "0x0000000000000000000000000000000000000000";
private async getUserSettings(network: Network, newConsensusDnpName: string | null): Promise<UserSettings> {
if (!newConsensusDnpName) return {};

const isPkgInstalled = await this.isPackageInstalled(newConsensusDnpName);

const userSettings = {
// If the package is not installed, we use the default environment
environment: isPkgInstalled ? {} : getDefaultConsensusUserSettings({ network }).environment,
networks: this.getStakerNetworkSettings(network)
};

return userSettings;
}

private getStakerNetworkSettings(network: Network): UserSettings["networks"] {
const validatorServiceName = "validator";
const beaconServiceName = "beacon-chain";

return {
environment: shouldSetEnvironment
? beaconServiceName === validatorServiceName
? {
[validatorServiceName]: {
// Fee recipient is set as global env, keep this for backwards compatibility
["FEE_RECIPIENT_ADDRESS"]: defaultFeeRecipient, // TODO: consider setting the MEV fee recipient as the default
// Graffiti is a mandatory value
["GRAFFITI"]: defaultDappnodeGraffiti,
// Checkpoint sync is an optional value
["CHECKPOINT_SYNC_URL"]: Consensus.DefaultCheckpointSync[network]
}
}
: {
[validatorServiceName]: {
// Fee recipient is set as global env, keep this for backwards compatibility
["FEE_RECIPIENT_ADDRESS"]: defaultFeeRecipient,
// Graffiti is a mandatory value
["GRAFFITI"]: defaultDappnodeGraffiti
},

[beaconServiceName]: {
// Fee recipient is set as global env, keep this for backwards compatibility
["FEE_RECIPIENT_ADDRESS"]: defaultFeeRecipient,
// Checkpoint sync is an optional value
["CHECKPOINT_SYNC_URL"]: Consensus.DefaultCheckpointSync[network]
}
}
: {},
networks: {
rootNetworks: {
rootNetworks: this.getComposeRootNetworks(network),
serviceNetworks: {
[beaconServiceName]: {
[params.DOCKER_STAKER_NETWORKS[network]]: {
external: true
aliases: [`${beaconServiceName}.${network}.staker.dappnode`]
},
[params.DOCKER_PRIVATE_NETWORK_NAME]: {
external: true
aliases: [`${beaconServiceName}.${network}.dncore.dappnode`]
}
},
serviceNetworks:
beaconServiceName === validatorServiceName
? {
"beacon-validator": {
[params.DOCKER_STAKER_NETWORKS[network]]: {
aliases: [`beacon-chain.${network}.staker.dappnode`, `validator.${network}.staker.dappnode`]
},
[params.DOCKER_PRIVATE_NETWORK_NAME]: {
aliases: [`beacon-chain.${network}.dncore.dappnode`, `validator.${network}.dncore.dappnode`]
}
}
}
: {
"beacon-chain": {
[params.DOCKER_STAKER_NETWORKS[network]]: {
aliases: [`beacon-chain.${network}.staker.dappnode`]
},
[params.DOCKER_PRIVATE_NETWORK_NAME]: {
aliases: [`beacon-chain.${network}.dncore.dappnode`]
}
},
validator: {
[params.DOCKER_STAKER_NETWORKS[network]]: {
aliases: [`validator.${network}.staker.dappnode`]
},
[params.DOCKER_PRIVATE_NETWORK_NAME]: {
aliases: [`validator.${network}.dncore.dappnode`]
}
}
}
[validatorServiceName]: {
[params.DOCKER_STAKER_NETWORKS[network]]: {
aliases: [`${validatorServiceName}.${network}.staker.dappnode`]
},
[params.DOCKER_PRIVATE_NETWORK_NAME]: {
aliases: [`${validatorServiceName}.${network}.dncore.dappnode`]
}
}
}
};
}

/**
* Get the validator service name.
* - Nimbus package is monoservice (beacon-validator)
* - Prysm, Teku, Lighthouse, and Lodestar are multiservice (beacon, validator)
*/
private getValidatorServiceName(newConsensusDnpName: string | null): string {
return newConsensusDnpName ? (newConsensusDnpName.includes("nimbus") ? "beacon-validator" : "validator") : "";
}

/**
* Get the beacon service name
* - Nimbus package is monoservice (beacon-validator)
* - Prysm, Teku, Lighthouse, and Lodestar are multiservice (beacon, validator)
*/
private getBeaconServiceName(newConsensusDnpName: string | null): string {
return newConsensusDnpName ? (newConsensusDnpName.includes("nimbus") ? "beacon-validator" : "beacon-chain") : "";
}
}
Loading

0 comments on commit 081c80c

Please sign in to comment.