Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: return blocks by timestamp or height #222

Merged
merged 1 commit into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading