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, UserInputResult<(SuiAddress, TypeTag)>>,
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 once the object exists, so successful
39        // lookups are always coherent. Errors other than "not found" (e.g. the
40        // object exists but is not a balance accumulator field) are also
41        // permanent for a given object_id and so safe to cache.
42        //
43        // Don't cache "not found": the accumulator object may not exist on this
44        // node yet but will appear once the settlement transaction executes.
45        // Caching a transient not-found would poison the cache for all
46        // subsequent lookups of that id.
47        if let Some(cached) = self.cache.get(&object_id) {
48            return cached;
49        }
50        match self
51            .inner
52            .get_owner_and_type_for_object(object_id, accumulator_version)
53        {
54            Ok(Some(value)) => {
55                self.cache.insert(object_id, Ok(value.clone()));
56                Ok(value)
57            }
58            Ok(None) => Err(UserInputError::InvalidWithdrawReservation {
59                error: format!("coin reservation object id {} not found", object_id),
60            }),
61            Err(e) => {
62                self.cache.insert(object_id, Err(e.clone()));
63                Err(e)
64            }
65        }
66    }
67
68    pub fn resolve_funds_withdrawal(
69        &self,
70        sender: SuiAddress,
71        coin_reservation: ParsedObjectRefWithdrawal,
72        accumulator_version: Option<SequenceNumber>,
73    ) -> UserInputResult<FundsWithdrawalArg> {
74        let (owner, type_tag) = self
75            .get_owner_and_type_cached(coin_reservation.unmasked_object_id, accumulator_version)?;
76
77        if sender != owner {
78            return Err(UserInputError::InvalidWithdrawReservation {
79                error: format!(
80                    "coin reservation object id {} is owned by {}, not sender {}",
81                    coin_reservation.unmasked_object_id, owner, sender
82                ),
83            });
84        }
85
86        Ok(FundsWithdrawalArg::balance_from_sender(
87            coin_reservation.reservation_amount(),
88            type_tag,
89        ))
90    }
91}
92
93impl CoinReservationResolverTrait for CachingCoinReservationResolver {
94    fn resolve_funds_withdrawal(
95        &self,
96        sender: SuiAddress,
97        coin_reservation: ParsedObjectRefWithdrawal,
98        accumulator_version: Option<SequenceNumber>,
99    ) -> UserInputResult<FundsWithdrawalArg> {
100        CachingCoinReservationResolver::resolve_funds_withdrawal(
101            self,
102            sender,
103            coin_reservation,
104            accumulator_version,
105        )
106    }
107}
108
109impl CoinReservationResolverTrait for &'_ CachingCoinReservationResolver {
110    fn resolve_funds_withdrawal(
111        &self,
112        sender: SuiAddress,
113        coin_reservation: ParsedObjectRefWithdrawal,
114        accumulator_version: Option<SequenceNumber>,
115    ) -> UserInputResult<FundsWithdrawalArg> {
116        CachingCoinReservationResolver::resolve_funds_withdrawal(
117            self,
118            sender,
119            coin_reservation,
120            accumulator_version,
121        )
122    }
123}