Skip to content
Merged
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
22 changes: 21 additions & 1 deletion Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ tracing = "0.1"
thiserror = "2.0.9"
serde = { version = "1", features = ["derive"] }
serde_json = "1.0.117"
hex = "0.4"

tokio = "1.0"

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ This milestone focuses on connecting to other clients and listening for new bloc

This milestone focuses on computing the chain state from the gossiped by peers received.

- Generate initial state from genesis configuration 🏗️
- Implement state transition function
- Generate initial state from genesis configuration ✅
- Implement state transition function 🏗️
- Transition state on each new block

### Apply fork-choice rule
Expand Down
5 changes: 4 additions & 1 deletion bin/ethlambda/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ tracing.workspace = true
tracing-subscriber = "0.3"

serde.workspace = true
ethereum-types.workspace = true
serde_json.workspace = true
serde_yaml_ng = "0.10"
hex.workspace = true

ethereum-types.workspace = true
clap.workspace = true
91 changes: 82 additions & 9 deletions bin/ethlambda/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
use std::{
collections::BTreeMap,
path::{Path, PathBuf},
};

use clap::Parser;
use ethlambda_p2p::{parse_validators_file, start_p2p};
use ethlambda_p2p::{Bootnode, parse_enrs, start_p2p};
use ethlambda_rpc::metrics::start_prometheus_metrics_api;
use ethlambda_types::{genesis::Genesis, state::State};
use ethlambda_types::{
genesis::Genesis,
state::{State, Validator, ValidatorPubkey},
};
use serde::Deserialize;
use tracing::info;
use tracing_subscriber::{Registry, layer::SubscriberExt};

Expand All @@ -16,9 +25,7 @@ const ASCII_ART: &str = r#"
#[derive(Debug, clap::Parser)]
struct CliOptions {
#[arg(long)]
custom_genesis_json_file: String,
#[arg(long)]
validators_file: String,
custom_network_config_dir: PathBuf,
#[arg(long)]
gossipsub_port: u16,
}
Expand All @@ -31,14 +38,21 @@ async fn main() {

println!("{ASCII_ART}");

let genesis_json = std::fs::read_to_string(&options.custom_genesis_json_file)
.expect("Failed to read genesis.json");
let genesis_path = options.custom_network_config_dir.join("genesis.json");
let bootnodes_path = options.custom_network_config_dir.join("nodes.yaml");
let validators_path = options
.custom_network_config_dir
.join("annotated_validators.yaml");

let genesis_json = std::fs::read_to_string(&genesis_path).expect("Failed to read genesis.json");
let genesis: Genesis =
serde_json::from_str(&genesis_json).expect("Failed to parse genesis.json");

let initial_state = State::from_genesis(&genesis);
let bootnodes = read_bootnodes(&bootnodes_path);

let bootnodes = parse_validators_file(&options.validators_file);
let validators = read_validators(&validators_path);

let initial_state = State::from_genesis(&genesis, validators);

let p2p_handle = tokio::spawn(start_p2p(bootnodes, options.gossipsub_port));

Expand All @@ -58,3 +72,62 @@ async fn main() {
}
println!("Shutting down...");
}

fn read_bootnodes(bootnodes_path: impl AsRef<Path>) -> Vec<Bootnode> {
let bootnodes_yaml =
std::fs::read_to_string(bootnodes_path).expect("Failed to read bootnodes file");
let enrs: Vec<String> =
serde_yaml_ng::from_str(&bootnodes_yaml).expect("Failed to parse bootnodes file");
parse_enrs(enrs)
}

#[derive(Debug, Deserialize)]
struct AnnotatedValidator {
index: u64,
#[serde(rename = "pubkey_hex")]
#[serde(deserialize_with = "deser_pubkey_hex")]
pubkey: ValidatorPubkey,
// privkey_file: PathBuf,
}

// Taken from ethrex-common
pub fn deser_pubkey_hex<'de, D>(d: D) -> Result<ValidatorPubkey, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;

let value = String::deserialize(d)?;
let pubkey: ValidatorPubkey = hex::decode(&value)
.map_err(|_| D::Error::custom("ValidatorPubkey value is not valid hex"))?
.try_into()
.map_err(|_| D::Error::custom("ValidatorPubkey length != 52"))?;
Ok(pubkey)
}

