cfx_executor/state/state_object/checkpoints.rs
1//! Checkpoints: Defines the account entry type within checkpoint layers and
2//! implements checkpoint maintenance logic.
3
4use cfx_types::AddressWithSpace;
5use std::collections::{hash_map::Entry::*, HashMap};
6
7use super::{
8 super::checkpoints::CheckpointEntry::{self, Recorded, Unchanged},
9 AccountEntry, AccountEntryWithWarm, GlobalStat, OverlayAccount, State,
10};
11use crate::{state::checkpoints::CheckpointLayerTrait, unwrap_or_return};
12
13pub(super) type AccountCheckpointEntry = CheckpointEntry<AccountEntryWithWarm>;
14
15/// Represents a recoverable point within a checkpoint. including
16/// and. The addition of account entries to the checkpoint is lazy; they are
17/// only added when the account in the cache is modified, at which point the old
18/// version is incorporated into the checkpoint. Therefore, if an account does
19/// not exist in the checkpoint, it is implied to be the same as in the cache.
20/// This struct efficiently manages state changes and ensures data consistency
21/// across transactions.
22#[cfg_attr(test, derive(Clone))]
23pub(super) struct CheckpointLayer {
24 /// Checkpoint for global statistic variables.
25 global_stat: GlobalStat,
26 /// Checkpoint for modified account entries.
27 ///
28 /// An account will only be added only if its cache version is modified. If
29 /// an account does not exist in the checkpoint, it is implied to be the
30 /// same as in the cache.
31 entries: HashMap<AddressWithSpace, AccountCheckpointEntry>,
32}
33
34impl CheckpointLayerTrait for CheckpointLayer {
35 type Key = AddressWithSpace;
36 type Value = AccountEntryWithWarm;
37
38 fn as_hash_map(&self) -> &HashMap<Self::Key, CheckpointEntry<Self::Value>> {
39 &self.entries
40 }
41
42 fn as_hash_map_mut(
43 &mut self,
44 ) -> &mut HashMap<Self::Key, CheckpointEntry<Self::Value>> {
45 &mut self.entries
46 }
47}
48
49impl State {
50 /// Create a recoverable checkpoint of this state. Return the checkpoint
51 /// index. The checkpoint records any old value which is alive at the
52 /// creation time of the checkpoint and updated after that and before
53 /// the creation of the next checkpoint.
54 pub(crate) fn checkpoint(&mut self) -> usize {
55 self.checkpoints.get_mut().push_checkpoint(CheckpointLayer {
56 global_stat: self.global_stat,
57 entries: HashMap::new(),
58 })
59 }
60
61 /// Merge last checkpoint with previous.
62 pub(crate) fn discard_checkpoint(&mut self) {
63 let cleared_addresses =
64 unwrap_or_return!(self.checkpoints.get_mut().discard_checkpoint());
65
66 // if there is no checkpoint in state, the state's checkpoints are
67 // cleared directly, thus, the accounts in state's cache should
68 // all discard all checkpoints
69 for addr in cleared_addresses {
70 if let Some(AccountEntry::Cached(ref mut overlay_account, _dirty)) =
71 self.cache.get_mut().get_mut(&addr).map(|x| &mut x.entry)
72 {
73 overlay_account.clear_checkpoint();
74 }
75 }
76 }
77
78 /// Revert to the last checkpoint and discard it.
79 pub(crate) fn revert_to_checkpoint(&mut self) {
80 for (layer_id, reverted_layer) in
81 unwrap_or_return!(self.checkpoints.get_mut().revert_to_checkpoint())
82 {
83 self.global_stat = reverted_layer.global_stat;
84 apply_checkpoint_layer_to_cache(
85 reverted_layer.entries,
86 self.cache.get_mut(),
87 layer_id,
88 );
89 }
90 }
91
92 pub fn no_checkpoint(&self) -> bool { self.checkpoints.read().is_empty() }
93
94 /// Insert a new overlay account to cache and incoroprating the old version
95 /// to the checkpoint in needed.
96 pub(super) fn insert_to_cache(&mut self, account: OverlayAccount) {
97 let address = *account.address();
98 let old_account_entry = self
99 .cache
100 .get_mut()
101 .insert(address, AccountEntry::new_dirty(account).with_warm(true));
102
103 self.checkpoints
104 .get_mut()
105 .insert_element(address, move |_| {
106 AccountCheckpointEntry::from_cache(old_account_entry)
107 });
108 }
109
110 /// The caller has changed (or will change) an account in cache and notify
111 /// this function to incoroprates the old version to the checkpoint in
112 /// needed.
113 pub(super) fn copy_cache_entry_to_checkpoint(
114 &self, address: AddressWithSpace,
115 entry_in_cache: &mut AccountEntryWithWarm,
116 ) {
117 self.checkpoints
118 .write()
119 .insert_element(address, |checkpoint_id| {
120 let mut new_entry_in_cache = entry_in_cache
121 .entry
122 .clone_cache_entry_for_checkpoint(checkpoint_id)
123 .with_warm(true);
124
125 std::mem::swap(&mut new_entry_in_cache, entry_in_cache);
126 // Rename after memswap
127 let entry_to_checkpoint = new_entry_in_cache;
128
129 Recorded(entry_to_checkpoint)
130 });
131 // If there is no checkpoint, the above function will not be executed,
132 // so we need to mark warm bit here.
133 entry_in_cache.warm = true;
134 }
135
136 #[cfg(any(test, feature = "testonly_code"))]
137 pub fn clear(&mut self) {
138 assert!(self.no_checkpoint());
139 self.cache.get_mut().clear();
140 self.committed_cache.clear();
141 self.global_stat = GlobalStat::loaded(&self.db).expect("no db error");
142 }
143}
144
145fn apply_checkpoint_layer_to_cache(
146 entries: HashMap<AddressWithSpace, AccountCheckpointEntry>,
147 cache: &mut HashMap<AddressWithSpace, AccountEntryWithWarm>,
148 checkpoint_id: usize,
149) {
150 for (k, v) in entries.into_iter() {
151 let Occupied(mut entry_in_cache) = cache.entry(k) else {
152 // All the entries in checkpoint must be copied from cache by the
153 // following function `insert_to_cache` and `clone_to_checkpoint`.
154 //
155 // A cache entries will never be removed, except it is revert to an
156 // `Unchanged` checkpoint. If this exceptional case happens, this
157 // entry has never be loaded or written during transaction execution
158 // (regardless the reverted operations), and thus cannot have keys
159 // in the checkpoint.
160
161 unreachable!("Cache should always have more keys than checkpoint");
162 };
163 match v {
164 Recorded(entry_in_checkpoint) => {
165 if let Some(acc) = entry_in_cache.get_mut().account_mut() {
166 acc.revert_checkpoint(checkpoint_id);
167 }
168 *entry_in_cache.get_mut() = entry_in_checkpoint;
169 }
170
171 Unchanged => {
172 // Here we have an optimization: if a checkpoint entry was
173 // Unchanged and the corresponding cache entry
174 // was clean (i.e., its dirty bit was not set),
175 // the cache entry would be retained rather than
176 // removed by checkpoint semantics.
177 //
178 // A theoretical concern arose: if a CheckpointEntry is
179 // Unchanged, its cache entry is clean, and the
180 // account is absent from the database (DbAbsent), the output of
181 // `State::code_hash` might differ with and
182 // without this optimization.
183 //
184 // However, this scenario is impossible under the current
185 // implementation. In this system, DbAbsent and
186 // Recorded() are treated as distinct values, and transitioning
187 // from one to the other would require the dirty flag to be set.
188
189 if entry_in_cache.get().entry.is_dirty() {
190 entry_in_cache.remove();
191 } else {
192 entry_in_cache.get_mut().warm = false;
193 }
194 }
195 }
196 }
197}