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

View File

@@ -13,6 +13,8 @@ use tokio::sync::RwLock;
use jsonrpsee::RpcModule;
use super::utils::Error;
pub(crate) fn module<
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>> {
let mut module = RpcModule::new((id, client, RwLock::new(authority_discovery)));
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() {
"serai" => NetworkId::Serai,
"bitcoin" => ExternalNetworkId::Bitcoin.into(),
"ethereum" => ExternalNetworkId::Ethereum.into(),
"monero" => ExternalNetworkId::Monero.into(),
_ => Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
-1,
"network to fetch the `p2p_validators` of was unrecognized".to_string(),
Option::<()>::None,
))?,
_ => Err(Error::InvalidRequest("network to fetch the `p2p_validators` of was unrecognized"))?,
};
let (id, client, authority_discovery) = &*context;
let latest_block = client.info().best_hash;
let validators = client.runtime_api().validators(latest_block, network).map_err(|_| {
jsonrpsee::types::error::ErrorObjectOwned::owned(
-2,
format!(
"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 = client
.runtime_api()
.validators(latest_block, network)
.map_err(|_| Error::Internal("couldn't get validators from the latest block"));
let validators = match validators {
Ok(validators) => validators,
Err(e) => return Err(e),
Err(e) => Err(e)?,
};
// Always return the protocol's bootnodes
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};
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<
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> {
) -> Result<<Block as sp_runtime::traits::Block>::Hash, Error> {
#[derive(sp_core::serde::Deserialize)]
#[serde(crate = "sp_core::serde")]
struct BlockByHash {
@@ -26,27 +48,15 @@ pub(super) fn block_hash<
.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,
));
return Err(Error::InvalidRequest("requested block hash wasn't a valid hash"));
};
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,
));
return Err(Error::InvalidRequest("requested block wasn't a valid hash nor number"));
};
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,
));
return Err(Error::Missing("no block hash for that block number"));
};
block_hash
})

View File

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