@@ -67,6 +67,18 @@ impl TransactionsWithOutput {
67
67
self . persisted_auxiliary_infos. iter( )
68
68
)
69
69
}
70
+
71
+ pub fn last ( & self ) -> Option < ( & Transaction , & TransactionOutput , & PersistedAuxiliaryInfo ) > {
72
+ self . transactions . last ( ) . map ( |txn| {
73
+ (
74
+ txn,
75
+ self . transaction_outputs . last ( ) . expect ( "Must be non-empty" ) ,
76
+ self . persisted_auxiliary_infos
77
+ . last ( )
78
+ . expect ( "Must be non-empty" ) ,
79
+ )
80
+ } )
81
+ }
70
82
}
71
83
72
84
#[ ouroboros:: self_referencing]
@@ -79,13 +91,18 @@ pub struct TransactionsToKeep {
79
91
}
80
92
81
93
impl TransactionsToKeep {
94
+ /// `must_be_block` is a hint to tell the implementation that the last transaction is the only
95
+ /// checkpoint and there is no need to look further to find other checkpoints.
82
96
pub fn index (
83
97
first_version : Version ,
84
98
transactions_with_output : TransactionsWithOutput ,
85
- is_reconfig : bool ,
99
+ must_be_block : bool ,
86
100
) -> Self {
87
101
let _timer = TIMER . timer_with ( & [ "transactions_to_keep__index" ] ) ;
88
102
103
+ let ( all_checkpoint_indices, is_reconfig) =
104
+ Self :: get_all_checkpoint_indices ( & transactions_with_output, must_be_block) ;
105
+
89
106
TransactionsToKeepBuilder {
90
107
transactions_with_output,
91
108
is_reconfig,
@@ -94,10 +111,7 @@ impl TransactionsToKeep {
94
111
. transaction_outputs
95
112
. iter ( )
96
113
. map ( TransactionOutput :: write_set) ;
97
- let last_checkpoint_index = Self :: get_last_checkpoint_index (
98
- is_reconfig,
99
- & transactions_with_output. transactions ,
100
- ) ;
114
+ let last_checkpoint_index = all_checkpoint_indices. last ( ) . copied ( ) ;
101
115
StateUpdateRefs :: index_write_sets (
102
116
first_version,
103
117
write_sets,
@@ -114,18 +128,17 @@ impl TransactionsToKeep {
114
128
transactions : Vec < Transaction > ,
115
129
transaction_outputs : Vec < TransactionOutput > ,
116
130
persisted_auxiliary_infos : Vec < PersistedAuxiliaryInfo > ,
117
- is_reconfig : bool ,
118
131
) -> Self {
119
132
let txns_with_output = TransactionsWithOutput :: new (
120
133
transactions,
121
134
transaction_outputs,
122
135
persisted_auxiliary_infos,
123
136
) ;
124
- Self :: index ( first_version, txns_with_output, is_reconfig )
137
+ Self :: index ( first_version, txns_with_output, false )
125
138
}
126
139
127
140
pub fn new_empty ( ) -> Self {
128
- Self :: make ( 0 , vec ! [ ] , vec ! [ ] , vec ! [ ] , false )
141
+ Self :: make ( 0 , vec ! [ ] , vec ! [ ] , vec ! [ ] )
129
142
}
130
143
131
144
pub fn new_dummy_success ( txns : Vec < Transaction > ) -> Self {
@@ -135,9 +148,10 @@ impl TransactionsToKeep {
135
148
transaction_index : i as u32 ,
136
149
} )
137
150
. collect ( ) ;
138
- Self :: make ( 0 , txns, txn_outputs, persisted_auxiliary_infos, false )
151
+ Self :: make ( 0 , txns, txn_outputs, persisted_auxiliary_infos)
139
152
}
140
153
154
+ /// Whether the last txn of this block/chunk is reconfig.
141
155
pub fn is_reconfig ( & self ) -> bool {
142
156
* self . borrow_is_reconfig ( )
143
157
}
@@ -162,16 +176,31 @@ impl TransactionsToKeep {
162
176
}
163
177
}
164
178
165
- fn get_last_checkpoint_index ( is_reconfig : bool , transactions : & [ Transaction ] ) -> Option < usize > {
166
- let _timer = TIMER . timer_with ( & [ "get_last_checkpoint_index" ] ) ;
179
+ fn get_all_checkpoint_indices (
180
+ transactions_with_output : & TransactionsWithOutput ,
181
+ must_be_block : bool ,
182
+ ) -> ( Vec < usize > , bool ) {
183
+ let _timer = TIMER . timer_with ( & [ "get_all_checkpoint_indices" ] ) ;
184
+
185
+ let ( last_txn, last_output) = match transactions_with_output. last ( ) {
186
+ Some ( ( txn, output, _) ) => ( txn, output) ,
187
+ None => return ( Vec :: new ( ) , false ) ,
188
+ } ;
189
+ let is_reconfig = last_output. has_new_epoch_event ( ) ;
167
190
168
- if is_reconfig {
169
- return Some ( transactions. len ( ) - 1 ) ;
191
+ if must_be_block {
192
+ assert ! ( last_txn. is_non_reconfig_block_ending( ) || is_reconfig) ;
193
+ return ( vec ! [ transactions_with_output. len( ) - 1 ] , is_reconfig) ;
170
194
}
171
195
172
- transactions
196
+ let all_ckpt_indices = transactions_with_output
173
197
. iter ( )
174
- . rposition ( Transaction :: is_non_reconfig_block_ending)
198
+ . enumerate ( )
199
+ . filter_map ( |( idx, ( txn, output, _) ) | {
200
+ ( txn. is_non_reconfig_block_ending ( ) || output. has_new_epoch_event ( ) ) . then_some ( idx)
201
+ } )
202
+ . collect ( ) ;
203
+ ( all_ckpt_indices, is_reconfig)
175
204
}
176
205
177
206
pub fn ensure_at_most_one_checkpoint ( & self ) -> Result < ( ) > {
@@ -217,3 +246,214 @@ impl Debug for TransactionsToKeep {
217
246
. finish ( )
218
247
}
219
248
}
249
+
250
+ #[ cfg( test) ]
251
+ mod tests {
252
+ use super :: { TransactionsToKeep , TransactionsWithOutput } ;
253
+ use aptos_crypto:: { ed25519:: Ed25519PrivateKey , HashValue , PrivateKey , Uniform } ;
254
+ use aptos_types:: {
255
+ account_address:: AccountAddress ,
256
+ contract_event:: ContractEvent ,
257
+ test_helpers:: transaction_test_helpers:: get_test_signed_txn,
258
+ transaction:: {
259
+ ExecutionStatus , PersistedAuxiliaryInfo , Transaction , TransactionAuxiliaryData ,
260
+ TransactionOutput , TransactionStatus ,
261
+ } ,
262
+ write_set:: WriteSet ,
263
+ } ;
264
+
265
+ fn dummy_txn ( ) -> Transaction {
266
+ let private_key = Ed25519PrivateKey :: generate_for_testing ( ) ;
267
+ let public_key = private_key. public_key ( ) ;
268
+ let sender = AccountAddress :: ZERO ;
269
+ Transaction :: UserTransaction ( get_test_signed_txn (
270
+ sender,
271
+ 0 ,
272
+ & private_key,
273
+ public_key,
274
+ None ,
275
+ ) )
276
+ }
277
+
278
+ fn ckpt_txn ( ) -> Transaction {
279
+ Transaction :: StateCheckpoint ( HashValue :: zero ( ) )
280
+ }
281
+
282
+ fn default_output ( ) -> TransactionOutput {
283
+ TransactionOutput :: new (
284
+ WriteSet :: default ( ) ,
285
+ vec ! [ ] ,
286
+ 0 ,
287
+ TransactionStatus :: Keep ( ExecutionStatus :: Success ) ,
288
+ TransactionAuxiliaryData :: default ( ) ,
289
+ )
290
+ }
291
+
292
+ fn output_with_reconfig ( ) -> TransactionOutput {
293
+ let reconfig_event = ContractEvent :: new_v2_with_type_tag_str (
294
+ "0x1::reconfiguration::NewEpochEvent" ,
295
+ b"" . to_vec ( ) ,
296
+ ) ;
297
+ TransactionOutput :: new (
298
+ WriteSet :: default ( ) ,
299
+ vec ! [ reconfig_event] ,
300
+ 0 ,
301
+ TransactionStatus :: Keep ( ExecutionStatus :: Success ) ,
302
+ TransactionAuxiliaryData :: default ( ) ,
303
+ )
304
+ }
305
+
306
+ fn default_aux_info ( ) -> PersistedAuxiliaryInfo {
307
+ PersistedAuxiliaryInfo :: None
308
+ }
309
+
310
+ #[ test]
311
+ fn test_regular_block_without_reconfig ( ) {
312
+ let txns = vec ! [ dummy_txn( ) , dummy_txn( ) , ckpt_txn( ) ] ;
313
+ let outputs = vec ! [ default_output( ) , default_output( ) , default_output( ) ] ;
314
+ let aux_infos = vec ! [ default_aux_info( ) , default_aux_info( ) , default_aux_info( ) ] ;
315
+ let txn_with_outputs = TransactionsWithOutput :: new ( txns, outputs, aux_infos) ;
316
+
317
+ {
318
+ let ( all_ckpt_indices, is_reconfig) =
319
+ TransactionsToKeep :: get_all_checkpoint_indices ( & txn_with_outputs, true ) ;
320
+ assert_eq ! ( all_ckpt_indices, vec![ 2 ] ) ;
321
+ assert ! ( !is_reconfig) ;
322
+ }
323
+
324
+ {
325
+ let ( all_ckpt_indices, is_reconfig) =
326
+ TransactionsToKeep :: get_all_checkpoint_indices ( & txn_with_outputs, false ) ;
327
+ assert_eq ! ( all_ckpt_indices, vec![ 2 ] ) ;
328
+ assert ! ( !is_reconfig) ;
329
+ }
330
+ }
331
+
332
+ #[ test]
333
+ fn test_regular_block_with_reconfig ( ) {
334
+ let txns = vec ! [ dummy_txn( ) , dummy_txn( ) , dummy_txn( ) ] ;
335
+ let outputs = vec ! [ default_output( ) , default_output( ) , output_with_reconfig( ) ] ;
336
+ let aux_infos = vec ! [ default_aux_info( ) , default_aux_info( ) , default_aux_info( ) ] ;
337
+ let txn_with_outputs = TransactionsWithOutput :: new ( txns, outputs, aux_infos) ;
338
+
339
+ {
340
+ let ( all_ckpt_indices, is_reconfig) =
341
+ TransactionsToKeep :: get_all_checkpoint_indices ( & txn_with_outputs, true ) ;
342
+ assert_eq ! ( all_ckpt_indices, vec![ 2 ] ) ;
343
+ assert ! ( is_reconfig) ;
344
+ }
345
+
346
+ {
347
+ let ( all_ckpt_indices, is_reconfig) =
348
+ TransactionsToKeep :: get_all_checkpoint_indices ( & txn_with_outputs, false ) ;
349
+ assert_eq ! ( all_ckpt_indices, vec![ 2 ] ) ;
350
+ assert ! ( is_reconfig) ;
351
+ }
352
+ }
353
+
354
+ #[ test]
355
+ fn test_chunk_with_no_ckpt ( ) {
356
+ let txns = vec ! [ dummy_txn( ) , dummy_txn( ) , dummy_txn( ) ] ;
357
+ let outputs = vec ! [ default_output( ) , default_output( ) , default_output( ) ] ;
358
+ let aux_infos = vec ! [ default_aux_info( ) , default_aux_info( ) , default_aux_info( ) ] ;
359
+ let txn_with_outputs = TransactionsWithOutput :: new ( txns, outputs, aux_infos) ;
360
+
361
+ let ( all_ckpt_indices, is_reconfig) =
362
+ TransactionsToKeep :: get_all_checkpoint_indices ( & txn_with_outputs, false ) ;
363
+ assert ! ( all_ckpt_indices. is_empty( ) ) ;
364
+ assert ! ( !is_reconfig) ;
365
+ }
366
+
367
+ #[ test]
368
+ fn test_chunk_with_ckpts_no_reconfig ( ) {
369
+ let txns = vec ! [
370
+ dummy_txn( ) ,
371
+ ckpt_txn( ) ,
372
+ dummy_txn( ) ,
373
+ ckpt_txn( ) ,
374
+ dummy_txn( ) ,
375
+ ] ;
376
+ let outputs = vec ! [
377
+ default_output( ) ,
378
+ default_output( ) ,
379
+ default_output( ) ,
380
+ default_output( ) ,
381
+ default_output( ) ,
382
+ ] ;
383
+ let aux_infos = vec ! [
384
+ default_aux_info( ) ,
385
+ default_aux_info( ) ,
386
+ default_aux_info( ) ,
387
+ default_aux_info( ) ,
388
+ default_aux_info( ) ,
389
+ ] ;
390
+ let txn_with_outputs = TransactionsWithOutput :: new ( txns, outputs, aux_infos) ;
391
+
392
+ let ( all_ckpt_indices, is_reconfig) =
393
+ TransactionsToKeep :: get_all_checkpoint_indices ( & txn_with_outputs, false ) ;
394
+ assert_eq ! ( all_ckpt_indices, vec![ 1 , 3 ] ) ;
395
+ assert ! ( !is_reconfig) ;
396
+ }
397
+
398
+ #[ test]
399
+ fn test_chunk_with_ckpts_with_reconfig_in_the_middle ( ) {
400
+ let txns = vec ! [
401
+ dummy_txn( ) ,
402
+ ckpt_txn( ) ,
403
+ dummy_txn( ) ,
404
+ dummy_txn( ) ,
405
+ dummy_txn( ) ,
406
+ ] ;
407
+ let outputs = vec ! [
408
+ default_output( ) ,
409
+ default_output( ) ,
410
+ default_output( ) ,
411
+ output_with_reconfig( ) ,
412
+ default_output( ) ,
413
+ ] ;
414
+ let aux_infos = vec ! [
415
+ default_aux_info( ) ,
416
+ default_aux_info( ) ,
417
+ default_aux_info( ) ,
418
+ default_aux_info( ) ,
419
+ default_aux_info( ) ,
420
+ ] ;
421
+ let txn_with_outputs = TransactionsWithOutput :: new ( txns, outputs, aux_infos) ;
422
+
423
+ let ( all_ckpt_indices, is_reconfig) =
424
+ TransactionsToKeep :: get_all_checkpoint_indices ( & txn_with_outputs, false ) ;
425
+ assert_eq ! ( all_ckpt_indices, vec![ 1 , 3 ] ) ;
426
+ assert ! ( !is_reconfig) ;
427
+ }
428
+
429
+ #[ test]
430
+ fn test_chunk_with_ckpts_with_reconfig_at_end ( ) {
431
+ let txns = vec ! [
432
+ dummy_txn( ) ,
433
+ ckpt_txn( ) ,
434
+ dummy_txn( ) ,
435
+ dummy_txn( ) ,
436
+ dummy_txn( ) ,
437
+ ] ;
438
+ let outputs = vec ! [
439
+ default_output( ) ,
440
+ default_output( ) ,
441
+ default_output( ) ,
442
+ default_output( ) ,
443
+ output_with_reconfig( ) ,
444
+ ] ;
445
+ let aux_infos = vec ! [
446
+ default_aux_info( ) ,
447
+ default_aux_info( ) ,
448
+ default_aux_info( ) ,
449
+ default_aux_info( ) ,
450
+ default_aux_info( ) ,
451
+ ] ;
452
+ let txn_with_outputs = TransactionsWithOutput :: new ( txns, outputs, aux_infos) ;
453
+
454
+ let ( all_ckpt_indices, is_reconfig) =
455
+ TransactionsToKeep :: get_all_checkpoint_indices ( & txn_with_outputs, false ) ;
456
+ assert_eq ! ( all_ckpt_indices, vec![ 1 , 4 ] ) ;
457
+ assert ! ( is_reconfig) ;
458
+ }
459
+ }
0 commit comments