diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5bb00d459..79834d076 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,19 +54,19 @@ jobs: # Test for 32 bit and wasm32-unknown-unknown compatibility cargo build --target wasm32-unknown-unknown --no-default-features --features sync-api, - # Test for async compilation - cargo build --no-default-features --features "std jsonrpsee-client", - # Compile async examples separately to enable async-mode - cargo build --release -p ac-examples --example get_blocks_async --no-default-features, - cargo build --release -p ac-examples --example runtime_update_async --no-default-features, + # Compile examples and integration test separately to ensure features are not cross-imported + cargo test --release -p ac-examples-async, + cargo test --release -p ac-examples-sync, + cargo test --release -p ac-testing-async, + cargo test --release -p ac-testing-sync, # Clippy cargo clippy -- -D warnings, cargo clippy --no-default-features -- -D warnings, cargo clippy --all-features --examples -- -D warnings, - # Run tests and build examples - cargo test --release --workspace --all-features --exclude test-no-std, + # Run unit tests + cargo test --release, # Fmt cargo fmt --all -- --check @@ -148,7 +148,7 @@ jobs: custom_nonce, check_extrinsic_events, get_account_identity, - get_blocks_async, + get_blocks, get_storage, print_metadata, staking_batch_payout, diff --git a/Cargo.lock b/Cargo.lock index e6b3dc435..d93c80c2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,13 +22,12 @@ dependencies = [ ] [[package]] -name = "ac-examples" +name = "ac-examples-async" version = "0.4.2" dependencies = [ "env_logger", "frame-support", "frame-system", - "futures", "kitchensink-runtime", "log", "pallet-balances", @@ -39,7 +38,6 @@ dependencies = [ "sp-core", "sp-keyring", "sp-runtime", - "sp-version", "sp-weights", "substrate-api-client", "tokio", @@ -47,6 +45,19 @@ dependencies = [ "wabt", ] +[[package]] +name = "ac-examples-sync" +version = "0.4.2" +dependencies = [ + "env_logger", + "log", + "sp-core", + "sp-keyring", + "sp-runtime", + "sp-weights", + "substrate-api-client", +] + [[package]] name = "ac-node-api" version = "0.5.1" @@ -103,27 +114,31 @@ dependencies = [ ] [[package]] -name = "ac-testing" +name = "ac-testing-async" version = "0.3.2" dependencies = [ "frame-support", - "frame-system", "kitchensink-runtime", "pallet-balances", - "pallet-identity", "pallet-staking", "parity-scale-codec", - "serde_json", - "sp-application-crypto", "sp-core", "sp-keyring", "sp-runtime", "sp-staking", - "sp-version", "substrate-api-client", - "substrate-client-keystore", "tokio", - "wabt", +] + +[[package]] +name = "ac-testing-sync" +version = "0.3.2" +dependencies = [ + "sp-application-crypto", + "sp-core", + "sp-runtime", + "substrate-api-client", + "substrate-client-keystore", ] [[package]] @@ -7276,7 +7291,7 @@ version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "digest 0.10.7", "rand 0.8.5", "static_assertions", diff --git a/Cargo.toml b/Cargo.toml index a1530d1a4..00ee68df8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,10 +16,12 @@ members = [ ".", "client-keystore", "compose-macros", - "examples", + "examples/async", + "examples/sync", "node-api", "test-no-std", - "testing", + "testing/async", + "testing/sync", ] [dependencies] @@ -40,7 +42,6 @@ serde_json = { version = "1.0.79", default-features = false } url = { version = "2.0.0", optional = true } # websocket dependent features -futures = { version = "0.3", optional = true } jsonrpsee = { version = "0.16", optional = true, features = ["async-client", "client-ws-transport", "jsonrpsee-types"] } tungstenite = { version = "0.21", optional = true, features = ["native-tls"] } ws = { version = "0.9.2", optional = true, features = ["ssl"] } @@ -66,9 +67,10 @@ ac-node-api = { path = "node-api", features = ["mocks"] } kitchensink-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } scale-info = { version = "2.1.1", features = ["derive"] } test-case = "3.1.0" +futures = "0.3.28" [features] -default = ["std", "jsonrpsee-client", "sync-api"] +default = ["std", "jsonrpsee-client"] # To support `no_std` builds in non-32 bit environments. disable_target_static_assertions = [ "sp-runtime-interface/disable_target_static_assertions", @@ -77,10 +79,10 @@ disable_target_static_assertions = [ # If this is active all the code compiles in synchronous mode. If not selected, code will compile to async mode. sync-api = ["ac-compose-macros/sync-api", "maybe-async/is_sync"] -# Use the `jsonrpsee` crate for websocket communication. Does provide sync and async support but needs a tokio runtime. +# Use the `jsonrpsee` crate for websocket communication. Does only provide async support and needs a tokio runtime. # Provides convenience functions such as subscription callbacks. # Most examples use the `jsonrpsee` feature and can be used for reference. -jsonrpsee-client = ["std", "jsonrpsee", "futures"] +jsonrpsee-client = ["std", "jsonrpsee"] # Use the `tungstenite` crate for websocket communication. No async support but has some reconnection capabilities. # See the example `transfer_with_tungstenite_client` on how to use it. diff --git a/README.md b/README.md index 287b0c77a..0c4e81fd0 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ The substrate-api-client connects to the substrate's RPC interface via WebSocket * Support `no_std` builds. Only the rpc-client is std only. For `no_std` builds, a custom rpc client needs to be implemented. * Watch events and execute code upon events. * Parse and print the node metadata. +* Support async and sync implementations. * Support three different websocket crates (`jsonrpsee`, `tungstenite` and `ws`). See `Cargo.toml` for more information and limitations. ## Prerequisites @@ -30,14 +31,18 @@ docker run -p 9944:9944 -p 9933:9933 -p 30333:30333 parity/substrate:latest --de For more information, please refer to the [substrate](https://github.com/paritytech/substrate) repository. ## Examples - +The api-client provides several examples which show how to fetch node states or submit extrinsic. Examples are differentiated between `sync` and `async` implementations. Don't forget to check the feature import of the associated `Cargo.toml`. It shows how to import the api-client as an `async` or `sync` library. To run an example, clone the `substrate-api-client` repository and run the desired example directly with the cargo command: ```bash git clone https://github.com/scs/substrate-api-client.git cd substrate-api-client -cargo run -p ac-examples --example get_storage +# Run an async example: +cargo run -p ac-examples-async --example get_storage +# Run a sync example: +cargo run -p ac-examples-sync --example runtime_update_sync ``` + or download the already built binaries from [GitHub Actions](https://github.com/scs/substrate-api-client/actions) and run them without any previous building: ```bash @@ -50,24 +55,26 @@ chmod +x Set the output verbosity by prepending `RUST_LOG=info` or `RUST_LOG=debug`. -The following examples can be found in the [examples](/examples/examples) folder: - -* [benchmark_bulk_xt](/examples/examples/benchmark_bulk_xt.rs): Float the node with a series of transactions. -* [check_extrinsic_events](/examples/examples/check_extrinsic_events.rs): Check and react according to events associated to an extrinsic. -* [compose_extrinsic](/examples/examples/compose_extrinsic.rs): Compose an extrinsic without interacting with the node or in no_std mode. -* [contract_instantiate_with_code](/examples/examples/contract_instantiate_with_code.rs): Instantiate a contract on the chain. -* [custom_nonce](/examples/examples/custom_nonce.rs): Compose an with a custom nonce. -* [get_account_identity](/examples/examples/get_account_identity.rs): Create an custom Unchecked Extrinsic to set an account identity and retrieve it afterwards with a getter. -* [get_block_async](/examples/examples/get_block_async.rs): Read header, block and signed block from storage. -* [get_storage](/examples/examples/get_storage.rs): Read storage values. -* [print_metadata](/examples/examples/print_metadata.rs): Print the metadata of the node in a readable way. -* [runtime_update_async](/src/examples/examples/runtime_update_async.rs): How to do an runtime upgrade asynchronously. -* [runtime_update_sync](/src/examples/examples/runtime_update_sync.rs): How to do an runtime upgrade synchronously. -* [staking_batch_payout](/src/examples/examples/staking_batch_payout.rs): Batch reward payout for validator. -* [subscribe_events](/examples/examples/subscribe_events.rs): Subscribe and react on events. -* [sudo](/examples/examples/sudo.rs): Create and send a sudo wrapped call. -* [transfer_with_tungstenite_client](/examples/examples/transfer_with_tungstenite_client.rs): Transfer tokens by using a wrapper of compose_extrinsic with an account generated with a seed. -* [transfer_with_ws_client](/examples/examples/transfer_with_ws_client.rs): Transfer tokens by using a wrapper of compose_extrinsic with an account generated with a seed. +The following async examples can be found in the [async examples](/examples/async/examples) folder: +* [benchmark_bulk_xt](/examples/async/examples/benchmark_bulk_xt.rs): Float the node with a series of transactions. +* [check_extrinsic_events](/examples/async/examples/check_extrinsic_events.rs): Check and react according to events associated to an extrinsic. +* [compose_extrinsic](/examples/async/examples/compose_extrinsic.rs): Compose an extrinsic without interacting with the node or in no_std mode. +* [contract_instantiate_with_code](/examples/async/examples/contract_instantiate_with_code.rs): Instantiate a contract on the chain. +* [custom_nonce](/examples/async/examples/custom_nonce.rs): Compose an with a custom nonce. +* [get_account_identity](/examples/async/examples/get_account_identity.rs): Create an custom Unchecked Extrinsic to set an account identity and retrieve it afterwards with a getter. +* [get_blocks](/examples/async/examples/get_blocks.rs): Read header, block and signed block from storage. +* [get_storage](/examples/async/examples/get_storage.rs): Read storage values. +* [print_metadata](/examples/async/examples/print_metadata.rs): Print the metadata of the node in a readable way. +* [runtime_update_async](/examples/async/examples/runtime_update_async.rs): How to do an runtime upgrade asynchronously. +* [staking_batch_payout](/examples/async/examples/staking_batch_payout.rs): Batch reward payout for validator. +* [subscribe_events](/examples/async/examples/subscribe_events.rs): Subscribe and react on events. +* [sudo](/examples/async/examples/sudo.rs): Create and send a sudo wrapped call. + +The following sync examples can be found in the [sync examples](/examples/sync/examples) folder: +* [runtime_update_sync](/examples/sync/examples/runtime_update_sync.rs): How to do an runtime upgrade synchronously. +* [transfer_with_tungstenite_client](/examples/sync/examples/transfer_with_tungstenite_client.rs): Transfer tokens by using a wrapper of compose_extrinsic with an account generated with a seed. +* [transfer_with_ws_client](/examples/sync/examples/transfer_with_ws_client.rs): Transfer tokens by using a wrapper of compose_extrinsic with an account generated with a seed. + ## `no_std` build Almost everything in the api-client, except for the [rpc-clients](https://github.com/scs/substrate-api-client/tree/master/src/rpc) and a few additional features, is `no_std` compatible. @@ -78,19 +85,17 @@ To import the api-client in `no_std` make sure the default features are turned o ```toml # In the Cargo.toml import the api-client as following: substrate-api-client = { git = "https://github.com/scs/substrate-api-client.git", default-features = false, features = ["disable_target_static_assertions"] } - ``` ### RPC Client -Depending on the usage, there are two traits that the RPC Client needs to implement. +Depending on the usage, there are two traits that the RPC Client needs to implement. You can choose between the sync and async implementation. If you decide to use the async implementation, you need to use the library `async-trait` for now (until it is integrated into the rust toolchain). #### Request - For simple requests (send one request and receive one answer) the trait [`Request`](https://github.com/scs/substrate-api-client/blob/d0a875e70f688c8ae2ce641935189c6374bc0ced/src/rpc/mod.rs#L44-L48) is required: ```rust /// Trait to be implemented by the ws-client for sending rpc requests and extrinsic. pub trait Request { /// Sends a RPC request to the substrate node and returns the answer as string. - fn request(&self, method: &str, params: RpcParams) -> Result; + (async) fn request(&self, method: &str, params: RpcParams) -> Result; } ``` By implementing this trait with a custom RPC client, most basic functionalities of the `Api` can already be used. @@ -107,7 +112,7 @@ pub trait Subscribe { where Notification: DeserializeOwned; - fn subscribe( + (async) fn subscribe( &self, sub: &str, params: RpcParams, @@ -123,10 +128,10 @@ pub trait HandleSubscription { /// Returns the next notification from the stream. /// This may return `None` if the subscription has been terminated, /// which may happen if the channel becomes full or is dropped. - fn next(&mut self) -> Option>; + (async) fn next(&mut self) -> Option>; /// Unsubscribe and consume the subscription. - fn unsubscribe(self) -> Result<()>; + (async) fn unsubscribe(self) -> Result<()>; } ``` Refering to the `std` example of the tungstenite, the `HandleSubscription` impl can be looked up [here](https://github.com/scs/substrate-api-client/blob/d0a875e70f688c8ae2ce641935189c6374bc0ced/src/rpc/tungstenite_client/subscription.rs#L23-L54). It implements a simple channel receiver, waiting for the sender of the websocket client to send something. diff --git a/examples/Cargo.toml b/examples/Cargo.toml deleted file mode 100644 index df98e70d3..000000000 --- a/examples/Cargo.toml +++ /dev/null @@ -1,42 +0,0 @@ -[package] -name = "ac-examples" -version = "0.4.2" -license = "Apache-2.0" -edition = "2021" - -[dev-dependencies] -codec = { package = "parity-scale-codec", version = "3.6.1", features = ['derive'] } -env_logger = "0.10.0" -futures = "0.3.28" -log = { version = "0.4.14" } -serde_json = { version = "1.0.79" } -tokio = { version = "1.24", features = ["rt-multi-thread", "macros", "time"] } -wabt = "0.10.0" - -# Substrate dependencies -frame-support = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } -frame-system = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } -kitchensink-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } -pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } -pallet-identity = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } -pallet-staking = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } -sp-core = { features = ["full_crypto"], git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } -sp-keyring = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } -sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } -sp-version = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } -sp-weights = { default-features = false, features = ["serde"], git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } - -# local deps -substrate-api-client = { path = "..", default-features = false, features = ["jsonrpsee-client", "staking-xt", "contracts-xt"] } - -[features] -default = ["sync-examples"] -sync-examples = [ - "substrate-api-client/std", - "substrate-api-client/sync-api", - "substrate-api-client/tungstenite-client", - "substrate-api-client/ws-client", -] - -[dependencies] -tokio-util = "0.7.8" diff --git a/testing/Cargo.toml b/examples/async/Cargo.toml similarity index 63% rename from testing/Cargo.toml rename to examples/async/Cargo.toml index 1da53d9f3..6d2bd42d7 100644 --- a/testing/Cargo.toml +++ b/examples/async/Cargo.toml @@ -1,42 +1,29 @@ [package] -name = "ac-testing" -version = "0.3.2" -authors = ["Supercomputing Systems AG "] +name = "ac-examples-async" +version = "0.4.2" license = "Apache-2.0" edition = "2021" [dev-dependencies] -codec = { package = "parity-scale-codec", version = "3.6.1", features = [ - 'derive', -] } -serde_json = { version = "1.0.79" } +codec = { package = "parity-scale-codec", version = "3.6.1", features = ['derive'] } +env_logger = "0.10.0" +log = "0.4.14" +serde_json = "1.0.79" tokio = { version = "1.24", features = ["rt-multi-thread", "macros", "time"] } +tokio-util = "0.7.8" wabt = "0.10.0" # Substrate dependencies frame-support = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } frame-system = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } kitchensink-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } -sp-application-crypto = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } -sp-core = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } -sp-keyring = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } -sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } -sp-staking = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } -sp-version = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } pallet-identity = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } pallet-staking = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } +sp-keyring = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } +sp-weights = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } # local deps -substrate-api-client = { path = "..", features = [ - "tungstenite-client", - "ws-client", - "staking-xt", - "contracts-xt", -] } -substrate-client-keystore = { path = "../client-keystore" } - - -[features] -default = ["ws-client"] -ws-client = [] +substrate-api-client = { path = "../..", features = ["staking-xt", "contracts-xt"] } diff --git a/examples/examples/benchmark_bulk_xt.rs b/examples/async/examples/benchmark_bulk_xt.rs similarity index 92% rename from examples/examples/benchmark_bulk_xt.rs rename to examples/async/examples/benchmark_bulk_xt.rs index e0183ad05..ff4eb1e18 100644 --- a/examples/examples/benchmark_bulk_xt.rs +++ b/examples/async/examples/benchmark_bulk_xt.rs @@ -44,8 +44,8 @@ async fn main() { // Initialize api and set the signer (sender) that is used to sign the extrinsics. let signer = AccountKeyring::Alice.pair(); - let client = JsonrpseeClient::with_default_url().unwrap(); - let mut api = Api::::new(client).unwrap(); + let client = JsonrpseeClient::with_default_url().await.unwrap(); + let mut api = Api::::new(client).await.unwrap(); api.set_signer(signer.into()); let recipient: ExtrinsicAddressOf = AccountKeyring::Bob.to_account_id().into(); @@ -53,7 +53,7 @@ async fn main() { // to wait for the response of the node (and the actual execution of the previous extrinsic). // But because we want to spam the node with extrinsic, we simple monotonically increase the nonce, without // waiting for the response of the node. - let mut nonce = api.get_nonce().unwrap(); + let mut nonce = api.get_nonce().await.unwrap(); let first_nonce = nonce; while nonce < first_nonce + 500 { // Compose a balance extrinsic. @@ -64,7 +64,7 @@ async fn main() { let xt = api.compose_extrinsic_offline(call, nonce); println!("Sending extrinsic with nonce {}", nonce); - let _tx_hash = api.submit_extrinsic(xt).unwrap(); + let _tx_hash = api.submit_extrinsic(xt).await.unwrap(); nonce += 1; } diff --git a/examples/examples/check_extrinsic_events.rs b/examples/async/examples/check_extrinsic_events.rs similarity index 84% rename from examples/examples/check_extrinsic_events.rs rename to examples/async/examples/check_extrinsic_events.rs index 3a4bdfcb7..ecdd55dc7 100644 --- a/examples/examples/check_extrinsic_events.rs +++ b/examples/async/examples/check_extrinsic_events.rs @@ -35,16 +35,18 @@ async fn main() { // Initialize api and set the signer (sender) that is used to sign the extrinsics. let alice_signer = AccountKeyring::Alice.pair(); - let client = JsonrpseeClient::with_default_url().unwrap(); - let mut api = Api::::new(client).unwrap(); + let client = JsonrpseeClient::with_default_url().await.unwrap(); + let mut api = Api::::new(client).await.unwrap(); api.set_signer(alice_signer.into()); let alice = AccountKeyring::Alice.to_account_id(); - let balance_of_alice = api.get_account_data(&alice).unwrap().unwrap().free; - println!("[+] Alice's Free Balance is {balance_of_alice}\n"); - let bob = AccountKeyring::Bob.to_account_id(); - let balance_of_bob = api.get_account_data(&bob).unwrap().unwrap_or_default().free; + + let (maybe_data_of_alice, maybe_data_of_bob) = + tokio::try_join!(api.get_account_data(&alice), api.get_account_data(&bob)).unwrap(); + let balance_of_alice = maybe_data_of_alice.unwrap().free; + let balance_of_bob = maybe_data_of_bob.unwrap_or_default().free; + println!("[+] Alice's Free Balance is {balance_of_alice}\n"); println!("[+] Bob's Free Balance is {balance_of_bob}\n"); // First we want to see the events of a failed extrinsic. @@ -52,11 +54,13 @@ async fn main() { // Alice tries so transfer all her balance, but that will not work, because // she will not have enough balance left to pay the fees. let bad_transfer_extrinsic = - api.balance_transfer_allow_death(bob.clone().into(), balance_of_alice); + api.balance_transfer_allow_death(bob.clone().into(), balance_of_alice).await; println!("[+] Composed extrinsic: {bad_transfer_extrinsic:?}\n",); // Send and watch extrinsic until InBlock. - let result = api.submit_and_watch_extrinsic_until(bad_transfer_extrinsic, XtStatus::InBlock); + let result = api + .submit_and_watch_extrinsic_until(bad_transfer_extrinsic, XtStatus::InBlock) + .await; println!("[+] Sent the transfer extrinsic. Result {result:?}"); // Check if the transfer really has failed: @@ -72,21 +76,23 @@ async fn main() { }; // Verify that Bob's free Balance hasn't changed. - let new_balance_of_bob = api.get_account_data(&bob).unwrap().unwrap().free; + let new_balance_of_bob = api.get_account_data(&bob).await.unwrap().unwrap().free; println!("[+] Bob's Free Balance is now {}\n", new_balance_of_bob); assert_eq!(balance_of_bob, new_balance_of_bob); // Verify that Alice's free Balance decreased: paid fees. - let new_balance_of_alice = api.get_account_data(&alice).unwrap().unwrap().free; + let new_balance_of_alice = api.get_account_data(&alice).await.unwrap().unwrap().free; println!("[+] Alice's Free Balance is now {}\n", new_balance_of_alice); assert!(balance_of_alice > new_balance_of_alice); // Next, we send an extrinsic that should succeed: let balance_to_transfer = 1000; let good_transfer_extrinsic = - api.balance_transfer_allow_death(bob.clone().into(), balance_to_transfer); + api.balance_transfer_allow_death(bob.clone().into(), balance_to_transfer).await; // Send and watch extrinsic until InBlock. - let result = api.submit_and_watch_extrinsic_until(good_transfer_extrinsic, XtStatus::InBlock); + let result = api + .submit_and_watch_extrinsic_until(good_transfer_extrinsic, XtStatus::InBlock) + .await; println!("[+] Sent the transfer extrinsic."); // Check if the transfer really was successful: @@ -110,7 +116,7 @@ async fn main() { }; // Verify that Bob release has received the transferred amount. - let new_balance_of_bob = api.get_account_data(&bob).unwrap().unwrap().free; + let new_balance_of_bob = api.get_account_data(&bob).await.unwrap().unwrap().free; println!("[+] Bob's Free Balance is now {}\n", new_balance_of_bob); let expected_balance_of_bob = balance_of_bob + balance_to_transfer; assert_eq!(expected_balance_of_bob, new_balance_of_bob); diff --git a/examples/examples/compose_extrinsic.rs b/examples/async/examples/compose_extrinsic.rs similarity index 93% rename from examples/examples/compose_extrinsic.rs rename to examples/async/examples/compose_extrinsic.rs index f178bb3e0..5db6c13fc 100644 --- a/examples/examples/compose_extrinsic.rs +++ b/examples/async/examples/compose_extrinsic.rs @@ -54,9 +54,9 @@ async fn main() { // Initialize api and set the signer (sender) that is used to sign the extrinsics. let signer = AccountKeyring::Alice.pair(); - let client = JsonrpseeClient::with_default_url().unwrap(); + let client = JsonrpseeClient::with_default_url().await.unwrap(); - let mut api = Api::::new(client).unwrap(); + let mut api = Api::::new(client).await.unwrap(); let extrinsic_signer = ExtrinsicSigner::::new(signer); // Signer is needed to set the nonce and sign the extrinsic. api.set_signer(extrinsic_signer.clone()); @@ -64,8 +64,8 @@ async fn main() { let recipient: Address = MultiAddress::Id(AccountKeyring::Bob.to_account_id()); // Get the last finalized header to retrieve information for Era for mortal transactions (online). - let last_finalized_header_hash = api.get_finalized_head().unwrap().unwrap(); - let header = api.get_header(Some(last_finalized_header_hash)).unwrap().unwrap(); + let last_finalized_header_hash = api.get_finalized_head().await.unwrap().unwrap(); + let header = api.get_header(Some(last_finalized_header_hash)).await.unwrap().unwrap(); let period = 5; // Construct extrinsic params needed for the extrinsic construction. For more information on what these parameters mean, take a look at Substrate docs: https://docs.substrate.io/reference/transaction-format/. @@ -81,7 +81,7 @@ async fn main() { let transaction_version = api.runtime_version().transaction_version; let genesis_hash = api.genesis_hash(); let metadata = api.metadata(); - let signer_nonce = api.get_nonce().unwrap(); + let signer_nonce = api.get_nonce().await.unwrap(); println!("[+] Alice's Account Nonce is {}", signer_nonce); let recipients_extrinsic_address: ExtrinsicAddressOf = @@ -111,6 +111,7 @@ async fn main() { // To send the extrinsic to the node, we need an rpc client which is only available within std-environment. If you want to operate a rpc client in your own no-std environment, take a look at https://github.com/scs/substrate-api-client#rpc-client on how to implement one yourself. let hash = api .submit_and_watch_extrinsic_until(xt, XtStatus::InBlock) + .await .unwrap() .block_hash .unwrap(); @@ -119,7 +120,7 @@ async fn main() { println!(); println!("Compose extrinsic offline"); - let signer_nonce = api.get_nonce().unwrap(); + let signer_nonce = api.get_nonce().await.unwrap(); println!("[+] Alice's Account Nonce is {}", signer_nonce); // Construct an extrinsic offline (without any calls to the node) with the help of the api client. For example, this allows you to set your own nonce (to achieve future calls or construct an extrsinic that must be sent at a later time). @@ -138,6 +139,7 @@ async fn main() { println!("[+] Composed Extrinsic:\n {:?}", xt); let hash = api .submit_and_watch_extrinsic_until(xt, XtStatus::InBlock) + .await .unwrap() .block_hash .unwrap(); diff --git a/examples/examples/contract_instantiate_with_code.rs b/examples/async/examples/contract_instantiate_with_code.rs similarity index 88% rename from examples/examples/contract_instantiate_with_code.rs rename to examples/async/examples/contract_instantiate_with_code.rs index a4940716b..c8e036536 100644 --- a/examples/examples/contract_instantiate_with_code.rs +++ b/examples/async/examples/contract_instantiate_with_code.rs @@ -46,11 +46,11 @@ async fn main() { // Initialize api and set the signer (sender) that is used to sign the extrinsics. let signer = AccountKeyring::Alice.pair(); - let client = JsonrpseeClient::with_default_url().unwrap(); - let mut api = Api::::new(client).unwrap(); + let client = JsonrpseeClient::with_default_url().await.unwrap(); + let mut api = Api::::new(client).await.unwrap(); api.set_signer(signer.into()); - println!("[+] Alice's Account Nonce is {}", api.get_nonce().unwrap()); + println!("[+] Alice's Account Nonce is {}", api.get_nonce().await.unwrap()); // contract to be deployed on the chain const CONTRACT: &str = r#" @@ -61,16 +61,12 @@ async fn main() { "#; let wasm = wabt::wat2wasm(CONTRACT).expect("invalid wabt"); - let xt = api.contract_instantiate_with_code( - 1_000_000_000_000_000, - 500_000, - wasm, - vec![1u8], - vec![1u8], - ); + let xt = api + .contract_instantiate_with_code(1_000_000_000_000_000, 500_000, wasm, vec![1u8], vec![1u8]) + .await; println!("[+] Creating a contract instance with extrinsic:\n\n{:?}\n", xt); - let report = api.submit_and_watch_extrinsic_until(xt, XtStatus::InBlock).unwrap(); + let report = api.submit_and_watch_extrinsic_until(xt, XtStatus::InBlock).await.unwrap(); println!("[+] Extrinsic is in Block. Hash: {:?}\n", report.block_hash.unwrap()); println!("[+] Waiting for the contracts.Instantiated event"); @@ -87,9 +83,9 @@ async fn main() { let contract = contract_instantiated_events[0].contract.clone(); println!("[+] Event was received. Contract deployed at: {contract:?}\n"); - let xt = api.contract_call(contract.into(), 500_000, 500_000, vec![0u8]); + let xt = api.contract_call(contract.into(), 500_000, 500_000, vec![0u8]).await; println!("[+] Calling the contract with extrinsic Extrinsic:\n{:?}\n\n", xt); - let report = api.submit_and_watch_extrinsic_until(xt, XtStatus::Finalized).unwrap(); + let report = api.submit_and_watch_extrinsic_until(xt, XtStatus::Finalized).await.unwrap(); println!("[+] Extrinsic got finalized. Extrinsic Hash: {:?}", report.extrinsic_hash); } diff --git a/examples/examples/custom_nonce.rs b/examples/async/examples/custom_nonce.rs similarity index 87% rename from examples/examples/custom_nonce.rs rename to examples/async/examples/custom_nonce.rs index 97979a3b6..5198ef1d7 100644 --- a/examples/examples/custom_nonce.rs +++ b/examples/async/examples/custom_nonce.rs @@ -36,13 +36,13 @@ async fn main() { // Initialize api and set the signer (sender) that is used to sign the extrinsics. let signer = AccountKeyring::Alice.pair(); - let client = JsonrpseeClient::with_default_url().unwrap(); - let mut api = Api::::new(client).unwrap(); + let client = JsonrpseeClient::with_default_url().await.unwrap(); + let mut api = Api::::new(client).await.unwrap(); api.set_signer(signer.into()); // Information for Era for mortal transactions. - let last_finalized_header_hash = api.get_finalized_head().unwrap().unwrap(); - let header = api.get_header(Some(last_finalized_header_hash)).unwrap().unwrap(); + let last_finalized_header_hash = api.get_finalized_head().await.unwrap().unwrap(); + let header = api.get_header(Some(last_finalized_header_hash)).await.unwrap().unwrap(); let period = 5; let tx_params = GenericAdditionalParams::new() .era(Era::mortal(period, header.number.into()), last_finalized_header_hash) @@ -52,7 +52,7 @@ async fn main() { api.set_additional_params(tx_params); // Get the nonce of Alice. - let signer_nonce = api.get_nonce().unwrap(); + let signer_nonce = api.get_nonce().await.unwrap(); println!("[+] Signer's Account Nonce is {}\n", signer_nonce); // Create an extrinsic that should get included in the future pool due to a nonce that is too high. @@ -63,7 +63,7 @@ async fn main() { println!("[+] Composed Extrinsic:\n {:?}\n", xt); // Send and watch extrinsic until InBlock. - let result = api.submit_and_watch_extrinsic_until(xt, XtStatus::InBlock); + let result = api.submit_and_watch_extrinsic_until(xt, XtStatus::InBlock).await; println!("Returned Result {:?}", result); match result { Err(Error::UnexpectedTxStatus(UnexpectedTxStatus::Future)) => { diff --git a/examples/examples/get_account_identity.rs b/examples/async/examples/get_account_identity.rs similarity index 95% rename from examples/examples/get_account_identity.rs rename to examples/async/examples/get_account_identity.rs index b57ebe062..97760ebc9 100644 --- a/examples/examples/get_account_identity.rs +++ b/examples/async/examples/get_account_identity.rs @@ -43,9 +43,9 @@ async fn main() { env_logger::init(); // Create the node-api client and set the signer. - let client = JsonrpseeClient::with_default_url().unwrap(); + let client = JsonrpseeClient::with_default_url().await.unwrap(); let signer = AccountKeyring::Alice.pair(); - let mut api = Api::::new(client).unwrap(); + let mut api = Api::::new(client).await.unwrap(); api.set_signer(signer.clone().into()); // Fill Identity storage. @@ -68,6 +68,7 @@ async fn main() { // Send and watch extrinsic until InBlock. let _block_hash = api .submit_and_watch_extrinsic_until(xt, XtStatus::InBlock) + .await .unwrap() .block_hash .unwrap(); @@ -82,6 +83,7 @@ async fn main() { let registration: RegistrationType = api .get_storage_map("Identity", "IdentityOf", signer.public(), None) + .await .unwrap() .unwrap(); println!("[+] Retrieved {:?}", registration); diff --git a/examples/examples/get_blocks_async.rs b/examples/async/examples/get_blocks.rs similarity index 83% rename from examples/examples/get_blocks_async.rs rename to examples/async/examples/get_blocks.rs index 5ab46485c..64319ba46 100644 --- a/examples/examples/get_blocks_async.rs +++ b/examples/async/examples/get_blocks.rs @@ -16,40 +16,30 @@ //! Very simple example that shows how to fetch chain information with async. //! To compile this example for async you need to set the `--no-default-features` flag -#[cfg(not(feature = "sync-examples"))] use substrate_api_client::{ ac_primitives::AssetRuntimeConfig, rpc::{HandleSubscription, JsonrpseeClient}, Api, GetChainInfo, SubscribeChain, }; -// Empty implementation that is used in case the example is compiled in synchronous mode -#[cfg(feature = "sync-examples")] -#[tokio::main] -async fn main() { - println!("Please compile this example with `--no-default-features` for it to run properly.") -} - // To test this example with CI we run it against the Substrate kitchensink node, which uses the asset pallet. // Therefore, we need to use the `AssetRuntimeConfig` in this example. // ! However, most Substrate runtimes do not use the asset pallet at all. So if you run an example against your own node // you most likely should use `DefaultRuntimeConfig` instead. -#[cfg(not(feature = "sync-examples"))] #[tokio::main] async fn main() { env_logger::init(); // Initialize the api. - let client = JsonrpseeClient::with_default_url().unwrap(); + let client = JsonrpseeClient::with_default_url().await.unwrap(); let api = Api::::new(client).await.unwrap(); - let (genesis_block, header_hash, signed_block) = futures::future::try_join3( + let (genesis_block, header_hash, signed_block) = tokio::try_join!( api.get_genesis_block(), api.get_finalized_head(), api.get_finalized_block(), ) - .await .unwrap(); let header_hash = header_hash.unwrap(); let signed_block = signed_block.unwrap(); @@ -61,13 +51,12 @@ async fn main() { let number_of_last_three_blocks: Vec<_> = (last_block_number.saturating_sub(3)..last_block_number).collect(); - let (header, blocks, latest_header, latest_block) = futures::future::try_join4( + let (header, blocks, latest_header, latest_block) = tokio::try_join!( api.get_header(Some(header_hash)), api.get_signed_blocks(&number_of_last_three_blocks), api.get_header(None), api.get_block(None), ) - .await .unwrap(); println!("Finalized header:\n {:?} \n", header.unwrap()); println!("Finalized block:\n {:?} \n", signed_block); diff --git a/examples/examples/get_storage.rs b/examples/async/examples/get_storage.rs similarity index 64% rename from examples/examples/get_storage.rs rename to examples/async/examples/get_storage.rs index eb02737dc..3f9a13ea0 100644 --- a/examples/examples/get_storage.rs +++ b/examples/async/examples/get_storage.rs @@ -42,43 +42,46 @@ async fn main() { env_logger::init(); // Initialize the api. - let client = JsonrpseeClient::with_default_url().unwrap(); - let mut api = Api::::new(client).unwrap(); - - // get some plain storage value - let result: Balance = api.get_storage("Balances", "TotalIssuance", None).unwrap().unwrap(); - println!("[+] TotalIssuance is {}", result); - - let proof = api.get_storage_value_proof("Balances", "TotalIssuance", None).unwrap(); + let client = JsonrpseeClient::with_default_url().await.unwrap(); + let mut api = Api::::new(client).await.unwrap(); + + // Get some plain storage values. + let (maybe_balance, proof) = tokio::try_join!( + api.get_storage::>("Balances", "TotalIssuance", None), + api.get_storage_value_proof("Balances", "TotalIssuance", None) + ) + .unwrap(); + println!("[+] TotalIssuance is {:?}", maybe_balance.unwrap()); println!("[+] StorageValueProof: {:?}", proof); - let account = AccountKeyring::Alice.public(); - let result: AccountInfo = api - .get_storage_map("System", "Account", account, None) - .unwrap() - .or_else(|| Some(AccountInfo::default())) - .unwrap(); - println!("[+] AccountInfo for Alice is {:?}", result); + // Get the AccountInfo of Alice and the associated StoragePrefix. + let account: sp_core::sr25519::Public = AccountKeyring::Alice.public(); + let (maybe_account_info, key_prefix) = tokio::try_join!( + api.get_storage_map::<_, Option>("System", "Account", account, None), + api.get_storage_map_key_prefix("System", "Account") + ) + .unwrap(); - // get StorageMap key prefix - let result = api.get_storage_map_key_prefix("System", "Account").unwrap(); - println!("[+] key prefix for System Account map is {:?}", result); + println!("[+] AccountInfo for Alice is {:?}", maybe_account_info.unwrap()); + println!("[+] Key prefix for System Account map is {:?}", key_prefix); - // get Alice's AccountNonce with api.get_nonce() + // Get Alice's and Bobs AccountNonce with api.get_nonce(). Alice will be set as the signer for + // the current api, so the nonce retrieval can be simplified: let signer = AccountKeyring::Alice.pair(); api.set_signer(signer.into()); - println!("[+] Alice's Account Nonce is {}", api.get_nonce().unwrap()); + let bob = AccountKeyring::Bob.to_account_id(); - println!( - "[+] Bob's Account Nonce is {}", - api.get_account_nonce(&AccountKeyring::Bob.to_account_id()).unwrap() - ); + let (alice_nonce, bob_nonce) = + tokio::try_join!(api.get_nonce(), api.get_account_nonce(&bob)).unwrap(); + println!("[+] Alice's Account Nonce is {}", alice_nonce); + println!("[+] Bob's Account Nonce is {}", bob_nonce); // Get an vector of storage keys, numbering up to the given max keys and that start with the (optionally) given storage key prefix. - let storage_key_prefix = api.get_storage_map_key_prefix("System", "Account").unwrap(); + let storage_key_prefix = api.get_storage_map_key_prefix("System", "Account").await.unwrap(); let max_keys = 3; let storage_keys = api .get_storage_keys_paged(Some(storage_key_prefix), max_keys, None, None) + .await .unwrap(); assert_eq!(storage_keys.len() as u32, max_keys); // Get the storage values that belong to the retrieved storage keys. @@ -86,14 +89,17 @@ async fn main() { println!("Retrieving value for key {:?}", storage_key); // We're expecting account info as return value because we fetch a storage value with prefix combination of "System" + "Account". let storage_data: AccountInfo = - api.get_storage_by_key(storage_key.clone(), None).unwrap().unwrap(); + api.get_storage_by_key(storage_key.clone(), None).await.unwrap().unwrap(); println!("Retrieved data {:?}", storage_data); } - let storage_double_map_key_prefix = - api.get_storage_double_map_key_prefix("Staking", "ErasStakers", 0).unwrap(); + let storage_double_map_key_prefix = api + .get_storage_double_map_key_prefix("Staking", "ErasStakers", 0) + .await + .unwrap(); let double_map_storage_keys = api .get_storage_keys_paged(Some(storage_double_map_key_prefix), max_keys, None, None) + .await .unwrap(); // Get the storage values that belong to the retrieved storage keys. @@ -101,7 +107,7 @@ async fn main() { println!("Retrieving value for key {:?}", storage_key); // We're expecting Exposure as return value because we fetch a storage value with prefix combination of "Staking" + "EraStakers" + 0. let storage_data: Exposure = - api.get_storage_by_key(storage_key.clone(), None).unwrap().unwrap(); + api.get_storage_by_key(storage_key.clone(), None).await.unwrap().unwrap(); println!("Retrieved data {:?}", storage_data); } } diff --git a/examples/examples/kitchensink_runtime.compact.compressed.wasm b/examples/async/examples/kitchensink_runtime.compact.compressed.wasm similarity index 100% rename from examples/examples/kitchensink_runtime.compact.compressed.wasm rename to examples/async/examples/kitchensink_runtime.compact.compressed.wasm diff --git a/examples/examples/print_metadata.rs b/examples/async/examples/print_metadata.rs similarity index 90% rename from examples/examples/print_metadata.rs rename to examples/async/examples/print_metadata.rs index cbda282bf..6634b1f26 100644 --- a/examples/examples/print_metadata.rs +++ b/examples/async/examples/print_metadata.rs @@ -30,8 +30,8 @@ async fn main() { env_logger::init(); // Initialize the api, which retrieves the metadata from the node upon initialization. - let client = JsonrpseeClient::with_default_url().unwrap(); - let mut api = Api::::new(client).unwrap(); + let client = JsonrpseeClient::with_default_url().await.unwrap(); + let mut api = Api::::new(client).await.unwrap(); let meta = api.metadata().clone(); @@ -43,7 +43,7 @@ async fn main() { meta.print_pallets_with_constants(); // Update the runtime and metadata. - api.update_runtime().unwrap(); + api.update_runtime().await.unwrap(); // Print full substrate metadata json formatted. println!("{}", (&api.metadata().pretty_format().unwrap())) diff --git a/examples/examples/runtime_update_async.rs b/examples/async/examples/runtime_update_async.rs similarity index 90% rename from examples/examples/runtime_update_async.rs rename to examples/async/examples/runtime_update_async.rs index 3d313abfa..452a8c7c8 100644 --- a/examples/examples/runtime_update_async.rs +++ b/examples/async/examples/runtime_update_async.rs @@ -27,40 +27,12 @@ use tokio_util::sync::CancellationToken; type Hash = ::Hash; -#[cfg(feature = "sync-examples")] -#[tokio::main] -async fn main() { - println!("This example is for async use-cases. Please see runtime_update_sync.rs for the sync implementation.") -} - -#[cfg(not(feature = "sync-examples"))] -pub async fn send_code_update_extrinsic( - api: &substrate_api_client::Api, -) { - let new_wasm: &[u8] = include_bytes!("kitchensink_runtime.compact.compressed.wasm"); - - // this call can only be called by sudo - let call = compose_call!(api.metadata(), "System", "set_code", new_wasm.to_vec()); - let weight: Weight = 0.into(); - let xt = compose_extrinsic!(&api, "Sudo", "sudo_unchecked_weight", call, weight); - - println!("Sending extrinsic to trigger runtime update"); - let block_hash = api - .submit_and_watch_extrinsic_until(xt, XtStatus::InBlock) - .await - .unwrap() - .block_hash - .unwrap(); - println!("[+] Extrinsic got included. Block Hash: {:?}", block_hash); -} - -#[cfg(not(feature = "sync-examples"))] #[tokio::main] async fn main() { env_logger::init(); // Initialize the api. - let client = JsonrpseeClient::with_default_url().unwrap(); + let client = JsonrpseeClient::with_default_url().await.unwrap(); let mut api = Api::::new(client).await.unwrap(); let sudoer = AccountKeyring::Alice.pair(); api.set_signer(sudoer.into()); @@ -101,3 +73,23 @@ async fn main() { assert!(api.spec_version() == 1268); assert!(runtime_update_detected); } + +pub async fn send_code_update_extrinsic( + api: &substrate_api_client::Api, +) { + let new_wasm: &[u8] = include_bytes!("kitchensink_runtime.compact.compressed.wasm"); + + // this call can only be called by sudo + let call = compose_call!(api.metadata(), "System", "set_code", new_wasm.to_vec()); + let weight: Weight = 0.into(); + let xt = compose_extrinsic!(&api, "Sudo", "sudo_unchecked_weight", call, weight); + + println!("Sending extrinsic to trigger runtime update"); + let block_hash = api + .submit_and_watch_extrinsic_until(xt, XtStatus::InBlock) + .await + .unwrap() + .block_hash + .unwrap(); + println!("[+] Extrinsic got included. Block Hash: {:?}", block_hash); +} diff --git a/examples/examples/staking_batch_payout.rs b/examples/async/examples/staking_batch_payout.rs similarity index 90% rename from examples/examples/staking_batch_payout.rs rename to examples/async/examples/staking_batch_payout.rs index 1c11988b2..fb4d47542 100644 --- a/examples/examples/staking_batch_payout.rs +++ b/examples/async/examples/staking_batch_payout.rs @@ -53,8 +53,8 @@ async fn main() { // Initialize api and set the signer (sender) that is used to sign the extrinsics. let alice = AccountKeyring::Alice.pair(); - let client = JsonrpseeClient::with_default_url().unwrap(); - let mut api = Api::::new(client).unwrap(); + let client = JsonrpseeClient::with_default_url().await.unwrap(); + let mut api = Api::::new(client).await.unwrap(); api.set_signer(alice.into()); // Give a valid validator account address. In the kitchinsink runtime, this is Alice. @@ -63,7 +63,8 @@ async fn main() { let validator_stash = AccountId32::from_ss58check("5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY").unwrap(); - let active_era: ActiveEraInfo = api.get_storage("Staking", "ActiveEra", None).unwrap().unwrap(); + let active_era: ActiveEraInfo = + api.get_storage("Staking", "ActiveEra", None).await.unwrap().unwrap(); println!("{:?}", active_era); let current_era_index = active_era.index; @@ -72,14 +73,14 @@ async fn main() { // error is returned from the node, the extrinsic has been created correctly. // Sidenote: We could theoretically force a new era with sudo, but this takes at least 10 minutes ( = 1 epoch) in the // kitchensink rutime. We don't want to wait that long. - let payout_staker_xt = api.payout_stakers(0, validator_stash); - let result = api.submit_and_watch_extrinsic_until(payout_staker_xt, XtStatus::InBlock); + let payout_staker_xt = api.payout_stakers(0, validator_stash).await; + let result = api.submit_and_watch_extrinsic_until(payout_staker_xt, XtStatus::InBlock).await; assert!(result.is_err()); assert!(format!("{result:?}").contains("InvalidEraToReward")); // From here on, this is not CI tested, but it serves as a nice example nonetheless. if let Some(mut last_reward_received_at_era) = - get_last_reward_received_for(&validator_account, current_era_index, &api) + get_last_reward_received_for(&validator_account, current_era_index, &api).await { let grace_period = GracePeriod { enabled: false, eras: 0 }; let mut num_of_unclaimed_payouts: u32 = @@ -113,6 +114,7 @@ async fn main() { &validator_account, None, ) + .await .unwrap() { Some(exposure) => exposure, @@ -122,7 +124,7 @@ async fn main() { if exposure.total.to_be_bytes() > 0_u128.to_be_bytes() && is_grace_period_satisfied { let payout_extrinsic = - api.payout_stakers(payout_era_index, validator_account.clone()); + api.payout_stakers(payout_era_index, validator_account.clone()).await; payout_calls.push(payout_extrinsic.function); } i += 1; @@ -130,9 +132,10 @@ async fn main() { } num_of_claimed_payouts += payout_calls.len(); num_of_unclaimed_payouts -= tx_limit_in_current_batch; - let batch_xt = api.batch(payout_calls); + let batch_xt = api.batch(payout_calls).await; - let report = api.submit_and_watch_extrinsic_until(batch_xt, XtStatus::InBlock).unwrap(); + let report = + api.submit_and_watch_extrinsic_until(batch_xt, XtStatus::InBlock).await.unwrap(); results.push(format!("{report:?}")); } println!("{:?}", results); @@ -140,7 +143,7 @@ async fn main() { }; } -pub fn get_last_reward_received_for( +pub async fn get_last_reward_received_for( account: &AccountId32, current_era: EraIndex, api: &substrate_api_client::Api, @@ -148,14 +151,14 @@ pub fn get_last_reward_received_for( let ledger_storage_key = api.metadata().storage_map_key("Staking", "Ledger", account).unwrap(); let claimed_rewards: Vec = - match api.get_storage_by_key::(ledger_storage_key, None) { + match api.get_storage_by_key::(ledger_storage_key, None).await { Ok(Some(ledger)) => ledger.claimed_rewards, _ => Vec::new(), }; // Get the era index the last reward has been retrieved. let last_reward_received_at_era = if claimed_rewards.is_empty() { - let history_depth: u32 = api.get_constant("Staking", "HistoryDepth").unwrap(); + let history_depth: u32 = api.get_constant("Staking", "HistoryDepth").await.unwrap(); // Ensure we don't get below zero here. if current_era > history_depth { let last_known_era = current_era - history_depth; diff --git a/examples/examples/subscribe_events.rs b/examples/async/examples/subscribe_events.rs similarity index 89% rename from examples/examples/subscribe_events.rs rename to examples/async/examples/subscribe_events.rs index c068fd635..84520ec5c 100644 --- a/examples/examples/subscribe_events.rs +++ b/examples/async/examples/subscribe_events.rs @@ -36,15 +36,16 @@ async fn main() { env_logger::init(); // Initialize the api. - let client = JsonrpseeClient::with_default_url().unwrap(); - let api = Api::::new(client).unwrap(); + let client = JsonrpseeClient::with_default_url().await.unwrap(); + let api = Api::::new(client).await.unwrap(); println!("Subscribe to events"); - let mut subscription = api.subscribe_events().unwrap(); + let mut subscription = api.subscribe_events().await.unwrap(); // Wait for event callbacks from the node, which are received via subscription. for _ in 0..5 { - let event_records = subscription.next_events::().unwrap().unwrap(); + let event_records = + subscription.next_events::().await.unwrap().unwrap(); for event_record in &event_records { println!("decoded: {:?} {:?}", event_record.phase, event_record.event); match &event_record.event { @@ -81,5 +82,5 @@ async fn main() { // After we finished whatever we wanted, unusubscribe from the subscription, // to ensure, that the node does not keep sending us events. - subscription.unsubscribe().unwrap(); + subscription.unsubscribe().await.unwrap(); } diff --git a/examples/examples/sudo.rs b/examples/async/examples/sudo.rs similarity index 91% rename from examples/examples/sudo.rs rename to examples/async/examples/sudo.rs index 43eb1a13f..836482043 100644 --- a/examples/examples/sudo.rs +++ b/examples/async/examples/sudo.rs @@ -50,15 +50,15 @@ async fn main() { // Initialize api and set the signer (sender) that is used to sign the extrinsics. let sudoer = AccountKeyring::Alice.pair(); - let client = JsonrpseeClient::with_default_url().unwrap(); - let mut api = Api::::new(client).unwrap(); + let client = JsonrpseeClient::with_default_url().await.unwrap(); + let mut api = Api::::new(client).await.unwrap(); api.set_signer(sudoer.into()); // Set the recipient of newly issued funds. let recipient = AccountKeyring::Bob.to_account_id(); // Get the current balance of the recipient. - let recipient_balance = api.get_account_data(&recipient).unwrap().unwrap().free; + let recipient_balance = api.get_account_data(&recipient).await.unwrap().unwrap().free; println!("[+] Recipients's Free Balance is now {}\n", recipient_balance); // Compose a call that should only be executable via Sudo. @@ -78,12 +78,13 @@ async fn main() { // Send and watch extrinsic until in block. let block_hash = api .submit_and_watch_extrinsic_until(xt, XtStatus::InBlock) + .await .unwrap() .block_hash .unwrap(); println!("[+] Extrinsic got included. Block Hash: {:?}", block_hash); // Ensure the extrinsic has been executed. - let recipient_new_balance = api.get_account_data(&recipient).unwrap().unwrap().free; + let recipient_new_balance = api.get_account_data(&recipient).await.unwrap().unwrap().free; assert_eq!(recipient_new_balance, new_balance); } diff --git a/examples/sync/Cargo.toml b/examples/sync/Cargo.toml new file mode 100644 index 000000000..8cd550c8a --- /dev/null +++ b/examples/sync/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "ac-examples-sync" +version = "0.4.2" +license = "Apache-2.0" +edition = "2021" + +[dev-dependencies] +env_logger = "0.10.0" +log = "0.4.14" + +# Substrate dependencies +sp-core = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } +sp-keyring = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } +sp-weights = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } + +# local deps +substrate-api-client = { path = "../..", default-features = false, features = ["tungstenite-client", "ws-client"] } diff --git a/examples/sync/examples/kitchensink_runtime.compact.compressed.wasm b/examples/sync/examples/kitchensink_runtime.compact.compressed.wasm new file mode 100644 index 000000000..514449ec0 Binary files /dev/null and b/examples/sync/examples/kitchensink_runtime.compact.compressed.wasm differ diff --git a/examples/examples/runtime_update_sync.rs b/examples/sync/examples/runtime_update_sync.rs similarity index 85% rename from examples/examples/runtime_update_sync.rs rename to examples/sync/examples/runtime_update_sync.rs index c02e13cdc..fe9c1090f 100644 --- a/examples/examples/runtime_update_sync.rs +++ b/examples/sync/examples/runtime_update_sync.rs @@ -12,6 +12,7 @@ */ //! Example that shows how to detect a runtime update and afterwards update the metadata. + use core::{ sync::atomic::{AtomicBool, Ordering}, time::Duration, @@ -23,52 +24,25 @@ use substrate_api_client::{ ac_compose_macros::{compose_call, compose_extrinsic}, ac_primitives::{AssetRuntimeConfig, Config}, api_client::UpdateRuntime, - rpc::JsonrpseeClient, + rpc::TungsteniteRpcClient, rpc_api::RuntimeUpdateDetector, Api, SubmitAndWatch, SubscribeEvents, XtStatus, }; type Hash = ::Hash; -#[cfg(not(feature = "sync-examples"))] -#[tokio::main] -async fn main() { - println!("This example is for sync use-cases. Please see runtime_update_async.rs for the async implementation.") -} - -pub fn send_code_update_extrinsic( - api: &substrate_api_client::Api, -) { - let new_wasm: &[u8] = include_bytes!("kitchensink_runtime.compact.compressed.wasm"); - - // Create a sudo `set_code` call. - let call = compose_call!(api.metadata(), "System", "set_code", new_wasm.to_vec()); - let weight: Weight = 0.into(); - let xt = compose_extrinsic!(&api, "Sudo", "sudo_unchecked_weight", call, weight); - - println!("Sending extrinsic to trigger runtime update"); - let block_hash = api - .submit_and_watch_extrinsic_until(xt, XtStatus::InBlock) - .unwrap() - .block_hash - .unwrap(); - println!("[+] Extrinsic got included. Block Hash: {:?}", block_hash); -} - -#[cfg(feature = "sync-examples")] -#[tokio::main] -async fn main() { +fn main() { env_logger::init(); // Initialize the api. - let client = JsonrpseeClient::with_default_url().unwrap(); + let client = TungsteniteRpcClient::with_default_url(1); let mut api = Api::::new(client).unwrap(); let sudoer = AccountKeyring::Alice.pair(); api.set_signer(sudoer.into()); let subscription = api.subscribe_events().unwrap(); let cancellation = Arc::new(AtomicBool::new(false)); - let mut update_detector: RuntimeUpdateDetector = + let mut update_detector: RuntimeUpdateDetector = RuntimeUpdateDetector::new_with_cancellation(subscription, cancellation.clone()); println!("Current spec_version: {}", api.spec_version()); @@ -97,3 +71,22 @@ async fn main() { println!("New spec_version: {}", api.spec_version()); assert!(api.spec_version() == 1268); } + +pub fn send_code_update_extrinsic( + api: &substrate_api_client::Api, +) { + let new_wasm: &[u8] = include_bytes!("kitchensink_runtime.compact.compressed.wasm"); + + // Create a sudo `set_code` call. + let call = compose_call!(api.metadata(), "System", "set_code", new_wasm.to_vec()); + let weight: Weight = 0.into(); + let xt = compose_extrinsic!(&api, "Sudo", "sudo_unchecked_weight", call, weight); + + println!("Sending extrinsic to trigger runtime update"); + let block_hash = api + .submit_and_watch_extrinsic_until(xt, XtStatus::InBlock) + .unwrap() + .block_hash + .unwrap(); + println!("[+] Extrinsic got included. Block Hash: {:?}", block_hash); +} diff --git a/examples/examples/transfer_with_tungstenite_client.rs b/examples/sync/examples/transfer_with_tungstenite_client.rs similarity index 100% rename from examples/examples/transfer_with_tungstenite_client.rs rename to examples/sync/examples/transfer_with_tungstenite_client.rs diff --git a/examples/examples/transfer_with_ws_client.rs b/examples/sync/examples/transfer_with_ws_client.rs similarity index 100% rename from examples/examples/transfer_with_ws_client.rs rename to examples/sync/examples/transfer_with_ws_client.rs diff --git a/src/api/api_client.rs b/src/api/api_client.rs index 98a94d494..84606ab23 100644 --- a/src/api/api_client.rs +++ b/src/api/api_client.rs @@ -30,58 +30,6 @@ use sp_version::RuntimeVersion; /// Api to talk with substrate-nodes /// /// It is generic over the `Request` trait, so you can use any rpc-backend you like. -/// -/// # Custom Client Example -/// -/// ```no_run -/// use substrate_api_client::{ -/// Api, rpc::Request, rpc::Error as RpcClientError, XtStatus, rpc::Result as RpcResult, ac_primitives::AssetRuntimeConfig -/// }; -/// use serde::de::DeserializeOwned; -/// use ac_primitives::RpcParams; -/// use serde_json::{Value, json}; -/// ; -/// /// -/// struct MyClient { -/// // pick any request crate, such as ureq::Agent -/// _inner: (), -/// } -/// -/// impl MyClient { -/// pub fn new() -> Self { -/// Self { -/// _inner: (), -/// } -/// } -/// -/// pub fn send_json( -/// &self, -/// _path: String, -/// _json: Value, -/// ) -> Result { -/// // Send json to node via web socket connection. -/// todo!() -/// } -/// } -/// -/// impl Request for MyClient { -/// fn request(&self, method: &str, params: RpcParams) -> Result { -/// let jsonreq = json!({ -/// "method": method, -/// "params": params.to_json_value()?, -/// "jsonrpc": "2.0", -/// "id": "1", -/// }); -/// let json_value = self.send_json::("".into(), jsonreq)?; -/// let value = serde_json::from_value(json_value)?; -/// Ok(value) -/// } -/// } -/// -/// let client = MyClient::new(); -/// let _api = Api::::new(client); -/// -/// ``` #[derive(Clone)] pub struct Api { signer: Option, @@ -312,6 +260,7 @@ mod tests { PlainTip, }; use frame_metadata::{v14::ExtrinsicMetadata, RuntimeMetadata}; + use futures::executor; use scale_info::form::PortableForm; use sp_core::H256; use std::{collections::HashMap, fs}; @@ -399,7 +348,7 @@ mod tests { assert_ne!(api.runtime_version, runtime_version); // Update runtime. - api.update_runtime().unwrap(); + executor::block_on(api.update_runtime()).unwrap(); // Ensure metadata and runtime version have been updated. assert_eq!(api.metadata.extrinsic(), metadata.extrinsic()); diff --git a/src/api/rpc_api/events.rs b/src/api/rpc_api/events.rs index fe53bb5f0..1c75d4c63 100644 --- a/src/api/rpc_api/events.rs +++ b/src/api/rpc_api/events.rs @@ -236,6 +236,7 @@ mod tests { use ac_primitives::DefaultRuntimeConfig; use codec::{Decode, Encode}; use frame_metadata::RuntimeMetadataPrefixed; + use futures::executor::block_on; use kitchensink_runtime::{BalancesCall, RuntimeCall, UncheckedExtrinsic}; use scale_info::TypeInfo; use sp_core::{crypto::Ss58Codec, sr25519, Bytes, H256}; @@ -346,7 +347,7 @@ mod tests { let api = create_mock_api(metadata, data); - let fetched_events = api.fetch_events_from_block(H256::random()).unwrap(); + let fetched_events = block_on(api.fetch_events_from_block(H256::random())).unwrap(); assert_eq!(fetched_events.event_bytes(), block_events.event_bytes()); } @@ -396,9 +397,15 @@ mod tests { let api = create_mock_api(metadata, data); let block_hash = H256::default(); - let index1 = api.retrieve_extrinsic_index_from_block(block_hash, xt_hash1).unwrap(); - let index2 = api.retrieve_extrinsic_index_from_block(block_hash, xt_hash2).unwrap(); - let index3 = api.retrieve_extrinsic_index_from_block(block_hash, xt_hash3).unwrap(); + let (index1, index2, index3) = block_on(async { + futures::future::try_join3( + api.retrieve_extrinsic_index_from_block(block_hash, xt_hash1), + api.retrieve_extrinsic_index_from_block(block_hash, xt_hash2), + api.retrieve_extrinsic_index_from_block(block_hash, xt_hash3), + ) + .await + .unwrap() + }); assert_eq!(index1, 0); assert_eq!(index2, 1); diff --git a/src/rpc/jsonrpsee_client/mod.rs b/src/rpc/jsonrpsee_client/mod.rs index 987a5dfb3..57bac91fb 100644 --- a/src/rpc/jsonrpsee_client/mod.rs +++ b/src/rpc/jsonrpsee_client/mod.rs @@ -12,7 +12,6 @@ */ use crate::rpc::{Error, Request, Result, RpcParams, Subscribe}; -use futures::executor::block_on; use jsonrpsee::{ client_transport::ws::{Uri, WsTransportClientBuilder}, core::{ @@ -34,15 +33,11 @@ pub struct JsonrpseeClient { } impl JsonrpseeClient { - pub fn new(url: &str) -> Result { - block_on(Self::async_new(url)) + pub async fn with_default_url() -> Result { + Self::new("ws://127.0.0.1:9944").await } - pub fn with_default_url() -> Result { - Self::new("ws://127.0.0.1:9944") - } - - pub async fn async_new(url: &str) -> Result { + pub async fn new(url: &str) -> Result { let uri: Uri = url.parse().map_err(|e| Error::Client(Box::new(e)))?; let (tx, rx) = WsTransportClientBuilder::default() .build(uri) @@ -64,13 +59,6 @@ impl Request for JsonrpseeClient { .map_err(|e| Error::Client(Box::new(e))) } } -#[maybe_async::sync_impl] -impl Request for JsonrpseeClient { - fn request(&self, method: &str, params: RpcParams) -> Result { - block_on(self.inner.request(method, RpcParamsWrapper(params))) - .map_err(|e| Error::Client(Box::new(e))) - } -} #[maybe_async::async_impl(?Send)] impl Subscribe for JsonrpseeClient { @@ -90,22 +78,6 @@ impl Subscribe for JsonrpseeClient { } } -#[maybe_async::sync_impl(?Send)] -impl Subscribe for JsonrpseeClient { - type Subscription = SubscriptionWrapper where Notification: DeserializeOwned; - - fn subscribe( - &self, - sub: &str, - params: RpcParams, - unsub: &str, - ) -> Result> { - block_on(self.inner.subscribe(sub, RpcParamsWrapper(params), unsub)) - .map(|sub| sub.into()) - .map_err(|e| Error::Client(Box::new(e))) - } -} - struct RpcParamsWrapper(RpcParams); impl ToRpcParams for RpcParamsWrapper { diff --git a/src/rpc/jsonrpsee_client/subscription.rs b/src/rpc/jsonrpsee_client/subscription.rs index d42591b13..e67816a00 100644 --- a/src/rpc/jsonrpsee_client/subscription.rs +++ b/src/rpc/jsonrpsee_client/subscription.rs @@ -12,8 +12,6 @@ */ use crate::rpc::{Error, HandleSubscription, Result}; -#[cfg(feature = "sync-api")] -use futures::executor::block_on; use jsonrpsee::core::client::Subscription; use serde::de::DeserializeOwned; @@ -22,19 +20,6 @@ pub struct SubscriptionWrapper { inner: Subscription, } -#[maybe_async::sync_impl(?Send)] -impl HandleSubscription - for SubscriptionWrapper -{ - fn next(&mut self) -> Option> { - block_on(self.inner.next()).map(|result| result.map_err(|e| Error::Client(Box::new(e)))) - } - - fn unsubscribe(self) -> Result<()> { - block_on(self.inner.unsubscribe()).map_err(|e| Error::Client(Box::new(e))) - } -} - #[maybe_async::async_impl(?Send)] impl HandleSubscription for SubscriptionWrapper diff --git a/src/rpc/mod.rs b/src/rpc/mod.rs index dbe8e1883..24479ac80 100644 --- a/src/rpc/mod.rs +++ b/src/rpc/mod.rs @@ -31,9 +31,9 @@ pub use tungstenite_client::TungsteniteRpcClient; #[cfg(feature = "tungstenite-client")] pub mod tungstenite_client; -#[cfg(feature = "jsonrpsee-client")] +#[cfg(all(feature = "jsonrpsee-client", not(feature = "sync-api")))] pub use jsonrpsee_client::JsonrpseeClient; -#[cfg(feature = "jsonrpsee-client")] +#[cfg(all(feature = "jsonrpsee-client", not(feature = "sync-api")))] pub mod jsonrpsee_client; pub mod error; diff --git a/testing/async/Cargo.toml b/testing/async/Cargo.toml new file mode 100644 index 000000000..2688f742d --- /dev/null +++ b/testing/async/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "ac-testing-async" +version = "0.3.2" +authors = ["Supercomputing Systems AG "] +license = "Apache-2.0" +edition = "2021" + +[dev-dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", features = ['derive'] } +tokio = { version = "1.24", features = ["rt-multi-thread", "macros", "time"] } + +# Substrate dependencies +frame-support = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } +kitchensink-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } +sp-keyring = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } +sp-staking = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } +pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } +pallet-staking = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } + +# local deps +substrate-api-client = { path = "../..", features = ["staking-xt", "contracts-xt"] } diff --git a/testing/async/examples/author_tests.rs b/testing/async/examples/author_tests.rs new file mode 100644 index 000000000..fa05d6acf --- /dev/null +++ b/testing/async/examples/author_tests.rs @@ -0,0 +1,200 @@ +/* + Copyright 2019 Supercomputing Systems AG + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +//! Tests for the author rpc interface functions. + +use kitchensink_runtime::{AccountId, BalancesCall, RuntimeCall}; +use sp_core::{Encode, H256}; +use sp_keyring::AccountKeyring; +use substrate_api_client::{ + ac_node_api::EventDetails, + ac_primitives::{ + AssetRuntimeConfig, Config, ExtrinsicSigner as GenericExtrinsicSigner, SignExtrinsic, + }, + rpc::{HandleSubscription, JsonrpseeClient}, + Api, SubmitAndWatch, SubmitExtrinsic, TransactionStatus, XtStatus, +}; + +type ExtrinsicSigner = GenericExtrinsicSigner; +type ExtrinsicAddressOf = >::ExtrinsicAddress; +type Hash = ::Hash; +type MyApi = Api; +type Index = ::Index; + +#[tokio::main] +async fn main() { + // Setup + let client = JsonrpseeClient::with_default_url().await.unwrap(); + let alice_pair = AccountKeyring::Alice.pair(); + let mut api = MyApi::new(client).await.unwrap(); + api.set_signer(alice_pair.into()); + let bob: ExtrinsicAddressOf = AccountKeyring::Bob.to_account_id().into(); + let signer_nonce = api.get_nonce().await.unwrap(); + let transfer_call = RuntimeCall::Balances(BalancesCall::transfer_allow_death { + dest: bob.clone(), + value: 1000, + }); + + // Submit_extrinsic. + let xt0 = api.compose_extrinsic_offline(transfer_call.clone(), signer_nonce); + let _tx_hash = api.submit_extrinsic(xt0).await.unwrap(); + + tokio::join!( + test_submit_and_watch(&api, transfer_call.clone(), signer_nonce + 1), + test_submit_and_watch_until_ready(&api, transfer_call.clone(), signer_nonce + 2), + test_submit_and_watch_until_broadcast(&api, transfer_call.clone(), signer_nonce + 3), + test_submit_and_watch_until_in_block(&api, transfer_call.clone(), signer_nonce + 4), + test_submit_and_watch_until_finalized(&api, transfer_call.clone(), signer_nonce + 5), + // Test some _watch_untils_without_events. We don't need to test all, because it is tested implicitly by `submit_and_watch_extrinsic_until` + // as internal call. + test_submit_and_watch_extrinsic_until_ready_without_events( + &api, + transfer_call.clone(), + signer_nonce + 6 + ), + test_submit_and_watch_extrinsic_until_in_block_without_events( + &api, + transfer_call.clone(), + signer_nonce + 7 + ) + ); +} + +async fn test_submit_and_watch(api: &MyApi, transfer_call: RuntimeCall, nonce: Index) { + let xt = api.compose_extrinsic_offline(transfer_call, nonce); + + let mut tx_subscription = api.submit_and_watch_extrinsic(xt).await.unwrap(); + let tx_status = tx_subscription.next().await.unwrap().unwrap(); + assert!(matches!(tx_status, TransactionStatus::Ready)); + let tx_status = tx_subscription.next().await.unwrap().unwrap(); + assert!(matches!(tx_status, TransactionStatus::InBlock(_))); + let tx_status = tx_subscription.next().await.unwrap().unwrap(); + assert!(matches!(tx_status, TransactionStatus::Finalized(_))); + tx_subscription.unsubscribe().await.unwrap(); + println!("Success: submit_and_watch_extrinsic: {:?}", tx_status); +} + +async fn test_submit_and_watch_until_ready(api: &MyApi, transfer_call: RuntimeCall, nonce: Index) { + std::thread::sleep(std::time::Duration::from_secs(1)); + let xt = api.compose_extrinsic_offline(transfer_call, nonce); + let extrinsic_hash: H256 = sp_core::blake2_256(&xt.encode()).into(); + let report = api.submit_and_watch_extrinsic_until(xt, XtStatus::Ready).await.unwrap(); + assert_eq!(extrinsic_hash, report.extrinsic_hash); + assert!(report.block_hash.is_none()); + assert!(matches!(report.status, TransactionStatus::Ready)); + assert!(report.events.is_none()); + println!("Success: submit_and_watch_extrinsic_until {:?}", report.status); +} + +async fn test_submit_and_watch_until_broadcast( + api: &MyApi, + transfer_call: RuntimeCall, + nonce: Index, +) { + std::thread::sleep(std::time::Duration::from_secs(1)); + let xt = api.compose_extrinsic_offline(transfer_call, nonce); + let report = api.submit_and_watch_extrinsic_until(xt, XtStatus::Broadcast).await.unwrap(); + // The xt is not broadcast - we only have one node running. Therefore, InBlock is returned. + assert!(report.block_hash.is_some()); + assert!(matches!(report.status, TransactionStatus::InBlock(_))); + // But we still don't fetch events, since we originally only waited for Broadcast. + assert!(report.events.is_none()); + println!("Success: submit_and_watch_extrinsic_until {:?}", report.status); +} + +async fn test_submit_and_watch_until_in_block( + api: &MyApi, + transfer_call: RuntimeCall, + nonce: Index, +) { + std::thread::sleep(std::time::Duration::from_secs(1)); + let xt = api.compose_extrinsic_offline(transfer_call, nonce); + let report = api.submit_and_watch_extrinsic_until(xt, XtStatus::InBlock).await.unwrap(); + assert!(report.block_hash.is_some()); + assert!(matches!(report.status, TransactionStatus::InBlock(_))); + assert_associated_events_match_expected(report.events.unwrap()); + println!("Success: submit_and_watch_extrinsic_until {:?}", report.status); +} + +async fn test_submit_and_watch_until_finalized( + api: &MyApi, + transfer_call: RuntimeCall, + nonce: Index, +) { + std::thread::sleep(std::time::Duration::from_secs(1)); + let xt = api.compose_extrinsic_offline(transfer_call, nonce); + let report = api.submit_and_watch_extrinsic_until(xt, XtStatus::Finalized).await.unwrap(); + assert!(report.block_hash.is_some()); + assert!(matches!(report.status, TransactionStatus::Finalized(_))); + assert_associated_events_match_expected(report.events.unwrap()); + println!("Success: submit_and_watch_extrinsic_until {:?}", report.status); +} + +async fn test_submit_and_watch_extrinsic_until_ready_without_events( + api: &MyApi, + transfer_call: RuntimeCall, + nonce: Index, +) { + std::thread::sleep(std::time::Duration::from_secs(1)); + let xt = api.compose_extrinsic_offline(transfer_call, nonce); + let report = api + .submit_and_watch_extrinsic_until_without_events(xt, XtStatus::Ready) + .await + .unwrap(); + assert!(report.block_hash.is_none()); + assert!(report.events.is_none()); + println!("Success: submit_and_watch_extrinsic_until_without_events {:?}", report.status); +} + +async fn test_submit_and_watch_extrinsic_until_in_block_without_events( + api: &MyApi, + transfer_call: RuntimeCall, + nonce: Index, +) { + // Wait a little, otherwise we may run into future + std::thread::sleep(std::time::Duration::from_secs(1)); + let xt = api.compose_extrinsic_offline(transfer_call, nonce); + let report = api + .submit_and_watch_extrinsic_until_without_events(xt, XtStatus::InBlock) + .await + .unwrap(); + println!("Extrinsic got successfully included in Block!"); + assert!(report.block_hash.is_some()); + assert!(report.events.is_none()); +} + +fn assert_associated_events_match_expected(events: Vec>) { + // First event + assert_eq!(events[0].pallet_name(), "Balances"); + assert_eq!(events[0].variant_name(), "Withdraw"); + + assert_eq!(events[1].pallet_name(), "Balances"); + assert_eq!(events[1].variant_name(), "Transfer"); + + assert_eq!(events[2].pallet_name(), "Balances"); + assert_eq!(events[2].variant_name(), "Deposit"); + + assert_eq!(events[3].pallet_name(), "Treasury"); + assert_eq!(events[3].variant_name(), "Deposit"); + + assert_eq!(events[4].pallet_name(), "Balances"); + assert_eq!(events[4].variant_name(), "Deposit"); + + assert_eq!(events[5].pallet_name(), "TransactionPayment"); + assert_eq!(events[5].variant_name(), "TransactionFeePaid"); + + assert_eq!(events[6].pallet_name(), "System"); + assert_eq!(events[6].variant_name(), "ExtrinsicSuccess"); +} diff --git a/testing/examples/chain_tests.rs b/testing/async/examples/chain_tests.rs similarity index 50% rename from testing/examples/chain_tests.rs rename to testing/async/examples/chain_tests.rs index 4e12dba9c..49f47beb7 100644 --- a/testing/examples/chain_tests.rs +++ b/testing/async/examples/chain_tests.rs @@ -18,7 +18,7 @@ use sp_keyring::AccountKeyring; use substrate_api_client::{ - ac_primitives::{DefaultRuntimeConfig, ExtrinsicSigner}, + ac_primitives::DefaultRuntimeConfig, rpc::{HandleSubscription, JsonrpseeClient}, Api, GetChainInfo, SubscribeChain, }; @@ -26,27 +26,27 @@ use substrate_api_client::{ #[tokio::main] async fn main() { // Setup - let client = JsonrpseeClient::with_default_url().unwrap(); - let mut api = Api::::new(client).unwrap(); + let client = JsonrpseeClient::with_default_url().await.unwrap(); + let mut api = Api::::new(client).await.unwrap(); let signer = AccountKeyring::Alice.pair(); - api.set_signer(ExtrinsicSigner::::new(signer)); + api.set_signer(signer.into()); // GetChainInfo - let finalized_header_hash = api.get_finalized_head().unwrap().unwrap(); - let _latest_header = api.get_header(None).unwrap().unwrap(); - let _some_header = api.get_header(Some(finalized_header_hash)).unwrap().unwrap(); - let _block_hash = api.get_block_hash(None).unwrap().unwrap(); - let block_hash = api.get_block_hash(Some(1)).unwrap().unwrap(); - let _block = api.get_block(None).unwrap().unwrap(); - let _block = api.get_block(Some(block_hash)).unwrap().unwrap(); - let _block = api.get_block_by_num(None).unwrap().unwrap(); - let _block = api.get_block_by_num(Some(2)).unwrap().unwrap(); - let _signed_block = api.get_signed_block(None).unwrap().unwrap(); - let _signed_block = api.get_signed_block(Some(block_hash)).unwrap().unwrap(); - let _signed_block = api.get_signed_block_by_num(None).unwrap().unwrap(); - let _signed_block = api.get_signed_block_by_num(Some(1)).unwrap().unwrap(); + let finalized_header_hash = api.get_finalized_head().await.unwrap().unwrap(); + let _latest_header = api.get_header(None).await.unwrap().unwrap(); + let _some_header = api.get_header(Some(finalized_header_hash)).await.unwrap().unwrap(); + let _block_hash = api.get_block_hash(None).await.unwrap().unwrap(); + let block_hash = api.get_block_hash(Some(1)).await.unwrap().unwrap(); + let _block = api.get_block(None).await.unwrap().unwrap(); + let _block = api.get_block(Some(block_hash)).await.unwrap().unwrap(); + let _block = api.get_block_by_num(None).await.unwrap().unwrap(); + let _block = api.get_block_by_num(Some(2)).await.unwrap().unwrap(); + let _signed_block = api.get_signed_block(None).await.unwrap().unwrap(); + let _signed_block = api.get_signed_block(Some(block_hash)).await.unwrap().unwrap(); + let _signed_block = api.get_signed_block_by_num(None).await.unwrap().unwrap(); + let _signed_block = api.get_signed_block_by_num(Some(1)).await.unwrap().unwrap(); // Subscription - let mut finalized_head_subscription = api.subscribe_finalized_heads().unwrap(); - let _some_head = finalized_head_subscription.next().unwrap().unwrap(); + let mut finalized_head_subscription = api.subscribe_finalized_heads().await.unwrap(); + let _some_head = finalized_head_subscription.next().await.unwrap().unwrap(); } diff --git a/testing/examples/dispatch_errors_tests.rs b/testing/async/examples/dispatch_errors_tests.rs similarity index 80% rename from testing/examples/dispatch_errors_tests.rs rename to testing/async/examples/dispatch_errors_tests.rs index 6700441aa..8969c7653 100644 --- a/testing/examples/dispatch_errors_tests.rs +++ b/testing/async/examples/dispatch_errors_tests.rs @@ -25,37 +25,37 @@ use substrate_api_client::{ #[tokio::main] async fn main() { // Setup - let client = JsonrpseeClient::with_default_url().unwrap(); + let client = JsonrpseeClient::with_default_url().await.unwrap(); let alice_signer = AccountKeyring::Alice.pair(); let bob_signer = AccountKeyring::Bob.pair(); - let mut api = Api::::new(client).unwrap(); + let mut api = Api::::new(client).await.unwrap(); let alice = AccountKeyring::Alice.to_account_id(); - let balance_of_alice = api.get_account_data(&alice).unwrap().unwrap().free; + let balance_of_alice = api.get_account_data(&alice).await.unwrap().unwrap().free; println!("[+] Alice's Free Balance is is {}\n", balance_of_alice); let bob = AccountKeyring::Bob.to_account_id(); - let balance_of_bob = api.get_account_data(&bob).unwrap().unwrap_or_default().free; + let balance_of_bob = api.get_account_data(&bob).await.unwrap().unwrap_or_default().free; println!("[+] Bob's Free Balance is {}\n", balance_of_bob); let one = AccountKeyring::One.to_account_id(); - let balance_of_one = api.get_account_data(&one).unwrap().unwrap_or_default().free; + let balance_of_one = api.get_account_data(&one).await.unwrap().unwrap_or_default().free; println!("[+] One's Free Balance is {}\n", balance_of_one); //BadOrigin api.set_signer(bob_signer.into()); //Can only be called by root - let xt = api.balance_force_set_balance(MultiAddress::Id(alice.clone()), 10); + let xt = api.balance_force_set_balance(MultiAddress::Id(alice.clone()), 10).await; - let result = api.submit_and_watch_extrinsic_until(xt, XtStatus::InBlock); + let result = api.submit_and_watch_extrinsic_until(xt, XtStatus::InBlock).await; assert!(result.is_err()); assert!(format!("{result:?}").contains("BadOrigin")); println!("[+] BadOrigin error: Bob can't force set balance"); //BelowMinimum api.set_signer(alice_signer.into()); - let xt = api.balance_transfer_allow_death(MultiAddress::Id(one.clone()), 999999); - let result = api.submit_and_watch_extrinsic_until(xt, XtStatus::InBlock); + let xt = api.balance_transfer_allow_death(MultiAddress::Id(one.clone()), 999999).await; + let result = api.submit_and_watch_extrinsic_until(xt, XtStatus::InBlock).await; assert!(result.is_err()); assert!(format!("{result:?}").contains("(BelowMinimum")); println!("[+] BelowMinimum error: balance (999999) is below the existential deposit"); diff --git a/testing/examples/dump_metadata.rs b/testing/async/examples/dump_metadata.rs similarity index 90% rename from testing/examples/dump_metadata.rs rename to testing/async/examples/dump_metadata.rs index ec8b61ff4..be488a882 100644 --- a/testing/examples/dump_metadata.rs +++ b/testing/async/examples/dump_metadata.rs @@ -10,8 +10,8 @@ use substrate_api_client::{ #[tokio::main] async fn main() { - let client = JsonrpseeClient::new("wss://kusama-rpc.polkadot.io:443").unwrap(); - let metadata_bytes: Bytes = client.request("state_getMetadata", rpc_params![]).unwrap(); + let client = JsonrpseeClient::new("wss://kusama-rpc.polkadot.io:443").await.unwrap(); + let metadata_bytes: Bytes = client.request("state_getMetadata", rpc_params![]).await.unwrap(); let mut file = File::create("new_ksm_metadata.bin").unwrap(); file.write_all(&metadata_bytes.0).unwrap(); } diff --git a/testing/examples/events_tests.rs b/testing/async/examples/events_tests.rs similarity index 87% rename from testing/examples/events_tests.rs rename to testing/async/examples/events_tests.rs index 9debcc6c5..4a739cc03 100644 --- a/testing/examples/events_tests.rs +++ b/testing/async/examples/events_tests.rs @@ -43,38 +43,41 @@ impl StaticEvent for ExtrinsicSuccess { #[tokio::main] async fn main() { // Setup - let client = JsonrpseeClient::with_default_url().unwrap(); + let client = JsonrpseeClient::with_default_url().await.unwrap(); let alice_pair = AccountKeyring::Alice.pair(); - let mut api = Api::::new(client).unwrap(); + let mut api = Api::::new(client).await.unwrap(); api.set_signer(alice_pair.into()); let bob = AccountKeyring::Bob.to_account_id(); // Test `fetch_events_from_block`: There should always be at least the // timestamp set event. - let block_hash = api.get_block_hash(None).unwrap().unwrap(); - let events = api.fetch_events_from_block(block_hash).unwrap(); + let block_hash = api.get_block_hash(None).await.unwrap().unwrap(); + let events = api.fetch_events_from_block(block_hash).await.unwrap(); assert!(!events.is_empty()); println!("{events:?}"); // Submit a test-extrinsic to test `fetch_events_for_extrinsic`. - let xt = api.balance_transfer_allow_death(bob.into(), 1000); + let xt = api.balance_transfer_allow_death(bob.into(), 1000).await; let report = api .submit_and_watch_extrinsic_until_without_events(xt, XtStatus::InBlock) + .await .unwrap(); let extrinsic_events = api .fetch_events_for_extrinsic(report.extrinsic_hash, report.block_hash.unwrap()) + .await .unwrap(); assert_associated_events_match_expected(extrinsic_events); // Subscribe to system events. - let mut event_subscription = api.subscribe_events().unwrap(); + let mut event_subscription = api.subscribe_events().await.unwrap(); // Wait for event callbacks from the node, which are received via subscription. for _ in 0..5 { let event_records = event_subscription .next_events::::Hash>() + .await .unwrap() .unwrap(); for event_record in &event_records { @@ -88,7 +91,7 @@ async fn main() { // Wait for event callbacks from the node, which are received via subscription, in case no RuntimeEvents are accessible. for _ in 0..5 { - let events = event_subscription.next_events_from_metadata().unwrap().unwrap(); + let events = event_subscription.next_events_from_metadata().await.unwrap().unwrap(); for event in events.iter() { let event = event.unwrap(); println!("got event: {:?} {:?}", event.pallet_name(), event.variant_name()); diff --git a/testing/examples/frame_system_tests.rs b/testing/async/examples/frame_system_tests.rs similarity index 72% rename from testing/examples/frame_system_tests.rs rename to testing/async/examples/frame_system_tests.rs index 3870b885f..2ecd187ba 100644 --- a/testing/examples/frame_system_tests.rs +++ b/testing/async/examples/frame_system_tests.rs @@ -37,50 +37,50 @@ impl StaticEvent for ExtrinsicSuccess { #[tokio::main] async fn main() { // Setup - let client = JsonrpseeClient::with_default_url().unwrap(); + let client = JsonrpseeClient::with_default_url().await.unwrap(); let alice_pair = AccountKeyring::Alice.pair(); - let mut api = Api::::new(client).unwrap(); + let mut api = Api::::new(client).await.unwrap(); api.set_signer(alice_pair.into()); let alice = AccountKeyring::Alice.to_account_id(); // GetAccountInformation - let _account_info = api.get_account_info(&alice).unwrap().unwrap(); - let _account_data = api.get_account_data(&alice).unwrap().unwrap(); + let _account_info = api.get_account_info(&alice).await.unwrap().unwrap(); + let _account_data = api.get_account_data(&alice).await.unwrap().unwrap(); // Empty account information let inexistent_account = AccountKeyring::Two.to_account_id(); - let maybe_account_info = api.get_account_info(&inexistent_account).unwrap(); + let maybe_account_info = api.get_account_info(&inexistent_account).await.unwrap(); assert!(maybe_account_info.is_none()); - let maybe_account_data = api.get_account_data(&inexistent_account).unwrap(); + let maybe_account_data = api.get_account_data(&inexistent_account).await.unwrap(); assert!(maybe_account_data.is_none()); // System Api - let next_index = api.get_system_account_next_index(alice).unwrap(); + let next_index = api.get_system_account_next_index(alice).await.unwrap(); // Alice has not yet sent any extrinsic, so next_index should be 0. assert_eq!(next_index, 0); - let system_name = api.get_system_name().unwrap(); + let system_name = api.get_system_name().await.unwrap(); println!("System name: {system_name}"); - let system_version = api.get_system_version().unwrap(); + let system_version = api.get_system_version().await.unwrap(); println!("System version: {system_version}"); - let system_chain = api.get_system_chain().unwrap(); + let system_chain = api.get_system_chain().await.unwrap(); println!("System chain: {system_chain}"); - let system_chain_type = api.get_system_chain_type().unwrap(); + let system_chain_type = api.get_system_chain_type().await.unwrap(); println!("System chain type: {system_chain_type:?}"); - let system_properties = api.get_system_properties().unwrap(); + let system_properties = api.get_system_properties().await.unwrap(); println!("System properties: {system_properties:?}"); - let system_health = api.get_system_health().unwrap(); + let system_health = api.get_system_health().await.unwrap(); println!("System health: {system_health}"); - let system_local_peer_id = api.get_system_local_peer_id().unwrap(); + let system_local_peer_id = api.get_system_local_peer_id().await.unwrap(); println!("System local peer id: {system_local_peer_id:?}"); - let system_local_listen_addresses = api.get_system_local_listen_addresses().unwrap(); + let system_local_listen_addresses = api.get_system_local_listen_addresses().await.unwrap(); println!("System local listen addresses: {system_local_listen_addresses:?}"); } diff --git a/testing/examples/pallet_balances_tests.rs b/testing/async/examples/pallet_balances_tests.rs similarity index 80% rename from testing/examples/pallet_balances_tests.rs rename to testing/async/examples/pallet_balances_tests.rs index 318f590a1..0eae6abca 100644 --- a/testing/examples/pallet_balances_tests.rs +++ b/testing/async/examples/pallet_balances_tests.rs @@ -22,8 +22,8 @@ use substrate_api_client::{ #[tokio::main] async fn main() { // Setup - let client = JsonrpseeClient::with_default_url().unwrap(); - let api = Api::::new(client).unwrap(); + let client = JsonrpseeClient::with_default_url().await.unwrap(); + let api = Api::::new(client).await.unwrap(); - let _ed = api.get_existential_deposit().unwrap(); + let _ed = api.get_existential_deposit().await.unwrap(); } diff --git a/testing/examples/pallet_transaction_payment_tests.rs b/testing/async/examples/pallet_transaction_payment_tests.rs similarity index 77% rename from testing/examples/pallet_transaction_payment_tests.rs rename to testing/async/examples/pallet_transaction_payment_tests.rs index d5793dd91..6a3c2c63f 100644 --- a/testing/examples/pallet_transaction_payment_tests.rs +++ b/testing/async/examples/pallet_transaction_payment_tests.rs @@ -25,21 +25,25 @@ use substrate_api_client::{ #[tokio::main] async fn main() { // Setup - let client = JsonrpseeClient::with_default_url().unwrap(); + let client = JsonrpseeClient::with_default_url().await.unwrap(); let alice_pair = AccountKeyring::Alice.pair(); - let mut api = Api::::new(client).unwrap(); + let mut api = Api::::new(client).await.unwrap(); api.set_signer(alice_pair.into()); let bob = AccountKeyring::Bob.to_account_id(); - let block_hash = api.get_block_hash(None).unwrap().unwrap(); - let encoded_xt = api.balance_transfer_allow_death(bob.into(), 1000000000000).encode(); + let block_hash = api.get_block_hash(None).await.unwrap().unwrap(); + let encoded_xt = api.balance_transfer_allow_death(bob.into(), 1000000000000).await.encode(); // Tests let _fee_details = api .get_fee_details(&encoded_xt.clone().into(), Some(block_hash)) + .await + .unwrap() + .unwrap(); + let _payment_info = api + .get_payment_info(&encoded_xt.into(), Some(block_hash)) + .await .unwrap() .unwrap(); - let _payment_info = - api.get_payment_info(&encoded_xt.into(), Some(block_hash)).unwrap().unwrap(); } diff --git a/testing/examples/state_tests.rs b/testing/async/examples/state_tests.rs similarity index 67% rename from testing/examples/state_tests.rs rename to testing/async/examples/state_tests.rs index 88f58034b..b1051de80 100644 --- a/testing/examples/state_tests.rs +++ b/testing/async/examples/state_tests.rs @@ -38,42 +38,53 @@ type ErasStakers = Exposure< #[tokio::main] async fn main() { // Setup - let client = JsonrpseeClient::with_default_url().unwrap(); - let api = Api::::new(client).unwrap(); + let client = JsonrpseeClient::with_default_url().await.unwrap(); + let api = Api::::new(client).await.unwrap(); let alice = AccountKeyring::Alice.to_account_id(); - let block_hash = api.get_block_hash(None).unwrap().unwrap(); + let block_hash = api.get_block_hash(None).await.unwrap().unwrap(); let alice_stash = sr25519::Public::from_ss58check("5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY") .unwrap(); // Tests let _total_issuance: Balance = - api.get_storage("Balances", "TotalIssuance", None).unwrap().unwrap(); - let _total_issuance: Balance = - api.get_storage("Balances", "TotalIssuance", Some(block_hash)).unwrap().unwrap(); + api.get_storage("Balances", "TotalIssuance", None).await.unwrap().unwrap(); + let _total_issuance: Balance = api + .get_storage("Balances", "TotalIssuance", Some(block_hash)) + .await + .unwrap() + .unwrap(); let _account_info: AccountData = - api.get_storage_map("System", "Account", &alice, None).unwrap().unwrap(); + api.get_storage_map("System", "Account", &alice, None).await.unwrap().unwrap(); let _era_stakers: ErasStakers = api .get_storage_double_map("Staking", "ErasStakers", EraIndex::default(), alice_stash, None) + .await .unwrap() .unwrap(); // Ensure the prefix matches the actual storage key: - let storage_key_prefix = api.get_storage_map_key_prefix("System", "Account").unwrap(); + let storage_key_prefix = api.get_storage_map_key_prefix("System", "Account").await.unwrap(); let storage_key = api.metadata().storage_map_key("System", "Account", &alice).unwrap(); let prefix_len = storage_key_prefix.0.len(); assert_eq!(storage_key_prefix.0, storage_key.0[..prefix_len]); let _account_data: AccountData = - api.get_storage_by_key(storage_key.clone(), None).unwrap().unwrap(); + api.get_storage_by_key(storage_key.clone(), None).await.unwrap().unwrap(); let account_data_opaque = - api.get_opaque_storage_by_key(storage_key.clone(), None).unwrap().unwrap(); + api.get_opaque_storage_by_key(storage_key.clone(), None).await.unwrap().unwrap(); let _account_data = AccountData::decode(&mut account_data_opaque.as_slice()).unwrap(); - let _value_proof = - api.get_storage_value_proof("Balances", "TotalIssuance", None).unwrap().unwrap(); - let _map_proof = api.get_storage_map_proof("System", "Account", &alice, None).unwrap().unwrap(); + let _value_proof = api + .get_storage_value_proof("Balances", "TotalIssuance", None) + .await + .unwrap() + .unwrap(); + let _map_proof = api + .get_storage_map_proof("System", "Account", &alice, None) + .await + .unwrap() + .unwrap(); let _double_map_proof = api .get_storage_double_map_proof( "Staking", @@ -82,33 +93,42 @@ async fn main() { alice_stash, None, ) + .await .unwrap() .unwrap(); - let _storage_proof = - api.get_storage_proof_by_keys(vec![storage_key.clone()], None).unwrap().unwrap(); - let _keys = api.get_keys(storage_key, None).unwrap().unwrap(); - let _constants: Balance = api.get_constant("Balances", "ExistentialDeposit").unwrap(); + let _storage_proof = api + .get_storage_proof_by_keys(vec![storage_key.clone()], None) + .await + .unwrap() + .unwrap(); + let _keys = api.get_keys(storage_key, None).await.unwrap().unwrap(); + let _constants: Balance = api.get_constant("Balances", "ExistentialDeposit").await.unwrap(); let max_keys = 2003; - let result = api.get_storage_keys_paged_limited( - Some(storage_key_prefix.clone()), - max_keys.clone(), - None, - None, - ); + let result = api + .get_storage_keys_paged_limited( + Some(storage_key_prefix.clone()), + max_keys.clone(), + None, + None, + ) + .await; assert!(result.is_err()); assert!(format!("{result:?}").contains("count exceeds maximum value")); let storage_keys = api .get_storage_keys_paged(Some(storage_key_prefix), max_keys, None, None) + .await .unwrap(); assert_eq!(storage_keys.len() as u32, 13); let max_keys = 20; - let storage_keys = - api.get_storage_keys_paged_limited(None, max_keys.clone(), None, None).unwrap(); + let storage_keys = api + .get_storage_keys_paged_limited(None, max_keys.clone(), None, None) + .await + .unwrap(); assert_eq!(storage_keys.len() as u32, max_keys); - let storage_keys = api.get_storage_keys_paged(None, max_keys, None, None).unwrap(); + let storage_keys = api.get_storage_keys_paged(None, max_keys, None, None).await.unwrap(); assert_eq!(storage_keys.len() as u32, max_keys); } diff --git a/testing/examples/author_tests.rs b/testing/examples/author_tests.rs deleted file mode 100644 index 4d25fddca..000000000 --- a/testing/examples/author_tests.rs +++ /dev/null @@ -1,157 +0,0 @@ -/* - Copyright 2019 Supercomputing Systems AG - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -//! Tests for the author rpc interface functions. - -use kitchensink_runtime::AccountId; -use sp_core::{Encode, H256}; -use sp_keyring::AccountKeyring; -use std::{thread, time::Duration}; -use substrate_api_client::{ - ac_node_api::EventDetails, - ac_primitives::{ - AssetRuntimeConfig, Config, ExtrinsicSigner as GenericExtrinsicSigner, SignExtrinsic, - }, - extrinsic::BalancesExtrinsics, - rpc::{HandleSubscription, JsonrpseeClient}, - Api, SubmitAndWatch, SubmitExtrinsic, TransactionStatus, XtStatus, -}; - -type ExtrinsicSigner = GenericExtrinsicSigner; -type ExtrinsicAddressOf = >::ExtrinsicAddress; -type Hash = ::Hash; - -#[tokio::main] -async fn main() { - // Setup - let client = JsonrpseeClient::with_default_url().unwrap(); - let alice_pair = AccountKeyring::Alice.pair(); - let mut api = Api::::new(client).unwrap(); - - api.set_signer(alice_pair.into()); - - let bob: ExtrinsicAddressOf = AccountKeyring::Bob.to_account_id().into(); - - // Submit extrinsic. - let xt0 = api.balance_transfer_allow_death(bob.clone(), 1000); - let _tx_hash = api.submit_extrinsic(xt0).unwrap(); - - // Submit and watch. - thread::sleep(Duration::from_secs(6)); // Wait a little to avoid transaction too low priority error. - let api1 = api.clone(); - let xt1 = api.balance_transfer_allow_death(bob.clone(), 1000); - let watch_handle = thread::spawn(move || { - let mut tx_subscription = api1.submit_and_watch_extrinsic(xt1).unwrap(); - let tx_status = tx_subscription.next().unwrap().unwrap(); - assert!(matches!(tx_status, TransactionStatus::Ready)); - let tx_status = tx_subscription.next().unwrap().unwrap(); - assert!(matches!(tx_status, TransactionStatus::InBlock(_))); - let tx_status = tx_subscription.next().unwrap().unwrap(); - assert!(matches!(tx_status, TransactionStatus::Finalized(_))); - tx_subscription.unsubscribe().unwrap(); - println!("Success: submit_and_watch_extrinsic"); - }); - - // Test different _watch_untils with events - thread::sleep(Duration::from_secs(6)); // Wait a little to avoid transaction too low priority error. - let xt2 = api.balance_transfer_allow_death(bob.clone(), 1000); - let extrinsic_hash: H256 = sp_core::blake2_256(&xt2.encode()).into(); - let report = api.submit_and_watch_extrinsic_until(xt2, XtStatus::Ready).unwrap(); - assert_eq!(extrinsic_hash, report.extrinsic_hash); - assert!(report.block_hash.is_none()); - assert!(matches!(report.status, TransactionStatus::Ready)); - assert!(report.events.is_none()); - println!("Success: submit_and_watch_extrinsic_until Ready"); - - thread::sleep(Duration::from_secs(6)); // Wait a little to avoid transaction too low priority error. - let xt3 = api.balance_transfer_allow_death(bob.clone(), 1000); - let report = api.submit_and_watch_extrinsic_until(xt3, XtStatus::Broadcast).unwrap(); - // The xt is not broadcast - we only have one node running. Therefore, InBlock is returned. - assert!(report.block_hash.is_some()); - assert!(matches!(report.status, TransactionStatus::InBlock(_))); - // But we still don't fetch events, since we originally only waited for Broadcast. - assert!(report.events.is_none()); - println!("Success: submit_and_watch_extrinsic_until Broadcast"); - - let api2 = api.clone(); - thread::sleep(Duration::from_secs(6)); // Wait a little to avoid transaction too low priority error. - let xt4 = api2.balance_transfer_allow_death(bob.clone(), 1000); - let until_in_block_handle = thread::spawn(move || { - let report = api2.submit_and_watch_extrinsic_until(xt4, XtStatus::InBlock).unwrap(); - assert!(report.block_hash.is_some()); - assert!(matches!(report.status, TransactionStatus::InBlock(_))); - assert_associated_events_match_expected(report.events.unwrap()); - println!("Success: submit_and_watch_extrinsic_until InBlock"); - }); - - let api3 = api.clone(); - thread::sleep(Duration::from_secs(6)); // Wait a little to avoid transaction too low priority error. - let xt5 = api.balance_transfer_allow_death(bob.clone(), 1000); - let until_finalized_handle = thread::spawn(move || { - let report = api3.submit_and_watch_extrinsic_until(xt5, XtStatus::Finalized).unwrap(); - assert!(report.block_hash.is_some()); - assert!(matches!(report.status, TransactionStatus::Finalized(_))); - assert_associated_events_match_expected(report.events.unwrap()); - println!("Success: submit_and_watch_extrinsic_until Finalized"); - }); - - // Test some _watch_untils_without_events. One is enough, because it is tested implicitly by `submit_and_watch_extrinsic_until` - // as internal call. - thread::sleep(Duration::from_secs(6)); // Wait a little to avoid transaction too low priority error. - let xt6 = api.balance_transfer_allow_death(bob.clone(), 1000); - let report = api - .submit_and_watch_extrinsic_until_without_events(xt6, XtStatus::Ready) - .unwrap(); - assert!(report.block_hash.is_none()); - assert!(report.events.is_none()); - println!("Success: submit_and_watch_extrinsic_until_without_events Ready!"); - - thread::sleep(Duration::from_secs(6)); // Wait a little to avoid transaction too low priority error. - let xt7 = api.balance_transfer_allow_death(bob, 1000); - let report = api - .submit_and_watch_extrinsic_until_without_events(xt7, XtStatus::InBlock) - .unwrap(); - println!("Extrinsic got successfully included in Block!"); - assert!(report.block_hash.is_some()); - assert!(report.events.is_none()); - - watch_handle.join().unwrap(); - until_in_block_handle.join().unwrap(); - until_finalized_handle.join().unwrap(); -} - -fn assert_associated_events_match_expected(events: Vec>) { - // First event - assert_eq!(events[0].pallet_name(), "Balances"); - assert_eq!(events[0].variant_name(), "Withdraw"); - - assert_eq!(events[1].pallet_name(), "Balances"); - assert_eq!(events[1].variant_name(), "Transfer"); - - assert_eq!(events[2].pallet_name(), "Balances"); - assert_eq!(events[2].variant_name(), "Deposit"); - - assert_eq!(events[3].pallet_name(), "Treasury"); - assert_eq!(events[3].variant_name(), "Deposit"); - - assert_eq!(events[4].pallet_name(), "Balances"); - assert_eq!(events[4].variant_name(), "Deposit"); - - assert_eq!(events[5].pallet_name(), "TransactionPayment"); - assert_eq!(events[5].variant_name(), "TransactionFeePaid"); - - assert_eq!(events[6].pallet_name(), "System"); - assert_eq!(events[6].variant_name(), "ExtrinsicSuccess"); -} diff --git a/testing/sync/Cargo.toml b/testing/sync/Cargo.toml new file mode 100644 index 000000000..eaf954edc --- /dev/null +++ b/testing/sync/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "ac-testing-sync" +version = "0.3.2" +authors = ["Supercomputing Systems AG "] +license = "Apache-2.0" +edition = "2021" + +[dev-dependencies] +# Substrate dependencies +sp-application-crypto = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" } + +# local deps +substrate-api-client = { path = "../..", default-features = false, features = ["tungstenite-client", "ws-client"] } +substrate-client-keystore = { path = "../../client-keystore" } diff --git a/testing/examples/keystore_tests.rs b/testing/sync/examples/keystore_tests.rs similarity index 97% rename from testing/examples/keystore_tests.rs rename to testing/sync/examples/keystore_tests.rs index 041b0e8b7..ceb3c7915 100644 --- a/testing/examples/keystore_tests.rs +++ b/testing/sync/examples/keystore_tests.rs @@ -23,8 +23,7 @@ use substrate_client_keystore::{Keystore, KeystoreExt, LocalKeystore}; pub const KEYSTORE_PATH: &str = "my_keystore"; pub const SR25519: KeyTypeId = KeyTypeId(*b"sr25"); -#[tokio::main] -async fn main() { +fn main() { let store = LocalKeystore::open(PathBuf::from(&KEYSTORE_PATH), None).unwrap(); let seed = "//Ferdie"; diff --git a/testing/examples/tungstenite_client_test.rs b/testing/sync/examples/tungstenite_client_test.rs similarity index 100% rename from testing/examples/tungstenite_client_test.rs rename to testing/sync/examples/tungstenite_client_test.rs diff --git a/testing/examples/ws_client_test.rs b/testing/sync/examples/ws_client_test.rs similarity index 100% rename from testing/examples/ws_client_test.rs rename to testing/sync/examples/ws_client_test.rs