Merge branch 'unstable' into vc-fallback

This commit is contained in:
Mac L
2023-12-13 12:24:11 +11:00
129 changed files with 5284 additions and 3944 deletions

View File

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

View File

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

View 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(),
)
})
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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