Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

approval-voting: Make sure we always mark approved candidates approved in a different relay chain context #4153

Merged
merged 13 commits into from
Apr 18, 2024
46 changes: 46 additions & 0 deletions polkadot/node/core/approval-voting/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -978,6 +978,7 @@ where
woken_block,
woken_candidate,
&subsystem.metrics,
&wakeups,
).await?
}
next_msg = ctx.recv().fuse() => {
Expand Down Expand Up @@ -1152,6 +1153,7 @@ async fn handle_actions<Context>(
candidate_hash,
delayed_approvals_timers,
approval_request,
&wakeups,
)
.await?
.into_iter()
Expand Down Expand Up @@ -1663,6 +1665,7 @@ async fn handle_from_overseer<Context>(
|r| {
let _ = res.send(r);
},
&wakeups,
)
.await?
.0,
Expand Down Expand Up @@ -2477,6 +2480,7 @@ async fn check_and_import_approval<T, Sender>(
metrics: &Metrics,
approval: IndirectSignedApprovalVoteV2,
with_response: impl FnOnce(ApprovalCheckResult) -> T,
wakeups: &Wakeups,
) -> SubsystemResult<(Vec<Action>, T)>
where
Sender: SubsystemSender<RuntimeApiMessage>,
Expand Down Expand Up @@ -2655,6 +2659,7 @@ where
approved_candidate_hash,
candidate_entry,
ApprovalStateTransition::RemoteApproval(approval.validator),
wakeups,
)
.await;
actions.extend(new_actions);
Expand Down Expand Up @@ -2689,6 +2694,14 @@ impl ApprovalStateTransition {
ApprovalStateTransition::WakeupProcessed => false,
}
}

fn is_remote_approval(&self) -> bool {
match *self {
ApprovalStateTransition::RemoteApproval(_) => true,
ApprovalStateTransition::LocalApproval(_) => false,
ApprovalStateTransition::WakeupProcessed => false,
}
alexggh marked this conversation as resolved.
Show resolved Hide resolved
}
}

// Advance the approval state, either by importing an approval vote which is already checked to be
Expand All @@ -2705,6 +2718,7 @@ async fn advance_approval_state<Sender>(
candidate_hash: CandidateHash,
mut candidate_entry: CandidateEntry,
transition: ApprovalStateTransition,
wakeups: &Wakeups,
) -> Vec<Action>
where
Sender: SubsystemSender<RuntimeApiMessage>,
Expand Down Expand Up @@ -2835,6 +2849,34 @@ where
status.required_tranches,
));

if is_approved && transition.is_remote_approval() {
// Make sure we wake other blocks in case they have
// a no-show that might be covered by this approval.
for (other_block_hash, other_approval_entry) in candidate_entry
alexggh marked this conversation as resolved.
Show resolved Hide resolved
.block_assignments
.iter()
.filter(|(hash, _)| **hash != block_hash && is_approved)
alexggh marked this conversation as resolved.
Show resolved Hide resolved
{
if wakeups.wakeup_for(*other_block_hash, candidate_hash).is_none() &&
!other_approval_entry.is_approved() &&
validator_index
.as_ref()
.map(|validator_index| other_approval_entry.is_assigned(*validator_index))
.unwrap_or_default()
alexggh marked this conversation as resolved.
Show resolved Hide resolved
{
if let Ok(Some(other_block_entry)) = db.load_block_entry(other_block_hash) {
alexggh marked this conversation as resolved.
Show resolved Hide resolved
actions.push(Action::ScheduleWakeup {
block_hash: *other_block_hash,
block_number: other_block_entry.block_number(),
candidate_hash,
// Schedule the wakeup next tick, since the assignment must be a
// no-show, because there is no-wakeup scheduled.
tick: tick_now + 1,
})
}
}
}
}
// We have no need to write the candidate entry if all of the following
// is true:
//
Expand Down Expand Up @@ -2896,6 +2938,7 @@ async fn process_wakeup<Context>(
relay_block: Hash,
candidate_hash: CandidateHash,
metrics: &Metrics,
wakeups: &Wakeups,
) -> SubsystemResult<Vec<Action>> {
let mut span = state
.spans
Expand Down Expand Up @@ -3064,6 +3107,7 @@ async fn process_wakeup<Context>(
candidate_hash,
candidate_entry,
ApprovalStateTransition::WakeupProcessed,
wakeups,
)
.await,
);
Expand Down Expand Up @@ -3294,6 +3338,7 @@ async fn issue_approval<Context>(
candidate_hash: CandidateHash,
delayed_approvals_timers: &mut DelayedApprovalTimer,
ApprovalVoteRequest { validator_index, block_hash }: ApprovalVoteRequest,
wakeups: &Wakeups,
) -> SubsystemResult<Vec<Action>> {
let mut issue_approval_span = state
.spans
Expand Down Expand Up @@ -3415,6 +3460,7 @@ async fn issue_approval<Context>(
candidate_hash,
candidate_entry,
ApprovalStateTransition::LocalApproval(validator_index as _),
wakeups,
)
.await;

Expand Down
182 changes: 181 additions & 1 deletion polkadot/node/core/approval-voting/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -834,7 +834,6 @@ impl ChainBuilder {
cur_hash = cur_header.parent_hash;
}
ancestry.reverse();

import_block(
overseer,
ancestry.as_ref(),
Expand Down Expand Up @@ -1922,6 +1921,187 @@ fn subsystem_assignment_import_updates_candidate_entry_and_schedules_wakeup() {
});
}

