Skip to main content

sui_core/accumulators/
unsettled_object_withdrawals.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::{
5    collections::{BTreeMap, BTreeSet},
6    sync::Arc,
7};
8
9use parking_lot::RwLock;
10use sui_types::{accumulator_root::AccumulatorObjId, base_types::SequenceNumber};
11
12use crate::accumulators::object_funds_checker::metrics::ObjectFundsCheckerMetrics;
13
14pub struct UnsettledObjectWithdrawals {
15    inner: RwLock<Inner>,
16    metrics: Arc<ObjectFundsCheckerMetrics>,
17}
18
19#[derive(Default)]
20struct Inner {
21    /// Tracks the amount of pending unsettled withdraws for each account at each accumulator version.
22    /// When we check object funds sufficiency, we read the balance bounded by the withdraw accumulator version.
23    /// Balance are updated only by settlement transactions, not when we withdraw funds.
24    /// Hence when we are checking object funds, on top of the settled balance, we also need to account for
25    /// the amount of withdraws from the same consensus commit (that all reads from the same accumulator version).
26    unsettled_withdraws: BTreeMap<AccumulatorObjId, BTreeMap<SequenceNumber, u128>>,
27    /// Tracks the accounts that have pending withdraws at each accumulator version.
28    /// This information is not required for functional correctness, but needed to garbage collect
29    /// unused entries in unsettled_withdraws that are now fully committed. Without doing so unsettled_withdraws
30    /// may grow unbounded.
31    unsettled_accounts: BTreeMap<SequenceNumber, BTreeSet<AccumulatorObjId>>,
32}
33
34impl UnsettledObjectWithdrawals {
35    pub fn new(metrics: Arc<ObjectFundsCheckerMetrics>) -> Self {
36        Self {
37            inner: RwLock::new(Inner::default()),
38            metrics,
39        }
40    }
41
42    /// Get the current unsettled withdraw amount for an account at a given accumulator version.
43    pub(crate) fn get_unsettled_object_withdraw(
44        &self,
45        account: &AccumulatorObjId,
46        accumulator_version: SequenceNumber,
47    ) -> u128 {
48        self.inner
49            .read()
50            .unsettled_withdraws
51            .get(account)
52            .and_then(|withdraws| withdraws.get(&accumulator_version))
53            .copied()
54            .unwrap_or_default()
55    }
56
57    /// Record a new unsettled withdraw for an account at a given accumulator version.
58    /// This updates the unsettled withdraws map, and is called after a transaction successfully executed
59    /// that withdraws funds from an object balance account.
60    pub(crate) fn record_unsettled_withdraws<'a>(
61        &self,
62        withdraws: impl Iterator<Item = (&'a AccumulatorObjId, &'a u128)>,
63        accumulator_version: SequenceNumber,
64    ) {
65        let mut inner = self.inner.write();
66        for (account, amount) in withdraws {
67            let entry = inner
68                .unsettled_withdraws
69                .entry(*account)
70                .or_default()
71                .entry(accumulator_version)
72                .or_default();
73            *entry = entry.checked_add(*amount).unwrap();
74            inner
75                .unsettled_accounts
76                .entry(accumulator_version)
77                .or_default()
78                .insert(*account);
79        }
80        self.update_unsettled_metrics(&inner);
81    }
82
83    fn update_unsettled_metrics(&self, inner: &Inner) {
84        self.metrics
85            .unsettled_accounts
86            .set(inner.unsettled_withdraws.len() as i64);
87        self.metrics
88            .unsettled_versions
89            .set(inner.unsettled_accounts.len() as i64);
90    }
91
92    /// Garbage collect tracking of unsettled withdraws for committed accumulator versions.
93    /// This isn't required for functional correctness, but ensures that the tracking data structure doesn't grow unbounded.
94    pub fn commit_accumulator_versions(&self, committed_accumulator_versions: Vec<SequenceNumber>) {
95        let mut inner = self.inner.write();
96        for accumulator_version in committed_accumulator_versions {
97            let accounts = inner
98                .unsettled_accounts
99                .remove(&accumulator_version)
100                .unwrap_or_default();
101            for account in accounts {
102                if let Some(withdraws) = inner.unsettled_withdraws.get_mut(&account) {
103                    withdraws.remove(&accumulator_version);
104                    if withdraws.is_empty() {
105                        inner.unsettled_withdraws.remove(&account);
106                    }
107                }
108            }
109        }
110        self.update_unsettled_metrics(&inner);
111    }
112
113    #[cfg(test)]
114    pub fn is_empty(&self) -> bool {
115        let inner = self.inner.read();
116        inner.unsettled_withdraws.is_empty() && inner.unsettled_accounts.is_empty()
117    }
118}