1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
//! Checkpoints: Defines the account entry type within checkpoint layers and
//! implements checkpoint maintenance logic.

use cfx_types::AddressWithSpace;
use std::collections::{hash_map::Entry::*, HashMap};

use super::{
    super::checkpoints::CheckpointEntry::{self, Recorded, Unchanged},
    AccountEntry, GlobalStat, OverlayAccount, State,
};
use crate::{state::checkpoints::CheckpointLayerTrait, unwrap_or_return};

pub(super) type AccountCheckpointEntry = CheckpointEntry<AccountEntry>;

/// Represents a recoverable point within a checkpoint. including
/// and. The addition of account entries to the checkpoint is lazy; they are
/// only added when the account in the cache is modified, at which point the old
/// version is incorporated into the checkpoint. Therefore, if an account does
/// not exist in the checkpoint, it is implied to be the same as in the cache.
/// This struct efficiently manages state changes and ensures data consistency
/// across transactions.
#[cfg_attr(test, derive(Clone))]
pub(super) struct CheckpointLayer {
    /// Checkpoint for global statistic variables.
    global_stat: GlobalStat,
    /// Checkpoint for  modified account entries.
    ///
    /// An account will only be added only if its cache version is modified. If
    /// an account does not exist in the checkpoint, it is implied to be the
    /// same as in the cache.
    entries: HashMap<AddressWithSpace, AccountCheckpointEntry>,
}

impl CheckpointLayerTrait for CheckpointLayer {
    type Key = AddressWithSpace;
    type Value = AccountEntry;

    fn as_hash_map(&self) -> &HashMap<Self::Key, CheckpointEntry<Self::Value>> {
        &self.entries
    }

    fn as_hash_map_mut(
        &mut self,
    ) -> &mut HashMap<Self::Key, CheckpointEntry<Self::Value>> {
        &mut self.entries
    }
}

impl State {
    /// Create a recoverable checkpoint of this state. Return the checkpoint
    /// index. The checkpoint records any old value which is alive at the
    /// creation time of the checkpoint and updated after that and before
    /// the creation of the next checkpoint.
    pub(crate) fn checkpoint(&mut self) -> usize {
        self.checkpoints.get_mut().push_checkpoint(CheckpointLayer {
            global_stat: self.global_stat,
            entries: HashMap::new(),
        })
    }

    /// Merge last checkpoint with previous.
    pub(crate) fn discard_checkpoint(&mut self) {
        let cleared_addresses =
            unwrap_or_return!(self.checkpoints.get_mut().discard_checkpoint());

        // if there is no checkpoint in state, the state's checkpoints are
        // cleared directly, thus, the accounts in state's cache should
        // all discard all checkpoints
        for addr in cleared_addresses {
            if let Some(AccountEntry::Cached(ref mut overlay_account, true)) =
                self.cache.get_mut().get_mut(&addr)
            {
                overlay_account.clear_checkpoint();
            }
        }
    }

    /// Revert to the last checkpoint and discard it.
    pub(crate) fn revert_to_checkpoint(&mut self) {
        for (layer_id, reverted_layer) in
            unwrap_or_return!(self.checkpoints.get_mut().revert_to_checkpoint())
        {
            self.global_stat = reverted_layer.global_stat;
            apply_checkpoint_layer_to_cache(
                reverted_layer.entries,
                self.cache.get_mut(),
                layer_id,
            );
        }
    }

    pub fn no_checkpoint(&self) -> bool { self.checkpoints.read().is_empty() }

    /// Insert a new overlay account to cache and incoroprating the old version
    /// to the checkpoint in needed.
    pub(super) fn insert_to_cache(&mut self, account: OverlayAccount) {
        let address = *account.address();
        let old_account_entry = self
            .cache
            .get_mut()
            .insert(address, AccountEntry::new_dirty(account));

        self.checkpoints
            .get_mut()
            .insert_element(address, move |_| {
                AccountCheckpointEntry::from_cache(old_account_entry)
            });
    }

    /// The caller has changed (or will change) an account in cache and notify
    /// this function to incoroprates the old version to the checkpoint in
    /// needed.
    pub(super) fn copy_cache_entry_to_checkpoint(
        &self, address: AddressWithSpace, entry_in_cache: &mut AccountEntry,
    ) {
        self.checkpoints
            .write()
            .insert_element(address, |checkpoint_id| {
                let mut new_entry_in_cache = entry_in_cache
                    .clone_cache_entry_for_checkpoint(checkpoint_id);

                std::mem::swap(&mut new_entry_in_cache, entry_in_cache);
                // Rename after memswap
                let old_entry_in_cache = new_entry_in_cache;

                Recorded(old_entry_in_cache)
            });
    }

    #[cfg(any(test, feature = "testonly_code"))]
    pub fn clear(&mut self) {
        assert!(self.no_checkpoint());
        self.cache.get_mut().clear();
        self.global_stat = GlobalStat::loaded(&self.db).expect("no db error");
    }
}

fn apply_checkpoint_layer_to_cache(
    entries: HashMap<AddressWithSpace, AccountCheckpointEntry>,
    cache: &mut HashMap<AddressWithSpace, AccountEntry>, checkpoint_id: usize,
) {
    for (k, v) in entries.into_iter() {
        let mut entry_in_cache = if let Occupied(e) = cache.entry(k) {
            e
        } else {
            // All the entries in checkpoint must be copied from cache by the
            // following function `insert_to_cache` and `clone_to_checkpoint`.
            //
            // A cache entries will never be removed, except it is revert to an
            // `Unchanged` checkpoint. If this exceptional case happens, this
            // entry has never be loaded or written during transaction execution
            // (regardless the reverted operations), and thus cannot have keys
            // in the checkpoint.

            unreachable!("Cache should always have more keys than checkpoint");
        };
        match v {
            Recorded(entry_in_checkpoint) => {
                if let Some(acc) = entry_in_cache.get_mut().dirty_account_mut()
                {
                    acc.revert_checkpoint(checkpoint_id);
                }
                *entry_in_cache.get_mut() = entry_in_checkpoint;
            }
            Unchanged => {
                // If the AccountEntry in cache does not have a dirty bit, we
                // can keep it in cache to avoid an duplicate db load.
                if entry_in_cache.get().is_dirty() {
                    entry_in_cache.remove();
                }
            }
        }
    }
}