mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-18 12:22:51 +00:00
Merge branch 'unstable' into vc-fallback
This commit is contained in:
@@ -10,7 +10,7 @@ use crate::{
|
||||
};
|
||||
use bls::SignatureBytes;
|
||||
use environment::RuntimeContext;
|
||||
use eth2::types::{BlockContents, SignedBlockContents};
|
||||
use eth2::types::{FullBlockContents, PublishBlockRequest};
|
||||
use eth2::{BeaconNodeHttpClient, StatusCode};
|
||||
use slog::{crit, debug, error, info, trace, warn, Logger};
|
||||
use slot_clock::SlotClock;
|
||||
@@ -21,7 +21,7 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc;
|
||||
use types::{
|
||||
AbstractExecPayload, BlindedPayload, BlockType, EthSpec, FullPayload, Graffiti, PublicKeyBytes,
|
||||
BlindedBeaconBlock, BlockType, EthSpec, Graffiti, PublicKeyBytes, SignedBlindedBeaconBlock,
|
||||
Slot,
|
||||
};
|
||||
|
||||
@@ -304,10 +304,7 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockService<T, E> {
|
||||
self.inner.context.executor.spawn(
|
||||
async move {
|
||||
if builder_proposals {
|
||||
let result = service
|
||||
.clone()
|
||||
.publish_block::<BlindedPayload<E>>(slot, validator_pubkey)
|
||||
.await;
|
||||
let result = service.publish_block(slot, validator_pubkey, true).await;
|
||||
match result {
|
||||
Err(BlockError::Recoverable(e)) => {
|
||||
error!(
|
||||
@@ -317,9 +314,8 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockService<T, E> {
|
||||
"block_slot" => ?slot,
|
||||
"info" => "blinded proposal failed, attempting full block"
|
||||
);
|
||||
if let Err(e) = service
|
||||
.publish_block::<FullPayload<E>>(slot, validator_pubkey)
|
||||
.await
|
||||
if let Err(e) =
|
||||
service.publish_block(slot, validator_pubkey, false).await
|
||||
{
|
||||
// Log a `crit` since a full block
|
||||
// (non-builder) proposal failed.
|
||||
@@ -346,9 +342,8 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockService<T, E> {
|
||||
}
|
||||
Ok(_) => {}
|
||||
};
|
||||
} else if let Err(e) = service
|
||||
.publish_block::<FullPayload<E>>(slot, validator_pubkey)
|
||||
.await
|
||||
} else if let Err(e) =
|
||||
service.publish_block(slot, validator_pubkey, false).await
|
||||
{
|
||||
// Log a `crit` since a full block (non-builder)
|
||||
// proposal failed.
|
||||
@@ -369,10 +364,11 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockService<T, E> {
|
||||
}
|
||||
|
||||
/// Produce a block at the given slot for validator_pubkey
|
||||
async fn publish_block<Payload: AbstractExecPayload<E>>(
|
||||
self,
|
||||
async fn publish_block(
|
||||
&self,
|
||||
slot: Slot,
|
||||
validator_pubkey: PublicKeyBytes,
|
||||
builder_proposal: bool,
|
||||
) -> Result<(), BlockError> {
|
||||
let log = self.context.log();
|
||||
let _timer =
|
||||
@@ -435,7 +431,7 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockService<T, E> {
|
||||
//
|
||||
// Try the proposer nodes last, since it's likely that they don't have a
|
||||
// great view of attestations on the network.
|
||||
let block_contents = proposer_fallback
|
||||
let unsigned_block = proposer_fallback
|
||||
.request_proposers_last(move |beacon_node| {
|
||||
Self::get_validator_block(
|
||||
beacon_node,
|
||||
@@ -443,19 +439,31 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockService<T, E> {
|
||||
randao_reveal_ref,
|
||||
graffiti,
|
||||
proposer_index,
|
||||
builder_proposal,
|
||||
log,
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
|
||||
let (block, maybe_blob_sidecars) = block_contents.deconstruct();
|
||||
let signing_timer = metrics::start_timer(&metrics::BLOCK_SIGNING_TIMES);
|
||||
|
||||
let signed_block = match self_ref
|
||||
.validator_store
|
||||
.sign_block::<Payload>(*validator_pubkey_ref, block, current_slot)
|
||||
.await
|
||||
{
|
||||
let res = match unsigned_block {
|
||||
UnsignedBlock::Full(block_contents) => {
|
||||
let (block, maybe_blobs) = block_contents.deconstruct();
|
||||
self_ref
|
||||
.validator_store
|
||||
.sign_block(*validator_pubkey_ref, block, current_slot)
|
||||
.await
|
||||
.map(|b| SignedBlock::Full(PublishBlockRequest::new(b, maybe_blobs)))
|
||||
}
|
||||
UnsignedBlock::Blinded(block) => self_ref
|
||||
.validator_store
|
||||
.sign_block(*validator_pubkey_ref, block, current_slot)
|
||||
.await
|
||||
.map(SignedBlock::Blinded),
|
||||
};
|
||||
|
||||
let signed_block = match res {
|
||||
Ok(block) => block,
|
||||
Err(ValidatorStoreError::UnknownPubkey(pubkey)) => {
|
||||
// A pubkey can be missing when a validator was recently removed
|
||||
@@ -477,36 +485,6 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockService<T, E> {
|
||||
}
|
||||
};
|
||||
|
||||
let maybe_signed_blobs = match maybe_blob_sidecars {
|
||||
Some(blob_sidecars) => {
|
||||
match self_ref
|
||||
.validator_store
|
||||
.sign_blobs::<Payload>(*validator_pubkey_ref, blob_sidecars)
|
||||
.await
|
||||
{
|
||||
Ok(signed_blobs) => Some(signed_blobs),
|
||||
Err(ValidatorStoreError::UnknownPubkey(pubkey)) => {
|
||||
// A pubkey can be missing when a validator was recently removed
|
||||
// via the API.
|
||||
warn!(
|
||||
log,
|
||||
"Missing pubkey for blobs";
|
||||
"info" => "a validator may have recently been removed from this VC",
|
||||
"pubkey" => ?pubkey,
|
||||
"slot" => ?slot
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(BlockError::Recoverable(format!(
|
||||
"Unable to sign blobs: {:?}",
|
||||
e
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
let signing_time_ms =
|
||||
Duration::from_secs_f64(signing_timer.map_or(0.0, |t| t.stop_and_record())).as_millis();
|
||||
|
||||
@@ -517,8 +495,6 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockService<T, E> {
|
||||
"signing_time_ms" => signing_time_ms,
|
||||
);
|
||||
|
||||
let signed_block_contents = SignedBlockContents::from((signed_block, maybe_signed_blobs));
|
||||
|
||||
// Publish block with first available beacon node.
|
||||
//
|
||||
// Try the proposer nodes first, since we've likely gone to efforts to
|
||||
@@ -526,7 +502,7 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockService<T, E> {
|
||||
// publish a block.
|
||||
proposer_fallback
|
||||
.request_proposers_first(|beacon_node| async {
|
||||
self.publish_signed_block_contents::<Payload>(&signed_block_contents, beacon_node)
|
||||
self.publish_signed_block_contents(&signed_block, beacon_node)
|
||||
.await
|
||||
})
|
||||
.await?;
|
||||
@@ -534,42 +510,41 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockService<T, E> {
|
||||
info!(
|
||||
log,
|
||||
"Successfully published block";
|
||||
"block_type" => ?Payload::block_type(),
|
||||
"deposits" => signed_block_contents.signed_block().message().body().deposits().len(),
|
||||
"attestations" => signed_block_contents.signed_block().message().body().attestations().len(),
|
||||
"block_type" => ?signed_block.block_type(),
|
||||
"deposits" => signed_block.num_deposits(),
|
||||
"attestations" => signed_block.num_attestations(),
|
||||
"graffiti" => ?graffiti.map(|g| g.as_utf8_lossy()),
|
||||
"slot" => signed_block_contents.signed_block().slot().as_u64(),
|
||||
"slot" => signed_block.slot().as_u64(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn publish_signed_block_contents<Payload: AbstractExecPayload<E>>(
|
||||
async fn publish_signed_block_contents(
|
||||
&self,
|
||||
signed_block_contents: &SignedBlockContents<E, Payload>,
|
||||
signed_block: &SignedBlock<E>,
|
||||
beacon_node: BeaconNodeHttpClient,
|
||||
) -> Result<(), BlockError> {
|
||||
let log = self.context.log();
|
||||
let slot = signed_block_contents.signed_block().slot();
|
||||
match Payload::block_type() {
|
||||
BlockType::Full => {
|
||||
let slot = signed_block.slot();
|
||||
match signed_block {
|
||||
SignedBlock::Full(signed_block) => {
|
||||
let _post_timer = metrics::start_timer_vec(
|
||||
&metrics::BLOCK_SERVICE_TIMES,
|
||||
&[metrics::BEACON_BLOCK_HTTP_POST],
|
||||
);
|
||||
beacon_node
|
||||
.clone()
|
||||
.post_beacon_blocks(signed_block_contents)
|
||||
.post_beacon_blocks(signed_block)
|
||||
.await
|
||||
.or_else(|e| handle_block_post_error(e, slot, log))?
|
||||
}
|
||||
BlockType::Blinded => {
|
||||
SignedBlock::Blinded(signed_block) => {
|
||||
let _post_timer = metrics::start_timer_vec(
|
||||
&metrics::BLOCK_SERVICE_TIMES,
|
||||
&[metrics::BLINDED_BEACON_BLOCK_HTTP_POST],
|
||||
);
|
||||
beacon_node
|
||||
.post_beacon_blinded_blocks(signed_block_contents)
|
||||
.post_beacon_blinded_blocks(signed_block)
|
||||
.await
|
||||
.or_else(|e| handle_block_post_error(e, slot, log))?
|
||||
}
|
||||
@@ -577,22 +552,23 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockService<T, E> {
|
||||
Ok::<_, BlockError>(())
|
||||
}
|
||||
|
||||
async fn get_validator_block<Payload: AbstractExecPayload<E>>(
|
||||
async fn get_validator_block(
|
||||
beacon_node: BeaconNodeHttpClient,
|
||||
slot: Slot,
|
||||
randao_reveal_ref: &SignatureBytes,
|
||||
graffiti: Option<Graffiti>,
|
||||
proposer_index: Option<u64>,
|
||||
builder_proposal: bool,
|
||||
log: &Logger,
|
||||
) -> Result<BlockContents<E, Payload>, BlockError> {
|
||||
let block_contents: BlockContents<E, Payload> = match Payload::block_type() {
|
||||
BlockType::Full => {
|
||||
let _get_timer = metrics::start_timer_vec(
|
||||
&metrics::BLOCK_SERVICE_TIMES,
|
||||
&[metrics::BEACON_BLOCK_HTTP_GET],
|
||||
);
|
||||
) -> Result<UnsignedBlock<E>, BlockError> {
|
||||
let unsigned_block = if !builder_proposal {
|
||||
let _get_timer = metrics::start_timer_vec(
|
||||
&metrics::BLOCK_SERVICE_TIMES,
|
||||
&[metrics::BEACON_BLOCK_HTTP_GET],
|
||||
);
|
||||
UnsignedBlock::Full(
|
||||
beacon_node
|
||||
.get_validator_blocks::<E, Payload>(slot, randao_reveal_ref, graffiti.as_ref())
|
||||
.get_validator_blocks::<E>(slot, randao_reveal_ref, graffiti.as_ref())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
BlockError::Recoverable(format!(
|
||||
@@ -600,19 +576,16 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockService<T, E> {
|
||||
e
|
||||
))
|
||||
})?
|
||||
.data
|
||||
}
|
||||
BlockType::Blinded => {
|
||||
let _get_timer = metrics::start_timer_vec(
|
||||
&metrics::BLOCK_SERVICE_TIMES,
|
||||
&[metrics::BLINDED_BEACON_BLOCK_HTTP_GET],
|
||||
);
|
||||
.data,
|
||||
)
|
||||
} else {
|
||||
let _get_timer = metrics::start_timer_vec(
|
||||
&metrics::BLOCK_SERVICE_TIMES,
|
||||
&[metrics::BLINDED_BEACON_BLOCK_HTTP_GET],
|
||||
);
|
||||
UnsignedBlock::Blinded(
|
||||
beacon_node
|
||||
.get_validator_blinded_blocks::<E, Payload>(
|
||||
slot,
|
||||
randao_reveal_ref,
|
||||
graffiti.as_ref(),
|
||||
)
|
||||
.get_validator_blinded_blocks::<E>(slot, randao_reveal_ref, graffiti.as_ref())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
BlockError::Recoverable(format!(
|
||||
@@ -620,8 +593,8 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockService<T, E> {
|
||||
e
|
||||
))
|
||||
})?
|
||||
.data
|
||||
}
|
||||
.data,
|
||||
)
|
||||
};
|
||||
|
||||
info!(
|
||||
@@ -629,13 +602,59 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockService<T, E> {
|
||||
"Received unsigned block";
|
||||
"slot" => slot.as_u64(),
|
||||
);
|
||||
if proposer_index != Some(block_contents.block().proposer_index()) {
|
||||
if proposer_index != Some(unsigned_block.proposer_index()) {
|
||||
return Err(BlockError::Recoverable(
|
||||
"Proposer index does not match block proposer. Beacon chain re-orged".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok::<_, BlockError>(block_contents)
|
||||
Ok::<_, BlockError>(unsigned_block)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum UnsignedBlock<E: EthSpec> {
|
||||
Full(FullBlockContents<E>),
|
||||
Blinded(BlindedBeaconBlock<E>),
|
||||
}
|
||||
|
||||
impl<E: EthSpec> UnsignedBlock<E> {
|
||||
pub fn proposer_index(&self) -> u64 {
|
||||
match self {
|
||||
UnsignedBlock::Full(block) => block.block().proposer_index(),
|
||||
UnsignedBlock::Blinded(block) => block.proposer_index(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SignedBlock<E: EthSpec> {
|
||||
Full(PublishBlockRequest<E>),
|
||||
Blinded(SignedBlindedBeaconBlock<E>),
|
||||
}
|
||||
|
||||
impl<E: EthSpec> SignedBlock<E> {
|
||||
pub fn block_type(&self) -> BlockType {
|
||||
match self {
|
||||
SignedBlock::Full(_) => BlockType::Full,
|
||||
SignedBlock::Blinded(_) => BlockType::Blinded,
|
||||
}
|
||||
}
|
||||
pub fn slot(&self) -> Slot {
|
||||
match self {
|
||||
SignedBlock::Full(block) => block.signed_block().message().slot(),
|
||||
SignedBlock::Blinded(block) => block.message().slot(),
|
||||
}
|
||||
}
|
||||
pub fn num_deposits(&self) -> usize {
|
||||
match self {
|
||||
SignedBlock::Full(block) => block.signed_block().message().body().deposits().len(),
|
||||
SignedBlock::Blinded(block) => block.message().body().deposits().len(),
|
||||
}
|
||||
}
|
||||
pub fn num_attestations(&self) -> usize {
|
||||
match self {
|
||||
SignedBlock::Full(block) => block.signed_block().message().body().attestations().len(),
|
||||
SignedBlock::Blinded(block) => block.message().body().attestations().len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -162,8 +162,6 @@ async fn beacon_node_liveness<'a, T: 'static + SlotClock, E: EthSpec>(
|
||||
current_epoch: Epoch,
|
||||
validator_indices: Vec<u64>,
|
||||
) -> LivenessResponses {
|
||||
let validator_indices = validator_indices.as_slice();
|
||||
|
||||
let previous_epoch = current_epoch.saturating_sub(1_u64);
|
||||
|
||||
let previous_epoch_responses = if previous_epoch == current_epoch {
|
||||
@@ -176,12 +174,25 @@ async fn beacon_node_liveness<'a, T: 'static + SlotClock, E: EthSpec>(
|
||||
} else {
|
||||
// Request the previous epoch liveness state from the beacon node.
|
||||
beacon_nodes
|
||||
.first_success(|beacon_node| async move {
|
||||
beacon_node
|
||||
.post_lighthouse_liveness(validator_indices, previous_epoch)
|
||||
.first_success(|beacon_node| async {
|
||||
let owned_beacon_node = beacon_node.clone();
|
||||
drop(beacon_node);
|
||||
|
||||
owned_beacon_node
|
||||
.post_validator_liveness_epoch(previous_epoch, &validator_indices)
|
||||
.await
|
||||
.map_err(|e| format!("Failed query for validator liveness: {:?}", e))
|
||||
.map(|result| result.data)
|
||||
.map(|result| {
|
||||
result
|
||||
.data
|
||||
.into_iter()
|
||||
.map(|response| LivenessResponseData {
|
||||
index: response.index,
|
||||
epoch: previous_epoch,
|
||||
is_live: response.is_live,
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap_or_else(|e| {
|
||||
@@ -199,12 +210,25 @@ async fn beacon_node_liveness<'a, T: 'static + SlotClock, E: EthSpec>(
|
||||
|
||||
// Request the current epoch liveness state from the beacon node.
|
||||
let current_epoch_responses = beacon_nodes
|
||||
.first_success(|beacon_node| async move {
|
||||
beacon_node
|
||||
.post_lighthouse_liveness(validator_indices, current_epoch)
|
||||
.first_success(|beacon_node| async {
|
||||
let owned_beacon_node = beacon_node.clone();
|
||||
drop(beacon_node);
|
||||
|
||||
owned_beacon_node
|
||||
.post_validator_liveness_epoch(current_epoch, &validator_indices)
|
||||
.await
|
||||
.map_err(|e| format!("Failed query for validator liveness: {:?}", e))
|
||||
.map(|result| result.data)
|
||||
.map(|result| {
|
||||
result
|
||||
.data
|
||||
.into_iter()
|
||||
.map(|response| LivenessResponseData {
|
||||
index: response.index,
|
||||
epoch: current_epoch,
|
||||
is_live: response.is_live,
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap_or_else(|e| {
|
||||
|
||||
80
validator_client/src/http_api/graffiti.rs
Normal file
80
validator_client/src/http_api/graffiti.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
use crate::validator_store::ValidatorStore;
|
||||
use bls::PublicKey;
|
||||
use slot_clock::SlotClock;
|
||||
use std::sync::Arc;
|
||||
use types::{graffiti::GraffitiString, EthSpec, Graffiti};
|
||||
|
||||
pub fn get_graffiti<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
validator_pubkey: PublicKey,
|
||||
validator_store: Arc<ValidatorStore<T, E>>,
|
||||
graffiti_flag: Option<Graffiti>,
|
||||
) -> Result<Graffiti, warp::Rejection> {
|
||||
let initialized_validators_rw_lock = validator_store.initialized_validators();
|
||||
let initialized_validators = initialized_validators_rw_lock.read();
|
||||
match initialized_validators.validator(&validator_pubkey.compress()) {
|
||||
None => Err(warp_utils::reject::custom_not_found(
|
||||
"The key was not found on the server".to_string(),
|
||||
)),
|
||||
Some(_) => {
|
||||
let Some(graffiti) = initialized_validators.graffiti(&validator_pubkey.into()) else {
|
||||
return graffiti_flag.ok_or(warp_utils::reject::custom_server_error(
|
||||
"No graffiti found, unable to return the process-wide default".to_string(),
|
||||
));
|
||||
};
|
||||
Ok(graffiti)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_graffiti<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
validator_pubkey: PublicKey,
|
||||
graffiti: GraffitiString,
|
||||
validator_store: Arc<ValidatorStore<T, E>>,
|
||||
) -> Result<(), warp::Rejection> {
|
||||
let initialized_validators_rw_lock = validator_store.initialized_validators();
|
||||
let mut initialized_validators = initialized_validators_rw_lock.write();
|
||||
match initialized_validators.validator(&validator_pubkey.compress()) {
|
||||
None => Err(warp_utils::reject::custom_not_found(
|
||||
"The key was not found on the server, nothing to update".to_string(),
|
||||
)),
|
||||
Some(initialized_validator) => {
|
||||
if initialized_validator.get_graffiti() == Some(graffiti.clone().into()) {
|
||||
Ok(())
|
||||
} else {
|
||||
initialized_validators
|
||||
.set_graffiti(&validator_pubkey, graffiti)
|
||||
.map_err(|_| {
|
||||
warp_utils::reject::custom_server_error(
|
||||
"A graffiti was found, but failed to be updated.".to_string(),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_graffiti<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
validator_pubkey: PublicKey,
|
||||
validator_store: Arc<ValidatorStore<T, E>>,
|
||||
) -> Result<(), warp::Rejection> {
|
||||
let initialized_validators_rw_lock = validator_store.initialized_validators();
|
||||
let mut initialized_validators = initialized_validators_rw_lock.write();
|
||||
match initialized_validators.validator(&validator_pubkey.compress()) {
|
||||
None => Err(warp_utils::reject::custom_not_found(
|
||||
"The key was not found on the server, nothing to delete".to_string(),
|
||||
)),
|
||||
Some(initialized_validator) => {
|
||||
if initialized_validator.get_graffiti().is_none() {
|
||||
Ok(())
|
||||
} else {
|
||||
initialized_validators
|
||||
.delete_graffiti(&validator_pubkey)
|
||||
.map_err(|_| {
|
||||
warp_utils::reject::custom_server_error(
|
||||
"A graffiti was found, but failed to be removed.".to_string(),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
mod api_secret;
|
||||
mod create_signed_voluntary_exit;
|
||||
mod create_validator;
|
||||
mod graffiti;
|
||||
mod keystores;
|
||||
mod remotekeys;
|
||||
mod tests;
|
||||
@@ -9,6 +10,8 @@ pub mod test_utils;
|
||||
|
||||
use crate::beacon_node_fallback::CandidateError;
|
||||
use crate::beacon_node_health::BeaconNodeHealth;
|
||||
use crate::http_api::graffiti::{delete_graffiti, get_graffiti, set_graffiti};
|
||||
|
||||
use crate::http_api::create_signed_voluntary_exit::create_signed_voluntary_exit;
|
||||
use crate::{determine_graffiti, BlockService, GraffitiFile, ValidatorStore};
|
||||
use account_utils::{
|
||||
@@ -21,7 +24,10 @@ use create_validator::{
|
||||
};
|
||||
use eth2::lighthouse_vc::{
|
||||
std_types::{AuthResponse, GetFeeRecipientResponse, GetGasLimitResponse},
|
||||
types::{self as api_types, GenericResponse, Graffiti, PublicKey, PublicKeyBytes},
|
||||
types::{
|
||||
self as api_types, GenericResponse, GetGraffitiResponse, Graffiti, PublicKey,
|
||||
PublicKeyBytes, SetGraffitiRequest,
|
||||
},
|
||||
};
|
||||
use lighthouse_version::version_with_platform;
|
||||
use logging::SSELoggingComponents;
|
||||
@@ -690,7 +696,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
.and(warp::path::end())
|
||||
.and(warp::body::json())
|
||||
.and(validator_store_filter.clone())
|
||||
.and(graffiti_file_filter)
|
||||
.and(graffiti_file_filter.clone())
|
||||
.and(signer.clone())
|
||||
.and(task_executor_filter.clone())
|
||||
.and_then(
|
||||
@@ -1065,6 +1071,86 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
},
|
||||
);
|
||||
|
||||
// GET /eth/v1/validator/{pubkey}/graffiti
|
||||
let get_graffiti = eth_v1
|
||||
.and(warp::path("validator"))
|
||||
.and(warp::path::param::<PublicKey>())
|
||||
.and(warp::path("graffiti"))
|
||||
.and(warp::path::end())
|
||||
.and(validator_store_filter.clone())
|
||||
.and(graffiti_flag_filter)
|
||||
.and(signer.clone())
|
||||
.and_then(
|
||||
|pubkey: PublicKey,
|
||||
validator_store: Arc<ValidatorStore<T, E>>,
|
||||
graffiti_flag: Option<Graffiti>,
|
||||
signer| {
|
||||
blocking_signed_json_task(signer, move || {
|
||||
let graffiti = get_graffiti(pubkey.clone(), validator_store, graffiti_flag)?;
|
||||
Ok(GenericResponse::from(GetGraffitiResponse {
|
||||
pubkey: pubkey.into(),
|
||||
graffiti,
|
||||
}))
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// POST /eth/v1/validator/{pubkey}/graffiti
|
||||
let post_graffiti = eth_v1
|
||||
.and(warp::path("validator"))
|
||||
.and(warp::path::param::<PublicKey>())
|
||||
.and(warp::path("graffiti"))
|
||||
.and(warp::body::json())
|
||||
.and(warp::path::end())
|
||||
.and(validator_store_filter.clone())
|
||||
.and(graffiti_file_filter.clone())
|
||||
.and(signer.clone())
|
||||
.and_then(
|
||||
|pubkey: PublicKey,
|
||||
query: SetGraffitiRequest,
|
||||
validator_store: Arc<ValidatorStore<T, E>>,
|
||||
graffiti_file: Option<GraffitiFile>,
|
||||
signer| {
|
||||
blocking_signed_json_task(signer, move || {
|
||||
if graffiti_file.is_some() {
|
||||
return Err(warp_utils::reject::invalid_auth(
|
||||
"Unable to update graffiti as the \"--graffiti-file\" flag is set"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
set_graffiti(pubkey.clone(), query.graffiti, validator_store)
|
||||
})
|
||||
},
|
||||
)
|
||||
.map(|reply| warp::reply::with_status(reply, warp::http::StatusCode::ACCEPTED));
|
||||
|
||||
// DELETE /eth/v1/validator/{pubkey}/graffiti
|
||||
let delete_graffiti = eth_v1
|
||||
.and(warp::path("validator"))
|
||||
.and(warp::path::param::<PublicKey>())
|
||||
.and(warp::path("graffiti"))
|
||||
.and(warp::path::end())
|
||||
.and(validator_store_filter.clone())
|
||||
.and(graffiti_file_filter.clone())
|
||||
.and(signer.clone())
|
||||
.and_then(
|
||||
|pubkey: PublicKey,
|
||||
validator_store: Arc<ValidatorStore<T, E>>,
|
||||
graffiti_file: Option<GraffitiFile>,
|
||||
signer| {
|
||||
blocking_signed_json_task(signer, move || {
|
||||
if graffiti_file.is_some() {
|
||||
return Err(warp_utils::reject::invalid_auth(
|
||||
"Unable to delete graffiti as the \"--graffiti-file\" flag is set"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
delete_graffiti(pubkey.clone(), validator_store)
|
||||
})
|
||||
},
|
||||
)
|
||||
.map(|reply| warp::reply::with_status(reply, warp::http::StatusCode::NO_CONTENT));
|
||||
|
||||
// GET /eth/v1/keystores
|
||||
let get_std_keystores = std_keystores
|
||||
.and(signer.clone())
|
||||
@@ -1213,6 +1299,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
.or(get_lighthouse_ui_fallback_health)
|
||||
.or(get_fee_recipient)
|
||||
.or(get_gas_limit)
|
||||
.or(get_graffiti)
|
||||
.or(get_std_keystores)
|
||||
.or(get_std_remotekeys)
|
||||
.recover(warp_utils::reject::handle_rejection),
|
||||
@@ -1227,6 +1314,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
.or(post_gas_limit)
|
||||
.or(post_std_keystores)
|
||||
.or(post_std_remotekeys)
|
||||
.or(post_graffiti)
|
||||
.recover(warp_utils::reject::handle_rejection),
|
||||
))
|
||||
.or(warp::patch()
|
||||
@@ -1237,6 +1325,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
.or(delete_gas_limit)
|
||||
.or(delete_std_keystores)
|
||||
.or(delete_std_remotekeys)
|
||||
.or(delete_graffiti)
|
||||
.recover(warp_utils::reject::handle_rejection),
|
||||
)),
|
||||
)
|
||||
|
||||
@@ -641,6 +641,49 @@ impl ApiTester {
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn test_set_graffiti(self, index: usize, graffiti: &str) -> Self {
|
||||
let validator = &self.client.get_lighthouse_validators().await.unwrap().data[index];
|
||||
let graffiti_str = GraffitiString::from_str(graffiti).unwrap();
|
||||
let resp = self
|
||||
.client
|
||||
.set_graffiti(&validator.voting_pubkey, graffiti_str)
|
||||
.await;
|
||||
|
||||
assert!(resp.is_ok());
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn test_delete_graffiti(self, index: usize) -> Self {
|
||||
let validator = &self.client.get_lighthouse_validators().await.unwrap().data[index];
|
||||
let resp = self.client.get_graffiti(&validator.voting_pubkey).await;
|
||||
|
||||
assert!(resp.is_ok());
|
||||
let old_graffiti = resp.unwrap().graffiti;
|
||||
|
||||
let resp = self.client.delete_graffiti(&validator.voting_pubkey).await;
|
||||
|
||||
assert!(resp.is_ok());
|
||||
|
||||
let resp = self.client.get_graffiti(&validator.voting_pubkey).await;
|
||||
|
||||
assert!(resp.is_ok());
|
||||
assert_ne!(old_graffiti, resp.unwrap().graffiti);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn test_get_graffiti(self, index: usize, expected_graffiti: &str) -> Self {
|
||||
let validator = &self.client.get_lighthouse_validators().await.unwrap().data[index];
|
||||
let expected_graffiti_str = GraffitiString::from_str(expected_graffiti).unwrap();
|
||||
let resp = self.client.get_graffiti(&validator.voting_pubkey).await;
|
||||
|
||||
assert!(resp.is_ok());
|
||||
assert_eq!(&resp.unwrap().graffiti, &expected_graffiti_str.into());
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
struct HdValidatorScenario {
|
||||
@@ -772,6 +815,20 @@ async fn routes_with_invalid_auth() {
|
||||
})
|
||||
.await
|
||||
})
|
||||
.await
|
||||
.test_with_invalid_auth(|client| async move {
|
||||
client.delete_graffiti(&PublicKeyBytes::empty()).await
|
||||
})
|
||||
.await
|
||||
.test_with_invalid_auth(|client| async move {
|
||||
client.get_graffiti(&PublicKeyBytes::empty()).await
|
||||
})
|
||||
.await
|
||||
.test_with_invalid_auth(|client| async move {
|
||||
client
|
||||
.set_graffiti(&PublicKeyBytes::empty(), GraffitiString::default())
|
||||
.await
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
@@ -955,6 +1012,31 @@ async fn validator_graffiti() {
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn validator_graffiti_api() {
|
||||
ApiTester::new()
|
||||
.await
|
||||
.create_hd_validators(HdValidatorScenario {
|
||||
count: 2,
|
||||
specify_mnemonic: false,
|
||||
key_derivation_path_offset: 0,
|
||||
disabled: vec![],
|
||||
})
|
||||
.await
|
||||
.assert_enabled_validators_count(2)
|
||||
.assert_validators_count(2)
|
||||
.set_graffiti(0, "Mr F was here")
|
||||
.await
|
||||
.test_get_graffiti(0, "Mr F was here")
|
||||
.await
|
||||
.test_set_graffiti(0, "Uncle Bill was here")
|
||||
.await
|
||||
.test_get_graffiti(0, "Uncle Bill was here")
|
||||
.await
|
||||
.test_delete_graffiti(0)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn keystore_validator_creation() {
|
||||
ApiTester::new()
|
||||
|
||||
@@ -59,11 +59,6 @@ lazy_static::lazy_static! {
|
||||
"Total count of attempted block signings",
|
||||
&["status"]
|
||||
);
|
||||
pub static ref SIGNED_BLOBS_TOTAL: Result<IntCounterVec> = try_create_int_counter_vec(
|
||||
"vc_signed_beacon_blobs_total",
|
||||
"Total count of attempted blob signings",
|
||||
&["status"]
|
||||
);
|
||||
pub static ref SIGNED_ATTESTATIONS_TOTAL: Result<IntCounterVec> = try_create_int_counter_vec(
|
||||
"vc_signed_attestations_total",
|
||||
"Total count of attempted Attestation signings",
|
||||
|
||||
@@ -716,6 +716,74 @@ impl InitializedValidators {
|
||||
self.validators.get(public_key).and_then(|v| v.graffiti)
|
||||
}
|
||||
|
||||
/// Sets the `InitializedValidator` and `ValidatorDefinition` `graffiti` values.
|
||||
///
|
||||
/// ## Notes
|
||||
///
|
||||
/// Setting a validator `graffiti` will cause `self.definitions` to be updated and saved to
|
||||
/// disk.
|
||||
///
|
||||
/// Saves the `ValidatorDefinitions` to file, even if no definitions were changed.
|
||||
pub fn set_graffiti(
|
||||
&mut self,
|
||||
voting_public_key: &PublicKey,
|
||||
graffiti: GraffitiString,
|
||||
) -> Result<(), Error> {
|
||||
if let Some(def) = self
|
||||
.definitions
|
||||
.as_mut_slice()
|
||||
.iter_mut()
|
||||
.find(|def| def.voting_public_key == *voting_public_key)
|
||||
{
|
||||
def.graffiti = Some(graffiti.clone());
|
||||
}
|
||||
|
||||
if let Some(val) = self
|
||||
.validators
|
||||
.get_mut(&PublicKeyBytes::from(voting_public_key))
|
||||
{
|
||||
val.graffiti = Some(graffiti.into());
|
||||
}
|
||||
|
||||
self.definitions
|
||||
.save(&self.validators_dir)
|
||||
.map_err(Error::UnableToSaveDefinitions)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes the `InitializedValidator` and `ValidatorDefinition` `graffiti` values.
|
||||
///
|
||||
/// ## Notes
|
||||
///
|
||||
/// Removing a validator `graffiti` will cause `self.definitions` to be updated and saved to
|
||||
/// disk. The graffiti for the validator will then fall back to the process level default if
|
||||
/// it is set.
|
||||
///
|
||||
/// Saves the `ValidatorDefinitions` to file, even if no definitions were changed.
|
||||
pub fn delete_graffiti(&mut self, voting_public_key: &PublicKey) -> Result<(), Error> {
|
||||
if let Some(def) = self
|
||||
.definitions
|
||||
.as_mut_slice()
|
||||
.iter_mut()
|
||||
.find(|def| def.voting_public_key == *voting_public_key)
|
||||
{
|
||||
def.graffiti = None;
|
||||
}
|
||||
|
||||
if let Some(val) = self
|
||||
.validators
|
||||
.get_mut(&PublicKeyBytes::from(voting_public_key))
|
||||
{
|
||||
val.graffiti = None;
|
||||
}
|
||||
|
||||
self.definitions
|
||||
.save(&self.validators_dir)
|
||||
.map_err(Error::UnableToSaveDefinitions)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns a `HashMap` of `public_key` -> `graffiti` for all initialized validators.
|
||||
pub fn get_all_validators_graffiti(&self) -> HashMap<&PublicKeyBytes, Option<Graffiti>> {
|
||||
let mut result = HashMap::new();
|
||||
|
||||
@@ -37,7 +37,6 @@ pub enum Error {
|
||||
pub enum SignableMessage<'a, T: EthSpec, Payload: AbstractExecPayload<T> = FullPayload<T>> {
|
||||
RandaoReveal(Epoch),
|
||||
BeaconBlock(&'a BeaconBlock<T, Payload>),
|
||||
BlobSidecar(&'a Payload::Sidecar),
|
||||
AttestationData(&'a AttestationData),
|
||||
SignedAggregateAndProof(&'a AggregateAndProof<T>),
|
||||
SelectionProof(Slot),
|
||||
@@ -60,7 +59,6 @@ impl<'a, T: EthSpec, Payload: AbstractExecPayload<T>> SignableMessage<'a, T, Pay
|
||||
match self {
|
||||
SignableMessage::RandaoReveal(epoch) => epoch.signing_root(domain),
|
||||
SignableMessage::BeaconBlock(b) => b.signing_root(domain),
|
||||
SignableMessage::BlobSidecar(b) => b.signing_root(domain),
|
||||
SignableMessage::AttestationData(a) => a.signing_root(domain),
|
||||
SignableMessage::SignedAggregateAndProof(a) => a.signing_root(domain),
|
||||
SignableMessage::SelectionProof(slot) => slot.signing_root(domain),
|
||||
@@ -184,10 +182,6 @@ impl SigningMethod {
|
||||
Web3SignerObject::RandaoReveal { epoch }
|
||||
}
|
||||
SignableMessage::BeaconBlock(block) => Web3SignerObject::beacon_block(block)?,
|
||||
SignableMessage::BlobSidecar(_) => {
|
||||
// https://github.com/ConsenSys/web3signer/issues/726
|
||||
unimplemented!("Web3Signer blob signing not implemented.")
|
||||
}
|
||||
SignableMessage::AttestationData(a) => Web3SignerObject::Attestation(a),
|
||||
SignableMessage::SignedAggregateAndProof(a) => {
|
||||
Web3SignerObject::AggregateAndProof(a)
|
||||
|
||||
@@ -6,7 +6,6 @@ use crate::{
|
||||
Config,
|
||||
};
|
||||
use account_utils::validator_definitions::{PasswordStorage, ValidatorDefinition};
|
||||
use eth2::types::VariableList;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use slashing_protection::{
|
||||
interchange::Interchange, InterchangeError, NotSafe, Safe, SlashingDatabase,
|
||||
@@ -18,16 +17,14 @@ use std::marker::PhantomData;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use task_executor::TaskExecutor;
|
||||
use types::sidecar::Sidecar;
|
||||
use types::{
|
||||
attestation::Error as AttestationError, graffiti::GraffitiString, AbstractExecPayload, Address,
|
||||
AggregateAndProof, Attestation, BeaconBlock, BlindedPayload, ChainSpec, ContributionAndProof,
|
||||
Domain, Epoch, EthSpec, Fork, ForkName, Graffiti, Hash256, Keypair, PublicKeyBytes,
|
||||
SelectionProof, SidecarList, Signature, SignedAggregateAndProof, SignedBeaconBlock,
|
||||
SignedContributionAndProof, SignedRoot, SignedSidecar, SignedSidecarList,
|
||||
SignedValidatorRegistrationData, SignedVoluntaryExit, Slot, SyncAggregatorSelectionData,
|
||||
SyncCommitteeContribution, SyncCommitteeMessage, SyncSelectionProof, SyncSubnetId,
|
||||
ValidatorRegistrationData, VoluntaryExit,
|
||||
SelectionProof, Signature, SignedAggregateAndProof, SignedBeaconBlock,
|
||||
SignedContributionAndProof, SignedRoot, SignedValidatorRegistrationData, SignedVoluntaryExit,
|
||||
Slot, SyncAggregatorSelectionData, SyncCommitteeContribution, SyncCommitteeMessage,
|
||||
SyncSelectionProof, SyncSubnetId, ValidatorRegistrationData, VoluntaryExit,
|
||||
};
|
||||
use validator_dir::ValidatorDir;
|
||||
|
||||
@@ -567,39 +564,6 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn sign_blobs<Payload: AbstractExecPayload<E>>(
|
||||
&self,
|
||||
validator_pubkey: PublicKeyBytes,
|
||||
blob_sidecars: SidecarList<E, Payload::Sidecar>,
|
||||
) -> Result<SignedSidecarList<E, Payload::Sidecar>, Error> {
|
||||
let mut signed_blob_sidecars = Vec::new();
|
||||
for blob_sidecar in blob_sidecars.into_iter() {
|
||||
let slot = blob_sidecar.slot();
|
||||
let signing_epoch = slot.epoch(E::slots_per_epoch());
|
||||
let signing_context = self.signing_context(Domain::BlobSidecar, signing_epoch);
|
||||
let signing_method = self.doppelganger_checked_signing_method(validator_pubkey)?;
|
||||
|
||||
let signature = signing_method
|
||||
.get_signature::<E, Payload>(
|
||||
SignableMessage::BlobSidecar(blob_sidecar.as_ref()),
|
||||
signing_context,
|
||||
&self.spec,
|
||||
&self.task_executor,
|
||||
)
|
||||
.await?;
|
||||
|
||||
metrics::inc_counter_vec(&metrics::SIGNED_BLOBS_TOTAL, &[metrics::SUCCESS]);
|
||||
|
||||
signed_blob_sidecars.push(SignedSidecar {
|
||||
message: blob_sidecar,
|
||||
signature,
|
||||
_phantom: PhantomData,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(VariableList::from(signed_blob_sidecars))
|
||||
}
|
||||
|
||||
pub async fn sign_attestation(
|
||||
&self,
|
||||
validator_pubkey: PublicKeyBytes,
|
||||
|
||||
Reference in New Issue
Block a user