Merge remote-tracking branch 'origin/master' into na-jsonrpsee-core-client

This commit is contained in:
Niklas
2022-01-04 16:36:32 +01:00
6 changed files with 134 additions and 116 deletions
+3 -2
View File
@@ -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
+8
View File
@@ -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.
+7 -6
View File
@@ -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<dyn std::error::Error>> {
.sign_and_submit_then_watch(&signer)
.await?
.wait_for_finalized_success()
.await
.unwrap()?;
.await?;
let transfer_event =
balance_transfer.find_first_event::<polkadot::balances::events::Transfer>()?;
@@ -94,8 +94,7 @@ async fn simple_transfer_separate_events() -> Result<(), Box<dyn std::error::Err
.sign_and_submit_then_watch(&signer)
.await?
.wait_for_finalized()
.await
.unwrap()?;
.await?;
// Now we know it's been finalized, we can get hold of a couple of
// details, including events. Calling `wait_for_finalized_success` is
@@ -146,9 +145,9 @@ async fn handle_transfer_events() -> Result<(), Box<dyn std::error::Error>> {
.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<dyn std::error::Error>> {
println!("Current transaction status: {:?}", ev);
}
}
Ok(())
}
+1 -1
View File
@@ -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,
}
}
+113 -105
View File
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
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<T>,
}
// 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<SubstrateTransactionStatus<T::Hash, T::Hash>>,
@@ -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<Result<TransactionStatus<'client, T>, 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<Result<TransactionStatus<'client, T>, 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<TransactionInBlock<'client, T>, 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<TransactionInBlock<'client, T>, 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<TransactionEvents<T>, 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<TransactionStatus<'client, T>, Error>;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
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<Result<TransactionInBlock<'client, T>, 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<Result<TransactionInBlock<'client, T>, 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<Result<TransactionEvents<T>, 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:
///
+2 -2
View File
@@ -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]