Type the errors yielded by serai-node's RPC

This commit is contained in:
Luke Parker
2025-11-14 03:52:35 -05:00
parent f9e3d1b142
commit 6ff0ef7aa6
4 changed files with 93 additions and 130 deletions

View File

@@ -15,7 +15,7 @@ use serai_runtime::SeraiApi;
use jsonrpsee::RpcModule; use jsonrpsee::RpcModule;
use super::utils::block_hash; use super::utils::{Error, block_hash};
pub(crate) fn module< pub(crate) fn module<
C: 'static C: 'static
@@ -34,50 +34,37 @@ pub(crate) fn module<
client.info().finalized_number client.info().finalized_number
}); });
module.register_method("blockchain/is_finalized", |params, client, _ext| { module.register_method(
"blockchain/is_finalized",
|params, client, _ext| -> Result<_, Error> {
let block_hash = block_hash(&**client, &params)?; let block_hash = block_hash(&**client, &params)?;
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 {
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned( Err(Error::Missing("failed to fetch block's number"))?
-2,
"failed to fetch block's number",
Option::<()>::None,
));
}; };
let Ok(status) = client.block_status(block_hash) else { let Ok(status) = client.block_status(block_hash) else {
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned( Err(Error::Internal("failed to fetch block's status"))?
-3,
"failed to fetch block's status",
Option::<()>::None,
));
}; };
Ok( Ok(
matches!(status, BlockStatus::InChainWithState | BlockStatus::InChainPruned) && matches!(status, BlockStatus::InChainWithState | BlockStatus::InChainPruned) &&
(number <= finalized), (number <= finalized),
) )
})?; },
)?;
module.register_method("blockchain/block", |params, client, _ext| { module.register_method("blockchain/block", |params, client, _ext| -> Result<_, Error> {
let block_hash = block_hash(&**client, &params)?; let block_hash = block_hash(&**client, &params)?;
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( Err(Error::Missing("couldn't find requested block"))?
-2,
"couldn't find requested block",
Option::<()>::None,
));
}; };
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("blockchain/events", |params, client, _ext| { module.register_method("blockchain/events", |params, client, _ext| -> Result<_, Error> {
let block_hash = block_hash(&**client, &params)?; let block_hash = block_hash(&**client, &params)?;
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( Err(Error::Missing("couldn't fetch the events for the requested block"))?
-2,
"couldn't fetch the events for the requested block",
Option::<()>::None,
));
}; };
Ok(events.into_iter().map(hex::encode).collect::<Vec<String>>()) Ok(events.into_iter().map(hex::encode).collect::<Vec<String>>())
})?; })?;

View File

