sui_rpc_api/grpc/alpha/
proof_service.rs1use 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}