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

Basic light client CLI #15187

Merged
merged 21 commits into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Added docs
  • Loading branch information
gdanezis committed Dec 18, 2023
commit b51c7ff6f24f56d5122734071657c1e0e5dc5f79
62 changes: 62 additions & 0 deletions crates/sui-light-client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
This create contains a Command Line Interface light client for Sui.

# What is a light client?

A light client allows checking the authenticity and validity of on-chain state, such as transactions, their effects including events and object contents, without the cost of running a full node.

Running a *full node* requires downloading the full sequence of all transaction and re-executing them. Any reads against the full node are guaranteed to be correct since the full state of the blockchain was recreated an in the process checked. It is however an expensive process in terms of network bandwidth needed to download the full sequence of transactions, as well as CPU to re-execute it, and storage to store the full state of the blockchain.

Alternatively, a *light client* only needs to download minimal information to authenticate blockchain state. Specifically in Sui, the light client needs to *sync* all end-of-epoch checkpoints that contain information about the committee in the next epoch. Sync involves downloading the checkpoints and checking their validity by checking their certificate.

Once all end-of-epoch checkpoints are downloaded and checked, any event or current object can be checked for its validity. To do that the light client downloads the checkpoint in which the transaction was executed, and the effects structure that summarizes its effects on the system, including events emitted and objects created. The chain of validity from the checkpoint to the effects and its contents is checked via the certificate on the checkpoint and the hashes of all structures.

## Ensuring valid data display

A light client can ensure the correctness of the event and object data using the techniques defined above. However, the light client CLI utility also needs to pretty-print the structures in JSON, which requires knowledge of the correct type for each event or object. Types themselves are defined in modules that have been uploaded by past transactions. Therefore to ensure correct display the light client authenticates that all modules needed to display sought items are also correct.

# Usage

The light client requires a config file and a directory to cache checkpoints, and then can be used to check the validity of transaction and their events or of objects.

## Setup

The config file for the light client takes a URL for a full node, a directory (that must exist) and within the directory to name of the genesis blob for the Sui network.

```
full_node_url: "http://ord-mnt-rpcbig-06.mainnet.sui.io:9000/rest"
checkpoint_summary_dir: "checkpoints_dir"
genesis_filename: "genesis.blob"
```

The genesis blob for the Sui mainnet can be found here: https://github.com/MystenLabs/sui-genesis/blob/main/mainnet/genesis.blob

## Sync

Every day there is a need to download new checkpoints through sync by doing:
```
$ sui-light-client --config light_client.yaml sync
```

Where `light_client.yaml` is the config file above.

This command will download all end-of-epoch checkpoints, and check them for validity. They will be cached within the checkpoint summary directory for use by future invocations.

## Check Transaction

To check a transaction was executed, as well as the events it emitted do:
```
$ sui-light-client --config light_client.yaml transaction -t 8RiKBwuAbtu8zNCtz8SrcfHyEUzto6zi6cMVA9t4WhWk
```

Where the base58 encoding of the transaction ID is specified. If the transaction has been executed the transaction ID the effects digest are displayed and all the events are printed in JSON. If not an error is printed.

## Check Object

To check an object provide its ID in the following way:

```
$ sui-light-client --config light_client.yaml object -o 0xc646887891adfc0540ec271fd0203603fb4c841a119ec1e00c469441
abfc7078
```

