Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
051d71e
Mockup
kianenigma Sep 24, 2020
46686d6
Fix offchain election to respect the weight
kianenigma Sep 25, 2020
649a6d6
Fix builds a bit
kianenigma Sep 25, 2020
ad19e02
Update frame/staking/src/offchain_election.rs
kianenigma Sep 28, 2020
08aec98
Update frame/staking/src/offchain_election.rs
kianenigma Sep 28, 2020
a1feaf8
Make it build, binary search
kianenigma Sep 28, 2020
b7138f9
Merge branch 'kiz-staking-OCW-check-size' of github.com:paritytech/su…
kianenigma Sep 28, 2020
2274196
Fix a number of grumbles
kianenigma Sep 28, 2020
2572e4b
one more fix.
kianenigma Sep 28, 2020
3f53149
remove unwrap.
kianenigma Sep 28, 2020
cc89113
better alg.
kianenigma Sep 28, 2020
a72069f
Better alg again.
kianenigma Sep 29, 2020
8048e4f
Merge branch 'master' of github.com:paritytech/substrate into kiz-sta…
kianenigma Sep 29, 2020
0e3c91f
Final fixes
kianenigma Sep 29, 2020
47d69f7
Fix
kianenigma Sep 30, 2020
86d24a3
Rollback to normal
kianenigma Sep 30, 2020
0b618d4
Merge branch 'master' of github.com:paritytech/substrate into kiz-sta…
kianenigma Oct 2, 2020
6b6ef3e
Final touches.
kianenigma Oct 2, 2020
addd535
Better tests.
kianenigma Oct 2, 2020
e11ab9d
Update frame/staking/src/lib.rs
kianenigma Oct 2, 2020
f1d9f2b
Proper maxExtWeight
kianenigma Oct 2, 2020
4573d36
Merge branch 'kiz-staking-OCW-check-size' of github.com:paritytech/su…
kianenigma Oct 2, 2020
a1ef1b4
Merge branch 'master' of github.com:paritytech/substrate into kiz-sta…
kianenigma Oct 2, 2020
43b149c
Final fix
kianenigma Oct 2, 2020
3ff8c0a
Final fix for the find_voter
kianenigma Oct 2, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -441,13 +441,13 @@ parameter_types! {
pub const MaxIterations: u32 = 10;
// 0.05%. The higher the value, the more strict solution acceptance becomes.
pub MinSolutionScoreBump: Perbill = Perbill::from_rational_approximation(5u32, 10_000);
// The unsigned solution is operational, so as long as the average on_initialize weights are
// less than `MaximumBlockWeight * (1 - T::AvailableRatio)`, it can consume at most most this
// amount.
// The unsigned solution is mandatory, and this is the absolute maximum weight that we allow for
// it.
pub OffchainSolutionWeightLimit: Weight =
MaximumBlockWeight::get()
.saturating_sub(BlockExecutionWeight::get())
Copy link
Member

Choose a reason for hiding this comment

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

This is supposed to be the "largest" extrinsic possible, but doesnt take into account weight introduced by on_initialize. Is that an issue?

Copy link
Member

Choose a reason for hiding this comment

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

It seems you may be better off querying the current block weight as registered in System and using that to determine how much space you can fill?

Copy link
Contributor Author

@kianenigma kianenigma Sep 30, 2020

Choose a reason for hiding this comment

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

Indeed the intention is to be the big boy here, generate the largest possible extrinsic. Because this is now operational, it is fine even if we have some on_initialize weight in the block, as long as average_on_initialize < operational_size. See operational_works_on_full_block in check_weight.rs

Yes, the on_initialize is an issue.

It seems you may be better off querying the current block weight as registered in System and using that to determine how much space you can fill?

maybe... But there are flaws to this as well:

  1. Configurability of the current system is better. A chain might want to allow simply 10% of block weight for npos solution, not always all.
  2. The transaction is almost likely produced with the data of block N and validated later for authoring against N + 1 or N + 2 or.. so this is a good hint, but not accurate.

.saturating_sub(ExtrinsicBaseWeight::get());
.saturating_sub(ExtrinsicBaseWeight::get())
.saturating_sub(AVERAGE_ON_INITIALIZE_WEIGHT * MaximumBlockWeight::get());
}

