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