mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-09 04:39: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:
@@ -15,46 +15,7 @@ use serai_runtime::SeraiApi;
|
||||
|
||||
use jsonrpsee::RpcModule;
|
||||
|
||||
fn 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
|
||||
})
|
||||
}
|
||||
use super::utils::block_hash;
|
||||
|
||||
pub(crate) fn module<
|
||||
C: 'static
|
||||
@@ -69,11 +30,11 @@ pub(crate) fn module<
|
||||
) -> Result<RpcModule<impl 'static + Send + Sync>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
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
|
||||
});
|
||||
|
||||
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 finalized = client.info().finalized_number;
|
||||
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 Ok(Some(block)) = client.block(block_hash) else {
|
||||
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()))
|
||||
})?;
|
||||
|
||||
module.register_method("serai_events", |params, client, _ext| {
|
||||
module.register_method("blockchain/events", |params, client, _ext| {
|
||||
let block_hash = block_hash(&**client, ¶ms)?;
|
||||
let Ok(events) = client.runtime_api().events(block_hash) else {
|
||||
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
|
||||
|
||||
@@ -16,7 +16,9 @@ use jsonrpsee::RpcModule;
|
||||
use sc_client_api::BlockBackend;
|
||||
use sc_transaction_pool_api::TransactionPool;
|
||||
|
||||
mod utils;
|
||||
mod blockchain;
|
||||
mod validator_sets;
|
||||
mod p2p_validators;
|
||||
|
||||
pub struct FullDeps<C, P> {
|
||||
@@ -42,6 +44,7 @@ pub fn create_full<
|
||||
|
||||
let mut root = RpcModule::new(());
|
||||
root.merge(blockchain::module(client.clone())?)?;
|
||||
root.merge(validator_sets::module(client.clone()))?;
|
||||
if let Some(authority_discovery) = 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
|
||||
}
|
||||
Reference in New Issue
Block a user