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:
Michael Sproul 2022-09-19 07:58:48 +00:00
parent ca42ef2e5a
commit f2ac0738d8
8 changed files with 116 additions and 145 deletions

View File

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

View File

@ -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,30 +2003,24 @@ 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| {
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 {
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
@ -2064,30 +2059,24 @@ 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| {
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 {
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
@ -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))

View File

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

View File

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

View File

@ -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)]

View File

@ -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> {}

View File

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

View File

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