impl pallet_staking::Trait for Runtime {
Expand Down
66 changes: 42 additions & 24 deletions frame/staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1239,6 +1239,8 @@ decl_error! {
OffchainElectionBogusScore,
/// The election size is invalid.
OffchainElectionBogusElectionSize,
/// The election weight is invalid.
OffchainElectionBogusElectionWeight,
/// The call is not allowed at the given time due to restrictions of election period.
CallNotAllowed,
/// Incorrect previous history depth input provided.
Expand Down Expand Up @@ -1346,12 +1348,12 @@ decl_module! {
if Self::era_election_status().is_open_at(now) {
let offchain_status = set_check_offchain_execution_status::<T>(now);
if let Err(why) = offchain_status {
log!(debug, "skipping offchain worker in open election window due to [{}]", why);
log!(warn, "💸 skipping offchain worker in open election window due to [{}]", why);
} else {
if let Err(e) = compute_offchain_election::<T>() {
log!(error, "💸 Error in election offchain worker: {:?}", e);
} else {
log!(debug, "Executed offchain worker thread without errors.");
log!(debug, "💸 Executed offchain worker thread without errors.");
}
}
}
Expand Down Expand Up @@ -1380,17 +1382,6 @@ decl_module! {
)
);

// Offchain solution is operational, so it can consume all the block weight except for
// `System::BlockExecutionWeight`. So setting the limit above this is invalid.
assert!(
T::OffchainSolutionWeightLimit::get() <=
(
<T as frame_system::Trait>::MaximumBlockWeight::get() -
<T as frame_system::Trait>::BlockExecutionWeight::get() -
<T as frame_system::Trait>::ExtrinsicBaseWeight::get()
)
);

use sp_runtime::UpperOf;
// see the documentation of `Assignment::try_normalize`. Now we can ensure that this
// will always return `Ok`.
Expand Down Expand Up @@ -2159,15 +2150,15 @@ decl_module! {
/// transaction in the block.
///
/// # <weight>
/// See `crate::weight` module.
/// The weight of this call is not enforced by the system module and is enforced in this
/// module directly.
/// # </weight>
#[weight = (
T::WeightInfo::submit_solution_better(
#[weight = (T::WeightInfo::submit_solution_better(
size.validators.into(),
size.nominators.into(),
compact.len() as u32,
winners.len() as u32,
), frame_support::weights::DispatchClass::Operational)]
), frame_support::weights::DispatchClass::Mandatory)]
pub fn submit_election_solution_unsigned(
origin,
winners: Vec<ValidatorIndex>,
Expand All @@ -2177,6 +2168,13 @@ decl_module! {
size: ElectionSize,
) -> DispatchResultWithPostInfo {
ensure_none(origin)?;
// The signed solution is `Normal` and the system module checks the weight. But for this
// mandatory one, we need to do it.
ensure!(
Self::check_unsigned_solution_weight(&winners, &compact, &size),
Error::<T>::OffchainElectionBogusElectionWeight.with_weight(0),
);

let adjustments = Self::check_and_replace_solution(
winners,
compact,
Expand All @@ -2190,6 +2188,7 @@ decl_module! {
effectively depriving the validators from their authoring reward. Hence, this panic
is expected."
);

Ok(adjustments)
}
}
Expand All @@ -2209,6 +2208,21 @@ impl<T: Trait> Module<T> {
)
}

/// Check the weight of an unsigned solution. Returns true of the weight is below the designated
/// limit .
pub fn check_unsigned_solution_weight(
winners: &Vec<ValidatorIndex>,
compact: &CompactAssignments,
size: &ElectionSize,
) -> bool {
T::WeightInfo::submit_solution_better(
size.validators.into(),
size.nominators.into(),
compact.len() as u32,
winners.len() as u32,
) <= T::OffchainSolutionWeightLimit::get()
}

/// Dump the list of validators and nominators into vectors and keep them on-chain.
///
/// This data is used to efficiently evaluate election results. returns `true` if the operation
Expand Down Expand Up @@ -3101,7 +3115,6 @@ impl<T: Trait> Module<T> {
pub fn set_slash_reward_fraction(fraction: Perbill) {
SlashRewardFraction::put(fraction);
}

}

