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] Rest api docs #3774

Merged
merged 3 commits into from
Sep 7, 2022
Merged
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
2,221 changes: 2,065 additions & 156 deletions api/doc/spec.json

Large diffs are not rendered by default.

1,807 changes: 1,650 additions & 157 deletions api/doc/spec.yaml

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions api/src/accept_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@
use aptos_api_types::mime_types::BCS;
use poem::{web::Accept, FromRequest, Request, RequestBody, Result};

/// Accept types from input headers
///
/// Determines the output type of each API
#[derive(PartialEq, Eq, Debug)]
pub enum AcceptType {
/// Convert and resolve types to JSON
Json,
/// Take types with as little conversion as possible from the database
Bcs,
}

// This impl allows us to get the data straight from the arguments to the
// endpoint handler.
/// This impl allows us to get the data straight from the arguments to the
/// endpoint handler.
#[async_trait::async_trait]
impl<'a> FromRequest<'a> for AcceptType {
async fn from_request(request: &'a Request, _body: &mut RequestBody) -> Result<Self> {
Expand Down
81 changes: 65 additions & 16 deletions api/src/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ use std::collections::BTreeMap;
use std::convert::TryInto;
use std::sync::Arc;

/// API for accounts, their associated resources, and modules
pub struct AccountsApi {
pub context: Arc<Context>,
}
Expand All @@ -40,7 +41,10 @@ pub struct AccountsApi {
impl AccountsApi {
/// Get account
///
/// Return high level information about an account such as its sequence number.
/// Retrieves high level information about an account such as its sequence number and
/// authentication key
///
/// Returns a 404 if the account doesn't exist
#[oai(
path = "/accounts/:address",
method = "get",
Expand All @@ -50,7 +54,11 @@ impl AccountsApi {
async fn get_account(
&self,
accept_type: AcceptType,
/// Address of account with or without a `0x` prefix
address: Path<Address>,
/// Ledger version to get state of account
gregnazario marked this conversation as resolved.
Show resolved Hide resolved
///
/// If not provided, it will be the latest version
ledger_version: Query<Option<U64>>,
) -> BasicResultWith404<AccountData> {
fail_point_poem("endpoint_get_account")?;
Expand All @@ -62,12 +70,11 @@ impl AccountsApi {

/// Get account resources
///
/// This endpoint returns all account resources at a given address at a
/// specific ledger version (AKA transaction version). If the ledger
/// version is not specified in the request, the latest ledger version is used.
/// Retrieves all account resources for a given account and a specific ledger version. If the
gregnazario marked this conversation as resolved.
Show resolved Hide resolved
/// ledger version is not specified in the request, the latest ledger version is used.
///
/// The Aptos nodes prune account state history, via a configurable time window (link).
/// If the requested data has been pruned, the server responds with a 404.
/// The Aptos nodes prune account state history, via a configurable time window.
/// If the requested ledger version has been pruned, the server responds with a 410.
gregnazario marked this conversation as resolved.
Show resolved Hide resolved
#[oai(
path = "/accounts/:address/resources",
method = "get",
Expand All @@ -77,7 +84,11 @@ impl AccountsApi {
async fn get_account_resources(
&self,
accept_type: AcceptType,
/// Address of account with or without a `0x` prefix
address: Path<Address>,
/// Ledger version to get state of account
///
/// If not provided, it will be the latest version
ledger_version: Query<Option<U64>>,
) -> BasicResultWith404<Vec<MoveResource>> {
fail_point_poem("endpoint_get_account_resources")?;
Expand All @@ -89,12 +100,11 @@ impl AccountsApi {

/// Get account modules
///
/// This endpoint returns all account modules at a given address at a
/// specific ledger version (AKA transaction version). If the ledger
/// version is not specified in the request, the latest ledger version is used.
/// Retrieves all account modules' bytecode for a given account at a specific ledger version.
/// If the ledger version is not specified in the request, the latest ledger version is used.
///
/// The Aptos nodes prune account state history, via a configurable time window (link).
/// If the requested data has been pruned, the server responds with a 404.
/// The Aptos nodes prune account state history, via a configurable time window.
/// If the requested ledger version has been pruned, the server responds with a 410.
#[oai(
path = "/accounts/:address/modules",
method = "get",
Expand All @@ -104,7 +114,11 @@ impl AccountsApi {
async fn get_account_modules(
&self,
accept_type: AcceptType,
/// Address of account with or without a `0x` prefix
address: Path<Address>,
/// Ledger version to get state of account
///
/// If not provided, it will be the latest version
ledger_version: Query<Option<U64>>,
) -> BasicResultWith404<Vec<MoveModuleBytecode>> {
fail_point_poem("endpoint_get_account_modules")?;
Expand All @@ -115,19 +129,26 @@ impl AccountsApi {
}
}

/// A struct representing Account related lookups for resources and modules
pub struct Account {
context: Arc<Context>,
/// Address of account
address: Address,
/// Lookup ledger version
ledger_version: u64,
/// Current ledger info
pub latest_ledger_info: LedgerInfo,
}

impl Account {
/// Creates a new account struct and determines the current ledger info, and determines the
/// ledger version to query
pub fn new(
context: Arc<Context>,
address: Address,
requested_ledger_version: Option<U64>,
) -> Result<Self, BasicErrorWith404> {
// Use the latest ledger version, or the requested associated version
let (latest_ledger_info, requested_ledger_version) = context
.get_latest_ledger_info_and_verify_lookup_version(
requested_ledger_version.map(|inner| inner.0),
Expand All @@ -143,7 +164,12 @@ impl Account {

// These functions map directly to endpoint functions.

/// Retrieves the [`AccountData`] for the associated account
gregnazario marked this conversation as resolved.
Show resolved Hide resolved
///
/// * JSON: Return a JSON encoded version of [`AccountData`]
/// * BCS: Return a BCS encoded version of [`AccountData`]
pub fn account(self, accept_type: &AcceptType) -> BasicResultWith404<AccountData> {
// Retrieve the Account resource and convert it accordingly
let state_key = StateKey::AccessPath(AccessPath::resource_access_path(ResourceKey::new(
self.address.into(),
AccountResource::struct_tag(),
Expand All @@ -158,15 +184,17 @@ impl Account {
let state_value = match state_value {
Some(state_value) => state_value,
None => {
// If there's no account info, then it's not found
return Err(resource_not_found(
self.address,
&AccountResource::struct_tag(),
self.ledger_version,
&self.latest_ledger_info,
))
));
}
};

// Convert the AccountResource into the summary object AccountData
let account_resource: AccountResource = bcs::from_bytes(&state_value)
.context("Internal error deserializing response from DB")
.map_err(|err| {
Expand All @@ -192,12 +220,17 @@ impl Account {
}
}

/// Retrieves the move resources associated with the account
///
/// * JSON: Return a JSON encoded version of [`Vec<MoveResource>`]
/// * BCS: Return a sorted BCS encoded version of BCS encoded resources [`BTreeMap<StructTag, Vec<u8>>`]
pub fn resources(self, accept_type: &AcceptType) -> BasicResultWith404<Vec<MoveResource>> {
let account_state = self.account_state()?;
let resources = account_state.get_resources();

match accept_type {
AcceptType::Json => {
// Resolve the BCS encoded versions into `MoveResource`s
let move_resolver = self.context.move_resolver_poem(&self.latest_ledger_info)?;
let converted_resources = move_resolver
.as_converter(self.context.db.clone())
Expand All @@ -218,6 +251,7 @@ impl Account {
))
}
AcceptType::Bcs => {
// Put resources in a BTreeMap to ensure they're ordered the same every time
let resources: BTreeMap<StructTag, Vec<u8>> = resources
.map(|(key, value)| (key, value.to_vec()))
.collect();
Expand All @@ -230,10 +264,15 @@ impl Account {
}
}

/// Retrieves the move modules' bytecode associated with the account
///
/// * JSON: Return a JSON encoded version of [`Vec<MoveModuleBytecode>`] with parsed ABIs
/// * BCS: Return a sorted BCS encoded version of bytecode [`BTreeMap<MoveModuleId, Vec<u8>>`]
pub fn modules(self, accept_type: &AcceptType) -> BasicResultWith404<Vec<MoveModuleBytecode>> {
let modules = self.account_state()?.into_modules();
match accept_type {
AcceptType::Json => {
// Read bytecode and parse ABIs for output
let mut converted_modules = Vec::new();
for (_, module) in modules {
converted_modules.push(
Expand All @@ -256,6 +295,7 @@ impl Account {
))
}
AcceptType::Bcs => {
// Sort modules by name
let modules: BTreeMap<MoveModuleId, Vec<u8>> = modules
.map(|(key, value)| (key.into(), value.to_vec()))
.collect();
Expand All @@ -270,6 +310,7 @@ impl Account {

// Helpers for processing account state.

/// Retrieves the account state
pub fn account_state(&self) -> Result<AccountState, BasicErrorWith404> {
let state = self
.context
Expand All @@ -291,12 +332,17 @@ impl Account {

// Events specific stuff.

/// Retrieves an event key from a [`MoveStructTag`] and a [`Identifier`] field name
///
/// e.g. If there's the `CoinStore` module, it has a field named `withdraw_events` for
/// the withdraw events to lookup the key
pub fn find_event_key(
&self,
event_handle: MoveStructTag,
struct_tag: MoveStructTag,
field_name: Identifier,
) -> Result<EventKey, BasicErrorWith404> {
let struct_tag: StructTag = event_handle
// Parse the struct tag
let struct_tag: StructTag = struct_tag
.try_into()
.context("Given event handle was invalid")
.map_err(|err| {
Expand All @@ -307,8 +353,8 @@ impl Account {
)
})?;

// Find the resource and retrieve the struct field
let resource = self.find_resource(&struct_tag)?;

let (_id, value) = resource
.into_iter()
.find(|(id, _)| id == &field_name)
Expand All @@ -322,7 +368,7 @@ impl Account {
)
})?;

// Serialization should not fail, otherwise it's internal bug
// Deserialize the event handle to retrieve the key
let event_handle_bytes = bcs::to_bytes(&value)
.context("Failed to serialize event handle from storage")
.map_err(|err| {
Expand All @@ -348,6 +394,7 @@ impl Account {
Ok(*event_handle.key())
}

/// Find a resource associated with an account
fn find_resource(
&self,
struct_tag: &StructTag,
Expand All @@ -364,6 +411,8 @@ impl Account {
&self.latest_ledger_info,
)
})?;

// Convert to fields in move struct
let move_resolver = self.context.move_resolver_poem(&self.latest_ledger_info)?;
move_resolver
.as_converter(self.context.db.clone())
Expand Down
7 changes: 7 additions & 0 deletions api/src/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ generate_success_response!(HealthCheckResponse, (200, Ok));
generate_error_response!(HealthCheckError, (503, ServiceUnavailable), (500, Internal));
pub type HealthCheckResult<T> = poem::Result<HealthCheckResponse<T>, HealthCheckError>;

/// Basic API does healthchecking and shows the OpenAPI spec
pub struct BasicApi {
pub context: Arc<Context>,
}

/// Representation of a successful healthcheck
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Object)]
pub struct HealthCheckSuccess {
message: String,
Expand Down Expand Up @@ -74,9 +76,14 @@ impl BasicApi {
async fn healthy(
&self,
accept_type: AcceptType,
/// Threshold in seconds that the server can be behind to be considered healthy
///
/// If not provided, the healthcheck will always succeed
duration_secs: Query<Option<u32>>,
) -> HealthCheckResult<HealthCheckSuccess> {
let ledger_info = self.context.get_latest_ledger_info()?;

// If we have a duration, check that it's close to the current time, otherwise it's ok
if let Some(duration) = duration_secs.0 {
let timestamp = ledger_info.timestamp();

Expand Down
1 change: 1 addition & 0 deletions api/src/bcs_payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use poem_openapi::{
ApiResponse,
};

/// A wrapper struct for a payload containing BCS encoded bytes
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Bcs(pub Vec<u8>);

Expand Down
14 changes: 14 additions & 0 deletions api/src/blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use poem_openapi::param::{Path, Query};
use poem_openapi::OpenApi;
use std::sync::Arc;

/// API for block transactions and information
pub struct BlocksApi {
pub context: Arc<Context>,
}
Expand All @@ -21,6 +22,8 @@ impl BlocksApi {
///
/// This endpoint allows you to get the transactions in a block
/// and the corresponding block information.
///
/// If the block is pruned, it will return a 410
gregnazario marked this conversation as resolved.
Show resolved Hide resolved
#[oai(
path = "/blocks/by_height/:block_height",
method = "get",
Expand All @@ -30,7 +33,11 @@ impl BlocksApi {
async fn get_block_by_height(
&self,
accept_type: AcceptType,
/// Block height to lookup. Starts at 0
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: too many spaces

block_height: Path<u64>,
/// If set to true, include all transactions in the block
///
/// If not provided, no transactions will be retrieved
with_transactions: Query<Option<bool>>,
) -> BasicResultWith404<Block> {
fail_point_poem("endpoint_get_block_by_height")?;
Expand All @@ -47,6 +54,8 @@ impl BlocksApi {
///
/// This endpoint allows you to get the transactions in a block
/// and the corresponding block information given a version in the block.
///
/// If the block has been pruned, it will return a 410
#[oai(
path = "/blocks/by_version/:version",
method = "get",
Expand All @@ -56,7 +65,11 @@ impl BlocksApi {
async fn get_block_by_version(
&self,
accept_type: AcceptType,
/// Ledger version to lookup block information for.
version: Path<u64>,
/// If set to true, include all transactions in the block
///
/// If not provided, no transactions will be retrieved
with_transactions: Query<Option<bool>>,
) -> BasicResultWith404<Block> {
fail_point_poem("endpoint_get_block_by_version")?;
Expand Down Expand Up @@ -101,6 +114,7 @@ impl BlocksApi {
self.render_bcs_block(&accept_type, latest_ledger_info, bcs_block)
}

/// Renders a [`BcsBlock`] into a [`Block`] if it's a JSON accept type
fn render_bcs_block(
&self,
accept_type: &AcceptType,
Expand Down
Loading