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