Skip to content

Commit

Permalink
Change response on db conflict for transactions to 409 instead of 500. (
Browse files Browse the repository at this point in the history
#52)

* Change response on db conflict for transactions to 409 instead of 500.

---------

Co-authored-by: Jakub Trąd <[email protected]>
  • Loading branch information
piohei and Dzejkop authored Aug 22, 2024
1 parent a299258 commit 49dd452
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 11 deletions.
11 changes: 6 additions & 5 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub enum ClientError {
Serde(#[from] serde_json::Error),

#[error("API error: {0}")]
TxSitter(String),
TxSitter(reqwest::StatusCode, String),

#[error("Invalid API key: {0}")]
InvalidApiKey(eyre::Error),
Expand Down Expand Up @@ -118,9 +118,10 @@ impl TxSitterClient {
async fn validate_response(
response: Response,
) -> Result<Response, ClientError> {
if !response.status().is_success() {
let status = response.status();
if !status.is_success() {
let body: String = response.text().await?;
return Err(ClientError::TxSitter(body));
return Err(ClientError::TxSitter(status, body));
}

Ok(response)
Expand Down Expand Up @@ -213,9 +214,9 @@ impl TxSitterClient {
}

impl ClientError {
pub fn tx_sitter(&self) -> Option<&str> {
pub fn tx_sitter_message(&self) -> Option<&str> {
match self {
Self::TxSitter(s) => Some(s),
Self::TxSitter(_, s) => Some(s),
_ => None,
}
}
Expand Down
21 changes: 17 additions & 4 deletions src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ pub struct Database {
pub pool: Pool<Postgres>,
}

pub enum CreateResult {
SUCCESS,
CONFLICT,
}

impl Database {
pub async fn new(config: &DatabaseConfig) -> eyre::Result<Self> {
let connection_string = config.to_connection_string();
Expand Down Expand Up @@ -277,7 +282,7 @@ impl Database {
priority: TransactionPriority,
blobs: Option<Vec<Vec<u8>>>,
relayer_id: &str,
) -> eyre::Result<()> {
) -> eyre::Result<CreateResult> {
let mut tx = self.pool.begin().await?;

let mut value_bytes = [0u8; 32];
Expand All @@ -299,7 +304,7 @@ impl Database {
.fetch_one(tx.as_mut())
.await?;

sqlx::query(
let res = sqlx::query(
r#"
INSERT INTO transactions (id, tx_to, data, value, gas_limit, priority, relayer_id, nonce, blobs)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
Expand All @@ -315,11 +320,19 @@ impl Database {
.bind(nonce)
.bind(blobs)
.execute(tx.as_mut())
.await?;
.await;

if let Err(sqlx::Error::Database(ref err)) = res {
if err.constraint() == Some("transactions_pkey") {
return Ok(CreateResult::CONFLICT);
}
}

res?;

tx.commit().await?;

Ok(())
Ok(CreateResult::SUCCESS)
}

#[instrument(skip(self), level = "debug")]
Expand Down
11 changes: 10 additions & 1 deletion src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use url::Url;

use crate::api_key::ApiKey;
use crate::app::App;
use crate::db::CreateResult;
use crate::service::Service;
use crate::task_runner::TaskRunner;
use crate::types::{
Expand Down Expand Up @@ -250,7 +251,8 @@ impl RelayerApi {
));
}

app.db
let res = app
.db
.create_transaction(
&tx_id,
req.to.0,
Expand All @@ -263,6 +265,13 @@ impl RelayerApi {
)
.await?;

if let CreateResult::CONFLICT = res {
return Err(poem::error::Error::from_string(
"Transaction with same id already exists.".to_string(),
StatusCode::CONFLICT,
));
}

tracing::info!(tx_id, "Transaction created");

Ok(Json(SendTxResponse { tx_id }))
Expand Down
2 changes: 1 addition & 1 deletion tests/send_too_many_txs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ async fn send_too_many_txs() -> eyre::Result<()> {

// TODO: Fix checking errors by string
assert_eq!(
result.as_ref().err().and_then(|e| e.tx_sitter()),
result.as_ref().err().and_then(|e| e.tx_sitter_message()),
Some("Relayer queue is full"),
"Result {:?} should be too many transactions",
result
Expand Down
58 changes: 58 additions & 0 deletions tests/send_tx_with_same_id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
mod common;

use reqwest::StatusCode;
use tx_sitter::client::ClientError;

use crate::common::prelude::*;

#[tokio::test]
async fn send_tx_with_same_id() -> eyre::Result<()> {
setup_tracing();

let (db_url, _db_container) = setup_db().await?;
let anvil = AnvilBuilder::default().spawn().await?;

let (_service, client) =
ServiceBuilder::default().build(&anvil, &db_url).await?;
let CreateApiKeyResponse { api_key } =
client.create_relayer_api_key(DEFAULT_RELAYER_ID).await?;

let tx_id = Some("tx-1".to_string());

// Send a transaction
let value: U256 = parse_units("1", "ether")?.into();
client
.send_tx(
&api_key,
&SendTxRequest {
to: ARBITRARY_ADDRESS.into(),
value: value.into(),
gas_limit: U256::from(21_000).into(),
tx_id: tx_id.clone(),
..Default::default()
},
)
.await?;

let res = client
.send_tx(
&api_key,
&SendTxRequest {
to: ARBITRARY_ADDRESS.into(),
value: value.into(),
gas_limit: U256::from(21_000).into(),
tx_id: tx_id.clone(),
..Default::default()
},
)
.await;

if let ClientError::TxSitter(status_code, message) = res.unwrap_err() {
assert_eq!(status_code, StatusCode::CONFLICT);
assert_eq!(message, "Transaction with same id already exists.");

return Ok(());
}

panic!("Should return error on second insert with same id.")
}

0 comments on commit 49dd452

Please sign in to comment.