Allow multiple proposals per validator per epoch (#662)

Closes #658
This commit is contained in:
Michael Sproul 2019-12-05 10:56:37 +11:00 committed by GitHub
parent a0549e3842
commit 5e8f958977
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 37 additions and 30 deletions

View File

@ -28,8 +28,8 @@ pub struct ValidatorDuty {
pub attestation_committee_index: Option<CommitteeIndex>, pub attestation_committee_index: Option<CommitteeIndex>,
/// The position of the validator in the committee. /// The position of the validator in the committee.
pub attestation_committee_position: Option<usize>, pub attestation_committee_position: Option<usize>,
/// The slot in which a validator must propose a block, or `null` if block production is not required. /// The slots in which a validator must propose a block (can be empty).
pub block_proposal_slot: Option<Slot>, pub block_proposal_slots: Vec<Slot>,
} }
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone, Encode, Decode)] #[derive(PartialEq, Debug, Serialize, Deserialize, Clone, Encode, Decode)]
@ -161,17 +161,18 @@ fn return_validator_duties<T: BeaconChainTypes>(
)) ))
})?; })?;
let block_proposal_slot = validator_proposers let block_proposal_slots = validator_proposers
.iter() .iter()
.find(|(i, _slot)| validator_index == *i) .filter(|(i, _slot)| validator_index == *i)
.map(|(_i, slot)| *slot); .map(|(_i, slot)| *slot)
.collect();
Ok(ValidatorDuty { Ok(ValidatorDuty {
validator_pubkey, validator_pubkey,
attestation_slot: duties.map(|d| d.slot), attestation_slot: duties.map(|d| d.slot),
attestation_committee_index: duties.map(|d| d.index), attestation_committee_index: duties.map(|d| d.index),
attestation_committee_position: duties.map(|d| d.committee_position), attestation_committee_position: duties.map(|d| d.committee_position),
block_proposal_slot, block_proposal_slots,
}) })
} else { } else {
Ok(ValidatorDuty { Ok(ValidatorDuty {
@ -179,7 +180,7 @@ fn return_validator_duties<T: BeaconChainTypes>(
attestation_slot: None, attestation_slot: None,
attestation_committee_index: None, attestation_committee_index: None,
attestation_committee_position: None, attestation_committee_position: None,
block_proposal_slot: None, block_proposal_slots: vec![],
}) })
} }
}) })

View File

@ -294,14 +294,16 @@ fn check_duties<T: BeaconChainTypes>(
"attestation index should match" "attestation index should match"
); );
if let Some(slot) = duty.block_proposal_slot { if !duty.block_proposal_slots.is_empty() {
let expected_proposer = state for slot in &duty.block_proposal_slots {
.get_beacon_proposer_index(slot, spec) let expected_proposer = state
.expect("should know proposer"); .get_beacon_proposer_index(*slot, spec)
assert_eq!( .expect("should know proposer");
expected_proposer, validator_index, assert_eq!(
"should get correct proposal slot" expected_proposer, validator_index,
); "should get correct proposal slot"
);
}
} else { } else {
epoch.slot_iter(E::slots_per_epoch()).for_each(|slot| { epoch.slot_iter(E::slots_per_epoch()).for_each(|slot| {
let slot_proposer = state let slot_proposer = state
@ -314,6 +316,16 @@ fn check_duties<T: BeaconChainTypes>(
}) })
} }
}); });
// Validator duties should include a proposer for every slot of the epoch.
let mut all_proposer_slots: Vec<Slot> = duties
.iter()
.flat_map(|duty| duty.block_proposal_slots.clone())
.collect();
all_proposer_slots.sort();
let all_slots: Vec<Slot> = epoch.slot_iter(E::slots_per_epoch()).collect();
assert_eq!(all_proposer_slots, all_slots);
} }
#[test] #[test]

View File

@ -52,7 +52,7 @@ impl DutiesStore {
let epoch = slot.epoch(slots_per_epoch); let epoch = slot.epoch(slots_per_epoch);
validator_map.get(&epoch).and_then(|duties| { validator_map.get(&epoch).and_then(|duties| {
if duties.block_proposal_slot == Some(slot) { if duties.block_proposal_slots.contains(&slot) {
Some(duties.validator_pubkey.clone()) Some(duties.validator_pubkey.clone())
} else { } else {
None None
@ -363,7 +363,7 @@ impl<T: SlotClock + 'static, E: EthSpec> DutiesService<T, E> {
info!( info!(
log, log,
"First duty assignment for validator"; "First duty assignment for validator";
"proposal_slot" => format!("{:?}", &duties.block_proposal_slot), "proposal_slots" => format!("{:?}", &duties.block_proposal_slots),
"attestation_slot" => format!("{:?}", &duties.attestation_slot), "attestation_slot" => format!("{:?}", &duties.attestation_slot),
"validator" => format!("{:?}", &duties.validator_pubkey) "validator" => format!("{:?}", &duties.validator_pubkey)
); );
@ -408,17 +408,11 @@ impl<T: SlotClock + 'static, E: EthSpec> DutiesService<T, E> {
/// Returns `true` if the slots in the `duties` are from the given `epoch` /// Returns `true` if the slots in the `duties` are from the given `epoch`
fn duties_match_epoch(duties: &ValidatorDuty, epoch: Epoch, slots_per_epoch: u64) -> bool { fn duties_match_epoch(duties: &ValidatorDuty, epoch: Epoch, slots_per_epoch: u64) -> bool {
if let Some(attestation_slot) = duties.attestation_slot { duties
if attestation_slot.epoch(slots_per_epoch) != epoch { .attestation_slot
return false; .map_or(true, |slot| slot.epoch(slots_per_epoch) == epoch)
} && duties
} .block_proposal_slots
.iter()
if let Some(block_proposal_slot) = duties.block_proposal_slot { .all(|slot| slot.epoch(slots_per_epoch) == epoch)
if block_proposal_slot.epoch(slots_per_epoch) != epoch {
return false;
}
}
true
} }