Implement skip_randao_verification
and blinded block rewards API (#3540)
## Issue Addressed https://github.com/ethereum/beacon-APIs/pull/222 ## Proposed Changes Update Lighthouse's randao verification API to match the `beacon-APIs` spec. We implemented the API before spec stabilisation, and it changed slightly in the course of review. Rather than a flag `verify_randao` taking a boolean value, the new API uses a `skip_randao_verification` flag which takes no argument. The new spec also requires the randao reveal to be present and equal to the point-at-infinity when `skip_randao_verification` is set. I've also updated the `POST /lighthouse/analysis/block_rewards` API to take blinded blocks as input, as the execution payload is irrelevant and we may want to assess blocks produced by builders. ## Additional Info This is technically a breaking change, but seeing as I suspect I'm the only one using these parameters/APIs, I think we're OK to include this in a patch release.
This commit is contained in:
parent
ca42ef2e5a
commit
f2ac0738d8
@ -4,7 +4,7 @@ use lru::LruCache;
|
||||
use slog::{debug, warn, Logger};
|
||||
use state_processing::BlockReplayer;
|
||||
use std::sync::Arc;
|
||||
use types::BeaconBlock;
|
||||
use types::BlindedBeaconBlock;
|
||||
use warp_utils::reject::{
|
||||
beacon_chain_error, beacon_state_error, custom_bad_request, custom_server_error,
|
||||
};
|
||||
@ -96,7 +96,7 @@ pub fn get_block_rewards<T: BeaconChainTypes>(
|
||||
|
||||
/// Compute block rewards for blocks passed in as input.
|
||||
pub fn compute_block_rewards<T: BeaconChainTypes>(
|
||||
blocks: Vec<BeaconBlock<T::EthSpec>>,
|
||||
blocks: Vec<BlindedBeaconBlock<T::EthSpec>>,
|
||||
chain: Arc<BeaconChain<T>>,
|
||||
log: Logger,
|
||||
) -> Result<Vec<BlockReward>, warp::Rejection> {
|
||||
|
@ -25,7 +25,9 @@ use beacon_chain::{
|
||||
BeaconChainTypes, ProduceBlockVerification, WhenSlotSkipped,
|
||||
};
|
||||
pub use block_id::BlockId;
|
||||
use eth2::types::{self as api_types, EndpointVersion, ValidatorId, ValidatorStatus};
|
||||
use eth2::types::{
|
||||
self as api_types, EndpointVersion, SkipRandaoVerification, ValidatorId, ValidatorStatus,
|
||||
};
|
||||
use lighthouse_network::{types::SyncState, EnrExt, NetworkGlobals, PeerId, PubsubMessage};
|
||||
use lighthouse_version::version_with_platform;
|
||||
use network::{NetworkMessage, NetworkSenders, ValidatorSubscriptionMessage};
|
||||
@ -35,7 +37,6 @@ use slot_clock::SlotClock;
|
||||
use ssz::Encode;
|
||||
pub use state_id::StateId;
|
||||
use std::borrow::Cow;
|
||||
use std::convert::TryInto;
|
||||
use std::future::Future;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::path::PathBuf;
|
||||
@ -46,7 +47,7 @@ use tokio_stream::{wrappers::BroadcastStream, StreamExt};
|
||||
use types::{
|
||||
Attestation, AttestationData, AttesterSlashing, BeaconStateError, BlindedPayload,
|
||||
CommitteeCache, ConfigAndPreset, Epoch, EthSpec, ForkName, FullPayload,
|
||||
ProposerPreparationData, ProposerSlashing, RelativeEpoch, Signature, SignedAggregateAndProof,
|
||||
ProposerPreparationData, ProposerSlashing, RelativeEpoch, SignedAggregateAndProof,
|
||||
SignedBeaconBlock, SignedBlindedBeaconBlock, SignedContributionAndProof,
|
||||
SignedValidatorRegistrationData, SignedVoluntaryExit, Slot, SyncCommitteeMessage,
|
||||
SyncContributionData,
|
||||
@ -2002,31 +2003,25 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
slot: Slot,
|
||||
query: api_types::ValidatorBlocksQuery,
|
||||
chain: Arc<BeaconChain<T>>| async move {
|
||||
let randao_reveal = query.randao_reveal.as_ref().map_or_else(
|
||||
|| {
|
||||
if query.verify_randao {
|
||||
Err(warp_utils::reject::custom_bad_request(
|
||||
"randao_reveal is mandatory unless verify_randao=false".into(),
|
||||
))
|
||||
} else {
|
||||
Ok(Signature::empty())
|
||||
}
|
||||
},
|
||||
|sig_bytes| {
|
||||
sig_bytes.try_into().map_err(|e| {
|
||||
warp_utils::reject::custom_bad_request(format!(
|
||||
"randao reveal is not a valid BLS signature: {:?}",
|
||||
e
|
||||
))
|
||||
})
|
||||
},
|
||||
)?;
|
||||
let randao_reveal = query.randao_reveal.decompress().map_err(|e| {
|
||||
warp_utils::reject::custom_bad_request(format!(
|
||||
"randao reveal is not a valid BLS signature: {:?}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
let randao_verification = if query.verify_randao {
|
||||
ProduceBlockVerification::VerifyRandao
|
||||
} else {
|
||||
ProduceBlockVerification::NoVerification
|
||||
};
|
||||
let randao_verification =
|
||||
if query.skip_randao_verification == SkipRandaoVerification::Yes {
|
||||
if !randao_reveal.is_infinity() {
|
||||
return Err(warp_utils::reject::custom_bad_request(
|
||||
"randao_reveal must be point-at-infinity if verification is skipped"
|
||||
.into(),
|
||||
));
|
||||
}
|
||||
ProduceBlockVerification::NoVerification
|
||||
} else {
|
||||
ProduceBlockVerification::VerifyRandao
|
||||
};
|
||||
|
||||
let (block, _) = chain
|
||||
.produce_block_with_verification::<FullPayload<T::EthSpec>>(
|
||||
@ -2064,31 +2059,25 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
|slot: Slot,
|
||||
query: api_types::ValidatorBlocksQuery,
|
||||
chain: Arc<BeaconChain<T>>| async move {
|
||||
let randao_reveal = query.randao_reveal.as_ref().map_or_else(
|
||||
|| {
|
||||
if query.verify_randao {
|
||||
Err(warp_utils::reject::custom_bad_request(
|
||||
"randao_reveal is mandatory unless verify_randao=false".into(),
|
||||
))
|
||||
} else {
|
||||
Ok(Signature::empty())
|
||||
}
|
||||
},
|
||||
|sig_bytes| {
|
||||
sig_bytes.try_into().map_err(|e| {
|
||||
warp_utils::reject::custom_bad_request(format!(
|
||||
"randao reveal is not a valid BLS signature: {:?}",
|
||||
e
|
||||
))
|
||||
})
|
||||
},
|
||||
)?;
|
||||
let randao_reveal = query.randao_reveal.decompress().map_err(|e| {
|
||||
warp_utils::reject::custom_bad_request(format!(
|
||||
"randao reveal is not a valid BLS signature: {:?}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
let randao_verification = if query.verify_randao {
|
||||
ProduceBlockVerification::VerifyRandao
|
||||
} else {
|
||||
ProduceBlockVerification::NoVerification
|
||||
};
|
||||
let randao_verification =
|
||||
if query.skip_randao_verification == SkipRandaoVerification::Yes {
|
||||
if !randao_reveal.is_infinity() {
|
||||
return Err(warp_utils::reject::custom_bad_request(
|
||||
"randao_reveal must be point-at-infinity if verification is skipped"
|
||||
.into()
|
||||
));
|
||||
}
|
||||
ProduceBlockVerification::NoVerification
|
||||
} else {
|
||||
ProduceBlockVerification::VerifyRandao
|
||||
};
|
||||
|
||||
let (block, _) = chain
|
||||
.produce_block_with_verification::<BlindedPayload<T::EthSpec>>(
|
||||
@ -2103,6 +2092,7 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
.to_ref()
|
||||
.fork_name(&chain.spec)
|
||||
.map_err(inconsistent_fork_rejection)?;
|
||||
|
||||
// Pose as a V2 endpoint so we return the fork `version`.
|
||||
fork_versioned_response(V2, fork_name, block)
|
||||
.map(|response| warp::reply::json(&response))
|
||||
|
@ -1939,11 +1939,11 @@ impl ApiTester {
|
||||
|
||||
let block = self
|
||||
.client
|
||||
.get_validator_blocks_with_verify_randao::<E, FullPayload<E>>(
|
||||
.get_validator_blocks_modular::<E, FullPayload<E>>(
|
||||
slot,
|
||||
&Signature::infinity().unwrap().into(),
|
||||
None,
|
||||
None,
|
||||
Some(false),
|
||||
SkipRandaoVerification::Yes,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
@ -1993,45 +1993,23 @@ impl ApiTester {
|
||||
sk.sign(message).into()
|
||||
};
|
||||
|
||||
// Check failure with no `verify_randao` passed.
|
||||
// Check failure with no `skip_randao_verification` passed.
|
||||
self.client
|
||||
.get_validator_blocks::<E, FullPayload<E>>(slot, &bad_randao_reveal, None)
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
// Check failure with `verify_randao=true`.
|
||||
// Check failure with `skip_randao_verification` (requires infinity sig).
|
||||
self.client
|
||||
.get_validator_blocks_with_verify_randao::<E, FullPayload<E>>(
|
||||
.get_validator_blocks_modular::<E, FullPayload<E>>(
|
||||
slot,
|
||||
Some(&bad_randao_reveal),
|
||||
&bad_randao_reveal,
|
||||
None,
|
||||
Some(true),
|
||||
SkipRandaoVerification::Yes,
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
// Check failure with no randao reveal provided.
|
||||
self.client
|
||||
.get_validator_blocks_with_verify_randao::<E, FullPayload<E>>(
|
||||
slot, None, None, None,
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
// Check success with `verify_randao=false`.
|
||||
let block = self
|
||||
.client
|
||||
.get_validator_blocks_with_verify_randao::<E, FullPayload<E>>(
|
||||
slot,
|
||||
Some(&bad_randao_reveal),
|
||||
None,
|
||||
Some(false),
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.data;
|
||||
|
||||
assert_eq!(block.slot(), slot);
|
||||
self.chain.slot_clock.set_slot(slot.as_u64() + 1);
|
||||
}
|
||||
|
||||
@ -2106,11 +2084,11 @@ impl ApiTester {
|
||||
|
||||
let block = self
|
||||
.client
|
||||
.get_validator_blinded_blocks_with_verify_randao::<E, Payload>(
|
||||
.get_validator_blinded_blocks_modular::<E, Payload>(
|
||||
slot,
|
||||
&Signature::infinity().unwrap().into(),
|
||||
None,
|
||||
None,
|
||||
Some(false),
|
||||
SkipRandaoVerification::Yes,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
@ -2162,45 +2140,23 @@ impl ApiTester {
|
||||
sk.sign(message).into()
|
||||
};
|
||||
|
||||
// Check failure with no `verify_randao` passed.
|
||||
// Check failure with full randao verification enabled.
|
||||
self.client
|
||||
.get_validator_blinded_blocks::<E, Payload>(slot, &bad_randao_reveal, None)
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
// Check failure with `verify_randao=true`.
|
||||
// Check failure with `skip_randao_verification` (requires infinity sig).
|
||||
self.client
|
||||
.get_validator_blinded_blocks_with_verify_randao::<E, Payload>(
|
||||
.get_validator_blinded_blocks_modular::<E, Payload>(
|
||||
slot,
|
||||
Some(&bad_randao_reveal),
|
||||
&bad_randao_reveal,
|
||||
None,
|
||||
Some(true),
|
||||
SkipRandaoVerification::Yes,
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
// Check failure with no randao reveal provided.
|
||||
self.client
|
||||
.get_validator_blinded_blocks_with_verify_randao::<E, Payload>(
|
||||
slot, None, None, None,
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
// Check success with `verify_randao=false`.
|
||||
let block = self
|
||||
.client
|
||||
.get_validator_blinded_blocks_with_verify_randao::<E, Payload>(
|
||||
slot,
|
||||
Some(&bad_randao_reveal),
|
||||
None,
|
||||
Some(false),
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.data;
|
||||
|
||||
assert_eq!(block.slot(), slot);
|
||||
self.chain.slot_clock.set_slot(slot.as_u64() + 1);
|
||||
}
|
||||
|
||||
|
@ -1233,17 +1233,17 @@ impl BeaconNodeHttpClient {
|
||||
randao_reveal: &SignatureBytes,
|
||||
graffiti: Option<&Graffiti>,
|
||||
) -> Result<ForkVersionedResponse<BeaconBlock<T, Payload>>, Error> {
|
||||
self.get_validator_blocks_with_verify_randao(slot, Some(randao_reveal), graffiti, None)
|
||||
self.get_validator_blocks_modular(slot, randao_reveal, graffiti, SkipRandaoVerification::No)
|
||||
.await
|
||||
}
|
||||
|
||||
/// `GET v2/validator/blocks/{slot}`
|
||||
pub async fn get_validator_blocks_with_verify_randao<T: EthSpec, Payload: ExecPayload<T>>(
|
||||
pub async fn get_validator_blocks_modular<T: EthSpec, Payload: ExecPayload<T>>(
|
||||
&self,
|
||||
slot: Slot,
|
||||
randao_reveal: Option<&SignatureBytes>,
|
||||
randao_reveal: &SignatureBytes,
|
||||
graffiti: Option<&Graffiti>,
|
||||
verify_randao: Option<bool>,
|
||||
skip_randao_verification: SkipRandaoVerification,
|
||||
) -> Result<ForkVersionedResponse<BeaconBlock<T, Payload>>, Error> {
|
||||
let mut path = self.eth_path(V2)?;
|
||||
|
||||
@ -1253,19 +1253,17 @@ impl BeaconNodeHttpClient {
|
||||
.push("blocks")
|
||||
.push(&slot.to_string());
|
||||
|
||||
if let Some(randao_reveal) = randao_reveal {
|
||||
path.query_pairs_mut()
|
||||
.append_pair("randao_reveal", &randao_reveal.to_string());
|
||||
}
|
||||
path.query_pairs_mut()
|
||||
.append_pair("randao_reveal", &randao_reveal.to_string());
|
||||
|
||||
if let Some(graffiti) = graffiti {
|
||||
path.query_pairs_mut()
|
||||
.append_pair("graffiti", &graffiti.to_string());
|
||||
}
|
||||
|
||||
if let Some(verify_randao) = verify_randao {
|
||||
if skip_randao_verification == SkipRandaoVerification::Yes {
|
||||
path.query_pairs_mut()
|
||||
.append_pair("verify_randao", &verify_randao.to_string());
|
||||
.append_pair("skip_randao_verification", "");
|
||||
}
|
||||
|
||||
self.get(path).await
|
||||
@ -1278,25 +1276,22 @@ impl BeaconNodeHttpClient {
|
||||
randao_reveal: &SignatureBytes,
|
||||
graffiti: Option<&Graffiti>,
|
||||
) -> Result<ForkVersionedResponse<BeaconBlock<T, Payload>>, Error> {
|
||||
self.get_validator_blinded_blocks_with_verify_randao(
|
||||
self.get_validator_blinded_blocks_modular(
|
||||
slot,
|
||||
Some(randao_reveal),
|
||||
randao_reveal,
|
||||
graffiti,
|
||||
None,
|
||||
SkipRandaoVerification::No,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// `GET v1/validator/blinded_blocks/{slot}`
|
||||
pub async fn get_validator_blinded_blocks_with_verify_randao<
|
||||
T: EthSpec,
|
||||
Payload: ExecPayload<T>,
|
||||
>(
|
||||
pub async fn get_validator_blinded_blocks_modular<T: EthSpec, Payload: ExecPayload<T>>(
|
||||
&self,
|
||||
slot: Slot,
|
||||
randao_reveal: Option<&SignatureBytes>,
|
||||
randao_reveal: &SignatureBytes,
|
||||
graffiti: Option<&Graffiti>,
|
||||
verify_randao: Option<bool>,
|
||||
skip_randao_verification: SkipRandaoVerification,
|
||||
) -> Result<ForkVersionedResponse<BeaconBlock<T, Payload>>, Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
@ -1306,19 +1301,17 @@ impl BeaconNodeHttpClient {
|
||||
.push("blinded_blocks")
|
||||
.push(&slot.to_string());
|
||||
|
||||
if let Some(randao_reveal) = randao_reveal {
|
||||
path.query_pairs_mut()
|
||||
.append_pair("randao_reveal", &randao_reveal.to_string());
|
||||
}
|
||||
path.query_pairs_mut()
|
||||
.append_pair("randao_reveal", &randao_reveal.to_string());
|
||||
|
||||
if let Some(graffiti) = graffiti {
|
||||
path.query_pairs_mut()
|
||||
.append_pair("graffiti", &graffiti.to_string());
|
||||
}
|
||||
|
||||
if let Some(verify_randao) = verify_randao {
|
||||
if skip_randao_verification == SkipRandaoVerification::Yes {
|
||||
path.query_pairs_mut()
|
||||
.append_pair("verify_randao", &verify_randao.to_string());
|
||||
.append_key_only("skip_randao_verification");
|
||||
}
|
||||
|
||||
self.get(path).await
|
||||
|
@ -658,16 +658,34 @@ pub struct ProposerData {
|
||||
pub slot: Slot,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct ValidatorBlocksQuery {
|
||||
pub randao_reveal: Option<SignatureBytes>,
|
||||
pub randao_reveal: SignatureBytes,
|
||||
pub graffiti: Option<Graffiti>,
|
||||
#[serde(default = "default_verify_randao")]
|
||||
pub verify_randao: bool,
|
||||
pub skip_randao_verification: SkipRandaoVerification,
|
||||
}
|
||||
|
||||
fn default_verify_randao() -> bool {
|
||||
true
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
|
||||
#[serde(try_from = "Option<String>")]
|
||||
pub enum SkipRandaoVerification {
|
||||
Yes,
|
||||
#[default]
|
||||
No,
|
||||
}
|
||||
|
||||
/// Parse a `skip_randao_verification` query parameter.
|
||||
impl TryFrom<Option<String>> for SkipRandaoVerification {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(opt: Option<String>) -> Result<Self, String> {
|
||||
match opt.as_deref() {
|
||||
None => Ok(SkipRandaoVerification::No),
|
||||
Some("") => Ok(SkipRandaoVerification::Yes),
|
||||
Some(s) => Err(format!(
|
||||
"skip_randao_verification does not take a value, got: {s}"
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
|
@ -66,6 +66,8 @@ pub struct BeaconBlock<T: EthSpec, Payload: ExecPayload<T> = FullPayload<T>> {
|
||||
pub body: BeaconBlockBodyMerge<T, Payload>,
|
||||
}
|
||||
|
||||
pub type BlindedBeaconBlock<E> = BeaconBlock<E, BlindedPayload<E>>;
|
||||
|
||||
impl<T: EthSpec, Payload: ExecPayload<T>> SignedRoot for BeaconBlock<T, Payload> {}
|
||||
impl<'a, T: EthSpec, Payload: ExecPayload<T>> SignedRoot for BeaconBlockRef<'a, T, Payload> {}
|
||||
|
||||
|
@ -99,7 +99,7 @@ pub use crate::attestation_duty::AttestationDuty;
|
||||
pub use crate::attester_slashing::AttesterSlashing;
|
||||
pub use crate::beacon_block::{
|
||||
BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BeaconBlockMerge, BeaconBlockRef,
|
||||
BeaconBlockRefMut,
|
||||
BeaconBlockRefMut, BlindedBeaconBlock,
|
||||
};
|
||||
pub use crate::beacon_block_body::{
|
||||
BeaconBlockBody, BeaconBlockBodyAltair, BeaconBlockBodyBase, BeaconBlockBodyMerge,
|
||||
|
@ -80,6 +80,18 @@ where
|
||||
self.point.is_none()
|
||||
}
|
||||
|
||||
/// Initialize self to the point-at-infinity.
|
||||
///
|
||||
/// In general `AggregateSignature::infinity` should be used in favour of this function.
|
||||
pub fn infinity() -> Result<Self, Error> {
|
||||
Self::deserialize(&INFINITY_SIGNATURE)
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` is equal to the point at infinity.
|
||||
pub fn is_infinity(&self) -> bool {
|
||||
self.is_infinity
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying BLS point.
|
||||
pub(crate) fn point(&self) -> Option<&Sig> {
|
||||
self.point.as_ref()
|
||||
|
Loading…
Reference in New Issue
Block a user