mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Add method to fetch a block's events to the RPC
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use core::future::Future;
|
||||
use core::{ops::Deref, convert::AsRef, future::Future};
|
||||
use std::{sync::Arc, io::Read};
|
||||
|
||||
use thiserror::Error;
|
||||
@@ -19,6 +19,10 @@ use abi::{
|
||||
|
||||
use async_lock::RwLock;
|
||||
|
||||
/// RPC client functionality for the validator sets module.
|
||||
pub mod validator_sets;
|
||||
use validator_sets::*;
|
||||
|
||||
/// An error from the RPC.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RpcError {
|
||||
@@ -177,3 +181,45 @@ impl Serai {
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TemporalSerai<'a> {
|
||||
/// Fetch the events for this block.
|
||||
///
|
||||
/// The returned `Option` will always be `Some(_)`.
|
||||
async fn events(&self) -> Result<async_lock::RwLockReadGuard<'_, Option<Vec<Event>>>, RpcError> {
|
||||
let mut events = self.events.read().await;
|
||||
if events.is_none() {
|
||||
drop(events);
|
||||
{
|
||||
let mut events_mut = self.events.write().await;
|
||||
if events_mut.is_none() {
|
||||
*events_mut = Some(
|
||||
self
|
||||
.serai
|
||||
.call::<Vec<String>>("serai_events", &format!(r#"["{}"]"#, self.block))
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|event| {
|
||||
Event::deserialize(
|
||||
&mut hex::decode(&event)
|
||||
.map_err(|_| {
|
||||
RpcError::InvalidNode("node returned non-hex-encoded event".to_string())
|
||||
})?
|
||||
.as_slice(),
|
||||
)
|
||||
.map_err(|_| RpcError::InvalidNode("node returned invalid event".to_string()))
|
||||
})
|
||||
.collect::<Result<_, _>>()?,
|
||||
);
|
||||
}
|
||||
}
|
||||
events = self.events.read().await;
|
||||
}
|
||||
Ok(events)
|
||||
}
|
||||
|
||||
/// Scope to the validator sets module.
|
||||
pub fn validator_sets(&self) -> ValidatorSets<'_> {
|
||||
ValidatorSets(self)
|
||||
}
|
||||
}
|
||||
|
||||
50
substrate/client/serai/src/validator_sets.rs
Normal file
50
substrate/client/serai/src/validator_sets.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
pub use serai_abi::validator_sets::Event;
|
||||
use crate::{RpcError, TemporalSerai};
|
||||
|
||||
/// A `TemporalSerai` scoped to the validator sets module.
|
||||
#[derive(Clone)]
|
||||
pub struct ValidatorSets<'a>(pub(super) &'a TemporalSerai<'a>);
|
||||
|
||||
impl<'a> ValidatorSets<'a> {
|
||||
/// The events from the validator sets module.
|
||||
pub async fn events(&self) -> Result<Vec<Event>, RpcError> {
|
||||
Ok(
|
||||
self
|
||||
.0
|
||||
.events()
|
||||
.await?
|
||||
.as_ref()
|
||||
.expect("`TemporalSerai::events` returned None")
|
||||
.iter()
|
||||
.filter_map(|event| match event {
|
||||
serai_abi::Event::ValidatorSets(event) => Some(event.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
/// The `SetDecided` events from the validator sets module.
|
||||
pub async fn set_decided_events(&self) -> Result<Vec<Event>, RpcError> {
|
||||
Ok(
|
||||
self
|
||||
.events()
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|event| matches!(event, Event::SetDecided { .. }))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
/// The `AcceptedHandover` events from the validator sets module.
|
||||
pub async fn accepted_handover_events(&self) -> Result<Vec<Event>, RpcError> {
|
||||
Ok(
|
||||
self
|
||||
.events()
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|event| matches!(event, Event::AcceptedHandover { .. }))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,18 @@ async fn blockchain() {
|
||||
.run_async(async |ops| {
|
||||
let serai = serai_substrate_tests::rpc(&ops, handle).await;
|
||||
|
||||
'outer: {
|
||||
for _ in 0 .. (5 * 10) {
|
||||
tokio::time::sleep(core::time::Duration::from_secs(6)).await;
|
||||
|
||||
let latest_finalized = serai.latest_finalized_block_number().await.unwrap();
|
||||
if latest_finalized > 0 {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
panic!("finalized block remained the genesis block for over five minutes");
|
||||
};
|
||||
|
||||
// Check the sanity of fetching a block
|
||||
let test_finalized_block = |number| {
|
||||
let serai = &serai;
|
||||
|
||||
131
substrate/client/serai/tests/validator_sets.rs
Normal file
131
substrate/client/serai/tests/validator_sets.rs
Normal file
@@ -0,0 +1,131 @@
|
||||
use serai_abi::{
|
||||
primitives::{
|
||||
network_id::{ExternalNetworkId, NetworkId},
|
||||
validator_sets::{Session, ValidatorSet},
|
||||
},
|
||||
validator_sets::Event,
|
||||
};
|
||||
|
||||
use serai_client_serai::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn validator_sets() {
|
||||
let mut test = dockertest::DockerTest::new();
|
||||
let (composition, handle) = serai_substrate_tests::composition(
|
||||
"alice",
|
||||
serai_docker_tests::fresh_logs_folder(true, "serai-client/validator_sets"),
|
||||
);
|
||||
test.provide_container(
|
||||
composition
|
||||
.replace_cmd(
|
||||
["serai-node", "--unsafe-rpc-external", "--rpc-cors", "all", "--dev"]
|
||||
.into_iter()
|
||||
.map(str::to_owned)
|
||||
.collect(),
|
||||
)
|
||||
.replace_env([("RUST_LOG".to_string(), "runtime=debug".to_string())].into()),
|
||||
);
|
||||
|
||||
test
|
||||
.run_async(async |ops| {
|
||||
let serai = serai_substrate_tests::rpc(&ops, handle).await;
|
||||
|
||||
'outer: {
|
||||
for _ in 0 .. (5 * 10) {
|
||||
tokio::time::sleep(core::time::Duration::from_secs(6)).await;
|
||||
|
||||
let latest_finalized = serai.latest_finalized_block_number().await.unwrap();
|
||||
if latest_finalized > 0 {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
panic!("finalized block remained the genesis block for over five minutes");
|
||||
};
|
||||
|
||||
// The genesis block should have the expected events
|
||||
{
|
||||
{
|
||||
let mut events = serai
|
||||
.as_of(serai.block_by_number(0).await.unwrap().header.hash())
|
||||
.await
|
||||
.unwrap()
|
||||
.validator_sets()
|
||||
.set_decided_events()
|
||||
.await
|
||||
.unwrap();
|
||||
events.sort_by_key(|event| borsh::to_vec(event).unwrap());
|
||||
let mut expected = vec![
|
||||
Event::SetDecided {
|
||||
set: ValidatorSet { network: NetworkId::Serai, session: Session(0) },
|
||||
},
|
||||
Event::SetDecided {
|
||||
set: ValidatorSet { network: NetworkId::Serai, session: Session(1) },
|
||||
},
|
||||
Event::SetDecided {
|
||||
set: ValidatorSet {
|
||||
network: NetworkId::External(ExternalNetworkId::Bitcoin),
|
||||
session: Session(0),
|
||||
},
|
||||
},
|
||||
Event::SetDecided {
|
||||
set: ValidatorSet {
|
||||
network: NetworkId::External(ExternalNetworkId::Ethereum),
|
||||
session: Session(0),
|
||||
},
|
||||
},
|
||||
Event::SetDecided {
|
||||
set: ValidatorSet {
|
||||
network: NetworkId::External(ExternalNetworkId::Monero),
|
||||
session: Session(0),
|
||||
},
|
||||
},
|
||||
];
|
||||
expected.sort_by_key(|event| borsh::to_vec(event).unwrap());
|
||||
assert_eq!(events, expected);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
serai
|
||||
.as_of(serai.block_by_number(0).await.unwrap().header.hash())
|
||||
.await
|
||||
.unwrap()
|
||||
.validator_sets()
|
||||
.accepted_handover_events()
|
||||
.await
|
||||
.unwrap(),
|
||||
vec![Event::AcceptedHandover {
|
||||
set: ValidatorSet { network: NetworkId::Serai, session: Session(0) }
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
// The next block should not have these events
|
||||
{
|
||||
assert_eq!(
|
||||
serai
|
||||
.as_of(serai.block_by_number(1).await.unwrap().header.hash())
|
||||
.await
|
||||
.unwrap()
|
||||
.validator_sets()
|
||||
.set_decided_events()
|
||||
.await
|
||||
.unwrap(),
|
||||
vec![],
|
||||
);
|
||||
assert_eq!(
|
||||
serai
|
||||
.as_of(serai.block_by_number(1).await.unwrap().header.hash())
|
||||
.await
|
||||
.unwrap()
|
||||
.validator_sets()
|
||||
.accepted_handover_events()
|
||||
.await
|
||||
.unwrap(),
|
||||
vec![],
|
||||
);
|
||||
}
|
||||
|
||||
println!("Finished `serai-client/blockchain` test");
|
||||
})
|
||||
.await;
|
||||
}
|
||||
Reference in New Issue
Block a user