From ad01137bff64a9d007673c1d5f5af78386fa805a Mon Sep 17 00:00:00 2001 From: Sam Hart Date: Sun, 13 Jul 2025 23:34:43 +0200 Subject: [PATCH 1/6] adds geth and lighthouse for mac --- .gitignore | 14 ++ flakeModules/default.nix | 4 + flakeModules/ethereum-development/default.nix | 49 +++++ nixosModules/default.nix | 6 + nixosModules/ethereum-node/default.nix | 193 ++++++++++++++++++ nixosModules/ethereum-node/node-opts.nix | 141 +++++++++++++ packages/default.nix | 1 + packages/ethereum.nix | 79 +++++++ templates/default.nix | 16 ++ templates/ethereum-node/flake.nix | 59 ++++++ 10 files changed, 562 insertions(+) create mode 100644 .gitignore create mode 100644 flakeModules/ethereum-development/default.nix create mode 100644 nixosModules/ethereum-node/default.nix create mode 100644 nixosModules/ethereum-node/node-opts.nix create mode 100644 packages/ethereum.nix create mode 100644 templates/ethereum-node/flake.nix diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a86ee78 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# Nix build artifacts +result* + +# Ethereum node data directories +data/ +geth/ +lighthouse/ + +# Ethereum authentication +*.hex +jwt.secret + +# Test directories +/tmp/test-* \ No newline at end of file diff --git a/flakeModules/default.nix b/flakeModules/default.nix index 1dafb87..e38123d 100644 --- a/flakeModules/default.nix +++ b/flakeModules/default.nix @@ -15,5 +15,9 @@ in imports = [ ./valence-contracts.nix ]; perSystem.valence-contracts = { inherit default-inputs; }; }; + ethereum-development = { + imports = [ ./ethereum-development/default.nix ]; + perSystem.ethereum-development = { inherit default-inputs; }; + }; }; } diff --git a/flakeModules/ethereum-development/default.nix b/flakeModules/ethereum-development/default.nix new file mode 100644 index 0000000..a6a082a --- /dev/null +++ b/flakeModules/ethereum-development/default.nix @@ -0,0 +1,49 @@ +# Ethereum development flake module for zero.nix +# Provides development environment with ethereum tools +{ default-inputs, ... }: +{ config, self', lib, ... }: +let + cfg = config.ethereum-development; +in +{ + options = { + ethereum-development.enable = lib.mkEnableOption "ethereum development environment"; + + ethereum-development.network = lib.mkOption { + type = lib.types.enum [ "mainnet" "goerli" "sepolia" "holesky" ]; + default = "sepolia"; + description = "Default ethereum network for development"; + }; + }; + + config = lib.mkIf cfg.enable { + devShells.ethereum = { pkgs, ... }: pkgs.mkShell { + name = "ethereum-development"; + + packages = with pkgs; [ + # Basic ethereum tools + self'.packages.geth + self'.packages.lighthouse + + # Development tools + curl + jq + openssl + ]; + + shellHook = '' + echo "Ethereum development environment" + echo "Network: ${cfg.network}" + echo "" + echo "Available tools:" + echo " - geth: Ethereum execution client" + echo " - lighthouse: Ethereum consensus client" + echo "" + echo "Quick start:" + echo " geth --${cfg.network} --datadir ./data/geth --http --ws" + echo " lighthouse bn --network ${cfg.network} --datadir ./data/lighthouse" + echo "" + ''; + }; + }; +} \ No newline at end of file diff --git a/nixosModules/default.nix b/nixosModules/default.nix index 9acbf11..35f0ec2 100644 --- a/nixosModules/default.nix +++ b/nixosModules/default.nix @@ -16,5 +16,11 @@ zero-nix = self'; } ); + ethereum-node = moduleWithSystem ( + {self', ... }: + import ./ethereum-node { + zero-nix = self'; + } + ); }; } diff --git a/nixosModules/ethereum-node/default.nix b/nixosModules/ethereum-node/default.nix new file mode 100644 index 0000000..d40e7a8 --- /dev/null +++ b/nixosModules/ethereum-node/default.nix @@ -0,0 +1,193 @@ +# Main ethereum node module for zero.nix +# Provides systemd services for geth and lighthouse +{ zero-nix, ... }: +{ config, options, lib, pkgs, ... }: +let + inherit (lib) types; + + cfg = config.services.ethereum; + + nodeNames = lib.attrNames cfg.nodes; + + getPort = port: portIndex: port + portIndex; + + defaultNodeAddressesModule = { name, ... }: + let + portIndex = lib.lists.findFirstIndex (x: x == name) null nodeNames; + defaults = cfg.nodeDefaults; + in + { + execution = { + rpcPort = getPort defaults.execution.rpcPort portIndex; + p2pPort = getPort defaults.execution.p2pPort portIndex; + }; + consensus = { + restPort = getPort defaults.consensus.restPort portIndex; + p2pPort = getPort defaults.consensus.p2pPort portIndex; + }; + }; + + nodeOpts = { + imports = [ + ./node-opts.nix + ]; + _module.args = { + inherit pkgs nodeNames; + }; + }; + + # Generate JWT secret for Engine API authentication + mkJwtSecret = name: nodeCfg: { + name = "ethereum-jwt-${name}"; + value = { + description = "Generate JWT secret for ${name}"; + wantedBy = [ "multi-user.target" ]; + script = '' + if [ ! -f "${nodeCfg.jwtSecret}" ]; then + mkdir -p "$(dirname "${nodeCfg.jwtSecret}")" + openssl rand -hex 32 > "${nodeCfg.jwtSecret}" + chmod 600 "${nodeCfg.jwtSecret}" + fi + ''; + serviceConfig = { + Type = "oneshot"; + User = "ethereum"; + Group = "ethereum"; + }; + }; + }; + + # Create geth execution client service + mkGethService = name: nodeCfg: + let + gethCmd = "${zero-nix.packages.geth}/bin/geth"; + networkFlag = if nodeCfg.execution.network == "mainnet" then "" else "--${nodeCfg.execution.network}"; + in + { + name = "ethereum-execution-${name}"; + value = { + description = "Geth execution client for ${name}"; + wantedBy = [ "multi-user.target" ]; + wants = [ "ethereum-jwt-${name}.service" ]; + after = [ "network.target" "ethereum-jwt-${name}.service" ]; + + environment = { + HOME = nodeCfg.execution.datadir; + }; + + script = '' + ${gethCmd} ${networkFlag} \ + --datadir "${nodeCfg.execution.datadir}" \ + --http --http.addr "0.0.0.0" --http.port ${toString nodeCfg.execution.rpcPort} \ + --http.api "eth,net,web3,txpool" \ + --ws --ws.addr "0.0.0.0" --ws.port ${toString (nodeCfg.execution.rpcPort + 1)} \ + --ws.api "eth,net,web3,txpool" \ + --port ${toString nodeCfg.execution.p2pPort} \ + --syncmode ${nodeCfg.execution.syncMode} \ + --authrpc.addr "127.0.0.1" --authrpc.port ${toString (nodeCfg.execution.rpcPort + 100)} \ + --authrpc.jwtsecret "${nodeCfg.jwtSecret}" \ + --metrics --metrics.addr "127.0.0.1" --metrics.port ${toString (nodeCfg.execution.rpcPort + 200)} + ''; + + serviceConfig = { + Type = "exec"; + User = "ethereum"; + Group = "ethereum"; + StateDirectory = "ethereum-node-${name}"; + WorkingDirectory = nodeCfg.execution.datadir; + }; + }; + }; + + # Create lighthouse consensus client service + mkLighthouseService = name: nodeCfg: + let + lighthouseCmd = "${zero-nix.packages.lighthouse}/bin/lighthouse"; + networkFlag = if nodeCfg.consensus.network == "mainnet" then "mainnet" else nodeCfg.consensus.network; + checkpointSyncArgs = lib.optionalString (nodeCfg.consensus.checkpointSyncUrl != null) + "--checkpoint-sync-url ${nodeCfg.consensus.checkpointSyncUrl}"; + in + { + name = "ethereum-consensus-${name}"; + value = { + description = "Lighthouse consensus client for ${name}"; + wantedBy = [ "multi-user.target" ]; + wants = [ "ethereum-execution-${name}.service" ]; + after = [ "network.target" "ethereum-execution-${name}.service" ]; + + environment = { + HOME = nodeCfg.consensus.datadir; + }; + + script = '' + ${lighthouseCmd} bn \ + --network ${networkFlag} \ + --datadir "${nodeCfg.consensus.datadir}" \ + --http --http-address "0.0.0.0" --http-port ${toString nodeCfg.consensus.restPort} \ + --port ${toString nodeCfg.consensus.p2pPort} \ + --execution-endpoint "http://127.0.0.1:${toString (nodeCfg.execution.rpcPort + 100)}" \ + --execution-jwt "${nodeCfg.jwtSecret}" \ + --metrics --metrics-address "127.0.0.1" --metrics-port ${toString (nodeCfg.consensus.restPort + 100)} \ + ${checkpointSyncArgs} + ''; + + serviceConfig = { + Type = "exec"; + User = "ethereum"; + Group = "ethereum"; + StateDirectory = "ethereum-node-${name}"; + WorkingDirectory = nodeCfg.consensus.datadir; + }; + }; + }; + +in +{ + options = { + services.ethereum.nodeDefaults = lib.mkOption { + type = types.submodule { + _module.args.name = lib.mkForce ""; + imports = [ nodeOpts ]; + }; + default = {}; + }; + + services.ethereum.nodes = lib.mkOption { + type = types.attrsOf (types.submodule { + imports = [ + nodeOpts + defaultNodeAddressesModule + ] ++ options.services.ethereum.nodeDefaults.definitions; + }); + default = {}; + }; + }; + + config = { + # Create ethereum user and group + users.users.ethereum = { + isSystemUser = true; + group = "ethereum"; + home = "/var/lib/ethereum"; + createHome = true; + }; + + users.groups.ethereum = {}; + + # Create systemd services + systemd.services = + (lib.mapAttrs' mkJwtSecret cfg.nodes) // + (lib.mapAttrs' mkGethService (lib.filterAttrs (n: v: v.enable && v.execution.enable) cfg.nodes)) // + (lib.mapAttrs' mkLighthouseService (lib.filterAttrs (n: v: v.enable && v.consensus.enable) cfg.nodes)); + + # Open firewall ports if requested + networking.firewall.allowedTCPPorts = lib.flatten ( + lib.mapAttrsToList (n: v: lib.optionals v.openFirewall [ + v.execution.rpcPort + v.execution.p2pPort + v.consensus.restPort + v.consensus.p2pPort + ]) cfg.nodes + ); + }; +} \ No newline at end of file diff --git a/nixosModules/ethereum-node/node-opts.nix b/nixosModules/ethereum-node/node-opts.nix new file mode 100644 index 0000000..e6d73bb --- /dev/null +++ b/nixosModules/ethereum-node/node-opts.nix @@ -0,0 +1,141 @@ +# Node options for ethereum nodes +# Provides configuration options for geth and lighthouse +{ name, lib, pkgs, nodeNames, config, options, ... }: +let + inherit (lib) types; + + tomlFormat = pkgs.formats.toml {}; + jsonFormat = pkgs.formats.json {}; + + executionClientOpts = { + options = { + enable = lib.mkOption { + type = types.bool; + default = true; + description = "Whether to enable the execution client"; + }; + + client = lib.mkOption { + type = types.enum [ "geth" ]; + default = "geth"; + description = "Which execution client to use"; + }; + + network = lib.mkOption { + type = types.enum [ "mainnet" "goerli" "sepolia" "holesky" ]; + default = "mainnet"; + description = "Ethereum network to connect to"; + }; + + datadir = lib.mkOption { + type = types.path; + default = "/var/lib/ethereum-node-${name}/execution"; + description = "Data directory for execution client"; + }; + + rpcPort = lib.mkOption { + type = types.port; + default = 8545; + description = "HTTP RPC port"; + }; + + p2pPort = lib.mkOption { + type = types.port; + default = 30303; + description = "P2P networking port"; + }; + + syncMode = lib.mkOption { + type = types.enum [ "snap" "full" ]; + default = "snap"; + description = "Synchronization mode"; + }; + }; + }; + + consensusClientOpts = { + options = { + enable = lib.mkOption { + type = types.bool; + default = true; + description = "Whether to enable the consensus client"; + }; + + client = lib.mkOption { + type = types.enum [ "lighthouse" ]; + default = "lighthouse"; + description = "Which consensus client to use"; + }; + + network = lib.mkOption { + type = types.enum [ "mainnet" "goerli" "sepolia" "holesky" ]; + default = "mainnet"; + description = "Ethereum network to connect to"; + }; + + datadir = lib.mkOption { + type = types.path; + default = "/var/lib/ethereum-node-${name}/consensus"; + description = "Data directory for consensus client"; + }; + + restPort = lib.mkOption { + type = types.port; + default = 5052; + description = "REST API port"; + }; + + p2pPort = lib.mkOption { + type = types.port; + default = 9000; + description = "P2P networking port"; + }; + + checkpointSyncUrl = lib.mkOption { + type = types.nullOr types.str; + default = null; + description = "Checkpoint sync URL for faster sync"; + }; + }; + }; + +in +{ + options = { + enable = lib.mkOption { + type = types.bool; + default = true; + description = "Whether to enable this ethereum node"; + }; + + execution = lib.mkOption { + type = types.submodule executionClientOpts; + default = {}; + description = "Execution client configuration"; + }; + + consensus = lib.mkOption { + type = types.submodule consensusClientOpts; + default = {}; + description = "Consensus client configuration"; + }; + + jwtSecret = lib.mkOption { + type = types.path; + default = "/var/lib/ethereum-node-${name}/jwt.hex"; + description = "JWT secret file path for Engine API authentication"; + }; + + dataDir = lib.mkOption { + type = types.path; + default = "/var/lib/ethereum-node-${name}"; + description = "Base data directory for the node"; + }; + + openFirewall = lib.mkOption { + type = types.bool; + default = false; + description = "Whether to open firewall ports"; + }; + }; +} \ No newline at end of file diff --git a/packages/default.nix b/packages/default.nix index 87bf224..38a8a9c 100644 --- a/packages/default.nix +++ b/packages/default.nix @@ -1,6 +1,7 @@ { imports = [ ./valence-contracts.nix + ./ethereum.nix ]; perSystem = { pkgs, config, ... }: { packages = { diff --git a/packages/ethereum.nix b/packages/ethereum.nix new file mode 100644 index 0000000..7fb1bac --- /dev/null +++ b/packages/ethereum.nix @@ -0,0 +1,79 @@ +# Ethereum packages module for zero.nix +# Provides geth and lighthouse packages +{ + perSystem = { system, pkgs, ... }: { + packages = { + # Geth - Ethereum execution client + geth = pkgs.buildGoModule rec { + pname = "geth"; + version = "1.15.6"; + + src = pkgs.fetchFromGitHub { + owner = "ethereum"; + repo = "go-ethereum"; + rev = "v${version}"; + hash = "sha256-BdNv0rx+9/F0leNj2AAej8psy8X8HysDrIXheVOOkSo="; + }; + + vendorHash = "sha256-KRVI1DxjoABZFJkmjGaMVlmxIHvtSFuvmpuMuvr8Pws="; + + doCheck = false; + + subPackages = [ "cmd/geth" ]; + + # Build configuration + env.CGO_ENABLED = "0"; + buildFlags = [ "-mod=readonly" ]; + + meta = with pkgs.lib; { + description = "Official Go implementation of the Ethereum protocol"; + homepage = "https://github.com/ethereum/go-ethereum"; + license = licenses.lgpl3Plus; + mainProgram = "geth"; + }; + }; + + # Lighthouse - Ethereum consensus client + lighthouse = pkgs.rustPlatform.buildRustPackage rec { + pname = "lighthouse"; + version = "5.3.0"; + + src = pkgs.fetchFromGitHub { + owner = "sigp"; + repo = "lighthouse"; + rev = "v${version}"; + hash = "sha256-wIj+YabyUrgLjWCfjCAH/Xb8jUG6ss+5SwnE2M82a+4="; + }; + + cargoHash = "sha256-v/gOTbkzcwmqV8XCzkLzAl6LyshVBWxUclZxx1mr53o="; + useFetchCargoVendor = true; + + buildInputs = with pkgs; [ + openssl + ] ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [ + pkgs.darwin.apple_sdk.frameworks.Security + pkgs.darwin.apple_sdk.frameworks.SystemConfiguration + ]; + + nativeBuildInputs = with pkgs; [ + pkg-config + protobuf + cmake + ]; + + # Disable tests that require network access + doCheck = false; + + # Build only the lighthouse binary + cargoBuildFlags = [ "--bin" "lighthouse" ]; + + meta = with pkgs.lib; { + description = "Ethereum consensus client written in Rust"; + homepage = "https://github.com/sigp/lighthouse"; + license = licenses.asl20; + mainProgram = "lighthouse"; + }; + }; + }; + }; +} \ No newline at end of file diff --git a/templates/default.nix b/templates/default.nix index d4c11bf..4a54824 100644 --- a/templates/default.nix +++ b/templates/default.nix @@ -18,5 +18,21 @@ in ${commonWelcome} ''; }; + ethereum-node = { + path = ./ethereum-node; + description = "Ethereum node deployment with geth and lighthouse"; + welcomeText = '' + # Ethereum node deployment + ## Provided services + - geth: Ethereum execution client + - lighthouse: Ethereum consensus client + + ## Usage + - Deploy: `nixos-rebuild switch --flake .#ethereum-node` + - Monitor: `systemctl status ethereum-execution-mainnet ethereum-consensus-mainnet` + + ${commonWelcome} + ''; + }; }; } diff --git a/templates/ethereum-node/flake.nix b/templates/ethereum-node/flake.nix new file mode 100644 index 0000000..39d29eb --- /dev/null +++ b/templates/ethereum-node/flake.nix @@ -0,0 +1,59 @@ +{ + description = "Ethereum node deployment using zero.nix"; + + nixConfig.extra-experimental-features = "nix-command flakes"; + nixConfig.extra-substituters = "https://timewave.cachix.org"; + nixConfig.extra-trusted-public-keys = '' + timewave.cachix.org-1:nu3Uqsm3sikI9xFK3Mt4AD4Q6z+j6eS9+kND1vtznq4= + ''; + + inputs = { + nixpkgs.url = "nixpkgs/nixos-24.11"; + flake-parts.url = "github:hercules-ci/flake-parts"; + zero-nix.url = "github:timewave-computer/zero.nix"; + }; + + outputs = { + self, + flake-parts, + ... + } @ inputs: + flake-parts.lib.mkFlake {inherit inputs;} { + systems = [ + "aarch64-linux" + "x86_64-linux" + ]; + + flake = { + nixosConfigurations.ethereum-node = inputs.nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + inputs.zero-nix.nixosModules.ethereum-node + { + # Basic system configuration + boot.isContainer = true; + system.stateVersion = "23.11"; + networking.hostName = "ethereum-node"; + + # Ethereum node configuration + services.ethereum.nodes.mainnet = { + execution = { + network = "mainnet"; + syncMode = "snap"; + }; + consensus = { + network = "mainnet"; + checkpointSyncUrl = "https://mainnet.checkpoint.sigp.io"; + }; + openFirewall = true; + }; + + # Disable systemd-resolved for container compatibility + systemd.services.systemd-resolved.enable = false; + networking.useHostResolvConf = true; + } + ]; + }; + }; + }; +} \ No newline at end of file From 6b1598ab8e188ba8f095ffdd7bb0f9abf5710bf7 Mon Sep 17 00:00:00 2001 From: Sam Hart Date: Sun, 13 Jul 2025 23:44:27 +0200 Subject: [PATCH 2/6] update documentation summary and index files --- docs/reference/flake-modules/index.md | 2 +- docs/reference/nixos-modules/index.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 docs/reference/nixos-modules/index.md diff --git a/docs/reference/flake-modules/index.md b/docs/reference/flake-modules/index.md index 8e2055a..4c44936 100644 --- a/docs/reference/flake-modules/index.md +++ b/docs/reference/flake-modules/index.md @@ -1,4 +1,4 @@ # Flake Modules Reference - [Upload Contracts Options](./upload-contracts.md) -- [Upload Valence Contracts Options](./upload-valence-contracts.md) +- [Valence Contracts Options](./valence-contracts.md) diff --git a/docs/reference/nixos-modules/index.md b/docs/reference/nixos-modules/index.md new file mode 100644 index 0000000..5650642 --- /dev/null +++ b/docs/reference/nixos-modules/index.md @@ -0,0 +1 @@ +# NixOS Modules Reference \ No newline at end of file From bff07188c89a011045169fb3183c934cdb8bf0c3 Mon Sep 17 00:00:00 2001 From: Sam Hart Date: Mon, 14 Jul 2025 00:01:36 +0200 Subject: [PATCH 3/6] address PR comments --- flakeModules/ethereum-development/default.nix | 100 ++++++++++-------- nixosModules/ethereum-node/default.nix | 5 +- nixosModules/ethereum-node/node-opts.nix | 9 -- templates/ethereum-node/flake.nix | 57 +++++----- 4 files changed, 90 insertions(+), 81 deletions(-) diff --git a/flakeModules/ethereum-development/default.nix b/flakeModules/ethereum-development/default.nix index a6a082a..260faa3 100644 --- a/flakeModules/ethereum-development/default.nix +++ b/flakeModules/ethereum-development/default.nix @@ -1,49 +1,65 @@ # Ethereum development flake module for zero.nix # Provides development environment with ethereum tools -{ default-inputs, ... }: -{ config, self', lib, ... }: -let - cfg = config.ethereum-development; -in { + lib, + flake-parts-lib, + ... +}: let + inherit (flake-parts-lib) mkPerSystemOption; + inherit (lib) types; +in { + _file = ./default.nix; options = { - ethereum-development.enable = lib.mkEnableOption "ethereum development environment"; - - ethereum-development.network = lib.mkOption { - type = lib.types.enum [ "mainnet" "goerli" "sepolia" "holesky" ]; - default = "sepolia"; - description = "Default ethereum network for development"; - }; - }; - - config = lib.mkIf cfg.enable { - devShells.ethereum = { pkgs, ... }: pkgs.mkShell { - name = "ethereum-development"; - - packages = with pkgs; [ - # Basic ethereum tools - self'.packages.geth - self'.packages.lighthouse + perSystem = mkPerSystemOption ( + { + config, + pkgs, + self', + ... + }: let + cfg = config.ethereum-development; + in { + options = { + ethereum-development.enable = lib.mkEnableOption "ethereum development environment"; + + ethereum-development.network = lib.mkOption { + type = lib.types.enum [ "mainnet" "goerli" "sepolia" "holesky" ]; + default = "sepolia"; + description = "Default ethereum network for development"; + }; + }; - # Development tools - curl - jq - openssl - ]; - - shellHook = '' - echo "Ethereum development environment" - echo "Network: ${cfg.network}" - echo "" - echo "Available tools:" - echo " - geth: Ethereum execution client" - echo " - lighthouse: Ethereum consensus client" - echo "" - echo "Quick start:" - echo " geth --${cfg.network} --datadir ./data/geth --http --ws" - echo " lighthouse bn --network ${cfg.network} --datadir ./data/lighthouse" - echo "" - ''; - }; + config = lib.mkIf cfg.enable { + devShells.ethereum = pkgs.mkShell { + name = "ethereum-development"; + + packages = with pkgs; [ + # Basic ethereum tools + self'.packages.geth + self'.packages.lighthouse + + # Development tools + curl + jq + openssl + ]; + + shellHook = '' + echo "Ethereum development environment" + echo "Network: ${cfg.network}" + echo "" + echo "Available tools:" + echo " - geth: Ethereum execution client" + echo " - lighthouse: Ethereum consensus client" + echo "" + echo "Quick start:" + echo " geth ${if cfg.network == "mainnet" then "" else "--${cfg.network}"} --datadir ./data/geth --http --ws" + echo " lighthouse bn --network ${if cfg.network == "mainnet" then "mainnet" else cfg.network} --datadir ./data/lighthouse" + echo "" + ''; + }; + }; + } + ); }; } \ No newline at end of file diff --git a/nixosModules/ethereum-node/default.nix b/nixosModules/ethereum-node/default.nix index d40e7a8..5288de3 100644 --- a/nixosModules/ethereum-node/default.nix +++ b/nixosModules/ethereum-node/default.nix @@ -53,6 +53,7 @@ let Type = "oneshot"; User = "ethereum"; Group = "ethereum"; + StateDirectory = "ethereum-node-${name}"; }; }; }; @@ -79,9 +80,9 @@ let ${gethCmd} ${networkFlag} \ --datadir "${nodeCfg.execution.datadir}" \ --http --http.addr "0.0.0.0" --http.port ${toString nodeCfg.execution.rpcPort} \ - --http.api "eth,net,web3,txpool" \ + --http.api "eth,net,web3" \ --ws --ws.addr "0.0.0.0" --ws.port ${toString (nodeCfg.execution.rpcPort + 1)} \ - --ws.api "eth,net,web3,txpool" \ + --ws.api "eth,net,web3" \ --port ${toString nodeCfg.execution.p2pPort} \ --syncmode ${nodeCfg.execution.syncMode} \ --authrpc.addr "127.0.0.1" --authrpc.port ${toString (nodeCfg.execution.rpcPort + 100)} \ diff --git a/nixosModules/ethereum-node/node-opts.nix b/nixosModules/ethereum-node/node-opts.nix index e6d73bb..356c978 100644 --- a/nixosModules/ethereum-node/node-opts.nix +++ b/nixosModules/ethereum-node/node-opts.nix @@ -4,9 +4,6 @@ let inherit (lib) types; - tomlFormat = pkgs.formats.toml {}; - jsonFormat = pkgs.formats.json {}; - executionClientOpts = { options = { enable = lib.mkOption { @@ -126,12 +123,6 @@ in description = "JWT secret file path for Engine API authentication"; }; - dataDir = lib.mkOption { - type = types.path; - default = "/var/lib/ethereum-node-${name}"; - description = "Base data directory for the node"; - }; - openFirewall = lib.mkOption { type = types.bool; default = false; diff --git a/templates/ethereum-node/flake.nix b/templates/ethereum-node/flake.nix index 39d29eb..f989d1c 100644 --- a/templates/ethereum-node/flake.nix +++ b/templates/ethereum-node/flake.nix @@ -25,35 +25,36 @@ ]; flake = { - nixosConfigurations.ethereum-node = inputs.nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; - modules = [ - inputs.zero-nix.nixosModules.ethereum-node - { - # Basic system configuration - boot.isContainer = true; - system.stateVersion = "23.11"; - networking.hostName = "ethereum-node"; - - # Ethereum node configuration - services.ethereum.nodes.mainnet = { - execution = { - network = "mainnet"; - syncMode = "snap"; + nixosConfigurations = inputs.nixpkgs.lib.genAttrs systems (system: + inputs.nixpkgs.lib.nixosSystem { + inherit system; + modules = [ + inputs.zero-nix.nixosModules.ethereum-node + { + # Basic system configuration + boot.isContainer = true; + system.stateVersion = "23.11"; + networking.hostName = "ethereum-node"; + + # Ethereum node configuration + services.ethereum.nodes.mainnet = { + execution = { + network = "mainnet"; + syncMode = "snap"; + }; + consensus = { + network = "mainnet"; + checkpointSyncUrl = "https://mainnet.checkpoint.sigp.io"; + }; + openFirewall = true; }; - consensus = { - network = "mainnet"; - checkpointSyncUrl = "https://mainnet.checkpoint.sigp.io"; - }; - openFirewall = true; - }; - - # Disable systemd-resolved for container compatibility - systemd.services.systemd-resolved.enable = false; - networking.useHostResolvConf = true; - } - ]; - }; + + # Disable systemd-resolved for container compatibility + systemd.services.systemd-resolved.enable = false; + networking.useHostResolvConf = true; + } + ]; + }); }; }; } \ No newline at end of file From 8bef6c5a2937475977b9ecbdf501d6ad201fb2d8 Mon Sep 17 00:00:00 2001 From: Sam Hart Date: Mon, 14 Jul 2025 06:58:15 +0200 Subject: [PATCH 4/6] addressing PR comments --- flakeModules/ethereum-development/default.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/flakeModules/ethereum-development/default.nix b/flakeModules/ethereum-development/default.nix index 260faa3..81c605e 100644 --- a/flakeModules/ethereum-development/default.nix +++ b/flakeModules/ethereum-development/default.nix @@ -15,6 +15,7 @@ in { config, pkgs, self', + system, ... }: let cfg = config.ethereum-development; From 5adbe89ab9e0cea0ee147cad1a06a1102e1d59d4 Mon Sep 17 00:00:00 2001 From: Sam Hart Date: Mon, 14 Jul 2025 17:29:21 +0200 Subject: [PATCH 5/6] Implement devshell improvements for ethereum-development module - Add devshell input to main flake.nix - Convert ethereum-development module to use numtide/devshell - Create ethereum-development template with devshell configuration - Enable ethereum-development in main flake for .#ethereum devshell --- flake.lock | 41 +++++++++++++-- flake.nix | 13 +++++ flakeModules/ethereum-development/default.nix | 51 +++++++++++-------- templates/default.nix | 18 +++++++ templates/ethereum-development/flake.nix | 40 +++++++++++++++ 5 files changed, 138 insertions(+), 25 deletions(-) create mode 100644 templates/ethereum-development/flake.nix diff --git a/flake.lock b/flake.lock index bd37018..afcac07 100644 --- a/flake.lock +++ b/flake.lock @@ -333,6 +333,24 @@ "type": "github" } }, + "devshell": { + "inputs": { + "nixpkgs": "nixpkgs_5" + }, + "locked": { + "lastModified": 1741473158, + "narHash": "sha256-kWNaq6wQUbUMlPgw8Y+9/9wP0F8SHkjy24/mN3UAppg=", + "owner": "numtide", + "repo": "devshell", + "rev": "7c9e793ebe66bcba8292989a68c0419b737a22a0", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "devshell", + "type": "github" + } + }, "dydx-src": { "flake": false, "locked": { @@ -1533,6 +1551,22 @@ } }, "nixpkgs_5": { + "locked": { + "lastModified": 1722073938, + "narHash": "sha256-OpX0StkL8vpXyWOGUD6G+MA26wAXK6SpT94kLJXo6B4=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e36e9f57337d0ff0cf77aceb58af4c805472bfae", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_6": { "locked": { "lastModified": 1743199800, "narHash": "sha256-8JlZ3nNqGPlGJnxu+X6jEyr2FZVHbSU9iZ+hjjU2Tx4=", @@ -1547,7 +1581,7 @@ "type": "github" } }, - "nixpkgs_6": { + "nixpkgs_7": { "locked": { "lastModified": 1736320768, "narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=", @@ -1697,9 +1731,10 @@ "inputs": { "cosmos-nix": "cosmos-nix", "crane": "crane", + "devshell": "devshell", "flake-parts": "flake-parts_2", "flake-parts-website": "flake-parts-website", - "nixpkgs": "nixpkgs_5", + "nixpkgs": "nixpkgs_6", "rust-overlay": "rust-overlay_2", "valence-contracts-main": "valence-contracts-main", "valence-contracts-v0_1_1": "valence-contracts-v0_1_1", @@ -1727,7 +1762,7 @@ }, "rust-overlay_2": { "inputs": { - "nixpkgs": "nixpkgs_6" + "nixpkgs": "nixpkgs_7" }, "locked": { "lastModified": 1743129211, diff --git a/flake.nix b/flake.nix index ac55db7..fd8d1a2 100644 --- a/flake.nix +++ b/flake.nix @@ -17,7 +17,9 @@ "x86_64-linux" ]; imports = [ + inputs.devshell.flakeModule ./flakeModules/default.nix + ./flakeModules/ethereum-development/default.nix ./nixosModules/default.nix ./packages/default.nix ./tools/default.nix @@ -25,6 +27,16 @@ ./templates/default.nix ./flakeModules/valence-contracts.nix ]; + + perSystem = { + pkgs, + inputs', + ... + }: { + # Enable ethereum development environment + ethereum-development.enable = true; + ethereum-development.network = "sepolia"; + }; }; inputs = { @@ -33,6 +45,7 @@ flake-parts.url = "github:hercules-ci/flake-parts"; crane.url = "github:ipetkov/crane"; rust-overlay.url = "github:oxalica/rust-overlay"; + devshell.url = "github:numtide/devshell"; flake-parts-website.url = "github:hercules-ci/flake.parts-website"; # This is a flake but we just need the render module # Theres a lot of inputs so it would pollute the lock file if its added as a flake diff --git a/flakeModules/ethereum-development/default.nix b/flakeModules/ethereum-development/default.nix index 81c605e..d22c601 100644 --- a/flakeModules/ethereum-development/default.nix +++ b/flakeModules/ethereum-development/default.nix @@ -31,32 +31,39 @@ in { }; config = lib.mkIf cfg.enable { - devShells.ethereum = pkgs.mkShell { + devshells.ethereum = { name = "ethereum-development"; - packages = with pkgs; [ - # Basic ethereum tools - self'.packages.geth - self'.packages.lighthouse - - # Development tools - curl - jq - openssl + commands = [ + { + package = self'.packages.geth; + help = "Ethereum execution client"; + } + { + package = self'.packages.lighthouse; + help = "Ethereum consensus client"; + } + { + package = pkgs.curl; + help = "HTTP client for API testing"; + } + { + package = pkgs.jq; + help = "JSON processor for API responses"; + } + { + package = pkgs.openssl; + help = "SSL/TLS toolkit"; + } ]; - shellHook = '' - echo "Ethereum development environment" - echo "Network: ${cfg.network}" - echo "" - echo "Available tools:" - echo " - geth: Ethereum execution client" - echo " - lighthouse: Ethereum consensus client" - echo "" - echo "Quick start:" - echo " geth ${if cfg.network == "mainnet" then "" else "--${cfg.network}"} --datadir ./data/geth --http --ws" - echo " lighthouse bn --network ${if cfg.network == "mainnet" then "mainnet" else cfg.network} --datadir ./data/lighthouse" - echo "" + motd = '' + {14}{bold}Ethereum development environment{reset} + {9}Network: ${cfg.network}{reset} + + {13}Quick start:{reset} + geth ${if cfg.network == "mainnet" then "" else "--${cfg.network}"} --datadir ./data/geth --http --ws + lighthouse bn --network ${if cfg.network == "mainnet" then "mainnet" else cfg.network} --datadir ./data/lighthouse ''; }; }; diff --git a/templates/default.nix b/templates/default.nix index 4a54824..0cbcc52 100644 --- a/templates/default.nix +++ b/templates/default.nix @@ -34,5 +34,23 @@ in ${commonWelcome} ''; }; + ethereum-development = { + path = ./ethereum-development; + description = "Ethereum development environment with geth and lighthouse"; + welcomeText = '' + # Ethereum development environment + ## Provided tools + - geth: Ethereum execution client + - lighthouse: Ethereum consensus client + - curl, jq, openssl: Development utilities + + ## Usage + - Enter shell: `nix develop` + - Start testnet: `geth --sepolia --datadir ./data/geth --http --ws` + - Start consensus: `lighthouse bn --network sepolia --datadir ./data/lighthouse` + + ${commonWelcome} + ''; + }; }; } diff --git a/templates/ethereum-development/flake.nix b/templates/ethereum-development/flake.nix new file mode 100644 index 0000000..16f08da --- /dev/null +++ b/templates/ethereum-development/flake.nix @@ -0,0 +1,40 @@ +{ + description = "Ethereum development environment using zero.nix"; + + nixConfig.extra-experimental-features = "nix-command flakes"; + nixConfig.extra-substituters = "https://timewave.cachix.org"; + nixConfig.extra-trusted-public-keys = '' + timewave.cachix.org-1:nu3Uqsm3sikI9xFK3Mt4AD4Q6z+j6eS9+kND1vtznq4= + ''; + + inputs = { + nixpkgs.url = "nixpkgs/nixos-24.11"; + flake-parts.url = "github:hercules-ci/flake-parts"; + devshell.url = "github:numtide/devshell"; + zero-nix.url = "github:timewave-computer/zero.nix"; + }; + + outputs = { + self, + flake-parts, + ... + } @ inputs: + flake-parts.lib.mkFlake {inherit inputs;} { + imports = [ + inputs.devshell.flakeModule + inputs.zero-nix.flakeModules.ethereum-development + ]; + + systems = ["x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin"]; + + perSystem = { + pkgs, + inputs', + ... + }: { + # Enable the ethereum development environment + ethereum-development.enable = true; + ethereum-development.network = "sepolia"; + }; + }; +} \ No newline at end of file From d60ff581dcb7bcc04d67f56529482ffdda41ef5c Mon Sep 17 00:00:00 2001 From: Sam Hart Date: Mon, 14 Jul 2025 22:07:35 +0200 Subject: [PATCH 6/6] Fix Linux builds and documentation issues - Add correct Linux hashes for solana-release and platform-tools - Replace manual wrapper scripts with wrapProgram in solana-tools.nix - Enable CGO for geth builds on Linux with proper dependencies - Add dontStrip = true to solana-node to avoid stripping text files - Fix documentation generation by making devshells conditional - Add devshell flake module to docs generation context - All packages now build correctly and documentation generates successfully --- docs/default.nix | 5 ++- flakeModules/ethereum-development/default.nix | 5 +-- packages/ethereum.nix | 12 ++++++- packages/solana-tools.nix | 31 +++++++------------ 4 files changed, 30 insertions(+), 23 deletions(-) diff --git a/docs/default.nix b/docs/default.nix index dac2a23..e29530f 100644 --- a/docs/default.nix +++ b/docs/default.nix @@ -36,7 +36,10 @@ docsFlake = flake-parts-lib.mkFlake { inherit inputs; } { systems = [ system ]; - imports = [ "${inputs.flake-parts-website}/render/render-module.nix" ]; + imports = [ + "${inputs.flake-parts-website}/render/render-module.nix" + inputs.devshell.flakeModule + ]; perSystem.render.officialFlakeInputs = inputs; perSystem.render.inputs = { diff --git a/flakeModules/ethereum-development/default.nix b/flakeModules/ethereum-development/default.nix index d22c601..0db1cad 100644 --- a/flakeModules/ethereum-development/default.nix +++ b/flakeModules/ethereum-development/default.nix @@ -16,6 +16,7 @@ in { pkgs, self', system, + options, ... }: let cfg = config.ethereum-development; @@ -30,7 +31,7 @@ in { }; }; - config = lib.mkIf cfg.enable { + config = lib.mkIf cfg.enable (lib.optionalAttrs (options ? devshells) { devshells.ethereum = { name = "ethereum-development"; @@ -66,7 +67,7 @@ in { lighthouse bn --network ${if cfg.network == "mainnet" then "mainnet" else cfg.network} --datadir ./data/lighthouse ''; }; - }; + }); } ); }; diff --git a/packages/ethereum.nix b/packages/ethereum.nix index 7fb1bac..641cc2f 100644 --- a/packages/ethereum.nix +++ b/packages/ethereum.nix @@ -22,9 +22,19 @@ subPackages = [ "cmd/geth" ]; # Build configuration - env.CGO_ENABLED = "0"; + env.CGO_ENABLED = if pkgs.stdenv.isLinux then "1" else "0"; buildFlags = [ "-mod=readonly" ]; + # Add necessary inputs for CGO on Linux + buildInputs = with pkgs; lib.optionals stdenv.isLinux [ + glibc + gcc + ]; + + nativeBuildInputs = with pkgs; lib.optionals stdenv.isLinux [ + pkg-config + ]; + meta = with pkgs.lib; { description = "Official Go implementation of the Ethereum protocol"; homepage = "https://github.com/ethereum/go-ethereum"; diff --git a/packages/solana-tools.nix b/packages/solana-tools.nix index a1375cd..8476ca1 100644 --- a/packages/solana-tools.nix +++ b/packages/solana-tools.nix @@ -47,7 +47,7 @@ let sha256 = if pkgs.stdenv.isDarwin then "sha256-upgxwAEvh11+IKVQ1FaZGlx8Z8Ps0CEScsbu4Hv3WH0=" # v2.0.22 macOS ARM64 hash else - "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; # TODO: Get correct Linux hash with: nix store prefetch-file --json https://github.com/anza-xyz/agave/releases/download/v2.0.22/solana-release-x86_64-unknown-linux-gnu.tar.bz2 + "sha256-kDXSnCXZHYSQt18AtnuBHcqyK9wxEemNx9on7CKW328="; # v2.0.22 Linux x86_64 hash }; # Platform tools source @@ -59,7 +59,7 @@ let sha256 = if pkgs.stdenv.isDarwin then "sha256-eZ5M/O444icVXIP7IpT5b5SoQ9QuAcA1n7cSjiIW0t0=" # v1.48 macOS ARM64 hash else - "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; # TODO: Get correct Linux hash with: nix store prefetch-file --json https://github.com/anza-xyz/platform-tools/releases/download/v1.48/platform-tools-linux-x86_64.tar.bz2 + "sha256-qdMVf5N9X2+vQyGjWoA14PgnEUpmOwFQ20kuiT7CdZc="; # v1.48 Linux x86_64 hash }; nativeBuildInputs = with pkgs; [ @@ -82,6 +82,9 @@ let darwin.apple_sdk.frameworks.Security darwin.apple_sdk.frameworks.SystemConfiguration ]; + + # Disable stripping to avoid issues with text files and scripts + dontStrip = true; unpackPhase = '' runHook preUnpack @@ -156,30 +159,20 @@ let # Fix broken symlinks find $out -type l ! -exec test -e {} \; -delete 2>/dev/null || true - # Create wrapper scripts for key tools + # Use wrapProgram to wrap key tools with proper environment for tool in solana solana-keygen solana-test-validator; do if [ -f "$out/bin/$tool" ]; then - # Backup original binary - mv "$out/bin/$tool" "$out/bin/.$tool-original" - - # Create wrapper script - cat > "$out/bin/$tool" << EOF -#!/bin/bash -export PLATFORM_TOOLS_DIR="$out/platform-tools" -export SBF_SDK_PATH="$out/platform-tools" -export PATH="$out/platform-tools/rust/bin:$out/platform-tools/llvm/bin:\$PATH" -exec "$out/bin/.$tool-original" "\$@" -EOF - chmod +x "$out/bin/$tool" + wrapProgram "$out/bin/$tool" \ + --set PLATFORM_TOOLS_DIR "$out/platform-tools" \ + --set SBF_SDK_PATH "$out/platform-tools" \ + --prefix PATH : "$out/platform-tools/rust/bin" \ + --prefix PATH : "$out/platform-tools/llvm/bin" fi done # Create special wrapper for cargo-build-sbf that bypasses platform tools installation if [ -f "$out/bin/cargo-build-sbf" ]; then - # Backup original binary - mv "$out/bin/cargo-build-sbf" "$out/bin/.cargo-build-sbf-original" - - # Create wrapper script that uses cargo directly with SBF target + # Create a custom script that uses cargo directly with SBF target cat > "$out/bin/cargo-build-sbf" << EOF #!/bin/bash export PLATFORM_TOOLS_DIR="$out/platform-tools"