1use 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 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}