Add storage cache for child trie and notification internals (#2639)

* child cache, and test failing notifications

* fix tests and no listen child on top wildcard

* remove useless method

* bump impl version

* Update core/client/src/notifications.rs

Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

* Update core/client/src/notifications.rs

Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

* Update core/client/src/notifications.rs

Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

* Update core/client/src/notifications.rs

Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

* factoring notification methods to remove some redundant code.

* test child sub removal

* HStorage implementation and some type alias.

* Remove HStorage cache: does not fit

* fix removal

* Make cache use byte length (shared) instead of number of kv

* Make use of hashes cache in rpc

* applying ratio on different lru caches

* Fix format

* break a line

* Remove per element overhead of lru cache.

* typo
This commit is contained in:
cheme
2019-06-14 11:25:20 +02:00
committed by Gavin Wood
parent 5c3d1f82cd
commit e777038418
16 changed files with 679 additions and 196 deletions
+12 -2
View File
@@ -28,6 +28,12 @@ use hash_db::Hasher;
use trie::MemoryDB;
use parking_lot::Mutex;
/// In memory array of storage values.
pub type StorageCollection = Vec<(Vec<u8>, Option<Vec<u8>>)>;
/// In memory arrays of storage values for multiple child tries.
pub type ChildStorageCollection = Vec<(Vec<u8>, StorageCollection)>;
/// State of a new block.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NewBlockState {
@@ -82,8 +88,12 @@ pub trait BlockImportOperation<Block, H> where
fn update_db_storage(&mut self, update: <Self::State as StateBackend<H>>::Transaction) -> error::Result<()>;
/// Inject storage data into the database replacing any existing data.
fn reset_storage(&mut self, top: StorageOverlay, children: ChildrenStorageOverlay) -> error::Result<H::Out>;
/// Set top level storage changes.
fn update_storage(&mut self, update: Vec<(Vec<u8>, Option<Vec<u8>>)>) -> error::Result<()>;
/// Set storage changes.
fn update_storage(
&mut self,
update: StorageCollection,
child_update: ChildStorageCollection,
) -> error::Result<()>;
/// Inject changes trie data into the database.
fn update_changes_trie(&mut self, update: MemoryDB<H>) -> error::Result<()>;
/// Insert auxiliary keys. Values are `None` if should be deleted.
+66 -11
View File
@@ -59,7 +59,10 @@ use state_machine::{
};
use hash_db::Hasher;
use crate::backend::{self, BlockImportOperation, PrunableStateChangesTrieStorage};
use crate::backend::{
self, BlockImportOperation, PrunableStateChangesTrieStorage,
StorageCollection, ChildStorageCollection
};
use crate::blockchain::{
self, Info as ChainInfo, Backend as ChainBackend,
HeaderBackend as ChainHeaderBackend, ProvideCache, Cache,
@@ -77,6 +80,7 @@ use substrate_telemetry::{telemetry, SUBSTRATE_INFO};
use log::{info, trace, warn};
/// Type that implements `futures::Stream` of block import events.
pub type ImportNotifications<Block> = mpsc::UnboundedReceiver<BlockImportNotification<Block>>;
@@ -133,7 +137,15 @@ pub struct Client<B, E, Block, RA> where Block: BlockT {
/// Client import operation, a wrapper for the backend.
pub struct ClientImportOperation<Block: BlockT, H: Hasher<Out=Block::Hash>, B: backend::Backend<Block, H>> {
op: B::BlockImportOperation,
notify_imported: Option<(Block::Hash, BlockOrigin, Block::Header, bool, Option<Vec<(Vec<u8>, Option<Vec<u8>>)>>)>,
notify_imported: Option<(
Block::Hash,
BlockOrigin,
Block::Header,
bool,
Option<(
StorageCollection,
ChildStorageCollection,
)>)>,
notify_finalized: Vec<Block::Hash>,
}
@@ -150,8 +162,10 @@ pub trait BlockchainEvents<Block: BlockT> {
/// Get storage changes event stream.
///
/// Passing `None` as `filter_keys` subscribes to all storage changes.
fn storage_changes_notification_stream(&self,
filter_keys: Option<&[StorageKey]>
fn storage_changes_notification_stream(
&self,
filter_keys: Option<&[StorageKey]>,
child_filter_keys: Option<&[(StorageKey, Option<Vec<StorageKey>>)]>,
) -> error::Result<StorageEventStream<Block::Hash>>;
}
@@ -351,6 +365,14 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
.map(StorageData))
}
/// Given a `BlockId` and a key, return the value under the hash in that block.
pub fn storage_hash(&self, id: &BlockId<Block>, key: &StorageKey)
-> error::Result<Option<Block::Hash>> {
Ok(self.state_at(id)?
.storage_hash(&key.0).map_err(|e| error::Error::from_state(Box::new(e)))?
)
}
/// Given a `BlockId`, a key prefix, and a child storage key, return the matching child storage keys.
pub fn child_storage_keys(
&self,
@@ -378,6 +400,18 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
.map(StorageData))
}
/// Given a `BlockId`, a key and a child storage key, return the hash under the key in that block.
pub fn child_storage_hash(
&self,
id: &BlockId<Block>,
child_storage_key: &StorageKey,
key: &StorageKey
) -> error::Result<Option<Block::Hash>> {
Ok(self.state_at(id)?
.child_storage_hash(&child_storage_key.0, &key.0).map_err(|e| error::Error::from_state(Box::new(e)))?
)
}
/// Get the code at a given block.
pub fn code_at(&self, id: &BlockId<Block>) -> error::Result<Vec<u8>> {
Ok(self.storage(id, &StorageKey(well_known_keys::CODE.to_vec()))?
@@ -913,7 +947,7 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
operation.op.update_db_storage(storage_update)?;
}
if let Some(storage_changes) = storage_changes.clone() {
operation.op.update_storage(storage_changes)?;
operation.op.update_storage(storage_changes.0, storage_changes.1)?;
}
if let Some(Some(changes_update)) = changes_update {
operation.op.update_changes_trie(changes_update)?;
@@ -942,7 +976,10 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
) -> error::Result<(
Option<StorageUpdate<B, Block>>,
Option<Option<ChangesUpdate>>,
Option<Vec<(Vec<u8>, Option<Vec<u8>>)>>,
Option<(
Vec<(Vec<u8>, Option<Vec<u8>>)>,
Vec<(Vec<u8>, Vec<(Vec<u8>, Option<Vec<u8>>)>)>
)>
)>
where
E: CallExecutor<Block, Blake2Hasher> + Send + Sync + Clone,
@@ -985,7 +1022,9 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
overlay.commit_prospective();
Ok((Some(storage_update), Some(changes_update), Some(overlay.into_committed().collect())))
let (top, children) = overlay.into_committed();
let children = children.map(|(sk, it)| (sk, it.collect())).collect();
Ok((Some(storage_update), Some(changes_update), Some((top.collect(), children))))
},
None => Ok((None, None, None))
}
@@ -1084,14 +1123,26 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
fn notify_imported(
&self,
notify_import: (Block::Hash, BlockOrigin, Block::Header, bool, Option<Vec<(Vec<u8>, Option<Vec<u8>>)>>),
notify_import: (
Block::Hash, BlockOrigin,
Block::Header,
bool,
Option<(
Vec<(Vec<u8>, Option<Vec<u8>>)>,
Vec<(Vec<u8>, Vec<(Vec<u8>, Option<Vec<u8>>)>)>,
)
>),
) -> error::Result<()> {
let (hash, origin, header, is_new_best, storage_changes) = notify_import;
if let Some(storage_changes) = storage_changes {
// TODO [ToDr] How to handle re-orgs? Should we re-emit all storage changes?
self.storage_notifications.lock()
.trigger(&hash, storage_changes.into_iter());
.trigger(
&hash,
storage_changes.0.into_iter(),
storage_changes.1.into_iter().map(|(sk, v)| (sk, v.into_iter())),
);
}
let notification = BlockImportNotification::<Block> {
@@ -1467,8 +1518,12 @@ where
}
/// Get storage changes event stream.
fn storage_changes_notification_stream(&self, filter_keys: Option<&[StorageKey]>) -> error::Result<StorageEventStream<Block::Hash>> {
Ok(self.storage_notifications.lock().listen(filter_keys))
fn storage_changes_notification_stream(
&self,
filter_keys: Option<&[StorageKey]>,
child_filter_keys: Option<&[(StorageKey, Option<Vec<StorageKey>>)]>,
) -> error::Result<StorageEventStream<Block::Hash>> {
Ok(self.storage_notifications.lock().listen(filter_keys, child_filter_keys))
}
}
+6 -2
View File
@@ -33,7 +33,7 @@ use trie::MemoryDB;
use consensus::well_known_cache_keys::Id as CacheKeyId;
use crate::error;
use crate::backend::{self, NewBlockState};
use crate::backend::{self, NewBlockState, StorageCollection, ChildStorageCollection};
use crate::light;
use crate::leaves::LeafSet;
use crate::blockchain::{self, BlockStatus, HeaderBackend};
@@ -515,7 +515,11 @@ where
Ok(())
}
fn update_storage(&mut self, _update: Vec<(Vec<u8>, Option<Vec<u8>>)>) -> error::Result<()> {
fn update_storage(
&mut self,
_update: StorageCollection,
_child_update: ChildStorageCollection,
) -> error::Result<()> {
Ok(())
}
+1 -1
View File
@@ -59,7 +59,7 @@ pub use crate::client::{
new_in_mem,
BlockBody, BlockStatus, ImportNotifications, FinalityNotifications, BlockchainEvents,
BlockImportNotification, Client, ClientInfo, ExecutionStrategies,
LongestChain
LongestChain,
};
#[cfg(feature = "std")]
pub use crate::notifications::{StorageEventStream, StorageChangeSet};
+9 -2
View File
@@ -26,7 +26,10 @@ use runtime_primitives::{generic::BlockId, Justification, StorageOverlay, Childr
use state_machine::{Backend as StateBackend, TrieBackend, backend::InMemory as InMemoryState};
use runtime_primitives::traits::{Block as BlockT, NumberFor, Zero, Header};
use crate::in_mem::{self, check_genesis_storage};
use crate::backend::{AuxStore, Backend as ClientBackend, BlockImportOperation, RemoteBackend, NewBlockState};
use crate::backend::{
AuxStore, Backend as ClientBackend, BlockImportOperation, RemoteBackend, NewBlockState,
StorageCollection, ChildStorageCollection,
};
use crate::blockchain::HeaderBackend as BlockchainHeaderBackend;
use crate::error::{Error as ClientError, Result as ClientResult};
use crate::light::blockchain::{Blockchain, Storage as BlockchainStorage};
@@ -310,7 +313,11 @@ where
Ok(())
}
fn update_storage(&mut self, _update: Vec<(Vec<u8>, Option<Vec<u8>>)>) -> ClientResult<()> {
fn update_storage(
&mut self,
_update: StorageCollection,
_child_update: ChildStorageCollection,
) -> ClientResult<()> {
// we're not storing anything locally => ignore changes
Ok(())
}
+234 -57
View File
@@ -30,18 +30,39 @@ use runtime_primitives::traits::Block as BlockT;
#[derive(Debug)]
pub struct StorageChangeSet {
changes: Arc<Vec<(StorageKey, Option<StorageData>)>>,
child_changes: Arc<Vec<(StorageKey, Vec<(StorageKey, Option<StorageData>)>)>>,
filter: Option<HashSet<StorageKey>>,
child_filters: Option<HashMap<StorageKey, Option<HashSet<StorageKey>>>>,
}
impl StorageChangeSet {
/// Convert the change set into iterator over storage items.
pub fn iter<'a>(&'a self) -> impl Iterator<Item=&'a (StorageKey, Option<StorageData>)> + 'a {
self.changes
pub fn iter<'a>(&'a self)
-> impl Iterator<Item=(Option<&'a StorageKey>, &'a StorageKey, Option<&'a StorageData>)> + 'a {
let top = self.changes
.iter()
.filter(move |&(key, _)| match self.filter {
Some(ref filter) => filter.contains(key),
None => true,
})
.map(move |(k,v)| (None, k, v.as_ref()));
let children = self.child_changes
.iter()
.filter_map(move |(sk, changes)| {
if let Some(cf) = self.child_filters.as_ref() {
if let Some(filter) = cf.get(sk) {
Some(changes
.iter()
.filter(move |&(key, _)| match filter {
Some(ref filter) => filter.contains(key),
None => true,
})
.map(move |(k,v)| (Some(sk), k, v.as_ref())))
} else { None }
} else { None }
})
.flatten();
top.chain(children)
}
}
@@ -56,9 +77,14 @@ pub struct StorageNotifications<Block: BlockT> {
next_id: SubscriberId,
wildcard_listeners: FnvHashSet<SubscriberId>,
listeners: HashMap<StorageKey, FnvHashSet<SubscriberId>>,
child_listeners: HashMap<StorageKey, (
HashMap<StorageKey, FnvHashSet<SubscriberId>>,
FnvHashSet<SubscriberId>
)>,
sinks: FnvHashMap<SubscriberId, (
mpsc::UnboundedSender<(Block::Hash, StorageChangeSet)>,
Option<HashSet<StorageKey>>,
Option<HashMap<StorageKey, Option<HashSet<StorageKey>>>>,
)>,
}
@@ -68,6 +94,7 @@ impl<Block: BlockT> Default for StorageNotifications<Block> {
next_id: Default::default(),
wildcard_listeners: Default::default(),
listeners: Default::default(),
child_listeners: Default::default(),
sinks: Default::default(),
}
}
@@ -78,16 +105,24 @@ impl<Block: BlockT> StorageNotifications<Block> {
///
/// Note the changes are going to be filtered by listener's filter key.
/// In fact no event might be sent if clients are not interested in the changes.
pub fn trigger(&mut self, hash: &Block::Hash, changeset: impl Iterator<Item=(Vec<u8>, Option<Vec<u8>>)>) {
pub fn trigger(
&mut self,
hash: &Block::Hash,
changeset: impl Iterator<Item=(Vec<u8>, Option<Vec<u8>>)>,
child_changeset: impl Iterator<
Item=(Vec<u8>, impl Iterator<Item=(Vec<u8>, Option<Vec<u8>>)>)
>,
) {
let has_wildcard = !self.wildcard_listeners.is_empty();
// early exit if no listeners
if !has_wildcard && self.listeners.is_empty() {
if !has_wildcard && self.listeners.is_empty() && self.child_listeners.is_empty() {
return;
}
let mut subscribers = self.wildcard_listeners.clone();
let mut changes = Vec::new();
let mut child_changes = Vec::new();
// Collect subscribers and changes
for (k, v) in changeset {
@@ -102,21 +137,47 @@ impl<Block: BlockT> StorageNotifications<Block> {
changes.push((k, v.map(StorageData)));
}
}
for (sk, changeset) in child_changeset {
let sk = StorageKey(sk);
if let Some((cl, cw)) = self.child_listeners.get(&sk) {
let mut changes = Vec::new();
for (k, v) in changeset {
let k = StorageKey(k);
let listeners = cl.get(&k);
if let Some(ref listeners) = listeners {
subscribers.extend(listeners.iter());
}
subscribers.extend(cw.iter());
if !cw.is_empty() || listeners.is_some() {
changes.push((k, v.map(StorageData)));
}
}
if !changes.is_empty() {
child_changes.push((sk, changes));
}
}
}
// Don't send empty notifications
if changes.is_empty() {
if changes.is_empty() && child_changes.is_empty() {
return;
}
let changes = Arc::new(changes);
let child_changes = Arc::new(child_changes);
// Trigger the events
for subscriber in subscribers {
let should_remove = {
let &(ref sink, ref filter) = self.sinks.get(&subscriber)
let &(ref sink, ref filter, ref child_filters) = self.sinks.get(&subscriber)
.expect("subscribers returned from self.listeners are always in self.sinks; qed");
sink.unbounded_send((hash.clone(), StorageChangeSet {
changes: changes.clone(),
child_changes: child_changes.clone(),
filter: filter.clone(),
child_filters: child_filters.clone(),
})).is_err()
};
@@ -126,53 +187,120 @@ impl<Block: BlockT> StorageNotifications<Block> {
}
}
fn remove_subscriber(&mut self, subscriber: SubscriberId) {
if let Some((_, filters)) = self.sinks.remove(&subscriber) {
match filters {
None => {
self.wildcard_listeners.remove(&subscriber);
},
Some(filters) => {
for key in filters {
let remove_key = match self.listeners.get_mut(&key) {
Some(ref mut set) => {
set.remove(&subscriber);
set.is_empty()
},
None => false,
};
fn remove_subscriber_from(
subscriber: &SubscriberId,
filters: &Option<HashSet<StorageKey>>,
listeners: &mut HashMap<StorageKey, FnvHashSet<SubscriberId>>,
wildcards: &mut FnvHashSet<SubscriberId>,
){
match filters {
None => {
wildcards.remove(subscriber);
},
Some(filters) => {
if remove_key {
self.listeners.remove(&key);
}
for key in filters.iter() {
let remove_key = match listeners.get_mut(key) {
Some(ref mut set) => {
set.remove(subscriber);
set.is_empty()
},
None => false,
};
if remove_key {
listeners.remove(key);
}
},
}
}
}
}
/// Start listening for particular storage keys.
pub fn listen(&mut self, filter_keys: Option<&[StorageKey]>) -> StorageEventStream<Block::Hash> {
self.next_id += 1;
fn remove_subscriber(&mut self, subscriber: SubscriberId) {
if let Some((_, filters, child_filters)) = self.sinks.remove(&subscriber) {
Self::remove_subscriber_from(
&subscriber,
&filters,
&mut self.listeners,
&mut self.wildcard_listeners,
);
if let Some(child_filters) = child_filters.as_ref() {
for (c_key, filters) in child_filters {
// add subscriber for every key
let keys = match filter_keys {
if let Some((listeners, wildcards)) = self.child_listeners.get_mut(&c_key) {
Self::remove_subscriber_from(
&subscriber,
&filters,
&mut *listeners,
&mut *wildcards,
);
if listeners.is_empty() && wildcards.is_empty() {
self.child_listeners.remove(&c_key);
}
}
}
}
}
}
fn listen_from(
current_id: SubscriberId,
filter_keys: &Option<impl AsRef<[StorageKey]>>,
listeners: &mut HashMap<StorageKey, FnvHashSet<SubscriberId>>,
wildcards: &mut FnvHashSet<SubscriberId>,
) -> Option<HashSet<StorageKey>>
{
match filter_keys {
None => {
self.wildcard_listeners.insert(self.next_id);
wildcards.insert(current_id);
None
},
Some(keys) => Some(keys.iter().map(|key| {
self.listeners
Some(keys) => Some(keys.as_ref().iter().map(|key| {
listeners
.entry(key.clone())
.or_insert_with(Default::default)
.insert(self.next_id);
.insert(current_id);
key.clone()
}).collect())
};
}
}
/// Start listening for particular storage keys.
pub fn listen(
&mut self,
filter_keys: Option<&[StorageKey]>,
filter_child_keys: Option<&[(StorageKey, Option<Vec<StorageKey>>)]>,
) -> StorageEventStream<Block::Hash> {
self.next_id += 1;
let current_id = self.next_id;
// add subscriber for every key
let keys = Self::listen_from(
current_id,
&filter_keys,
&mut self.listeners,
&mut self.wildcard_listeners,
);
let child_keys = filter_child_keys.map(|filter_child_keys| {
filter_child_keys.iter().map(|(c_key, o_keys)| {
let (c_listeners, c_wildcards) = self.child_listeners
.entry(c_key.clone())
.or_insert_with(Default::default);
(c_key.clone(), Self::listen_from(
current_id,
o_keys,
&mut *c_listeners,
&mut *c_wildcards,
))
}).collect()
});
// insert sink
let (tx, rx) = mpsc::unbounded();
self.sinks.insert(self.next_id, (tx, keys));
self.sinks.insert(current_id, (tx, keys, child_keys));
rx
}
}
@@ -182,13 +310,26 @@ mod tests {
use runtime_primitives::testing::{H256 as Hash, Block as RawBlock, ExtrinsicWrapper};
use super::*;
use futures::Stream;
use std::iter::{empty, Empty};
type TestChangeSet = (
Vec<(StorageKey, Option<StorageData>)>,
Vec<(StorageKey, Vec<(StorageKey, Option<StorageData>)>)>,
);
#[cfg(test)]
impl From<Vec<(StorageKey, Option<StorageData>)>> for StorageChangeSet {
fn from(changes: Vec<(StorageKey, Option<StorageData>)>) -> Self {
impl From<TestChangeSet> for StorageChangeSet {
fn from(changes: TestChangeSet) -> Self {
// warning hardcoded child trie wildcard to test upon
let child_filters = Some([
(StorageKey(vec![4]), None),
(StorageKey(vec![5]), None),
].into_iter().cloned().collect());
StorageChangeSet {
changes: Arc::new(changes),
changes: Arc::new(changes.0),
child_changes: Arc::new(changes.1),
filter: None,
child_filters,
}
}
}
@@ -206,43 +347,73 @@ mod tests {
fn triggering_change_should_notify_wildcard_listeners() {
// given
let mut notifications = StorageNotifications::<Block>::default();
let mut recv = notifications.listen(None).wait();
let child_filter = [(StorageKey(vec![4]), None)];
let mut recv = notifications.listen(None, Some(&child_filter[..])).wait();
// when
let changeset = vec![
(vec![2], Some(vec![3])),
(vec![3], None),
];
notifications.trigger(&Hash::from_low_u64_be(1), changeset.into_iter());
let c_changeset_1 = vec![
(vec![5], Some(vec![4])),
(vec![6], None),
];
let c_changeset = vec![(vec![4], c_changeset_1)];
notifications.trigger(
&Hash::from_low_u64_be(1),
changeset.into_iter(),
c_changeset.into_iter().map(|(a,b)| (a, b.into_iter())),
);
// then
assert_eq!(recv.next().unwrap(), Ok((Hash::from_low_u64_be(1), vec![
assert_eq!(recv.next().unwrap(), Ok((Hash::from_low_u64_be(1), (vec![
(StorageKey(vec![2]), Some(StorageData(vec![3]))),
(StorageKey(vec![3]), None),
].into())));
], vec![(StorageKey(vec![4]), vec![
(StorageKey(vec![5]), Some(StorageData(vec![4]))),
(StorageKey(vec![6]), None),
])]).into())));
}
#[test]
fn should_only_notify_interested_listeners() {
// given
let mut notifications = StorageNotifications::<Block>::default();
let mut recv1 = notifications.listen(Some(&[StorageKey(vec![1])])).wait();
let mut recv2 = notifications.listen(Some(&[StorageKey(vec![2])])).wait();
let child_filter = [(StorageKey(vec![4]), Some(vec![StorageKey(vec![5])]))];
let mut recv1 = notifications.listen(Some(&[StorageKey(vec![1])]), None).wait();
let mut recv2 = notifications.listen(Some(&[StorageKey(vec![2])]), None).wait();
let mut recv3 = notifications.listen(Some(&[]), Some(&child_filter)).wait();
// when
let changeset = vec![
(vec![2], Some(vec![3])),
(vec![1], None),
];
notifications.trigger(&Hash::from_low_u64_be(1), changeset.into_iter());
let c_changeset_1 = vec![
(vec![5], Some(vec![4])),
(vec![6], None),
];
let c_changeset = vec![(vec![4], c_changeset_1)];
notifications.trigger(
&Hash::from_low_u64_be(1),
changeset.into_iter(),
c_changeset.into_iter().map(|(a,b)| (a, b.into_iter())),
);
// then
assert_eq!(recv1.next().unwrap(), Ok((Hash::from_low_u64_be(1), vec![
assert_eq!(recv1.next().unwrap(), Ok((Hash::from_low_u64_be(1), (vec![
(StorageKey(vec![1]), None),
].into())));
assert_eq!(recv2.next().unwrap(), Ok((Hash::from_low_u64_be(1), vec![
], vec![]).into())));
assert_eq!(recv2.next().unwrap(), Ok((Hash::from_low_u64_be(1), (vec![
(StorageKey(vec![2]), Some(StorageData(vec![3]))),
].into())));
], vec![]).into())));
assert_eq!(recv3.next().unwrap(), Ok((Hash::from_low_u64_be(1), (vec![],
vec![
(StorageKey(vec![4]), vec![(StorageKey(vec![5]), Some(StorageData(vec![4])))]),
]).into())));
}
#[test]
@@ -250,11 +421,14 @@ mod tests {
// given
let mut notifications = StorageNotifications::<Block>::default();
{
let _recv1 = notifications.listen(Some(&[StorageKey(vec![1])])).wait();
let _recv2 = notifications.listen(Some(&[StorageKey(vec![2])])).wait();
let _recv3 = notifications.listen(None).wait();
let child_filter = [(StorageKey(vec![4]), Some(vec![StorageKey(vec![5])]))];
let _recv1 = notifications.listen(Some(&[StorageKey(vec![1])]), None).wait();
let _recv2 = notifications.listen(Some(&[StorageKey(vec![2])]), None).wait();
let _recv3 = notifications.listen(None, None).wait();
let _recv4 = notifications.listen(None, Some(&child_filter)).wait();
assert_eq!(notifications.listeners.len(), 2);
assert_eq!(notifications.wildcard_listeners.len(), 1);
assert_eq!(notifications.wildcard_listeners.len(), 2);
assert_eq!(notifications.child_listeners.len(), 1);
}
// when
@@ -262,11 +436,13 @@ mod tests {
(vec![2], Some(vec![3])),
(vec![1], None),
];
notifications.trigger(&Hash::from_low_u64_be(1), changeset.into_iter());
let c_changeset = empty::<(_, Empty<_>)>();
notifications.trigger(&Hash::from_low_u64_be(1), changeset.into_iter(), c_changeset);
// then
assert_eq!(notifications.listeners.len(), 0);
assert_eq!(notifications.wildcard_listeners.len(), 0);
assert_eq!(notifications.child_listeners.len(), 0);
}
#[test]
@@ -274,11 +450,12 @@ mod tests {
// given
let mut recv = {
let mut notifications = StorageNotifications::<Block>::default();
let recv = notifications.listen(None).wait();
let recv = notifications.listen(None, None).wait();
// when
let changeset = vec![];
notifications.trigger(&Hash::from_low_u64_be(1), changeset.into_iter());
let c_changeset = empty::<(_, Empty<_>)>();
notifications.trigger(&Hash::from_low_u64_be(1), changeset.into_iter(), c_changeset);
recv
};