1use 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
35pub mod funds_read;
37pub mod balances;
39pub mod coin_reservations;
40
41#[derive(Debug, Copy, Clone)]
48enum MergedValue {
49 SumU128(u128),
50 SumU128U128(u128, u128),
51 EventDigest(Digest, 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 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#[derive(Debug, Clone)]
162enum MergedValueIntermediate {
163 SumU128(u128),
164 SumU128U128(u128, u128),
165 Events(Vec<EventCommitment>),
166}
167
168impl MergedValueIntermediate {
169 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 input_sui: u64,
214 output_sui: u64,
215}
216
217pub(crate) struct AccumulatorSettlementTxBuilder {
218 updates: BTreeMap<AccumulatorObjId, Update>,
220 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 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 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 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 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 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
448pub 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}