Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b7ab607
docs: add Poseidon SSZ hasher documentation (#646)
zclawz Mar 12, 2026
6faa4b9
docs: add LeanSpec client documentation links in zeam.md (#645)
zclawz Mar 12, 2026
5becc21
fix: handle and fetch missing roots in gossip attestation and aggrega…
zclawz Mar 12, 2026
68ee3d1
docs: add proxy troubleshooting note for EndOfStream build errors (#660)
zclawz Mar 12, 2026
4c2d69f
pre-generated test keys for faster CI (#587)
anshalshuklabot Mar 12, 2026
0d0a989
fix: consolidate key loading logic into key-manager (#662)
zclawz Mar 13, 2026
2190013
fix: use while loop for reverse iteration (closes #664) (#665)
zclawz Mar 13, 2026
e8d6ac6
fix: skip STF re-processing for blocks already known to fork choice (…
zclawz Mar 13, 2026
5f5fe9e
fix: replace {any} with {f} for types with format methods, fix Gossip…
zclawz Mar 13, 2026
ac3083c
fix: properly free BeamState on error paths to prevent segfaults (clo…
zclawz Mar 16, 2026
cda4651
fix: wire attestation-committee-count CLI flag through to ChainOption…
zclawz Mar 18, 2026
71f6a67
refactor: use idiomatic iterators and avoid manual index loops (#667)
zclawz Mar 19, 2026
979610b
perf: iterate HashMap entries to avoid unnecessary hash lookups (clos…
zclawz Mar 19, 2026
bd3d49f
forkchoice: align /lean/v0/fork_choice response with leanSpec (#680)
ch4r10t33r Mar 19, 2026
eb601b3
fix: wipe stale database on genesis time mismatch (#638)
zclawz Mar 20, 2026
49762c9
devnet 3 metrics (#632)
bomanaps Mar 20, 2026
dcb7e75
refactor: extract wipeAndReopenDb helper, remove duplicated db-wipe l…
zclawz Mar 20, 2026
49824ea
chore: bump lean-quickstart to a96b5142ea37f7ec3f5ee7046e6d3111854912…
zclawz Mar 20, 2026
bfa1621
clock, chain, node: fix silent crashes during cached-block catch-up s…
ch4r10t33r Mar 23, 2026
af0ee66
cli: fix checkpoint sync panic on chunked HTTP responses (#689)
ch4r10t33r Mar 23, 2026
aac823e
fix: subnet subscription for attestations (#685)
zclawz Mar 25, 2026
c79b25d
fix: skip pre-finalized attestations instead of aborting block import…
ch4r10t33r Mar 25, 2026
5ac1439
refactor: update store to key by AttestationData (#656)
zclawz Mar 25, 2026
6c633d6
feat: use xev Dynamic API for runtime io_uring/epoll detection
GrapeBaBa Apr 1, 2026
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
4 changes: 2 additions & 2 deletions .github/workflows/auto-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,9 @@ jobs:
- name: Build zeam natively
run: |
if [ "${{ matrix.arch }}" = "amd64" ]; then
zig build -Doptimize=ReleaseFast -Dtarget=x86_64-linux-gnu -Dcpu=baseline -Dgit_version="$(git rev-parse --short HEAD)"
zig build -Doptimize=ReleaseSafe -Dtarget=x86_64-linux-gnu -Dcpu=baseline -Dgit_version="$(git rev-parse --short HEAD)"
else
zig build -Doptimize=ReleaseFast -Dtarget=aarch64-linux-gnu -Dcpu=baseline -Dgit_version="$(git rev-parse --short HEAD)"
zig build -Doptimize=ReleaseSafe -Dtarget=aarch64-linux-gnu -Dcpu=baseline -Dgit_version="$(git rev-parse --short HEAD)"
fi

- name: Build and push Docker image with pre-built binary (${{ matrix.arch }})
Expand Down
11 changes: 10 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Set up Zig
uses: mlugg/[email protected]
Expand Down Expand Up @@ -80,6 +82,8 @@ jobs:
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Free disk space (Ubuntu)
if: runner.os == 'Linux'
Expand Down Expand Up @@ -149,6 +153,8 @@ jobs:
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Free disk space (Ubuntu)
if: runner.os == 'Linux'
Expand Down Expand Up @@ -318,6 +324,8 @@ jobs:
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Free disk space (Ubuntu)
if: runner.os == 'Linux'
Expand Down Expand Up @@ -388,6 +396,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0 # Fetch full history to get git commit info

- name: Free disk space (Ubuntu)
Expand Down Expand Up @@ -439,7 +448,7 @@ jobs:
run: |
max_attempts=3
for attempt in $(seq 1 $max_attempts); do
if zig build -Doptimize=ReleaseFast -Dgit_version="$(git rev-parse --short HEAD)"; then
if zig build -Doptimize=ReleaseSafe -Dgit_version="$(git rev-parse --short HEAD)"; then
echo "Successfully built on attempt $attempt"
exit 0
fi
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "leanSpec"]
path = leanSpec
url = https://github.com/leanEthereum/leanSpec
[submodule "test-keys"]
path = test-keys
url = https://github.com/blockblaz/zeam-test-keys.git
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ RUN --mount=type=cache,target=/root/.cache/zig \
else \
GIT_VERSION=$(echo "$GIT_VERSION" | head -c 7); \
fi && \
zig build -Doptimize=ReleaseFast -Dgit_version="$GIT_VERSION"
zig build -Doptimize=ReleaseSafe -Dgit_version="$GIT_VERSION"

# Intermediate stage to prepare runtime libraries
FROM ubuntu:24.04 AS runtime-prep
Expand Down
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ Zeam is developing and contributing to the Zig Ethereum ecosystem. These librari

| Library | Description |
|---------|-------------|
| [ssz.zig](https://github.com/blockblaz/ssz.zig) | SSZ serialization with configurable hash function |
| [ssz.zig](https://github.com/blockblaz/ssz.zig) | SSZ serialization with configurable hash function (SHA256 or Poseidon2) |
| [zig-snappy](https://github.com/blockblaz/zig-snappy) / [snappyframesz](https://github.com/blockblaz/snappyframesz) | Snappy compression |
| [zig-libp2p-pocs](https://github.com/blockblaz/zig-libp2p-pocs) | Zig ↔ Rust libp2p interop |
| [hash-sigz](https://github.com/blockblaz/hash-sigz) | Hash-based signature schemes |
Expand Down Expand Up @@ -143,6 +143,8 @@ To include the git version in the binary:
zig build -Doptimize=ReleaseFast -Dgit_version="$(git rev-parse --short HEAD)"
```


> To use Poseidon2 as the SSZ hash function instead of SHA256, see [resources/poseidon.md](resources/poseidon.md).
### Running the Prover Demo

```bash
Expand Down Expand Up @@ -172,6 +174,24 @@ docker build -f Dockerfile.prebuilt \
-t blockblaz/zeam:latest .
```

### Troubleshooting

**Build fails with `EndOfStream` error**

If you encounter errors like:
```
error: invalid HTTP response: EndOfStream
```

This may be caused by proxy environment variables interfering with Zig's HTTP client (related to the [Zig HTTP connection pool bug](https://github.com/ziglang/zig/issues/21316) mentioned above).

Try building without proxy settings:
```bash
env -u https_proxy -u HTTPS_PROXY -u http_proxy -u HTTP_PROXY \
-u all_proxy -u ALL_PROXY -u no_proxy -u NO_PROXY \
zig build -Doptimize=ReleaseFast
```

---

## Running a Local Devnet
Expand Down
17 changes: 16 additions & 1 deletion build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ pub fn build(b: *Builder) !void {
// LTO option (disabled by default for faster builds)
const enable_lto = b.option(bool, "lto", "Enable Link Time Optimization (slower builds, smaller binaries)") orelse false;

// xev backend override is no longer needed — Dynamic API handles runtime detection.

// add ssz
const ssz = b.dependency("ssz", .{
.target = target,
Expand Down Expand Up @@ -156,6 +158,8 @@ pub fn build(b: *Builder) !void {
build_options.addOption(bool, "has_openvm", prover == .openvm or prover == .all);
const use_poseidon = b.option(bool, "use_poseidon", "Use Poseidon SSZ hasher (default: false)") orelse false;
build_options.addOption(bool, "use_poseidon", use_poseidon);
// Absolute path to test-keys for pre-generated validator keys
build_options.addOption([]const u8, "test_keys_path", b.pathFromRoot("test-keys/hash-sig-keys"));
const build_options_module = build_options.createModule();

// add zeam-utils
Expand Down Expand Up @@ -240,6 +244,7 @@ pub fn build(b: *Builder) !void {
.target = target,
.optimize = optimize,
});
zeam_key_manager.addImport("build_options", build_options_module);
zeam_key_manager.addImport("@zeam/xmss", zeam_xmss);
zeam_key_manager.addImport("@zeam/types", zeam_types);
zeam_key_manager.addImport("@zeam/utils", zeam_utils);
Expand Down Expand Up @@ -421,6 +426,10 @@ pub fn build(b: *Builder) !void {
tools_cli_exe.root_module.addImport("enr", enr);
tools_cli_exe.root_module.addImport("build_options", build_options_module);
tools_cli_exe.root_module.addImport("simargs", simargs);
tools_cli_exe.root_module.addImport("@zeam/xmss", zeam_xmss);
tools_cli_exe.root_module.addImport("@zeam/types", zeam_types);
tools_cli_exe.step.dependOn(&build_rust_lib_steps.step);
addRustGlueLib(b, tools_cli_exe, target, prover);

const install_tools_cli = b.addInstallArtifact(tools_cli_exe, .{});
tools_step.dependOn(&install_tools_cli.step);
Expand Down Expand Up @@ -602,6 +611,10 @@ pub fn build(b: *Builder) !void {
.root_module = tools_cli_exe.root_module,
});
tools_cli_tests.root_module.addImport("enr", enr);
tools_cli_tests.root_module.addImport("@zeam/xmss", zeam_xmss);
tools_cli_tests.root_module.addImport("@zeam/types", zeam_types);
tools_cli_tests.step.dependOn(&build_rust_lib_steps.step);
addRustGlueLib(b, tools_cli_tests, target, prover);
const run_tools_cli_test = b.addRunArtifact(tools_cli_tests);
setTestRunLabelFromCompile(b, run_tools_cli_test, tools_cli_tests);
tools_test_step.dependOn(&run_tools_cli_test.step);
Expand Down Expand Up @@ -722,6 +735,8 @@ fn build_zkvm_targets(
build_options_module: *std.Build.Module,
use_poseidon: bool,
) !void {
// zkvm targets (riscv32-freestanding-none) require ReleaseFast; ReleaseSafe
// triggers "invalid operand for inline asm constraint 'i'" in LLVM on riscv32.
const optimize = .ReleaseFast;

for (zkvm_targets) |zkvm_target| {
Expand Down Expand Up @@ -839,7 +854,7 @@ fn build_zkvm_targets(
.root_module = b.createModule(.{
.root_source_file = b.path("build/risc0.zig"),
.target = host_target,
.optimize = .ReleaseSafe,
.optimize = .ReleaseFast,
}),
});
const run_risc0_postbuild_gen_step = b.addRunArtifact(risc0_postbuild_gen);
Expand Down
1 change: 1 addition & 0 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
.name = .zeam,
.fingerprint = 0x243fd12cc5f554a5,
.version = "0.0.0",
.minimum_zig_version = "0.15.0",
.dependencies = .{
.ssz = .{
.url = "git+https://github.com/blockblaz/ssz.zig#c5394395dd7d0f8eda685c4723ad25ebbf550570",
Expand Down
2 changes: 1 addition & 1 deletion lean-quickstart
Submodule lean-quickstart updated 63 files
+ .DS_Store
+148 −65 .github/workflows/auto-release.yml
+165 −0 .github/workflows/ci.yml
+3 −0 .gitignore
+247 −11 README.md
+159 −0 TESTING_DEVNET3.md
+83 −19 ansible-devnet/genesis/validator-config.yaml
+10 −3 ansible/README.md
+2 −1 ansible/ansible.cfg
+127 −0 ansible/playbooks/clean-node-data.yml
+119 −5 ansible/playbooks/deploy-nodes.yml
+24 −6 ansible/playbooks/helpers/deploy-single-node.yml
+318 −0 ansible/playbooks/prepare.yml
+12 −4 ansible/playbooks/site.yml
+3 −8 ansible/roles/common/tasks/main.yml
+7 −0 ansible/roles/ethlambda/defaults/main.yml
+109 −0 ansible/roles/ethlambda/tasks/main.yml
+8 −0 ansible/roles/grandine/defaults/main.yml
+116 −0 ansible/roles/grandine/tasks/main.yml
+1 −1 ansible/roles/lantern/defaults/main.yml
+16 −22 ansible/roles/lantern/tasks/main.yml
+12 −9 ansible/roles/lighthouse/tasks/main.yml
+12 −0 ansible/roles/observability/defaults/main.yml
+141 −0 ansible/roles/observability/tasks/main.yml
+23 −0 ansible/roles/observability/templates/prometheus.yml.j2
+28 −0 ansible/roles/observability/templates/promtail.yml.j2
+1 −1 ansible/roles/qlean/defaults/main.yml
+21 −15 ansible/roles/qlean/tasks/main.yml
+12 −9 ansible/roles/ream/tasks/main.yml
+1 −1 ansible/roles/zeam/defaults/main.yml
+12 −9 ansible/roles/zeam/tasks/main.yml
+51 −0 client-cmds/ethlambda-cmd.sh
+59 −0 client-cmds/gean-cmd.sh
+57 −0 client-cmds/grandine-cmd.sh
+31 −7 client-cmds/lantern-cmd.sh
+28 −2 client-cmds/lighthouse-cmd.sh
+123 −0 client-cmds/nlean-cmd.sh
+49 −5 client-cmds/qlean-cmd.sh
+29 −3 client-cmds/ream-cmd.sh
+30 −4 client-cmds/zeam-cmd.sh
+125 −0 convert-validator-config.py
+229 −0 docs/devnets/devnet-1/devnet-1-retrospective.md
+ docs/devnets/devnet-1/images/devnet1-3-of-4-clients-finalization.png
+ docs/devnets/devnet-1/images/devnet1-head-sync.png
+ docs/devnets/devnet-1/images/devnet1-memory-issue.png
+ docs/devnets/devnet-1/images/devnet1-pq-signatures.png
+ docs/devnets/devnet-1/images/devnet1-storage-issue.png
+ docs/devnets/devnet-1/images/devnet1-summary-success.png
+5 −1 generate-ansible-inventory.sh
+1 −1 generate-genesis.sh
+76 −0 generate-prometheus-config.sh
+44 −0 local-devnet-nlean/genesis/validator-config.yaml
+84 −11 local-devnet/genesis/validator-config.yaml
+50 −0 metrics/docker-compose-metrics.yaml
+5,503 −0 metrics/grafana/dashboards/client-dashboard.json
+13 −0 metrics/grafana/provisioning/dashboards/dashboards.yml
+10 −0 metrics/grafana/provisioning/datasources/prometheus.yml
+41 −3 parse-env.sh
+34 −0 parse-vc.sh
+149 −0 release-process.md
+24 −2 run-ansible.sh
+291 −11 spin-node.sh
+98 −0 sync-leanpoint-upstreams.sh
1 change: 1 addition & 0 deletions pkgs/api/src/event_broadcaster.zig
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ test "event broadcaster basic functionality" {
// Test broadcasting an event (writes to the socket; read end fds[0] is left open so the write succeeds)
const proto_block = types.ProtoBlock{
.slot = 123,
.proposer_index = 0,
.blockRoot = [_]u8{1} ** 32,
.parentRoot = [_]u8{2} ** 32,
.stateRoot = [_]u8{3} ** 32,
Expand Down
2 changes: 2 additions & 0 deletions pkgs/api/src/events.zig
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ test "serialize new head event" {

const proto_block = types.ProtoBlock{
.slot = 123,
.proposer_index = 0,
.blockRoot = [_]u8{1} ** 32,
.parentRoot = [_]u8{2} ** 32,
.stateRoot = [_]u8{3} ** 32,
Expand Down Expand Up @@ -271,6 +272,7 @@ fn makeSampleChainEvent(allocator: Allocator, tag: ChainEventType) !ChainEvent {
.new_head => blk: {
const proto_block = types.ProtoBlock{
.slot = 999_999,
.proposer_index = 0,
.blockRoot = [_]u8{0xab} ** 32,
.parentRoot = [_]u8{0xcd} ** 32,
.stateRoot = [_]u8{0xef} ** 32,
Expand Down
41 changes: 13 additions & 28 deletions pkgs/cli/src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const node_lib = @import("@zeam/node");
const Clock = node_lib.Clock;
const state_proving_manager = @import("@zeam/state-proving-manager");
const BeamNode = node_lib.BeamNode;
const xev = @import("xev");
const xev = @import("xev").Dynamic;
const Multiaddr = @import("multiaddr").Multiaddr;

const configs = @import("@zeam/configs");
Expand All @@ -28,6 +28,7 @@ const Chain = configs.Chain;
const ChainOptions = configs.ChainOptions;

const utils_lib = @import("@zeam/utils");
const key_manager_lib = @import("@zeam/key-manager");
const zeam_metrics = @import("@zeam/metrics");

const database = @import("@zeam/database");
Expand Down Expand Up @@ -70,6 +71,8 @@ pub const NodeCommand = struct {
@"data-dir": []const u8 = constants.DEFAULT_DATA_DIR,
@"checkpoint-sync-url": ?[]const u8 = null,
@"is-aggregator": bool = false,
@"attestation-committee-count": ?u64 = null,
@"aggregate-subnet-ids": ?[]const u8 = null,

pub const __shorts__ = .{
.help = .h,
Expand All @@ -90,6 +93,8 @@ pub const NodeCommand = struct {
.@"data-dir" = "Path to the data directory",
.@"checkpoint-sync-url" = "URL to fetch finalized checkpoint state from for checkpoint sync (e.g., http://localhost:5052/lean/v0/states/finalized)",
.@"is-aggregator" = "Enable aggregator mode for committee signature aggregation",
.@"attestation-committee-count" = "Number of attestation committees (subnets); overrides config.yaml ATTESTATION_COMMITTEE_COUNT",
.@"aggregate-subnet-ids" = "Comma-separated list of subnet ids to additionally subscribe and aggregate gossip attestations (e.g. '0,1,2'); adds to automatic computation from validator ids",
.help = "Show help information for the node command",
};
};
Expand Down Expand Up @@ -276,6 +281,9 @@ fn mainInner() !void {

std.debug.print("opts={any} genesis={d}\n", .{ opts.args, genesis });

// Detect the best available I/O backend (io_uring or epoll on Linux).
node_lib.detectBackend();

switch (opts.args.__commands__) {
.clock => {
var loop = xev.Loop.init(.{}) catch |err| {
Expand Down Expand Up @@ -396,7 +404,6 @@ fn mainInner() !void {
var chain_options = (try json.parseFromSlice(ChainOptions, gpa.allocator(), chain_spec, options)).value;

// Create key manager FIRST to get validator pubkeys for genesis
const key_manager_lib = @import("@zeam/key-manager");
// Using 3 validators for 3-node setup with initial sync testing
// Nodes 1,2 start immediately; Node 3 starts after finalization to test sync
const num_validators: usize = 3;
Expand Down Expand Up @@ -484,7 +491,7 @@ fn mainInner() !void {
backend1 = network.getNetworkInterface();
backend2 = network.getNetworkInterface();
backend3 = network.getNetworkInterface();
logger1_config.logger(null).debug("--- mock gossip {any}", .{backend1.gossip});
logger1_config.logger(null).debug("--- mock gossip {f}", .{backend1.gossip});
} else {
network1 = try allocator.create(networks.EthLibp2p);
const key_pair1 = enr_lib.KeyPair.generate();
Expand Down Expand Up @@ -554,7 +561,7 @@ fn mainInner() !void {
.attestation_committee_count = chain_config.spec.attestation_committee_count,
}, logger3_config.logger(.network));
backend3 = network3.getNetworkInterface();
logger1_config.logger(null).debug("--- ethlibp2p gossip {any}", .{backend1.gossip});
logger1_config.logger(null).debug("--- ethlibp2p gossip {f}", .{backend1.gossip});
}

var clock = try allocator.create(Clock);
Expand Down Expand Up @@ -794,30 +801,8 @@ fn mainInner() !void {
};
defer allocator.free(sk_path);

const pk_file = std.fs.cwd().openFile(key_path, .{}) catch |err| {
ErrorHandler.logErrorWithDetails(err, "open public key file", .{ .path = key_path });
return err;
};
defer pk_file.close();
const pk_bytes = pk_file.readToEndAlloc(allocator, 256) catch |err| {
ErrorHandler.logErrorWithOperation(err, "read public key file");
return err;
};
defer allocator.free(pk_bytes);

const sk_file = std.fs.cwd().openFile(sk_path, .{}) catch |err| {
ErrorHandler.logErrorWithDetails(err, "open private key file", .{ .path = sk_path });
return err;
};
defer sk_file.close();
const sk_bytes = sk_file.readToEndAlloc(allocator, 16 * 1024 * 1024) catch |err| {
ErrorHandler.logErrorWithOperation(err, "read private key file");
return err;
};
defer allocator.free(sk_bytes);

keypair = xmss.KeyPair.fromSsz(allocator, sk_bytes, pk_bytes) catch |err| {
ErrorHandler.logErrorWithOperation(err, "load keypair from SSZ");
keypair = key_manager_lib.loadKeypairFromFiles(allocator, sk_path, key_path) catch |err| {
ErrorHandler.logErrorWithOperation(err, "load keypair from SSZ files");
return err;
};
} else if (cmd.@"private-key") |seed| {
Expand Down
Loading
Loading