1use std::sync::Arc;
5use std::{iter, mem, thread};
6
7use crate::authority::authority_per_epoch_store::AuthorityPerEpochStore;
8use crate::authority::authority_store_pruner::{
9 AuthorityStorePruner, AuthorityStorePruningMetrics, EPOCH_DURATION_MS_FOR_TESTING,
10};
11use crate::authority::authority_store_types::{StoreObject, StoreObjectWrapper, get_store_object};
12use crate::authority::epoch_marker_key::EpochMarkerKey;
13use crate::authority::epoch_start_configuration::{EpochFlag, EpochStartConfiguration};
14use crate::global_state_hasher::GlobalStateHashStore;
15use crate::transaction_outputs::TransactionOutputs;
16use fastcrypto::hash::{HashFunction, MultisetHash, Sha3_256};
17use futures::stream::FuturesUnordered;
18use move_core_types::account_address::AccountAddress;
19use move_core_types::resolver::{ModuleResolver, SerializedPackage};
20use sui_config::node::AuthorityStorePruningConfig;
21use sui_macros::fail_point_arg;
22use sui_package_resolver::{PackageStoreWithLruCache, SyncResolver};
23use sui_types::execution::TypeLayoutStore;
24use sui_types::global_state_hash::GlobalStateHash;
25use sui_types::message_envelope::Message;
26use sui_types::storage::{
27 BackingPackageStore, FullObjectKey, MarkerValue, ObjectKey, ObjectOrTombstone, ObjectStore,
28 get_module, get_package,
29};
30use sui_types::sui_system_state::get_sui_system_state;
31use sui_types::{base_types::SequenceNumber, fp_ensure};
32use tokio::time::Instant;
33use tracing::{debug, info, trace};
34use typed_store::traits::Map;
35use typed_store::{TypedStoreError, rocks::DBBatch};
36
37use super::authority_store_tables::LiveObject;
38use super::{authority_store_tables::AuthorityPerpetualTables, *};
39use mysten_common::sync::notify_read::NotifyRead;
40use sui_types::effects::{TransactionEffects, TransactionEvents};
41use sui_types::gas_coin::TOTAL_SUPPLY_MIST;
42
43struct AuthorityStoreMetrics {
44 sui_conservation_check_latency: IntGauge,
45 sui_conservation_live_object_count: IntGauge,
46 sui_conservation_live_object_size: IntGauge,
47 sui_conservation_imbalance: IntGauge,
48 sui_conservation_storage_fund: IntGauge,
49 sui_conservation_storage_fund_imbalance: IntGauge,
50 epoch_flags: IntGaugeVec,
51}
52
53impl AuthorityStoreMetrics {
54 pub fn new(registry: &Registry) -> Self {
55 Self {
56 sui_conservation_check_latency: register_int_gauge_with_registry!(
57 "sui_conservation_check_latency",
58 "Number of seconds took to scan all live objects in the store for SUI conservation check",
59 registry,
60 ).unwrap(),
61 sui_conservation_live_object_count: register_int_gauge_with_registry!(
62 "sui_conservation_live_object_count",
63 "Number of live objects in the store",
64 registry,
65 ).unwrap(),
66 sui_conservation_live_object_size: register_int_gauge_with_registry!(
67 "sui_conservation_live_object_size",
68 "Size in bytes of live objects in the store",
69 registry,
70 ).unwrap(),
71 sui_conservation_imbalance: register_int_gauge_with_registry!(
72 "sui_conservation_imbalance",
73 "Total amount of SUI in the network - 10B * 10^9. This delta shows the amount of imbalance",
74 registry,
75 ).unwrap(),
76 sui_conservation_storage_fund: register_int_gauge_with_registry!(
77 "sui_conservation_storage_fund",
78 "Storage Fund pool balance (only includes the storage fund proper that represents object storage)",
79 registry,
80 ).unwrap(),
81 sui_conservation_storage_fund_imbalance: register_int_gauge_with_registry!(
82 "sui_conservation_storage_fund_imbalance",
83 "Imbalance of storage fund, computed with storage_fund_balance - total_object_storage_rebates",
84 registry,
85 ).unwrap(),
86 epoch_flags: register_int_gauge_vec_with_registry!(
87 "epoch_flags",
88 "Local flags of the currently running epoch",
89 &["flag"],
90 registry,
91 ).unwrap(),
92 }
93 }
94}
95
96pub struct AuthorityStore {
103 pub(crate) perpetual_tables: Arc<AuthorityPerpetualTables>,
104
105 pub(crate) root_state_notify_read:
106 NotifyRead<EpochId, (CheckpointSequenceNumber, GlobalStateHash)>,
107
108 enable_epoch_sui_conservation_check: bool,
110
111 metrics: AuthorityStoreMetrics,
112}
113
114pub type ExecutionLockReadGuard<'a> = tokio::sync::RwLockReadGuard<'a, EpochId>;
115pub type ExecutionLockWriteGuard<'a> = tokio::sync::RwLockWriteGuard<'a, EpochId>;
116
117impl AuthorityStore {
118 pub async fn open(
121 perpetual_tables: Arc<AuthorityPerpetualTables>,
122 genesis: &Genesis,
123 config: &NodeConfig,
124 registry: &Registry,
125 ) -> SuiResult<Arc<Self>> {
126 let enable_epoch_sui_conservation_check = config
127 .expensive_safety_check_config
128 .enable_epoch_sui_conservation_check();
129
130 let epoch_start_configuration = if perpetual_tables.database_is_empty()? {
131 info!("Creating new epoch start config from genesis");
132
133 #[allow(unused_mut)]
134 let mut initial_epoch_flags = EpochFlag::default_flags_for_new_epoch(config);
135 fail_point_arg!("initial_epoch_flags", |flags: Vec<EpochFlag>| {
136 info!("Setting initial epoch flags to {:?}", flags);
137 initial_epoch_flags = flags;
138 });
139
140 let epoch_start_configuration = EpochStartConfiguration::new(
141 genesis.sui_system_object().into_epoch_start_state(),
142 *genesis.checkpoint().digest(),
143 &genesis.objects(),
144 initial_epoch_flags,
145 )?;
146 perpetual_tables.set_epoch_start_configuration(&epoch_start_configuration)?;
147 epoch_start_configuration
148 } else {
149 info!("Loading epoch start config from DB");
150 perpetual_tables
151 .epoch_start_configuration
152 .get(&())?
153 .expect("Epoch start configuration must be set in non-empty DB")
154 };
155 let cur_epoch = perpetual_tables.get_recovery_epoch_at_restart()?;
156 info!("Epoch start config: {:?}", epoch_start_configuration);
157 info!("Cur epoch: {:?}", cur_epoch);
158 let this = Self::open_inner(
159 genesis,
160 perpetual_tables,
161 enable_epoch_sui_conservation_check,
162 registry,
163 )
164 .await?;
165 this.update_epoch_flags_metrics(&[], epoch_start_configuration.flags());
166 Ok(this)
167 }
168
169 pub fn update_epoch_flags_metrics(&self, old: &[EpochFlag], new: &[EpochFlag]) {
170 for flag in old {
171 self.metrics
172 .epoch_flags
173 .with_label_values(&[&flag.to_string()])
174 .set(0);
175 }
176 for flag in new {
177 self.metrics
178 .epoch_flags
179 .with_label_values(&[&flag.to_string()])
180 .set(1);
181 }
182 }
183
184 pub fn clear_object_per_epoch_marker_table(
187 &self,
188 _execution_guard: &ExecutionLockWriteGuard<'_>,
189 ) -> SuiResult<()> {
190 self.perpetual_tables
194 .object_per_epoch_marker_table
195 .schedule_delete_all()?;
196 #[cfg(not(tidehunter))]
197 {
198 self.perpetual_tables
199 .object_per_epoch_marker_table_v2
200 .schedule_delete_all()?;
201 }
202 #[cfg(tidehunter)]
203 {
204 self.perpetual_tables
205 .object_per_epoch_marker_table_v2
206 .drop_cells_in_range_raw(&EpochMarkerKey::MIN_KEY, &EpochMarkerKey::MAX_KEY)?;
207 }
208 Ok(())
209 }
210
211 pub async fn open_with_committee_for_testing(
212 perpetual_tables: Arc<AuthorityPerpetualTables>,
213 committee: &Committee,
214 genesis: &Genesis,
215 ) -> SuiResult<Arc<Self>> {
216 assert_eq!(committee.epoch, 0);
219 Self::open_inner(genesis, perpetual_tables, true, &Registry::new()).await
220 }
221
222 async fn open_inner(
223 genesis: &Genesis,
224 perpetual_tables: Arc<AuthorityPerpetualTables>,
225 enable_epoch_sui_conservation_check: bool,
226 registry: &Registry,
227 ) -> SuiResult<Arc<Self>> {
228 let store = Arc::new(Self {
229 perpetual_tables,
230 root_state_notify_read: NotifyRead::<
231 EpochId,
232 (CheckpointSequenceNumber, GlobalStateHash),
233 >::new(),
234 enable_epoch_sui_conservation_check,
235 metrics: AuthorityStoreMetrics::new(registry),
236 });
237 if store
239 .database_is_empty()
240 .expect("Database read should not fail at init.")
241 {
242 store
243 .bulk_insert_genesis_objects(genesis.objects())
244 .expect("Cannot bulk insert genesis objects");
245
246 let transaction = VerifiedTransaction::new_unchecked(genesis.transaction().clone());
248
249 store
250 .perpetual_tables
251 .transactions
252 .insert(transaction.digest(), transaction.serializable_ref())
253 .unwrap();
254
255 store
256 .perpetual_tables
257 .effects
258 .insert(&genesis.effects().digest(), genesis.effects())
259 .unwrap();
260 if genesis.effects().events_digest().is_some() {
264 store
265 .perpetual_tables
266 .events_2
267 .insert(transaction.digest(), genesis.events())
268 .unwrap();
269 }
270 }
271
272 Ok(store)
273 }
274
275 pub fn open_no_genesis(
279 perpetual_tables: Arc<AuthorityPerpetualTables>,
280 enable_epoch_sui_conservation_check: bool,
281 registry: &Registry,
282 ) -> SuiResult<Arc<Self>> {
283 let store = Arc::new(Self {
284 perpetual_tables,
285 root_state_notify_read: NotifyRead::<
286 EpochId,
287 (CheckpointSequenceNumber, GlobalStateHash),
288 >::new(),
289 enable_epoch_sui_conservation_check,
290 metrics: AuthorityStoreMetrics::new(registry),
291 });
292 Ok(store)
293 }
294
295 pub fn get_recovery_epoch_at_restart(&self) -> SuiResult<EpochId> {
296 self.perpetual_tables.get_recovery_epoch_at_restart()
297 }
298
299 pub fn get_effects(
300 &self,
301 effects_digest: &TransactionEffectsDigest,
302 ) -> SuiResult<Option<TransactionEffects>> {
303 Ok(self.perpetual_tables.effects.get(effects_digest)?)
304 }
305
306 pub fn get_events(
307 &self,
308 digest: &TransactionDigest,
309 ) -> Result<Option<TransactionEvents>, TypedStoreError> {
310 self.perpetual_tables.events_2.get(digest)
311 }
312
313 pub fn multi_get_events(
314 &self,
315 event_digests: &[TransactionDigest],
316 ) -> SuiResult<Vec<Option<TransactionEvents>>> {
317 Ok(event_digests
318 .iter()
319 .map(|digest| self.get_events(digest))
320 .collect::<Result<Vec<_>, _>>()?)
321 }
322
323 pub fn get_unchanged_loaded_runtime_objects(
324 &self,
325 digest: &TransactionDigest,
326 ) -> Result<Option<Vec<ObjectKey>>, TypedStoreError> {
327 self.perpetual_tables
328 .unchanged_loaded_runtime_objects
329 .get(digest)
330 }
331
332 pub fn multi_get_unchanged_loaded_runtime_objects(
333 &self,
334 digests: &[TransactionDigest],
335 ) -> Result<Vec<Option<Vec<ObjectKey>>>, TypedStoreError> {
336 self.perpetual_tables
337 .unchanged_loaded_runtime_objects
338 .multi_get(digests)
339 }
340
341 pub fn multi_get_effects<'a>(
342 &self,
343 effects_digests: impl Iterator<Item = &'a TransactionEffectsDigest>,
344 ) -> Result<Vec<Option<TransactionEffects>>, TypedStoreError> {
345 self.perpetual_tables.effects.multi_get(effects_digests)
346 }
347
348 pub fn get_executed_effects(
349 &self,
350 tx_digest: &TransactionDigest,
351 ) -> Result<Option<TransactionEffects>, TypedStoreError> {
352 let effects_digest = self.perpetual_tables.executed_effects.get(tx_digest)?;
353 match effects_digest {
354 Some(digest) => Ok(self.perpetual_tables.effects.get(&digest)?),
355 None => Ok(None),
356 }
357 }
358
359 pub fn multi_get_executed_effects_digests(
362 &self,
363 digests: &[TransactionDigest],
364 ) -> Result<Vec<Option<TransactionEffectsDigest>>, TypedStoreError> {
365 self.perpetual_tables.executed_effects.multi_get(digests)
366 }
367
368 pub fn multi_get_executed_effects(
371 &self,
372 digests: &[TransactionDigest],
373 ) -> Result<Vec<Option<TransactionEffects>>, TypedStoreError> {
374 let executed_effects_digests = self.perpetual_tables.executed_effects.multi_get(digests)?;
375 let effects = self.multi_get_effects(executed_effects_digests.iter().flatten())?;
376 let mut tx_to_effects_map = effects
377 .into_iter()
378 .flatten()
379 .map(|effects| (*effects.transaction_digest(), effects))
380 .collect::<HashMap<_, _>>();
381 Ok(digests
382 .iter()
383 .map(|digest| tx_to_effects_map.remove(digest))
384 .collect())
385 }
386
387 pub fn is_tx_already_executed(&self, digest: &TransactionDigest) -> SuiResult<bool> {
388 Ok(self
389 .perpetual_tables
390 .executed_effects
391 .contains_key(digest)?)
392 }
393
394 pub fn get_marker_value(
395 &self,
396 object_key: FullObjectKey,
397 epoch_id: EpochId,
398 ) -> SuiResult<Option<MarkerValue>> {
399 Ok(self
400 .perpetual_tables
401 .object_per_epoch_marker_table_v2
402 .get(&EpochMarkerKey(epoch_id, object_key))?)
403 }
404
405 pub fn get_latest_marker(
406 &self,
407 object_id: FullObjectID,
408 epoch_id: EpochId,
409 ) -> SuiResult<Option<(SequenceNumber, MarkerValue)>> {
410 let min_key = EpochMarkerKey(epoch_id, FullObjectKey::min_for_id(&object_id));
411 let max_key = EpochMarkerKey(epoch_id, FullObjectKey::max_for_id(&object_id));
412
413 let marker_entry = self
414 .perpetual_tables
415 .object_per_epoch_marker_table_v2
416 .reversed_safe_iter_with_bounds(Some(min_key), Some(max_key))?
417 .next();
418 match marker_entry {
419 Some(Ok((EpochMarkerKey(epoch, key), marker))) => {
420 assert_eq!(epoch, epoch_id);
422 assert_eq!(key.id(), object_id);
423 Ok(Some((key.version(), marker)))
424 }
425 Some(Err(e)) => Err(e.into()),
426 None => Ok(None),
427 }
428 }
429
430 pub fn deprecated_insert_finalized_transactions(
432 &self,
433 digests: &[TransactionDigest],
434 epoch: EpochId,
435 sequence: CheckpointSequenceNumber,
436 ) -> SuiResult {
437 let mut batch = self
438 .perpetual_tables
439 .executed_transactions_to_checkpoint
440 .batch();
441 batch.insert_batch(
442 &self.perpetual_tables.executed_transactions_to_checkpoint,
443 digests.iter().map(|d| (*d, (epoch, sequence))),
444 )?;
445 batch.write()?;
446 trace!("Transactions {digests:?} finalized at checkpoint {sequence} epoch {epoch}");
447 Ok(())
448 }
449
450 pub fn deprecated_get_transaction_checkpoint(
452 &self,
453 digest: &TransactionDigest,
454 ) -> SuiResult<Option<(EpochId, CheckpointSequenceNumber)>> {
455 Ok(self
456 .perpetual_tables
457 .executed_transactions_to_checkpoint
458 .get(digest)?)
459 }
460
461 pub fn deprecated_multi_get_transaction_checkpoint(
463 &self,
464 digests: &[TransactionDigest],
465 ) -> SuiResult<Vec<Option<(EpochId, CheckpointSequenceNumber)>>> {
466 Ok(self
467 .perpetual_tables
468 .executed_transactions_to_checkpoint
469 .multi_get(digests)?
470 .into_iter()
471 .collect())
472 }
473
474 pub fn database_is_empty(&self) -> SuiResult<bool> {
476 self.perpetual_tables.database_is_empty()
477 }
478
479 pub fn object_exists_by_key(
480 &self,
481 object_id: &ObjectID,
482 version: VersionNumber,
483 ) -> SuiResult<bool> {
484 Ok(self
485 .perpetual_tables
486 .objects
487 .contains_key(&ObjectKey(*object_id, version))?)
488 }
489
490 pub fn multi_object_exists_by_key(&self, object_keys: &[ObjectKey]) -> SuiResult<Vec<bool>> {
491 Ok(self
492 .perpetual_tables
493 .objects
494 .multi_contains_keys(object_keys.to_vec())?
495 .into_iter()
496 .collect())
497 }
498
499 fn get_object_ref_prior_to_key(
500 &self,
501 object_id: &ObjectID,
502 version: VersionNumber,
503 ) -> Result<Option<ObjectRef>, SuiError> {
504 let Some(prior_version) = version.one_before() else {
505 return Ok(None);
506 };
507 let mut iterator = self
508 .perpetual_tables
509 .objects
510 .reversed_safe_iter_with_bounds(
511 Some(ObjectKey::min_for_id(object_id)),
512 Some(ObjectKey(*object_id, prior_version)),
513 )?;
514
515 if let Some((object_key, value)) = iterator.next().transpose()?
516 && object_key.0 == *object_id
517 {
518 return Ok(Some(
519 self.perpetual_tables.object_reference(&object_key, value)?,
520 ));
521 }
522 Ok(None)
523 }
524
525 pub fn multi_get_objects_by_key(
526 &self,
527 object_keys: &[ObjectKey],
528 ) -> Result<Vec<Option<Object>>, SuiError> {
529 let wrappers = self
530 .perpetual_tables
531 .objects
532 .multi_get(object_keys.to_vec())?;
533 let mut ret = vec![];
534
535 for (idx, w) in wrappers.into_iter().enumerate() {
536 ret.push(
537 w.map(|object| self.perpetual_tables.object(&object_keys[idx], object))
538 .transpose()?
539 .flatten(),
540 );
541 }
542 Ok(ret)
543 }
544
545 pub fn get_objects(&self, objects: &[ObjectID]) -> Result<Vec<Option<Object>>, SuiError> {
547 let mut result = Vec::new();
548 for id in objects {
549 result.push(self.get_object(id));
550 }
551 Ok(result)
552 }
553
554 pub(crate) fn insert_genesis_object(&self, object: Object) -> SuiResult {
559 debug_assert!(object.previous_transaction == TransactionDigest::genesis_marker());
561 let object_ref = object.compute_object_reference();
562 self.insert_object_direct(object_ref, &object)
563 }
564
565 fn insert_object_direct(&self, object_ref: ObjectRef, object: &Object) -> SuiResult {
569 let mut write_batch = self.perpetual_tables.objects.batch();
570
571 let store_object = get_store_object(object.clone());
573 write_batch.insert_batch(
574 &self.perpetual_tables.objects,
575 std::iter::once((ObjectKey::from(object_ref), store_object)),
576 )?;
577
578 write_batch.write()?;
579
580 Ok(())
581 }
582
583 #[instrument(level = "debug", skip_all)]
585 pub(crate) fn bulk_insert_genesis_objects(&self, objects: &[Object]) -> SuiResult<()> {
586 let mut batch = self.perpetual_tables.objects.batch();
587 let ref_and_objects: Vec<_> = objects
588 .iter()
589 .map(|o| (o.compute_object_reference(), o))
590 .collect();
591
592 batch.insert_batch(
593 &self.perpetual_tables.objects,
594 ref_and_objects
595 .iter()
596 .map(|(oref, o)| (ObjectKey::from(oref), get_store_object((*o).clone()))),
597 )?;
598
599 batch.write()?;
600
601 Ok(())
602 }
603
604 pub async fn bulk_insert_live_objects(
605 perpetual_db: Arc<AuthorityPerpetualTables>,
606 objects: Vec<LiveObject>,
607 expected_sha3_digest: &[u8; 32],
608 num_parallel_chunks: usize,
609 ) -> SuiResult<()> {
610 let mut hasher = Sha3_256::default();
612 for object in &objects {
613 hasher.update(object.object_reference().2.inner());
614 }
615 let sha3_digest = hasher.finalize().digest;
616 if *expected_sha3_digest != sha3_digest {
617 error!(
618 "Sha does not match! expected: {:?}, actual: {:?}",
619 expected_sha3_digest, sha3_digest
620 );
621 return Err(SuiError::from("Sha does not match"));
622 }
623
624 let chunk_size = objects.len().div_ceil(num_parallel_chunks).max(1);
625 let mut remaining = objects;
626 let mut handles = Vec::new();
627 while !remaining.is_empty() {
628 let take = chunk_size.min(remaining.len());
629 let chunk: Vec<LiveObject> = remaining.drain(..take).collect();
630 let db = perpetual_db.clone();
631 handles.push(tokio::task::spawn_blocking(move || {
632 Self::insert_objects_chunk(db, chunk)
633 }));
634 }
635 for handle in handles {
636 handle.await.expect("insert task panicked")?;
637 }
638 Ok(())
639 }
640
641 fn insert_objects_chunk(
642 perpetual_db: Arc<AuthorityPerpetualTables>,
643 objects: Vec<LiveObject>,
644 ) -> SuiResult<()> {
645 let mut batch = perpetual_db.objects.batch();
646 let mut written = 0usize;
647 const MAX_BATCH_SIZE: usize = 100_000;
648 for object in objects {
649 match object {
650 LiveObject::Normal(object) => {
651 let object_key = ObjectKey::from(object.compute_object_reference());
652 let store_object_wrapper = get_store_object(object);
653 batch.insert_batch(
654 &perpetual_db.objects,
655 std::iter::once((object_key, store_object_wrapper)),
656 )?;
657 }
658 LiveObject::Wrapped(object_key) => {
659 batch.insert_batch(
660 &perpetual_db.objects,
661 std::iter::once::<(ObjectKey, StoreObjectWrapper)>((
662 object_key,
663 StoreObject::Wrapped.into(),
664 )),
665 )?;
666 }
667 }
668 written += 1;
669 if written > MAX_BATCH_SIZE {
670 batch.write()?;
671 batch = perpetual_db.objects.batch();
672 written = 0;
673 }
674 }
675 batch.write()?;
676 Ok(())
677 }
678
679 pub fn set_epoch_start_configuration(
680 &self,
681 epoch_start_configuration: &EpochStartConfiguration,
682 ) -> SuiResult {
683 self.perpetual_tables
684 .set_epoch_start_configuration(epoch_start_configuration)?;
685 Ok(())
686 }
687
688 pub fn get_epoch_start_configuration(&self) -> SuiResult<Option<EpochStartConfiguration>> {
689 Ok(self.perpetual_tables.epoch_start_configuration.get(&())?)
690 }
691
692 #[instrument(level = "debug", skip_all)]
697 pub fn build_db_batch(
698 &self,
699 epoch_id: EpochId,
700 tx_outputs: &[Arc<TransactionOutputs>],
701 ) -> SuiResult<DBBatch> {
702 let mut write_batch = self.perpetual_tables.transactions.batch();
703 for outputs in tx_outputs {
704 self.write_one_transaction_outputs(&mut write_batch, epoch_id, outputs)?;
705 }
706 fail_point!("crash");
708
709 trace!(
710 "built batch for committed transactions: {:?}",
711 tx_outputs
712 .iter()
713 .map(|tx| tx.transaction.digest())
714 .collect::<Vec<_>>()
715 );
716
717 fail_point!("crash");
719
720 Ok(write_batch)
721 }
722
723 fn write_one_transaction_outputs(
724 &self,
725 write_batch: &mut DBBatch,
726 epoch_id: EpochId,
727 tx_outputs: &TransactionOutputs,
728 ) -> SuiResult {
729 let TransactionOutputs {
730 transaction,
731 effects,
732 markers,
733 wrapped,
734 deleted,
735 written,
736 events,
737 unchanged_loaded_runtime_objects,
738 ..
739 } = tx_outputs;
740
741 let effects_digest = effects.digest();
742 let transaction_digest = transaction.digest();
743 write_batch
746 .insert_batch(
747 &self.perpetual_tables.effects,
748 [(effects_digest, effects.clone())],
749 )?
750 .insert_batch(
751 &self.perpetual_tables.executed_effects,
752 [(transaction_digest, effects_digest)],
753 )?;
754
755 write_batch.insert_batch(
757 &self.perpetual_tables.transactions,
758 iter::once((transaction_digest, transaction.serializable_ref())),
759 )?;
760
761 write_batch.insert_batch(
762 &self.perpetual_tables.executed_transaction_digests,
763 [((epoch_id, *transaction_digest), ())],
764 )?;
765
766 write_batch.insert_batch(
768 &self.perpetual_tables.object_per_epoch_marker_table_v2,
769 markers
770 .iter()
771 .map(|(key, marker_value)| (EpochMarkerKey(epoch_id, *key), *marker_value)),
772 )?;
773 write_batch.insert_batch(
774 &self.perpetual_tables.objects,
775 deleted
776 .iter()
777 .map(|key| (key, StoreObject::Deleted))
778 .chain(wrapped.iter().map(|key| (key, StoreObject::Wrapped)))
779 .map(|(key, store_object)| (key, StoreObjectWrapper::from(store_object))),
780 )?;
781
782 let new_objects = written.iter().map(|(id, new_object)| {
784 let version = new_object.version();
785 trace!(?id, ?version, "writing object");
786 let store_object = get_store_object(new_object.clone());
787 (ObjectKey(*id, version), store_object)
788 });
789
790 write_batch.insert_batch(&self.perpetual_tables.objects, new_objects)?;
791
792 if effects.events_digest().is_some() {
794 write_batch.insert_batch(
795 &self.perpetual_tables.events_2,
796 [(transaction_digest, events)],
797 )?;
798 }
799
800 if !unchanged_loaded_runtime_objects.is_empty() {
802 write_batch.insert_batch(
803 &self.perpetual_tables.unchanged_loaded_runtime_objects,
804 [(transaction_digest, unchanged_loaded_runtime_objects)],
805 )?;
806 }
807
808 debug!(effects_digest = ?effects.digest(), "commit_certificate finished");
809
810 Ok(())
811 }
812
813 pub(crate) fn persist_transaction(&self, tx: &VerifiedExecutableTransaction) -> SuiResult {
816 let mut batch = self.perpetual_tables.transactions.batch();
817 batch.insert_batch(
818 &self.perpetual_tables.transactions,
819 [(tx.digest(), tx.clone().into_unsigned().serializable_ref())],
820 )?;
821 batch.write()?;
822 Ok(())
823 }
824
825 pub fn find_object_lt_or_eq_version(
830 &self,
831 object_id: ObjectID,
832 version: SequenceNumber,
833 ) -> SuiResult<Option<Object>> {
834 self.perpetual_tables
835 .find_object_lt_or_eq_version(object_id, version)
836 }
837
838 pub fn get_latest_object_ref_or_tombstone(
848 &self,
849 object_id: ObjectID,
850 ) -> Result<Option<ObjectRef>, SuiError> {
851 self.perpetual_tables
852 .get_latest_object_ref_or_tombstone(object_id)
853 }
854
855 pub fn get_latest_object_or_tombstone(
859 &self,
860 object_id: ObjectID,
861 ) -> Result<Option<(ObjectKey, ObjectOrTombstone)>, SuiError> {
862 let Some((object_key, store_object)) = self
863 .perpetual_tables
864 .get_latest_object_or_tombstone(object_id)?
865 else {
866 return Ok(None);
867 };
868
869 if let Some(object_ref) = self
870 .perpetual_tables
871 .tombstone_reference(&object_key, &store_object)?
872 {
873 return Ok(Some((object_key, ObjectOrTombstone::Tombstone(object_ref))));
874 }
875
876 let object = self
877 .perpetual_tables
878 .object(&object_key, store_object)?
879 .expect("Non tombstone store object could not be converted to object");
880
881 Ok(Some((object_key, ObjectOrTombstone::Object(object))))
882 }
883
884 pub fn insert_transaction_and_effects(
885 &self,
886 transaction: &VerifiedTransaction,
887 transaction_effects: &TransactionEffects,
888 ) -> Result<(), TypedStoreError> {
889 let mut write_batch = self.perpetual_tables.transactions.batch();
890 write_batch
893 .insert_batch(
894 &self.perpetual_tables.effects,
895 [(transaction_effects.digest(), transaction_effects)],
896 )?
897 .insert_batch(
898 &self.perpetual_tables.transactions,
899 [(transaction.digest(), transaction.serializable_ref())],
900 )?;
901
902 write_batch.write()?;
903 Ok(())
904 }
905
906 pub fn multi_insert_transaction_and_effects<'a>(
907 &self,
908 transactions: impl Iterator<Item = &'a VerifiedExecutionData>,
909 ) -> Result<(), TypedStoreError> {
910 let mut write_batch = self.perpetual_tables.transactions.batch();
911 for tx in transactions {
912 write_batch
913 .insert_batch(
914 &self.perpetual_tables.effects,
915 [(tx.effects.digest(), &tx.effects)],
916 )?
917 .insert_batch(
918 &self.perpetual_tables.transactions,
919 [(tx.transaction.digest(), tx.transaction.serializable_ref())],
920 )?;
921 }
922
923 write_batch.write()?;
924 Ok(())
925 }
926
927 pub fn multi_get_transaction_blocks(
928 &self,
929 tx_digests: &[TransactionDigest],
930 ) -> Result<Vec<Option<VerifiedTransaction>>, TypedStoreError> {
931 self.perpetual_tables
932 .transactions
933 .multi_get(tx_digests)
934 .map(|v| v.into_iter().map(|v| v.map(|v| v.into())).collect())
935 }
936
937 pub fn get_transaction_block(
938 &self,
939 tx_digest: &TransactionDigest,
940 ) -> Result<Option<VerifiedTransaction>, TypedStoreError> {
941 self.perpetual_tables
942 .transactions
943 .get(tx_digest)
944 .map(|v| v.map(|v| v.into()))
945 }
946
947 pub fn list_transactions_from(
948 &self,
949 start: Option<TransactionDigest>,
950 limit: usize,
951 ) -> Result<Vec<TransactionDigest>, TypedStoreError> {
952 self.perpetual_tables.list_transactions_from(start, limit)
953 }
954
955 pub fn get_executed_effects_digest_for_tx(
956 &self,
957 tx_digest: &TransactionDigest,
958 ) -> Result<Option<TransactionEffectsDigest>, TypedStoreError> {
959 self.perpetual_tables.get_executed_effects_digest(tx_digest)
960 }
961
962 pub fn get_sui_system_state_object_unsafe(&self) -> SuiResult<SuiSystemState> {
969 get_sui_system_state(self.perpetual_tables.as_ref())
970 }
971
972 pub fn expensive_check_sui_conservation<T>(
973 self: &Arc<Self>,
974 type_layout_store: T,
975 old_epoch_store: &AuthorityPerEpochStore,
976 ) -> SuiResult
977 where
978 T: TypeLayoutStore + Send + Sync + Copy,
979 {
980 if !self.enable_epoch_sui_conservation_check {
981 return Ok(());
982 }
983
984 let package_store = PackageStoreWithLruCache::new(type_layout_store);
985 info!("Starting SUI conservation check. This may take a while..");
986 let cur_time = Instant::now();
987 let mut pending_objects = vec![];
988 let mut count = 0;
989 let mut size = 0;
990 let (mut total_sui, mut total_storage_rebate) = thread::scope(|s| {
991 let pending_tasks = FuturesUnordered::new();
992 for o in self.iter_live_object_set(false) {
993 match o {
994 LiveObject::Normal(object) => {
995 size += object.object_size_for_gas_metering();
996 count += 1;
997 pending_objects.push(object);
998 if count % 1_000_000 == 0 {
999 let mut task_objects = vec![];
1000 mem::swap(&mut pending_objects, &mut task_objects);
1001 let package_store = &package_store;
1002 pending_tasks.push(s.spawn(move || {
1003 let mut layout_resolver = SyncResolver::new(package_store);
1004 let mut total_storage_rebate = 0;
1005 let mut total_sui = 0;
1006 for object in task_objects {
1007 total_storage_rebate += object.storage_rebate;
1008 let object_contained_sui = match object
1011 .get_total_sui(&mut layout_resolver)
1012 {
1013 Ok(sui) => sui,
1014 Err(e)
1015 if old_epoch_store.get_chain()
1016 == sui_protocol_config::Chain::Testnet =>
1017 {
1018 error!(
1019 "Error calculating total SUI for object {:?}: {:?}",
1020 object.compute_object_reference(),
1021 e
1022 );
1023 0
1024 }
1025 Err(e) => panic!(
1026 "Error calculating total SUI for object {:?}: {:?}",
1027 object.compute_object_reference(),
1028 e
1029 ),
1030 };
1031 total_sui += object_contained_sui - object.storage_rebate;
1032 }
1033 if count % 50_000_000 == 0 {
1034 info!("Processed {} objects", count);
1035 }
1036 (total_sui, total_storage_rebate)
1037 }));
1038 }
1039 }
1040 LiveObject::Wrapped(_) => {
1041 unreachable!("Explicitly asked to not include wrapped tombstones")
1042 }
1043 }
1044 }
1045 pending_tasks.into_iter().fold((0, 0), |init, result| {
1046 let result = result.join().unwrap();
1047 (init.0 + result.0, init.1 + result.1)
1048 })
1049 });
1050 let mut layout_resolver = SyncResolver::new(&package_store);
1051 for object in pending_objects {
1052 total_storage_rebate += object.storage_rebate;
1053 total_sui +=
1054 object.get_total_sui(&mut layout_resolver).unwrap() - object.storage_rebate;
1055 }
1056 info!(
1057 "Scanned {} live objects, took {:?}",
1058 count,
1059 cur_time.elapsed()
1060 );
1061 self.metrics
1062 .sui_conservation_live_object_count
1063 .set(count as i64);
1064 self.metrics
1065 .sui_conservation_live_object_size
1066 .set(size as i64);
1067 self.metrics
1068 .sui_conservation_check_latency
1069 .set(cur_time.elapsed().as_secs() as i64);
1070
1071 let system_state = self
1073 .get_sui_system_state_object_unsafe()
1074 .expect("Reading sui system state object cannot fail")
1075 .into_sui_system_state_summary();
1076 let storage_fund_balance = system_state.storage_fund_total_object_storage_rebates;
1077 info!(
1078 "Total SUI amount in the network: {}, storage fund balance: {}, total storage rebate: {} at beginning of epoch {}",
1079 total_sui, storage_fund_balance, total_storage_rebate, system_state.epoch
1080 );
1081
1082 let imbalance = (storage_fund_balance as i64) - (total_storage_rebate as i64);
1083 self.metrics
1084 .sui_conservation_storage_fund
1085 .set(storage_fund_balance as i64);
1086 self.metrics
1087 .sui_conservation_storage_fund_imbalance
1088 .set(imbalance);
1089 self.metrics
1090 .sui_conservation_imbalance
1091 .set((total_sui as i128 - TOTAL_SUPPLY_MIST as i128) as i64);
1092
1093 if let Some(expected_imbalance) = self
1094 .perpetual_tables
1095 .expected_storage_fund_imbalance
1096 .get(&())
1097 .expect("DB read cannot fail")
1098 {
1099 fp_ensure!(
1100 imbalance == expected_imbalance,
1101 SuiError::from(
1102 format!(
1103 "Inconsistent state detected at epoch {}: total storage rebate: {}, storage fund balance: {}, expected imbalance: {}",
1104 system_state.epoch, total_storage_rebate, storage_fund_balance, expected_imbalance
1105 ).as_str()
1106 )
1107 );
1108 } else {
1109 self.perpetual_tables
1110 .expected_storage_fund_imbalance
1111 .insert(&(), &imbalance)
1112 .expect("DB write cannot fail");
1113 }
1114
1115 if let Some(expected_sui) = self
1116 .perpetual_tables
1117 .expected_network_sui_amount
1118 .get(&())
1119 .expect("DB read cannot fail")
1120 {
1121 fp_ensure!(
1122 total_sui == expected_sui,
1123 SuiError::from(
1124 format!(
1125 "Inconsistent state detected at epoch {}: total sui: {}, expecting {}",
1126 system_state.epoch, total_sui, expected_sui
1127 )
1128 .as_str()
1129 )
1130 );
1131 } else {
1132 self.perpetual_tables
1133 .expected_network_sui_amount
1134 .insert(&(), &total_sui)
1135 .expect("DB write cannot fail");
1136 }
1137
1138 Ok(())
1139 }
1140
1141 #[instrument(level = "error", skip_all)]
1144 pub fn maybe_reaccumulate_state_hash(
1145 &self,
1146 cur_epoch_store: &AuthorityPerEpochStore,
1147 new_protocol_version: ProtocolVersion,
1148 ) {
1149 let old_simplified_unwrap_then_delete = cur_epoch_store
1150 .protocol_config()
1151 .simplified_unwrap_then_delete();
1152 let new_simplified_unwrap_then_delete =
1153 ProtocolConfig::get_for_version(new_protocol_version, cur_epoch_store.get_chain())
1154 .simplified_unwrap_then_delete();
1155 let should_reaccumulate =
1158 !old_simplified_unwrap_then_delete && new_simplified_unwrap_then_delete;
1159 if !should_reaccumulate {
1160 return;
1161 }
1162 info!(
1163 "[Re-accumulate] simplified_unwrap_then_delete is enabled in the new protocol version, re-accumulating state hash"
1164 );
1165 let cur_time = Instant::now();
1166 std::thread::scope(|s| {
1167 let pending_tasks = FuturesUnordered::new();
1168 const BITS: u8 = 5;
1177 for index in 0u8..(1 << BITS) {
1178 pending_tasks.push(s.spawn(move || {
1179 let mut id_bytes = [0; ObjectID::LENGTH];
1180 id_bytes[0] = index << (8 - BITS);
1181 let start_id = ObjectID::new(id_bytes);
1182
1183 id_bytes[0] |= (1 << (8 - BITS)) - 1;
1184 for element in id_bytes.iter_mut().skip(1) {
1185 *element = u8::MAX;
1186 }
1187 let end_id = ObjectID::new(id_bytes);
1188
1189 info!(
1190 "[Re-accumulate] Scanning object ID range {:?}..{:?}",
1191 start_id, end_id
1192 );
1193 let mut prev = (
1194 ObjectKey::min_for_id(&ObjectID::ZERO),
1195 StoreObjectWrapper::V1(StoreObject::Deleted),
1196 );
1197 let mut object_scanned: u64 = 0;
1198 let mut wrapped_objects_to_remove = vec![];
1199 for db_result in self.perpetual_tables.objects.safe_range_iter(
1200 ObjectKey::min_for_id(&start_id)..=ObjectKey::max_for_id(&end_id),
1201 ) {
1202 match db_result {
1203 Ok((object_key, object)) => {
1204 object_scanned += 1;
1205 if object_scanned.is_multiple_of(100000) {
1206 info!(
1207 "[Re-accumulate] Task {}: object scanned: {}",
1208 index, object_scanned,
1209 );
1210 }
1211 if matches!(prev.1.inner(), StoreObject::Wrapped)
1212 && object_key.0 != prev.0.0
1213 {
1214 wrapped_objects_to_remove
1215 .push(WrappedObject::new(prev.0.0, prev.0.1));
1216 }
1217
1218 prev = (object_key, object);
1219 }
1220 Err(err) => {
1221 warn!("Object iterator encounter RocksDB error {:?}", err);
1222 return Err(err);
1223 }
1224 }
1225 }
1226 if matches!(prev.1.inner(), StoreObject::Wrapped) {
1227 wrapped_objects_to_remove.push(WrappedObject::new(prev.0.0, prev.0.1));
1228 }
1229 info!(
1230 "[Re-accumulate] Task {}: object scanned: {}, wrapped objects: {}",
1231 index,
1232 object_scanned,
1233 wrapped_objects_to_remove.len(),
1234 );
1235 Ok((wrapped_objects_to_remove, object_scanned))
1236 }));
1237 }
1238 let (last_checkpoint_of_epoch, cur_accumulator) = self
1239 .get_root_state_hash_for_epoch(cur_epoch_store.epoch())
1240 .expect("read cannot fail")
1241 .expect("accumulator must exist");
1242 let (accumulator, total_objects_scanned, total_wrapped_objects) =
1243 pending_tasks.into_iter().fold(
1244 (cur_accumulator, 0u64, 0usize),
1245 |(mut accumulator, total_objects_scanned, total_wrapped_objects), task| {
1246 let (wrapped_objects_to_remove, object_scanned) =
1247 task.join().unwrap().unwrap();
1248 accumulator.remove_all(
1249 wrapped_objects_to_remove
1250 .iter()
1251 .map(|wrapped| bcs::to_bytes(wrapped).unwrap().to_vec())
1252 .collect::<Vec<Vec<u8>>>(),
1253 );
1254 (
1255 accumulator,
1256 total_objects_scanned + object_scanned,
1257 total_wrapped_objects + wrapped_objects_to_remove.len(),
1258 )
1259 },
1260 );
1261 info!(
1262 "[Re-accumulate] Total objects scanned: {}, total wrapped objects: {}",
1263 total_objects_scanned, total_wrapped_objects,
1264 );
1265 info!(
1266 "[Re-accumulate] New accumulator value: {:?}",
1267 accumulator.digest()
1268 );
1269 self.insert_state_hash_for_epoch(
1270 cur_epoch_store.epoch(),
1271 &last_checkpoint_of_epoch,
1272 &accumulator,
1273 )
1274 .unwrap();
1275 });
1276 info!(
1277 "[Re-accumulate] Re-accumulating took {}seconds",
1278 cur_time.elapsed().as_secs()
1279 );
1280 }
1281
1282 pub async fn prune_objects_and_compact_for_testing(
1283 &self,
1284 checkpoint_store: &Arc<CheckpointStore>,
1285 ) {
1286 let pruning_config = AuthorityStorePruningConfig {
1287 num_epochs_to_retain: 0,
1288 ..Default::default()
1289 };
1290 let _ = AuthorityStorePruner::prune_objects_for_eligible_epochs(
1291 &self.perpetual_tables,
1292 checkpoint_store,
1293 None,
1294 &mut sui_rpc_store::RetractionCursors::default(),
1295 pruning_config,
1296 AuthorityStorePruningMetrics::new_for_test(),
1297 EPOCH_DURATION_MS_FOR_TESTING,
1298 )
1299 .await;
1300 let _ = AuthorityStorePruner::compact(&self.perpetual_tables);
1301 }
1302
1303 pub fn remove_executed_effects_for_testing(
1304 &self,
1305 tx_digest: &TransactionDigest,
1306 ) -> anyhow::Result<()> {
1307 let effects_digest = self.perpetual_tables.executed_effects.get(tx_digest)?;
1308 if let Some(effects_digest) = effects_digest {
1309 self.perpetual_tables.executed_effects.remove(tx_digest)?;
1310 self.perpetual_tables.effects.remove(&effects_digest)?;
1311 }
1312 Ok(())
1313 }
1314
1315 #[cfg(test)]
1316 pub async fn prune_objects_immediately_for_testing(
1317 &self,
1318 transaction_effects: Vec<TransactionEffects>,
1319 ) -> anyhow::Result<()> {
1320 let mut wb = self.perpetual_tables.objects.batch();
1321
1322 let mut object_keys_to_prune = vec![];
1323 for effects in &transaction_effects {
1324 for (object_id, seq_number) in effects.modified_at_versions() {
1325 info!("Pruning object {:?} version {:?}", object_id, seq_number);
1326 object_keys_to_prune.push(ObjectKey(object_id, seq_number));
1327 }
1328 }
1329
1330 wb.delete_batch(&self.perpetual_tables.objects, object_keys_to_prune)?;
1331 wb.write()?;
1332 Ok(())
1333 }
1334
1335 #[cfg(msim)]
1337 pub fn count_object_versions(&self, object_id: ObjectID) -> usize {
1338 self.perpetual_tables
1339 .objects
1340 .safe_iter_with_bounds(
1341 Some(ObjectKey(object_id, VersionNumber::MIN)),
1342 Some(ObjectKey(object_id, VersionNumber::MAX)),
1343 )
1344 .collect::<Result<Vec<_>, _>>()
1345 .unwrap()
1346 .len()
1347 }
1348}
1349
1350impl GlobalStateHashStore for AuthorityStore {
1351 fn get_object_ref_prior_to_key_deprecated(
1352 &self,
1353 object_id: &ObjectID,
1354 version: VersionNumber,
1355 ) -> SuiResult<Option<ObjectRef>> {
1356 self.get_object_ref_prior_to_key(object_id, version)
1357 }
1358
1359 fn get_root_state_hash_for_epoch(
1360 &self,
1361 epoch: EpochId,
1362 ) -> SuiResult<Option<(CheckpointSequenceNumber, GlobalStateHash)>> {
1363 self.perpetual_tables
1364 .root_state_hash_by_epoch
1365 .get(&epoch)
1366 .map_err(Into::into)
1367 }
1368
1369 fn insert_state_hash_for_epoch(
1370 &self,
1371 epoch: EpochId,
1372 last_checkpoint_of_epoch: &CheckpointSequenceNumber,
1373 acc: &GlobalStateHash,
1374 ) -> SuiResult {
1375 self.perpetual_tables
1376 .root_state_hash_by_epoch
1377 .insert(&epoch, &(*last_checkpoint_of_epoch, acc.clone()))?;
1378 self.root_state_notify_read
1379 .notify(&epoch, &(*last_checkpoint_of_epoch, acc.clone()));
1380
1381 Ok(())
1382 }
1383
1384 fn iter_live_object_set(
1385 &self,
1386 include_wrapped_object: bool,
1387 ) -> Box<dyn Iterator<Item = LiveObject> + '_> {
1388 Box::new(
1389 self.perpetual_tables
1390 .iter_live_object_set(include_wrapped_object),
1391 )
1392 }
1393}
1394
1395impl ObjectStore for AuthorityStore {
1396 fn get_object(&self, object_id: &ObjectID) -> Option<Object> {
1398 self.perpetual_tables.as_ref().get_object(object_id)
1399 }
1400
1401 fn get_object_by_key(&self, object_id: &ObjectID, version: VersionNumber) -> Option<Object> {
1402 self.perpetual_tables.get_object_by_key(object_id, version)
1403 }
1404}
1405
1406pub struct ResolverWrapper {
1408 pub resolver: Arc<dyn BackingPackageStore + Send + Sync>,
1409 pub metrics: Arc<ResolverMetrics>,
1410}
1411
1412impl ResolverWrapper {
1413 pub fn new(
1414 resolver: Arc<dyn BackingPackageStore + Send + Sync>,
1415 metrics: Arc<ResolverMetrics>,
1416 ) -> Self {
1417 metrics.module_cache_size.set(0);
1418 ResolverWrapper { resolver, metrics }
1419 }
1420
1421 fn inc_cache_size_gauge(&self) {
1422 let current = self.metrics.module_cache_size.get();
1424 self.metrics.module_cache_size.set(current + 1);
1425 }
1426}
1427
1428impl ModuleResolver for ResolverWrapper {
1429 type Error = SuiError;
1430 fn get_module(&self, module_id: &ModuleId) -> Result<Option<Vec<u8>>, Self::Error> {
1431 self.inc_cache_size_gauge();
1432 get_module(&*self.resolver, module_id)
1433 }
1434
1435 fn get_packages_static<const N: usize>(
1436 &self,
1437 ids: [AccountAddress; N],
1438 ) -> Result<[Option<SerializedPackage>; N], Self::Error> {
1439 let mut packages = [const { None }; N];
1440 for (i, id) in ids.iter().enumerate() {
1441 packages[i] = get_package(&*self.resolver, &ObjectID::from(*id))?;
1442 }
1443 Ok(packages)
1444 }
1445
1446 fn get_packages<'a>(
1447 &self,
1448 ids: impl ExactSizeIterator<Item = &'a AccountAddress>,
1449 ) -> Result<Vec<Option<SerializedPackage>>, Self::Error> {
1450 ids.map(|id| get_package(&*self.resolver, &ObjectID::from(*id)))
1451 .collect()
1452 }
1453}
1454
1455#[cfg(test)]
1456pub type SuiLockResult = SuiResult<ObjectLockStatus>;
1457
1458#[cfg(test)]
1459#[derive(Debug, PartialEq, Eq)]
1460pub enum ObjectLockStatus {
1461 Initialized,
1462 LockedToTx { locked_by_tx: LockDetailsDeprecated },
1463 LockedAtDifferentVersion { locked_ref: ObjectRef },
1464}
1465
1466#[cfg(test)]
1467#[derive(Clone, Debug, PartialEq, Eq)]
1468pub struct LockDetailsV1Deprecated {
1469 pub epoch: EpochId,
1470 pub tx_digest: TransactionDigest,
1471}
1472
1473#[cfg(test)]
1474pub type LockDetailsDeprecated = LockDetailsV1Deprecated;