mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Expand validator sets API with the rest of the events and some getters
We could've added a storage API, and fetched fields that way, except we want the storage to be opaque. That meant we needed to add the RPC routes to the node, which also simplifies other people writing RPC code and fetching these fields. Then the node could've used the storage API, except a lot of the storage in validator-sets is marked opaque and to only be read via functions, so extending the runtime made the most sense.
This commit is contained in:
@@ -125,12 +125,12 @@ impl Serai {
|
||||
|
||||
/// Fetch the latest finalized block number.
|
||||
pub async fn latest_finalized_block_number(&self) -> Result<u64, RpcError> {
|
||||
self.call("serai_latestFinalizedBlockNumber", "[]").await
|
||||
self.call("blockchain/latest_finalized_block_number", "[]").await
|
||||
}
|
||||
|
||||
/// Fetch if a block is finalized.
|
||||
pub async fn finalized(&self, block: BlockHash) -> Result<bool, RpcError> {
|
||||
self.call("serai_isFinalized", &format!(r#"["{block}"]"#)).await
|
||||
self.call("blockchain/is_finalized", &format!(r#"{{ "block": "{block}" }}"#)).await
|
||||
}
|
||||
|
||||
async fn block_internal(
|
||||
@@ -147,12 +147,14 @@ impl Serai {
|
||||
|
||||
/// Fetch a block from the Serai blockchain.
|
||||
pub async fn block(&self, block: BlockHash) -> Result<Block, RpcError> {
|
||||
Self::block_internal(self.call("serai_block", &format!(r#"["{block}"]"#))).await
|
||||
Self::block_internal(self.call("blockchain/block", &format!(r#"{{ "block": "{block}" }}"#)))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Fetch a block from the Serai blockchain by its number.
|
||||
pub async fn block_by_number(&self, block: u64) -> Result<Block, RpcError> {
|
||||
Self::block_internal(self.call("serai_block", &format!("[{block}]"))).await
|
||||
Self::block_internal(self.call("blockchain/block", &format!(r#"{{ "block": "{block}" }}"#)))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Scope this RPC client to the state as of a specific block.
|
||||
@@ -183,6 +185,14 @@ impl Serai {
|
||||
}
|
||||
|
||||
impl<'a> TemporalSerai<'a> {
|
||||
async fn call<ResponseValue: Default + JsonDeserialize>(
|
||||
&self,
|
||||
method: &str,
|
||||
params: &str,
|
||||
) -> Result<ResponseValue, RpcError> {
|
||||
self.serai.call(method, &format!(r#"{{ "block": "{}", {params} }}"#, self.block)).await
|
||||
}
|
||||
|
||||
/// Fetch the events for this block.
|
||||
///
|
||||
/// The returned `Option` will always be `Some(_)`.
|
||||
@@ -195,8 +205,7 @@ impl<'a> TemporalSerai<'a> {
|
||||
if events_mut.is_none() {
|
||||
*events_mut = Some(
|
||||
self
|
||||
.serai
|
||||
.call::<Vec<String>>("serai_events", &format!(r#"["{}"]"#, self.block))
|
||||
.call::<Vec<String>>("blockchain/events", "")
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|event| {
|
||||
|
||||
@@ -1,6 +1,27 @@
|
||||
pub use serai_abi::validator_sets::Event;
|
||||
use borsh::BorshDeserialize;
|
||||
|
||||
pub use serai_abi::{
|
||||
primitives::{
|
||||
crypto::KeyPair,
|
||||
network_id::{ExternalNetworkId, NetworkId},
|
||||
validator_sets::{Session, ExternalValidatorSet, ValidatorSet},
|
||||
balance::Amount,
|
||||
},
|
||||
validator_sets::Event,
|
||||
};
|
||||
|
||||
use crate::{RpcError, TemporalSerai};
|
||||
|
||||
fn rpc_network(network: impl Into<NetworkId>) -> Result<&'static str, RpcError> {
|
||||
Ok(match network.into() {
|
||||
NetworkId::Serai => r#""serai""#,
|
||||
NetworkId::External(ExternalNetworkId::Bitcoin) => r#""bitcoin""#,
|
||||
NetworkId::External(ExternalNetworkId::Ethereum) => r#""ethereum""#,
|
||||
NetworkId::External(ExternalNetworkId::Monero) => r#""monero""#,
|
||||
_ => Err(RpcError::InternalError("unrecognized network ID".to_string()))?,
|
||||
})
|
||||
}
|
||||
|
||||
/// A `TemporalSerai` scoped to the validator sets module.
|
||||
#[derive(Clone)]
|
||||
pub struct ValidatorSets<'a>(pub(super) &'a TemporalSerai<'a>);
|
||||
@@ -36,6 +57,18 @@ impl<'a> ValidatorSets<'a> {
|
||||
)
|
||||
}
|
||||
|
||||
/// The `SetKeys` events from the validator sets module.
|
||||
pub async fn set_keys_events(&self) -> Result<Vec<Event>, RpcError> {
|
||||
Ok(
|
||||
self
|
||||
.events()
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|event| matches!(event, Event::SetKeys { .. }))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
/// The `AcceptedHandover` events from the validator sets module.
|
||||
pub async fn accepted_handover_events(&self) -> Result<Vec<Event>, RpcError> {
|
||||
Ok(
|
||||
@@ -47,4 +80,69 @@ impl<'a> ValidatorSets<'a> {
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
/// The `SlashReport` events from the validator sets module.
|
||||
pub async fn slash_report_events(&self) -> Result<Vec<Event>, RpcError> {
|
||||
Ok(
|
||||
self
|
||||
.events()
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|event| matches!(event, Event::SlashReport { .. }))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
/// The current session for the specified network.
|
||||
pub async fn current_session(&self, network: NetworkId) -> Result<Option<Session>, RpcError> {
|
||||
Ok(
|
||||
self
|
||||
.0
|
||||
.call::<Option<_>>(
|
||||
"validator-sets/session",
|
||||
&format!(r#" "network": {} "#, rpc_network(network)?),
|
||||
)
|
||||
.await?
|
||||
.map(Session),
|
||||
)
|
||||
}
|
||||
|
||||
/// The stake for the current validators for specified network.
|
||||
pub async fn current_stake(&self, network: NetworkId) -> Result<Option<Amount>, RpcError> {
|
||||
Ok(
|
||||
self
|
||||
.0
|
||||
.call::<Option<_>>(
|
||||
"validator-sets/current_stake",
|
||||
&format!(r#" "network": {} "#, rpc_network(network)?),
|
||||
)
|
||||
.await?
|
||||
.map(Amount),
|
||||
)
|
||||
}
|
||||
|
||||
/// The keys for the specified validator set.
|
||||
pub async fn keys(&self, set: ExternalValidatorSet) -> Result<Option<KeyPair>, RpcError> {
|
||||
let Some(key_pair) = self
|
||||
.0
|
||||
.call::<Option<String>>(
|
||||
"validator-sets/keys",
|
||||
&format!(
|
||||
r#" "set": {{ "network": {}, "session": {} }} "#,
|
||||
rpc_network(set.network)?,
|
||||
set.session.0
|
||||
),
|
||||
)
|
||||
.await?
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
KeyPair::deserialize(
|
||||
&mut hex::decode(key_pair)
|
||||
.map_err(|_| RpcError::InvalidNode("validator set's keys weren't valid hex".to_string()))?
|
||||
.as_slice(),
|
||||
)
|
||||
.map(Some)
|
||||
.map_err(|_| RpcError::InvalidNode("validator set's keys weren't a valid key pair".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use serai_abi::{
|
||||
primitives::{
|
||||
network_id::{ExternalNetworkId, NetworkId},
|
||||
validator_sets::{Session, ValidatorSet},
|
||||
balance::Amount,
|
||||
validator_sets::{Session, ExternalValidatorSet, ValidatorSet},
|
||||
},
|
||||
validator_sets::Event,
|
||||
};
|
||||
@@ -125,7 +126,24 @@ async fn validator_sets() {
|
||||
);
|
||||
}
|
||||
|
||||
println!("Finished `serai-client/blockchain` test");
|
||||
{
|
||||
let serai =
|
||||
serai.as_of(serai.block_by_number(0).await.unwrap().header.hash()).await.unwrap();
|
||||
let serai = serai.validator_sets();
|
||||
for network in NetworkId::all() {
|
||||
assert_eq!(serai.current_session(network).await.unwrap(), Some(Session(0)));
|
||||
assert_eq!(serai.current_stake(network).await.unwrap(), Some(Amount(0)));
|
||||
match network {
|
||||
NetworkId::Serai => {}
|
||||
NetworkId::External(network) => assert_eq!(
|
||||
serai.keys(ExternalValidatorSet { network, session: Session(0) }).await.unwrap(),
|
||||
None
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("Finished `serai-client/validator_sets` test");
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user