fn read_validators(validators_path: impl AsRef<Path>) -> Vec<Validator> {
let validators_yaml =
std::fs::read_to_string(validators_path).expect("Failed to read validators file");
// File is a map from validator name to its annotated info (the info is inside a vec for some reason)
let validator_infos: BTreeMap<String, Vec<AnnotatedValidator>> =
serde_yaml_ng::from_str(&validators_yaml).expect("Failed to parse validators file");

let mut validators: Vec<Validator> = validator_infos
.into_iter()
.map(|(_, v)| Validator {
pubkey: v[0].pubkey,
index: v[0].index,
})
.collect();

validators.sort_by_key(|v| v.index);
let num_validators = validators.len();

validators.dedup_by_key(|v| v.index);

if validators.len() != num_validators {
panic!("Duplicate validator indices found in config");
}

validators
}
8 changes: 5 additions & 3 deletions crates/common/types/src/attestation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use ssz_derive::{Decode, Encode};
use ssz_types::typenum::U4096;
use tree_hash_derive::TreeHash;

use crate::{signature::Signature, state::Checkpoint};
use crate::{
signature::Signature,
state::{Checkpoint, ValidatorRegistryLimit},
};

/// Validator specific attestation wrapping shared attestation data.
#[derive(Debug, Clone, Encode, Decode, TreeHash)]
Expand Down Expand Up @@ -31,8 +34,7 @@ pub struct AttestationData {
}

/// List of validator attestations included in a block.
/// Size limited to [`crate::state::VALIDATOR_REGISTRY_LIMIT`].
pub type Attestations = ssz_types::VariableList<Attestation, U4096>;
pub type Attestations = ssz_types::VariableList<Attestation, ValidatorRegistryLimit>;

/// Validator attestation bundled with its signature.
#[derive(Clone, Encode, Decode)]
Expand Down
6 changes: 3 additions & 3 deletions crates/common/types/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::{
attestation::{Attestation, Attestations},
primitives::H256,
signature::{Signature, SignatureSize},
state::ValidatorRegistryLimit,
};

/// Envelope carrying a block, an attestation from proposer, and aggregated signatures.
Expand Down Expand Up @@ -37,9 +38,8 @@ impl core::fmt::Debug for SignedBlockWithAttestation {
}

/// Aggregated signature list included alongside the block.
/// Size limited to [`crate::state::VALIDATOR_REGISTRY_LIMIT`].
pub type BlockSignatures =
ssz_types::VariableList<ssz_types::FixedVector<u8, SignatureSize>, U4096>;
ssz_types::VariableList<ssz_types::FixedVector<u8, SignatureSize>, ValidatorRegistryLimit>;

