Skip to content

Commit f9c35bf

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 5fbcace commit f9c35bf

File tree

4 files changed

+270
-50
lines changed

4 files changed

+270
-50
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: 258 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use anyhow::{ensure, Result};
66
use aptos_metrics_core::TimerHelper;
77
use aptos_storage_interface::state_store::state_update_refs::StateUpdateRefs;
88
use aptos_types::transaction::{PersistedAuxiliaryInfo, Transaction, TransactionOutput, Version};
9-
use itertools::izip;
9+
use itertools::{izip, Itertools};
1010
use std::{
1111
fmt::{Debug, Formatter},
1212
ops::Deref,
@@ -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,32 @@ 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
173-
.iter()
174-
.rposition(Transaction::is_non_reconfig_block_ending)
196+
(
197+
transactions_with_output
198+
.iter()
199+
.positions(|(txn, output, _)| {
200+
txn.is_non_reconfig_block_ending() || output.has_new_epoch_event()
201+
})
202+
.collect(),
203+
is_reconfig,
204+
)
175205
}
176206

177207
pub fn ensure_at_most_one_checkpoint(&self) -> Result<()> {
@@ -217,3 +247,214 @@ impl Debug for TransactionsToKeep {
217247
.finish()
218248
}
219249
}
250+
251+
#[cfg(test)]
252+
mod tests {
253+
use super::{TransactionsToKeep, TransactionsWithOutput};
254+
use aptos_crypto::{ed25519::Ed25519PrivateKey, HashValue, PrivateKey, Uniform};
255+
use aptos_types::{
256+
account_address::AccountAddress,
257+
contract_event::ContractEvent,
258+
test_helpers::transaction_test_helpers::get_test_signed_txn,
259+
transaction::{
260+
ExecutionStatus, PersistedAuxiliaryInfo, Transaction, TransactionAuxiliaryData,
261+
TransactionOutput, TransactionStatus,
262+
},
263+
write_set::WriteSet,
264+
};
265+
266+
fn dummy_txn() -> Transaction {
267+
let private_key = Ed25519PrivateKey::generate_for_testing();
268+
let public_key = private_key.public_key();
269+
let sender = AccountAddress::ZERO;
270+
Transaction::UserTransaction(get_test_signed_txn(
271+
sender,
272+
0,
273+
&private_key,
274+
public_key,
275+
None,
276+
))
277+
}
278+
279+
fn ckpt_txn() -> Transaction {
280+
Transaction::StateCheckpoint(HashValue::zero())
281+
}
282+
283+
fn default_output() -> TransactionOutput {
284+
TransactionOutput::new(
285+
WriteSet::default(),
286+
vec![],
287+
0,
288+
TransactionStatus::Keep(ExecutionStatus::Success),
289+
TransactionAuxiliaryData::default(),
290+
)
291+
}
292+
293+
fn output_with_reconfig() -> TransactionOutput {
294+
let reconfig_event = ContractEvent::new_v2_with_type_tag_str(
295+
"0x1::reconfiguration::NewEpochEvent",
296+
b"".to_vec(),
297+
);
298+
TransactionOutput::new(
299+
WriteSet::default(),
300+
vec![reconfig_event],
301+
0,
302+
TransactionStatus::Keep(ExecutionStatus::Success),
303+
TransactionAuxiliaryData::default(),
304+
)
305+
}
306+
307+
fn default_aux_info() -> PersistedAuxiliaryInfo {
308+
PersistedAuxiliaryInfo::None
309+
}
310+
311+
#[test]
312+
fn test_regular_block_without_reconfig() {
313+
let txns = vec![dummy_txn(), dummy_txn(), ckpt_txn()];
314+
let outputs = vec![default_output(), default_output(), default_output()];
315+
let aux_infos = vec![default_aux_info(), default_aux_info(), default_aux_info()];
316+
let txn_with_outputs = TransactionsWithOutput::new(txns, outputs, aux_infos);
317+
318+
{
319+
let (all_ckpt_indices, is_reconfig) =
320+
TransactionsToKeep::get_all_checkpoint_indices(&txn_with_outputs, true);
321+
assert_eq!(all_ckpt_indices, vec![2]);
322+
assert!(!is_reconfig);
323+
}
324+
325+
{
326+
let (all_ckpt_indices, is_reconfig) =
327+
TransactionsToKeep::get_all_checkpoint_indices(&txn_with_outputs, false);
328+
assert_eq!(all_ckpt_indices, vec![2]);
329+
assert!(!is_reconfig);
330+
}
331+
}
332+
333+
#[test]
334+
fn test_regular_block_with_reconfig() {
335+
let txns = vec![dummy_txn(), dummy_txn(), dummy_txn()];
336+
let outputs = vec![default_output(), default_output(), output_with_reconfig()];
337+
let aux_infos = vec![default_aux_info(), default_aux_info(), default_aux_info()];
338+
let txn_with_outputs = TransactionsWithOutput::new(txns, outputs, aux_infos);
339+
340+
{
341+
let (all_ckpt_indices, is_reconfig) =
342+
TransactionsToKeep::get_all_checkpoint_indices(&txn_with_outputs, true);
343+
assert_eq!(all_ckpt_indices, vec![2]);
344+
assert!(is_reconfig);
345+
}
346+
347+
{
348+
let (all_ckpt_indices, is_reconfig) =
349+
TransactionsToKeep::get_all_checkpoint_indices(&txn_with_outputs, false);
350+
assert_eq!(all_ckpt_indices, vec![2]);
351+
assert!(is_reconfig);
352+
}
353+
}
354+
355+
#[test]
356+
fn test_chunk_with_no_ckpt() {
357+
let txns = vec![dummy_txn(), dummy_txn(), dummy_txn()];
358+
let outputs = vec![default_output(), default_output(), default_output()];
359+
let aux_infos = vec![default_aux_info(), default_aux_info(), default_aux_info()];
360+
let txn_with_outputs = TransactionsWithOutput::new(txns, outputs, aux_infos);
361+
362+
let (all_ckpt_indices, is_reconfig) =
363+
TransactionsToKeep::get_all_checkpoint_indices(&txn_with_outputs, false);
364+
assert!(all_ckpt_indices.is_empty());
365+
assert!(!is_reconfig);
366+
}
367+
368+
#[test]
369+
fn test_chunk_with_ckpts_no_reconfig() {
370+
let txns = vec![
371+
dummy_txn(),
372+
ckpt_txn(),
373+
dummy_txn(),
374+
ckpt_txn(),
375+
dummy_txn(),
376+
];
377+
let outputs = vec![
378+
default_output(),
379+
default_output(),
380+
default_output(),
381+
default_output(),
382+
default_output(),
383+
];
384+
let aux_infos = vec![
385+
default_aux_info(),
386+
default_aux_info(),
387+
default_aux_info(),
388+
default_aux_info(),
389+
default_aux_info(),
390+
];
391+
let txn_with_outputs = TransactionsWithOutput::new(txns, outputs, aux_infos);
392+
393+
let (all_ckpt_indices, is_reconfig) =
394+
TransactionsToKeep::get_all_checkpoint_indices(&txn_with_outputs, false);
395+
assert_eq!(all_ckpt_indices, vec![1, 3]);
396+
assert!(!is_reconfig);
397+
}
398+
399+
#[test]
400+
fn test_chunk_with_ckpts_with_reconfig_in_the_middle() {
401+
let txns = vec![
402+
dummy_txn(),
403+
ckpt_txn(),
404+
dummy_txn(),
405+
dummy_txn(),
406+
dummy_txn(),
407+
];
408+
let outputs = vec![
409+
default_output(),
410+
default_output(),
411+
default_output(),
412+
output_with_reconfig(),
413+
default_output(),
414+
];
415+
let aux_infos = vec![
416+
default_aux_info(),
417+
default_aux_info(),
418+
default_aux_info(),
419+
default_aux_info(),
420+
default_aux_info(),
421+
];
422+
let txn_with_outputs = TransactionsWithOutput::new(txns, outputs, aux_infos);
423+
424+
let (all_ckpt_indices, is_reconfig) =
425+
TransactionsToKeep::get_all_checkpoint_indices(&txn_with_outputs, false);
426+
assert_eq!(all_ckpt_indices, vec![1, 3]);
427+
assert!(!is_reconfig);
428+
}
429+
430+
#[test]
431+
fn test_chunk_with_ckpts_with_reconfig_at_end() {
432+
let txns = vec![
433+
dummy_txn(),
434+
ckpt_txn(),
435+
dummy_txn(),
436+
dummy_txn(),
437+
dummy_txn(),
438+
];
439+
let outputs = vec![
440+
default_output(),
441+
default_output(),
442+
default_output(),
443+
default_output(),
444+
output_with_reconfig(),
445+
];
446+
let aux_infos = vec![
447+
default_aux_info(),
448+
default_aux_info(),
449+
default_aux_info(),
450+
default_aux_info(),
451+
default_aux_info(),
452+
];
453+
let txn_with_outputs = TransactionsWithOutput::new(txns, outputs, aux_infos);
454+
455+
let (all_ckpt_indices, is_reconfig) =
456+
TransactionsToKeep::get_all_checkpoint_indices(&txn_with_outputs, false);
457+
assert_eq!(all_ckpt_indices, vec![1, 4]);
458+
assert!(is_reconfig);
459+
}
460+
}

0 commit comments

Comments
 (0)