sui_light_client/
verifier.rs1use crate::checkpoint::{CheckpointsList, read_checkpoint, read_checkpoint_list};
5use crate::committee::extract_new_committee_info;
6use crate::config::Config;
7use crate::object_store::SuiObjectStore;
8use anyhow::{Result, anyhow};
9use sui_config::genesis::Genesis;
10use sui_rpc_api::Client;
11use sui_types::base_types::{ObjectID, TransactionDigest};
12use sui_types::committee::Committee;
13use sui_types::effects::{TransactionEffects, TransactionEvents};
14use sui_types::full_checkpoint_content::CheckpointData;
15use sui_types::messages_checkpoint::CheckpointSequenceNumber;
16use sui_types::object::Object;
17use tracing::info;
18
19use sui_types::effects::TransactionEffectsAPI;
20
21pub fn extract_verified_effects_and_events(
22 checkpoint: &CheckpointData,
23 committee: &Committee,
24 tid: TransactionDigest,
25) -> Result<(TransactionEffects, Option<TransactionEvents>)> {
26 let summary = &checkpoint.checkpoint_summary;
27
28 summary.verify_with_contents(committee, Some(&checkpoint.checkpoint_contents))?;
30
31 let contents = &checkpoint.checkpoint_contents;
33 let (matching_tx, _) = checkpoint
34 .transactions
35 .iter()
36 .zip(contents.iter())
37 .find(|(tx, digest)| {
40 tx.effects.execution_digests() == **digest && digest.transaction == tid
41 })
42 .ok_or(anyhow!("Transaction not found in checkpoint contents"))?;
43
44 let events_digest = matching_tx.events.as_ref().map(|events| events.digest());
46 anyhow::ensure!(
47 events_digest.as_ref() == matching_tx.effects.events_digest(),
48 "Events digest does not match"
49 );
50
51 Ok((matching_tx.effects.clone(), matching_tx.events.clone()))
53}
54
55pub async fn get_verified_object(config: &Config, id: ObjectID) -> Result<Object> {
56 let mut client = Client::new(config.full_node_url.as_str())?;
57
58 info!("Getting object: {}", id);
59
60 let object = client.get_object(id).await?;
61
62 let (effects, _) = get_verified_effects_and_events(config, object.previous_transaction)
64 .await
65 .expect("Cannot get effects and events");
66
67 let target_object_ref = object.compute_object_reference();
69 effects
70 .all_changed_objects()
71 .iter()
72 .find(|object_ref| object_ref.0 == target_object_ref)
73 .ok_or(anyhow!("Object not found"))
74 .expect("Object not found");
75
76 Ok(object)
77}
78
79pub async fn get_verified_effects_and_events(
80 config: &Config,
81 tid: TransactionDigest,
82) -> Result<(TransactionEffects, Option<TransactionEvents>)> {
83 let mut client = Client::new(config.full_node_url.as_str())?;
84
85 info!("Getting effects and events for TID: {}", tid);
86
87 let seq = client
89 .get_transaction(&tid)
90 .await?
91 .checkpoint
92 .ok_or(anyhow!("Transaction not found"))?;
93
94 let object_store = SuiObjectStore::new(config)?;
96
97 let full_check_point = object_store
99 .get_full_checkpoint(seq)
100 .await
101 .map_err(|e| anyhow!(format!("Cannot get full checkpoint: {e}")))?;
102
103 let checkpoints_list: CheckpointsList = read_checkpoint_list(config)?;
105
106 let prev_ckp_id = checkpoints_list
108 .checkpoints
109 .iter()
110 .rfind(|ckp_id| **ckp_id < seq);
111
112 let committee = if let Some(prev_ckp_id) = prev_ckp_id {
113 let prev_ckp = read_checkpoint(config, *prev_ckp_id)?;
115
116 anyhow::ensure!(
118 prev_ckp.epoch().checked_add(1).unwrap() == full_check_point.checkpoint_summary.epoch(),
119 "Checkpoint sequence number does not match. Need to Sync."
120 );
121
122 extract_new_committee_info(&prev_ckp)?
124 } else {
125 let mut genesis_path = config.checkpoint_summary_dir.clone();
127 genesis_path.push(&config.genesis_filename);
128 Genesis::load(&genesis_path)?.committee()
129 };
130
131 info!("Extracting effects and events for TID: {}", tid);
132 extract_verified_effects_and_events(&full_check_point, &committee, tid)
133 .map_err(|e| anyhow!(format!("Cannot extract effects and events: {e}")))
134}
135
136pub async fn get_verified_checkpoint(
143 id: ObjectID,
144 config: &Config,
145) -> Result<CheckpointSequenceNumber> {
146 let mut client = Client::new(config.full_node_url.as_str())?;
147 let object = client.get_object(id).await?;
148
149 let seq = client
151 .get_transaction(&object.previous_transaction)
152 .await?
153 .checkpoint
154 .ok_or(anyhow!("Transaction not found"))?;
155
156 let (effects, _) = get_verified_effects_and_events(config, object.previous_transaction)
158 .await
159 .expect("Cannot get effects and events");
160
161 let target_object_ref = object.compute_object_reference();
163 effects
164 .all_changed_objects()
165 .iter()
166 .find(|object_ref| object_ref.0 == target_object_ref)
167 .ok_or(anyhow!("Object not found"))
168 .expect("Object not found");
169
170 let object_store = SuiObjectStore::new(config)?;
172
173 let full_check_point = object_store
175 .get_full_checkpoint(seq)
176 .await
177 .map_err(|e| anyhow!(format!("Cannot get full checkpoint: {e}")))?;
178
179 let checkpoints_list: CheckpointsList = read_checkpoint_list(config)?;
181
182 let prev_ckp_id = checkpoints_list
184 .checkpoints
185 .iter()
186 .rfind(|ckp_id| **ckp_id < seq);
187
188 let committee = if let Some(prev_ckp_id) = prev_ckp_id {
189 let prev_ckp = read_checkpoint(config, *prev_ckp_id)?;
191
192 anyhow::ensure!(
194 prev_ckp.epoch().checked_add(1).unwrap() == full_check_point.checkpoint_summary.epoch(),
195 "Checkpoint sequence number does not match. Need to Sync."
196 );
197
198 extract_new_committee_info(&prev_ckp)?
200 } else {
201 let mut genesis_path = config.checkpoint_summary_dir.clone();
203 genesis_path.push(&config.genesis_filename);
204 Genesis::load(&genesis_path)?.committee()
205 };
206
207 full_check_point
209 .checkpoint_summary
210 .verify_with_contents(&committee, Some(&full_check_point.checkpoint_contents))?;
211
212 if full_check_point
213 .transactions
214 .iter()
215 .any(|t| *t.transaction.digest() == object.previous_transaction)
216 {
217 Ok(seq)
218 } else {
219 Err(anyhow!("Transaction not found in checkpoint"))
220 }
221}
222
223#[cfg(test)]
225mod tests {
226 use std::fs;
227 use std::io::{Read, Write};
228 use sui_types::messages_checkpoint::{CheckpointSummary, FullCheckpointContents};
229
230 use super::*;
231 use std::path::{Path, PathBuf};
232 use std::str::FromStr;
233 use sui_types::crypto::AuthorityQuorumSignInfo;
234 use sui_types::message_envelope::Envelope;
235
236 async fn read_full_checkpoint(checkpoint_path: &PathBuf) -> anyhow::Result<CheckpointData> {
237 let mut reader = fs::File::open(checkpoint_path.clone())?;
238 let metadata = fs::metadata(checkpoint_path)?;
239 let mut buffer = vec![0; metadata.len() as usize];
240 reader.read_exact(&mut buffer)?;
241 bcs::from_bytes(&buffer).map_err(|_| anyhow!("Unable to parse checkpoint file"))
242 }
243
244 #[allow(dead_code)]
246 async fn write_full_checkpoint(
247 checkpoint_path: &Path,
248 checkpoint: &CheckpointData,
249 ) -> anyhow::Result<()> {
250 let mut writer = fs::File::create(checkpoint_path)?;
251 let bytes = bcs::to_bytes(&checkpoint)
252 .map_err(|_| anyhow!("Unable to serialize checkpoint summary"))?;
253 writer.write_all(&bytes)?;
254 Ok(())
255 }
256
257 async fn read_data() -> (Committee, CheckpointData) {
258 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
259 d.push("test_files/20873329.yaml");
260
261 let mut reader = fs::File::open(d.clone()).unwrap();
262 let metadata = fs::metadata(&d).unwrap();
263 let mut buffer = vec![0; metadata.len() as usize];
264 reader.read_exact(&mut buffer).unwrap();
265 let checkpoint: Envelope<CheckpointSummary, AuthorityQuorumSignInfo<true>> =
266 bcs::from_bytes(&buffer)
267 .map_err(|_| anyhow!("Unable to parse checkpoint file"))
268 .unwrap();
269
270 let committee = extract_new_committee_info(&checkpoint).unwrap();
271
272 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
273 d.push("test_files/20958462.bcs");
274
275 let full_checkpoint = read_full_checkpoint(&d).await.unwrap();
276
277 (committee, full_checkpoint)
278 }
279
280 #[tokio::test]
281 async fn test_checkpoint_all_good() {
282 let (committee, full_checkpoint) = read_data().await;
283
284 extract_verified_effects_and_events(
285 &full_checkpoint,
286 &committee,
287 TransactionDigest::from_str("8RiKBwuAbtu8zNCtz8SrcfHyEUzto6zi6cMVA9t4WhWk").unwrap(),
288 )
289 .unwrap();
290 }
291
292 #[tokio::test]
293 async fn test_checkpoint_bad_committee() {
294 let (mut committee, full_checkpoint) = read_data().await;
295
296 committee.epoch += 10;
298
299 assert!(
300 extract_verified_effects_and_events(
301 &full_checkpoint,
302 &committee,
303 TransactionDigest::from_str("8RiKBwuAbtu8zNCtz8SrcfHyEUzto6zi6cMVA9t4WhWk")
304 .unwrap(),
305 )
306 .is_err()
307 );
308 }
309
310 #[tokio::test]
311 async fn test_checkpoint_no_transaction() {
312 let (committee, full_checkpoint) = read_data().await;
313
314 assert!(
315 extract_verified_effects_and_events(
316 &full_checkpoint,
317 &committee,
318 TransactionDigest::from_str("8RiKBwuAbtu8zNCtz8SrcfHyEUzto6zj6cMVA9t4WhWk")
319 .unwrap(),
320 )
321 .is_err()
322 );
323 }
324
325 #[tokio::test]
326 async fn test_checkpoint_bad_contents() {
327 let (committee, mut full_checkpoint) = read_data().await;
328
329 let random_contents = FullCheckpointContents::random_for_testing();
331 full_checkpoint.checkpoint_contents = random_contents.checkpoint_contents();
332
333 assert!(
334 extract_verified_effects_and_events(
335 &full_checkpoint,
336 &committee,
337 TransactionDigest::from_str("8RiKBwuAbtu8zNCtz8SrcfHyEUzto6zj6cMVA9t4WhWk")
338 .unwrap(),
339 )
340 .is_err()
341 );
342 }
343
344 #[tokio::test]
345 async fn test_checkpoint_bad_events() {
346 let (committee, mut full_checkpoint) = read_data().await;
347
348 let event = full_checkpoint.transactions[4]
349 .events
350 .as_ref()
351 .unwrap()
352 .data[0]
353 .clone();
354
355 for t in &mut full_checkpoint.transactions {
356 if let Some(events) = &mut t.events {
357 events.data.push(event.clone());
358 }
359 }
360
361 assert!(
362 extract_verified_effects_and_events(
363 &full_checkpoint,
364 &committee,
365 TransactionDigest::from_str("8RiKBwuAbtu8zNCtz8SrcfHyEUzto6zj6cMVA9t4WhWk")
366 .unwrap(),
367 )
368 .is_err()
369 );
370 }
371}