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    checkpoint_seq: u64,
46) -> Result<(OcsInclusionProof, ObjectRef), RpcError> {
47    let effects_refs: Vec<&_> = checkpoint
48        .transactions
49        .iter()
50        .map(|tx| &tx.effects)
51        .collect();
52
53    let checkpoint_artifacts = CheckpointArtifacts::from(effects_refs.as_slice());
54
55    let object_states = checkpoint_artifacts
56        .object_states()
57        .map_err(|e| RpcError::new(tonic::Code::Internal, e.to_string()))?;
58
59    let object_ref_from_checkpoint = object_states.get(&object_id).ok_or_else(|| {
60        RpcError::new(
61            tonic::Code::FailedPrecondition,
62            format!(
63                "Object {} was not written at checkpoint {}",
64                object_id, checkpoint_seq
65            ),
66        )
67    })?;
68
69    let object_ref = (
70        object_id,
71        object_ref_from_checkpoint.0,
72        object_ref_from_checkpoint.1,
73    );
74
75    let leaves: Vec<ObjectRef> = object_states
76        .iter()
77        .map(|(id, (seq, digest))| (*id, *seq, *digest))
78        .collect();
79
80    let leaf_index = leaves
81        .iter()
82        .position(|r| *r == object_ref)
83        .ok_or_else(|| {
84            RpcError::new(
85                tonic::Code::Internal,
86                format!("Object {} not found in checkpoint", object_ref.0),
87            )
88        })?;
89
90    let tree = MerkleTree::<Blake2b256>::build_from_unserialized(leaves.iter())
91        .map_err(|e| RpcError::new(tonic::Code::Internal, e.to_string()))?;
92
93    let merkle_proof = tree
94        .get_proof(leaf_index)
95        .map_err(|e| RpcError::new(tonic::Code::Internal, e.to_string()))?;
96
97    let tree_root = Digest::new(tree.root().bytes());
98
99    let merkle_proof_bytes = bcs::to_bytes(&merkle_proof)
100        .map_err(|e| RpcError::new(tonic::Code::Internal, e.to_string()))?;
101
102    let proto_inclusion_proof = OcsInclusionProof {
103        merkle_proof: Some(merkle_proof_bytes),
104        leaf_index: Some(leaf_index as u64),
105        tree_root: Some(<Digest as AsRef<[u8; 32]>>::as_ref(&tree_root).to_vec()),
106    };
107
108    Ok((proto_inclusion_proof, object_ref))
109}
110
111#[tracing::instrument(skip(service))]
112fn get_object_inclusion_proof_impl(
113    service: &RpcService,
114    request: GetObjectInclusionProofRequest,
115) -> Result<GetObjectInclusionProofResponse, RpcError> {
116    if !service.config.authenticated_events_indexing() {
117        return Err(RpcError::new(
118            tonic::Code::Unimplemented,
119            "Authenticated events indexing is disabled".to_string(),
120        ));
121    }
122
123    let object_id_str = request.object_id.ok_or_else(|| {
124        RpcError::new(
125            tonic::Code::InvalidArgument,
126            "missing object_id".to_string(),
127        )
128    })?;
129
130    if object_id_str.trim().is_empty() {
131        return Err(RpcError::new(
132            tonic::Code::InvalidArgument,
133            "object_id cannot be empty".to_string(),
134        ));
135    }
136
137    let object_id = ObjectID::from_str(&object_id_str).map_err(|e| {
138        RpcError::new(
139            tonic::Code::InvalidArgument,
140            format!("invalid object_id: {e}"),
141        )
142    })?;
143
144    let checkpoint_seq = request.checkpoint.ok_or_else(|| {
145        RpcError::new(
146            tonic::Code::InvalidArgument,
147            "missing checkpoint".to_string(),
148        )
149    })?;
150
151    let reader = service.reader.inner();
152    let indexes = reader.indexes().ok_or_else(RpcError::not_found)?;
153
154    let highest_indexed = indexes
155        .get_highest_indexed_checkpoint_seq_number()
156        .map_err(|e| RpcError::new(tonic::Code::Internal, e.to_string()))?
157        .unwrap_or(0);
158
159    let lowest_available = reader
160        .get_lowest_available_checkpoint_objects()
161        .map_err(|e| RpcError::new(tonic::Code::Internal, e.to_string()))?;
162
163    if checkpoint_seq < lowest_available {
164        return Err(RpcError::new(
165            tonic::Code::NotFound,
166            format!(
167                "Requested checkpoint {} has been pruned. Lowest available checkpoint is {}",
168                checkpoint_seq, lowest_available
169            ),
170        ));
171    }
172
173    if checkpoint_seq > highest_indexed {
174        return Err(RpcError::new(
175            tonic::Code::NotFound,
176            format!(
177                "Requested checkpoint {} is not yet indexed. Highest indexed checkpoint is {}",
178                checkpoint_seq, highest_indexed
179            ),
180        ));
181    }
182
183    let checkpoint = reader
184        .get_checkpoint_by_sequence_number(checkpoint_seq)
185        .ok_or_else(|| {
186            RpcError::new(
187                tonic::Code::NotFound,
188                format!("checkpoint {} not found", checkpoint_seq),
189            )
190        })?;
191
192    let checkpoint_contents = reader
193        .get_checkpoint_contents_by_sequence_number(checkpoint_seq)
194        .ok_or_else(|| {
195            RpcError::new(
196                tonic::Code::NotFound,
197                format!("checkpoint contents for {} not found", checkpoint_seq),
198            )
199        })?;
200
201    let checkpoint_data = reader
202        .get_checkpoint_data(checkpoint, checkpoint_contents)
203        .map_err(|e| RpcError::new(tonic::Code::Internal, e.to_string()))?;
204
205    let (proto_inclusion_proof, object_ref) =
206        build_ocs_inclusion_proof(&checkpoint_data, object_id, checkpoint_seq)?;
207
208    let object = reader
209        .get_object_by_key(&object_id, object_ref.1)
210        .ok_or_else(|| {
211            RpcError::new(
212                tonic::Code::NotFound,
213                format!("Object {} not found at version {}", object_id, object_ref.1),
214            )
215        })?;
216
217    debug_assert_eq!(
218        object.compute_object_reference().2,
219        object_ref.2,
220        "Object digest mismatch for object {} at version {}",
221        object_id,
222        object_ref.1
223    );
224
225    let object_data_bytes =
226        bcs::to_bytes(&object).map_err(|e| RpcError::new(tonic::Code::Internal, e.to_string()))?;
227
228    let mut obj_ref = sui_rpc::proto::sui::rpc::v2::ObjectReference::default();
229    obj_ref.object_id = Some(object_ref.0.to_string());
230    obj_ref.version = Some(object_ref.1.value());
231    obj_ref.digest = Some(object_ref.2.to_string());
232
233    Ok(GetObjectInclusionProofResponse {
234        object_ref: Some(obj_ref),
235        inclusion_proof: Some(proto_inclusion_proof),
236        object_data: Some(object_data_bytes),
237    })
238}