1use std::collections::HashMap;
5use std::sync::Arc;
6
7use sui_sdk_types::{Address, Object, Version};
8use sui_sdk_types::{CheckpointSequenceNumber, EpochId, SignedTransaction, ValidatorCommittee};
9use sui_types::balance_change::BalanceChange;
10use sui_types::base_types::{ObjectID, ObjectType};
11use sui_types::storage::ObjectKey;
12use sui_types::storage::RpcStateReader;
13use sui_types::storage::error::{Error as StorageError, Result};
14use sui_types::storage::{ObjectStore, TransactionInfo};
15use tap::Pipe;
16
17use crate::Direction;
18
19#[derive(Clone)]
20pub struct StateReader {
21 inner: Arc<dyn RpcStateReader>,
22}
23
24impl StateReader {
25 pub fn new(inner: Arc<dyn RpcStateReader>) -> Self {
26 Self { inner }
27 }
28
29 pub fn inner(&self) -> &Arc<dyn RpcStateReader> {
30 &self.inner
31 }
32
33 #[tracing::instrument(skip(self))]
34 pub fn get_object(&self, object_id: Address) -> crate::Result<Option<Object>> {
35 self.inner
36 .get_object(&object_id.into())
37 .map(TryInto::try_into)
38 .transpose()
39 .map_err(Into::into)
40 }
41
42 #[tracing::instrument(skip(self))]
43 pub fn get_object_with_version(
44 &self,
45 object_id: Address,
46 version: Version,
47 ) -> crate::Result<Option<Object>> {
48 self.inner
49 .get_object_by_key(&object_id.into(), version.into())
50 .map(TryInto::try_into)
51 .transpose()
52 .map_err(Into::into)
53 }
54
55 #[tracing::instrument(skip(self))]
56 pub fn get_committee(&self, epoch: EpochId) -> Option<ValidatorCommittee> {
57 self.inner
58 .get_committee(epoch)
59 .map(|committee| (*committee).clone().into())
60 }
61
62 #[tracing::instrument(skip(self))]
63 pub fn get_system_state(&self) -> Result<sui_types::sui_system_state::SuiSystemState> {
64 sui_types::sui_system_state::get_sui_system_state(self.inner())
65 .map_err(StorageError::custom)
66 .map_err(StorageError::custom)
67 }
68
69 #[tracing::instrument(skip(self))]
70 pub fn get_system_state_summary(
71 &self,
72 ) -> Result<sui_types::sui_system_state::sui_system_state_summary::SuiSystemStateSummary> {
73 use sui_types::sui_system_state::SuiSystemStateTrait;
74
75 let system_state = self.get_system_state()?;
76 let summary = system_state.into_sui_system_state_summary();
77
78 Ok(summary)
79 }
80
81 pub fn get_authenticator_state(
82 &self,
83 ) -> Result<Option<sui_types::authenticator_state::AuthenticatorStateInner>> {
84 sui_types::authenticator_state::get_authenticator_state(self.inner())
85 .map_err(StorageError::custom)
86 }
87
88 #[tracing::instrument(skip(self))]
89 pub fn get_transaction(
90 &self,
91 digest: sui_sdk_types::Digest,
92 ) -> crate::Result<(
93 sui_sdk_types::SignedTransaction,
94 sui_sdk_types::TransactionEffects,
95 Option<sui_sdk_types::TransactionEvents>,
96 )> {
97 use sui_types::effects::TransactionEffectsAPI;
98
99 let transaction_digest = digest.into();
100
101 let transaction = (*self
102 .inner()
103 .get_transaction(&transaction_digest)
104 .ok_or(TransactionNotFoundError(digest))?)
105 .clone()
106 .into_inner();
107 let effects = self
108 .inner()
109 .get_transaction_effects(&transaction_digest)
110 .ok_or(TransactionNotFoundError(digest))?;
111 let events = if effects.events_digest().is_some() {
112 self.inner()
113 .get_events(effects.transaction_digest())
114 .ok_or(TransactionNotFoundError(digest))?
115 .pipe(Some)
116 } else {
117 None
118 };
119
120 Ok((
121 transaction.try_into()?,
122 effects.try_into()?,
123 events.map(TryInto::try_into).transpose()?,
124 ))
125 }
126
127 #[tracing::instrument(skip(self))]
128 pub fn get_transaction_info(
129 &self,
130 digest: &sui_types::digests::TransactionDigest,
131 ) -> Option<TransactionInfo> {
132 self.inner()
133 .indexes()?
134 .get_transaction_info(digest)
135 .ok()
136 .flatten()
137 }
138
139 #[tracing::instrument(skip(self))]
140 pub fn get_transaction_read(
141 &self,
142 digest: sui_sdk_types::Digest,
143 ) -> crate::Result<TransactionRead> {
144 let (
145 SignedTransaction {
146 transaction,
147 signatures,
148 },
149 effects,
150 events,
151 ) = self.get_transaction(digest)?;
152
153 let (checkpoint, balance_changes, object_types) =
154 if let Some(info) = self.get_transaction_info(&(digest.into())) {
155 (
156 Some(info.checkpoint),
157 Some(info.balance_changes),
158 Some(info.object_types),
159 )
160 } else {
161 let checkpoint = self.inner().get_transaction_checkpoint(&(digest.into()));
162 (checkpoint, None, None)
163 };
164 let timestamp_ms = if let Some(checkpoint) = checkpoint {
165 self.inner()
166 .get_checkpoint_by_sequence_number(checkpoint)
167 .map(|checkpoint| checkpoint.timestamp_ms)
168 } else {
169 None
170 };
171
172 let unchanged_loaded_runtime_objects = self
173 .inner()
174 .get_unchanged_loaded_runtime_objects(&(digest.into()));
175
176 Ok(TransactionRead {
177 digest: transaction.digest(),
178 transaction,
179 signatures,
180 effects,
181 events,
182 checkpoint,
183 timestamp_ms,
184 balance_changes,
185 object_types,
186 unchanged_loaded_runtime_objects,
187 })
188 }
189
190 #[allow(unused)]
191 pub fn checkpoint_iter(
192 &self,
193 direction: Direction,
194 start: CheckpointSequenceNumber,
195 ) -> CheckpointIter {
196 CheckpointIter::new(self.clone(), direction, start)
197 }
198
199 #[allow(unused)]
200 pub fn transaction_iter(
201 &self,
202 direction: Direction,
203 cursor: (CheckpointSequenceNumber, Option<usize>),
204 ) -> CheckpointTransactionsIter {
205 CheckpointTransactionsIter::new(self.clone(), direction, cursor)
206 }
207}
208
209#[derive(Debug)]
210pub struct TransactionRead {
211 pub digest: sui_sdk_types::Digest,
212 pub transaction: sui_sdk_types::Transaction,
213 pub signatures: Vec<sui_sdk_types::UserSignature>,
214 pub effects: sui_sdk_types::TransactionEffects,
215 pub events: Option<sui_sdk_types::TransactionEvents>,
216 pub checkpoint: Option<u64>,
217 pub timestamp_ms: Option<u64>,
218 pub balance_changes: Option<Vec<BalanceChange>>,
219 pub object_types: Option<HashMap<ObjectID, ObjectType>>,
220 pub unchanged_loaded_runtime_objects: Option<Vec<ObjectKey>>,
221}
222
223pub struct CheckpointTransactionsIter {
224 reader: StateReader,
225 direction: Direction,
226
227 next_cursor: Option<(CheckpointSequenceNumber, Option<usize>)>,
228 checkpoint: Option<(
229 sui_types::messages_checkpoint::CheckpointSummary,
230 sui_types::messages_checkpoint::CheckpointContents,
231 )>,
232}
233
234impl CheckpointTransactionsIter {
235 #[allow(unused)]
236 pub fn new(
237 reader: StateReader,
238 direction: Direction,
239 start: (CheckpointSequenceNumber, Option<usize>),
240 ) -> Self {
241 Self {
242 reader,
243 direction,
244 next_cursor: Some(start),
245 checkpoint: None,
246 }
247 }
248}
249
250impl Iterator for CheckpointTransactionsIter {
251 type Item = Result<(CursorInfo, sui_types::digests::TransactionDigest)>;
252
253 fn next(&mut self) -> Option<Self::Item> {
254 loop {
255 let (current_checkpoint, transaction_index) = self.next_cursor?;
256
257 let (checkpoint, contents) = if let Some(checkpoint) = &self.checkpoint {
258 if checkpoint.0.sequence_number != current_checkpoint {
259 self.checkpoint = None;
260 continue;
261 } else {
262 checkpoint
263 }
264 } else {
265 let checkpoint = self
266 .reader
267 .inner()
268 .get_checkpoint_by_sequence_number(current_checkpoint)?;
269 let contents = self
270 .reader
271 .inner()
272 .get_checkpoint_contents_by_sequence_number(checkpoint.sequence_number)?;
273
274 self.checkpoint = Some((checkpoint.into_inner().into_data(), contents));
275 self.checkpoint.as_ref().unwrap()
276 };
277
278 let index = transaction_index
279 .map(|idx| idx.clamp(0, contents.size().saturating_sub(1)))
280 .unwrap_or_else(|| match self.direction {
281 Direction::Ascending => 0,
282 Direction::Descending => contents.size().saturating_sub(1),
283 });
284
285 self.next_cursor = {
286 let next_index = match self.direction {
287 Direction::Ascending => {
288 let next_index = index + 1;
289 if next_index >= contents.size() {
290 None
291 } else {
292 Some(next_index)
293 }
294 }
295 Direction::Descending => index.checked_sub(1),
296 };
297
298 let next_checkpoint = if next_index.is_some() {
299 Some(current_checkpoint)
300 } else {
301 match self.direction {
302 Direction::Ascending => current_checkpoint.checked_add(1),
303 Direction::Descending => current_checkpoint.checked_sub(1),
304 }
305 };
306
307 next_checkpoint.map(|checkpoint| (checkpoint, next_index))
308 };
309
310 if contents.size() == 0 {
311 continue;
312 }
313
314 let digest = contents.inner()[index].transaction;
315
316 let cursor_info = CursorInfo {
317 checkpoint: checkpoint.sequence_number,
318 timestamp_ms: checkpoint.timestamp_ms,
319 index: index as u64,
320 next_cursor: self.next_cursor,
321 };
322
323 return Some(Ok((cursor_info, digest)));
324 }
325 }
326}
327
328#[allow(unused)]
329pub struct CursorInfo {
330 pub checkpoint: CheckpointSequenceNumber,
331 pub timestamp_ms: u64,
332 #[allow(unused)]
333 pub index: u64,
334
335 pub next_cursor: Option<(CheckpointSequenceNumber, Option<usize>)>,
337}
338
339pub struct CheckpointIter {
340 reader: StateReader,
341 direction: Direction,
342
343 next_cursor: Option<CheckpointSequenceNumber>,
344}
345
346impl CheckpointIter {
347 #[allow(unused)]
348 pub fn new(reader: StateReader, direction: Direction, start: CheckpointSequenceNumber) -> Self {
349 Self {
350 reader,
351 direction,
352 next_cursor: Some(start),
353 }
354 }
355}
356
357impl Iterator for CheckpointIter {
358 type Item = Result<(
359 sui_types::messages_checkpoint::CertifiedCheckpointSummary,
360 sui_types::messages_checkpoint::CheckpointContents,
361 )>;
362
363 fn next(&mut self) -> Option<Self::Item> {
364 let current_checkpoint = self.next_cursor?;
365
366 let checkpoint = self
367 .reader
368 .inner()
369 .get_checkpoint_by_sequence_number(current_checkpoint)?
370 .into_inner();
371 let contents = self
372 .reader
373 .inner()
374 .get_checkpoint_contents_by_sequence_number(checkpoint.sequence_number)?;
375
376 self.next_cursor = match self.direction {
377 Direction::Ascending => current_checkpoint.checked_add(1),
378 Direction::Descending => current_checkpoint.checked_sub(1),
379 };
380
381 Some(Ok((checkpoint, contents)))
382 }
383}
384
385#[derive(Debug)]
386pub struct TransactionNotFoundError(pub sui_sdk_types::Digest);
387
388impl std::fmt::Display for TransactionNotFoundError {
389 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
390 write!(f, "Transaction {} not found", self.0)
391 }
392}
393
394impl std::error::Error for TransactionNotFoundError {}
395
396impl From<TransactionNotFoundError> for crate::RpcError {
397 fn from(value: TransactionNotFoundError) -> Self {
398 Self::new(tonic::Code::NotFound, value.to_string())
399 }
400}