diff --git a/CHANGELOG.md b/CHANGELOG.md index a0a04a13c1..53fb99055a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.17.0] - 2022-02-04 + +### Added + +- introduce jsonrpsee client abstraction + kill HTTP support. ([#341](https://github.com/paritytech/subxt/pull/341)) +- Get event context on EventSubscription ([#423](https://github.com/paritytech/subxt/pull/423)) + +### Changed + +- Add more tests for events.rs/decode_and_consume_type ([#430](https://github.com/paritytech/subxt/pull/430)) +- Update substrate dependencies ([#429](https://github.com/paritytech/subxt/pull/429)) +- export RuntimeError struct ([#427](https://github.com/paritytech/subxt/pull/427)) +- remove unused PalletError struct ([#425](https://github.com/paritytech/subxt/pull/425)) +- Move Subxt crate into a subfolder ([#424](https://github.com/paritytech/subxt/pull/424)) +- Add release checklist ([#418](https://github.com/paritytech/subxt/pull/418)) + ## [0.16.0] - 2022-02-01 *Note*: This is a significant release which introduces support for V14 metadata and macro based codegen, as well as making many breaking changes to the API. diff --git a/RELEASING.md b/RELEASING.md index 53ff0cf34d..6972ffb341 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -26,12 +26,12 @@ We also assume that ongoing work done is being merged directly to the `master` b If there are minor issues with the documentation, they can be fixed in the release branch. -4. Bump the crate version in `Cargo.toml` to whatever was decided in step 2 for `subxt-codegen`, `subxt-macro`, `subxt` and `subxt-cli`. +4. Bump the crate version in `Cargo.toml` to whatever was decided in step 2 for `subxt-cli`, `subxt-codegen`, `subxt-examples`, `subxt-macro` ,`subxt`, `test-runtime`. 5. Update `CHANGELOG.md` to reflect the difference between this release and the last. If you're unsure of what to add, check with the Tools team. See the `CHANGELOG.md` file for details of the format it follows. - Any [closed PRs](https://github.com/paritytech/subxt/pulls?q=is%3Apr+is%3Aclosed) between the last release and + Any [closed PRs](https://github.com/paritytech/subxt/pulls?q=is%3Apr+sort%3Aupdated-desc+is%3Aclosed) between the last release and this release branch should be noted. 6. Commit any of the above changes to the release branch and open a PR in GitHub with a base of `master`. @@ -40,28 +40,38 @@ We also assume that ongoing work done is being merged directly to the `master` b 8. Now, we're ready to publish the release to crates.io. - Checkout `master`, ensuring we're looking at that latest merge (`git pull`). + 1. Checkout `master`, ensuring we're looking at that latest merge (`git pull`). - The crates in this repository need publishing in a specific order, since they depend on each other. - Additionally, `subxt-macro` has a circular dev dependency on `subxt`, so we use `cargo hack` to remove - dev dependencies (and `--allow-dirty` to ignore the git changes as a result) to publish it. + ``` + git checkout master && git pull + ``` - So, first install `cargo hack` with `cargo install cargo hack`. Next, you can run something like the following - command to publish each crate in the required order (allowing a little time inbetween each to let `crates.io` catch up) - with what we've published). + 2. Perform a dry-run publish to ensure the crates can be correctly published. - ``` - (cd codegen && cargo publish) && \ - sleep 10 && \ - (cd macro && cargo hack publish --no-dev-deps --allow-dirty) && \ - sleep 10 && \ - (cd subxt && cargo publish) && \ - sleep 10 && \ - (cd cli && cargo publish); - ``` + The crates in this repository need publishing in a specific order, since they depend on each other. - If you run into any issues regarding crates not being able to find suitable versions of other `subxt-*` crates, - you may just need to wait a little longer and then run the remaining portion of that command. + ``` + (cd codegen && cargo publish --dry-run) && \ + (cd macro && cargo publish --dry-run) && \ + (cd subxt && cargo publish --dry-run) && \ + (cd cli && cargo publish --dry-run); + ``` + + 3. If the dry-run was successful, run the following command to publish each crate in the required order (allowing + a little time in between each to let crates.io catch up with what we've published). + + ``` + (cd codegen && cargo publish) && \ + sleep 10 && \ + (cd macro && cargo publish) && \ + sleep 10 && \ + (cd subxt && cargo publish) && \ + sleep 10 && \ + (cd cli && cargo publish); + ``` + + If you run into any issues regarding crates not being able to find suitable versions of other `subxt-*` crates, + you may just need to wait a little longer and then run the remaining portion of that command. 9. If the release was successful, tag the commit that we released in the `master` branch with the version that we just released, for example: @@ -73,4 +83,4 @@ We also assume that ongoing work done is being merged directly to the `master` b Once this is pushed, go along to [the releases page on GitHub](https://github.com/paritytech/subxt/releases) and draft a new release which points to the tag you just pushed to `master` above. Copy the changelog comments - for the current release into the release description. \ No newline at end of file + for the current release into the release description. diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 3ad6e99bff..916999e367 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "subxt-cli" -version = "0.16.0" +version = "0.17.0" authors = ["Parity Technologies "] edition = "2021" @@ -16,7 +16,7 @@ path = "src/main.rs" [dependencies] # perform subxt codegen -subxt-codegen = { version = "0.16.0", path = "../codegen" } +subxt-codegen = { version = "0.17.0", path = "../codegen" } # parse command line args structopt = "0.3.25" # make the request to a substrate node to get the metadata diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index ea41458b7b..75965e344f 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "subxt-codegen" -version = "0.16.0" +version = "0.17.0" authors = ["Parity Technologies "] edition = "2021" @@ -15,7 +15,7 @@ async-trait = "0.1.49" codec = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive", "full", "bit-vec"] } darling = "0.13.0" frame-metadata = "14.0" -heck = "0.3.2" +heck = "0.4.0" proc-macro2 = "1.0.24" proc-macro-crate = "0.1.5" proc-macro-error = "1.0.4" diff --git a/codegen/src/api/calls.rs b/codegen/src/api/calls.rs index 96fc12fc9c..f6e1e00d87 100644 --- a/codegen/src/api/calls.rs +++ b/codegen/src/api/calls.rs @@ -23,8 +23,8 @@ use frame_metadata::{ PalletMetadata, }; use heck::{ - CamelCase as _, - SnakeCase as _, + ToSnakeCase as _, + ToUpperCamelCase as _, }; use proc_macro2::TokenStream as TokenStream2; use proc_macro_error::abort_call_site; @@ -43,7 +43,7 @@ pub fn generate_calls( let struct_defs = super::generate_structs_from_variants( type_gen, call.ty.id(), - |name| name.to_camel_case().into(), + |name| name.to_upper_camel_case().into(), "Call", ); let (call_structs, call_fns): (Vec<_>, Vec<_>) = struct_defs diff --git a/codegen/src/api/constants.rs b/codegen/src/api/constants.rs index 7cbb988450..d7786a6b39 100644 --- a/codegen/src/api/constants.rs +++ b/codegen/src/api/constants.rs @@ -16,7 +16,7 @@ use crate::types::TypeGenerator; use frame_metadata::PalletConstantMetadata; -use heck::SnakeCase as _; +use heck::ToSnakeCase as _; use proc_macro2::TokenStream as TokenStream2; use quote::{ format_ident, diff --git a/codegen/src/api/mod.rs b/codegen/src/api/mod.rs index d11071f156..651328261d 100644 --- a/codegen/src/api/mod.rs +++ b/codegen/src/api/mod.rs @@ -54,7 +54,7 @@ use frame_metadata::{ RuntimeMetadataPrefixed, StorageEntryType, }; -use heck::SnakeCase as _; +use heck::ToSnakeCase as _; use proc_macro2::TokenStream as TokenStream2; use proc_macro_error::abort_call_site; use quote::{ @@ -462,7 +462,7 @@ where type_gen, ); CompositeDef::struct_def( - var.name(), + struct_name.as_ref(), Default::default(), fields, Some(parse_quote!(pub)), diff --git a/codegen/src/api/storage.rs b/codegen/src/api/storage.rs index 6e2065c8a8..1a6a8bb5ce 100644 --- a/codegen/src/api/storage.rs +++ b/codegen/src/api/storage.rs @@ -23,7 +23,7 @@ use frame_metadata::{ StorageEntryType, StorageHasher, }; -use heck::SnakeCase as _; +use heck::ToSnakeCase as _; use proc_macro2::TokenStream as TokenStream2; use proc_macro_error::abort_call_site; use quote::{ diff --git a/examples/Cargo.toml b/examples/Cargo.toml index e30ba4d7f0..8d8cbf21b1 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "subxt-examples" -version = "0.16.0" +version = "0.17.0" authors = ["Parity Technologies "] edition = "2021" publish = false @@ -14,7 +14,7 @@ description = "Subxt example usage" [dev-dependencies] subxt = { path = "../subxt" } async-std = { version = "1.9.0", features = ["attributes", "tokio1"] } -sp-keyring = "4.0.0" +sp-keyring = "5.0.0" env_logger = "0.9.0" futures = "0.3.13" codec = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive", "full", "bit-vec"] } diff --git a/macro/Cargo.toml b/macro/Cargo.toml index 43b2534841..c8165e2da6 100644 --- a/macro/Cargo.toml +++ b/macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "subxt-macro" -version = "0.16.0" +version = "0.17.0" authors = ["Parity Technologies "] edition = "2021" autotests = false @@ -19,7 +19,7 @@ async-trait = "0.1.49" codec = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive", "full"] } darling = "0.13.0" frame-metadata = "14.0" -heck = "0.3.2" +heck = "0.4.0" proc-macro2 = "1.0.24" proc-macro-crate = "0.1.5" proc-macro-error = "1.0.4" @@ -27,10 +27,10 @@ quote = "1.0.8" syn = "1.0.58" scale-info = "1.0.0" -subxt-codegen = { path = "../codegen", version = "0.16.0" } +subxt-codegen = { path = "../codegen", version = "0.17.0" } [dev-dependencies] pretty_assertions = "1.0.0" -subxt = { path = "../subxt", version = "0.16.0" } +subxt = { path = "../subxt" } trybuild = "1.0.38" -sp-keyring = "4.0.0" +sp-keyring = "5.0.0" diff --git a/subxt/Cargo.toml b/subxt/Cargo.toml index 54e1b393ad..6dec5b7c90 100644 --- a/subxt/Cargo.toml +++ b/subxt/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "subxt" -version = "0.16.0" +version = "0.17.0" authors = ["Parity Technologies "] edition = "2021" @@ -27,10 +27,10 @@ serde = { version = "1.0.124", features = ["derive"] } serde_json = "1.0.64" thiserror = "1.0.24" -subxt-macro = { version = "0.16.0", path = "../macro" } +subxt-macro = { version = "0.17.0", path = "../macro" } -sp-core = { version = "4.0.0", default-features = false } -sp-runtime = { version = "4.0.0", default-features = false } +sp-core = { version = "5.0.0", default-features = false } +sp-runtime = "5.0.0" sp-version = "4.0.0" frame-metadata = "14.0.0" @@ -45,4 +45,4 @@ tempdir = "0.3.7" wabt = "0.10.0" which = "4.0.2" test-runtime = { path = "../test-runtime" } -sp-keyring = "4.0.0" +sp-keyring = "5.0.0" diff --git a/subxt/src/client.rs b/subxt/src/client.rs index bee3d70507..ad52ddbcf9 100644 --- a/subxt/src/client.rs +++ b/subxt/src/client.rs @@ -90,7 +90,7 @@ impl ClientBuilder { client } else { let url = self.url.as_deref().unwrap_or("ws://127.0.0.1:9944"); - crate::rpc::build_ws_client(url).await? + crate::rpc::ws_client(url).await? }; let rpc = Rpc::new(client); let (metadata_bytes, genesis_hash, runtime_version, properties) = future::join4( diff --git a/subxt/src/error.rs b/subxt/src/error.rs index 650f091536..59a0bdf731 100644 --- a/subxt/src/error.rs +++ b/subxt/src/error.rs @@ -155,18 +155,6 @@ impl RuntimeError { } } -/// Module error. -#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)] -#[error("{error} from {pallet}")] -pub struct PalletError { - /// The module where the error originated. - pub pallet: String, - /// The actual error code. - pub error: String, - /// The error description. - pub description: Vec, -} - /// Transaction error. #[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)] pub enum TransactionError { diff --git a/subxt/src/events.rs b/subxt/src/events.rs index d7f5fb9e93..e637774d9a 100644 --- a/subxt/src/events.rs +++ b/subxt/src/events.rs @@ -373,10 +373,17 @@ pub enum EventsDecodingError { mod tests { use super::*; use crate::{ + error::GenericError::{ + Codec, + EventsDecoding, + Other, + }, + events::EventsDecodingError::UnsupportedPrimitive, Config, DefaultConfig, Phase, }; + use assert_matches::assert_matches; use codec::Encode; use frame_metadata::{ v14::{ @@ -643,4 +650,185 @@ mod tests { bitvec::bitvec![Msb0, u64; 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::(); + let res = decode_and_consume_type(id.id(), ®, dummy_cursor); + assert_matches!( + res, + Err(EventsDecoding(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(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::(); + 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 { + 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![Lsb0, u8; 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 { + #[codec(compact)] + val: T, + } + decode_and_consume_type_consumes_all_bytes(CompactV4 { val: 0u8 }); + decode_and_consume_type_consumes_all_bytes(CompactV4 { val: 1u16 }); + } } diff --git a/subxt/src/lib.rs b/subxt/src/lib.rs index 02077fcd8c..50aac73692 100644 --- a/subxt/src/lib.rs +++ b/subxt/src/lib.rs @@ -81,7 +81,8 @@ pub use crate::{ error::{ BasicError, Error, - PalletError, + GenericError, + RuntimeError, TransactionError, }, events::{ diff --git a/subxt/src/rpc.rs b/subxt/src/rpc.rs index 7322f3148f..e105ddcf69 100644 --- a/subxt/src/rpc.rs +++ b/subxt/src/rpc.rs @@ -203,6 +203,10 @@ pub trait SubxtRpcApi { #[method(name = "author_hasKey")] async fn has_key(&self, public_key: Bytes, key_type: String) -> RpcResult; + /// Create and submit an extrinsic and return corresponding Hash if successful + #[method(name = "author_submitExtrinsic")] + async fn submit_extrinsic(&self, extrinsic: Bytes) -> RpcResult; + /// Subscribe to System Events that are imported into blocks. /// /// *WARNING* these may not be included in the finalized chain, use @@ -225,10 +229,6 @@ pub trait SubxtRpcApi { item = SubstrateTransactionStatus )] fn watch_extrinsic(&self, xt: Bytes) -> RpcResult<()>; - - /// Create and submit an extrinsic and return corresponding Hash if successful - #[method(name = "author_submitExtrinsic")] - async fn submit_extrinsic(&self, extrinsic: Bytes) -> RpcResult; } /// A number type that can be serialized both as a number or a string that encodes a number in a @@ -400,7 +400,7 @@ impl Rpc { } /// Build WS RPC client from URL -pub async fn build_ws_client(url: &str) -> Result { +pub async fn ws_client(url: &str) -> Result { let (sender, receiver) = ws_transport(url).await?; Ok(RpcClientBuilder::default() .max_notifs_per_subscription(4096) diff --git a/subxt/src/subscription.rs b/subxt/src/subscription.rs index e70b714547..6ff706a15a 100644 --- a/subxt/src/subscription.rs +++ b/subxt/src/subscription.rs @@ -42,6 +42,16 @@ use sp_core::{ use sp_runtime::traits::Header; use std::collections::VecDeque; +/// Raw bytes for an Event, including the block hash where it occurred and its +/// corresponding event index. +#[derive(Debug)] +#[cfg_attr(test, derive(PartialEq, Clone))] +pub struct EventContext { + pub block_hash: Hash, + pub event_idx: usize, + pub event: RawEvent, +} + /// Event subscription simplifies filtering a storage change set stream for /// events of interest. pub struct EventSubscription<'a, T: Config> { @@ -49,7 +59,7 @@ pub struct EventSubscription<'a, T: Config> { block: Option, extrinsic: Option, event: Option<(&'static str, &'static str)>, - events: VecDeque, + events: VecDeque>, finished: bool, } @@ -60,13 +70,19 @@ enum BlockReader<'a, T: Config> { }, /// Mock event listener for unit tests #[cfg(test)] - Mock(Box, BasicError>)>>), + Mock( + Box< + dyn Iterator< + Item = (T::Hash, Result, BasicError>), + >, + >, + ), } impl<'a, T: Config> BlockReader<'a, T> { async fn next( &mut self, - ) -> Option<(T::Hash, Result, BasicError>)> { + ) -> Option<(T::Hash, Result, BasicError>)> { match self { BlockReader::Decoder { subscription, @@ -81,7 +97,13 @@ impl<'a, T: Config> BlockReader<'a, T> { }) .collect(); - let flattened_events = events.map(|x| x.into_iter().flatten().collect()); + let flattened_events = events.map(|x| { + x.into_iter() + .flatten() + .enumerate() + .map(|(event_idx, (phase, raw))| (phase, event_idx, raw)) + .collect() + }); Some((change_set.block, flattened_events)) } #[cfg(test)] @@ -127,6 +149,15 @@ impl<'a, T: Config> EventSubscription<'a, T> { /// Gets the next event. pub async fn next(&mut self) -> Option> { + self.next_context() + .await + .map(|res| res.map(|ctx| ctx.event)) + } + /// Gets the next event with the associated block hash and its corresponding + /// event index. + pub async fn next_context( + &mut self, + ) -> Option, BasicError>> { loop { if let Some(raw_event) = self.events.pop_front() { return Some(Ok(raw_event)) @@ -147,7 +178,7 @@ impl<'a, T: Config> EventSubscription<'a, T> { match events { Err(err) => return Some(Err(err)), Ok(raw_events) => { - for (phase, raw) in raw_events { + for (phase, event_idx, raw) in raw_events { if let Some(ext_index) = self.extrinsic { if !matches!(phase, Phase::ApplyExtrinsic(i) if i as usize == ext_index) { @@ -159,7 +190,11 @@ impl<'a, T: Config> EventSubscription<'a, T> { continue } } - self.events.push_back(raw); + self.events.push_back(EventContext { + block_hash: received_hash, + event_idx, + event: raw, + }); } } } @@ -282,7 +317,7 @@ mod tests { #[async_std::test] /// test that filters work correctly, and are independent of each other async fn test_filters() { - let mut events = vec![]; + let mut events: Vec<(H256, Phase, usize, RawEvent)> = vec![]; // create all events for block_hash in [H256::from([0; 32]), H256::from([1; 32])] { for phase in [ @@ -291,14 +326,24 @@ mod tests { Phase::ApplyExtrinsic(1), Phase::Finalization, ] { - for event in [named_event("a"), named_event("b")] { - events.push((block_hash, phase.clone(), event)) - } + [named_event("a"), named_event("b")] + .iter() + .enumerate() + .for_each(|(idx, event)| { + events.push(( + block_hash, + phase.clone(), + // The event index + idx, + event.clone(), + )) + }); } } + // set variant index so we can uniquely identify the event events.iter_mut().enumerate().for_each(|(idx, event)| { - event.2.variant_index = idx as u8; + event.3.variant_index = idx as u8; }); let half_len = events.len() / 2; @@ -315,8 +360,8 @@ mod tests { Ok(events .iter() .take(half_len) - .map(|(_, phase, event)| { - (phase.clone(), event.clone()) + .map(|(_, phase, idx, event)| { + (phase.clone(), *idx, event.clone()) }) .collect()), ), @@ -325,8 +370,8 @@ mod tests { Ok(events .iter() .skip(half_len) - .map(|(_, phase, event)| { - (phase.clone(), event.clone()) + .map(|(_, phase, idx, event)| { + (phase.clone(), *idx, event.clone()) }) .collect()), ), @@ -339,21 +384,24 @@ mod tests { events: Default::default(), finished: false, }; - let mut expected_events = events.clone(); + + let mut expected_events: Vec<(H256, Phase, usize, RawEvent)> = + events.clone(); + if let Some(hash) = block_filter { - expected_events.retain(|(h, _, _)| h == &hash); + expected_events.retain(|(h, _, _, _)| h == &hash); } if let Some(idx) = extrinsic_filter { - expected_events.retain(|(_, phase, _)| matches!(phase, Phase::ApplyExtrinsic(i) if *i as usize == idx)); + expected_events.retain(|(_, phase, _, _)| matches!(phase, Phase::ApplyExtrinsic(i) if *i as usize == idx)); } if let Some(name) = event_filter { - expected_events.retain(|(_, _, event)| event.pallet == name.0); + expected_events.retain(|(_, _, _, event)| event.pallet == name.0); } for expected_event in expected_events { assert_eq!( subscription.next().await.unwrap().unwrap(), - expected_event.2 + expected_event.3 ); } assert!(subscription.next().await.is_none()); @@ -361,4 +409,79 @@ mod tests { } } } + + #[async_std::test] + async fn test_context() { + let mut events = vec![]; + // create all events + for block_hash in [H256::from([0; 32]), H256::from([1; 32])] { + for phase in [ + Phase::Initialization, + Phase::ApplyExtrinsic(0), + Phase::ApplyExtrinsic(1), + Phase::Finalization, + ] { + [named_event("a"), named_event("b")] + .iter() + .enumerate() + .for_each(|(idx, event)| { + events.push(( + phase.clone(), + EventContext { + block_hash, + event_idx: idx, + event: event.clone(), + }, + )); + }); + } + } + + // set variant index so we can uniquely identify the event + events.iter_mut().enumerate().for_each(|(idx, (_, ctx))| { + ctx.event.variant_index = idx as u8; + }); + + let half_len = events.len() / 2; + + let mut subscription: EventSubscription = EventSubscription { + block_reader: BlockReader::Mock(Box::new( + vec![ + ( + events[0].1.block_hash, + Ok(events + .iter() + .take(half_len) + .map(|(phase, ctx)| { + (phase.clone(), ctx.event_idx, ctx.event.clone()) + }) + .collect()), + ), + ( + events[half_len].1.block_hash, + Ok(events + .iter() + .skip(half_len) + .map(|(phase, ctx)| { + (phase.clone(), ctx.event_idx, ctx.event.clone()) + }) + .collect()), + ), + ] + .into_iter(), + )), + block: None, + extrinsic: None, + event: None, + events: Default::default(), + finished: false, + }; + + let expected_events = events.clone(); + + for exp in expected_events { + assert_eq!(subscription.next_context().await.unwrap().unwrap(), exp.1); + } + assert!(subscription.next().await.is_none()); + } } diff --git a/test-runtime/Cargo.toml b/test-runtime/Cargo.toml index 8848fdcd7b..fdd63aedc7 100644 --- a/test-runtime/Cargo.toml +++ b/test-runtime/Cargo.toml @@ -1,15 +1,15 @@ [package] name = "test-runtime" -version = "0.16.0" +version = "0.17.0" edition = "2021" [dependencies] subxt = { path = "../subxt" } -sp-runtime = "4.0.0" +sp-runtime = "5.0.0" codec = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive", "full", "bit-vec"] } [build-dependencies] -subxt = { path = "../subxt", version = "0.16.0" } -sp-core = "4.0.0" +subxt = { path = "../subxt" } +sp-core = "5.0.0" async-std = { version = "1.9.0", features = ["attributes", "tokio1"] } which = "4.2.2" diff --git a/test-runtime/README.md b/test-runtime/README.md index 9763412df2..07cc531360 100644 --- a/test-runtime/README.md +++ b/test-runtime/README.md @@ -3,8 +3,8 @@ The logic for this crate exists mainly in the `build.rs` file. At compile time, this crate will: -- Spin up a local `substrate` binary (set the `SUBSTRATE_NODE_PATH` env var to point to a custom binary, otehrwise it'll look for `substrate` on your PATH). +- Spin up a local `substrate` binary (set the `SUBSTRATE_NODE_PATH` env var to point to a custom binary, otherwise it'll look for `substrate` on your PATH). - Obtain metadata from this node. - Export the metadata and a `node_runtime` module which has been annotated using the `subxt` proc macro and is based off the above metadata. -The reason for doing this is that our integration tests (which also spin up a Substrate node) can then use the generated `subxt` types from the exact node being tested against, so that we don't have to worry about metadata getting out of sync with the binary under test. \ No newline at end of file +The reason for doing this is that our integration tests (which also spin up a Substrate node) can then use the generated `subxt` types from the exact node being tested against, so that we don't have to worry about metadata getting out of sync with the binary under test. diff --git a/test-runtime/build.rs b/test-runtime/build.rs index 3d10978a97..300bd82fe9 100644 --- a/test-runtime/build.rs +++ b/test-runtime/build.rs @@ -58,6 +58,10 @@ async fn run() { .spawn(); let mut cmd = match cmd { Ok(cmd) => KillOnDrop(cmd), + Err(ref e) if e.kind() == std::io::ErrorKind::NotFound => { + panic!("A substrate binary should be installed on your path for testing purposes. \ + See https://github.com/paritytech/subxt/tree/master#integration-testing") + } Err(e) => { panic!("Cannot spawn substrate command '{}': {}", substrate_bin, e) } @@ -75,11 +79,10 @@ async fn run() { // It might take a while for substrate node that spin up the RPC server. // Thus, the connection might get rejected a few times. - let res = - match rpc::build_ws_client(&format!("ws://localhost:{}", port)).await { - Ok(c) => c.request("state_getMetadata", None).await, - Err(e) => Err(e), - }; + let res = match rpc::ws_client(&format!("ws://localhost:{}", port)).await { + Ok(c) => c.request("state_getMetadata", None).await, + Err(e) => Err(e), + }; match res { Ok(res) => { @@ -165,7 +168,7 @@ fn next_open_port() -> Option { } } -/// If the substrate process isn't explicilty killed on drop, +/// If the substrate process isn't explicitly killed on drop, /// it seems that panics that occur while the command is running /// will leave it running and block the build step from ever finishing. /// Wrapping it in this prevents this from happening.