Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
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
125 changes: 124 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,125 @@
# qlean-mini
Lean Ethereum PQ devnet client implementation in C++

Lean Ethereum consensus client for devnets, implemented in modern C++.

qlean-mini follows the Lean Ethereum devnet specification:
- Spec: https://github.com/leanEthereum/leanSpec/

This repository builds a small, modular node binary called `qlean` and a set of loadable modules. It uses CMake, Ninja, and vcpkg (manifest mode) for dependency management.


## Quick start

### 0) Clone the repository and set system dependencies

```bash
git clone https://github.com/qdrvm/qlean-mini.git
cd qlean-mini
```

Prerequisites:
- CMake ≥ 3.25
- Ninja
- A C++23-capable compiler (AppleClang/Clang/GCC)
- Python 3 (required by the CMake build)
- vcpkg (manifest mode)

Note: Dependencies are also listed in the `.ci/.env` file for both macOS and Linux. To install on Debian Linux:

```bash
source .ci/.env
sudo apt update && sudo apt install -y $(echo $LINUX_PACKAGES)
```

Make sure gcc-$GCC_VERSION is default compiler (where GCC_VERSION is defined in .ci/.env):

```bash
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-$GCC_VERSION 100
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-$GCC_VERSION 100
```

For macOS:

```zsh
source .ci/.env
brew install $(echo $MACOS_PACKAGES)
```

### 1) Install and bootstrap vcpkg

Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Shell code blocks should use 'bash' instead of 'zsh' for better compatibility. While zsh-specific features like export commands work in bash, using 'bash' as the language identifier is more universally compatible.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense

```zsh
# Clone vcpkg somewhere on your machine (e.g. in your home directory)
git clone https://github.com/microsoft/vcpkg.git "$HOME/vcpkg"

# Bootstrap vcpkg
"$HOME/vcpkg"/bootstrap-vcpkg.sh

# Export VCPKG_ROOT for the current shell session (and add to ~/.zshrc if use zsh for convenience)
export VCPKG_ROOT="$HOME/vcpkg"
```

Notes:
- This project uses vcpkg in manifest mode with custom overlay ports from `vcpkg-overlay/` and the registry settings in `vcpkg-configuration.json`.
- CMake presets expect `CMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake`.

### 2) Configure and build

Use the provided CMake preset (generator: Ninja):

```zsh
# From the repository root
cmake --preset default
cmake --build build -j
```

You can also build the project's Docker images (builder + runtime) with:

```zsh
make docker_build_all
```

See the `Makefile` for more Docker targets.

This will:
- Configure the project into `./build/`
- Build the main node executable at `./build/src/executable/qlean`

### 3) Ensure build was successful
Print help:

```zsh
./build/src/executable/qlean --help
```

## Run the node

For step-by-step instructions to run a local single-node devnet, see `example/0-single/README.md`. It includes the exact CLI command and an explanation of all flags.


## Generate a node key

The binary includes a helper subcommand to generate a node key and corresponding PeerId:

```zsh
./build/src/executable/qlean key generate-node-key
```

This prints two lines:
1) The private key (hex)
2) The PeerId (base58)


## Tests

If `TESTING` is enabled (default ON in top-level CMakeLists), tests are built and can be run with CTest:

```zsh
# After building
ctest --test-dir build --output-on-failure
```

Individual test binaries are placed under `build/test_bin/`.

## License

SPDX-License-Identifier: Apache-2.0 — Copyright Quadrivium LLC
60 changes: 60 additions & 0 deletions example/0-single/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# 0-single — run a single-node devnet

This example shows how to start a one-node network using the `qlean` binary built from this repository.

Files in this folder:
- `genesis/config.yaml` — genesis settings (e.g., `GENESIS_TIME`, `VALIDATOR_COUNT`)
- `genesis/validators.yaml` — validator registry for this example
- `genesis/nodes.yaml` — static bootnodes list used at startup

Prerequisites:
- Build the project first. See the root README “Quick start” for setup and build steps.
- Run commands from the repository root so relative paths resolve correctly.

## Start the node

Before starting the node, update the GENESIS_TIME in the genesis config file to a future Unix timestamp (e.g., current time + 20 seconds) to allow the node to bootstrap the network. For example:

```bash
future_time=$(( $(date +%s) + 20 ))
sed -i "s/GENESIS_TIME: .*/GENESIS_TIME: $future_time/" example/0-single/genesis/config.yaml
```

Example CLI command:

Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Shell code blocks should use 'bash' instead of 'zsh' for better compatibility, as the commands shown are standard bash commands.

