Files
pezkuwi-subxt/substrate/frame/staking/src/offchain_election.rs
T
Rakan Alhneiti 25751c0562 Offchain signing (#5182)
* New approach to offchain signing.

* Use in im-online

* Rewrite to use Account<T>

* DRY signing.

* Implement send_raw_unsigned_transaction

* WiP

* Expunge LocalCall

* Expunge LocalCall

* Fix compilation.

* Solve call.

* Make it compile again.

* Finalize implementation.

* Change CreateTransaction

* Clear CreateTransaction.

* Add price payload

* Send raw transaction

* Submit signed payload / unsigned transaction (WIP)

* Supertrait requirements on T::Signature

* Validate signature of payload on an unsigned transaction

* Fix encoding - part 1

* Make it compile.

* Fix compilation of unsigned validator.

* Pass price payload to the transaction

* Make block number part of the signed payload

* Send signed transaction

* Implement all_accounts, any_account

* Fix formatting

* Implement submit_transaction

* Submit signed transaction (ForAll, ForAny)

* Fix formatting

* Implement CreateSignedTransaction

* Move sign and verify to AppCrypto

* Sign transaction

* Call `use_encoded`

* Remove SubmitAndSignTransaction

* Implement runtime using new SigningTypes

* Adapt offchain example to changes

* Fix im-online pallet

* Quick fix: rename AuthorityId2

* Fix offchain example tests

* Add a comment on why keystore is required in unsigned transaction test

* Use UintAuthorityId instead of u64

* WIP

* Remove IdentifyAccount from UintAuthorityId

* Implement PublicWrapper type

* Fix im-online tests

* Fix runtime test

* Bump spec version

* Fix executor tests

* Rename ImOnlineAuthId -> ImOnlineAuthorityId and formatting

* Fix merge

* Documentation

* Revert u64 -> UintAuthorityId conversion

* Fix string errors

* Document public members in offchain module

* Introduce SubmitTransaction

* Update pallets to use SubmitTransaction

* WIP

* Use SubmitTransaction in offchain

* Use `submit_unsigned_transaction`

* Fix tests

* Update docs

* Remove SigningTypes requirement from `SendTransactionTypes`

* Fix tests

* Update frame/system/src/offchain.rs

Co-Authored-By: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Update frame/system/src/offchain.rs

Co-Authored-By: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Update frame/example-offchain-worker/src/tests.rs

Co-Authored-By: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Update frame/system/src/offchain.rs

Co-Authored-By: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Update frame/system/src/offchain.rs

Co-Authored-By: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Remove leftover from previous iterations

* Change enum to struct

* Remove public

* Move mock to node/executor/tests

* Cleanup test-helpers

* Make `application-crypto` `std` feature internal

The macros should not generate code that requires that the calling crate
has a feature with the name `std` defined.

* Revert cargo lock update

* Use TestAuthorityId from common

* Restore members of account to public

* Tidy up imports

* Fix benchmarking pallet

* Add tests demonstrating ForAll, ForAny on signer

* Move definition of AppCrypto

in example-offchain-worker
from tests to mod::crypto

* Cleanup stray comment

* Fix ValidTransaction

* Re-fix CreateSignedTransaction

* Address PR feedback

* Add can_sign method to signer

* Propagate error

* Improve documentation

* Fix vec! macro not available

* Document SendTransactiontypes

* Add some docs.

* Split signing examples

* Add tests for signing examples

* WIP can_sign - PR feedback

* WIP

* Split for_any / for_all into different calls

* Verify payload and signature in test

* Fix can_sign implementation

* Fix impl_version

* Import Box from sp_std

* Create issues for TODOs

* Ignore doctest.

* Add test directly to system. Adjust UintTypes.

* Add some tests to account filtering.

* Remove code samples and point to example offchain worker

* Fix doc links

* Fix im-online tests using signatures.

Co-authored-by: Tomasz Drwięga <tomasz@parity.io>
Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
Co-authored-by: Bastian Köcher <git@kchr.de>
2020-04-21 14:55:05 +02:00

220 lines
8.0 KiB
Rust

// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Helpers for offchain worker election.
use crate::{
Call, CompactAssignments, Module, NominatorIndex, OffchainAccuracy, Trait, ValidatorIndex,
};
use frame_system::offchain::SubmitTransaction;
use sp_phragmen::{
build_support_map, evaluate_support, reduce, Assignment, ExtendedBalance, PhragmenResult,
PhragmenScore,
};
use sp_runtime::offchain::storage::StorageValueRef;
use sp_runtime::PerThing;
use sp_runtime::RuntimeDebug;
use sp_std::{convert::TryInto, prelude::*};
/// Error types related to the offchain election machinery.
#[derive(RuntimeDebug)]
pub enum OffchainElectionError {
/// Phragmen election returned None. This means less candidate that minimum number of needed
/// validators were present. The chain is in trouble and not much that we can do about it.
ElectionFailed,
/// Submission to the transaction pool failed.
PoolSubmissionFailed,
/// The snapshot data is not available.
SnapshotUnavailable,
/// Error from phragmen crate. This usually relates to compact operation.
PhragmenError(sp_phragmen::Error),
/// One of the computed winners is invalid.
InvalidWinner,
}
impl From<sp_phragmen::Error> for OffchainElectionError {
fn from(e: sp_phragmen::Error) -> Self {
Self::PhragmenError(e)
}
}
/// Storage key used to store the persistent offchain worker status.
pub(crate) const OFFCHAIN_HEAD_DB: &[u8] = b"parity/staking-election/";
/// The repeat threshold of the offchain worker. This means we won't run the offchain worker twice
/// within a window of 5 blocks.
pub(crate) const OFFCHAIN_REPEAT: u32 = 5;
/// Default number of blocks for which the unsigned transaction should stay in the pool
pub(crate) const DEFAULT_LONGEVITY: u64 = 25;
/// Checks if an execution of the offchain worker is permitted at the given block number, or not.
///
/// This essentially makes sure that we don't run on previous blocks in case of a re-org, and we
/// don't run twice within a window of length [`OFFCHAIN_REPEAT`].
///
/// Returns `Ok(())` if offchain worker should happen, `Err(reason)` otherwise.
pub(crate) fn set_check_offchain_execution_status<T: Trait>(
now: T::BlockNumber,
) -> Result<(), &'static str> {
let storage = StorageValueRef::persistent(&OFFCHAIN_HEAD_DB);
let threshold = T::BlockNumber::from(OFFCHAIN_REPEAT);
let mutate_stat =
storage.mutate::<_, &'static str, _>(|maybe_head: Option<Option<T::BlockNumber>>| {
match maybe_head {
Some(Some(head)) if now < head => Err("fork."),
Some(Some(head)) if now >= head && now <= head + threshold => {
Err("recently executed.")
}
Some(Some(head)) if now > head + threshold => {
// we can run again now. Write the new head.
Ok(now)
}
_ => {
// value doesn't exists. Probably this node just booted up. Write, and run
Ok(now)
}
}
});
match mutate_stat {
// all good
Ok(Ok(_)) => Ok(()),
// failed to write.
Ok(Err(_)) => Err("failed to write to offchain db."),
// fork etc.
Err(why) => Err(why),
}
}
/// The internal logic of the offchain worker of this module. This runs the phragmen election,
/// compacts and reduces the solution, computes the score and submits it back to the chain as an
/// unsigned transaction, without any signature.
pub(crate) fn compute_offchain_election<T: Trait>() -> Result<(), OffchainElectionError> {
// compute raw solution. Note that we use `OffchainAccuracy`.
let PhragmenResult {
winners,
assignments,
} = <Module<T>>::do_phragmen::<OffchainAccuracy>()
.ok_or(OffchainElectionError::ElectionFailed)?;
// process and prepare it for submission.
let (winners, compact, score) = prepare_submission::<T>(assignments, winners, true)?;
// defensive-only: current era can never be none except genesis.
let current_era = <Module<T>>::current_era().unwrap_or_default();
// send it.
let call = Call::submit_election_solution_unsigned(
winners,
compact,
score,
current_era,
).into();
SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call)
.map_err(|_| OffchainElectionError::PoolSubmissionFailed)
}
/// Takes a phragmen result and spits out some data that can be submitted to the chain.
///
/// This does a lot of stuff; read the inline comments.
pub fn prepare_submission<T: Trait>(
assignments: Vec<Assignment<T::AccountId, OffchainAccuracy>>,
winners: Vec<(T::AccountId, ExtendedBalance)>,
do_reduce: bool,
) -> Result<(Vec<ValidatorIndex>, CompactAssignments, PhragmenScore), OffchainElectionError> where
ExtendedBalance: From<<OffchainAccuracy as PerThing>::Inner>,
{
// make sure that the snapshot is available.
let snapshot_validators =
<Module<T>>::snapshot_validators().ok_or(OffchainElectionError::SnapshotUnavailable)?;
let snapshot_nominators =
<Module<T>>::snapshot_nominators().ok_or(OffchainElectionError::SnapshotUnavailable)?;
// all helper closures
let nominator_index = |a: &T::AccountId| -> Option<NominatorIndex> {
snapshot_nominators
.iter()
.position(|x| x == a)
.and_then(|i| <usize as TryInto<NominatorIndex>>::try_into(i).ok())
};
let validator_index = |a: &T::AccountId| -> Option<ValidatorIndex> {
snapshot_validators
.iter()
.position(|x| x == a)
.and_then(|i| <usize as TryInto<ValidatorIndex>>::try_into(i).ok())
};
// Clean winners.
let winners = winners
.into_iter()
.map(|(w, _)| w)
.collect::<Vec<T::AccountId>>();
// convert into absolute value and to obtain the reduced version.
let mut staked = sp_phragmen::assignment_ratio_to_staked(
assignments,
<Module<T>>::slashable_balance_of_vote_weight,
);
if do_reduce {
reduce(&mut staked);
}
// Convert back to ratio assignment. This takes less space.
let low_accuracy_assignment = sp_phragmen::assignment_staked_to_ratio(staked);
// convert back to staked to compute the score in the receiver's accuracy. This can be done
// nicer, for now we do it as such since this code is not time-critical. This ensure that the
// score _predicted_ here is the same as the one computed on chain and you will not get a
// `PhragmenBogusScore` error. This is totally NOT needed if we don't do reduce. This whole
// _accuracy glitch_ happens because reduce breaks that assumption of rounding and **scale**.
// The initial phragmen results are computed in `OffchainAccuracy` and the initial `staked`
// assignment set is also all multiples of this value. After reduce, this no longer holds. Hence
// converting to ratio thereafter is not trivially reversible.
let score = {
let staked = sp_phragmen::assignment_ratio_to_staked(
low_accuracy_assignment.clone(),
<Module<T>>::slashable_balance_of_vote_weight,
);
let (support_map, _) = build_support_map::<T::AccountId>(&winners, &staked);
evaluate_support::<T::AccountId>(&support_map)
};
// compact encode the assignment.
let compact = CompactAssignments::from_assignment(
low_accuracy_assignment,
nominator_index,
validator_index,
).map_err(|e| OffchainElectionError::from(e))?;
// winners to index. Use a simple for loop for a more expressive early exit in case of error.
let mut winners_indexed: Vec<ValidatorIndex> = Vec::with_capacity(winners.len());
for w in winners {
if let Some(idx) = snapshot_validators.iter().position(|v| *v == w) {
let compact_index: ValidatorIndex = idx
.try_into()
.map_err(|_| OffchainElectionError::InvalidWinner)?;
winners_indexed.push(compact_index);
} else {
return Err(OffchainElectionError::InvalidWinner);
}
}
Ok((winners_indexed, compact, score))
}