mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 21:11:07 +00:00
Decode raw events using scale_value and return the decoded Values, too (#576)
* Decode raw events using scale_value and return the decoded Values, too * cargo fmt * cargo fmt and slight rearranging of test code
This commit is contained in:
+1
-1
@@ -22,6 +22,7 @@ integration-tests = []
|
||||
bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] }
|
||||
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "full", "bit-vec"] }
|
||||
scale-info = { version = "2.0.0", features = ["bit-vec"] }
|
||||
scale-value = "0.2.0"
|
||||
futures = "0.3.13"
|
||||
hex = "0.4.3"
|
||||
jsonrpsee = { version = "0.14.0", features = ["async-client", "client-ws-transport"] }
|
||||
@@ -41,5 +42,4 @@ frame-metadata = "15.0.0"
|
||||
derivative = "2.2.0"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_matches = "1.5.0"
|
||||
tokio = { version = "1.8", features = ["macros", "time"] }
|
||||
|
||||
+5
-7
@@ -14,15 +14,13 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{
|
||||
events::EventsDecodingError,
|
||||
metadata::{
|
||||
InvalidMetadataError,
|
||||
MetadataError,
|
||||
},
|
||||
use crate::metadata::{
|
||||
InvalidMetadataError,
|
||||
MetadataError,
|
||||
};
|
||||
use core::fmt::Debug;
|
||||
use jsonrpsee::core::error::Error as RequestError;
|
||||
use scale_value::scale::DecodeError;
|
||||
use sp_core::crypto::SecretStringError;
|
||||
use sp_runtime::transaction_validity::TransactionValidityError;
|
||||
|
||||
@@ -66,7 +64,7 @@ pub enum GenericError<E> {
|
||||
Runtime(E),
|
||||
/// Events decoding error.
|
||||
#[error("Events decoding error: {0}")]
|
||||
EventsDecoding(#[from] EventsDecodingError),
|
||||
EventsDecoding(#[from] DecodeError),
|
||||
/// Transaction progress error.
|
||||
#[error("Transaction error: {0}")]
|
||||
Transaction(#[from] TransactionError),
|
||||
|
||||
@@ -1,487 +0,0 @@
|
||||
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
|
||||
// This file is part of subxt.
|
||||
//
|
||||
// subxt is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// subxt is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Dynamically decoding events.
|
||||
|
||||
use crate::{
|
||||
error::BasicError,
|
||||
metadata::MetadataError,
|
||||
};
|
||||
use bitvec::{
|
||||
order::Lsb0,
|
||||
vec::BitVec,
|
||||
};
|
||||
use codec::{
|
||||
Codec,
|
||||
Compact,
|
||||
Decode,
|
||||
};
|
||||
use scale_info::{
|
||||
PortableRegistry,
|
||||
TypeDef,
|
||||
TypeDefPrimitive,
|
||||
};
|
||||
|
||||
/// Given a type Id and a type registry, attempt to consume the bytes
|
||||
/// corresponding to that type from our input.
|
||||
pub fn decode_and_consume_type(
|
||||
type_id: u32,
|
||||
types: &PortableRegistry,
|
||||
input: &mut &[u8],
|
||||
) -> Result<(), BasicError> {
|
||||
let ty = types
|
||||
.resolve(type_id)
|
||||
.ok_or(MetadataError::TypeNotFound(type_id))?;
|
||||
|
||||
fn consume_type<T: Codec>(input: &mut &[u8]) -> Result<(), BasicError> {
|
||||
T::decode(input)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
match ty.type_def() {
|
||||
TypeDef::Composite(composite) => {
|
||||
for field in composite.fields() {
|
||||
decode_and_consume_type(field.ty().id(), types, input)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
TypeDef::Variant(variant) => {
|
||||
let variant_index = u8::decode(input)?;
|
||||
let variant = variant
|
||||
.variants()
|
||||
.iter()
|
||||
.find(|v| v.index() == variant_index)
|
||||
.ok_or_else(|| {
|
||||
BasicError::Other(format!("Variant {} not found", variant_index))
|
||||
})?;
|
||||
for field in variant.fields() {
|
||||
decode_and_consume_type(field.ty().id(), types, input)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
TypeDef::Sequence(seq) => {
|
||||
let len = <Compact<u32>>::decode(input)?;
|
||||
for _ in 0..len.0 {
|
||||
decode_and_consume_type(seq.type_param().id(), types, input)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
TypeDef::Array(arr) => {
|
||||
for _ in 0..arr.len() {
|
||||
decode_and_consume_type(arr.type_param().id(), types, input)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
TypeDef::Tuple(tuple) => {
|
||||
for field in tuple.fields() {
|
||||
decode_and_consume_type(field.id(), types, input)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
TypeDef::Primitive(primitive) => {
|
||||
match primitive {
|
||||
TypeDefPrimitive::Bool => consume_type::<bool>(input),
|
||||
TypeDefPrimitive::Char => {
|
||||
Err(
|
||||
EventsDecodingError::UnsupportedPrimitive(TypeDefPrimitive::Char)
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
TypeDefPrimitive::Str => consume_type::<String>(input),
|
||||
TypeDefPrimitive::U8 => consume_type::<u8>(input),
|
||||
TypeDefPrimitive::U16 => consume_type::<u16>(input),
|
||||
TypeDefPrimitive::U32 => consume_type::<u32>(input),
|
||||
TypeDefPrimitive::U64 => consume_type::<u64>(input),
|
||||
TypeDefPrimitive::U128 => consume_type::<u128>(input),
|
||||
TypeDefPrimitive::U256 => {
|
||||
Err(
|
||||
EventsDecodingError::UnsupportedPrimitive(TypeDefPrimitive::U256)
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
TypeDefPrimitive::I8 => consume_type::<i8>(input),
|
||||
TypeDefPrimitive::I16 => consume_type::<i16>(input),
|
||||
TypeDefPrimitive::I32 => consume_type::<i32>(input),
|
||||
TypeDefPrimitive::I64 => consume_type::<i64>(input),
|
||||
TypeDefPrimitive::I128 => consume_type::<i128>(input),
|
||||
TypeDefPrimitive::I256 => {
|
||||
Err(
|
||||
EventsDecodingError::UnsupportedPrimitive(TypeDefPrimitive::I256)
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
TypeDef::Compact(compact) => {
|
||||
let inner = types
|
||||
.resolve(compact.type_param().id())
|
||||
.ok_or(MetadataError::TypeNotFound(type_id))?;
|
||||
let mut decode_compact_primitive = |primitive: &TypeDefPrimitive| {
|
||||
match primitive {
|
||||
TypeDefPrimitive::U8 => consume_type::<Compact<u8>>(input),
|
||||
TypeDefPrimitive::U16 => consume_type::<Compact<u16>>(input),
|
||||
TypeDefPrimitive::U32 => consume_type::<Compact<u32>>(input),
|
||||
TypeDefPrimitive::U64 => consume_type::<Compact<u64>>(input),
|
||||
TypeDefPrimitive::U128 => consume_type::<Compact<u128>>(input),
|
||||
prim => {
|
||||
Err(EventsDecodingError::InvalidCompactPrimitive(prim.clone())
|
||||
.into())
|
||||
}
|
||||
}
|
||||
};
|
||||
match inner.type_def() {
|
||||
TypeDef::Primitive(primitive) => decode_compact_primitive(primitive),
|
||||
TypeDef::Composite(composite) => {
|
||||
match composite.fields() {
|
||||
[field] => {
|
||||
let field_ty =
|
||||
types.resolve(field.ty().id()).ok_or_else(|| {
|
||||
MetadataError::TypeNotFound(field.ty().id())
|
||||
})?;
|
||||
if let TypeDef::Primitive(primitive) = field_ty.type_def() {
|
||||
decode_compact_primitive(primitive)
|
||||
} else {
|
||||
Err(EventsDecodingError::InvalidCompactType(
|
||||
"Composite type must have a single primitive field"
|
||||
.into(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
Err(EventsDecodingError::InvalidCompactType(
|
||||
"Composite type must have a single field".into(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
Err(EventsDecodingError::InvalidCompactType(
|
||||
"Compact type must be a primitive or a composite type".into(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
TypeDef::BitSequence(bitseq) => {
|
||||
let bit_store_def = types
|
||||
.resolve(bitseq.bit_store_type().id())
|
||||
.ok_or(MetadataError::TypeNotFound(type_id))?
|
||||
.type_def();
|
||||
|
||||
// We just need to consume the correct number of bytes. Roughly, we encode this
|
||||
// as a Compact<u32> length, and then a slice of T of that length, where T is the
|
||||
// bit store type. So, we ignore the bit order and only care that the bit store type
|
||||
// used lines up in terms of the number of bytes it will take to encode/decode it.
|
||||
match bit_store_def {
|
||||
TypeDef::Primitive(TypeDefPrimitive::U8) => {
|
||||
consume_type::<BitVec<u8, Lsb0>>(input)
|
||||
}
|
||||
TypeDef::Primitive(TypeDefPrimitive::U16) => {
|
||||
consume_type::<BitVec<u16, Lsb0>>(input)
|
||||
}
|
||||
TypeDef::Primitive(TypeDefPrimitive::U32) => {
|
||||
consume_type::<BitVec<u32, Lsb0>>(input)
|
||||
}
|
||||
TypeDef::Primitive(TypeDefPrimitive::U64) => {
|
||||
consume_type::<BitVec<u64, Lsb0>>(input)
|
||||
}
|
||||
store => {
|
||||
return Err(EventsDecodingError::InvalidBitSequenceType(format!(
|
||||
"{:?}",
|
||||
store
|
||||
))
|
||||
.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The possible errors that we can run into attempting to decode events.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum EventsDecodingError {
|
||||
/// Unsupported primitive type
|
||||
#[error("Unsupported primitive type {0:?}")]
|
||||
UnsupportedPrimitive(TypeDefPrimitive),
|
||||
/// Invalid compact type, must be an unsigned int.
|
||||
#[error("Invalid compact primitive {0:?}")]
|
||||
InvalidCompactPrimitive(TypeDefPrimitive),
|
||||
/// Invalid compact type; error details in string.
|
||||
#[error("Invalid compact composite type {0}")]
|
||||
InvalidCompactType(String),
|
||||
/// Invalid bit sequence type; bit store type or bit order type used aren't supported.
|
||||
#[error("Invalid bit sequence type; bit store type {0} is not supported")]
|
||||
InvalidBitSequenceType(String),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::error::GenericError::{
|
||||
Codec,
|
||||
EventsDecoding,
|
||||
Other,
|
||||
};
|
||||
use assert_matches::assert_matches;
|
||||
use codec::Encode;
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
type TypeId = scale_info::interner::UntrackedSymbol<std::any::TypeId>;
|
||||
|
||||
/// Build a type registry that knows about the single type provided.
|
||||
fn singleton_type_registry<T: scale_info::TypeInfo + 'static>(
|
||||
) -> (TypeId, PortableRegistry) {
|
||||
let m = scale_info::MetaType::new::<T>();
|
||||
let mut types = scale_info::Registry::new();
|
||||
let id = types.register_type(&m);
|
||||
let portable_registry: PortableRegistry = types.into();
|
||||
|
||||
(id, portable_registry)
|
||||
}
|
||||
|
||||
fn decode_and_consume_type_consumes_all_bytes<
|
||||
T: codec::Encode + scale_info::TypeInfo + 'static,
|
||||
>(
|
||||
val: T,
|
||||
) {
|
||||
let (type_id, registry) = singleton_type_registry::<T>();
|
||||
let bytes = val.encode();
|
||||
let cursor = &mut &*bytes;
|
||||
|
||||
decode_and_consume_type(type_id.id(), ®istry, cursor).unwrap();
|
||||
assert_eq!(cursor.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_bitvec() {
|
||||
use bitvec::order::Msb0;
|
||||
|
||||
decode_and_consume_type_consumes_all_bytes(
|
||||
bitvec::bitvec![u8, Lsb0; 0, 1, 1, 0, 1],
|
||||
);
|
||||
decode_and_consume_type_consumes_all_bytes(
|
||||
bitvec::bitvec![u8, Msb0; 0, 1, 1, 0, 1, 0, 1, 0, 0],
|
||||
);
|
||||
|
||||
decode_and_consume_type_consumes_all_bytes(
|
||||
bitvec::bitvec![u16, Lsb0; 0, 1, 1, 0, 1],
|
||||
);
|
||||
decode_and_consume_type_consumes_all_bytes(
|
||||
bitvec::bitvec![u16, Msb0; 0, 1, 1, 0, 1, 0, 1, 0, 0],
|
||||
);
|
||||
|
||||
decode_and_consume_type_consumes_all_bytes(
|
||||
bitvec::bitvec![u32, Lsb0; 0, 1, 1, 0, 1],
|
||||
);
|
||||
decode_and_consume_type_consumes_all_bytes(
|
||||
bitvec::bitvec![u32, Msb0; 0, 1, 1, 0, 1, 0, 1, 0, 0],
|
||||
);
|
||||
|
||||
decode_and_consume_type_consumes_all_bytes(
|
||||
bitvec::bitvec![u64, Lsb0; 0, 1, 1, 0, 1],
|
||||
);
|
||||
decode_and_consume_type_consumes_all_bytes(
|
||||
bitvec::bitvec![u64, Msb0; 0, 1, 1, 0, 1, 0, 1, 0, 0],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_primitive() {
|
||||
decode_and_consume_type_consumes_all_bytes(false);
|
||||
decode_and_consume_type_consumes_all_bytes(true);
|
||||
|
||||
let dummy_data = vec![0u8];
|
||||
let dummy_cursor = &mut &*dummy_data;
|
||||
let (id, reg) = singleton_type_registry::<char>();
|
||||
let res = decode_and_consume_type(id.id(), ®, dummy_cursor);
|
||||
assert_matches!(
|
||||
res,
|
||||
Err(EventsDecoding(EventsDecodingError::UnsupportedPrimitive(
|
||||
TypeDefPrimitive::Char
|
||||
)))
|
||||
);
|
||||
|
||||
decode_and_consume_type_consumes_all_bytes("str".to_string());
|
||||
|
||||
decode_and_consume_type_consumes_all_bytes(1u8);
|
||||
decode_and_consume_type_consumes_all_bytes(1i8);
|
||||
|
||||
decode_and_consume_type_consumes_all_bytes(1u16);
|
||||
decode_and_consume_type_consumes_all_bytes(1i16);
|
||||
|
||||
decode_and_consume_type_consumes_all_bytes(1u32);
|
||||
decode_and_consume_type_consumes_all_bytes(1i32);
|
||||
|
||||
decode_and_consume_type_consumes_all_bytes(1u64);
|
||||
decode_and_consume_type_consumes_all_bytes(1i64);
|
||||
|
||||
decode_and_consume_type_consumes_all_bytes(1u128);
|
||||
decode_and_consume_type_consumes_all_bytes(1i128);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_tuple() {
|
||||
decode_and_consume_type_consumes_all_bytes(());
|
||||
|
||||
decode_and_consume_type_consumes_all_bytes((true,));
|
||||
|
||||
decode_and_consume_type_consumes_all_bytes((true, "str"));
|
||||
|
||||
// Incomplete bytes for decoding
|
||||
let dummy_data = false.encode();
|
||||
let dummy_cursor = &mut &*dummy_data;
|
||||
let (id, reg) = singleton_type_registry::<(bool, &'static str)>();
|
||||
let res = decode_and_consume_type(id.id(), ®, dummy_cursor);
|
||||
assert_matches!(res, Err(Codec(_)));
|
||||
|
||||
// Incomplete bytes for decoding, with invalid char type
|
||||
let dummy_data = (false, "str", 0u8).encode();
|
||||
let dummy_cursor = &mut &*dummy_data;
|
||||
let (id, reg) = singleton_type_registry::<(bool, &'static str, char)>();
|
||||
let res = decode_and_consume_type(id.id(), ®, dummy_cursor);
|
||||
assert_matches!(
|
||||
res,
|
||||
Err(EventsDecoding(EventsDecodingError::UnsupportedPrimitive(
|
||||
TypeDefPrimitive::Char
|
||||
)))
|
||||
);
|
||||
// The last byte (0x0 u8) should not be consumed
|
||||
assert_eq!(dummy_cursor.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_array_and_seq() {
|
||||
decode_and_consume_type_consumes_all_bytes([0]);
|
||||
decode_and_consume_type_consumes_all_bytes([1, 2, 3, 4, 5]);
|
||||
decode_and_consume_type_consumes_all_bytes([0; 500]);
|
||||
decode_and_consume_type_consumes_all_bytes(["str", "abc", "cde"]);
|
||||
|
||||
decode_and_consume_type_consumes_all_bytes(vec![0]);
|
||||
decode_and_consume_type_consumes_all_bytes(vec![1, 2, 3, 4, 5]);
|
||||
decode_and_consume_type_consumes_all_bytes(vec!["str", "abc", "cde"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_variant() {
|
||||
#[derive(Clone, Encode, TypeInfo)]
|
||||
enum EnumVar {
|
||||
A,
|
||||
B((&'static str, u8)),
|
||||
C { named: i16 },
|
||||
}
|
||||
const INVALID_TYPE_ID: u32 = 1024;
|
||||
|
||||
decode_and_consume_type_consumes_all_bytes(EnumVar::A);
|
||||
decode_and_consume_type_consumes_all_bytes(EnumVar::B(("str", 1)));
|
||||
decode_and_consume_type_consumes_all_bytes(EnumVar::C { named: 1 });
|
||||
|
||||
// Invalid variant index
|
||||
let dummy_data = 3u8.encode();
|
||||
let dummy_cursor = &mut &*dummy_data;
|
||||
let (id, reg) = singleton_type_registry::<EnumVar>();
|
||||
let res = decode_and_consume_type(id.id(), ®, dummy_cursor);
|
||||
assert_matches!(res, Err(Other(_)));
|
||||
|
||||
// Valid index, incomplete data
|
||||
let dummy_data = 2u8.encode();
|
||||
let dummy_cursor = &mut &*dummy_data;
|
||||
let res = decode_and_consume_type(id.id(), ®, dummy_cursor);
|
||||
assert_matches!(res, Err(Codec(_)));
|
||||
|
||||
let res = decode_and_consume_type(INVALID_TYPE_ID, ®, dummy_cursor);
|
||||
assert_matches!(res, Err(crate::error::GenericError::Metadata(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_composite() {
|
||||
#[derive(Clone, Encode, TypeInfo)]
|
||||
struct Composite {}
|
||||
decode_and_consume_type_consumes_all_bytes(Composite {});
|
||||
|
||||
#[derive(Clone, Encode, TypeInfo)]
|
||||
struct CompositeV2 {
|
||||
id: u32,
|
||||
name: String,
|
||||
}
|
||||
decode_and_consume_type_consumes_all_bytes(CompositeV2 {
|
||||
id: 10,
|
||||
name: "str".to_string(),
|
||||
});
|
||||
|
||||
#[derive(Clone, Encode, TypeInfo)]
|
||||
struct CompositeV3<T> {
|
||||
id: u32,
|
||||
extra: T,
|
||||
}
|
||||
decode_and_consume_type_consumes_all_bytes(CompositeV3 {
|
||||
id: 10,
|
||||
extra: vec![0, 1, 2],
|
||||
});
|
||||
decode_and_consume_type_consumes_all_bytes(CompositeV3 {
|
||||
id: 10,
|
||||
extra: bitvec::bitvec![u8, Lsb0; 0, 1, 1, 0, 1],
|
||||
});
|
||||
decode_and_consume_type_consumes_all_bytes(CompositeV3 {
|
||||
id: 10,
|
||||
extra: ("str", 1),
|
||||
});
|
||||
decode_and_consume_type_consumes_all_bytes(CompositeV3 {
|
||||
id: 10,
|
||||
extra: CompositeV2 {
|
||||
id: 2,
|
||||
name: "str".to_string(),
|
||||
},
|
||||
});
|
||||
|
||||
#[derive(Clone, Encode, TypeInfo)]
|
||||
struct CompositeV4(u32, bool);
|
||||
decode_and_consume_type_consumes_all_bytes(CompositeV4(1, true));
|
||||
|
||||
#[derive(Clone, Encode, TypeInfo)]
|
||||
struct CompositeV5(u32);
|
||||
decode_and_consume_type_consumes_all_bytes(CompositeV5(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_compact() {
|
||||
#[derive(Clone, Encode, TypeInfo)]
|
||||
enum Compact {
|
||||
A(#[codec(compact)] u32),
|
||||
}
|
||||
decode_and_consume_type_consumes_all_bytes(Compact::A(1));
|
||||
|
||||
#[derive(Clone, Encode, TypeInfo)]
|
||||
struct CompactV2(#[codec(compact)] u32);
|
||||
decode_and_consume_type_consumes_all_bytes(CompactV2(1));
|
||||
|
||||
#[derive(Clone, Encode, TypeInfo)]
|
||||
struct CompactV3 {
|
||||
#[codec(compact)]
|
||||
val: u32,
|
||||
}
|
||||
decode_and_consume_type_consumes_all_bytes(CompactV3 { val: 1 });
|
||||
|
||||
#[derive(Clone, Encode, TypeInfo)]
|
||||
struct CompactV4<T> {
|
||||
#[codec(compact)]
|
||||
val: T,
|
||||
}
|
||||
decode_and_consume_type_consumes_all_bytes(CompactV4 { val: 0u8 });
|
||||
decode_and_consume_type_consumes_all_bytes(CompactV4 { val: 1u16 });
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,6 @@ pub use super::{
|
||||
EventDetails,
|
||||
EventFilter,
|
||||
Events,
|
||||
EventsDecodingError,
|
||||
FilterEvents,
|
||||
RawEventDetails,
|
||||
};
|
||||
|
||||
+168
-126
@@ -16,7 +16,6 @@
|
||||
|
||||
//! A representation of a block of events.
|
||||
|
||||
use super::decoding;
|
||||
use crate::{
|
||||
error::BasicError,
|
||||
Client,
|
||||
@@ -36,7 +35,6 @@ use parking_lot::RwLock;
|
||||
use sp_core::{
|
||||
storage::StorageKey,
|
||||
twox_128,
|
||||
Bytes,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -310,6 +308,9 @@ pub struct EventDetails<Evs> {
|
||||
pub event: Evs,
|
||||
}
|
||||
|
||||
/// A Value which has been decoded from some raw bytes.
|
||||
pub type DecodedValue = scale_value::Value<scale_value::scale::TypeId>;
|
||||
|
||||
/// The raw bytes for an event with associated details about
|
||||
/// where and when it was emitted.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@@ -326,15 +327,17 @@ pub struct RawEventDetails {
|
||||
pub variant: String,
|
||||
/// The index of the pallet Event variant.
|
||||
pub variant_index: u8,
|
||||
/// The raw Event data
|
||||
pub data: Bytes,
|
||||
/// The bytes representing the fields contained within the event.
|
||||
pub bytes: Vec<u8>,
|
||||
/// Generic values representing each field of the event.
|
||||
pub fields: Vec<DecodedValue>,
|
||||
}
|
||||
|
||||
impl RawEventDetails {
|
||||
/// Attempt to decode this [`RawEventDetails`] into a specific event.
|
||||
pub fn as_event<E: Event>(&self) -> Result<Option<E>, CodecError> {
|
||||
if self.pallet == E::PALLET && self.variant == E::EVENT {
|
||||
Ok(Some(E::decode(&mut &self.data[..])?))
|
||||
Ok(Some(E::decode(&mut &self.bytes[..])?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
@@ -369,15 +372,17 @@ fn decode_raw_event_details<T: Config>(
|
||||
|
||||
// Use metadata to figure out which bytes belong to this event:
|
||||
let mut event_bytes = Vec::new();
|
||||
let mut event_fields = Vec::new();
|
||||
for arg in event_metadata.variant().fields() {
|
||||
let type_id = arg.ty().id();
|
||||
let all_bytes = *input;
|
||||
// consume some bytes, moving the cursor forward:
|
||||
decoding::decode_and_consume_type(
|
||||
// consume some bytes for each event field, moving the cursor forward:
|
||||
let value = scale_value::scale::decode_as_type(
|
||||
input,
|
||||
type_id,
|
||||
&metadata.runtime_metadata().types,
|
||||
input,
|
||||
)?;
|
||||
event_fields.push(value);
|
||||
// count how many bytes were consumed based on remaining length:
|
||||
let consumed_len = all_bytes.len() - input.len();
|
||||
// move those consumed bytes to the output vec unaltered:
|
||||
@@ -396,7 +401,8 @@ fn decode_raw_event_details<T: Config>(
|
||||
pallet: event_metadata.pallet().to_string(),
|
||||
variant_index,
|
||||
variant: event_metadata.event().to_string(),
|
||||
data: event_bytes.into(),
|
||||
bytes: event_bytes,
|
||||
fields: event_fields,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -522,12 +528,69 @@ mod tests {
|
||||
use crate::Phase;
|
||||
use codec::Encode;
|
||||
use scale_info::TypeInfo;
|
||||
use scale_value::Value;
|
||||
|
||||
/// Build a fake wrapped metadata.
|
||||
fn metadata<E: TypeInfo + 'static>() -> Arc<RwLock<Metadata>> {
|
||||
Arc::new(RwLock::new(test_utils::metadata::<E>()))
|
||||
}
|
||||
|
||||
/// [`RawEventDetails`] can be annoying to test, because it contains
|
||||
/// type info in the decoded field Values. Strip that here so that
|
||||
/// we can compare fields more easily.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct TestRawEventDetails {
|
||||
pub phase: Phase,
|
||||
pub index: u32,
|
||||
pub pallet: String,
|
||||
pub pallet_index: u8,
|
||||
pub variant: String,
|
||||
pub variant_index: u8,
|
||||
pub fields: Vec<scale_value::Value>,
|
||||
}
|
||||
|
||||
/// Compare some actual [`RawEventDetails`] with a hand-constructed
|
||||
/// (probably) [`TestRawEventDetails`].
|
||||
pub fn assert_raw_events_match(
|
||||
// Just for convenience, pass in the metadata type constructed
|
||||
// by the `metadata` function above to simplify caller code.
|
||||
metadata: &Arc<RwLock<Metadata>>,
|
||||
actual: RawEventDetails,
|
||||
expected: TestRawEventDetails,
|
||||
) {
|
||||
let metadata = metadata.read();
|
||||
let types = &metadata.runtime_metadata().types;
|
||||
|
||||
// Make sure that the bytes handed back line up with the fields handed back;
|
||||
// encode the fields back into bytes and they should be equal.
|
||||
let mut actual_bytes = vec![];
|
||||
for field in &actual.fields {
|
||||
scale_value::scale::encode_as_type(
|
||||
field.clone(),
|
||||
field.context,
|
||||
types,
|
||||
&mut actual_bytes,
|
||||
)
|
||||
.expect("should be able to encode properly");
|
||||
}
|
||||
assert_eq!(actual_bytes, actual.bytes);
|
||||
|
||||
let actual_fields_no_context: Vec<_> = actual
|
||||
.fields
|
||||
.into_iter()
|
||||
.map(|f| f.remove_context())
|
||||
.collect();
|
||||
|
||||
// Check each of the other fields:
|
||||
assert_eq!(actual.phase, expected.phase);
|
||||
assert_eq!(actual.index, expected.index);
|
||||
assert_eq!(actual.pallet, expected.pallet);
|
||||
assert_eq!(actual.pallet_index, expected.pallet_index);
|
||||
assert_eq!(actual.variant, expected.variant);
|
||||
assert_eq!(actual.variant_index, expected.variant_index);
|
||||
assert_eq!(actual_fields_no_context, expected.fields);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn statically_decode_single_event() {
|
||||
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
|
||||
@@ -657,9 +720,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn dynamically_decode_single_event() {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Decode, Encode, TypeInfo)]
|
||||
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
|
||||
enum Event {
|
||||
A(u8),
|
||||
A(u8, bool, Vec<String>),
|
||||
}
|
||||
|
||||
// Create fake metadata that knows about our single event, above:
|
||||
@@ -667,33 +730,31 @@ mod tests {
|
||||
|
||||
// Encode our events in the format we expect back from a node, and
|
||||
// construst an Events object to iterate them:
|
||||
let event = Event::A(1);
|
||||
let event = Event::A(1, true, vec!["Hi".into()]);
|
||||
let events = events::<Event>(
|
||||
metadata,
|
||||
metadata.clone(),
|
||||
vec![event_record(Phase::ApplyExtrinsic(123), event)],
|
||||
);
|
||||
|
||||
let event_details: Vec<RawEventDetails> =
|
||||
events.iter_raw().collect::<Result<_, _>>().unwrap();
|
||||
let expected_event_data = {
|
||||
let mut bytes = event.encode();
|
||||
// Strip variant tag off event bytes:
|
||||
bytes.drain(0..1);
|
||||
bytes
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
event_details,
|
||||
vec![RawEventDetails {
|
||||
index: 0,
|
||||
let mut event_details = events.iter_raw();
|
||||
assert_raw_events_match(
|
||||
&metadata,
|
||||
event_details.next().unwrap().unwrap(),
|
||||
TestRawEventDetails {
|
||||
phase: Phase::ApplyExtrinsic(123),
|
||||
index: 0,
|
||||
pallet: "Test".to_string(),
|
||||
pallet_index: 0,
|
||||
variant: "A".to_string(),
|
||||
variant_index: 0,
|
||||
data: expected_event_data.into()
|
||||
}]
|
||||
fields: vec![
|
||||
Value::uint(1u8),
|
||||
Value::bool(true),
|
||||
Value::unnamed_composite(vec![Value::string("Hi")]),
|
||||
],
|
||||
},
|
||||
);
|
||||
assert!(event_details.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -714,7 +775,7 @@ mod tests {
|
||||
let event3 = Event::A(234);
|
||||
|
||||
let events = events::<Event>(
|
||||
metadata,
|
||||
metadata.clone(),
|
||||
vec![
|
||||
event_record(Phase::Initialization, event1),
|
||||
event_record(Phase::ApplyExtrinsic(123), event2),
|
||||
@@ -722,47 +783,48 @@ mod tests {
|
||||
],
|
||||
);
|
||||
|
||||
let event_details: Vec<RawEventDetails> =
|
||||
events.iter_raw().collect::<Result<_, _>>().unwrap();
|
||||
let event_bytes = |ev: Event| {
|
||||
let mut bytes = ev.encode();
|
||||
// Strip variant tag off event bytes:
|
||||
bytes.drain(0..1);
|
||||
bytes.into()
|
||||
};
|
||||
let mut event_details = events.iter_raw();
|
||||
|
||||
assert_eq!(
|
||||
event_details,
|
||||
vec![
|
||||
RawEventDetails {
|
||||
index: 0,
|
||||
phase: Phase::Initialization,
|
||||
pallet: "Test".to_string(),
|
||||
pallet_index: 0,
|
||||
variant: "A".to_string(),
|
||||
variant_index: 0,
|
||||
data: event_bytes(event1)
|
||||
},
|
||||
RawEventDetails {
|
||||
index: 1,
|
||||
phase: Phase::ApplyExtrinsic(123),
|
||||
pallet: "Test".to_string(),
|
||||
pallet_index: 0,
|
||||
variant: "B".to_string(),
|
||||
variant_index: 1,
|
||||
data: event_bytes(event2)
|
||||
},
|
||||
RawEventDetails {
|
||||
index: 2,
|
||||
phase: Phase::Finalization,
|
||||
pallet: "Test".to_string(),
|
||||
pallet_index: 0,
|
||||
variant: "A".to_string(),
|
||||
variant_index: 0,
|
||||
data: event_bytes(event3)
|
||||
},
|
||||
]
|
||||
assert_raw_events_match(
|
||||
&metadata,
|
||||
event_details.next().unwrap().unwrap(),
|
||||
TestRawEventDetails {
|
||||
index: 0,
|
||||
phase: Phase::Initialization,
|
||||
pallet: "Test".to_string(),
|
||||
pallet_index: 0,
|
||||
variant: "A".to_string(),
|
||||
variant_index: 0,
|
||||
fields: vec![Value::uint(1u8)],
|
||||
},
|
||||
);
|
||||
assert_raw_events_match(
|
||||
&metadata,
|
||||
event_details.next().unwrap().unwrap(),
|
||||
TestRawEventDetails {
|
||||
index: 1,
|
||||
phase: Phase::ApplyExtrinsic(123),
|
||||
pallet: "Test".to_string(),
|
||||
pallet_index: 0,
|
||||
variant: "B".to_string(),
|
||||
variant_index: 1,
|
||||
fields: vec![Value::bool(true)],
|
||||
},
|
||||
);
|
||||
assert_raw_events_match(
|
||||
&metadata,
|
||||
event_details.next().unwrap().unwrap(),
|
||||
TestRawEventDetails {
|
||||
index: 2,
|
||||
phase: Phase::Finalization,
|
||||
pallet: "Test".to_string(),
|
||||
pallet_index: 0,
|
||||
variant: "A".to_string(),
|
||||
variant_index: 0,
|
||||
fields: vec![Value::uint(234u8)],
|
||||
},
|
||||
);
|
||||
assert!(event_details.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -788,42 +850,37 @@ mod tests {
|
||||
// Encode our events in the format we expect back from a node, and
|
||||
// construst an Events object to iterate them:
|
||||
let events = events_raw::<Event>(
|
||||
metadata,
|
||||
metadata.clone(),
|
||||
event_bytes,
|
||||
3, // 2 "good" events, and then it'll hit the naff bytes.
|
||||
);
|
||||
|
||||
let event_bytes = |ev: Event| {
|
||||
let mut bytes = ev.encode();
|
||||
// Strip variant tag off event bytes:
|
||||
bytes.drain(0..1);
|
||||
bytes.into()
|
||||
};
|
||||
|
||||
let mut events_iter = events.iter_raw();
|
||||
assert_eq!(
|
||||
assert_raw_events_match(
|
||||
&metadata,
|
||||
events_iter.next().unwrap().unwrap(),
|
||||
RawEventDetails {
|
||||
TestRawEventDetails {
|
||||
index: 0,
|
||||
phase: Phase::Initialization,
|
||||
pallet: "Test".to_string(),
|
||||
pallet_index: 0,
|
||||
variant: "A".to_string(),
|
||||
variant_index: 0,
|
||||
data: event_bytes(Event::A(1))
|
||||
}
|
||||
fields: vec![Value::uint(1u8)],
|
||||
},
|
||||
);
|
||||
assert_eq!(
|
||||
assert_raw_events_match(
|
||||
&metadata,
|
||||
events_iter.next().unwrap().unwrap(),
|
||||
RawEventDetails {
|
||||
TestRawEventDetails {
|
||||
index: 1,
|
||||
phase: Phase::ApplyExtrinsic(123),
|
||||
pallet: "Test".to_string(),
|
||||
pallet_index: 0,
|
||||
variant: "B".to_string(),
|
||||
variant_index: 1,
|
||||
data: event_bytes(Event::B(true))
|
||||
}
|
||||
fields: vec![Value::bool(true)],
|
||||
},
|
||||
);
|
||||
|
||||
// We'll hit an error trying to decode the third event:
|
||||
@@ -846,7 +903,7 @@ mod tests {
|
||||
// Encode our events in the format we expect back from a node, and
|
||||
// construst an Events object to iterate them:
|
||||
let events = events::<Event>(
|
||||
metadata,
|
||||
metadata.clone(),
|
||||
vec![event_record(Phase::Finalization, Event::A(1))],
|
||||
);
|
||||
|
||||
@@ -863,26 +920,21 @@ mod tests {
|
||||
);
|
||||
|
||||
// Dynamically decode:
|
||||
let event_details: Vec<RawEventDetails> =
|
||||
events.iter_raw().collect::<Result<_, _>>().unwrap();
|
||||
let expected_event_data = {
|
||||
let mut bytes = Event::A(1).encode();
|
||||
// Strip variant tag off event bytes:
|
||||
bytes.drain(0..1);
|
||||
bytes
|
||||
};
|
||||
assert_eq!(
|
||||
event_details,
|
||||
vec![RawEventDetails {
|
||||
let mut event_details = events.iter_raw();
|
||||
assert_raw_events_match(
|
||||
&metadata,
|
||||
event_details.next().unwrap().unwrap(),
|
||||
TestRawEventDetails {
|
||||
index: 0,
|
||||
phase: Phase::Finalization,
|
||||
pallet: "Test".to_string(),
|
||||
pallet_index: 0,
|
||||
variant: "A".to_string(),
|
||||
variant_index: 0,
|
||||
data: expected_event_data.into()
|
||||
}]
|
||||
fields: vec![Value::uint(1u8)],
|
||||
},
|
||||
);
|
||||
assert!(event_details.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -901,7 +953,7 @@ mod tests {
|
||||
// Encode our events in the format we expect back from a node, and
|
||||
// construct an Events object to iterate them:
|
||||
let events = events::<Event>(
|
||||
metadata,
|
||||
metadata.clone(),
|
||||
vec![event_record(
|
||||
Phase::Finalization,
|
||||
Event::A(CompactWrapper(1)),
|
||||
@@ -921,26 +973,21 @@ mod tests {
|
||||
);
|
||||
|
||||
// Dynamically decode:
|
||||
let event_details: Vec<RawEventDetails> =
|
||||
events.iter_raw().collect::<Result<_, _>>().unwrap();
|
||||
let expected_event_data = {
|
||||
let mut bytes = Event::A(CompactWrapper(1)).encode();
|
||||
// Strip variant tag off event bytes:
|
||||
bytes.drain(0..1);
|
||||
bytes
|
||||
};
|
||||
assert_eq!(
|
||||
event_details,
|
||||
vec![RawEventDetails {
|
||||
let mut event_details = events.iter_raw();
|
||||
assert_raw_events_match(
|
||||
&metadata,
|
||||
event_details.next().unwrap().unwrap(),
|
||||
TestRawEventDetails {
|
||||
index: 0,
|
||||
phase: Phase::Finalization,
|
||||
pallet: "Test".to_string(),
|
||||
pallet_index: 0,
|
||||
variant: "A".to_string(),
|
||||
variant_index: 0,
|
||||
data: expected_event_data.into()
|
||||
}]
|
||||
fields: vec![Value::unnamed_composite(vec![Value::uint(1u8)])],
|
||||
},
|
||||
);
|
||||
assert!(event_details.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -963,7 +1010,7 @@ mod tests {
|
||||
// Encode our events in the format we expect back from a node, and
|
||||
// construct an Events object to iterate them:
|
||||
let events = events::<Event>(
|
||||
metadata,
|
||||
metadata.clone(),
|
||||
vec![event_record(Phase::Finalization, Event::A(MyType::B))],
|
||||
);
|
||||
|
||||
@@ -980,25 +1027,20 @@ mod tests {
|
||||
);
|
||||
|
||||
// Dynamically decode:
|
||||
let event_details: Vec<RawEventDetails> =
|
||||
events.iter_raw().collect::<Result<_, _>>().unwrap();
|
||||
let expected_event_data = {
|
||||
let mut bytes = Event::A(MyType::B).encode();
|
||||
// Strip variant tag off event bytes:
|
||||
bytes.drain(0..1);
|
||||
bytes
|
||||
};
|
||||
assert_eq!(
|
||||
event_details,
|
||||
vec![RawEventDetails {
|
||||
let mut event_details = events.iter_raw();
|
||||
assert_raw_events_match(
|
||||
&metadata,
|
||||
event_details.next().unwrap().unwrap(),
|
||||
TestRawEventDetails {
|
||||
index: 0,
|
||||
phase: Phase::Finalization,
|
||||
pallet: "Test".to_string(),
|
||||
pallet_index: 0,
|
||||
variant: "A".to_string(),
|
||||
variant_index: 0,
|
||||
data: expected_event_data.into()
|
||||
}]
|
||||
fields: vec![Value::unnamed_variant("B", vec![])],
|
||||
},
|
||||
);
|
||||
assert!(event_details.next().is_none());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,12 +95,10 @@
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
mod decoding;
|
||||
mod event_subscription;
|
||||
mod events_type;
|
||||
mod filter_events;
|
||||
|
||||
pub use decoding::EventsDecodingError;
|
||||
pub use event_subscription::{
|
||||
subscribe,
|
||||
subscribe_finalized,
|
||||
@@ -111,6 +109,7 @@ pub use event_subscription::{
|
||||
};
|
||||
pub use events_type::{
|
||||
at,
|
||||
DecodedValue,
|
||||
EventDetails,
|
||||
Events,
|
||||
RawEventDetails,
|
||||
|
||||
@@ -386,7 +386,7 @@ impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode>
|
||||
for ev in events.iter_raw() {
|
||||
let ev = ev?;
|
||||
if &ev.pallet == "System" && &ev.variant == "ExtrinsicFailed" {
|
||||
let dispatch_error = E::decode(&mut &*ev.data)?;
|
||||
let dispatch_error = E::decode(&mut &*ev.bytes)?;
|
||||
if let Some(error_data) = dispatch_error.module_error_data() {
|
||||
// Error index is utilized as the first byte from the error array.
|
||||
let locked_metadata = self.client.metadata();
|
||||
|
||||
Reference in New Issue
Block a user