referrerpolicy=no-referrer-when-downgrade

pallet_election_provider_multi_block/
helpers.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Some helper functions/macros for this crate.
19
20use crate::{
21	types::{PageIndex, VoterOf},
22	unsigned::miner::MinerConfig,
23	AllVoterPagesOf, SolutionTargetIndexOf, SolutionVoterIndexOf, VoteWeight,
24};
25use frame_support::{traits::Get, BoundedVec};
26use sp_runtime::SaturatedConversion;
27use sp_std::{collections::btree_map::BTreeMap, convert::TryInto, prelude::*};
28
29/// Emit a log specific to this pallet, setting the target to [`crate::LOG_PREFIX`]
30#[macro_export]
31macro_rules! log {
32	($level:tt, $pattern:expr $(, $values:expr)* $(,)?) => {
33		log::$level!(
34			target: $crate::LOG_PREFIX,
35			concat!("[#{:?}] πŸ—³πŸ—³πŸ—³  ", $pattern), <frame_system::Pallet<T>>::block_number() $(, $values)*
36		)
37	};
38}
39
40/// Emit a log within a submodule of the pallet
41#[macro_export]
42macro_rules! sublog {
43	($level:tt, $sub_pallet:tt, $pattern:expr $(, $values:expr)* $(,)?) => {
44		#[cfg(not(feature = "std"))]
45		log!($level, $pattern $(, $values )*);
46		#[cfg(feature = "std")]
47		log::$level!(
48			target: format!("{}::{}", $crate::LOG_PREFIX, $sub_pallet).as_ref(),
49			concat!("[#{:?}] πŸ—³πŸ—³πŸ—³  ", $pattern), <frame_system::Pallet<T>>::block_number() $(, $values )*
50		)
51	};
52}
53
54/// Emit a log from within the offchain miner.
55#[macro_export]
56macro_rules! miner_log {
57	($level:tt, $pattern:expr $(, $values:expr)* $(,)?) => {
58		log::$level!(
59			target: $crate::LOG_PREFIX,
60			concat!("[⛏️miner] πŸ—³πŸ—³πŸ—³  ", $pattern) $(, $values)*
61		)
62	};
63}
64
65/// Generate an `efficient closure of voters and the page in which they live in.
66pub(crate) fn generate_voter_page_fn<T: MinerConfig>(
67	paged_snapshot: &AllVoterPagesOf<T>,
68) -> impl Fn(&T::AccountId) -> Option<PageIndex> {
69	let mut cache: BTreeMap<T::AccountId, PageIndex> = BTreeMap::new();
70	paged_snapshot
71		.iter()
72		.enumerate()
73		.map(|(page, whatever)| (page.saturated_into::<PageIndex>(), whatever))
74		.for_each(|(page, page_voters)| {
75			page_voters.iter().for_each(|(v, _, _)| {
76				let _existed = cache.insert(v.clone(), page);
77				// if a duplicate exists, we only consider the last one. Defensive only, should
78				// never happen.
79				debug_assert!(_existed.is_none());
80			});
81		});
82	move |who| cache.get(who).copied()
83}
84
85/// Generate a btree-map cache of the voters and their indices within the provided `snapshot`.
86///
87/// This does not care about pagination. `snapshot` might be a single page or the entire blob of
88/// voters.
89///
90/// This can be used to efficiently build index getter closures.
91pub(crate) fn generate_voter_cache<T: MinerConfig, AnyBound: Get<u32>>(
92	snapshot: &BoundedVec<VoterOf<T>, AnyBound>,
93) -> BTreeMap<T::AccountId, usize> {
94	let mut cache: BTreeMap<T::AccountId, usize> = BTreeMap::new();
95	snapshot.iter().enumerate().for_each(|(i, (x, _, _))| {
96		let _existed = cache.insert(x.clone(), i);
97		// if a duplicate exists, we only consider the last one. Defensive only, should never
98		// happen.
99		debug_assert!(_existed.is_none());
100	});
101
102	cache
103}
104
105/// Create a function that returns the index of a voter in the snapshot.
106///
107/// Same as [`voter_index_fn`] but the returned function owns all its necessary data; nothing is
108/// borrowed.
109pub(crate) fn voter_index_fn_owned<T: MinerConfig>(
110	cache: BTreeMap<T::AccountId, usize>,
111) -> impl Fn(&T::AccountId) -> Option<SolutionVoterIndexOf<T>> {
112	move |who| {
113		cache
114			.get(who)
115			.and_then(|i| <usize as TryInto<SolutionVoterIndexOf<T>>>::try_into(*i).ok())
116	}
117}
118
119/// Same as [`voter_index_fn`], but the returning index is converted into usize, if possible.
120///
121/// ## Warning
122///
123/// Note that this will represent the snapshot data from which the `cache` is generated.
124pub(crate) fn voter_index_fn_usize<T: MinerConfig>(
125	cache: &BTreeMap<T::AccountId, usize>,
126) -> impl Fn(&T::AccountId) -> Option<usize> + '_ {
127	move |who| cache.get(who).cloned()
128}
129
130/// A non-optimized, linear version of [`voter_index_fn`] that does not need a cache and does a
131/// linear search.
132///
133/// ## Warning
134///
135/// Not meant to be used in production.
136#[cfg(test)]
137pub(crate) fn voter_index_fn_linear<T: MinerConfig>(
138	snapshot: &Vec<VoterOf<T>>,
139) -> impl Fn(&T::AccountId) -> Option<SolutionVoterIndexOf<T>> + '_ {
140	move |who| {
141		snapshot
142			.iter()
143			.position(|(x, _, _)| x == who)
144			.and_then(|i| <usize as TryInto<SolutionVoterIndexOf<T>>>::try_into(i).ok())
145	}
146}
147
148/// Create a function that returns the index of a target in the snapshot.
149///
150/// The returned index type is the same as the one defined in `T::Solution::Target`.
151///
152/// Note: to the extent possible, the returned function should be cached and reused. Producing that
153/// function requires a `O(n log n)` data transform. Each invocation of that function completes
154/// in `O(log n)`.
155pub(crate) fn target_index_fn<T: MinerConfig>(
156	snapshot: &Vec<T::AccountId>,
157) -> impl Fn(&T::AccountId) -> Option<SolutionTargetIndexOf<T>> + '_ {
158	let cache: BTreeMap<_, _> =
159		snapshot.iter().enumerate().map(|(idx, account_id)| (account_id, idx)).collect();
160	move |who| {
161		cache
162			.get(who)
163			.and_then(|i| <usize as TryInto<SolutionTargetIndexOf<T>>>::try_into(*i).ok())
164	}
165}
166
167/// Create a function the returns the index to a target in the snapshot.
168///
169/// The returned index type is the same as the one defined in `T::Solution::Target`.
170///
171/// ## Warning
172///
173/// Not meant to be used in production.
174#[cfg(test)]
175pub(crate) fn target_index_fn_linear<T: MinerConfig>(
176	snapshot: &Vec<T::AccountId>,
177) -> impl Fn(&T::AccountId) -> Option<SolutionTargetIndexOf<T>> + '_ {
178	move |who| {
179		snapshot
180			.iter()
181			.position(|x| x == who)
182			.and_then(|i| <usize as TryInto<SolutionTargetIndexOf<T>>>::try_into(i).ok())
183	}
184}
185
186/// Create a function that can map a voter index ([`SolutionVoterIndexOf`]) to the actual voter
187/// account using a linearly indexible snapshot.
188pub(crate) fn voter_at_fn<T: MinerConfig>(
189	snapshot: &Vec<VoterOf<T>>,
190) -> impl Fn(SolutionVoterIndexOf<T>) -> Option<T::AccountId> + '_ {
191	move |i| {
192		<SolutionVoterIndexOf<T> as TryInto<usize>>::try_into(i)
193			.ok()
194			.and_then(|i| snapshot.get(i).map(|(x, _, _)| x).cloned())
195	}
196}
197
198/// Create a function that can map a target index ([`SolutionTargetIndexOf`]) to the actual target
199/// account using a linearly indexible snapshot.
200pub(crate) fn target_at_fn<T: MinerConfig>(
201	snapshot: &Vec<T::AccountId>,
202) -> impl Fn(SolutionTargetIndexOf<T>) -> Option<T::AccountId> + '_ {
203	move |i| {
204		<SolutionTargetIndexOf<T> as TryInto<usize>>::try_into(i)
205			.ok()
206			.and_then(|i| snapshot.get(i).cloned())
207	}
208}
209
210/// Create a function to get the stake of a voter.
211///
212/// ## Warning
213///
214/// The cache need must be derived from the same snapshot. Zero is returned if a voter is
215/// non-existent.
216pub(crate) fn stake_of_fn<'a, T: MinerConfig, AnyBound: Get<u32>>(
217	snapshot: &'a BoundedVec<VoterOf<T>, AnyBound>,
218	cache: &'a BTreeMap<T::AccountId, usize>,
219) -> impl Fn(&T::AccountId) -> VoteWeight + 'a {
220	move |who| {
221		if let Some(index) = cache.get(who) {
222			snapshot.get(*index).map(|(_, x, _)| x).cloned().unwrap_or_default()
223		} else {
224			0
225		}
226	}
227}