Skip to content

Commit

Permalink
Add service for Prysm validator (#279)
Browse files Browse the repository at this point in the history
Co-authored-by: Richard Warfield <warfield@gmail.com>
  • Loading branch information
aldoborrero and RichardWarfield authored Jun 8, 2023
1 parent a20a93c commit a0d8d5c
Show file tree
Hide file tree
Showing 8 changed files with 356 additions and 6 deletions.
7 changes: 4 additions & 3 deletions modules/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
flake.nixosModules.default = {
imports = [
./backup
./restore
./erigon
./geth
./geth-bootnode
./mev-boost
./nethermind
./erigon
./prysm-beacon
./mev-boost
./prysm-validator
./restore
];
};
}
12 changes: 12 additions & 0 deletions modules/prysm-beacon/args.nix
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,16 @@ with lib; {
default = 6060;
description = mdDoc "pprof HTTP server listening port.";
};

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.";
};
}
14 changes: 11 additions & 3 deletions modules/prysm-beacon/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ in {
};

# filter out certain args which need to be treated differently
specialArgs = ["--network" "--jwt-secret"];
specialArgs = ["--network" "--jwt-secret" "--datadir" "--user"];
isNormalArg = name: (findFirst (arg: hasPrefix arg name) null specialArgs) == null;
filteredArgs = builtins.filter isNormalArg args;

