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;
40pub mod object_funds_checker;
41pub(crate) mod transaction_rewriting;
42
43#[derive(Debug, Copy, Clone)]
50enum MergedValue {
51 SumU128(u128),
52 SumU128U128(u128, u128),
53 EventDigest(Digest, u64),
55}
56
57enum ClassifiedType {
58 Balance,
59 Unknown,
60}
61
62impl ClassifiedType {
63 fn classify(ty: &TypeTag) -> Self {
64 let TypeTag::Struct(struct_tag) = ty else {
65 return Self::Unknown;
66 };
67
68 if struct_tag.address == SUI_FRAMEWORK_ADDRESS
69 && struct_tag.module.as_ident_str() == BALANCE_MODULE_NAME
70 && struct_tag.name.as_ident_str() == BALANCE_STRUCT_NAME
71 {
72 return Self::Balance;
73 }
74
75 Self::Unknown
76 }
77}
78
79impl MergedValue {
80 fn add_move_call(
81 merge: Self,
82 split: Self,
83 root: Argument,
84 address: &AccumulatorAddress,
85 checkpoint_seq: u64,
86 builder: &mut ProgrammableTransactionBuilder,
87 ) {
88 let ty = ClassifiedType::classify(&address.ty);
89 let address_arg = builder.pure(address.address).unwrap();
90
91 match (ty, merge, split) {
92 (
93 ClassifiedType::Balance,
94 MergedValue::SumU128(merge_amount),
95 MergedValue::SumU128(split_amount),
96 ) => {
97 let (merge_amount, split_amount) = if merge_amount >= split_amount {
99 (merge_amount - split_amount, 0)
100 } else {
101 (0, split_amount - merge_amount)
102 };
103
104 if merge_amount != 0 || split_amount != 0 {
105 let merge_amount = builder.pure(merge_amount).unwrap();
106 let split_amount = builder.pure(split_amount).unwrap();
107 builder.programmable_move_call(
108 SUI_FRAMEWORK_PACKAGE_ID,
109 ACCUMULATOR_SETTLEMENT_MODULE.into(),
110 ACCUMULATOR_ROOT_SETTLE_U128_FUNC.into(),
111 vec![address.ty.clone()],
112 vec![root, address_arg, merge_amount, split_amount],
113 );
114 }
115 }
116 (_, MergedValue::SumU128U128(_v1, _v2), MergedValue::SumU128U128(_w1, _w2)) => todo!(),
117 (_, MergedValue::EventDigest(digest, event_count), MergedValue::EventDigest(_, _)) => {
118 let args = vec![
119 root,
120 builder.pure(address.address).unwrap(),
121 builder
122 .pure(U256::from_le_bytes(&digest.into_inner()))
123 .unwrap(),
124 builder.pure(event_count).unwrap(),
125 builder.pure(checkpoint_seq).unwrap(),
126 ];
127 builder.programmable_move_call(
128 SUI_FRAMEWORK_PACKAGE_ID,
129 ACCUMULATOR_SETTLEMENT_MODULE.into(),
130 sui_types::accumulator_root::ACCUMULATOR_ROOT_SETTLEMENT_SETTLE_EVENTS_FUNC
131 .into(),
132 vec![],
133 args,
134 );
135 }
136 _ => fatal!("invalid merge {:?} {:?}", merge, split),
137 }
138 }
139}
140
141impl From<MergedValueIntermediate> for MergedValue {
142 fn from(value: MergedValueIntermediate) -> Self {
143 match value {
144 MergedValueIntermediate::SumU128(v) => MergedValue::SumU128(v),
145 MergedValueIntermediate::SumU128U128(v1, v2) => MergedValue::SumU128U128(v1, v2),
146 MergedValueIntermediate::Events(events) => {
147 let event_count = events.len() as u64;
148 MergedValue::EventDigest(build_event_merkle_root(&events), event_count)
149 }
150 }
151 }
152}
153
154#[derive(Debug, Clone)]
164enum MergedValueIntermediate {
165 SumU128(u128),
166 SumU128U128(u128, u128),
167 Events(Vec<EventCommitment>),
168}
169
170impl MergedValueIntermediate {
171 fn zero(value: &AccumulatorValue) -> Self {
173 match value {
174 AccumulatorValue::Integer(_) => Self::SumU128(0),
175 AccumulatorValue::IntegerTuple(_, _) => Self::SumU128U128(0, 0),
176 AccumulatorValue::EventDigest(_) => Self::Events(vec![]),
177 }
178 }
179
180 fn accumulate_into(
181 &mut self,
182 value: AccumulatorValue,
183 checkpoint_seq: u64,
184 transaction_idx: u64,
185 ) {
186 match (self, value) {
187 (Self::SumU128(v1), AccumulatorValue::Integer(v2)) => *v1 += v2 as u128,
188 (Self::SumU128U128(v1, v2), AccumulatorValue::IntegerTuple(w1, w2)) => {
189 *v1 += w1 as u128;
190 *v2 += w2 as u128;
191 }
192 (Self::Events(commitments), AccumulatorValue::EventDigest(event_digests)) => {
193 for (event_idx, digest) in event_digests {
194 commitments.push(EventCommitment::new(
195 checkpoint_seq,
196 transaction_idx,
197 event_idx,
198 digest,
199 ));
200 }
201 }
202 _ => {
203 fatal!("invalid merge");
204 }
205 }
206 }
207}
208
209struct Update {
210 merge: MergedValueIntermediate,
211 split: MergedValueIntermediate,
212 input_sui: u64,
216 output_sui: u64,
217}
218
219pub(crate) struct AccumulatorSettlementTxBuilder {
220 updates: BTreeMap<AccumulatorObjId, Update>,
222 addresses: HashMap<AccumulatorObjId, AccumulatorAddress>,
224 num_deposits: u64,
225 num_withdrawals: u64,
226}
227
228impl AccumulatorSettlementTxBuilder {
229 pub fn new(
230 cache: Option<&dyn TransactionCacheRead>,
231 ckpt_effects: &[TransactionEffects],
232 checkpoint_seq: u64,
233 tx_index_offset: u64,
234 ) -> Self {
235 let mut updates = BTreeMap::<_, _>::new();
236 let mut addresses = HashMap::<_, _>::new();
237 let mut num_deposits = 0u64;
238 let mut num_withdrawals = 0u64;
239
240 for (tx_index, effect) in ckpt_effects.iter().enumerate() {
241 let tx = effect.transaction_digest();
242 let events = match cache.and_then(|c| c.take_accumulator_events(tx)) {
247 Some(events) => events,
248 None => effect.accumulator_events(),
249 };
250
251 for event in events {
252 let (event_input_sui, event_output_sui) = event.total_sui_in_event();
255
256 let AccumulatorEvent {
257 accumulator_obj,
258 write:
259 AccumulatorWriteV1 {
260 operation,
261 value,
262 address,
263 },
264 } = event;
265
266 if let Some(prev) = addresses.insert(accumulator_obj, address.clone()) {
267 debug_assert_eq!(prev, address);
268 }
269
270 let entry = updates.entry(accumulator_obj).or_insert_with(|| {
271 let zero = MergedValueIntermediate::zero(&value);
272 Update {
273 merge: zero.clone(),
274 split: zero,
275 input_sui: 0,
276 output_sui: 0,
277 }
278 });
279
280 entry.input_sui += event_output_sui;
282 entry.output_sui += event_input_sui;
283
284 match operation {
285 AccumulatorOperation::Merge => {
286 num_deposits += 1;
287 entry.merge.accumulate_into(
288 value,
289 checkpoint_seq,
290 tx_index as u64 + tx_index_offset,
291 );
292 }
293 AccumulatorOperation::Split => {
294 num_withdrawals += 1;
295 entry.split.accumulate_into(
296 value,
297 checkpoint_seq,
298 tx_index as u64 + tx_index_offset,
299 );
300 }
301 }
302 }
303 }
304
305 Self {
306 updates,
307 addresses,
308 num_deposits,
309 num_withdrawals,
310 }
311 }
312
313 pub fn num_updates(&self) -> usize {
314 self.updates.len()
315 }
316
317 pub fn num_deposits(&self) -> u64 {
318 self.num_deposits
319 }
320
321 pub fn num_withdrawals(&self) -> u64 {
322 self.num_withdrawals
323 }
324
325 pub fn collect_funds_changes(&self) -> BTreeMap<AccumulatorObjId, i128> {
328 self.updates
329 .iter()
330 .filter_map(|(object_id, update)| match (&update.merge, &update.split) {
331 (
332 MergedValueIntermediate::SumU128(merge),
333 MergedValueIntermediate::SumU128(split),
334 ) => Some((*object_id, *merge as i128 - *split as i128)),
335 _ => None,
336 })
337 .collect()
338 }
339
340 pub fn build_tx(
342 self,
343 protocol_config: &ProtocolConfig,
344 epoch: u64,
345 accumulator_root_obj_initial_shared_version: SequenceNumber,
346 checkpoint_height: u64,
347 checkpoint_seq: u64,
348 ) -> Vec<TransactionKind> {
349 let Self {
350 updates, addresses, ..
351 } = self;
352
353 let build_one_settlement_txn = |idx: u64, updates: &mut Vec<(AccumulatorObjId, Update)>| {
354 let (total_input_sui, total_output_sui) =
355 updates
356 .iter()
357 .fold((0, 0), |(acc_input, acc_output), (_, update)| {
358 (acc_input + update.input_sui, acc_output + update.output_sui)
359 });
360
361 Self::build_one_settlement_txn(
362 &addresses,
363 epoch,
364 idx,
365 checkpoint_height,
366 accumulator_root_obj_initial_shared_version,
367 updates.drain(..),
368 total_input_sui,
369 total_output_sui,
370 checkpoint_seq,
371 )
372 };
373
374 let chunk_size = protocol_config
375 .max_updates_per_settlement_txn_as_option()
376 .unwrap_or(u32::MAX) as usize;
377
378 updates
379 .into_iter()
380 .chunks(chunk_size)
381 .into_iter()
382 .enumerate()
383 .map(|(idx, chunk)| {
384 build_one_settlement_txn(idx as u64, &mut chunk.collect::<Vec<_>>())
385 })
386 .collect()
387 }
388
389 fn add_prologue(
390 builder: &mut ProgrammableTransactionBuilder,
391 root: Argument,
392 epoch: u64,
393 checkpoint_height: u64,
394 idx: u64,
395 total_input_sui: u64,
396 total_output_sui: u64,
397 ) {
398 let epoch_arg = builder.pure(epoch).unwrap();
399 let checkpoint_height_arg = builder.pure(checkpoint_height).unwrap();
400 let idx_arg = builder.pure(idx).unwrap();
401 let total_input_sui_arg = builder.pure(total_input_sui).unwrap();
402 let total_output_sui_arg = builder.pure(total_output_sui).unwrap();
403
404 builder.programmable_move_call(
405 SUI_FRAMEWORK_PACKAGE_ID,
406 ACCUMULATOR_SETTLEMENT_MODULE.into(),
407 ACCUMULATOR_ROOT_SETTLEMENT_PROLOGUE_FUNC.into(),
408 vec![],
409 vec![
410 root,
411 epoch_arg,
412 checkpoint_height_arg,
413 idx_arg,
414 total_input_sui_arg,
415 total_output_sui_arg,
416 ],
417 );
418 }
419
420 fn build_one_settlement_txn(
421 addresses: &HashMap<AccumulatorObjId, AccumulatorAddress>,
422 epoch: u64,
423 idx: u64,
424 checkpoint_height: u64,
425 accumulator_root_obj_initial_shared_version: SequenceNumber,
426 updates: impl Iterator<Item = (AccumulatorObjId, Update)>,
427 total_input_sui: u64,
428 total_output_sui: u64,
429 checkpoint_seq: u64,
430 ) -> TransactionKind {
431 let mut builder = ProgrammableTransactionBuilder::new();
432
433 let root = builder
434 .input(CallArg::Object(ObjectArg::SharedObject {
435 id: SUI_ACCUMULATOR_ROOT_OBJECT_ID,
436 initial_shared_version: accumulator_root_obj_initial_shared_version,
437 mutability: SharedObjectMutability::NonExclusiveWrite,
438 }))
439 .unwrap();
440
441 Self::add_prologue(
442 &mut builder,
443 root,
444 epoch,
445 checkpoint_height,
446 idx,
447 total_input_sui,
448 total_output_sui,
449 );
450
451 for (accumulator_obj, update) in updates {
452 let Update { merge, split, .. } = update;
453 let address = addresses.get(&accumulator_obj).unwrap();
454 let merged_value = MergedValue::from(merge);
455 let split_value = MergedValue::from(split);
456 MergedValue::add_move_call(
457 merged_value,
458 split_value,
459 root,
460 address,
461 checkpoint_seq,
462 &mut builder,
463 );
464 }
465
466 TransactionKind::ProgrammableSystemTransaction(builder.finish())
467 }
468}
469
470pub fn build_accumulator_barrier_tx(
474 epoch: u64,
475 accumulator_root_obj_initial_shared_version: SequenceNumber,
476 checkpoint_height: u64,
477 settlement_effects: &[TransactionEffects],
478) -> TransactionKind {
479 let num_settlements = settlement_effects.len() as u64;
480
481 let (objects_created, objects_destroyed) = count_accumulator_object_changes(settlement_effects);
482
483 let mut builder = ProgrammableTransactionBuilder::new();
484 let root = builder
485 .input(CallArg::Object(ObjectArg::SharedObject {
486 id: SUI_ACCUMULATOR_ROOT_OBJECT_ID,
487 initial_shared_version: accumulator_root_obj_initial_shared_version,
488 mutability: SharedObjectMutability::Mutable,
489 }))
490 .unwrap();
491
492 AccumulatorSettlementTxBuilder::add_prologue(
493 &mut builder,
494 root,
495 epoch,
496 checkpoint_height,
497 num_settlements,
498 0,
499 0,
500 );
501
502 let objects_created_arg = builder.pure(objects_created).unwrap();
503 let objects_destroyed_arg = builder.pure(objects_destroyed).unwrap();
504 builder.programmable_move_call(
505 SUI_FRAMEWORK_PACKAGE_ID,
506 ACCUMULATOR_METADATA_MODULE.into(),
507 ident_str!("record_accumulator_object_changes").into(),
508 vec![],
509 vec![root, objects_created_arg, objects_destroyed_arg],
510 );
511
512 TransactionKind::ProgrammableSystemTransaction(builder.finish())
513}
514
515pub(crate) fn count_accumulator_object_changes(
516 settlement_effects: &[TransactionEffects],
517) -> (u64, u64) {
518 settlement_effects
519 .iter()
520 .flat_map(|effects| effects.object_changes())
521 .fold((0u64, 0u64), |(created, destroyed), change| {
522 match change.id_operation {
523 IDOperation::Created => (created + 1, destroyed),
524 IDOperation::Deleted => (created, destroyed + 1),
525 IDOperation::None => (created, destroyed),
526 }
527 })
528}
529
530#[cfg(test)]
531mod barrier_settlement_key_tests {
532 use super::*;
533 use sui_types::transaction::TransactionKey;
534
535 #[test]
536 fn test_barrier_tx_returns_accumulator_settlement_key() {
537 let epoch = 5u64;
538 let checkpoint_height = 42u64;
539
540 let kind = build_accumulator_barrier_tx(
541 epoch,
542 SequenceNumber::from_u64(1),
543 checkpoint_height,
544 &[], );
546
547 assert_eq!(
548 kind.accumulator_barrier_settlement_key(),
549 Some(TransactionKey::AccumulatorSettlement(
550 epoch,
551 checkpoint_height
552 ))
553 );
554 assert!(kind.is_accumulator_barrier_settle_tx());
555 }
556
557 #[test]
558 fn test_settlement_tx_has_no_barrier_key() {
559 let protocol_config = ProtocolConfig::get_for_max_version_UNSAFE();
562 let builder = AccumulatorSettlementTxBuilder::new(None, &[], 0, 0);
563 let txns = builder.build_tx(&protocol_config, 5, SequenceNumber::from_u64(1), 42, 0);
564 for txn in txns {
565 assert_eq!(txn.accumulator_barrier_settlement_key(), None);
566 assert!(!txn.is_accumulator_barrier_settle_tx());
567 }
568 }
569}