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:
Mac L 2022-05-16 01:59:46 +00:00
parent bcdd960ab1
commit e81a428ffb
3 changed files with 0 additions and 442 deletions

View File

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

View File

@ -1 +0,0 @@
pub mod block_efficiency;

View File

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