Extend the new api.blocks() to be the primary way to subscribe and fetch blocks/extrinsics/events (#691)

* First pass adding functions to get blocks and extrinsics

* cargo fmt and cache block events

* prefix block hash with 0x

* pin streams for better ergonomics and add an example of subscribing to blocks

* remove unused var

* standardise on _all, _best and _finalized for different block header subs

* WIP center subscribing around blocks

* Remove the event filtering/subscribing  stuff

* clippy

* we need tokio, silly clippy

* add extrinsic_index() call

* Update subxt/src/blocks/block_types.rs

Co-authored-by: Andrew Jones <ascjones@gmail.com>

Co-authored-by: Andrew Jones <ascjones@gmail.com>
This commit is contained in:
James Wilson
2022-11-01 16:53:35 +01:00
committed by GitHub
parent 52d4762d13
commit 33a9ec91af
25 changed files with 646 additions and 1279 deletions
+3 -3
View File
@@ -11,7 +11,7 @@ async fn non_finalized_headers_subscription() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let mut sub = api.blocks().subscribe_headers().await?;
let mut sub = api.blocks().subscribe_best().await?;
// Wait for the next set of headers, and check that the
// associated block hash is the one we just finalized.
@@ -30,7 +30,7 @@ async fn finalized_headers_subscription() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let mut sub = api.blocks().subscribe_finalized_headers().await?;
let mut sub = api.blocks().subscribe_finalized().await?;
// Wait for the next set of headers, and check that the
// associated block hash is the one we just finalized.
@@ -52,7 +52,7 @@ async fn missing_block_headers_will_be_filled_in() -> Result<(), subxt::Error> {
// that there will be some gaps, even if there aren't any from the subscription.
let some_finalized_blocks = api
.rpc()
.subscribe_finalized_blocks()
.subscribe_finalized_block_headers()
.await?
.enumerate()
.take(6)
+12 -3
View File
@@ -74,11 +74,20 @@ async fn fetch_read_proof() {
}
#[tokio::test]
async fn chain_subscribe_blocks() {
async fn chain_subscribe_all_blocks() {
let ctx = test_context().await;
let api = ctx.client();
let mut blocks = api.rpc().subscribe_blocks().await.unwrap();
let mut blocks = api.rpc().subscribe_all_block_headers().await.unwrap();
blocks.next().await.unwrap().unwrap();
}
#[tokio::test]
async fn chain_subscribe_best_blocks() {
let ctx = test_context().await;
let api = ctx.client();
let mut blocks = api.rpc().subscribe_best_block_headers().await.unwrap();
blocks.next().await.unwrap().unwrap();
}
@@ -87,7 +96,7 @@ async fn chain_subscribe_finalized_blocks() {
let ctx = test_context().await;
let api = ctx.client();
let mut blocks = api.rpc().subscribe_finalized_blocks().await.unwrap();
let mut blocks = api.rpc().subscribe_finalized_block_headers().await.unwrap();
blocks.next().await.unwrap().unwrap();
}
-212
View File
@@ -1,212 +0,0 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::{
node_runtime::{
self,
balances,
system,
},
pair_signer,
test_context,
utils::wait_for_blocks,
};
use futures::StreamExt;
use sp_keyring::AccountKeyring;
// Check that we can subscribe to non-finalized block events.
#[tokio::test]
async fn non_finalized_block_subscription() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let mut event_sub = api.events().subscribe().await?;
// Wait for the next set of events, and check that the
// associated block hash is not finalized yet.
let events = event_sub.next().await.unwrap()?;
let event_block_hash = events.block_hash();
let current_block_hash = api.rpc().block_hash(None).await?.unwrap();
assert_eq!(event_block_hash, current_block_hash);
Ok(())
}
// Check that we can subscribe to finalized block events.
#[tokio::test]
async fn finalized_block_subscription() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let mut event_sub = api.events().subscribe_finalized().await?;
// Wait for the next set of events, and check that the
// associated block hash is the one we just finalized.
// (this can be a bit slow as we have to wait for finalization)
let events = event_sub.next().await.unwrap()?;
let event_block_hash = events.block_hash();
let finalized_hash = api.rpc().finalized_head().await?;
assert_eq!(event_block_hash, finalized_hash);
Ok(())
}
// Check that our subscription actually keeps producing events for
// a few blocks.
#[tokio::test]
async fn subscription_produces_events_each_block() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
wait_for_blocks(&api).await;
let mut event_sub = api.events().subscribe().await?;
for i in 0..3 {
let events = event_sub
.next()
.await
.expect("events expected each block")?;
let success_event = events
.find_first::<system::events::ExtrinsicSuccess>()
.expect("decode error");
if success_event.is_none() {
let n = events.len();
panic!("Expected an extrinsic success event on iteration {i} (saw {n} other events)")
}
}
Ok(())
}
// Iterate all of the events in a few blocks to ensure we can decode them properly.
#[tokio::test]
async fn decoding_all_events_in_a_block_works() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
wait_for_blocks(&api).await;
let mut event_sub = api.events().subscribe().await?;
tokio::spawn(async move {
let alice = pair_signer(AccountKeyring::Alice.pair());
let bob = AccountKeyring::Bob.to_account_id();
let transfer_tx = node_runtime::tx()
.balances()
.transfer(bob.clone().into(), 10_000);
// Make a load of transfers to get lots of events going.
for _i in 0..10 {
api.tx()
.sign_and_submit_then_watch_default(&transfer_tx, &alice)
.await
.expect("can submit_transaction");
}
});
for _ in 0..4 {
let events = event_sub
.next()
.await
.expect("events expected each block")?;
for event in events.iter() {
// make sure that we can get every event properly.
let event = event.expect("valid event decoded");
// make sure that we can decode the field values from every event.
event.field_values().expect("can decode fields");
}
}
Ok(())
}
// Check that our subscription receives events, and we can filter them based on
// it's Stream impl, and ultimately see the event we expect.
#[tokio::test]
async fn balance_transfer_subscription() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
// Subscribe to balance transfer events, ignoring all else.
let event_sub = api
.events()
.subscribe()
.await?
.filter_events::<(balances::events::Transfer,)>();
// Calling `.next()` on the above borrows it, and the `filter_map`
// means it's no longer `Unpin`, so we pin it on the stack:
futures::pin_mut!(event_sub);
// Make a transfer:
let alice = pair_signer(AccountKeyring::Alice.pair());
let bob = AccountKeyring::Bob.to_account_id();
let transfer_tx = node_runtime::tx()
.balances()
.transfer(bob.clone().into(), 10_000);
api.tx()
.sign_and_submit_then_watch_default(&transfer_tx, &alice)
.await?;
// Wait for the next balance transfer event in our subscription stream
// and check that it lines up:
let event = event_sub.next().await.unwrap().unwrap().event;
assert_eq!(
event,
balances::events::Transfer {
from: alice.account_id().clone(),
to: bob.clone(),
amount: 10_000
}
);
Ok(())
}
// This is just a compile-time check that we can subscribe to events in
// a context that requires the event subscription/filtering to be Send-able.
// We test a typical use of EventSubscription and FilterEvents. We don't need
// to run this code; just check that it compiles.
#[allow(unused)]
async fn check_events_are_sendable() {
// check that EventSubscription can be used across await points.
tokio::task::spawn(async {
let ctx = test_context().await;
let mut event_sub = ctx.client().events().subscribe().await?;
while let Some(ev) = event_sub.next().await {
// if `event_sub` doesn't implement Send, we can't hold
// it across an await point inside of a tokio::spawn, which
// requires Send. This will lead to a compile error.
}
Ok::<_, subxt::Error>(())
});
// Check that FilterEvents can be used across await points.
tokio::task::spawn(async {
let ctx = test_context().await;
let mut event_sub = ctx
.client()
.events()
.subscribe()
.await?
.filter_events::<(balances::events::Transfer,)>();
while let Some(ev) = event_sub.next().await {
// if `event_sub` doesn't implement Send, we can't hold
// it across an await point inside of a tokio::spawn, which
// requires Send; This will lead to a compile error.
}
Ok::<_, subxt::Error>(())
});
}
-2
View File
@@ -14,8 +14,6 @@ mod blocks;
#[cfg(test)]
mod client;
#[cfg(test)]
mod events;
#[cfg(test)]
mod frame;
#[cfg(test)]
mod metadata;
@@ -11,7 +11,7 @@ use subxt::{
/// (the genesis block and another one) seems to be enough to allow tests
/// like `dry_run_passes` to work properly.
pub async fn wait_for_blocks<C: Config>(api: &impl OnlineClientT<C>) {
let mut sub = api.rpc().subscribe_blocks().await.unwrap();
let mut sub = api.rpc().subscribe_all_block_headers().await.unwrap();
sub.next().await;
sub.next().await;
}