Skip to content

Commit

Permalink
[API] Add POST /transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
banool committed Jul 22, 2022
1 parent 64c8c08 commit ecffe6e
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 3 deletions.
10 changes: 10 additions & 0 deletions api/src/poem_backend/post.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,13 @@ pub enum AptosPost<T: ToJSON + ParseFromJSON + Send + Sync + Type + for<'b> Dese
#[oai(content_type = "application/json")]
Json(Json<T>),
}

impl<T: ToJSON + ParseFromJSON + Send + Sync + Type + for<'b> Deserialize<'b>> AptosPost<T> {
/// Consume the AptosPost and return the T inside.
pub fn take(self) -> T {
match self {
AptosPost::Bcs(bcs) => bcs.0,
AptosPost::Json(json) => json.0,
}
}
}
9 changes: 8 additions & 1 deletion api/src/poem_backend/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,14 @@ generate_success_response!(BasicResponse, (200, Ok));

// Generate traits defining a "from" function for each of these status types.
// The error response then impls these traits for each status type they mention.
generate_error_traits!(Internal, BadRequest, NotFound);
generate_error_traits!(
BadRequest,
NotFound,
PayloadTooLarge,
UnsupportedMediaType,
Internal,
InsufficientStorage
);

// Generate an error response that only has options for 400 and 500.
generate_error_response!(BasicError, (400, BadRequest), (500, Internal));
Expand Down
107 changes: 105 additions & 2 deletions api/src/poem_backend/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,35 @@ use std::sync::Arc;

use super::accept_type::{parse_accept, AcceptType};
use super::page::Page;
use super::AptosErrorCode;
use super::{
ApiTags, AptosErrorResponse, BasicErrorWith404, BasicResponse, BasicResponseStatus,
BasicResultWith404, InternalError,
};
use super::{AptosErrorCode, AptosPost, BadRequestError, InsufficientStorageError};
use crate::context::Context;
use crate::failpoint::fail_point_poem;
use crate::{generate_error_response, generate_success_response};
use anyhow::Context as AnyhowContext;
use aptos_api_types::{AsConverter, LedgerInfo, Transaction, TransactionOnChainData};
use aptos_api_types::{
AsConverter, LedgerInfo, Transaction, TransactionOnChainData, UserTransactionRequest,
};
use aptos_types::mempool_status::MempoolStatusCode;
use aptos_types::transaction::SignedTransaction;
use poem::web::Accept;
use poem_openapi::param::Query;
use poem_openapi::OpenApi;

generate_success_response!(SubmitTransactionResponse, (202, Accepted));
generate_error_response!(
SubmitTransactionError,
(400, BadRequest),
(500, Internal),
(507, InsufficientStorage)
);

type SubmitTransactionResult<T> =
poem::Result<SubmitTransactionResponse<T>, SubmitTransactionError>;

pub struct TransactionsApi {
pub context: Arc<Context>,
}
Expand All @@ -47,6 +63,31 @@ impl TransactionsApi {
let page = Page::new(start.0, limit.0);
self.list(&accept_type, page)
}

// TODO: Despite what the old API spec says, this endpoint does not return
// a PendingTransaction, it returns a Transaction where the enum variant
// used happens to always be a PendingTransaction. Change this endpoint and
// underlying code to actually return a PendingTransaction directly.
//
// TODO: The previous spec says this function can return 413 and 415 but
// none of the code actually does that. I imagine it should though,
// investigate that.
#[oai(
path = "/transactions",
method = "post",
operation_id = "submit_transaction",
tag = "ApiTags::General"
)]
async fn submit_transaction(
&self,
accept: Accept,
user_transaction_request: AptosPost<UserTransactionRequest>,
) -> SubmitTransactionResult<Transaction> {
fail_point_poem::<SubmitTransactionError>("endppoint_submit_transaction")?;
let accept_type = parse_accept::<SubmitTransactionError>(&accept)?;
self.create_from_request(&accept_type, user_transaction_request.take())
.await
}
}

impl TransactionsApi {
Expand Down Expand Up @@ -110,4 +151,66 @@ impl TransactionsApi {
accept_type,
))
}

async fn create_from_request(
&self,
accept_type: &AcceptType,
req: UserTransactionRequest,
) -> SubmitTransactionResult<Transaction> {
let txn = self
.context
.move_resolver_poem::<SubmitTransactionError>()?
.as_converter()
.try_into_signed_transaction(req, self.context.chain_id())
.context("Failed to create SignedTransaction from UserTransactionRequest")
.map_err(SubmitTransactionError::bad_request)?;
self.create(accept_type, txn).await
}

async fn create(
&self,
accept_type: &AcceptType,
txn: SignedTransaction,
) -> SubmitTransactionResult<Transaction> {
let ledger_info = self
.context
.get_latest_ledger_info_poem::<SubmitTransactionError>()?;
let (mempool_status, vm_status_opt) = self
.context
.submit_transaction(txn.clone())
.await
.context("Mempool failed to initially evaluate submitted transaction")
.map_err(SubmitTransactionError::internal)?;
match mempool_status.code {
MempoolStatusCode::Accepted => {
let resolver = self
.context
.move_resolver_poem::<SubmitTransactionError>()?;
let pending_txn = resolver
.as_converter()
.try_into_pending_transaction(txn)
.context("Failed to build PendingTransaction from mempool response, even though it said the request was accepted")
.map_err(SubmitTransactionError::internal)?;
SubmitTransactionResponse::try_from_rust_value((
pending_txn,
&ledger_info,
SubmitTransactionResponseStatus::Accepted,
accept_type,
))
}
MempoolStatusCode::MempoolIsFull => Err(
SubmitTransactionError::insufficient_storage_str(&mempool_status.message),
),
MempoolStatusCode::VmError => Err(SubmitTransactionError::bad_request_str(&format!(
"invalid transaction: {}",
vm_status_opt
.map(|s| format!("{:?}", s))
.unwrap_or_else(|| "UNKNOWN".to_owned())
))),
_ => Err(SubmitTransactionError::bad_request_str(&format!(
"transaction is rejected: {}",
mempool_status,
))),
}
}
}

0 comments on commit ecffe6e

Please sign in to comment.