diff --git a/crates/sui-rest-api/openapi/openapi.json b/crates/sui-rest-api/openapi/openapi.json index 1a41ebad1d62b..494555500fef9 100644 --- a/crates/sui-rest-api/openapi/openapi.json +++ b/crates/sui-rest-api/openapi/openapi.json @@ -77,149 +77,6 @@ } } }, - "/checkpoints": { - "get": { - "tags": [ - "Checkpoint" - ], - "description": "[![stable](https://img.shields.io/badge/api-stable-53b576?style=for-the-badge)](#)\n\nList Checkpoints\n\nRequest a page of checkpoints, and optionally their contents, ordered by\n`CheckpointSequenceNumber`.\n\nIf the requested page is below the Node's `lowest_available_checkpoint`, a 410 will be\nreturned.", - "operationId": "List Checkpoints", - "parameters": [ - { - "in": "query", - "name": "contents", - "description": "Request `CheckpointContents` be included in the response", - "schema": { - "description": "Request `CheckpointContents` be included in the response", - "default": false, - "type": "boolean" - }, - "style": "form" - }, - { - "in": "query", - "name": "direction", - "description": "The direction to paginate in.\n\nDefaults to `descending` if not provided.", - "schema": { - "description": "The direction to paginate in.\n\nDefaults to `descending` if not provided.", - "allOf": [ - { - "$ref": "#/components/schemas/Direction" - } - ] - }, - "style": "form" - }, - { - "in": "query", - "name": "limit", - "description": "Page size limit for the response.\n\nDefaults to `50` if not provided with a maximum page size of `100`.", - "schema": { - "description": "Page size limit for the response.\n\nDefaults to `50` if not provided with a maximum page size of `100`.", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "style": "form" - }, - { - "in": "query", - "name": "start", - "description": "The checkpoint to start listing from.\n\nDefaults to the latest checkpoint if not provided.", - "schema": { - "description": "The checkpoint to start listing from.\n\nDefaults to the latest checkpoint if not provided.", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "style": "form" - } - ], - "responses": { - "200": { - "description": "", - "headers": { - "x-sui-cursor": { - "style": "simple", - "schema": { - "type": "string" - } - } - }, - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/CheckpointResponse" - } - } - }, - "application/bcs": {} - } - }, - "410": { - "description": "" - }, - "500": { - "description": "" - } - } - } - }, - "/checkpoints/{checkpoint}": { - "get": { - "tags": [ - "Checkpoint" - ], - "description": "[![stable](https://img.shields.io/badge/api-stable-53b576?style=for-the-badge)](#)\n\nFetch a Checkpoint\n\nFetch a checkpoint either by `CheckpointSequenceNumber` (checkpoint height) or by\n`CheckpointDigest` and optionally request its contents.\n\nIf the checkpoint has been pruned and is not available, a 410 will be returned.", - "operationId": "Get Checkpoint", - "parameters": [ - { - "in": "path", - "name": "checkpoint", - "required": true, - "schema": { - "$ref": "#/components/schemas/CheckpointId" - }, - "style": "simple" - }, - { - "in": "query", - "name": "contents", - "description": "Request `CheckpointContents` be included in the response", - "schema": { - "description": "Request `CheckpointContents` be included in the response", - "default": false, - "type": "boolean" - }, - "style": "form" - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CheckpointResponse" - } - }, - "application/bcs": {} - } - }, - "404": { - "description": "" - }, - "410": { - "description": "" - }, - "500": { - "description": "" - } - } - } - }, "/accounts/{account}/objects": { "get": { "tags": [ @@ -425,71 +282,26 @@ } } }, - "/checkpoints/{checkpoint}/full": { - "get": { - "tags": [ - "Checkpoint" - ], - "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\nFetch a Full Checkpoint\n\nRequest a checkpoint and all data associated with it including:\n- CheckpointSummary\n- Validator Signature\n- CheckpointContents\n- Transactions, Effects, Events, as well as all input and output objects\n\nIf the requested checkpoint is below the Node's `lowest_available_checkpoint_objects`, a 410\nwill be returned.", - "operationId": "Get Full Checkpoint", - "parameters": [ - { - "in": "path", - "name": "checkpoint", - "required": true, - "schema": { - "$ref": "#/components/schemas/CheckpointId" - }, - "style": "simple" - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/bcs": {} - } - }, - "404": { - "description": "" - }, - "410": { - "description": "" - }, - "500": { - "description": "" - } - } - } - }, - "/checkpoints/full": { + "/checkpoints": { "get": { "tags": [ "Checkpoint" ], - "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\nList Full Checkpoints\n\nRequest a page of checkpoints and all data associated with them including:\n- CheckpointSummary\n- Validator Signature\n- CheckpointContents\n- Transactions, Effects, Events, as well as all input and output objects\n\nIf the requested page is below the Node's `lowest_available_checkpoint_objects`, a 410 will be\nreturned.", - "operationId": "List Full Checkpoints", + "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", + "operationId": "ListCheckpoints", "parameters": [ { "in": "query", "name": "direction", - "description": "The direction to paginate in.\n\nDefaults to `descending` if not provided.", "schema": { - "description": "The direction to paginate in.\n\nDefaults to `descending` if not provided.", - "allOf": [ - { - "$ref": "#/components/schemas/Direction" - } - ] + "$ref": "#/components/schemas/Direction" }, "style": "form" }, { "in": "query", "name": "limit", - "description": "Page size limit for the response.\n\nDefaults to `5` if not provided with a maximum page size of `10`.", "schema": { - "description": "Page size limit for the response.\n\nDefaults to `5` if not provided with a maximum page size of `10`.", "type": "integer", "format": "uint32", "minimum": 0.0 @@ -512,14 +324,68 @@ "responses": { "200": { "description": "", + "headers": { + "x-sui-cursor": { + "style": "simple", + "schema": { + "type": "string" + } + } + }, "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SignedCheckpointSummary" + } + } + }, "application/bcs": {} } }, "410": { "description": "" + } + } + } + }, + "/checkpoints/{checkpoint}": { + "get": { + "tags": [ + "Checkpoint" + ], + "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", + "operationId": "GetCheckpoint", + "parameters": [ + { + "in": "path", + "name": "checkpoint", + "required": true, + "schema": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "style": "simple" + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SignedCheckpointSummary" + } + }, + "application/bcs": {} + } + }, + "404": { + "description": "" }, - "500": { + "410": { "description": "" } } @@ -1358,61 +1224,12 @@ } ] }, - "CheckpointContents": { - "type": "array", - "items": { - "$ref": "#/components/schemas/CheckpointTransactionInfo" - } - }, "CheckpointContentsDigest": { "$ref": "#/components/schemas/Digest" }, "CheckpointDigest": { "$ref": "#/components/schemas/Digest" }, - "CheckpointId": { - "anyOf": [ - { - "title": "SequenceNumber", - "description": "Sequence number or height of a Checkpoint", - "examples": [ - 0 - ], - "type": "string", - "format": "u64" - }, - { - "title": "Digest", - "description": "Base58 encoded 32-byte digest of a Checkpoint", - "examples": [ - "4btiuiMPvEENsttpZC7CZ53DruC3MAgfznDbASZ7DR6S" - ], - "allOf": [ - { - "$ref": "#/components/schemas/CheckpointDigest" - } - ] - } - ] - }, - "CheckpointResponse": { - "type": "object", - "required": [ - "checkpoint", - "signature" - ], - "properties": { - "checkpoint": { - "$ref": "#/components/schemas/CheckpointSummary" - }, - "contents": { - "$ref": "#/components/schemas/CheckpointContents" - }, - "signature": { - "$ref": "#/components/schemas/ValidatorAggregatedSignature" - } - } - }, "CheckpointSummary": { "type": "object", "required": [ @@ -1480,28 +1297,6 @@ } } }, - "CheckpointTransactionInfo": { - "type": "object", - "required": [ - "effects", - "signatures", - "transaction" - ], - "properties": { - "effects": { - "$ref": "#/components/schemas/TransactionEffectsDigest" - }, - "signatures": { - "type": "array", - "items": { - "$ref": "#/components/schemas/UserSignature" - } - }, - "transaction": { - "$ref": "#/components/schemas/TransactionDigest" - } - } - }, "CircomG1": { "description": "A G1 point in BN254 serialized as a vector of three strings which is the canonical decimal representation of the projective coordinates in Fq.", "type": "array", @@ -4201,6 +3996,21 @@ "type": "string", "format": "base64" }, + "SignedCheckpointSummary": { + "type": "object", + "required": [ + "checkpoint", + "signature" + ], + "properties": { + "checkpoint": { + "$ref": "#/components/schemas/CheckpointSummary" + }, + "signature": { + "$ref": "#/components/schemas/ValidatorAggregatedSignature" + } + } + }, "SimpleSignature": { "oneOf": [ { @@ -4849,9 +4659,6 @@ } ] }, - "TransactionEffectsDigest": { - "$ref": "#/components/schemas/Digest" - }, "TransactionEvents": { "type": "array", "items": { diff --git a/crates/sui-rest-api/src/checkpoints.rs b/crates/sui-rest-api/src/checkpoints.rs index 0da6439b04c75..425828d398c6a 100644 --- a/crates/sui-rest-api/src/checkpoints.rs +++ b/crates/sui-rest-api/src/checkpoints.rs @@ -4,34 +4,96 @@ use axum::extract::Query; use axum::extract::{Path, State}; use sui_sdk_types::types::{ - CheckpointContents, CheckpointDigest, CheckpointSequenceNumber, CheckpointSummary, - SignedCheckpointSummary, ValidatorAggregatedSignature, + CheckpointData, CheckpointDigest, CheckpointSequenceNumber, SignedCheckpointSummary, }; use sui_types::storage::ReadStore; use tap::Pipe; use crate::openapi::{ApiEndpoint, OperationBuilder, ResponseBuilder, RouteHandler}; use crate::reader::StateReader; -use crate::response::Bcs; use crate::Page; -use crate::{accept::AcceptFormat, response::ResponseContent, RestError, Result}; +use crate::{accept::AcceptFormat, response::ResponseContent, Result}; use crate::{Direction, RestService}; -use documented::Documented; -#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] -pub struct CheckpointResponse { - pub checkpoint: CheckpointSummary, - pub signature: ValidatorAggregatedSignature, - pub contents: Option, +pub struct GetCheckpointFull; + +impl ApiEndpoint for GetCheckpointFull { + fn method(&self) -> axum::http::Method { + axum::http::Method::GET + } + + fn path(&self) -> &'static str { + "/checkpoints/{checkpoint}/full" + } + + fn hidden(&self) -> bool { + true + } + + fn operation( + &self, + generator: &mut schemars::gen::SchemaGenerator, + ) -> openapiv3::v3_1::Operation { + OperationBuilder::new() + .tag("Checkpoint") + .operation_id("GetCheckpointFull") + .path_parameter::("checkpoint", generator) + .response( + 200, + ResponseBuilder::new() + .json_content::(generator) + .bcs_content() + .build(), + ) + .response(404, ResponseBuilder::new().build()) + .response(410, ResponseBuilder::new().build()) + .build() + } + + fn handler(&self) -> RouteHandler { + RouteHandler::new(self.method(), get_checkpoint_full) + } +} + +async fn get_checkpoint_full( + Path(checkpoint_id): Path, + accept: AcceptFormat, + State(state): State, +) -> Result> { + let verified_summary = match checkpoint_id { + CheckpointId::SequenceNumber(s) => { + // Since we need object contents we need to check for the lowest available checkpoint + // with objects that hasn't been pruned + let oldest_checkpoint = state.inner().get_lowest_available_checkpoint_objects()?; + if s < oldest_checkpoint { + return Err(crate::RestError::new( + axum::http::StatusCode::GONE, + "Old checkpoints have been pruned", + )); + } + + state.inner().get_checkpoint_by_sequence_number(s) + } + CheckpointId::Digest(d) => state.inner().get_checkpoint_by_digest(&d.into()), + }? + .ok_or(CheckpointNotFoundError(checkpoint_id))?; + + let checkpoint_contents = state + .inner() + .get_checkpoint_contents_by_digest(&verified_summary.content_digest)? + .ok_or(CheckpointNotFoundError(checkpoint_id))?; + + let checkpoint_data = state + .inner() + .get_checkpoint_data(verified_summary, checkpoint_contents)?; + + match accept { + AcceptFormat::Json => ResponseContent::Json(checkpoint_data), + AcceptFormat::Bcs => ResponseContent::Bcs(checkpoint_data), + } + .pipe(Ok) } -/// Fetch a Checkpoint -/// -/// Fetch a checkpoint either by `CheckpointSequenceNumber` (checkpoint height) or by -/// `CheckpointDigest` and optionally request its contents. -/// -/// If the checkpoint has been pruned and is not available, a 410 will be returned. -#[derive(Documented)] pub struct GetCheckpoint; impl ApiEndpoint for GetCheckpoint { @@ -43,30 +105,23 @@ impl ApiEndpoint for GetCheckpoint { "/checkpoints/{checkpoint}" } - fn stable(&self) -> bool { - true - } - fn operation( &self, generator: &mut schemars::gen::SchemaGenerator, ) -> openapiv3::v3_1::Operation { OperationBuilder::new() .tag("Checkpoint") - .operation_id("Get Checkpoint") - .description(Self::DOCS) - .path_parameter::("checkpoint", generator) - .query_parameters::(generator) + .operation_id("GetCheckpoint") + .path_parameter::("checkpoint", generator) .response( 200, ResponseBuilder::new() - .json_content::(generator) + .json_content::(generator) .bcs_content() .build(), ) .response(404, ResponseBuilder::new().build()) .response(410, ResponseBuilder::new().build()) - .response(500, ResponseBuilder::new().build()) .build() } @@ -77,14 +132,10 @@ impl ApiEndpoint for GetCheckpoint { async fn get_checkpoint( Path(checkpoint_id): Path, - Query(parameters): Query, accept: AcceptFormat, State(state): State, -) -> Result> { - let SignedCheckpointSummary { - checkpoint, - signature, - } = match checkpoint_id { +) -> Result> { + let summary = match checkpoint_id { CheckpointId::SequenceNumber(s) => { let oldest_checkpoint = state.inner().get_lowest_available_checkpoint()?; if s < oldest_checkpoint { @@ -102,51 +153,19 @@ async fn get_checkpoint( .into_inner() .try_into()?; - let contents = if parameters.contents { - Some( - state - .inner() - .get_checkpoint_contents_by_sequence_number(checkpoint.sequence_number)? - .ok_or(CheckpointNotFoundError(checkpoint_id))? - .try_into()?, - ) - } else { - None - }; - - let response = CheckpointResponse { - checkpoint, - signature, - contents, - }; - match accept { - AcceptFormat::Json => ResponseContent::Json(response), - AcceptFormat::Bcs => ResponseContent::Bcs(response), + AcceptFormat::Json => ResponseContent::Json(summary), + AcceptFormat::Bcs => ResponseContent::Bcs(summary), } .pipe(Ok) } -#[derive(Debug, Copy, Clone, Eq, PartialEq, schemars::JsonSchema)] -#[schemars(untagged)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum CheckpointId { - #[schemars( - title = "SequenceNumber", - example = "CheckpointSequenceNumber::default" - )] - /// Sequence number or height of a Checkpoint - SequenceNumber(#[schemars(with = "crate::_schemars::U64")] CheckpointSequenceNumber), - #[schemars(title = "Digest", example = "example_digest")] - /// Base58 encoded 32-byte digest of a Checkpoint + SequenceNumber(CheckpointSequenceNumber), Digest(CheckpointDigest), } -fn example_digest() -> CheckpointDigest { - "4btiuiMPvEENsttpZC7CZ53DruC3MAgfznDbASZ7DR6S" - .parse() - .unwrap() -} - impl<'de> serde::Deserialize<'de> for CheckpointId { fn deserialize(deserializer: D) -> std::result::Result where @@ -202,22 +221,6 @@ impl From for crate::RestError { } } -/// Query parameters for the GetCheckpoint endpoint -#[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] -pub struct GetCheckpointQueryParameters { - /// Request `CheckpointContents` be included in the response - #[serde(default)] - pub contents: bool, -} - -/// List Checkpoints -/// -/// Request a page of checkpoints, and optionally their contents, ordered by -/// `CheckpointSequenceNumber`. -/// -/// If the requested page is below the Node's `lowest_available_checkpoint`, a 410 will be -/// returned. -#[derive(Documented)] pub struct ListCheckpoints; impl ApiEndpoint for ListCheckpoints { @@ -229,29 +232,23 @@ impl ApiEndpoint for ListCheckpoints { "/checkpoints" } - fn stable(&self) -> bool { - true - } - fn operation( &self, generator: &mut schemars::gen::SchemaGenerator, ) -> openapiv3::v3_1::Operation { OperationBuilder::new() .tag("Checkpoint") - .operation_id("List Checkpoints") - .description(Self::DOCS) + .operation_id("ListCheckpoints") .query_parameters::(generator) .response( 200, ResponseBuilder::new() - .json_content::>(generator) + .json_content::>(generator) .bcs_content() .header::(crate::types::X_SUI_CURSOR, generator) .build(), ) .response(410, ResponseBuilder::new().build()) - .response(500, ResponseBuilder::new().build()) .build() } @@ -264,7 +261,7 @@ async fn list_checkpoints( Query(parameters): Query, accept: AcceptFormat, State(state): State, -) -> Result> { +) -> Result> { let latest_checkpoint = state.inner().get_latest_checkpoint()?.sequence_number; let oldest_checkpoint = state.inner().get_lowest_available_checkpoint()?; let limit = parameters.limit(); @@ -284,36 +281,15 @@ async fn list_checkpoints( .map(|result| { result .map_err(Into::into) - .and_then(|(checkpoint, contents)| { - let SignedCheckpointSummary { - checkpoint, - signature, - } = checkpoint.try_into()?; - let contents = if parameters.contents { - Some(contents.try_into()?) - } else { - None - }; - Ok(CheckpointResponse { - checkpoint, - signature, - contents, - }) + .and_then(|(checkpoint, _contents)| { + SignedCheckpointSummary::try_from(checkpoint).map_err(Into::into) }) }) .collect::>>()?; let cursor = checkpoints.last().and_then(|checkpoint| match direction { Direction::Ascending => checkpoint.checkpoint.sequence_number.checked_add(1), - Direction::Descending => { - let cursor = checkpoint.checkpoint.sequence_number.checked_sub(1); - // If we've exhausted our available checkpoint range then there are no more pages left - if cursor < Some(oldest_checkpoint) { - None - } else { - cursor - } - } + Direction::Descending => checkpoint.checkpoint.sequence_number.checked_sub(1), }); match accept { @@ -326,21 +302,12 @@ async fn list_checkpoints( #[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] pub struct ListCheckpointsQueryParameters { - /// Page size limit for the response. - /// - /// Defaults to `50` if not provided with a maximum page size of `100`. pub limit: Option, /// The checkpoint to start listing from. /// /// Defaults to the latest checkpoint if not provided. pub start: Option, - /// The direction to paginate in. - /// - /// Defaults to `descending` if not provided. pub direction: Option, - /// Request `CheckpointContents` be included in the response - #[serde(default)] - pub contents: bool, } impl ListCheckpointsQueryParameters { @@ -358,250 +325,3 @@ impl ListCheckpointsQueryParameters { self.direction.unwrap_or(Direction::Descending) } } - -/// Fetch a Full Checkpoint -/// -/// Request a checkpoint and all data associated with it including: -/// - CheckpointSummary -/// - Validator Signature -/// - CheckpointContents -/// - Transactions, Effects, Events, as well as all input and output objects -/// -/// If the requested checkpoint is below the Node's `lowest_available_checkpoint_objects`, a 410 -/// will be returned. -#[derive(Documented)] -pub struct GetFullCheckpoint; - -impl ApiEndpoint for GetFullCheckpoint { - fn method(&self) -> axum::http::Method { - axum::http::Method::GET - } - - fn path(&self) -> &'static str { - "/checkpoints/{checkpoint}/full" - } - - fn stable(&self) -> bool { - // TODO transactions are serialized with an intent message, do we want to change this - // format to remove it (and remove user signature duplication) prior to stabalizing the - // format? - false - } - - fn operation( - &self, - generator: &mut schemars::gen::SchemaGenerator, - ) -> openapiv3::v3_1::Operation { - OperationBuilder::new() - .tag("Checkpoint") - .operation_id("Get Full Checkpoint") - .description(Self::DOCS) - .path_parameter::("checkpoint", generator) - .response(200, ResponseBuilder::new().bcs_content().build()) - .response(404, ResponseBuilder::new().build()) - .response(410, ResponseBuilder::new().build()) - .response(500, ResponseBuilder::new().build()) - .build() - } - - fn handler(&self) -> RouteHandler { - RouteHandler::new(self.method(), get_full_checkpoint) - } -} - -async fn get_full_checkpoint( - Path(checkpoint_id): Path, - accept: AcceptFormat, - State(state): State, -) -> Result> { - match accept { - AcceptFormat::Bcs => {} - _ => { - return Err(RestError::new( - axum::http::StatusCode::BAD_REQUEST, - "invalid accept type; only 'application/bcs' is supported", - )) - } - } - - let verified_summary = match checkpoint_id { - CheckpointId::SequenceNumber(s) => { - // Since we need object contents we need to check for the lowest available checkpoint - // with objects that hasn't been pruned - let oldest_checkpoint = state.inner().get_lowest_available_checkpoint_objects()?; - if s < oldest_checkpoint { - return Err(crate::RestError::new( - axum::http::StatusCode::GONE, - "Old checkpoints have been pruned", - )); - } - - state.inner().get_checkpoint_by_sequence_number(s) - } - CheckpointId::Digest(d) => state.inner().get_checkpoint_by_digest(&d.into()), - }? - .ok_or(CheckpointNotFoundError(checkpoint_id))?; - - let checkpoint_contents = state - .inner() - .get_checkpoint_contents_by_digest(&verified_summary.content_digest)? - .ok_or(CheckpointNotFoundError(checkpoint_id))?; - - let checkpoint_data = state - .inner() - .get_checkpoint_data(verified_summary, checkpoint_contents)?; - - Ok(Bcs(checkpoint_data)) -} - -/// List Full Checkpoints -/// -/// Request a page of checkpoints and all data associated with them including: -/// - CheckpointSummary -/// - Validator Signature -/// - CheckpointContents -/// - Transactions, Effects, Events, as well as all input and output objects -/// -/// If the requested page is below the Node's `lowest_available_checkpoint_objects`, a 410 will be -/// returned. -#[derive(Documented)] -pub struct ListFullCheckpoints; - -impl ApiEndpoint for ListFullCheckpoints { - fn method(&self) -> axum::http::Method { - axum::http::Method::GET - } - - fn path(&self) -> &'static str { - "/checkpoints/full" - } - - fn stable(&self) -> bool { - // TODO transactions are serialized with an intent message, do we want to change this - // format to remove it (and remove user signature duplication) prior to stabalizing the - // format? - false - } - - fn operation( - &self, - generator: &mut schemars::gen::SchemaGenerator, - ) -> openapiv3::v3_1::Operation { - OperationBuilder::new() - .tag("Checkpoint") - .operation_id("List Full Checkpoints") - .description(Self::DOCS) - .query_parameters::(generator) - .response(200, ResponseBuilder::new().bcs_content().build()) - .response(410, ResponseBuilder::new().build()) - .response(500, ResponseBuilder::new().build()) - .build() - } - - fn handler(&self) -> RouteHandler { - RouteHandler::new(self.method(), list_full_checkpoints) - } -} - -async fn list_full_checkpoints( - Query(parameters): Query, - accept: AcceptFormat, - State(state): State, -) -> Result> { - match accept { - AcceptFormat::Bcs => {} - _ => { - return Err(RestError::new( - axum::http::StatusCode::BAD_REQUEST, - "invalid accept type; only 'application/bcs' is supported", - )) - } - } - - let latest_checkpoint = state.inner().get_latest_checkpoint()?.sequence_number; - let oldest_checkpoint = state.inner().get_lowest_available_checkpoint_objects()?; - let limit = parameters.limit(); - let start = parameters.start(latest_checkpoint); - let direction = parameters.direction(); - - if start < oldest_checkpoint { - return Err(crate::RestError::new( - axum::http::StatusCode::GONE, - "Old checkpoints have been pruned", - )); - } - - let checkpoints = state - .checkpoint_iter(direction, start) - .take( - // only iterate until we've reached the edge of our objects available window - if direction.is_descending() { - std::cmp::min(limit, start.saturating_sub(oldest_checkpoint) as usize) - } else { - limit - }, - ) - .map(|result| { - result - .map_err(Into::into) - .and_then(|(checkpoint, contents)| { - state - .inner() - .get_checkpoint_data( - sui_types::messages_checkpoint::VerifiedCheckpoint::new_from_verified( - checkpoint, - ), - contents, - ) - .map_err(Into::into) - }) - }) - .collect::>>()?; - - let cursor = checkpoints.last().and_then(|checkpoint| match direction { - Direction::Ascending => checkpoint.checkpoint_summary.sequence_number.checked_add(1), - Direction::Descending => { - let cursor = checkpoint.checkpoint_summary.sequence_number.checked_sub(1); - // If we've exhausted our available object range then there are no more pages left - if cursor < Some(oldest_checkpoint) { - None - } else { - cursor - } - } - }); - - ResponseContent::Bcs(checkpoints) - .pipe(|entries| Page { entries, cursor }) - .pipe(Ok) -} - -#[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] -pub struct ListFullCheckpointsQueryParameters { - /// Page size limit for the response. - /// - /// Defaults to `5` if not provided with a maximum page size of `10`. - pub limit: Option, - /// The checkpoint to start listing from. - /// - /// Defaults to the latest checkpoint if not provided. - pub start: Option, - /// The direction to paginate in. - /// - /// Defaults to `descending` if not provided. - pub direction: Option, -} - -impl ListFullCheckpointsQueryParameters { - pub fn limit(&self) -> usize { - self.limit.map(|l| (l as usize).clamp(1, 10)).unwrap_or(5) - } - - pub fn start(&self, default: CheckpointSequenceNumber) -> CheckpointSequenceNumber { - self.start.unwrap_or(default) - } - - pub fn direction(&self) -> Direction { - self.direction.unwrap_or(Direction::Descending) - } -} diff --git a/crates/sui-rest-api/src/client/mod.rs b/crates/sui-rest-api/src/client/mod.rs index 1ee1f5d804682..a5582a0928662 100644 --- a/crates/sui-rest-api/src/client/mod.rs +++ b/crates/sui-rest-api/src/client/mod.rs @@ -70,14 +70,7 @@ impl Client { .get_checkpoint(checkpoint_sequence_number) .await .map(Response::into_inner) - .and_then(|checkpoint| { - sui_sdk_types::types::SignedCheckpointSummary { - checkpoint: checkpoint.checkpoint, - signature: checkpoint.signature, - } - .try_into() - .map_err(Into::into) - }) + .and_then(|checkpoint| checkpoint.try_into().map_err(Into::into)) } pub async fn get_object(&self, object_id: ObjectID) -> Result { diff --git a/crates/sui-rest-api/src/client/sdk.rs b/crates/sui-rest-api/src/client/sdk.rs index f5e94ef898c58..36702a316cc17 100644 --- a/crates/sui-rest-api/src/client/sdk.rs +++ b/crates/sui-rest-api/src/client/sdk.rs @@ -23,7 +23,6 @@ use tap::Pipe; use crate::accounts::AccountOwnedObjectInfo; use crate::accounts::ListAccountOwnedObjectsQueryParameters; -use crate::checkpoints::CheckpointResponse; use crate::checkpoints::ListCheckpointsQueryParameters; use crate::coins::CoinInfo; use crate::health::Threshold; @@ -285,7 +284,7 @@ impl Client { pub async fn get_checkpoint( &self, checkpoint_sequence_number: CheckpointSequenceNumber, - ) -> Result> { + ) -> Result> { let url = self .url() .join(&format!("checkpoints/{checkpoint_sequence_number}"))?; @@ -305,7 +304,6 @@ impl Client { limit: Some(1), start: None, direction: None, - contents: false, }; let (mut page, parts) = self.list_checkpoints(¶meters).await?.into_parts(); @@ -313,10 +311,6 @@ impl Client { let checkpoint = page .pop() .ok_or_else(|| Error::new_message("server returned empty checkpoint list"))?; - let checkpoint = SignedCheckpointSummary { - checkpoint: checkpoint.checkpoint, - signature: checkpoint.signature, - }; Ok(Response::new(checkpoint, parts)) } @@ -324,7 +318,7 @@ impl Client { pub async fn list_checkpoints( &self, parameters: &ListCheckpointsQueryParameters, - ) -> Result>> { + ) -> Result>> { let url = self.url().join("checkpoints")?; let response = self diff --git a/crates/sui-rest-api/src/lib.rs b/crates/sui-rest-api/src/lib.rs index 3cf1e49952efe..b0bd1696a4f3b 100644 --- a/crates/sui-rest-api/src/lib.rs +++ b/crates/sui-rest-api/src/lib.rs @@ -46,12 +46,6 @@ pub enum Direction { Descending, } -impl Direction { - pub fn is_descending(self) -> bool { - matches!(self, Self::Descending) - } -} - #[derive(Debug)] pub struct Page { pub entries: response::ResponseContent>, @@ -75,15 +69,14 @@ const ENDPOINTS: &[&dyn ApiEndpoint] = &[ // stable APIs &info::GetNodeInfo, &health::HealthCheck, - &checkpoints::ListCheckpoints, - &checkpoints::GetCheckpoint, // unstable APIs &accounts::ListAccountObjects, &objects::GetObject, &objects::GetObjectWithVersion, &objects::ListDynamicFields, - &checkpoints::GetFullCheckpoint, - &checkpoints::ListFullCheckpoints, + &checkpoints::ListCheckpoints, + &checkpoints::GetCheckpoint, + &checkpoints::GetCheckpointFull, &transactions::GetTransaction, &transactions::ListTransactions, &committee::GetCommittee,