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