Remove dupe info between ChainSpec and EthSpec

This commit is contained in:
Paul Hauner
2019-06-08 07:57:25 -04:00
parent f69d9093a3
commit e74d49fc8a
57 changed files with 299 additions and 252 deletions

View File

@@ -458,7 +458,7 @@ impl<T: EthSpec> BeaconState<T> {
epoch: Epoch,
spec: &ChainSpec,
) -> Result<&Hash256, BeaconStateError> {
self.get_block_root(epoch.start_slot(spec.slots_per_epoch))
self.get_block_root(epoch.start_slot(T::slots_per_epoch()))
}
/// Sets the block root for some given slot.

View File

@@ -1,5 +1,5 @@
use crate::*;
use fixed_len_vec::typenum::{Unsigned, U1024, U8, U8192};
use fixed_len_vec::typenum::{Unsigned, U0, U1024, U64, U8, U8192};
use serde_derive::{Deserialize, Serialize};
use std::fmt::Debug;
@@ -9,14 +9,24 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq {
type LatestRandaoMixesLength: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type LatestActiveIndexRootsLength: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type LatestSlashedExitLength: Unsigned + Clone + Sync + Send + Debug + PartialEq;
/// Note: `SlotsPerEpoch` is not necessarily required to be a compile-time constant. We include
/// it here just for the convenience of not passing `slots_per_epoch` around all the time.
type SlotsPerEpoch: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type GenesisEpoch: Unsigned + Clone + Sync + Send + Debug + PartialEq;
fn spec() -> ChainSpec;
fn default_spec() -> ChainSpec;
fn genesis_epoch() -> Epoch {
Epoch::new(Self::GenesisEpoch::to_u64())
}
/// Return the number of committees in one epoch.
///
/// Spec v0.6.1
fn get_epoch_committee_count(active_validator_count: usize) -> usize {
let target_committee_size = Self::spec().target_committee_size;
fn get_epoch_committee_count(
active_validator_count: usize,
target_committee_size: usize,
) -> usize {
let shard_count = Self::shard_count();
let slots_per_epoch = Self::slots_per_epoch() as usize;
@@ -35,21 +45,14 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq {
/// basic sense. This count is not required to provide any security guarantees regarding
/// decentralization, entropy, etc.
fn minimum_validator_count() -> usize {
Self::slots_per_epoch() as usize
Self::SlotsPerEpoch::to_usize()
}
/// Returns the `SLOTS_PER_EPOCH` constant for this specification.
///
/// Spec v0.6.1
fn slots_per_epoch() -> u64 {
Self::spec().slots_per_epoch
}
/// Returns the `SLOTS_PER_EPOCH` constant for this specification.
///
/// Spec v0.6.1
fn genesis_epoch() -> Epoch {
Self::spec().genesis_epoch
Self::SlotsPerEpoch::to_u64()
}
/// Returns the `SHARD_COUNT` constant for this specification.
@@ -100,8 +103,10 @@ impl EthSpec for FoundationEthSpec {
type LatestRandaoMixesLength = U8192;
type LatestActiveIndexRootsLength = U8192;
type LatestSlashedExitLength = U8192;
type SlotsPerEpoch = U64;
type GenesisEpoch = U0;
fn spec() -> ChainSpec {
fn default_spec() -> ChainSpec {
ChainSpec::foundation()
}
}
@@ -118,9 +123,11 @@ impl EthSpec for FewValidatorsEthSpec {
type LatestRandaoMixesLength = U8192;
type LatestActiveIndexRootsLength = U8192;
type LatestSlashedExitLength = U8192;
type SlotsPerEpoch = U8;
type GenesisEpoch = U0;
fn spec() -> ChainSpec {
ChainSpec::few_validators()
fn default_spec() -> ChainSpec {
ChainSpec::few_validators(Self::slots_per_epoch())
}
}
@@ -136,9 +143,11 @@ impl EthSpec for LighthouseTestnetEthSpec {
type LatestRandaoMixesLength = U8192;
type LatestActiveIndexRootsLength = U8192;
type LatestSlashedExitLength = U8192;
type SlotsPerEpoch = U8;
type GenesisEpoch = U0;
fn spec() -> ChainSpec {
ChainSpec::lighthouse_testnet()
fn default_spec() -> ChainSpec {
ChainSpec::lighthouse_testnet(Self::slots_per_epoch())
}
}

View File

@@ -45,13 +45,18 @@ impl CommitteeCache {
return Err(Error::InsufficientValidators);
}
let committee_count = T::get_epoch_committee_count(active_validator_indices.len()) as usize;
let committee_count = T::get_epoch_committee_count(
active_validator_indices.len(),
spec.target_committee_size,
) as usize;
let shuffling_start_shard = match relative_epoch {
RelativeEpoch::Current => state.latest_start_shard,
RelativeEpoch::Previous => {
let committees_in_previous_epoch =
T::get_epoch_committee_count(active_validator_indices.len()) as u64;
let committees_in_previous_epoch = T::get_epoch_committee_count(
active_validator_indices.len(),
spec.target_committee_size,
) as u64;
(state.latest_start_shard + T::shard_count() as u64 - committees_in_previous_epoch)
% T::shard_count() as u64
@@ -59,8 +64,10 @@ impl CommitteeCache {
RelativeEpoch::Next => {
let current_active_validators =
get_active_validator_count(&state.validator_registry, state.current_epoch());
let committees_in_current_epoch =
T::get_epoch_committee_count(current_active_validators) as u64;
let committees_in_current_epoch = T::get_epoch_committee_count(
current_active_validators,
spec.target_committee_size,
) as u64;
(state.latest_start_shard + committees_in_current_epoch) % T::shard_count() as u64
}

View File

@@ -20,7 +20,7 @@ fn default_values() {
}
fn new_state<T: EthSpec>(validator_count: usize, slot: Slot) -> BeaconState<T> {
let spec = &T::spec();
let spec = &T::default_spec();
let mut builder =
TestingBeaconStateBuilder::from_single_keypair(validator_count, &Keypair::random(), spec);
@@ -35,7 +35,7 @@ fn new_state<T: EthSpec>(validator_count: usize, slot: Slot) -> BeaconState<T> {
#[test]
fn fails_without_validators() {
let state = new_state::<FewValidatorsEthSpec>(0, Slot::new(0));
let spec = &FewValidatorsEthSpec::spec();
let spec = &FewValidatorsEthSpec::default_spec();
assert_eq!(
CommitteeCache::initialized(&state, state.current_epoch(), &spec),
@@ -46,7 +46,7 @@ fn fails_without_validators() {
#[test]
fn initializes_with_the_right_epoch() {
let state = new_state::<FewValidatorsEthSpec>(16, Slot::new(0));
let spec = &FewValidatorsEthSpec::spec();
let spec = &FewValidatorsEthSpec::default_spec();
let cache = CommitteeCache::default();
assert_eq!(cache.initialized_epoch, None);
@@ -68,7 +68,7 @@ fn shuffles_for_the_right_epoch() {
let slot = epoch.start_slot(FewValidatorsEthSpec::slots_per_epoch());
let mut state = new_state::<FewValidatorsEthSpec>(num_validators, slot);
let spec = &FewValidatorsEthSpec::spec();
let spec = &FewValidatorsEthSpec::default_spec();
let distinct_hashes: Vec<Hash256> = (0..FewValidatorsEthSpec::latest_randao_mixes_length())
.into_iter()
@@ -123,7 +123,7 @@ fn can_start_on_any_shard() {
let slot = epoch.start_slot(FewValidatorsEthSpec::slots_per_epoch());
let mut state = new_state::<FewValidatorsEthSpec>(num_validators, slot);
let spec = &FewValidatorsEthSpec::spec();
let spec = &FewValidatorsEthSpec::default_spec();
for i in 0..FewValidatorsEthSpec::shard_count() as u64 {
state.latest_start_shard = i;
@@ -150,15 +150,17 @@ impl EthSpec for ExcessShardsEthSpec {
type LatestRandaoMixesLength = U8192;
type LatestActiveIndexRootsLength = U8192;
type LatestSlashedExitLength = U8192;
type SlotsPerEpoch = U8;
type GenesisEpoch = U0;
fn spec() -> ChainSpec {
ChainSpec::few_validators()
fn default_spec() -> ChainSpec {
ChainSpec::few_validators(Self::slots_per_epoch())
}
}
#[test]
fn starts_on_the_correct_shard() {
let spec = &ExcessShardsEthSpec::spec();
let spec = &ExcessShardsEthSpec::default_spec();
let num_validators = ExcessShardsEthSpec::shard_count();
@@ -200,14 +202,16 @@ fn starts_on_the_correct_shard() {
let previous_shards = ExcessShardsEthSpec::get_epoch_committee_count(
get_active_validator_count(&state.validator_registry, previous_epoch),
spec.target_committee_size,
);
let current_shards = ExcessShardsEthSpec::get_epoch_committee_count(
get_active_validator_count(&state.validator_registry, current_epoch),
spec.target_committee_size,
);
let next_shards = ExcessShardsEthSpec::get_epoch_committee_count(
get_active_validator_count(&state.validator_registry, next_epoch),
spec.target_committee_size,
);
let next_shards = ExcessShardsEthSpec::get_epoch_committee_count(get_active_validator_count(
&state.validator_registry,
next_epoch,
));
assert_eq!(
previous_shards as usize,

View File

@@ -7,7 +7,7 @@ ssz_tests!(FoundationBeaconState);
cached_tree_hash_tests!(FoundationBeaconState);
fn test_beacon_proposer_index<T: EthSpec>() {
let spec = T::spec();
let spec = T::default_spec();
let relative_epoch = RelativeEpoch::Current;
// Build a state for testing.
@@ -61,7 +61,7 @@ fn beacon_proposer_index() {
/// (current_epoch - LATEST_ACTIVE_INDEX_ROOTS_LENGTH + ACTIVATION_EXIT_DELAY, current_epoch +
/// ACTIVATION_EXIT_DELAY]
fn active_index_range<T: EthSpec>(current_epoch: Epoch) -> RangeInclusive<Epoch> {
let delay = T::spec().activation_exit_delay;
let delay = T::default_spec().activation_exit_delay;
let start: i32 =
current_epoch.as_u64() as i32 - T::latest_active_index_roots() as i32 + delay as i32;
@@ -79,7 +79,7 @@ fn active_index_range<T: EthSpec>(current_epoch: Epoch) -> RangeInclusive<Epoch>
/// Test getting an active index root at the start and end of the valid range, and one either side
/// of that range.
fn test_active_index<T: EthSpec>(state_slot: Slot) {
let spec = T::spec();
let spec = T::default_spec();
let builder: TestingBeaconStateBuilder<T> =
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(16, &spec);
let (mut state, _keypairs) = builder.build();
@@ -133,8 +133,8 @@ fn test_cache_initialization<'a, T: EthSpec>(
spec: &ChainSpec,
) {
let slot = relative_epoch
.into_epoch(state.slot.epoch(spec.slots_per_epoch))
.start_slot(spec.slots_per_epoch);
.into_epoch(state.slot.epoch(T::slots_per_epoch()))
.start_slot(T::slots_per_epoch());
// Assuming the cache isn't already built, assert that a call to a cache-using function fails.
assert_eq!(
@@ -166,13 +166,13 @@ fn test_cache_initialization<'a, T: EthSpec>(
#[test]
fn cache_initialization() {
let spec = FewValidatorsEthSpec::spec();
let spec = FewValidatorsEthSpec::default_spec();
let builder: TestingBeaconStateBuilder<FewValidatorsEthSpec> =
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(16, &spec);
let (mut state, _keypairs) = builder.build();
state.slot = (spec.genesis_epoch + 1).start_slot(spec.slots_per_epoch);
state.slot = (spec.genesis_epoch + 1).start_slot(FewValidatorsEthSpec::slots_per_epoch());
test_cache_initialization(&mut state, RelativeEpoch::Previous, &spec);
test_cache_initialization(&mut state, RelativeEpoch::Current, &spec);
@@ -234,7 +234,7 @@ mod committees {
(start_shard..start_shard + T::shard_count() as u64).into_iter();
// Loop through all slots in the epoch being tested.
for slot in epoch.slot_iter(spec.slots_per_epoch) {
for slot in epoch.slot_iter(T::slots_per_epoch()) {
let crosslink_committees = state.get_crosslink_committees_at_slot(slot).unwrap();
// Assert that the number of committees in this slot is consistent with the reported number
@@ -290,7 +290,7 @@ mod committees {
state_epoch: Epoch,
cache_epoch: RelativeEpoch,
) {
let spec = &T::spec();
let spec = &T::default_spec();
let mut builder = TestingBeaconStateBuilder::from_single_keypair(
validator_count,
@@ -298,7 +298,7 @@ mod committees {
spec,
);
let slot = state_epoch.start_slot(spec.slots_per_epoch);
let slot = state_epoch.start_slot(T::slots_per_epoch());
builder.teleport_to_slot(slot, spec);
let (mut state, _keypairs): (BeaconState<T>, _) = builder.build();
@@ -325,7 +325,7 @@ mod committees {
}
fn committee_consistency_test_suite<T: EthSpec>(cached_epoch: RelativeEpoch) {
let spec = T::spec();
let spec = T::default_spec();
let validator_count = (T::shard_count() * spec.target_committee_size) + 1;

View File

@@ -1,6 +1,6 @@
use crate::*;
use int_to_bytes::int_to_bytes4;
use serde_derive::Deserialize;
use serde_derive::{Deserialize, Serialize};
use test_utils::u8_from_hex_str;
/// Each of the BLS signature domains.
@@ -18,7 +18,7 @@ pub enum Domain {
/// Holds all the "constants" for a BeaconChain.
///
/// Spec v0.6.1
#[derive(PartialEq, Debug, Clone, Deserialize)]
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct ChainSpec {
/*
@@ -59,7 +59,7 @@ pub struct ChainSpec {
*/
pub seconds_per_slot: u64,
pub min_attestation_inclusion_delay: u64,
pub slots_per_epoch: u64,
//pub slots_per_epoch: u64,
pub min_seed_lookahead: Epoch,
pub activation_exit_delay: u64,
pub slots_per_eth1_voting_period: u64,
@@ -137,7 +137,7 @@ impl ChainSpec {
/// Returns a `ChainSpec` compatible with the Ethereum Foundation specification.
///
/// Spec v0.6.1
pub(crate) fn foundation() -> Self {
pub fn foundation() -> Self {
Self {
/*
* Misc
@@ -176,7 +176,7 @@ impl ChainSpec {
*/
seconds_per_slot: 6,
min_attestation_inclusion_delay: 4,
slots_per_epoch: 64,
// slots_per_epoch: 64,
min_seed_lookahead: Epoch::new(1),
activation_exit_delay: 4,
slots_per_eth1_voting_period: 1_024,
@@ -226,7 +226,7 @@ impl ChainSpec {
/// Returns a `ChainSpec` compatible with the Lighthouse testnet specification.
///
/// Spec v0.4.0
pub(crate) fn lighthouse_testnet() -> Self {
pub fn lighthouse_testnet(slots_per_epoch: u64) -> Self {
/*
* Lighthouse testnet bootnodes
*/
@@ -237,21 +237,19 @@ impl ChainSpec {
Self {
boot_nodes,
chain_id: 2, // lighthouse testnet chain id
..ChainSpec::few_validators()
..ChainSpec::few_validators(slots_per_epoch)
}
}
/// Returns a `ChainSpec` compatible with the specification suitable for 8 validators.
pub(crate) fn few_validators() -> Self {
pub fn few_validators(slots_per_epoch: u64) -> Self {
let genesis_slot = Slot::new(0);
let slots_per_epoch = 8;
let genesis_epoch = genesis_slot.epoch(slots_per_epoch);
Self {
target_committee_size: 1,
genesis_slot,
genesis_epoch,
slots_per_epoch,
..ChainSpec::foundation()
}
}

View File

@@ -21,7 +21,7 @@ impl TestingAttestationDataBuilder {
let previous_epoch = state.previous_epoch();
let is_previous_epoch =
state.slot.epoch(spec.slots_per_epoch) != slot.epoch(spec.slots_per_epoch);
state.slot.epoch(T::slots_per_epoch()) != slot.epoch(T::slots_per_epoch());
let source_epoch = if is_previous_epoch {
state.previous_justified_epoch
@@ -37,11 +37,11 @@ impl TestingAttestationDataBuilder {
let target_root = if is_previous_epoch {
*state
.get_block_root(previous_epoch.start_slot(spec.slots_per_epoch))
.get_block_root(previous_epoch.start_slot(T::slots_per_epoch()))
.unwrap()
} else {
*state
.get_block_root(current_epoch.start_slot(spec.slots_per_epoch))
.get_block_root(current_epoch.start_slot(T::slots_per_epoch()))
.unwrap()
};
@@ -57,7 +57,7 @@ impl TestingAttestationDataBuilder {
};
let source_root = *state
.get_block_root(source_epoch.start_slot(spec.slots_per_epoch))
.get_block_root(source_epoch.start_slot(T::slots_per_epoch()))
.unwrap();
let data = AttestationData {

View File

@@ -36,9 +36,9 @@ impl TestingBeaconBlockBuilder {
/// Signs the block.
///
/// Modifying the block after signing may invalidate the signature.
pub fn sign(&mut self, sk: &SecretKey, fork: &Fork, spec: &ChainSpec) {
pub fn sign<T: EthSpec>(&mut self, sk: &SecretKey, fork: &Fork, spec: &ChainSpec) {
let message = self.block.signed_root();
let epoch = self.block.slot.epoch(spec.slots_per_epoch);
let epoch = self.block.slot.epoch(T::slots_per_epoch());
let domain = spec.get_domain(epoch, Domain::BeaconProposer, fork);
self.block.signature = Signature::new(&message, domain, sk);
}
@@ -46,8 +46,8 @@ impl TestingBeaconBlockBuilder {
/// Sets the randao to be a signature across the blocks epoch.
///
/// Modifying the block's slot after signing may invalidate the signature.
pub fn set_randao_reveal(&mut self, sk: &SecretKey, fork: &Fork, spec: &ChainSpec) {
let epoch = self.block.slot.epoch(spec.slots_per_epoch);
pub fn set_randao_reveal<T: EthSpec>(&mut self, sk: &SecretKey, fork: &Fork, spec: &ChainSpec) {
let epoch = self.block.slot.epoch(T::slots_per_epoch());
let message = epoch.tree_hash_root();
let domain = spec.get_domain(epoch, Domain::Randao, fork);
self.block.body.randao_reveal = Signature::new(&message, domain, sk);
@@ -59,14 +59,15 @@ impl TestingBeaconBlockBuilder {
}
/// Inserts a signed, valid `ProposerSlashing` for the validator.
pub fn insert_proposer_slashing(
pub fn insert_proposer_slashing<T: EthSpec>(
&mut self,
validator_index: u64,
secret_key: &SecretKey,
fork: &Fork,
spec: &ChainSpec,
) {
let proposer_slashing = build_proposer_slashing(validator_index, secret_key, fork, spec);
let proposer_slashing =
build_proposer_slashing::<T>(validator_index, secret_key, fork, spec);
self.block.body.proposer_slashings.push(proposer_slashing);
}
@@ -115,7 +116,7 @@ impl TestingBeaconBlockBuilder {
// - The slot is too old to be included in a block at this slot.
// - The `MAX_ATTESTATIONS`.
loop {
if state.slot >= slot + spec.slots_per_epoch {
if state.slot >= slot + T::slots_per_epoch() {
break;
}
@@ -194,7 +195,7 @@ impl TestingBeaconBlockBuilder {
builder.set_index(index);
builder.sign(
&keypair,
state.slot.epoch(spec.slots_per_epoch),
state.slot.epoch(T::slots_per_epoch()),
&state.fork,
spec,
);
@@ -211,7 +212,7 @@ impl TestingBeaconBlockBuilder {
spec: &ChainSpec,
) {
let mut builder = TestingVoluntaryExitBuilder::new(
state.slot.epoch(spec.slots_per_epoch),
state.slot.epoch(T::slots_per_epoch()),
validator_index,
);
@@ -234,14 +235,19 @@ impl TestingBeaconBlockBuilder {
spec: &ChainSpec,
) {
let mut builder = TestingTransferBuilder::new(from, to, amount, state.slot);
builder.sign(keypair, &state.fork, spec);
builder.sign::<T>(keypair, &state.fork, spec);
self.block.body.transfers.push(builder.build())
}
/// Signs and returns the block, consuming the builder.
pub fn build(mut self, sk: &SecretKey, fork: &Fork, spec: &ChainSpec) -> BeaconBlock {
self.sign(sk, fork, spec);
pub fn build<T: EthSpec>(
mut self,
sk: &SecretKey,
fork: &Fork,
spec: &ChainSpec,
) -> BeaconBlock {
self.sign::<T>(sk, fork, spec);
self.block
}
@@ -254,7 +260,7 @@ impl TestingBeaconBlockBuilder {
/// Builds an `ProposerSlashing` for some `validator_index`.
///
/// Signs the message using a `BeaconChainHarness`.
fn build_proposer_slashing(
fn build_proposer_slashing<T: EthSpec>(
validator_index: u64,
secret_key: &SecretKey,
fork: &Fork,
@@ -265,7 +271,7 @@ fn build_proposer_slashing(
Signature::new(message, domain, secret_key)
};
TestingProposerSlashingBuilder::double_vote(validator_index, signer, spec)
TestingProposerSlashingBuilder::double_vote::<T, _>(validator_index, signer, spec)
}
/// Builds an `AttesterSlashing` for some `validator_indices`.

View File

@@ -173,7 +173,7 @@ impl<T: EthSpec> TestingBeaconStateBuilder<T> {
/// Sets the `BeaconState` to be in a slot, calling `teleport_to_epoch` to update the epoch.
pub fn teleport_to_slot(&mut self, slot: Slot, spec: &ChainSpec) {
self.teleport_to_epoch(slot.epoch(spec.slots_per_epoch), spec);
self.teleport_to_epoch(slot.epoch(T::slots_per_epoch()), spec);
self.state.slot = slot;
}
@@ -184,7 +184,7 @@ impl<T: EthSpec> TestingBeaconStateBuilder<T> {
fn teleport_to_epoch(&mut self, epoch: Epoch, spec: &ChainSpec) {
let state = &mut self.state;
let slot = epoch.start_slot(spec.slots_per_epoch);
let slot = epoch.start_slot(T::slots_per_epoch());
state.slot = slot;
@@ -214,8 +214,8 @@ impl<T: EthSpec> TestingBeaconStateBuilder<T> {
let current_epoch = state.current_epoch();
let previous_epoch = state.previous_epoch();
let first_slot = previous_epoch.start_slot(spec.slots_per_epoch).as_u64();
let last_slot = current_epoch.end_slot(spec.slots_per_epoch).as_u64()
let first_slot = previous_epoch.start_slot(T::slots_per_epoch()).as_u64();
let last_slot = current_epoch.end_slot(T::slots_per_epoch()).as_u64()
- spec.min_attestation_inclusion_delay;
let last_slot = std::cmp::min(state.slot.as_u64(), last_slot);

View File

@@ -17,8 +17,9 @@ impl TestingProposerSlashingBuilder {
/// - `domain: Domain`
///
/// Where domain is a domain "constant" (e.g., `spec.domain_attestation`).
pub fn double_vote<F>(proposer_index: u64, signer: F, spec: &ChainSpec) -> ProposerSlashing
pub fn double_vote<T, F>(proposer_index: u64, signer: F, spec: &ChainSpec) -> ProposerSlashing
where
T: EthSpec,
F: Fn(u64, &[u8], Epoch, Domain) -> Signature,
{
let slot = Slot::new(0);
@@ -40,13 +41,13 @@ impl TestingProposerSlashingBuilder {
header_1.signature = {
let message = header_1.signed_root();
let epoch = slot.epoch(spec.slots_per_epoch);
let epoch = slot.epoch(T::slots_per_epoch());
signer(proposer_index, &message[..], epoch, Domain::BeaconProposer)
};
header_2.signature = {
let message = header_2.signed_root();
let epoch = slot.epoch(spec.slots_per_epoch);
let epoch = slot.epoch(T::slots_per_epoch());
signer(proposer_index, &message[..], epoch, Domain::BeaconProposer)
};

View File

@@ -29,10 +29,10 @@ impl TestingTransferBuilder {
/// Signs the transfer.
///
/// The keypair must match that of the `from` validator index.
pub fn sign(&mut self, keypair: Keypair, fork: &Fork, spec: &ChainSpec) {
pub fn sign<T: EthSpec>(&mut self, keypair: Keypair, fork: &Fork, spec: &ChainSpec) {
self.transfer.pubkey = keypair.pk;
let message = self.transfer.signed_root();
let epoch = self.transfer.slot.epoch(spec.slots_per_epoch);
let epoch = self.transfer.slot.epoch(T::slots_per_epoch());
let domain = spec.get_domain(epoch, Domain::Transfer, fork);
self.transfer.signature = Signature::new(&message, domain, &keypair.sk);