Standard beacon api updates (#1831)
## Issue Addressed Resolves #1809 Resolves #1824 Resolves #1818 Resolves #1828 (hopefully) ## Proposed Changes - add `validator_index` to the proposer duties endpoint - add the ability to query for historical proposer duties - `StateId` deserialization now fails with a 400 warp rejection - add the `validator_balances` endpoint - update the `aggregate_and_proofs` endpoint to accept an array - updates the attester duties endpoint from a `GET` to a `POST` - reduces the number of times we query for proposer duties from once per slot per validator to only once per slot Co-authored-by: realbigsean <seananderson33@gmail.com> Co-authored-by: Paul Hauner <paul@paulhauner.com>
This commit is contained in:
parent
556190ff46
commit
f8da151b0b
@ -984,9 +984,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
///
|
///
|
||||||
/// - `VerifiedUnaggregatedAttestation`
|
/// - `VerifiedUnaggregatedAttestation`
|
||||||
/// - `VerifiedAggregatedAttestation`
|
/// - `VerifiedAggregatedAttestation`
|
||||||
pub fn apply_attestation_to_fork_choice<'a>(
|
pub fn apply_attestation_to_fork_choice(
|
||||||
&self,
|
&self,
|
||||||
verified: &'a impl SignatureVerifiedAttestation<T>,
|
verified: &impl SignatureVerifiedAttestation<T>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let _timer = metrics::start_timer(&metrics::FORK_CHOICE_PROCESS_ATTESTATION_TIMES);
|
let _timer = metrics::start_timer(&metrics::FORK_CHOICE_PROCESS_ATTESTATION_TIMES);
|
||||||
|
|
||||||
|
@ -89,6 +89,7 @@ impl BeaconProposerCache {
|
|||||||
|
|
||||||
Ok(ProposerData {
|
Ok(ProposerData {
|
||||||
pubkey: PublicKeyBytes::from(pubkey),
|
pubkey: PublicKeyBytes::from(pubkey),
|
||||||
|
validator_index: i as u64,
|
||||||
slot,
|
slot,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -39,7 +39,7 @@ use std::sync::Arc;
|
|||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
use types::{
|
use types::{
|
||||||
Attestation, AttestationDuty, AttesterSlashing, CloneConfig, CommitteeCache, Epoch, EthSpec,
|
Attestation, AttestationDuty, AttesterSlashing, CloneConfig, CommitteeCache, Epoch, EthSpec,
|
||||||
Hash256, ProposerSlashing, PublicKey, RelativeEpoch, SignedAggregateAndProof,
|
Hash256, ProposerSlashing, PublicKey, PublicKeyBytes, RelativeEpoch, SignedAggregateAndProof,
|
||||||
SignedBeaconBlock, SignedVoluntaryExit, Slot, YamlConfig,
|
SignedBeaconBlock, SignedVoluntaryExit, Slot, YamlConfig,
|
||||||
};
|
};
|
||||||
use warp::{http::Response, Filter};
|
use warp::{http::Response, Filter};
|
||||||
@ -390,7 +390,13 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
let beacon_states_path = eth1_v1
|
let beacon_states_path = eth1_v1
|
||||||
.and(warp::path("beacon"))
|
.and(warp::path("beacon"))
|
||||||
.and(warp::path("states"))
|
.and(warp::path("states"))
|
||||||
.and(warp::path::param::<StateId>())
|
.and(warp::path::param::<StateId>().or_else(|_| {
|
||||||
|
blocking_task(|| {
|
||||||
|
Err(warp_utils::reject::custom_bad_request(
|
||||||
|
"Invalid state ID".to_string(),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}))
|
||||||
.and(chain_filter.clone());
|
.and(chain_filter.clone());
|
||||||
|
|
||||||
// GET beacon/states/{state_id}/root
|
// GET beacon/states/{state_id}/root
|
||||||
@ -435,6 +441,50 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// GET beacon/states/{state_id}/validator_balances?id
|
||||||
|
let get_beacon_state_validator_balances = beacon_states_path
|
||||||
|
.clone()
|
||||||
|
.and(warp::path("validator_balances"))
|
||||||
|
.and(warp::path::end())
|
||||||
|
.and(warp::query::<api_types::ValidatorBalancesQuery>())
|
||||||
|
.and_then(
|
||||||
|
|state_id: StateId,
|
||||||
|
chain: Arc<BeaconChain<T>>,
|
||||||
|
query: api_types::ValidatorBalancesQuery| {
|
||||||
|
blocking_json_task(move || {
|
||||||
|
state_id
|
||||||
|
.map_state(&chain, |state| {
|
||||||
|
Ok(state
|
||||||
|
.validators
|
||||||
|
.iter()
|
||||||
|
.zip(state.balances.iter())
|
||||||
|
.enumerate()
|
||||||
|
// filter by validator id(s) if provided
|
||||||
|
.filter(|(index, (validator, _))| {
|
||||||
|
query.id.as_ref().map_or(true, |ids| {
|
||||||
|
ids.0.iter().any(|id| match id {
|
||||||
|
ValidatorId::PublicKey(pubkey) => {
|
||||||
|
&validator.pubkey == pubkey
|
||||||
|
}
|
||||||
|
ValidatorId::Index(param_index) => {
|
||||||
|
*param_index == *index as u64
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.map(|(index, (_, balance))| {
|
||||||
|
Some(api_types::ValidatorBalanceData {
|
||||||
|
index: index as u64,
|
||||||
|
balance: *balance,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>())
|
||||||
|
})
|
||||||
|
.map(api_types::GenericResponse::from)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// GET beacon/states/{state_id}/validators?id,status
|
// GET beacon/states/{state_id}/validators?id,status
|
||||||
let get_beacon_state_validators = beacon_states_path
|
let get_beacon_state_validators = beacon_states_path
|
||||||
.clone()
|
.clone()
|
||||||
@ -747,7 +797,7 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
* beacon/blocks
|
* beacon/blocks
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// POST beacon/blocks/{block_id}
|
// POST beacon/blocks
|
||||||
let post_beacon_blocks = eth1_v1
|
let post_beacon_blocks = eth1_v1
|
||||||
.and(warp::path("beacon"))
|
.and(warp::path("beacon"))
|
||||||
.and(warp::path("blocks"))
|
.and(warp::path("blocks"))
|
||||||
@ -1370,18 +1420,158 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
* validator
|
* validator
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// GET validator/duties/attester/{epoch}
|
// GET validator/duties/proposer/{epoch}
|
||||||
let get_validator_duties_attester = eth1_v1
|
let get_validator_duties_proposer = eth1_v1
|
||||||
|
.and(warp::path("validator"))
|
||||||
|
.and(warp::path("duties"))
|
||||||
|
.and(warp::path("proposer"))
|
||||||
|
.and(warp::path::param::<Epoch>())
|
||||||
|
.and(warp::path::end())
|
||||||
|
.and(not_while_syncing_filter.clone())
|
||||||
|
.and(chain_filter.clone())
|
||||||
|
.and(beacon_proposer_cache())
|
||||||
|
.and_then(
|
||||||
|
|epoch: Epoch,
|
||||||
|
chain: Arc<BeaconChain<T>>,
|
||||||
|
beacon_proposer_cache: Arc<Mutex<BeaconProposerCache>>| {
|
||||||
|
blocking_json_task(move || {
|
||||||
|
let current_epoch = chain
|
||||||
|
.epoch()
|
||||||
|
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||||
|
|
||||||
|
if epoch > current_epoch {
|
||||||
|
return Err(warp_utils::reject::custom_bad_request(format!(
|
||||||
|
"request epoch {} is ahead of the current epoch {}",
|
||||||
|
epoch, current_epoch
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if epoch == current_epoch {
|
||||||
|
beacon_proposer_cache
|
||||||
|
.lock()
|
||||||
|
.get_proposers(&chain, epoch)
|
||||||
|
.map(api_types::GenericResponse::from)
|
||||||
|
} else {
|
||||||
|
let state =
|
||||||
|
StateId::slot(epoch.start_slot(T::EthSpec::slots_per_epoch()))
|
||||||
|
.state(&chain)?;
|
||||||
|
|
||||||
|
epoch
|
||||||
|
.slot_iter(T::EthSpec::slots_per_epoch())
|
||||||
|
.map(|slot| {
|
||||||
|
state
|
||||||
|
.get_beacon_proposer_index(slot, &chain.spec)
|
||||||
|
.map_err(warp_utils::reject::beacon_state_error)
|
||||||
|
.and_then(|i| {
|
||||||
|
let pubkey =
|
||||||
|
chain.validator_pubkey(i)
|
||||||
|
.map_err(warp_utils::reject::beacon_chain_error)?
|
||||||
|
.ok_or_else(||
|
||||||
|
warp_utils::reject::beacon_chain_error(
|
||||||
|
BeaconChainError::ValidatorPubkeyCacheIncomplete(i)
|
||||||
|
)
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(api_types::ProposerData {
|
||||||
|
pubkey: PublicKeyBytes::from(pubkey),
|
||||||
|
validator_index: i as u64,
|
||||||
|
slot,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<api_types::ProposerData>, _>>()
|
||||||
|
.map(api_types::GenericResponse::from)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// GET validator/blocks/{slot}
|
||||||
|
let get_validator_blocks = eth1_v1
|
||||||
|
.and(warp::path("validator"))
|
||||||
|
.and(warp::path("blocks"))
|
||||||
|
.and(warp::path::param::<Slot>())
|
||||||
|
.and(warp::path::end())
|
||||||
|
.and(not_while_syncing_filter.clone())
|
||||||
|
.and(warp::query::<api_types::ValidatorBlocksQuery>())
|
||||||
|
.and(chain_filter.clone())
|
||||||
|
.and_then(
|
||||||
|
|slot: Slot, query: api_types::ValidatorBlocksQuery, chain: Arc<BeaconChain<T>>| {
|
||||||
|
blocking_json_task(move || {
|
||||||
|
let randao_reveal = (&query.randao_reveal).try_into().map_err(|e| {
|
||||||
|
warp_utils::reject::custom_bad_request(format!(
|
||||||
|
"randao reveal is not valid BLS signature: {:?}",
|
||||||
|
e
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
chain
|
||||||
|
.produce_block(randao_reveal, slot, query.graffiti.map(Into::into))
|
||||||
|
.map(|block_and_state| block_and_state.0)
|
||||||
|
.map(api_types::GenericResponse::from)
|
||||||
|
.map_err(warp_utils::reject::block_production_error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// GET validator/attestation_data?slot,committee_index
|
||||||
|
let get_validator_attestation_data = eth1_v1
|
||||||
|
.and(warp::path("validator"))
|
||||||
|
.and(warp::path("attestation_data"))
|
||||||
|
.and(warp::path::end())
|
||||||
|
.and(warp::query::<api_types::ValidatorAttestationDataQuery>())
|
||||||
|
.and(not_while_syncing_filter.clone())
|
||||||
|
.and(chain_filter.clone())
|
||||||
|
.and_then(
|
||||||
|
|query: api_types::ValidatorAttestationDataQuery, chain: Arc<BeaconChain<T>>| {
|
||||||
|
blocking_json_task(move || {
|
||||||
|
chain
|
||||||
|
.produce_unaggregated_attestation(query.slot, query.committee_index)
|
||||||
|
.map(|attestation| attestation.data)
|
||||||
|
.map(api_types::GenericResponse::from)
|
||||||
|
.map_err(warp_utils::reject::beacon_chain_error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// GET validator/aggregate_attestation?attestation_data_root,slot
|
||||||
|
let get_validator_aggregate_attestation = eth1_v1
|
||||||
|
.and(warp::path("validator"))
|
||||||
|
.and(warp::path("aggregate_attestation"))
|
||||||
|
.and(warp::path::end())
|
||||||
|
.and(warp::query::<api_types::ValidatorAggregateAttestationQuery>())
|
||||||
|
.and(not_while_syncing_filter.clone())
|
||||||
|
.and(chain_filter.clone())
|
||||||
|
.and_then(
|
||||||
|
|query: api_types::ValidatorAggregateAttestationQuery, chain: Arc<BeaconChain<T>>| {
|
||||||
|
blocking_json_task(move || {
|
||||||
|
chain
|
||||||
|
.get_aggregated_attestation_by_slot_and_root(
|
||||||
|
query.slot,
|
||||||
|
&query.attestation_data_root,
|
||||||
|
)
|
||||||
|
.map(api_types::GenericResponse::from)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
warp_utils::reject::custom_not_found(
|
||||||
|
"no matching aggregate found".to_string(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// POST validator/duties/attester/{epoch}
|
||||||
|
let post_validator_duties_attester = eth1_v1
|
||||||
.and(warp::path("validator"))
|
.and(warp::path("validator"))
|
||||||
.and(warp::path("duties"))
|
.and(warp::path("duties"))
|
||||||
.and(warp::path("attester"))
|
.and(warp::path("attester"))
|
||||||
.and(warp::path::param::<Epoch>())
|
.and(warp::path::param::<Epoch>())
|
||||||
.and(warp::path::end())
|
.and(warp::path::end())
|
||||||
.and(not_while_syncing_filter.clone())
|
.and(not_while_syncing_filter.clone())
|
||||||
.and(warp::query::<api_types::ValidatorDutiesQuery>())
|
.and(warp::body::json())
|
||||||
.and(chain_filter.clone())
|
.and(chain_filter.clone())
|
||||||
.and_then(
|
.and_then(
|
||||||
|epoch: Epoch, query: api_types::ValidatorDutiesQuery, chain: Arc<BeaconChain<T>>| {
|
|epoch: Epoch, indices: api_types::ValidatorIndexData, chain: Arc<BeaconChain<T>>| {
|
||||||
blocking_json_task(move || {
|
blocking_json_task(move || {
|
||||||
let current_epoch = chain
|
let current_epoch = chain
|
||||||
.epoch()
|
.epoch()
|
||||||
@ -1397,30 +1587,22 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
let validator_count = StateId::head()
|
let validator_count = StateId::head()
|
||||||
.map_state(&chain, |state| Ok(state.validators.len() as u64))?;
|
.map_state(&chain, |state| Ok(state.validators.len() as u64))?;
|
||||||
|
|
||||||
let indices = query
|
|
||||||
.index
|
|
||||||
.as_ref()
|
|
||||||
.map(|index| index.0.clone())
|
|
||||||
.map(Result::Ok)
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
Ok::<_, warp::Rejection>((0..validator_count).collect())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let pubkeys = indices
|
let pubkeys = indices
|
||||||
.into_iter()
|
.0
|
||||||
.filter(|i| *i < validator_count as u64)
|
.iter()
|
||||||
|
.filter(|i| **i < validator_count as u64)
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let pubkey = chain
|
let pubkey = chain
|
||||||
.validator_pubkey(i as usize)
|
.validator_pubkey(*i as usize)
|
||||||
.map_err(warp_utils::reject::beacon_chain_error)?
|
.map_err(warp_utils::reject::beacon_chain_error)?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
warp_utils::reject::custom_bad_request(format!(
|
warp_utils::reject::custom_bad_request(format!(
|
||||||
"unknown validator index {}",
|
"unknown validator index {}",
|
||||||
i
|
*i
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok((i, pubkey))
|
Ok((*i, pubkey))
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, warp::Rejection>>()?;
|
.collect::<Result<Vec<_>, warp::Rejection>>()?;
|
||||||
|
|
||||||
@ -1536,103 +1718,6 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// GET validator/duties/proposer/{epoch}
|
|
||||||
let get_validator_duties_proposer = eth1_v1
|
|
||||||
.and(warp::path("validator"))
|
|
||||||
.and(warp::path("duties"))
|
|
||||||
.and(warp::path("proposer"))
|
|
||||||
.and(warp::path::param::<Epoch>())
|
|
||||||
.and(warp::path::end())
|
|
||||||
.and(not_while_syncing_filter.clone())
|
|
||||||
.and(chain_filter.clone())
|
|
||||||
.and(beacon_proposer_cache())
|
|
||||||
.and_then(
|
|
||||||
|epoch: Epoch,
|
|
||||||
chain: Arc<BeaconChain<T>>,
|
|
||||||
beacon_proposer_cache: Arc<Mutex<BeaconProposerCache>>| {
|
|
||||||
blocking_json_task(move || {
|
|
||||||
beacon_proposer_cache
|
|
||||||
.lock()
|
|
||||||
.get_proposers(&chain, epoch)
|
|
||||||
.map(api_types::GenericResponse::from)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// GET validator/blocks/{slot}
|
|
||||||
let get_validator_blocks = eth1_v1
|
|
||||||
.and(warp::path("validator"))
|
|
||||||
.and(warp::path("blocks"))
|
|
||||||
.and(warp::path::param::<Slot>())
|
|
||||||
.and(warp::path::end())
|
|
||||||
.and(not_while_syncing_filter.clone())
|
|
||||||
.and(warp::query::<api_types::ValidatorBlocksQuery>())
|
|
||||||
.and(chain_filter.clone())
|
|
||||||
.and_then(
|
|
||||||
|slot: Slot, query: api_types::ValidatorBlocksQuery, chain: Arc<BeaconChain<T>>| {
|
|
||||||
blocking_json_task(move || {
|
|
||||||
let randao_reveal = (&query.randao_reveal).try_into().map_err(|e| {
|
|
||||||
warp_utils::reject::custom_bad_request(format!(
|
|
||||||
"randao reveal is not valid BLS signature: {:?}",
|
|
||||||
e
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
chain
|
|
||||||
.produce_block(randao_reveal, slot, query.graffiti.map(Into::into))
|
|
||||||
.map(|block_and_state| block_and_state.0)
|
|
||||||
.map(api_types::GenericResponse::from)
|
|
||||||
.map_err(warp_utils::reject::block_production_error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// GET validator/attestation_data?slot,committee_index
|
|
||||||
let get_validator_attestation_data = eth1_v1
|
|
||||||
.and(warp::path("validator"))
|
|
||||||
.and(warp::path("attestation_data"))
|
|
||||||
.and(warp::path::end())
|
|
||||||
.and(warp::query::<api_types::ValidatorAttestationDataQuery>())
|
|
||||||
.and(not_while_syncing_filter.clone())
|
|
||||||
.and(chain_filter.clone())
|
|
||||||
.and_then(
|
|
||||||
|query: api_types::ValidatorAttestationDataQuery, chain: Arc<BeaconChain<T>>| {
|
|
||||||
blocking_json_task(move || {
|
|
||||||
chain
|
|
||||||
.produce_unaggregated_attestation(query.slot, query.committee_index)
|
|
||||||
.map(|attestation| attestation.data)
|
|
||||||
.map(api_types::GenericResponse::from)
|
|
||||||
.map_err(warp_utils::reject::beacon_chain_error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// GET validator/aggregate_attestation?attestation_data_root,slot
|
|
||||||
let get_validator_aggregate_attestation = eth1_v1
|
|
||||||
.and(warp::path("validator"))
|
|
||||||
.and(warp::path("aggregate_attestation"))
|
|
||||||
.and(warp::path::end())
|
|
||||||
.and(warp::query::<api_types::ValidatorAggregateAttestationQuery>())
|
|
||||||
.and(not_while_syncing_filter.clone())
|
|
||||||
.and(chain_filter.clone())
|
|
||||||
.and_then(
|
|
||||||
|query: api_types::ValidatorAggregateAttestationQuery, chain: Arc<BeaconChain<T>>| {
|
|
||||||
blocking_json_task(move || {
|
|
||||||
chain
|
|
||||||
.get_aggregated_attestation_by_slot_and_root(
|
|
||||||
query.slot,
|
|
||||||
&query.attestation_data_root,
|
|
||||||
)
|
|
||||||
.map(api_types::GenericResponse::from)
|
|
||||||
.ok_or_else(|| {
|
|
||||||
warp_utils::reject::custom_not_found(
|
|
||||||
"no matching aggregate found".to_string(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// POST validator/aggregate_and_proofs
|
// POST validator/aggregate_and_proofs
|
||||||
let post_validator_aggregate_and_proofs = eth1_v1
|
let post_validator_aggregate_and_proofs = eth1_v1
|
||||||
.and(warp::path("validator"))
|
.and(warp::path("validator"))
|
||||||
@ -1642,53 +1727,81 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
.and(chain_filter.clone())
|
.and(chain_filter.clone())
|
||||||
.and(warp::body::json())
|
.and(warp::body::json())
|
||||||
.and(network_tx_filter.clone())
|
.and(network_tx_filter.clone())
|
||||||
|
.and(log_filter.clone())
|
||||||
.and_then(
|
.and_then(
|
||||||
|chain: Arc<BeaconChain<T>>,
|
|chain: Arc<BeaconChain<T>>,
|
||||||
aggregate: SignedAggregateAndProof<T::EthSpec>,
|
aggregates: Vec<SignedAggregateAndProof<T::EthSpec>>,
|
||||||
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>| {
|
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>, log: Logger| {
|
||||||
blocking_json_task(move || {
|
blocking_json_task(move || {
|
||||||
let aggregate =
|
let mut verified_aggregates = Vec::with_capacity(aggregates.len());
|
||||||
|
let mut messages = Vec::with_capacity(aggregates.len());
|
||||||
|
let mut failures = Vec::new();
|
||||||
|
|
||||||
|
// Verify that all messages in the post are valid before processing further
|
||||||
|
for (index, aggregate) in aggregates.as_slice().iter().enumerate() {
|
||||||
match chain.verify_aggregated_attestation_for_gossip(aggregate.clone()) {
|
match chain.verify_aggregated_attestation_for_gossip(aggregate.clone()) {
|
||||||
Ok(aggregate) => aggregate,
|
Ok(verified_aggregate) => {
|
||||||
|
messages.push(PubsubMessage::AggregateAndProofAttestation(Box::new(
|
||||||
|
verified_aggregate.aggregate().clone(),
|
||||||
|
)));
|
||||||
|
verified_aggregates.push((index, verified_aggregate));
|
||||||
|
}
|
||||||
// If we already know the attestation, don't broadcast it or attempt to
|
// If we already know the attestation, don't broadcast it or attempt to
|
||||||
// further verify it. Return success.
|
// further verify it. Return success.
|
||||||
//
|
//
|
||||||
// It's reasonably likely that two different validators produce
|
// It's reasonably likely that two different validators produce
|
||||||
// identical aggregates, especially if they're using the same beacon
|
// identical aggregates, especially if they're using the same beacon
|
||||||
// node.
|
// node.
|
||||||
Err(AttnError::AttestationAlreadyKnown(_)) => return Ok(()),
|
Err(AttnError::AttestationAlreadyKnown(_)) => continue,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Err(warp_utils::reject::object_invalid(format!(
|
error!(log,
|
||||||
"gossip verification failed: {:?}",
|
"Failure verifying aggregate and proofs";
|
||||||
e
|
"error" => format!("{:?}", e),
|
||||||
)));
|
"request_index" => index,
|
||||||
|
"aggregator_index" => aggregate.message.aggregator_index,
|
||||||
|
"attestation_index" => aggregate.message.aggregate.data.index,
|
||||||
|
"attestation_slot" => aggregate.message.aggregate.data.slot,
|
||||||
|
);
|
||||||
|
failures.push(api_types::Failure::new(index, format!("Verification: {:?}", e)));
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
publish_pubsub_message(
|
// Publish aggregate attestations to the libp2p network
|
||||||
&network_tx,
|
if !messages.is_empty() {
|
||||||
PubsubMessage::AggregateAndProofAttestation(Box::new(
|
publish_network_message(&network_tx, NetworkMessage::Publish { messages })?;
|
||||||
aggregate.aggregate().clone(),
|
}
|
||||||
)),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
chain
|
// Import aggregate attestations
|
||||||
.apply_attestation_to_fork_choice(&aggregate)
|
for (index, verified_aggregate) in verified_aggregates {
|
||||||
.map_err(|e| {
|
if let Err(e) = chain.apply_attestation_to_fork_choice(&verified_aggregate) {
|
||||||
warp_utils::reject::broadcast_without_import(format!(
|
error!(log,
|
||||||
"not applied to fork choice: {:?}",
|
"Failure applying verified aggregate attestation to fork choice";
|
||||||
e
|
"error" => format!("{:?}", e),
|
||||||
|
"request_index" => index,
|
||||||
|
"aggregator_index" => verified_aggregate.aggregate().message.aggregator_index,
|
||||||
|
"attestation_index" => verified_aggregate.attestation().data.index,
|
||||||
|
"attestation_slot" => verified_aggregate.attestation().data.slot,
|
||||||
|
);
|
||||||
|
failures.push(api_types::Failure::new(index, format!("Fork choice: {:?}", e)));
|
||||||
|
}
|
||||||
|
if let Err(e) = chain.add_to_block_inclusion_pool(verified_aggregate) {
|
||||||
|
warn!(log,
|
||||||
|
"Could not add verified aggregate attestation to the inclusion pool";
|
||||||
|
"error" => format!("{:?}", e),
|
||||||
|
"request_index" => index,
|
||||||
|
);
|
||||||
|
failures.push(api_types::Failure::new(index, format!("Op pool: {:?}", e)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !failures.is_empty() {
|
||||||
|
Err(warp_utils::reject::indexed_bad_request("error processing aggregate and proofs".to_string(),
|
||||||
|
failures
|
||||||
))
|
))
|
||||||
})?;
|
} else {
|
||||||
|
|
||||||
chain.add_to_block_inclusion_pool(aggregate).map_err(|e| {
|
|
||||||
warp_utils::reject::broadcast_without_import(format!(
|
|
||||||
"not applied to block inclusion pool: {:?}",
|
|
||||||
e
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -1935,9 +2048,11 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
let routes = warp::get()
|
let routes = warp::get()
|
||||||
.and(
|
.and(
|
||||||
get_beacon_genesis
|
get_beacon_genesis
|
||||||
|
.boxed()
|
||||||
.or(get_beacon_state_root.boxed())
|
.or(get_beacon_state_root.boxed())
|
||||||
.or(get_beacon_state_fork.boxed())
|
.or(get_beacon_state_fork.boxed())
|
||||||
.or(get_beacon_state_finality_checkpoints.boxed())
|
.or(get_beacon_state_finality_checkpoints.boxed())
|
||||||
|
.or(get_beacon_state_validator_balances.boxed())
|
||||||
.or(get_beacon_state_validators.boxed())
|
.or(get_beacon_state_validators.boxed())
|
||||||
.or(get_beacon_state_validators_id.boxed())
|
.or(get_beacon_state_validators_id.boxed())
|
||||||
.or(get_beacon_state_committees.boxed())
|
.or(get_beacon_state_committees.boxed())
|
||||||
@ -1961,7 +2076,6 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
.or(get_node_health.boxed())
|
.or(get_node_health.boxed())
|
||||||
.or(get_node_peers_by_id.boxed())
|
.or(get_node_peers_by_id.boxed())
|
||||||
.or(get_node_peers.boxed())
|
.or(get_node_peers.boxed())
|
||||||
.or(get_validator_duties_attester.boxed())
|
|
||||||
.or(get_validator_duties_proposer.boxed())
|
.or(get_validator_duties_proposer.boxed())
|
||||||
.or(get_validator_blocks.boxed())
|
.or(get_validator_blocks.boxed())
|
||||||
.or(get_validator_attestation_data.boxed())
|
.or(get_validator_attestation_data.boxed())
|
||||||
@ -1976,23 +2090,19 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
.or(get_lighthouse_eth1_syncing.boxed())
|
.or(get_lighthouse_eth1_syncing.boxed())
|
||||||
.or(get_lighthouse_eth1_block_cache.boxed())
|
.or(get_lighthouse_eth1_block_cache.boxed())
|
||||||
.or(get_lighthouse_eth1_deposit_cache.boxed())
|
.or(get_lighthouse_eth1_deposit_cache.boxed())
|
||||||
.or(get_lighthouse_beacon_states_ssz.boxed())
|
.or(get_lighthouse_beacon_states_ssz.boxed()),
|
||||||
.boxed(),
|
|
||||||
)
|
)
|
||||||
.or(warp::post()
|
.or(warp::post().and(
|
||||||
.and(
|
|
||||||
post_beacon_blocks
|
post_beacon_blocks
|
||||||
|
.boxed()
|
||||||
.or(post_beacon_pool_attestations.boxed())
|
.or(post_beacon_pool_attestations.boxed())
|
||||||
.or(post_beacon_pool_attester_slashings.boxed())
|
.or(post_beacon_pool_attester_slashings.boxed())
|
||||||
.or(post_beacon_pool_proposer_slashings.boxed())
|
.or(post_beacon_pool_proposer_slashings.boxed())
|
||||||
.or(post_beacon_pool_voluntary_exits.boxed())
|
.or(post_beacon_pool_voluntary_exits.boxed())
|
||||||
|
.or(post_validator_duties_attester.boxed())
|
||||||
.or(post_validator_aggregate_and_proofs.boxed())
|
.or(post_validator_aggregate_and_proofs.boxed())
|
||||||
.or(post_validator_beacon_committee_subscriptions.boxed())
|
.or(post_validator_beacon_committee_subscriptions.boxed()),
|
||||||
.boxed(),
|
))
|
||||||
)
|
|
||||||
.boxed())
|
|
||||||
.boxed()
|
|
||||||
// Maps errors into HTTP responses.
|
|
||||||
.recover(warp_utils::reject::handle_rejection)
|
.recover(warp_utils::reject::handle_rejection)
|
||||||
.with(slog_logging(log.clone()))
|
.with(slog_logging(log.clone()))
|
||||||
.with(prometheus_metrics())
|
.with(prometheus_metrics())
|
||||||
|
@ -411,6 +411,73 @@ impl ApiTester {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn test_beacon_states_validator_balances(self) -> Self {
|
||||||
|
for state_id in self.interesting_state_ids() {
|
||||||
|
for validator_indices in self.interesting_validator_indices() {
|
||||||
|
let state_opt = self.get_state(state_id);
|
||||||
|
let validators: Vec<Validator> = match state_opt.as_ref() {
|
||||||
|
Some(state) => state.validators.clone().into(),
|
||||||
|
None => vec![],
|
||||||
|
};
|
||||||
|
let validator_index_ids = validator_indices
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(|i| ValidatorId::Index(i))
|
||||||
|
.collect::<Vec<ValidatorId>>();
|
||||||
|
let validator_pubkey_ids = validator_indices
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(|i| {
|
||||||
|
ValidatorId::PublicKey(
|
||||||
|
validators
|
||||||
|
.get(i as usize)
|
||||||
|
.map_or(PublicKeyBytes::empty(), |val| val.pubkey.clone()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<ValidatorId>>();
|
||||||
|
|
||||||
|
let result_index_ids = self
|
||||||
|
.client
|
||||||
|
.get_beacon_states_validator_balances(
|
||||||
|
state_id,
|
||||||
|
Some(validator_index_ids.as_slice()),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.map(|res| res.data);
|
||||||
|
let result_pubkey_ids = self
|
||||||
|
.client
|
||||||
|
.get_beacon_states_validator_balances(
|
||||||
|
state_id,
|
||||||
|
Some(validator_pubkey_ids.as_slice()),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.map(|res| res.data);
|
||||||
|
|
||||||
|
let expected = state_opt.map(|state| {
|
||||||
|
let mut validators = Vec::with_capacity(validator_indices.len());
|
||||||
|
|
||||||
|
for i in validator_indices {
|
||||||
|
if i < state.balances.len() as u64 {
|
||||||
|
validators.push(ValidatorBalanceData {
|
||||||
|
index: i as u64,
|
||||||
|
balance: state.balances[i as usize],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validators
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(result_index_ids, expected, "{:?}", state_id);
|
||||||
|
assert_eq!(result_pubkey_ids, expected, "{:?}", state_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn test_beacon_states_validators(self) -> Self {
|
pub async fn test_beacon_states_validators(self) -> Self {
|
||||||
for state_id in self.interesting_state_ids() {
|
for state_id in self.interesting_state_ids() {
|
||||||
for statuses in self.interesting_validator_statuses() {
|
for statuses in self.interesting_validator_statuses() {
|
||||||
@ -1235,7 +1302,7 @@ impl ApiTester {
|
|||||||
if epoch > current_epoch + 1 {
|
if epoch > current_epoch + 1 {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
self.client
|
self.client
|
||||||
.get_validator_duties_attester(epoch, Some(&indices))
|
.post_validator_duties_attester(epoch, indices.as_slice())
|
||||||
.await
|
.await
|
||||||
.unwrap_err()
|
.unwrap_err()
|
||||||
.status()
|
.status()
|
||||||
@ -1247,7 +1314,7 @@ impl ApiTester {
|
|||||||
|
|
||||||
let results = self
|
let results = self
|
||||||
.client
|
.client
|
||||||
.get_validator_duties_attester(epoch, Some(&indices))
|
.post_validator_duties_attester(epoch, indices.as_slice())
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.data;
|
.data;
|
||||||
@ -1336,7 +1403,11 @@ impl ApiTester {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let pubkey = state.validators[index].pubkey.clone().into();
|
let pubkey = state.validators[index].pubkey.clone().into();
|
||||||
|
|
||||||
ProposerData { pubkey, slot }
|
ProposerData {
|
||||||
|
pubkey,
|
||||||
|
validator_index: index as u64,
|
||||||
|
slot,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
@ -1473,17 +1544,17 @@ impl ApiTester {
|
|||||||
let fork = head.beacon_state.fork;
|
let fork = head.beacon_state.fork;
|
||||||
let genesis_validators_root = self.chain.genesis_validators_root;
|
let genesis_validators_root = self.chain.genesis_validators_root;
|
||||||
|
|
||||||
let mut duties = vec![];
|
let duties = self
|
||||||
for i in 0..self.validator_keypairs.len() {
|
.client
|
||||||
duties.push(
|
.post_validator_duties_attester(
|
||||||
self.client
|
epoch,
|
||||||
.get_validator_duties_attester(epoch, Some(&[i as u64]))
|
(0..self.validator_keypairs.len() as u64)
|
||||||
|
.collect::<Vec<u64>>()
|
||||||
|
.as_slice(),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.data[0]
|
.data;
|
||||||
.clone(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let (i, kp, duty, proof) = self
|
let (i, kp, duty, proof) = self
|
||||||
.validator_keypairs
|
.validator_keypairs
|
||||||
@ -1554,7 +1625,7 @@ impl ApiTester {
|
|||||||
let aggregate = self.get_aggregate().await;
|
let aggregate = self.get_aggregate().await;
|
||||||
|
|
||||||
self.client
|
self.client
|
||||||
.post_validator_aggregate_and_proof::<E>(&aggregate)
|
.post_validator_aggregate_and_proof::<E>(&[aggregate])
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@ -1569,7 +1640,7 @@ impl ApiTester {
|
|||||||
aggregate.message.aggregate.data.slot += 1;
|
aggregate.message.aggregate.data.slot += 1;
|
||||||
|
|
||||||
self.client
|
self.client
|
||||||
.post_validator_aggregate_and_proof::<E>(&aggregate)
|
.post_validator_aggregate_and_proof::<E>(&[aggregate])
|
||||||
.await
|
.await
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
|
|
||||||
@ -1700,6 +1771,8 @@ async fn beacon_get() {
|
|||||||
.await
|
.await
|
||||||
.test_beacon_states_validators()
|
.test_beacon_states_validators()
|
||||||
.await
|
.await
|
||||||
|
.test_beacon_states_validator_balances()
|
||||||
|
.await
|
||||||
.test_beacon_states_committees()
|
.test_beacon_states_committees()
|
||||||
.await
|
.await
|
||||||
.test_beacon_states_validator_id()
|
.test_beacon_states_validator_id()
|
||||||
|
@ -28,6 +28,8 @@ pub enum Error {
|
|||||||
Reqwest(reqwest::Error),
|
Reqwest(reqwest::Error),
|
||||||
/// The server returned an error message where the body was able to be parsed.
|
/// The server returned an error message where the body was able to be parsed.
|
||||||
ServerMessage(ErrorMessage),
|
ServerMessage(ErrorMessage),
|
||||||
|
/// The server returned an error message with an array of errors.
|
||||||
|
ServerIndexedMessage(IndexedErrorMessage),
|
||||||
/// The server returned an error message where the body was unable to be parsed.
|
/// The server returned an error message where the body was unable to be parsed.
|
||||||
StatusCode(StatusCode),
|
StatusCode(StatusCode),
|
||||||
/// The supplied URL is badly formatted. It should look something like `http://127.0.0.1:5052`.
|
/// The supplied URL is badly formatted. It should look something like `http://127.0.0.1:5052`.
|
||||||
@ -50,6 +52,7 @@ impl Error {
|
|||||||
match self {
|
match self {
|
||||||
Error::Reqwest(error) => error.status(),
|
Error::Reqwest(error) => error.status(),
|
||||||
Error::ServerMessage(msg) => StatusCode::try_from(msg.code).ok(),
|
Error::ServerMessage(msg) => StatusCode::try_from(msg.code).ok(),
|
||||||
|
Error::ServerIndexedMessage(msg) => StatusCode::try_from(msg.code).ok(),
|
||||||
Error::StatusCode(status) => Some(*status),
|
Error::StatusCode(status) => Some(*status),
|
||||||
Error::InvalidUrl(_) => None,
|
Error::InvalidUrl(_) => None,
|
||||||
Error::InvalidSecret(_) => None,
|
Error::InvalidSecret(_) => None,
|
||||||
@ -137,6 +140,26 @@ impl BeaconNodeHttpClient {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Perform a HTTP POST request, returning a JSON response.
|
||||||
|
async fn post_with_response<T: DeserializeOwned, U: IntoUrl, V: Serialize>(
|
||||||
|
&self,
|
||||||
|
url: U,
|
||||||
|
body: &V,
|
||||||
|
) -> Result<T, Error> {
|
||||||
|
let response = self
|
||||||
|
.client
|
||||||
|
.post(url)
|
||||||
|
.json(body)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(Error::Reqwest)?;
|
||||||
|
ok_or_error(response)
|
||||||
|
.await?
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.map_err(Error::Reqwest)
|
||||||
|
}
|
||||||
|
|
||||||
/// `GET beacon/genesis`
|
/// `GET beacon/genesis`
|
||||||
///
|
///
|
||||||
/// ## Errors
|
/// ## Errors
|
||||||
@ -210,6 +233,35 @@ impl BeaconNodeHttpClient {
|
|||||||
self.get_opt(path).await
|
self.get_opt(path).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `GET beacon/states/{state_id}/validator_balances?id`
|
||||||
|
///
|
||||||
|
/// Returns `Ok(None)` on a 404 error.
|
||||||
|
pub async fn get_beacon_states_validator_balances(
|
||||||
|
&self,
|
||||||
|
state_id: StateId,
|
||||||
|
ids: Option<&[ValidatorId]>,
|
||||||
|
) -> Result<Option<GenericResponse<Vec<ValidatorBalanceData>>>, Error> {
|
||||||
|
let mut path = self.eth_path()?;
|
||||||
|
|
||||||
|
path.path_segments_mut()
|
||||||
|
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||||
|
.push("beacon")
|
||||||
|
.push("states")
|
||||||
|
.push(&state_id.to_string())
|
||||||
|
.push("validator_balances");
|
||||||
|
|
||||||
|
if let Some(ids) = ids {
|
||||||
|
let id_string = ids
|
||||||
|
.iter()
|
||||||
|
.map(|i| i.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(",");
|
||||||
|
path.query_pairs_mut().append_pair("id", &id_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.get_opt(path).await
|
||||||
|
}
|
||||||
|
|
||||||
/// `GET beacon/states/{state_id}/validators?id,status`
|
/// `GET beacon/states/{state_id}/validators?id,status`
|
||||||
///
|
///
|
||||||
/// Returns `Ok(None)` on a 404 error.
|
/// Returns `Ok(None)` on a 404 error.
|
||||||
@ -713,37 +765,6 @@ impl BeaconNodeHttpClient {
|
|||||||
self.get(path).await
|
self.get(path).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `GET validator/duties/attester/{epoch}?index`
|
|
||||||
///
|
|
||||||
/// ## Note
|
|
||||||
///
|
|
||||||
/// The `index` query parameter accepts a list of validator indices.
|
|
||||||
pub async fn get_validator_duties_attester(
|
|
||||||
&self,
|
|
||||||
epoch: Epoch,
|
|
||||||
index: Option<&[u64]>,
|
|
||||||
) -> Result<GenericResponse<Vec<AttesterData>>, Error> {
|
|
||||||
let mut path = self.eth_path()?;
|
|
||||||
|
|
||||||
path.path_segments_mut()
|
|
||||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
|
||||||
.push("validator")
|
|
||||||
.push("duties")
|
|
||||||
.push("attester")
|
|
||||||
.push(&epoch.to_string());
|
|
||||||
|
|
||||||
if let Some(index) = index {
|
|
||||||
let string = index
|
|
||||||
.iter()
|
|
||||||
.map(|i| i.to_string())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(",");
|
|
||||||
path.query_pairs_mut().append_pair("index", &string);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.get(path).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `GET validator/duties/proposer/{epoch}`
|
/// `GET validator/duties/proposer/{epoch}`
|
||||||
pub async fn get_validator_duties_proposer(
|
pub async fn get_validator_duties_proposer(
|
||||||
&self,
|
&self,
|
||||||
@ -830,10 +851,28 @@ impl BeaconNodeHttpClient {
|
|||||||
self.get_opt(path).await
|
self.get_opt(path).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `POST validator/duties/attester/{epoch}`
|
||||||
|
pub async fn post_validator_duties_attester(
|
||||||
|
&self,
|
||||||
|
epoch: Epoch,
|
||||||
|
indices: &[u64],
|
||||||
|
) -> Result<GenericResponse<Vec<AttesterData>>, Error> {
|
||||||
|
let mut path = self.eth_path()?;
|
||||||
|
|
||||||
|
path.path_segments_mut()
|
||||||
|
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||||
|
.push("validator")
|
||||||
|
.push("duties")
|
||||||
|
.push("attester")
|
||||||
|
.push(&epoch.to_string());
|
||||||
|
|
||||||
|
self.post_with_response(path, &indices).await
|
||||||
|
}
|
||||||
|
|
||||||
/// `POST validator/aggregate_and_proofs`
|
/// `POST validator/aggregate_and_proofs`
|
||||||
pub async fn post_validator_aggregate_and_proof<T: EthSpec>(
|
pub async fn post_validator_aggregate_and_proof<T: EthSpec>(
|
||||||
&self,
|
&self,
|
||||||
aggregate: &SignedAggregateAndProof<T>,
|
aggregates: &[SignedAggregateAndProof<T>],
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut path = self.eth_path()?;
|
let mut path = self.eth_path()?;
|
||||||
|
|
||||||
@ -842,7 +881,14 @@ impl BeaconNodeHttpClient {
|
|||||||
.push("validator")
|
.push("validator")
|
||||||
.push("aggregate_and_proofs");
|
.push("aggregate_and_proofs");
|
||||||
|
|
||||||
self.post(path, aggregate).await?;
|
let response = self
|
||||||
|
.client
|
||||||
|
.post(path)
|
||||||
|
.json(aggregates)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(Error::Reqwest)?;
|
||||||
|
ok_or_indexed_error(response).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -878,3 +924,17 @@ async fn ok_or_error(response: Response) -> Result<Response, Error> {
|
|||||||
Err(Error::StatusCode(status))
|
Err(Error::StatusCode(status))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `Ok(response)` if the response is a `200 OK` response. Otherwise, creates an
|
||||||
|
/// appropriate indexed error message.
|
||||||
|
async fn ok_or_indexed_error(response: Response) -> Result<Response, Error> {
|
||||||
|
let status = response.status();
|
||||||
|
|
||||||
|
if status == StatusCode::OK {
|
||||||
|
Ok(response)
|
||||||
|
} else if let Ok(message) = response.json().await {
|
||||||
|
Err(Error::ServerIndexedMessage(message))
|
||||||
|
} else {
|
||||||
|
Err(Error::StatusCode(status))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -18,6 +18,30 @@ pub struct ErrorMessage {
|
|||||||
pub stacktraces: Vec<String>,
|
pub stacktraces: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An indexed API error serializable to JSON.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct IndexedErrorMessage {
|
||||||
|
pub code: u16,
|
||||||
|
pub message: String,
|
||||||
|
pub failures: Vec<Failure>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A single failure in an index of API errors, serializable to JSON.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct Failure {
|
||||||
|
pub index: u64,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Failure {
|
||||||
|
pub fn new(index: usize, message: String) -> Self {
|
||||||
|
Self {
|
||||||
|
index: index as u64,
|
||||||
|
message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct GenesisData {
|
pub struct GenesisData {
|
||||||
#[serde(with = "serde_utils::quoted_u64")]
|
#[serde(with = "serde_utils::quoted_u64")]
|
||||||
@ -206,6 +230,14 @@ pub struct ValidatorData {
|
|||||||
pub validator: Validator,
|
pub validator: Validator,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct ValidatorBalanceData {
|
||||||
|
#[serde(with = "serde_utils::quoted_u64")]
|
||||||
|
pub index: u64,
|
||||||
|
#[serde(with = "serde_utils::quoted_u64")]
|
||||||
|
pub balance: u64,
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: This does not currently match the spec, but I'm going to try and change the spec using
|
// TODO: This does not currently match the spec, but I'm going to try and change the spec using
|
||||||
// this proposal:
|
// this proposal:
|
||||||
//
|
//
|
||||||
@ -415,10 +447,14 @@ impl<T: FromStr> TryFrom<String> for QueryVec<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize)]
|
#[derive(Clone, Deserialize)]
|
||||||
pub struct ValidatorDutiesQuery {
|
pub struct ValidatorBalancesQuery {
|
||||||
pub index: Option<QueryVec<u64>>,
|
pub id: Option<QueryVec<ValidatorId>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct ValidatorIndexData(#[serde(with = "serde_utils::quoted_u64_vec")] pub Vec<u64>);
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct AttesterData {
|
pub struct AttesterData {
|
||||||
pub pubkey: PublicKeyBytes,
|
pub pubkey: PublicKeyBytes,
|
||||||
@ -438,6 +474,8 @@ pub struct AttesterData {
|
|||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct ProposerData {
|
pub struct ProposerData {
|
||||||
pub pubkey: PublicKeyBytes,
|
pub pubkey: PublicKeyBytes,
|
||||||
|
#[serde(with = "serde_utils::quoted_u64")]
|
||||||
|
pub validator_index: u64,
|
||||||
pub slot: Slot,
|
pub slot: Slot,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use eth2::types::ErrorMessage;
|
use eth2::types::{ErrorMessage, Failure, IndexedErrorMessage};
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
use warp::{http::StatusCode, reject::Reject};
|
use warp::{http::StatusCode, reject::Reject};
|
||||||
|
|
||||||
@ -110,12 +110,37 @@ pub fn invalid_auth(msg: String) -> warp::reject::Rejection {
|
|||||||
warp::reject::custom(InvalidAuthorization(msg))
|
warp::reject::custom(InvalidAuthorization(msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct IndexedBadRequestErrors {
|
||||||
|
pub message: String,
|
||||||
|
pub failures: Vec<Failure>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Reject for IndexedBadRequestErrors {}
|
||||||
|
|
||||||
|
pub fn indexed_bad_request(message: String, failures: Vec<Failure>) -> warp::reject::Rejection {
|
||||||
|
warp::reject::custom(IndexedBadRequestErrors { message, failures })
|
||||||
|
}
|
||||||
|
|
||||||
/// This function receives a `Rejection` and tries to return a custom
|
/// This function receives a `Rejection` and tries to return a custom
|
||||||
/// value, otherwise simply passes the rejection along.
|
/// value, otherwise simply passes the rejection along.
|
||||||
pub async fn handle_rejection(err: warp::Rejection) -> Result<impl warp::Reply, Infallible> {
|
pub async fn handle_rejection(err: warp::Rejection) -> Result<impl warp::Reply, Infallible> {
|
||||||
let code;
|
let code;
|
||||||
let message;
|
let message;
|
||||||
|
|
||||||
|
if let Some(e) = err.find::<crate::reject::IndexedBadRequestErrors>() {
|
||||||
|
message = format!("BAD_REQUEST: {}", e.message);
|
||||||
|
code = StatusCode::BAD_REQUEST;
|
||||||
|
|
||||||
|
let json = warp::reply::json(&IndexedErrorMessage {
|
||||||
|
code: code.as_u16(),
|
||||||
|
message,
|
||||||
|
failures: e.failures.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return Ok(warp::reply::with_status(json, code));
|
||||||
|
}
|
||||||
|
|
||||||
if err.is_not_found() {
|
if err.is_not_found() {
|
||||||
code = StatusCode::NOT_FOUND;
|
code = StatusCode::NOT_FOUND;
|
||||||
message = "NOT_FOUND".to_string();
|
message = "NOT_FOUND".to_string();
|
||||||
|
@ -437,6 +437,8 @@ impl<T: SlotClock + 'static, E: EthSpec> AttestationService<T, E> {
|
|||||||
.ok_or_else(|| format!("No aggregate available for {:?}", attestation_data))?
|
.ok_or_else(|| format!("No aggregate available for {:?}", attestation_data))?
|
||||||
.data;
|
.data;
|
||||||
|
|
||||||
|
let mut signed_aggregate_and_proofs = Vec::new();
|
||||||
|
|
||||||
for duty_and_proof in validator_duties {
|
for duty_and_proof in validator_duties {
|
||||||
let selection_proof = if let Some(proof) = duty_and_proof.selection_proof.as_ref() {
|
let selection_proof = if let Some(proof) = duty_and_proof.selection_proof.as_ref() {
|
||||||
proof
|
proof
|
||||||
@ -462,44 +464,53 @@ impl<T: SlotClock + 'static, E: EthSpec> AttestationService<T, E> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let signed_aggregate_and_proof = if let Some(aggregate) =
|
if let Some(aggregate) = self.validator_store.produce_signed_aggregate_and_proof(
|
||||||
self.validator_store.produce_signed_aggregate_and_proof(
|
|
||||||
pubkey,
|
pubkey,
|
||||||
validator_index,
|
validator_index,
|
||||||
aggregated_attestation.clone(),
|
aggregated_attestation.clone(),
|
||||||
selection_proof.clone(),
|
selection_proof.clone(),
|
||||||
) {
|
) {
|
||||||
aggregate
|
signed_aggregate_and_proofs.push(aggregate);
|
||||||
} else {
|
} else {
|
||||||
crit!(log, "Failed to sign attestation");
|
crit!(log, "Failed to sign attestation");
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let attestation = &signed_aggregate_and_proof.message.aggregate;
|
if !signed_aggregate_and_proofs.is_empty() {
|
||||||
|
|
||||||
match self
|
match self
|
||||||
.beacon_node
|
.beacon_node
|
||||||
.post_validator_aggregate_and_proof(&signed_aggregate_and_proof)
|
.post_validator_aggregate_and_proof(signed_aggregate_and_proofs.as_slice())
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(()) => info!(
|
Ok(()) => {
|
||||||
|
for signed_aggregate_and_proof in signed_aggregate_and_proofs {
|
||||||
|
let attestation = &signed_aggregate_and_proof.message.aggregate;
|
||||||
|
info!(
|
||||||
log,
|
log,
|
||||||
"Successfully published attestation";
|
"Successfully published attestations";
|
||||||
"aggregator" => signed_aggregate_and_proof.message.aggregator_index,
|
"aggregator" => signed_aggregate_and_proof.message.aggregator_index,
|
||||||
"signatures" => attestation.aggregation_bits.num_set_bits(),
|
"signatures" => attestation.aggregation_bits.num_set_bits(),
|
||||||
"head_block" => format!("{:?}", attestation.data.beacon_block_root),
|
"head_block" => format!("{:?}", attestation.data.beacon_block_root),
|
||||||
"committee_index" => attestation.data.index,
|
"committee_index" => attestation.data.index,
|
||||||
"slot" => attestation.data.slot.as_u64(),
|
"slot" => attestation.data.slot.as_u64(),
|
||||||
"type" => "aggregated",
|
"type" => "aggregated",
|
||||||
),
|
);
|
||||||
Err(e) => crit!(
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
for signed_aggregate_and_proof in signed_aggregate_and_proofs {
|
||||||
|
let attestation = &signed_aggregate_and_proof.message.aggregate;
|
||||||
|
crit!(
|
||||||
log,
|
log,
|
||||||
"Failed to publish attestation";
|
"Failed to publish attestation";
|
||||||
"error" => e.to_string(),
|
"error" => e.to_string(),
|
||||||
"committee_index" => attestation.data.index,
|
"committee_index" => attestation.data.index,
|
||||||
"slot" => attestation.data.slot.as_u64(),
|
"slot" => attestation.data.slot.as_u64(),
|
||||||
"type" => "aggregated",
|
"type" => "aggregated",
|
||||||
),
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,6 +106,10 @@ impl DutyAndProof {
|
|||||||
pub fn validator_pubkey(&self) -> &PublicKey {
|
pub fn validator_pubkey(&self) -> &PublicKey {
|
||||||
&self.duty.validator_pubkey
|
&self.duty.validator_pubkey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn validator_index(&self) -> Option<u64> {
|
||||||
|
self.duty.validator_index
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<DutyAndProof> for ValidatorDuty {
|
impl Into<DutyAndProof> for ValidatorDuty {
|
||||||
@ -229,6 +233,14 @@ impl DutiesStore {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_index(&self, pubkey: &PublicKey, epoch: Epoch) -> Option<u64> {
|
||||||
|
self.store
|
||||||
|
.read()
|
||||||
|
.get(pubkey)?
|
||||||
|
.get(&epoch)?
|
||||||
|
.validator_index()
|
||||||
|
}
|
||||||
|
|
||||||
fn is_aggregator(&self, validator_pubkey: &PublicKey, epoch: Epoch) -> Option<bool> {
|
fn is_aggregator(&self, validator_pubkey: &PublicKey, epoch: Epoch) -> Option<bool> {
|
||||||
Some(
|
Some(
|
||||||
self.store
|
self.store
|
||||||
@ -588,13 +600,25 @@ impl<T: SlotClock + 'static, E: EthSpec> DutiesService<T, E> {
|
|||||||
let mut replaced = 0;
|
let mut replaced = 0;
|
||||||
let mut invalid = 0;
|
let mut invalid = 0;
|
||||||
|
|
||||||
|
// Determine which pubkeys we already know the index of by checking the duties store for
|
||||||
|
// the current epoch.
|
||||||
|
let pubkeys: Vec<(PublicKey, Option<u64>)> = self
|
||||||
|
.validator_store
|
||||||
|
.voting_pubkeys()
|
||||||
|
.into_iter()
|
||||||
|
.map(|pubkey| {
|
||||||
|
let index = self.store.get_index(&pubkey, current_epoch);
|
||||||
|
(pubkey, index)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
let mut validator_subscriptions = vec![];
|
let mut validator_subscriptions = vec![];
|
||||||
for pubkey in self.validator_store.voting_pubkeys() {
|
let remote_duties: Vec<ValidatorDuty> = match ValidatorDuty::download(
|
||||||
let remote_duties = match ValidatorDuty::download(
|
|
||||||
&self.beacon_node,
|
&self.beacon_node,
|
||||||
current_epoch,
|
current_epoch,
|
||||||
request_epoch,
|
request_epoch,
|
||||||
pubkey,
|
pubkeys,
|
||||||
|
&log,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@ -605,12 +629,13 @@ impl<T: SlotClock + 'static, E: EthSpec> DutiesService<T, E> {
|
|||||||
"Failed to download validator duties";
|
"Failed to download validator duties";
|
||||||
"error" => e
|
"error" => e
|
||||||
);
|
);
|
||||||
continue;
|
vec![]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
remote_duties.iter().for_each(|remote_duty| {
|
||||||
// Convert the remote duties into our local representation.
|
// Convert the remote duties into our local representation.
|
||||||
let duties: DutyAndProof = remote_duties.clone().into();
|
let duties: DutyAndProof = remote_duty.clone().into();
|
||||||
|
|
||||||
let validator_pubkey = duties.duty.validator_pubkey.clone();
|
let validator_pubkey = duties.duty.validator_pubkey.clone();
|
||||||
|
|
||||||
@ -628,9 +653,9 @@ impl<T: SlotClock + 'static, E: EthSpec> DutiesService<T, E> {
|
|||||||
debug!(
|
debug!(
|
||||||
log,
|
log,
|
||||||
"First duty assignment for validator";
|
"First duty assignment for validator";
|
||||||
"proposal_slots" => format!("{:?}", &remote_duties.block_proposal_slots),
|
"proposal_slots" => format!("{:?}", &remote_duty.block_proposal_slots),
|
||||||
"attestation_slot" => format!("{:?}", &remote_duties.attestation_slot),
|
"attestation_slot" => format!("{:?}", &remote_duty.attestation_slot),
|
||||||
"validator" => format!("{:?}", &remote_duties.validator_pubkey)
|
"validator" => format!("{:?}", &remote_duty.validator_pubkey)
|
||||||
);
|
);
|
||||||
new_validator += 1;
|
new_validator += 1;
|
||||||
}
|
}
|
||||||
@ -645,7 +670,7 @@ impl<T: SlotClock + 'static, E: EthSpec> DutiesService<T, E> {
|
|||||||
self.store.is_aggregator(&validator_pubkey, request_epoch)
|
self.store.is_aggregator(&validator_pubkey, request_epoch)
|
||||||
{
|
{
|
||||||
if outcome.is_subscription_candidate() {
|
if outcome.is_subscription_candidate() {
|
||||||
if let Some(subscription) = remote_duties.subscription(is_aggregator) {
|
if let Some(subscription) = remote_duty.subscription(is_aggregator) {
|
||||||
validator_subscriptions.push(subscription)
|
validator_subscriptions.push(subscription)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -657,7 +682,7 @@ impl<T: SlotClock + 'static, E: EthSpec> DutiesService<T, E> {
|
|||||||
"error" => e
|
"error" => e
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
if invalid > 0 {
|
if invalid > 0 {
|
||||||
error!(
|
error!(
|
||||||
|
@ -3,6 +3,8 @@ use eth2::{
|
|||||||
BeaconNodeHttpClient,
|
BeaconNodeHttpClient,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use slog::{error, Logger};
|
||||||
|
use std::collections::HashMap;
|
||||||
use types::{CommitteeIndex, Epoch, PublicKey, PublicKeyBytes, Slot};
|
use types::{CommitteeIndex, Epoch, PublicKey, PublicKeyBytes, Slot};
|
||||||
|
|
||||||
/// This struct is being used as a shim since we deprecated the `rest_api` in favour of `http_api`.
|
/// This struct is being used as a shim since we deprecated the `rest_api` in favour of `http_api`.
|
||||||
@ -33,10 +35,10 @@ pub struct ValidatorDuty {
|
|||||||
|
|
||||||
impl ValidatorDuty {
|
impl ValidatorDuty {
|
||||||
/// Instantiate `Self` as if there are no known dutes for `validator_pubkey`.
|
/// Instantiate `Self` as if there are no known dutes for `validator_pubkey`.
|
||||||
fn no_duties(validator_pubkey: PublicKey) -> Self {
|
fn no_duties(validator_pubkey: PublicKey, validator_index: Option<u64>) -> Self {
|
||||||
ValidatorDuty {
|
ValidatorDuty {
|
||||||
validator_pubkey,
|
validator_pubkey,
|
||||||
validator_index: None,
|
validator_index,
|
||||||
attestation_slot: None,
|
attestation_slot: None,
|
||||||
attestation_committee_index: None,
|
attestation_committee_index: None,
|
||||||
attestation_committee_position: None,
|
attestation_committee_position: None,
|
||||||
@ -53,59 +55,114 @@ impl ValidatorDuty {
|
|||||||
beacon_node: &BeaconNodeHttpClient,
|
beacon_node: &BeaconNodeHttpClient,
|
||||||
current_epoch: Epoch,
|
current_epoch: Epoch,
|
||||||
request_epoch: Epoch,
|
request_epoch: Epoch,
|
||||||
pubkey: PublicKey,
|
mut pubkeys: Vec<(PublicKey, Option<u64>)>,
|
||||||
) -> Result<ValidatorDuty, String> {
|
log: &Logger,
|
||||||
let pubkey_bytes = PublicKeyBytes::from(&pubkey);
|
) -> Result<Vec<ValidatorDuty>, String> {
|
||||||
|
for (pubkey, index_opt) in &mut pubkeys {
|
||||||
let validator_index = if let Some(index) = beacon_node
|
if index_opt.is_none() {
|
||||||
|
*index_opt = beacon_node
|
||||||
.get_beacon_states_validator_id(
|
.get_beacon_states_validator_id(
|
||||||
StateId::Head,
|
StateId::Head,
|
||||||
&ValidatorId::PublicKey(pubkey_bytes.clone()),
|
&ValidatorId::PublicKey(PublicKeyBytes::from(&*pubkey)),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to get validator index: {}", e))?
|
.map_err(|e| {
|
||||||
.map(|body| body.data.index)
|
error!(
|
||||||
{
|
log,
|
||||||
index
|
"Failed to obtain validator index";
|
||||||
} else {
|
"pubkey" => ?pubkey,
|
||||||
return Ok(Self::no_duties(pubkey));
|
"error" => ?e
|
||||||
};
|
)
|
||||||
|
})
|
||||||
|
// Supress the error since we've already logged an error and we don't want to
|
||||||
|
// stop the rest of the code.
|
||||||
|
.ok()
|
||||||
|
.and_then(|body_opt| body_opt.map(|body| body.data.index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(attester) = beacon_node
|
// Query for all block proposer duties in the current epoch and map the response by index.
|
||||||
.get_validator_duties_attester(request_epoch, Some(&[validator_index]))
|
let proposal_slots_by_index: HashMap<u64, Vec<Slot>> = if current_epoch == request_epoch {
|
||||||
.await
|
|
||||||
.map_err(|e| format!("Failed to get attester duties: {}", e))?
|
|
||||||
.data
|
|
||||||
.first()
|
|
||||||
{
|
|
||||||
let block_proposal_slots = if current_epoch == request_epoch {
|
|
||||||
beacon_node
|
beacon_node
|
||||||
.get_validator_duties_proposer(current_epoch)
|
.get_validator_duties_proposer(current_epoch)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to get proposer indices: {}", e))?
|
.map(|resp| resp.data)
|
||||||
.data
|
// Exit early if there's an error.
|
||||||
|
.map_err(|e| format!("Failed to get proposer indices: {:?}", e))?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|data| data.pubkey == pubkey_bytes)
|
.fold(
|
||||||
.map(|data| data.slot)
|
HashMap::with_capacity(pubkeys.len()),
|
||||||
.collect()
|
|mut map, proposer_data| {
|
||||||
|
map.entry(proposer_data.validator_index)
|
||||||
|
.or_insert_with(Vec::new)
|
||||||
|
.push(proposer_data.slot);
|
||||||
|
map
|
||||||
|
},
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
HashMap::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(ValidatorDuty {
|
let query_indices = pubkeys
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(_, index_opt)| *index_opt)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let attester_data_map = beacon_node
|
||||||
|
.post_validator_duties_attester(request_epoch, query_indices.as_slice())
|
||||||
|
.await
|
||||||
|
.map(|resp| resp.data)
|
||||||
|
// Exit early if there's an error.
|
||||||
|
.map_err(|e| format!("Failed to get attester duties: {:?}", e))?
|
||||||
|
.into_iter()
|
||||||
|
.fold(
|
||||||
|
HashMap::with_capacity(pubkeys.len()),
|
||||||
|
|mut map, attester_data| {
|
||||||
|
map.insert(attester_data.validator_index, attester_data);
|
||||||
|
map
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let duties = pubkeys
|
||||||
|
.into_iter()
|
||||||
|
.map(|(pubkey, index_opt)| {
|
||||||
|
if let Some(index) = index_opt {
|
||||||
|
if let Some(attester_data) = attester_data_map.get(&index) {
|
||||||
|
match attester_data.pubkey.decompress() {
|
||||||
|
Ok(pubkey) => ValidatorDuty {
|
||||||
validator_pubkey: pubkey,
|
validator_pubkey: pubkey,
|
||||||
validator_index: Some(attester.validator_index),
|
validator_index: Some(attester_data.validator_index),
|
||||||
attestation_slot: Some(attester.slot),
|
attestation_slot: Some(attester_data.slot),
|
||||||
attestation_committee_index: Some(attester.committee_index),
|
attestation_committee_index: Some(attester_data.committee_index),
|
||||||
attestation_committee_position: Some(attester.validator_committee_index as usize),
|
attestation_committee_position: Some(
|
||||||
committee_count_at_slot: Some(attester.committees_at_slot),
|
attester_data.validator_committee_index as usize,
|
||||||
committee_length: Some(attester.committee_length),
|
),
|
||||||
block_proposal_slots: Some(block_proposal_slots),
|
committee_count_at_slot: Some(attester_data.committees_at_slot),
|
||||||
})
|
committee_length: Some(attester_data.committee_length),
|
||||||
} else {
|
block_proposal_slots: proposal_slots_by_index
|
||||||
Ok(Self::no_duties(pubkey))
|
.get(&attester_data.validator_index)
|
||||||
|
.cloned(),
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
error!(
|
||||||
|
log,
|
||||||
|
"Could not deserialize validator public key";
|
||||||
|
"error" => format!("{:?}", e),
|
||||||
|
"validator_index" => attester_data.validator_index
|
||||||
|
);
|
||||||
|
Self::no_duties(pubkey, Some(index))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Self::no_duties(pubkey, Some(index))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Self::no_duties(pubkey, None)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(duties)
|
||||||
|
}
|
||||||
|
|
||||||
/// Return `true` if these validator duties are equal, ignoring their `block_proposal_slots`.
|
/// Return `true` if these validator duties are equal, ignoring their `block_proposal_slots`.
|
||||||
pub fn eq_ignoring_proposal_slots(&self, other: &Self) -> bool {
|
pub fn eq_ignoring_proposal_slots(&self, other: &Self) -> bool {
|
||||||
|
Loading…
Reference in New Issue
Block a user