mirror of
https://github.com/sigp/lighthouse.git
synced 2026-04-27 09:43:36 +00:00
Merge branch 'unstable' of https://github.com/sigp/lighthouse into gloas-fc-proto
This commit is contained in:
@@ -205,8 +205,9 @@ pub fn get_attestation_performance<T: BeaconChainTypes>(
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
// TODO(gloas): add payloads
|
||||
replayer = replayer
|
||||
.apply_blocks(blocks, None)
|
||||
.apply_blocks(blocks, vec![], None)
|
||||
.map_err(|e| custom_server_error(format!("{:?}", e)))?;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,17 +3,20 @@ use crate::task_spawner::{Priority, TaskSpawner};
|
||||
use crate::utils::ResponseFilter;
|
||||
use crate::validator::pubkey_to_validator_index;
|
||||
use crate::version::{
|
||||
ResponseIncludesVersion, add_consensus_version_header,
|
||||
ResponseIncludesVersion, add_consensus_version_header, add_ssz_content_type_header,
|
||||
execution_optimistic_finalized_beacon_response,
|
||||
};
|
||||
use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes, WhenSlotSkipped};
|
||||
use eth2::types::{
|
||||
ValidatorBalancesRequestBody, ValidatorId, ValidatorIdentitiesRequestBody,
|
||||
ValidatorsRequestBody,
|
||||
self as api_types, ValidatorBalancesRequestBody, ValidatorId, ValidatorIdentitiesRequestBody,
|
||||
ValidatorIndexData, ValidatorsRequestBody,
|
||||
};
|
||||
use ssz::Encode;
|
||||
use std::sync::Arc;
|
||||
use types::{AttestationShufflingId, BeaconStateError, CommitteeCache, EthSpec, RelativeEpoch};
|
||||
use warp::filters::BoxedFilter;
|
||||
use warp::http::Response;
|
||||
use warp::hyper::Body;
|
||||
use warp::{Filter, Reply};
|
||||
use warp_utils::query::multi_key_query;
|
||||
|
||||
@@ -160,6 +163,67 @@ pub fn get_beacon_state_pending_deposits<T: BeaconChainTypes>(
|
||||
.boxed()
|
||||
}
|
||||
|
||||
// GET beacon/states/{state_id}/proposer_lookahead
|
||||
pub fn get_beacon_state_proposer_lookahead<T: BeaconChainTypes>(
|
||||
beacon_states_path: BeaconStatesPath<T>,
|
||||
) -> ResponseFilter {
|
||||
beacon_states_path
|
||||
.clone()
|
||||
.and(warp::path("proposer_lookahead"))
|
||||
.and(warp::path::end())
|
||||
.and(warp::header::optional::<api_types::Accept>("accept"))
|
||||
.then(
|
||||
|state_id: StateId,
|
||||
task_spawner: TaskSpawner<T::EthSpec>,
|
||||
chain: Arc<BeaconChain<T>>,
|
||||
accept_header: Option<api_types::Accept>| {
|
||||
task_spawner.blocking_response_task(Priority::P1, move || {
|
||||
let (data, execution_optimistic, finalized, fork_name) = state_id
|
||||
.map_state_and_execution_optimistic_and_finalized(
|
||||
&chain,
|
||||
|state, execution_optimistic, finalized| {
|
||||
let Ok(lookahead) = state.proposer_lookahead() else {
|
||||
return Err(warp_utils::reject::custom_bad_request(
|
||||
"Proposer lookahead is not available for pre-Fulu states"
|
||||
.to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
Ok((
|
||||
lookahead.to_vec(),
|
||||
execution_optimistic,
|
||||
finalized,
|
||||
state.fork_name_unchecked(),
|
||||
))
|
||||
},
|
||||
)?;
|
||||
|
||||
match accept_header {
|
||||
Some(api_types::Accept::Ssz) => Response::builder()
|
||||
.status(200)
|
||||
.body(data.as_ssz_bytes().into())
|
||||
.map(|res: Response<Body>| add_ssz_content_type_header(res))
|
||||
.map_err(|e| {
|
||||
warp_utils::reject::custom_server_error(format!(
|
||||
"failed to create response: {}",
|
||||
e
|
||||
))
|
||||
}),
|
||||
_ => execution_optimistic_finalized_beacon_response(
|
||||
ResponseIncludesVersion::Yes(fork_name),
|
||||
execution_optimistic,
|
||||
finalized,
|
||||
ValidatorIndexData(data),
|
||||
)
|
||||
.map(|res| warp::reply::json(&res).into_response()),
|
||||
}
|
||||
.map(|resp| add_consensus_version_header(resp, fork_name))
|
||||
})
|
||||
},
|
||||
)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
// GET beacon/states/{state_id}/randao?epoch
|
||||
pub fn get_beacon_state_randao<T: BeaconChainTypes>(
|
||||
beacon_states_path: BeaconStatesPath<T>,
|
||||
|
||||
@@ -398,8 +398,9 @@ pub fn get_block_packing_efficiency<T: BeaconChainTypes>(
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
// TODO(gloas): add payloads
|
||||
replayer = replayer
|
||||
.apply_blocks(blocks, None)
|
||||
.apply_blocks(blocks, vec![], None)
|
||||
.map_err(|e: PackingEfficiencyError| custom_server_error(format!("{:?}", e)))?;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ use beacon_chain::store::metadata::CURRENT_SCHEMA_VERSION;
|
||||
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||
use serde::Serialize;
|
||||
use std::sync::Arc;
|
||||
use store::invariants::InvariantCheckResult;
|
||||
use store::{AnchorInfo, BlobInfo, Split, StoreConfig};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
@@ -30,3 +31,11 @@ pub fn info<T: BeaconChainTypes>(
|
||||
blob_info,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn check_invariants<T: BeaconChainTypes>(
|
||||
chain: Arc<BeaconChain<T>>,
|
||||
) -> Result<InvariantCheckResult, warp::Rejection> {
|
||||
chain.check_database_invariants().map_err(|e| {
|
||||
warp_utils::reject::custom_bad_request(format!("error checking database invariants: {e:?}"))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -263,6 +263,7 @@ pub fn prometheus_metrics() -> warp::filters::log::Log<impl Fn(warp::filters::lo
|
||||
.or_else(|| starts_with("v1/validator/contribution_and_proofs"))
|
||||
.or_else(|| starts_with("v1/validator/duties/attester"))
|
||||
.or_else(|| starts_with("v1/validator/duties/proposer"))
|
||||
.or_else(|| starts_with("v2/validator/duties/proposer"))
|
||||
.or_else(|| starts_with("v1/validator/duties/sync"))
|
||||
.or_else(|| starts_with("v1/validator/liveness"))
|
||||
.or_else(|| starts_with("v1/validator/prepare_beacon_proposer"))
|
||||
@@ -649,6 +650,10 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
let get_beacon_state_pending_consolidations =
|
||||
states::get_beacon_state_pending_consolidations(beacon_states_path.clone());
|
||||
|
||||
// GET beacon/states/{state_id}/proposer_lookahead
|
||||
let get_beacon_state_proposer_lookahead =
|
||||
states::get_beacon_state_proposer_lookahead(beacon_states_path.clone());
|
||||
|
||||
// GET beacon/headers
|
||||
//
|
||||
// Note: this endpoint only returns information about blocks in the canonical chain. Given that
|
||||
@@ -2476,7 +2481,7 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
|
||||
// GET validator/duties/proposer/{epoch}
|
||||
let get_validator_duties_proposer = get_validator_duties_proposer(
|
||||
eth_v1.clone(),
|
||||
any_version.clone(),
|
||||
chain_filter.clone(),
|
||||
not_while_syncing_filter.clone(),
|
||||
task_spawner_filter.clone(),
|
||||
@@ -3014,6 +3019,19 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
},
|
||||
);
|
||||
|
||||
// GET lighthouse/database/invariants
|
||||
let get_lighthouse_database_invariants = database_path
|
||||
.and(warp::path("invariants"))
|
||||
.and(warp::path::end())
|
||||
.and(task_spawner_filter.clone())
|
||||
.and(chain_filter.clone())
|
||||
.then(
|
||||
|task_spawner: TaskSpawner<T::EthSpec>, chain: Arc<BeaconChain<T>>| {
|
||||
task_spawner
|
||||
.blocking_json_task(Priority::P1, move || database::check_invariants(chain))
|
||||
},
|
||||
);
|
||||
|
||||
// POST lighthouse/database/reconstruct
|
||||
let post_lighthouse_database_reconstruct = database_path
|
||||
.and(warp::path("reconstruct"))
|
||||
@@ -3295,6 +3313,7 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
.uor(get_beacon_state_pending_deposits)
|
||||
.uor(get_beacon_state_pending_partial_withdrawals)
|
||||
.uor(get_beacon_state_pending_consolidations)
|
||||
.uor(get_beacon_state_proposer_lookahead)
|
||||
.uor(get_beacon_headers)
|
||||
.uor(get_beacon_headers_block_id)
|
||||
.uor(get_beacon_block)
|
||||
@@ -3342,6 +3361,7 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
.uor(get_lighthouse_validator_inclusion)
|
||||
.uor(get_lighthouse_staking)
|
||||
.uor(get_lighthouse_database_info)
|
||||
.uor(get_lighthouse_database_invariants)
|
||||
.uor(get_lighthouse_custody_info)
|
||||
.uor(get_lighthouse_attestation_performance)
|
||||
.uor(get_beacon_light_client_optimistic_update)
|
||||
|
||||
@@ -70,7 +70,7 @@ pub async fn produce_block_v4<T: BeaconChainTypes>(
|
||||
|
||||
let graffiti_settings = GraffitiSettings::new(query.graffiti, query.graffiti_policy);
|
||||
|
||||
let (block, consensus_block_value) = chain
|
||||
let (block, _pending_state, consensus_block_value) = chain
|
||||
.produce_block_with_verification_gloas(
|
||||
randao_reveal,
|
||||
slot,
|
||||
|
||||
@@ -13,13 +13,45 @@ use slot_clock::SlotClock;
|
||||
use tracing::debug;
|
||||
use types::{Epoch, EthSpec, Hash256, Slot};
|
||||
|
||||
/// Selects which dependent root to return in the API response.
|
||||
///
|
||||
/// - `Legacy`: the block root at the last slot of epoch N-1 (v1 behaviour, for backwards compat).
|
||||
/// - `True`: the fork-aware proposer shuffling decision root (v2 behaviour). Pre-Fulu this equals
|
||||
/// the legacy root; post-Fulu it uses epoch N-2.
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum DependentRootSelection {
|
||||
Legacy,
|
||||
True,
|
||||
}
|
||||
|
||||
/// The struct that is returned to the requesting HTTP client.
|
||||
type ApiDuties = api_types::DutiesResponse<Vec<api_types::ProposerData>>;
|
||||
|
||||
/// Handles a request from the HTTP API for proposer duties.
|
||||
/// Handles a request from the HTTP API for v1 proposer duties.
|
||||
///
|
||||
/// Returns the legacy dependent root (block root at end of epoch N-1) for backwards compatibility.
|
||||
pub fn proposer_duties<T: BeaconChainTypes>(
|
||||
request_epoch: Epoch,
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<ApiDuties, warp::reject::Rejection> {
|
||||
proposer_duties_internal(request_epoch, chain, DependentRootSelection::Legacy)
|
||||
}
|
||||
|
||||
/// Handles a request from the HTTP API for v2 proposer duties.
|
||||
///
|
||||
/// Returns the true fork-aware dependent root. Pre-Fulu this equals the legacy root; post-Fulu it
|
||||
/// uses epoch N-2 due to deterministic proposer lookahead with `min_seed_lookahead`.
|
||||
pub fn proposer_duties_v2<T: BeaconChainTypes>(
|
||||
request_epoch: Epoch,
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<ApiDuties, warp::reject::Rejection> {
|
||||
proposer_duties_internal(request_epoch, chain, DependentRootSelection::True)
|
||||
}
|
||||
|
||||
fn proposer_duties_internal<T: BeaconChainTypes>(
|
||||
request_epoch: Epoch,
|
||||
chain: &BeaconChain<T>,
|
||||
root_selection: DependentRootSelection,
|
||||
) -> Result<ApiDuties, warp::reject::Rejection> {
|
||||
let current_epoch = chain
|
||||
.slot_clock
|
||||
@@ -49,24 +81,29 @@ pub fn proposer_duties<T: BeaconChainTypes>(
|
||||
if request_epoch == current_epoch || request_epoch == tolerant_current_epoch {
|
||||
// If we could consider ourselves in the `request_epoch` when allowing for clock disparity
|
||||
// tolerance then serve this request from the cache.
|
||||
if let Some(duties) = try_proposer_duties_from_cache(request_epoch, chain)? {
|
||||
if let Some(duties) = try_proposer_duties_from_cache(request_epoch, chain, root_selection)?
|
||||
{
|
||||
Ok(duties)
|
||||
} else {
|
||||
debug!(%request_epoch, "Proposer cache miss");
|
||||
compute_and_cache_proposer_duties(request_epoch, chain)
|
||||
compute_and_cache_proposer_duties(request_epoch, chain, root_selection)
|
||||
}
|
||||
} else if request_epoch
|
||||
== current_epoch
|
||||
.safe_add(1)
|
||||
.map_err(warp_utils::reject::arith_error)?
|
||||
{
|
||||
let (proposers, _dependent_root, legacy_dependent_root, execution_status, _fork) =
|
||||
let (proposers, dependent_root, legacy_dependent_root, execution_status, _fork) =
|
||||
compute_proposer_duties_from_head(request_epoch, chain)
|
||||
.map_err(warp_utils::reject::unhandled_error)?;
|
||||
let selected_root = match root_selection {
|
||||
DependentRootSelection::Legacy => legacy_dependent_root,
|
||||
DependentRootSelection::True => dependent_root,
|
||||
};
|
||||
convert_to_api_response(
|
||||
chain,
|
||||
request_epoch,
|
||||
legacy_dependent_root,
|
||||
selected_root,
|
||||
execution_status.is_optimistic_or_invalid(),
|
||||
proposers,
|
||||
)
|
||||
@@ -84,7 +121,7 @@ pub fn proposer_duties<T: BeaconChainTypes>(
|
||||
// request_epoch < current_epoch
|
||||
//
|
||||
// Queries about the past are handled with a slow path.
|
||||
compute_historic_proposer_duties(request_epoch, chain)
|
||||
compute_historic_proposer_duties(request_epoch, chain, root_selection)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +135,7 @@ pub fn proposer_duties<T: BeaconChainTypes>(
|
||||
fn try_proposer_duties_from_cache<T: BeaconChainTypes>(
|
||||
request_epoch: Epoch,
|
||||
chain: &BeaconChain<T>,
|
||||
root_selection: DependentRootSelection,
|
||||
) -> Result<Option<ApiDuties>, warp::reject::Rejection> {
|
||||
let head = chain.canonical_head.cached_head();
|
||||
let head_block = &head.snapshot.beacon_block;
|
||||
@@ -116,11 +154,14 @@ fn try_proposer_duties_from_cache<T: BeaconChainTypes>(
|
||||
.beacon_state
|
||||
.proposer_shuffling_decision_root_at_epoch(request_epoch, head_block_root, &chain.spec)
|
||||
.map_err(warp_utils::reject::beacon_state_error)?;
|
||||
let legacy_dependent_root = head
|
||||
.snapshot
|
||||
.beacon_state
|
||||
.legacy_proposer_shuffling_decision_root_at_epoch(request_epoch, head_block_root)
|
||||
.map_err(warp_utils::reject::beacon_state_error)?;
|
||||
let selected_root = match root_selection {
|
||||
DependentRootSelection::Legacy => head
|
||||
.snapshot
|
||||
.beacon_state
|
||||
.legacy_proposer_shuffling_decision_root_at_epoch(request_epoch, head_block_root)
|
||||
.map_err(warp_utils::reject::beacon_state_error)?,
|
||||
DependentRootSelection::True => head_decision_root,
|
||||
};
|
||||
let execution_optimistic = chain
|
||||
.is_optimistic_or_invalid_head_block(head_block)
|
||||
.map_err(warp_utils::reject::unhandled_error)?;
|
||||
@@ -134,7 +175,7 @@ fn try_proposer_duties_from_cache<T: BeaconChainTypes>(
|
||||
convert_to_api_response(
|
||||
chain,
|
||||
request_epoch,
|
||||
legacy_dependent_root,
|
||||
selected_root,
|
||||
execution_optimistic,
|
||||
indices.to_vec(),
|
||||
)
|
||||
@@ -155,6 +196,7 @@ fn try_proposer_duties_from_cache<T: BeaconChainTypes>(
|
||||
fn compute_and_cache_proposer_duties<T: BeaconChainTypes>(
|
||||
current_epoch: Epoch,
|
||||
chain: &BeaconChain<T>,
|
||||
root_selection: DependentRootSelection,
|
||||
) -> Result<ApiDuties, warp::reject::Rejection> {
|
||||
let (indices, dependent_root, legacy_dependent_root, execution_status, fork) =
|
||||
compute_proposer_duties_from_head(current_epoch, chain)
|
||||
@@ -168,10 +210,14 @@ fn compute_and_cache_proposer_duties<T: BeaconChainTypes>(
|
||||
.map_err(BeaconChainError::from)
|
||||
.map_err(warp_utils::reject::unhandled_error)?;
|
||||
|
||||
let selected_root = match root_selection {
|
||||
DependentRootSelection::Legacy => legacy_dependent_root,
|
||||
DependentRootSelection::True => dependent_root,
|
||||
};
|
||||
convert_to_api_response(
|
||||
chain,
|
||||
current_epoch,
|
||||
legacy_dependent_root,
|
||||
selected_root,
|
||||
execution_status.is_optimistic_or_invalid(),
|
||||
indices,
|
||||
)
|
||||
@@ -182,6 +228,7 @@ fn compute_and_cache_proposer_duties<T: BeaconChainTypes>(
|
||||
fn compute_historic_proposer_duties<T: BeaconChainTypes>(
|
||||
epoch: Epoch,
|
||||
chain: &BeaconChain<T>,
|
||||
root_selection: DependentRootSelection,
|
||||
) -> Result<ApiDuties, warp::reject::Rejection> {
|
||||
// If the head is quite old then it might still be relevant for a historical request.
|
||||
//
|
||||
@@ -219,9 +266,9 @@ fn compute_historic_proposer_duties<T: BeaconChainTypes>(
|
||||
};
|
||||
|
||||
// Ensure the state lookup was correct.
|
||||
if state.current_epoch() != epoch {
|
||||
if state.current_epoch() != epoch && state.current_epoch() + 1 != epoch {
|
||||
return Err(warp_utils::reject::custom_server_error(format!(
|
||||
"state epoch {} not equal to request epoch {}",
|
||||
"state from epoch {} cannot serve request epoch {}",
|
||||
state.current_epoch(),
|
||||
epoch
|
||||
)));
|
||||
@@ -234,18 +281,18 @@ fn compute_historic_proposer_duties<T: BeaconChainTypes>(
|
||||
|
||||
// We can supply the genesis block root as the block root since we know that the only block that
|
||||
// decides its own root is the genesis block.
|
||||
let legacy_dependent_root = state
|
||||
.legacy_proposer_shuffling_decision_root_at_epoch(epoch, chain.genesis_block_root)
|
||||
.map_err(BeaconChainError::from)
|
||||
.map_err(warp_utils::reject::unhandled_error)?;
|
||||
let selected_root = match root_selection {
|
||||
DependentRootSelection::Legacy => state
|
||||
.legacy_proposer_shuffling_decision_root_at_epoch(epoch, chain.genesis_block_root)
|
||||
.map_err(BeaconChainError::from)
|
||||
.map_err(warp_utils::reject::unhandled_error)?,
|
||||
DependentRootSelection::True => state
|
||||
.proposer_shuffling_decision_root_at_epoch(epoch, chain.genesis_block_root, &chain.spec)
|
||||
.map_err(BeaconChainError::from)
|
||||
.map_err(warp_utils::reject::unhandled_error)?,
|
||||
};
|
||||
|
||||
convert_to_api_response(
|
||||
chain,
|
||||
epoch,
|
||||
legacy_dependent_root,
|
||||
execution_optimistic,
|
||||
indices,
|
||||
)
|
||||
convert_to_api_response(chain, epoch, selected_root, execution_optimistic, indices)
|
||||
}
|
||||
|
||||
/// Converts the internal representation of proposer duties into one that is compatible with the
|
||||
|
||||
@@ -66,11 +66,12 @@ pub fn get_state_before_applying_block<T: BeaconChainTypes>(
|
||||
})
|
||||
.map_err(|e| custom_not_found(format!("Parent state is not available! {:?}", e)))?;
|
||||
|
||||
// TODO(gloas): handle payloads?
|
||||
let replayer = BlockReplayer::new(parent_state, &chain.spec)
|
||||
.no_signature_verification()
|
||||
.state_root_iter([Ok((parent_block.state_root(), parent_block.slot()))].into_iter())
|
||||
.minimal_block_root_verification()
|
||||
.apply_blocks(vec![], Some(block.slot()))
|
||||
.apply_blocks(vec![], vec![], Some(block.slot()))
|
||||
.map_err(unhandled_error::<BeaconChainError>)?;
|
||||
|
||||
Ok(replayer.into_state())
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::utils::{
|
||||
AnyVersionFilter, ChainFilter, EthV1Filter, NetworkTxFilter, NotWhileSyncingFilter,
|
||||
ResponseFilter, TaskSpawnerFilter, ValidatorSubscriptionTxFilter, publish_network_message,
|
||||
};
|
||||
use crate::version::V3;
|
||||
use crate::version::{V1, V2, V3, unsupported_version_rejection};
|
||||
use crate::{StateId, attester_duties, proposer_duties, sync_committees};
|
||||
use beacon_chain::attestation_verification::VerifiedAttestation;
|
||||
use beacon_chain::validator_monitor::timestamp_now;
|
||||
@@ -971,12 +971,12 @@ pub fn post_validator_aggregate_and_proofs<T: BeaconChainTypes>(
|
||||
|
||||
// GET validator/duties/proposer/{epoch}
|
||||
pub fn get_validator_duties_proposer<T: BeaconChainTypes>(
|
||||
eth_v1: EthV1Filter,
|
||||
any_version: AnyVersionFilter,
|
||||
chain_filter: ChainFilter<T>,
|
||||
not_while_syncing_filter: NotWhileSyncingFilter,
|
||||
task_spawner_filter: TaskSpawnerFilter<T>,
|
||||
) -> ResponseFilter {
|
||||
eth_v1
|
||||
any_version
|
||||
.and(warp::path("validator"))
|
||||
.and(warp::path("duties"))
|
||||
.and(warp::path("proposer"))
|
||||
@@ -990,13 +990,20 @@ pub fn get_validator_duties_proposer<T: BeaconChainTypes>(
|
||||
.and(task_spawner_filter)
|
||||
.and(chain_filter)
|
||||
.then(
|
||||
|epoch: Epoch,
|
||||
|endpoint_version: EndpointVersion,
|
||||
epoch: Epoch,
|
||||
not_synced_filter: Result<(), Rejection>,
|
||||
task_spawner: TaskSpawner<T::EthSpec>,
|
||||
chain: Arc<BeaconChain<T>>| {
|
||||
task_spawner.blocking_json_task(Priority::P0, move || {
|
||||
not_synced_filter?;
|
||||
proposer_duties::proposer_duties(epoch, &chain)
|
||||
if endpoint_version == V1 {
|
||||
proposer_duties::proposer_duties(epoch, &chain)
|
||||
} else if endpoint_version == V2 {
|
||||
proposer_duties::proposer_duties_v2(epoch, &chain)
|
||||
} else {
|
||||
Err(unsupported_version_rejection(endpoint_version))
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user