implement variant of subscription that returns finalized storage changes (#237)

Signed-off-by: Gregory Hill <gregorydhill@outlook.com>
This commit is contained in:
Greg Hill
2021-03-10 10:43:38 +00:00
committed by GitHub
parent c1d4804ccd
commit 568d6ea577
3 changed files with 208 additions and 65 deletions
+29 -5
View File
@@ -110,7 +110,11 @@ pub use crate::{
SystemProperties, SystemProperties,
}, },
runtimes::*, runtimes::*,
subscription::*, subscription::{
EventStorageSubscription,
EventSubscription,
FinalizedEventStorageSubscription,
},
substrate_subxt_proc_macro::*, substrate_subxt_proc_macro::*,
}; };
use crate::{ use crate::{
@@ -133,6 +137,7 @@ pub struct ClientBuilder<T: Runtime> {
page_size: Option<u32>, page_size: Option<u32>,
event_type_registry: EventTypeRegistry<T>, event_type_registry: EventTypeRegistry<T>,
skip_type_sizes_check: bool, skip_type_sizes_check: bool,
accept_weak_inclusion: bool,
} }
impl<T: Runtime> ClientBuilder<T> { impl<T: Runtime> ClientBuilder<T> {
@@ -144,6 +149,7 @@ impl<T: Runtime> ClientBuilder<T> {
page_size: None, page_size: None,
event_type_registry: EventTypeRegistry::new(), event_type_registry: EventTypeRegistry::new(),
skip_type_sizes_check: false, skip_type_sizes_check: false,
accept_weak_inclusion: false,
} }
} }
@@ -187,6 +193,12 @@ impl<T: Runtime> ClientBuilder<T> {
self self
} }
/// Only check that transactions are InBlock on submit.
pub fn accept_weak_inclusion(mut self) -> Self {
self.accept_weak_inclusion = true;
self
}
/// Creates a new Client. /// Creates a new Client.
pub async fn build<'a>(self) -> Result<Client<T>, Error> { pub async fn build<'a>(self) -> Result<Client<T>, Error> {
let client = if let Some(client) = self.client { let client = if let Some(client) = self.client {
@@ -202,7 +214,10 @@ impl<T: Runtime> ClientBuilder<T> {
RpcClient::Http(Arc::new(client)) RpcClient::Http(Arc::new(client))
} }
}; };
let rpc = Rpc::new(client); let mut rpc = Rpc::new(client);
if self.accept_weak_inclusion {
rpc.accept_weak_inclusion();
}
let (metadata, genesis_hash, runtime_version, properties) = future::join4( let (metadata, genesis_hash, runtime_version, properties) = future::join4(
rpc.metadata(), rpc.metadata(),
rpc.genesis_hash(), rpc.genesis_hash(),
@@ -466,13 +481,22 @@ impl<T: Runtime> Client<T> {
} }
/// Subscribe to events. /// Subscribe to events.
pub async fn subscribe_events( ///
&self, /// *WARNING* these may not be included in the finalized chain, use
) -> Result<Subscription<StorageChangeSet<T::Hash>>, Error> { /// `subscribe_finalized_events` to ensure events are finalized.
pub async fn subscribe_events(&self) -> Result<EventStorageSubscription<T>, Error> {
let events = self.rpc.subscribe_events().await?; let events = self.rpc.subscribe_events().await?;
Ok(events) Ok(events)
} }
/// Subscribe to finalized events.
pub async fn subscribe_finalized_events(
&self,
) -> Result<EventStorageSubscription<T>, Error> {
let events = self.rpc.subscribe_finalized_events().await?;
Ok(events)
}
/// Subscribe to new blocks. /// Subscribe to new blocks.
pub async fn subscribe_blocks(&self) -> Result<Subscription<T::Header>, Error> { pub async fn subscribe_blocks(&self) -> Result<Subscription<T::Header>, Error> {
let headers = self.rpc.subscribe_blocks().await?; let headers = self.rpc.subscribe_blocks().await?;
+96 -57
View File
@@ -58,7 +58,6 @@ use sp_core::{
StorageData, StorageData,
StorageKey, StorageKey,
}, },
twox_128,
Bytes, Bytes,
}; };
use sp_rpc::{ use sp_rpc::{
@@ -86,7 +85,12 @@ use crate::{
}, },
metadata::Metadata, metadata::Metadata,
runtimes::Runtime, runtimes::Runtime,
subscription::EventSubscription, subscription::{
EventStorageSubscription,
EventSubscription,
FinalizedEventStorageSubscription,
SystemEvents,
},
}; };
pub type ChainBlock<T> = pub type ChainBlock<T> =
@@ -256,6 +260,7 @@ pub struct ReadProof<Hash> {
pub struct Rpc<T: Runtime> { pub struct Rpc<T: Runtime> {
client: RpcClient, client: RpcClient,
marker: PhantomData<T>, marker: PhantomData<T>,
accept_weak_inclusion: bool,
} }
impl<T: Runtime> Clone for Rpc<T> { impl<T: Runtime> Clone for Rpc<T> {
@@ -263,6 +268,7 @@ impl<T: Runtime> Clone for Rpc<T> {
Self { Self {
client: self.client.clone(), client: self.client.clone(),
marker: PhantomData, marker: PhantomData,
accept_weak_inclusion: self.accept_weak_inclusion,
} }
} }
} }
@@ -272,9 +278,16 @@ impl<T: Runtime> Rpc<T> {
Self { Self {
client, client,
marker: PhantomData, marker: PhantomData,
accept_weak_inclusion: false,
} }
} }
/// Configure the Rpc to accept non-finalized blocks
/// in `submit_and_watch_extrinsic`
pub fn accept_weak_inclusion(&mut self) {
self.accept_weak_inclusion = true;
}
/// Fetch a storage key /// Fetch a storage key
pub async fn storage( pub async fn storage(
&self, &self,
@@ -439,22 +452,31 @@ impl<T: Runtime> Rpc<T> {
Ok(version) Ok(version)
} }
/// Subscribe to substrate System Events /// Subscribe to System Events that are imported into blocks.
pub async fn subscribe_events( ///
&self, /// *WARNING* these may not be included in the finalized chain, use
) -> Result<Subscription<StorageChangeSet<T::Hash>>, Error> { /// `subscribe_finalized_events` to ensure events are finalized.
let mut storage_key = twox_128(b"System").to_vec(); pub async fn subscribe_events(&self) -> Result<EventStorageSubscription<T>, Error> {
storage_key.extend(twox_128(b"Events").to_vec()); let keys = Some(vec![StorageKey::from(SystemEvents::new())]);
log::debug!("Events storage key {:?}", hex::encode(&storage_key));
let keys = Some(vec![StorageKey(storage_key)]);
let params = Params::Array(vec![to_json_value(keys)?]); let params = Params::Array(vec![to_json_value(keys)?]);
let subscription = self let subscription = self
.client .client
.subscribe("state_subscribeStorage", params, "state_unsubscribeStorage") .subscribe("state_subscribeStorage", params, "state_unsubscribeStorage")
.await?; .await?;
Ok(subscription) Ok(EventStorageSubscription::Imported(subscription))
}
/// Subscribe to finalized events.
pub async fn subscribe_finalized_events(
&self,
) -> Result<EventStorageSubscription<T>, Error> {
Ok(EventStorageSubscription::Finalized(
FinalizedEventStorageSubscription::new(
self.clone(),
self.subscribe_finalized_blocks().await?,
),
))
} }
/// Subscribe to blocks. /// Subscribe to blocks.
@@ -464,7 +486,7 @@ impl<T: Runtime> Rpc<T> {
.subscribe( .subscribe(
"chain_subscribeNewHeads", "chain_subscribeNewHeads",
Params::None, Params::None,
"chain_subscribeNewHeads", "chain_unsubscribeNewHeads",
) )
.await?; .await?;
@@ -480,7 +502,7 @@ impl<T: Runtime> Rpc<T> {
.subscribe( .subscribe(
"chain_subscribeFinalizedHeads", "chain_subscribeFinalizedHeads",
Params::None, Params::None,
"chain_subscribeFinalizedHeads", "chain_unsubscribeFinalizedHeads",
) )
.await?; .await?;
Ok(subscription) Ok(subscription)
@@ -526,56 +548,27 @@ impl<T: Runtime> Rpc<T> {
let ext_hash = T::Hashing::hash_of(&extrinsic); let ext_hash = T::Hashing::hash_of(&extrinsic);
log::info!("Submitting Extrinsic `{:?}`", ext_hash); log::info!("Submitting Extrinsic `{:?}`", ext_hash);
let events_sub = self.subscribe_events().await?; let events_sub = if self.accept_weak_inclusion {
self.subscribe_events().await
} else {
self.subscribe_finalized_events().await
}?;
let mut xt_sub = self.watch_extrinsic(extrinsic).await?; let mut xt_sub = self.watch_extrinsic(extrinsic).await?;
while let Some(status) = xt_sub.next().await { while let Some(status) = xt_sub.next().await {
// log::info!("received status {:?}", status); log::info!("received status {:?}", status);
match status { match status {
// ignore in progress extrinsic for now // ignore in progress extrinsic for now
TransactionStatus::Future TransactionStatus::Future
| TransactionStatus::Ready | TransactionStatus::Ready
| TransactionStatus::Broadcast(_) => continue, | TransactionStatus::Broadcast(_) => continue,
TransactionStatus::InBlock(block_hash) => { TransactionStatus::InBlock(block_hash) => {
log::info!("Fetching block {:?}", block_hash); if self.accept_weak_inclusion {
let block = self.block(Some(block_hash)).await?; return self
return match block { .process_block(events_sub, decoder, block_hash, ext_hash)
Some(signed_block) => { .await
log::info!(
"Found block {:?}, with {} extrinsics",
block_hash,
signed_block.block.extrinsics.len()
);
let ext_index = signed_block
.block
.extrinsics
.iter()
.position(|ext| {
let hash = T::Hashing::hash_of(ext);
hash == ext_hash
})
.ok_or_else(|| {
Error::Other(format!(
"Failed to find Extrinsic with hash {:?}",
ext_hash,
))
})?;
let mut sub = EventSubscription::new(events_sub, &decoder);
sub.filter_extrinsic(block_hash, ext_index);
let mut events = vec![];
while let Some(event) = sub.next().await {
events.push(event?);
}
Ok(ExtrinsicSuccess {
block: block_hash,
extrinsic: ext_hash,
events,
})
}
None => {
Err(format!("Failed to find block {:?}", block_hash).into())
}
} }
continue
} }
TransactionStatus::Invalid => return Err("Extrinsic Invalid".into()), TransactionStatus::Invalid => return Err("Extrinsic Invalid".into()),
TransactionStatus::Usurped(_) => return Err("Extrinsic Usurped".into()), TransactionStatus::Usurped(_) => return Err("Extrinsic Usurped".into()),
@@ -583,9 +576,11 @@ impl<T: Runtime> Rpc<T> {
TransactionStatus::Retracted(_) => { TransactionStatus::Retracted(_) => {
return Err("Extrinsic Retracted".into()) return Err("Extrinsic Retracted".into())
} }
// should have made it `InBlock` before either of these TransactionStatus::Finalized(block_hash) => {
TransactionStatus::Finalized(_) => { // read finalized blocks by default
return Err("Extrinsic Finalized".into()) return self
.process_block(events_sub, decoder, block_hash, ext_hash)
.await
} }
TransactionStatus::FinalityTimeout(_) => { TransactionStatus::FinalityTimeout(_) => {
return Err("Extrinsic FinalityTimeout".into()) return Err("Extrinsic FinalityTimeout".into())
@@ -595,6 +590,50 @@ impl<T: Runtime> Rpc<T> {
Err(RpcError::Custom("RPC subscription dropped".into()).into()) Err(RpcError::Custom("RPC subscription dropped".into()).into())
} }
async fn process_block<'a>(
&self,
events_sub: EventStorageSubscription<T>,
decoder: &'a EventsDecoder<T>,
block_hash: T::Hash,
ext_hash: T::Hash,
) -> Result<ExtrinsicSuccess<T>, Error> {
log::info!("Fetching block {:?}", block_hash);
if let Some(signed_block) = self.block(Some(block_hash)).await? {
log::info!(
"Found block {:?}, with {} extrinsics",
block_hash,
signed_block.block.extrinsics.len()
);
let ext_index = signed_block
.block
.extrinsics
.iter()
.position(|ext| {
let hash = T::Hashing::hash_of(ext);
hash == ext_hash
})
.ok_or_else(|| {
Error::Other(format!(
"Failed to find Extrinsic with hash {:?}",
ext_hash,
))
})?;
let mut sub = EventSubscription::new(events_sub, &decoder);
sub.filter_extrinsic(block_hash, ext_index);
let mut events = vec![];
while let Some(event) = sub.next().await {
events.push(event?);
}
Ok(ExtrinsicSuccess {
block: block_hash,
extrinsic: ext_hash,
events,
})
} else {
Err(format!("Failed to find block {:?}", block_hash).into())
}
}
/// Insert a key into the keystore. /// Insert a key into the keystore.
pub async fn insert_key( pub async fn insert_key(
&self, &self,
+83 -3
View File
@@ -16,7 +16,14 @@
use jsonrpsee_types::error::Error as RpcError; use jsonrpsee_types::error::Error as RpcError;
use jsonrpsee_ws_client::WsSubscription as Subscription; use jsonrpsee_ws_client::WsSubscription as Subscription;
use sp_core::storage::StorageChangeSet; use sp_core::{
storage::{
StorageChangeSet,
StorageKey,
},
twox_128,
};
use sp_runtime::traits::Header;
use std::collections::VecDeque; use std::collections::VecDeque;
use crate::{ use crate::{
@@ -30,13 +37,14 @@ use crate::{
system::Phase, system::Phase,
Event, Event,
}, },
rpc::Rpc,
runtimes::Runtime, runtimes::Runtime,
}; };
/// Event subscription simplifies filtering a storage change set stream for /// Event subscription simplifies filtering a storage change set stream for
/// events of interest. /// events of interest.
pub struct EventSubscription<'a, T: Runtime> { pub struct EventSubscription<'a, T: Runtime> {
subscription: Subscription<StorageChangeSet<T::Hash>>, subscription: EventStorageSubscription<T>,
decoder: &'a EventsDecoder<T>, decoder: &'a EventsDecoder<T>,
block: Option<T::Hash>, block: Option<T::Hash>,
extrinsic: Option<usize>, extrinsic: Option<usize>,
@@ -48,7 +56,7 @@ pub struct EventSubscription<'a, T: Runtime> {
impl<'a, T: Runtime> EventSubscription<'a, T> { impl<'a, T: Runtime> EventSubscription<'a, T> {
/// Creates a new event subscription. /// Creates a new event subscription.
pub fn new( pub fn new(
subscription: Subscription<StorageChangeSet<T::Hash>>, subscription: EventStorageSubscription<T>,
decoder: &'a EventsDecoder<T>, decoder: &'a EventsDecoder<T>,
) -> Self { ) -> Self {
Self { Self {
@@ -132,3 +140,75 @@ impl<'a, T: Runtime> EventSubscription<'a, T> {
} }
} }
} }
pub(crate) struct SystemEvents(StorageKey);
impl SystemEvents {
pub(crate) fn new() -> Self {
let mut storage_key = twox_128(b"System").to_vec();
storage_key.extend(twox_128(b"Events").to_vec());
log::debug!("Events storage key {:?}", hex::encode(&storage_key));
Self(StorageKey(storage_key))
}
}
impl From<SystemEvents> for StorageKey {
fn from(key: SystemEvents) -> Self {
key.0
}
}
/// Event subscription to only fetch finalized storage changes.
pub struct FinalizedEventStorageSubscription<T: Runtime> {
rpc: Rpc<T>,
subscription: Subscription<T::Header>,
storage_changes: VecDeque<StorageChangeSet<T::Hash>>,
storage_key: StorageKey,
}
impl<T: Runtime> FinalizedEventStorageSubscription<T> {
/// Creates a new finalized event storage subscription.
pub fn new(rpc: Rpc<T>, subscription: Subscription<T::Header>) -> Self {
Self {
rpc,
subscription,
storage_changes: Default::default(),
storage_key: SystemEvents::new().into(),
}
}
/// Gets the next change_set.
pub async fn next(&mut self) -> Option<StorageChangeSet<T::Hash>> {
loop {
if let Some(storage_change) = self.storage_changes.pop_front() {
return Some(storage_change)
}
let header: T::Header = self.subscription.next().await?;
if let Ok(storage_changes) = self
.rpc
.query_storage_at(&[self.storage_key.clone()], Some(header.hash()))
.await
{
self.storage_changes.extend(storage_changes);
}
}
}
}
/// Wrapper over imported and finalized event subscriptions.
pub enum EventStorageSubscription<T: Runtime> {
/// Events that are InBlock
Imported(Subscription<StorageChangeSet<T::Hash>>),
/// Events that are Finalized
Finalized(FinalizedEventStorageSubscription<T>),
}
impl<T: Runtime> EventStorageSubscription<T> {
/// Gets the next change_set from the subscription.
pub async fn next(&mut self) -> Option<StorageChangeSet<T::Hash>> {
match self {
Self::Imported(event_sub) => event_sub.next().await,
Self::Finalized(event_sub) => event_sub.next().await,
}
}
}