Skip to content
Closed
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
56 changes: 55 additions & 1 deletion beacon_node/http_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ use beacon_chain::{
pub use block_id::BlockId;
use directory::DEFAULT_ROOT_DIR;
use eth2::types::{
self as api_types, EndpointVersion, SkipRandaoVerification, ValidatorId, ValidatorStatus,
self as api_types, EndpointVersion, ForkChoice, ForkChoiceNode, SkipRandaoVerification,
ValidatorId, ValidatorStatus,
};
use lighthouse_network::{types::SyncState, EnrExt, NetworkGlobals, PeerId, PubsubMessage};
use lighthouse_version::version_with_platform;
Expand Down Expand Up @@ -2148,6 +2149,58 @@ pub fn serve<T: BeaconChainTypes>(
},
);

// GET debug/fork_choice
let get_debug_fork_choice = eth_v1
.and(warp::path("debug"))
.and(warp::path("fork_choice"))
.and(warp::path::end())
.and(chain_filter.clone())
.and_then(|chain: Arc<BeaconChain<T>>| {
blocking_json_task(move || {
let beacon_fork_choice = chain.canonical_head.fork_choice_read_lock();

let proto_array = beacon_fork_choice.proto_array().core_proto_array();

let fork_choice_nodes = proto_array
.nodes
.iter()
.map(|node| {
let execution_status = if node.execution_status.is_execution_enabled() {
Some(node.execution_status.to_string())
} else {
None
};

ForkChoiceNode {
slot: node.slot,
block_root: node.root,
parent_root: node
.parent
.and_then(|index| proto_array.nodes.get(index))
.map(|parent| parent.root),
justified_epoch: node
.justified_checkpoint
.map(|checkpoint| checkpoint.epoch),
finalized_epoch: node
.finalized_checkpoint
.map(|checkpoint| checkpoint.epoch),
weight: node.weight,
validity: execution_status,
execution_block_hash: node
.execution_status
.block_hash()
.map(|block_hash| block_hash.into_root()),
}
})
.collect::<Vec<_>>();
Ok(ForkChoice {
justified_checkpoint: proto_array.justified_checkpoint,
finalized_checkpoint: proto_array.finalized_checkpoint,
fork_choice_nodes,
})
})
});

/*
* node
*/
Expand Down Expand Up @@ -3676,6 +3729,7 @@ pub fn serve<T: BeaconChainTypes>(
.uor(get_config_deposit_contract)
.uor(get_debug_beacon_states)
.uor(get_debug_beacon_heads)
.uor(get_debug_fork_choice)
.uor(get_node_identity)
.uor(get_node_version)
.uor(get_node_syncing)
Expand Down
57 changes: 56 additions & 1 deletion beacon_node/http_api/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use environment::null_logger;
use eth2::{
mixin::{RequestAccept, ResponseForkName, ResponseOptional},
reqwest::RequestBuilder,
types::{BlockId as CoreBlockId, StateId as CoreStateId, *},
types::{BlockId as CoreBlockId, ForkChoiceNode, StateId as CoreStateId, *},
BeaconNodeHttpClient, Error, StatusCode, Timeouts,
};
use execution_layer::test_utils::TestingBuilder;
Expand Down Expand Up @@ -1679,6 +1679,59 @@ impl ApiTester {
self
}

pub async fn test_get_debug_fork_choice(self) -> Self {
let result = self.client.get_debug_fork_choice().await.unwrap();

let beacon_fork_choice = self.chain.canonical_head.fork_choice_read_lock();

let expected_proto_array = beacon_fork_choice.proto_array().core_proto_array();

assert_eq!(
result.justified_checkpoint,
expected_proto_array.justified_checkpoint
);
assert_eq!(
result.finalized_checkpoint,
expected_proto_array.finalized_checkpoint
);

let expected_fork_choice_nodes: Vec<ForkChoiceNode> = expected_proto_array
.nodes
.iter()
.map(|node| {
let execution_status = if node.execution_status.is_execution_enabled() {
Some(node.execution_status.to_string())
} else {
None
};
ForkChoiceNode {
slot: node.slot,
block_root: node.root,
parent_root: node
.parent
.and_then(|index| expected_proto_array.nodes.get(index))
.map(|parent| parent.root),
justified_epoch: node.justified_checkpoint.map(|checkpoint| checkpoint.epoch),
finalized_epoch: node.finalized_checkpoint.map(|checkpoint| checkpoint.epoch),
weight: node.weight,
validity: execution_status,
execution_block_hash: node
.execution_status
.block_hash()
.map(|block_hash| block_hash.into_root()),
}
})
.collect();

assert_eq!(result.fork_choice_nodes, expected_fork_choice_nodes);

// need to drop beacon_fork_choice here, else borrow checker will complain
// that self cannot be moved out since beacon_fork_choice borrowed self.chain
// and might still live after self is moved out
drop(beacon_fork_choice);
self
}

fn validator_count(&self) -> usize {
self.chain.head_snapshot().beacon_state.validators().len()
}
Expand Down Expand Up @@ -4148,6 +4201,8 @@ async fn debug_get() {
.test_get_debug_beacon_states()
.await
.test_get_debug_beacon_heads()
.await
.test_get_debug_fork_choice()
.await;
}

Expand Down
12 changes: 12 additions & 0 deletions common/eth2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1334,6 +1334,18 @@ impl BeaconNodeHttpClient {
self.get(path).await
}

/// `GET v1/debug/fork_choice`
pub async fn get_debug_fork_choice(&self) -> Result<ForkChoice, Error> {
let mut path = self.eth_path(V1)?;

path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("debug")
.push("fork_choice");

self.get(path).await
}

/// `GET validator/duties/proposer/{epoch}`
pub async fn get_validator_duties_proposer(
&self,
Expand Down
20 changes: 20 additions & 0 deletions common/eth2/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1197,6 +1197,26 @@ pub struct LivenessResponseData {
pub is_live: bool,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ForkChoice {
pub justified_checkpoint: Checkpoint,
pub finalized_checkpoint: Checkpoint,
pub fork_choice_nodes: Vec<ForkChoiceNode>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct ForkChoiceNode {
pub slot: Slot,
pub block_root: Hash256,
pub parent_root: Option<Hash256>,
pub justified_epoch: Option<Epoch>,
pub finalized_epoch: Option<Epoch>,
#[serde(with = "eth2_serde_utils::quoted_u64")]
pub weight: u64,
pub validity: Option<String>,
pub execution_block_hash: Option<Hash256>,
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
16 changes: 15 additions & 1 deletion consensus/proto_array/src/proto_array_fork_choice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ use crate::{
use serde_derive::{Deserialize, Serialize};
use ssz::{Decode, Encode};
use ssz_derive::{Decode, Encode};
use std::collections::{BTreeSet, HashMap};
use std::{
collections::{BTreeSet, HashMap},
fmt,
};
use types::{
AttestationShufflingId, ChainSpec, Checkpoint, Epoch, EthSpec, ExecutionBlockHash, Hash256,
Slot,
Expand Down Expand Up @@ -125,6 +128,17 @@ impl ExecutionStatus {
}
}

impl fmt::Display for ExecutionStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ExecutionStatus::Valid(_) => write!(f, "valid"),
ExecutionStatus::Invalid(_) => write!(f, "invalid"),
ExecutionStatus::Optimistic(_) => write!(f, "optimistic"),
ExecutionStatus::Irrelevant(_) => write!(f, "irrelevant"),
}
}
}

/// A block that is to be applied to the fork choice.
///
/// A simplified version of `types::BeaconBlock`.
Expand Down