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
19 changes: 19 additions & 0 deletions crates/blockchain/state_transition/src/beacon/helpers/altair.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,16 @@ pub fn get_next_sync_committee(state: &BeaconState) -> Result<altair::SyncCommit
})
}

/// The sync committee period `epoch` falls in.
///
/// Altair's `validator.md` `compute_sync_committee_period`. A state's
/// `current_sync_committee` serves its own period and `next_sync_committee`
/// the one after, so this is what tells a caller which of the two answers for
/// a given epoch.
pub fn compute_sync_committee_period(epoch: Epoch) -> u64 {
epoch / preset::EPOCHS_PER_SYNC_COMMITTEE_PERIOD
}

/// The reward every one-increment slice of a validator's effective balance
/// earns for a single timely, correct component of its attestation.
///
Expand Down Expand Up @@ -592,4 +602,13 @@ mod tests {
let elapsed = start.elapsed() / ITERATIONS;
println!("get_flag_index_deltas, {VALIDATOR_COUNT} validators -> {elapsed:?}/call");
}

#[test]
fn a_sync_committee_period_spans_its_epochs_and_no_more() {
let period = preset::EPOCHS_PER_SYNC_COMMITTEE_PERIOD;
assert_eq!(compute_sync_committee_period(0), 0);
assert_eq!(compute_sync_committee_period(period - 1), 0);
assert_eq!(compute_sync_committee_period(period), 1);
assert_eq!(compute_sync_committee_period(3 * period + 1), 3);
}
}
42 changes: 34 additions & 8 deletions crates/net/rpc/src/beacon/config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! `/eth/v1/config/spec`.
//! `/eth/v1/config/spec` and `/eth/v1/config/deposit_contract`.
//!
//! The Beacon API asks for three things in one flat object: the network's
//! configuration, the preset the node was built against, and the
Expand Down Expand Up @@ -29,7 +29,22 @@ use ethlambda_types::beacon::{config::Config, constants, preset, serde_helpers::
use serde_json::{Map, Value};

pub(crate) fn routes() -> Router<Store> {
Router::new().route("/eth/v1/config/spec", get(get_spec))
Router::new()
.route("/eth/v1/config/spec", get(get_spec))
.route("/eth/v1/config/deposit_contract", get(get_deposit_contract))
}

/// `GET /eth/v1/config/deposit_contract`: the network's deposit contract, off
/// the same `Config` `/eth/v1/config/spec` reports it from (as
/// `DEPOSIT_CHAIN_ID` and `DEPOSIT_CONTRACT_ADDRESS`).
async fn get_deposit_contract(State(store): State<Store>) -> Response {
let config = store.config();
crate::json_response(serde_json::json!({
"data": {
"chain_id": config.deposit_chain_id.to_string(),
"address": HexPrefixed(&config.deposit_contract_address).to_string(),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: hex_string(config.deposit_contract_address), defined further down in this file, does the same thing.

}
}))
}

async fn get_spec(State(store): State<Store>) -> Response {
Expand Down Expand Up @@ -202,15 +217,14 @@ mod tests {
use tower::ServiceExt as _;

async fn get_spec_json() -> serde_json::Value {
get_json("/eth/v1/config/spec").await
}

async fn get_json(uri: &str) -> serde_json::Value {
let fixture = beacon_fixture(64);
let app = routes().with_state(fixture.store);
let response = app
.oneshot(
Request::builder()
.uri("/eth/v1/config/spec")
.body(Body::empty())
.unwrap(),
)
.oneshot(Request::builder().uri(uri).body(Body::empty()).unwrap())
.await
.unwrap();

Expand All @@ -219,6 +233,18 @@ mod tests {
serde_json::from_slice(&body).unwrap()
}

/// The fixture's store is bootstrapped with `Config::mainnet()`, whose
/// deposit contract is the one on Ethereum mainnet.
#[tokio::test]
async fn the_deposit_contract_is_mainnets() {
let json = get_json("/eth/v1/config/deposit_contract").await;
assert_eq!(json["data"]["chain_id"], "1");
assert_eq!(
json["data"]["address"],
"0x00000000219ab540356cbb839cbe05303d7705fa"
);
}

#[tokio::test]
async fn the_spec_is_screaming_snake_case_with_quoted_values() {
let json = get_spec_json().await;
Expand Down
53 changes: 53 additions & 0 deletions crates/net/rpc/src/beacon/states.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ use crate::{
pub(crate) fn routes() -> Router<Store> {
Router::new()
.route("/eth/v2/debug/beacon/states/{state_id}", get(get_state))
.route("/eth/v1/beacon/states/{state_id}/fork", get(get_fork))
.route(
"/eth/v1/beacon/states/{state_id}/finality_checkpoints",
get(get_finality_checkpoints),
Expand Down Expand Up @@ -100,6 +101,20 @@ async fn get_state(
with_consensus_version(response, fork)
}

/// `GET /eth/v1/beacon/states/{state_id}/fork`: the `Fork` the state carries,
/// which is what a validator client builds its signing domains from.
async fn get_fork(Path(state_id): Path<String>, State(store): State<Store>) -> Response {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: the load, error and execution_optimistic + finalized envelope here is the same as get_finality_checkpoints'. A small helper that takes a closure over the state (state_json(store, state_id, |state| data)) would keep the state sub-resources consistent as more are added.

let (root, state) = match load(&store, &state_id) {
Ok(found) => found,
Err(err) => return err.into_response(),
};
crate::json_response(serde_json::json!({
"execution_optimistic": store.is_beacon_optimistic(root),
"finalized": is_finalized(&store, state.slot()),
"data": state.fork(),
}))
}

async fn get_finality_checkpoints(
Path(state_id): Path<String>,
State(store): State<Store>,
Expand Down Expand Up @@ -439,6 +454,44 @@ mod tests {
}
}

#[tokio::test]
async fn the_fork_is_the_one_the_state_carries() {
let fixture = beacon_fixture(ANCHOR_SLOT);
let head_state = fixture
.store
.get_state(&fixture.head_root)
.unwrap()
.unwrap();
let expected = serde_json::to_value(head_state.fork()).unwrap();

let response = get("/eth/v1/beacon/states/head/fork", None).await;
assert_eq!(response.status(), StatusCode::OK);
let json = body_json(response).await;
assert_eq!(json["data"], expected);
assert!(
json["data"]["epoch"].is_string(),
"the epoch must be quoted"
);
assert!(json["execution_optimistic"].is_boolean());
assert!(json["finalized"].is_boolean());
}

#[tokio::test]
async fn the_finalized_states_fork_is_marked_finalized() {
let response = get("/eth/v1/beacon/states/finalized/fork", None).await;
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(body_json(response).await["finalized"], true);
}

/// The same refusal every other state endpoint gives: state roots are not
/// indexed, so a `0x` id is a 404 rather than a guess.
#[tokio::test]
async fn a_fork_by_state_root_is_a_404() {
let root = format!("0x{}", "ab".repeat(32));
let response = get(&format!("/eth/v1/beacon/states/{root}/fork"), None).await;
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}

mod validators {
use super::*;
use crate::test_utils::beacon_store_at;
Expand Down
Loading
Loading