#[test]
fn subsystem_always_has_a_wakeup_when_pending() {
// Approvals sent after all assignments are no-show, the approval
// should be counted on the fork relay chain on the next tick.
test_approvals_on_fork_are_always_considered_after_no_show(
30,
vec![(29, false), (30, false), (31, true)],
);
// Approvals sent before fork no-shows, the approval
// should be counted on the fork relay chain when it no-shows.
test_approvals_on_fork_are_always_considered_after_no_show(
8, //
alexggh marked this conversation as resolved.
Show resolved Hide resolved
vec![(7, false), (8, false), (29, false), (30, true), (31, true)],
);
}

fn test_approvals_on_fork_are_always_considered_after_no_show(
tick_to_send_approval: Tick,
expected_approval_status: Vec<(Tick, bool)>,
) {
let config = HarnessConfig::default();
let store = config.backend();

test_harness(config, |test_harness| async move {
let TestHarness {
mut virtual_overseer,
clock,
sync_oracle_handle: _sync_oracle_handle,
..
} = test_harness;

assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let candidate_hash = Hash::repeat_byte(0x04);

let candidate_descriptor = make_candidate(ParaId::from(1_u32), &candidate_hash);
let candidate_hash = candidate_descriptor.hash();

let block_hash = Hash::repeat_byte(0x01);
let block_hash_fork = Hash::repeat_byte(0x02);

let candidate_index = 0;
let validator = ValidatorIndex(0);
let validators = vec![
Sr25519Keyring::Alice,
Sr25519Keyring::Bob,
Sr25519Keyring::Charlie,
Sr25519Keyring::Dave,
Sr25519Keyring::Eve,
];
// Add block hash 0x01 and for 0x02
ChainBuilder::new()
.add_block(
block_hash,
ChainBuilder::GENESIS_HASH,
1,
BlockConfig {
slot: Slot::from(1),
candidates: Some(vec![(
candidate_descriptor.clone(),
CoreIndex(0),
GroupIndex(0),
)]),
session_info: Some(SessionInfo {
validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(
vec![
vec![ValidatorIndex(0), ValidatorIndex(1)],
vec![ValidatorIndex(2)],
vec![ValidatorIndex(3), ValidatorIndex(4)],
],
),
needed_approvals: 1,
..session_info(&validators)
}),
end_syncing: false,
},
)
.add_block(
block_hash_fork,
ChainBuilder::GENESIS_HASH,
1,
BlockConfig {
slot: Slot::from(1),
candidates: Some(vec![(candidate_descriptor, CoreIndex(0), GroupIndex(0))]),
session_info: Some(SessionInfo {
validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(
vec![
vec![ValidatorIndex(0), ValidatorIndex(1)],
vec![ValidatorIndex(2)],
vec![ValidatorIndex(3), ValidatorIndex(4)],
],
),
needed_approvals: 1,
..session_info(&validators)
}),
end_syncing: false,
},
)
.build(&mut virtual_overseer)
.await;

// Send assignments for the same candidate on both forks
let rx = check_and_import_assignment(
&mut virtual_overseer,
block_hash,
candidate_index,
validator,
)
.await;
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted));

let rx = check_and_import_assignment(
&mut virtual_overseer,
block_hash_fork,
candidate_index,
validator,
)
.await;

assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted));
// Wake on APPROVAL_DELAY first
assert!(clock.inner.lock().current_wakeup_is(2));
clock.inner.lock().set_tick(2);
futures_timer::Delay::new(Duration::from_millis(100)).await;
alexggh marked this conversation as resolved.
Show resolved Hide resolved

// Wake up on no-show
assert!(clock.inner.lock().current_wakeup_is(30));

for (tick, status) in expected_approval_status
.iter()
.filter(|(tick, _)| *tick < tick_to_send_approval)
{
// Wake up on no-show
clock.inner.lock().set_tick(*tick);
futures_timer::Delay::new(Duration::from_millis(100)).await;
let block_entry = store.load_block_entry(&block_hash).unwrap().unwrap();
let block_entry_fork = store.load_block_entry(&block_hash_fork).unwrap().unwrap();
assert!(!block_entry.is_fully_approved());
assert_eq!(block_entry_fork.is_fully_approved(), *status);
}

clock.inner.lock().set_tick(tick_to_send_approval);
futures_timer::Delay::new(Duration::from_millis(100)).await;

// Send the approval for candidate just in the context of 0x01 block.
let rx = check_and_import_approval(
&mut virtual_overseer,
block_hash,
candidate_index,
validator,
candidate_hash,
1,
false,
None,
)
.await;

assert_eq!(rx.await, Ok(ApprovalCheckResult::Accepted),);

// Check approval status for the fork_block is correctly transitioned.
for (tick, status) in expected_approval_status
.iter()
.filter(|(tick, _)| *tick >= tick_to_send_approval)
{
// Wake up on no-show
clock.inner.lock().set_tick(*tick);
futures_timer::Delay::new(Duration::from_millis(100)).await;
let block_entry = store.load_block_entry(&block_hash).unwrap().unwrap();
let block_entry_fork = store.load_block_entry(&block_hash_fork).unwrap().unwrap();
assert!(block_entry.is_fully_approved());
assert_eq!(block_entry_fork.is_fully_approved(), *status);
}

virtual_overseer
});
}

#[test]
fn subsystem_process_wakeup_schedules_wakeup() {
test_harness(HarnessConfig::default(), |test_harness| async move {
Expand Down
Loading