Skip to content
Closed
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
40 changes: 35 additions & 5 deletions src/routes/trades/get_by_tx.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{RaindexTradesDataSource, TradesDataSource};
use super::{RaindexTradesDataSource, TradesDataSource, TxIndexState};
use crate::auth::AuthenticatedKey;
use crate::error::{ApiError, ApiErrorResponse};
use crate::fairings::{GlobalRateLimit, TracingSpan};
Expand Down Expand Up @@ -56,9 +56,14 @@ pub(super) async fn process_get_trades_by_tx(
let trades = result.trades();

if trades.is_empty() {
return Err(ApiError::NotFound(
"transaction has no associated trades".into(),
));
return match trades_ds.check_tx_index_state(tx_hash).await? {
TxIndexState::NotYetIndexed => Err(ApiError::NotYetIndexed(format!(
"transaction {tx_hash:#x} not yet indexed"
))),
TxIndexState::Indexed => Err(ApiError::NotFound(
"transaction has no associated trades".into(),
)),
};
}

let first_tx = trades[0].transaction();
Expand Down Expand Up @@ -130,6 +135,7 @@ mod tests {

struct MockTradesDataSource {
result: Result<RaindexTradesListResult, ApiError>,
tx_index_state: Result<TxIndexState, ApiError>,
}

#[async_trait]
Expand All @@ -143,12 +149,17 @@ mod tests {
Err(e) => Err(e.clone()),
}
}

async fn check_tx_index_state(&self, _tx_hash: B256) -> Result<TxIndexState, ApiError> {
self.tx_index_state.clone()
}
}

#[rocket::async_test]
async fn test_process_success() {
let trades_ds = MockTradesDataSource {
result: Ok(mock_trades_list_result()),
tx_index_state: Ok(TxIndexState::Indexed),
};
let result = process_get_trades_by_tx(
&trades_ds,
Expand Down Expand Up @@ -177,6 +188,7 @@ mod tests {
async fn test_process_tx_not_found() {
let trades_ds = MockTradesDataSource {
result: Ok(mock_empty_trades_list_result()),
tx_index_state: Ok(TxIndexState::Indexed),
};
let result = process_get_trades_by_tx(
&trades_ds,
Expand All @@ -191,7 +203,8 @@ mod tests {
#[rocket::async_test]
async fn test_process_tx_not_indexed() {
let trades_ds = MockTradesDataSource {
result: Err(ApiError::NotYetIndexed("not indexed".into())),
result: Ok(mock_empty_trades_list_result()),
tx_index_state: Ok(TxIndexState::NotYetIndexed),
};
let result = process_get_trades_by_tx(
&trades_ds,
Expand All @@ -207,6 +220,23 @@ mod tests {
async fn test_process_query_failure() {
let trades_ds = MockTradesDataSource {
result: Err(ApiError::Internal("subgraph error".into())),
tx_index_state: Ok(TxIndexState::Indexed),
};
let result = process_get_trades_by_tx(
&trades_ds,
"0x0000000000000000000000000000000000000000000000000000000000000001"
.parse()
.unwrap(),
)
.await;
assert!(matches!(result, Err(ApiError::Internal(_))));
}

#[rocket::async_test]
async fn test_process_index_check_failure() {
let trades_ds = MockTradesDataSource {
result: Ok(mock_empty_trades_list_result()),
tx_index_state: Err(ApiError::Internal("index check failed".into())),
};
let result = process_get_trades_by_tx(
&trades_ds,
Expand Down
52 changes: 52 additions & 0 deletions src/routes/trades/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,17 @@ use rain_orderbook_common::raindex_client::trades::RaindexTradesListResult;
use rain_orderbook_common::raindex_client::{RaindexClient, RaindexError};
use rocket::Route;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum TxIndexState {
Indexed,
NotYetIndexed,
}

#[async_trait]
pub(crate) trait TradesDataSource: Send + Sync {
async fn get_trades_by_tx(&self, tx_hash: B256) -> Result<RaindexTradesListResult, ApiError>;

async fn check_tx_index_state(&self, tx_hash: B256) -> Result<TxIndexState, ApiError>;
}

pub(crate) struct RaindexTradesDataSource<'a> {
Expand Down Expand Up @@ -40,6 +48,50 @@ impl TradesDataSource for RaindexTradesDataSource<'_> {
}
})
}

async fn check_tx_index_state(&self, tx_hash: B256) -> Result<TxIndexState, ApiError> {
let orderbooks = self.client.get_all_orderbooks().map_err(|e| {
tracing::error!(error = %e, "failed to get orderbooks");
ApiError::Internal("failed to query transaction".into())
})?;

let mut saw_timeout = false;

for orderbook in orderbooks.values() {
match self
.client
.get_transaction(
orderbook.network.chain_id,
orderbook.address,
tx_hash,
Some(1),
Some(0),
)
.await
{
Ok(_) => return Ok(TxIndexState::Indexed),
Err(RaindexError::TransactionIndexingTimeout { .. }) => {
saw_timeout = true;
}
Err(err) => {
tracing::error!(
error = %err,
tx_hash = %tx_hash,
chain_id = orderbook.network.chain_id,
orderbook = %orderbook.address,
"failed to query transaction status"
);
return Err(ApiError::Internal("failed to query transaction".into()));
}
}
}

if saw_timeout {
return Ok(TxIndexState::NotYetIndexed);
}

Ok(TxIndexState::Indexed)
}
}

pub fn routes() -> Vec<Route> {
Expand Down
Loading