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

feat: Implement lighthouse beacon service #394

Merged
merged 3 commits into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Implement lighthouse beacon module
  • Loading branch information
scottbot95 committed Oct 9, 2023
commit e1eda98fcdc9e45823e9ba520affe9b97a365edb
1 change: 1 addition & 0 deletions modules/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
./erigon
./geth
./geth-bootnode
./lighthouse-beacon
./mev-boost
./nethermind
./prysm-beacon
Expand Down
145 changes: 145 additions & 0 deletions modules/lighthouse-beacon/args.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
{
lib,
name,
config,
...
}:
with lib; {
network = mkOption {
type = types.nullOr (types.enum ["mainnet" "prater" "goerli" "gnosis" "chiado" "sepolia" "holesky"]);
default = name;
defaultText = "name";
description = mdDoc "The network to connect to. Mainnet is the default ethereum network.";
};

execution-endpoint = mkOption {
type = types.str;
default = "http://127.0.0.1:8551";
description = lib.mdDoc ''
Listen address for the execution layer.
'';
};

execution-jwt = mkOption {
type = types.str;
default = null;
description = mdDoc ''
Path to a file containing a hex-encoded string representing a 32 byte secret
used for authentication with an execution node via HTTP
'';
example = "/var/run/prysm/jwtsecret";
};

checkpoint-sync-url = mkOption {
type = types.nullOr types.str;
default = null;
description = mdDoc ''
URL of a synced beacon node to trust in obtaining checkpoint sync data.
As an additional safety measure, it is strongly recommended to only use this option in conjunction with --wss-checkpoint flag
'';
example = "https://goerli.checkpoint-sync.ethpandaops.io";
};

disable-deposit-contract-sync = mkOption {
type = types.bool;
default = false;
description = lib.mdDoc ''
Explicitly disables syncing of deposit logs from the execution node.
This overrides any previous option that depends on it.
Useful if you intend to run a non-validating beacon node.
'';
};

genesis-state-url = mkOption {
type = types.nullOr types.str;
default = null;
description = mdDoc ''
URL of a synced beacon node to trust for obtaining genesis state.
As an additional safety measure, it is strongly recommended to only use this option in conjunction with --wss-checkpoint flag
'';
example = "https://goerli.checkpoint-sync.ethpandaops.io";
};

disable-quic = mkOption {
type = types.bool;
default = false;
description = mdDoc ''
Disables the quic transport.
The node will rely solely on the TCP transport for libp2p connections.
'';
};

discovery-port = mkOption {
type = types.port;
default = 9000;
description = mdDoc "The port used by discv5.";
};

quic-port = mkOption {
type = types.port;
default = config.args.discovery-port + 1;
defaultText = literalExpression "args.discovery-port + 1";
description = mdDoc ''
The port used by libp2p.
Will use TCP if disable-quic is set
'';
};

metrics = {
enable = mkOption {
type = types.bool;
default = true;
description = mdDoc "Enable Prometheus metrics exporter.";
};

address = mkOption {
type = types.str;
default = "127.0.0.1";
description = mdDoc "Host used to listen and respond with metrics for prometheus.";
};

port = mkOption {
type = types.port;
default = 5054;
description = mdDoc "Port used to listen and respond with metrics for prometheus.";
};
};

http = {
enable = mkOption {
type = types.bool;
default = true;
description = mdDoc "Enable the HTTP RPC server";
};

address = mkOption {
type = types.str;
default = "127.0.0.1";
description = mdDoc "Host on which the RPC server should listen.";
};

port = mkOption {
type = types.port;
default = 5052;
description = mdDoc "RPC port exposed by a beacon node.";
};
};

disable-upnp = mkOption {
type = types.bool;
default = true;
description = mdDoc "Disable the UPnP configuration";
};

datadir = mkOption {
type = types.nullOr types.str;
default = null;
description = mdDoc "Data directory for the databases.";
};

user = mkOption {
type = types.nullOr types.str;
default = null;
description = mdDoc "User to run the systemd service.";
};
}
130 changes: 130 additions & 0 deletions modules/lighthouse-beacon/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
{
config,
lib,
pkgs,
...
}: let
modulesLib = import ../lib.nix lib;

inherit (lib.lists) optionals findFirst;
inherit (lib.strings) hasPrefix;
inherit (lib.attrsets) zipAttrsWith;
inherit
(lib)
concatStringsSep
filterAttrs
flatten
mapAttrs'
mapAttrsToList
mkIf
mkMerge
nameValuePair
;
inherit (modulesLib) mkArgs baseServiceConfig;

