diff --git a/packages/daemons/src/dockerNetworkConfigs/createStakerNetworkAndConnectStakerPkgs.ts b/packages/daemons/src/dockerNetworkConfigs/createStakerNetworkAndConnectStakerPkgs.ts index 3c7caa63e3..d3f1fbab32 100644 --- a/packages/daemons/src/dockerNetworkConfigs/createStakerNetworkAndConnectStakerPkgs.ts +++ b/packages/daemons/src/dockerNetworkConfigs/createStakerNetworkAndConnectStakerPkgs.ts @@ -2,10 +2,12 @@ import { docker } from "@dappnode/dockerapi"; import { params } from "@dappnode/params"; import { logs } from "@dappnode/logger"; import { Execution, Consensus, Signer, MevBoost } from "@dappnode/stakers"; -import { Network } from "@dappnode/types"; +import { L1_NETWORKS } from "@dappnode/types"; /** * Creates the staker network and connects the staker packages to it + * Note: Only L1 networks are handled here. L2 networks have their own setup. + * TODO: should L2 networks be handled here as well? */ export async function createStakerNetworkAndConnectStakerPkgs( execution: Execution, @@ -13,8 +15,8 @@ export async function createStakerNetworkAndConnectStakerPkgs( signer: Signer, mevBoost: MevBoost ): Promise { - for (const network of Object.values(Network)) { - await createDockerStakerNetwork(params.DOCKER_STAKER_NETWORKS[network]); + for (const network of L1_NETWORKS) { + await createDockerStakerNetwork(params.DOCKER_BLOCKCHAIN_NETWORKS[network]); const results = await Promise.allSettled([ execution.persistSelectedExecutionIfInstalled(network), consensus.persistSelectedConsensusIfInstalled(network), diff --git a/packages/dappmanager/src/api/routes/metrics.ts b/packages/dappmanager/src/api/routes/metrics.ts index 5d2262fc21..32a103d95e 100644 --- a/packages/dappmanager/src/api/routes/metrics.ts +++ b/packages/dappmanager/src/api/routes/metrics.ts @@ -3,7 +3,7 @@ import { wrapHandler } from "../utils.js"; import * as db from "@dappnode/db"; import { listPackageNoThrow } from "@dappnode/dockerapi"; import { isEmpty } from "lodash-es"; -import { Network } from "@dappnode/types"; +import { L1_NETWORKS } from "@dappnode/types"; import { getHostInfoMemoized } from "@dappnode/hostscriptsservices"; import si from "systeminformation"; import { mevBoost, execution, consensus } from "../../index.js"; @@ -69,7 +69,7 @@ register.registerMetric( return 0; } - for (const network of ["mainnet", "prater", "gnosis", "lukso", "holesky", "hoodi", "sepolia"] as Network[]) { + for (const network of L1_NETWORKS) { const isMevBoostSelected = mevBoost.DbHandlers[network].get(); const executionClient = execution.DbHandlers[network].get(); const consensusClient = consensus.DbHandlers[network].get(); diff --git a/packages/dappmanager/src/calls/consensusClientGet.ts b/packages/dappmanager/src/calls/consensusClientGet.ts index 54ea78999e..f1c23b62fe 100644 --- a/packages/dappmanager/src/calls/consensusClientGet.ts +++ b/packages/dappmanager/src/calls/consensusClientGet.ts @@ -7,10 +7,10 @@ import { consensusClientPrater, consensusClientSepolia } from "@dappnode/db"; -import { Network } from "@dappnode/types"; +import { Network, L1Network, isL1Network } from "@dappnode/types"; -// Mapping of each network to its corresponding consensus client getter -const consensusClientMap: { [key in Network]: () => string | null | undefined } = { +// Mapping of each L1 network to its corresponding consensus client getter +const consensusClientMap: { [key in L1Network]: () => string | null | undefined } = { mainnet: () => consensusClientMainnet.get(), gnosis: () => consensusClientGnosis.get(), hoodi: () => consensusClientHoodi.get(), @@ -28,8 +28,11 @@ export async function consensusClientsGetByNetworks({ const result: Partial> = {}; for (const network of networks) { - const getter = consensusClientMap[network]; - result[network] = getter ? getter() : undefined; + if (isL1Network(network)) { + const getter = consensusClientMap[network]; + result[network] = getter ? getter() : undefined; + } + // L2 networks don't have separate consensus clients, skip them } return result; diff --git a/packages/dappmanager/src/calls/packageInstall.ts b/packages/dappmanager/src/calls/packageInstall.ts index fb03cf0c29..a0f6813120 100644 --- a/packages/dappmanager/src/calls/packageInstall.ts +++ b/packages/dappmanager/src/calls/packageInstall.ts @@ -1,4 +1,4 @@ -import { Network, Routes } from "@dappnode/types"; +import { Network, L1Network, Routes } from "@dappnode/types"; import { packageInstall as pkgInstall } from "@dappnode/installer"; import { dappnodeInstaller } from "../index.js"; import { Consensus } from "@dappnode/stakers"; @@ -53,7 +53,7 @@ async function ensureNimbusConnection(dnpName: string): Promise { const consensus: Consensus = new Consensus(dappnodeInstaller); - const nimbusNetwork: Record = { + const nimbusNetwork: Record = { "nimbus.dnp.dappnode.eth": Network.Mainnet, "nimbus-prater.dnp.dappnode.eth": Network.Prater, "nimbus-gnosis.dnp.dappnode.eth": Network.Gnosis, diff --git a/packages/dappmanager/src/calls/stakerConfig.ts b/packages/dappmanager/src/calls/stakerConfig.ts index 77f2dabbcb..81f2c633ad 100644 --- a/packages/dappmanager/src/calls/stakerConfig.ts +++ b/packages/dappmanager/src/calls/stakerConfig.ts @@ -1,12 +1,18 @@ -import { StakerConfigGet, StakerConfigSet, Network } from "@dappnode/types"; +import { StakerConfigGet, StakerConfigSet, Network, isL1Network } from "@dappnode/types"; import { execution, consensus, mevBoost, signer } from "../index.js"; /** * Sets the staker configuration: execution and consensus clients, remote signer, * mev boost, graffiti, fee recipient address and checkpoint sync url + * Note: This only works for L1 networks */ export async function stakerConfigSet({ stakerConfig }: { stakerConfig: StakerConfigSet }): Promise { const { network, executionDnpName, consensusDnpName, mevBoostDnpName, relays, web3signerDnpName } = stakerConfig; + + if (!isL1Network(network)) { + throw new Error(`stakerConfigSet is only supported for L1 networks. Got: ${network}`); + } + await Promise.all([ await execution.setNewExecution(network, executionDnpName), await consensus.setNewConsensus(network, consensusDnpName), @@ -22,8 +28,13 @@ export async function stakerConfigSet({ stakerConfig }: { stakerConfig: StakerCo /** * Returns the current staker configuration: execution and consensus clients, * remote signer, mev boost, graffiti, fee recipient address and checkpoint sync url + * Note: This only works for L1 networks */ export async function stakerConfigGet({ network }: { network: Network }): Promise { + if (!isL1Network(network)) { + throw new Error(`stakerConfigGet is only supported for L1 networks. Got: ${network}`); + } + return await Promise.all([ await execution.getAllExecutions(network), await consensus.getAllConsensus(network), diff --git a/packages/db/src/stakerConfig.ts b/packages/db/src/stakerConfig.ts index 7c18c24410..3dc0f2bcd1 100644 --- a/packages/db/src/stakerConfig.ts +++ b/packages/db/src/stakerConfig.ts @@ -179,3 +179,22 @@ export const mevBoostLukso = interceptGlobalEnvOnSet( dbMain.staticKey(MEVBOOST_LUKSO, false), Object.keys({ MEVBOOST_LUKSO })[0] ); + +// Starknet (L2) + +const STARKNET_NODE = "starknet-node"; +const STARKNET_SIGNER = "starknet-signer"; + +// Null means not set +// Undefined means its set but the user has not selected any value +export const starknetNode = interceptGlobalEnvOnSet( + dbMain.staticKey(STARKNET_NODE, null), + Object.keys({ STARKNET_NODE })[0] +); + +// Null means not set +// Undefined means its set but the user has not selected any value +export const starknetSigner = interceptGlobalEnvOnSet( + dbMain.staticKey(STARKNET_SIGNER, null), + Object.keys({ STARKNET_SIGNER })[0] +); diff --git a/packages/params/src/params.ts b/packages/params/src/params.ts index 1704475ef3..f00d562f4a 100644 --- a/packages/params/src/params.ts +++ b/packages/params/src/params.ts @@ -122,14 +122,23 @@ export const params = { DOCKER_NETWORK_NEW_SUBNET: "10.20.0.0/24", DOCKER_PRIVATE_NETWORK_NEW_NAME: "dnprivate_network", DOCKER_EXTERNAL_NETWORK_NAME: "dnpublic_network", - DOCKER_STAKER_NETWORKS: { + // Blockchain-specific Docker networks (formerly DOCKER_STAKER_NETWORKS) + // Used by both L1 stakers and L2 nodes + DOCKER_BLOCKCHAIN_NETWORKS: { + // L1 Networks [Network.Mainnet]: "mainnet_network", [Network.Holesky]: "holesky_network", [Network.Hoodi]: "hoodi_network", [Network.Sepolia]: "sepolia_network", [Network.Prater]: "prater_network", [Network.Gnosis]: "gnosis_network", - [Network.Lukso]: "lukso_network" + [Network.Lukso]: "lukso_network", + // L2 Networks + [Network.Starknet]: "starknet_network" + }, + /** @deprecated Use DOCKER_BLOCKCHAIN_NETWORKS instead */ + get DOCKER_STAKER_NETWORKS() { + return this.DOCKER_BLOCKCHAIN_NETWORKS; }, DOCKER_LEGACY_DNS: "172.33.1.2", BIND_IP: "172.33.1.2", diff --git a/packages/stakers/src/stakerComponent.ts b/packages/stakers/src/core/BlockchainComponent.ts similarity index 63% rename from packages/stakers/src/stakerComponent.ts rename to packages/stakers/src/core/BlockchainComponent.ts index ade14c288a..93086dcc9e 100644 --- a/packages/stakers/src/stakerComponent.ts +++ b/packages/stakers/src/core/BlockchainComponent.ts @@ -7,13 +7,38 @@ import { getIsInstalled, getIsUpdated, getIsRunning, fileToGatewayUrl } from "@d import { lt } from "semver"; import { params } from "@dappnode/params"; -export class StakerComponent { +/** + * Compatible client definition used for version validation + */ +export interface CompatibleClient { + dnpName: string; + minVersion: string; +} + +/** + * BlockchainComponent is the base class for all blockchain-related package management. + * It provides generic infrastructure for: + * - Installing and configuring packages + * - Managing Docker network connections + * - Package lifecycle (start, stop, recreate) + * - Version compatibility checks + * + * This class is network-agnostic and can be extended for: + * - L1 staking components (Consensus, Execution, MevBoost, Signer) + * - L2 components (Starknet, etc.) + * - Any other blockchain packages that need network connectivity + */ +export class BlockchainComponent { protected dappnodeInstaller: DappnodeInstaller; constructor(dappnodeInstaller: DappnodeInstaller) { this.dappnodeInstaller = dappnodeInstaller; } + /** + * Get all available packages for a list of DNP names + * Returns status information for each package (installed, running, etc.) + */ protected async getAll({ dnpNames, currentClient, @@ -52,27 +77,28 @@ export class StakerComponent { ); } + /** + * Set a new blockchain package, handling the transition from previous to new client + * - Validates compatibility requirements + * - Disconnects previous client from network + * - Configures and starts new client + */ protected async setNew({ - newStakerDnpName, + newClientDnpName, dockerNetworkName, fullnodeAliases, compatibleClients, userSettings, prevClient }: { - newStakerDnpName: string | null | undefined; + newClientDnpName: string | null | undefined; dockerNetworkName: string; fullnodeAliases: string[]; - compatibleClients: - | { - dnpName: string; - minVersion: string; - }[] - | null; + compatibleClients: CompatibleClient[] | null; userSettings: UserSettings; prevClient?: string | null; }): Promise { - if (!prevClient && !newStakerDnpName) { + if (!prevClient && !newClientDnpName) { logs.info("no client selected, skipping"); return; } @@ -84,28 +110,41 @@ export class StakerComponent { if (currentPkg) { if (prevClient && compatibleClients) this.ensureCompatibilityRequirements(prevClient, compatibleClients, currentPkg.version); - if (prevClient !== newStakerDnpName) - await this.unsetStakerPkgConfig({ pkg: currentPkg, dockerNetworkName, fullnodeAliases }); + if (prevClient !== newClientDnpName) + await this.unsetPkgConfig({ pkg: currentPkg, dockerNetworkName, fullnodeAliases }); } - if (!newStakerDnpName) return; - // set staker config - await this.setStakerPkgConfig({ - dnpName: newStakerDnpName, - isInstalled: Boolean(await listPackageNoThrow({ dnpName: newStakerDnpName })), + if (!newClientDnpName) return; + // set package config + await this.setPkgConfig({ + dnpName: newClientDnpName, + isInstalled: Boolean(await listPackageNoThrow({ dnpName: newClientDnpName })), userSettings }); } + /** + * Check if a package is installed + */ protected async isPackageInstalled(dnpName: string): Promise { const dnp = await listPackageNoThrow({ dnpName }); - return Boolean(dnp); } + /** + * Get the Docker network name for a given network + */ + protected getDockerNetworkName(network: Network): string { + return params.DOCKER_BLOCKCHAIN_NETWORKS[network]; + } + + /** + * Build the root networks configuration for docker-compose + * Includes the blockchain-specific network and the private DAppNode network + */ protected getComposeRootNetworks(network: Network): NonNullable["rootNetworks"] { return { - [params.DOCKER_STAKER_NETWORKS[network]]: { + [this.getDockerNetworkName(network)]: { external: true }, [params.DOCKER_PRIVATE_NETWORK_NAME]: { @@ -115,13 +154,11 @@ export class StakerComponent { } /** - * Set the staker pkg: - * - ensures the staker pkg is installed - * - connects the staker pkg to the staker network - * - adds the staker network to the docker-compose file - * - starts the staker pkg + * Configure and start a blockchain package + * - If already installed: applies user settings and starts containers + * - If not installed: installs the package with user settings */ - protected async setStakerPkgConfig({ + protected async setPkgConfig({ dnpName, isInstalled, userSettings @@ -131,7 +168,7 @@ export class StakerComponent { userSettings: UserSettings; }): Promise { if (isInstalled) { - await this.setInstalledStakerPkgConfig({ dnpName, userSettings }); + await this.setInstalledPkgConfig({ dnpName, userSettings }); } else { await packageInstall(this.dappnodeInstaller, { name: dnpName, @@ -140,7 +177,10 @@ export class StakerComponent { } } - private async setInstalledStakerPkgConfig({ + /** + * Apply settings to an already installed package and start it + */ + private async setInstalledPkgConfig({ dnpName, userSettings }: { @@ -159,17 +199,16 @@ export class StakerComponent { await dockerComposeUpPackage({ composeArgs: { dnpName }, upAll: true - // dockerComposeUpOptions: { noRecreate: true } }); } /** - * Unset staker pkg: - * - disconnects the staker pkg from the staker network - * - stops the staker pkg - * - removes the staker network from the docker-compose file + * Disconnect and stop a blockchain package + * - Removes package from Docker network + * - Removes network aliases + * - Recreates containers in stopped state */ - private async unsetStakerPkgConfig({ + protected async unsetPkgConfig({ pkg, dockerNetworkName, fullnodeAliases @@ -178,8 +217,8 @@ export class StakerComponent { dockerNetworkName: string; fullnodeAliases: string[]; }): Promise { - this.removeStakerNetworkFromCompose(pkg.dnpName, dockerNetworkName); - this.removeFullnodeAliasFromDncoreNetwork(pkg.dnpName, fullnodeAliases); + this.removeNetworkFromCompose(pkg.dnpName, dockerNetworkName); + this.removeAliasesFromPrivateNetwork(pkg.dnpName, fullnodeAliases); // This recreates the package containers so that they include the recently added configuration // The flag --no-start is added so that the containers remain stopped after recreation @@ -190,7 +229,10 @@ export class StakerComponent { }); } - private removeFullnodeAliasFromDncoreNetwork(dnpName: string, fullnodeAliases: string[]): void { + /** + * Remove specific aliases from the private DAppNode network in docker-compose + */ + private removeAliasesFromPrivateNetwork(dnpName: string, aliasesToRemove: string[]): void { const composeEditor = new ComposeFileEditor(dnpName, false); const services = composeEditor.compose.services; @@ -202,7 +244,7 @@ export class StakerComponent { for (const [networkName, networkSettings] of Object.entries(serviceNetworks)) { if (networkName === params.DOCKER_PRIVATE_NETWORK_NAME) { const aliases = networkSettings.aliases; - if (aliases) networkSettings.aliases = aliases.filter((alias) => !fullnodeAliases.includes(alias)); + if (aliases) networkSettings.aliases = aliases.filter((alias) => !aliasesToRemove.includes(alias)); } } } @@ -210,7 +252,10 @@ export class StakerComponent { composeEditor.write(); } - private removeStakerNetworkFromCompose(dnpName: string, dockerNetworkName: string): void { + /** + * Remove a network from the docker-compose file (both root level and service level) + */ + private removeNetworkFromCompose(dnpName: string, dockerNetworkName: string): void { const composeEditor = new ComposeFileEditor(dnpName, false); const services = composeEditor.compose.services; @@ -232,12 +277,13 @@ export class StakerComponent { composeEditor.write(); } - private ensureCompatibilityRequirements( + /** + * Validate that a package meets compatibility requirements + * Throws if the package version is below the minimum required + */ + protected ensureCompatibilityRequirements( dnpName: string, - compatibleClients: { - dnpName: string; - minVersion: string; - }[], + compatibleClients: CompatibleClient[], pkgVersion: string ): void { if (!dnpName) return; @@ -245,12 +291,12 @@ export class StakerComponent { const compatibleClient = compatibleClients.find((c) => c.dnpName === dnpName); // ensure valid dnpName - if (!compatibleClient) throw Error("The selected staker is not compatible with the current network"); + if (!compatibleClient) throw Error("The selected client is not compatible with the current network"); // ensure valid version if (compatibleClient?.minVersion && lt(pkgVersion, compatibleClient.minVersion)) { throw Error( - `The selected staker version from ${dnpName} is not compatible with the current network. Required version: ${compatibleClient.minVersion}. Got: ${pkgVersion}` + `The selected client version from ${dnpName} is not compatible with the current network. Required version: ${compatibleClient.minVersion}. Got: ${pkgVersion}` ); } } diff --git a/packages/stakers/src/core/index.ts b/packages/stakers/src/core/index.ts new file mode 100644 index 0000000000..47998f6e62 --- /dev/null +++ b/packages/stakers/src/core/index.ts @@ -0,0 +1,2 @@ +export { BlockchainComponent } from "./BlockchainComponent.js"; +export type { CompatibleClient } from "./BlockchainComponent.js"; diff --git a/packages/stakers/src/index.ts b/packages/stakers/src/index.ts index 3354a778a6..a59f5c44f3 100644 --- a/packages/stakers/src/index.ts +++ b/packages/stakers/src/index.ts @@ -1,4 +1,9 @@ -export { Consensus } from "./consensus.js"; -export { Execution } from "./execution.js"; -export { MevBoost } from "./mevBoost.js"; -export { Signer } from "./signer.js"; +// Core infrastructure +export { BlockchainComponent } from "./core/index.js"; +export type { CompatibleClient } from "./core/index.js"; + +// L1 Staker components (Proof of Stake blockchains) +export { Consensus, Execution, MevBoost, Signer, StakerComponent } from "./l1/index.js"; + +// L2 components +export { L2Component, StarknetNodeComponent, StarknetSignerComponent } from "./l2/index.js"; diff --git a/packages/stakers/src/consensus.ts b/packages/stakers/src/l1/Consensus.ts similarity index 89% rename from packages/stakers/src/consensus.ts rename to packages/stakers/src/l1/Consensus.ts index 7797191ed1..dd9feb7bf2 100644 --- a/packages/stakers/src/consensus.ts +++ b/packages/stakers/src/l1/Consensus.ts @@ -7,20 +7,20 @@ import { ConsensusClientMainnet, ConsensusClientPrater, Network, + L1Network, StakerItem, UserSettings } from "@dappnode/types"; -import { StakerComponent } from "./stakerComponent.js"; +import { StakerComponent } from "./StakerComponent.js"; import { DappnodeInstaller } from "@dappnode/installer"; import * as db from "@dappnode/db"; import { params } from "@dappnode/params"; import { getDefaultConsensusUserSettings } from "@dappnode/utils"; - -// TODO: move ethereumClient logic here +import { CompatibleClient } from "../core/BlockchainComponent.js"; export class Consensus extends StakerComponent { readonly DbHandlers: Record< - Network, + L1Network, { get: () => string | null | undefined; set: (globEnvValue: string | null | undefined) => Promise; @@ -34,7 +34,8 @@ export class Consensus extends StakerComponent { [Network.Hoodi]: db.consensusClientHoodi, [Network.Lukso]: db.consensusClientLukso }; - protected static readonly CompatibleConsensus: Record = { + + protected static readonly CompatibleConsensus: Record = { [Network.Mainnet]: [ { dnpName: ConsensusClientMainnet.Prysm, minVersion: "3.0.4" }, { dnpName: ConsensusClientMainnet.Lighthouse, minVersion: "1.0.3" }, @@ -82,14 +83,14 @@ export class Consensus extends StakerComponent { super(dappnodeInstaller); } - async getAllConsensus(network: Network): Promise { + async getAllConsensus(network: L1Network): Promise { return await super.getAll({ dnpNames: Consensus.CompatibleConsensus[network].map((client) => client.dnpName), currentClient: this.DbHandlers[network].get() }); } - async persistSelectedConsensusIfInstalled(network: Network): Promise { + async persistSelectedConsensusIfInstalled(network: L1Network): Promise { const currentConsensusDnpName = this.DbHandlers[network].get(); if (currentConsensusDnpName) { const isInstalled = await this.isPackageInstalled(currentConsensusDnpName); @@ -108,14 +109,14 @@ export class Consensus extends StakerComponent { } } - async setNewConsensus(network: Network, newConsensusDnpName: string | null) { + async setNewConsensus(network: L1Network, 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], + newClientDnpName: newConsensusDnpName, + dockerNetworkName: params.DOCKER_BLOCKCHAIN_NETWORKS[network], fullnodeAliases: [`beacon-chain.${network}.dncore.dappnode`, `validator.${network}.dncore.dappnode`], compatibleClients: Consensus.CompatibleConsensus[network], userSettings, @@ -125,7 +126,7 @@ export class Consensus extends StakerComponent { if (newConsensusDnpName !== prevConsClientDnpName) await this.DbHandlers[network].set(newConsensusDnpName); } - private async getUserSettings(network: Network, newConsensusDnpName: string | null): Promise { + private async getUserSettings(network: L1Network, newConsensusDnpName: string | null): Promise { if (!newConsensusDnpName) return {}; const isPkgInstalled = await this.isPackageInstalled(newConsensusDnpName); @@ -183,13 +184,13 @@ export class Consensus extends StakerComponent { } } - private getStakerNetworkSettings(network: Network): UserSettings["networks"] { + private getStakerNetworkSettings(network: L1Network): UserSettings["networks"] { const validatorServiceName = "validator"; const beaconServiceName = "beacon-chain"; - // helper to build each service’s networks + // helper to build each service's networks const buildSvc = (svcName: string) => ({ - [params.DOCKER_STAKER_NETWORKS[network]]: { + [params.DOCKER_BLOCKCHAIN_NETWORKS[network]]: { aliases: [`${svcName}.${network}.staker.dappnode`] }, [params.DOCKER_PRIVATE_NETWORK_NAME]: { diff --git a/packages/stakers/src/execution.ts b/packages/stakers/src/l1/Execution.ts similarity index 90% rename from packages/stakers/src/execution.ts rename to packages/stakers/src/l1/Execution.ts index 50e9a0c77e..26fc7255a0 100644 --- a/packages/stakers/src/execution.ts +++ b/packages/stakers/src/l1/Execution.ts @@ -7,22 +7,22 @@ import { ExecutionClientMainnet, ExecutionClientPrater, Network, + L1Network, StakerItem, UserSettings } from "@dappnode/types"; -import { StakerComponent } from "./stakerComponent.js"; +import { StakerComponent } from "./StakerComponent.js"; import { DappnodeInstaller } from "@dappnode/installer"; import * as db from "@dappnode/db"; import { params } from "@dappnode/params"; import { listPackage } from "@dappnode/dockerapi"; import { logs } from "@dappnode/logger"; import { gt } from "semver"; - -// TODO: move ethereumClient logic here +import { CompatibleClient } from "../core/BlockchainComponent.js"; export class Execution extends StakerComponent { readonly DbHandlers: Record< - Network, + L1Network, { get: () => string | null | undefined; set: (globEnvValue: string | null | undefined) => Promise; @@ -37,7 +37,7 @@ export class Execution extends StakerComponent { [Network.Lukso]: db.executionClientLukso }; - protected static readonly CompatibleExecutions: Record = { + protected static readonly CompatibleExecutions: Record = { [Network.Mainnet]: [ { dnpName: ExecutionClientMainnet.Reth, minVersion: "0.1.0" }, { dnpName: ExecutionClientMainnet.Geth, minVersion: "0.1.37" }, @@ -82,14 +82,14 @@ export class Execution extends StakerComponent { super(dappnodeInstaller); } - async getAllExecutions(network: Network): Promise { + async getAllExecutions(network: L1Network): Promise { return await super.getAll({ dnpNames: Execution.CompatibleExecutions[network].map((client) => client.dnpName), currentClient: this.DbHandlers[network].get() }); } - async persistSelectedExecutionIfInstalled(network: Network): Promise { + async persistSelectedExecutionIfInstalled(network: L1Network): Promise { const currentExecutionDnpName = this.DbHandlers[network].get(); if (currentExecutionDnpName) { const isInstalled = await this.isPackageInstalled(currentExecutionDnpName); @@ -108,12 +108,12 @@ export class Execution extends StakerComponent { } } - async setNewExecution(network: Network, newExecutionDnpName: string | null) { + async setNewExecution(network: L1Network, newExecutionDnpName: string | null) { const prevExecClientDnpName = this.DbHandlers[network].get(); await super.setNew({ - newStakerDnpName: newExecutionDnpName, - dockerNetworkName: params.DOCKER_STAKER_NETWORKS[network], + newClientDnpName: newExecutionDnpName, + dockerNetworkName: params.DOCKER_BLOCKCHAIN_NETWORKS[network], fullnodeAliases: [`execution.${network}.dncore.dappnode`], compatibleClients: Execution.CompatibleExecutions[network], userSettings: await this.getUserSettings(network, newExecutionDnpName), @@ -126,7 +126,7 @@ export class Execution extends StakerComponent { } } - private async getUserSettings(network: Network, dnpName: string | null): Promise { + private async getUserSettings(network: L1Network, dnpName: string | null): Promise { if (!dnpName) return {}; const execService = await this.getExecutionServiceName(dnpName); @@ -136,7 +136,7 @@ export class Execution extends StakerComponent { rootNetworks: this.getComposeRootNetworks(network), serviceNetworks: { [execService]: { - [params.DOCKER_STAKER_NETWORKS[network]]: { + [params.DOCKER_BLOCKCHAIN_NETWORKS[network]]: { aliases: [`execution.${network}.staker.dappnode`] }, [params.DOCKER_PRIVATE_NETWORK_NAME]: { diff --git a/packages/stakers/src/mevBoost.ts b/packages/stakers/src/l1/MevBoost.ts similarity index 82% rename from packages/stakers/src/mevBoost.ts rename to packages/stakers/src/l1/MevBoost.ts index 5a4dfa9594..4c67575df9 100644 --- a/packages/stakers/src/mevBoost.ts +++ b/packages/stakers/src/l1/MevBoost.ts @@ -4,18 +4,20 @@ import { MevBoostMainnet, MevBoostPrater, Network, + L1Network, StakerItem, UserSettings } from "@dappnode/types"; -import { StakerComponent } from "./stakerComponent.js"; +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 { ComposeFileEditor } from "@dappnode/dockercompose"; +import { CompatibleClient } from "../core/BlockchainComponent.js"; export class MevBoost extends StakerComponent { - readonly DbHandlers: Record boolean; set: (globEnvValue: boolean) => Promise }> = { + readonly DbHandlers: Record boolean; set: (globEnvValue: boolean) => Promise }> = { [Network.Mainnet]: db.mevBoostMainnet, [Network.Gnosis]: db.mevBoostGnosis, [Network.Prater]: db.mevBoostPrater, @@ -25,7 +27,7 @@ export class MevBoost extends StakerComponent { [Network.Lukso]: db.mevBoostLukso }; - protected static readonly CompatibleMevBoost: Record = { + protected static readonly CompatibleMevBoost: Record = { [Network.Mainnet]: { dnpName: MevBoostMainnet.Mevboost, minVersion: "0.1.0" @@ -51,7 +53,7 @@ export class MevBoost extends StakerComponent { super(dappnodeInstaller); } - async getAllMevBoost(network: Network): Promise { + async getAllMevBoost(network: L1Network): Promise { const mevBoostDnpName = MevBoost.CompatibleMevBoost[network]?.dnpName; return await super.getAll({ dnpNames: mevBoostDnpName ? [mevBoostDnpName] : [], @@ -70,7 +72,7 @@ export class MevBoost extends StakerComponent { return relays; } - async persistMevBoostIfInstalledAndRunning(network: Network): Promise { + async persistMevBoostIfInstalledAndRunning(network: L1Network): Promise { const mevBoostDnpName = MevBoost.CompatibleMevBoost[network]?.dnpName; if (mevBoostDnpName) { @@ -94,11 +96,11 @@ export class MevBoost extends StakerComponent { } } - async setNewMevBoost(network: Network, newMevBoostDnpName: string | null, newRelays: string[]) { + async setNewMevBoost(network: L1Network, newMevBoostDnpName: string | null, newRelays: string[]) { const compatibleMevBoost = MevBoost.CompatibleMevBoost[network]; await super.setNew({ - newStakerDnpName: newMevBoostDnpName, - dockerNetworkName: params.DOCKER_STAKER_NETWORKS[network], + newClientDnpName: newMevBoostDnpName, + dockerNetworkName: params.DOCKER_BLOCKCHAIN_NETWORKS[network], fullnodeAliases: [`mev-boost.${network}.dncore.dappnode`], compatibleClients: compatibleMevBoost ? [compatibleMevBoost] : null, userSettings: newMevBoostDnpName ? this.getUserSettings(network, newRelays) : {}, @@ -109,7 +111,7 @@ export class MevBoost extends StakerComponent { await this.DbHandlers[network].set(newMevBoostDnpName ? true : false); } - private getUserSettings(network: Network, newRelays: string[] | null): UserSettings { + private getUserSettings(network: L1Network, newRelays: string[] | null): UserSettings { const mevBoostServiceName = "mev-boost"; const userSettings: UserSettings = { @@ -131,14 +133,14 @@ export class MevBoost extends StakerComponent { return userSettings; } - private getStakerNetworkSettings(network: Network): UserSettings["networks"] { + private getStakerNetworkSettings(network: L1Network): UserSettings["networks"] { const mevBoostServiceName = "mev-boost"; return { rootNetworks: this.getComposeRootNetworks(network), serviceNetworks: { [mevBoostServiceName]: { - [params.DOCKER_STAKER_NETWORKS[network]]: { + [params.DOCKER_BLOCKCHAIN_NETWORKS[network]]: { aliases: [`${mevBoostServiceName}.${network}.staker.dappnode`] }, [params.DOCKER_PRIVATE_NETWORK_NAME]: { diff --git a/packages/stakers/src/signer.ts b/packages/stakers/src/l1/Signer.ts similarity index 79% rename from packages/stakers/src/signer.ts rename to packages/stakers/src/l1/Signer.ts index a49a4a0b28..ff7241abbc 100644 --- a/packages/stakers/src/signer.ts +++ b/packages/stakers/src/l1/Signer.ts @@ -1,5 +1,6 @@ import { Network, + L1Network, SignerGnosis, SignerHolesky, SignerHoodi, @@ -10,15 +11,16 @@ import { StakerItem, UserSettings } from "@dappnode/types"; -import { StakerComponent } from "./stakerComponent.js"; +import { StakerComponent } from "./StakerComponent.js"; import { DappnodeInstaller } from "@dappnode/installer"; import { params } from "@dappnode/params"; import { listPackageNoThrow } from "@dappnode/dockerapi"; +import { CompatibleClient } from "../core/BlockchainComponent.js"; export class Signer extends StakerComponent { protected static readonly ServiceAliasesMap: Record = {}; - protected static readonly CompatibleSigners: Record = { + protected static readonly CompatibleSigners: Record = { [Network.Mainnet]: { dnpName: SignerMainnet.Web3signer, minVersion: "0.1.4" @@ -44,7 +46,7 @@ export class Signer extends StakerComponent { minVersion: "0.1.0" }, [Network.Sepolia]: { - dnpName: SignerSepolia.Web3signer, + dnpName: SignerSepolia.Web3signer, minVersion: "0.1.0" } }; @@ -53,14 +55,14 @@ export class Signer extends StakerComponent { super(dappnodeInstaller); } - async getAllSigners(network: Network): Promise { + async getAllSigners(network: L1Network): Promise { return await super.getAll({ dnpNames: [Signer.CompatibleSigners[network].dnpName], currentClient: Signer.CompatibleSigners[network].dnpName }); } - async persistSignerIfInstalledAndRunning(network: Network): Promise { + async persistSignerIfInstalledAndRunning(network: L1Network): Promise { const signerDnpName = Signer.CompatibleSigners[network].dnpName; const signerDnp = await listPackageNoThrow({ dnpName: signerDnpName }); const isRunning = signerDnp?.containers.some((container) => container.running); @@ -73,10 +75,10 @@ export class Signer extends StakerComponent { } } - async setNewSigner(network: Network, newWeb3signerDnpName: string | null) { + async setNewSigner(network: L1Network, newWeb3signerDnpName: string | null) { await super.setNew({ - newStakerDnpName: newWeb3signerDnpName, - dockerNetworkName: params.DOCKER_STAKER_NETWORKS[network], + newClientDnpName: newWeb3signerDnpName, + dockerNetworkName: params.DOCKER_BLOCKCHAIN_NETWORKS[network], fullnodeAliases: [`signer.${network}.dncore.dappnode`], compatibleClients: [Signer.CompatibleSigners[network]], userSettings: this.getUserSettings(network), @@ -84,7 +86,7 @@ export class Signer extends StakerComponent { }); } - private getUserSettings(network: Network): UserSettings { + private getUserSettings(network: L1Network): UserSettings { const serviceName = "web3signer"; return { @@ -92,7 +94,7 @@ export class Signer extends StakerComponent { rootNetworks: this.getComposeRootNetworks(network), serviceNetworks: { [serviceName]: { - [params.DOCKER_STAKER_NETWORKS[network]]: { + [params.DOCKER_BLOCKCHAIN_NETWORKS[network]]: { aliases: [`${serviceName}.${network}.staker.dappnode`, `signer.${network}.staker.dappnode`] }, [params.DOCKER_PRIVATE_NETWORK_NAME]: { diff --git a/packages/stakers/src/l1/StakerComponent.ts b/packages/stakers/src/l1/StakerComponent.ts new file mode 100644 index 0000000000..0bfe62f043 --- /dev/null +++ b/packages/stakers/src/l1/StakerComponent.ts @@ -0,0 +1,107 @@ +import { Network, UserSettings } from "@dappnode/types"; +import { BlockchainComponent, CompatibleClient } from "../core/BlockchainComponent.js"; +import { DappnodeInstaller } from "@dappnode/installer"; +import { params } from "@dappnode/params"; + +/** + * StakerComponent is the base class for L1 Proof-of-Stake blockchain components. + * It extends BlockchainComponent with L1-staking-specific functionality: + * - Staker network aliases (*.staker.dappnode) + * - Multi-network configuration (staker network + dncore networks) + * + * Used by: Consensus, Execution, MevBoost, Signer + */ +export class StakerComponent extends BlockchainComponent { + constructor(dappnodeInstaller: DappnodeInstaller) { + super(dappnodeInstaller); + } + + /** + * Wrapper for setNew that uses "staker" terminology for backward compatibility + * @deprecated Use setNew from BlockchainComponent directly for new implementations + */ + protected async setNewStaker({ + newStakerDnpName, + dockerNetworkName, + fullnodeAliases, + compatibleClients, + userSettings, + prevClient + }: { + newStakerDnpName: string | null | undefined; + dockerNetworkName: string; + fullnodeAliases: string[]; + compatibleClients: CompatibleClient[] | null; + userSettings: UserSettings; + prevClient?: string | null; + }): Promise { + return super.setNew({ + newClientDnpName: newStakerDnpName, + dockerNetworkName, + fullnodeAliases, + compatibleClients, + userSettings, + prevClient + }); + } + + /** + * Wrapper for setPkgConfig that uses "staker" terminology for backward compatibility + * @deprecated Use setPkgConfig from BlockchainComponent directly for new implementations + */ + protected async setStakerPkgConfig({ + dnpName, + isInstalled, + userSettings + }: { + dnpName: string; + isInstalled: boolean; + userSettings: UserSettings; + }): Promise { + return super.setPkgConfig({ dnpName, isInstalled, userSettings }); + } + + /** + * Get the compose root networks configuration for L1 staker packages + * Includes both the staker-specific network and the private network + */ + protected override getComposeRootNetworks(network: Network): NonNullable["rootNetworks"] { + return { + [params.DOCKER_BLOCKCHAIN_NETWORKS[network]]: { + external: true + }, + [params.DOCKER_PRIVATE_NETWORK_NAME]: { + external: true + } + }; + } + + /** + * Build service network configuration with staker-specific aliases + * Creates aliases for: + * - *.{network}.staker.dappnode (staker network) + * - *.{network}.dncore.dappnode (legacy private network) + * - *.{network}.dappnode.private (new private network) + */ + protected buildStakerServiceNetworks( + network: Network, + serviceName: string, + aliases?: string[] + ): NonNullable["serviceNetworks"] { + const defaultAliases = aliases || [serviceName]; + + return { + [serviceName]: { + [params.DOCKER_BLOCKCHAIN_NETWORKS[network]]: { + aliases: defaultAliases.map((alias) => `${alias}.${network}.staker.dappnode`) + }, + [params.DOCKER_PRIVATE_NETWORK_NAME]: { + aliases: defaultAliases.map((alias) => `${alias}.${network}.dncore.dappnode`) + }, + [params.DOCKER_PRIVATE_NETWORK_NEW_NAME]: { + aliases: defaultAliases.map((alias) => `${alias}.${network}.dappnode.private`) + } + } + }; + } +} diff --git a/packages/stakers/src/l1/index.ts b/packages/stakers/src/l1/index.ts new file mode 100644 index 0000000000..f0be8faf18 --- /dev/null +++ b/packages/stakers/src/l1/index.ts @@ -0,0 +1,5 @@ +export { StakerComponent } from "./StakerComponent.js"; +export { Consensus } from "./Consensus.js"; +export { Execution } from "./Execution.js"; +export { MevBoost } from "./MevBoost.js"; +export { Signer } from "./Signer.js"; diff --git a/packages/stakers/src/l2/L2Component.ts b/packages/stakers/src/l2/L2Component.ts new file mode 100644 index 0000000000..a0c9c7f777 --- /dev/null +++ b/packages/stakers/src/l2/L2Component.ts @@ -0,0 +1,95 @@ +import { Network, UserSettings } from "@dappnode/types"; +import { BlockchainComponent, CompatibleClient } from "../core/BlockchainComponent.js"; +import { DappnodeInstaller } from "@dappnode/installer"; +import { params } from "@dappnode/params"; + +/** + * L2Component is the base class for Layer 2 blockchain components. + * It extends BlockchainComponent with L2-specific functionality: + * - L2 network aliases (*.l2.dappnode or similar) + * - Simpler architecture (typically node + signer instead of consensus/execution split) + * + * L2s typically have: + * - A single node package (e.g., Juno for Starknet) that handles both consensus and execution + * - An optional signer package (e.g., starknetstaking) + */ +export class L2Component extends BlockchainComponent { + constructor(dappnodeInstaller: DappnodeInstaller) { + super(dappnodeInstaller); + } + + /** + * Get the compose root networks configuration for L2 packages + * Includes the L2-specific network and the private DAppNode networks + */ + protected override getComposeRootNetworks(network: Network): NonNullable["rootNetworks"] { + return { + [params.DOCKER_BLOCKCHAIN_NETWORKS[network]]: { + external: true + }, + [params.DOCKER_PRIVATE_NETWORK_NAME]: { + external: true + }, + [params.DOCKER_PRIVATE_NETWORK_NEW_NAME]: { + external: true + } + }; + } + + /** + * Build service network configuration with L2-specific aliases + * Creates aliases for: + * - *.{network}.dappnode (main network) + * - *.{network}.dncore.dappnode (legacy private network) + * - *.{network}.dappnode.private (new private network) + */ + protected buildL2ServiceNetworks( + network: Network, + serviceName: string, + aliases?: string[] + ): NonNullable["serviceNetworks"] { + const defaultAliases = aliases || [serviceName]; + + return { + [serviceName]: { + [params.DOCKER_BLOCKCHAIN_NETWORKS[network]]: { + aliases: defaultAliases.map((alias) => `${alias}.${network}.dappnode`) + }, + [params.DOCKER_PRIVATE_NETWORK_NAME]: { + aliases: defaultAliases.map((alias) => `${alias}.${network}.dncore.dappnode`) + }, + [params.DOCKER_PRIVATE_NETWORK_NEW_NAME]: { + aliases: defaultAliases.map((alias) => `${alias}.${network}.dappnode.private`) + } + } + }; + } + + /** + * Convenience method to set a new L2 client + */ + protected async setNewL2Client({ + newClientDnpName, + network, + fullnodeAliases, + compatibleClients, + userSettings, + prevClient + }: { + newClientDnpName: string | null | undefined; + network: Network; + fullnodeAliases: string[]; + compatibleClients: CompatibleClient[] | null; + userSettings: UserSettings; + prevClient?: string | null; + }): Promise { + return super.setNew({ + newClientDnpName, + dockerNetworkName: params.DOCKER_BLOCKCHAIN_NETWORKS[network], + fullnodeAliases, + compatibleClients, + userSettings, + prevClient + }); + } +} diff --git a/packages/stakers/src/l2/index.ts b/packages/stakers/src/l2/index.ts new file mode 100644 index 0000000000..8f8fca8a3d --- /dev/null +++ b/packages/stakers/src/l2/index.ts @@ -0,0 +1,2 @@ +export { L2Component } from "./L2Component.js"; +export { StarknetNodeComponent, StarknetSignerComponent } from "./starknet/index.js"; diff --git a/packages/stakers/src/l2/starknet/StarknetNode.ts b/packages/stakers/src/l2/starknet/StarknetNode.ts new file mode 100644 index 0000000000..72939710fb --- /dev/null +++ b/packages/stakers/src/l2/starknet/StarknetNode.ts @@ -0,0 +1,95 @@ +import { Network, StakerItem, StarknetNode as StarknetNodeEnum, UserSettings } from "@dappnode/types"; +import { L2Component } from "../L2Component.js"; +import { DappnodeInstaller } from "@dappnode/installer"; +import * as db from "@dappnode/db"; +import { params } from "@dappnode/params"; +import { CompatibleClient } from "../../core/BlockchainComponent.js"; + +/** + * StarknetNodeComponent manages the Starknet node package (Juno). + * Juno is a full node for Starknet that handles both consensus and execution-like functionality. + */ +export class StarknetNodeComponent extends L2Component { + readonly DbHandler = db.starknetNode; + + protected static readonly CompatibleNodes: CompatibleClient[] = [ + { dnpName: StarknetNodeEnum.Juno, minVersion: "0.1.0" } + ]; + + constructor(dappnodeInstaller: DappnodeInstaller) { + super(dappnodeInstaller); + } + + /** + * Get all available Starknet node clients + */ + async getAllNodes(): Promise { + return await super.getAll({ + dnpNames: StarknetNodeComponent.CompatibleNodes.map((client) => client.dnpName), + currentClient: this.DbHandler.get() + }); + } + + /** + * Persist the selected node if it's installed + */ + async persistSelectedNodeIfInstalled(): Promise { + const currentNodeDnpName = this.DbHandler.get(); + if (currentNodeDnpName) { + const isInstalled = await this.isPackageInstalled(currentNodeDnpName); + + if (!isInstalled) { + await this.DbHandler.set(undefined); + return; + } + + const userSettings = this.getUserSettings(); + await this.setPkgConfig({ dnpName: currentNodeDnpName, isInstalled, userSettings }); + await this.DbHandler.set(currentNodeDnpName); + } + } + + /** + * Set a new Starknet node + */ + async setNewNode(newNodeDnpName: string | null): Promise { + const prevNodeDnpName = this.DbHandler.get(); + + await super.setNewL2Client({ + newClientDnpName: newNodeDnpName, + network: Network.Starknet, + fullnodeAliases: [`node.${Network.Starknet}.dncore.dappnode`], + compatibleClients: StarknetNodeComponent.CompatibleNodes, + userSettings: newNodeDnpName ? this.getUserSettings() : {}, + prevClient: prevNodeDnpName + }); + + // persist on db + if (newNodeDnpName !== prevNodeDnpName) { + await this.DbHandler.set(newNodeDnpName); + } + } + + private getUserSettings(): UserSettings { + const serviceName = "juno"; // Juno's main service name + + return { + networks: { + rootNetworks: this.getComposeRootNetworks(Network.Starknet), + serviceNetworks: { + [serviceName]: { + [params.DOCKER_BLOCKCHAIN_NETWORKS[Network.Starknet]]: { + aliases: [`node.${Network.Starknet}.dappnode`, `juno.${Network.Starknet}.dappnode`] + }, + [params.DOCKER_PRIVATE_NETWORK_NAME]: { + aliases: [`node.${Network.Starknet}.dncore.dappnode`, `juno.${Network.Starknet}.dncore.dappnode`] + }, + [params.DOCKER_PRIVATE_NETWORK_NEW_NAME]: { + aliases: [`node.${Network.Starknet}.dappnode.private`, `juno.${Network.Starknet}.dappnode.private`] + } + } + } + } + }; + } +} diff --git a/packages/stakers/src/l2/starknet/StarknetSigner.ts b/packages/stakers/src/l2/starknet/StarknetSigner.ts new file mode 100644 index 0000000000..87d689fd3f --- /dev/null +++ b/packages/stakers/src/l2/starknet/StarknetSigner.ts @@ -0,0 +1,97 @@ +import { Network, StakerItem, StarknetSigner as StarknetSignerEnum, UserSettings } from "@dappnode/types"; +import { L2Component } from "../L2Component.js"; +import { DappnodeInstaller } from "@dappnode/installer"; +import * as db from "@dappnode/db"; +import { params } from "@dappnode/params"; +import { CompatibleClient } from "../../core/BlockchainComponent.js"; +import { listPackageNoThrow } from "@dappnode/dockerapi"; + +/** + * StarknetSignerComponent manages the Starknet staking/signer package. + * This is separate from the node and handles staking-related operations. + */ +export class StarknetSignerComponent extends L2Component { + readonly DbHandler = db.starknetSigner; + + protected static readonly CompatibleSigners: CompatibleClient[] = [ + { dnpName: StarknetSignerEnum.StarknetStaking, minVersion: "0.1.0" } + ]; + + constructor(dappnodeInstaller: DappnodeInstaller) { + super(dappnodeInstaller); + } + + /** + * Get all available Starknet signers + */ + async getAllSigners(): Promise { + return await super.getAll({ + dnpNames: StarknetSignerComponent.CompatibleSigners.map((client) => client.dnpName), + currentClient: this.DbHandler.get() + }); + } + + /** + * Persist the selected signer if it's installed and running + */ + async persistSignerIfInstalledAndRunning(): Promise { + const signerDnpName = StarknetSignerComponent.CompatibleSigners[0]?.dnpName; + if (!signerDnpName) return; + + const signerDnp = await listPackageNoThrow({ dnpName: signerDnpName }); + const isRunning = signerDnp?.containers.some((container) => container.running); + + if (isRunning) { + const userSettings = this.getUserSettings(); + await this.setPkgConfig({ dnpName: signerDnpName, isInstalled: true, userSettings }); + await this.DbHandler.set(signerDnpName); + } + } + + /** + * Set a new Starknet signer + */ + async setNewSigner(newSignerDnpName: string | null): Promise { + const prevSignerDnpName = this.DbHandler.get(); + + await super.setNewL2Client({ + newClientDnpName: newSignerDnpName, + network: Network.Starknet, + fullnodeAliases: [`signer.${Network.Starknet}.dncore.dappnode`], + compatibleClients: StarknetSignerComponent.CompatibleSigners, + userSettings: newSignerDnpName ? this.getUserSettings() : {}, + prevClient: prevSignerDnpName + }); + + // persist on db + if (newSignerDnpName !== prevSignerDnpName) { + await this.DbHandler.set(newSignerDnpName); + } + } + + private getUserSettings(): UserSettings { + const serviceName = "starknetstaking"; // Main service name for starknet staking + + return { + networks: { + rootNetworks: this.getComposeRootNetworks(Network.Starknet), + serviceNetworks: { + [serviceName]: { + [params.DOCKER_BLOCKCHAIN_NETWORKS[Network.Starknet]]: { + aliases: [`signer.${Network.Starknet}.dappnode`, `staking.${Network.Starknet}.dappnode`] + }, + [params.DOCKER_PRIVATE_NETWORK_NAME]: { + aliases: [`signer.${Network.Starknet}.dncore.dappnode`, `staking.${Network.Starknet}.dncore.dappnode`] + }, + [params.DOCKER_PRIVATE_NETWORK_NEW_NAME]: { + aliases: [ + `signer.${Network.Starknet}.dappnode.private`, + `staking.${Network.Starknet}.dappnode.private` + ] + } + } + } + } + }; + } +} diff --git a/packages/stakers/src/l2/starknet/index.ts b/packages/stakers/src/l2/starknet/index.ts new file mode 100644 index 0000000000..79bb1132d0 --- /dev/null +++ b/packages/stakers/src/l2/starknet/index.ts @@ -0,0 +1,2 @@ +export { StarknetNodeComponent } from "./StarknetNode.js"; +export { StarknetSignerComponent } from "./StarknetSigner.js"; diff --git a/packages/types/src/stakers.ts b/packages/types/src/stakers.ts index ae41ecf176..d67dcf0a79 100644 --- a/packages/types/src/stakers.ts +++ b/packages/types/src/stakers.ts @@ -9,12 +9,45 @@ export enum Network { Lukso = "lukso", Holesky = "holesky", Sepolia = "sepolia", - Hoodi = "hoodi" + Hoodi = "hoodi", + // L2 Networks + Starknet = "starknet" +} + +/** L1 Networks - PoS blockchains with separate execution/consensus clients */ +export type L1Network = Exclude; + +/** L2 Networks - Layer 2 blockchains */ +export type L2Network = Network.Starknet; + +/** List of all L1 networks */ +export const L1_NETWORKS: L1Network[] = [ + Network.Mainnet, + Network.Prater, + Network.Gnosis, + Network.Lukso, + Network.Holesky, + Network.Sepolia, + Network.Hoodi +]; + +/** List of all L2 networks */ +export const L2_NETWORKS: L2Network[] = [Network.Starknet]; + +/** Check if a network is an L1 network */ +export function isL1Network(network: Network): network is L1Network { + return L1_NETWORKS.includes(network as L1Network); +} + +/** Check if a network is an L2 network */ +export function isL2Network(network: Network): network is L2Network { + return L2_NETWORKS.includes(network as L2Network); } export const networksByType = { mainnets: [Network.Mainnet, Network.Gnosis, Network.Lukso], - testnets: [Network.Hoodi, Network.Prater, Network.Holesky, Network.Sepolia] + testnets: [Network.Hoodi, Network.Prater, Network.Holesky, Network.Sepolia], + l2: [Network.Starknet] }; // MAINNET @@ -151,6 +184,15 @@ export enum SignerSepolia { Web3signer = "web3signer-sepolia.dnp.dappnode.eth" } +// STARKNET (L2) +export enum StarknetNode { + Juno = "juno.dnp.dappnode.eth" +} + +export enum StarknetSigner { + StarknetStaking = "starknetstaking.dnp.dappnode.eth" +} + export type StakerItem = StakerItemOk | StakerItemError; interface StakerItemBasic {