refactor: use wildcard paths, remove dup handlers
We use axum route wildcards to merge duplicate page routes and handlers
into a single route and handler.
This makes the routes simpler/cleaner as well as the handlers, as there
is now just one of each for each html page.
Previous:
.route("/block/:selector", get(block_page))
.route("/block/:selector/value", get(block_page_with_value))
New:
.route("/block/*selector", get(block_page))
This is achieved by replacing PathBlockSelector with
BlockSelectorExtended which wraps BlockSelector to provide parsing for
height_or_digest/value. (which is needed for javascript-free input
form)
Changes:
* merge dup handlers for each of block, block_digest, block_info
* merge routes for same
* add BlockSelectorExtended and HeightOrDigest
* remove PathBlockSelector
This commit is contained in:
parent
6fedadab47
commit
0c54b50b83
@ -1,7 +1,7 @@
|
|||||||
use crate::html::component::header::HeaderHtml;
|
use crate::html::component::header::HeaderHtml;
|
||||||
use crate::html::page::not_found::not_found_html_response;
|
use crate::html::page::not_found::not_found_html_response;
|
||||||
use crate::model::app_state::AppState;
|
use crate::model::app_state::AppState;
|
||||||
use crate::model::path_block_selector::PathBlockSelector;
|
use crate::model::block_selector_extended::BlockSelectorExtended;
|
||||||
use axum::extract::rejection::PathRejection;
|
use axum::extract::rejection::PathRejection;
|
||||||
use axum::extract::Path;
|
use axum::extract::Path;
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
@ -13,20 +13,9 @@ use neptune_core::models::blockchain::block::block_info::BlockInfo;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tarpc::context;
|
use tarpc::context;
|
||||||
|
|
||||||
pub async fn block_page(
|
|
||||||
user_input_maybe: Result<Path<PathBlockSelector>, PathRejection>,
|
|
||||||
state: State<Arc<AppState>>,
|
|
||||||
) -> Result<Html<String>, Response> {
|
|
||||||
let Path(path_block_selector) = user_input_maybe
|
|
||||||
.map_err(|e| not_found_html_response(state.clone(), Some(e.to_string())))?;
|
|
||||||
|
|
||||||
let value_path: Path<(PathBlockSelector, String)> = Path((path_block_selector, "".to_string()));
|
|
||||||
block_page_with_value(Ok(value_path), state).await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[axum::debug_handler]
|
#[axum::debug_handler]
|
||||||
pub async fn block_page_with_value(
|
pub async fn block_page(
|
||||||
user_input_maybe: Result<Path<(PathBlockSelector, String)>, PathRejection>,
|
user_input_maybe: Result<Path<BlockSelectorExtended>, PathRejection>,
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
) -> Result<Html<String>, Response> {
|
) -> Result<Html<String>, Response> {
|
||||||
#[derive(boilerplate::Boilerplate)]
|
#[derive(boilerplate::Boilerplate)]
|
||||||
@ -36,21 +25,17 @@ pub async fn block_page_with_value(
|
|||||||
block_info: BlockInfo,
|
block_info: BlockInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
let Path((path_block_selector, value)) = user_input_maybe
|
let Path(block_selector) = user_input_maybe
|
||||||
.map_err(|e| not_found_html_response(State(state.clone()), Some(e.to_string())))?;
|
.map_err(|e| not_found_html_response(State(state.clone()), Some(e.to_string())))?;
|
||||||
|
|
||||||
let header = HeaderHtml {
|
let header = HeaderHtml {
|
||||||
state: state.clone(),
|
state: state.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let block_selector = path_block_selector
|
|
||||||
.as_block_selector(&value)
|
|
||||||
.map_err(|e| not_found_html_response(State(state.clone()), Some(e.to_string())))?;
|
|
||||||
|
|
||||||
let block_info = match state
|
let block_info = match state
|
||||||
.clone()
|
.clone()
|
||||||
.rpc_client
|
.rpc_client
|
||||||
.block_info(context::current(), block_selector)
|
.block_info(context::current(), block_selector.into())
|
||||||
.await
|
.await
|
||||||
.map_err(|e| not_found_html_response(State(state.clone()), Some(e.to_string())))?
|
.map_err(|e| not_found_html_response(State(state.clone()), Some(e.to_string())))?
|
||||||
{
|
{
|
||||||
|
|||||||
18
src/main.rs
18
src/main.rs
@ -4,7 +4,6 @@ use clap::Parser;
|
|||||||
use neptune_core::models::blockchain::block::block_selector::BlockSelector;
|
use neptune_core::models::blockchain::block::block_selector::BlockSelector;
|
||||||
use neptune_core::rpc_server::RPCClient;
|
use neptune_core::rpc_server::RPCClient;
|
||||||
use neptune_explorer::html::page::block::block_page;
|
use neptune_explorer::html::page::block::block_page;
|
||||||
use neptune_explorer::html::page::block::block_page_with_value;
|
|
||||||
use neptune_explorer::html::page::not_found::not_found_html_fallback;
|
use neptune_explorer::html::page::not_found::not_found_html_fallback;
|
||||||
use neptune_explorer::html::page::redirect_qs_to_path::redirect_query_string_to_path;
|
use neptune_explorer::html::page::redirect_qs_to_path::redirect_query_string_to_path;
|
||||||
use neptune_explorer::html::page::root::root;
|
use neptune_explorer::html::page::root::root;
|
||||||
@ -12,9 +11,7 @@ use neptune_explorer::html::page::utxo::utxo_page;
|
|||||||
use neptune_explorer::model::app_state::AppState;
|
use neptune_explorer::model::app_state::AppState;
|
||||||
use neptune_explorer::model::config::Config;
|
use neptune_explorer::model::config::Config;
|
||||||
use neptune_explorer::rpc::block_digest::block_digest;
|
use neptune_explorer::rpc::block_digest::block_digest;
|
||||||
use neptune_explorer::rpc::block_digest::block_digest_with_value;
|
|
||||||
use neptune_explorer::rpc::block_info::block_info;
|
use neptune_explorer::rpc::block_info::block_info;
|
||||||
use neptune_explorer::rpc::block_info::block_info_with_value;
|
|
||||||
use neptune_explorer::rpc::utxo_digest::utxo_digest;
|
use neptune_explorer::rpc::utxo_digest::utxo_digest;
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
@ -43,21 +40,12 @@ async fn main() -> Result<(), RpcError> {
|
|||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
// -- RPC calls --
|
// -- RPC calls --
|
||||||
.route("/rpc/block_info/:selector", get(block_info))
|
.route("/rpc/block_info/*selector", get(block_info))
|
||||||
.route(
|
.route("/rpc/block_digest/*selector", get(block_digest))
|
||||||
"/rpc/block_info/:selector/:value",
|
|
||||||
get(block_info_with_value),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
"/rpc/block_digest/:selector/:value",
|
|
||||||
get(block_digest_with_value),
|
|
||||||
)
|
|
||||||
.route("/rpc/block_digest/:selector", get(block_digest))
|
|
||||||
.route("/rpc/utxo_digest/:index", get(utxo_digest))
|
.route("/rpc/utxo_digest/:index", get(utxo_digest))
|
||||||
// -- Dynamic HTML pages --
|
// -- Dynamic HTML pages --
|
||||||
.route("/", get(root))
|
.route("/", get(root))
|
||||||
.route("/block/:selector", get(block_page))
|
.route("/block/*selector", get(block_page))
|
||||||
.route("/block/:selector/:value", get(block_page_with_value))
|
|
||||||
.route("/utxo/:value", get(utxo_page))
|
.route("/utxo/:value", get(utxo_page))
|
||||||
// -- Rewrite query-strings to path --
|
// -- Rewrite query-strings to path --
|
||||||
.route("/rqs", get(redirect_query_string_to_path))
|
.route("/rqs", get(redirect_query_string_to_path))
|
||||||
|
|||||||
71
src/model/block_selector_extended.rs
Normal file
71
src/model/block_selector_extended.rs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
use super::height_or_digest::HeightOrDigest;
|
||||||
|
use neptune_core::models::blockchain::block::block_selector::BlockSelector;
|
||||||
|
use neptune_core::models::blockchain::block::block_selector::BlockSelectorParseError;
|
||||||
|
use serde::de::Error;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Deserializer;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
/// newtype for `BlockSelector` that provides ability to parse `height_or_digest/value`.
|
||||||
|
///
|
||||||
|
/// This is useful for HTML form(s) that allow user to enter either height or
|
||||||
|
/// digest into the same text input field.
|
||||||
|
///
|
||||||
|
/// In particular it is necessary to support javascript-free website with such
|
||||||
|
/// an html form.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct BlockSelectorExtended(BlockSelector);
|
||||||
|
|
||||||
|
impl std::fmt::Display for BlockSelectorExtended {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for BlockSelectorExtended {
|
||||||
|
type Err = BlockSelectorParseError;
|
||||||
|
|
||||||
|
// note: this parses BlockSelector, plus height_or_digest/<value>
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match BlockSelector::from_str(s) {
|
||||||
|
Ok(bs) => Ok(Self::from(bs)),
|
||||||
|
Err(e) => {
|
||||||
|
let parts: Vec<_> = s.split('/').collect();
|
||||||
|
if parts.len() == 2 && parts[0] == "height_or_digest" {
|
||||||
|
Ok(Self::from(HeightOrDigest::from_str(parts[1])?))
|
||||||
|
} else {
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// note: axum uses serde Deserialize for Path elements.
|
||||||
|
impl<'de> Deserialize<'de> for BlockSelectorExtended {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s = String::deserialize(deserializer)?;
|
||||||
|
Self::from_str(&s).map_err(D::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<HeightOrDigest> for BlockSelectorExtended {
|
||||||
|
fn from(hd: HeightOrDigest) -> Self {
|
||||||
|
Self(hd.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BlockSelector> for BlockSelectorExtended {
|
||||||
|
fn from(v: BlockSelector) -> Self {
|
||||||
|
Self(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BlockSelectorExtended> for BlockSelector {
|
||||||
|
fn from(v: BlockSelectorExtended) -> Self {
|
||||||
|
v.0
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/model/height_or_digest.rs
Normal file
47
src/model/height_or_digest.rs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
use neptune_core::models::blockchain::block::block_height::BlockHeight;
|
||||||
|
use neptune_core::models::blockchain::block::block_selector::BlockSelector;
|
||||||
|
use neptune_core::models::blockchain::block::block_selector::BlockSelectorParseError;
|
||||||
|
use neptune_core::prelude::tasm_lib::Digest;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
/// represents either a block-height or a block digest
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum HeightOrDigest {
|
||||||
|
/// Identifies block by Digest (hash)
|
||||||
|
Digest(Digest),
|
||||||
|
/// Identifies block by Height (count from genesis)
|
||||||
|
Height(BlockHeight),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for HeightOrDigest {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Digest(d) => write!(f, "{}", d),
|
||||||
|
Self::Height(h) => write!(f, "{}", h),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for HeightOrDigest {
|
||||||
|
type Err = BlockSelectorParseError;
|
||||||
|
|
||||||
|
// note: this parses the output of impl Display for HeightOrDigest
|
||||||
|
// note: this is used by clap parser in neptune-cli for block-info command
|
||||||
|
// and probably future commands as well.
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(match s.parse::<u64>() {
|
||||||
|
Ok(h) => Self::Height(h.into()),
|
||||||
|
Err(_) => Self::Digest(Digest::try_from_hex(s)?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<HeightOrDigest> for BlockSelector {
|
||||||
|
fn from(hd: HeightOrDigest) -> Self {
|
||||||
|
match hd {
|
||||||
|
HeightOrDigest::Height(h) => Self::Height(h),
|
||||||
|
HeightOrDigest::Digest(d) => Self::Digest(d),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
pub mod app_state;
|
pub mod app_state;
|
||||||
|
pub mod block_selector_extended;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod path_block_selector;
|
pub mod height_or_digest;
|
||||||
|
|||||||
@ -1,73 +0,0 @@
|
|||||||
use axum::http::StatusCode;
|
|
||||||
use axum::response::IntoResponse;
|
|
||||||
use axum::response::Response;
|
|
||||||
use neptune_core::models::blockchain::block::block_height::BlockHeight;
|
|
||||||
use neptune_core::models::blockchain::block::block_selector::BlockSelector;
|
|
||||||
use neptune_core::prelude::tasm_lib::Digest;
|
|
||||||
use neptune_core::prelude::twenty_first::error::TryFromHexDigestError;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
pub enum PathBlockSelector {
|
|
||||||
#[serde(rename = "genesis")]
|
|
||||||
Genesis,
|
|
||||||
#[serde(rename = "tip")]
|
|
||||||
Tip,
|
|
||||||
#[serde(rename = "digest")]
|
|
||||||
Digest,
|
|
||||||
#[serde(rename = "height")]
|
|
||||||
Height,
|
|
||||||
#[serde(rename = "height_or_digest")]
|
|
||||||
HeightOrDigest,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum PathBlockSelectorError {
|
|
||||||
#[error("Genesis does not accept an argument")]
|
|
||||||
GenesisNoArg,
|
|
||||||
|
|
||||||
#[error("Tip does not accept an argument")]
|
|
||||||
TipNoArg,
|
|
||||||
|
|
||||||
#[error("Digest could not be parsed")]
|
|
||||||
DigestNotParsed(#[from] TryFromHexDigestError),
|
|
||||||
|
|
||||||
#[error("Height could not be parsed")]
|
|
||||||
HeightNotParsed(#[from] std::num::ParseIntError),
|
|
||||||
}
|
|
||||||
impl PathBlockSelectorError {
|
|
||||||
fn as_response_tuple(&self) -> (StatusCode, String) {
|
|
||||||
(StatusCode::NOT_FOUND, self.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl IntoResponse for PathBlockSelectorError {
|
|
||||||
fn into_response(self) -> Response {
|
|
||||||
self.as_response_tuple().into_response()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<PathBlockSelectorError> for Response {
|
|
||||||
fn from(e: PathBlockSelectorError) -> Response {
|
|
||||||
e.as_response_tuple().into_response()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl PathBlockSelector {
|
|
||||||
pub fn as_block_selector(&self, value: &str) -> Result<BlockSelector, PathBlockSelectorError> {
|
|
||||||
match self {
|
|
||||||
PathBlockSelector::Genesis if !value.is_empty() => {
|
|
||||||
Err(PathBlockSelectorError::GenesisNoArg)
|
|
||||||
}
|
|
||||||
PathBlockSelector::Genesis => Ok(BlockSelector::Genesis),
|
|
||||||
PathBlockSelector::Tip if !value.is_empty() => Err(PathBlockSelectorError::TipNoArg),
|
|
||||||
PathBlockSelector::Tip => Ok(BlockSelector::Tip),
|
|
||||||
PathBlockSelector::Digest => Ok(BlockSelector::Digest(Digest::try_from_hex(value)?)),
|
|
||||||
PathBlockSelector::Height => Ok(BlockSelector::Height(BlockHeight::from(
|
|
||||||
u64::from_str(value)?,
|
|
||||||
))),
|
|
||||||
PathBlockSelector::HeightOrDigest => Ok(match u64::from_str(value) {
|
|
||||||
Ok(height) => BlockSelector::Height(BlockHeight::from(height)),
|
|
||||||
Err(_) => BlockSelector::Digest(Digest::try_from_hex(value)?),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,3 +1,7 @@
|
|||||||
|
use crate::http_util::not_found_err;
|
||||||
|
use crate::http_util::rpc_err;
|
||||||
|
use crate::model::app_state::AppState;
|
||||||
|
use crate::model::block_selector_extended::BlockSelectorExtended;
|
||||||
use axum::extract::Path;
|
use axum::extract::Path;
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use axum::response::IntoResponse;
|
use axum::response::IntoResponse;
|
||||||
@ -6,37 +10,14 @@ use neptune_core::prelude::twenty_first::math::digest::Digest;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tarpc::context;
|
use tarpc::context;
|
||||||
|
|
||||||
use crate::{
|
|
||||||
http_util::{not_found_err, rpc_err},
|
|
||||||
model::{app_state::AppState, path_block_selector::PathBlockSelector},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[axum::debug_handler]
|
#[axum::debug_handler]
|
||||||
pub async fn block_digest(
|
pub async fn block_digest(
|
||||||
Path(path_block_selector): Path<PathBlockSelector>,
|
Path(selector): Path<BlockSelectorExtended>,
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
) -> Result<Json<Digest>, impl IntoResponse> {
|
) -> Result<Json<Digest>, impl IntoResponse> {
|
||||||
block_digest_with_value_worker(state, path_block_selector, "").await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[axum::debug_handler]
|
|
||||||
pub async fn block_digest_with_value(
|
|
||||||
Path((path_block_selector, value)): Path<(PathBlockSelector, String)>,
|
|
||||||
State(state): State<Arc<AppState>>,
|
|
||||||
) -> Result<Json<Digest>, impl IntoResponse> {
|
|
||||||
block_digest_with_value_worker(state, path_block_selector, &value).await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn block_digest_with_value_worker(
|
|
||||||
state: Arc<AppState>,
|
|
||||||
path_block_selector: PathBlockSelector,
|
|
||||||
value: &str,
|
|
||||||
) -> Result<Json<Digest>, impl IntoResponse> {
|
|
||||||
let block_selector = path_block_selector.as_block_selector(value)?;
|
|
||||||
|
|
||||||
match state
|
match state
|
||||||
.rpc_client
|
.rpc_client
|
||||||
.block_digest(context::current(), block_selector)
|
.block_digest(context::current(), selector.into())
|
||||||
.await
|
.await
|
||||||
.map_err(rpc_err)?
|
.map_err(rpc_err)?
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
|
use crate::http_util::not_found_err;
|
||||||
|
use crate::http_util::rpc_err;
|
||||||
|
use crate::model::app_state::AppState;
|
||||||
|
use crate::model::block_selector_extended::BlockSelectorExtended;
|
||||||
use axum::extract::Path;
|
use axum::extract::Path;
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use axum::response::Json;
|
use axum::response::Json;
|
||||||
@ -6,43 +10,17 @@ use neptune_core::models::blockchain::block::block_info::BlockInfo;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tarpc::context;
|
use tarpc::context;
|
||||||
|
|
||||||
use crate::{
|
|
||||||
http_util::{not_found_err, rpc_err},
|
|
||||||
model::{app_state::AppState, path_block_selector::PathBlockSelector},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[axum::debug_handler]
|
#[axum::debug_handler]
|
||||||
pub async fn block_info(
|
pub async fn block_info(
|
||||||
Path(path_block_selector): Path<PathBlockSelector>,
|
Path(selector): Path<BlockSelectorExtended>,
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
) -> Result<Json<BlockInfo>, Response> {
|
) -> Result<Json<BlockInfo>, Response> {
|
||||||
let block_info = block_info_with_value_worker(state, path_block_selector, "").await?;
|
let block_info = state
|
||||||
Ok(Json(block_info))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[axum::debug_handler]
|
|
||||||
pub async fn block_info_with_value(
|
|
||||||
Path((path_block_selector, value)): Path<(PathBlockSelector, String)>,
|
|
||||||
State(state): State<Arc<AppState>>,
|
|
||||||
) -> Result<Json<BlockInfo>, Response> {
|
|
||||||
let block_info = block_info_with_value_worker(state, path_block_selector, &value).await?;
|
|
||||||
Ok(Json(block_info))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn block_info_with_value_worker(
|
|
||||||
state: Arc<AppState>,
|
|
||||||
path_block_selector: PathBlockSelector,
|
|
||||||
value: &str,
|
|
||||||
) -> Result<BlockInfo, Response> {
|
|
||||||
let block_selector = path_block_selector.as_block_selector(value)?;
|
|
||||||
|
|
||||||
match state
|
|
||||||
.rpc_client
|
.rpc_client
|
||||||
.block_info(context::current(), block_selector)
|
.block_info(context::current(), selector.into())
|
||||||
.await
|
.await
|
||||||
.map_err(rpc_err)?
|
.map_err(rpc_err)?
|
||||||
{
|
.ok_or_else(not_found_err)?;
|
||||||
Some(info) => Ok(info),
|
|
||||||
None => Err(not_found_err()),
|
Ok(Json(block_info))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user