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}