Expand All @@ -81,9 +81,14 @@ in {
if cfg.args.jwt-secret != null
then "--jwt-secret %d/jwt-secret"
else "";

datadir =
if cfg.args.datadir != null
then "--datadir ${cfg.args.datadir}"
else "--datadir %S/${serviceName}";
in ''
--accept-terms-of-use ${network} ${jwtSecret} \
--datadir %S/${serviceName} \
${datadir} \
${concatStringsSep " \\\n" filteredArgs} \
${lib.escapeShellArgs cfg.extraArgs}
'';
Expand All @@ -102,7 +107,10 @@ in {
serviceConfig = mkMerge [
baseServiceConfig
{
User = serviceName;
User =
if cfg.args.user != null
then cfg.args.user
else serviceName;
StateDirectory = serviceName;
ExecStart = "${cfg.package}/bin/beacon-chain ${scriptArgs}";
MemoryDenyWriteExecute = "false"; # causes a library loading error
Expand Down
98 changes: 98 additions & 0 deletions modules/prysm-validator/args.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
lib:
with lib; {
network = mkOption {
type = types.nullOr (types.enum ["goerli" "prater" "ropsten" "sepolia"]);
default = null;
description = mdDoc "The network to connect to. Mainnet (null) is the default ethereum network.";
};

disable-monitoring = mkOption {
type = types.bool;
default = false;
description = mdDoc "Disable monitoring service.";
};

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

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

grpc-gateway-host = mkOption {
type = types.str;
default = "127.0.0.1";
description = mdDoc "The host on which the gateway server runs on.";
};

grpc-gateway-port = mkOption {
type = types.port;
default = 7500;
description = mdDoc "The port on which the gateway server runs.";
};

rpc = {
enable = mkOption {
type = types.bool;
default = false;
description = mdDoc "Enable the Enables the RPC server for the validator.";
};

host = 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 = 7000;
description = mdDoc "RPC port exposed by a validator client.";
};
};

wallet-dir = mkOption {
type = types.nullOr types.str;
default = null;
description = mdDoc "Path to a wallet directory on-disk for Prysm validator accounts";
};

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

wallet-password-file = mkOption {
type = types.nullOr types.str;
default = null;
description = mdDoc "Path to a plain-text, .txt file containing your wallet password";
};

suggested-fee-recipient = mkOption {
type = types.nullOr types.str;
default = null;
description = mdDoc '' Sets ALL validators' mapping to a suggested eth\
address to receive gas fees when proposing a block. note\
that this is only a suggestion when integrating with a Builder API,\
which may choose to specify a different fee recipient as payment\
for the blocks it builds.'';
};

graffiti = mkOption {
type = types.nullOr types.str;
default = null;
description = mdDoc "String to include in proposed blocks";
};

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

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

eachValidator = config.services.ethereum.prysm-validator;
in {
###### interface
inherit (import ./options.nix {inherit lib pkgs;}) options;

###### implementation

config = mkIf (eachValidator != {}) {
# configure the firewall for each service
networking.firewall = let
openFirewall = filterAttrs (_: cfg: cfg.openFirewall) eachValidator;
perService =
mapAttrsToList
(
_: cfg:
with cfg.args; {
allowedTCPPorts =
[grpc-gateway-port]
++ (optionals rpc.enable [rpc.port])
++ (optionals (!disable-monitoring) [monitoring-port]);
}
)
openFirewall;
in
zipAttrsWith (_name: flatten) perService;

systemd.services =
mapAttrs'
(
validatorName: let
serviceName = "prysm-validator-${validatorName}";
beaconServiceName = "prysm-beacon-${validatorName}";
in
cfg: let
scriptArgs = let
# generate args
args = let
opts = import ./args.nix lib;
in
mkArgs {
inherit opts;
inherit (cfg) args;
};

# filter out certain args which need to be treated differently
specialArgs = [
"--datadir"
"--graffiti"
"--network"
"--rpc-enable"
"--user"
];
isNormalArg = name: (findFirst (arg: hasPrefix arg name) null specialArgs) == null;
filteredArgs = builtins.filter isNormalArg args;

network =
if cfg.args.network != null
then "--${cfg.args.network}"
else "";

datadir =
if cfg.args.datadir != null
then "--datadir ${cfg.args.datadir}"
else "--datadir %S/${beaconServiceName}";
in ''
--accept-terms-of-use \
${network} \
${datadir} \
${concatStringsSep " \\\n" filteredArgs} \
${lib.escapeShellArgs cfg.extraArgs}
'';
in
nameValuePair serviceName (mkIf cfg.enable {
after = ["network.target"];
wantedBy = ["multi-user.target"];
description = "Prysm Validator Node (${validatorName})";

environment = {
GRPC_GATEWAY_HOST = cfg.args.grpc-gateway-host;
GRPC_GATEWAY_PORT = builtins.toString cfg.args.grpc-gateway-port;
};

# create service config by merging with the base config
serviceConfig = mkMerge [
baseServiceConfig
{
User =
if cfg.args.user != null
then cfg.args.user
else beaconServiceName;
StateDirectory = serviceName;
ExecStart = "${cfg.package}/bin/validator ${scriptArgs}";
MemoryDenyWriteExecute = "false"; # causes a library loading error
}
];
})
)
eachValidator;
};
}
62 changes: 62 additions & 0 deletions modules/prysm-validator/default.test.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
systems = ["x86_64-linux"];

module = {
pkgs,
lib,
...
}:
with lib; let
wallet-generator = pkgs.writers.writeBashBin "wallet-generator" ''
set -eu -o errtrace -o pipefail
mkdir -p /tmp/wallet
password=12345678
echo $password > /tmp/wallet/password.txt
mnemonic="tooth moon mad fun romance athlete envelope next mix divert tip top symbol resemble stock family melody desk sheriff drift bargain need jaguar method"
echo $mnemonic > /tmp/wallet/mnemonic.txt
${pkgs.prysm}/bin/validator wallet create \
--accept-terms-of-use \
--goerli \
--keymanager-kind="direct" \
--mnemonic-25th-word-file /tmp/wallet/mnemonic.txt \
--skip-mnemonic-25th-word-check true \
--wallet-dir /tmp/wallet \
--wallet-password-file /tmp/wallet/password.txt
'';
in {
name = "prysm-validator";

nodes = {
machine = {
virtualisation.cores = 2;
virtualisation.memorySize = 4096;
virtualisation.writableStore = true;

environment.systemPackages = [wallet-generator pkgs.ethdo pkgs.prysm];

services.ethereum.prysm-validator.test = {
enable = true;
args = {
datadir = "/tmp/prysm-validator";
network = "goerli";
rpc.enable = true;
rpc.host = "127.0.0.1";
rpc.port = 7000;
wallet-dir = "/tmp/wallet/";
wallet-password-file = "/tmp/wallet/password.txt";
};
};

systemd.services.prysm-validator-test.serviceConfig.ExecStartPre = ''${wallet-generator}/bin/wallet-generator'';
};
};

testScript = ''
machine.wait_for_unit("prysm-validator-test.service")
'';
};
}
Loading

0 comments on commit a0d8d5c

Please sign in to comment.