1use super::*;
5#[cfg(tidehunter)]
6use crate::authority::epoch_marker_key::EPOCH_MARKER_KEY_SIZE;
7use crate::authority::epoch_marker_key::EpochMarkerKey;
8use serde::{Deserialize, Serialize};
9use std::path::Path;
10use std::sync::atomic::AtomicU64;
11use sui_types::base_types::SequenceNumber;
12use sui_types::effects::{TransactionEffects, TransactionEvents};
13use sui_types::global_state_hash::GlobalStateHash;
14use sui_types::messages_consensus::SharedTransactionDenyConfig;
15use sui_types::storage::MarkerValue;
16use typed_store::metrics::SamplingInterval;
17use typed_store::rocks::{DBBatch, DBMap, MetricConf};
18#[cfg(not(tidehunter))]
19use typed_store::rocks::{DBMapTableConfigMap, DBOptions, default_db_options, read_size_from_env};
20use typed_store::traits::Map;
21
22use crate::authority::authority_store_types::{
23 StoreObject, StoreObjectValue, StoreObjectWrapper, get_store_object, try_construct_object,
24};
25use crate::authority::epoch_start_configuration::EpochStartConfiguration;
26use typed_store::{DBMapUtils, DbIterator};
27
28#[cfg(not(tidehunter))]
29const ENV_VAR_OBJECTS_BLOCK_CACHE_SIZE: &str = "OBJECTS_BLOCK_CACHE_MB";
30#[cfg(not(tidehunter))]
31pub(crate) const ENV_VAR_LOCKS_BLOCK_CACHE_SIZE: &str = "LOCKS_BLOCK_CACHE_MB";
32#[cfg(not(tidehunter))]
33const ENV_VAR_TRANSACTIONS_BLOCK_CACHE_SIZE: &str = "TRANSACTIONS_BLOCK_CACHE_MB";
34#[cfg(not(tidehunter))]
35const ENV_VAR_EFFECTS_BLOCK_CACHE_SIZE: &str = "EFFECTS_BLOCK_CACHE_MB";
36
37#[derive(Default)]
39pub struct AuthorityPerpetualTablesOptions {
40 pub enable_write_stall: bool,
42 pub enable_objects_compactor: bool,
46}
47
48impl AuthorityPerpetualTablesOptions {
49 #[cfg(not(tidehunter))]
50 fn apply_to(&self, mut db_options: DBOptions) -> DBOptions {
51 if !self.enable_write_stall {
52 db_options = db_options.disable_write_throttling();
53 }
54 db_options
55 }
56}
57
58#[derive(DBMapUtils)]
60#[cfg_attr(tidehunter, tidehunter)]
61pub struct AuthorityPerpetualTables {
62 pub(crate) objects: DBMap<ObjectKey, StoreObjectWrapper>,
75
76 pub(crate) transactions: DBMap<TransactionDigest, TrustedTransaction>,
80
81 pub(crate) effects: DBMap<TransactionEffectsDigest, TransactionEffects>,
90
91 pub(crate) executed_effects: DBMap<TransactionDigest, TransactionEffectsDigest>,
96
97 pub(crate) events_2: DBMap<TransactionDigest, TransactionEvents>,
99
100 pub(crate) unchanged_loaded_runtime_objects: DBMap<TransactionDigest, Vec<ObjectKey>>,
102
103 pub(crate) executed_transactions_to_checkpoint:
107 DBMap<TransactionDigest, (EpochId, CheckpointSequenceNumber)>,
108
109 pub(crate) root_state_hash_by_epoch:
113 DBMap<EpochId, (CheckpointSequenceNumber, GlobalStateHash)>,
114
115 pub(crate) epoch_start_configuration: DBMap<(), EpochStartConfiguration>,
117
118 pub(crate) pruned_checkpoint: DBMap<(), CheckpointSequenceNumber>,
120
121 pub(crate) expected_network_sui_amount: DBMap<(), u64>,
126
127 pub(crate) expected_storage_fund_imbalance: DBMap<(), i64>,
131
132 pub(crate) object_per_epoch_marker_table: DBMap<(EpochId, ObjectKey), MarkerValue>,
138 pub(crate) object_per_epoch_marker_table_v2: DBMap<EpochMarkerKey, MarkerValue>,
139
140 pub(crate) executed_transaction_digests: DBMap<(EpochId, TransactionDigest), ()>,
144
145 pub(crate) highest_committed_checkpoint: DBMap<(), CheckpointSequenceNumber>,
161
162 pub(crate) shared_transaction_deny_configs: DBMap<AuthorityName, SharedTransactionDenyConfig>,
167
168 pub(crate) last_broadcast_deny_generation: DBMap<(), u64>,
171}
172
173impl AuthorityPerpetualTables {
174 pub fn path(parent_path: &Path) -> PathBuf {
175 parent_path.join("perpetual")
176 }
177
178 #[cfg(not(tidehunter))]
179 pub fn open(
180 parent_path: &Path,
181 db_options_override: Option<AuthorityPerpetualTablesOptions>,
182 _pruner_watermark: Option<Arc<AtomicU64>>,
183 ) -> Self {
184 let db_options_override = db_options_override.unwrap_or_default();
185 let db_options = db_options_override
186 .apply_to(default_db_options().optimize_db_for_write_throughput(4, false));
187 let table_options = DBMapTableConfigMap::new(BTreeMap::from([
188 (
189 "objects".to_string(),
190 objects_table_config(db_options.clone()),
191 ),
192 (
193 "transactions".to_string(),
194 transactions_table_config(db_options.clone()),
195 ),
196 (
197 "effects".to_string(),
198 effects_table_config(db_options.clone()),
199 ),
200 ]));
201
202 Self::open_tables_read_write(
203 Self::path(parent_path),
204 MetricConf::new("perpetual")
205 .with_sampling(SamplingInterval::new(Duration::from_secs(60), 0)),
206 Some(db_options.options),
207 Some(table_options),
208 )
209 }
210
211 #[cfg(tidehunter)]
212 pub fn open(
213 parent_path: &Path,
214 db_options_override: Option<AuthorityPerpetualTablesOptions>,
215 pruner_watermark: Option<Arc<AtomicU64>>,
216 ) -> Self {
217 use crate::authority::authority_store_pruner::apply_relocation_filter;
218 tracing::warn!("AuthorityPerpetualTables using tidehunter");
219 use typed_store::tidehunter_util::{
220 Bytes, Decision, KeyIndexing, KeySpaceConfig, KeyType, ThConfig,
221 default_cells_per_mutex, default_max_dirty_keys, default_mutex_count,
222 default_value_cache_size,
223 };
224 let mutexes = default_mutex_count() * 2;
225 let transaction_mutexes = mutexes * 4;
226 let value_cache_size = default_value_cache_size();
227 let pruner_watermark = pruner_watermark.unwrap_or(Arc::new(AtomicU64::new(0)));
229
230 let bloom_config = KeySpaceConfig::new().with_bloom_filter(0.001, 32_000);
231 let objects_compactor = |iter: &mut dyn DoubleEndedIterator<Item = &Bytes>| {
232 let mut retain = HashSet::new();
233 let mut previous: Option<&[u8]> = None;
234 const OID_SIZE: usize = 32;
235 for key in iter.rev() {
236 if let Some(prev) = previous
237 && prev == &key[..OID_SIZE]
238 {
239 continue;
240 }
241 previous = Some(&key[..OID_SIZE]);
242 retain.insert(key.clone());
243 }
244 retain
245 };
246 let mut digest_prefix = vec![0; 8];
247 digest_prefix[7] = 32;
248 let uniform_key = KeyType::uniform(default_cells_per_mutex());
249 let epoch_prefix_key = KeyType::from_prefix_bits(9 * 8 + 4);
250 let epoch_tx_digest_prefix_key =
252 KeyType::from_prefix_bits((8+ 8) * 8 + 12);
253 let object_indexing = KeyIndexing::fixed(32 + 8); let mut objects_config = KeySpaceConfig::new()
255 .with_max_dirty_keys(16 * default_max_dirty_keys())
256 .with_value_cache_size(value_cache_size);
257 if matches!(db_options_override, Some(options) if options.enable_objects_compactor) {
258 objects_config = objects_config.with_compactor(Box::new(objects_compactor));
259 }
260
261 let configs = vec![
262 (
263 "objects".to_string(),
264 ThConfig::new_with_config_indexing(
265 object_indexing,
266 mutexes * 4,
267 KeyType::uniform(1),
268 objects_config,
269 ),
270 ),
271 (
272 "transactions".to_string(),
273 ThConfig::new_with_rm_prefix_indexing(
274 KeyIndexing::key_reduction(32, 0..16),
275 transaction_mutexes,
276 uniform_key,
277 KeySpaceConfig::new()
278 .with_value_cache_size(value_cache_size)
279 .with_relocation_filter(|_, _| Decision::Remove),
280 digest_prefix.clone(),
281 ),
282 ),
283 (
284 "effects".to_string(),
285 ThConfig::new_with_rm_prefix_indexing(
286 KeyIndexing::key_reduction(32, 0..16),
287 transaction_mutexes,
288 uniform_key,
289 apply_relocation_filter(
290 bloom_config.clone().with_value_cache_size(value_cache_size),
291 pruner_watermark.clone(),
292 |effects: TransactionEffects| effects.executed_epoch(),
293 false,
294 ),
295 digest_prefix.clone(),
296 ),
297 ),
298 (
299 "executed_effects".to_string(),
300 ThConfig::new_with_rm_prefix_indexing(
301 KeyIndexing::key_reduction(32, 0..16),
302 transaction_mutexes,
303 uniform_key,
304 bloom_config
305 .clone()
306 .with_value_cache_size(value_cache_size)
307 .with_relocation_filter(|_, _| Decision::Remove),
308 digest_prefix.clone(),
309 ),
310 ),
311 (
312 "events".to_string(),
313 ThConfig::new_with_rm_prefix(
314 32 + 8,
315 mutexes,
316 uniform_key,
317 KeySpaceConfig::default().with_relocation_filter(|_, _| Decision::Remove),
318 digest_prefix.clone(),
319 ),
320 ),
321 (
322 "events_2".to_string(),
323 ThConfig::new_with_rm_prefix(
324 32,
325 mutexes,
326 uniform_key,
327 KeySpaceConfig::default().with_relocation_filter(|_, _| Decision::Remove),
328 digest_prefix.clone(),
329 ),
330 ),
331 (
332 "unchanged_loaded_runtime_objects".to_string(),
333 ThConfig::new_with_rm_prefix(
334 32,
335 mutexes,
336 uniform_key,
337 KeySpaceConfig::default().with_relocation_filter(|_, _| Decision::Remove),
338 digest_prefix.clone(),
339 ),
340 ),
341 (
342 "executed_transactions_to_checkpoint".to_string(),
343 ThConfig::new_with_rm_prefix(
344 32,
345 mutexes,
346 uniform_key,
347 apply_relocation_filter(
348 KeySpaceConfig::default(),
349 pruner_watermark.clone(),
350 |(epoch_id, _): (EpochId, CheckpointSequenceNumber)| epoch_id,
351 false,
352 ),
353 digest_prefix.clone(),
354 ),
355 ),
356 (
357 "root_state_hash_by_epoch".to_string(),
358 ThConfig::new(8, 1, KeyType::uniform(1)),
359 ),
360 (
361 "epoch_start_configuration".to_string(),
362 ThConfig::new(0, 1, KeyType::uniform(1)),
363 ),
364 (
365 "pruned_checkpoint".to_string(),
366 ThConfig::new(0, 1, KeyType::uniform(1)),
367 ),
368 (
369 "expected_network_sui_amount".to_string(),
370 ThConfig::new(0, 1, KeyType::uniform(1)),
371 ),
372 (
373 "expected_storage_fund_imbalance".to_string(),
374 ThConfig::new(0, 1, KeyType::uniform(1)),
375 ),
376 (
377 "object_per_epoch_marker_table".to_string(),
378 ThConfig::new_with_config_indexing(
379 KeyIndexing::VariableLength,
380 mutexes,
381 epoch_prefix_key,
382 apply_relocation_filter(
383 KeySpaceConfig::default(),
384 pruner_watermark.clone(),
385 |(epoch_id, _): (EpochId, ObjectKey)| epoch_id,
386 true,
387 ),
388 ),
389 ),
390 (
391 "object_per_epoch_marker_table_v2".to_string(),
392 ThConfig::new_with_config_indexing(
393 KeyIndexing::fixed(EPOCH_MARKER_KEY_SIZE),
394 mutexes,
395 epoch_prefix_key,
396 apply_relocation_filter(
397 bloom_config.clone(),
398 pruner_watermark.clone(),
399 |k: EpochMarkerKey| k.0,
400 true,
401 ),
402 ),
403 ),
404 (
405 "executed_transaction_digests".to_string(),
406 ThConfig::new_with_config_indexing(
407 KeyIndexing::fixed(8 + (32 + 8)),
409 transaction_mutexes,
410 epoch_tx_digest_prefix_key,
411 apply_relocation_filter(
412 bloom_config.clone(),
413 pruner_watermark.clone(),
414 |(epoch_id, _): (EpochId, TransactionDigest)| epoch_id,
415 true,
416 ),
417 ),
418 ),
419 (
420 "highest_committed_checkpoint".to_string(),
421 ThConfig::new(0, 1, KeyType::uniform(1)),
422 ),
423 (
424 "shared_transaction_deny_configs".to_string(),
425 ThConfig::new(104, 1, KeyType::uniform(1)),
429 ),
430 (
431 "last_broadcast_deny_generation".to_string(),
432 ThConfig::new(0, 1, KeyType::uniform(1)),
433 ),
434 ];
435 Self::open_tables_read_write(
436 Self::path(parent_path),
437 MetricConf::new("perpetual")
438 .with_sampling(SamplingInterval::new(Duration::from_secs(60), 0)),
439 configs.into_iter().collect(),
440 )
441 }
442
443 #[cfg(not(tidehunter))]
444 pub fn open_readonly(parent_path: &Path) -> AuthorityPerpetualTablesReadOnly {
445 Self::get_read_only_handle(
446 Self::path(parent_path),
447 None,
448 None,
449 MetricConf::new("perpetual_readonly"),
450 )
451 }
452
453 #[cfg(tidehunter)]
454 pub fn open_readonly(parent_path: &Path) -> Self {
455 Self::open(parent_path, None, None)
456 }
457
458 #[cfg(tidehunter)]
459 pub fn force_rebuild_control_region(&self) -> anyhow::Result<()> {
460 self.objects.db.force_rebuild_control_region()
461 }
462
463 #[cfg(tidehunter)]
468 pub fn wait_for_tidehunter_background_threads(self: Arc<Self>) {
469 let strong = Arc::strong_count(&self);
470 if strong != 1 {
471 println!(
472 "WARNING: wait_for_tidehunter_background_threads called with Arc<AuthorityPerpetualTables> strong_count={} (expected 1); other clones will keep DBMap.db Arc<Database> alive past drop(self) and the inner Database wait will warn/timeout",
473 strong,
474 );
475 }
476 let db = self.objects.db.clone();
477 drop(self);
478 db.wait_for_tidehunter_background_threads();
479 }
480
481 pub fn find_object_lt_or_eq_version(
485 &self,
486 object_id: ObjectID,
487 version: SequenceNumber,
488 ) -> SuiResult<Option<Object>> {
489 let mut iter = self.objects.reversed_safe_iter_with_bounds(
490 Some(ObjectKey::min_for_id(&object_id)),
491 Some(ObjectKey(object_id, version)),
492 )?;
493 match iter.next() {
494 Some(Ok((key, o))) => self.object(&key, o),
495 Some(Err(e)) => Err(e.into()),
496 None => Ok(None),
497 }
498 }
499
500 fn construct_object(
501 &self,
502 object_key: &ObjectKey,
503 store_object: StoreObjectValue,
504 ) -> Result<Object, SuiError> {
505 try_construct_object(object_key, store_object)
506 }
507
508 pub fn object(
511 &self,
512 object_key: &ObjectKey,
513 store_object: StoreObjectWrapper,
514 ) -> Result<Option<Object>, SuiError> {
515 let StoreObject::Value(store_object) = store_object.migrate().into_inner() else {
516 return Ok(None);
517 };
518 Ok(Some(self.construct_object(object_key, *store_object)?))
519 }
520
521 pub fn object_reference(
522 &self,
523 object_key: &ObjectKey,
524 store_object: StoreObjectWrapper,
525 ) -> Result<ObjectRef, SuiError> {
526 let obj_ref = match store_object.migrate().into_inner() {
527 StoreObject::Value(object) => self
528 .construct_object(object_key, *object)?
529 .compute_object_reference(),
530 StoreObject::Deleted => (
531 object_key.0,
532 object_key.1,
533 ObjectDigest::OBJECT_DIGEST_DELETED,
534 ),
535 StoreObject::Wrapped => (
536 object_key.0,
537 object_key.1,
538 ObjectDigest::OBJECT_DIGEST_WRAPPED,
539 ),
540 };
541 Ok(obj_ref)
542 }
543
544 pub fn tombstone_reference(
545 &self,
546 object_key: &ObjectKey,
547 store_object: &StoreObjectWrapper,
548 ) -> Result<Option<ObjectRef>, SuiError> {
549 let obj_ref = match store_object.inner() {
550 StoreObject::Deleted => Some((
551 object_key.0,
552 object_key.1,
553 ObjectDigest::OBJECT_DIGEST_DELETED,
554 )),
555 StoreObject::Wrapped => Some((
556 object_key.0,
557 object_key.1,
558 ObjectDigest::OBJECT_DIGEST_WRAPPED,
559 )),
560 _ => None,
561 };
562 Ok(obj_ref)
563 }
564
565 pub fn get_latest_object_ref_or_tombstone(
566 &self,
567 object_id: ObjectID,
568 ) -> Result<Option<ObjectRef>, SuiError> {
569 let mut iterator = self.objects.reversed_safe_iter_with_bounds(
570 Some(ObjectKey::min_for_id(&object_id)),
571 Some(ObjectKey::max_for_id(&object_id)),
572 )?;
573
574 if let Some(Ok((object_key, value))) = iterator.next()
575 && object_key.0 == object_id
576 {
577 return Ok(Some(self.object_reference(&object_key, value)?));
578 }
579 Ok(None)
580 }
581
582 pub fn get_latest_object_or_tombstone(
583 &self,
584 object_id: ObjectID,
585 ) -> Result<Option<(ObjectKey, StoreObjectWrapper)>, SuiError> {
586 let mut iterator = self.objects.reversed_safe_iter_with_bounds(
587 Some(ObjectKey::min_for_id(&object_id)),
588 Some(ObjectKey::max_for_id(&object_id)),
589 )?;
590
591 if let Some(Ok((object_key, value))) = iterator.next()
592 && object_key.0 == object_id
593 {
594 return Ok(Some((object_key, value)));
595 }
596 Ok(None)
597 }
598
599 pub fn get_recovery_epoch_at_restart(&self) -> SuiResult<EpochId> {
600 Ok(self
601 .epoch_start_configuration
602 .get(&())?
603 .expect("Must have current epoch.")
604 .epoch_start_state()
605 .epoch())
606 }
607
608 pub fn set_epoch_start_configuration(
609 &self,
610 epoch_start_configuration: &EpochStartConfiguration,
611 ) -> SuiResult {
612 let mut wb = self.epoch_start_configuration.batch();
613 wb.insert_batch(
614 &self.epoch_start_configuration,
615 std::iter::once(((), epoch_start_configuration)),
616 )?;
617 wb.write()?;
618 Ok(())
619 }
620
621 pub fn get_highest_pruned_checkpoint(
622 &self,
623 ) -> Result<Option<CheckpointSequenceNumber>, TypedStoreError> {
624 self.pruned_checkpoint.get(&())
625 }
626
627 pub fn set_highest_pruned_checkpoint(
628 &self,
629 wb: &mut DBBatch,
630 checkpoint_number: CheckpointSequenceNumber,
631 ) -> SuiResult {
632 wb.insert_batch(&self.pruned_checkpoint, [((), checkpoint_number)])?;
633 Ok(())
634 }
635
636 pub fn get_highest_committed_checkpoint(
637 &self,
638 ) -> Result<Option<CheckpointSequenceNumber>, TypedStoreError> {
639 self.highest_committed_checkpoint.get(&())
640 }
641
642 pub fn set_highest_committed_checkpoint(
646 &self,
647 wb: &mut DBBatch,
648 checkpoint_number: CheckpointSequenceNumber,
649 ) -> SuiResult {
650 wb.insert_batch(
651 &self.highest_committed_checkpoint,
652 [((), checkpoint_number)],
653 )?;
654 Ok(())
655 }
656
657 pub fn get_transaction(
658 &self,
659 digest: &TransactionDigest,
660 ) -> SuiResult<Option<TrustedTransaction>> {
661 let Some(transaction) = self.transactions.get(digest)? else {
662 return Ok(None);
663 };
664 Ok(Some(transaction))
665 }
666
667 pub fn list_transactions_from(
668 &self,
669 start: Option<TransactionDigest>,
670 limit: usize,
671 ) -> Result<Vec<TransactionDigest>, typed_store::TypedStoreError> {
672 let iter = self.transactions.safe_iter_with_bounds(start, None);
673 let mut result = Vec::with_capacity(limit);
674 for item in iter.take(limit) {
675 let (digest, _) = item?;
676 result.push(digest);
677 }
678 Ok(result)
679 }
680
681 pub fn get_executed_effects_digest(
682 &self,
683 tx_digest: &TransactionDigest,
684 ) -> Result<Option<TransactionEffectsDigest>, typed_store::TypedStoreError> {
685 self.executed_effects.get(tx_digest)
686 }
687
688 pub fn get_effects_by_digest(
689 &self,
690 effects_digest: &TransactionEffectsDigest,
691 ) -> Result<Option<TransactionEffects>, typed_store::TypedStoreError> {
692 self.effects.get(effects_digest)
693 }
694
695 pub fn insert_executed_transaction_digests_batch(
698 &self,
699 epoch: EpochId,
700 digests: impl Iterator<Item = TransactionDigest>,
701 ) -> SuiResult {
702 let mut batch = self.executed_transaction_digests.batch();
703 batch.insert_batch(
704 &self.executed_transaction_digests,
705 digests.map(|digest| ((epoch, digest), ())),
706 )?;
707 batch.write()?;
708 Ok(())
709 }
710
711 pub fn get_effects(&self, digest: &TransactionDigest) -> SuiResult<Option<TransactionEffects>> {
712 let Some(effect_digest) = self.executed_effects.get(digest)? else {
713 return Ok(None);
714 };
715 Ok(self.effects.get(&effect_digest)?)
716 }
717
718 pub(crate) fn was_transaction_executed_in_last_epoch(
719 &self,
720 digest: &TransactionDigest,
721 current_epoch: EpochId,
722 ) -> bool {
723 if current_epoch == 0 {
724 return false;
725 }
726 self.executed_transaction_digests
727 .contains_key(&(current_epoch - 1, *digest))
728 .expect("db error")
729 }
730
731 pub fn get_checkpoint_sequence_number(
734 &self,
735 digest: &TransactionDigest,
736 ) -> SuiResult<Option<(EpochId, CheckpointSequenceNumber)>> {
737 Ok(self.executed_transactions_to_checkpoint.get(digest)?)
738 }
739
740 pub fn set_highest_pruned_checkpoint_without_wb(
741 &self,
742 checkpoint_number: CheckpointSequenceNumber,
743 ) -> SuiResult {
744 let mut wb = self.pruned_checkpoint.batch();
745 self.set_highest_pruned_checkpoint(&mut wb, checkpoint_number)?;
746 wb.write()?;
747 Ok(())
748 }
749
750 pub fn database_is_empty(&self) -> SuiResult<bool> {
751 Ok(self.objects.safe_iter().next().is_none())
752 }
753
754 pub fn iter_live_object_set(&self, include_wrapped_object: bool) -> LiveSetIter<'_> {
755 LiveSetIter {
756 iter: Box::new(self.objects.safe_iter()),
757 tables: self,
758 prev: None,
759 include_wrapped_object,
760 }
761 }
762
763 pub fn range_iter_live_object_set(
764 &self,
765 lower_bound: Option<ObjectID>,
766 upper_bound: Option<ObjectID>,
767 include_wrapped_object: bool,
768 ) -> LiveSetIter<'_> {
769 let lower_bound = lower_bound.as_ref().map(ObjectKey::min_for_id);
770 let upper_bound = upper_bound.as_ref().map(ObjectKey::max_for_id);
771
772 LiveSetIter {
773 iter: Box::new(self.objects.safe_iter_with_bounds(lower_bound, upper_bound)),
774 tables: self,
775 prev: None,
776 include_wrapped_object,
777 }
778 }
779
780 pub fn checkpoint_db(&self, path: &Path) -> SuiResult {
781 self.objects.checkpoint_db(path).map_err(Into::into)
783 }
784
785 pub fn insert_root_state_hash(
786 &self,
787 epoch: EpochId,
788 last_checkpoint_of_epoch: CheckpointSequenceNumber,
789 hash: GlobalStateHash,
790 ) -> SuiResult {
791 self.root_state_hash_by_epoch
792 .insert(&epoch, &(last_checkpoint_of_epoch, hash))?;
793 Ok(())
794 }
795
796 pub fn insert_object_test_only(&self, object: Object) -> SuiResult {
797 let object_reference = object.compute_object_reference();
798 let wrapper = get_store_object(object);
799 let mut wb = self.objects.batch();
800 wb.insert_batch(
801 &self.objects,
802 std::iter::once((ObjectKey::from(object_reference), wrapper)),
803 )?;
804 wb.write()?;
805 Ok(())
806 }
807
808 pub fn get_object_fallible(&self, object_id: &ObjectID) -> SuiResult<Option<Object>> {
810 let obj_entry = self
811 .objects
812 .reversed_safe_iter_with_bounds(None, Some(ObjectKey::max_for_id(object_id)))?
813 .next();
814
815 match obj_entry.transpose()? {
816 Some((ObjectKey(obj_id, version), obj)) if obj_id == *object_id => {
817 Ok(self.object(&ObjectKey(obj_id, version), obj)?)
818 }
819 _ => Ok(None),
820 }
821 }
822
823 pub fn get_object_by_key_fallible(
824 &self,
825 object_id: &ObjectID,
826 version: VersionNumber,
827 ) -> SuiResult<Option<Object>> {
828 Ok(self
829 .objects
830 .get(&ObjectKey(*object_id, version))?
831 .and_then(|object| {
832 self.object(&ObjectKey(*object_id, version), object)
833 .expect("object construction error")
834 }))
835 }
836}
837
838impl ObjectStore for AuthorityPerpetualTables {
839 fn get_object(&self, object_id: &ObjectID) -> Option<Object> {
841 self.get_object_fallible(object_id).expect("db error")
842 }
843
844 fn get_object_by_key(&self, object_id: &ObjectID, version: VersionNumber) -> Option<Object> {
845 self.get_object_by_key_fallible(object_id, version)
846 .expect("db error")
847 }
848}
849
850pub struct LiveSetIter<'a> {
851 iter: DbIterator<'a, (ObjectKey, StoreObjectWrapper)>,
852 tables: &'a AuthorityPerpetualTables,
853 prev: Option<(ObjectKey, StoreObjectWrapper)>,
854 include_wrapped_object: bool,
856}
857
858#[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize, Hash)]
859pub enum LiveObject {
860 Normal(Object),
861 Wrapped(ObjectKey),
862}
863
864impl LiveObject {
865 pub fn object_id(&self) -> ObjectID {
866 match self {
867 LiveObject::Normal(obj) => obj.id(),
868 LiveObject::Wrapped(key) => key.0,
869 }
870 }
871
872 pub fn version(&self) -> SequenceNumber {
873 match self {
874 LiveObject::Normal(obj) => obj.version(),
875 LiveObject::Wrapped(key) => key.1,
876 }
877 }
878
879 pub fn object_reference(&self) -> ObjectRef {
880 match self {
881 LiveObject::Normal(obj) => obj.compute_object_reference(),
882 LiveObject::Wrapped(key) => (key.0, key.1, ObjectDigest::OBJECT_DIGEST_WRAPPED),
883 }
884 }
885}
886
887impl LiveSetIter<'_> {
888 fn store_object_wrapper_to_live_object(
889 &self,
890 object_key: ObjectKey,
891 store_object: StoreObjectWrapper,
892 ) -> Option<LiveObject> {
893 match store_object.migrate().into_inner() {
894 StoreObject::Value(object) => {
895 let object = self
896 .tables
897 .construct_object(&object_key, *object)
898 .expect("Constructing object from store cannot fail");
899 Some(LiveObject::Normal(object))
900 }
901 StoreObject::Wrapped => {
902 if self.include_wrapped_object {
903 Some(LiveObject::Wrapped(object_key))
904 } else {
905 None
906 }
907 }
908 StoreObject::Deleted => None,
909 }
910 }
911}
912
913impl Iterator for LiveSetIter<'_> {
914 type Item = LiveObject;
915
916 fn next(&mut self) -> Option<Self::Item> {
917 loop {
918 if let Some(Ok((next_key, next_value))) = self.iter.next() {
919 let prev = self.prev.take();
920 self.prev = Some((next_key, next_value));
921
922 if let Some((prev_key, prev_value)) = prev
923 && prev_key.0 != next_key.0
924 {
925 let live_object =
926 self.store_object_wrapper_to_live_object(prev_key, prev_value);
927 if live_object.is_some() {
928 return live_object;
929 }
930 }
931 continue;
932 }
933 if let Some((key, value)) = self.prev.take() {
934 let live_object = self.store_object_wrapper_to_live_object(key, value);
935 if live_object.is_some() {
936 return live_object;
937 }
938 }
939 return None;
940 }
941 }
942}
943
944#[cfg(not(tidehunter))]
946fn objects_table_config(db_options: DBOptions) -> DBOptions {
947 db_options
948 .optimize_for_write_throughput()
949 .optimize_for_read(read_size_from_env(ENV_VAR_OBJECTS_BLOCK_CACHE_SIZE).unwrap_or(5 * 1024))
950}
951
952#[cfg(not(tidehunter))]
953fn transactions_table_config(db_options: DBOptions) -> DBOptions {
954 db_options
955 .optimize_for_write_throughput()
956 .optimize_for_point_lookup(
957 read_size_from_env(ENV_VAR_TRANSACTIONS_BLOCK_CACHE_SIZE).unwrap_or(512),
958 )
959}
960
961#[cfg(not(tidehunter))]
962fn effects_table_config(db_options: DBOptions) -> DBOptions {
963 db_options
964 .optimize_for_write_throughput()
965 .optimize_for_point_lookup(
966 read_size_from_env(ENV_VAR_EFFECTS_BLOCK_CACHE_SIZE).unwrap_or(1024),
967 )
968}