Light client friendly events (#2491)

* Sketch of indexed events.

* Get EventIndex by holding another variable.

* Add some docs.

* Use DoubleMap to store reverse topic index

* Implement StorageDoubleMap::append

* Use append for EventTopics.

* Refactor.

* Avoid `mutate`

* Docs.

* Add topics to EventRecord

* Update tests.

* Rebuild.

* Bump version.

* Event topics test.

* Mix in BlockNumber to distinguish updates

* Fix srml-system test.

* Post merge fixes.

* Comments/TODO.
This commit is contained in:
Sergei Pepyakin
2019-05-13 20:56:01 +02:00
committed by Gavin Wood
parent d974189e3c
commit 21773b3a07
8 changed files with 325 additions and 56 deletions
+30 -15
View File
@@ -490,7 +490,8 @@ mod tests {
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: Event::system(system::Event::ExtrinsicSuccess)
event: Event::system(system::Event::ExtrinsicSuccess),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(1),
@@ -499,23 +500,28 @@ mod tests {
bob().into(),
69,
0
))
)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(1),
event: Event::system(system::Event::ExtrinsicSuccess)
event: Event::system(system::Event::ExtrinsicSuccess),
topics: vec![],
},
EventRecord {
phase: Phase::Finalization,
event: Event::treasury(treasury::RawEvent::Spending(0))
event: Event::treasury(treasury::RawEvent::Spending(0)),
topics: vec![],
},
EventRecord {
phase: Phase::Finalization,
event: Event::treasury(treasury::RawEvent::Burnt(0))
event: Event::treasury(treasury::RawEvent::Burnt(0)),
topics: vec![],
},
EventRecord {
phase: Phase::Finalization,
event: Event::treasury(treasury::RawEvent::Rollover(0))
event: Event::treasury(treasury::RawEvent::Rollover(0)),
topics: vec![],
},
]);
});
@@ -537,7 +543,8 @@ mod tests {
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: Event::system(system::Event::ExtrinsicSuccess)
event: Event::system(system::Event::ExtrinsicSuccess),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(1),
@@ -548,11 +555,13 @@ mod tests {
5,
0
)
)
),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(1),
event: Event::system(system::Event::ExtrinsicSuccess)
event: Event::system(system::Event::ExtrinsicSuccess),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(2),
@@ -563,27 +572,33 @@ mod tests {
15,
0
)
)
),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(2),
event: Event::system(system::Event::ExtrinsicSuccess)
event: Event::system(system::Event::ExtrinsicSuccess),
topics: vec![],
},
EventRecord {
phase: Phase::Finalization,
event: Event::treasury(treasury::RawEvent::Spending(0))
event: Event::treasury(treasury::RawEvent::Spending(0)),
topics: vec![],
},
EventRecord {
phase: Phase::Finalization,
event: Event::treasury(treasury::RawEvent::Burnt(0))
event: Event::treasury(treasury::RawEvent::Burnt(0)),
topics: vec![],
},
EventRecord {
phase: Phase::Finalization,
event: Event::treasury(treasury::RawEvent::Rollover(0))
event: Event::treasury(treasury::RawEvent::Rollover(0)),
topics: vec![],
},
EventRecord {
phase: Phase::Finalization,
event: Event::session(session::RawEvent::NewSession(1))
event: Event::session(session::RawEvent::NewSession(1)),
topics: vec![],
},
]);
});
+28 -10
View File
@@ -363,28 +363,34 @@ fn instantiate_and_call_and_deposit_event() {
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(balances::RawEvent::NewAccount(1, 1_000_000)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::CodeStored(HASH_RETURN_FROM_START_FN.into())),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(
balances::RawEvent::NewAccount(BOB, 100)
)
),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::Transfer(ALICE, BOB, 100))
event: MetaEvent::contract(RawEvent::Transfer(ALICE, BOB, 100)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::Contract(BOB, vec![1, 2, 3, 4]))
event: MetaEvent::contract(RawEvent::Contract(BOB, vec![1, 2, 3, 4])),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::Instantiated(ALICE, BOB))
event: MetaEvent::contract(RawEvent::Instantiated(ALICE, BOB)),
topics: vec![],
}
]);
@@ -434,10 +440,12 @@ fn dispatch_call() {
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(balances::RawEvent::NewAccount(1, 1_000_000)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::CodeStored(HASH_DISPATCH_CALL.into())),
topics: vec![],
},
]);
@@ -461,24 +469,29 @@ fn dispatch_call() {
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(balances::RawEvent::NewAccount(1, 1_000_000)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::CodeStored(HASH_DISPATCH_CALL.into())),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(
balances::RawEvent::NewAccount(BOB, 100)
)
),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::Transfer(ALICE, BOB, 100))
event: MetaEvent::contract(RawEvent::Transfer(ALICE, BOB, 100)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::Instantiated(ALICE, BOB))
event: MetaEvent::contract(RawEvent::Instantiated(ALICE, BOB)),
topics: vec![],
},
// Dispatching the call.
@@ -486,19 +499,22 @@ fn dispatch_call() {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(
balances::RawEvent::NewAccount(CHARLIE, 50)
)
),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(
balances::RawEvent::Transfer(BOB, CHARLIE, 50, 0)
)
),
topics: vec![],
},
// Event emited as a result of dispatch.
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::Dispatched(BOB, true))
event: MetaEvent::contract(RawEvent::Dispatched(BOB, true)),
topics: vec![],
}
]);
},
@@ -644,10 +660,12 @@ fn set_rent_hash_and_code() {
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(balances::RawEvent::NewAccount(1, 1_000_000)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::CodeStored(HASH_SET_RENT.into())),
topics: vec![],
},
]);
}
+20 -10
View File
@@ -234,7 +234,8 @@ mod tests {
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: OuterEvent::motions(RawEvent::Proposed(1, 0, hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(), 3))
event: OuterEvent::motions(RawEvent::Proposed(1, 0, hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(), 3)),
topics: vec![],
}
]);
});
@@ -287,11 +288,13 @@ mod tests {
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: OuterEvent::motions(RawEvent::Proposed(1, 0, hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(), 2))
event: OuterEvent::motions(RawEvent::Proposed(1, 0, hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(), 2)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: OuterEvent::motions(RawEvent::Voted(1, hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(), false, 0, 1))
event: OuterEvent::motions(RawEvent::Voted(1, hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(), false, 0, 1)),
topics: vec![],
}
]);
});
@@ -309,15 +312,18 @@ mod tests {
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: OuterEvent::motions(RawEvent::Proposed(1, 0, hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(), 3))
event: OuterEvent::motions(RawEvent::Proposed(1, 0, hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(), 3)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: OuterEvent::motions(RawEvent::Voted(2, hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(), false, 1, 1))
event: OuterEvent::motions(RawEvent::Voted(2, hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(), false, 1, 1)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: OuterEvent::motions(RawEvent::Disapproved(hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into()))
event: OuterEvent::motions(RawEvent::Disapproved(hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into())),
topics: vec![],
}
]);
});
@@ -335,19 +341,23 @@ mod tests {
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: OuterEvent::motions(RawEvent::Proposed(1, 0, hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(), 2))
event: OuterEvent::motions(RawEvent::Proposed(1, 0, hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(), 2)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: OuterEvent::motions(RawEvent::Voted(2, hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(), true, 2, 0))
event: OuterEvent::motions(RawEvent::Voted(2, hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(), true, 2, 0)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: OuterEvent::motions(RawEvent::Approved(hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into()))
event: OuterEvent::motions(RawEvent::Approved(hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into())),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: OuterEvent::motions(RawEvent::Executed(hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(), false))
event: OuterEvent::motions(RawEvent::Executed(hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(), false)),
topics: vec![],
}
]);
});
+2
View File
@@ -47,6 +47,7 @@ fn authorities_change_logged() {
EventRecord {
phase: Phase::Finalization,
event: RawEvent::NewAuthorities(vec![(4, 1), (5, 1), (6, 1)]).into(),
topics: vec![],
},
]);
});
@@ -77,6 +78,7 @@ fn authorities_change_logged_after_delay() {
EventRecord {
phase: Phase::Finalization,
event: RawEvent::NewAuthorities(vec![(4, 1), (5, 1), (6, 1)]).into(),
topics: vec![],
},
]);
});
+31
View File
@@ -238,6 +238,7 @@ mod tests {
pub DataDM config(test_config) build(|_| vec![(15u32, 16u32, 42u64)]): double_map hasher(twox_64_concat) u32, blake2_256(u32) => u64;
pub GenericDataDM: double_map T::BlockNumber, twox_128(T::BlockNumber) => T::BlockNumber;
pub GenericData2DM: double_map T::BlockNumber, twox_256(T::BlockNumber) => Option<T::BlockNumber>;
pub AppendableDM: double_map u32, blake2_256(T::BlockNumber) => Vec<u32>;
}
}
@@ -367,6 +368,21 @@ mod tests {
assert_eq!(DoubleMap::get(key1, key2+1), 0u64);
assert_eq!(DoubleMap::get(key1+1, key2), 4u64);
assert_eq!(DoubleMap::get(key1+1, key2+1), 4u64);
});
}
#[test]
fn double_map_append_should_work() {
with_externalities(&mut new_test_ext(), || {
type DoubleMap = AppendableDM<Test>;
let key1 = 17u32;
let key2 = 18u32;
DoubleMap::insert(key1, key2, vec![1]);
DoubleMap::append(key1, key2, &[2, 3]);
assert_eq!(DoubleMap::get(key1, key2), vec![1, 2, 3]);
});
}
@@ -453,6 +469,21 @@ mod tests {
),
documentation: DecodeDifferent::Encode(&[]),
},
StorageFunctionMetadata {
name: DecodeDifferent::Encode("AppendableDM"),
modifier: StorageFunctionModifier::Default,
ty: StorageFunctionType::DoubleMap{
hasher: StorageHasher::Blake2_256,
key1: DecodeDifferent::Encode("u32"),
key2: DecodeDifferent::Encode("T::BlockNumber"),
value: DecodeDifferent::Encode("Vec<u32>"),
key2_hasher: DecodeDifferent::Encode("blake2_256"),
},
default: DecodeDifferent::Encode(
DefaultByteGetter(&__GetByteStructGenericData2DM(PhantomData::<Test>))
),
documentation: DecodeDifferent::Encode(&[]),
},
])
};
+28
View File
@@ -401,6 +401,20 @@ pub trait StorageDoubleMap<K1: Codec, K2: Codec, V: Codec> {
KArg1: Borrow<K1>,
KArg2: Borrow<K2>,
F: FnOnce(&mut Self::Query) -> R;
/// Append the given items to the value under the key specified.
///
/// `V` is required to implement `codec::EncodeAppend<Item=I>`.
fn append<KArg1, KArg2, I>(
k1: KArg1,
k2: KArg2,
items: &[I],
) -> Result<(), &'static str>
where
KArg1: Borrow<K1>,
KArg2: Borrow<K2>,
I: codec::Encode,
V: EncodeAppend<Item=I>;
}
impl<K1: Codec, K2: Codec, V: Codec, U> StorageDoubleMap<K1, K2, V> for U
@@ -453,6 +467,20 @@ where
{
U::mutate(k1.borrow(), k2.borrow(), f, &RuntimeStorage)
}
fn append<KArg1, KArg2, I>(
k1: KArg1,
k2: KArg2,
items: &[I],
) -> Result<(), &'static str>
where
KArg1: Borrow<K1>,
KArg2: Borrow<K2>,
I: codec::Encode,
V: EncodeAppend<Item=I>,
{
U::append(k1.borrow(), k2.borrow(), items, &RuntimeStorage)
}
}
/// child storage NOTE could replace unhashed by having only one kind of storage (root being null storage
@@ -150,4 +150,24 @@ pub trait StorageDoubleMap<K1: codec::Codec, K2: codec::Codec, V: codec::Codec>
/// Mutate the value under a key.
fn mutate<R, F: FnOnce(&mut Self::Query) -> R, S: UnhashedStorage>(k1: &K1, k2: &K2, f: F, storage: &S) -> R;
/// Append the given items to the value under the key specified.
fn append<I, S: UnhashedStorage>(
k1: &K1,
k2: &K2,
items: &[I],
storage: &S,
) -> Result<(), &'static str>
where
I: codec::Encode,
V: codec::EncodeAppend<Item=I>,
{
let key = Self::key_for(k1, k2);
let new_val = <V as codec::EncodeAppend>::append(
storage.get_raw(&key).unwrap_or_default(),
items,
).ok_or_else(|| "Could not append given item")?;
storage.put_raw(&key, &new_val);
Ok(())
}
}
+166 -21
View File
@@ -83,7 +83,10 @@ use primitives::traits::{self, CheckEqual, SimpleArithmetic, SimpleBitOps, One,
#[cfg(any(feature = "std", test))]
use primitives::traits::Zero;
use substrate_primitives::storage::well_known_keys;
use srml_support::{storage, StorageValue, StorageMap, Parameter, decl_module, decl_event, decl_storage};
use srml_support::{
storage, decl_module, decl_event, decl_storage, StorageDoubleMap, StorageValue,
StorageMap, Parameter,
};
use safe_mix::TripletMix;
use parity_codec::{Encode, Decode};
@@ -182,18 +185,7 @@ decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
/// Deposits an event into this block's event record.
pub fn deposit_event(event: T::Event) {
let extrinsic_index = Self::extrinsic_index();
let phase = extrinsic_index.map_or(Phase::Finalization, |c| Phase::ApplyExtrinsic(c));
let event = EventRecord { phase, event };
// Appending can only fail if `Events<T>` can not be decoded or
// when we try to insert more than `u32::max_value()` events.
// If one of these conditions is met, we just insert the new event.
let events = [event];
if <Events<T>>::append(&events).is_err() {
let [event] = events;
<Events<T>>::put(vec![event]);
}
Self::deposit_event_indexed(&[], event);
}
}
}
@@ -211,11 +203,13 @@ pub enum Phase {
/// Record of an event happening.
#[derive(Encode, Decode)]
#[cfg_attr(feature = "std", derive(Serialize, PartialEq, Eq, Clone, Debug))]
pub struct EventRecord<E: Parameter + Member> {
pub struct EventRecord<E: Parameter + Member, T> {
/// The phase of the block it happened in.
pub phase: Phase,
/// The event itself.
pub event: E,
/// The list of the topics this event has.
pub topics: Vec<T>,
}
decl_event!(
@@ -295,6 +289,12 @@ fn hash69<T: AsMut<[u8]> + Default>() -> T {
h
}
/// This type alias represents an index of an event.
///
/// We use `u32` here because this index is used as index for `Events<T>`
/// which can't contain more than `u32::max_value()` items.
type EventIndex = u32;
decl_storage! {
trait Store for Module<T: Trait> as System {
/// Extrinsics nonce for accounts.
@@ -319,7 +319,30 @@ decl_storage! {
/// Digest of the current block, also part of the block header.
Digest get(digest): T::Digest;
/// Events deposited for the current block.
Events get(events): Vec<EventRecord<T::Event>>;
Events get(events): Vec<EventRecord<T::Event, T::Hash>>;
/// The number of events in the `Events<T>` list.
EventCount get(event_count): EventIndex;
// TODO: https://github.com/paritytech/substrate/issues/2553
// Possibly, we can improve it by using something like:
// `Option<(BlockNumber, Vec<EventIndex>)>`, however in this case we won't be able to use
// `EventTopics::append`.
/// Mapping between a topic (represented by T::Hash) and a vector of indexes
/// of events in the `<Events<T>>` list.
///
/// The first key serves no purpose. This field is declared as double_map just
/// for convenience of using `remove_prefix`.
///
/// All topic vectors have deterministic storage locations depending on the topic. This
/// allows light-clients to leverage the changes trie storage tracking mechanism and
/// in case of changes fetch the list of events of interest.
///
/// The value has the type `(T::BlockNumber, EventIndex)` because if we used only just
/// the `EventIndex` then in case if the topic has the same contents on the next block
/// no notification will be triggered thus the event might be lost.
EventTopics get(event_topics): double_map hasher(blake2_256) (), blake2_256(T::Hash)
=> Vec<(T::BlockNumber, EventIndex)>;
}
add_extra_genesis {
config(changes_trie_config): Option<ChangesTrieConfiguration>;
@@ -378,6 +401,54 @@ pub fn ensure_none<OuterOrigin, AccountId>(o: OuterOrigin) -> Result<(), &'stati
}
impl<T: Trait> Module<T> {
/// Deposits an event into this block's event record adding this event
/// to the corresponding topic indexes.
///
/// This will update storage entries that correpond to the specified topics.
/// It is expected that light-clients could subscribe to this topics.
pub fn deposit_event_indexed(topics: &[T::Hash], event: T::Event) {
let extrinsic_index = Self::extrinsic_index();
let phase = extrinsic_index.map_or(Phase::Finalization, |c| Phase::ApplyExtrinsic(c));
let event = EventRecord {
phase,
event,
topics: topics.iter().cloned().collect::<Vec<_>>(),
};
// Index of the to be added event.
let event_idx = {
let old_event_count = <EventCount<T>>::get();
let new_event_count = match old_event_count.checked_add(1) {
// We've reached the maximum number of events at this block, just
// don't do anything and leave the event_count unaltered.
None => return,
Some(nc) => nc,
};
<EventCount<T>>::put(new_event_count);
old_event_count
};
// Appending can only fail if `Events<T>` can not be decoded or
// when we try to insert more than `u32::max_value()` events.
//
// We perform early return if we've reached the maximum capacity of the event list,
// so `Events<T>` seems to be corrupted. Also, this has happened after the start of execution
// (since the event list is cleared at the block initialization).
if <Events<T>>::append(&[event]).is_err() {
// The most sensible thing to do here is to just ignore this event and wait until the
// new block.
return;
}
let block_no = Self::block_number();
for topic in topics {
// The same applies here.
if <EventTopics<T>>::append(&(), topic, &[(block_no, event_idx)]).is_err() {
return;
}
}
}
/// Gets the index of extrinsic that is currently executing.
pub fn extrinsic_index() -> Option<u32> {
storage::unhashed::get(well_known_keys::EXTRINSIC_INDEX)
@@ -408,6 +479,8 @@ impl<T: Trait> Module<T> {
*index = (*index + 1) % 81;
});
<Events<T>>::kill();
<EventCount<T>>::kill();
<EventTopics<T>>::remove_prefix(&());
}
/// Remove temporary "environment" entries in storage.
@@ -430,7 +503,13 @@ impl<T: Trait> Module<T> {
digest.push(item);
}
// <Events<T>> stays to be inspected by the client.
// The following fields
//
// - <Events<T>>
// - <EventCount<T>>
// - <EventTopics<T>>
//
// stay to be inspected by the client and will be cleared by `Self::initialize`.
<T::Header as traits::Header>::new(number, extrinsics_root, storage_root, parent_hash, digest)
}
@@ -649,7 +728,13 @@ mod tests {
System::finalize();
assert_eq!(
System::events(),
vec![EventRecord { phase: Phase::Finalization, event: 1u16 }]
vec![
EventRecord {
phase: Phase::Finalization,
event: 1u16,
topics: vec![],
}
]
);
System::initialize(&2, &[0u8; 32].into(), &[0u8; 32].into());
@@ -660,11 +745,71 @@ mod tests {
System::deposit_event(3u16);
System::finalize();
assert_eq!(System::events(), vec![
EventRecord { phase: Phase::ApplyExtrinsic(0), event: 42u16 },
EventRecord { phase: Phase::ApplyExtrinsic(0), event: 100u16 },
EventRecord { phase: Phase::ApplyExtrinsic(1), event: 101u16 },
EventRecord { phase: Phase::Finalization, event: 3u16 }
EventRecord { phase: Phase::ApplyExtrinsic(0), event: 42u16, topics: vec![] },
EventRecord { phase: Phase::ApplyExtrinsic(0), event: 100u16, topics: vec![] },
EventRecord { phase: Phase::ApplyExtrinsic(1), event: 101u16, topics: vec![] },
EventRecord { phase: Phase::Finalization, event: 3u16, topics: vec![] }
]);
});
}
#[test]
fn deposit_event_topics() {
with_externalities(&mut new_test_ext(), || {
const BLOCK_NUMBER: u64 = 1;
System::initialize(&BLOCK_NUMBER, &[0u8; 32].into(), &[0u8; 32].into());
System::note_finished_extrinsics();
let topics = vec![
H256::repeat_byte(1),
H256::repeat_byte(2),
H256::repeat_byte(3),
];
// We deposit a few events with different sets of topics.
System::deposit_event_indexed(&topics[0..3], 1u16);
System::deposit_event_indexed(&topics[0..1], 2u16);
System::deposit_event_indexed(&topics[1..2], 3u16);
System::finalize();
// Check that topics are reflected in the event record.
assert_eq!(
System::events(),
vec![
EventRecord {
phase: Phase::Finalization,
event: 1u16,
topics: topics[0..3].to_vec(),
},
EventRecord {
phase: Phase::Finalization,
event: 2u16,
topics: topics[0..1].to_vec(),
},
EventRecord {
phase: Phase::Finalization,
event: 3u16,
topics: topics[1..2].to_vec(),
}
]
);
// Check that the topic-events mapping reflects the deposited topics.
// Note that these are indexes of the events.
assert_eq!(
System::event_topics(&(), &topics[0]),
vec![(BLOCK_NUMBER, 0), (BLOCK_NUMBER, 1)],
);
assert_eq!(
System::event_topics(&(), &topics[1]),
vec![(BLOCK_NUMBER, 0), (BLOCK_NUMBER, 2)],
);
assert_eq!(
System::event_topics(&(), &topics[2]),
vec![(BLOCK_NUMBER, 0)],
);
});
}
}