eachBeacon = config.services.ethereum.lighthouse-beacon;
in {
###### interface
inherit (import ./options.nix {inherit lib pkgs;}) options;

###### implementation

config = mkIf (eachBeacon != {}) {
# configure the firewall for each service
networking.firewall = let
openFirewall = filterAttrs (_: cfg: cfg.openFirewall) eachBeacon;
perService =
mapAttrsToList
(
_: cfg:
with cfg.args; {
allowedUDPPorts =
[discovery-port]
++ (optionals (!disable-quic) [quic-port]);
allowedTCPPorts =
(optionals disable-quic [quic-port])
++ (optionals http.enable [http.port])
++ (optionals metrics.enable [metrics.port]);
}
)
openFirewall;
in
zipAttrsWith (_name: flatten) perService;

systemd.services =
mapAttrs'
(
beaconName: let
user = "lighthouse-${beaconName}";
serviceName = "lighthouse-beacon-${beaconName}";
in
cfg: let
scriptArgs = let
args = mkArgs {
opts = import ./args.nix {
inherit lib;
name = beaconName;
config = cfg;
};
inherit (cfg) args;
};

# filter out certain args which need to be treated differently
specialArgs = [
"--execution-jwt"
"--datadir"
"--http-enable"
"--http-address"
"--http-port"
"--metrics-enable"
"--metrics-address"
"--metrics-port"
"--user"
];
isNormalArg = name: (findFirst (arg: hasPrefix arg name) null specialArgs) == null;
filteredArgs =
(builtins.filter isNormalArg args)
++ (optionals cfg.args.http.enable ["--http" "--http-address=${cfg.args.http.address}" "--http-port=${toString cfg.args.http.port}"])
++ (optionals cfg.args.metrics.enable ["--metrics" "--metrics-address=${cfg.args.metrics.address}" "--metrics-port=${toString cfg.args.metrics.port}"]);

jwtSecret =
if cfg.args.execution-jwt != null
then "--execution-jwt %d/execution-jwt"
else "";

datadir =
if cfg.args.datadir != null
then "--datadir ${cfg.args.datadir}"
else "--datadir %S/${user}";
in ''
${jwtSecret} \
${datadir} \
${concatStringsSep " \\\n" filteredArgs} \
${lib.escapeShellArgs cfg.extraArgs}
'';
in
nameValuePair serviceName (mkIf cfg.enable {
after = ["network.target"];
wantedBy = ["multi-user.target"];
description = "Lighthouse Beacon Node (${beaconName})";

# create service config by merging with the base config
serviceConfig = mkMerge [
baseServiceConfig
{
User =
if cfg.args.user != null
then cfg.args.user
else user;
StateDirectory = user;
ExecStart = "${cfg.package}/bin/lighthouse beacon ${scriptArgs}";
}
(mkIf (cfg.args.execution-jwt != null) {
LoadCredential = ["execution-jwt:${cfg.args.execution-jwt}"];
})
];
})
)
eachBeacon;
};
}
56 changes: 56 additions & 0 deletions modules/lighthouse-beacon/options.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
lib,
pkgs,
...
}: let
beaconOpts = with lib;
{
name,
config,
...
}: {
options = {
enable = mkEnableOption (mdDoc "Lighthouse Ethereum Beacon Chain Node written in Rust from Sigma Prime");

args = import ./args.nix {inherit lib name config;};

extraArgs = mkOption {
type = types.listOf types.str;
description = mdDoc "Additional arguments to pass to Lighthouse Beacon Chain.";
default = [];
};

package = mkOption {
default = pkgs.lighthouse;
defaultText = literalExpression "pkgs.lighthouse";
type = types.package;
description = mdDoc "Package to use for Lighthouse binary";
};

openFirewall = mkOption {
type = types.bool;
default = false;
description = lib.mdDoc "Open ports in the firewall for any enabled networking services";
};

# mixin backup options
backup = let
inherit (import ../backup/lib.nix lib) options;
in
options;

# mixin restore options
restore = let
inherit (import ../restore/lib.nix lib) options;
in
options;
};
};
in {
options.services.ethereum.lighthouse-beacon = with lib;
mkOption {
type = types.attrsOf (types.submodule beaconOpts);
default = {};
description = mdDoc "Specification of one or more lighthouse beacon chain instances.";
};
}