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.
|
/// Fetch the latest finalized block number.
|
||||||
pub async fn latest_finalized_block_number(&self) -> Result<u64, RpcError> {
|
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.
|
/// Fetch if a block is finalized.
|
||||||
pub async fn finalized(&self, block: BlockHash) -> Result<bool, RpcError> {
|
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(
|
async fn block_internal(
|
||||||
@@ -147,12 +147,14 @@ impl Serai {
|
|||||||
|
|
||||||
/// Fetch a block from the Serai blockchain.
|
/// Fetch a block from the Serai blockchain.
|
||||||
pub async fn block(&self, block: BlockHash) -> Result<Block, RpcError> {
|
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.
|
/// Fetch a block from the Serai blockchain by its number.
|
||||||
pub async fn block_by_number(&self, block: u64) -> Result<Block, RpcError> {
|
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.
|
/// Scope this RPC client to the state as of a specific block.
|
||||||
@@ -183,6 +185,14 @@ impl Serai {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TemporalSerai<'a> {
|
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.
|
/// Fetch the events for this block.
|
||||||
///
|
///
|
||||||
/// The returned `Option` will always be `Some(_)`.
|
/// The returned `Option` will always be `Some(_)`.
|
||||||
@@ -195,8 +205,7 @@ impl<'a> TemporalSerai<'a> {
|
|||||||
if events_mut.is_none() {
|
if events_mut.is_none() {
|
||||||
*events_mut = Some(
|
*events_mut = Some(
|
||||||
self
|
self
|
||||||
.serai
|
.call::<Vec<String>>("blockchain/events", "")
|
||||||
.call::<Vec<String>>("serai_events", &format!(r#"["{}"]"#, self.block))
|
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|event| {
|
.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};
|
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.
|
/// A `TemporalSerai` scoped to the validator sets module.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ValidatorSets<'a>(pub(super) &'a TemporalSerai<'a>);
|
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.
|
/// The `AcceptedHandover` events from the validator sets module.
|
||||||
pub async fn accepted_handover_events(&self) -> Result<Vec<Event>, RpcError> {
|
pub async fn accepted_handover_events(&self) -> Result<Vec<Event>, RpcError> {
|
||||||
Ok(
|
Ok(
|
||||||
@@ -47,4 +80,69 @@ impl<'a> ValidatorSets<'a> {
|
|||||||
.collect(),
|
.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::{
|
use serai_abi::{
|
||||||
primitives::{
|
primitives::{
|
||||||
network_id::{ExternalNetworkId, NetworkId},
|
network_id::{ExternalNetworkId, NetworkId},
|
||||||
validator_sets::{Session, ValidatorSet},
|
balance::Amount,
|
||||||
|
validator_sets::{Session, ExternalValidatorSet, ValidatorSet},
|
||||||
},
|
},
|
||||||
validator_sets::Event,
|
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;
|
.await;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,46 +15,7 @@ use serai_runtime::SeraiApi;
|
|||||||
|
|
||||||
use jsonrpsee::RpcModule;
|
use jsonrpsee::RpcModule;
|
||||||
|
|
||||||
fn block_hash<
|
use super::utils::block_hash;
|
||||||
C: HeaderMetadata<Block, Error = BlockchainError>
|
|
||||||
+ HeaderBackend<Block>
|
|
||||||
+ BlockBackend<Block>
|
|
||||||
+ ProvideRuntimeApi<Block>,
|
|
||||||
>(
|
|
||||||
client: &C,
|
|
||||||
params: &jsonrpsee::types::params::Params,
|
|
||||||
) -> Result<<Block as sp_runtime::traits::Block>::Hash, jsonrpsee::types::error::ErrorObjectOwned> {
|
|
||||||
Ok(if let Ok(block_hash) = params.sequence().next::<String>() {
|
|
||||||
let Some(block_hash) = hex::decode(&block_hash).ok().and_then(|bytes| {
|
|
||||||
<[u8; 32]>::try_from(bytes.as_slice())
|
|
||||||
.map(<Block as sp_runtime::traits::Block>::Hash::from)
|
|
||||||
.ok()
|
|
||||||
}) else {
|
|
||||||
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
|
|
||||||
-1,
|
|
||||||
"requested block hash wasn't a valid hash",
|
|
||||||
Option::<()>::None,
|
|
||||||
));
|
|
||||||
};
|
|
||||||
block_hash
|
|
||||||
} else {
|
|
||||||
let Ok(block_number) = params.sequence().next::<u64>() else {
|
|
||||||
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
|
|
||||||
-1,
|
|
||||||
"requested block wasn't a valid hash nor number",
|
|
||||||
Option::<()>::None,
|
|
||||||
));
|
|
||||||
};
|
|
||||||
let Ok(Some(block_hash)) = client.block_hash(block_number) else {
|
|
||||||
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
|
|
||||||
-2,
|
|
||||||
"couldn't find requested block's hash",
|
|
||||||
Option::<()>::None,
|
|
||||||
));
|
|
||||||
};
|
|
||||||
block_hash
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn module<
|
pub(crate) fn module<
|
||||||
C: 'static
|
C: 'static
|
||||||
@@ -69,11 +30,11 @@ pub(crate) fn module<
|
|||||||
) -> Result<RpcModule<impl 'static + Send + Sync>, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<RpcModule<impl 'static + Send + Sync>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let mut module = RpcModule::new(client);
|
let mut module = RpcModule::new(client);
|
||||||
|
|
||||||
module.register_method("serai_latestFinalizedBlockNumber", |_params, client, _ext| {
|
module.register_method("blockchain/latest_finalized_block_number", |_params, client, _ext| {
|
||||||
client.info().finalized_number
|
client.info().finalized_number
|
||||||
});
|
});
|
||||||
|
|
||||||
module.register_method("serai_isFinalized", |params, client, _ext| {
|
module.register_method("blockchain/is_finalized", |params, client, _ext| {
|
||||||
let block_hash = block_hash(&**client, ¶ms)?;
|
let block_hash = block_hash(&**client, ¶ms)?;
|
||||||
let finalized = client.info().finalized_number;
|
let finalized = client.info().finalized_number;
|
||||||
let Ok(Some(number)) = client.number(block_hash) else {
|
let Ok(Some(number)) = client.number(block_hash) else {
|
||||||
@@ -96,7 +57,7 @@ pub(crate) fn module<
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
module.register_method("serai_block", |params, client, _ext| {
|
module.register_method("blockchain/block", |params, client, _ext| {
|
||||||
let block_hash = block_hash(&**client, ¶ms)?;
|
let block_hash = block_hash(&**client, ¶ms)?;
|
||||||
let Ok(Some(block)) = client.block(block_hash) else {
|
let Ok(Some(block)) = client.block(block_hash) else {
|
||||||
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
|
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
|
||||||
@@ -109,7 +70,7 @@ pub(crate) fn module<
|
|||||||
Ok(hex::encode(borsh::to_vec(&serai_abi::Block::from(block.block)).unwrap()))
|
Ok(hex::encode(borsh::to_vec(&serai_abi::Block::from(block.block)).unwrap()))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
module.register_method("serai_events", |params, client, _ext| {
|
module.register_method("blockchain/events", |params, client, _ext| {
|
||||||
let block_hash = block_hash(&**client, ¶ms)?;
|
let block_hash = block_hash(&**client, ¶ms)?;
|
||||||
let Ok(events) = client.runtime_api().events(block_hash) else {
|
let Ok(events) = client.runtime_api().events(block_hash) else {
|
||||||
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
|
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ use jsonrpsee::RpcModule;
|
|||||||
use sc_client_api::BlockBackend;
|
use sc_client_api::BlockBackend;
|
||||||
use sc_transaction_pool_api::TransactionPool;
|
use sc_transaction_pool_api::TransactionPool;
|
||||||
|
|
||||||
|
mod utils;
|
||||||
mod blockchain;
|
mod blockchain;
|
||||||
|
mod validator_sets;
|
||||||
mod p2p_validators;
|
mod p2p_validators;
|
||||||
|
|
||||||
pub struct FullDeps<C, P> {
|
pub struct FullDeps<C, P> {
|
||||||
@@ -42,6 +44,7 @@ pub fn create_full<
|
|||||||
|
|
||||||
let mut root = RpcModule::new(());
|
let mut root = RpcModule::new(());
|
||||||
root.merge(blockchain::module(client.clone())?)?;
|
root.merge(blockchain::module(client.clone())?)?;
|
||||||
|
root.merge(validator_sets::module(client.clone()))?;
|
||||||
if let Some(authority_discovery) = authority_discovery {
|
if let Some(authority_discovery) = authority_discovery {
|
||||||
root.merge(p2p_validators::module(id, client, authority_discovery)?)?;
|
root.merge(p2p_validators::module(id, client, authority_discovery)?)?;
|
||||||
}
|
}
|
||||||
|
|||||||
53
substrate/node/src/rpc/utils.rs
Normal file
53
substrate/node/src/rpc/utils.rs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
use sp_blockchain::{Error as BlockchainError, HeaderMetadata, HeaderBackend};
|
||||||
|
use sc_client_api::BlockBackend;
|
||||||
|
|
||||||
|
use serai_abi::{primitives::prelude::*, SubstrateBlock as Block};
|
||||||
|
|
||||||
|
pub(super) fn block_hash<
|
||||||
|
C: HeaderMetadata<Block, Error = BlockchainError> + HeaderBackend<Block> + BlockBackend<Block>,
|
||||||
|
>(
|
||||||
|
client: &C,
|
||||||
|
params: &jsonrpsee::types::params::Params,
|
||||||
|
) -> Result<<Block as sp_runtime::traits::Block>::Hash, jsonrpsee::types::error::ErrorObjectOwned> {
|
||||||
|
#[derive(sp_core::serde::Deserialize)]
|
||||||
|
#[serde(crate = "sp_core::serde")]
|
||||||
|
struct BlockByHash {
|
||||||
|
block: String,
|
||||||
|
};
|
||||||
|
#[derive(sp_core::serde::Deserialize)]
|
||||||
|
#[serde(crate = "sp_core::serde")]
|
||||||
|
struct BlockByNumber {
|
||||||
|
block: u64,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(if let Ok(block_hash) = params.parse::<BlockByHash>() {
|
||||||
|
let Some(block_hash) = hex::decode(&block_hash.block).ok().and_then(|bytes| {
|
||||||
|
<[u8; 32]>::try_from(bytes.as_slice())
|
||||||
|
.map(<Block as sp_runtime::traits::Block>::Hash::from)
|
||||||
|
.ok()
|
||||||
|
}) else {
|
||||||
|
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
|
||||||
|
-1,
|
||||||
|
"requested block hash wasn't a valid hash",
|
||||||
|
Option::<()>::None,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
block_hash
|
||||||
|
} else {
|
||||||
|
let Ok(block_number) = params.parse::<BlockByNumber>() else {
|
||||||
|
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
|
||||||
|
-1,
|
||||||
|
"requested block wasn't a valid hash nor number",
|
||||||
|
Option::<()>::None,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
let Ok(Some(block_hash)) = client.block_hash(block_number.block) else {
|
||||||
|
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
|
||||||
|
-2,
|
||||||
|
"couldn't find requested block's hash",
|
||||||
|
Option::<()>::None,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
block_hash
|
||||||
|
})
|
||||||
|
}
|
||||||
143
substrate/node/src/rpc/validator_sets.rs
Normal file
143
substrate/node/src/rpc/validator_sets.rs
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
use std::{sync::Arc, ops::Deref, collections::HashSet};
|
||||||
|
|
||||||
|
use rand_core::{RngCore, OsRng};
|
||||||
|
|
||||||
|
use sp_core::Encode;
|
||||||
|
use sp_blockchain::{Error as BlockchainError, HeaderMetadata, HeaderBackend};
|
||||||
|
use sp_consensus::BlockStatus;
|
||||||
|
use sp_block_builder::BlockBuilder;
|
||||||
|
use sp_api::ProvideRuntimeApi;
|
||||||
|
use sc_client_api::BlockBackend;
|
||||||
|
|
||||||
|
use serai_abi::{primitives::prelude::*, SubstrateBlock as Block};
|
||||||
|
|
||||||
|
use serai_runtime::SeraiApi;
|
||||||
|
|
||||||
|
use jsonrpsee::RpcModule;
|
||||||
|
|
||||||
|
use super::utils::block_hash;
|
||||||
|
|
||||||
|
pub(super) fn network(
|
||||||
|
params: &jsonrpsee::types::params::Params,
|
||||||
|
) -> Result<NetworkId, jsonrpsee::types::error::ErrorObjectOwned> {
|
||||||
|
#[derive(sp_core::serde::Deserialize)]
|
||||||
|
#[serde(crate = "sp_core::serde")]
|
||||||
|
struct Network {
|
||||||
|
network: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
let Ok(network) = params.parse::<Network>() else {
|
||||||
|
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
|
||||||
|
-1,
|
||||||
|
r#"missing `string` "network" field"#,
|
||||||
|
Option::<()>::None,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(match network.network.to_lowercase().as_str() {
|
||||||
|
"serai" => NetworkId::Serai,
|
||||||
|
"bitcoin" => NetworkId::External(ExternalNetworkId::Bitcoin),
|
||||||
|
"ethereum" => NetworkId::External(ExternalNetworkId::Ethereum),
|
||||||
|
"monero" => NetworkId::External(ExternalNetworkId::Monero),
|
||||||
|
_ => Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
|
||||||
|
-1,
|
||||||
|
"unrecognized network requested",
|
||||||
|
Option::<()>::None,
|
||||||
|
))?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn set(
|
||||||
|
params: &jsonrpsee::types::params::Params,
|
||||||
|
) -> Result<ValidatorSet, jsonrpsee::types::error::ErrorObjectOwned> {
|
||||||
|
#[derive(sp_core::serde::Deserialize)]
|
||||||
|
#[serde(crate = "sp_core::serde")]
|
||||||
|
struct Set {
|
||||||
|
network: String,
|
||||||
|
session: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
let Ok(set) = params.parse::<Set>() else {
|
||||||
|
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
|
||||||
|
-1,
|
||||||
|
r#"missing `object` "set" field"#,
|
||||||
|
Option::<()>::None,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let network = match set.network.to_lowercase().as_str() {
|
||||||
|
"serai" => NetworkId::Serai,
|
||||||
|
"bitcoin" => NetworkId::External(ExternalNetworkId::Bitcoin),
|
||||||
|
"ethereum" => NetworkId::External(ExternalNetworkId::Ethereum),
|
||||||
|
"monero" => NetworkId::External(ExternalNetworkId::Monero),
|
||||||
|
_ => Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
|
||||||
|
-1,
|
||||||
|
"unrecognized network requested",
|
||||||
|
Option::<()>::None,
|
||||||
|
))?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ValidatorSet { network, session: Session(set.session) })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn module<
|
||||||
|
C: 'static
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ HeaderMetadata<Block, Error = BlockchainError>
|
||||||
|
+ HeaderBackend<Block>
|
||||||
|
+ BlockBackend<Block>
|
||||||
|
+ ProvideRuntimeApi<Block, Api: SeraiApi<Block>>,
|
||||||
|
>(
|
||||||
|
client: Arc<C>,
|
||||||
|
) -> RpcModule<impl 'static + Send + Sync> {
|
||||||
|
let mut module = RpcModule::new(client);
|
||||||
|
|
||||||
|
module.register_method("validator-sets/current_session", |params, client, _ext| {
|
||||||
|
let block_hash = block_hash(&**client, ¶ms)?;
|
||||||
|
let network = network(¶ms)?;
|
||||||
|
let Ok(session) = client.runtime_api().current_session(block_hash, network) else {
|
||||||
|
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
|
||||||
|
-2,
|
||||||
|
"couldn't fetch the session for the requested network",
|
||||||
|
Option::<()>::None,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
Ok(session.map(|session| session.0))
|
||||||
|
});
|
||||||
|
|
||||||
|
module.register_method("validator-sets/current_stake", |params, client, _ext| {
|
||||||
|
let block_hash = block_hash(&**client, ¶ms)?;
|
||||||
|
let network = network(¶ms)?;
|
||||||
|
let Ok(stake) = client.runtime_api().current_stake(block_hash, network) else {
|
||||||
|
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
|
||||||
|
-2,
|
||||||
|
"couldn't fetch the total allocated stake for the requested network",
|
||||||
|
Option::<()>::None,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
Ok(stake.map(|stake| stake.0))
|
||||||
|
});
|
||||||
|
|
||||||
|
module.register_method("validator-sets/keys", |params, client, _ext| {
|
||||||
|
let block_hash = block_hash(&**client, ¶ms)?;
|
||||||
|
let set = set(¶ms)?;
|
||||||
|
let Ok(set) = ExternalValidatorSet::try_from(set) else {
|
||||||
|
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
|
||||||
|
-1,
|
||||||
|
"requested keys for a non-extenral validator set",
|
||||||
|
Option::<()>::None,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
let Ok(key_pair) = client.runtime_api().keys(block_hash, set) else {
|
||||||
|
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
|
||||||
|
-2,
|
||||||
|
"couldn't fetch the keys for the requested validator set",
|
||||||
|
Option::<()>::None,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
Ok(hex::encode(borsh::to_vec(&key_pair).unwrap()))
|
||||||
|
});
|
||||||
|
|
||||||
|
module
|
||||||
|
}
|
||||||
@@ -6,9 +6,10 @@ extern crate alloc;
|
|||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use serai_abi::{
|
use serai_abi::{
|
||||||
primitives::{
|
primitives::{
|
||||||
crypto::{Public, SignedEmbeddedEllipticCurveKeys},
|
crypto::{Public, SignedEmbeddedEllipticCurveKeys, KeyPair},
|
||||||
network_id::NetworkId,
|
network_id::NetworkId,
|
||||||
balance::Balance,
|
validator_sets::{Session, ExternalValidatorSet, ValidatorSet},
|
||||||
|
balance::{Amount, Balance},
|
||||||
},
|
},
|
||||||
Event,
|
Event,
|
||||||
};
|
};
|
||||||
@@ -34,7 +35,10 @@ sp_api::decl_runtime_apis! {
|
|||||||
}
|
}
|
||||||
pub trait SeraiApi {
|
pub trait SeraiApi {
|
||||||
fn events() -> Vec<Vec<u8>>;
|
fn events() -> Vec<Vec<u8>>;
|
||||||
fn validators(network_id: NetworkId) -> Vec<Public>;
|
fn validators(network: NetworkId) -> Vec<Public>;
|
||||||
|
fn current_session(network: NetworkId) -> Option<Session>;
|
||||||
|
fn current_stake(network: NetworkId) -> Option<Amount>;
|
||||||
|
fn keys(set: ExternalValidatorSet) -> Option<KeyPair>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,6 +48,8 @@ mod apis {
|
|||||||
use alloc::borrow::Cow;
|
use alloc::borrow::Cow;
|
||||||
use serai_abi::{SubstrateHeader as Header, SubstrateBlock as Block};
|
use serai_abi::{SubstrateHeader as Header, SubstrateBlock as Block};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
#[sp_version::runtime_version]
|
#[sp_version::runtime_version]
|
||||||
pub const VERSION: sp_version::RuntimeVersion = sp_version::RuntimeVersion {
|
pub const VERSION: sp_version::RuntimeVersion = sp_version::RuntimeVersion {
|
||||||
spec_name: Cow::Borrowed("serai"),
|
spec_name: Cow::Borrowed("serai"),
|
||||||
@@ -181,6 +187,15 @@ mod apis {
|
|||||||
) -> Vec<serai_abi::primitives::crypto::Public> {
|
) -> Vec<serai_abi::primitives::crypto::Public> {
|
||||||
unimplemented!("runtime is only implemented when WASM")
|
unimplemented!("runtime is only implemented when WASM")
|
||||||
}
|
}
|
||||||
|
fn current_session(network: NetworkId) -> Option<Session> {
|
||||||
|
unimplemented!("runtime is only implemented when WASM")
|
||||||
|
}
|
||||||
|
fn current_stake(network: NetworkId) -> Option<Amount> {
|
||||||
|
unimplemented!("runtime is only implemented when WASM")
|
||||||
|
}
|
||||||
|
fn keys(set: ExternalValidatorSet) -> Option<KeyPair> {
|
||||||
|
unimplemented!("runtime is only implemented when WASM")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use serai_abi::{
|
|||||||
primitives::{
|
primitives::{
|
||||||
network_id::{ExternalNetworkId, NetworkId},
|
network_id::{ExternalNetworkId, NetworkId},
|
||||||
balance::{Amount, ExternalBalance},
|
balance::{Amount, ExternalBalance},
|
||||||
validator_sets::ValidatorSet,
|
validator_sets::{Session, ExternalValidatorSet, ValidatorSet},
|
||||||
address::SeraiAddress,
|
address::SeraiAddress,
|
||||||
},
|
},
|
||||||
SubstrateHeader as Header, SubstrateBlock,
|
SubstrateHeader as Header, SubstrateBlock,
|
||||||
@@ -522,6 +522,21 @@ sp_api::impl_runtime_apis! {
|
|||||||
.map(|validator| validator.0.into())
|
.map(|validator| validator.0.into())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
fn current_session(network: NetworkId) -> Option<Session> {
|
||||||
|
ValidatorSets::current_session(network)
|
||||||
|
}
|
||||||
|
fn current_stake(network: NetworkId) -> Option<Amount> {
|
||||||
|
ValidatorSets::stake_for_current_validator_set(network)
|
||||||
|
}
|
||||||
|
fn keys(set: ExternalValidatorSet) -> Option<serai_abi::primitives::crypto::KeyPair> {
|
||||||
|
ValidatorSets::oraclization_key(set)
|
||||||
|
.and_then(|oraclization_key| {
|
||||||
|
ValidatorSets::external_key(set)
|
||||||
|
.map(|external_key| {
|
||||||
|
serai_abi::primitives::crypto::KeyPair(oraclization_key.into(), external_key)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ pub(crate) trait Keys {
|
|||||||
|
|
||||||
/// The oraclization key for a validator set.
|
/// The oraclization key for a validator set.
|
||||||
fn oraclization_key(set: ExternalValidatorSet) -> Option<Public>;
|
fn oraclization_key(set: ExternalValidatorSet) -> Option<Public>;
|
||||||
|
|
||||||
|
/// The external key for a validator set.
|
||||||
|
fn external_key(set: ExternalValidatorSet) -> Option<ExternalKey>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: KeysStorage> Keys for S {
|
impl<S: KeysStorage> Keys for S {
|
||||||
@@ -53,4 +56,8 @@ impl<S: KeysStorage> Keys for S {
|
|||||||
fn oraclization_key(set: ExternalValidatorSet) -> Option<Public> {
|
fn oraclization_key(set: ExternalValidatorSet) -> Option<Public> {
|
||||||
S::OraclizationKeys::get(set)
|
S::OraclizationKeys::get(set)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn external_key(set: ExternalValidatorSet) -> Option<ExternalKey> {
|
||||||
|
S::ExternalKeys::get(set)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -307,6 +307,14 @@ mod pallet {
|
|||||||
Abstractions::<T>::selected_validators(set)
|
Abstractions::<T>::selected_validators(set)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn oraclization_key(set: ExternalValidatorSet) -> Option<Public> {
|
||||||
|
Abstractions::<T>::oraclization_key(set)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn external_key(set: ExternalValidatorSet) -> Option<ExternalKey> {
|
||||||
|
Abstractions::<T>::external_key(set)
|
||||||
|
}
|
||||||
|
|
||||||
/* TODO
|
/* TODO
|
||||||
pub fn distribute_block_rewards(
|
pub fn distribute_block_rewards(
|
||||||
network: NetworkId,
|
network: NetworkId,
|
||||||
|
|||||||
Reference in New Issue
Block a user