Skip to content

Commit

Permalink
[API] Fix up spec generation for some types
Browse files Browse the repository at this point in the history
  • Loading branch information
banool committed Jul 28, 2022
1 parent 44db7a1 commit fb81b0c
Show file tree
Hide file tree
Showing 218 changed files with 29,966 additions and 505 deletions.
32 changes: 20 additions & 12 deletions Cargo.lock

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

7 changes: 5 additions & 2 deletions api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ edition = "2018"

[dependencies]
anyhow = "1.0.57"
async-trait = "0.1.53"
bcs = "0.1.3"
bytes = "1.1.0"
fail = "0.5.0"
Expand All @@ -21,10 +22,11 @@ mime = "0.3.16"
once_cell = "1.10.0"
paste = "1.0.7"
percent-encoding = "2.1.0"
poem = { version = "1.3.35", features = ["anyhow", "rustls"] }
poem-openapi = { version = "2.0.5", features = ["swagger-ui", "url"] }
poem = { git = "https://github.com/poem-web/poem", features = ["anyhow", "rustls"] }
poem-openapi = { git = "https://github.com/poem-web/poem", features = ["url"] }
serde = { version = "1.0.137", features = ["derive"], default-features = false }
serde_json = { version = "1.0.81", features = ["preserve_order"] }
thiserror = "1.0.31"
tokio = { version = "1.18.2", features = ["full"] }
url = "2.2.2"
warp = { version = "0.3.2", features = ["default", "tls"] }
Expand Down Expand Up @@ -65,6 +67,7 @@ executor = { path = "../execution/executor" }
executor-types = { path = "../execution/executor-types" }
mempool-notifications = { path = "../state-sync/inter-component/mempool-notifications" }
move-deps = { path = "../aptos-move/move-deps" }
serde_path_to_error = "0.1"
vm-validator = { path = "../vm-validator" }

[features]
Expand Down
45 changes: 33 additions & 12 deletions api/Makefile
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
# Copyright (c) Aptos
# SPDX-License-Identifier: Apache-2.0

test: clean lint test-code-gen test-api-spec clean
test: clean lint-v0 lint-v1 test-code-gen-v0 test-code-gen-v1 test-api-spec-v0 test-api-spec-v1 clean

lint:
npx @redocly/openapi-cli lint doc/openapi.yaml --skip-rule no-empty-servers
lint-v0:
$(call lint,doc/v0/openapi.yaml)

test-code-gen:
echo '{"generator-cli": {"version": "5.2.1"}}' > openapitools.json # v5.3 has bug, ping the version to 5.2.1
npx @openapitools/openapi-generator-cli generate -g rust -i doc/openapi.yaml -o /tmp/aptos_api_client --package-name aptos_api_client
cd /tmp/aptos_api_client && cargo build
lint-v1:
$(call lint,doc/v1/spec.yaml)

test-code-gen-v0:
$(call test_code_gen,doc/v0/openapi.yaml)

# This doesn't work right now: https://github.com/OpenAPITools/openapi-generator/issues/13038.
test-code-gen-v1:
$(call test_code_gen,doc/v1/spec.yaml)

clean:
- pkill aptos-node
- rm -rf /tmp/aptos_api_client
- rm -f openapitools.json
- rm -rf .hypothesis

test-api-spec:
test-api-spec-v0:
$(call test_api_spec,openapi.yaml)

test-api-spec-v1:
$(call test_api_spec,spec.yaml)

serve:
cd doc && python -m http.server 8888

define test_api_spec
- pkill aptos-node
cargo build -p aptos-node
./../target/debug/aptos-node --test --lazy &
Expand All @@ -32,10 +46,17 @@ test-api-spec:
--store-network-log=./../target/schemathesis-network-log.yaml \
--checks all \
--base-url http://localhost:8080 \
http://localhost:8080/openapi.yaml
http://localhost:8080/$(1)
endef

define lint
npx @redocly/openapi-cli lint $(1) --skip-rule no-empty-servers
endef

serve:
cd doc && python -m http.server 8888
define test_code_gen
echo '{"generator-cli": {"version": "6.0.1"}}' > openapitools.json # v5.3 has bug, pin the version to 5.2.1
npx @openapitools/openapi-generator-cli generate -g rust -i $(1) -o /tmp/aptos_api_client --package-name aptos_api_client
cd /tmp/aptos_api_client && cargo build
endef

.PHONY: test lint test-code-gen test-api-spec clean serve
.PHONY: test lint-v0 lint-v1 test-code-gen-v0 test-code-gen-v1 test-api-spec-v0 test-api-spec-v1 clean serve
127 changes: 5 additions & 122 deletions api/README.md
Original file line number Diff line number Diff line change
@@ -1,124 +1,7 @@
# API
# Aptos Node API

This module provides REST API for client applications to query the Aptos blockchain.
This module provides a REST API for client applications to query the Aptos blockchain.

* [API specification](doc/openapi.yaml)

> For an Aptos node, you can view the documentation at `/spec.html`.
## Overview

API routes and handlers are managed by `warp` framework; endpoints/handlers are grouped into files named by resource names (e.g. accounts, transactions).

Each handler defines:
1. Routes: all routes of the handlers supported in the file.
2. `warp` handler: an async function returns `Result<impl Reply, Rejection>`.
3. Endpoint handler: this may not be required if the endpoint logic is super simple.

`index.rs` is the root of all routes, it handles `GET /`API and connects all resources' routes with error handling.

The service is launched with a `Context` instance, which holds all external components (e.g. AptosDB, mempool sender).
The `Context` object also serves as a facade of external components, and sharing some general functionalities across
all handlers.

