sui_rpc_api/grpc/v2/ledger_service/
get_object.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::ErrorReason;
5use crate::RpcError;
6use crate::RpcService;
7use crate::error::ObjectNotFoundError;
8use prost_types::FieldMask;
9use sui_rpc::field::FieldMaskTree;
10use sui_rpc::field::FieldMaskUtil;
11use sui_rpc::proto::google::rpc::bad_request::FieldViolation;
12use sui_rpc::proto::sui::rpc::v2::BatchGetObjectsRequest;
13use sui_rpc::proto::sui::rpc::v2::BatchGetObjectsResponse;
14use sui_rpc::proto::sui::rpc::v2::GetObjectRequest;
15use sui_rpc::proto::sui::rpc::v2::GetObjectResponse;
16use sui_rpc::proto::sui::rpc::v2::GetObjectResult;
17use sui_rpc::proto::sui::rpc::v2::Object;
18use sui_sdk_types::Address;
19use sui_types::full_checkpoint_content::ObjectSet;
20
21pub const MAX_BATCH_REQUESTS: usize = 1000;
22pub const READ_MASK_DEFAULT: &str = "object_id,version,digest";
23
24type ValidationResult = Result<(Vec<(Address, Option<u64>)>, FieldMaskTree), RpcError>;
25
26pub fn validate_get_object_requests(
27    requests: Vec<(Option<String>, Option<u64>)>,
28    read_mask: Option<FieldMask>,
29) -> ValidationResult {
30    let read_mask = {
31        let read_mask = read_mask.unwrap_or_else(|| FieldMask::from_str(READ_MASK_DEFAULT));
32        read_mask.validate::<Object>().map_err(|path| {
33            FieldViolation::new("read_mask")
34                .with_description(format!("invalid read_mask path: {path}"))
35                .with_reason(ErrorReason::FieldInvalid)
36        })?;
37        FieldMaskTree::from(read_mask)
38    };
39    let requests = requests
40        .into_iter()
41        .enumerate()
42        .map(|(idx, (object_id, version))| {
43            let object_id = object_id
44                .as_ref()
45                .ok_or_else(|| {
46                    FieldViolation::new("object_id")
47                        .with_reason(ErrorReason::FieldMissing)
48                        .nested_at("requests", idx)
49                })?
50                .parse()
51                .map_err(|e| {
52                    FieldViolation::new("object_id")
53                        .with_description(format!("invalid object_id: {e}"))
54                        .with_reason(ErrorReason::FieldInvalid)
55                        .nested_at("requests", idx)
56                })?;
57            Ok((object_id, version))
58        })
59        .collect::<Result<_, RpcError>>()?;
60    Ok((requests, read_mask))
61}
62
63#[tracing::instrument(skip(service))]
64pub fn get_object(
65    service: &RpcService,
66    GetObjectRequest {
67        object_id,
68        version,
69        read_mask,
70        ..
71    }: GetObjectRequest,
72) -> Result<GetObjectResponse, RpcError> {
73    let (requests, read_mask) =
74        validate_get_object_requests(vec![(object_id, version)], read_mask)?;
75    let (object_id, version) = requests[0];
76    get_object_impl(service, object_id, version, &read_mask).map(GetObjectResponse::new)
77}
78
79#[tracing::instrument(skip(service))]
80pub fn batch_get_objects(
81    service: &RpcService,
82    BatchGetObjectsRequest {
83        requests,
84        read_mask,
85        ..
86    }: BatchGetObjectsRequest,
87) -> Result<BatchGetObjectsResponse, RpcError> {
88    if requests.len() > MAX_BATCH_REQUESTS {
89        return Err(RpcError::new(
90            tonic::Code::InvalidArgument,
91            format!("number of batch requests exceed limit of {MAX_BATCH_REQUESTS}"),
92        ));
93    }
94
95    let requests = requests
96        .into_iter()
97        .map(|req| (req.object_id, req.version))
98        .collect();
99    let (requests, read_mask) = validate_get_object_requests(requests, read_mask)?;
100    let objects = requests
101        .into_iter()
102        .map(|(object_id, version)| get_object_impl(service, object_id, version, &read_mask))
103        .map(|result| match result {
104            Ok(object) => GetObjectResult::new_object(object),
105            Err(error) => GetObjectResult::new_error(error.into_status_proto()),
106        })
107        .collect();
108    Ok(BatchGetObjectsResponse::new(objects))
109}
110
111#[tracing::instrument(skip(service))]
112fn get_object_impl(
113    service: &RpcService,
114    object_id: Address,
115    version: Option<u64>,
116    read_mask: &FieldMaskTree,
117) -> Result<Object, RpcError> {
118    let object = if let Some(version) = version {
119        service
120            .reader
121            .inner()
122            .get_object_by_key(&object_id.into(), version.into())
123            .ok_or_else(|| ObjectNotFoundError::new_with_version(object_id, version))?
124    } else {
125        service
126            .reader
127            .inner()
128            .get_object(&object_id.into())
129            .ok_or_else(|| ObjectNotFoundError::new(object_id))?
130    };
131
132    Ok(service.render_object_to_proto(&object, read_mask, &ObjectSet::default()))
133}