Introduce stacked filtering (#6273)

* Introduce stacked filtering.

* Benchmarks

* Remove unneeded crates

* Fix proxy type's permissiveness checks.

* Repot multisig to make utility stateless.

* Repot filter stack impl into macro

* Fix wasm build

* Tests

* Final test.

* Tests for the macro

* Fix test

* Line width

* Fix

* Update frame/multisig/src/benchmarking.rs

Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>

* Update primitives/std/with_std.rs

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Grumble

* Update frame/support/src/traits.rs

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Update frame/support/src/traits.rs

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Update frame/support/src/traits.rs

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Update frame/support/src/traits.rs

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Update frame/support/src/traits.rs

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Update frame/multisig/src/tests.rs

Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>

* Update frame/multisig/src/tests.rs

Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>

* Grumble

* Migration

* Grumble

* Comments

* Migration

* Fix

* Fix

* Line width

* Allow unused

* Update frame/multisig/src/lib.rs

Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>

* Fix up grumble.

* Remove Utility constraint in NonTransfer

Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>
This commit is contained in:
Gavin Wood
2020-06-08 13:09:12 +02:00
committed by GitHub
parent 663cd09be9
commit 15ecac5cb6
19 changed files with 1630 additions and 890 deletions
+231 -15
View File
@@ -33,6 +33,10 @@ use crate::storage::StorageMap;
use crate::weights::Weight;
use impl_trait_for_tuples::impl_for_tuples;
/// Re-expected for the macro.
#[doc(hidden)]
pub use sp_std::{mem::{swap, take}, cell::RefCell, vec::Vec, boxed::Box};
/// Simple trait for providing a filter over a reference to some type.
pub trait Filter<T> {
/// Determine if a given value should be allowed through the filter (returns `true`) or not.
@@ -43,29 +47,241 @@ impl<T> Filter<T> for () {
fn filter(_: &T) -> bool { true }
}
/// Trait to add a constraint onto the filter.
pub trait FilterStack<T>: Filter<T> {
/// The type used to archive the stack.
type Stack;
/// Add a new `constraint` onto the filter.
fn push(constraint: impl Fn(&T) -> bool + 'static);
/// Removes the most recently pushed, and not-yet-popped, constraint from the filter.
fn pop();
/// Clear the filter, returning a value that may be used later to `restore` it.
fn take() -> Self::Stack;
/// Restore the filter from a previous `take` operation.
fn restore(taken: Self::Stack);
}
/// Guard type for pushing a constraint to a `FilterStack` and popping when dropped.
pub struct FilterStackGuard<F: FilterStack<T>, T>(PhantomData<(F, T)>);
/// Guard type for clearing all pushed constraints from a `FilterStack` and reinstating them when
/// dropped.
pub struct ClearFilterGuard<F: FilterStack<T>, T>(Option<F::Stack>, PhantomData<T>);
impl<F: FilterStack<T>, T> FilterStackGuard<F, T> {
/// Create a new instance, adding a new `constraint` onto the filter `T`, and popping it when
/// this instance is dropped.
pub fn new(constraint: impl Fn(&T) -> bool + 'static) -> Self {
F::push(constraint);
Self(PhantomData)
}
}
impl<F: FilterStack<T>, T> Drop for FilterStackGuard<F, T> {
fn drop(&mut self) {
F::pop();
}
}
impl<F: FilterStack<T>, T> ClearFilterGuard<F, T> {
/// Create a new instance, adding a new `constraint` onto the filter `T`, and popping it when
/// this instance is dropped.
pub fn new() -> Self {
Self(Some(F::take()), PhantomData)
}
}
impl<F: FilterStack<T>, T> Drop for ClearFilterGuard<F, T> {
fn drop(&mut self) {
if let Some(taken) = self.0.take() {
F::restore(taken);
}
}
}
/// Simple trait for providing a filter over a reference to some type, given an instance of itself.
pub trait InstanceFilter<T> {
pub trait InstanceFilter<T>: Sized + Send + Sync {
/// Determine if a given value should be allowed through the filter (returns `true`) or not.
fn filter(&self, _: &T) -> bool;
/// Determines whether `self` matches at least all items that `o` does.
fn is_no_less_permissive(&self, o: &Self) -> bool { !self.is_less_permissive(o) }
/// Determines whether `self` matches at most only the items that `o` does.
fn is_no_more_permissive(&self, o: &Self) -> bool { !o.is_less_permissive(&self) }
/// Determines whether `self` matches all the items that `o` does and others.
fn is_more_permissive(&self, o: &Self) -> bool { o.is_less_permissive(self) }
/// Determines whether `self` does not match all the items that `_o` does, nor any others.
///
/// NOTE: This is the only `*permissive` function that needs to be reimplemented.
fn is_less_permissive(&self, _o: &Self) -> bool { true }
/// Determines whether `self` matches at least everything that `_o` does.
fn is_superset(&self, _o: &Self) -> bool { false }
}
impl<T> InstanceFilter<T> for () {
fn filter(&self, _: &T) -> bool { true }
fn is_less_permissive(&self, _o: &Self) -> bool { false }
fn is_superset(&self, _o: &Self) -> bool { true }
}
#[macro_export]
macro_rules! impl_filter_stack {
($target:ty, $base:ty, $call:ty, $module:ident) => {
#[cfg(feature = "std")]
mod $module {
#[allow(unused_imports)]
use super::*;
use $crate::traits::{swap, take, RefCell, Vec, Box, Filter, FilterStack};
thread_local! {
static FILTER: RefCell<Vec<Box<dyn Fn(&$call) -> bool + 'static>>> = RefCell::new(Vec::new());
}
impl Filter<$call> for $target {
fn filter(call: &$call) -> bool {
<$base>::filter(call) &&
FILTER.with(|filter| filter.borrow().iter().all(|f| f(call)))
}
}
impl FilterStack<$call> for $target {
type Stack = Vec<Box<dyn Fn(&$call) -> bool + 'static>>;
fn push(f: impl Fn(&$call) -> bool + 'static) {
FILTER.with(|filter| filter.borrow_mut().push(Box::new(f)));
}
fn pop() {
FILTER.with(|filter| filter.borrow_mut().pop());
}
fn take() -> Self::Stack {
FILTER.with(|filter| take(filter.borrow_mut().as_mut()))
}
fn restore(mut s: Self::Stack) {
FILTER.with(|filter| swap(filter.borrow_mut().as_mut(), &mut s));
}
}
}
#[cfg(not(feature = "std"))]
mod $module {
#[allow(unused_imports)]
use super::*;
use $crate::traits::{swap, take, RefCell, Vec, Box, Filter, FilterStack};
struct ThisFilter(RefCell<Vec<Box<dyn Fn(&$call) -> bool + 'static>>>);
// NOTE: Safe only in wasm (guarded above) because there's only one thread.
unsafe impl Send for ThisFilter {}
unsafe impl Sync for ThisFilter {}
static FILTER: ThisFilter = ThisFilter(RefCell::new(Vec::new()));
impl Filter<$call> for $target {
fn filter(call: &$call) -> bool {
<$base>::filter(call) && FILTER.0.borrow().iter().all(|f| f(call))
}
}
impl FilterStack<$call> for $target {
type Stack = Vec<Box<dyn Fn(&$call) -> bool + 'static>>;
fn push(f: impl Fn(&$call) -> bool + 'static) {
FILTER.0.borrow_mut().push(Box::new(f));
}
fn pop() {
FILTER.0.borrow_mut().pop();
}
fn take() -> Self::Stack {
take(FILTER.0.borrow_mut().as_mut())
}
fn restore(mut s: Self::Stack) {
swap(FILTER.0.borrow_mut().as_mut(), &mut s);
}
}
}
}
}
#[cfg(test)]
mod test_impl_filter_stack {
use super::*;
pub struct IsCallable;
pub struct BaseFilter;
impl Filter<u32> for BaseFilter {
fn filter(x: &u32) -> bool { x % 2 == 0 }
}
impl_filter_stack!(
crate::traits::test_impl_filter_stack::IsCallable,
crate::traits::test_impl_filter_stack::BaseFilter,
u32,
is_callable
);
#[test]
fn impl_filter_stack_should_work() {
assert!(IsCallable::filter(&36));
assert!(IsCallable::filter(&40));
assert!(IsCallable::filter(&42));
assert!(!IsCallable::filter(&43));
IsCallable::push(|x| *x < 42);
assert!(IsCallable::filter(&36));
assert!(IsCallable::filter(&40));
assert!(!IsCallable::filter(&42));
IsCallable::push(|x| *x % 3 == 0);
assert!(IsCallable::filter(&36));
assert!(!IsCallable::filter(&40));
IsCallable::pop();
assert!(IsCallable::filter(&36));
assert!(IsCallable::filter(&40));
assert!(!IsCallable::filter(&42));
let saved = IsCallable::take();
assert!(IsCallable::filter(&36));
assert!(IsCallable::filter(&40));
assert!(IsCallable::filter(&42));
assert!(!IsCallable::filter(&43));
IsCallable::restore(saved);
assert!(IsCallable::filter(&36));
assert!(IsCallable::filter(&40));
assert!(!IsCallable::filter(&42));
IsCallable::pop();
assert!(IsCallable::filter(&36));
assert!(IsCallable::filter(&40));
assert!(IsCallable::filter(&42));
assert!(!IsCallable::filter(&43));
}
#[test]
fn guards_should_work() {
assert!(IsCallable::filter(&36));
assert!(IsCallable::filter(&40));
assert!(IsCallable::filter(&42));
assert!(!IsCallable::filter(&43));
{
let _guard_1 = FilterStackGuard::<IsCallable, u32>::new(|x| *x < 42);
assert!(IsCallable::filter(&36));
assert!(IsCallable::filter(&40));
assert!(!IsCallable::filter(&42));
{
let _guard_2 = FilterStackGuard::<IsCallable, u32>::new(|x| *x % 3 == 0);
assert!(IsCallable::filter(&36));
assert!(!IsCallable::filter(&40));
}
assert!(IsCallable::filter(&36));
assert!(IsCallable::filter(&40));
assert!(!IsCallable::filter(&42));
{
let _guard_2 = ClearFilterGuard::<IsCallable, u32>::new();
assert!(IsCallable::filter(&36));
assert!(IsCallable::filter(&40));
assert!(IsCallable::filter(&42));
assert!(!IsCallable::filter(&43));
}
assert!(IsCallable::filter(&36));
assert!(IsCallable::filter(&40));
assert!(!IsCallable::filter(&42));
}
assert!(IsCallable::filter(&36));
assert!(IsCallable::filter(&40));
assert!(IsCallable::filter(&42));
assert!(!IsCallable::filter(&43));
}
}
/// An abstraction of a value stored within storage, but possibly as part of a larger composite