Copilot uses AI. Check for mistakes.
```zsh
./build/src/executable/qlean \
--modules-dir ./build/src/modules \
--bootnodes example/0-single/genesis/nodes.yaml \
--validator-registry-path example/0-single/genesis/validators.yaml \
--node-id node_0 \
--genesis example/0-single/genesis/config.yaml \
--node-key cb920fbda3b96e18f03e22825f4a5a61343ec43c7be1c8c4a717fffee2f4c4ce \
--listen-addr /ip4/0.0.0.0/udp/9000/quic-v1
```

## What each flag means

- `--modules-dir ./build/src/modules`
- Where the node looks for loadable modules built by this repository. The default build places them under `./build/src/modules`.
- `--bootnodes example/0-single/genesis/nodes.yaml`
- A YAML file with a list of peers (ENRs or multiaddrs) used for initial connectivity.
- `--validator-registry-path example/0-single/genesis/validators.yaml`
- The validator registry for this devnet. For a single-node network, it typically contains a single validator.
- `--node-id node_0`
- A human-friendly identifier used in logs and for distinguishing nodes when running multiple instances.
- `--genesis <path>`
- Genesis configuration describing initial chain parameters, such as `GENESIS_TIME` and the number of validators.
- `--node-key <hex>`
- Hex-encoded libp2p private key. Using a fixed key gives a stable PeerId across restarts. You can generate one with:

```zsh
./build/src/executable/qlean key generate-node-key
```
- `--listen-addr /ip4/0.0.0.0/udp/9000/quic-v1`
- libp2p multiaddress to bind the QUIC transport. Adjust the port if `9000` is taken, or bind to `127.0.0.1` if you want local-only access.

## Tips

- If the binary cannot find modules, double-check you built the project and the `--modules-dir` path is correct.
- You can run multiple nodes by copying this command and changing `--node-id`, `--node-key`, ports in `--listen-addr`, and using appropriate bootnodes.
5 changes: 5 additions & 0 deletions example/0-single/genesis/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Genesis Settings
GENESIS_TIME: 946684803

# Validator Settings
VALIDATOR_COUNT: 1
2 changes: 2 additions & 0 deletions example/0-single/genesis/nodes.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- enr:-IW4QIh9cSo0CPOcsTq5T6SAYr0HrGFMYekZjrgC7ZTgdsMhBKjIQgzCfgqsxulCO4O1TXyjRLZ3BYc4GgqVRvl3d1sBgmlkgnY0gmlwhAoAAAqEcXVpY4IjKIlzZWNwMjU2azGhAmVoFLEuozqtJ7uxbf6RlL8ow-UlDKSYLzxKcbpZ13Zg

2 changes: 2 additions & 0 deletions example/0-single/genesis/validators.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_0:
- 0
88 changes: 88 additions & 0 deletions example/1-network/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# 1-network — run a 4-validator devnet

This example shows how to start a 4-validator network using the `qlean` binary built from this repository.

Files in this folder:
- `genesis/config.yaml` — genesis settings (e.g., `GENESIS_TIME`, `VALIDATOR_COUNT`)
- `genesis/validators.yaml` — validator registry for this network
- `genesis/nodes.yaml` — bootnodes list used at startup

Prerequisites:
- Build the project first. See the root README “Quick start” for setup and build steps.
- Run commands from the repository root so relative paths resolve correctly.

## Prepare genesis time

Before starting nodes, set `GENESIS_TIME` in `example/1-network/genesis/config.yaml` to a future Unix timestamp so the chain can start. For example, current time + 20 seconds:

```bash
future_time=$(( $(date +%s) + 20 ))
sed -i "s/GENESIS_TIME: .*/GENESIS_TIME: $future_time/" example/1-network/genesis/config.yaml
```

## Start the 4 validators

Open four terminals (or run in background) and launch each node with its own key and ports.

Node 0:

Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Shell code blocks should use 'bash' instead of 'zsh' for better compatibility, as the commands shown are standard bash commands that work across different shells.

Copilot uses AI. Check for mistakes.
```zsh
./build/src/executable/qlean \
--modules-dir ./build/src/modules \
--bootnodes example/1-network/genesis/nodes.yaml \
--genesis example/1-network/genesis/config.yaml \
--validator-registry-path example/1-network/genesis/validators.yaml \
--node-id node_0 \
--node-key cb920fbda3b96e18f03e22825f4a5a61343ec43c7be1c8c4a717fffee2f4c4ce \
--listen-addr /ip4/0.0.0.0/udp/9000/quic-v1 \
--prometheus_port 9100
```

