Merge remote-tracking branch 'origin/unstable' into tree-states

This commit is contained in:
Michael Sproul
2022-09-14 13:51:23 +10:00
404 changed files with 28947 additions and 12000 deletions

View File

@@ -0,0 +1,16 @@
/// This value is an application index of 0 with the bitmask applied (so it's equivalent to the bit mask).
/// Little endian hex: 0x00000001, Binary: 1000000000000000000000000
pub const APPLICATION_DOMAIN_BUILDER: u32 = 16777216;
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum ApplicationDomain {
Builder,
}
impl ApplicationDomain {
pub fn get_domain_constant(&self) -> u32 {
match self {
ApplicationDomain::Builder => APPLICATION_DOMAIN_BUILDER,
}
}
}

View File

@@ -38,7 +38,7 @@ use tree_hash_derive::TreeHash;
derive(Debug, PartialEq, TreeHash),
tree_hash(enum_behaviour = "transparent")
),
map_ref_into(BeaconBlockBodyRef),
map_ref_into(BeaconBlockBodyRef, BeaconBlock),
map_ref_mut_into(BeaconBlockBodyRefMut)
)]
#[derive(Debug, Clone, Serialize, Deserialize, Encode, TreeHash, Derivative)]
@@ -541,6 +541,50 @@ impl_from!(BeaconBlockBase, <E, FullPayload<E>>, <E, BlindedPayload<E>>, |body:
impl_from!(BeaconBlockAltair, <E, FullPayload<E>>, <E, BlindedPayload<E>>, |body: BeaconBlockBodyAltair<_, _>| body.into());
impl_from!(BeaconBlockMerge, <E, FullPayload<E>>, <E, BlindedPayload<E>>, |body: BeaconBlockBodyMerge<_, _>| body.into());
// We can clone blocks with payloads to blocks without payloads, without cloning the payload.
macro_rules! impl_clone_as_blinded {
($ty_name:ident, <$($from_params:ty),*>, <$($to_params:ty),*>) => {
impl<E: EthSpec> $ty_name<$($from_params),*>
{
pub fn clone_as_blinded(&self) -> $ty_name<$($to_params),*> {
let $ty_name {
slot,
proposer_index,
parent_root,
state_root,
body,
} = self;
$ty_name {
slot: *slot,
proposer_index: *proposer_index,
parent_root: *parent_root,
state_root: *state_root,
body: body.clone_as_blinded(),
}
}
}
}
}
impl_clone_as_blinded!(BeaconBlockBase, <E, FullPayload<E>>, <E, BlindedPayload<E>>);
impl_clone_as_blinded!(BeaconBlockAltair, <E, FullPayload<E>>, <E, BlindedPayload<E>>);
impl_clone_as_blinded!(BeaconBlockMerge, <E, FullPayload<E>>, <E, BlindedPayload<E>>);
// A reference to a full beacon block can be cloned into a blinded beacon block, without cloning the
// execution payload.
impl<'a, E: EthSpec> From<BeaconBlockRef<'a, E, FullPayload<E>>>
for BeaconBlock<E, BlindedPayload<E>>
{
fn from(
full_block: BeaconBlockRef<'a, E, FullPayload<E>>,
) -> BeaconBlock<E, BlindedPayload<E>> {
map_beacon_block_ref_into_beacon_block!(&'a _, full_block, |inner, cons| {
cons(inner.clone_as_blinded())
})
}
}
impl<E: EthSpec> From<BeaconBlock<E, FullPayload<E>>>
for (
BeaconBlock<E, BlindedPayload<E>>,
@@ -607,19 +651,17 @@ mod tests {
#[test]
fn decode_base_and_altair() {
type E = MainnetEthSpec;
let spec = E::default_spec();
let rng = &mut XorShiftRng::from_seed([42; 16]);
let fork_epoch = Epoch::from_ssz_bytes(&[7, 6, 5, 4, 3, 2, 1, 0]).unwrap();
let fork_epoch = spec.altair_fork_epoch.unwrap();
let base_epoch = fork_epoch.saturating_sub(1_u64);
let base_slot = base_epoch.end_slot(E::slots_per_epoch());
let altair_epoch = fork_epoch;
let altair_slot = altair_epoch.start_slot(E::slots_per_epoch());
let mut spec = E::default_spec();
spec.altair_fork_epoch = Some(fork_epoch);
// BeaconBlockBase
{
let good_base_block = BeaconBlock::Base(BeaconBlockBase {

View File

@@ -251,6 +251,53 @@ impl<E: EthSpec> From<BeaconBlockBodyMerge<E, FullPayload<E>>>
}
}
// We can clone a full block into a blinded block, without cloning the payload.
impl<E: EthSpec> BeaconBlockBodyBase<E, FullPayload<E>> {
pub fn clone_as_blinded(&self) -> BeaconBlockBodyBase<E, BlindedPayload<E>> {
let (block_body, _payload) = self.clone().into();
block_body
}
}
impl<E: EthSpec> BeaconBlockBodyAltair<E, FullPayload<E>> {
pub fn clone_as_blinded(&self) -> BeaconBlockBodyAltair<E, BlindedPayload<E>> {
let (block_body, _payload) = self.clone().into();
block_body
}
}
impl<E: EthSpec> BeaconBlockBodyMerge<E, FullPayload<E>> {
pub fn clone_as_blinded(&self) -> BeaconBlockBodyMerge<E, BlindedPayload<E>> {
let BeaconBlockBodyMerge {
randao_reveal,
eth1_data,
graffiti,
proposer_slashings,
attester_slashings,
attestations,
deposits,
voluntary_exits,
sync_aggregate,
execution_payload: FullPayload { execution_payload },
} = self;
BeaconBlockBodyMerge {
randao_reveal: randao_reveal.clone(),
eth1_data: eth1_data.clone(),
graffiti: *graffiti,
proposer_slashings: proposer_slashings.clone(),
attester_slashings: attester_slashings.clone(),
attestations: attestations.clone(),
deposits: deposits.clone(),
voluntary_exits: voluntary_exits.clone(),
sync_aggregate: sync_aggregate.clone(),
execution_payload: BlindedPayload {
execution_payload_header: From::from(execution_payload),
},
}
}
}
impl<E: EthSpec> From<BeaconBlockBody<E, FullPayload<E>>>
for (
BeaconBlockBody<E, BlindedPayload<E>>,

View File

@@ -985,6 +985,13 @@ impl<T: EthSpec> BeaconState<T> {
}
}
/// Return the minimum epoch for which `get_randao_mix` will return a non-error value.
pub fn min_randao_epoch(&self) -> Epoch {
self.current_epoch()
.saturating_add(1u64)
.saturating_sub(T::EpochsPerHistoricalVector::to_u64())
}
/// XOR-assigns the existing `epoch` randao mix with the hash of the `signature`.
///
/// # Errors:
@@ -1625,15 +1632,17 @@ impl<T: EthSpec> BeaconState<T> {
Ok(self.validators().tree_hash_root())
}
pub fn is_eligible_validator(&self, val: &Validator) -> bool {
let previous_epoch = self.previous_epoch();
/// Passing `previous_epoch` to this function rather than computing it internally provides
/// a tangible speed improvement in state processing.
pub fn is_eligible_validator(&self, previous_epoch: Epoch, val: &Validator) -> bool {
val.is_active_at(previous_epoch)
|| (val.slashed && previous_epoch + Epoch::new(1) < val.withdrawable_epoch)
}
pub fn is_in_inactivity_leak(&self, spec: &ChainSpec) -> bool {
(self.previous_epoch() - self.finalized_checkpoint().epoch)
> spec.min_epochs_to_inactivity_penalty
/// Passing `previous_epoch` to this function rather than computing it internally provides
/// a tangible speed improvement in state processing.
pub fn is_in_inactivity_leak(&self, previous_epoch: Epoch, spec: &ChainSpec) -> bool {
(previous_epoch - self.finalized_checkpoint().epoch) > spec.min_epochs_to_inactivity_penalty
}
/// Get the `SyncCommittee` associated with the next slot. Useful because sync committees

View File

@@ -39,8 +39,18 @@ impl CommitteeCache {
epoch: Epoch,
spec: &ChainSpec,
) -> Result<Arc<CommitteeCache>, Error> {
RelativeEpoch::from_epoch(state.current_epoch(), epoch)
.map_err(|_| Error::EpochOutOfBounds)?;
// Check that the cache is being built for an in-range epoch.
//
// We allow caches to be constructed for historic epochs, per:
//
// https://github.com/sigp/lighthouse/issues/3270
let reqd_randao_epoch = epoch
.saturating_sub(spec.min_seed_lookahead)
.saturating_sub(1u64);
if reqd_randao_epoch < state.min_randao_epoch() || epoch > state.current_epoch() + 1 {
return Err(Error::EpochOutOfBounds);
}
// May cause divide-by-zero errors.
if T::slots_per_epoch() == 0 {

View File

@@ -34,32 +34,34 @@ fn default_values() {
assert!(cache.get_beacon_committees_at_slot(Slot::new(0)).is_err());
}
fn new_state<T: EthSpec>(validator_count: usize, slot: Slot) -> BeaconState<T> {
async fn new_state<T: EthSpec>(validator_count: usize, slot: Slot) -> BeaconState<T> {
let harness = get_harness(validator_count);
let head_state = harness.get_current_state();
if slot > Slot::new(0) {
harness.add_attested_blocks_at_slots(
head_state,
Hash256::zero(),
(1..slot.as_u64())
.map(Slot::new)
.collect::<Vec<_>>()
.as_slice(),
(0..validator_count).collect::<Vec<_>>().as_slice(),
);
harness
.add_attested_blocks_at_slots(
head_state,
Hash256::zero(),
(1..=slot.as_u64())
.map(Slot::new)
.collect::<Vec<_>>()
.as_slice(),
(0..validator_count).collect::<Vec<_>>().as_slice(),
)
.await;
}
harness.get_current_state()
}
#[test]
#[tokio::test]
#[should_panic]
fn fails_without_validators() {
new_state::<MinimalEthSpec>(0, Slot::new(0));
async fn fails_without_validators() {
new_state::<MinimalEthSpec>(0, Slot::new(0)).await;
}
#[test]
fn initializes_with_the_right_epoch() {
let state = new_state::<MinimalEthSpec>(16, Slot::new(0));
#[tokio::test]
async fn initializes_with_the_right_epoch() {
let state = new_state::<MinimalEthSpec>(16, Slot::new(0)).await;
let spec = &MinimalEthSpec::default_spec();
let cache = CommitteeCache::default();
@@ -75,17 +77,20 @@ fn initializes_with_the_right_epoch() {
assert!(cache.is_initialized_at(state.next_epoch().unwrap()));
}
#[test]
fn shuffles_for_the_right_epoch() {
#[tokio::test]
async fn shuffles_for_the_right_epoch() {
let num_validators = MinimalEthSpec::minimum_validator_count() * 2;
let epoch = Epoch::new(6);
let slot = epoch.start_slot(MinimalEthSpec::slots_per_epoch());
let mut state = new_state::<MinimalEthSpec>(num_validators, slot);
let mut state = new_state::<MinimalEthSpec>(num_validators, slot).await;
let spec = &MinimalEthSpec::default_spec();
let distinct_hashes = (0..MinimalEthSpec::epochs_per_historical_vector())
.map(|i| Hash256::from_low_u64_be(i as u64));
assert_eq!(state.current_epoch(), epoch);
let distinct_hashes: Vec<Hash256> = (0..MinimalEthSpec::epochs_per_historical_vector())
.map(|i| Hash256::from_low_u64_be(i as u64))
.collect();
*state.randao_mixes_mut() = FixedVector::try_from_iter(distinct_hashes).unwrap();
@@ -121,15 +126,41 @@ fn shuffles_for_the_right_epoch() {
}
};
let cache = CommitteeCache::initialized(&state, state.current_epoch(), spec).unwrap();
assert_eq!(cache.shuffling(), shuffling_with_seed(current_seed));
assert_shuffling_positions_accurate(&cache);
// We can initialize the committee cache at recent epochs in the past, and one epoch into the
// future.
for e in (0..=epoch.as_u64() + 1).map(Epoch::new) {
let seed = state.get_seed(e, Domain::BeaconAttester, spec).unwrap();
let cache = CommitteeCache::initialized(&state, e, spec)
.unwrap_or_else(|_| panic!("failed at epoch {}", e));
assert_eq!(cache.shuffling(), shuffling_with_seed(seed));
assert_shuffling_positions_accurate(&cache);
}
let cache = CommitteeCache::initialized(&state, state.previous_epoch(), spec).unwrap();
assert_eq!(cache.shuffling(), shuffling_with_seed(previous_seed));
assert_shuffling_positions_accurate(&cache);
let cache = CommitteeCache::initialized(&state, state.next_epoch().unwrap(), spec).unwrap();
assert_eq!(cache.shuffling(), shuffling_with_seed(next_seed));
assert_shuffling_positions_accurate(&cache);
// We should *not* be able to build a committee cache for the epoch after the next epoch.
assert_eq!(
CommitteeCache::initialized(&state, epoch + 2, spec),
Err(BeaconStateError::EpochOutOfBounds)
);
}
#[tokio::test]
async fn min_randao_epoch_correct() {
let num_validators = MinimalEthSpec::minimum_validator_count() * 2;
let current_epoch = Epoch::new(MinimalEthSpec::epochs_per_historical_vector() as u64 * 2);
let mut state = new_state::<MinimalEthSpec>(
num_validators,
Epoch::new(1).start_slot(MinimalEthSpec::slots_per_epoch()),
)
.await;
// Override the epoch so that there's some room to move.
*state.slot_mut() = current_epoch.start_slot(MinimalEthSpec::slots_per_epoch());
assert_eq!(state.current_epoch(), current_epoch);
// The min_randao_epoch should be the minimum epoch such that `get_randao_mix` returns `Ok`.
let min_randao_epoch = state.min_randao_epoch();
state.get_randao_mix(min_randao_epoch).unwrap();
state.get_randao_mix(min_randao_epoch - 1).unwrap_err();
state.get_randao_mix(min_randao_epoch + 1).unwrap();
}

View File

@@ -7,7 +7,9 @@ use beacon_chain::types::{
ChainSpec, Domain, Epoch, EthSpec, FixedVector, Hash256, Keypair, MainnetEthSpec,
MinimalEthSpec, RelativeEpoch, Slot,
};
use safe_arith::SafeArith;
use ssz::{Decode, Encode};
use state_processing::per_slot_processing;
use std::ops::Mul;
use swap_or_not_shuffle::compute_shuffled_index;
use tree_hash::TreeHash;
@@ -20,7 +22,7 @@ lazy_static! {
static ref KEYPAIRS: Vec<Keypair> = generate_deterministic_keypairs(MAX_VALIDATOR_COUNT);
}
fn get_harness<E: EthSpec>(
async fn get_harness<E: EthSpec>(
validator_count: usize,
slot: Slot,
) -> BeaconChainHarness<EphemeralHarnessType<E>> {
@@ -36,24 +38,26 @@ fn get_harness<E: EthSpec>(
.map(Slot::new)
.collect::<Vec<_>>();
let state = harness.get_current_state();
harness.add_attested_blocks_at_slots(
state,
Hash256::zero(),
slots.as_slice(),
(0..validator_count).collect::<Vec<_>>().as_slice(),
);
harness
.add_attested_blocks_at_slots(
state,
Hash256::zero(),
slots.as_slice(),
(0..validator_count).collect::<Vec<_>>().as_slice(),
)
.await;
}
harness
}
fn build_state<E: EthSpec>(validator_count: usize) -> BeaconState<E> {
async fn build_state<E: EthSpec>(validator_count: usize) -> BeaconState<E> {
get_harness(validator_count, Slot::new(0))
.await
.chain
.head_beacon_state()
.unwrap()
.head_beacon_state_cloned()
}
fn test_beacon_proposer_index<T: EthSpec>() {
async fn test_beacon_proposer_index<T: EthSpec>() {
let spec = T::default_spec();
// Get the i'th candidate proposer for the given state and slot
@@ -80,20 +84,20 @@ fn test_beacon_proposer_index<T: EthSpec>() {
// Test where we have one validator per slot.
// 0th candidate should be chosen every time.
let state = build_state(T::slots_per_epoch() as usize);
let state = build_state(T::slots_per_epoch() as usize).await;
for i in 0..T::slots_per_epoch() {
test(&state, Slot::from(i), 0);
}
// Test where we have two validators per slot.
// 0th candidate should be chosen every time.
let state = build_state((T::slots_per_epoch() as usize).mul(2));
let state = build_state((T::slots_per_epoch() as usize).mul(2)).await;
for i in 0..T::slots_per_epoch() {
test(&state, Slot::from(i), 0);
}
// Test with two validators per slot, first validator has zero balance.
let mut state = build_state::<T>((T::slots_per_epoch() as usize).mul(2));
let mut state = build_state::<T>((T::slots_per_epoch() as usize).mul(2)).await;
let slot0_candidate0 = ith_candidate(&state, Slot::new(0), 0, &spec);
state
.validators_mut()
@@ -106,9 +110,9 @@ fn test_beacon_proposer_index<T: EthSpec>() {
}
}
#[test]
fn beacon_proposer_index() {
test_beacon_proposer_index::<MinimalEthSpec>();
#[tokio::test]
async fn beacon_proposer_index() {
test_beacon_proposer_index::<MinimalEthSpec>().await;
}
/// Test that
@@ -143,11 +147,11 @@ fn test_cache_initialization<T: EthSpec>(
);
}
#[test]
fn cache_initialization() {
#[tokio::test]
async fn cache_initialization() {
let spec = MinimalEthSpec::default_spec();
let mut state = build_state::<MinimalEthSpec>(16);
let mut state = build_state::<MinimalEthSpec>(16).await;
*state.slot_mut() =
(MinimalEthSpec::genesis_epoch() + 1).start_slot(MinimalEthSpec::slots_per_epoch());
@@ -236,7 +240,7 @@ mod committees {
assert!(expected_indices_iter.next().is_none());
}
fn committee_consistency_test<T: EthSpec>(
async fn committee_consistency_test<T: EthSpec>(
validator_count: usize,
state_epoch: Epoch,
cache_epoch: RelativeEpoch,
@@ -244,7 +248,7 @@ mod committees {
let spec = &T::default_spec();
let slot = state_epoch.start_slot(T::slots_per_epoch());
let harness = get_harness::<T>(validator_count, slot);
let harness = get_harness::<T>(validator_count, slot).await;
let mut new_head_state = harness.get_current_state();
let distinct_hashes =
@@ -271,7 +275,7 @@ mod committees {
);
}
fn committee_consistency_test_suite<T: EthSpec>(cached_epoch: RelativeEpoch) {
async fn committee_consistency_test_suite<T: EthSpec>(cached_epoch: RelativeEpoch) {
let spec = T::default_spec();
let validator_count = spec
@@ -280,13 +284,15 @@ mod committees {
.mul(spec.target_committee_size)
.add(1);
committee_consistency_test::<T>(validator_count as usize, Epoch::new(0), cached_epoch);
committee_consistency_test::<T>(validator_count as usize, Epoch::new(0), cached_epoch)
.await;
committee_consistency_test::<T>(
validator_count as usize,
T::genesis_epoch() + 4,
cached_epoch,
);
)
.await;
committee_consistency_test::<T>(
validator_count as usize,
@@ -295,38 +301,39 @@ mod committees {
.mul(T::slots_per_epoch())
.mul(4),
cached_epoch,
);
)
.await;
}
#[test]
fn current_epoch_committee_consistency() {
committee_consistency_test_suite::<MinimalEthSpec>(RelativeEpoch::Current);
#[tokio::test]
async fn current_epoch_committee_consistency() {
committee_consistency_test_suite::<MinimalEthSpec>(RelativeEpoch::Current).await;
}
#[test]
fn previous_epoch_committee_consistency() {
committee_consistency_test_suite::<MinimalEthSpec>(RelativeEpoch::Previous);
#[tokio::test]
async fn previous_epoch_committee_consistency() {
committee_consistency_test_suite::<MinimalEthSpec>(RelativeEpoch::Previous).await;
}
#[test]
fn next_epoch_committee_consistency() {
committee_consistency_test_suite::<MinimalEthSpec>(RelativeEpoch::Next);
#[tokio::test]
async fn next_epoch_committee_consistency() {
committee_consistency_test_suite::<MinimalEthSpec>(RelativeEpoch::Next).await;
}
}
mod get_outstanding_deposit_len {
use super::*;
fn state() -> BeaconState<MinimalEthSpec> {
async fn state() -> BeaconState<MinimalEthSpec> {
get_harness(16, Slot::new(0))
.await
.chain
.head_beacon_state()
.unwrap()
.head_beacon_state_cloned()
}
#[test]
fn returns_ok() {
let mut state = state();
#[tokio::test]
async fn returns_ok() {
let mut state = state().await;
assert_eq!(state.get_outstanding_deposit_len(), Ok(0));
state.eth1_data_mut().deposit_count = 17;
@@ -334,9 +341,9 @@ mod get_outstanding_deposit_len {
assert_eq!(state.get_outstanding_deposit_len(), Ok(1));
}
#[test]
fn returns_err_if_the_state_is_invalid() {
let mut state = state();
#[tokio::test]
async fn returns_err_if_the_state_is_invalid() {
let mut state = state().await;
// The state is invalid, deposit count is lower than deposit index.
state.eth1_data_mut().deposit_count = 16;
*state.eth1_deposit_index_mut() = 17;
@@ -354,62 +361,60 @@ mod get_outstanding_deposit_len {
#[test]
fn decode_base_and_altair() {
type E = MainnetEthSpec;
let spec = E::default_spec();
let rng = &mut XorShiftRng::from_seed([42; 16]);
let fork_epoch = Epoch::from_ssz_bytes(&[7, 6, 5, 4, 3, 2, 1, 0]).unwrap();
let fork_epoch = spec.altair_fork_epoch.unwrap();
let base_epoch = fork_epoch.saturating_sub(1_u64);
let base_slot = base_epoch.end_slot(E::slots_per_epoch());
let altair_epoch = fork_epoch;
let altair_slot = altair_epoch.start_slot(E::slots_per_epoch());
let mut spec = E::default_spec();
spec.altair_fork_epoch = Some(altair_epoch);
// BeaconStateBase
{
let good_base_block: BeaconState<MainnetEthSpec> = BeaconState::Base(BeaconStateBase {
let good_base_state: BeaconState<MainnetEthSpec> = BeaconState::Base(BeaconStateBase {
slot: base_slot,
..<_>::random_for_test(rng)
});
// It's invalid to have a base block with a slot higher than the fork slot.
let bad_base_block = {
let mut bad = good_base_block.clone();
// It's invalid to have a base state with a slot higher than the fork slot.
let bad_base_state = {
let mut bad = good_base_state.clone();
*bad.slot_mut() = altair_slot;
bad
};
assert_eq!(
BeaconState::from_ssz_bytes(&good_base_block.as_ssz_bytes(), &spec)
.expect("good base block can be decoded"),
good_base_block
BeaconState::from_ssz_bytes(&good_base_state.as_ssz_bytes(), &spec)
.expect("good base state can be decoded"),
good_base_state
);
<BeaconState<MainnetEthSpec>>::from_ssz_bytes(&bad_base_block.as_ssz_bytes(), &spec)
.expect_err("bad base block cannot be decoded");
<BeaconState<MainnetEthSpec>>::from_ssz_bytes(&bad_base_state.as_ssz_bytes(), &spec)
.expect_err("bad base state cannot be decoded");
}
// BeaconStateAltair
{
let good_altair_block: BeaconState<MainnetEthSpec> =
let good_altair_state: BeaconState<MainnetEthSpec> =
BeaconState::Altair(BeaconStateAltair {
slot: altair_slot,
..<_>::random_for_test(rng)
});
// It's invalid to have an Altair block with a slot lower than the fork slot.
let bad_altair_block = {
let mut bad = good_altair_block.clone();
// It's invalid to have an Altair state with a slot lower than the fork slot.
let bad_altair_state = {
let mut bad = good_altair_state.clone();
*bad.slot_mut() = base_slot;
bad
};
assert_eq!(
BeaconState::from_ssz_bytes(&good_altair_block.as_ssz_bytes(), &spec)
.expect("good altair block can be decoded"),
good_altair_block
BeaconState::from_ssz_bytes(&good_altair_state.as_ssz_bytes(), &spec)
.expect("good altair state can be decoded"),
good_altair_state
);
<BeaconState<MainnetEthSpec>>::from_ssz_bytes(&bad_altair_block.as_ssz_bytes(), &spec)
.expect_err("bad altair block cannot be decoded");
<BeaconState<MainnetEthSpec>>::from_ssz_bytes(&bad_altair_state.as_ssz_bytes(), &spec)
.expect_err("bad altair state cannot be decoded");
}
}

View File

@@ -0,0 +1,70 @@
use crate::{ChainSpec, EthSpec, ExecPayload, ExecutionPayloadHeader, SignedRoot, Uint256};
use bls::PublicKeyBytes;
use bls::Signature;
use serde::{Deserialize as De, Deserializer, Serialize as Ser, Serializer};
use serde_derive::{Deserialize, Serialize};
use serde_with::{serde_as, DeserializeAs, SerializeAs};
use std::marker::PhantomData;
use tree_hash_derive::TreeHash;
#[serde_as]
#[derive(PartialEq, Debug, Serialize, Deserialize, TreeHash, Clone)]
#[serde(bound = "E: EthSpec, Payload: ExecPayload<E>")]
pub struct BuilderBid<E: EthSpec, Payload: ExecPayload<E>> {
#[serde_as(as = "BlindedPayloadAsHeader<E>")]
pub header: Payload,
#[serde(with = "eth2_serde_utils::quoted_u256")]
pub value: Uint256,
pub pubkey: PublicKeyBytes,
#[serde(skip)]
#[tree_hash(skip_hashing)]
_phantom_data: PhantomData<E>,
}
impl<E: EthSpec, Payload: ExecPayload<E>> SignedRoot for BuilderBid<E, Payload> {}
/// Validator registration, for use in interacting with servers implementing the builder API.
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
#[serde(bound = "E: EthSpec, Payload: ExecPayload<E>")]
pub struct SignedBuilderBid<E: EthSpec, Payload: ExecPayload<E>> {
pub message: BuilderBid<E, Payload>,
pub signature: Signature,
}
struct BlindedPayloadAsHeader<E>(PhantomData<E>);
impl<E: EthSpec, Payload: ExecPayload<E>> SerializeAs<Payload> for BlindedPayloadAsHeader<E> {
fn serialize_as<S>(source: &Payload, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
source.to_execution_payload_header().serialize(serializer)
}
}
impl<'de, E: EthSpec, Payload: ExecPayload<E>> DeserializeAs<'de, Payload>
for BlindedPayloadAsHeader<E>
{
fn deserialize_as<D>(deserializer: D) -> Result<Payload, D::Error>
where
D: Deserializer<'de>,
{
let payload_header = ExecutionPayloadHeader::deserialize(deserializer)?;
Payload::try_from(payload_header)
.map_err(|_| serde::de::Error::custom("unable to convert payload header to payload"))
}
}
impl<E: EthSpec, Payload: ExecPayload<E>> SignedBuilderBid<E, Payload> {
pub fn verify_signature(&self, spec: &ChainSpec) -> bool {
self.message
.pubkey
.decompress()
.map(|pubkey| {
let domain = spec.get_builder_domain();
let message = self.message.signing_root(domain);
self.signature.verify(&pubkey, message)
})
.unwrap_or(false)
}
}

View File

@@ -1,3 +1,4 @@
use crate::application_domain::{ApplicationDomain, APPLICATION_DOMAIN_BUILDER};
use crate::*;
use eth2_serde_utils::quoted_u64::MaybeQuoted;
use int_to_bytes::int_to_bytes4;
@@ -20,6 +21,7 @@ pub enum Domain {
SyncCommittee,
ContributionAndProof,
SyncCommitteeSelectionProof,
ApplicationMask(ApplicationDomain),
}
/// Lighthouse's internal configuration struct.
@@ -159,6 +161,11 @@ pub struct ChainSpec {
pub attestation_subnet_count: u64,
pub random_subnets_per_validator: u64,
pub epochs_per_random_subnet_subscription: u64,
/*
* Application params
*/
pub(crate) domain_application_mask: u32,
}
impl ChainSpec {
@@ -333,6 +340,7 @@ impl ChainSpec {
Domain::SyncCommittee => self.domain_sync_committee,
Domain::ContributionAndProof => self.domain_contribution_and_proof,
Domain::SyncCommitteeSelectionProof => self.domain_sync_committee_selection_proof,
Domain::ApplicationMask(application_domain) => application_domain.get_domain_constant(),
}
}
@@ -360,6 +368,17 @@ impl ChainSpec {
self.compute_domain(Domain::Deposit, self.genesis_fork_version, Hash256::zero())
}
// This should be updated to include the current fork and the genesis validators root, but discussion is ongoing:
//
// https://github.com/ethereum/builder-specs/issues/14
pub fn get_builder_domain(&self) -> Hash256 {
self.compute_domain(
Domain::ApplicationMask(ApplicationDomain::Builder),
self.genesis_fork_version,
Hash256::zero(),
)
}
/// Return the 32-byte fork data root for the `current_version` and `genesis_validators_root`.
///
/// This is used primarily in signature domains to avoid collisions across forks/chains.
@@ -549,14 +568,9 @@ impl ChainSpec {
.expect("pow does not overflow"),
proportional_slashing_multiplier_bellatrix: 3,
bellatrix_fork_version: [0x02, 0x00, 0x00, 0x00],
bellatrix_fork_epoch: None,
terminal_total_difficulty: Uint256::MAX
.checked_sub(Uint256::from(2u64.pow(10)))
.expect("subtraction does not overflow")
// Add 1 since the spec declares `2**256 - 2**10` and we use
// `Uint256::MAX` which is `2*256- 1`.
.checked_add(Uint256::one())
.expect("addition does not overflow"),
bellatrix_fork_epoch: Some(Epoch::new(144896)),
terminal_total_difficulty: Uint256::from_dec_str("58750000000000000000000")
.expect("terminal_total_difficulty is a valid integer"),
terminal_block_hash: ExecutionBlockHash::zero(),
terminal_block_hash_activation_epoch: Epoch::new(u64::MAX),
safe_slots_to_import_optimistically: 128u64,
@@ -572,6 +586,11 @@ impl ChainSpec {
maximum_gossip_clock_disparity_millis: 500,
target_aggregators_per_committee: 16,
epochs_per_random_subnet_subscription: 256,
/*
* Application specific
*/
domain_application_mask: APPLICATION_DOMAIN_BUILDER,
}
}
@@ -604,6 +623,13 @@ impl ChainSpec {
// Merge
bellatrix_fork_version: [0x02, 0x00, 0x00, 0x01],
bellatrix_fork_epoch: None,
terminal_total_difficulty: Uint256::MAX
.checked_sub(Uint256::from(2u64.pow(10)))
.expect("subtraction does not overflow")
// Add 1 since the spec declares `2**256 - 2**10` and we use
// `Uint256::MAX` which is `2*256- 1`.
.checked_add(Uint256::one())
.expect("addition does not overflow"),
// Other
network_id: 2, // lighthouse testnet network id
deposit_chain_id: 5,
@@ -770,6 +796,11 @@ impl ChainSpec {
maximum_gossip_clock_disparity_millis: 500,
target_aggregators_per_committee: 16,
epochs_per_random_subnet_subscription: 256,
/*
* Application specific
*/
domain_application_mask: APPLICATION_DOMAIN_BUILDER,
}
}
}
@@ -781,6 +812,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 {
@@ -791,17 +826,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,
@@ -821,12 +852,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>>,
@@ -868,10 +897,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
@@ -1126,6 +1151,27 @@ mod tests {
&spec,
);
test_domain(Domain::SyncCommittee, spec.domain_sync_committee, &spec);
// The builder domain index is zero
let builder_domain_pre_mask = [0; 4];
test_domain(
Domain::ApplicationMask(ApplicationDomain::Builder),
apply_bit_mask(builder_domain_pre_mask, &spec),
&spec,
);
}
fn apply_bit_mask(domain_bytes: [u8; 4], spec: &ChainSpec) -> u32 {
let mut domain = [0; 4];
let mask_bytes = int_to_bytes4(spec.domain_application_mask);
// Apply application bit mask
for (i, (domain_byte, mask_byte)) in domain_bytes.iter().zip(mask_bytes.iter()).enumerate()
{
domain[i] = domain_byte | mask_byte;
}
u32::from_le_bytes(domain)
}
// Test that `fork_name_at_epoch` and `fork_epoch` are consistent.
@@ -1292,10 +1338,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,
@@ -1312,4 +1355,12 @@ mod yaml_tests {
)
);
}
#[test]
fn test_domain_builder() {
assert_eq!(
int_to_bytes4(ApplicationDomain::Builder.get_domain_constant()),
[0, 0, 0, 1]
);
}
}

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,76 +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),
),
(
"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(),
}
}
@@ -104,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");
@@ -121,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

@@ -1,12 +1,14 @@
use crate::test_utils::TestRandom;
use crate::Hash256;
use derivative::Derivative;
use rand::RngCore;
use serde_derive::{Deserialize, Serialize};
use ssz::{Decode, DecodeError, Encode};
use std::fmt;
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq, Hash)]
#[derive(Default, Clone, Copy, Serialize, Deserialize, Eq, PartialEq, Hash, Derivative)]
#[derivative(Debug = "transparent")]
#[serde(transparent)]
pub struct ExecutionBlockHash(Hash256);

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

@@ -18,6 +18,7 @@ extern crate lazy_static;
pub mod test_utils;
pub mod aggregate_and_proof;
pub mod application_domain;
pub mod attestation;
pub mod attestation_data;
pub mod attestation_duty;
@@ -27,6 +28,7 @@ pub mod beacon_block_body;
pub mod beacon_block_header;
pub mod beacon_committee;
pub mod beacon_state;
pub mod builder_bid;
pub mod chain_spec;
pub mod checkpoint;
pub mod consts;
@@ -81,6 +83,7 @@ pub mod sync_committee_contribution;
pub mod sync_committee_message;
pub mod sync_selection_proof;
pub mod sync_subnet_id;
pub mod validator_registration_data;
pub mod slot_data;
#[cfg(feature = "sqlite")]
@@ -106,7 +109,9 @@ pub use crate::beacon_committee::{BeaconCommittee, OwnedBeaconCommittee};
pub use crate::beacon_state::{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;
@@ -156,6 +161,7 @@ pub use crate::sync_duty::SyncDuty;
pub use crate::sync_selection_proof::SyncSelectionProof;
pub use crate::sync_subnet_id::SyncSubnetId;
pub use crate::validator::{Validator, ValidatorImmutable};
pub use crate::validator_registration_data::*;
pub use crate::validator_subscription::ValidatorSubscription;
pub use crate::voluntary_exit::VoluntaryExit;

View File

@@ -9,6 +9,7 @@ use std::hash::Hash;
use test_random_derive::TestRandom;
use tree_hash::TreeHash;
#[derive(Debug)]
pub enum BlockType {
Blinded,
Full,
@@ -18,6 +19,7 @@ pub trait ExecPayload<T: EthSpec>:
Debug
+ Clone
+ Encode
+ Debug
+ Decode
+ TestRandom
+ TreeHash
@@ -28,6 +30,8 @@ pub trait ExecPayload<T: EthSpec>:
+ Hash
+ TryFrom<ExecutionPayloadHeader<T>>
+ From<ExecutionPayload<T>>
+ Send
+ 'static
{
fn block_type() -> BlockType;
@@ -42,6 +46,8 @@ pub trait ExecPayload<T: EthSpec>:
fn block_number(&self) -> u64;
fn timestamp(&self) -> u64;
fn block_hash(&self) -> ExecutionBlockHash;
fn fee_recipient(&self) -> Address;
fn gas_limit(&self) -> u64;
}
impl<T: EthSpec> ExecPayload<T> for FullPayload<T> {
@@ -72,6 +78,14 @@ impl<T: EthSpec> ExecPayload<T> for FullPayload<T> {
fn block_hash(&self) -> ExecutionBlockHash {
self.execution_payload.block_hash
}
fn fee_recipient(&self) -> Address {
self.execution_payload.fee_recipient
}
fn gas_limit(&self) -> u64 {
self.execution_payload.gas_limit
}
}
impl<T: EthSpec> ExecPayload<T> for BlindedPayload<T> {
@@ -102,6 +116,14 @@ impl<T: EthSpec> ExecPayload<T> for BlindedPayload<T> {
fn block_hash(&self) -> ExecutionBlockHash {
self.execution_payload_header.block_hash
}
fn fee_recipient(&self) -> Address {
self.execution_payload_header.fee_recipient
}
fn gas_limit(&self) -> u64 {
self.execution_payload_header.gas_limit
}
}
#[derive(Debug, Clone, TestRandom, Serialize, Deserialize, Derivative)]

View File

@@ -18,6 +18,13 @@ pub struct ProposerSlashing {
pub signed_header_2: SignedBeaconBlockHeader,
}
impl ProposerSlashing {
/// Get proposer index, assuming slashing validity has already been checked.
pub fn proposer_index(&self) -> u64 {
self.signed_header_1.message.proposer_index
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -346,6 +346,14 @@ impl<E: EthSpec> From<SignedBeaconBlock<E>> for SignedBlindedBeaconBlock<E> {
}
}
// We can blind borrowed blocks with payloads by converting the payload into a header (without
// cloning the payload contents).
impl<E: EthSpec> SignedBeaconBlock<E> {
pub fn clone_as_blinded(&self) -> SignedBlindedBeaconBlock<E> {
SignedBeaconBlock::from_block(self.message().into(), self.signature().clone())
}
}
#[cfg(test)]
mod test {
use super::*;

View File

@@ -13,8 +13,8 @@ macro_rules! ssz_tests {
($type: ty) => {
#[test]
pub fn test_ssz_round_trip() {
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::{ssz_encode, Decode};
use $crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = <$type>::random_for_test(&mut rng);
@@ -33,8 +33,8 @@ macro_rules! tree_hash_tests {
($type: ty) => {
#[test]
pub fn test_tree_hash_root() {
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use tree_hash::TreeHash;
use $crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = <$type>::random_for_test(&mut rng);

View File

@@ -129,6 +129,7 @@ macro_rules! impl_test_random_for_u8_array {
};
}
impl_test_random_for_u8_array!(3);
impl_test_random_for_u8_array!(4);
impl_test_random_for_u8_array!(32);
impl_test_random_for_u8_array!(48);

View File

@@ -0,0 +1,23 @@
use crate::*;
use serde::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use tree_hash_derive::TreeHash;
/// Validator registration, for use in interacting with servers implementing the builder API.
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
pub struct SignedValidatorRegistrationData {
pub message: ValidatorRegistrationData,
pub signature: Signature,
}
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone, Encode, Decode, TreeHash)]
pub struct ValidatorRegistrationData {
pub fee_recipient: Address,
#[serde(with = "eth2_serde_utils::quoted_u64")]
pub gas_limit: u64,
#[serde(with = "eth2_serde_utils::quoted_u64")]
pub timestamp: u64,
pub pubkey: PublicKeyBytes,
}
impl SignedRoot for ValidatorRegistrationData {}