Skip to content

Commit

Permalink
Merge pull request #222 from anoma/feat/return-block-by-timestamp
Browse files Browse the repository at this point in the history
feat: return blocks by timestamp or height
  • Loading branch information
mateuszjasiuk authored Dec 17, 2024
2 parents 52bc198 + 001e1e0 commit cc865d9
Show file tree
Hide file tree
Showing 15 changed files with 286 additions and 4 deletions.
50 changes: 50 additions & 0 deletions swagger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,40 @@ paths:
name:
type: string
enum: [unknown, timeout, success, fail]
/api/v1/block/height/{value}:
get:
summary: Get the block by height
parameters:
- in: path
name: value
schema:
type: number
required: true
description: Block height
responses:
'200':
description: Block info
content:
application/json:
schema:
$ref: '#/components/schemas/Block'
/api/v1/block/timestamp/{value}:
get:
summary: Get the block by timestamp
parameters:
- in: path
name: value
schema:
type: number
required: true
description: Block timestamp
responses:
'200':
description: Block info
content:
application/json:
schema:
$ref: '#/components/schemas/Block'
/api/v1/crawlers/timestamps:
get:
summary: Get timestamps of the last activity of the crawlers
Expand Down Expand Up @@ -981,3 +1015,19 @@ components:
type: string
data:
type: string
Block:
type: object
required: [height]
properties:
height:
type: string
hash:
type: string
appHash:
type: string
timestamp:
type: string
proposer:
type: string
epoch:
type: string
1 change: 1 addition & 0 deletions webserver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ production = []

[dependencies]
axum.workspace = true
chrono.workspace = true
tokio.workspace = true
tower.workspace = true
tower-http.workspace = true
Expand Down
17 changes: 13 additions & 4 deletions webserver/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ use tower_http::trace::TraceLayer;
use crate::appstate::AppState;
use crate::config::AppConfig;
use crate::handler::{
balance as balance_handlers, chain as chain_handlers,
crawler_state as crawler_state_handlers, gas as gas_handlers,
governance as gov_handlers, ibc as ibc_handler, pk as pk_handlers,
pos as pos_handlers, transaction as transaction_handlers,
balance as balance_handlers, block as block_handlers,
chain as chain_handlers, crawler_state as crawler_state_handlers,
gas as gas_handlers, governance as gov_handlers, ibc as ibc_handler,
pk as pk_handlers, pos as pos_handlers,
transaction as transaction_handlers,
};
use crate::state::common::CommonState;

