mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-30 04:37:13 +00:00
Merge branch 'unstable' into merge-unstable-to-deneb-20230808
# Conflicts: # Cargo.lock # beacon_node/beacon_chain/src/lib.rs # beacon_node/execution_layer/src/engine_api.rs # beacon_node/execution_layer/src/engine_api/http.rs # beacon_node/execution_layer/src/test_utils/mod.rs # beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs # beacon_node/lighthouse_network/src/rpc/handler.rs # beacon_node/lighthouse_network/src/rpc/protocol.rs # beacon_node/lighthouse_network/src/service/utils.rs # beacon_node/lighthouse_network/tests/rpc_tests.rs # beacon_node/network/Cargo.toml # beacon_node/network/src/network_beacon_processor/tests.rs # lcli/src/parse_ssz.rs # scripts/cross/Dockerfile # validator_client/src/block_service.rs # validator_client/src/validator_store.rs
This commit is contained in:
@@ -13,6 +13,9 @@ use std::fs::{self, File};
|
||||
use std::io;
|
||||
use std::io::prelude::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::from_utf8;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
pub mod validator_definitions;
|
||||
@@ -30,6 +33,8 @@ pub const MINIMUM_PASSWORD_LEN: usize = 12;
|
||||
/// array of length 32.
|
||||
const DEFAULT_PASSWORD_LEN: usize = 48;
|
||||
|
||||
pub const MNEMONIC_PROMPT: &str = "Enter the mnemonic phrase:";
|
||||
|
||||
/// Returns the "default" path where a wallet should store its password file.
|
||||
pub fn default_wallet_password_path<P: AsRef<Path>>(wallet_name: &str, secrets_dir: P) -> PathBuf {
|
||||
secrets_dir.as_ref().join(format!("{}.pass", wallet_name))
|
||||
@@ -59,6 +64,18 @@ pub fn read_password<P: AsRef<Path>>(path: P) -> Result<PlainText, io::Error> {
|
||||
fs::read(path).map(strip_off_newlines).map(Into::into)
|
||||
}
|
||||
|
||||
/// Reads a password file into a `ZeroizeString` struct, with new-lines removed.
|
||||
pub fn read_password_string<P: AsRef<Path>>(path: P) -> Result<ZeroizeString, String> {
|
||||
fs::read(path)
|
||||
.map_err(|e| format!("Error opening file: {:?}", e))
|
||||
.map(strip_off_newlines)
|
||||
.and_then(|bytes| {
|
||||
String::from_utf8(bytes)
|
||||
.map_err(|e| format!("Error decoding utf8: {:?}", e))
|
||||
.map(Into::into)
|
||||
})
|
||||
}
|
||||
|
||||
/// Write a file atomically by using a temporary file as an intermediate.
|
||||
///
|
||||
/// Care is taken to preserve the permissions of the file at `file_path` being written.
|
||||
@@ -220,6 +237,46 @@ impl AsRef<[u8]> for ZeroizeString {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_mnemonic_from_cli(
|
||||
mnemonic_path: Option<PathBuf>,
|
||||
stdin_inputs: bool,
|
||||
) -> Result<Mnemonic, String> {
|
||||
let mnemonic = match mnemonic_path {
|
||||
Some(path) => fs::read(&path)
|
||||
.map_err(|e| format!("Unable to read {:?}: {:?}", path, e))
|
||||
.and_then(|bytes| {
|
||||
let bytes_no_newlines: PlainText = strip_off_newlines(bytes).into();
|
||||
let phrase = from_utf8(bytes_no_newlines.as_ref())
|
||||
.map_err(|e| format!("Unable to derive mnemonic: {:?}", e))?;
|
||||
Mnemonic::from_phrase(phrase, Language::English).map_err(|e| {
|
||||
format!(
|
||||
"Unable to derive mnemonic from string {:?}: {:?}",
|
||||
phrase, e
|
||||
)
|
||||
})
|
||||
})?,
|
||||
None => loop {
|
||||
eprintln!();
|
||||
eprintln!("{}", MNEMONIC_PROMPT);
|
||||
|
||||
let mnemonic = read_input_from_user(stdin_inputs)?;
|
||||
|
||||
match Mnemonic::from_phrase(mnemonic.as_str(), Language::English) {
|
||||
Ok(mnemonic_m) => {
|
||||
eprintln!("Valid mnemonic provided.");
|
||||
eprintln!();
|
||||
sleep(Duration::from_secs(1));
|
||||
break mnemonic_m;
|
||||
}
|
||||
Err(_) => {
|
||||
eprintln!("Invalid mnemonic");
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
Ok(mnemonic)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
//! Serves as the source-of-truth of which validators this validator client should attempt (or not
|
||||
//! attempt) to load into the `crate::intialized_validators::InitializedValidators` struct.
|
||||
|
||||
use crate::{default_keystore_password_path, write_file_via_temporary, ZeroizeString};
|
||||
use crate::{
|
||||
default_keystore_password_path, read_password_string, write_file_via_temporary, ZeroizeString,
|
||||
};
|
||||
use directory::ensure_dir_exists;
|
||||
use eth2_keystore::Keystore;
|
||||
use regex::Regex;
|
||||
@@ -43,6 +45,18 @@ pub enum Error {
|
||||
UnableToOpenKeystore(eth2_keystore::Error),
|
||||
/// The validator directory could not be created.
|
||||
UnableToCreateValidatorDir(PathBuf),
|
||||
UnableToReadKeystorePassword(String),
|
||||
KeystoreWithoutPassword,
|
||||
}
|
||||
|
||||
/// Defines how a password for a validator keystore will be persisted.
|
||||
pub enum PasswordStorage {
|
||||
/// Store the password in the `validator_definitions.yml` file.
|
||||
ValidatorDefinitions(ZeroizeString),
|
||||
/// Store the password in a separate, dedicated file (likely in the "secrets" directory).
|
||||
File(PathBuf),
|
||||
/// Don't store the password at all.
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Serialize, Deserialize, Hash, Eq)]
|
||||
@@ -92,6 +106,34 @@ impl SigningDefinition {
|
||||
pub fn is_local_keystore(&self) -> bool {
|
||||
matches!(self, SigningDefinition::LocalKeystore { .. })
|
||||
}
|
||||
|
||||
pub fn voting_keystore_password(&self) -> Result<Option<ZeroizeString>, Error> {
|
||||
match self {
|
||||
SigningDefinition::LocalKeystore {
|
||||
voting_keystore_password: Some(password),
|
||||
..
|
||||
} => Ok(Some(password.clone())),
|
||||
SigningDefinition::LocalKeystore {
|
||||
voting_keystore_password_path: Some(path),
|
||||
..
|
||||
} => read_password_string(path)
|
||||
.map(Into::into)
|
||||
.map(Option::Some)
|
||||
.map_err(Error::UnableToReadKeystorePassword),
|
||||
SigningDefinition::LocalKeystore { .. } => Err(Error::KeystoreWithoutPassword),
|
||||
SigningDefinition::Web3Signer(_) => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn voting_keystore_password_path(&self) -> Option<&PathBuf> {
|
||||
match self {
|
||||
SigningDefinition::LocalKeystore {
|
||||
voting_keystore_password_path: Some(path),
|
||||
..
|
||||
} => Some(path),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A validator that may be initialized by this validator client.
|
||||
@@ -129,7 +171,7 @@ impl ValidatorDefinition {
|
||||
/// This function does not check the password against the keystore.
|
||||
pub fn new_keystore_with_password<P: AsRef<Path>>(
|
||||
voting_keystore_path: P,
|
||||
voting_keystore_password: Option<ZeroizeString>,
|
||||
voting_keystore_password_storage: PasswordStorage,
|
||||
graffiti: Option<GraffitiString>,
|
||||
suggested_fee_recipient: Option<Address>,
|
||||
gas_limit: Option<u64>,
|
||||
@@ -139,6 +181,12 @@ impl ValidatorDefinition {
|
||||
let keystore =
|
||||
Keystore::from_json_file(&voting_keystore_path).map_err(Error::UnableToOpenKeystore)?;
|
||||
let voting_public_key = keystore.public_key().ok_or(Error::InvalidKeystorePubkey)?;
|
||||
let (voting_keystore_password_path, voting_keystore_password) =
|
||||
match voting_keystore_password_storage {
|
||||
PasswordStorage::ValidatorDefinitions(password) => (None, Some(password)),
|
||||
PasswordStorage::File(path) => (Some(path), None),
|
||||
PasswordStorage::None => (None, None),
|
||||
};
|
||||
|
||||
Ok(ValidatorDefinition {
|
||||
enabled: true,
|
||||
@@ -150,7 +198,7 @@ impl ValidatorDefinition {
|
||||
builder_proposals,
|
||||
signing_definition: SigningDefinition::LocalKeystore {
|
||||
voting_keystore_path,
|
||||
voting_keystore_password_path: None,
|
||||
voting_keystore_password_path,
|
||||
voting_keystore_password,
|
||||
},
|
||||
})
|
||||
@@ -346,6 +394,13 @@ impl ValidatorDefinitions {
|
||||
pub fn as_mut_slice(&mut self) -> &mut [ValidatorDefinition] {
|
||||
self.0.as_mut_slice()
|
||||
}
|
||||
|
||||
// Returns an iterator over all the `voting_keystore_password_paths` in self.
|
||||
pub fn iter_voting_keystore_password_paths(&self) -> impl Iterator<Item = &PathBuf> {
|
||||
self.0
|
||||
.iter()
|
||||
.filter_map(|def| def.signing_definition.voting_keystore_password_path())
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform an exhaustive tree search of `dir`, adding any discovered voting keystore paths to
|
||||
|
||||
@@ -21,10 +21,14 @@ use futures_util::StreamExt;
|
||||
use lighthouse_network::PeerId;
|
||||
use pretty_reqwest_error::PrettyReqwestError;
|
||||
pub use reqwest;
|
||||
use reqwest::{IntoUrl, RequestBuilder, Response};
|
||||
use reqwest::{
|
||||
header::{HeaderMap, HeaderValue},
|
||||
Body, IntoUrl, RequestBuilder, Response,
|
||||
};
|
||||
pub use reqwest::{StatusCode, Url};
|
||||
pub use sensitive_url::{SensitiveError, SensitiveUrl};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use ssz::Encode;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::iter::Iterator;
|
||||
@@ -322,6 +326,25 @@ impl BeaconNodeHttpClient {
|
||||
ok_or_error(response).await
|
||||
}
|
||||
|
||||
/// Generic POST function supporting arbitrary responses and timeouts.
|
||||
async fn post_generic_with_ssz_body<T: Into<Body>, U: IntoUrl>(
|
||||
&self,
|
||||
url: U,
|
||||
body: T,
|
||||
timeout: Option<Duration>,
|
||||
) -> Result<Response, Error> {
|
||||
let mut builder = self.client.post(url);
|
||||
if let Some(timeout) = timeout {
|
||||
builder = builder.timeout(timeout);
|
||||
}
|
||||
let response = builder
|
||||
.header("Content-Type", "application/octet-stream")
|
||||
.body(body)
|
||||
.send()
|
||||
.await?;
|
||||
ok_or_error(response).await
|
||||
}
|
||||
|
||||
/// Generic POST function supporting arbitrary responses and timeouts.
|
||||
async fn post_generic_with_consensus_version<T: Serialize, U: IntoUrl>(
|
||||
&self,
|
||||
@@ -342,6 +365,31 @@ impl BeaconNodeHttpClient {
|
||||
ok_or_error(response).await
|
||||
}
|
||||
|
||||
/// Generic POST function supporting arbitrary responses and timeouts.
|
||||
async fn post_generic_with_consensus_version_and_ssz_body<T: Into<Body>, U: IntoUrl>(
|
||||
&self,
|
||||
url: U,
|
||||
body: T,
|
||||
timeout: Option<Duration>,
|
||||
fork: ForkName,
|
||||
) -> Result<Response, Error> {
|
||||
let mut builder = self.client.post(url);
|
||||
if let Some(timeout) = timeout {
|
||||
builder = builder.timeout(timeout);
|
||||
}
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
CONSENSUS_VERSION_HEADER,
|
||||
HeaderValue::from_str(&fork.to_string()).expect("Failed to create header value"),
|
||||
);
|
||||
headers.insert(
|
||||
"Content-Type",
|
||||
HeaderValue::from_static("application/octet-stream"),
|
||||
);
|
||||
let response = builder.headers(headers).body(body).send().await?;
|
||||
ok_or_error(response).await
|
||||
}
|
||||
|
||||
/// `GET beacon/genesis`
|
||||
///
|
||||
/// ## Errors
|
||||
@@ -654,6 +702,26 @@ impl BeaconNodeHttpClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `POST beacon/blocks`
|
||||
///
|
||||
/// Returns `Ok(None)` on a 404 error.
|
||||
pub async fn post_beacon_blocks_ssz<T: EthSpec, Payload: AbstractExecPayload<T>>(
|
||||
&self,
|
||||
block: &SignedBeaconBlock<T, Payload>,
|
||||
) -> Result<(), Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
.push("beacon")
|
||||
.push("blocks");
|
||||
|
||||
self.post_generic_with_ssz_body(path, block.as_ssz_bytes(), Some(self.timeouts.proposal))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `POST beacon/blinded_blocks`
|
||||
///
|
||||
/// Returns `Ok(None)` on a 404 error.
|
||||
@@ -674,6 +742,26 @@ impl BeaconNodeHttpClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `POST beacon/blinded_blocks`
|
||||
///
|
||||
/// Returns `Ok(None)` on a 404 error.
|
||||
pub async fn post_beacon_blinded_blocks_ssz<T: EthSpec, Payload: AbstractExecPayload<T>>(
|
||||
&self,
|
||||
block: &SignedBeaconBlock<T, Payload>,
|
||||
) -> Result<(), Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
.push("beacon")
|
||||
.push("blinded_blocks");
|
||||
|
||||
self.post_generic_with_ssz_body(path, block.as_ssz_bytes(), Some(self.timeouts.proposal))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn post_beacon_blocks_v2_path(
|
||||
&self,
|
||||
validation_level: Option<BroadcastValidation>,
|
||||
@@ -727,6 +815,23 @@ impl BeaconNodeHttpClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `POST v2/beacon/blocks`
|
||||
pub async fn post_beacon_blocks_v2_ssz<T: EthSpec, Payload: AbstractExecPayload<T>>(
|
||||
&self,
|
||||
block: &SignedBeaconBlock<T, Payload>,
|
||||
validation_level: Option<BroadcastValidation>,
|
||||
) -> Result<(), Error> {
|
||||
self.post_generic_with_consensus_version_and_ssz_body(
|
||||
self.post_beacon_blocks_v2_path(validation_level)?,
|
||||
block.as_ssz_bytes(),
|
||||
Some(self.timeouts.proposal),
|
||||
block.message().body().fork_name(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `POST v2/beacon/blinded_blocks`
|
||||
//TODO(sean) update this along with builder updates
|
||||
pub async fn post_beacon_blinded_blocks_v2<T: EthSpec>(
|
||||
@@ -745,6 +850,23 @@ impl BeaconNodeHttpClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `POST v2/beacon/blinded_blocks`
|
||||
pub async fn post_beacon_blinded_blocks_v2_ssz<T: EthSpec>(
|
||||
&self,
|
||||
block: &SignedBlindedBeaconBlock<T>,
|
||||
validation_level: Option<BroadcastValidation>,
|
||||
) -> Result<(), Error> {
|
||||
self.post_generic_with_consensus_version_and_ssz_body(
|
||||
self.post_beacon_blinded_blocks_v2_path(validation_level)?,
|
||||
block.as_ssz_bytes(),
|
||||
Some(self.timeouts.proposal),
|
||||
block.message().body().fork_name(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Path for `v2/beacon/blocks`
|
||||
pub fn get_beacon_blocks_path(&self, block_id: BlockId) -> Result<Url, Error> {
|
||||
let mut path = self.eth_path(V2)?;
|
||||
@@ -1665,6 +1787,24 @@ impl BeaconNodeHttpClient {
|
||||
.await
|
||||
}
|
||||
|
||||
/// `POST validator/liveness/{epoch}`
|
||||
pub async fn post_validator_liveness_epoch(
|
||||
&self,
|
||||
epoch: Epoch,
|
||||
indices: Vec<u64>,
|
||||
) -> Result<GenericResponse<Vec<StandardLivenessResponseData>>, Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
.push("validator")
|
||||
.push("liveness")
|
||||
.push(&epoch.to_string());
|
||||
|
||||
self.post_with_timeout_and_response(path, &indices, self.timeouts.liveness)
|
||||
.await
|
||||
}
|
||||
|
||||
/// `POST validator/duties/attester/{epoch}`
|
||||
pub async fn post_validator_duties_attester(
|
||||
&self,
|
||||
|
||||
@@ -490,6 +490,21 @@ impl ValidatorClientHttpClient {
|
||||
.await
|
||||
}
|
||||
|
||||
/// `DELETE eth/v1/keystores`
|
||||
pub async fn delete_lighthouse_keystores(
|
||||
&self,
|
||||
req: &DeleteKeystoresRequest,
|
||||
) -> Result<ExportKeystoresResponse, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
.push("lighthouse")
|
||||
.push("keystores");
|
||||
|
||||
self.delete_with_unsigned_response(path, req).await
|
||||
}
|
||||
|
||||
fn make_keystores_url(&self) -> Result<Url, Error> {
|
||||
let mut url = self.server.full.clone();
|
||||
url.path_segments_mut()
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use account_utils::ZeroizeString;
|
||||
use eth2_keystore::Keystore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use slashing_protection::interchange::Interchange;
|
||||
use types::{Address, PublicKeyBytes};
|
||||
|
||||
pub use slashing_protection::interchange::Interchange;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct GetFeeRecipientResponse {
|
||||
pub pubkey: PublicKeyBytes,
|
||||
@@ -27,7 +28,7 @@ pub struct ListKeystoresResponse {
|
||||
pub data: Vec<SingleKeystoreResponse>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
|
||||
pub struct SingleKeystoreResponse {
|
||||
pub validating_pubkey: PublicKeyBytes,
|
||||
pub derivation_path: Option<String>,
|
||||
|
||||
@@ -152,3 +152,19 @@ pub struct UpdateGasLimitRequest {
|
||||
pub struct VoluntaryExitQuery {
|
||||
pub epoch: Option<Epoch>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct ExportKeystoresResponse {
|
||||
pub data: Vec<SingleExportKeystoresResponse>,
|
||||
#[serde(with = "serde_utils::json_str")]
|
||||
pub slashing_protection: Interchange,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct SingleExportKeystoresResponse {
|
||||
pub status: Status<DeleteKeystoreStatus>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub validating_keystore: Option<KeystoreJsonStr>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub validating_keystore_password: Option<ZeroizeString>,
|
||||
}
|
||||
|
||||
@@ -1233,6 +1233,13 @@ impl FromStr for Accept {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct StandardLivenessResponseData {
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub index: u64,
|
||||
pub is_live: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct LivenessRequestData {
|
||||
pub epoch: Epoch,
|
||||
@@ -1440,6 +1447,13 @@ impl<T: EthSpec, Payload: AbstractExecPayload<T>> SignedBlockContents<T, Payload
|
||||
}
|
||||
}
|
||||
|
||||
/// SSZ decode with fork variant determined by slot.
|
||||
pub fn from_ssz_bytes(bytes: &[u8], spec: &ChainSpec) -> Result<Self, ssz::DecodeError> {
|
||||
// FIXME(jimmy): SSZ decode not implemented for `SignedBeaconBlockAndBlobSidecars`
|
||||
SignedBeaconBlock::from_ssz_bytes(bytes, spec)
|
||||
.map(|block| SignedBlockContents::Block(block))
|
||||
}
|
||||
|
||||
pub fn signed_block(&self) -> &SignedBeaconBlock<T, Payload> {
|
||||
match self {
|
||||
SignedBlockContents::BlockAndBlobSidecars(block_and_sidecars) => {
|
||||
|
||||
@@ -20,4 +20,4 @@ types = { path = "../../consensus/types"}
|
||||
kzg = { path = "../../crypto/kzg" }
|
||||
ethereum_ssz = "0.5.0"
|
||||
eth2_config = { path = "../eth2_config"}
|
||||
discv5 = "0.3.0"
|
||||
discv5 = "0.3.1"
|
||||
@@ -92,4 +92,14 @@ DEPOSIT_CONTRACT_ADDRESS: 0x0B98057eA310F4d31F2a452B414647007d1645d9
|
||||
|
||||
# Network
|
||||
# ---------------------------------------------------------------
|
||||
SUBNETS_PER_NODE: 4
|
||||
SUBNETS_PER_NODE: 4
|
||||
GOSSIP_MAX_SIZE: 10485760
|
||||
MIN_EPOCHS_FOR_BLOCK_REQUESTS: 33024
|
||||
MAX_CHUNK_SIZE: 10485760
|
||||
TTFB_TIMEOUT: 5
|
||||
RESP_TIMEOUT: 10
|
||||
MESSAGE_DOMAIN_INVALID_SNAPPY: 0x00000000
|
||||
MESSAGE_DOMAIN_VALID_SNAPPY: 0x01000000
|
||||
ATTESTATION_SUBNET_COUNT: 64
|
||||
ATTESTATION_SUBNET_EXTRA_BITS: 0
|
||||
ATTESTATION_SUBNET_PREFIX_BITS: 6
|
||||
|
||||
@@ -92,4 +92,14 @@ DEPOSIT_CONTRACT_ADDRESS: 0x00000000219ab540356cBB839Cbe05303d7705Fa
|
||||
|
||||
# Network
|
||||
# ---------------------------------------------------------------
|
||||
SUBNETS_PER_NODE: 2
|
||||
SUBNETS_PER_NODE: 2
|
||||
GOSSIP_MAX_SIZE: 10485760
|
||||
MIN_EPOCHS_FOR_BLOCK_REQUESTS: 33024
|
||||
MAX_CHUNK_SIZE: 10485760
|
||||
TTFB_TIMEOUT: 5
|
||||
RESP_TIMEOUT: 10
|
||||
MESSAGE_DOMAIN_INVALID_SNAPPY: 0x00000000
|
||||
MESSAGE_DOMAIN_VALID_SNAPPY: 0x01000000
|
||||
ATTESTATION_SUBNET_COUNT: 64
|
||||
ATTESTATION_SUBNET_EXTRA_BITS: 0
|
||||
ATTESTATION_SUBNET_PREFIX_BITS: 6
|
||||
|
||||
@@ -89,4 +89,14 @@ DEPOSIT_CONTRACT_ADDRESS: 0xff50ed3d0ec03aC01D4C79aAd74928BFF48a7b2b
|
||||
|
||||
# Network
|
||||
# ---------------------------------------------------------------
|
||||
SUBNETS_PER_NODE: 2
|
||||
SUBNETS_PER_NODE: 2
|
||||
GOSSIP_MAX_SIZE: 10485760
|
||||
MIN_EPOCHS_FOR_BLOCK_REQUESTS: 33024
|
||||
MAX_CHUNK_SIZE: 10485760
|
||||
TTFB_TIMEOUT: 5
|
||||
RESP_TIMEOUT: 10
|
||||
MESSAGE_DOMAIN_INVALID_SNAPPY: 0x00000000
|
||||
MESSAGE_DOMAIN_VALID_SNAPPY: 0x01000000
|
||||
ATTESTATION_SUBNET_COUNT: 64
|
||||
ATTESTATION_SUBNET_EXTRA_BITS: 0
|
||||
ATTESTATION_SUBNET_PREFIX_BITS: 6
|
||||
|
||||
@@ -81,4 +81,14 @@ DEPOSIT_CONTRACT_ADDRESS: 0x7f02C3E3c98b133055B8B348B2Ac625669Ed295D
|
||||
|
||||
# Network
|
||||
# ---------------------------------------------------------------
|
||||
SUBNETS_PER_NODE: 2
|
||||
SUBNETS_PER_NODE: 2
|
||||
GOSSIP_MAX_SIZE: 10485760
|
||||
MIN_EPOCHS_FOR_BLOCK_REQUESTS: 33024
|
||||
MAX_CHUNK_SIZE: 10485760
|
||||
TTFB_TIMEOUT: 5
|
||||
RESP_TIMEOUT: 10
|
||||
MESSAGE_DOMAIN_INVALID_SNAPPY: 0x00000000
|
||||
MESSAGE_DOMAIN_VALID_SNAPPY: 0x01000000
|
||||
ATTESTATION_SUBNET_COUNT: 64
|
||||
ATTESTATION_SUBNET_EXTRA_BITS: 0
|
||||
ATTESTATION_SUBNET_PREFIX_BITS: 6
|
||||
|
||||
@@ -20,6 +20,7 @@ tree_hash = "0.5.2"
|
||||
hex = "0.4.2"
|
||||
derivative = "2.1.1"
|
||||
lockfile = { path = "../lockfile" }
|
||||
directory = { path = "../directory" }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.1.0"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::{Error as DirError, ValidatorDir};
|
||||
use bls::get_withdrawal_credentials;
|
||||
use deposit_contract::{encode_eth1_tx_data, Error as DepositError};
|
||||
use directory::ensure_dir_exists;
|
||||
use eth2_keystore::{Error as KeystoreError, Keystore, KeystoreBuilder, PlainText};
|
||||
use filesystem::create_with_600_perms;
|
||||
use rand::{distributions::Alphanumeric, Rng};
|
||||
@@ -41,6 +42,7 @@ pub enum Error {
|
||||
#[cfg(feature = "insecure_keys")]
|
||||
InsecureKeysError(String),
|
||||
MissingPasswordDir,
|
||||
UnableToCreatePasswordDir(String),
|
||||
}
|
||||
|
||||
impl From<KeystoreError> for Error {
|
||||
@@ -78,6 +80,13 @@ impl<'a> Builder<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Optionally supply a directory in which to store the passwords for the validator keystores.
|
||||
/// If `None` is provided, do not store the password.
|
||||
pub fn password_dir_opt(mut self, password_dir_opt: Option<PathBuf>) -> Self {
|
||||
self.password_dir = password_dir_opt;
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the `ValidatorDir` use the given `keystore` which can be unlocked with `password`.
|
||||
///
|
||||
/// The builder will not necessarily check that `password` can unlock `keystore`.
|
||||
@@ -153,6 +162,10 @@ impl<'a> Builder<'a> {
|
||||
create_dir_all(&dir).map_err(Error::UnableToCreateDir)?;
|
||||
}
|
||||
|
||||
if let Some(password_dir) = &self.password_dir {
|
||||
ensure_dir_exists(password_dir).map_err(Error::UnableToCreatePasswordDir)?;
|
||||
}
|
||||
|
||||
// The withdrawal keystore must be initialized in order to store it or create an eth1
|
||||
// deposit.
|
||||
if (self.store_withdrawal_keystore || self.deposit_info.is_some())
|
||||
@@ -234,7 +247,7 @@ impl<'a> Builder<'a> {
|
||||
if self.store_withdrawal_keystore {
|
||||
// Write the withdrawal password to file.
|
||||
write_password_to_file(
|
||||
password_dir.join(withdrawal_keypair.pk.as_hex_string()),
|
||||
keystore_password_path(password_dir, &withdrawal_keystore),
|
||||
withdrawal_password.as_bytes(),
|
||||
)?;
|
||||
|
||||
@@ -250,7 +263,7 @@ impl<'a> Builder<'a> {
|
||||
if let Some(password_dir) = self.password_dir.as_ref() {
|
||||
// Write the voting password to file.
|
||||
write_password_to_file(
|
||||
password_dir.join(format!("0x{}", voting_keystore.pubkey())),
|
||||
keystore_password_path(password_dir, &voting_keystore),
|
||||
voting_password.as_bytes(),
|
||||
)?;
|
||||
}
|
||||
@@ -262,6 +275,12 @@ impl<'a> Builder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keystore_password_path<P: AsRef<Path>>(password_dir: P, keystore: &Keystore) -> PathBuf {
|
||||
password_dir
|
||||
.as_ref()
|
||||
.join(format!("0x{}", keystore.pubkey()))
|
||||
}
|
||||
|
||||
/// Writes a JSON keystore to file.
|
||||
fn write_keystore_to_file(path: PathBuf, keystore: &Keystore) -> Result<(), Error> {
|
||||
if path.exists() {
|
||||
|
||||
@@ -15,6 +15,6 @@ pub use crate::validator_dir::{
|
||||
ETH1_DEPOSIT_TX_HASH_FILE,
|
||||
};
|
||||
pub use builder::{
|
||||
Builder, Error as BuilderError, ETH1_DEPOSIT_DATA_FILE, VOTING_KEYSTORE_FILE,
|
||||
WITHDRAWAL_KEYSTORE_FILE,
|
||||
keystore_password_path, Builder, Error as BuilderError, ETH1_DEPOSIT_DATA_FILE,
|
||||
VOTING_KEYSTORE_FILE, WITHDRAWAL_KEYSTORE_FILE,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::builder::{
|
||||
ETH1_DEPOSIT_AMOUNT_FILE, ETH1_DEPOSIT_DATA_FILE, VOTING_KEYSTORE_FILE,
|
||||
keystore_password_path, ETH1_DEPOSIT_AMOUNT_FILE, ETH1_DEPOSIT_DATA_FILE, VOTING_KEYSTORE_FILE,
|
||||
WITHDRAWAL_KEYSTORE_FILE,
|
||||
};
|
||||
use deposit_contract::decode_eth1_tx_data;
|
||||
@@ -219,9 +219,7 @@ pub fn unlock_keypair<P: AsRef<Path>>(
|
||||
)
|
||||
.map_err(Error::UnableToReadKeystore)?;
|
||||
|
||||
let password_path = password_dir
|
||||
.as_ref()
|
||||
.join(format!("0x{}", keystore.pubkey()));
|
||||
let password_path = keystore_password_path(password_dir, &keystore);
|
||||
let password: PlainText = read(&password_path)
|
||||
.map_err(|_| Error::UnableToReadPassword(password_path))?
|
||||
.into();
|
||||
|
||||
Reference in New Issue
Block a user