Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
76 changes: 73 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
'allocator',
'arithmetic',
'attestation_verifier',
'beacon_api_types',
'benches',
'binary_utils',
'bindings/c',
Expand All @@ -13,6 +14,7 @@ members = [
'bls/bls-blst',
'bls/bls-core',
'bls/bls-zkcrypto',
'builder',
'builder_api',
'clock',
'data_dumper',
Expand Down Expand Up @@ -58,6 +60,7 @@ members = [
'slashing_protection',
'snapshot_test_utils',
'spec_test_utils',
'sse',
'ssz',
'ssz_derive',
'state_cache',
Expand Down Expand Up @@ -327,6 +330,7 @@ enum-map = '2'
enumset = '1'
env_logger = '0.11'
ethereum-types = '0.14'
eventsource-stream = '0.2'
ff = '0.13'
fixed-hash = '0.8'
fnv = '1'
Expand Down Expand Up @@ -479,13 +483,15 @@ zkm-sdk = { git = 'https://github.com/ProjectZKM/Ziren.git', tag = 'v1.2.5' }
allocator = { path = 'allocator' }
arithmetic = { path = 'arithmetic' }
attestation_verifier = { path = 'attestation_verifier' }
beacon_api_types = { path = 'beacon_api_types' }
binary_utils = { path = 'binary_utils' }
block_producer = { path = 'block_producer' }
# Replace the line below with `bls = { path = 'bls', features = ['blst'] }` for `ad_hoc_bench` and other utilities
bls = { path = 'bls' }
bls-blst = { path = 'bls/bls-blst' }
bls-core = { path = 'bls/bls-core' }
bls-zkcrypto = { path = 'bls/bls-zkcrypto' }
builder = { path = 'builder' }
builder_api = { path = 'builder_api' }
c_grandine = { path = 'bindings/c', default-features = false }
clock = { path = 'clock' }
Expand Down Expand Up @@ -534,6 +540,7 @@ slasher = { path = 'slasher' }
slashing_protection = { path = 'slashing_protection' }
snapshot_test_utils = { path = 'snapshot_test_utils' }
spec_test_utils = { path = 'spec_test_utils' }
sse = { path = 'sse' }
ssz = { path = 'ssz' }
ssz_derive = { path = 'ssz_derive' }
state_cache = { path = 'state_cache' }
Expand Down
5 changes: 5 additions & 0 deletions Dockerfile.builder.cross
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM --platform=$TARGETPLATFORM ubuntu:24.04
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates libssl-dev && apt-get clean
COPY grandine-builder /usr/local/bin/grandine-builder

ENTRYPOINT ["grandine-builder"]
58 changes: 58 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ check:
build:
cargo build --release --bin grandine $(FEATURES) $(EXCLUDES)

.PHONY: builder
builder:
cargo build --release --bin grandine-builder -p builder

.PHONY: download-spec-tests
download-spec-tests:
./scripts/download_spec_tests.sh
Expand Down Expand Up @@ -322,3 +326,57 @@ else
--tag $(DOCKER_REPO):$(DOCKER_LABEL)-amd64$(DOCKER_SUFFIX)-nethermind-$(NETHERMIND_VERSION) \
./target/x86_64-unknown-linux-gnu/compact
endif

# ----- GRANDINE-BUILDER DOCKER -----

BUILDER_DOCKER_REPO ?= sifrai/grandine-builder

./target/x86_64-unknown-linux-gnu/compact/grandine-builder:
cross build --bin grandine-builder -p builder --target x86_64-unknown-linux-gnu --profile compact

./target/aarch64-unknown-linux-gnu/compact/grandine-builder:
cross build --bin grandine-builder -p builder --target aarch64-unknown-linux-gnu --profile compact

.PHONY: builder-docker
builder-docker: builder-docker-arm64 builder-docker-amd64
ifeq ($(DOCKER_LABEL),)
@echo "Failed to build docker image - please provide DOCKER_LABEL=, either 'stable' or 'unstable'"
@exit 1
endif
docker buildx imagetools create -t $(BUILDER_DOCKER_REPO):$(DOCKER_LABEL)$(DOCKER_SUFFIX) \
$(BUILDER_DOCKER_REPO):$(DOCKER_LABEL)-amd64$(DOCKER_SUFFIX) \
$(BUILDER_DOCKER_REPO):$(DOCKER_LABEL)-arm64$(DOCKER_SUFFIX)
ifeq ($(DOCKER_LABEL),stable)
docker buildx imagetools create -t $(BUILDER_DOCKER_REPO):$(GRANDINE_VERSION) \
$(BUILDER_DOCKER_REPO):$(DOCKER_LABEL)$(DOCKER_SUFFIX)
docker buildx imagetools create -t $(BUILDER_DOCKER_REPO):latest \
$(BUILDER_DOCKER_REPO):$(DOCKER_LABEL)$(DOCKER_SUFFIX)
endif

.PHONY: builder-docker-arm64
builder-docker-arm64: ./target/aarch64-unknown-linux-gnu/compact/grandine-builder
ifeq ($(DOCKER_LABEL),)
@echo "Failed to build docker image - please provide DOCKER_LABEL=, either 'stable' or 'unstable'"
else
docker buildx build \
--file Dockerfile.builder.cross \
--platform linux/arm64 \
--push \
--tag $(BUILDER_DOCKER_REPO):$(DOCKER_LABEL)-arm64$(DOCKER_SUFFIX) \
./target/aarch64-unknown-linux-gnu/compact
endif

.PHONY: builder-docker-amd64
builder-docker-amd64: ./target/x86_64-unknown-linux-gnu/compact/grandine-builder
ifeq ($(DOCKER_LABEL),)
@echo "Failed to build docker image - please provide DOCKER_LABEL=, either 'stable' or 'unstable'"
else
docker buildx build \
--file Dockerfile.builder.cross \
--platform linux/amd64 \
--push \
--tag $(BUILDER_DOCKER_REPO):$(DOCKER_LABEL)-amd64$(DOCKER_SUFFIX) \
./target/x86_64-unknown-linux-gnu/compact
endif


15 changes: 15 additions & 0 deletions beacon_api_types/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = 'beacon_api_types'
edition = { workspace = true }
authors = ["Grandine <[email protected]>"]

[lints]
workspace = true

[dependencies]
bls = { workspace = true }
parse-display = { workspace = true }
serde = { workspace = true }
serde_utils = { workspace = true }
serde_with = { workspace = true }
types = { workspace = true }
89 changes: 89 additions & 0 deletions beacon_api_types/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//! Shared Beacon Node REST API request and response types.
//!
//! These mirror the JSON payloads of the standard Beacon Node API and are used
//! by both the `http_api` server (deserialize) and the standalone `builder`
//! client (serialize), so the wire format stays defined in one place.
//!
//! For now this holds only the types that genuinely have two sides in the
//! workspace. The remaining request/response types still live in `http_api`,
//! where they have a single consumer; we expect to move them all here once the
//! validator client is decoupled and becomes a second consumer of the wire
//! format.

use bls::PublicKeyBytes;
use parse_display::{Display, FromStr};
use serde::{Deserialize, Serialize};
use serde_with::{DeserializeFromStr, SerializeDisplay};
use types::{
gloas::{containers::Builder, primitives::BuilderIndex},
phase0::{
containers::Checkpoint,
primitives::{H256, Slot, UnixSeconds, ValidatorIndex, Version},
},
};

/// `GET /eth/v1/beacon/genesis`
#[expect(clippy::struct_field_names)]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GetGenesisResponse {
#[serde(with = "serde_utils::string_or_native")]
pub genesis_time: UnixSeconds,
pub genesis_validators_root: H256,
pub genesis_fork_version: Version,
}

/// `GET /eth/v1/validator/duties/proposer/{epoch}`
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ValidatorProposerDutyResponse {
pub pubkey: PublicKeyBytes,
#[serde(with = "serde_utils::string_or_native")]
pub validator_index: ValidatorIndex,
#[serde(with = "serde_utils::string_or_native")]
pub slot: Slot,
}

#[derive(
Debug, Clone, Copy, PartialEq, Eq, Display, FromStr, DeserializeFromStr, SerializeDisplay,
)]
#[display(style = "snake_case")]
pub enum BuilderStatus {
Pending,
Active,
Exited,
}

#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, Display, FromStr, DeserializeFromStr, SerializeDisplay,
)]
pub enum BuilderId {
#[display("{0}")]
BuilderIndex(BuilderIndex),
#[display("{0:?}")]
PublicKey(PublicKeyBytes),
}

/// `POST /eth/v1/beacon/states/{state_id}/builders` request body
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct BuildersQuery {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub ids: Vec<BuilderId>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub statuses: Vec<BuilderStatus>,
}

/// `GET /eth/v1/beacon/states/{state_id}/finality_checkpoints` response body
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct StateFinalityCheckpointsResponse {
pub previous_justified: Checkpoint,
pub current_justified: Checkpoint,
pub finalized: Checkpoint,
}

/// `POST /eth/v1/beacon/states/{state_id}/builders` response body
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct StateBuilderResponse {
#[serde(with = "serde_utils::string_or_native")]
pub index: BuilderIndex,
pub status: BuilderStatus,
pub builder: Builder,
}
Loading
Loading