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