sui_rpc_api/grpc/alpha/
proof_service.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::RpcError;
5use crate::RpcService;
6use crate::grpc::alpha::proof_service_proto::{
7    GetObjectInclusionProofRequest, GetObjectInclusionProofResponse, OcsInclusionProof,
8    proof_service_server::ProofService,
9};
10use bcs;
11use fastcrypto::hash::Blake2b256;
12use fastcrypto::merkle::MerkleTree;
13use std::str::FromStr;
14use sui_types::{
15    base_types::{ObjectID, ObjectRef},
16    digests::Digest,
17    messages_checkpoint::CheckpointArtifacts,
18};
19
20pub struct ProofServiceImpl {
21    service: RpcService,
22}
23
24impl ProofServiceImpl {
25    pub fn new(service: RpcService) -> Self {
26        Self { service }
27    }
28}
29
30#[tonic::async_trait]
31impl ProofService for ProofServiceImpl {
32    async fn get_object_inclusion_proof(
33        &self,
34        request: tonic::Request<GetObjectInclusionProofRequest>,
35    ) -> Result<tonic::Response<GetObjectInclusionProofResponse>, tonic::Status> {
36        let response = get_object_inclusion_proof_impl(&self.service, request.into_inner())
37            .map_err(tonic::Status::from)?;
38        Ok(tonic::Response::new(response))
39    }
40}
41
42fn build_ocs_inclusion_proof(
43    checkpoint: &sui_types::full_checkpoint_content::Checkpoint,
44    object_id: ObjectID,
45) -> Result<(OcsInclusionProof, ObjectRef), RpcError> {
46    let effects_refs: Vec<&_> = checkpoint
47        .transactions
48        .iter()
49        .map(|tx| &tx.effects)
50        .collect();
51
52    let checkpoint_artifacts = CheckpointArtifacts::from(effects_refs.as_slice());
53
54    let object_states = checkpoint_artifacts
55        .object_states()
56        .map_err(|e| RpcError::new(tonic::Code::Internal, e.to_string()))?;
57
58    let object_ref_from_checkpoint = object_states.get(&object_id).ok_or_else(|| {
59        RpcError::new(
60            tonic::Code::NotFound,
61            format!("Object {} not found in checkpoint", object_id),
62        )
63    })?;
64
65    let object_ref = (
66        object_id,
67        object_ref_from_checkpoint.0,
68        object_ref_from_checkpoint.1,
69    );
70
71    let leaves: Vec<ObjectRef> = object_states
72        .iter()
73        .map(|(id, (seq, digest))| (*id, *seq, *digest))
74        .collect();
75
76    let leaf_index = leaves
77        .iter()
78        .position(|r| *r == object_ref)
79        .ok_or_else(|| {
80            RpcError::new(
81                tonic::Code::Internal,
82                format!("Object {} not found in checkpoint", object_ref.0),
83            )
84        })?;
85
86    let tree = MerkleTree::<Blake2b256>::build_from_unserialized(leaves.iter())
87        .map_err(|e| RpcError::new(tonic::Code::Internal, e.to_string()))?;
88
89    let merkle_proof = tree
90        .get_proof(leaf_index)
91        .map_err(|e| RpcError::new(tonic::Code::Internal, e.to_string()))?;
92
93    let tree_root = Digest::new(tree.root().bytes());
94
95    let merkle_proof_bytes = bcs::to_bytes(&merkle_proof)
96        .map_err(|e| RpcError::new(tonic::Code::Internal, e.to_string()))?;
97
98    let proto_inclusion_proof = OcsInclusionProof {
99        merkle_proof: Some(merkle_proof_bytes),
100        leaf_index: Some(leaf_index as u64),
101        tree_root: Some(<Digest as AsRef<[u8; 32]>>::as_ref(&tree_root).to_vec()),
102    };
103
104    Ok((proto_inclusion_proof, object_ref))
105}
106
107#[tracing::instrument(skip(service))]
108fn get_object_inclusion_proof_impl(
109    service: &RpcService,
110    request: GetObjectInclusionProofRequest,
111) -> Result<GetObjectInclusionProofResponse, RpcError> {
112    if !service.config.authenticated_events_indexing() {
113        return Err(RpcError::new(
114            tonic::Code::Unimplemented,
115            "Authenticated events indexing is disabled".to_string(),
116        ));
117    }
118
119    let object_id_str = request.object_id.ok_or_else(|| {
120        RpcError::new(
121            tonic::Code::InvalidArgument,
122            "missing object_id".to_string(),
123        )
124    })?;
125
126    if object_id_str.trim().is_empty() {
127        return Err(RpcError::new(
128            tonic::Code::InvalidArgument,
129            "object_id cannot be empty".to_string(),
130        ));
131    }
132
133    let object_id = ObjectID::from_str(&object_id_str).map_err(|e| {
134        RpcError::new(
135            tonic::Code::InvalidArgument,
136            format!("invalid object_id: {e}"),
137        )
138    })?;
139
140    let checkpoint_seq = request.checkpoint.ok_or_else(|| {
141        RpcError::new(
142            tonic::Code::InvalidArgument,
143            "missing checkpoint".to_string(),
144        )
145    })?;
146
147    let reader = service.reader.inner();
148    let indexes = reader.indexes().ok_or_else(RpcError::not_found)?;
149
150    let highest_indexed = indexes
151        .get_highest_indexed_checkpoint_seq_number()
152        .map_err(|e| RpcError::new(tonic::Code::Internal, e.to_string()))?
153        .unwrap_or(0);
154
155    let lowest_available = reader
156        .get_lowest_available_checkpoint_objects()
157        .map_err(|e| RpcError::new(tonic::Code::Internal, e.to_string()))?;
158
159    if checkpoint_seq < lowest_available {
160        return Err(RpcError::new(
161            tonic::Code::NotFound,
162            format!(
163                "Requested checkpoint {} has been pruned. Lowest available checkpoint is {}",
164                checkpoint_seq, lowest_available
165            ),
166        ));
167    }
168
169    if checkpoint_seq > highest_indexed {
170        return Err(RpcError::new(
171            tonic::Code::NotFound,
172            format!(
173                "Requested checkpoint {} is not yet indexed. Highest indexed checkpoint is {}",
174                checkpoint_seq, highest_indexed
175            ),
176        ));
177    }
178
179    let checkpoint = reader
180        .get_checkpoint_by_sequence_number(checkpoint_seq)
181        .ok_or_else(|| {
182            RpcError::new(
183                tonic::Code::NotFound,
184                format!("checkpoint {} not found", checkpoint_seq),
185            )
186        })?;
187
188    let checkpoint_contents = reader
189        .get_checkpoint_contents_by_sequence_number(checkpoint_seq)
190        .ok_or_else(|| {
191            RpcError::new(
192                tonic::Code::NotFound,
193                format!("checkpoint contents for {} not found", checkpoint_seq),
194            )
195        })?;
196
197    let checkpoint_data = reader
198        .get_checkpoint_data(checkpoint, checkpoint_contents)
199        .map_err(|e| RpcError::new(tonic::Code::Internal, e.to_string()))?;
200
201    let (proto_inclusion_proof, object_ref) =
202        build_ocs_inclusion_proof(&checkpoint_data, object_id)?;
203
204    let object = reader
205        .get_object_by_key(&object_id, object_ref.1)
206        .ok_or_else(|| {
207            RpcError::new(
208                tonic::Code::NotFound,
209                format!("Object {} not found at version {}", object_id, object_ref.1),
210            )
211        })?;
212
213    debug_assert_eq!(
214        object.compute_object_reference().2,
215        object_ref.2,
216        "Object digest mismatch for object {} at version {}",
217        object_id,
218        object_ref.1
219    );
220
221    let object_data_bytes =
222        bcs::to_bytes(&object).map_err(|e| RpcError::new(tonic::Code::Internal, e.to_string()))?;
223
224    let mut obj_ref = sui_rpc::proto::sui::rpc::v2::ObjectReference::default();
225    obj_ref.object_id = Some(object_ref.0.to_string());
226    obj_ref.version = Some(object_ref.1.value());
227    obj_ref.digest = Some(object_ref.2.to_string());
228
229    Ok(GetObjectInclusionProofResponse {
230        object_ref: Some(obj_ref),
231        inclusion_proof: Some(proto_inclusion_proof),
232        object_data: Some(object_data_bytes),
233    })
234}