Import BLS to execution changes before Capella (#3892)

* Import BLS to execution changes before Capella

* Test for BLS to execution change HTTP API

* Pack BLS to execution changes in LIFO order

* Remove unused var

* Clippy
This commit is contained in:
Michael Sproul
2023-01-21 10:39:59 +11:00
committed by GitHub
parent bb0e99c097
commit d8abf2fc41
12 changed files with 517 additions and 91 deletions

View File

@@ -2227,32 +2227,74 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
}
/// Verify a signed BLS to execution change before allowing it to propagate on the gossip network.
pub fn verify_bls_to_execution_change_for_gossip(
pub fn verify_bls_to_execution_change_for_http_api(
&self,
bls_to_execution_change: SignedBlsToExecutionChange,
) -> Result<ObservationOutcome<SignedBlsToExecutionChange, T::EthSpec>, Error> {
let current_fork = self.spec.fork_name_at_slot::<T::EthSpec>(self.slot()?);
if let ForkName::Base | ForkName::Altair | ForkName::Merge = current_fork {
// Disallow BLS to execution changes prior to the Capella fork.
return Err(Error::BlsToExecutionChangeBadFork(current_fork));
// Before checking the gossip duplicate filter, check that no prior change is already
// in our op pool. Ignore these messages: do not gossip, do not try to override the pool.
match self
.op_pool
.bls_to_execution_change_in_pool_equals(&bls_to_execution_change)
{
Some(true) => return Ok(ObservationOutcome::AlreadyKnown),
Some(false) => return Err(Error::BlsToExecutionConflictsWithPool),
None => (),
}
let wall_clock_state = self.wall_clock_state()?;
// Use the head state to save advancing to the wall-clock slot unnecessarily. The message is
// signed with respect to the genesis fork version, and the slot check for gossip is applied
// separately. This `Arc` clone of the head is nice and cheap.
let head_snapshot = self.head().snapshot;
let head_state = &head_snapshot.beacon_state;
Ok(self
.observed_bls_to_execution_changes
.lock()
.verify_and_observe(bls_to_execution_change, &wall_clock_state, &self.spec)?)
.verify_and_observe(bls_to_execution_change, head_state, &self.spec)?)
}
/// Verify a signed BLS to execution change before allowing it to propagate on the gossip network.
pub fn verify_bls_to_execution_change_for_gossip(
&self,
bls_to_execution_change: SignedBlsToExecutionChange,
) -> Result<ObservationOutcome<SignedBlsToExecutionChange, T::EthSpec>, Error> {
// Ignore BLS to execution changes on gossip prior to Capella.
if !self.current_slot_is_post_capella()? {
return Err(Error::BlsToExecutionPriorToCapella);
}
self.verify_bls_to_execution_change_for_http_api(bls_to_execution_change)
.or_else(|e| {
// On gossip treat conflicts the same as duplicates [IGNORE].
match e {
Error::BlsToExecutionConflictsWithPool => Ok(ObservationOutcome::AlreadyKnown),
e => Err(e),
}
})
}
/// Check if the current slot is greater than or equal to the Capella fork epoch.
pub fn current_slot_is_post_capella(&self) -> Result<bool, Error> {
let current_fork = self.spec.fork_name_at_slot::<T::EthSpec>(self.slot()?);
if let ForkName::Base | ForkName::Altair | ForkName::Merge = current_fork {
Ok(false)
} else {
Ok(true)
}
}
/// Import a BLS to execution change to the op pool.
///
/// Return `true` if the change was added to the pool.
pub fn import_bls_to_execution_change(
&self,
bls_to_execution_change: SigVerifiedOp<SignedBlsToExecutionChange, T::EthSpec>,
) {
) -> bool {
if self.eth1_chain.is_some() {
self.op_pool
.insert_bls_to_execution_change(bls_to_execution_change);
.insert_bls_to_execution_change(bls_to_execution_change)
} else {
false
}
}

View File

@@ -206,7 +206,8 @@ pub enum BeaconChainError {
MissingPersistedForkChoice,
CommitteePromiseFailed(oneshot_broadcast::Error),
MaxCommitteePromises(usize),
BlsToExecutionChangeBadFork(ForkName),
BlsToExecutionPriorToCapella,
BlsToExecutionConflictsWithPool,
InconsistentFork(InconsistentFork),
ProposerHeadForkChoiceError(fork_choice::Error<proto_array::Error>),
}

View File

@@ -148,6 +148,7 @@ pub struct Builder<T: BeaconChainTypes> {
eth_spec_instance: T::EthSpec,
spec: Option<ChainSpec>,
validator_keypairs: Option<Vec<Keypair>>,
withdrawal_keypairs: Vec<Option<Keypair>>,
chain_config: Option<ChainConfig>,
store_config: Option<StoreConfig>,
#[allow(clippy::type_complexity)]
@@ -170,6 +171,17 @@ impl<E: EthSpec> Builder<EphemeralHarnessType<E>> {
.clone()
.expect("cannot build without validator keypairs");
// For the interop genesis state we know that the withdrawal credentials are set equal
// to the validator keypairs. Check for any manually initialised credentials.
assert!(
self.withdrawal_keypairs.is_empty(),
"withdrawal credentials are ignored by fresh_ephemeral_store"
);
self.withdrawal_keypairs = validator_keypairs
.iter()
.map(|kp| Some(kp.clone()))
.collect();
let store = Arc::new(
HotColdDB::open_ephemeral(
self.store_config.clone().unwrap_or_default(),
@@ -282,6 +294,7 @@ where
eth_spec_instance,
spec: None,
validator_keypairs: None,
withdrawal_keypairs: vec![],
chain_config: None,
store_config: None,
store: None,
@@ -539,6 +552,7 @@ where
spec: chain.spec.clone(),
chain: Arc::new(chain),
validator_keypairs,
withdrawal_keypairs: self.withdrawal_keypairs,
shutdown_receiver: Arc::new(Mutex::new(shutdown_receiver)),
runtime: self.runtime,
mock_execution_layer: self.mock_execution_layer,
@@ -554,6 +568,12 @@ where
/// Used for testing.
pub struct BeaconChainHarness<T: BeaconChainTypes> {
pub validator_keypairs: Vec<Keypair>,
/// Optional BLS withdrawal keys for each validator.
///
/// If a validator index is missing from this vec or their entry is `None` then either
/// no BLS withdrawal key was set for them (they had an address from genesis) or the test
/// initializer neglected to set this field.
pub withdrawal_keypairs: Vec<Option<Keypair>>,
pub chain: Arc<BeaconChain<T>>,
pub spec: ChainSpec,
@@ -1465,6 +1485,44 @@ where
.sign(sk, &fork, genesis_validators_root, &self.chain.spec)
}
pub fn make_bls_to_execution_change(
&self,
validator_index: u64,
address: Address,
) -> SignedBlsToExecutionChange {
let keypair = self.get_withdrawal_keypair(validator_index);
self.make_bls_to_execution_change_with_keys(
validator_index,
address,
&keypair.pk,
&keypair.sk,
)
}
pub fn make_bls_to_execution_change_with_keys(
&self,
validator_index: u64,
address: Address,
pubkey: &PublicKey,
secret_key: &SecretKey,
) -> SignedBlsToExecutionChange {
let genesis_validators_root = self.chain.genesis_validators_root;
BlsToExecutionChange {
validator_index,
from_bls_pubkey: pubkey.compress(),
to_execution_address: address,
}
.sign(secret_key, genesis_validators_root, &self.chain.spec)
}
pub fn get_withdrawal_keypair(&self, validator_index: u64) -> &Keypair {
self.withdrawal_keypairs
.get(validator_index as usize)
.expect("BLS withdrawal key missing from harness")
.as_ref()
.expect("no withdrawal key for validator")
}
pub fn add_voluntary_exit(
&self,
block: &mut BeaconBlock<E>,