Remove lcli block packing analysis (#3179)
## Proposed Changes Remove the `lcli` code which performs block packing analysis. The `lcli` code has been deprecated by a more performant version available in the HTTP API added in #2879. ## Additional Info Any applications depending on the `lcli` code should migrate to the version in the HTTP API. The only feature which is unavailable in the API version is an estimate of live/dead validators. This was originally used to determine a closer approximation of block packing efficiencies since offline validators have a disproportionate impact on efficiencies. However the implimentation in `lcli` is a poor approximation which cannot account for a multitude of factors. It is recommended to simply calculate relative efficiencies instead or use a more advanced method of determining live/dead validators.
This commit is contained in:
parent
bcdd960ab1
commit
e81a428ffb
@ -1,379 +0,0 @@
|
||||
use clap::ArgMatches;
|
||||
use eth2::types::*;
|
||||
use eth2::{BeaconNodeHttpClient, Timeouts};
|
||||
use log::{error, info};
|
||||
use sensitive_url::SensitiveUrl;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
type CommitteePosition = usize;
|
||||
type Committee = u64;
|
||||
type InclusionDistance = u64;
|
||||
type ValidatorIndex = u64;
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
struct UniqueAttestation {
|
||||
slot: Slot,
|
||||
committee_index: Committee,
|
||||
committee_position: CommitteePosition,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
struct ProposerInfo {
|
||||
proposer_index: ValidatorIndex,
|
||||
graffiti: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CommitteeInfo {
|
||||
number_of_committees: usize,
|
||||
validators_per_committee: usize,
|
||||
}
|
||||
|
||||
async fn get_validator_set_len<T: EthSpec>(
|
||||
node: &BeaconNodeHttpClient,
|
||||
slot: Slot,
|
||||
) -> Result<usize, String> {
|
||||
let active_validator_set = node
|
||||
.get_beacon_states_validators(StateId::Slot(slot), None, None)
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))?
|
||||
.ok_or_else(|| "No validators found".to_string())?
|
||||
.data;
|
||||
Ok(active_validator_set
|
||||
.iter()
|
||||
.filter(|x| x.status.superstatus() == ValidatorStatus::Active)
|
||||
.count())
|
||||
}
|
||||
|
||||
async fn get_block_attestations_set<'a, T: EthSpec>(
|
||||
node: &BeaconNodeHttpClient,
|
||||
slot: Slot,
|
||||
) -> Result<Option<(HashMap<UniqueAttestation, InclusionDistance>, ProposerInfo)>, String> {
|
||||
let mut unique_attestations_set: HashMap<UniqueAttestation, InclusionDistance> = HashMap::new();
|
||||
|
||||
let option_block: Option<ForkVersionedResponse<SignedBeaconBlock<T>>> = node
|
||||
.get_beacon_blocks(BlockId::Slot(slot))
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
|
||||
let block = match option_block {
|
||||
Some(block) => block.data,
|
||||
// No block was proposed for this slot.
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let proposer = ProposerInfo {
|
||||
proposer_index: block.message().proposer_index(),
|
||||
graffiti: block
|
||||
.message()
|
||||
.body()
|
||||
.graffiti()
|
||||
.as_utf8_lossy()
|
||||
// Remove commas and apostropes from graffiti to ensure correct CSV format.
|
||||
.replace(',', "")
|
||||
.replace('"', "")
|
||||
.replace('\'', ""),
|
||||
};
|
||||
|
||||
let attestations = block.message().body().attestations();
|
||||
|
||||
for attestation in attestations.iter() {
|
||||
for (position, voted) in attestation.aggregation_bits.iter().enumerate() {
|
||||
if voted {
|
||||
let unique_attestation = UniqueAttestation {
|
||||
slot: attestation.data.slot,
|
||||
committee_index: attestation.data.index,
|
||||
committee_position: position,
|
||||
};
|
||||
let inclusion_distance: u64 = slot
|
||||
.as_u64()
|
||||
.checked_sub(attestation.data.slot.as_u64())
|
||||
.ok_or_else(|| "Attestation slot is larger than the block slot".to_string())?;
|
||||
unique_attestations_set.insert(unique_attestation, inclusion_distance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some((unique_attestations_set, proposer)))
|
||||
}
|
||||
|
||||
async fn get_epoch_committee_data<T: EthSpec>(
|
||||
node: &BeaconNodeHttpClient,
|
||||
epoch: Epoch,
|
||||
) -> Result<(Vec<CommitteeData>, CommitteeInfo), String> {
|
||||
let committee_data = node
|
||||
.get_beacon_states_committees(
|
||||
StateId::Slot(Epoch::start_slot(epoch, T::slots_per_epoch())),
|
||||
None,
|
||||
None,
|
||||
Some(epoch),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))?
|
||||
.ok_or_else(|| "No committees found".to_string())?
|
||||
.data;
|
||||
|
||||
let committee_info = CommitteeInfo {
|
||||
number_of_committees: committee_data.len(),
|
||||
// FIXME: validators.len() isn't consistent between different committees in the
|
||||
// same epoch.
|
||||
validators_per_committee: committee_data[0].validators.len(),
|
||||
};
|
||||
|
||||
Ok((committee_data, committee_info))
|
||||
}
|
||||
|
||||
pub async fn run<T: EthSpec>(matches: &ArgMatches<'_>) -> Result<(), String> {
|
||||
const SECONDS_PER_SLOT: Duration = Duration::from_secs(12);
|
||||
let output_path: PathBuf = clap_utils::parse_required(matches, "output")?;
|
||||
let start_epoch: Epoch = clap_utils::parse_required(matches, "start-epoch")?;
|
||||
let offline_window: u64 = matches
|
||||
.value_of("offline-window")
|
||||
.unwrap_or("3")
|
||||
.parse()
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
let calculate_offline_vals = offline_window != 0;
|
||||
|
||||
if start_epoch == 0 {
|
||||
return Err("start_epoch cannot be 0.".to_string());
|
||||
}
|
||||
let initialization_epoch: Epoch = start_epoch - 1;
|
||||
let end_epoch: Epoch = clap_utils::parse_required(matches, "end-epoch")?;
|
||||
|
||||
if end_epoch < start_epoch {
|
||||
return Err("start_epoch must be smaller than end_epoch".to_string());
|
||||
}
|
||||
|
||||
let mut available_attestations_set: HashSet<UniqueAttestation> = HashSet::new();
|
||||
let mut included_attestations_set: HashMap<UniqueAttestation, InclusionDistance> =
|
||||
HashMap::new();
|
||||
|
||||
// Build validator set HashMap<Index, EpochOfMostRecentAttestation)>
|
||||
// This allows a 'best effort' attempt to normalize block efficiencies.
|
||||
let mut online_validator_set: HashMap<ValidatorIndex, Epoch> = HashMap::new();
|
||||
|
||||
let mut proposer_map: HashMap<Slot, ProposerInfo> = HashMap::new();
|
||||
|
||||
let mut file = File::options()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(output_path)
|
||||
.map_err(|e| format!("Unable to open file: {}", e))?;
|
||||
|
||||
write!(file, "slot,proposer,available,included,offline,graffiti").unwrap();
|
||||
|
||||
// Initialize API.
|
||||
let endpoint = matches
|
||||
.value_of("endpoint")
|
||||
.unwrap_or("http://localhost:5052/");
|
||||
let node = BeaconNodeHttpClient::new(
|
||||
SensitiveUrl::parse(endpoint).map_err(|_| "Unable to parse endpoint.".to_string())?,
|
||||
Timeouts::set_all(SECONDS_PER_SLOT),
|
||||
);
|
||||
|
||||
// Check we can connect to the API.
|
||||
let version =
|
||||
match node.get_node_version().await {
|
||||
Ok(version_response) => version_response.data.version,
|
||||
Err(_) => return Err(
|
||||
"Error: A working HTTP API server is required. Ensure one is synced and available."
|
||||
.to_string(),
|
||||
),
|
||||
};
|
||||
|
||||
// Check we are synced past the required epoch range.
|
||||
let head_slot_synced =
|
||||
match node.get_node_syncing().await {
|
||||
Ok(synced_response) => synced_response.data.head_slot,
|
||||
Err(_) => return Err(
|
||||
"Error: A working HTTP API server is required. Ensure one is synced and available."
|
||||
.to_string(),
|
||||
),
|
||||
};
|
||||
|
||||
if head_slot_synced < end_epoch.end_slot(T::slots_per_epoch()) {
|
||||
return Err(
|
||||
"Error: The beacon node is not sufficiently synced. Make sure your node is synced \
|
||||
past the desired `end-epoch` and that you aren't requesting future epochs."
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
// Whether the beacon node is responding to requests. This is helpful for logging.
|
||||
let mut connected: bool = true;
|
||||
info!("Connected to endpoint at: {:?} - {:?}", endpoint, version);
|
||||
|
||||
// Loop over epochs.
|
||||
for epoch in (initialization_epoch.as_u64()..=end_epoch.as_u64()).map(Epoch::new) {
|
||||
if epoch != initialization_epoch {
|
||||
info!("Analysing epoch {}...", epoch);
|
||||
} else {
|
||||
info!("Initializing...");
|
||||
}
|
||||
let mut epoch_data: Vec<(Slot, Option<ProposerInfo>, usize, usize)> = Vec::new();
|
||||
|
||||
// Current epochs available attestations set
|
||||
let (committee_data, committee_info) = loop {
|
||||
if let Ok(committee_result) = get_epoch_committee_data::<T>(&node, epoch).await {
|
||||
if !connected {
|
||||
info!("Connected to endpoint at: {:?} - {:?}", endpoint, version);
|
||||
connected = true;
|
||||
}
|
||||
break committee_result;
|
||||
}
|
||||
|
||||
if connected {
|
||||
connected = false;
|
||||
error!("A request to the Beacon Node API failed. Check connectivity.");
|
||||
}
|
||||
};
|
||||
|
||||
// Ensure available attestations don't exceed the possible amount of attestations
|
||||
// as determined by the committee size/number.
|
||||
// This is unlikely to happen, but not impossible.
|
||||
let max_possible_attesations =
|
||||
committee_info.validators_per_committee * committee_info.number_of_committees;
|
||||
|
||||
// Get number of active validators.
|
||||
let active_validators =
|
||||
get_validator_set_len::<T>(&node, epoch.start_slot(T::slots_per_epoch())).await?;
|
||||
|
||||
for slot in epoch.slot_iter(T::slots_per_epoch()) {
|
||||
// Get all included attestations.
|
||||
let block_result = loop {
|
||||
if let Ok(block_result) = get_block_attestations_set::<T>(&node, slot).await {
|
||||
if !connected {
|
||||
info!("Connected to endpoint at: {:?} - {:?}", endpoint, version);
|
||||
connected = true;
|
||||
}
|
||||
break block_result;
|
||||
};
|
||||
if connected {
|
||||
connected = false;
|
||||
error!("A request to the Beacon Node API failed. Check connectivity.");
|
||||
}
|
||||
};
|
||||
let (mut attestations_in_block, proposer) = match block_result {
|
||||
Some(output) => (output.0, Some(output.1)),
|
||||
None => (HashMap::new(), None),
|
||||
};
|
||||
|
||||
// Insert block proposer into proposer_map.
|
||||
if let Some(proposer_info) = proposer {
|
||||
proposer_map.insert(slot, proposer_info.clone());
|
||||
}
|
||||
|
||||
// Remove duplicate attestations.
|
||||
attestations_in_block.retain(|x, _| included_attestations_set.get(x).is_none());
|
||||
|
||||
// Add them to the set.
|
||||
included_attestations_set.extend(attestations_in_block.clone());
|
||||
|
||||
// Remove expired available attestations.
|
||||
available_attestations_set.retain(|x| x.slot >= (slot.as_u64().saturating_sub(32)));
|
||||
|
||||
// Don't write data from the initialization epoch.
|
||||
if epoch != initialization_epoch {
|
||||
let included = attestations_in_block.len();
|
||||
|
||||
let available = if max_possible_attesations < available_attestations_set.len() {
|
||||
max_possible_attesations
|
||||
} else {
|
||||
available_attestations_set.len()
|
||||
};
|
||||
|
||||
// Get proposer information.
|
||||
let proposer = proposer_map.get(&slot).cloned();
|
||||
|
||||
// Store slot data.
|
||||
epoch_data.push((slot, proposer, available, included));
|
||||
}
|
||||
|
||||
// Included attestations are no longer available.
|
||||
for new_attestation in &attestations_in_block {
|
||||
available_attestations_set.remove(new_attestation.0);
|
||||
}
|
||||
|
||||
// Get all available attestations.
|
||||
for committee in &committee_data {
|
||||
if committee.slot == slot {
|
||||
for position in 0..committee.validators.len() {
|
||||
let unique_attestation = UniqueAttestation {
|
||||
slot: committee.slot,
|
||||
committee_index: committee.index,
|
||||
committee_position: position,
|
||||
};
|
||||
available_attestations_set.insert(unique_attestation.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut offline = "None".to_string();
|
||||
if calculate_offline_vals {
|
||||
// Get all online validators for the epoch.
|
||||
for committee in &committee_data {
|
||||
for position in 0..committee.validators.len() {
|
||||
let unique_attestation = UniqueAttestation {
|
||||
slot: committee.slot,
|
||||
committee_index: committee.index,
|
||||
committee_position: position,
|
||||
};
|
||||
let index = committee.validators.get(position).ok_or_else(|| {
|
||||
"Error parsing validator indices from committee data".to_string()
|
||||
})?;
|
||||
|
||||
if included_attestations_set.get(&unique_attestation).is_some() {
|
||||
online_validator_set.insert(*index, epoch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate offline validators.
|
||||
offline = if epoch >= start_epoch + offline_window {
|
||||
active_validators
|
||||
.checked_sub(online_validator_set.len())
|
||||
.ok_or_else(|| "Online set is greater than active set".to_string())?
|
||||
.to_string()
|
||||
} else {
|
||||
"None".to_string()
|
||||
};
|
||||
}
|
||||
|
||||
// Write epoch data.
|
||||
for (slot, proposer, available, included) in epoch_data {
|
||||
let proposer_index = proposer
|
||||
.clone()
|
||||
.map_or("None".to_string(), |x| x.proposer_index.to_string());
|
||||
let graffiti = proposer.map_or("None".to_string(), |x| x.graffiti);
|
||||
write!(
|
||||
file,
|
||||
"\n{},{},{},{},{},{}",
|
||||
slot, proposer_index, available, included, offline, graffiti
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Free some memory by removing included attestations older than 1 epoch.
|
||||
included_attestations_set.retain(|x, _| {
|
||||
x.slot >= Epoch::new(epoch.as_u64().saturating_sub(1)).start_slot(T::slots_per_epoch())
|
||||
});
|
||||
|
||||
if calculate_offline_vals {
|
||||
// Remove old validators from the validator set which are outside the offline window.
|
||||
online_validator_set.retain(|_, x| {
|
||||
*x >= Epoch::new(
|
||||
epoch
|
||||
.as_u64()
|
||||
.saturating_sub(offline_window.saturating_sub(1)),
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
@ -1 +0,0 @@
|
||||
pub mod block_efficiency;
|
@ -5,7 +5,6 @@ mod check_deposit_data;
|
||||
mod create_payload_header;
|
||||
mod deploy_deposit_contract;
|
||||
mod eth1_genesis;
|
||||
mod etl;
|
||||
mod generate_bootnode_enr;
|
||||
mod insecure_validators;
|
||||
mod interop_genesis;
|
||||
@ -599,63 +598,6 @@ fn main() {
|
||||
.help("The number of nodes to divide the validator keys to"),
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("etl-block-efficiency")
|
||||
.about(
|
||||
"Performs ETL analysis of block efficiency. Requires a Beacon Node API to \
|
||||
extract data from.",
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("endpoint")
|
||||
.long("endpoint")
|
||||
.short("e")
|
||||
.takes_value(true)
|
||||
.default_value("http://localhost:5052")
|
||||
.help(
|
||||
"The endpoint of the Beacon Node API."
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("output")
|
||||
.long("output")
|
||||
.short("o")
|
||||
.takes_value(true)
|
||||
.help("The path of the output data in CSV file.")
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("start-epoch")
|
||||
.long("start-epoch")
|
||||
.takes_value(true)
|
||||
.help(
|
||||
"The first epoch in the range of epochs to be evaluated. Use with \
|
||||
--end-epoch.",
|
||||
)
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("end-epoch")
|
||||
.long("end-epoch")
|
||||
.takes_value(true)
|
||||
.help(
|
||||
"The last epoch in the range of epochs to be evaluated. Use with \
|
||||
--start-epoch.",
|
||||
)
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("offline-window")
|
||||
.long("offline-window")
|
||||
.takes_value(true)
|
||||
.default_value("3")
|
||||
.help(
|
||||
"If a validator does not submit an attestion within this many epochs, \
|
||||
they are deemed offline. For example, for a offline window of 3, if a \
|
||||
validator does not attest in epochs 4, 5 or 6, it is deemed offline \
|
||||
during epoch 6. A value of 0 will skip these checks."
|
||||
)
|
||||
)
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let result = matches
|
||||
@ -737,10 +679,6 @@ fn run<T: EthSpec>(
|
||||
.map_err(|e| format!("Failed to run generate-bootnode-enr command: {}", e)),
|
||||
("insecure-validators", Some(matches)) => insecure_validators::run(matches)
|
||||
.map_err(|e| format!("Failed to run insecure-validators command: {}", e)),
|
||||
("etl-block-efficiency", Some(matches)) => env
|
||||
.runtime()
|
||||
.block_on(etl::block_efficiency::run::<T>(matches))
|
||||
.map_err(|e| format!("Failed to run etl-block_efficiency: {}", e)),
|
||||
(other, _) => Err(format!("Unknown subcommand {}. See --help.", other)),
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user