Serve Bellatrix preset in BN API (#3425)

## Issue Addressed

Resolves #3388
Resolves #2638

## Proposed Changes

- Return the `BellatrixPreset` on `/eth/v1/config/spec` by default.
- Allow users to opt out of this by providing `--http-spec-fork=altair` (unless there's a Bellatrix fork epoch set).
- Add the Altair constants from #2638 and make serving the constants non-optional (the `http-disable-legacy-spec` flag is deprecated).
- Modify the VC to only read the `Config` and not to log extra fields. This prevents it from having to muck around parsing the `ConfigAndPreset` fields it doesn't need.

## Additional Info

This change is backwards-compatible for the VC and the BN, but is marked as a breaking change for the removal of `--http-disable-legacy-spec`.

I tried making `Config` a `superstruct` too, but getting the automatic decoding to work was a huge pain and was going to require a lot of hacks, so I gave up in favour of keeping the default-based approach we have now.
This commit is contained in:
Michael Sproul
2022-08-10 07:52:59 +00:00
parent c25934956b
commit 4e05f19fb5
19 changed files with 167 additions and 142 deletions

View File

@@ -803,6 +803,10 @@ impl Default for ChainSpec {
}
/// Exact implementation of the *config* object from the Ethereum spec (YAML/JSON).
///
/// Fields relevant to hard forks after Altair should be optional so that we can continue
/// to parse Altair configs. This default approach turns out to be much simpler than trying to
/// make `Config` a superstruct because of the hassle of deserializing an untagged enum.
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(rename_all = "UPPERCASE")]
pub struct Config {
@@ -813,17 +817,13 @@ pub struct Config {
#[serde(default)]
pub preset_base: String,
// TODO(merge): remove this default
#[serde(default = "default_terminal_total_difficulty")]
#[serde(with = "eth2_serde_utils::quoted_u256")]
pub terminal_total_difficulty: Uint256,
// TODO(merge): remove this default
#[serde(default = "default_terminal_block_hash")]
pub terminal_block_hash: ExecutionBlockHash,
// TODO(merge): remove this default
#[serde(default = "default_terminal_block_hash_activation_epoch")]
pub terminal_block_hash_activation_epoch: Epoch,
// TODO(merge): remove this default
#[serde(default = "default_safe_slots_to_import_optimistically")]
#[serde(with = "eth2_serde_utils::quoted_u64")]
pub safe_slots_to_import_optimistically: u64,
@@ -843,12 +843,10 @@ pub struct Config {
#[serde(deserialize_with = "deserialize_fork_epoch")]
pub altair_fork_epoch: Option<MaybeQuoted<Epoch>>,
// TODO(merge): remove this default
#[serde(default = "default_bellatrix_fork_version")]
#[serde(with = "eth2_serde_utils::bytes_4_hex")]
bellatrix_fork_version: [u8; 4],
// TODO(merge): remove this default
#[serde(default = "default_bellatrix_fork_epoch")]
#[serde(default)]
#[serde(serialize_with = "serialize_fork_epoch")]
#[serde(deserialize_with = "deserialize_fork_epoch")]
pub bellatrix_fork_epoch: Option<MaybeQuoted<Epoch>>,
@@ -890,10 +888,6 @@ fn default_bellatrix_fork_version() -> [u8; 4] {
[0xff, 0xff, 0xff, 0xff]
}
fn default_bellatrix_fork_epoch() -> Option<MaybeQuoted<Epoch>> {
None
}
/// Placeholder value: 2^256-2^10 (115792089237316195423570985008687907853269984665640564039457584007913129638912).
///
/// Taken from https://github.com/ethereum/consensus-specs/blob/d5e4828aecafaf1c57ef67a5f23c4ae7b08c5137/configs/mainnet.yaml#L15-L16
@@ -1335,10 +1329,7 @@ mod yaml_tests {
default_safe_slots_to_import_optimistically()
);
assert_eq!(
chain_spec.bellatrix_fork_epoch,
default_bellatrix_fork_epoch()
);
assert_eq!(chain_spec.bellatrix_fork_epoch, None);
assert_eq!(
chain_spec.bellatrix_fork_version,

View File

@@ -1,12 +1,21 @@
use crate::{AltairPreset, BasePreset, BellatrixPreset, ChainSpec, Config, EthSpec};
use crate::{
consts::altair, AltairPreset, BasePreset, BellatrixPreset, ChainSpec, Config, EthSpec, ForkName,
};
use maplit::hashmap;
use serde_derive::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use superstruct::superstruct;
/// Fusion of a runtime-config with the compile-time preset values.
///
/// Mostly useful for the API.
#[superstruct(
variants(Altair, Bellatrix),
variant_attributes(derive(Serialize, Deserialize, Debug, PartialEq, Clone))
)]
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(untagged)]
pub struct ConfigAndPreset {
#[serde(flatten)]
pub config: Config,
@@ -15,80 +24,75 @@ pub struct ConfigAndPreset {
pub base_preset: BasePreset,
#[serde(flatten)]
pub altair_preset: AltairPreset,
// TODO(merge): re-enable
// #[serde(flatten)]
// pub bellatrix_preset: BellatrixPreset,
#[superstruct(only(Bellatrix))]
#[serde(flatten)]
pub bellatrix_preset: BellatrixPreset,
/// The `extra_fields` map allows us to gracefully decode fields intended for future hard forks.
#[serde(flatten)]
pub extra_fields: HashMap<String, Value>,
}
impl ConfigAndPreset {
pub fn from_chain_spec<T: EthSpec>(spec: &ChainSpec) -> Self {
pub fn from_chain_spec<T: EthSpec>(spec: &ChainSpec, fork_name: Option<ForkName>) -> Self {
let config = Config::from_chain_spec::<T>(spec);
let base_preset = BasePreset::from_chain_spec::<T>(spec);
let altair_preset = AltairPreset::from_chain_spec::<T>(spec);
// TODO(merge): re-enable
let _bellatrix_preset = BellatrixPreset::from_chain_spec::<T>(spec);
let extra_fields = HashMap::new();
let extra_fields = get_extra_fields(spec);
Self {
config,
base_preset,
altair_preset,
extra_fields,
if spec.bellatrix_fork_epoch.is_some()
|| fork_name == None
|| fork_name == Some(ForkName::Merge)
{
let bellatrix_preset = BellatrixPreset::from_chain_spec::<T>(spec);
ConfigAndPreset::Bellatrix(ConfigAndPresetBellatrix {
config,
base_preset,
altair_preset,
bellatrix_preset,
extra_fields,
})
} else {
ConfigAndPreset::Altair(ConfigAndPresetAltair {
config,
base_preset,
altair_preset,
extra_fields,
})
}
}
}
/// Add fields that were previously part of the config but are now constants.
pub fn make_backwards_compat(&mut self, spec: &ChainSpec) {
let hex_string = |value: &[u8]| format!("0x{}", hex::encode(&value));
let u32_hex = |v: u32| hex_string(&v.to_le_bytes());
let u8_hex = |v: u8| hex_string(&v.to_le_bytes());
let fields = vec![
(
"bls_withdrawal_prefix",
u8_hex(spec.bls_withdrawal_prefix_byte),
),
(
"domain_beacon_proposer",
u32_hex(spec.domain_beacon_proposer),
),
(
"domain_beacon_attester",
u32_hex(spec.domain_beacon_attester),
),
("domain_randao", u32_hex(spec.domain_randao)),
("domain_deposit", u32_hex(spec.domain_deposit)),
("domain_voluntary_exit", u32_hex(spec.domain_voluntary_exit)),
(
"domain_selection_proof",
u32_hex(spec.domain_selection_proof),
),
(
"domain_aggregate_and_proof",
u32_hex(spec.domain_aggregate_and_proof),
),
(
"domain_application_mask",
u32_hex(spec.domain_application_mask),
),
(
"target_aggregators_per_committee",
spec.target_aggregators_per_committee.to_string(),
),
(
"random_subnets_per_validator",
spec.random_subnets_per_validator.to_string(),
),
(
"epochs_per_random_subnet_subscription",
spec.epochs_per_random_subnet_subscription.to_string(),
),
];
for (key, value) in fields {
self.extra_fields.insert(key.to_uppercase(), value.into());
}
/// Get a hashmap of constants to add to the `PresetAndConfig`
pub fn get_extra_fields(spec: &ChainSpec) -> HashMap<String, Value> {
let hex_string = |value: &[u8]| format!("0x{}", hex::encode(&value)).into();
let u32_hex = |v: u32| hex_string(&v.to_le_bytes());
let u8_hex = |v: u8| hex_string(&v.to_le_bytes());
hashmap! {
"bls_withdrawal_prefix".to_uppercase() => u8_hex(spec.bls_withdrawal_prefix_byte),
"domain_beacon_proposer".to_uppercase() => u32_hex(spec.domain_beacon_proposer),
"domain_beacon_attester".to_uppercase() => u32_hex(spec.domain_beacon_attester),
"domain_randao".to_uppercase()=> u32_hex(spec.domain_randao),
"domain_deposit".to_uppercase()=> u32_hex(spec.domain_deposit),
"domain_voluntary_exit".to_uppercase() => u32_hex(spec.domain_voluntary_exit),
"domain_selection_proof".to_uppercase() => u32_hex(spec.domain_selection_proof),
"domain_aggregate_and_proof".to_uppercase() => u32_hex(spec.domain_aggregate_and_proof),
"domain_application_mask".to_uppercase()=> u32_hex(spec.domain_application_mask),
"target_aggregators_per_committee".to_uppercase() =>
spec.target_aggregators_per_committee.to_string().into(),
"random_subnets_per_validator".to_uppercase() =>
spec.random_subnets_per_validator.to_string().into(),
"epochs_per_random_subnet_subscription".to_uppercase() =>
spec.epochs_per_random_subnet_subscription.to_string().into(),
"domain_contribution_and_proof".to_uppercase() =>
u32_hex(spec.domain_contribution_and_proof),
"domain_sync_committee".to_uppercase() => u32_hex(spec.domain_sync_committee),
"domain_sync_committee_selection_proof".to_uppercase() =>
u32_hex(spec.domain_sync_committee_selection_proof),
"sync_committee_subnet_count".to_uppercase() =>
altair::SYNC_COMMITTEE_SUBNET_COUNT.to_string().into(),
"target_aggregators_per_sync_subcommittee".to_uppercase() =>
altair::TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE.to_string().into(),
}
}
@@ -108,15 +112,16 @@ mod test {
.open(tmp_file.as_ref())
.expect("error opening file");
let mainnet_spec = ChainSpec::mainnet();
let mut yamlconfig = ConfigAndPreset::from_chain_spec::<MainnetEthSpec>(&mainnet_spec);
let mut yamlconfig =
ConfigAndPreset::from_chain_spec::<MainnetEthSpec>(&mainnet_spec, None);
let (k1, v1) = ("SAMPLE_HARDFORK_KEY1", "123456789");
let (k2, v2) = ("SAMPLE_HARDFORK_KEY2", "987654321");
let (k3, v3) = ("SAMPLE_HARDFORK_KEY3", 32);
let (k4, v4) = ("SAMPLE_HARDFORK_KEY4", Value::Null);
yamlconfig.extra_fields.insert(k1.into(), v1.into());
yamlconfig.extra_fields.insert(k2.into(), v2.into());
yamlconfig.extra_fields.insert(k3.into(), v3.into());
yamlconfig.extra_fields.insert(k4.into(), v4);
yamlconfig.extra_fields_mut().insert(k1.into(), v1.into());
yamlconfig.extra_fields_mut().insert(k2.into(), v2.into());
yamlconfig.extra_fields_mut().insert(k3.into(), v3.into());
yamlconfig.extra_fields_mut().insert(k4.into(), v4);
serde_yaml::to_writer(writer, &yamlconfig).expect("failed to write or serialize");
@@ -125,8 +130,8 @@ mod test {
.write(false)
.open(tmp_file.as_ref())
.expect("error while opening the file");
let from: ConfigAndPreset =
let from: ConfigAndPresetBellatrix =
serde_yaml::from_reader(reader).expect("error while deserializing");
assert_eq!(from, yamlconfig);
assert_eq!(ConfigAndPreset::Bellatrix(from), yamlconfig);
}
}

View File

@@ -106,14 +106,14 @@ macro_rules! map_fork_name_with {
}
impl FromStr for ForkName {
type Err = ();
type Err = String;
fn from_str(fork_name: &str) -> Result<Self, ()> {
fn from_str(fork_name: &str) -> Result<Self, String> {
Ok(match fork_name.to_lowercase().as_ref() {
"phase0" | "base" => ForkName::Base,
"altair" => ForkName::Altair,
"bellatrix" | "merge" => ForkName::Merge,
_ => return Err(()),
_ => return Err(format!("unknown fork name: {}", fork_name)),
})
}
}
@@ -138,7 +138,7 @@ impl TryFrom<String> for ForkName {
type Error = String;
fn try_from(s: String) -> Result<Self, Self::Error> {
Self::from_str(&s).map_err(|()| format!("Invalid fork name: {}", s))
Self::from_str(&s)
}
}
@@ -178,8 +178,8 @@ mod test {
assert_eq!(ForkName::from_str("AlTaIr"), Ok(ForkName::Altair));
assert_eq!(ForkName::from_str("altair"), Ok(ForkName::Altair));
assert_eq!(ForkName::from_str("NO_NAME"), Err(()));
assert_eq!(ForkName::from_str("no_name"), Err(()));
assert!(ForkName::from_str("NO_NAME").is_err());
assert!(ForkName::from_str("no_name").is_err());
}
#[test]

View File

@@ -110,7 +110,9 @@ pub use crate::beacon_committee::{BeaconCommittee, OwnedBeaconCommittee};
pub use crate::beacon_state::{BeaconTreeHashCache, Error as BeaconStateError, *};
pub use crate::chain_spec::{ChainSpec, Config, Domain};
pub use crate::checkpoint::Checkpoint;
pub use crate::config_and_preset::ConfigAndPreset;
pub use crate::config_and_preset::{
ConfigAndPreset, ConfigAndPresetAltair, ConfigAndPresetBellatrix,
};
pub use crate::contribution_and_proof::ContributionAndProof;
pub use crate::deposit::{Deposit, DEPOSIT_TREE_DEPTH};
pub use crate::deposit_data::DepositData;