Skip to content

Commit

Permalink
getCheckpoints (MystenLabs#9263)
Browse files Browse the repository at this point in the history
## Description 

add getCheckpoints paginated

## Test Plan 

How did you test the new or updated feature?

added test_get_checkpoints() in rpc_server_tests.rs

---
If your changes are not user-facing and not a breaking change, you can
skip the following section. Otherwise, please indicate what changed, and
then add to the Release Notes section as highlighted during the release
process.

### Type of Change (Check all that apply)

- [ ] user-visible impact
- [ ] breaking change for a client SDKs
- [ ] breaking change for FNs (FN binary must upgrade)
- [ ] breaking change for validators or node operators (must upgrade
binaries)
- [ ] breaking change for on-chain data layout
- [ ] necessitate either a data wipe or data migration

### Release notes

---------

Co-authored-by: Chris Li <[email protected]>
  • Loading branch information
siomari and 666lcz authored Mar 19, 2023
1 parent 5b0cb07 commit 8700809
Show file tree
Hide file tree
Showing 13 changed files with 417 additions and 37 deletions.
5 changes: 5 additions & 0 deletions .changeset/polite-owls-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@mysten/sui.js": minor
---

Add a new `getCheckpoints` endpoint that returns a paginated list of checkpoints.
173 changes: 169 additions & 4 deletions crates/sui-core/src/authority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ use sui_adapter::{adapter, execution_mode};
use sui_config::genesis::Genesis;
use sui_config::node::{AuthorityStorePruningConfig, DBCheckpointConfig};
use sui_json_rpc_types::{
DevInspectResults, DryRunTransactionResponse, EventFilter, SuiEvent, SuiMoveValue,
Checkpoint, DevInspectResults, DryRunTransactionResponse, EventFilter, SuiEvent, SuiMoveValue,
SuiObjectDataFilter, SuiTransactionEvents,
};
use sui_macros::{fail_point, fail_point_async, nondeterministic};
Expand Down Expand Up @@ -2149,6 +2149,7 @@ impl AuthorityState {
pub fn get_owner_objects(
&self,
owner: SuiAddress,
// If `Some`, the query will start from the next item after the specified cursor
cursor: Option<ObjectID>,
limit: usize,
filter: Option<SuiObjectDataFilter>,
Expand All @@ -2163,6 +2164,7 @@ impl AuthorityState {
pub fn get_owner_objects_iterator(
&self,
owner: SuiAddress,
// If `Some`, the query will start from the next item after the specified cursor
cursor: Option<ObjectID>,
limit: Option<usize>,
filter: Option<SuiObjectDataFilter>,
Expand Down Expand Up @@ -2202,7 +2204,7 @@ impl AuthorityState {
pub fn get_dynamic_fields(
&self,
owner: ObjectID,
// exclusive cursor if `Some`, otherwise start from the beginning
// If `Some`, the query will start from the next item after the specified cursor
cursor: Option<ObjectID>,
limit: usize,
) -> SuiResult<Vec<DynamicFieldInfo>> {
Expand All @@ -2215,6 +2217,7 @@ impl AuthorityState {
pub fn get_dynamic_fields_iterator(
&self,
owner: ObjectID,
// If `Some`, the query will start from the next item after the specified cursor
cursor: Option<ObjectID>,
) -> SuiResult<impl Iterator<Item = DynamicFieldInfo> + '_> {
if let Some(indexes) = &self.indexes {
Expand Down Expand Up @@ -2342,7 +2345,7 @@ impl AuthorityState {
pub fn get_transactions(
&self,
filter: Option<TransactionFilter>,
// exclusive cursor if `Some`, otherwise start from the beginning
// If `Some`, the query will start from the next item after the specified cursor
cursor: Option<TransactionDigest>,
limit: Option<usize>,
reverse: bool,
Expand Down Expand Up @@ -2423,6 +2426,46 @@ impl AuthorityState {
}
}

pub fn get_checkpoints(
&self,
// If `Some`, the query will start from the next item after the specified cursor
cursor: Option<CheckpointSequenceNumber>,
limit: u64,
descending_order: bool,
) -> Result<Vec<Checkpoint>, anyhow::Error> {
let max_checkpoint = self.get_latest_checkpoint_sequence_number()?;
let checkpoint_numbers =
calculate_checkpoint_numbers(cursor, limit, descending_order, max_checkpoint);

let verified_checkpoints = self
.get_checkpoint_store()
.multi_get_checkpoint_by_sequence_number(&checkpoint_numbers)?;

let checkpoint_summaries: Vec<CheckpointSummary> = verified_checkpoints
.into_iter()
.flatten()
.map(|check| check.into_summary_and_sequence().1)
.collect();

let checkpoint_contents_digest: Vec<CheckpointContentsDigest> = checkpoint_summaries
.iter()
.map(|summary| summary.content_digest)
.collect();

let checkpoint_contents = self
.get_checkpoint_store()
.multi_get_checkpoint_content(checkpoint_contents_digest.as_slice())?;
let contents: Vec<CheckpointContents> = checkpoint_contents.into_iter().flatten().collect();

let mut checkpoints: Vec<Checkpoint> = vec![];

for (summary, content) in checkpoint_summaries.into_iter().zip(contents.into_iter()) {
checkpoints.push(Checkpoint::from((summary, content)));
}

Ok(checkpoints)
}

pub async fn get_timestamp_ms(
&self,
digest: &TransactionDigest,
Expand All @@ -2433,7 +2476,7 @@ impl AuthorityState {
pub async fn query_events(
&self,
query: EventFilter,
// exclusive cursor if `Some`, otherwise start from the beginning
// If `Some`, the query will start from the next item after the specified cursor
cursor: Option<EventID>,
limit: usize,
descending: bool,
Expand Down Expand Up @@ -3341,6 +3384,128 @@ impl AuthorityState {
}
}

fn calculate_checkpoint_numbers(
// If `Some`, the query will start from the next item after the specified cursor
cursor: Option<CheckpointSequenceNumber>,
limit: u64,
descending_order: bool,
max_checkpoint: CheckpointSequenceNumber,
) -> Vec<CheckpointSequenceNumber> {
let (start_index, end_index) = match cursor {
Some(t) => {
if descending_order {
let start = std::cmp::min(t.saturating_sub(1), max_checkpoint);
let end = start.saturating_sub(limit - 1);
(end, start)
} else {
let start =
std::cmp::min(t.checked_add(1).unwrap_or(max_checkpoint), max_checkpoint);
let end = std::cmp::min(
start.checked_add(limit - 1).unwrap_or(max_checkpoint),
max_checkpoint,
);
(start, end)
}
}
None => {
if descending_order {
(max_checkpoint.saturating_sub(limit - 1), max_checkpoint)
} else {
(0, std::cmp::min(limit - 1, max_checkpoint))
}
}
};

if descending_order {
(start_index..=end_index).rev().collect()
} else {
(start_index..=end_index).collect()
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_calculate_checkpoint_numbers() {
let cursor = Some(10);
let limit = 5;
let descending_order = true;
let max_checkpoint = 15;

let checkpoint_numbers =
calculate_checkpoint_numbers(cursor, limit, descending_order, max_checkpoint);

assert_eq!(checkpoint_numbers, vec![9, 8, 7, 6, 5]);
}

#[test]
fn test_calculate_checkpoint_numbers_descending_no_cursor() {
let cursor = None;
let limit = 5;
let descending_order = true;
let max_checkpoint = 15;

let checkpoint_numbers =
calculate_checkpoint_numbers(cursor, limit, descending_order, max_checkpoint);

assert_eq!(checkpoint_numbers, vec![15, 14, 13, 12, 11]);
}

#[test]
fn test_calculate_checkpoint_numbers_ascending_no_cursor() {
let cursor = None;
let limit = 5;
let descending_order = false;
let max_checkpoint = 15;

let checkpoint_numbers =
calculate_checkpoint_numbers(cursor, limit, descending_order, max_checkpoint);

assert_eq!(checkpoint_numbers, vec![0, 1, 2, 3, 4]);
}

#[test]
fn test_calculate_checkpoint_numbers_ascending_with_cursor() {
let cursor = Some(10);
let limit = 5;
let descending_order = false;
let max_checkpoint = 15;

let checkpoint_numbers =
calculate_checkpoint_numbers(cursor, limit, descending_order, max_checkpoint);

assert_eq!(checkpoint_numbers, vec![11, 12, 13, 14, 15]);
}

#[test]
fn test_calculate_checkpoint_numbers_ascending_limit_exceeds_max() {
let cursor = None;
let limit = 20;
let descending_order = false;
let max_checkpoint = 15;

let checkpoint_numbers =
calculate_checkpoint_numbers(cursor, limit, descending_order, max_checkpoint);

assert_eq!(checkpoint_numbers, (0..=15).collect::<Vec<_>>());
}

#[test]
fn test_calculate_checkpoint_numbers_descending_limit_exceeds_max() {
let cursor = None;
let limit = 20;
let descending_order = true;
let max_checkpoint = 15;

let checkpoint_numbers =
calculate_checkpoint_numbers(cursor, limit, descending_order, max_checkpoint);

assert_eq!(checkpoint_numbers, (0..=15).rev().collect::<Vec<_>>());
}
}

#[cfg(msim)]
pub mod sui_framework_injection {
use std::cell::RefCell;
Expand Down
7 changes: 7 additions & 0 deletions crates/sui-core/src/checkpoints/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,13 @@ impl CheckpointStore {
Ok(checkpoints)
}

pub fn multi_get_checkpoint_content(
&self,
contents_digest: &[CheckpointContentsDigest],
) -> Result<Vec<Option<CheckpointContents>>, TypedStoreError> {
self.checkpoint_content.multi_get(contents_digest)
}

pub fn get_highest_verified_checkpoint(
&self,
) -> Result<Option<VerifiedCheckpoint>, TypedStoreError> {
Expand Down
16 changes: 14 additions & 2 deletions crates/sui-indexer/src/apis/read_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use std::collections::BTreeMap;
use sui_json_rpc::api::{cap_page_limit, ReadApiClient, ReadApiServer};
use sui_json_rpc::SuiRpcModule;
use sui_json_rpc_types::{
Checkpoint, CheckpointId, DynamicFieldPage, MoveFunctionArgType, ObjectsPage, Page,
SuiGetPastObjectRequest, SuiMoveNormalizedFunction, SuiMoveNormalizedModule,
Checkpoint, CheckpointId, CheckpointPage, DynamicFieldPage, MoveFunctionArgType, ObjectsPage,
Page, SuiGetPastObjectRequest, SuiMoveNormalizedFunction, SuiMoveNormalizedModule,
SuiMoveNormalizedStruct, SuiObjectDataOptions, SuiObjectResponse, SuiObjectResponseQuery,
SuiPastObjectResponse, SuiTransactionResponse, SuiTransactionResponseOptions,
SuiTransactionResponseQuery, TransactionsPage,
Expand Down Expand Up @@ -443,6 +443,18 @@ where
}
Ok(self.get_checkpoint_internal(id)?)
}

async fn get_checkpoints(
&self,
cursor: Option<CheckpointSequenceNumber>,
limit: Option<usize>,
descending_order: bool,
) -> RpcResult<CheckpointPage> {
return self
.fullnode
.get_checkpoints(cursor, limit, descending_order)
.await;
}
}

impl<S> SuiRpcModule for ReadApi<S>
Expand Down
6 changes: 6 additions & 0 deletions crates/sui-json-rpc-types/src/sui_checkpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ use sui_types::messages_checkpoint::{
CheckpointTimestamp, EndOfEpochData,
};

use crate::Page;

pub type CheckpointPage = Page<Checkpoint, CheckpointSequenceNumber>;

#[derive(Clone, Debug, JsonSchema, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Checkpoint {
Expand All @@ -26,6 +30,7 @@ pub struct Checkpoint {
/// checkpoint.
pub network_total_transactions: u64,
/// Digest of the previous checkpoint
#[serde(skip_serializing_if = "Option::is_none")]
pub previous_digest: Option<CheckpointDigest>,
/// The running total gas costs of all transactions included in the current epoch so far
/// until this checkpoint.
Expand All @@ -35,6 +40,7 @@ pub struct Checkpoint {
/// checkpoints can have same timestamp if they originate from the same underlining consensus commit
pub timestamp_ms: CheckpointTimestamp,
/// Present only on the final checkpoint of the epoch.
#[serde(skip_serializing_if = "Option::is_none")]
pub end_of_epoch_data: Option<EndOfEpochData>,
/// Transaction digests
pub transactions: Vec<TransactionDigest>,
Expand Down
12 changes: 12 additions & 0 deletions crates/sui-json-rpc/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod read;
mod transaction_builder;
mod write;

use anyhow::anyhow;
pub use coin::CoinReadApiClient;
pub use coin::CoinReadApiOpenRpc;
pub use coin::CoinReadApiServer;
Expand Down Expand Up @@ -38,6 +39,8 @@ pub use transaction_builder::TransactionBuilderServer;
/// To avoid unnecessary dependency on that crate, we have a reference here
/// for document purposes.
pub const QUERY_MAX_RESULT_LIMIT: usize = 1000;
// TODOD(chris): make this configurable
pub const QUERY_MAX_RESULT_LIMIT_CHECKPOINTS: usize = 100;

pub const MAX_GET_OWNED_OBJECT_LIMIT: usize = 256;

Expand All @@ -59,3 +62,12 @@ pub fn cap_page_objects_limit(limit: Option<usize>) -> Result<usize, anyhow::Err
Ok(limit)
}
}

pub fn validate_limit(limit: Option<usize>, max: usize) -> Result<usize, anyhow::Error> {
match limit {
Some(l) if l > max => Err(anyhow!("Page size limit {l} exceeds max limit {max}")),
Some(0) => Err(anyhow!("Page size limit cannot be smaller than 1")),
Some(l) => Ok(l),
None => Ok(max),
}
}
20 changes: 16 additions & 4 deletions crates/sui-json-rpc/src/api/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use jsonrpsee::core::RpcResult;
use jsonrpsee_proc_macros::rpc;
use std::collections::BTreeMap;
use sui_json_rpc_types::{
Checkpoint, CheckpointId, DynamicFieldPage, MoveFunctionArgType, ObjectsPage,
Checkpoint, CheckpointId, CheckpointPage, DynamicFieldPage, MoveFunctionArgType, ObjectsPage,
SuiGetPastObjectRequest, SuiMoveNormalizedFunction, SuiMoveNormalizedModule,
SuiMoveNormalizedStruct, SuiObjectDataOptions, SuiObjectResponse, SuiObjectResponseQuery,
SuiPastObjectResponse, SuiTransactionResponse, SuiTransactionResponseOptions,
Expand All @@ -29,7 +29,7 @@ pub trait ReadApi {
address: SuiAddress,
/// the objects query criteria.
query: Option<SuiObjectResponseQuery>,
/// Optional paging cursor
/// An optional paging cursor. If provided, the query will start from the next item after the specified cursor. Default to start from the first item if not specified.
cursor: Option<ObjectID>,
/// Max number of items returned per page, default to [MAX_GET_OWNED_OBJECT_SIZE] if not specified.
limit: Option<usize>,
Expand All @@ -43,7 +43,7 @@ pub trait ReadApi {
&self,
/// The ID of the parent object
parent_object_id: ObjectID,
/// Optional paging cursor
/// An optional paging cursor. If provided, the query will start from the next item after the specified cursor. Default to start from the first item if not specified.
cursor: Option<ObjectID>,
/// Maximum item returned per page, default to [QUERY_MAX_RESULT_LIMIT] if not specified.
limit: Option<usize>,
Expand Down Expand Up @@ -153,7 +153,7 @@ pub trait ReadApi {
&self,
/// the transaction query criteria.
query: SuiTransactionResponseQuery,
/// Optional paging cursor
/// An optional paging cursor. If provided, the query will start from the next item after the specified cursor. Default to start from the first item if not specified.
cursor: Option<TransactionDigest>,
/// Maximum item returned per page, default to QUERY_MAX_RESULT_LIMIT if not specified.
limit: Option<usize>,
Expand Down Expand Up @@ -212,4 +212,16 @@ pub trait ReadApi {
/// Checkpoint identifier, can use either checkpoint digest, or checkpoint sequence number as input.
id: CheckpointId,
) -> RpcResult<Checkpoint>;

/// Return paginated list of checkpoints
#[method(name = "getCheckpoints")]
async fn get_checkpoints(
&self,
/// An optional paging cursor. If provided, the query will start from the next item after the specified cursor. Default to start from the first item if not specified.
cursor: Option<CheckpointSequenceNumber>,
/// Maximum item returned per page, default to [QUERY_MAX_RESULT_LIMIT_CHECKPOINTS] if not specified.
limit: Option<usize>,
/// query result ordering, default to false (ascending order), oldest record first.
descending_order: bool,
) -> RpcResult<CheckpointPage>;
}
Loading

0 comments on commit 8700809

Please sign in to comment.