sui_core/accumulators/
unsettled_object_withdrawals.rs1use std::{
5 collections::{BTreeMap, BTreeSet},
6 sync::Arc,
7};
8
9use parking_lot::RwLock;
10use sui_protocol_config::assert_reachable_gated;
11use sui_types::{
12 accumulator_root::{AccumulatorObjId, UnsettledObjectFundsRead},
13 base_types::SequenceNumber,
14 digests::ChainIdentifier,
15 effects::{TransactionEffects, TransactionEffectsAPI},
16 transaction::{TransactionData, TransactionDataAPI},
17};
18
19use crate::accumulators::object_funds_checker::metrics::ObjectFundsCheckerMetrics;
20
21pub struct UnsettledObjectWithdrawals {
22 inner: RwLock<Inner>,
23 metrics: Arc<ObjectFundsCheckerMetrics>,
24}
25
26#[derive(Default)]
27struct Inner {
28 unsettled_withdraws: BTreeMap<AccumulatorObjId, BTreeMap<SequenceNumber, u128>>,
37 unsettled_accounts: BTreeMap<SequenceNumber, BTreeSet<AccumulatorObjId>>,
42}
43
44impl UnsettledObjectFundsRead for UnsettledObjectWithdrawals {
45 fn get_unsettled_object_withdraw(
46 &self,
47 account: &AccumulatorObjId,
48 accumulator_version: SequenceNumber,
49 ) -> u128 {
50 UnsettledObjectWithdrawals::get_unsettled_object_withdraw(
51 self,
52 account,
53 accumulator_version,
54 )
55 }
56}
57
58impl UnsettledObjectWithdrawals {
59 pub fn new(metrics: Arc<ObjectFundsCheckerMetrics>) -> Self {
60 Self {
61 inner: RwLock::new(Inner::default()),
62 metrics,
63 }
64 }
65
66 pub(crate) fn get_unsettled_object_withdraw(
68 &self,
69 account: &AccumulatorObjId,
70 accumulator_version: SequenceNumber,
71 ) -> u128 {
72 self.inner
73 .read()
74 .unsettled_withdraws
75 .get(account)
76 .and_then(|withdraws| withdraws.get(&accumulator_version))
77 .copied()
78 .unwrap_or_default()
79 }
80
81 pub(crate) fn record_unsettled_withdraws(
85 &self,
86 withdraws: impl IntoIterator<Item = (AccumulatorObjId, u128)>,
87 accumulator_version: SequenceNumber,
88 ) {
89 let mut inner = self.inner.write();
90 for (account, amount) in withdraws {
91 let entry = inner
92 .unsettled_withdraws
93 .entry(account)
94 .or_default()
95 .entry(accumulator_version)
96 .or_default();
97 *entry = entry.checked_add(amount).unwrap();
98 inner
99 .unsettled_accounts
100 .entry(accumulator_version)
101 .or_default()
102 .insert(account);
103 }
104 self.update_unsettled_metrics(&inner);
105 }
106
107 pub fn record_object_funds_withdraws(
108 &self,
109 tx_data: &TransactionData,
110 effects: &TransactionEffects,
111 accumulator_running_max_withdraws: &BTreeMap<AccumulatorObjId, u128>,
112 accumulator_version: SequenceNumber,
113 chain_identifier: ChainIdentifier,
114 ) {
115 if accumulator_running_max_withdraws.is_empty() {
116 return;
117 }
118 let address_funds_reservations: BTreeSet<_> = tx_data
119 .process_funds_withdrawals_for_execution(chain_identifier)
120 .into_keys()
121 .collect();
122 let updates: Vec<_> = effects
123 .accumulator_events()
124 .into_iter()
125 .filter(|event| !address_funds_reservations.contains(&event.accumulator_obj))
126 .filter_map(|event| {
127 event
128 .write
129 .get_fund_withdraw_amount()
130 .filter(|amount| *amount > 0)
131 .map(|amount| (event.accumulator_obj, amount))
132 })
133 .collect();
134 if updates.is_empty() {
135 return;
136 }
137 debug_assert!(
138 updates.iter().all(|(obj_id, net)| {
139 accumulator_running_max_withdraws
140 .get(obj_id)
141 .is_some_and(|max| net <= max)
142 }),
143 "net withdraw exceeds running max: tx={:?} updates={:?} running_max={:?}",
144 tx_data.digest(),
145 updates,
146 accumulator_running_max_withdraws,
147 );
148 self.record_unsettled_withdraws(updates, accumulator_version);
149 assert_reachable_gated!(
150 "record unsettled object withdraws from in-execution check",
151 |pc| pc.check_object_funds_withdraw_in_execution()
152 );
153 self.metrics
154 .in_execution_check_result
155 .with_label_values(&["sufficient"])
156 .inc();
157 }
158
159 fn update_unsettled_metrics(&self, inner: &Inner) {
160 self.metrics
161 .unsettled_accounts
162 .set(inner.unsettled_withdraws.len() as i64);
163 self.metrics
164 .unsettled_versions
165 .set(inner.unsettled_accounts.len() as i64);
166 }
167
168 pub fn commit_accumulator_versions(&self, committed_accumulator_versions: Vec<SequenceNumber>) {
171 let mut inner = self.inner.write();
172 for accumulator_version in committed_accumulator_versions {
173 let accounts = inner
174 .unsettled_accounts
175 .remove(&accumulator_version)
176 .unwrap_or_default();
177 for account in accounts {
178 if let Some(withdraws) = inner.unsettled_withdraws.get_mut(&account) {
179 withdraws.remove(&accumulator_version);
180 if withdraws.is_empty() {
181 inner.unsettled_withdraws.remove(&account);
182 }
183 }
184 }
185 }
186 self.update_unsettled_metrics(&inner);
187 }
188
189 #[cfg(test)]
190 pub fn is_empty(&self) -> bool {
191 let inner = self.inner.read();
192 inner.unsettled_withdraws.is_empty() && inner.unsettled_accounts.is_empty()
193 }
194}