Add debug fork choice api (#4003)

## Issue Addressed

Which issue # does this PR address?
https://github.com/sigp/lighthouse/issues/3669

## Proposed Changes

Please list or describe the changes introduced by this PR.
- A new API to fetch fork choice data, as specified [here](https://github.com/ethereum/beacon-APIs/pull/232)
- A new integration test to test the new API

## Additional Info

Please provide any additional information. For example, future considerations
or information useful for reviewers.

- `extra_data` field specified in the beacon-API spec is not implemented, please let me know if I should instead.

Co-authored-by: Michael Sproul <micsproul@gmail.com>
This commit is contained in:
Christopher Chong 2023-03-29 02:56:37 +00:00
parent d3c20ffa9d
commit 6bb28bc806
5 changed files with 158 additions and 3 deletions

View File

@ -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;
@ -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
*/
@ -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)

View File

@ -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;
@ -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()
}
@ -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;
}

View File

@ -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,

View File

@ -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::*;

View File

@ -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,
@ -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`.