Skip to content

Commit

Permalink
suface tx error codes from drift program
Browse files Browse the repository at this point in the history
normalize error json response
  • Loading branch information
jordy25519 committed Dec 14, 2023
1 parent 6bdaec6 commit eac9352
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 80 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 23 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,38 +105,33 @@ $> curl localhost:8080/v2/orders -X DELETE -H 'content-type: application/json' -
$> curl localhost:8080/v2/orders -X DELETE -H 'content-type: application/json'
```

### Get Positions
```bash
curl localhost:8080/v2/positions | jq .

{
"spot": [
{
"amount": "0.400429",
"type": "deposit",
"market_id": 0
},
{
"amount": "9.531343582",
"type": "deposit",
"market_id": 1
}
],
"perp": []
}
```

### Stream Orderbook
```bash
$> curl localhost:8080/v2/orderbooks -N -X GET -H 'content-type: application/json' -d '{"market":{"id":3,"type":"perp"}'
```


# TODO:
- implement orderbook ws stream
- parse/return error codes for failed txs
- integration tests for the endpoints
```rs
Sdk(
Rpc(
ClientError {
request: Some(SendTransaction),
kind: RpcError(
RpcResponseError { code: -32002, message: "Transaction simulation failed: Error processing Instruction 0: custom program error: 0x17b7", data: SendTransactionPreflightFailure(
RpcSimulateTransactionResult {
err: Some(InstructionError(0, Custom(6071))),
logs: Some([
"Program dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH invoke [1]",
"Program log: Instruction: PlaceOrders",
"Program log: user_order_id is already in use 101",
"Program log: AnchorError occurred. Error Code: UserOrderIdAlreadyInUse. Error Number: 6071. Error Message: User Order Id Already In Use.",
"Program dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH consumed 15857 of 200000 compute units",
"Program dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH failed: custom program error: 0x17b7"
]),
accounts: None,
units_consumed: Some(0),
return_data: None
})
})
}
)
)
```
- integration tests for the endpoints
43 changes: 32 additions & 11 deletions src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ pub enum ControllerError {
#[error("internal server error")]
Sdk(#[from] SdkError),
#[error("order id not found")]
UnknownOrderId,
UnknownOrderId(u32),
#[error("tx failed ({code}): {reason}")]
TxFailed { reason: String, code: u32 },
}

#[derive(Clone)]
Expand Down Expand Up @@ -99,9 +101,11 @@ impl AppState {
}
.build();

let signature = self.client.sign_and_send(&self.wallet, tx).await?;

Ok(signature.to_string())
self.client
.sign_and_send(&self.wallet, tx)
.await
.map(|s| s.to_string())
.map_err(handle_tx_err)
}

/// Return orders by position if given, otherwise return all positions
Expand Down Expand Up @@ -192,9 +196,11 @@ impl AppState {
.place_orders(orders)
.build();

let signature = self.client.sign_and_send(&self.wallet, tx).await?;

Ok(signature.to_string())
self.client
.sign_and_send(&self.wallet, tx)
.await
.map(|s| s.to_string())
.map_err(handle_tx_err)
}

pub async fn modify_orders(&self, req: ModifyOrdersRequest) -> Result<String, ControllerError> {
Expand All @@ -216,17 +222,21 @@ impl AppState {
));
}
} else {
return Err(ControllerError::UnknownOrderId);
return Err(ControllerError::UnknownOrderId(
order.order_id.unwrap_or(order.user_order_id as u32),
));
}
}

let tx = TransactionBuilder::new(&self.wallet, user_data)
.modify_orders(params)
.build();

let signature = self.client.sign_and_send(&self.wallet, tx).await?;

Ok(signature.to_string())
self.client
.sign_and_send(&self.wallet, tx)
.await
.map(|s| s.to_string())
.map_err(handle_tx_err)
}

pub fn stream_orderbook(&self, req: GetOrderbookRequest) -> DlobStream {
Expand Down Expand Up @@ -260,3 +270,14 @@ impl Stream for DlobStream {
}
}
}

fn handle_tx_err(err: SdkError) -> ControllerError {
if let Some(code) = err.to_anchor_error_code() {
ControllerError::TxFailed {
reason: code.name(),
code: code.into(),
}
} else {
ControllerError::Sdk(err)
}
}
89 changes: 50 additions & 39 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use argh::FromArgs;
use log::{error, info};

use controller::{AppState, ControllerError};
use serde_json::json;
use types::{
CancelOrdersRequest, GetOrderbookRequest, GetOrdersRequest, GetPositionsRequest,
ModifyOrdersRequest, PlaceOrdersRequest,
Expand All @@ -31,62 +32,41 @@ async fn get_orders(
match serde_json::from_slice(body.as_ref()) {
Ok(deser) => req = deser,
Err(err) => {
return Either::Left(HttpResponse::BadRequest().message_body(err.to_string()))
return Either::Left(HttpResponse::BadRequest().json(json!(
{
"code": 400,
"reason": err.to_string(),
}
)))
}
}
};

match controller.get_orders(req).await {
Err(err) => {
error!("{err:?}");
Either::Left(HttpResponse::InternalServerError().message_body(err.to_string()))
}
Ok(payload) => Either::Right(Json(payload)),
}
handle_result(controller.get_orders(req).await)
}

#[post("/orders")]
async fn create_orders(
controller: web::Data<AppState>,
req: Json<PlaceOrdersRequest>,
) -> impl Responder {
match controller.place_orders(req.0).await {
Err(err) => {
// TODO: convert into http status code / return error to client
error!("{err:?}");
Either::Left(HttpResponse::InternalServerError())
}
Ok(payload) => Either::Right(Json(payload)),
}
handle_result(controller.place_orders(req.0).await)
}

#[patch("/orders")]
async fn modify_orders(
controller: web::Data<AppState>,
req: Json<ModifyOrdersRequest>,
) -> impl Responder {
match controller.modify_orders(req.0).await {
Err(ControllerError::Sdk(err)) => {
error!("{err:?}");
Either::Left(HttpResponse::InternalServerError())
}
Err(ControllerError::UnknownOrderId) => Either::Left(HttpResponse::NotFound()),
Ok(payload) => Either::Right(Json(payload)),
}
handle_result(controller.modify_orders(req.0).await)
}

#[delete("/orders")]
async fn cancel_orders(
controller: web::Data<AppState>,
req: Json<CancelOrdersRequest>,
) -> impl Responder {
match controller.cancel_orders(req.0).await {
Err(err) => {
error!("{err:?}");
Either::Left(HttpResponse::InternalServerError())
}
Ok(payload) => Either::Right(Json(payload)),
}
handle_result(controller.cancel_orders(req.0).await)
}

#[get("/positions")]
Expand All @@ -95,22 +75,22 @@ async fn get_positions(
body: actix_web::web::Bytes,
) -> impl Responder {
let mut req = GetPositionsRequest::default();
// handle the body manually to allow empty payload `Json` requires some body is set
if body.len() > 0 {
match serde_json::from_slice(body.as_ref()) {
Ok(deser) => req = deser,
Err(err) => {
return Either::Left(HttpResponse::BadRequest().message_body(err.to_string()))
return Either::Left(HttpResponse::BadRequest().json(json!(
{
"code": 400,
"reason": err.to_string(),
}
)))
}
}
};

match controller.get_positions(req).await {
Err(err) => {
error!("{err:?}");
Either::Left(HttpResponse::InternalServerError().message_body(err.to_string()))
}
Ok(payload) => Either::Right(Json(payload)),
}
handle_result(controller.get_positions(req).await)
}

#[get("/orderbooks")]
Expand Down Expand Up @@ -175,3 +155,34 @@ struct GatewayConfig {
#[argh(option, default = "8080")]
port: u16,
}

fn handle_result<T>(result: Result<T, ControllerError>) -> Either<HttpResponse, Json<T>> {
match result {
Ok(payload) => Either::Right(Json(payload)),
Err(ControllerError::Sdk(err)) => {
error!("{err:?}");
Either::Left(HttpResponse::InternalServerError().json(json!(
{
"code": 500,
"reason": err.to_string(),
}
)))
}
Err(ControllerError::TxFailed { code, reason }) => {
Either::Left(HttpResponse::BadRequest().json(json!(
{
"code": code,
"reason": reason,
}
)))
}
Err(ControllerError::UnknownOrderId(id)) => {
Either::Left(HttpResponse::NotFound().json(json!(
{
"code": 404,
"reason": format!("order: {id}"),
}
)))
}
}
}

0 comments on commit eac9352

Please sign in to comment.