1use std::{sync::Arc, time::Instant};
9
10use async_trait::async_trait;
11pub use iota_node_storage::{
12 KVStoreCheckpointData, KVStoreTransactionData, TransactionKeyValueStoreTrait,
13};
14use iota_sdk_types::{
15 CheckpointContents, CheckpointDigest, ObjectId, TransactionDigest, TransactionEffects,
16 TransactionEvents, Version,
17};
18use iota_types::{
19 base_types::VersionNumber,
20 error::{IotaError, IotaResult, UserInputError},
21 messages_checkpoint::{CertifiedCheckpointSummary, CheckpointSequenceNumber},
22 object::Object,
23 storage::ObjectKey,
24 transaction::TransactionEnvelope,
25};
26use tracing::instrument;
27
28use crate::key_value_store_metrics::KeyValueStoreMetrics;
29
30pub struct TransactionKeyValueStore {
31 store_name: &'static str,
32 metrics: Arc<KeyValueStoreMetrics>,
33 inner: Arc<dyn TransactionKeyValueStoreTrait + Send + Sync>,
34}
35
36impl TransactionKeyValueStore {
37 pub fn new(
38 store_name: &'static str,
39 metrics: Arc<KeyValueStoreMetrics>,
40 inner: Arc<dyn TransactionKeyValueStoreTrait + Send + Sync>,
41 ) -> Self {
42 Self {
43 store_name,
44 metrics,
45 inner,
46 }
47 }
48
49 pub async fn multi_get(
52 &self,
53 transaction_keys: &[TransactionDigest],
54 effects_keys: &[TransactionDigest],
55 ) -> IotaResult<KVStoreTransactionData> {
56 let start = Instant::now();
57 let res = self.inner.multi_get(transaction_keys, effects_keys).await;
58 let elapsed = start.elapsed();
59
60 let num_txns = transaction_keys.len() as u64;
61 let num_effects = effects_keys.len() as u64;
62 let total_keys = num_txns + num_effects;
63
64 self.metrics
65 .key_value_store_num_fetches_latency_ms
66 .with_label_values(&[self.store_name, "tx"])
67 .observe(elapsed.as_millis() as f64);
68 self.metrics
69 .key_value_store_num_fetches_batch_size
70 .with_label_values(&[self.store_name, "tx"])
71 .observe(total_keys as f64);
72
73 if let Ok((transactions, effects)) = &res {
74 let txns_not_found = transactions.iter().filter(|v| v.is_none()).count() as u64;
75 let effects_not_found = effects.iter().filter(|v| v.is_none()).count() as u64;
76
77 if num_txns > 0 {
78 self.metrics
79 .key_value_store_num_fetches_success
80 .with_label_values(&[self.store_name, "tx"])
81 .inc_by(num_txns);
82 }
83 if num_effects > 0 {
84 self.metrics
85 .key_value_store_num_fetches_success
86 .with_label_values(&[self.store_name, "fx"])
87 .inc_by(num_effects);
88 }
89
90 if txns_not_found > 0 {
91 self.metrics
92 .key_value_store_num_fetches_not_found
93 .with_label_values(&[self.store_name, "tx"])
94 .inc_by(txns_not_found);
95 }
96 if effects_not_found > 0 {
97 self.metrics
98 .key_value_store_num_fetches_not_found
99 .with_label_values(&[self.store_name, "fx"])
100 .inc_by(effects_not_found);
101 }
102 } else {
103 self.metrics
104 .key_value_store_num_fetches_error
105 .with_label_values(&[self.store_name, "tx"])
106 .inc_by(num_txns);
107 self.metrics
108 .key_value_store_num_fetches_error
109 .with_label_values(&[self.store_name, "fx"])
110 .inc_by(num_effects);
111 }
112
113 res
114 }
115
116 pub async fn multi_get_checkpoints(
117 &self,
118 checkpoint_summaries: &[CheckpointSequenceNumber],
119 checkpoint_contents: &[CheckpointSequenceNumber],
120 checkpoint_summaries_by_digest: &[CheckpointDigest],
121 ) -> IotaResult<(
122 Vec<Option<CertifiedCheckpointSummary>>,
123 Vec<Option<CheckpointContents>>,
124 Vec<Option<CertifiedCheckpointSummary>>,
125 )> {
126 let start = Instant::now();
127 let res = self
128 .inner
129 .multi_get_checkpoints(
130 checkpoint_summaries,
131 checkpoint_contents,
132 checkpoint_summaries_by_digest,
133 )
134 .await;
135 let elapsed = start.elapsed();
136
137 let num_summaries =
138 checkpoint_summaries.len() as u64 + checkpoint_summaries_by_digest.len() as u64;
139 let num_contents = checkpoint_contents.len() as u64;
140
141 self.metrics
142 .key_value_store_num_fetches_latency_ms
143 .with_label_values(&[self.store_name, "checkpoint"])
144 .observe(elapsed.as_millis() as f64);
145 self.metrics
146 .key_value_store_num_fetches_batch_size
147 .with_label_values(&[self.store_name, "checkpoint_summary"])
148 .observe(num_summaries as f64);
149 self.metrics
150 .key_value_store_num_fetches_batch_size
151 .with_label_values(&[self.store_name, "checkpoint_content"])
152 .observe(num_contents as f64);
153
154 if let Ok((summaries, contents, summaries_by_digest)) = &res {
155 let summaries_not_found = summaries.iter().filter(|v| v.is_none()).count() as u64
156 + summaries_by_digest.iter().filter(|v| v.is_none()).count() as u64;
157 let contents_not_found = contents.iter().filter(|v| v.is_none()).count() as u64;
158
159 if num_summaries > 0 {
160 self.metrics
161 .key_value_store_num_fetches_success
162 .with_label_values(&[self.store_name, "ckpt_summary"])
163 .inc_by(num_summaries);
164 }
165 if num_contents > 0 {
166 self.metrics
167 .key_value_store_num_fetches_success
168 .with_label_values(&[self.store_name, "ckpt_contents"])
169 .inc_by(num_contents);
170 }
171
172 if summaries_not_found > 0 {
173 self.metrics
174 .key_value_store_num_fetches_not_found
175 .with_label_values(&[self.store_name, "ckpt_summary"])
176 .inc_by(summaries_not_found);
177 }
178 if contents_not_found > 0 {
179 self.metrics
180 .key_value_store_num_fetches_not_found
181 .with_label_values(&[self.store_name, "ckpt_contents"])
182 .inc_by(contents_not_found);
183 }
184 } else {
185 self.metrics
186 .key_value_store_num_fetches_error
187 .with_label_values(&[self.store_name, "ckpt_summary"])
188 .inc_by(num_summaries);
189 self.metrics
190 .key_value_store_num_fetches_error
191 .with_label_values(&[self.store_name, "ckpt_contents"])
192 .inc_by(num_contents);
193 }
194
195 res
196 }
197
198 pub async fn multi_get_checkpoints_summaries(
199 &self,
200 keys: &[CheckpointSequenceNumber],
201 ) -> IotaResult<Vec<Option<CertifiedCheckpointSummary>>> {
202 self.multi_get_checkpoints(keys, &[], &[])
203 .await
204 .map(|(summaries, _, _)| summaries)
205 }
206
207 pub async fn multi_get_checkpoints_contents(
208 &self,
209 keys: &[CheckpointSequenceNumber],
210 ) -> IotaResult<Vec<Option<CheckpointContents>>> {
211 self.multi_get_checkpoints(&[], keys, &[])
212 .await
213 .map(|(_, contents, _)| contents)
214 }
215
216 pub async fn multi_get_checkpoints_summaries_by_digest(
217 &self,
218 keys: &[CheckpointDigest],
219 ) -> IotaResult<Vec<Option<CertifiedCheckpointSummary>>> {
220 self.multi_get_checkpoints(&[], &[], keys)
221 .await
222 .map(|(_, _, summaries)| summaries)
223 }
224
225 pub async fn multi_get_tx(
226 &self,
227 keys: &[TransactionDigest],
228 ) -> IotaResult<Vec<Option<TransactionEnvelope>>> {
229 self.multi_get(keys, &[]).await.map(|(txns, _)| txns)
230 }
231
232 pub async fn multi_get_fx_by_tx_digest(
233 &self,
234 keys: &[TransactionDigest],
235 ) -> IotaResult<Vec<Option<TransactionEffects>>> {
236 self.multi_get(&[], keys).await.map(|(_, fx)| fx)
237 }
238
239 pub async fn get_tx(&self, digest: TransactionDigest) -> IotaResult<TransactionEnvelope> {
242 self.multi_get_tx(&[digest])
243 .await?
244 .into_iter()
245 .next()
246 .flatten()
247 .ok_or(IotaError::TransactionNotFound { digest })
248 }
249
250 pub async fn get_fx_by_tx_digest(
254 &self,
255 digest: TransactionDigest,
256 ) -> IotaResult<TransactionEffects> {
257 self.multi_get_fx_by_tx_digest(&[digest])
258 .await?
259 .into_iter()
260 .next()
261 .flatten()
262 .ok_or(IotaError::TransactionNotFound { digest })
263 }
264
265 pub async fn get_checkpoint_summary(
269 &self,
270 checkpoint: CheckpointSequenceNumber,
271 ) -> IotaResult<CertifiedCheckpointSummary> {
272 self.multi_get_checkpoints_summaries(&[checkpoint])
273 .await?
274 .into_iter()
275 .next()
276 .flatten()
277 .ok_or(IotaError::UserInput {
278 error: UserInputError::VerifiedCheckpointNotFound(checkpoint),
279 })
280 }
281
282 pub async fn get_checkpoint_contents(
286 &self,
287 checkpoint: CheckpointSequenceNumber,
288 ) -> IotaResult<CheckpointContents> {
289 self.multi_get_checkpoints_contents(&[checkpoint])
290 .await?
291 .into_iter()
292 .next()
293 .flatten()
294 .ok_or(IotaError::UserInput {
295 error: UserInputError::VerifiedCheckpointNotFound(checkpoint),
296 })
297 }
298
299 pub async fn get_checkpoint_summary_by_digest(
303 &self,
304 digest: CheckpointDigest,
305 ) -> IotaResult<CertifiedCheckpointSummary> {
306 self.multi_get_checkpoints_summaries_by_digest(&[digest])
307 .await?
308 .into_iter()
309 .next()
310 .flatten()
311 .ok_or(IotaError::UserInput {
312 error: UserInputError::VerifiedCheckpointDigestNotFound(format!("{digest}")),
313 })
314 }
315
316 pub async fn get_transaction_perpetual_checkpoint(
317 &self,
318 digest: TransactionDigest,
319 ) -> IotaResult<Option<CheckpointSequenceNumber>> {
320 self.inner
321 .get_transaction_perpetual_checkpoint(digest)
322 .await
323 }
324
325 pub async fn get_object(
326 &self,
327 object_id: ObjectId,
328 version: VersionNumber,
329 ) -> IotaResult<Option<Object>> {
330 self.inner.get_object(object_id, version).await
331 }
332
333 pub async fn multi_get_objects(
334 &self,
335 object_keys: &[ObjectKey],
336 ) -> IotaResult<Vec<Option<Object>>> {
337 self.inner.multi_get_objects(object_keys).await
338 }
339
340 pub async fn multi_get_transactions_perpetual_checkpoints(
341 &self,
342 digests: &[TransactionDigest],
343 ) -> IotaResult<Vec<Option<CheckpointSequenceNumber>>> {
344 self.inner
345 .multi_get_transactions_perpetual_checkpoints(digests)
346 .await
347 }
348
349 pub async fn multi_get_events_by_tx_digests(
350 &self,
351 digests: &[TransactionDigest],
352 ) -> IotaResult<Vec<Option<TransactionEvents>>> {
353 self.inner.multi_get_events_by_tx_digests(digests).await
354 }
355}
356
357#[async_trait]
360impl TransactionKeyValueStoreTrait for TransactionKeyValueStore {
361 async fn multi_get(
362 &self,
363 transaction_keys: &[TransactionDigest],
364 effects_keys: &[TransactionDigest],
365 ) -> IotaResult<KVStoreTransactionData> {
366 Self::multi_get(self, transaction_keys, effects_keys).await
367 }
368
369 async fn multi_get_checkpoints(
370 &self,
371 checkpoint_summaries: &[CheckpointSequenceNumber],
372 checkpoint_contents: &[CheckpointSequenceNumber],
373 checkpoint_summaries_by_digest: &[CheckpointDigest],
374 ) -> IotaResult<KVStoreCheckpointData> {
375 Self::multi_get_checkpoints(
376 self,
377 checkpoint_summaries,
378 checkpoint_contents,
379 checkpoint_summaries_by_digest,
380 )
381 .await
382 }
383
384 async fn get_transaction_perpetual_checkpoint(
385 &self,
386 digest: TransactionDigest,
387 ) -> IotaResult<Option<CheckpointSequenceNumber>> {
388 self.inner
389 .get_transaction_perpetual_checkpoint(digest)
390 .await
391 }
392
393 async fn get_object(
394 &self,
395 object_id: ObjectId,
396 version: Version,
397 ) -> IotaResult<Option<Object>> {
398 self.inner.get_object(object_id, version).await
399 }
400
401 async fn multi_get_objects(
402 &self,
403 object_keys: &[ObjectKey],
404 ) -> IotaResult<Vec<Option<Object>>> {
405 self.inner.multi_get_objects(object_keys).await
406 }
407
408 async fn multi_get_transactions_perpetual_checkpoints(
409 &self,
410 digests: &[TransactionDigest],
411 ) -> IotaResult<Vec<Option<CheckpointSequenceNumber>>> {
412 self.inner
413 .multi_get_transactions_perpetual_checkpoints(digests)
414 .await
415 }
416
417 async fn multi_get_events_by_tx_digests(
418 &self,
419 digests: &[TransactionDigest],
420 ) -> IotaResult<Vec<Option<TransactionEvents>>> {
421 self.inner.multi_get_events_by_tx_digests(digests).await
422 }
423 async fn evict_objects(&self, object_keys: &[ObjectKey]) {
424 self.inner.evict_objects(object_keys).await
425 }
426
427 async fn evict_events_by_tx_digests(&self, digests: &[TransactionDigest]) {
428 self.inner.evict_events_by_tx_digests(digests).await
429 }
430}
431
432pub struct FallbackTransactionKVStore {
438 primary: TransactionKeyValueStore,
439 fallback: TransactionKeyValueStore,
440}
441
442impl FallbackTransactionKVStore {
443 pub fn new_kv(
444 primary: TransactionKeyValueStore,
445 fallback: TransactionKeyValueStore,
446 metrics: Arc<KeyValueStoreMetrics>,
447 label: &'static str,
448 ) -> TransactionKeyValueStore {
449 let store = Arc::new(Self { primary, fallback });
450 TransactionKeyValueStore::new(label, metrics, store)
451 }
452}
453
454#[async_trait]
455impl TransactionKeyValueStoreTrait for FallbackTransactionKVStore {
456 #[instrument(level = "trace", skip_all)]
457 async fn multi_get(
458 &self,
459 transaction_keys: &[TransactionDigest],
460 effects_keys: &[TransactionDigest],
461 ) -> IotaResult<KVStoreTransactionData> {
462 let (mut transactions, mut effects) = self
463 .primary
464 .multi_get(transaction_keys, effects_keys)
465 .await?;
466
467 let (fallback_transaction_keys, indices_transactions) =
468 find_fallback(&transactions, transaction_keys);
469 let (fallback_effects_keys, indices_effects) = find_fallback(&effects, effects_keys);
470
471 if fallback_transaction_keys.is_empty() && fallback_effects_keys.is_empty() {
472 return Ok((transactions, effects));
473 }
474
475 let (fallback_transactions, fallback_effects) = self
476 .fallback
477 .multi_get(&fallback_transaction_keys, &fallback_effects_keys)
478 .await?;
479
480 merge_res(
481 &mut transactions,
482 fallback_transactions,
483 &indices_transactions,
484 );
485 merge_res(&mut effects, fallback_effects, &indices_effects);
486
487 Ok((transactions, effects))
488 }
489
490 #[instrument(level = "trace", skip_all)]
491 async fn multi_get_checkpoints(
492 &self,
493 checkpoint_summaries: &[CheckpointSequenceNumber],
494 checkpoint_contents: &[CheckpointSequenceNumber],
495 checkpoint_summaries_by_digest: &[CheckpointDigest],
496 ) -> IotaResult<(
497 Vec<Option<CertifiedCheckpointSummary>>,
498 Vec<Option<CheckpointContents>>,
499 Vec<Option<CertifiedCheckpointSummary>>,
500 )> {
501 let (mut summaries, mut contents, mut summaries_by_digest) = self
502 .primary
503 .multi_get_checkpoints(
504 checkpoint_summaries,
505 checkpoint_contents,
506 checkpoint_summaries_by_digest,
507 )
508 .await?;
509
510 let (fallback_summaries, indices_summaries) =
511 find_fallback(&summaries, checkpoint_summaries);
512 let (fallback_contents, indices_contents) = find_fallback(&contents, checkpoint_contents);
513 let (fallback_summaries_by_digest, indices_summaries_by_digest) =
514 find_fallback(&summaries_by_digest, checkpoint_summaries_by_digest);
515
516 if fallback_summaries.is_empty()
517 && fallback_contents.is_empty()
518 && fallback_summaries_by_digest.is_empty()
519 {
520 return Ok((summaries, contents, summaries_by_digest));
521 }
522
523 let (fallback_summaries, fallback_contents, fallback_summaries_by_digest) = self
524 .fallback
525 .multi_get_checkpoints(
526 &fallback_summaries,
527 &fallback_contents,
528 &fallback_summaries_by_digest,
529 )
530 .await?;
531
532 merge_res(&mut summaries, fallback_summaries, &indices_summaries);
533 merge_res(&mut contents, fallback_contents, &indices_contents);
534 merge_res(
535 &mut summaries_by_digest,
536 fallback_summaries_by_digest,
537 &indices_summaries_by_digest,
538 );
539
540 Ok((summaries, contents, summaries_by_digest))
541 }
542
543 #[instrument(level = "trace", skip_all)]
544 async fn get_transaction_perpetual_checkpoint(
545 &self,
546 digest: TransactionDigest,
547 ) -> IotaResult<Option<CheckpointSequenceNumber>> {
548 let mut res = self
549 .primary
550 .get_transaction_perpetual_checkpoint(digest)
551 .await?;
552 if res.is_none() {
553 res = self
554 .fallback
555 .get_transaction_perpetual_checkpoint(digest)
556 .await?;
557 }
558 Ok(res)
559 }
560
561 #[instrument(level = "trace", skip_all)]
562 async fn get_object(
563 &self,
564 object_id: ObjectId,
565 version: Version,
566 ) -> IotaResult<Option<Object>> {
567 let mut res = self.primary.get_object(object_id, version).await?;
568 if res.is_none() {
569 res = self.fallback.get_object(object_id, version).await?;
570 }
571 Ok(res)
572 }
573
574 #[instrument(level = "trace", skip_all)]
575 async fn multi_get_objects(
576 &self,
577 object_keys: &[ObjectKey],
578 ) -> IotaResult<Vec<Option<Object>>> {
579 let mut res = self.primary.multi_get_objects(object_keys).await?;
580
581 let (fallback, indices) = find_fallback(&res, object_keys);
582
583 if fallback.is_empty() {
584 return Ok(res);
585 }
586
587 let secondary_res = self.fallback.multi_get_objects(&fallback).await?;
588
589 merge_res(&mut res, secondary_res, &indices);
590
591 Ok(res)
592 }
593
594 #[instrument(level = "trace", skip_all)]
595 async fn multi_get_transactions_perpetual_checkpoints(
596 &self,
597 digests: &[TransactionDigest],
598 ) -> IotaResult<Vec<Option<CheckpointSequenceNumber>>> {
599 let mut res = self
600 .primary
601 .multi_get_transactions_perpetual_checkpoints(digests)
602 .await?;
603
604 let (fallback, indices) = find_fallback(&res, digests);
605
606 if fallback.is_empty() {
607 return Ok(res);
608 }
609
610 let secondary_res = self
611 .fallback
612 .multi_get_transactions_perpetual_checkpoints(&fallback)
613 .await?;
614
615 merge_res(&mut res, secondary_res, &indices);
616
617 Ok(res)
618 }
619
620 #[instrument(level = "trace", skip_all)]
621 async fn multi_get_events_by_tx_digests(
622 &self,
623 digests: &[TransactionDigest],
624 ) -> IotaResult<Vec<Option<TransactionEvents>>> {
625 let mut res = self.primary.multi_get_events_by_tx_digests(digests).await?;
626 let (fallback, indices) = find_fallback(&res, digests);
627 if fallback.is_empty() {
628 return Ok(res);
629 }
630 let secondary_res = self
631 .fallback
632 .multi_get_events_by_tx_digests(&fallback)
633 .await?;
634 merge_res(&mut res, secondary_res, &indices);
635 Ok(res)
636 }
637
638 async fn evict_objects(&self, object_keys: &[ObjectKey]) {
639 self.primary.evict_objects(object_keys).await;
640 self.fallback.evict_objects(object_keys).await;
641 }
642
643 async fn evict_events_by_tx_digests(&self, digests: &[TransactionDigest]) {
644 self.primary.evict_events_by_tx_digests(digests).await;
645 self.fallback.evict_events_by_tx_digests(digests).await;
646 }
647}
648
649fn find_fallback<T, K: Clone>(values: &[Option<T>], keys: &[K]) -> (Vec<K>, Vec<usize>) {
650 let num_nones = values.iter().filter(|v| v.is_none()).count();
651 let mut fallback_keys = Vec::with_capacity(num_nones);
652 let mut fallback_indices = Vec::with_capacity(num_nones);
653 for (i, value) in values.iter().enumerate() {
654 if value.is_none() {
655 fallback_keys.push(keys[i].clone());
656 fallback_indices.push(i);
657 }
658 }
659 (fallback_keys, fallback_indices)
660}
661
662fn merge_res<T>(values: &mut [Option<T>], fallback_values: Vec<Option<T>>, indices: &[usize]) {
663 for (&index, fallback_value) in indices.iter().zip(fallback_values) {
664 values[index] = fallback_value;
665 }
666}