### Parameter Handling

There are four types HTTP input:

1. Path parameter.
2. Query parameter.
3. Request body.
4. Request header.

We process parameters in three stages:

1. Capture HTTP parameter values; this is done by `warp` routes definition.
2. Parse/deserialize HTTP parameter values into API internal data types; this should be done in the `warp` handler (the async function passed into `warp::Filter#and_then` function) or endpoint handler's constructor (`new` function).
3. Process internal data types; this is done by the endpoint handler functions.

For path parameter:

1. Create a `Param<TargetType>` type alias `TargetTypeParam` in the param.rs, and use it in the `warp` route definition for capturing the HTTP path parameter. We don't parse the parameter at this stage, because `warp` will drop error and return `not_found` error without a meaningful message.
2. The `TargetType` is required to implement `FromStr` trait, and `Param#parse` uses it for parsing the HTTP path parameter string and returning `400` error code with a meaningful invalid parameter error message.

Query parameters should not be required, always provide default values for the case they are not provided.

### Principles

To create easy to use API, the following principles are valued

1. [Robustness](https://en.wikipedia.org/wiki/Robustness_principle): be conservative in what you do, be liberal in what you accept from others. Specifically, the API should accept variant formats of valid input data, but be restricted to the output it produces. For example, an account address may have three valid hex-encoded formats: `0x1`, `0x00000000000000000000000000000001` and `00000000000000000000000000000001`; API accepts all of them as input, but all API should output consistent same format (`0x1`). The API should also only expose must-have and the most stable concepts as data structure.
2. Layered Architecture: the API is a layer on top of Aptos core/blockchain. JSON is the primary content type we used, a client application should be able to do all aspects of interaction with Aptos blockchain using JSON.
3. Compatible with JSON standard and most of the tools, e.g. output `string` type for `u64` instead of integer.

### Models

Models or types are defined in the `aptos-api-types` package (in the directory `/api/types`).

These types handle the JSON serialization and deserialization between internal data types and API response JSON types.

`From` / `TryFrom` traits are implemented for converting between API data type and Aptos core data types instead of special constructors.

Move data are converted by procedures defined in the `convert.rs`, because Move data type definitions are defined by the Move module stored in the Aptos DB. We first retrieve Move data types from the database, then convert them into API data types.

When we convert internal Move struct values into JSON, the data type information will be lost, thus we can't direct convert move struct value JSON data back to any internal data structure while deserializing HTTP request data.
For this reason:
1. `aptos_api_types::MoveValue` is only used internally for converting move values into JSON before we create external facing API types (e.g. `TransactionPayload`).
2. When deserializing API request JSON data, we first convert them into external facing API types with Move values as JSON value, then convert Move JSON values into internal move value type `move_core_types::value::MoveValue` when we need.

### Error Handling

Errors are handled by the `warp.Rejection` handler defined in the `index.rs` for all routes.
An `anyhow::Error` is considered as server internal error (500) by default.
All internal errors should be converted into `anyhow::Error` first.
An `aptos_api_types.Error` is defined for converting `anyhow::Error` to `warp.Rejection` with HTTP error code.

## Testing

### Unit Test

Handler tests should cover all aspects of features and functions.

A `TestContext` is implemented to create components' stubs that API handlers are connected to.
These stubs are more close to real production components, instead of mocks, so that tests can ensure the API
handlers are working well with other components in the systems.
For example, we use real AptosDB implementation in tests for API layers to interact with the database.

Most of the utility functions are provided by the `TestContext`.

### Integration/Smoke Test

Run integration/smoke tests in `testsuite/smoke-test`

```
cargo test --test "forge" "api::"
```

### API Specification Test

* Run `scripts/dev_setup.sh -a` to setup tools.
* Run `make test` inside the `api` directory.

### [Failpoint](https://docs.rs/fail/latest/fail/) setup

Failpoint configuration example:

```
failpoints
api::endpoint_index: 1%return
api::endpoint_get_account: 1%return
api::endpoint_get_account_resources: 1%return
api::endpoint_get_account_modules: 1%return
api::endpoint_get_transaction: 1%return
api::endpoint_get_transactions: 1%return
api::endpoint_get_account_transactions: 1%return
api::endpoint_submit_json_transactions: 1%return
api::endpoint_submit_bcs_transactions: 1%return
api::endpoint_create_signing_message: 1%return
api::endpoint_get_events_by_event_key: 1%return
api::endpoint_get_events_by_event_handle: 1%return
```

## Aptos Node Operation

Please refer to [Operation](Operation.md) document for details, including configuration, logging, metrics etc.
This API is versioned. See the README for each version below:
- v0: [doc/v0/README.md](doc/v0/README.md)
- v1: [doc/v1/README.md](doc/v1/README.md)
2 changes: 1 addition & 1 deletion api/Operation.md → api/doc/v0/Operation.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ The latency and counts of requests that are processed by a handler are recorded
named `aptos_api_requests` and labelled by:

* method: HTTP request method
* operation_id: request handler/operation id, it should be same `operationId` defined in [OpenAPI specification](doc/openapi.yaml), except couple cases that are not defined in the [OpenAPI specification](doc/openapi.yaml), e.g. `json_rpc`.
* operation_id: request handler/operation id, it should be same `operationId` defined in [OpenAPI specification](openapi.yaml), except couple cases that are not defined in the [OpenAPI specification](openapi.yaml), e.g. `json_rpc`.
* status: HTTP response statuc code

This metrics covers all requests responses served the API handlers.
Expand Down
Loading

0 comments on commit fb81b0c

Please sign in to comment.