/// In this implementation `new_session(session)` must be called before `end_session(session-1)`
Expand Down Expand Up @@ -3365,11 +3378,11 @@ impl<T: Trait> frame_support::unsigned::ValidateUnsigned for Module<T> {
type Call = Call<T>;
fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity {
if let Call::submit_election_solution_unsigned(
_,
_,
winners,
compact,
score,
era,
_,
size,
) = call {
use offchain_election::DEFAULT_LONGEVITY;

Expand All @@ -3382,17 +3395,22 @@ impl<T: Trait> frame_support::unsigned::ValidateUnsigned for Module<T> {
}
}

// discard solution if it is too big.
if !Self::check_unsigned_solution_weight(winners, compact, size) {
return Err(InvalidTransaction::ExhaustsResources.into())
}

if let Err(error_with_post_info) = Self::pre_dispatch_checks(*score, *era) {
let invalid = to_invalid(error_with_post_info);
log!(
debug,
"validate unsigned pre dispatch checks failed due to error #{:?}.",
"💸 validate unsigned pre dispatch checks failed due to error #{:?}.",
invalid,
);
return invalid .into();
return invalid.into();
}

log!(debug, "validateUnsigned succeeded for a solution at era {}.", era);
log!(debug, "💸 validateUnsigned succeeded for a solution at era {}.", era);

ValidTransaction::with_tag_prefix("StakingOffchain")
// The higher the score[0], the better a solution is.
Expand Down
97 changes: 50 additions & 47 deletions frame/staking/src/offchain_election.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ pub(crate) fn compute_offchain_election<T: Trait>() -> Result<(), OffchainElecti

crate::log!(
info,
"prepared a seq-phragmen solution with {} balancing iterations and score {:?}",
"💸 prepared a seq-phragmen solution with {} balancing iterations and score {:?}",
iters,
score,
);
Expand Down Expand Up @@ -281,54 +281,57 @@ pub fn trim_to_weight<T: Trait, FN>(
where
for<'r> FN: Fn(&'r T::AccountId) -> Option<NominatorIndex>,
{
if let Some(to_remove) = compact.len().checked_sub(maximum_allowed_voters as usize) {
// grab all voters and sort them by least stake.
let mut voters_sorted = <Nominators<T>>::iter()
.map(|(who, _)| {
(
who.clone(),
<Module<T>>::slashable_balance_of_vote_weight(&who),
)
})
.collect::<Vec<_>>();
voters_sorted.sort_by_key(|(_, y)| *y);

// start removing from the least stake. Iterate until we know enough have been removed.
let mut removed = 0;
for (maybe_index, _stake) in voters_sorted
.iter()
.map(|(who, stake)| (nominator_index(&who), stake))
{
let index = maybe_index.ok_or(OffchainElectionError::NominatorSnapshotCorrupt)?;
if compact.remove_voter(index) {
crate::log!(
debug,
"removed a voter at index {} with stake {:?} from compact to reduce the size",
index,
_stake,
);
removed += 1
}
match compact.len().checked_sub(maximum_allowed_voters as usize) {
Some(to_remove) if to_remove > 0 => {
// grab all voters and sort them by least stake.
let mut voters_sorted = <Nominators<T>>::iter()
.map(|(who, _)| {
(
who.clone(),
<Module<T>>::slashable_balance_of_vote_weight(&who),
)
})
.collect::<Vec<_>>();
voters_sorted.sort_by_key(|(_, y)| *y);

// start removing from the least stake. Iterate until we know enough have been removed.
let mut removed = 0;
for (maybe_index, _stake) in voters_sorted
.iter()
.map(|(who, stake)| (nominator_index(&who), stake))
{
let index = maybe_index.ok_or(OffchainElectionError::NominatorSnapshotCorrupt)?;
if compact.remove_voter(index) {
crate::log!(
trace,
"💸 removed a voter at index {} with stake {:?} from compact to reduce the size",
index,
_stake,
);
removed += 1
}

if removed >= to_remove {
break;
if removed >= to_remove {
break;
}
}
}

crate::log!(
warn,
"{} voters of {} had to be removed from compact solution due to size limits.",
removed,
compact.len() + removed,
);
Ok(compact)
} else {
// nada, return as-is
crate::log!(
info,
"Compact solution did not get trimmed due to block weight limits.",
);
Ok(compact)
crate::log!(
warn,
"💸 {} nominators out of {} had to be removed from compact solution due to size limits.",
removed,
compact.len() + removed,
);
Ok(compact)
}
_ => {
// nada, return as-is
crate::log!(
info,
"💸 Compact solution did not get trimmed due to block weight limits.",
);
Ok(compact)
}
}
}

Expand Down Expand Up @@ -415,7 +418,7 @@ where
let maximum_allowed_voters =
maximum_compact_len::<T::WeightInfo>(winners.len() as u32, size, maximum_weight);

crate::log!(info, "Maximum weight = {:?} // current weight = {:?} // maximum voters = {:?} // current votes = {:?}",
crate::log!(debug, "💸 Maximum weight = {:?} // current weight = {:?} // maximum voters = {:?} // current votes = {:?}",
maximum_weight,
T::WeightInfo::submit_solution_better(
size.validators.into(),
Expand Down
Loading