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