sui_core/accumulators/
coin_reservations.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::sync::Arc;
5
6use moka::sync::Cache as MokaCache;
7use move_core_types::language_storage::TypeTag;
8use sui_types::{
9    base_types::{ObjectID, SequenceNumber, SuiAddress},
10    coin_reservation::{
11        CoinReservationResolver, CoinReservationResolverTrait, ParsedObjectRefWithdrawal,
12    },
13    error::{UserInputError, UserInputResult},
14    storage::ChildObjectResolver,
15    transaction::FundsWithdrawalArg,
16};
17
18/// A caching wrapper around `CoinReservationResolver` that caches the lookup
19/// of (owner, type_tag) for each accumulator object ID.
20pub struct CachingCoinReservationResolver {
21    inner: CoinReservationResolver,
22    cache: MokaCache<ObjectID, Result<(SuiAddress, TypeTag), UserInputError>>,
23}
24
25impl CachingCoinReservationResolver {
26    pub fn new(child_object_resolver: Arc<dyn ChildObjectResolver + Send + Sync>) -> Self {
27        Self {
28            inner: CoinReservationResolver::new(child_object_resolver),
29            cache: MokaCache::builder().max_capacity(1000).build(),
30        }
31    }
32
33    fn get_owner_and_type_cached(
34        &self,
35        object_id: ObjectID,
36        accumulator_version: Option<SequenceNumber>,
37    ) -> UserInputResult<(SuiAddress, TypeTag)> {
38        // Owner and type_tag never change, so the cache is always coherent.
39        // On cache miss, use MVCC to read at the specified version.
40        self.cache.get_with(object_id, || {
41            self.inner
42                .get_owner_and_type_for_object(object_id, accumulator_version)
43        })
44    }
45
46    pub fn resolve_funds_withdrawal(
47        &self,
48        sender: SuiAddress,
49        coin_reservation: ParsedObjectRefWithdrawal,
50        accumulator_version: Option<SequenceNumber>,
51    ) -> UserInputResult<FundsWithdrawalArg> {
52        let (owner, type_tag) = self
53            .get_owner_and_type_cached(coin_reservation.unmasked_object_id, accumulator_version)?;
54
55        if sender != owner {
56            return Err(UserInputError::InvalidWithdrawReservation {
57                error: format!(
58                    "coin reservation object id {} is owned by {}, not sender {}",
59                    coin_reservation.unmasked_object_id, owner, sender
60                ),
61            });
62        }
63
64        Ok(FundsWithdrawalArg::balance_from_sender(
65            coin_reservation.reservation_amount(),
66            type_tag,
67        ))
68    }
69}
70
71impl CoinReservationResolverTrait for CachingCoinReservationResolver {
72    fn resolve_funds_withdrawal(
73        &self,
74        sender: SuiAddress,
75        coin_reservation: ParsedObjectRefWithdrawal,
76        accumulator_version: Option<SequenceNumber>,
77    ) -> UserInputResult<FundsWithdrawalArg> {
78        CachingCoinReservationResolver::resolve_funds_withdrawal(
79            self,
80            sender,
81            coin_reservation,
82            accumulator_version,
83        )
84    }
85}
86
87impl CoinReservationResolverTrait for &'_ CachingCoinReservationResolver {
88    fn resolve_funds_withdrawal(
89        &self,
90        sender: SuiAddress,
91        coin_reservation: ParsedObjectRefWithdrawal,
92        accumulator_version: Option<SequenceNumber>,
93    ) -> UserInputResult<FundsWithdrawalArg> {
94        CachingCoinReservationResolver::resolve_funds_withdrawal(
95            self,
96            sender,
97            coin_reservation,
98            accumulator_version,
99        )
100    }
101}