Skip to main content

sui_rpc_api/grpc/v2/move_package_service/
get_package.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{
5    ErrorReason, Result, RpcError, RpcService,
6    grpc::v2::move_package_service::{
7        conversions::{convert_error, convert_module},
8        load_package, load_package_by_id,
9    },
10};
11use sui_rpc::proto::google::rpc::bad_request::FieldViolation;
12use sui_rpc::proto::sui::rpc::v2::get_package_request::Selector;
13use sui_rpc::proto::sui::rpc::v2::{GetPackageRequest, GetPackageResponse, Package};
14use sui_types::move_package::MovePackage;
15
16#[tracing::instrument(skip(service))]
17pub fn get_package(service: &RpcService, request: GetPackageRequest) -> Result<GetPackageResponse> {
18    let package_id_str = request.package_id.as_ref().ok_or_else(|| {
19        FieldViolation::new("package_id")
20            .with_description("missing package_id")
21            .with_reason(ErrorReason::FieldMissing)
22    })?;
23
24    let package = match request.selector {
25        Some(Selector::Version(version)) => {
26            load_package_at_version(service, package_id_str, version)?
27        }
28        Some(Selector::AtCheckpoint(at_checkpoint)) => {
29            load_package_at_checkpoint(service, package_id_str, at_checkpoint)?
30        }
31        None => load_package(service, package_id_str)?,
32        _ => {
33            return Err(FieldViolation::new("selector")
34                .with_description("unknown selector variant")
35                .with_reason(ErrorReason::FieldInvalid)
36                .into());
37        }
38    };
39
40    get_package_response(&package)
41}
42
43/// Load the package in `package_id_str`'s upgrade lineage with exactly
44/// `version`.
45///
46/// Package-version index rows are keyed by the lineage's original package id,
47/// so a member's storage id is mapped to that original id before the lookup.
48fn load_package_at_version(
49    service: &RpcService,
50    package_id_str: &str,
51    version: u64,
52) -> Result<MovePackage> {
53    let package = load_package(service, package_id_str)?;
54    let original_id = package.original_package_id();
55    if package.version().value() == version {
56        return Ok(package);
57    }
58
59    let indexes = service
60        .reader
61        .inner()
62        .indexes()
63        .ok_or_else(|| RpcError::new(tonic::Code::Unavailable, "rpc indexes are disabled"))?;
64
65    let storage_id = indexes
66        .get_package_version_storage_id(original_id, version)
67        .map_err(|e| RpcError::new(tonic::Code::Internal, e.to_string()))?
68        .ok_or_else(RpcError::not_found)?;
69
70    load_package_by_id(service, storage_id)
71}
72
73/// Load the latest package in `package_id_str`'s upgrade lineage that
74/// existed at or before `at_checkpoint`.
75///
76/// The bound is checked against the highest checkpoint every rpc-store
77/// pipeline has committed before any package read: a bound above it is
78/// rejected rather than served from a partially indexed index, so a
79/// successful answer is exact as of `at_checkpoint`.
80fn load_package_at_checkpoint(
81    service: &RpcService,
82    package_id_str: &str,
83    at_checkpoint: u64,
84) -> Result<MovePackage> {
85    let original_id = load_package(service, package_id_str)?.original_package_id();
86
87    let indexes = service
88        .reader
89        .inner()
90        .indexes()
91        .ok_or_else(|| RpcError::new(tonic::Code::Unavailable, "rpc indexes are disabled"))?;
92
93    let lowest_available = service.reader.get_lowest_available_checkpoint()?;
94    if at_checkpoint < lowest_available {
95        return Err(RpcError::new(
96            tonic::Code::NotFound,
97            format!(
98                "requested checkpoint {at_checkpoint} has been pruned; lowest available \
99                 checkpoint is {lowest_available}",
100            ),
101        ));
102    }
103
104    let highest_indexed = indexes
105        .get_highest_indexed_checkpoint_seq_number()
106        .map_err(|e| RpcError::new(tonic::Code::Internal, e.to_string()))?
107        .ok_or_else(|| RpcError::new(tonic::Code::Unavailable, "rpc index is empty"))?;
108    if at_checkpoint > highest_indexed {
109        return Err(RpcError::new(
110            tonic::Code::NotFound,
111            format!(
112                "requested checkpoint {at_checkpoint} is not yet indexed; highest indexed \
113                 checkpoint is {highest_indexed}",
114            ),
115        ));
116    }
117
118    let (_, storage_id) = indexes
119        .get_package_at_checkpoint(original_id, at_checkpoint)
120        .map_err(|e| RpcError::new(tonic::Code::Internal, e.to_string()))?
121        .ok_or_else(RpcError::not_found)?;
122
123    load_package_by_id(service, storage_id)
124}
125
126/// Build a `GetPackage` response from an already-loaded package. Shared with other implementors
127/// of `MovePackageService` (e.g. sui-kv-rpc) that load packages from a different backend.
128pub fn get_package_response(package: &MovePackage) -> Result<GetPackageResponse> {
129    let package_id = package.id();
130
131    let resolved_package =
132        sui_package_resolver::Package::read_from_package(package).map_err(convert_error)?;
133
134    let modules: Vec<_> = resolved_package
135        .modules()
136        .iter()
137        .map(|(module_name, resolver_module)| {
138            convert_module(module_name, resolver_module, &package_id)
139        })
140        .collect::<Result<Vec<_>>>()?;
141
142    let mut message = Package::default();
143    message.storage_id = Some(package_id.to_canonical_string(true));
144    message.original_id = Some(package.original_package_id().to_canonical_string(true));
145    message.version = Some(package.version().value());
146    message.modules = modules;
147
148    Ok(GetPackageResponse::new(message))
149}