Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Ensure election offchain workers don't overlap #8828

Merged
15 commits merged into from
May 18, 2021
Merged
2 changes: 1 addition & 1 deletion bin/node/cli/src/chain_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ pub fn testnet_genesis(
}).collect::<Vec<_>>(),
},
pallet_staking: StakingConfig {
validator_count: initial_authorities.len() as u32 * 2,
validator_count: initial_authorities.len() as u32,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

offchain elections are only acceptable if the provide exactly enough validators. So since we have double the initial_authorities, offchain elections always fail on a dev chain and it produces some warn/erorr logs.

can revert if it offends anything else.

minimum_validator_count: initial_authorities.len() as u32,
invulnerables: initial_authorities.iter().map(|x| x.0.clone()).collect(),
slash_reward_fraction: Perbill::from_percent(10),
Expand Down
2 changes: 2 additions & 0 deletions frame/election-provider-multi-phase/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ frame-system = { version = "3.0.0", default-features = false, path = "../system"

sp-io = { version = "3.0.0", default-features = false, path = "../../primitives/io" }
sp-std = { version = "3.0.0", default-features = false, path = "../../primitives/std" }
sp-core = { version = "3.0.0", default-features = false, path = "../../primitives/core" }
sp-runtime = { version = "3.0.0", default-features = false, path = "../../primitives/runtime" }
sp-npos-elections = { version = "3.0.0", default-features = false, path = "../../primitives/npos-elections" }
sp-arithmetic = { version = "3.0.0", default-features = false, path = "../../primitives/arithmetic" }
Expand Down Expand Up @@ -56,6 +57,7 @@ std = [

"sp-io/std",
"sp-std/std",
"sp-core/std",
"sp-runtime/std",
"sp-npos-elections/std",
"sp-arithmetic/std",
Expand Down
86 changes: 55 additions & 31 deletions frame/election-provider-multi-phase/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -653,38 +653,24 @@ pub mod pallet {
}

fn offchain_worker(now: T::BlockNumber) {
match Self::current_phase() {
Phase::Unsigned((true, opened)) if opened == now => {
// mine a new solution, cache it, and attempt to submit it
let initial_output = Self::try_acquire_offchain_lock(now)
.and_then(|_| Self::mine_check_save_submit());
log!(info, "initial OCW output at {:?}: {:?}", now, initial_output);
}
Phase::Unsigned((true, opened)) if opened < now => {
// keep trying to submit solutions. worst case, we note that the stored solution
// is better than our cached/computed one, and decide not to submit after all.
//
// the offchain_lock prevents us from spamming submissions too often.
let resubmit_output = Self::try_acquire_offchain_lock(now)
.and_then(|_| Self::restore_or_compute_then_maybe_submit());
log!(info, "resubmit OCW output at {:?}: {:?}", now, resubmit_output);
use sp_runtime::offchain::storage_lock::{StorageLock, BlockAndTime};

// create a lock with the maximum deadline of number of blocks in the unsigned phase.
// This should only come useful in an **abrupt** termination of execution, otherwise the
// guard will be dropped upon successful execution.
let mut lock = StorageLock::<BlockAndTime<frame_system::Pallet::<T>>>::with_block_deadline(
unsigned::OFFCHAIN_LOCK,
T::UnsignedPhase::get().saturated_into(),
);

match lock.try_lock() {
Ok(_guard) => {
Self::do_synchronized_offchain_worker(now);
},
Err(deadline) => {
log!(debug, "offchain worker lock not released, deadline is {:?}", deadline);
}
_ => {}
}
// after election finalization, clear OCW solution storage
if <frame_system::Pallet<T>>::events()
.into_iter()
.filter_map(|event_record| {
let local_event = <T as Config>::Event::from(event_record.event);
local_event.try_into().ok()
})
.find(|event| {
matches!(event, Event::ElectionFinalized(_))
})
.is_some()
{
unsigned::kill_ocw_solution::<T>();
}
};
}

fn integrity_test() {
Expand Down Expand Up @@ -929,6 +915,44 @@ pub mod pallet {
}

impl<T: Config> Pallet<T> {
/// Internal logic of the offchain worker, to be executed only when the offchain lock is
/// acquired with success.
fn do_synchronized_offchain_worker(now: T::BlockNumber) {
log!(trace, "lock for offchain worker acquired.");
match Self::current_phase() {
Phase::Unsigned((true, opened)) if opened == now => {
// mine a new solution, cache it, and attempt to submit it
let initial_output = Self::ensure_offchain_repeat_frequency(now).and_then(|_| {
Self::mine_check_save_submit()
});
log!(debug, "initial offchain thread output: {:?}", initial_output);
}
Phase::Unsigned((true, opened)) if opened < now => {
// try and resubmit the cached solution, and recompute ONLY if it is not
// feasible.
let resubmit_output = Self::ensure_offchain_repeat_frequency(now).and_then(|_| {
Self::restore_or_compute_then_maybe_submit()
});
log!(debug, "resubmit offchain thread output: {:?}", resubmit_output);
}
_ => {}
}

// after election finalization, clear OCW solution storage.
if <frame_system::Pallet<T>>::events()
.into_iter()
.filter_map(|event_record| {
let local_event = <T as Config>::Event::from(event_record.event);
local_event.try_into().ok()
})
.any(|event| {
matches!(event, Event::ElectionFinalized(_))
})
{
unsigned::kill_ocw_solution::<T>();
}
}

/// Logic for [`<Pallet as Hooks>::on_initialize`] when signed phase is being opened.
///
/// This is decoupled for easy weight calculation.
Expand Down
Loading