Node 1:

```zsh
./build/src/executable/qlean \
--modules-dir ./build/src/modules \
--bootnodes example/1-network/genesis/nodes.yaml \
--genesis example/1-network/genesis/config.yaml \
--validator-registry-path example/1-network/genesis/validators.yaml \
--node-id node_1 \
--node-key a87e7d23bb1de4613b67002b700bce41e031f4ab1529a3436bd73c893ea039b3 \
--listen-addr /ip4/0.0.0.0/udp/9001/quic-v1 \
--prometheus_port 9101
```

Node 2:

```zsh
./build/src/executable/qlean \
--modules-dir ./build/src/modules \
--bootnodes example/1-network/genesis/nodes.yaml \
--genesis example/1-network/genesis/config.yaml \
--validator-registry-path example/1-network/genesis/validators.yaml \
--node-id node_2 \
--node-key f2f53f6acf312c5e92c2a611bbca7a1932b4db0b9e0c43bec413badca9b76760 \
--listen-addr /ip4/0.0.0.0/udp/9002/quic-v1 \
--prometheus_port 9102
```

Node 3:

```zsh
./build/src/executable/qlean \
--modules-dir ./build/src/modules \
--bootnodes example/1-network/genesis/nodes.yaml \
--genesis example/1-network/genesis/config.yaml \
--validator-registry-path example/1-network/genesis/validators.yaml \
--node-id node_3 \
--node-key fa5ddbec80f964d17d28221c2c5bac0f4a3f9cfcf4b86674e605f459e195a1c4 \
--listen-addr /ip4/0.0.0.0/udp/9003/quic-v1 \
--prometheus_port 9103
```

## Notes and tips

- Start Node 0 first so others can discover it via the bootnodes list; the rest can follow in any order.
- Ports (9000–9003) must be free; adjust if they’re taken. Prometheus ports (9100–9103) are optional and can be changed or omitted.
- For an explanation of the common flags, see `example/0-single/README.md` (the meanings are the same).

5 changes: 5 additions & 0 deletions example/1-network/genesis/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Genesis Settings
GENESIS_TIME: 946684803

# Validator Settings
VALIDATOR_COUNT: 4
5 changes: 5 additions & 0 deletions example/1-network/genesis/nodes.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- enr:-IW4QIh9cSo0CPOcsTq5T6SAYr0HrGFMYekZjrgC7ZTgdsMhBKjIQgzCfgqsxulCO4O1TXyjRLZ3BYc4GgqVRvl3d1sBgmlkgnY0gmlwhAoAAAqEcXVpY4IjKIlzZWNwMjU2azGhAmVoFLEuozqtJ7uxbf6RlL8ow-UlDKSYLzxKcbpZ13Zg
- enr:-IW4QG_-KOB94fI18bEqxW8B_lKgG3cGVdvKIIKdZJBLftoaZE5y3Vg4PFQQIPmIRpeD1QawVKrd_6HDd1D2K7WLsLQBgmlkgnY0gmlwhAoAAAuEcXVpY4IjKYlzZWNwMjU2azGhA8PFJzjZs3Nmzn34yVzbnN5Mo5RhzwiWDxLnmoW1U7AV
- enr:-IW4QDEHjhkVEcEX5GS4qAjAHbiqCevwjwFd6ce1SYxLEIgYXDmozUjm8ao4Nl1YoFVhNBs1cn8zW2kwb6yaJpgVDLkBgmlkgnY0gmlwhAoAAAyEcXVpY4IjKolzZWNwMjU2azGhAsQeX5os8a2pG4v2cGuMMXZYY2B-yzYLcZM3yEHa3_kW
- enr:-IW4QEgn7uYKIhbom8qWeFGWBTOh_WZGKjLCfoJay5PHND9yAG359yxsK84DBxfOWm86U5zvVF_UbCO5n1Uz6P2tG28BgmlkgnY0gmlwhAoAAA2EcXVpY4IjK4lzZWNwMjU2azGhAhXhXn0CrP2llJ7PNcWcimcUJ31GeGVMfk0MF2lnH4Ri

8 changes: 8 additions & 0 deletions example/1-network/genesis/validators.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
node_0:
- 0
node_1:
- 1
node_2:
- 2
node_3:
- 3
35 changes: 35 additions & 0 deletions shadow/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
Shadow example
===============

Quick steps

1) Install Shadow

Follow the official guide: https://github.com/shadow/shadow

2) Update paths

