Skip to content

Commit a334a22

Browse files
authored
Merge pull request #2760 from pyth-network/cprussin/add-result-count-to-fortuna-log
feat(fortuna): add result count to results
2 parents ccd74fc + 8709cdb commit a334a22

File tree

3 files changed

+82
-25
lines changed

3 files changed

+82
-25
lines changed

apps/fortuna/src/api/explorer.rs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,26 @@ pub struct ExplorerQueryParams {
3535
pub offset: Option<u64>,
3636
}
3737

38+
#[derive(Debug, serde::Serialize, utoipa::ToSchema)]
39+
pub struct ExplorerResponse {
40+
pub requests: Vec<RequestStatus>,
41+
pub total_results: u64,
42+
}
43+
3844
/// Returns the logs of all requests captured by the keeper.
3945
///
4046
/// This endpoint allows you to filter the logs by a specific network ID, a query string (which can be a transaction hash, sender address, or sequence number), and a time range.
4147
/// This is useful for debugging and monitoring the requests made to the Entropy contracts on various chains.
4248
#[utoipa::path(
4349
get,
4450
path = "/v1/logs",
45-
responses((status = 200, description = "A list of Entropy request logs", body = Vec<RequestStatus>)),
51+
responses((status = 200, description = "A list of Entropy request logs", body = ExplorerResponse)),
4652
params(ExplorerQueryParams)
4753
)]
4854
pub async fn explorer(
4955
State(state): State<crate::api::ApiState>,
5056
Query(query_params): Query<ExplorerQueryParams>,
51-
) -> anyhow::Result<Json<Vec<RequestStatus>>, RestError> {
57+
) -> anyhow::Result<Json<ExplorerResponse>, RestError> {
5258
if let Some(network_id) = &query_params.network_id {
5359
if !state
5460
.chains
@@ -89,10 +95,13 @@ pub async fn explorer(
8995
if let Some(max_timestamp) = query_params.max_timestamp {
9096
query = query.max_timestamp(max_timestamp);
9197
}
92-
Ok(Json(
93-
query
94-
.execute()
95-
.await
96-
.map_err(|_| RestError::TemporarilyUnavailable)?,
97-
))
98+
99+
let (requests, total_results) = tokio::join!(query.execute(), query.count_results());
100+
let requests = requests.map_err(|_| RestError::TemporarilyUnavailable)?;
101+
let total_results = total_results.map_err(|_| RestError::TemporarilyUnavailable)?;
102+
103+
Ok(Json(ExplorerResponse {
104+
requests,
105+
total_results,
106+
}))
98107
}

apps/fortuna/src/command/run.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ pub async fn run_api(
4545
crate::api::Blob,
4646
crate::api::BinaryEncoding,
4747
crate::api::StateTag,
48+
crate::api::ExplorerResponse,
4849
)
4950
),
5051
tags(

apps/fortuna/src/history.rs

Lines changed: 64 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -427,8 +427,34 @@ impl<'a> RequestQueryBuilder<'a> {
427427
}
428428

429429
pub async fn execute(&self) -> Result<Vec<RequestStatus>> {
430-
let mut query_builder =
431-
QueryBuilder::new("SELECT * FROM request WHERE created_at BETWEEN ");
430+
let mut query_builder = self.build_query("*");
431+
query_builder.push(" LIMIT ");
432+
query_builder.push_bind(self.limit);
433+
query_builder.push(" OFFSET ");
434+
query_builder.push_bind(self.offset);
435+
436+
let result: sqlx::Result<Vec<RequestRow>> =
437+
query_builder.build_query_as().fetch_all(self.pool).await;
438+
439+
if let Err(e) = &result {
440+
tracing::error!("Failed to fetch request: {}", e);
441+
}
442+
443+
Ok(result?.into_iter().filter_map(|row| row.into()).collect())
444+
}
445+
446+
pub async fn count_results(&self) -> Result<u64> {
447+
self.build_query("COUNT(*) AS count")
448+
.build_query_scalar::<u64>()
449+
.fetch_one(self.pool)
450+
.await
451+
.map_err(|err| err.into())
452+
}
453+
454+
fn build_query(&self, columns: &str) -> QueryBuilder<Sqlite> {
455+
let mut query_builder = QueryBuilder::new(format!(
456+
"SELECT {columns} FROM request WHERE created_at BETWEEN "
457+
));
432458
query_builder.push_bind(self.min_timestamp);
433459
query_builder.push(" AND ");
434460
query_builder.push_bind(self.max_timestamp);
@@ -464,21 +490,8 @@ impl<'a> RequestQueryBuilder<'a> {
464490
query_builder.push_bind(state);
465491
}
466492

467-
query_builder.push(" ORDER BY created_at DESC LIMIT ");
468-
query_builder.push_bind(self.limit);
469-
query_builder.push(" OFFSET ");
470-
query_builder.push_bind(self.offset);
471-
472-
let rows = query_builder
473-
.build_query_as::<RequestRow>()
474-
.fetch_all(self.pool)
475-
.await;
476-
477-
if let Err(e) = &rows {
478-
tracing::error!("Failed to fetch request by time: {}", e);
479-
}
480-
481-
Ok(rows?.into_iter().filter_map(|row| row.into()).collect())
493+
query_builder.push(" ORDER BY created_at DESC");
494+
query_builder
482495
}
483496
}
484497

@@ -928,4 +941,38 @@ mod test {
928941
.unwrap();
929942
assert_eq!(logs, vec![status]);
930943
}
944+
945+
#[tokio::test]
946+
async fn test_count_results() {
947+
let history = History::new_in_memory().await.unwrap();
948+
History::update_request_status(&history.pool, get_random_request_status()).await;
949+
History::update_request_status(&history.pool, get_random_request_status()).await;
950+
let mut failed_status = get_random_request_status();
951+
History::update_request_status(&history.pool, failed_status.clone()).await;
952+
failed_status.state = RequestEntryState::Failed {
953+
reason: "Failed".to_string(),
954+
provider_random_number: None,
955+
};
956+
History::update_request_status(&history.pool, failed_status.clone()).await;
957+
958+
let results = history.query().count_results().await.unwrap();
959+
assert_eq!(results, 3);
960+
961+
let results = history
962+
.query()
963+
.limit(1)
964+
.unwrap()
965+
.count_results()
966+
.await
967+
.unwrap();
968+
assert_eq!(results, 3);
969+
970+
let results = history
971+
.query()
972+
.state(StateTag::Pending)
973+
.count_results()
974+
.await
975+
.unwrap();
976+
assert_eq!(results, 2);
977+
}
931978
}

0 commit comments

Comments
 (0)