mirror of
https://github.com/sigp/lighthouse.git
synced 2026-07-03 04:44:28 +00:00
Merge remote-tracking branch 'origin/unstable' into tree-states
This commit is contained in:
@@ -45,6 +45,29 @@ pub enum Error {
|
||||
UnableToCreateValidatorDir(PathBuf),
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Serialize, Deserialize, Hash, Eq)]
|
||||
pub struct Web3SignerDefinition {
|
||||
pub url: String,
|
||||
/// Path to a .pem file.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub root_certificate_path: Option<PathBuf>,
|
||||
/// Specifies a request timeout.
|
||||
///
|
||||
/// The timeout is applied from when the request starts connecting until the response body has finished.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub request_timeout_ms: Option<u64>,
|
||||
|
||||
/// Path to a PKCS12 file.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub client_identity_path: Option<PathBuf>,
|
||||
|
||||
/// Password for the PKCS12 file.
|
||||
///
|
||||
/// An empty password will be used if this is omitted.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub client_identity_password: Option<String>,
|
||||
}
|
||||
|
||||
/// Defines how the validator client should attempt to sign messages for this validator.
|
||||
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
@@ -62,27 +85,7 @@ pub enum SigningDefinition {
|
||||
///
|
||||
/// https://github.com/ConsenSys/web3signer
|
||||
#[serde(rename = "web3signer")]
|
||||
Web3Signer {
|
||||
url: String,
|
||||
/// Path to a .pem file.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
root_certificate_path: Option<PathBuf>,
|
||||
/// Specifies a request timeout.
|
||||
///
|
||||
/// The timeout is applied from when the request starts connecting until the response body has finished.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
request_timeout_ms: Option<u64>,
|
||||
|
||||
/// Path to a PKCS12 file.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
client_identity_path: Option<PathBuf>,
|
||||
|
||||
/// Password for the PKCS12 file.
|
||||
///
|
||||
/// An empty password will be used if this is omitted.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
client_identity_password: Option<String>,
|
||||
},
|
||||
Web3Signer(Web3SignerDefinition),
|
||||
}
|
||||
|
||||
impl SigningDefinition {
|
||||
@@ -106,6 +109,12 @@ pub struct ValidatorDefinition {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub suggested_fee_recipient: Option<Address>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub gas_limit: Option<u64>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub builder_proposals: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub description: String,
|
||||
#[serde(flatten)]
|
||||
pub signing_definition: SigningDefinition,
|
||||
@@ -123,6 +132,8 @@ impl ValidatorDefinition {
|
||||
voting_keystore_password: Option<ZeroizeString>,
|
||||
graffiti: Option<GraffitiString>,
|
||||
suggested_fee_recipient: Option<Address>,
|
||||
gas_limit: Option<u64>,
|
||||
builder_proposals: Option<bool>,
|
||||
) -> Result<Self, Error> {
|
||||
let voting_keystore_path = voting_keystore_path.as_ref().into();
|
||||
let keystore =
|
||||
@@ -135,6 +146,8 @@ impl ValidatorDefinition {
|
||||
description: keystore.description().unwrap_or("").to_string(),
|
||||
graffiti,
|
||||
suggested_fee_recipient,
|
||||
gas_limit,
|
||||
builder_proposals,
|
||||
signing_definition: SigningDefinition::LocalKeystore {
|
||||
voting_keystore_path,
|
||||
voting_keystore_password_path: None,
|
||||
@@ -281,6 +294,8 @@ impl ValidatorDefinitions {
|
||||
description: keystore.description().unwrap_or("").to_string(),
|
||||
graffiti: None,
|
||||
suggested_fee_recipient: None,
|
||||
gas_limit: None,
|
||||
builder_proposals: None,
|
||||
signing_definition: SigningDefinition::LocalKeystore {
|
||||
voting_keystore_path,
|
||||
voting_keystore_password_path,
|
||||
@@ -523,4 +538,84 @@ mod tests {
|
||||
Some(Address::from_str("0xa2e334e71511686bcfe38bb3ee1ad8f6babcc03d").unwrap())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gas_limit_checks() {
|
||||
let no_gas_limit = r#"---
|
||||
description: ""
|
||||
enabled: true
|
||||
type: local_keystore
|
||||
suggested_fee_recipient: "0xa2e334e71511686bcfe38bb3ee1ad8f6babcc03d"
|
||||
voting_keystore_path: ""
|
||||
voting_public_key: "0xaf3c7ddab7e293834710fca2d39d068f884455ede270e0d0293dc818e4f2f0f975355067e8437955cb29aec674e5c9e7"
|
||||
"#;
|
||||
let def: ValidatorDefinition = serde_yaml::from_str(no_gas_limit).unwrap();
|
||||
assert!(def.gas_limit.is_none());
|
||||
|
||||
let invalid_gas_limit = r#"---
|
||||
description: ""
|
||||
enabled: true
|
||||
type: local_keystore
|
||||
suggested_fee_recipient: "0xa2e334e71511686bcfe38bb3ee1ad8f6babcc03d"
|
||||
gas_limit: "banana"
|
||||
voting_keystore_path: ""
|
||||
voting_public_key: "0xaf3c7ddab7e293834710fca2d39d068f884455ede270e0d0293dc818e4f2f0f975355067e8437955cb29aec674e5c9e7"
|
||||
"#;
|
||||
|
||||
let def: Result<ValidatorDefinition, _> = serde_yaml::from_str(invalid_gas_limit);
|
||||
assert!(def.is_err());
|
||||
|
||||
let valid_gas_limit = r#"---
|
||||
description: ""
|
||||
enabled: true
|
||||
type: local_keystore
|
||||
suggested_fee_recipient: "0xa2e334e71511686bcfe38bb3ee1ad8f6babcc03d"
|
||||
gas_limit: 35000000
|
||||
voting_keystore_path: ""
|
||||
voting_public_key: "0xaf3c7ddab7e293834710fca2d39d068f884455ede270e0d0293dc818e4f2f0f975355067e8437955cb29aec674e5c9e7"
|
||||
"#;
|
||||
|
||||
let def: ValidatorDefinition = serde_yaml::from_str(valid_gas_limit).unwrap();
|
||||
assert_eq!(def.gas_limit, Some(35000000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builder_proposals_checks() {
|
||||
let no_builder_proposals = r#"---
|
||||
description: ""
|
||||
enabled: true
|
||||
type: local_keystore
|
||||
suggested_fee_recipient: "0xa2e334e71511686bcfe38bb3ee1ad8f6babcc03d"
|
||||
voting_keystore_path: ""
|
||||
voting_public_key: "0xaf3c7ddab7e293834710fca2d39d068f884455ede270e0d0293dc818e4f2f0f975355067e8437955cb29aec674e5c9e7"
|
||||
"#;
|
||||
let def: ValidatorDefinition = serde_yaml::from_str(no_builder_proposals).unwrap();
|
||||
assert!(def.builder_proposals.is_none());
|
||||
|
||||
let invalid_builder_proposals = r#"---
|
||||
description: ""
|
||||
enabled: true
|
||||
type: local_keystore
|
||||
suggested_fee_recipient: "0xa2e334e71511686bcfe38bb3ee1ad8f6babcc03d"
|
||||
builder_proposals: "banana"
|
||||
voting_keystore_path: ""
|
||||
voting_public_key: "0xaf3c7ddab7e293834710fca2d39d068f884455ede270e0d0293dc818e4f2f0f975355067e8437955cb29aec674e5c9e7"
|
||||
"#;
|
||||
|
||||
let def: Result<ValidatorDefinition, _> = serde_yaml::from_str(invalid_builder_proposals);
|
||||
assert!(def.is_err());
|
||||
|
||||
let valid_builder_proposals = r#"---
|
||||
description: ""
|
||||
enabled: true
|
||||
type: local_keystore
|
||||
suggested_fee_recipient: "0xa2e334e71511686bcfe38bb3ee1ad8f6babcc03d"
|
||||
builder_proposals: true
|
||||
voting_keystore_path: ""
|
||||
voting_public_key: "0xaf3c7ddab7e293834710fca2d39d068f884455ede270e0d0293dc818e4f2f0f975355067e8437955cb29aec674e5c9e7"
|
||||
"#;
|
||||
|
||||
let def: ValidatorDefinition = serde_yaml::from_str(valid_builder_proposals).unwrap();
|
||||
assert_eq!(def.builder_proposals, Some(true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,12 +54,10 @@ fn read_contract_file_from_url(url: Url) -> Result<Value, String> {
|
||||
.map_err(|e| format!("Respsonse is not a valid json {:?}", e))?;
|
||||
Ok(contract)
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(format!(
|
||||
"No abi file found. Failed to download from github: {:?}",
|
||||
e
|
||||
))
|
||||
}
|
||||
Err(e) => Err(format!(
|
||||
"No abi file found. Failed to download from github: {:?}",
|
||||
e
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ use lighthouse_network::PeerId;
|
||||
pub use reqwest;
|
||||
use reqwest::{IntoUrl, RequestBuilder, Response};
|
||||
pub use reqwest::{StatusCode, Url};
|
||||
use sensitive_url::SensitiveUrl;
|
||||
pub use sensitive_url::SensitiveUrl;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
@@ -110,7 +110,10 @@ pub struct Timeouts {
|
||||
pub liveness: Duration,
|
||||
pub proposal: Duration,
|
||||
pub proposer_duties: Duration,
|
||||
pub sync_committee_contribution: Duration,
|
||||
pub sync_duties: Duration,
|
||||
pub get_beacon_blocks_ssz: Duration,
|
||||
pub get_debug_beacon_states: Duration,
|
||||
}
|
||||
|
||||
impl Timeouts {
|
||||
@@ -121,7 +124,10 @@ impl Timeouts {
|
||||
liveness: timeout,
|
||||
proposal: timeout,
|
||||
proposer_duties: timeout,
|
||||
sync_committee_contribution: timeout,
|
||||
sync_duties: timeout,
|
||||
get_beacon_blocks_ssz: timeout,
|
||||
get_debug_beacon_states: timeout,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -237,9 +243,10 @@ impl BeaconNodeHttpClient {
|
||||
&self,
|
||||
url: U,
|
||||
accept_header: Accept,
|
||||
timeout: Duration,
|
||||
) -> Result<Option<Vec<u8>>, Error> {
|
||||
let opt_response = self
|
||||
.get_response(url, |b| b.accept(accept_header))
|
||||
.get_response(url, |b| b.accept(accept_header).timeout(timeout))
|
||||
.await
|
||||
.optional()?;
|
||||
match opt_response {
|
||||
@@ -330,7 +337,7 @@ impl BeaconNodeHttpClient {
|
||||
pub async fn get_beacon_states_root(
|
||||
&self,
|
||||
state_id: StateId,
|
||||
) -> Result<Option<GenericResponse<RootData>>, Error> {
|
||||
) -> Result<Option<ExecutionOptimisticResponse<RootData>>, Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -349,7 +356,7 @@ impl BeaconNodeHttpClient {
|
||||
pub async fn get_beacon_states_fork(
|
||||
&self,
|
||||
state_id: StateId,
|
||||
) -> Result<Option<GenericResponse<Fork>>, Error> {
|
||||
) -> Result<Option<ExecutionOptimisticResponse<Fork>>, Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -368,7 +375,7 @@ impl BeaconNodeHttpClient {
|
||||
pub async fn get_beacon_states_finality_checkpoints(
|
||||
&self,
|
||||
state_id: StateId,
|
||||
) -> Result<Option<GenericResponse<FinalityCheckpointsData>>, Error> {
|
||||
) -> Result<Option<ExecutionOptimisticResponse<FinalityCheckpointsData>>, Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -388,7 +395,7 @@ impl BeaconNodeHttpClient {
|
||||
&self,
|
||||
state_id: StateId,
|
||||
ids: Option<&[ValidatorId]>,
|
||||
) -> Result<Option<GenericResponse<Vec<ValidatorBalanceData>>>, Error> {
|
||||
) -> Result<Option<ExecutionOptimisticResponse<Vec<ValidatorBalanceData>>>, Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -418,7 +425,7 @@ impl BeaconNodeHttpClient {
|
||||
state_id: StateId,
|
||||
ids: Option<&[ValidatorId]>,
|
||||
statuses: Option<&[ValidatorStatus]>,
|
||||
) -> Result<Option<GenericResponse<Vec<ValidatorData>>>, Error> {
|
||||
) -> Result<Option<ExecutionOptimisticResponse<Vec<ValidatorData>>>, Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -458,7 +465,7 @@ impl BeaconNodeHttpClient {
|
||||
slot: Option<Slot>,
|
||||
index: Option<u64>,
|
||||
epoch: Option<Epoch>,
|
||||
) -> Result<Option<GenericResponse<Vec<CommitteeData>>>, Error> {
|
||||
) -> Result<Option<ExecutionOptimisticResponse<Vec<CommitteeData>>>, Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -491,7 +498,7 @@ impl BeaconNodeHttpClient {
|
||||
&self,
|
||||
state_id: StateId,
|
||||
epoch: Option<Epoch>,
|
||||
) -> Result<GenericResponse<SyncCommitteeByValidatorIndices>, Error> {
|
||||
) -> Result<ExecutionOptimisticResponse<SyncCommitteeByValidatorIndices>, Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -516,7 +523,7 @@ impl BeaconNodeHttpClient {
|
||||
&self,
|
||||
state_id: StateId,
|
||||
validator_id: &ValidatorId,
|
||||
) -> Result<Option<GenericResponse<ValidatorData>>, Error> {
|
||||
) -> Result<Option<ExecutionOptimisticResponse<ValidatorData>>, Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -537,7 +544,7 @@ impl BeaconNodeHttpClient {
|
||||
&self,
|
||||
slot: Option<Slot>,
|
||||
parent_root: Option<Hash256>,
|
||||
) -> Result<Option<GenericResponse<Vec<BlockHeaderData>>>, Error> {
|
||||
) -> Result<Option<ExecutionOptimisticResponse<Vec<BlockHeaderData>>>, Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -564,7 +571,7 @@ impl BeaconNodeHttpClient {
|
||||
pub async fn get_beacon_headers_block_id(
|
||||
&self,
|
||||
block_id: BlockId,
|
||||
) -> Result<Option<GenericResponse<BlockHeaderData>>, Error> {
|
||||
) -> Result<Option<ExecutionOptimisticResponse<BlockHeaderData>>, Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -633,7 +640,7 @@ impl BeaconNodeHttpClient {
|
||||
pub async fn get_beacon_blocks<T: EthSpec>(
|
||||
&self,
|
||||
block_id: BlockId,
|
||||
) -> Result<Option<ForkVersionedResponse<SignedBeaconBlock<T>>>, Error> {
|
||||
) -> Result<Option<ExecutionOptimisticForkVersionedResponse<SignedBeaconBlock<T>>>, Error> {
|
||||
let path = self.get_beacon_blocks_path(block_id)?;
|
||||
let response = match self.get_response(path, |b| b).await.optional()? {
|
||||
Some(res) => res,
|
||||
@@ -642,20 +649,31 @@ impl BeaconNodeHttpClient {
|
||||
|
||||
// If present, use the fork provided in the headers to decode the block. Gracefully handle
|
||||
// missing and malformed fork names by falling back to regular deserialisation.
|
||||
let (block, version) = match response.fork_name_from_header() {
|
||||
let (block, version, execution_optimistic) = match response.fork_name_from_header() {
|
||||
Ok(Some(fork_name)) => {
|
||||
map_fork_name_with!(fork_name, SignedBeaconBlock, {
|
||||
let ForkVersionedResponse { version, data } = response.json().await?;
|
||||
(data, version)
|
||||
})
|
||||
let (data, (version, execution_optimistic)) =
|
||||
map_fork_name_with!(fork_name, SignedBeaconBlock, {
|
||||
let ExecutionOptimisticForkVersionedResponse {
|
||||
version,
|
||||
execution_optimistic,
|
||||
data,
|
||||
} = response.json().await?;
|
||||
(data, (version, execution_optimistic))
|
||||
});
|
||||
(data, version, execution_optimistic)
|
||||
}
|
||||
Ok(None) | Err(_) => {
|
||||
let ForkVersionedResponse { version, data } = response.json().await?;
|
||||
(data, version)
|
||||
let ExecutionOptimisticForkVersionedResponse {
|
||||
version,
|
||||
execution_optimistic,
|
||||
data,
|
||||
} = response.json().await?;
|
||||
(data, version, execution_optimistic)
|
||||
}
|
||||
};
|
||||
Ok(Some(ForkVersionedResponse {
|
||||
Ok(Some(ExecutionOptimisticForkVersionedResponse {
|
||||
version,
|
||||
execution_optimistic,
|
||||
data: block,
|
||||
}))
|
||||
}
|
||||
@@ -688,7 +706,7 @@ impl BeaconNodeHttpClient {
|
||||
) -> Result<Option<SignedBeaconBlock<T>>, Error> {
|
||||
let path = self.get_beacon_blocks_path(block_id)?;
|
||||
|
||||
self.get_bytes_opt_accept_header(path, Accept::Ssz)
|
||||
self.get_bytes_opt_accept_header(path, Accept::Ssz, self.timeouts.get_beacon_blocks_ssz)
|
||||
.await?
|
||||
.map(|bytes| SignedBeaconBlock::from_ssz_bytes(&bytes, spec).map_err(Error::InvalidSsz))
|
||||
.transpose()
|
||||
@@ -700,7 +718,7 @@ impl BeaconNodeHttpClient {
|
||||
pub async fn get_beacon_blocks_root(
|
||||
&self,
|
||||
block_id: BlockId,
|
||||
) -> Result<Option<GenericResponse<RootData>>, Error> {
|
||||
) -> Result<Option<ExecutionOptimisticResponse<RootData>>, Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -719,7 +737,7 @@ impl BeaconNodeHttpClient {
|
||||
pub async fn get_beacon_blocks_attestations<T: EthSpec>(
|
||||
&self,
|
||||
block_id: BlockId,
|
||||
) -> Result<Option<GenericResponse<Vec<Attestation<T>>>>, Error> {
|
||||
) -> Result<Option<ExecutionOptimisticResponse<Vec<Attestation<T>>>>, Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -907,7 +925,12 @@ impl BeaconNodeHttpClient {
|
||||
.push("validator")
|
||||
.push("contribution_and_proofs");
|
||||
|
||||
self.post(path, &signed_contributions).await?;
|
||||
self.post_with_timeout(
|
||||
path,
|
||||
&signed_contributions,
|
||||
self.timeouts.sync_committee_contribution,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -929,6 +952,23 @@ impl BeaconNodeHttpClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `POST validator/register_validator`
|
||||
pub async fn post_validator_register_validator(
|
||||
&self,
|
||||
registration_data: &[SignedValidatorRegistrationData],
|
||||
) -> Result<(), Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
.push("validator")
|
||||
.push("register_validator");
|
||||
|
||||
self.post(path, ®istration_data).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `GET config/fork_schedule`
|
||||
pub async fn get_config_fork_schedule(&self) -> Result<GenericResponse<Vec<Fork>>, Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
@@ -942,7 +982,9 @@ impl BeaconNodeHttpClient {
|
||||
}
|
||||
|
||||
/// `GET config/spec`
|
||||
pub async fn get_config_spec(&self) -> Result<GenericResponse<ConfigAndPreset>, Error> {
|
||||
pub async fn get_config_spec<T: Serialize + DeserializeOwned>(
|
||||
&self,
|
||||
) -> Result<GenericResponse<T>, Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -1099,7 +1141,7 @@ impl BeaconNodeHttpClient {
|
||||
pub async fn get_debug_beacon_states<T: EthSpec>(
|
||||
&self,
|
||||
state_id: StateId,
|
||||
) -> Result<Option<ForkVersionedResponse<BeaconState<T>>>, Error> {
|
||||
) -> Result<Option<ExecutionOptimisticForkVersionedResponse<BeaconState<T>>>, Error> {
|
||||
let path = self.get_debug_beacon_states_path(state_id)?;
|
||||
self.get_opt(path).await
|
||||
}
|
||||
@@ -1108,7 +1150,7 @@ impl BeaconNodeHttpClient {
|
||||
pub async fn get_debug_beacon_states_v1<T: EthSpec>(
|
||||
&self,
|
||||
state_id: StateId,
|
||||
) -> Result<Option<ForkVersionedResponse<BeaconState<T>>>, Error> {
|
||||
) -> Result<Option<ExecutionOptimisticForkVersionedResponse<BeaconState<T>>>, Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -1130,15 +1172,30 @@ impl BeaconNodeHttpClient {
|
||||
) -> Result<Option<BeaconState<T>>, Error> {
|
||||
let path = self.get_debug_beacon_states_path(state_id)?;
|
||||
|
||||
self.get_bytes_opt_accept_header(path, Accept::Ssz)
|
||||
self.get_bytes_opt_accept_header(path, Accept::Ssz, self.timeouts.get_debug_beacon_states)
|
||||
.await?
|
||||
.map(|bytes| BeaconState::from_ssz_bytes(&bytes, spec).map_err(Error::InvalidSsz))
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// `GET debug/beacon/heads`
|
||||
/// `GET v2/debug/beacon/heads`
|
||||
pub async fn get_debug_beacon_heads(
|
||||
&self,
|
||||
) -> Result<GenericResponse<Vec<ChainHeadData>>, Error> {
|
||||
let mut path = self.eth_path(V2)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
.push("debug")
|
||||
.push("beacon")
|
||||
.push("heads");
|
||||
|
||||
self.get(path).await
|
||||
}
|
||||
|
||||
/// `GET v1/debug/beacon/heads` (LEGACY)
|
||||
pub async fn get_debug_beacon_heads_v1(
|
||||
&self,
|
||||
) -> Result<GenericResponse<Vec<ChainHeadData>>, Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
@@ -1230,7 +1287,7 @@ impl BeaconNodeHttpClient {
|
||||
.await
|
||||
}
|
||||
|
||||
/// `GET v2/validator/blocks/{slot}`
|
||||
/// `GET v1/validator/blinded_blocks/{slot}`
|
||||
pub async fn get_validator_blinded_blocks_with_verify_randao<
|
||||
T: EthSpec,
|
||||
Payload: ExecPayload<T>,
|
||||
@@ -1241,7 +1298,7 @@ impl BeaconNodeHttpClient {
|
||||
graffiti: Option<&Graffiti>,
|
||||
verify_randao: Option<bool>,
|
||||
) -> Result<ForkVersionedResponse<BeaconBlock<T, Payload>>, Error> {
|
||||
let mut path = self.eth_path(V2)?;
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
@@ -1470,7 +1527,7 @@ impl BeaconNodeHttpClient {
|
||||
&self,
|
||||
epoch: Epoch,
|
||||
indices: &[u64],
|
||||
) -> Result<GenericResponse<Vec<SyncDuty>>, Error> {
|
||||
) -> Result<ExecutionOptimisticResponse<Vec<SyncDuty>>, Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -1491,7 +1548,7 @@ impl BeaconNodeHttpClient {
|
||||
|
||||
/// Returns `Ok(response)` if the response is a `200 OK` response. Otherwise, creates an
|
||||
/// appropriate error message.
|
||||
async fn ok_or_error(response: Response) -> Result<Response, Error> {
|
||||
pub async fn ok_or_error(response: Response) -> Result<Response, Error> {
|
||||
let status = response.status();
|
||||
|
||||
if status == StatusCode::OK {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use types::{Hash256, Slot};
|
||||
use types::{AttestationData, Hash256, Slot};
|
||||
|
||||
/// Details about the rewards paid to a block proposer for proposing a block.
|
||||
///
|
||||
@@ -42,6 +42,9 @@ pub struct AttestationRewards {
|
||||
///
|
||||
/// Each element of the vec is a map from validator index to reward.
|
||||
pub per_attestation_rewards: Vec<HashMap<u64, u64>>,
|
||||
/// The attestations themselves (optional).
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub attestations: Vec<AttestationData>,
|
||||
}
|
||||
|
||||
/// Query parameters for the `/lighthouse/block_rewards` endpoint.
|
||||
@@ -51,4 +54,7 @@ pub struct BlockRewardsQuery {
|
||||
pub start_slot: Slot,
|
||||
/// Upper slot limit for block rewards returned (inclusive).
|
||||
pub end_slot: Slot,
|
||||
/// Include the full attestations themselves?
|
||||
#[serde(default)]
|
||||
pub include_attestations: bool,
|
||||
}
|
||||
|
||||
@@ -303,11 +303,11 @@ impl ValidatorClientHttpClient {
|
||||
}
|
||||
|
||||
/// Perform a HTTP DELETE request.
|
||||
async fn delete_with_unsigned_response<T: Serialize, U: IntoUrl, V: DeserializeOwned>(
|
||||
async fn delete_with_raw_response<T: Serialize, U: IntoUrl>(
|
||||
&self,
|
||||
url: U,
|
||||
body: &T,
|
||||
) -> Result<V, Error> {
|
||||
) -> Result<Response, Error> {
|
||||
let response = self
|
||||
.client
|
||||
.delete(url)
|
||||
@@ -316,7 +316,16 @@ impl ValidatorClientHttpClient {
|
||||
.send()
|
||||
.await
|
||||
.map_err(Error::Reqwest)?;
|
||||
let response = ok_or_error(response).await?;
|
||||
ok_or_error(response).await
|
||||
}
|
||||
|
||||
/// Perform a HTTP DELETE request.
|
||||
async fn delete_with_unsigned_response<T: Serialize, U: IntoUrl, V: DeserializeOwned>(
|
||||
&self,
|
||||
url: U,
|
||||
body: &T,
|
||||
) -> Result<V, Error> {
|
||||
let response = self.delete_with_raw_response(url, body).await?;
|
||||
Ok(response.json().await?)
|
||||
}
|
||||
|
||||
@@ -345,7 +354,9 @@ impl ValidatorClientHttpClient {
|
||||
}
|
||||
|
||||
/// `GET lighthouse/spec`
|
||||
pub async fn get_lighthouse_spec(&self) -> Result<GenericResponse<ConfigAndPreset>, Error> {
|
||||
pub async fn get_lighthouse_spec<T: Serialize + DeserializeOwned>(
|
||||
&self,
|
||||
) -> Result<GenericResponse<T>, Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -453,7 +464,9 @@ impl ValidatorClientHttpClient {
|
||||
pub async fn patch_lighthouse_validators(
|
||||
&self,
|
||||
voting_pubkey: &PublicKeyBytes,
|
||||
enabled: bool,
|
||||
enabled: Option<bool>,
|
||||
gas_limit: Option<u64>,
|
||||
builder_proposals: Option<bool>,
|
||||
) -> Result<(), Error> {
|
||||
let mut path = self.server.full.clone();
|
||||
|
||||
@@ -463,7 +476,15 @@ impl ValidatorClientHttpClient {
|
||||
.push("validators")
|
||||
.push(&voting_pubkey.to_string());
|
||||
|
||||
self.patch(path, &ValidatorPatchRequest { enabled }).await
|
||||
self.patch(
|
||||
path,
|
||||
&ValidatorPatchRequest {
|
||||
enabled,
|
||||
gas_limit,
|
||||
builder_proposals,
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn make_keystores_url(&self) -> Result<Url, Error> {
|
||||
@@ -486,6 +507,30 @@ impl ValidatorClientHttpClient {
|
||||
Ok(url)
|
||||
}
|
||||
|
||||
fn make_fee_recipient_url(&self, pubkey: &PublicKeyBytes) -> Result<Url, Error> {
|
||||
let mut url = self.server.full.clone();
|
||||
url.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
.push("eth")
|
||||
.push("v1")
|
||||
.push("validator")
|
||||
.push(&pubkey.to_string())
|
||||
.push("feerecipient");
|
||||
Ok(url)
|
||||
}
|
||||
|
||||
fn make_gas_limit_url(&self, pubkey: &PublicKeyBytes) -> Result<Url, Error> {
|
||||
let mut url = self.server.full.clone();
|
||||
url.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
.push("eth")
|
||||
.push("v1")
|
||||
.push("validator")
|
||||
.push(&pubkey.to_string())
|
||||
.push("gas_limit");
|
||||
Ok(url)
|
||||
}
|
||||
|
||||
/// `GET lighthouse/auth`
|
||||
pub async fn get_auth(&self) -> Result<AuthResponse, Error> {
|
||||
let mut url = self.server.full.clone();
|
||||
@@ -543,14 +588,71 @@ impl ValidatorClientHttpClient {
|
||||
let url = self.make_remotekeys_url()?;
|
||||
self.delete_with_unsigned_response(url, req).await
|
||||
}
|
||||
|
||||
/// `GET /eth/v1/validator/{pubkey}/feerecipient`
|
||||
pub async fn get_fee_recipient(
|
||||
&self,
|
||||
pubkey: &PublicKeyBytes,
|
||||
) -> Result<GetFeeRecipientResponse, Error> {
|
||||
let url = self.make_fee_recipient_url(pubkey)?;
|
||||
self.get(url)
|
||||
.await
|
||||
.map(|generic: GenericResponse<GetFeeRecipientResponse>| generic.data)
|
||||
}
|
||||
|
||||
/// `POST /eth/v1/validator/{pubkey}/feerecipient`
|
||||
pub async fn post_fee_recipient(
|
||||
&self,
|
||||
pubkey: &PublicKeyBytes,
|
||||
req: &UpdateFeeRecipientRequest,
|
||||
) -> Result<Response, Error> {
|
||||
let url = self.make_fee_recipient_url(pubkey)?;
|
||||
self.post_with_raw_response(url, req).await
|
||||
}
|
||||
|
||||
/// `DELETE /eth/v1/validator/{pubkey}/feerecipient`
|
||||
pub async fn delete_fee_recipient(&self, pubkey: &PublicKeyBytes) -> Result<Response, Error> {
|
||||
let url = self.make_fee_recipient_url(pubkey)?;
|
||||
self.delete_with_raw_response(url, &()).await
|
||||
}
|
||||
|
||||
/// `GET /eth/v1/validator/{pubkey}/gas_limit`
|
||||
pub async fn get_gas_limit(
|
||||
&self,
|
||||
pubkey: &PublicKeyBytes,
|
||||
) -> Result<GetGasLimitResponse, Error> {
|
||||
let url = self.make_gas_limit_url(pubkey)?;
|
||||
self.get(url)
|
||||
.await
|
||||
.map(|generic: GenericResponse<GetGasLimitResponse>| generic.data)
|
||||
}
|
||||
|
||||
/// `POST /eth/v1/validator/{pubkey}/gas_limit`
|
||||
pub async fn post_gas_limit(
|
||||
&self,
|
||||
pubkey: &PublicKeyBytes,
|
||||
req: &UpdateGasLimitRequest,
|
||||
) -> Result<Response, Error> {
|
||||
let url = self.make_gas_limit_url(pubkey)?;
|
||||
self.post_with_raw_response(url, req).await
|
||||
}
|
||||
|
||||
/// `DELETE /eth/v1/validator/{pubkey}/gas_limit`
|
||||
pub async fn delete_gas_limit(&self, pubkey: &PublicKeyBytes) -> Result<Response, Error> {
|
||||
let url = self.make_gas_limit_url(pubkey)?;
|
||||
self.delete_with_raw_response(url, &()).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Ok(response)` if the response is a `200 OK` response. Otherwise, creates an
|
||||
/// appropriate error message.
|
||||
/// Returns `Ok(response)` if the response is a `200 OK` response or a
|
||||
/// `202 Accepted` response. Otherwise, creates an appropriate error message.
|
||||
async fn ok_or_error(response: Response) -> Result<Response, Error> {
|
||||
let status = response.status();
|
||||
|
||||
if status == StatusCode::OK {
|
||||
if status == StatusCode::OK
|
||||
|| status == StatusCode::ACCEPTED
|
||||
|| status == StatusCode::NO_CONTENT
|
||||
{
|
||||
Ok(response)
|
||||
} else if let Ok(message) = response.json().await {
|
||||
Err(Error::ServerMessage(message))
|
||||
|
||||
@@ -2,7 +2,20 @@ use account_utils::ZeroizeString;
|
||||
use eth2_keystore::Keystore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use slashing_protection::interchange::Interchange;
|
||||
use types::PublicKeyBytes;
|
||||
use types::{Address, PublicKeyBytes};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct GetFeeRecipientResponse {
|
||||
pub pubkey: PublicKeyBytes,
|
||||
pub ethaddress: Address,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct GetGasLimitResponse {
|
||||
pub pubkey: PublicKeyBytes,
|
||||
#[serde(with = "eth2_serde_utils::quoted_u64")]
|
||||
pub gas_limit: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct AuthResponse {
|
||||
|
||||
@@ -26,6 +26,12 @@ pub struct ValidatorRequest {
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub suggested_fee_recipient: Option<Address>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub gas_limit: Option<u64>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub builder_proposals: Option<bool>,
|
||||
#[serde(with = "eth2_serde_utils::quoted_u64")]
|
||||
pub deposit_gwei: u64,
|
||||
}
|
||||
@@ -49,6 +55,12 @@ pub struct CreatedValidator {
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub suggested_fee_recipient: Option<Address>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub gas_limit: Option<u64>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub builder_proposals: Option<bool>,
|
||||
pub eth1_deposit_tx_data: String,
|
||||
#[serde(with = "eth2_serde_utils::quoted_u64")]
|
||||
pub deposit_gwei: u64,
|
||||
@@ -62,7 +74,15 @@ pub struct PostValidatorsResponseData {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ValidatorPatchRequest {
|
||||
pub enabled: bool,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub enabled: Option<bool>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub gas_limit: Option<u64>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub builder_proposals: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
||||
@@ -70,8 +90,18 @@ pub struct KeystoreValidatorsPostRequest {
|
||||
pub password: ZeroizeString,
|
||||
pub enable: bool,
|
||||
pub keystore: Keystore,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub graffiti: Option<GraffitiString>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub suggested_fee_recipient: Option<Address>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub gas_limit: Option<u64>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub builder_proposals: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
@@ -84,6 +114,12 @@ pub struct Web3SignerValidatorRequest {
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub suggested_fee_recipient: Option<Address>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub gas_limit: Option<u64>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub builder_proposals: Option<bool>,
|
||||
pub voting_public_key: PublicKey,
|
||||
pub url: String,
|
||||
#[serde(default)]
|
||||
@@ -97,3 +133,14 @@ pub struct Web3SignerValidatorRequest {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub client_identity_password: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct UpdateFeeRecipientRequest {
|
||||
pub ethaddress: Address,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct UpdateGasLimitRequest {
|
||||
#[serde(with = "eth2_serde_utils::quoted_u64")]
|
||||
pub gas_limit: u64,
|
||||
}
|
||||
|
||||
@@ -21,17 +21,17 @@ impl ResponseOptional for Result<Response, Error> {
|
||||
/// Trait for extracting the fork name from the headers of a response.
|
||||
pub trait ResponseForkName {
|
||||
#[allow(clippy::result_unit_err)]
|
||||
fn fork_name_from_header(&self) -> Result<Option<ForkName>, ()>;
|
||||
fn fork_name_from_header(&self) -> Result<Option<ForkName>, String>;
|
||||
}
|
||||
|
||||
impl ResponseForkName for Response {
|
||||
fn fork_name_from_header(&self) -> Result<Option<ForkName>, ()> {
|
||||
fn fork_name_from_header(&self) -> Result<Option<ForkName>, String> {
|
||||
self.headers()
|
||||
.get(CONSENSUS_VERSION_HEADER)
|
||||
.map(|fork_name| {
|
||||
fork_name
|
||||
.to_str()
|
||||
.map_err(|_| ())
|
||||
.map_err(|e| e.to_string())
|
||||
.and_then(ForkName::from_str)
|
||||
})
|
||||
.transpose()
|
||||
|
||||
@@ -189,6 +189,14 @@ impl fmt::Display for StateId {
|
||||
#[serde(bound = "T: Serialize + serde::de::DeserializeOwned")]
|
||||
pub struct DutiesResponse<T: Serialize + serde::de::DeserializeOwned> {
|
||||
pub dependent_root: Hash256,
|
||||
pub execution_optimistic: Option<bool>,
|
||||
pub data: T,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(bound = "T: Serialize + serde::de::DeserializeOwned")]
|
||||
pub struct ExecutionOptimisticResponse<T: Serialize + serde::de::DeserializeOwned> {
|
||||
pub execution_optimistic: Option<bool>,
|
||||
pub data: T,
|
||||
}
|
||||
|
||||
@@ -204,6 +212,18 @@ impl<T: Serialize + serde::de::DeserializeOwned> From<T> for GenericResponse<T>
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Serialize + serde::de::DeserializeOwned> GenericResponse<T> {
|
||||
pub fn add_execution_optimistic(
|
||||
self,
|
||||
execution_optimistic: bool,
|
||||
) -> ExecutionOptimisticResponse<T> {
|
||||
ExecutionOptimisticResponse {
|
||||
execution_optimistic: Some(execution_optimistic),
|
||||
data: self.data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize)]
|
||||
#[serde(bound = "T: Serialize")]
|
||||
pub struct GenericResponseRef<'a, T: Serialize> {
|
||||
@@ -216,6 +236,14 @@ impl<'a, T: Serialize> From<&'a T> for GenericResponseRef<'a, T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub struct ExecutionOptimisticForkVersionedResponse<T> {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub version: Option<ForkName>,
|
||||
pub execution_optimistic: Option<bool>,
|
||||
pub data: T,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub struct ForkVersionedResponse<T> {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -495,6 +523,8 @@ pub struct DepositContractData {
|
||||
pub struct ChainHeadData {
|
||||
pub slot: Slot,
|
||||
pub root: Hash256,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub execution_optimistic: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
@@ -522,6 +552,7 @@ pub struct VersionData {
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SyncingData {
|
||||
pub is_syncing: bool,
|
||||
pub is_optimistic: Option<bool>,
|
||||
pub head_slot: Slot,
|
||||
pub sync_distance: Slot,
|
||||
}
|
||||
@@ -651,7 +682,7 @@ pub struct ValidatorAggregateAttestationQuery {
|
||||
pub slot: Slot,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||
pub struct BeaconCommitteeSubscription {
|
||||
#[serde(with = "eth2_serde_utils::quoted_u64")]
|
||||
pub validator_index: u64,
|
||||
@@ -794,6 +825,7 @@ pub struct PeerCount {
|
||||
pub struct SseBlock {
|
||||
pub slot: Slot,
|
||||
pub block: Hash256,
|
||||
pub execution_optimistic: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
|
||||
@@ -801,6 +833,7 @@ pub struct SseFinalizedCheckpoint {
|
||||
pub block: Hash256,
|
||||
pub state: Hash256,
|
||||
pub epoch: Epoch,
|
||||
pub execution_optimistic: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
|
||||
@@ -811,6 +844,7 @@ pub struct SseHead {
|
||||
pub current_duty_dependent_root: Hash256,
|
||||
pub previous_duty_dependent_root: Hash256,
|
||||
pub epoch_transition: bool,
|
||||
pub execution_optimistic: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
|
||||
@@ -823,6 +857,7 @@ pub struct SseChainReorg {
|
||||
pub new_head_block: Hash256,
|
||||
pub new_head_state: Hash256,
|
||||
pub epoch: Epoch,
|
||||
pub execution_optimistic: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
|
||||
@@ -838,6 +873,7 @@ pub struct SseLateHead {
|
||||
pub attestable_delay: Option<Duration>,
|
||||
pub imported_delay: Option<Duration>,
|
||||
pub set_as_head_delay: Option<Duration>,
|
||||
pub execution_optimistic: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Serialize, Clone)]
|
||||
|
||||
@@ -69,7 +69,7 @@ impl Eth2Config {
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct Eth2NetArchiveAndDirectory<'a> {
|
||||
pub name: &'a str,
|
||||
pub unique_id: &'a str,
|
||||
pub config_dir: &'a str,
|
||||
pub genesis_is_known: bool,
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ impl<'a> Eth2NetArchiveAndDirectory<'a> {
|
||||
.parse::<PathBuf>()
|
||||
.expect("should parse manifest dir as path")
|
||||
.join(PREDEFINED_NETWORKS_DIR)
|
||||
.join(self.unique_id)
|
||||
.join(self.config_dir)
|
||||
}
|
||||
|
||||
pub fn genesis_state_archive(&self) -> PathBuf {
|
||||
@@ -96,6 +96,7 @@ const GENESIS_STATE_IS_KNOWN: bool = true;
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct HardcodedNet {
|
||||
pub name: &'static str,
|
||||
pub config_dir: &'static str,
|
||||
pub genesis_is_known: bool,
|
||||
pub config: &'static [u8],
|
||||
pub deploy_block: &'static [u8],
|
||||
@@ -108,15 +109,15 @@ pub struct HardcodedNet {
|
||||
/// It also defines a `include_<title>_file!` macro which provides a wrapper around
|
||||
/// `std::include_bytes`, allowing the inclusion of bytes from the specific testnet directory.
|
||||
macro_rules! define_archive {
|
||||
($name_ident: ident, $name_str: tt, $genesis_is_known: ident) => {
|
||||
($name_ident: ident, $config_dir: tt, $genesis_is_known: ident) => {
|
||||
paste! {
|
||||
#[macro_use]
|
||||
pub mod $name_ident {
|
||||
use super::*;
|
||||
|
||||
pub const ETH2_NET_DIR: Eth2NetArchiveAndDirectory = Eth2NetArchiveAndDirectory {
|
||||
name: $name_str,
|
||||
unique_id: $name_str,
|
||||
name: stringify!($name_ident),
|
||||
config_dir: $config_dir,
|
||||
genesis_is_known: $genesis_is_known,
|
||||
};
|
||||
|
||||
@@ -130,7 +131,7 @@ macro_rules! define_archive {
|
||||
"/",
|
||||
$this_crate::predefined_networks_dir!(),
|
||||
"/",
|
||||
$name_str,
|
||||
$config_dir,
|
||||
"/",
|
||||
$filename
|
||||
))
|
||||
@@ -149,6 +150,7 @@ macro_rules! define_net {
|
||||
|
||||
$this_crate::HardcodedNet {
|
||||
name: ETH2_NET_DIR.name,
|
||||
config_dir: ETH2_NET_DIR.config_dir,
|
||||
genesis_is_known: ETH2_NET_DIR.genesis_is_known,
|
||||
config: $this_crate::$include_file!($this_crate, "../", "config.yaml"),
|
||||
deploy_block: $this_crate::$include_file!($this_crate, "../", "deploy_block.txt"),
|
||||
@@ -164,13 +166,13 @@ macro_rules! define_net {
|
||||
/// - `HARDCODED_NET_NAMES`: a list of the *names* of the networks defined by this macro.
|
||||
#[macro_export]
|
||||
macro_rules! define_nets {
|
||||
($this_crate: ident, $($name_ident: ident, $name_str: tt,)+) => {
|
||||
($this_crate: ident, $($name_ident: ident,)+) => {
|
||||
$this_crate::paste! {
|
||||
$(
|
||||
const [<$name_ident:upper>]: $this_crate::HardcodedNet = $this_crate::define_net!($this_crate, $name_ident, [<include_ $name_ident _file>]);
|
||||
)+
|
||||
const HARDCODED_NETS: &[$this_crate::HardcodedNet] = &[$([<$name_ident:upper>],)+];
|
||||
pub const HARDCODED_NET_NAMES: &[&'static str] = &[$($name_str,)+];
|
||||
pub const HARDCODED_NET_NAMES: &[&'static str] = &[$(stringify!($name_ident),)+];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -197,9 +199,9 @@ macro_rules! define_nets {
|
||||
/// `build.rs` which will unzip the genesis states. Then, that `eth2_network_configs` crate can
|
||||
/// perform the final step of using `std::include_bytes` to bake the files (bytes) into the binary.
|
||||
macro_rules! define_hardcoded_nets {
|
||||
($(($name_ident: ident, $name_str: tt, $genesis_is_known: ident)),+) => {
|
||||
($(($name_ident: ident, $config_dir: tt, $genesis_is_known: ident)),+) => {
|
||||
$(
|
||||
define_archive!($name_ident, $name_str, $genesis_is_known);
|
||||
define_archive!($name_ident, $config_dir, $genesis_is_known);
|
||||
)+
|
||||
|
||||
pub const ETH2_NET_DIRS: &[Eth2NetArchiveAndDirectory<'static>] = &[$($name_ident::ETH2_NET_DIR,)+];
|
||||
@@ -213,7 +215,7 @@ macro_rules! define_hardcoded_nets {
|
||||
#[macro_export]
|
||||
macro_rules! instantiate_hardcoded_nets {
|
||||
($this_crate: ident) => {
|
||||
$this_crate::define_nets!($this_crate, $($name_ident, $name_str,)+);
|
||||
$this_crate::define_nets!($this_crate, $($name_ident,)+);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -234,9 +236,76 @@ macro_rules! define_hardcoded_nets {
|
||||
//
|
||||
// The directory containing the testnet files should match the human-friendly name (element 1).
|
||||
define_hardcoded_nets!(
|
||||
(mainnet, "mainnet", GENESIS_STATE_IS_KNOWN),
|
||||
(prater, "prater", GENESIS_STATE_IS_KNOWN),
|
||||
(gnosis, "gnosis", GENESIS_STATE_IS_KNOWN),
|
||||
(kiln, "kiln", GENESIS_STATE_IS_KNOWN),
|
||||
(ropsten, "ropsten", GENESIS_STATE_IS_KNOWN)
|
||||
(
|
||||
// Network name (must be unique among all networks).
|
||||
mainnet,
|
||||
// The name of the directory in the `eth2_network_config/built_in_network_configs`
|
||||
// directory where the configuration files are located for this network.
|
||||
"mainnet",
|
||||
// Set to `true` if the genesis state can be found in the `built_in_network_configs`
|
||||
// directory.
|
||||
GENESIS_STATE_IS_KNOWN
|
||||
),
|
||||
(
|
||||
// Network name (must be unique among all networks).
|
||||
prater,
|
||||
// The name of the directory in the `eth2_network_config/built_in_network_configs`
|
||||
// directory where the configuration files are located for this network.
|
||||
"prater",
|
||||
// Set to `true` if the genesis state can be found in the `built_in_network_configs`
|
||||
// directory.
|
||||
GENESIS_STATE_IS_KNOWN
|
||||
),
|
||||
(
|
||||
// Network name (must be unique among all networks).
|
||||
goerli,
|
||||
// The name of the directory in the `eth2_network_config/built_in_network_configs`
|
||||
// directory where the configuration files are located for this network.
|
||||
//
|
||||
// The Goerli network is effectively an alias to Prater.
|
||||
"prater",
|
||||
// Set to `true` if the genesis state can be found in the `built_in_network_configs`
|
||||
// directory.
|
||||
GENESIS_STATE_IS_KNOWN
|
||||
),
|
||||
(
|
||||
// Network name (must be unique among all networks).
|
||||
gnosis,
|
||||
// The name of the directory in the `eth2_network_config/built_in_network_configs`
|
||||
// directory where the configuration files are located for this network.
|
||||
"gnosis",
|
||||
// Set to `true` if the genesis state can be found in the `built_in_network_configs`
|
||||
// directory.
|
||||
GENESIS_STATE_IS_KNOWN
|
||||
),
|
||||
(
|
||||
// Network name (must be unique among all networks).
|
||||
kiln,
|
||||
// The name of the directory in the `eth2_network_config/built_in_network_configs`
|
||||
// directory where the configuration files are located for this network.
|
||||
"kiln",
|
||||
// Set to `true` if the genesis state can be found in the `built_in_network_configs`
|
||||
// directory.
|
||||
GENESIS_STATE_IS_KNOWN
|
||||
),
|
||||
(
|
||||
// Network name (must be unique among all networks).
|
||||
ropsten,
|
||||
// The name of the directory in the `eth2_network_config/built_in_network_configs`
|
||||
// directory where the configuration files are located for this network.
|
||||
"ropsten",
|
||||
// Set to `true` if the genesis state can be found in the `built_in_network_configs`
|
||||
// directory.
|
||||
GENESIS_STATE_IS_KNOWN
|
||||
),
|
||||
(
|
||||
// Network name (must be unique among all networks).
|
||||
sepolia,
|
||||
// The name of the directory in the `eth2_network_config/built_in_network_configs`
|
||||
// directory where the configuration files are located for this network.
|
||||
"sepolia",
|
||||
// Set to `true` if the genesis state can be found in the `built_in_network_configs`
|
||||
// directory.
|
||||
GENESIS_STATE_IS_KNOWN
|
||||
)
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ PRESET_BASE: 'mainnet'
|
||||
MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 95000
|
||||
# Mar 11th, 2022, 14:00 UTC
|
||||
MIN_GENESIS_TIME: 1647007200
|
||||
# Gensis fork
|
||||
# Genesis fork
|
||||
GENESIS_FORK_VERSION: 0x70000069
|
||||
# 300 seconds (5 min)
|
||||
GENESIS_DELAY: 300
|
||||
|
||||
@@ -6,8 +6,8 @@ PRESET_BASE: 'mainnet'
|
||||
|
||||
# Transition
|
||||
# ---------------------------------------------------------------
|
||||
# TBD, 2**256-2**10 is a placeholder
|
||||
TERMINAL_TOTAL_DIFFICULTY: 115792089237316195423570985008687907853269984665640564039457584007913129638912
|
||||
# Estimated on Sept 15, 2022
|
||||
TERMINAL_TOTAL_DIFFICULTY: 58750000000000000000000
|
||||
# By default, don't use these params
|
||||
TERMINAL_BLOCK_HASH: 0x0000000000000000000000000000000000000000000000000000000000000000
|
||||
TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: 18446744073709551615
|
||||
@@ -35,7 +35,7 @@ ALTAIR_FORK_VERSION: 0x01000000
|
||||
ALTAIR_FORK_EPOCH: 74240
|
||||
# Merge
|
||||
BELLATRIX_FORK_VERSION: 0x02000000
|
||||
BELLATRIX_FORK_EPOCH: 18446744073709551615
|
||||
BELLATRIX_FORK_EPOCH: 144896 # Sept 6, 2022, 11:34:47am UTC
|
||||
# Sharding
|
||||
SHARDING_FORK_VERSION: 0x03000000
|
||||
SHARDING_FORK_EPOCH: 18446744073709551615
|
||||
|
||||
@@ -7,4 +7,11 @@
|
||||
# Prysm bootnode #1
|
||||
- enr:-Ku4QFmUkNp0g9bsLX2PfVeIyT-9WO-PZlrqZBNtEyofOOfLMScDjaTzGxIb1Ns9Wo5Pm_8nlq-SZwcQfTH2cgO-s88Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpDkvpOTAAAQIP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQLV_jMOIxKbjHFKgrkFvwDvpexo6Nd58TK5k7ss4Vt0IoN1ZHCCG1g
|
||||
# Lighthouse bootnode #1
|
||||
- enr:-LK4QLINdtobGquK7jukLDAKmsrH2ZuHM4k0TklY5jDTD4ZgfxR9weZmo5Jwu81hlKu3qPAvk24xHGBDjYs4o8f1gZ0Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpB53wQoAAAQIP__________gmlkgnY0gmlwhDRN_P6Jc2VjcDI1NmsxoQJuNujTgsJUHUgVZML3pzrtgNtYg7rQ4K1tkWERgl0DdoN0Y3CCIyiDdWRwgiMo
|
||||
- enr:-Ly4QFPk-cTMxZ3jWTafiNblEZkQIXGF2aVzCIGW0uHp6KaEAvBMoctE8S7YU0qZtuS7By0AA4YMfKoN9ls_GJRccVpFh2F0dG5ldHOI__________-EZXRoMpCC9KcrAgAQIIS2AQAAAAAAgmlkgnY0gmlwhKh3joWJc2VjcDI1NmsxoQKrxz8M1IHwJqRIpDqdVW_U1PeixMW5SfnBD-8idYIQrIhzeW5jbmV0cw-DdGNwgiMog3VkcIIjKA
|
||||
# Lighthouse bootnode #2
|
||||
- enr:-L64QJmwSDtaHVgGiqIxJWUtxWg6uLCipsms6j-8BdsOJfTWAs7CLF9HJnVqFE728O-JYUDCxzKvRdeMqBSauHVCMdaCAVWHYXR0bmV0c4j__________4RldGgykIL0pysCABAghLYBAAAAAACCaWSCdjSCaXCEQWxOdolzZWNwMjU2azGhA7Qmod9fK86WidPOzLsn5_8QyzL7ZcJ1Reca7RnD54vuiHN5bmNuZXRzD4N0Y3CCIyiDdWRwgiMo
|
||||
# Nimbus bootstrap nodes
|
||||
- enr:-LK4QMzPq4Q7w5R-rnGQDcI8BYky6oPVBGQTbS1JJLVtNi_8PzBLV7Bdzsoame9nJK5bcJYpGHn4SkaDN2CM6tR5G_4Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpB53wQoAAAQIP__________gmlkgnY0gmlwhAN4yvyJc2VjcDI1NmsxoQKa8Qnp_P2clLIP6VqLKOp_INvEjLszalEnW0LoBZo4YYN0Y3CCI4yDdWRwgiOM
|
||||
- enr:-LK4QLM_pPHa78R8xlcU_s40Y3XhFjlb3kPddW9lRlY67N5qeFE2Wo7RgzDgRs2KLCXODnacVHMFw1SfpsW3R474RZEBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpB53wQoAAAQIP__________gmlkgnY0gmlwhANBY-yJc2VjcDI1NmsxoQNsZkFXgKbTzuxF7uwxlGauTGJelE6HD269CcFlZ_R7A4N0Y3CCI4yDdWRwgiOM
|
||||
# Teku bootnode
|
||||
- enr:-KK4QH0RsNJmIG0EX9LSnVxMvg-CAOr3ZFF92hunU63uE7wcYBjG1cFbUTvEa5G_4nDJkRhUq9q2ck9xY-VX1RtBsruBtIRldGgykIL0pysBABAg__________-CaWSCdjSCaXCEEnXQ0YlzZWNwMjU2azGhA1grTzOdMgBvjNrk-vqWtTZsYQIi0QawrhoZrsn5Hd56g3RjcIIjKIN1ZHCCIyg
|
||||
|
||||
@@ -6,8 +6,7 @@ PRESET_BASE: 'mainnet'
|
||||
|
||||
# Transition
|
||||
# ---------------------------------------------------------------
|
||||
# TBD, 2**256-2**10 is a placeholder
|
||||
TERMINAL_TOTAL_DIFFICULTY: 115792089237316195423570985008687907853269984665640564039457584007913129638912
|
||||
TERMINAL_TOTAL_DIFFICULTY: 10790000
|
||||
# By default, don't use these params
|
||||
TERMINAL_BLOCK_HASH: 0x0000000000000000000000000000000000000000000000000000000000000000
|
||||
TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: 18446744073709551615
|
||||
@@ -35,7 +34,7 @@ ALTAIR_FORK_VERSION: 0x01001020
|
||||
ALTAIR_FORK_EPOCH: 36660
|
||||
# Merge
|
||||
BELLATRIX_FORK_VERSION: 0x02001020
|
||||
BELLATRIX_FORK_EPOCH: 18446744073709551615
|
||||
BELLATRIX_FORK_EPOCH: 112260
|
||||
# Sharding
|
||||
SHARDING_FORK_VERSION: 0x03001020
|
||||
SHARDING_FORK_EPOCH: 18446744073709551615
|
||||
|
||||
@@ -23,7 +23,7 @@ ALTAIR_FORK_EPOCH: 500
|
||||
# Merge
|
||||
BELLATRIX_FORK_VERSION: 0x80000071
|
||||
BELLATRIX_FORK_EPOCH: 750
|
||||
TERMINAL_TOTAL_DIFFICULTY: 43531756765713534
|
||||
TERMINAL_TOTAL_DIFFICULTY: 50000000000000000
|
||||
TERMINAL_BLOCK_HASH: 0x0000000000000000000000000000000000000000000000000000000000000000
|
||||
TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: 18446744073709551615
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
- enr:-Iq4QMCTfIMXnow27baRUb35Q8iiFHSIDBJh6hQM5Axohhf4b6Kr_cOCu0htQ5WvVqKvFgY28893DHAg8gnBAXsAVqmGAX53x8JggmlkgnY0gmlwhLKAlv6Jc2VjcDI1NmsxoQK6S-Cii_KmfFdUJL2TANL3ksaKUnNXvTCv1tLwXs0QgIN1ZHCCIyk
|
||||
@@ -0,0 +1,76 @@
|
||||
# Extends the mainnet preset
|
||||
PRESET_BASE: 'mainnet'
|
||||
CONFIG_NAME: 'sepolia'
|
||||
|
||||
# Genesis
|
||||
# ---------------------------------------------------------------
|
||||
MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 1300
|
||||
# Sunday, June 19, 2022 2:00:00 PM +UTC
|
||||
MIN_GENESIS_TIME: 1655647200
|
||||
GENESIS_FORK_VERSION: 0x90000069
|
||||
GENESIS_DELAY: 86400
|
||||
|
||||
|
||||
# Forking
|
||||
# ---------------------------------------------------------------
|
||||
# Some forks are disabled for now:
|
||||
# - These may be re-assigned to another fork-version later
|
||||
# - Temporarily set to max uint64 value: 2**64 - 1
|
||||
|
||||
# Altair
|
||||
ALTAIR_FORK_VERSION: 0x90000070
|
||||
ALTAIR_FORK_EPOCH: 50
|
||||
|
||||
# Merge
|
||||
BELLATRIX_FORK_VERSION: 0x90000071
|
||||
BELLATRIX_FORK_EPOCH: 100
|
||||
TERMINAL_TOTAL_DIFFICULTY: 17000000000000000
|
||||
TERMINAL_BLOCK_HASH: 0x0000000000000000000000000000000000000000000000000000000000000000
|
||||
TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: 18446744073709551615
|
||||
|
||||
# Capella
|
||||
CAPELLA_FORK_VERSION: 0x03001020
|
||||
CAPELLA_FORK_EPOCH: 18446744073709551615
|
||||
|
||||
# Sharding
|
||||
SHARDING_FORK_VERSION: 0x04001020
|
||||
SHARDING_FORK_EPOCH: 18446744073709551615
|
||||
|
||||
# Time parameters
|
||||
# ---------------------------------------------------------------
|
||||
# 12 seconds
|
||||
SECONDS_PER_SLOT: 12
|
||||
# 14 (estimate from Eth1 mainnet)
|
||||
SECONDS_PER_ETH1_BLOCK: 14
|
||||
# 2**8 (= 256) epochs ~27 hours
|
||||
MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256
|
||||
# 2**8 (= 256) epochs ~27 hours
|
||||
SHARD_COMMITTEE_PERIOD: 256
|
||||
# 2**11 (= 2,048) Eth1 blocks ~8 hours
|
||||
ETH1_FOLLOW_DISTANCE: 2048
|
||||
|
||||
|
||||
# Validator cycle
|
||||
# ---------------------------------------------------------------
|
||||
# 2**2 (= 4)
|
||||
INACTIVITY_SCORE_BIAS: 4
|
||||
# 2**4 (= 16)
|
||||
INACTIVITY_SCORE_RECOVERY_RATE: 16
|
||||
# 2**4 * 10**9 (= 16,000,000,000) Gwei
|
||||
EJECTION_BALANCE: 16000000000
|
||||
# 2**2 (= 4)
|
||||
MIN_PER_EPOCH_CHURN_LIMIT: 4
|
||||
# 2**16 (= 65,536)
|
||||
CHURN_LIMIT_QUOTIENT: 65536
|
||||
|
||||
|
||||
# Fork choice
|
||||
# ---------------------------------------------------------------
|
||||
# 40%
|
||||
PROPOSER_SCORE_BOOST: 40
|
||||
|
||||
# Deposit contract
|
||||
# ---------------------------------------------------------------
|
||||
DEPOSIT_CHAIN_ID: 11155111
|
||||
DEPOSIT_NETWORK_ID: 11155111
|
||||
DEPOSIT_CONTRACT_ADDRESS: 0x7f02C3E3c98b133055B8B348B2Ac625669Ed295D
|
||||
@@ -0,0 +1 @@
|
||||
1273020
|
||||
Binary file not shown.
@@ -256,6 +256,13 @@ mod tests {
|
||||
config.beacon_state::<E>().expect("beacon state can decode");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prater_and_goerli_are_equal() {
|
||||
let goerli = Eth2NetworkConfig::from_hardcoded_net(&GOERLI).unwrap();
|
||||
let prater = Eth2NetworkConfig::from_hardcoded_net(&PRATER).unwrap();
|
||||
assert_eq!(goerli, prater);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hard_coded_nets_work() {
|
||||
for net in HARDCODED_NETS {
|
||||
@@ -275,7 +282,7 @@ mod tests {
|
||||
"{:?}",
|
||||
net.name
|
||||
);
|
||||
assert_eq!(config.config.config_name, Some(net.name.to_string()));
|
||||
assert_eq!(config.config.config_name, Some(net.config_dir.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ impl<T> Fallback<T> {
|
||||
{
|
||||
match error {
|
||||
FallbackError::AllErrored(v) => format!(
|
||||
"All fallback errored: {}",
|
||||
"All fallbacks errored: {}",
|
||||
join(
|
||||
zip(self.servers.iter().map(f), v.iter())
|
||||
.map(|(server, error)| format!("{} => {:?}", server, error)),
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
[package]
|
||||
name = "hashset_delay"
|
||||
version = "0.2.0"
|
||||
authors = ["Sigma Prime <contact@sigmaprime.io>"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
futures = "0.3.7"
|
||||
tokio-util = { version = "0.6.2", features = ["time"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.14.0", features = ["time", "rt-multi-thread", "macros"] }
|
||||
@@ -1,197 +0,0 @@
|
||||
//NOTE: This is just a specific case of a HashMapDelay.
|
||||
// The code has been copied to make unique `insert` and `insert_at` functions.
|
||||
|
||||
/// The default delay for entries, in seconds. This is only used when `insert()` is used to add
|
||||
/// entries.
|
||||
const DEFAULT_DELAY: u64 = 30;
|
||||
|
||||
use futures::prelude::*;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tokio_util::time::delay_queue::{self, DelayQueue};
|
||||
|
||||
pub struct HashSetDelay<K>
|
||||
where
|
||||
K: std::cmp::Eq + std::hash::Hash + std::clone::Clone + Unpin,
|
||||
{
|
||||
/// The given entries.
|
||||
entries: HashMap<K, MapEntry>,
|
||||
/// A queue holding the timeouts of each entry.
|
||||
expirations: DelayQueue<K>,
|
||||
/// The default expiration timeout of an entry.
|
||||
default_entry_timeout: Duration,
|
||||
}
|
||||
|
||||
/// A wrapping around entries that adds the link to the entry's expiration, via a `delay_queue` key.
|
||||
struct MapEntry {
|
||||
/// The expiration key for the entry.
|
||||
key: delay_queue::Key,
|
||||
/// The actual entry.
|
||||
value: Instant,
|
||||
}
|
||||
|
||||
impl<K> Default for HashSetDelay<K>
|
||||
where
|
||||
K: std::cmp::Eq + std::hash::Hash + std::clone::Clone + Unpin,
|
||||
{
|
||||
fn default() -> Self {
|
||||
HashSetDelay::new(Duration::from_secs(DEFAULT_DELAY))
|
||||
}
|
||||
}
|
||||
|
||||
impl<K> HashSetDelay<K>
|
||||
where
|
||||
K: std::cmp::Eq + std::hash::Hash + std::clone::Clone + Unpin,
|
||||
{
|
||||
/// Creates a new instance of `HashSetDelay`.
|
||||
pub fn new(default_entry_timeout: Duration) -> Self {
|
||||
HashSetDelay {
|
||||
entries: HashMap::new(),
|
||||
expirations: DelayQueue::new(),
|
||||
default_entry_timeout,
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert an entry into the mapping. Entries will expire after the `default_entry_timeout`.
|
||||
pub fn insert(&mut self, key: K) {
|
||||
self.insert_at(key, self.default_entry_timeout);
|
||||
}
|
||||
|
||||
/// Inserts an entry that will expire at a given instant. If the entry already exists, the
|
||||
/// timeout is updated.
|
||||
pub fn insert_at(&mut self, key: K, entry_duration: Duration) {
|
||||
if self.contains(&key) {
|
||||
// update the timeout
|
||||
self.update_timeout(&key, entry_duration);
|
||||
} else {
|
||||
let delay_key = self.expirations.insert(key.clone(), entry_duration);
|
||||
let entry = MapEntry {
|
||||
key: delay_key,
|
||||
value: Instant::now() + entry_duration,
|
||||
};
|
||||
self.entries.insert(key, entry);
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a reference to an entry if it exists.
|
||||
///
|
||||
/// Returns None if the entry does not exist.
|
||||
pub fn get(&self, key: &K) -> Option<&Instant> {
|
||||
self.entries.get(key).map(|entry| &entry.value)
|
||||
}
|
||||
|
||||
/// Returns true if the key exists, false otherwise.
|
||||
pub fn contains(&self, key: &K) -> bool {
|
||||
self.entries.contains_key(key)
|
||||
}
|
||||
|
||||
/// Returns the length of the mapping.
|
||||
pub fn len(&self) -> usize {
|
||||
self.entries.len()
|
||||
}
|
||||
|
||||
/// Checks if the mapping is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.entries.is_empty()
|
||||
}
|
||||
|
||||
/// Updates the timeout for a given key. Returns true if the key existed, false otherwise.
|
||||
///
|
||||
/// Panics if the duration is too far in the future.
|
||||
pub fn update_timeout(&mut self, key: &K, timeout: Duration) -> bool {
|
||||
if let Some(entry) = self.entries.get(key) {
|
||||
self.expirations.reset(&entry.key, timeout);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes a key from the map returning the value associated with the key that was in the map.
|
||||
///
|
||||
/// Return false if the key was not in the map.
|
||||
pub fn remove(&mut self, key: &K) -> bool {
|
||||
if let Some(entry) = self.entries.remove(key) {
|
||||
self.expirations.remove(&entry.key);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Retains only the elements specified by the predicate.
|
||||
///
|
||||
/// In other words, remove all pairs `(k, v)` such that `f(&k,&mut v)` returns false.
|
||||
pub fn retain<F: FnMut(&K) -> bool>(&mut self, mut f: F) {
|
||||
let expiration = &mut self.expirations;
|
||||
self.entries.retain(|key, entry| {
|
||||
let result = f(key);
|
||||
if !result {
|
||||
expiration.remove(&entry.key);
|
||||
}
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
/// Removes all entries from the map.
|
||||
pub fn clear(&mut self) {
|
||||
self.entries.clear();
|
||||
self.expirations.clear();
|
||||
}
|
||||
|
||||
/// Returns a vector of referencing all keys in the map.
|
||||
pub fn keys(&self) -> impl Iterator<Item = &K> {
|
||||
self.entries.keys()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K> Stream for HashSetDelay<K>
|
||||
where
|
||||
K: std::cmp::Eq + std::hash::Hash + std::clone::Clone + Unpin,
|
||||
{
|
||||
type Item = Result<K, String>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
|
||||
match self.expirations.poll_expired(cx) {
|
||||
Poll::Ready(Some(Ok(key))) => match self.entries.remove(key.get_ref()) {
|
||||
Some(_) => Poll::Ready(Some(Ok(key.into_inner()))),
|
||||
None => Poll::Ready(Some(Err("Value no longer exists in expirations".into()))),
|
||||
},
|
||||
Poll::Ready(Some(Err(e))) => {
|
||||
Poll::Ready(Some(Err(format!("delay queue error: {:?}", e))))
|
||||
}
|
||||
Poll::Ready(None) => Poll::Ready(None),
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn should_not_panic() {
|
||||
let key = 2u8;
|
||||
|
||||
let mut map = HashSetDelay::default();
|
||||
|
||||
map.insert(key);
|
||||
map.update_timeout(&key, Duration::from_secs(100));
|
||||
|
||||
let fut = |cx: &mut Context| {
|
||||
let _ = map.poll_next_unpin(cx);
|
||||
let _ = map.poll_next_unpin(cx);
|
||||
Poll::Ready(())
|
||||
};
|
||||
|
||||
future::poll_fn(fut).await;
|
||||
|
||||
map.insert(key);
|
||||
map.update_timeout(&key, Duration::from_secs(100));
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
//! This crate provides a single type (its counter-part HashMapDelay has been removed as it
|
||||
//! currently is not in use in lighthouse):
|
||||
//! - `HashSetDelay`
|
||||
//!
|
||||
//! # HashSetDelay
|
||||
//!
|
||||
//! This is similar to a `HashMapDelay` except the mapping maps to the expiry time. This
|
||||
//! allows users to add objects and check their expiry deadlines before the `Stream`
|
||||
//! consumes them.
|
||||
|
||||
mod hashset_delay;
|
||||
pub use crate::hashset_delay::HashSetDelay;
|
||||
@@ -54,14 +54,15 @@
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use prometheus::{HistogramOpts, Opts};
|
||||
use prometheus::{Error, HistogramOpts, Opts};
|
||||
use std::time::Duration;
|
||||
|
||||
use prometheus::core::{Atomic, GenericGauge, GenericGaugeVec};
|
||||
pub use prometheus::{
|
||||
exponential_buckets, linear_buckets,
|
||||
proto::{Metric, MetricFamily, MetricType},
|
||||
Encoder, Gauge, GaugeVec, Histogram, HistogramTimer, HistogramVec, IntCounter, IntCounterVec,
|
||||
IntGauge, IntGaugeVec, Result, TextEncoder,
|
||||
IntGauge, IntGaugeVec, Result, TextEncoder, DEFAULT_BUCKETS,
|
||||
};
|
||||
|
||||
/// Collect all the metrics for reporting.
|
||||
@@ -99,7 +100,17 @@ pub fn try_create_float_gauge(name: &str, help: &str) -> Result<Gauge> {
|
||||
/// Attempts to create a `Histogram`, returning `Err` if the registry does not accept the counter
|
||||
/// (potentially due to naming conflict).
|
||||
pub fn try_create_histogram(name: &str, help: &str) -> Result<Histogram> {
|
||||
let opts = HistogramOpts::new(name, help);
|
||||
try_create_histogram_with_buckets(name, help, Ok(DEFAULT_BUCKETS.to_vec()))
|
||||
}
|
||||
|
||||
/// Attempts to create a `Histogram` with specified buckets, returning `Err` if the registry does not accept the counter
|
||||
/// (potentially due to naming conflict) or no valid buckets are provided.
|
||||
pub fn try_create_histogram_with_buckets(
|
||||
name: &str,
|
||||
help: &str,
|
||||
buckets: Result<Vec<f64>>,
|
||||
) -> Result<Histogram> {
|
||||
let opts = HistogramOpts::new(name, help).buckets(buckets?);
|
||||
let histogram = Histogram::with_opts(opts)?;
|
||||
prometheus::register(Box::new(histogram.clone()))?;
|
||||
Ok(histogram)
|
||||
@@ -112,7 +123,18 @@ pub fn try_create_histogram_vec(
|
||||
help: &str,
|
||||
label_names: &[&str],
|
||||
) -> Result<HistogramVec> {
|
||||
let opts = HistogramOpts::new(name, help);
|
||||
try_create_histogram_vec_with_buckets(name, help, Ok(DEFAULT_BUCKETS.to_vec()), label_names)
|
||||
}
|
||||
|
||||
/// Attempts to create a `HistogramVec` with specified buckets, returning `Err` if the registry does not accept the counter
|
||||
/// (potentially due to naming conflict) or no valid buckets are provided.
|
||||
pub fn try_create_histogram_vec_with_buckets(
|
||||
name: &str,
|
||||
help: &str,
|
||||
buckets: Result<Vec<f64>>,
|
||||
label_names: &[&str],
|
||||
) -> Result<HistogramVec> {
|
||||
let opts = HistogramOpts::new(name, help).buckets(buckets?);
|
||||
let histogram_vec = HistogramVec::new(opts, label_names)?;
|
||||
prometheus::register(Box::new(histogram_vec.clone()))?;
|
||||
Ok(histogram_vec)
|
||||
@@ -357,3 +379,28 @@ fn duration_to_f64(duration: Duration) -> f64 {
|
||||
let nanos = f64::from(duration.subsec_nanos()) / 1e9;
|
||||
duration.as_secs() as f64 + nanos
|
||||
}
|
||||
|
||||
/// Create buckets using divisors of 10 multiplied by powers of 10, e.g.,
|
||||
/// […, 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, …]
|
||||
///
|
||||
/// The buckets go from `10^min_power` to `5 × 10^max_power`, inclusively.
|
||||
/// The total number of buckets is `3 * (max_power - min_power + 1)`.
|
||||
///
|
||||
/// assert_eq!(vec![0.1, 0.2, 0.5, 1.0, 2.0, 5.0, 10.0, 20.0, 50.0], decimal_buckets(-1, 1));
|
||||
/// assert_eq!(vec![1.0, 2.0, 5.0, 10.0, 20.0, 50.0, 100.0, 200.0, 500.0], decimal_buckets(0, 2));
|
||||
pub fn decimal_buckets(min_power: i32, max_power: i32) -> Result<Vec<f64>> {
|
||||
if max_power < min_power {
|
||||
return Err(Error::Msg(format!(
|
||||
"decimal_buckets min_power needs to be <= max_power, given {} and {}",
|
||||
min_power, max_power
|
||||
)));
|
||||
}
|
||||
|
||||
let mut buckets = Vec::with_capacity(3 * (max_power - min_power + 1) as usize);
|
||||
for n in min_power..=max_power {
|
||||
for m in &[1f64, 2f64, 5f64] {
|
||||
buckets.push(m * 10f64.powi(n))
|
||||
}
|
||||
}
|
||||
Ok(buckets)
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ pub const VERSION: &str = git_version!(
|
||||
// NOTE: using --match instead of --exclude for compatibility with old Git
|
||||
"--match=thiswillnevermatchlol"
|
||||
],
|
||||
prefix = "Lighthouse/v2.2.1-",
|
||||
fallback = "Lighthouse/v2.2.1"
|
||||
prefix = "Lighthouse/v3.1.0-",
|
||||
fallback = "Lighthouse/v3.1.0"
|
||||
);
|
||||
|
||||
/// Returns `VERSION`, but with platform information appended to the end.
|
||||
@@ -37,8 +37,9 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn version_formatting() {
|
||||
let re = Regex::new(r"^Lighthouse/v[0-9]+\.[0-9]+\.[0-9]+(-rc.[0-9])?-[[:xdigit:]]{7}\+?$")
|
||||
.unwrap();
|
||||
let re =
|
||||
Regex::new(r"^Lighthouse/v[0-9]+\.[0-9]+\.[0-9]+(-rc.[0-9])?(-[[:xdigit:]]{7})?\+?$")
|
||||
.unwrap();
|
||||
assert!(
|
||||
re.is_match(VERSION),
|
||||
"version doesn't match regex: {}",
|
||||
|
||||
@@ -16,7 +16,7 @@ use types::*;
|
||||
pub use types::ProcessType;
|
||||
|
||||
/// Duration after which we collect and send metrics to remote endpoint.
|
||||
pub const UPDATE_DURATION: u64 = 60;
|
||||
pub const DEFAULT_UPDATE_DURATION: u64 = 60;
|
||||
/// Timeout for HTTP requests.
|
||||
pub const TIMEOUT_DURATION: u64 = 5;
|
||||
|
||||
@@ -55,6 +55,8 @@ pub struct Config {
|
||||
/// Path for the cold database required for fetching beacon db size metrics.
|
||||
/// Note: not relevant for validator and system metrics.
|
||||
pub freezer_db_path: Option<PathBuf>,
|
||||
/// User-defined update period in seconds.
|
||||
pub update_period_secs: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -64,6 +66,7 @@ pub struct MonitoringHttpClient {
|
||||
db_path: Option<PathBuf>,
|
||||
/// Path to the freezer database.
|
||||
freezer_db_path: Option<PathBuf>,
|
||||
update_period: Duration,
|
||||
monitoring_endpoint: SensitiveUrl,
|
||||
log: slog::Logger,
|
||||
}
|
||||
@@ -74,6 +77,9 @@ impl MonitoringHttpClient {
|
||||
client: reqwest::Client::new(),
|
||||
db_path: config.db_path.clone(),
|
||||
freezer_db_path: config.freezer_db_path.clone(),
|
||||
update_period: Duration::from_secs(
|
||||
config.update_period_secs.unwrap_or(DEFAULT_UPDATE_DURATION),
|
||||
),
|
||||
monitoring_endpoint: SensitiveUrl::parse(&config.monitoring_endpoint)
|
||||
.map_err(|e| format!("Invalid monitoring endpoint: {:?}", e))?,
|
||||
log,
|
||||
@@ -100,10 +106,15 @@ impl MonitoringHttpClient {
|
||||
let mut interval = interval_at(
|
||||
// Have some initial delay for the metrics to get initialized
|
||||
Instant::now() + Duration::from_secs(25),
|
||||
Duration::from_secs(UPDATE_DURATION),
|
||||
self.update_period,
|
||||
);
|
||||
|
||||
info!(self.log, "Starting monitoring api"; "endpoint" => %self.monitoring_endpoint);
|
||||
info!(
|
||||
self.log,
|
||||
"Starting monitoring API";
|
||||
"endpoint" => %self.monitoring_endpoint,
|
||||
"update_period" => format!("{}s", self.update_period.as_secs()),
|
||||
);
|
||||
|
||||
let update_future = async move {
|
||||
loop {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -9,6 +10,12 @@ pub enum SensitiveError {
|
||||
RedactError(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for SensitiveError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper around Url which provides a custom `Display` implementation to protect user secrets.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct SensitiveUrl {
|
||||
@@ -39,7 +46,7 @@ impl Serialize for SensitiveUrl {
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.full.to_string())
|
||||
serializer.serialize_str(self.full.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +61,14 @@ impl<'de> Deserialize<'de> for SensitiveUrl {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SensitiveUrl {
|
||||
type Err = SensitiveError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Self::parse(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl SensitiveUrl {
|
||||
pub fn parse(url: &str) -> Result<Self, SensitiveError> {
|
||||
let surl = Url::parse(url).map_err(SensitiveError::ParseError)?;
|
||||
|
||||
@@ -5,7 +5,7 @@ authors = ["Sigma Prime <contact@sigmaprime.io>"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.14.0", features = ["rt-multi-thread"] }
|
||||
tokio = { version = "1.14.0", features = ["rt-multi-thread", "macros"] }
|
||||
slog = "2.5.2"
|
||||
futures = "0.3.7"
|
||||
exit-future = "0.2.0"
|
||||
|
||||
@@ -7,6 +7,8 @@ use slog::{crit, debug, o, trace};
|
||||
use std::sync::Weak;
|
||||
use tokio::runtime::{Handle, Runtime};
|
||||
|
||||
pub use tokio::task::JoinHandle;
|
||||
|
||||
/// Provides a reason when Lighthouse is shut down.
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum ShutdownReason {
|
||||
@@ -312,6 +314,61 @@ impl TaskExecutor {
|
||||
Some(future)
|
||||
}
|
||||
|
||||
/// Block the current (non-async) thread on the completion of some future.
|
||||
///
|
||||
/// ## Warning
|
||||
///
|
||||
/// This method is "dangerous" since calling it from an async thread will result in a panic! Any
|
||||
/// use of this outside of testing should be very deeply considered as Lighthouse has been
|
||||
/// burned by this function in the past.
|
||||
///
|
||||
/// Determining what is an "async thread" is rather challenging; just because a function isn't
|
||||
/// marked as `async` doesn't mean it's not being called from an `async` function or there isn't
|
||||
/// a `tokio` context present in the thread-local storage due to some `rayon` funkiness. Talk to
|
||||
/// @paulhauner if you plan to use this function in production. He has put metrics in here to
|
||||
/// track any use of it, so don't think you can pull a sneaky one on him.
|
||||
pub fn block_on_dangerous<F: Future>(
|
||||
&self,
|
||||
future: F,
|
||||
name: &'static str,
|
||||
) -> Option<F::Output> {
|
||||
let timer = metrics::start_timer_vec(&metrics::BLOCK_ON_TASKS_HISTOGRAM, &[name]);
|
||||
metrics::inc_gauge_vec(&metrics::BLOCK_ON_TASKS_COUNT, &[name]);
|
||||
let log = self.log.clone();
|
||||
let handle = self.handle()?;
|
||||
let exit = self.exit.clone();
|
||||
|
||||
debug!(
|
||||
log,
|
||||
"Starting block_on task";
|
||||
"name" => name
|
||||
);
|
||||
|
||||
handle.block_on(async {
|
||||
let output = tokio::select! {
|
||||
output = future => {
|
||||
debug!(
|
||||
log,
|
||||
"Completed block_on task";
|
||||
"name" => name
|
||||
);
|
||||
Some(output)
|
||||
},
|
||||
_ = exit => {
|
||||
debug!(
|
||||
log,
|
||||
"Cancelled block_on task";
|
||||
"name" => name,
|
||||
);
|
||||
None
|
||||
}
|
||||
};
|
||||
metrics::dec_gauge_vec(&metrics::BLOCK_ON_TASKS_COUNT, &[name]);
|
||||
drop(timer);
|
||||
output
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a `Handle` to the current runtime.
|
||||
pub fn handle(&self) -> Option<Handle> {
|
||||
self.handle_provider.handle()
|
||||
|
||||
@@ -18,6 +18,16 @@ lazy_static! {
|
||||
"Time taken by blocking tasks",
|
||||
&["blocking_task_hist"]
|
||||
);
|
||||
pub static ref BLOCK_ON_TASKS_COUNT: Result<IntGaugeVec> = try_create_int_gauge_vec(
|
||||
"block_on_tasks_count",
|
||||
"Total number of block_on_dangerous tasks spawned",
|
||||
&["name"]
|
||||
);
|
||||
pub static ref BLOCK_ON_TASKS_HISTOGRAM: Result<HistogramVec> = try_create_histogram_vec(
|
||||
"block_on_tasks_histogram",
|
||||
"Time taken by block_on_dangerous tasks",
|
||||
&["name"]
|
||||
);
|
||||
pub static ref TASKS_HISTOGRAM: Result<HistogramVec> = try_create_histogram_vec(
|
||||
"async_tasks_time_histogram",
|
||||
"Time taken by async tasks",
|
||||
|
||||
@@ -205,8 +205,13 @@ pub async fn handle_rejection(err: warp::Rejection) -> Result<impl warp::Reply,
|
||||
code = StatusCode::FORBIDDEN;
|
||||
message = format!("FORBIDDEN: Invalid auth token: {}", e.0);
|
||||
} else if let Some(e) = err.find::<warp::reject::MissingHeader>() {
|
||||
code = StatusCode::BAD_REQUEST;
|
||||
message = format!("BAD_REQUEST: missing {} header", e.name());
|
||||
if e.name().eq("Authorization") {
|
||||
code = StatusCode::UNAUTHORIZED;
|
||||
message = "UNAUTHORIZED: missing Authorization header".to_string();
|
||||
} else {
|
||||
code = StatusCode::BAD_REQUEST;
|
||||
message = format!("BAD_REQUEST: missing {} header", e.name());
|
||||
}
|
||||
} else if let Some(e) = err.find::<warp::reject::InvalidHeader>() {
|
||||
code = StatusCode::BAD_REQUEST;
|
||||
message = format!("BAD_REQUEST: invalid {} header", e.name());
|
||||
|
||||
Reference in New Issue
Block a user