Expand Down Expand Up @@ -136,6 +137,14 @@ impl ApplicationServer {
)
// Server sent events endpoints
.route("/chain/status", get(chain_handlers::chain_status))
.route(
"/block/height/:value",
get(block_handlers::get_block_by_height),
)
.route(
"/block/timestamp/:value",
get(block_handlers::get_block_by_timestamp),
)
.route(
"/metrics",
get(|| async move { metric_handle.render() }),
Expand Down
4 changes: 4 additions & 0 deletions webserver/src/error/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use axum::response::{IntoResponse, Response};
use thiserror::Error;

use super::balance::BalanceError;
use super::block::BlockError;
use super::chain::ChainError;
use super::crawler_state::CrawlerStateError;
use super::gas::GasError;
Expand All @@ -13,6 +14,8 @@ use super::transaction::TransactionError;

#[derive(Error, Debug)]
pub enum ApiError {
#[error(transparent)]
BlockError(#[from] BlockError),
#[error(transparent)]
TransactionError(#[from] TransactionError),
#[error(transparent)]
Expand All @@ -36,6 +39,7 @@ pub enum ApiError {
impl IntoResponse for ApiError {
fn into_response(self) -> Response {
match self {
ApiError::BlockError(error) => error.into_response(),
ApiError::TransactionError(error) => error.into_response(),
ApiError::ChainError(error) => error.into_response(),
ApiError::PoSError(error) => error.into_response(),
Expand Down
28 changes: 28 additions & 0 deletions webserver/src/error/block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use thiserror::Error;

use crate::response::api::ApiErrorResponse;

#[derive(Error, Debug)]
pub enum BlockError {
#[error("Block not found error at {0}: {1}")]
NotFound(String, String),
#[error("Database error: {0}")]
Database(String),
#[error("Unknown error: {0}")]
Unknown(String),
}

impl IntoResponse for BlockError {
fn into_response(self) -> Response {
let status_code = match self {
BlockError::Unknown(_) | BlockError::Database(_) => {
StatusCode::INTERNAL_SERVER_ERROR
}
BlockError::NotFound(_, _) => StatusCode::NOT_FOUND,
};

ApiErrorResponse::send(status_code.as_u16(), Some(self.to_string()))
}
}
1 change: 1 addition & 0 deletions webserver/src/error/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod api;
pub mod balance;
pub mod block;
pub mod chain;
pub mod crawler_state;
pub mod gas;
Expand Down
30 changes: 30 additions & 0 deletions webserver/src/handler/block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use axum::extract::{Path, State};
use axum::http::HeaderMap;
use axum::Json;
use axum_macros::debug_handler;

use crate::error::api::ApiError;
use crate::response::block::Block;
use crate::state::common::CommonState;

#[debug_handler]
pub async fn get_block_by_height(
_headers: HeaderMap,
Path(value): Path<i32>,
State(state): State<CommonState>,
) -> Result<Json<Block>, ApiError> {
let block = state.block_service.get_block_by_height(value).await?;

Ok(Json(block))
}

#[debug_handler]
pub async fn get_block_by_timestamp(
_headers: HeaderMap,
Path(value): Path<i64>,
State(state): State<CommonState>,
) -> Result<Json<Block>, ApiError> {
let block = state.block_service.get_block_by_timestamp(value).await?;

Ok(Json(block))
}
1 change: 1 addition & 0 deletions webserver/src/handler/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod balance;
pub mod block;
pub mod chain;
pub mod crawler_state;
pub mod gas;
Expand Down
72 changes: 72 additions & 0 deletions webserver/src/repository/block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use axum::async_trait;
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl, SelectableHelper};
use orm::blocks::BlockDb;
use orm::schema::blocks;

use crate::appstate::AppState;

#[derive(Clone)]
pub struct BlockRepository {
pub(crate) app_state: AppState,
}

#[async_trait]
pub trait BlockRepositoryTrait {
fn new(app_state: AppState) -> Self;

async fn find_block_by_height(
&self,
height: i32,
) -> Result<Option<BlockDb>, String>;

async fn find_block_by_timestamp(
&self,
timestamp: i64,
) -> Result<Option<BlockDb>, String>;
}

#[async_trait]
impl BlockRepositoryTrait for BlockRepository {
fn new(app_state: AppState) -> Self {
Self { app_state }
}

async fn find_block_by_height(
&self,
height: i32,
) -> Result<Option<BlockDb>, String> {
let conn = self.app_state.get_db_connection().await;

conn.interact(move |conn| {
blocks::table
.filter(blocks::dsl::height.eq(height))
.select(BlockDb::as_select())
.first(conn)
.ok()
})
.await
.map_err(|e| e.to_string())
}

/// Gets the last block preceeding the given timestamp
async fn find_block_by_timestamp(
&self,
timestamp: i64,
) -> Result<Option<BlockDb>, String> {
let conn = self.app_state.get_db_connection().await;
let timestamp = chrono::DateTime::from_timestamp(timestamp, 0)
.expect("Invalid timestamp")
.naive_utc();

conn.interact(move |conn| {
blocks::table
.filter(blocks::timestamp.le(timestamp))
.order(blocks::timestamp.desc())
.select(BlockDb::as_select())
.first(conn)
.ok()
})
.await
.map_err(|e| e.to_string())
}
}
1 change: 1 addition & 0 deletions webserver/src/repository/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod balance;
pub mod block;
pub mod chain;
pub mod gas;
pub mod governance;
Expand Down
28 changes: 28 additions & 0 deletions webserver/src/response/block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use orm::blocks::BlockDb;
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Block {
pub height: i32,
pub hash: Option<String>,
pub app_hash: Option<String>,
pub timestamp: Option<String>,
pub proposer: Option<String>,
pub epoch: Option<String>,
}

impl From<BlockDb> for Block {
fn from(block_db: BlockDb) -> Self {
Self {
height: block_db.height,
hash: block_db.hash,
app_hash: block_db.app_hash,
timestamp: block_db
.timestamp
.map(|t| t.and_utc().timestamp().to_string()),
proposer: block_db.proposer,
epoch: block_db.epoch.map(|e| e.to_string()),
}
}
}
1 change: 1 addition & 0 deletions webserver/src/response/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod api;
pub mod balance;
pub mod block;
pub mod chain;
pub mod crawler_state;
pub mod gas;
Expand Down
52 changes: 52 additions & 0 deletions webserver/src/service/block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use crate::appstate::AppState;
use crate::error::block::BlockError;
use crate::repository::block::{BlockRepository, BlockRepositoryTrait};
use crate::response::block::Block;

#[derive(Clone)]
pub struct BlockService {
block_repo: BlockRepository,
}

impl BlockService {
pub fn new(app_state: AppState) -> Self {
Self {
block_repo: BlockRepository::new(app_state),
}
}

pub async fn get_block_by_height(
&self,
height: i32,
) -> Result<Block, BlockError> {
let block = self
.block_repo
.find_block_by_height(height)
.await
.map_err(BlockError::Database)?;
let block = block.ok_or(BlockError::NotFound(
"height".to_string(),
height.to_string(),
))?;

Ok(Block::from(block))
}

pub async fn get_block_by_timestamp(
&self,
timestamp: i64,
) -> Result<Block, BlockError> {
let block = self
.block_repo
.find_block_by_timestamp(timestamp)
.await
.map_err(BlockError::Database)?;

let block = block.ok_or(BlockError::NotFound(
"timestamp".to_string(),
timestamp.to_string(),
))?;

Ok(Block::from(block))
}
}
1 change: 1 addition & 0 deletions webserver/src/service/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod balance;
pub mod block;
pub mod chain;
pub mod crawler_state;
pub mod gas;
Expand Down
3 changes: 3 additions & 0 deletions webserver/src/state/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use namada_sdk::tendermint_rpc::HttpClient;
use crate::appstate::AppState;
use crate::config::AppConfig;
use crate::service::balance::BalanceService;
use crate::service::block::BlockService;
use crate::service::chain::ChainService;
use crate::service::crawler_state::CrawlerStateService;
use crate::service::gas::GasService;
Expand All @@ -15,6 +16,7 @@ use crate::service::transaction::TransactionService;
#[derive(Clone)]
pub struct CommonState {
pub pos_service: PosService,
pub block_service: BlockService,
pub gov_service: GovernanceService,
pub balance_service: BalanceService,
pub chain_service: ChainService,
Expand All @@ -30,6 +32,7 @@ pub struct CommonState {
impl CommonState {
pub fn new(client: HttpClient, config: AppConfig, data: AppState) -> Self {
Self {
block_service: BlockService::new(data.clone()),
pos_service: PosService::new(data.clone()),
gov_service: GovernanceService::new(data.clone()),
balance_service: BalanceService::new(data.clone()),
Expand Down

0 comments on commit cc865d9

Please sign in to comment.