-
-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement Reth service module (#531)
* Implement Reth service module * Remove mdDoc * Fix formatting
- Loading branch information
1 parent
72d993c
commit d3d8046
Showing
4 changed files
with
350 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ | |
./prysm-beacon | ||
./prysm-validator | ||
./restore | ||
./reth | ||
]; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
lib: | ||
with lib; { | ||
datadir = mkOption { | ||
type = types.nullOr types.str; | ||
default = null; | ||
description = "Data directory for Reth. Defaults to '%S/reth-\<name\>', which generally resolves to /var/lib/reth-\<name\>."; | ||
}; | ||
|
||
port = mkOption { | ||
type = types.port; | ||
default = 30303; | ||
description = "Network listening port."; | ||
}; | ||
|
||
chain = mkOption { | ||
type = types.enum [ | ||
"mainnet" | ||
"sepolia" | ||
"holesky" | ||
"dev" | ||
]; | ||
default = "mainnet"; | ||
description = "Name of the network to join. If null the network is mainnet."; | ||
}; | ||
|
||
full = mkOption { | ||
type = types.bool; | ||
default = false; | ||
description = "Run full node. Only the most recent [`MINIMUM_PRUNING_DISTANCE`] block states are stored. This flag takes priority over pruning configuration in reth.toml"; | ||
}; | ||
|
||
http = { | ||
enable = mkOption { | ||
type = types.bool; | ||
default = true; | ||
description = "Enable HTTP-RPC server"; | ||
}; | ||
|
||
addr = mkOption { | ||
type = types.str; | ||
default = "127.0.0.1"; | ||
description = "HTTP-RPC server listening interface."; | ||
}; | ||
|
||
port = mkOption { | ||
type = types.port; | ||
default = 8545; | ||
description = "HTTP-RPC server listening port."; | ||
}; | ||
|
||
corsdomain = mkOption { | ||
type = types.nullOr (types.listOf types.str); | ||
default = null; | ||
description = "List of domains from which to accept cross origin requests."; | ||
example = ["*"]; | ||
}; | ||
|
||
api = mkOption { | ||
type = types.nullOr (types.listOf types.str); | ||
description = "API's offered over the HTTP-RPC interface."; | ||
example = ["net" "eth"]; | ||
}; | ||
}; | ||
|
||
ws = { | ||
enable = mkEnableOption "Reth WebSocket API"; | ||
addr = mkOption { | ||
type = types.nullOr types.str; | ||
default = null; | ||
description = "WS server listening interface."; | ||
example = "127.0.0.1"; | ||
}; | ||
|
||
port = mkOption { | ||
type = types.nullOr types.port; | ||
default = null; | ||
description = "WS server listening port."; | ||
example = 8545; | ||
}; | ||
|
||
origins = mkOption { | ||
type = types.nullOr (types.listOf types.str); | ||
default = null; | ||
description = "List of origins from which to accept `WebSocket` requests"; | ||
}; | ||
|
||
api = mkOption { | ||
type = types.nullOr (types.listOf types.str); | ||
default = null; | ||
description = "API's offered over the WS interface."; | ||
example = ["net" "eth"]; | ||
}; | ||
}; | ||
|
||
authrpc = { | ||
addr = mkOption { | ||
type = types.str; | ||
default = "127.0.0.1"; | ||
description = "HTTP-RPC server listening interface for the Engine API."; | ||
}; | ||
|
||
port = mkOption { | ||
type = types.port; | ||
default = 8551; | ||
description = "HTTP-RPC server listening port for the Engine API"; | ||
}; | ||
|
||
jwtsecret = mkOption { | ||
type = types.nullOr types.str; | ||
default = null; | ||
description = "Path to the token that ensures safe connection between CL and EL."; | ||
example = "/var/run/reth/jwtsecret"; | ||
}; | ||
}; | ||
|
||
metrics = { | ||
enable = mkEnableOption "Enable Prometheus metrics collection and reporting."; | ||
|
||
addr = mkOption { | ||
type = types.str; | ||
default = "127.0.0.1"; | ||
description = "Enable stand-alone metrics HTTP server listening interface."; | ||
}; | ||
|
||
port = mkOption { | ||
type = types.port; | ||
default = 6060; | ||
description = "Metrics HTTP server listening port"; | ||
}; | ||
}; | ||
|
||
log = let | ||
mkFormatOpt = channel: | ||
mkOption { | ||
type = types.nullOr (types.enum ["terminal" "log-fmt" "json"]); | ||
default = null; | ||
description = "The format to use for logs written to ${channel}."; | ||
example = "log-fmt"; | ||
}; | ||
mkFilterOpt = channel: | ||
mkOption { | ||
type = types.nullOr types.str; | ||
default = null; | ||
description = "The filter to use for logs written to ${channel}."; | ||
example = "info"; | ||
}; | ||
in { | ||
stdout = { | ||
format = mkFormatOpt "stdout"; | ||
filter = mkFilterOpt "stdout"; | ||
}; | ||
file = { | ||
format = mkFormatOpt "the log file"; | ||
filter = mkFilterOpt "the log file"; | ||
directory = mkOption { | ||
type = types.nullOr types.str; | ||
default = null; | ||
description = "The path to put log files in"; | ||
example = "/var/log/reth"; | ||
}; | ||
}; | ||
journald = { | ||
filter = mkFilterOpt "journald"; | ||
}; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
{ | ||
config, | ||
lib, | ||
pkgs, | ||
... | ||
}: let | ||
inherit (lib.lists) optionals findFirst; | ||
inherit (lib.strings) hasPrefix; | ||
inherit | ||
(lib) | ||
concatStringsSep | ||
filterAttrs | ||
flatten | ||
mapAttrs' | ||
mapAttrsToList | ||
mkIf | ||
mkMerge | ||
nameValuePair | ||
zipAttrsWith | ||
; | ||
|
||
modulesLib = import ../lib.nix lib; | ||
inherit (modulesLib) baseServiceConfig mkArgs dotPathReducer; | ||
|
||
eachNode = config.services.ethereum.reth; | ||
in { | ||
# Disable the service definition currently in nixpkgs | ||
disabledModules = ["services/blockchain/ethereum/reth.nix"]; | ||
|
||
###### interface | ||
inherit (import ./options.nix {inherit lib pkgs;}) options; | ||
|
||
###### implementation | ||
|
||
config = mkIf (eachNode != {}) { | ||
# configure the firewall for each service | ||
networking.firewall = let | ||
openFirewall = filterAttrs (_: cfg: cfg.openFirewall) eachNode; | ||
perService = | ||
mapAttrsToList | ||
( | ||
_: cfg: | ||
with cfg.args; { | ||
allowedTCPPorts = | ||
[port authrpc.port] | ||
++ (optionals http.enable [http.port]) | ||
++ (optionals ws.enable [ws.port]) | ||
++ (optionals metrics.enable [metrics.port]); | ||
} | ||
) | ||
openFirewall; | ||
in | ||
zipAttrsWith (_name: flatten) perService; | ||
|
||
# configure systemd to create the state directory with a subvolume | ||
systemd.tmpfiles.rules = | ||
map | ||
(name: "v /var/lib/private/reth-${name}") | ||
(builtins.attrNames (filterAttrs (_: v: v.subVolume) eachNode)); | ||
|
||
# create a service for each instance | ||
systemd.services = | ||
mapAttrs' | ||
( | ||
rethName: let | ||
serviceName = "reth-${rethName}"; | ||
in | ||
cfg: let | ||
scriptArgs = let | ||
args = mkArgs { | ||
opts = import ./args.nix lib; | ||
pathReducer = dotPathReducer; | ||
args = builtins.removeAttrs cfg.args ["ws"]; | ||
}; | ||
|
||
wsArgs = mkArgs { | ||
opts = import ./args.nix lib; | ||
args = { | ||
ws = builtins.removeAttrs cfg.args.ws ["enable"]; | ||
}; | ||
}; | ||
|
||
# filter out certain args which need to be treated differently | ||
specialArgs = [ | ||
"--authrpc.jwtsecret" | ||
"--http.enable" | ||
"--metrics.enable" | ||
"--metrics.addr" | ||
"--metrics.port" | ||
]; | ||
|
||
isNormalArg = name: (findFirst (arg: hasPrefix arg name) null specialArgs) == null; | ||
filteredArgs = | ||
(builtins.filter isNormalArg args) | ||
++ (optionals cfg.args.http.enable ["--http"]) | ||
++ (optionals cfg.args.ws.enable wsArgs) | ||
++ (optionals cfg.args.metrics.enable ["--metrics" "${cfg.args.metrics.addr}:${toString cfg.args.metrics.port}"]); | ||
|
||
jwtSecret = | ||
if cfg.args.authrpc.jwtsecret != null | ||
then "--authrpc.jwtsecret %d/jwtsecret" | ||
else ""; | ||
|
||
datadir = | ||
if cfg.args.datadir != null | ||
then "${cfg.args.datadir}" | ||
else "%S/${serviceName}"; | ||
in '' | ||
--log.file.directory ${datadir}/logs \ | ||
--datadir ${datadir} \ | ||
${jwtSecret} \ | ||
${concatStringsSep " \\\n" filteredArgs} \ | ||
${lib.escapeShellArgs cfg.extraArgs} | ||
''; | ||
in | ||
nameValuePair serviceName (mkIf cfg.enable { | ||
description = "Reth Ethereum node (${rethName})"; | ||
wantedBy = ["multi-user.target"]; | ||
after = ["network.target"]; | ||
|
||
# create service config by merging with the base config | ||
serviceConfig = mkMerge [ | ||
baseServiceConfig | ||
{ | ||
User = serviceName; | ||
StateDirectory = serviceName; | ||
ExecStart = "${cfg.package}/bin/reth node ${scriptArgs}"; | ||
|
||
# Reth needs this system call for some reason | ||
SystemCallFilter = ["@system-service" "~@privileged" "mincore"]; | ||
} | ||
(mkIf (cfg.args.authrpc.jwtsecret != null) { | ||
LoadCredential = ["jwtsecret:${cfg.args.authrpc.jwtsecret}"]; | ||
}) | ||
]; | ||
}) | ||
) | ||
eachNode; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
{ | ||
lib, | ||
pkgs, | ||
... | ||
}: let | ||
args = import ./args.nix lib; | ||
|
||
rethOpts = with lib; { | ||
options = { | ||
enable = mkEnableOption "Reth Ethereum Node."; | ||
|
||
subVolume = mkEnableOption "Use a subvolume for the state directory if the underlying filesystem supports it e.g. btrfs"; | ||
|
||
inherit args; | ||
|
||
extraArgs = mkOption { | ||
type = types.listOf types.str; | ||
description = "Additional arguments to pass to Reth."; | ||
default = []; | ||
}; | ||
|
||
package = mkOption { | ||
type = types.package; | ||
default = pkgs.reth; | ||
defaultText = literalExpression "pkgs.reth"; | ||
description = "Package to use as Reth node."; | ||
}; | ||
|
||
openFirewall = mkOption { | ||
type = types.bool; | ||
default = false; | ||
description = lib."Open ports in the firewall for any enabled networking services"; | ||
}; | ||
}; | ||
}; | ||
in { | ||
options.services.ethereum.reth = with lib; | ||
mkOption { | ||
type = types.attrsOf (types.submodule rethOpts); | ||
default = {}; | ||
description = "Specification of one or more Reth instances."; | ||
}; | ||
} |