@@ -13,6 +13,8 @@ use tokio::sync::RwLock;
use jsonrpsee::RpcModule; use jsonrpsee::RpcModule;
use super::utils::Error;
pub(crate) fn module< pub(crate) fn module<
C: 'static + Send + Sync + HeaderBackend<Block> + ProvideRuntimeApi<Block, Api: SeraiApi<Block>>, C: 'static + Send + Sync + HeaderBackend<Block> + ProvideRuntimeApi<Block, Api: SeraiApi<Block>>,
>( >(
@@ -22,34 +24,28 @@ 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((id, client, RwLock::new(authority_discovery))); let mut module = RpcModule::new((id, client, RwLock::new(authority_discovery)));
module.register_async_method("p2p_validators", |params, context, _ext| async move { module.register_async_method("p2p_validators", |params, context, _ext| async move {
let [network]: [String; 1] = params.parse()?; let network = match params.parse::<[String; 1]>() {
Ok([network]) => network,
Err(e) => return Err(e),
};
let network = match network.to_lowercase().as_str() { let network = match network.to_lowercase().as_str() {
"serai" => NetworkId::Serai, "serai" => NetworkId::Serai,
"bitcoin" => ExternalNetworkId::Bitcoin.into(), "bitcoin" => ExternalNetworkId::Bitcoin.into(),
"ethereum" => ExternalNetworkId::Ethereum.into(), "ethereum" => ExternalNetworkId::Ethereum.into(),
"monero" => ExternalNetworkId::Monero.into(), "monero" => ExternalNetworkId::Monero.into(),
_ => Err(jsonrpsee::types::error::ErrorObjectOwned::owned( _ => Err(Error::InvalidRequest("network to fetch the `p2p_validators` of was unrecognized"))?,
-1,
"network to fetch the `p2p_validators` of was unrecognized".to_string(),
Option::<()>::None,
))?,
}; };
let (id, client, authority_discovery) = &*context; let (id, client, authority_discovery) = &*context;
let latest_block = client.info().best_hash; let latest_block = client.info().best_hash;
let validators = client.runtime_api().validators(latest_block, network).map_err(|_| { let validators = client
jsonrpsee::types::error::ErrorObjectOwned::owned( .runtime_api()
-2, .validators(latest_block, network)
format!( .map_err(|_| Error::Internal("couldn't get validators from the latest block"));
"couldn't get validators from the latest block, which is likely a fatal bug. {}",
"please report this at https://github.com/serai-dex/serai",
),
Option::<()>::None,
)
});
let validators = match validators { let validators = match validators {
Ok(validators) => validators, Ok(validators) => validators,
Err(e) => return Err(e), Err(e) => Err(e)?,
}; };
// Always return the protocol's bootnodes // Always return the protocol's bootnodes
let mut all_p2p_addresses = crate::chain_spec::bootnode_multiaddrs(id); let mut all_p2p_addresses = crate::chain_spec::bootnode_multiaddrs(id);

View File

@@ -3,12 +3,34 @@ use sc_client_api::BlockBackend;
use serai_abi::{primitives::prelude::*, SubstrateBlock as Block}; use serai_abi::{primitives::prelude::*, SubstrateBlock as Block};
pub(super) enum Error {
Internal(&'static str),
InvalidRequest(&'static str),
Missing(&'static str),
}
impl From<Error> for jsonrpsee::types::error::ErrorObjectOwned {
fn from(error: Error) -> Self {
match error {
Error::Internal(str) => {
jsonrpsee::types::error::ErrorObjectOwned::owned(-1, str, Option::<()>::None)
}
Error::InvalidRequest(str) => {
jsonrpsee::types::error::ErrorObjectOwned::owned(-2, str, Option::<()>::None)
}
Error::Missing(str) => {
jsonrpsee::types::error::ErrorObjectOwned::owned(-3, str, Option::<()>::None)
}
}
}
}
pub(super) fn block_hash< pub(super) fn block_hash<
C: HeaderMetadata<Block, Error = BlockchainError> + HeaderBackend<Block> + BlockBackend<Block>, C: HeaderMetadata<Block, Error = BlockchainError> + HeaderBackend<Block> + BlockBackend<Block>,
>( >(
client: &C, client: &C,
params: &jsonrpsee::types::params::Params, params: &jsonrpsee::types::params::Params,
) -> Result<<Block as sp_runtime::traits::Block>::Hash, jsonrpsee::types::error::ErrorObjectOwned> { ) -> Result<<Block as sp_runtime::traits::Block>::Hash, Error> {
#[derive(sp_core::serde::Deserialize)] #[derive(sp_core::serde::Deserialize)]
#[serde(crate = "sp_core::serde")] #[serde(crate = "sp_core::serde")]
struct BlockByHash { struct BlockByHash {
@@ -26,27 +48,15 @@ pub(super) fn block_hash<
.map(<Block as sp_runtime::traits::Block>::Hash::from) .map(<Block as sp_runtime::traits::Block>::Hash::from)
.ok() .ok()
}) else { }) else {
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned( return Err(Error::InvalidRequest("requested block hash wasn't a valid hash"));
-1,
"requested block hash wasn't a valid hash",
Option::<()>::None,
));
}; };
block_hash block_hash
} else { } else {
let Ok(block_number) = params.parse::<BlockByNumber>() else { let Ok(block_number) = params.parse::<BlockByNumber>() else {
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned( return Err(Error::InvalidRequest("requested block wasn't a valid hash nor number"));
-1,
"requested block wasn't a valid hash nor number",
Option::<()>::None,
));
}; };
let Ok(Some(block_hash)) = client.block_hash(block_number.block) else { let Ok(Some(block_hash)) = client.block_hash(block_number.block) else {
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned( return Err(Error::Missing("no block hash for that block number"));
-2,
"couldn't find requested block's hash",
Option::<()>::None,
));
}; };
block_hash block_hash
}) })

View File

@@ -15,11 +15,9 @@ use serai_runtime::SeraiApi;
use jsonrpsee::RpcModule; use jsonrpsee::RpcModule;
use super::utils::block_hash; use super::utils::{Error, block_hash};
pub(super) fn network( pub(super) fn network(params: &jsonrpsee::types::params::Params) -> Result<NetworkId, Error> {
params: &jsonrpsee::types::params::Params,
) -> Result<NetworkId, jsonrpsee::types::error::ErrorObjectOwned> {
#[derive(sp_core::serde::Deserialize)] #[derive(sp_core::serde::Deserialize)]
#[serde(crate = "sp_core::serde")] #[serde(crate = "sp_core::serde")]
struct Network { struct Network {
@@ -27,11 +25,7 @@ pub(super) fn network(
} }
let Ok(network) = params.parse::<Network>() else { let Ok(network) = params.parse::<Network>() else {
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned( Err(Error::InvalidRequest(r#"missing `string` "network" field"#))?
-1,
r#"missing `string` "network" field"#,
Option::<()>::None,
));
}; };
Ok(match network.network.to_lowercase().as_str() { Ok(match network.network.to_lowercase().as_str() {
@@ -39,17 +33,11 @@ pub(super) fn network(
"bitcoin" => NetworkId::External(ExternalNetworkId::Bitcoin), "bitcoin" => NetworkId::External(ExternalNetworkId::Bitcoin),
"ethereum" => NetworkId::External(ExternalNetworkId::Ethereum), "ethereum" => NetworkId::External(ExternalNetworkId::Ethereum),
"monero" => NetworkId::External(ExternalNetworkId::Monero), "monero" => NetworkId::External(ExternalNetworkId::Monero),
_ => Err(jsonrpsee::types::error::ErrorObjectOwned::owned( _ => Err(Error::InvalidRequest("unrecognized network requested"))?,
-1,
"unrecognized network requested",
Option::<()>::None,
))?,
}) })
} }
pub(super) fn set( pub(super) fn set(params: &jsonrpsee::types::params::Params) -> Result<ValidatorSet, Error> {
params: &jsonrpsee::types::params::Params,
) -> Result<ValidatorSet, jsonrpsee::types::error::ErrorObjectOwned> {
#[derive(sp_core::serde::Deserialize)] #[derive(sp_core::serde::Deserialize)]
#[serde(crate = "sp_core::serde")] #[serde(crate = "sp_core::serde")]
struct Set { struct Set {
@@ -58,11 +46,7 @@ pub(super) fn set(
} }
let Ok(set) = params.parse::<Set>() else { let Ok(set) = params.parse::<Set>() else {
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned( Err(Error::InvalidRequest(r#"missing `object` "set" field"#))?
-1,
r#"missing `object` "set" field"#,
Option::<()>::None,
));
}; };
let network = match set.network.to_lowercase().as_str() { let network = match set.network.to_lowercase().as_str() {
@@ -70,11 +54,7 @@ pub(super) fn set(
"bitcoin" => NetworkId::External(ExternalNetworkId::Bitcoin), "bitcoin" => NetworkId::External(ExternalNetworkId::Bitcoin),
"ethereum" => NetworkId::External(ExternalNetworkId::Ethereum), "ethereum" => NetworkId::External(ExternalNetworkId::Ethereum),
"monero" => NetworkId::External(ExternalNetworkId::Monero), "monero" => NetworkId::External(ExternalNetworkId::Monero),
_ => Err(jsonrpsee::types::error::ErrorObjectOwned::owned( _ => Err(Error::InvalidRequest("unrecognized network requested"))?,
-1,
"unrecognized network requested",
Option::<()>::None,
))?,
}; };
Ok(ValidatorSet { network, session: Session(set.session) }) Ok(ValidatorSet { network, session: Session(set.session) })
@@ -93,48 +73,38 @@ pub(crate) fn module<
) -> RpcModule<impl 'static + Send + Sync> { ) -> RpcModule<impl 'static + Send + Sync> {
let mut module = RpcModule::new(client); let mut module = RpcModule::new(client);
module.register_method("validator-sets/current_session", |params, client, _ext| { module.register_method(
"validator-sets/current_session",
|params, client, _ext| -> Result<_, Error> {
let block_hash = block_hash(&**client, &params)?; let block_hash = block_hash(&**client, &params)?;
let network = network(&params)?; let network = network(&params)?;
let Ok(session) = client.runtime_api().current_session(block_hash, network) else { let Ok(session) = client.runtime_api().current_session(block_hash, network) else {
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned( Err(Error::Internal("couldn't fetch the session for the requested network"))?
-2,
"couldn't fetch the session for the requested network",
Option::<()>::None,
));
}; };
Ok(session.map(|session| session.0)) Ok(session.map(|session| session.0))
}); },
);
module.register_method("validator-sets/current_stake", |params, client, _ext| { module.register_method(
"validator-sets/current_stake",
|params, client, _ext| -> Result<_, Error> {
let block_hash = block_hash(&**client, &params)?; let block_hash = block_hash(&**client, &params)?;
let network = network(&params)?; let network = network(&params)?;
let Ok(stake) = client.runtime_api().current_stake(block_hash, network) else { let Ok(stake) = client.runtime_api().current_stake(block_hash, network) else {
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned( Err(Error::Internal("couldn't fetch the total allocated stake for the requested network"))?
-2,
"couldn't fetch the total allocated stake for the requested network",
Option::<()>::None,
));
}; };
Ok(stake.map(|stake| stake.0)) Ok(stake.map(|stake| stake.0))
}); },
);
module.register_method("validator-sets/keys", |params, client, _ext| { module.register_method("validator-sets/keys", |params, client, _ext| -> Result<_, Error> {
let block_hash = block_hash(&**client, &params)?; let block_hash = block_hash(&**client, &params)?;
let set = set(&params)?; let set = set(&params)?;
let Ok(set) = ExternalValidatorSet::try_from(set) else { let Ok(set) = ExternalValidatorSet::try_from(set) else {
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned( Err(Error::InvalidRequest("requested keys for a non-external validator set"))?
-1,
"requested keys for a non-extenral validator set",
Option::<()>::None,
));
}; };
let Ok(key_pair) = client.runtime_api().keys(block_hash, set) else { let Ok(key_pair) = client.runtime_api().keys(block_hash, set) else {
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned( Err(Error::Internal("couldn't fetch the keys for the requested validator set"))?
-2,
"couldn't fetch the keys for the requested validator set",
Option::<()>::None,
));
}; };
Ok(hex::encode(borsh::to_vec(&key_pair).unwrap())) Ok(hex::encode(borsh::to_vec(&key_pair).unwrap()))
}); });