Optionally skip RANDAO verification during block production (#3116)
## Proposed Changes Allow Lighthouse to speculatively create blocks via the `/eth/v1/validators/blocks` endpoint by optionally skipping the RANDAO verification that we introduced in #2740. When `verify_randao=false` is passed as a query parameter the `randao_reveal` is not required to be present, and if present will only be lightly checked (must be a valid BLS sig). If `verify_randao` is omitted it defaults to true and Lighthouse behaves exactly as it did previously, hence this PR is backwards-compatible. I'd like to get this change into `unstable` pretty soon as I've got 3 projects building on top of it: - [`blockdreamer`](https://github.com/michaelsproul/blockdreamer), which mocks block production every slot in order to fingerprint clients - analysis of Lighthouse's block packing _optimality_, which uses `blockdreamer` to extract interesting instances of the attestation packing problem - analysis of Lighthouse's block packing _performance_ (as in speed) on the `tree-states` branch ## Additional Info Having tested `blockdreamer` with Prysm, Nimbus and Teku I noticed that none of them verify the randao signature on `/eth/v1/validator/blocks`. I plan to open a PR to the `beacon-APIs` repo anyway so that this parameter can be standardised in case the other clients add RANDAO verification by default in future.
This commit is contained in:
parent
986044370e
commit
6efd95496b
@ -147,6 +147,12 @@ pub enum ChainSegmentResult<T: EthSpec> {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configure the signature verification of produced blocks.
|
||||||
|
pub enum ProduceBlockVerification {
|
||||||
|
VerifyRandao,
|
||||||
|
NoVerification,
|
||||||
|
}
|
||||||
|
|
||||||
/// The accepted clock drift for nodes gossiping blocks and attestations. See:
|
/// The accepted clock drift for nodes gossiping blocks and attestations. See:
|
||||||
///
|
///
|
||||||
/// https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/p2p-interface.md#configuration
|
/// https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/p2p-interface.md#configuration
|
||||||
@ -2891,6 +2897,22 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
randao_reveal: Signature,
|
randao_reveal: Signature,
|
||||||
slot: Slot,
|
slot: Slot,
|
||||||
validator_graffiti: Option<Graffiti>,
|
validator_graffiti: Option<Graffiti>,
|
||||||
|
) -> Result<BeaconBlockAndState<T::EthSpec>, BlockProductionError> {
|
||||||
|
self.produce_block_with_verification(
|
||||||
|
randao_reveal,
|
||||||
|
slot,
|
||||||
|
validator_graffiti,
|
||||||
|
ProduceBlockVerification::VerifyRandao,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as `produce_block` but allowing for configuration of RANDAO-verification.
|
||||||
|
pub fn produce_block_with_verification(
|
||||||
|
&self,
|
||||||
|
randao_reveal: Signature,
|
||||||
|
slot: Slot,
|
||||||
|
validator_graffiti: Option<Graffiti>,
|
||||||
|
verification: ProduceBlockVerification,
|
||||||
) -> Result<BeaconBlockAndState<T::EthSpec>, BlockProductionError> {
|
) -> Result<BeaconBlockAndState<T::EthSpec>, BlockProductionError> {
|
||||||
metrics::inc_counter(&metrics::BLOCK_PRODUCTION_REQUESTS);
|
metrics::inc_counter(&metrics::BLOCK_PRODUCTION_REQUESTS);
|
||||||
let _complete_timer = metrics::start_timer(&metrics::BLOCK_PRODUCTION_TIMES);
|
let _complete_timer = metrics::start_timer(&metrics::BLOCK_PRODUCTION_TIMES);
|
||||||
@ -2948,6 +2970,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
slot,
|
slot,
|
||||||
randao_reveal,
|
randao_reveal,
|
||||||
validator_graffiti,
|
validator_graffiti,
|
||||||
|
verification,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2970,6 +2993,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
produce_at_slot: Slot,
|
produce_at_slot: Slot,
|
||||||
randao_reveal: Signature,
|
randao_reveal: Signature,
|
||||||
validator_graffiti: Option<Graffiti>,
|
validator_graffiti: Option<Graffiti>,
|
||||||
|
verification: ProduceBlockVerification,
|
||||||
) -> Result<BeaconBlockAndState<T::EthSpec>, BlockProductionError> {
|
) -> Result<BeaconBlockAndState<T::EthSpec>, BlockProductionError> {
|
||||||
let eth1_chain = self
|
let eth1_chain = self
|
||||||
.eth1_chain
|
.eth1_chain
|
||||||
@ -3160,11 +3184,15 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let process_timer = metrics::start_timer(&metrics::BLOCK_PRODUCTION_PROCESS_TIMES);
|
let process_timer = metrics::start_timer(&metrics::BLOCK_PRODUCTION_PROCESS_TIMES);
|
||||||
|
let signature_strategy = match verification {
|
||||||
|
ProduceBlockVerification::VerifyRandao => BlockSignatureStrategy::VerifyRandao,
|
||||||
|
ProduceBlockVerification::NoVerification => BlockSignatureStrategy::NoVerification,
|
||||||
|
};
|
||||||
per_block_processing(
|
per_block_processing(
|
||||||
&mut state,
|
&mut state,
|
||||||
&block,
|
&block,
|
||||||
None,
|
None,
|
||||||
BlockSignatureStrategy::VerifyRandao,
|
signature_strategy,
|
||||||
VerifyBlockRoot::True,
|
VerifyBlockRoot::True,
|
||||||
&self.spec,
|
&self.spec,
|
||||||
)?;
|
)?;
|
||||||
|
@ -41,8 +41,8 @@ mod validator_pubkey_cache;
|
|||||||
|
|
||||||
pub use self::beacon_chain::{
|
pub use self::beacon_chain::{
|
||||||
AttestationProcessingOutcome, BeaconChain, BeaconChainTypes, BeaconStore, ChainSegmentResult,
|
AttestationProcessingOutcome, BeaconChain, BeaconChainTypes, BeaconStore, ChainSegmentResult,
|
||||||
ForkChoiceError, HeadInfo, HeadSafetyStatus, StateSkipConfig, WhenSlotSkipped,
|
ForkChoiceError, HeadInfo, HeadSafetyStatus, ProduceBlockVerification, StateSkipConfig,
|
||||||
INVALID_JUSTIFIED_PAYLOAD_SHUTDOWN_REASON, MAXIMUM_GOSSIP_CLOCK_DISPARITY,
|
WhenSlotSkipped, INVALID_JUSTIFIED_PAYLOAD_SHUTDOWN_REASON, MAXIMUM_GOSSIP_CLOCK_DISPARITY,
|
||||||
};
|
};
|
||||||
pub use self::beacon_snapshot::BeaconSnapshot;
|
pub use self::beacon_snapshot::BeaconSnapshot;
|
||||||
pub use self::chain_config::ChainConfig;
|
pub use self::chain_config::ChainConfig;
|
||||||
|
@ -2,7 +2,7 @@ pub use crate::persisted_beacon_chain::PersistedBeaconChain;
|
|||||||
pub use crate::{
|
pub use crate::{
|
||||||
beacon_chain::{BEACON_CHAIN_DB_KEY, ETH1_CACHE_DB_KEY, FORK_CHOICE_DB_KEY, OP_POOL_DB_KEY},
|
beacon_chain::{BEACON_CHAIN_DB_KEY, ETH1_CACHE_DB_KEY, FORK_CHOICE_DB_KEY, OP_POOL_DB_KEY},
|
||||||
migrate::MigratorConfig,
|
migrate::MigratorConfig,
|
||||||
BeaconChainError,
|
BeaconChainError, ProduceBlockVerification,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
builder::{BeaconChainBuilder, Witness},
|
builder::{BeaconChainBuilder, Witness},
|
||||||
@ -604,7 +604,14 @@ where
|
|||||||
|
|
||||||
let (block, state) = self
|
let (block, state) = self
|
||||||
.chain
|
.chain
|
||||||
.produce_block_on_state(state, None, slot, randao_reveal, Some(graffiti))
|
.produce_block_on_state(
|
||||||
|
state,
|
||||||
|
None,
|
||||||
|
slot,
|
||||||
|
randao_reveal,
|
||||||
|
Some(graffiti),
|
||||||
|
ProduceBlockVerification::VerifyRandao,
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let signed_block = block.sign(
|
let signed_block = block.sign(
|
||||||
@ -658,7 +665,14 @@ where
|
|||||||
|
|
||||||
let (block, state) = self
|
let (block, state) = self
|
||||||
.chain
|
.chain
|
||||||
.produce_block_on_state(state, None, slot, randao_reveal, Some(graffiti))
|
.produce_block_on_state(
|
||||||
|
state,
|
||||||
|
None,
|
||||||
|
slot,
|
||||||
|
randao_reveal,
|
||||||
|
Some(graffiti),
|
||||||
|
ProduceBlockVerification::VerifyRandao,
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let signed_block = block.sign(
|
let signed_block = block.sign(
|
||||||
|
@ -23,7 +23,7 @@ use beacon_chain::{
|
|||||||
observed_operations::ObservationOutcome,
|
observed_operations::ObservationOutcome,
|
||||||
validator_monitor::{get_block_delay_ms, timestamp_now},
|
validator_monitor::{get_block_delay_ms, timestamp_now},
|
||||||
AttestationError as AttnError, BeaconChain, BeaconChainError, BeaconChainTypes,
|
AttestationError as AttnError, BeaconChain, BeaconChainError, BeaconChainTypes,
|
||||||
HeadSafetyStatus, WhenSlotSkipped,
|
HeadSafetyStatus, ProduceBlockVerification, WhenSlotSkipped,
|
||||||
};
|
};
|
||||||
use block_id::BlockId;
|
use block_id::BlockId;
|
||||||
use eth2::types::{self as api_types, EndpointVersion, ValidatorId};
|
use eth2::types::{self as api_types, EndpointVersion, ValidatorId};
|
||||||
@ -46,7 +46,7 @@ use tokio::sync::mpsc::UnboundedSender;
|
|||||||
use tokio_stream::{wrappers::BroadcastStream, StreamExt};
|
use tokio_stream::{wrappers::BroadcastStream, StreamExt};
|
||||||
use types::{
|
use types::{
|
||||||
Attestation, AttesterSlashing, BeaconStateError, CommitteeCache, ConfigAndPreset, Epoch,
|
Attestation, AttesterSlashing, BeaconStateError, CommitteeCache, ConfigAndPreset, Epoch,
|
||||||
EthSpec, ForkName, ProposerPreparationData, ProposerSlashing, RelativeEpoch,
|
EthSpec, ForkName, ProposerPreparationData, ProposerSlashing, RelativeEpoch, Signature,
|
||||||
SignedAggregateAndProof, SignedBeaconBlock, SignedContributionAndProof, SignedVoluntaryExit,
|
SignedAggregateAndProof, SignedBeaconBlock, SignedContributionAndProof, SignedVoluntaryExit,
|
||||||
Slot, SyncCommitteeMessage, SyncContributionData,
|
Slot, SyncCommitteeMessage, SyncContributionData,
|
||||||
};
|
};
|
||||||
@ -1872,15 +1872,39 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
query: api_types::ValidatorBlocksQuery,
|
query: api_types::ValidatorBlocksQuery,
|
||||||
chain: Arc<BeaconChain<T>>| {
|
chain: Arc<BeaconChain<T>>| {
|
||||||
blocking_json_task(move || {
|
blocking_json_task(move || {
|
||||||
let randao_reveal = (&query.randao_reveal).try_into().map_err(|e| {
|
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!(
|
warp_utils::reject::custom_bad_request(format!(
|
||||||
"randao reveal is not valid BLS signature: {:?}",
|
"randao reveal is not a valid BLS signature: {:?}",
|
||||||
e
|
e
|
||||||
))
|
))
|
||||||
})?;
|
})
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let randao_verification = if query.verify_randao {
|
||||||
|
ProduceBlockVerification::VerifyRandao
|
||||||
|
} else {
|
||||||
|
ProduceBlockVerification::NoVerification
|
||||||
|
};
|
||||||
|
|
||||||
let (block, _) = chain
|
let (block, _) = chain
|
||||||
.produce_block(randao_reveal, slot, query.graffiti.map(Into::into))
|
.produce_block_with_verification(
|
||||||
|
randao_reveal,
|
||||||
|
slot,
|
||||||
|
query.graffiti.map(Into::into),
|
||||||
|
randao_verification,
|
||||||
|
)
|
||||||
.map_err(warp_utils::reject::block_production_error)?;
|
.map_err(warp_utils::reject::block_production_error)?;
|
||||||
let fork_name = block
|
let fork_name = block
|
||||||
.to_ref()
|
.to_ref()
|
||||||
|
@ -1919,6 +1919,104 @@ impl ApiTester {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn test_block_production_no_verify_randao(self) -> Self {
|
||||||
|
for _ in 0..E::slots_per_epoch() {
|
||||||
|
let slot = self.chain.slot().unwrap();
|
||||||
|
|
||||||
|
let block = self
|
||||||
|
.client
|
||||||
|
.get_validator_blocks_with_verify_randao::<E>(slot, None, None, Some(false))
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
assert_eq!(block.slot(), slot);
|
||||||
|
self.chain.slot_clock.set_slot(slot.as_u64() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn test_block_production_verify_randao_invalid(self) -> Self {
|
||||||
|
let fork = self.chain.head_info().unwrap().fork;
|
||||||
|
let genesis_validators_root = self.chain.genesis_validators_root;
|
||||||
|
|
||||||
|
for _ in 0..E::slots_per_epoch() {
|
||||||
|
let slot = self.chain.slot().unwrap();
|
||||||
|
let epoch = self.chain.epoch().unwrap();
|
||||||
|
|
||||||
|
let proposer_pubkey_bytes = self
|
||||||
|
.client
|
||||||
|
.get_validator_duties_proposer(epoch)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.data
|
||||||
|
.into_iter()
|
||||||
|
.find(|duty| duty.slot == slot)
|
||||||
|
.map(|duty| duty.pubkey)
|
||||||
|
.unwrap();
|
||||||
|
let proposer_pubkey = (&proposer_pubkey_bytes).try_into().unwrap();
|
||||||
|
|
||||||
|
let sk = self
|
||||||
|
.validator_keypairs
|
||||||
|
.iter()
|
||||||
|
.find(|kp| kp.pk == proposer_pubkey)
|
||||||
|
.map(|kp| kp.sk.clone())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let bad_randao_reveal = {
|
||||||
|
let domain = self.chain.spec.get_domain(
|
||||||
|
epoch,
|
||||||
|
Domain::Randao,
|
||||||
|
&fork,
|
||||||
|
genesis_validators_root,
|
||||||
|
);
|
||||||
|
let message = (epoch + 1).signing_root(domain);
|
||||||
|
sk.sign(message).into()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check failure with no `verify_randao` passed.
|
||||||
|
self.client
|
||||||
|
.get_validator_blocks::<E>(slot, &bad_randao_reveal, None)
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
// Check failure with `verify_randao=true`.
|
||||||
|
self.client
|
||||||
|
.get_validator_blocks_with_verify_randao::<E>(
|
||||||
|
slot,
|
||||||
|
Some(&bad_randao_reveal),
|
||||||
|
None,
|
||||||
|
Some(true),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
// Check failure with no randao reveal provided.
|
||||||
|
self.client
|
||||||
|
.get_validator_blocks_with_verify_randao::<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>(
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn test_get_validator_attestation_data(self) -> Self {
|
pub async fn test_get_validator_attestation_data(self) -> Self {
|
||||||
let mut state = self.chain.head_beacon_state().unwrap();
|
let mut state = self.chain.head_beacon_state().unwrap();
|
||||||
let slot = state.slot();
|
let slot = state.slot();
|
||||||
@ -2770,6 +2868,22 @@ async fn block_production_with_skip_slots() {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn block_production_no_verify_randao() {
|
||||||
|
ApiTester::new()
|
||||||
|
.await
|
||||||
|
.test_block_production_no_verify_randao()
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn block_production_verify_randao_invalid() {
|
||||||
|
ApiTester::new()
|
||||||
|
.await
|
||||||
|
.test_block_production_verify_randao_invalid()
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn get_validator_attestation_data() {
|
async fn get_validator_attestation_data() {
|
||||||
ApiTester::new()
|
ApiTester::new()
|
||||||
|
@ -1155,6 +1155,18 @@ impl BeaconNodeHttpClient {
|
|||||||
slot: Slot,
|
slot: Slot,
|
||||||
randao_reveal: &SignatureBytes,
|
randao_reveal: &SignatureBytes,
|
||||||
graffiti: Option<&Graffiti>,
|
graffiti: Option<&Graffiti>,
|
||||||
|
) -> Result<ForkVersionedResponse<BeaconBlock<T>>, Error> {
|
||||||
|
self.get_validator_blocks_with_verify_randao(slot, Some(randao_reveal), graffiti, None)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `GET v2/validator/blocks/{slot}`
|
||||||
|
pub async fn get_validator_blocks_with_verify_randao<T: EthSpec>(
|
||||||
|
&self,
|
||||||
|
slot: Slot,
|
||||||
|
randao_reveal: Option<&SignatureBytes>,
|
||||||
|
graffiti: Option<&Graffiti>,
|
||||||
|
verify_randao: Option<bool>,
|
||||||
) -> Result<ForkVersionedResponse<BeaconBlock<T>>, Error> {
|
) -> Result<ForkVersionedResponse<BeaconBlock<T>>, Error> {
|
||||||
let mut path = self.eth_path(V2)?;
|
let mut path = self.eth_path(V2)?;
|
||||||
|
|
||||||
@ -1164,14 +1176,21 @@ impl BeaconNodeHttpClient {
|
|||||||
.push("blocks")
|
.push("blocks")
|
||||||
.push(&slot.to_string());
|
.push(&slot.to_string());
|
||||||
|
|
||||||
|
if let Some(randao_reveal) = randao_reveal {
|
||||||
path.query_pairs_mut()
|
path.query_pairs_mut()
|
||||||
.append_pair("randao_reveal", &randao_reveal.to_string());
|
.append_pair("randao_reveal", &randao_reveal.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(graffiti) = graffiti {
|
if let Some(graffiti) = graffiti {
|
||||||
path.query_pairs_mut()
|
path.query_pairs_mut()
|
||||||
.append_pair("graffiti", &graffiti.to_string());
|
.append_pair("graffiti", &graffiti.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(verify_randao) = verify_randao {
|
||||||
|
path.query_pairs_mut()
|
||||||
|
.append_pair("verify_randao", &verify_randao.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
self.get(path).await
|
self.get(path).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -627,8 +627,14 @@ pub struct ProposerData {
|
|||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct ValidatorBlocksQuery {
|
pub struct ValidatorBlocksQuery {
|
||||||
pub randao_reveal: SignatureBytes,
|
pub randao_reveal: Option<SignatureBytes>,
|
||||||
pub graffiti: Option<Graffiti>,
|
pub graffiti: Option<Graffiti>,
|
||||||
|
#[serde(default = "default_verify_randao")]
|
||||||
|
pub verify_randao: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_verify_randao() -> bool {
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
Loading…
Reference in New Issue
Block a user