feat: Rebrand Polkadot/Substrate references to PezkuwiChain
This commit systematically rebrands various references from Parity Technologies' Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk. Key changes include: - Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks. - Modified internal documentation and code comments to reflect PezkuwiChain naming and structure. - Replaced direct references to with or specific paths within the for XCM, Pezkuwi, and other modules. - Cleaned up deprecated issue and PR references in various and files, particularly in and modules. - Adjusted image and logo URLs in documentation to point to PezkuwiChain assets. - Removed or rephrased comments related to external Polkadot/Substrate PRs and issues. This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::{
|
||||
unsigned::{miner::OffchainWorkerMiner, Call, Config, Pallet},
|
||||
verifier::Verifier,
|
||||
CurrentPhase, Phase,
|
||||
};
|
||||
use pezframe_benchmarking::v2::*;
|
||||
use pezframe_election_provider_support::ElectionProvider;
|
||||
use pezframe_support::{assert_ok, pezpallet_prelude::*};
|
||||
use pezframe_system::RawOrigin;
|
||||
use pezsp_std::boxed::Box;
|
||||
|
||||
#[benchmarks(where T: crate::Config + crate::signed::Config + crate::verifier::Config)]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
#[benchmark(pov_mode = Measured)]
|
||||
fn validate_unsigned() -> Result<(), BenchmarkError> {
|
||||
#[cfg(test)]
|
||||
crate::mock::ElectionStart::set(pezsp_runtime::traits::Bounded::max_value());
|
||||
crate::Pallet::<T>::start().unwrap();
|
||||
|
||||
crate::Pallet::<T>::roll_until_matches(|| {
|
||||
matches!(CurrentPhase::<T>::get(), Phase::Unsigned(_))
|
||||
});
|
||||
let call: Call<T> = OffchainWorkerMiner::<T>::mine_solution(T::MinerPages::get(), false)
|
||||
.map(|solution| Call::submit_unsigned { paged_solution: Box::new(solution) })
|
||||
.unwrap();
|
||||
|
||||
#[block]
|
||||
{
|
||||
assert_ok!(Pallet::<T>::validate_unsigned(TransactionSource::Local, &call));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark(pov_mode = Measured)]
|
||||
fn submit_unsigned() -> Result<(), BenchmarkError> {
|
||||
#[cfg(test)]
|
||||
crate::mock::ElectionStart::set(pezsp_runtime::traits::Bounded::max_value());
|
||||
crate::Pallet::<T>::start().unwrap();
|
||||
|
||||
// roll to unsigned phase open
|
||||
crate::Pallet::<T>::roll_until_matches(|| {
|
||||
matches!(CurrentPhase::<T>::get(), Phase::Unsigned(_))
|
||||
});
|
||||
// TODO: we need to better ensure that this is actually worst case
|
||||
let solution =
|
||||
OffchainWorkerMiner::<T>::mine_solution(T::MinerPages::get(), false).unwrap();
|
||||
|
||||
// nothing is queued
|
||||
assert!(T::Verifier::queued_score().is_none());
|
||||
#[block]
|
||||
{
|
||||
assert_ok!(Pallet::<T>::submit_unsigned(RawOrigin::None.into(), Box::new(solution)));
|
||||
}
|
||||
|
||||
// something is queued
|
||||
assert!(T::Verifier::queued_score().is_some());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark(extra, pov_mode = Measured)]
|
||||
fn mine_solution(p: Linear<1, { T::Pages::get() }>) -> Result<(), BenchmarkError> {
|
||||
#[cfg(test)]
|
||||
crate::mock::ElectionStart::set(pezsp_runtime::traits::Bounded::max_value());
|
||||
crate::Pallet::<T>::start().unwrap();
|
||||
|
||||
// roll to unsigned phase open
|
||||
crate::Pallet::<T>::roll_until_matches(|| {
|
||||
matches!(CurrentPhase::<T>::get(), Phase::Unsigned(_))
|
||||
});
|
||||
|
||||
#[block]
|
||||
{
|
||||
OffchainWorkerMiner::<T>::mine_solution(p, true).unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(
|
||||
Pallet,
|
||||
crate::mock::ExtBuilder::full().build_unchecked(),
|
||||
crate::mock::Runtime
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,648 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! ## The unsigned phase, and its miner.
|
||||
//!
|
||||
//! This pallet deals with unsigned submissions. These are backup, "possibly" multi-page submissions
|
||||
//! from validators.
|
||||
//!
|
||||
//! This pallet has two miners, described in [`unsigned::miner`].
|
||||
//!
|
||||
//! As it stands, a validator can, during the unsigned phase, submit up to
|
||||
//! [`unsigned::Config::MinerPages`] pages. While this can be more than 1, it can likely not be a
|
||||
//! full, high quality solution. This is because unsigned validator solutions are verified on the
|
||||
//! fly, all within a single block. The exact value of this parameter should be determined by the
|
||||
//! benchmarks of a runtime.
|
||||
//!
|
||||
//! We could implement a protocol to allow multi-block, multi-page collaborative submissions from
|
||||
//! different validators, but it is not trivial. Moreover, recall that the unsigned phase is merely
|
||||
//! a backup and we should primarily rely on offchain staking miners to fulfill this role during
|
||||
//! `Phase::Signed`.
|
||||
//!
|
||||
//! ## Future Idea: Multi-Page unsigned submission
|
||||
//!
|
||||
//! the following is the idea of how to implement multi-page unsigned, which we don't have.
|
||||
//!
|
||||
//! All validators will run their miners and compute the full paginated solution. They submit all
|
||||
//! pages as individual unsigned transactions to their local tx-pool.
|
||||
//!
|
||||
//! Upon validation, if any page is now present the corresponding transaction is dropped.
|
||||
//!
|
||||
//! At each block, the first page that may be valid is included as a high priority operational
|
||||
//! transaction. This page is validated on the fly to be correct. Since this transaction is sourced
|
||||
//! from a validator, we can panic if they submit an invalid transaction.
|
||||
//!
|
||||
//! Then, once the final page is submitted, some extra checks are done, as explained in
|
||||
//! [`crate::verifier`]:
|
||||
//!
|
||||
//! 1. bounds
|
||||
//! 2. total score
|
||||
//!
|
||||
//! These checks might still fail. If they do, the solution is dropped. At this point, we don't know
|
||||
//! which validator may have submitted a slightly-faulty solution.
|
||||
//!
|
||||
//! In order to prevent this, the transaction validation process always includes a check to ensure
|
||||
//! all of the previous pages that have been submitted match what the local validator has computed.
|
||||
//! If they match, the validator knows that they are putting skin in a game that is valid.
|
||||
//!
|
||||
//! If any bad paged are detected, the next validator can bail. This process means:
|
||||
//!
|
||||
//! * As long as all validators are honest, and run the same miner code, a correct solution is
|
||||
//! found.
|
||||
//! * As little as one malicious validator can stall the process, but no one is accidentally
|
||||
//! slashed, and no panic happens.
|
||||
//!
|
||||
//! Alternatively, we can keep track of submitters, and report a slash if it occurs. Or, if
|
||||
//! the signed process is bullet-proof, we can be okay with the status quo.
|
||||
|
||||
/// Export weights
|
||||
pub use crate::weights::traits::pezpallet_election_provider_multi_block_unsigned::*;
|
||||
/// Exports of this pallet
|
||||
pub use pallet::*;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
mod benchmarking;
|
||||
|
||||
/// The miner.
|
||||
pub mod miner;
|
||||
|
||||
#[pezframe_support::pallet]
|
||||
mod pallet {
|
||||
use super::WeightInfo;
|
||||
use crate::{
|
||||
types::*,
|
||||
unsigned::miner::{self},
|
||||
verifier::Verifier,
|
||||
CommonError,
|
||||
};
|
||||
use pezframe_support::pezpallet_prelude::*;
|
||||
use pezframe_system::{offchain::CreateBare, pezpallet_prelude::*};
|
||||
use pezsp_runtime::traits::SaturatedConversion;
|
||||
use pezsp_std::prelude::*;
|
||||
|
||||
/// convert a [`crate::CommonError`] to a custom InvalidTransaction with the inner code being
|
||||
/// the index of the variant.
|
||||
fn base_error_to_invalid(error: CommonError) -> InvalidTransaction {
|
||||
let index = error.encode().pop().unwrap_or(0);
|
||||
InvalidTransaction::Custom(index)
|
||||
}
|
||||
|
||||
pub(crate) type UnsignedWeightsOf<T> = <T as Config>::WeightInfo;
|
||||
|
||||
#[pallet::config]
|
||||
#[pallet::disable_pezframe_system_supertrait_check]
|
||||
pub trait Config: crate::Config + CreateBare<Call<Self>> {
|
||||
/// The repeat threshold of the offchain worker.
|
||||
///
|
||||
/// For example, if it is `5`, that means that at least 5 blocks will elapse between
|
||||
/// attempts to submit the worker's solution.
|
||||
type OffchainRepeat: Get<BlockNumberFor<Self>>;
|
||||
|
||||
/// The solver used in hte offchain worker miner
|
||||
type OffchainSolver: pezframe_election_provider_support::NposSolver<
|
||||
AccountId = Self::AccountId,
|
||||
>;
|
||||
|
||||
/// Whether the offchain worker miner would attempt to store the solutions in a local
|
||||
/// database and reuse then. If set to `false`, it will try and re-mine solutions every
|
||||
/// time.
|
||||
type OffchainStorage: Get<bool>;
|
||||
|
||||
/// The priority of the unsigned transaction submitted in the unsigned-phase
|
||||
type MinerTxPriority: Get<TransactionPriority>;
|
||||
|
||||
/// The number of pages that the offchain miner will try and submit.
|
||||
type MinerPages: Get<PageIndex>;
|
||||
|
||||
/// Runtime weight information of this pallet.
|
||||
type WeightInfo: WeightInfo;
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(PhantomData<T>);
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Submit an unsigned solution.
|
||||
///
|
||||
/// This works very much like an inherent, as only the validators are permitted to submit
|
||||
/// anything. By default validators will compute this call in their `offchain_worker` hook
|
||||
/// and try and submit it back.
|
||||
///
|
||||
/// This is different from signed page submission mainly in that the solution page is
|
||||
/// verified on the fly.
|
||||
///
|
||||
/// The `paged_solution` may contain at most [`Config::MinerPages`] pages. They are
|
||||
/// interpreted as msp -> lsp, as per [`crate::Pallet::msp_range_for`].
|
||||
///
|
||||
/// For example, if `Pages = 4`, and `MinerPages = 2`, our full snapshot range would be [0,
|
||||
/// 1, 2, 3], with 3 being msp. But, in this case, then the `paged_raw_solution.pages` is
|
||||
/// expected to correspond to `[snapshot(2), snapshot(3)]`.
|
||||
#[pallet::weight((UnsignedWeightsOf::<T>::submit_unsigned(), DispatchClass::Operational))]
|
||||
#[pallet::call_index(0)]
|
||||
pub fn submit_unsigned(
|
||||
origin: OriginFor<T>,
|
||||
paged_solution: Box<PagedRawSolution<T::MinerConfig>>,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
ensure_none(origin)?;
|
||||
let error_message = "Invalid unsigned submission must produce invalid block and \
|
||||
deprive validator from their authoring reward.";
|
||||
|
||||
// phase, round, claimed score, page-count and hash are checked in pre-dispatch. we
|
||||
// don't check them here anymore.
|
||||
debug_assert!(Self::validate_unsigned_checks(&paged_solution).is_ok());
|
||||
|
||||
let claimed_score = paged_solution.score;
|
||||
|
||||
// we select the most significant pages, based on `T::MinerPages`.
|
||||
let page_indices = crate::Pallet::<T>::msp_range_for(T::MinerPages::get() as usize);
|
||||
<T::Verifier as Verifier>::verify_synchronous_multi(
|
||||
paged_solution.solution_pages,
|
||||
page_indices,
|
||||
claimed_score,
|
||||
)
|
||||
.expect(error_message);
|
||||
|
||||
Ok(None.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::validate_unsigned]
|
||||
impl<T: Config> ValidateUnsigned for Pallet<T> {
|
||||
type Call = Call<T>;
|
||||
fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity {
|
||||
if let Call::submit_unsigned { paged_solution, .. } = call {
|
||||
match source {
|
||||
TransactionSource::Local | TransactionSource::InBlock => { /* allowed */ },
|
||||
_ => return InvalidTransaction::Call.into(),
|
||||
}
|
||||
|
||||
let _ = Self::validate_unsigned_checks(paged_solution.as_ref())
|
||||
.map_err(|err| {
|
||||
sublog!(
|
||||
debug,
|
||||
"unsigned",
|
||||
"unsigned transaction validation failed due to {:?}",
|
||||
err
|
||||
);
|
||||
err
|
||||
})
|
||||
.map_err(base_error_to_invalid)?;
|
||||
|
||||
ValidTransaction::with_tag_prefix("OffchainElection")
|
||||
// The higher the score.minimal_stake, the better a paged_solution is.
|
||||
.priority(
|
||||
T::MinerTxPriority::get()
|
||||
.saturating_add(paged_solution.score.minimal_stake.saturated_into()),
|
||||
)
|
||||
// Used to deduplicate unsigned solutions: each validator should produce one
|
||||
// paged_solution per round at most, and solutions are not propagate.
|
||||
.and_provides(paged_solution.round)
|
||||
// Transaction should stay in the pool for the duration of the unsigned phase.
|
||||
.longevity(T::UnsignedPhase::get().saturated_into::<u64>())
|
||||
// We don't propagate this. This can never be validated at a remote node.
|
||||
.propagate(false)
|
||||
.build()
|
||||
} else {
|
||||
InvalidTransaction::Call.into()
|
||||
}
|
||||
}
|
||||
|
||||
fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
|
||||
if let Call::submit_unsigned { paged_solution, .. } = call {
|
||||
Self::validate_unsigned_checks(paged_solution.as_ref())
|
||||
.map_err(base_error_to_invalid)
|
||||
.map_err(Into::into)
|
||||
} else {
|
||||
Err(InvalidTransaction::Call.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
||||
fn integrity_test() {
|
||||
assert!(
|
||||
UnsignedWeightsOf::<T>::submit_unsigned().all_lte(T::BlockWeights::get().max_block),
|
||||
"weight of `submit_unsigned` is too high"
|
||||
);
|
||||
assert!(
|
||||
<T as Config>::MinerPages::get() as usize <=
|
||||
<T as crate::Config>::Pages::get() as usize,
|
||||
"number of pages in the unsigned phase is too high"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn try_state(now: BlockNumberFor<T>) -> Result<(), pezsp_runtime::TryRuntimeError> {
|
||||
Self::do_try_state(now)
|
||||
}
|
||||
|
||||
fn offchain_worker(now: BlockNumberFor<T>) {
|
||||
use pezsp_runtime::offchain::storage_lock::{BlockAndTime, StorageLock};
|
||||
|
||||
// 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<pezframe_system::Pallet<T>>>::with_block_deadline(
|
||||
miner::OffchainWorkerMiner::<T>::OFFCHAIN_LOCK,
|
||||
T::UnsignedPhase::get().saturated_into(),
|
||||
);
|
||||
|
||||
match lock.try_lock() {
|
||||
Ok(_guard) => {
|
||||
Self::do_synchronized_offchain_worker(now);
|
||||
},
|
||||
Err(deadline) => {
|
||||
sublog!(
|
||||
trace,
|
||||
"unsigned",
|
||||
"offchain worker lock not released, deadline is {:?}",
|
||||
deadline
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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: BlockNumberFor<T>) {
|
||||
use miner::OffchainWorkerMiner;
|
||||
let current_phase = crate::Pallet::<T>::current_phase();
|
||||
sublog!(
|
||||
trace,
|
||||
"unsigned",
|
||||
"lock for offchain worker acquired. Phase = {:?}",
|
||||
current_phase
|
||||
);
|
||||
|
||||
// do the repeat frequency check just one, if we are in unsigned phase.
|
||||
if current_phase.is_unsigned() {
|
||||
if let Err(reason) = OffchainWorkerMiner::<T>::ensure_offchain_repeat_frequency(now)
|
||||
{
|
||||
sublog!(
|
||||
debug,
|
||||
"unsigned",
|
||||
"offchain worker repeat frequency check failed: {:?}",
|
||||
reason
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if current_phase.is_unsigned_opened_now() {
|
||||
// Mine a new solution, (maybe) cache it, and attempt to submit it
|
||||
let initial_output = if T::OffchainStorage::get() {
|
||||
OffchainWorkerMiner::<T>::mine_check_maybe_save_submit(true)
|
||||
} else {
|
||||
OffchainWorkerMiner::<T>::mine_check_maybe_save_submit(false)
|
||||
};
|
||||
sublog!(debug, "unsigned", "initial offchain worker output: {:?}", initial_output);
|
||||
} else if current_phase.is_unsigned() {
|
||||
// Maybe resubmit the cached solution, else re-compute.
|
||||
let resubmit_output = if T::OffchainStorage::get() {
|
||||
OffchainWorkerMiner::<T>::restore_or_compute_then_maybe_submit()
|
||||
} else {
|
||||
OffchainWorkerMiner::<T>::mine_check_maybe_save_submit(false)
|
||||
};
|
||||
sublog!(debug, "unsigned", "later offchain worker output: {:?}", resubmit_output);
|
||||
};
|
||||
}
|
||||
|
||||
/// The checks that should happen in the `ValidateUnsigned`'s `pre_dispatch` and
|
||||
/// `validate_unsigned` functions.
|
||||
///
|
||||
/// These check both for snapshot independent checks, and some checks that are specific to
|
||||
/// the unsigned phase.
|
||||
pub(crate) fn validate_unsigned_checks(
|
||||
paged_solution: &PagedRawSolution<T::MinerConfig>,
|
||||
) -> Result<(), CommonError> {
|
||||
Self::unsigned_specific_checks(paged_solution)
|
||||
.and(crate::Pallet::<T>::snapshot_independent_checks(paged_solution, None))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// The checks that are specific to the (this) unsigned pallet.
|
||||
///
|
||||
/// ensure solution has the correct phase, and it has only 1 page.
|
||||
pub fn unsigned_specific_checks(
|
||||
paged_solution: &PagedRawSolution<T::MinerConfig>,
|
||||
) -> Result<(), CommonError> {
|
||||
ensure!(
|
||||
crate::Pallet::<T>::current_phase().is_unsigned(),
|
||||
CommonError::EarlySubmission
|
||||
);
|
||||
ensure!(
|
||||
paged_solution.solution_pages.len() == T::MinerPages::get() as usize,
|
||||
CommonError::WrongPageCount
|
||||
);
|
||||
ensure!(
|
||||
paged_solution.solution_pages.len() <= <T as crate::Config>::Pages::get() as usize,
|
||||
CommonError::WrongPageCount
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "runtime-benchmarks", feature = "try-runtime"))]
|
||||
pub(crate) fn do_try_state(
|
||||
_now: BlockNumberFor<T>,
|
||||
) -> Result<(), pezsp_runtime::TryRuntimeError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod validate_unsigned {
|
||||
use pezframe_election_provider_support::Support;
|
||||
use pezframe_support::{
|
||||
pezpallet_prelude::InvalidTransaction,
|
||||
unsigned::{TransactionSource, TransactionValidityError, ValidateUnsigned},
|
||||
};
|
||||
|
||||
use super::Call;
|
||||
use crate::{mock::*, types::*, verifier::Verifier};
|
||||
|
||||
#[test]
|
||||
fn retracts_weak_score_accepts_better() {
|
||||
ExtBuilder::unsigned().build_and_execute(|| {
|
||||
roll_to_snapshot_created();
|
||||
|
||||
let base_minimal_stake = 55;
|
||||
let solution = mine_full_solution().unwrap();
|
||||
load_mock_signed_and_start(solution.clone());
|
||||
roll_to_full_verification();
|
||||
|
||||
// Some good solution is queued now.
|
||||
assert_eq!(
|
||||
<VerifierPallet as Verifier>::queued_score(),
|
||||
Some(ElectionScore {
|
||||
minimal_stake: base_minimal_stake,
|
||||
sum_stake: 130,
|
||||
sum_stake_squared: 8650
|
||||
})
|
||||
);
|
||||
|
||||
roll_to_unsigned_open();
|
||||
|
||||
// This is just worse.
|
||||
let attempt = fake_solution(ElectionScore {
|
||||
minimal_stake: base_minimal_stake - 1,
|
||||
..Default::default()
|
||||
});
|
||||
let call = Call::submit_unsigned { paged_solution: Box::new(attempt) };
|
||||
assert_eq!(
|
||||
UnsignedPallet::validate_unsigned(TransactionSource::Local, &call).unwrap_err(),
|
||||
TransactionValidityError::Invalid(InvalidTransaction::Custom(2)),
|
||||
);
|
||||
|
||||
// This is better, but the number of winners is incorrect.
|
||||
let attempt = fake_solution(ElectionScore {
|
||||
minimal_stake: base_minimal_stake + 1,
|
||||
..Default::default()
|
||||
});
|
||||
let call = Call::submit_unsigned { paged_solution: Box::new(attempt) };
|
||||
assert_eq!(
|
||||
UnsignedPallet::validate_unsigned(TransactionSource::Local, &call).unwrap_err(),
|
||||
TransactionValidityError::Invalid(InvalidTransaction::Custom(4)),
|
||||
);
|
||||
|
||||
// Note that we now have to use a solution with 2 winners, just to pass all of the
|
||||
// snapshot independent checks.
|
||||
let mut paged = raw_paged_from_supports(
|
||||
vec![vec![
|
||||
(40, Support { total: 10, voters: vec![(3, 5)] }),
|
||||
(30, Support { total: 10, voters: vec![(3, 5)] }),
|
||||
]],
|
||||
0,
|
||||
);
|
||||
|
||||
paged.score =
|
||||
ElectionScore { minimal_stake: base_minimal_stake + 1, ..Default::default() };
|
||||
let call = Call::submit_unsigned { paged_solution: Box::new(paged) };
|
||||
assert!(UnsignedPallet::validate_unsigned(TransactionSource::Local, &call).is_ok());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retracts_wrong_round() {
|
||||
ExtBuilder::unsigned().build_and_execute(|| {
|
||||
roll_to_unsigned_open();
|
||||
|
||||
let mut attempt =
|
||||
fake_solution(ElectionScore { minimal_stake: 5, ..Default::default() });
|
||||
attempt.round += 1;
|
||||
let call = Call::submit_unsigned { paged_solution: Box::new(attempt) };
|
||||
|
||||
assert_eq!(
|
||||
UnsignedPallet::validate_unsigned(TransactionSource::Local, &call).unwrap_err(),
|
||||
// WrongRound is index 1
|
||||
TransactionValidityError::Invalid(InvalidTransaction::Custom(1)),
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retracts_too_many_pages_unsigned() {
|
||||
ExtBuilder::unsigned().build_and_execute(|| {
|
||||
// NOTE: unsigned solutions should have just 1 page, regardless of the configured
|
||||
// page count.
|
||||
roll_to_unsigned_open();
|
||||
let attempt = mine_full_solution().unwrap();
|
||||
let call = Call::submit_unsigned { paged_solution: Box::new(attempt) };
|
||||
|
||||
assert_eq!(
|
||||
UnsignedPallet::validate_unsigned(TransactionSource::Local, &call).unwrap_err(),
|
||||
// WrongPageCount is index 3
|
||||
TransactionValidityError::Invalid(InvalidTransaction::Custom(3)),
|
||||
);
|
||||
|
||||
let attempt = mine_solution(2).unwrap();
|
||||
let call = Call::submit_unsigned { paged_solution: Box::new(attempt) };
|
||||
|
||||
assert_eq!(
|
||||
UnsignedPallet::validate_unsigned(TransactionSource::Local, &call).unwrap_err(),
|
||||
TransactionValidityError::Invalid(InvalidTransaction::Custom(3)),
|
||||
);
|
||||
|
||||
let attempt = mine_solution(1).unwrap();
|
||||
let call = Call::submit_unsigned { paged_solution: Box::new(attempt) };
|
||||
|
||||
assert!(UnsignedPallet::validate_unsigned(TransactionSource::Local, &call).is_ok(),);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retracts_wrong_winner_count() {
|
||||
ExtBuilder::unsigned().desired_targets(2).build_and_execute(|| {
|
||||
roll_to_unsigned_open();
|
||||
|
||||
let paged = raw_paged_from_supports(
|
||||
vec![vec![(40, Support { total: 10, voters: vec![(3, 10)] })]],
|
||||
0,
|
||||
);
|
||||
|
||||
let call = Call::submit_unsigned { paged_solution: Box::new(paged) };
|
||||
|
||||
assert_eq!(
|
||||
UnsignedPallet::validate_unsigned(TransactionSource::Local, &call).unwrap_err(),
|
||||
// WrongWinnerCount is index 4
|
||||
TransactionValidityError::Invalid(InvalidTransaction::Custom(4)),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retracts_wrong_phase() {
|
||||
ExtBuilder::unsigned().signed_phase(5, 6).build_and_execute(|| {
|
||||
let solution = raw_paged_solution_low_score();
|
||||
let call = Call::submit_unsigned { paged_solution: Box::new(solution.clone()) };
|
||||
|
||||
// initial
|
||||
assert_eq!(MultiBlock::current_phase(), Phase::Off);
|
||||
assert!(matches!(
|
||||
<UnsignedPallet as ValidateUnsigned>::validate_unsigned(
|
||||
TransactionSource::Local,
|
||||
&call
|
||||
)
|
||||
.unwrap_err(),
|
||||
// because EarlySubmission is index 0.
|
||||
TransactionValidityError::Invalid(InvalidTransaction::Custom(0))
|
||||
));
|
||||
assert!(matches!(
|
||||
<UnsignedPallet as ValidateUnsigned>::pre_dispatch(&call).unwrap_err(),
|
||||
TransactionValidityError::Invalid(InvalidTransaction::Custom(0))
|
||||
));
|
||||
|
||||
// signed
|
||||
roll_to_signed_open();
|
||||
assert!(MultiBlock::current_phase().is_signed());
|
||||
assert!(matches!(
|
||||
<UnsignedPallet as ValidateUnsigned>::validate_unsigned(
|
||||
TransactionSource::Local,
|
||||
&call
|
||||
)
|
||||
.unwrap_err(),
|
||||
TransactionValidityError::Invalid(InvalidTransaction::Custom(0))
|
||||
));
|
||||
assert!(matches!(
|
||||
<UnsignedPallet as ValidateUnsigned>::pre_dispatch(&call).unwrap_err(),
|
||||
TransactionValidityError::Invalid(InvalidTransaction::Custom(0))
|
||||
));
|
||||
|
||||
// unsigned
|
||||
roll_to_unsigned_open();
|
||||
assert!(MultiBlock::current_phase().is_unsigned());
|
||||
|
||||
assert_ok!(<UnsignedPallet as ValidateUnsigned>::validate_unsigned(
|
||||
TransactionSource::Local,
|
||||
&call
|
||||
));
|
||||
assert_ok!(<UnsignedPallet as ValidateUnsigned>::pre_dispatch(&call));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn priority_is_set() {
|
||||
ExtBuilder::unsigned()
|
||||
.miner_tx_priority(20)
|
||||
.desired_targets(0)
|
||||
.build_and_execute(|| {
|
||||
roll_to_unsigned_open();
|
||||
assert!(MultiBlock::current_phase().is_unsigned());
|
||||
|
||||
let solution =
|
||||
fake_solution(ElectionScore { minimal_stake: 5, ..Default::default() });
|
||||
let call = Call::submit_unsigned { paged_solution: Box::new(solution.clone()) };
|
||||
|
||||
assert_eq!(
|
||||
<UnsignedPallet as ValidateUnsigned>::validate_unsigned(
|
||||
TransactionSource::Local,
|
||||
&call
|
||||
)
|
||||
.unwrap()
|
||||
.priority,
|
||||
25
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod call {
|
||||
use crate::{mock::*, verifier::Verifier, Snapshot};
|
||||
|
||||
#[test]
|
||||
fn unsigned_submission_e2e() {
|
||||
let (mut ext, pool) = ExtBuilder::unsigned().build_offchainify();
|
||||
ext.execute_with_sanity_checks(|| {
|
||||
roll_to_unsigned_open();
|
||||
|
||||
// snapshot is created..
|
||||
assert_full_snapshot();
|
||||
// ..txpool is empty..
|
||||
assert_eq!(pool.read().transactions.len(), 0);
|
||||
// ..but nothing queued.
|
||||
assert_eq!(<VerifierPallet as Verifier>::queued_score(), None);
|
||||
|
||||
// now the OCW should submit something.
|
||||
roll_next_with_ocw(Some(pool.clone()));
|
||||
assert_eq!(pool.read().transactions.len(), 1);
|
||||
assert_eq!(<VerifierPallet as Verifier>::queued_score(), None);
|
||||
|
||||
// and now it should be applied.
|
||||
roll_next_with_ocw(Some(pool.clone()));
|
||||
assert_eq!(pool.read().transactions.len(), 0);
|
||||
assert!(matches!(<VerifierPallet as Verifier>::queued_score(), Some(_)));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "Invalid unsigned submission must produce invalid block and deprive validator from their authoring reward."
|
||||
)]
|
||||
fn unfeasible_solution_panics() {
|
||||
let (mut ext, pool) = ExtBuilder::unsigned().build_offchainify();
|
||||
ext.execute_with_sanity_checks(|| {
|
||||
roll_to_unsigned_open();
|
||||
|
||||
// snapshot is created..
|
||||
assert_full_snapshot();
|
||||
// ..txpool is empty..
|
||||
assert_eq!(pool.read().transactions.len(), 0);
|
||||
// ..but nothing queued.
|
||||
assert_eq!(<VerifierPallet as Verifier>::queued_score(), None);
|
||||
|
||||
// now the OCW should submit something.
|
||||
roll_next_with_ocw(Some(pool.clone()));
|
||||
assert_eq!(pool.read().transactions.len(), 1);
|
||||
assert_eq!(<VerifierPallet as Verifier>::queued_score(), None);
|
||||
|
||||
// now we change the snapshot -- this should ensure that the solution becomes invalid.
|
||||
// Note that we don't change the known fingerprint of the solution.
|
||||
Snapshot::<Runtime>::remove_target(2);
|
||||
|
||||
// and now it should be applied.
|
||||
roll_next_with_ocw(Some(pool.clone()));
|
||||
assert_eq!(pool.read().transactions.len(), 0);
|
||||
assert!(matches!(<VerifierPallet as Verifier>::queued_score(), Some(_)));
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user