/// Bundle containing a block and the proposer's attestation.
#[derive(Debug, Clone, Encode, Decode, TreeHash)]
Expand Down Expand Up @@ -92,7 +92,7 @@ pub struct Block {
///
/// Currently, the main operation is voting. Validators submit attestations which are
/// packaged into blocks.
#[derive(Debug, Clone, Encode, Decode, TreeHash)]
#[derive(Debug, Default, Clone, Encode, Decode, TreeHash)]
pub struct BlockBody {
/// Plain validator attestations carried in the block body.
///
Expand Down
4 changes: 2 additions & 2 deletions crates/common/types/src/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub struct Genesis {
pub historical_block_hashes: Vec<H256>,
pub justified_slots: Vec<bool>,
// TODO: uncomment
// pub justifications_roots: Vec<String>,
pub justifications_roots: Vec<String>,
// TODO: this is an SSZ bitlist
// pub justifications_validators: String,
pub justifications_validators: String,
}
104 changes: 78 additions & 26 deletions crates/common/types/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
use serde::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use ssz_types::typenum::{U4096, U262144};
use tree_hash::TreeHash;
use tree_hash_derive::TreeHash;

use crate::{block::BlockHeader, genesis::Genesis, primitives::H256};
use crate::{
block::{BlockBody, BlockHeader},
genesis::Genesis,
primitives::H256,
};

// Constants

/// The maximum number of validators that can be in the registry.
pub const VALIDATOR_REGISTRY_LIMIT: u64 = 4096; // 2 ** 12
pub type ValidatorRegistryLimit = U4096;

/// The main consensus state object
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Encode, Decode, TreeHash)]
pub struct State {
/// The chain's configuration parameters
config: NetworkConfig,
Expand All @@ -20,34 +28,79 @@ pub struct State {
latest_justified: Checkpoint,
/// The latest finalized checkpoint
latest_finalized: Checkpoint,
// /// A list of historical block root hashes
// historical_block_hashes: HistoricalBlockHashes,
// /// A bitfield indicating which historical slots were justified
// justified_slots: JustifiedSlots,
// /// Registry of validators tracked by the state
// validators: Validators,
// /// Roots of justified blocks
// justifications_roots: JustificationRoots,
// /// A bitlist of validators who participated in justifications
// justifications_validators: JustificationValidators,
/// A list of historical block root hashes
historical_block_hashes: HistoricalBlockHashes,
/// A bitfield indicating which historical slots were justified
justified_slots: JustifiedSlots,
/// Registry of validators tracked by the state
validators: ssz_types::VariableList<Validator, ValidatorRegistryLimit>,
/// Roots of justified blocks
justifications_roots: JustificationRoots,
/// A bitlist of validators who participated in justifications
justifications_validators: JustificationValidators,
}

/// The maximum number of historical block roots to store in the state.
///
/// With a 4-second slot, this corresponds to a history
/// of approximately 12.1 days.
type HistoricalRootsLimit = U262144; // 2**18

/// List of historical block root hashes up to historical_roots_limit.
type HistoricalBlockHashes = ssz_types::VariableList<H256, HistoricalRootsLimit>;

/// Bitlist tracking justified slots up to historical roots limit.
type JustifiedSlots = ssz_types::BitList<HistoricalRootsLimit>;

/// List of justified block roots up to historical_roots_limit.
type JustificationRoots = ssz_types::VariableList<H256, HistoricalRootsLimit>;

/// Bitlist for tracking validator justifications per historical root.
type JustificationValidators =
ssz_types::BitList<ssz_types::typenum::Prod<HistoricalRootsLimit, ValidatorRegistryLimit>>;

/// Represents a validator's static metadata and operational interface.
#[derive(Debug, Clone, Encode, Decode, TreeHash)]
pub struct Validator {
/// XMSS one-time signature public key.
pub pubkey: ValidatorPubkey,
/// Validator index in the registry.
pub index: u64,
}

pub type ValidatorPubkey = [u8; 52];

impl State {
pub fn from_genesis(genesis: &Genesis) -> Self {
State {
pub fn from_genesis(genesis: &Genesis, validators: Vec<Validator>) -> Self {
let genesis_header = BlockHeader {
slot: 0,
proposer_index: 0,
parent_root: H256::ZERO,
state_root: H256::ZERO,
body_root: BlockBody::default().tree_hash_root(),
};
let validators = ssz_types::VariableList::new(validators).unwrap();
let justified_slots =
JustifiedSlots::with_capacity(0).expect("failed to initialize empty list");
let justifications_validators =
JustificationValidators::with_capacity(0).expect("failed to initialize empty list");

let state = State {
config: genesis.config.clone(),
slot: 0,
latest_block_header: BlockHeader {
slot: 0,
proposer_index: 0,
parent_root: H256::ZERO,
state_root: H256::ZERO,
// TODO: this should be the hash_tree_root of an empty block body
body_root: H256::ZERO,
},
latest_block_header: genesis_header,
latest_justified: genesis.latest_justified.clone(),
latest_finalized: genesis.latest_finalized.clone(),
}
historical_block_hashes: Default::default(),
justified_slots,
validators,
justifications_roots: Default::default(),
justifications_validators,
};

dbg!(state.tree_hash_root());

state
}
}

Expand All @@ -72,8 +125,7 @@ where
.map_err(|_| D::Error::custom("Failed to deserialize u64 value"))
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)]
pub struct NetworkConfig {
pub num_validators: u64,
pub genesis_time: u64,
}
Loading