mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-02 04:03:35 +00:00
Split the VC into crates making it more modular (#6453)
* Starting to modularize the VC * Revert changes to eth2 * More progress * More progress * Compiles * Merge latest unstable and make it compile * Fix some lints * Tests compile * Merge latest unstable * Remove unnecessary deps * Merge latest unstable * Correct release tests * Merge latest unstable * Merge remote-tracking branch 'origin/unstable' into modularize-vc * Merge branch 'unstable' into modularize-vc * Revert unnecessary cargo lock changes * Update validator_client/beacon_node_fallback/Cargo.toml * Update validator_client/http_metrics/Cargo.toml * Update validator_client/http_metrics/src/lib.rs * Update validator_client/initialized_validators/Cargo.toml * Update validator_client/signing_method/Cargo.toml * Update validator_client/validator_metrics/Cargo.toml * Update validator_client/validator_services/Cargo.toml * Update validator_client/validator_store/Cargo.toml * Update validator_client/validator_store/src/lib.rs * Merge remote-tracking branch 'origin/unstable' into modularize-vc * Fix format string * Rename doppelganger trait * Don't drop the tempdir * Cargo fmt
This commit is contained in:
17
validator_client/signing_method/Cargo.toml
Normal file
17
validator_client/signing_method/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "signing_method"
|
||||
version = "0.1.0"
|
||||
edition = { workspace = true }
|
||||
authors = ["Sigma Prime <contact@sigmaprime.io>"]
|
||||
|
||||
[dependencies]
|
||||
eth2_keystore = { workspace = true }
|
||||
lockfile = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
reqwest = { workspace = true }
|
||||
task_executor = { workspace = true }
|
||||
types = { workspace = true }
|
||||
url = { workspace = true }
|
||||
validator_metrics = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
ethereum_serde_utils = { workspace = true }
|
||||
264
validator_client/signing_method/src/lib.rs
Normal file
264
validator_client/signing_method/src/lib.rs
Normal file
@@ -0,0 +1,264 @@
|
||||
//! Provides methods for obtaining validator signatures, including:
|
||||
//!
|
||||
//! - Via a local `Keypair`.
|
||||
//! - Via a remote signer (Web3Signer)
|
||||
|
||||
use eth2_keystore::Keystore;
|
||||
use lockfile::Lockfile;
|
||||
use parking_lot::Mutex;
|
||||
use reqwest::{header::ACCEPT, Client};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use task_executor::TaskExecutor;
|
||||
use types::*;
|
||||
use url::Url;
|
||||
use web3signer::{ForkInfo, SigningRequest, SigningResponse};
|
||||
|
||||
pub use web3signer::Web3SignerObject;
|
||||
|
||||
mod web3signer;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
InconsistentDomains {
|
||||
message_type_domain: Domain,
|
||||
domain: Domain,
|
||||
},
|
||||
Web3SignerRequestFailed(String),
|
||||
Web3SignerJsonParsingFailed(String),
|
||||
ShuttingDown,
|
||||
TokioJoin(String),
|
||||
MergeForkNotSupported,
|
||||
GenesisForkVersionRequired,
|
||||
}
|
||||
|
||||
/// Enumerates all messages that can be signed by a validator.
|
||||
pub enum SignableMessage<'a, E: EthSpec, Payload: AbstractExecPayload<E> = FullPayload<E>> {
|
||||
RandaoReveal(Epoch),
|
||||
BeaconBlock(&'a BeaconBlock<E, Payload>),
|
||||
AttestationData(&'a AttestationData),
|
||||
SignedAggregateAndProof(AggregateAndProofRef<'a, E>),
|
||||
SelectionProof(Slot),
|
||||
SyncSelectionProof(&'a SyncAggregatorSelectionData),
|
||||
SyncCommitteeSignature {
|
||||
beacon_block_root: Hash256,
|
||||
slot: Slot,
|
||||
},
|
||||
SignedContributionAndProof(&'a ContributionAndProof<E>),
|
||||
ValidatorRegistration(&'a ValidatorRegistrationData),
|
||||
VoluntaryExit(&'a VoluntaryExit),
|
||||
}
|
||||
|
||||
impl<'a, E: EthSpec, Payload: AbstractExecPayload<E>> SignableMessage<'a, E, Payload> {
|
||||
/// Returns the `SignedRoot` for the contained message.
|
||||
///
|
||||
/// The actual `SignedRoot` trait is not used since it also requires a `TreeHash` impl, which is
|
||||
/// not required here.
|
||||
pub fn signing_root(&self, domain: Hash256) -> Hash256 {
|
||||
match self {
|
||||
SignableMessage::RandaoReveal(epoch) => epoch.signing_root(domain),
|
||||
SignableMessage::BeaconBlock(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),
|
||||
SignableMessage::SyncSelectionProof(s) => s.signing_root(domain),
|
||||
SignableMessage::SyncCommitteeSignature {
|
||||
beacon_block_root, ..
|
||||
} => beacon_block_root.signing_root(domain),
|
||||
SignableMessage::SignedContributionAndProof(c) => c.signing_root(domain),
|
||||
SignableMessage::ValidatorRegistration(v) => v.signing_root(domain),
|
||||
SignableMessage::VoluntaryExit(exit) => exit.signing_root(domain),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A method used by a validator to sign messages.
|
||||
///
|
||||
/// Presently there is only a single variant, however we expect more variants to arise (e.g.,
|
||||
/// remote signing).
|
||||
pub enum SigningMethod {
|
||||
/// A validator that is defined by an EIP-2335 keystore on the local filesystem.
|
||||
LocalKeystore {
|
||||
voting_keystore_path: PathBuf,
|
||||
voting_keystore_lockfile: Mutex<Option<Lockfile>>,
|
||||
voting_keystore: Keystore,
|
||||
voting_keypair: Arc<Keypair>,
|
||||
},
|
||||
/// A validator that defers to a Web3Signer server for signing.
|
||||
///
|
||||
/// See: https://docs.web3signer.consensys.net/en/latest/
|
||||
Web3Signer {
|
||||
signing_url: Url,
|
||||
http_client: Client,
|
||||
voting_public_key: PublicKey,
|
||||
},
|
||||
}
|
||||
|
||||
/// The additional information used to construct a signature. Mostly used for protection from replay
|
||||
/// attacks.
|
||||
pub struct SigningContext {
|
||||
pub domain: Domain,
|
||||
pub epoch: Epoch,
|
||||
pub fork: Fork,
|
||||
pub genesis_validators_root: Hash256,
|
||||
}
|
||||
|
||||
impl SigningContext {
|
||||
/// Returns the `Hash256` to be mixed-in with the signature.
|
||||
pub fn domain_hash(&self, spec: &ChainSpec) -> Hash256 {
|
||||
spec.get_domain(
|
||||
self.epoch,
|
||||
self.domain,
|
||||
&self.fork,
|
||||
self.genesis_validators_root,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SigningMethod {
|
||||
/// Return whether this signing method requires local slashing protection.
|
||||
pub fn requires_local_slashing_protection(
|
||||
&self,
|
||||
enable_web3signer_slashing_protection: bool,
|
||||
) -> bool {
|
||||
match self {
|
||||
// Slashing protection is ALWAYS required for local keys. DO NOT TURN THIS OFF.
|
||||
SigningMethod::LocalKeystore { .. } => true,
|
||||
// Slashing protection is only required for remote signer keys when the configuration
|
||||
// dictates that it is desired.
|
||||
SigningMethod::Web3Signer { .. } => enable_web3signer_slashing_protection,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the signature of `signable_message`, with respect to the `signing_context`.
|
||||
pub async fn get_signature<E: EthSpec, Payload: AbstractExecPayload<E>>(
|
||||
&self,
|
||||
signable_message: SignableMessage<'_, E, Payload>,
|
||||
signing_context: SigningContext,
|
||||
spec: &ChainSpec,
|
||||
executor: &TaskExecutor,
|
||||
) -> Result<Signature, Error> {
|
||||
let domain_hash = signing_context.domain_hash(spec);
|
||||
let SigningContext {
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
..
|
||||
} = signing_context;
|
||||
|
||||
let signing_root = signable_message.signing_root(domain_hash);
|
||||
|
||||
let fork_info = Some(ForkInfo {
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
});
|
||||
|
||||
self.get_signature_from_root(signable_message, signing_root, executor, fork_info)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_signature_from_root<E: EthSpec, Payload: AbstractExecPayload<E>>(
|
||||
&self,
|
||||
signable_message: SignableMessage<'_, E, Payload>,
|
||||
signing_root: Hash256,
|
||||
executor: &TaskExecutor,
|
||||
fork_info: Option<ForkInfo>,
|
||||
) -> Result<Signature, Error> {
|
||||
match self {
|
||||
SigningMethod::LocalKeystore { voting_keypair, .. } => {
|
||||
let _timer = validator_metrics::start_timer_vec(
|
||||
&validator_metrics::SIGNING_TIMES,
|
||||
&[validator_metrics::LOCAL_KEYSTORE],
|
||||
);
|
||||
|
||||
let voting_keypair = voting_keypair.clone();
|
||||
// Spawn a blocking task to produce the signature. This avoids blocking the core
|
||||
// tokio executor.
|
||||
let signature = executor
|
||||
.spawn_blocking_handle(
|
||||
move || voting_keypair.sk.sign(signing_root),
|
||||
"local_keystore_signer",
|
||||
)
|
||||
.ok_or(Error::ShuttingDown)?
|
||||
.await
|
||||
.map_err(|e| Error::TokioJoin(e.to_string()))?;
|
||||
Ok(signature)
|
||||
}
|
||||
SigningMethod::Web3Signer {
|
||||
signing_url,
|
||||
http_client,
|
||||
..
|
||||
} => {
|
||||
let _timer = validator_metrics::start_timer_vec(
|
||||
&validator_metrics::SIGNING_TIMES,
|
||||
&[validator_metrics::WEB3SIGNER],
|
||||
);
|
||||
|
||||
// Map the message into a Web3Signer type.
|
||||
let object = match signable_message {
|
||||
SignableMessage::RandaoReveal(epoch) => {
|
||||
Web3SignerObject::RandaoReveal { epoch }
|
||||
}
|
||||
SignableMessage::BeaconBlock(block) => Web3SignerObject::beacon_block(block)?,
|
||||
SignableMessage::AttestationData(a) => Web3SignerObject::Attestation(a),
|
||||
SignableMessage::SignedAggregateAndProof(a) => {
|
||||
Web3SignerObject::AggregateAndProof(a)
|
||||
}
|
||||
SignableMessage::SelectionProof(slot) => {
|
||||
Web3SignerObject::AggregationSlot { slot }
|
||||
}
|
||||
SignableMessage::SyncSelectionProof(s) => {
|
||||
Web3SignerObject::SyncAggregatorSelectionData(s)
|
||||
}
|
||||
SignableMessage::SyncCommitteeSignature {
|
||||
beacon_block_root,
|
||||
slot,
|
||||
} => Web3SignerObject::SyncCommitteeMessage {
|
||||
beacon_block_root,
|
||||
slot,
|
||||
},
|
||||
SignableMessage::SignedContributionAndProof(c) => {
|
||||
Web3SignerObject::ContributionAndProof(c)
|
||||
}
|
||||
SignableMessage::ValidatorRegistration(v) => {
|
||||
Web3SignerObject::ValidatorRegistration(v)
|
||||
}
|
||||
SignableMessage::VoluntaryExit(e) => Web3SignerObject::VoluntaryExit(e),
|
||||
};
|
||||
|
||||
// Determine the Web3Signer message type.
|
||||
let message_type = object.message_type();
|
||||
|
||||
if matches!(
|
||||
object,
|
||||
Web3SignerObject::Deposit { .. } | Web3SignerObject::ValidatorRegistration(_)
|
||||
) && fork_info.is_some()
|
||||
{
|
||||
return Err(Error::GenesisForkVersionRequired);
|
||||
}
|
||||
|
||||
let request = SigningRequest {
|
||||
message_type,
|
||||
fork_info,
|
||||
signing_root,
|
||||
object,
|
||||
};
|
||||
|
||||
// Request a signature from the Web3Signer instance via HTTP(S).
|
||||
let response: SigningResponse = http_client
|
||||
.post(signing_url.clone())
|
||||
.header(ACCEPT, "application/json")
|
||||
.json(&request)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| Error::Web3SignerRequestFailed(e.to_string()))?
|
||||
.error_for_status()
|
||||
.map_err(|e| Error::Web3SignerRequestFailed(e.to_string()))?
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| Error::Web3SignerJsonParsingFailed(e.to_string()))?;
|
||||
|
||||
Ok(response.signature)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
150
validator_client/signing_method/src/web3signer.rs
Normal file
150
validator_client/signing_method/src/web3signer.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
//! Contains the types required to make JSON requests to Web3Signer servers.
|
||||
|
||||
use super::Error;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use types::*;
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone, Serialize)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum MessageType {
|
||||
AggregationSlot,
|
||||
AggregateAndProof,
|
||||
Attestation,
|
||||
BlockV2,
|
||||
Deposit,
|
||||
RandaoReveal,
|
||||
VoluntaryExit,
|
||||
SyncCommitteeMessage,
|
||||
SyncCommitteeSelectionProof,
|
||||
SyncCommitteeContributionAndProof,
|
||||
ValidatorRegistration,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone, Serialize)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum ForkName {
|
||||
Phase0,
|
||||
Altair,
|
||||
Bellatrix,
|
||||
Capella,
|
||||
Deneb,
|
||||
Electra,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize)]
|
||||
pub struct ForkInfo {
|
||||
pub fork: Fork,
|
||||
pub genesis_validators_root: Hash256,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize)]
|
||||
#[serde(bound = "E: EthSpec", rename_all = "snake_case")]
|
||||
pub enum Web3SignerObject<'a, E: EthSpec, Payload: AbstractExecPayload<E>> {
|
||||
AggregationSlot {
|
||||
slot: Slot,
|
||||
},
|
||||
AggregateAndProof(AggregateAndProofRef<'a, E>),
|
||||
Attestation(&'a AttestationData),
|
||||
BeaconBlock {
|
||||
version: ForkName,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
block: Option<&'a BeaconBlock<E, Payload>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
block_header: Option<BeaconBlockHeader>,
|
||||
},
|
||||
#[allow(dead_code)]
|
||||
Deposit {
|
||||
pubkey: PublicKeyBytes,
|
||||
withdrawal_credentials: Hash256,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
amount: u64,
|
||||
#[serde(with = "serde_utils::bytes_4_hex")]
|
||||
genesis_fork_version: [u8; 4],
|
||||
},
|
||||
RandaoReveal {
|
||||
epoch: Epoch,
|
||||
},
|
||||
VoluntaryExit(&'a VoluntaryExit),
|
||||
SyncCommitteeMessage {
|
||||
beacon_block_root: Hash256,
|
||||
slot: Slot,
|
||||
},
|
||||
SyncAggregatorSelectionData(&'a SyncAggregatorSelectionData),
|
||||
ContributionAndProof(&'a ContributionAndProof<E>),
|
||||
ValidatorRegistration(&'a ValidatorRegistrationData),
|
||||
}
|
||||
|
||||
impl<'a, E: EthSpec, Payload: AbstractExecPayload<E>> Web3SignerObject<'a, E, Payload> {
|
||||
pub fn beacon_block(block: &'a BeaconBlock<E, Payload>) -> Result<Self, Error> {
|
||||
match block {
|
||||
BeaconBlock::Base(_) => Ok(Web3SignerObject::BeaconBlock {
|
||||
version: ForkName::Phase0,
|
||||
block: Some(block),
|
||||
block_header: None,
|
||||
}),
|
||||
BeaconBlock::Altair(_) => Ok(Web3SignerObject::BeaconBlock {
|
||||
version: ForkName::Altair,
|
||||
block: Some(block),
|
||||
block_header: None,
|
||||
}),
|
||||
BeaconBlock::Bellatrix(_) => Ok(Web3SignerObject::BeaconBlock {
|
||||
version: ForkName::Bellatrix,
|
||||
block: None,
|
||||
block_header: Some(block.block_header()),
|
||||
}),
|
||||
BeaconBlock::Capella(_) => Ok(Web3SignerObject::BeaconBlock {
|
||||
version: ForkName::Capella,
|
||||
block: None,
|
||||
block_header: Some(block.block_header()),
|
||||
}),
|
||||
BeaconBlock::Deneb(_) => Ok(Web3SignerObject::BeaconBlock {
|
||||
version: ForkName::Deneb,
|
||||
block: None,
|
||||
block_header: Some(block.block_header()),
|
||||
}),
|
||||
BeaconBlock::Electra(_) => Ok(Web3SignerObject::BeaconBlock {
|
||||
version: ForkName::Electra,
|
||||
block: None,
|
||||
block_header: Some(block.block_header()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn message_type(&self) -> MessageType {
|
||||
match self {
|
||||
Web3SignerObject::AggregationSlot { .. } => MessageType::AggregationSlot,
|
||||
Web3SignerObject::AggregateAndProof(_) => MessageType::AggregateAndProof,
|
||||
Web3SignerObject::Attestation(_) => MessageType::Attestation,
|
||||
Web3SignerObject::BeaconBlock { .. } => MessageType::BlockV2,
|
||||
Web3SignerObject::Deposit { .. } => MessageType::Deposit,
|
||||
Web3SignerObject::RandaoReveal { .. } => MessageType::RandaoReveal,
|
||||
Web3SignerObject::VoluntaryExit(_) => MessageType::VoluntaryExit,
|
||||
Web3SignerObject::SyncCommitteeMessage { .. } => MessageType::SyncCommitteeMessage,
|
||||
Web3SignerObject::SyncAggregatorSelectionData(_) => {
|
||||
MessageType::SyncCommitteeSelectionProof
|
||||
}
|
||||
Web3SignerObject::ContributionAndProof(_) => {
|
||||
MessageType::SyncCommitteeContributionAndProof
|
||||
}
|
||||
Web3SignerObject::ValidatorRegistration(_) => MessageType::ValidatorRegistration,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize)]
|
||||
#[serde(bound = "E: EthSpec")]
|
||||
pub struct SigningRequest<'a, E: EthSpec, Payload: AbstractExecPayload<E>> {
|
||||
#[serde(rename = "type")]
|
||||
pub message_type: MessageType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub fork_info: Option<ForkInfo>,
|
||||
#[serde(rename = "signingRoot")]
|
||||
pub signing_root: Hash256,
|
||||
#[serde(flatten)]
|
||||
pub object: Web3SignerObject<'a, E, Payload>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Deserialize)]
|
||||
pub struct SigningResponse {
|
||||
pub signature: Signature,
|
||||
}
|
||||
Reference in New Issue
Block a user