diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 155b6f786c..df6b4b9a9f 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -52,8 +52,9 @@ jobs: with: # Use this issue template: filename: .github/issue_templates/nightly_run_failed.md - # Don't create a new issue; skip updating existing: - update_existing: false + # Update existing issue if found; hopefully will make it clearer + # that it is still an issue: + update_existing: true # Look for new *open* issues in this search (we want to # create a new one if we only find closed versions): search_existing: open diff --git a/README.md b/README.md index 5d681c8c37..976d8b848c 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,14 @@ This can be installed from source via cargo: cargo install --git https://github.com/paritytech/substrate node-cli --tag=polkadot-v0.9.10 --force ``` +## Real world usage + +Please add your project to this list via a PR. + +- [cargo-contract](https://github.com/paritytech/cargo-contract/pull/79) CLI for interacting with Wasm smart contracts. +- [xcm-cli](https://github.com/ascjones/xcm-cli) CLI for submitting XCM messages. +- [phala-pherry](https://github.com/Phala-Network/phala-blockchain/tree/master/standalone/pherry) The relayer between Phala blockchain and the off-chain Secure workers. + **Alternatives** [substrate-api-client](https://github.com/scs/substrate-api-client) provides similar functionality. diff --git a/examples/submit_and_watch.rs b/examples/submit_and_watch.rs index 552d88db1c..ddb9c97c6c 100644 --- a/examples/submit_and_watch.rs +++ b/examples/submit_and_watch.rs @@ -22,6 +22,7 @@ //! polkadot --dev --tmp //! ``` +use futures::StreamExt; use sp_keyring::AccountKeyring; use subxt::{ ClientBuilder, @@ -61,8 +62,7 @@ async fn simple_transfer() -> Result<(), Box> { .sign_and_submit_then_watch(&signer) .await? .wait_for_finalized_success() - .await - .unwrap()?; + .await?; let transfer_event = balance_transfer.find_first_event::()?; @@ -94,8 +94,7 @@ async fn simple_transfer_separate_events() -> Result<(), Box Result<(), Box> { .sign_and_submit_then_watch(&signer) .await?; - loop { + while let Some(ev) = balance_transfer_progress.next().await { + let ev = ev?; use subxt::TransactionStatus::*; - let ev = balance_transfer_progress.next().await.unwrap()?; // Made it into a block, but not finalized. if let InBlock(details) = ev { @@ -194,4 +193,6 @@ async fn handle_transfer_events() -> Result<(), Box> { println!("Current transaction status: {:?}", ev); } } + + Ok(()) } diff --git a/src/subscription.rs b/src/subscription.rs index 7ddd9d6f09..693c160a6a 100644 --- a/src/subscription.rs +++ b/src/subscription.rs @@ -247,12 +247,12 @@ where T: DeserializeOwned, { match sub.next().await { - None => None, Some(Ok(next)) => Some(next), Some(Err(e)) => { log::error!("Subscription {} failed: {:?} dropping", sub_name, e); None } + None => None, } } diff --git a/src/transaction.rs b/src/transaction.rs index e14320c2d5..625a0e4fd8 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with subxt. If not, see . +use std::task::Poll; + use sp_core::storage::StorageKey; use sp_runtime::traits::Hash; pub use sp_runtime::traits::SignedExtension; @@ -30,7 +32,14 @@ use crate::{ Config, Phase, }; -use jsonrpsee::core::client::Subscription as RpcSubscription; +use futures::{ + Stream, + StreamExt, +}; +use jsonrpsee::core::{ + client::Subscription as RpcSubscription, + Error as RpcError, +}; /// This struct represents a subscription to the progress of some transaction, and is /// returned from [`crate::SubmittableExtrinsic::sign_and_submit_then_watch()`]. @@ -41,6 +50,11 @@ pub struct TransactionProgress<'client, T: Config> { client: &'client Client, } +// The above type is not `Unpin` by default unless the generic param `T` is, +// so we manually make it clear that Unpin is actually fine regardless of `T` +// (we don't care if this moves around in memory while it's "pinned"). +impl<'client, T: Config> Unpin for TransactionProgress<'client, T> {} + impl<'client, T: Config> TransactionProgress<'client, T> { pub(crate) fn new( sub: RpcSubscription>, @@ -54,18 +68,106 @@ impl<'client, T: Config> TransactionProgress<'client, T> { } } - /// Return the next transaction status when it's emitted. - pub async fn next(&mut self) -> Option, Error>> { - // Return `None` if the subscription has been dropped: - let sub = match &mut self.sub { + /// Return the next transaction status when it's emitted. This just delegates to the + /// [`futures::Stream`] implementation for [`TransactionProgress`], but allows you to + /// avoid importing that trait if you don't otherwise need it. + pub async fn next_item( + &mut self, + ) -> Option, Error>> { + self.next().await + } + + /// Wait for the transaction to be in a block (but not necessarily finalized), and return + /// an [`TransactionInBlock`] instance when this happens, or an error if there was a problem + /// waiting for this to happen. + /// + /// **Note:** consumes `self`. If you'd like to perform multiple actions as the state of the + /// transaction progresses, use [`TransactionProgress::next_item()`] instead. + /// + /// **Note:** transaction statuses like `Invalid` and `Usurped` are ignored, because while they + /// may well indicate with some probability that the transaction will not make it into a block, + /// there is no guarantee that this is true. Thus, we prefer to "play it safe" here. Use the lower + /// level [`TransactionProgress::next_item()`] API if you'd like to handle these statuses yourself. + pub async fn wait_for_in_block( + mut self, + ) -> Result, Error> { + while let Some(status) = self.next_item().await { + match status? { + // Finalized or otherwise in a block! Return. + TransactionStatus::InBlock(s) | TransactionStatus::Finalized(s) => { + return Ok(s) + } + // Error scenarios; return the error. + TransactionStatus::FinalityTimeout(_) => { + return Err(TransactionError::FinalitySubscriptionTimeout.into()) + } + // Ignore anything else and wait for next status event: + _ => continue, + } + } + Err(RpcError::Custom("RPC subscription dropped".into()).into()) + } + + /// Wait for the transaction to be finalized, and return a [`TransactionInBlock`] + /// instance when it is, or an error if there was a problem waiting for finalization. + /// + /// **Note:** consumes `self`. If you'd like to perform multiple actions as the state of the + /// transaction progresses, use [`TransactionProgress::next_item()`] instead. + /// + /// **Note:** transaction statuses like `Invalid` and `Usurped` are ignored, because while they + /// may well indicate with some probability that the transaction will not make it into a block, + /// there is no guarantee that this is true. Thus, we prefer to "play it safe" here. Use the lower + /// level [`TransactionProgress::next_item()`] API if you'd like to handle these statuses yourself. + pub async fn wait_for_finalized( + mut self, + ) -> Result, Error> { + while let Some(status) = self.next_item().await { + match status? { + // Finalized! Return. + TransactionStatus::Finalized(s) => return Ok(s), + // Error scenarios; return the error. + TransactionStatus::FinalityTimeout(_) => { + return Err(TransactionError::FinalitySubscriptionTimeout.into()) + } + // Ignore and wait for next status event: + _ => continue, + } + } + Err(RpcError::Custom("RPC subscription dropped".into()).into()) + } + + /// Wait for the transaction to be finalized, and for the transaction events to indicate + /// that the transaction was successful. Returns the events associated with the transaction, + /// as well as a couple of other details (block hash and extrinsic hash). + /// + /// **Note:** consumes self. If you'd like to perform multiple actions as progress is made, + /// use [`TransactionProgress::next_item()`] instead. + /// + /// **Note:** transaction statuses like `Invalid` and `Usurped` are ignored, because while they + /// may well indicate with some probability that the transaction will not make it into a block, + /// there is no guarantee that this is true. Thus, we prefer to "play it safe" here. Use the lower + /// level [`TransactionProgress::next_item()`] API if you'd like to handle these statuses yourself. + pub async fn wait_for_finalized_success(self) -> Result, Error> { + let evs = self.wait_for_finalized().await?.wait_for_success().await?; + Ok(evs) + } +} + +impl<'client, T: Config> Stream for TransactionProgress<'client, T> { + type Item = Result, Error>; + + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + let sub = match self.sub.as_mut() { Some(sub) => sub, - None => return None, + None => return Poll::Ready(None), }; - // Return the next item otherwise: - let res = sub.next().await?; - Some( - res.map(|status| { + sub.poll_next_unpin(cx) + .map_err(|e| e.into()) + .map_ok(|status| { match status { SubstrateTransactionStatus::Future => TransactionStatus::Future, SubstrateTransactionStatus::Ready => TransactionStatus::Ready, @@ -110,100 +212,6 @@ impl<'client, T: Config> TransactionProgress<'client, T> { } } }) - .map_err(Into::into), - ) - } - - /// Wait for the transaction to be in a block (but not necessarily finalized), and return - /// an [`TransactionInBlock`] instance when this happens, or an error if there was a problem - /// waiting for this to happen. - /// - /// **Note:** consumes `self`. If you'd like to perform multiple actions as the state of the - /// transaction progresses, use [`TransactionProgress::next()`] instead. - /// - /// **Note:** transaction statuses like `Invalid` and `Usurped` are ignored, because while they - /// may well indicate with some probability that the transaction will not make it into a block, - /// there is no guarantee that this is true. Thus, we prefer to "play it safe" here. Use the lower - /// level [`TransactionProgress::next()`] API if you'd like to handle these statuses yourself. - pub async fn wait_for_in_block( - mut self, - ) -> Option, Error>> { - loop { - match self.next().await? { - Ok(status) => { - match status { - // Finalized or otherwise in a block! Return. - TransactionStatus::InBlock(s) - | TransactionStatus::Finalized(s) => return Some(Ok(s)), - // Error scenarios; return the error. - TransactionStatus::FinalityTimeout(_) => { - return Some(Err( - TransactionError::FinalitySubscriptionTimeout.into(), - )) - } - // Ignore anything else and wait for next status event: - _ => continue, - } - } - Err(err) => return Some(Err(err)), - } - } - } - - /// Wait for the transaction to be finalized, and return a [`TransactionInBlock`] - /// instance when it is, or an error if there was a problem waiting for finalization. - /// - /// **Note:** consumes `self`. If you'd like to perform multiple actions as the state of the - /// transaction progresses, use [`TransactionProgress::next()`] instead. - /// - /// **Note:** transaction statuses like `Invalid` and `Usurped` are ignored, because while they - /// may well indicate with some probability that the transaction will not make it into a block, - /// there is no guarantee that this is true. Thus, we prefer to "play it safe" here. Use the lower - /// level [`TransactionProgress::next()`] API if you'd like to handle these statuses yourself. - pub async fn wait_for_finalized( - mut self, - ) -> Option, Error>> { - loop { - match self.next().await? { - Ok(status) => { - match status { - // finalized! return. - TransactionStatus::Finalized(s) => return Some(Ok(s)), - // error scenarios; return the error. - TransactionStatus::FinalityTimeout(_) => { - return Some(Err( - TransactionError::FinalitySubscriptionTimeout.into(), - )) - } - // ignore and wait for next status event: - _ => continue, - } - } - Err(err) => return Some(Err(err)), - } - } - } - - /// Wait for the transaction to be finalized, and for the transaction events to indicate - /// that the transaction was successful. Returns the events associated with the transaction, - /// as well as a couple of other details (block hash and extrinsic hash). - /// - /// **Note:** consumes self. If you'd like to perform multiple actions as progress is made, - /// use [`TransactionProgress::next()`] instead. - /// - /// **Note:** transaction statuses like `Invalid` and `Usurped` are ignored, because while they - /// may well indicate with some probability that the transaction will not make it into a block, - /// there is no guarantee that this is true. Thus, we prefer to "play it safe" here. Use the lower - /// level [`TransactionProgress::next()`] API if you'd like to handle these statuses yourself. - pub async fn wait_for_finalized_success( - self, - ) -> Option, Error>> { - let finalized = match self.wait_for_finalized().await? { - Ok(f) => f, - Err(err) => return Some(Err(err)), - }; - - Some(finalized.wait_for_success().await) } } @@ -212,7 +220,7 @@ impl<'client, T: Config> TransactionProgress<'client, T> { //* Note that the number of finality watchers is, at the time of writing, found in the constant //* `MAX_FINALITY_WATCHERS` in the `sc_transaction_pool` crate. //* -/// Possible transaction statuses returned from our [`TransactionProgress::next()`] call. +/// Possible transaction statuses returned from our [`TransactionProgress::next_item()`] call. /// /// These status events can be grouped based on their kinds as: /// diff --git a/tests/integration/client.rs b/tests/integration/client.rs index a583d3586b..66898837c6 100644 --- a/tests/integration/client.rs +++ b/tests/integration/client.rs @@ -84,7 +84,7 @@ async fn chain_subscribe_blocks() { let node_process = test_node_process().await; let client = node_process.client(); let mut blocks = client.rpc().subscribe_blocks().await.unwrap(); - blocks.next().await.unwrap(); + blocks.next().await.unwrap().unwrap(); } #[async_std::test] @@ -92,7 +92,7 @@ async fn chain_subscribe_finalized_blocks() { let node_process = test_node_process().await; let client = node_process.client(); let mut blocks = client.rpc().subscribe_finalized_blocks().await.unwrap(); - blocks.next().await.unwrap(); + blocks.next().await.unwrap().unwrap(); } #[async_std::test]