Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[API] Add Poem as an alternative backend #1906

Merged
merged 22 commits into from
Jul 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7a0b10e
[API] Add crate to help types self-describe for the OpenAPI spec gene…
banool Jul 19, 2022
1b60317
[API] Add boilerplate for using Poem as an alternative backend
banool Jul 18, 2022
2a79877
[API] Add support for index with Poem backend
banool Jul 12, 2022
1c7af6c
[API] Add TLS support to Poem backend
banool Jul 12, 2022
3c5fcb1
[API] Add CORS for Poem API backend
banool Jul 12, 2022
80ce552
[API] Add support for logging middleware to the Poem backend
banool Jul 15, 2022
5a9e00d
[API] Add response status tracking to Poem backend
banool Jul 12, 2022
6932152
[API] Break APIs up in Poem backend
banool Jul 12, 2022
104ba5e
[API] Make a BCS payload type
banool Jul 19, 2022
4814a9c
[API] Make all API types self-describe for generating an OpenAPI spec
banool Jul 19, 2022
c4fabdd
[API] Fix up Rosetta following adding IdentifierWrapper
banool Jul 19, 2022
ff2e509
[API] Add request and response types for the JSON / BCS enum
banool Jul 18, 2022
3028d9c
[API] Add headers to response in Poem backend
banool Jul 15, 2022
933569a
[API] Add endpoint for get_account for Poem backend
banool Jul 20, 2022
fbee6a4
[API] Add /accounts/{address}/resources endpoint
banool Jul 19, 2022
e201c91
[API] Add /accounts/{address}/modules endpoint
banool Jul 19, 2022
e25e250
[API] Add /events/{event_key} endpoint
banool Jul 19, 2022
a7675d5
[API] Add /accounts/{address}/events/{event_handle_struct}/{field_nam…
banool Jul 20, 2022
32f11f6
[API] Run both APIs alongside each other
banool Jul 20, 2022
78be793
[API] Add GET /transactions
banool Jul 20, 2022
ca5c73b
fixup: Remove use_poem_backend_flag
banool Jul 22, 2022
bbca085
Add comments explaining what the aptos-openapi macros do
banool Jul 22, 2022
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
266 changes: 197 additions & 69 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ members = [
"crates/aptos-log-derive",
"crates/aptos-logger",
"crates/aptos-metrics-core",
"crates/aptos-openapi",
"crates/aptos-proptest-helpers",
"crates/aptos-rate-limiter",
"crates/aptos-rest-client",
Expand Down
7 changes: 6 additions & 1 deletion api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "aptos-api"
version = "0.1.0"
version = "0.2.0"
authors = ["Aptos Labs <[email protected]>"]
description = "Aptos REST API"
repository = "https://github.com/aptos-labs/aptos-core"
Expand All @@ -17,12 +17,17 @@ fail = "0.5.0"
futures = "0.3.21"
hex = "0.4.3"
hyper = "0.14.18"
mime = "0.3.16"
once_cell = "1.10.0"
percent-encoding = "2.1.0"
poem = { version = "1.3.35", features = ["anyhow", "rustls"] }
poem-openapi = { version = "2.0.5", features = ["swagger-ui", "url"] }
serde = { version = "1.0.137", features = ["derive"], default-features = false }
serde_json = { version = "1.0.81", features = ["preserve_order"] }
tokio = { version = "1.18.2", features = ["full"] }
url = "2.2.2"
warp = { version = "0.3.2", features = ["default", "tls"] }
warp-reverse-proxy = "0.5.0"

aptos-api-types = { path = "./types", package = "aptos-api-types" }
aptos-config = { path = "../config" }
Expand Down
4 changes: 2 additions & 2 deletions api/src/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,9 @@ impl Account {
.map_err(anyhow::Error::from)?
.ok_or_else(|| self.resource_not_found(&AccountResource::struct_tag()))?;

let account: AccountData = account_resource.into();
let account_data: AccountData = account_resource.into();

Response::new(self.latest_ledger_info, &account)
Response::new(self.latest_ledger_info, &account_data)
}

pub fn resources(self) -> Result<impl Reply, Error> {
Expand Down
58 changes: 50 additions & 8 deletions api/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,24 @@ use aptos_types::{
contract_event::ContractEvent,
event::EventKey,
ledger_info::LedgerInfoWithSignatures,
state_store::{state_key::StateKey, state_key_prefix::StateKeyPrefix},
state_store::{state_key::StateKey, state_key_prefix::StateKeyPrefix, state_value::StateValue},
transaction::{SignedTransaction, TransactionWithProof, Version},
write_set::WriteOp,
};
use aptos_vm::data_cache::{IntoMoveResolver, RemoteStorageOwned};
use futures::{channel::oneshot, SinkExt};
use move_deps::move_core_types::ident_str;
use poem_openapi::payload::Json;
use serde::{Deserialize, Serialize};
use std::{convert::Infallible, sync::Arc};
use std::{collections::HashMap, convert::Infallible, sync::Arc};
use storage_interface::{
state_view::{DbStateView, DbStateViewAtVersion, LatestDbStateCheckpointView},
DbReader, Order,
};
use warp::{filters::BoxedFilter, Filter, Reply};

use crate::poem_backend::{AptosError, AptosErrorCode, AptosErrorResponse, AptosInternalResult};

// Context holds application scope context
#[derive(Clone)]
pub struct Context {
Expand Down Expand Up @@ -61,6 +64,18 @@ impl Context {
.map(|state_view| state_view.into_move_resolver())
}

pub fn move_resolver_poem(&self) -> AptosInternalResult<RemoteStorageOwned<DbStateView>> {
self.move_resolver().map_err(|e| {
AptosErrorResponse::InternalServerError(Json(
AptosError::new(
format_err!("Failed to read latest state checkpoint from DB: {}", e)
.to_string(),
)
.error_code(AptosErrorCode::ReadFromStorageError),
))
})
}

pub fn state_view_at_version(&self, version: Version) -> Result<DbStateView> {
self.db.state_view_at_version(Some(version))
}
Expand Down Expand Up @@ -103,6 +118,15 @@ impl Context {
}
}

pub fn get_latest_ledger_info_poem(&self) -> AptosInternalResult<LedgerInfo> {
self.get_latest_ledger_info().map_err(|e| {
AptosErrorResponse::InternalServerError(Json(
AptosError::new(format_err!("Failed to retrieve ledger info: {}", e).to_string())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can probably just make these format! if you're converting them to a string anyways.

I do like this adding error codes though super slick.

If you're adding Json( to everything, I'd just make it a function AptosErrorResponse::internal_server_error(err: AptosError), or AptosErrorResponse::internal_server_error(msg: String, error_code: AptosErrorCode)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The response / error stuff definitely needs love, I'm just deferring it to later as less critical. Agreed it's too verbose right now.

.error_code(AptosErrorCode::ReadFromStorageError),
))
})
}

pub fn get_latest_ledger_info_with_signatures(&self) -> Result<LedgerInfoWithSignatures> {
self.db.get_latest_ledger_info()
}
Expand All @@ -113,16 +137,34 @@ impl Context {
.get_state_value(state_key)
}

pub fn get_state_value_poem(
&self,
state_key: &StateKey,
version: u64,
) -> Result<Option<Vec<u8>>, AptosErrorResponse> {
self.get_state_value(state_key, version).map_err(|e| {
AptosErrorResponse::InternalServerError(Json(
AptosError::new(format_err!("Failed to retrieve state value: {}", e).to_string())
.error_code(AptosErrorCode::ReadFromStorageError),
))
})
}

pub fn get_state_values(
&self,
address: AccountAddress,
version: u64,
) -> Result<HashMap<StateKey, StateValue>> {
self.db
.get_state_values_by_key_prefix(&StateKeyPrefix::from(address), version)
}

pub fn get_account_state(
&self,
address: AccountAddress,
version: u64,
) -> Result<Option<AccountState>> {
AccountState::from_access_paths_and_values(
&self
.db
.get_state_values_by_key_prefix(&StateKeyPrefix::from(address), version)?,
)
AccountState::from_access_paths_and_values(&self.get_state_values(address, version)?)
}

pub fn get_block_timestamp(&self, version: u64) -> Result<u64> {
Expand Down Expand Up @@ -198,7 +240,7 @@ impl Context {
if path.address == CORE_CODE_ADDRESS && typ == block_metadata_type {
if let WriteOp::Value(value) = op {
if let Ok(mut resource) = converter.try_into_resource(&typ, value) {
if let Some(value) = resource.data.0.remove(height_id) {
if let Some(value) = resource.data.0.remove(&height_id.into()) {
if let Ok(height) = serde_json::from_value::<U64>(value) {
return Some(height.0);
}
Expand Down
16 changes: 15 additions & 1 deletion api/src/failpoint.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

#[allow(unused_imports)]
#![allow(unused_imports)]

use anyhow::{format_err, Result};
use aptos_api_types::Error;

use crate::poem_backend::{AptosError, AptosErrorResponse};
use poem_openapi::payload::Json;

#[allow(unused_variables)]
#[inline]
pub fn fail_point(name: &str) -> Result<(), Error> {
Ok(fail::fail_point!(format!("api::{}", name).as_str(), |_| {
Err(format_err!("unexpected internal error for {}", name).into())
}))
}

#[allow(unused_variables)]
#[inline]
pub fn fail_point_poem(name: &str) -> Result<(), AptosErrorResponse> {
Ok(fail::fail_point!(format!("api::{}", name).as_str(), |_| {
Err(AptosErrorResponse::InternalServerError(Json(
AptosError::new(format!("unexpected internal error for {}", name)),
)))
}))
}
1 change: 1 addition & 0 deletions api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod log;
pub mod metrics;
mod page;
pub mod param;
mod poem_backend;
pub mod runtime;
mod state;
mod transactions;
Expand Down
38 changes: 38 additions & 0 deletions api/src/poem_backend/accept_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

use std::convert::TryFrom;

use poem::web::Accept;
use poem_openapi::payload::Json;

use super::{AptosError, AptosErrorCode, AptosErrorResponse};

#[derive(PartialEq)]
pub enum AcceptType {
Json,
Bcs,
}

impl TryFrom<&Accept> for AcceptType {
type Error = AptosErrorResponse;

fn try_from(accept: &Accept) -> Result<Self, Self::Error> {
for mime in &accept.0 {
match mime.as_ref() {
"application/json" => return Ok(AcceptType::Json),
"application/x-bcs" => return Ok(AcceptType::Bcs),
"*/*" => {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, does it default to this? or would "application/*" be valid? Basically, if I curl the endpoint, do I need to specify the header?

Copy link
Contributor Author

@banool banool Jul 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You do not need to specify the header, it'll be json by default. You need to not error on */* since most clients add it by default.

wildcard => {
return Err(AptosErrorResponse::BadRequest(Json(
AptosError::new(format!("Invalid Accept type: {:?}", wildcard))
.error_code(AptosErrorCode::UnsupportedAcceptType),
)));
}
}
}

// Default to returning content as JSON.
Ok(AcceptType::Json)
}
}
Loading