The object ID is represented in Hex as displayed in explorers. If the object exists in the latest state it is printed out in JSON, otherwise an error is printed.
30 changes: 14 additions & 16 deletions crates/sui-light-client/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ async fn download_checkpoint_summary(
/// Run binary search to for each end of epoch checkpoint that is missing
/// between the latest on the list and the latest checkpoint.
async fn pre_sync_checkpoints_to_latest(config: &Config) -> anyhow::Result<()> {
// Get the local checlpoint list
// Get the local checkpoint list
let mut checkpoints_list: CheckpointsList = read_checkpoint_list(config)?;
let latest_in_list = checkpoints_list
.checkpoints
Expand All @@ -229,9 +229,9 @@ async fn pre_sync_checkpoints_to_latest(config: &Config) -> anyhow::Result<()> {
let mut start = last_checkpoint_seq;
let mut end = latest.sequence_number;

let taget_epoch = last_epoch + 1;
let target_epoch = last_epoch + 1;
// Print target
println!("Target Epoch: {}", taget_epoch);
println!("Target Epoch: {}", target_epoch);
let mut found_summary = None;

while start < end {
Expand All @@ -246,12 +246,12 @@ async fn pre_sync_checkpoints_to_latest(config: &Config) -> anyhow::Result<()> {
summary.end_of_epoch_data.is_some()
);

if summary.epoch() == taget_epoch && summary.end_of_epoch_data.is_some() {
if summary.epoch() == target_epoch && summary.end_of_epoch_data.is_some() {
found_summary = Some(summary);
break;
}

if summary.epoch() <= taget_epoch {
if summary.epoch() <= target_epoch {
start = mid + 1;
} else {
end = mid;
Expand All @@ -278,7 +278,7 @@ async fn pre_sync_checkpoints_to_latest(config: &Config) -> anyhow::Result<()> {
async fn check_and_sync_checkpoints(config: &Config) -> anyhow::Result<()> {
pre_sync_checkpoints_to_latest(config).await?;

// Get the local checlpoint list
// Get the local checkpoint list
let checkpoints_list: CheckpointsList = read_checkpoint_list(config)?;

// Load the genesis committee
Expand Down Expand Up @@ -345,11 +345,11 @@ async fn read_full_checkpoint(checkpoint_path: &PathBuf) -> anyhow::Result<Check

async fn write_full_checkpoint(
checkpoint_path: &Path,
ckpt: &CheckpointData,
checkpoint: &CheckpointData,
) -> anyhow::Result<()> {
let mut writer = fs::File::create(checkpoint_path)?;
let bytes =
bcs::to_bytes(&ckpt).map_err(|_| anyhow!("Unable to serialize checkpoint summary"))?;
bcs::to_bytes(&checkpoint).map_err(|_| anyhow!("Unable to serialize checkpoint summary"))?;
writer.write_all(&bytes)?;
Ok(())
}
Expand All @@ -360,11 +360,11 @@ async fn get_full_checkpoint(config: &Config, seq: u64) -> anyhow::Result<Checkp
checkpoint_path.push(format!("{}.bcs", seq));

// Try reading the cache
if let Ok(ckpt) = read_full_checkpoint(&checkpoint_path).await {
return Ok(ckpt);
if let Ok(checkpoint) = read_full_checkpoint(&checkpoint_path).await {
return Ok(checkpoint);
}

// Downlioading the checkpoint from the server
// Downloading the checkpoint from the server
let client: Client = Client::new(config.full_node_url.as_str());
let full_check_point = client.get_full_checkpoint(seq).await?;

Expand All @@ -384,7 +384,7 @@ fn assert_contains_transaction_effects(
// Verify the checkpoint summary using the committee
summary.clone().verify(committee)?;

// Check the validty of the checkpoint contents
// Check the validity of the checkpoint contents
let contents = &checkpoint.checkpoint_contents;
anyhow::ensure!(
contents.digest() == &summary.content_digest,
Expand Down Expand Up @@ -461,10 +461,8 @@ async fn check_transaction_tid(
.cloned()
.collect();

// Make a commitee object using this
// Make a committee object using this
let committee = Committee::new(prev_ckp.epoch().saturating_add(1), prev_committee);
println!("Committee: {:?}", prev_ckp.sequence_number());

assert_contains_transaction_effects(&full_check_point, &committee, tid)
}

Expand Down Expand Up @@ -609,7 +607,7 @@ mod tests {
.cloned()
.collect();

// Make a commitee object using this
// Make a committee object using this
let committee = Committee::new(checkpoint.epoch().saturating_add(1), prev_committee);

let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
Expand Down