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 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
//! 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, AccountEntryWithWarm, GlobalStat, OverlayAccount, State,
};
use crate::{state::checkpoints::CheckpointLayerTrait, unwrap_or_return};
pub(super) type AccountCheckpointEntry = CheckpointEntry<AccountEntryWithWarm>;
/// 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 = AccountEntryWithWarm;
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, _dirty)) =
self.cache.get_mut().get_mut(&addr).map(|x| &mut x.entry)
{
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).with_warm(true));
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 AccountEntryWithWarm,
) {
self.checkpoints
.write()
.insert_element(address, |checkpoint_id| {
let mut new_entry_in_cache = entry_in_cache
.entry
.clone_cache_entry_for_checkpoint(checkpoint_id)
.with_warm(true);
std::mem::swap(&mut new_entry_in_cache, entry_in_cache);
// Rename after memswap
let entry_to_checkpoint = new_entry_in_cache;
Recorded(entry_to_checkpoint)
});
// If there is no checkpoint, the above function will not be executed,
// so we need to mark warm bit here.
entry_in_cache.warm = true;
}
#[cfg(any(test, feature = "testonly_code"))]
pub fn clear(&mut self) {
assert!(self.no_checkpoint());
self.cache.get_mut().clear();
self.committed_cache.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, AccountEntryWithWarm>,
checkpoint_id: usize,
) {
for (k, v) in entries.into_iter() {
let Occupied(mut entry_in_cache) = cache.entry(k) 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().account_mut() {
acc.revert_checkpoint(checkpoint_id);
}
*entry_in_cache.get_mut() = entry_in_checkpoint;
}
Unchanged => {
// Here we have an optimization: if a checkpoint entry was
// Unchanged and the corresponding cache entry
// was clean (i.e., its dirty bit was not set),
// the cache entry would be retained rather than
// removed by checkpoint semantics.
//
// A theoretical concern arose: if a CheckpointEntry is
// Unchanged, its cache entry is clean, and the
// account is absent from the database (DbAbsent), the output of
// `State::code_hash` might differ with and
// without this optimization.
//
// However, this scenario is impossible under the current
// implementation. In this system, DbAbsent and
// Recorded() are treated as distinct values, and transitioning
// from one to the other would require the dirty flag to be set.
if entry_in_cache.get().entry.is_dirty() {
entry_in_cache.remove();
} else {
entry_in_cache.get_mut().warm = false;
}
}
}
}
}