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