Skip to content

Commit

Permalink
feat: return blocks by timestamp or height
Browse files Browse the repository at this point in the history
  • Loading branch information
mateuszjasiuk committed Dec 17, 2024
1 parent d1eb8df commit 001e1e0
Show file tree
Hide file tree
Showing 18 changed files with 297 additions and 10 deletions.
3 changes: 2 additions & 1 deletion chain/src/repository/balance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ pub fn insert_tokens(
#[cfg(test)]
mod tests {

use std::collections::HashSet;

use anyhow::Context;
use diesel::{
BoolExpressionMethods, ExpressionMethods, QueryDsl, SelectableHelper,
Expand All @@ -86,7 +88,6 @@ mod tests {
use shared::balance::{Amount, Balance};
use shared::id::Id;
use shared::token::IbcToken;
use std::collections::HashSet;
use test_helpers::db::TestDb;

use super::*;
Expand Down
6 changes: 4 additions & 2 deletions orm/src/blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,11 @@ impl BlockInsertDb {
pub fn fake(height: i32) -> Self {
Self {
height,
hash: Some(height.to_string()), /* fake hash but ensures uniqueness
hash: Some(height.to_string()), /* fake hash but ensures
* uniqueness
* with height */
app_hash: Some("fake_app_hash".to_string()), // doesn't require uniqueness
app_hash: Some("fake_app_hash".to_string()), /* doesn't require
* uniqueness */
timestamp: Some(
chrono::DateTime::from_timestamp(0, 0).unwrap().naive_utc(),
),
Expand Down
8 changes: 5 additions & 3 deletions shared/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,16 @@ pub struct Block {
pub hash: Id,
pub header: BlockHeader,
pub transactions: Vec<(WrapperTransaction, Vec<InnerTransaction>)>,
pub epoch: Epoch
pub epoch: Epoch,
}

impl Block {
pub fn from(
block_response: &TendermintBlockResponse,
block_results: &BlockResult,
proposer_address_namada: &Option<Id>, // Provide the namada address of the proposer, if available
proposer_address_namada: &Option<Id>, /* Provide the namada address
* of the proposer, if
* available */
checksums: Checksums,
epoch: Epoch,
block_height: BlockHeight,
Expand Down Expand Up @@ -154,7 +156,7 @@ impl Block {
app_hash: Id::from(&block_response.block.header.app_hash),
},
transactions,
epoch
epoch,
}
}

Expand Down
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 @@ -979,3 +1013,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
Loading

0 comments on commit 001e1e0

Please sign in to comment.