Skip to content

Commit f58709d

Browse files
committed
[Hot State] Prepare for having all checkpoint indices in StateUpdateRefs
Right now `StateUpdateRefs` has the most recent checkpoint. In order to compute hot state, we need to perform eviction at the end of each block. When applying to state sync, this means that we need to know all the checkpoints and not just the most recent one. Also refactored the code a bit such that `extract_retries_and_discards` is simply responsible for extracting retries and discards, not for computing `has_reconfig`.
1 parent c032887 commit f58709d

File tree

4 files changed

+267
-48
lines changed

4 files changed

+267
-48
lines changed

execution/executor-types/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ serde = { workspace = true }
3333
thiserror = { workspace = true }
3434

3535
[dev-dependencies]
36-
aptos-types = { workspace = true, features = ["fuzzing"] }
36+
aptos-types = { workspace = true, features = ["fuzzing", "testing"] }
3737

3838
[features]
3939
default = []

execution/executor-types/src/transactions_with_output.rs

Lines changed: 255 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,18 @@ impl TransactionsWithOutput {
6767
self.persisted_auxiliary_infos.iter()
6868
)
6969
}
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+
}
7082
}
7183

7284
#[ouroboros::self_referencing]
@@ -79,13 +91,18 @@ pub struct TransactionsToKeep {
7991
}
8092

8193
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.
8296
pub fn index(
8397
first_version: Version,
8498
transactions_with_output: TransactionsWithOutput,
85-
is_reconfig: bool,
99+
must_be_block: bool,
86100
) -> Self {
87101
let _timer = TIMER.timer_with(&["transactions_to_keep__index"]);
88102

103+
let (all_checkpoint_indices, is_reconfig) =
104+
Self::get_all_checkpoint_indices(&transactions_with_output, must_be_block);
105+
89106
TransactionsToKeepBuilder {
90107
transactions_with_output,
91108
is_reconfig,
@@ -94,10 +111,7 @@ impl TransactionsToKeep {
94111
.transaction_outputs
95112
.iter()
96113
.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();
101115
StateUpdateRefs::index_write_sets(
102116
first_version,
103117
write_sets,
@@ -114,18 +128,17 @@ impl TransactionsToKeep {
114128
transactions: Vec<Transaction>,
115129
transaction_outputs: Vec<TransactionOutput>,
116130
persisted_auxiliary_infos: Vec<PersistedAuxiliaryInfo>,
117-
is_reconfig: bool,
118131
) -> Self {
119132
let txns_with_output = TransactionsWithOutput::new(
120133
transactions,
121134
transaction_outputs,
122135
persisted_auxiliary_infos,
123136
);
124-
Self::index(first_version, txns_with_output, is_reconfig)
137+
Self::index(first_version, txns_with_output, false)
125138
}
126139

127140
pub fn new_empty() -> Self {
128-
Self::make(0, vec![], vec![], vec![], false)
141+
Self::make(0, vec![], vec![], vec![])
129142
}
130143

131144
pub fn new_dummy_success(txns: Vec<Transaction>) -> Self {
@@ -135,9 +148,10 @@ impl TransactionsToKeep {
135148
transaction_index: i as u32,
136149
})
137150
.collect();
138-
Self::make(0, txns, txn_outputs, persisted_auxiliary_infos, false)
151+
Self::make(0, txns, txn_outputs, persisted_auxiliary_infos)
139152
}
140153

154+
/// Whether the last txn of this block/chunk is reconfig.
141155
pub fn is_reconfig(&self) -> bool {
142156
*self.borrow_is_reconfig()
143157
}
@@ -162,16 +176,31 @@ impl TransactionsToKeep {
162176
}
163177
}
164178

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();
167190

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);
170194
}
171195

172-
transactions
196+
let all_ckpt_indices = transactions_with_output
173197
.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)
175204
}
176205

177206
pub fn ensure_at_most_one_checkpoint(&self) -> Result<()> {
@@ -217,3 +246,214 @@ impl Debug for TransactionsToKeep {
217246
.finish()
218247
}
219248
}
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

Comments
 (0)