Replace the placeholder `/path/to/qlean-mini` in `shadow/shadow.yaml` with your project root. From the project root run:

```bash
cd /path/to/qlean-mini
sed -i "s|/path/to/qlean-mini|$(pwd)|g" shadow/shadow.yaml
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May make template shadow.yaml.in with some placeholders/variables instead of "/path/to/qlean-mini"

```

3) Run the example

```bash
shadow --progress true --parallelism $(nproc) shadow/shadow.yaml
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May put rm -rf shadow.data here before running shadow (may chain with &&),
instead of step "4) Cleanup"

```

4) Cleanup

Remove generated state before re-running:

```bash
rm -rf shadow.data
```

Notes
- Ensure the `qlean` executable path in `shadow/shadow.yaml` points to your built binary (build it if needed).
- If you run Shadow from a different directory, make all paths in `shadow/shadow.yaml` absolute.
40 changes: 40 additions & 0 deletions shadow/shadow.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
general:
stop_time: 60s
model_unblocked_syscall_latency: true
experimental:
native_preemption_enabled: true
network:
graph:
type: 1_gbit_switch
hosts:
node0:
network_node_id: 0
ip_addr: 10.0.0.10
processes:
- path: build/src/executable/qlean
args: --modules-dir /path/to/qlean-mini/build/src/modules --bootnodes /path/to/qlean-mini/example/1-network/genesis/nodes.yaml --genesis /path/to/qlean-mini/example/1-network/genesis/config.yaml --validator-registry-path /path/to/qlean-mini/example/1-network/genesis/validators.yaml --node-id node_0 --node-key cb920fbda3b96e18f03e22825f4a5a61343ec43c7be1c8c4a717fffee2f4c4ce --listen-addr /ip4/0.0.0.0/udp/9000/quic-v1 --prometheus_port 9100
expected_final_state: running

node1:
network_node_id: 0
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All nodes are configured with the same network_node_id: 0. Each node should have a unique network_node_id (0, 1, 2, 3) to properly simulate different network positions in Shadow.

Copilot uses AI. Check for mistakes.
ip_addr: 10.0.0.11
processes:
- path: build/src/executable/qlean
args: --modules-dir /path/to/qlean-mini/build/src/modules --bootnodes /path/to/qlean-mini/example/1-network/genesis/nodes.yaml --genesis /path/to/qlean-mini/example/1-network/genesis/config.yaml --validator-registry-path /path/to/qlean-mini/example/1-network/genesis/validators.yaml --node-id node_1 --node-key a87e7d23bb1de4613b67002b700bce41e031f4ab1529a3436bd73c893ea039b3 --listen-addr /ip4/0.0.0.0/udp/9001/quic-v1 --prometheus_port 9101
expected_final_state: running

node2:
network_node_id: 0
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All nodes are configured with the same network_node_id: 0. Each node should have a unique network_node_id (0, 1, 2, 3) to properly simulate different network positions in Shadow.

Copilot uses AI. Check for mistakes.
ip_addr: 10.0.0.12
processes:
- path: build/src/executable/qlean
args: --modules-dir /path/to/qlean-mini/build/src/modules --bootnodes /path/to/qlean-mini/example/1-network/genesis/nodes.yaml --genesis /path/to/qlean-mini/example/1-network/genesis/config.yaml --validator-registry-path /path/to/qlean-mini/example/1-network/genesis/validators.yaml --node-id node_2 --node-key f2f53f6acf312c5e92c2a611bbca7a1932b4db0b9e0c43bec413badca9b76760 --listen-addr /ip4/0.0.0.0/udp/9002/quic-v1 --prometheus_port 9102
expected_final_state: running

node3:
network_node_id: 0
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All nodes are configured with the same network_node_id: 0. Each node should have a unique network_node_id (0, 1, 2, 3) to properly simulate different network positions in Shadow.

Copilot uses AI. Check for mistakes.
ip_addr: 10.0.0.13
processes:
- path: build/src/executable/qlean
args: --modules-dir /path/to/qlean-mini/build/src/modules --bootnodes /path/to/qlean-mini/example/1-network/genesis/nodes.yaml --genesis /path/to/qlean-mini/example/1-network/genesis/config.yaml --validator-registry-path /path/to/qlean-mini/example/1-network/genesis/validators.yaml --node-id node_3 --node-key fa5ddbec80f964d17d28221c2c5bac0f4a3f9cfcf4b86674e605f459e195a1c4 --listen-addr /ip4/0.0.0.0/udp/9003/quic-v1 --prometheus_port 9103
expected_final_state: running
Loading