sui_core/accumulators/
mod.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::collections::{BTreeMap, HashMap};
5
6use itertools::Itertools;
7use move_core_types::u256::U256;
8use mysten_common::fatal;
9use sui_protocol_config::ProtocolConfig;
10use sui_types::accumulator_event::AccumulatorEvent;
11use sui_types::accumulator_root::{
12    ACCUMULATOR_ROOT_SETTLE_U128_FUNC, ACCUMULATOR_ROOT_SETTLEMENT_PROLOGUE_FUNC,
13    ACCUMULATOR_SETTLEMENT_MODULE, AccumulatorObjId, EventCommitment, build_event_merkle_root,
14};
15use sui_types::balance::{BALANCE_MODULE_NAME, BALANCE_STRUCT_NAME};
16use sui_types::base_types::SequenceNumber;
17
18use sui_types::digests::Digest;
19use sui_types::effects::{
20    AccumulatorAddress, AccumulatorOperation, AccumulatorValue, AccumulatorWriteV1,
21    TransactionEffects, TransactionEffectsAPI,
22};
23use sui_types::programmable_transaction_builder::ProgrammableTransactionBuilder;
24use sui_types::transaction::{
25    Argument, CallArg, ObjectArg, SharedObjectMutability, TransactionKind,
26};
27use sui_types::{
28    SUI_ACCUMULATOR_ROOT_OBJECT_ID, SUI_FRAMEWORK_ADDRESS, SUI_FRAMEWORK_PACKAGE_ID, TypeTag,
29};
30
31use crate::execution_cache::TransactionCacheRead;
32
33// provides balance read functionality for the scheduler
34pub mod funds_read;
35// provides balance read functionality for RPC
36pub mod balances;
37pub mod coin_reservations;
38
39/// Merged value is the value stored inside accumulator objects.
40/// Each mergeable Move type will map to a single variant as its representation.
41///
42/// For instance, Balance<T> stores a single u64 value, so it will map to SumU128.
43/// A clawback Balance<T> will map to SumU128U128 since it also needs to represent
44/// the amount of the balance that has been frozen.
45#[derive(Debug, Copy, Clone)]
46enum MergedValue {
47    SumU128(u128),
48    SumU128U128(u128, u128),
49    /// Merkle root of events in this checkpoint and event count.
50    EventDigest(/* merkle root */ Digest, /* event count */ u64),
51}
52
53enum ClassifiedType {
54    Balance,
55    Unknown,
56}
57
58impl ClassifiedType {
59    fn classify(ty: &TypeTag) -> Self {
60        let TypeTag::Struct(struct_tag) = ty else {
61            return Self::Unknown;
62        };
63
64        if struct_tag.address == SUI_FRAMEWORK_ADDRESS
65            && struct_tag.module.as_ident_str() == BALANCE_MODULE_NAME
66            && struct_tag.name.as_ident_str() == BALANCE_STRUCT_NAME
67        {
68            return Self::Balance;
69        }
70
71        Self::Unknown
72    }
73}
74
75impl MergedValue {
76    fn add_move_call(
77        merge: Self,
78        split: Self,
79        root: Argument,
80        address: &AccumulatorAddress,
81        checkpoint_seq: u64,
82        builder: &mut ProgrammableTransactionBuilder,
83    ) {
84        let ty = ClassifiedType::classify(&address.ty);
85        let address_arg = builder.pure(address.address).unwrap();
86
87        match (ty, merge, split) {
88            (
89                ClassifiedType::Balance,
90                MergedValue::SumU128(merge_amount),
91                MergedValue::SumU128(split_amount),
92            ) => {
93                // Net out the merge and split amounts.
94                let (merge_amount, split_amount) = if merge_amount >= split_amount {
95                    (merge_amount - split_amount, 0)
96                } else {
97                    (0, split_amount - merge_amount)
98                };
99
100                if merge_amount != 0 || split_amount != 0 {
101                    let merge_amount = builder.pure(merge_amount).unwrap();
102                    let split_amount = builder.pure(split_amount).unwrap();
103                    builder.programmable_move_call(
104                        SUI_FRAMEWORK_PACKAGE_ID,
105                        ACCUMULATOR_SETTLEMENT_MODULE.into(),
106                        ACCUMULATOR_ROOT_SETTLE_U128_FUNC.into(),
107                        vec![address.ty.clone()],
108                        vec![root, address_arg, merge_amount, split_amount],
109                    );
110                }
111            }
112            (_, MergedValue::SumU128U128(_v1, _v2), MergedValue::SumU128U128(_w1, _w2)) => todo!(),
113            (_, MergedValue::EventDigest(digest, event_count), MergedValue::EventDigest(_, _)) => {
114                let args = vec![
115                    root,
116                    builder.pure(address.address).unwrap(),
117                    builder
118                        .pure(U256::from_le_bytes(&digest.into_inner()))
119                        .unwrap(),
120                    builder.pure(event_count).unwrap(),
121                    builder.pure(checkpoint_seq).unwrap(),
122                ];
123                builder.programmable_move_call(
124                    SUI_FRAMEWORK_PACKAGE_ID,
125                    ACCUMULATOR_SETTLEMENT_MODULE.into(),
126                    sui_types::accumulator_root::ACCUMULATOR_ROOT_SETTLEMENT_SETTLE_EVENTS_FUNC
127                        .into(),
128                    vec![],
129                    args,
130                );
131            }
132            _ => fatal!("invalid merge {:?} {:?}", merge, split),
133        }
134    }
135}
136
137impl From<MergedValueIntermediate> for MergedValue {
138    fn from(value: MergedValueIntermediate) -> Self {
139        match value {
140            MergedValueIntermediate::SumU128(v) => MergedValue::SumU128(v),
141            MergedValueIntermediate::SumU128U128(v1, v2) => MergedValue::SumU128U128(v1, v2),
142            MergedValueIntermediate::Events(events) => {
143                let event_count = events.len() as u64;
144                MergedValue::EventDigest(build_event_merkle_root(&events), event_count)
145            }
146        }
147    }
148}
149
150/// MergedValueIntermediate is an intermediate / in-memory representation of the for
151/// accumulators. It is used to store the merged result of all accumulator writes in a single
152/// checkpoint.
153///
154/// This pattern is not necessary for fully commutative operations, since those could use MergedValue directly.
155///
156/// However, this supports the commutative-merge + non-commutative-update pattern, which will be used by event
157/// streams. In this pattern, everything within a checkpoint is merged commutatively, and then a single
158/// non-commutative update is applied to the accumulator at the end of the checkpoint.
159#[derive(Debug, Clone)]
160enum MergedValueIntermediate {
161    SumU128(u128),
162    SumU128U128(u128, u128),
163    Events(Vec<EventCommitment>),
164}
165
166impl MergedValueIntermediate {
167    // Create a zero value with the appropriate type for the accumulator value.
168    fn zero(value: &AccumulatorValue) -> Self {
169        match value {
170            AccumulatorValue::Integer(_) => Self::SumU128(0),
171            AccumulatorValue::IntegerTuple(_, _) => Self::SumU128U128(0, 0),
172            AccumulatorValue::EventDigest(_) => Self::Events(vec![]),
173        }
174    }
175
176    fn accumulate_into(
177        &mut self,
178        value: AccumulatorValue,
179        checkpoint_seq: u64,
180        transaction_idx: u64,
181    ) {
182        match (self, value) {
183            (Self::SumU128(v1), AccumulatorValue::Integer(v2)) => *v1 += v2 as u128,
184            (Self::SumU128U128(v1, v2), AccumulatorValue::IntegerTuple(w1, w2)) => {
185                *v1 += w1 as u128;
186                *v2 += w2 as u128;
187            }
188            (Self::Events(commitments), AccumulatorValue::EventDigest(event_digests)) => {
189                for (event_idx, digest) in event_digests {
190                    commitments.push(EventCommitment::new(
191                        checkpoint_seq,
192                        transaction_idx,
193                        event_idx,
194                        digest,
195                    ));
196                }
197            }
198            _ => {
199                fatal!("invalid merge");
200            }
201        }
202    }
203}
204
205struct Update {
206    merge: MergedValueIntermediate,
207    split: MergedValueIntermediate,
208    // Track input and output SUI for each update. Necessary so that when we construct
209    // a settlement transaction from a collection of Updates, they can accurately
210    // track the net SUI flows.
211    input_sui: u64,
212    output_sui: u64,
213}
214
215pub(crate) struct AccumulatorSettlementTxBuilder {
216    // updates is iterated over, must be a BTreeMap
217    updates: BTreeMap<AccumulatorObjId, Update>,
218    // addresses is only used for lookups.
219    addresses: HashMap<AccumulatorObjId, AccumulatorAddress>,
220}
221
222impl AccumulatorSettlementTxBuilder {
223    pub fn new(
224        cache: Option<&dyn TransactionCacheRead>,
225        ckpt_effects: &[TransactionEffects],
226        checkpoint_seq: u64,
227        tx_index_offset: u64,
228    ) -> Self {
229        let mut updates = BTreeMap::<_, _>::new();
230
231        let mut addresses = HashMap::<_, _>::new();
232
233        for (tx_index, effect) in ckpt_effects.iter().enumerate() {
234            let tx = effect.transaction_digest();
235            // TransactionEffectsAPI::accumulator_events() uses a linear scan of all
236            // object changes and allocates a new vector. In the common case (on validators),
237            // we still have still have the original vector in the writeback cache, so
238            // we can avoid the unnecessary work by just taking it from the cache.
239            let events = match cache.and_then(|c| c.take_accumulator_events(tx)) {
240                Some(events) => events,
241                None => effect.accumulator_events(),
242            };
243
244            for event in events {
245                // The input to the settlement is the sum of the outputs of accumulator events (i.e. deposits).
246                // and the output of the settlement is the sum of the inputs (i.e. withdraws).
247                let (event_input_sui, event_output_sui) = event.total_sui_in_event();
248
249                let AccumulatorEvent {
250                    accumulator_obj,
251                    write:
252                        AccumulatorWriteV1 {
253                            operation,
254                            value,
255                            address,
256                        },
257                } = event;
258
259                if let Some(prev) = addresses.insert(accumulator_obj, address.clone()) {
260                    debug_assert_eq!(prev, address);
261                }
262
263                let entry = updates.entry(accumulator_obj).or_insert_with(|| {
264                    let zero = MergedValueIntermediate::zero(&value);
265                    Update {
266                        merge: zero.clone(),
267                        split: zero,
268                        input_sui: 0,
269                        output_sui: 0,
270                    }
271                });
272
273                // The output of the event is the input of the settlement, and vice versa.
274                entry.input_sui += event_output_sui;
275                entry.output_sui += event_input_sui;
276
277                match operation {
278                    AccumulatorOperation::Merge => {
279                        entry.merge.accumulate_into(
280                            value,
281                            checkpoint_seq,
282                            tx_index as u64 + tx_index_offset,
283                        );
284                    }
285                    AccumulatorOperation::Split => {
286                        entry.split.accumulate_into(
287                            value,
288                            checkpoint_seq,
289                            tx_index as u64 + tx_index_offset,
290                        );
291                    }
292                }
293            }
294        }
295
296        Self { updates, addresses }
297    }
298
299    pub fn num_updates(&self) -> usize {
300        self.updates.len()
301    }
302
303    /// Returns a unified map of funds changes for all accounts.
304    /// The funds change for each account is merged from the merge and split operations.
305    pub fn collect_funds_changes(&self) -> BTreeMap<AccumulatorObjId, i128> {
306        self.updates
307            .iter()
308            .filter_map(|(object_id, update)| match (&update.merge, &update.split) {
309                (
310                    MergedValueIntermediate::SumU128(merge),
311                    MergedValueIntermediate::SumU128(split),
312                ) => Some((*object_id, *merge as i128 - *split as i128)),
313                _ => None,
314            })
315            .collect()
316    }
317
318    /// Builds settlement transactions that apply accumulator updates.
319    pub fn build_tx(
320        self,
321        protocol_config: &ProtocolConfig,
322        epoch: u64,
323        accumulator_root_obj_initial_shared_version: SequenceNumber,
324        checkpoint_height: u64,
325        checkpoint_seq: u64,
326    ) -> Vec<TransactionKind> {
327        let Self { updates, addresses } = self;
328
329        let build_one_settlement_txn = |idx: u64, updates: &mut Vec<(AccumulatorObjId, Update)>| {
330            let (total_input_sui, total_output_sui) =
331                updates
332                    .iter()
333                    .fold((0, 0), |(acc_input, acc_output), (_, update)| {
334                        (acc_input + update.input_sui, acc_output + update.output_sui)
335                    });
336
337            Self::build_one_settlement_txn(
338                &addresses,
339                epoch,
340                idx,
341                checkpoint_height,
342                accumulator_root_obj_initial_shared_version,
343                updates.drain(..),
344                total_input_sui,
345                total_output_sui,
346                checkpoint_seq,
347            )
348        };
349
350        let chunk_size = protocol_config
351            .max_updates_per_settlement_txn_as_option()
352            .unwrap_or(u32::MAX) as usize;
353
354        updates
355            .into_iter()
356            .chunks(chunk_size)
357            .into_iter()
358            .enumerate()
359            .map(|(idx, chunk)| {
360                build_one_settlement_txn(idx as u64, &mut chunk.collect::<Vec<_>>())
361            })
362            .collect()
363    }
364
365    fn add_prologue(
366        builder: &mut ProgrammableTransactionBuilder,
367        root: Argument,
368        epoch: u64,
369        checkpoint_height: u64,
370        idx: u64,
371        total_input_sui: u64,
372        total_output_sui: u64,
373    ) {
374        let epoch_arg = builder.pure(epoch).unwrap();
375        let checkpoint_height_arg = builder.pure(checkpoint_height).unwrap();
376        let idx_arg = builder.pure(idx).unwrap();
377        let total_input_sui_arg = builder.pure(total_input_sui).unwrap();
378        let total_output_sui_arg = builder.pure(total_output_sui).unwrap();
379
380        builder.programmable_move_call(
381            SUI_FRAMEWORK_PACKAGE_ID,
382            ACCUMULATOR_SETTLEMENT_MODULE.into(),
383            ACCUMULATOR_ROOT_SETTLEMENT_PROLOGUE_FUNC.into(),
384            vec![],
385            vec![
386                root,
387                epoch_arg,
388                checkpoint_height_arg,
389                idx_arg,
390                total_input_sui_arg,
391                total_output_sui_arg,
392            ],
393        );
394    }
395
396    fn build_one_settlement_txn(
397        addresses: &HashMap<AccumulatorObjId, AccumulatorAddress>,
398        epoch: u64,
399        idx: u64,
400        checkpoint_height: u64,
401        accumulator_root_obj_initial_shared_version: SequenceNumber,
402        updates: impl Iterator<Item = (AccumulatorObjId, Update)>,
403        total_input_sui: u64,
404        total_output_sui: u64,
405        checkpoint_seq: u64,
406    ) -> TransactionKind {
407        let mut builder = ProgrammableTransactionBuilder::new();
408
409        let root = builder
410            .input(CallArg::Object(ObjectArg::SharedObject {
411                id: SUI_ACCUMULATOR_ROOT_OBJECT_ID,
412                initial_shared_version: accumulator_root_obj_initial_shared_version,
413                mutability: SharedObjectMutability::NonExclusiveWrite,
414            }))
415            .unwrap();
416
417        Self::add_prologue(
418            &mut builder,
419            root,
420            epoch,
421            checkpoint_height,
422            idx,
423            total_input_sui,
424            total_output_sui,
425        );
426
427        for (accumulator_obj, update) in updates {
428            let Update { merge, split, .. } = update;
429            let address = addresses.get(&accumulator_obj).unwrap();
430            let merged_value = MergedValue::from(merge);
431            let split_value = MergedValue::from(split);
432            MergedValue::add_move_call(
433                merged_value,
434                split_value,
435                root,
436                address,
437                checkpoint_seq,
438                &mut builder,
439            );
440        }
441
442        TransactionKind::ProgrammableSystemTransaction(builder.finish())
443    }
444}
445
446/// Builds the barrier transaction that advances the version of the accumulator root object.
447/// This must be called after all settlement transactions have been executed.
448/// `settlement_effects` contains the effects of all settlement transactions.
449pub fn build_accumulator_barrier_tx(
450    epoch: u64,
451    accumulator_root_obj_initial_shared_version: SequenceNumber,
452    checkpoint_height: u64,
453    settlement_effects: &[TransactionEffects],
454) -> TransactionKind {
455    let num_settlements = settlement_effects.len() as u64;
456    let mut builder = ProgrammableTransactionBuilder::new();
457    let root = builder
458        .input(CallArg::Object(ObjectArg::SharedObject {
459            id: SUI_ACCUMULATOR_ROOT_OBJECT_ID,
460            initial_shared_version: accumulator_root_obj_initial_shared_version,
461            mutability: SharedObjectMutability::Mutable,
462        }))
463        .unwrap();
464
465    AccumulatorSettlementTxBuilder::add_prologue(
466        &mut builder,
467        root,
468        epoch,
469        checkpoint_height,
470        num_settlements,
471        0,
472        0,
473    );
474
475    TransactionKind::ProgrammableSystemTransaction(builder.finish())
476}