use crate::{FastHashMap, FastHashSet};
use anchor_lang::prelude::sysvar::SysvarId;
use anchor_lang::prelude::{Clock, EpochSchedule, SlotHashes, SlotHistory, StakeHistory};
use litesvm::LiteSVM;
use rustc_hash::FxHasher;
use solana_account::{Account, ReadableAccount};
use solana_pubkey::Pubkey;
use solana_sysvar::epoch_rewards::EpochRewards;
use solana_sysvar::fees::Fees;
use solana_sysvar::last_restart_slot::LastRestartSlot;
use solana_sysvar::recent_blockhashes::RecentBlockhashes;
use solana_sysvar::rent::Rent;
use std::collections::HashSet;
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use super::dirty_tracker::DirtyTracker;
#[derive(Clone)]
pub struct SvmSnapshot {
pub(crate) accounts: FastHashMap<Pubkey, Arc<Account>>,
pub sysvars: Vec<(Pubkey, Option<Account>)>,
}
impl SvmSnapshot {
pub fn take(svm: &LiteSVM, tracked_accounts: &HashSet<Pubkey>) -> Self {
let mut accounts = FastHashMap::default();
accounts.reserve(tracked_accounts.len());
for pubkey in tracked_accounts {
if let Some(account) = svm.get_account(pubkey) {
accounts.insert(*pubkey, Arc::new(account));
}
}
let sysvars = Self::take_sysvars(svm);
Self { accounts, sysvars }
}
pub fn restore(&self, svm: &mut LiteSVM, dirty: &DirtyTracker) -> usize {
let mut count = 0;
for pubkey in dirty.dirty_accounts() {
match self.accounts.get(pubkey) {
Some(original) => {
let _ = svm.set_account(*pubkey, (**original).clone());
}
None => {
if let Some(existing) = svm.get_account(pubkey) {
if existing.executable {
continue;
}
}
let _ = svm.set_account(
*pubkey,
Account {
lamports: 0,
..Default::default()
},
);
}
}
count += 1;
}
self.restore_sysvars(svm);
count
}
pub fn account_count(&self) -> usize {
self.accounts.len()
}
pub fn estimated_heap_bytes(&self) -> usize {
let map_overhead = self.accounts.len() * 80;
let account_data: usize = self
.accounts
.values()
.map(|a| std::mem::size_of::<Account>() + a.data.len())
.sum();
let sysvar_data: usize = self
.sysvars
.iter()
.map(|(_, opt)| match opt {
Some(a) => std::mem::size_of::<Account>() + a.data.len(),
None => 0,
})
.sum();
map_overhead + account_data + sysvar_data
}
pub fn take_full(svm: &LiteSVM, base_snapshot: &SvmSnapshot, dirty: &DirtyTracker) -> Self {
let mut accounts = base_snapshot.accounts.clone();
for pubkey in dirty.dirty_accounts() {
if let Some(account) = svm.get_account(pubkey) {
accounts.insert(*pubkey, Arc::new(account));
} else {
accounts.remove(pubkey);
}
}
let sysvars = Self::take_sysvars(svm);
Self { accounts, sysvars }
}
pub fn take_all(svm: &LiteSVM) -> Self {
let db = svm.accounts_db();
let mut accounts = FastHashMap::default();
accounts.reserve(db.inner.len());
for (pubkey, _) in &db.inner {
if let Some(account) = svm.get_account(pubkey) {
accounts.insert(*pubkey, Arc::new(account));
}
}
let sysvars = Self::take_sysvars(svm);
Self { accounts, sysvars }
}
pub fn restore_full(&self, svm: &mut LiteSVM) -> usize {
for (pubkey, account) in &self.accounts {
let _ = svm.set_account(*pubkey, (**account).clone());
}
self.restore_sysvars(svm);
self.accounts.len()
}
pub fn restore_selective(
&self,
svm: &mut LiteSVM,
divergent_keys: &FastHashSet<Pubkey>,
delta: &SvmSnapshot,
) -> usize {
let mut count = self.restore_divergent_to_initial(svm, divergent_keys, &delta.accounts);
for (pubkey, account) in &delta.accounts {
let _ = svm.set_account(*pubkey, (**account).clone());
count += 1;
}
delta.restore_sysvars(svm);
count
}
pub fn restore_selective_from(
&self,
svm: &mut LiteSVM,
divergent_keys: &FastHashSet<Pubkey>,
prev_delta: &SvmSnapshot,
next_delta: &SvmSnapshot,
prev_exec_dirty: &FastHashSet<Pubkey>,
) -> usize {
let mut count =
self.restore_divergent_to_initial(svm, divergent_keys, &next_delta.accounts);
for (pubkey, next_acct) in &next_delta.accounts {
if !prev_exec_dirty.contains(pubkey) {
if let Some(prev_acct) = prev_delta.accounts.get(pubkey) {
if Arc::ptr_eq(prev_acct, next_acct) {
continue;
}
}
}
let _ = svm.set_account(*pubkey, (**next_acct).clone());
count += 1;
}
next_delta.restore_sysvars(svm);
count
}
fn restore_divergent_to_initial(
&self,
svm: &mut LiteSVM,
divergent_keys: &FastHashSet<Pubkey>,
delta_keys: &FastHashMap<Pubkey, Arc<Account>>,
) -> usize {
let mut count = 0;
for pubkey in divergent_keys {
if delta_keys.contains_key(pubkey) {
continue; }
if let Some(initial_account) = self.accounts.get(pubkey) {
let _ = svm.set_account(*pubkey, (**initial_account).clone());
} else {
if let Some(existing) = svm.get_account(pubkey) {
if existing.executable {
continue;
}
}
let _ = svm.set_account(
*pubkey,
Account {
lamports: 0,
..Default::default()
},
);
}
count += 1;
}
count
}
pub fn accounts(&self) -> &FastHashMap<Pubkey, Arc<Account>> {
&self.accounts
}
pub fn hash_tracked_state(&self, svm: &LiteSVM) -> u64 {
use rustc_hash::FxHasher;
use std::hash::{Hash, Hasher};
let mut hasher = FxHasher::default();
let mut keys: Vec<_> = self.accounts.keys().copied().collect();
keys.sort();
for pk in &keys {
pk.hash(&mut hasher);
if let Some(acct) = svm.get_account(pk) {
acct.lamports.hash(&mut hasher);
acct.data.hash(&mut hasher);
acct.owner.hash(&mut hasher);
} else {
0u8.hash(&mut hasher);
}
}
hasher.finish()
}
pub fn clock(&self) -> Clock {
let clock_id = Clock::id();
for (id, account) in &self.sysvars {
if id == &clock_id {
if let Some(acct) = account {
if let Ok(clock) = bincode::deserialize::<Clock>(&acct.data) {
return clock;
}
}
}
}
Clock::default()
}
pub fn tombstone() -> Self {
Self {
accounts: FastHashMap::default(),
sysvars: Vec::new(),
}
}
pub fn empty(clock: Clock) -> Self {
let clock_data = bincode::serialize(&clock).unwrap_or_default();
Self {
accounts: FastHashMap::default(),
sysvars: vec![(
Clock::id(),
Some(Account {
lamports: 1,
data: clock_data,
owner: Pubkey::from_str_const("Sysvar1111111111111111111111111111111111111"),
executable: false,
rent_epoch: 0,
}),
)],
}
}
pub fn take_delta(svm: &LiteSVM, parent_delta: &SvmSnapshot, dirty: &DirtyTracker) -> Self {
let mut accounts = parent_delta.accounts.clone();
for pk in dirty.dirty_accounts() {
match svm.get_account(pk) {
Some(acct) => {
accounts.insert(*pk, Arc::new(acct));
}
None => {
accounts.insert(
*pk,
Arc::new(Account {
lamports: 0,
..Default::default()
}),
);
}
}
}
let sysvars = Self::take_sysvars(svm);
Self { accounts, sysvars }
}
pub fn take_delta_from_changed(
svm: &LiteSVM,
changed_accounts: FastHashMap<Pubkey, Arc<Account>>,
) -> Self {
let sysvars = Self::take_sysvars(svm);
Self {
accounts: changed_accounts,
sysvars,
}
}
fn take_sysvars(svm: &LiteSVM) -> Vec<(Pubkey, Option<Account>)> {
[
Clock::id(),
EpochRewards::id(),
Fees::id(),
LastRestartSlot::id(),
RecentBlockhashes::id(),
Rent::id(),
StakeHistory::id(),
]
.iter()
.map(|sysvar| (*sysvar, svm.get_account(sysvar)))
.collect()
}
fn restore_sysvars(&self, svm: &mut LiteSVM) -> usize {
for (sysvar, account) in &self.sysvars {
if let Some(acct) = account {
let _ = svm.set_account(*sysvar, acct.clone());
} else {
let _ = svm.set_account(
*sysvar,
Account {
lamports: 0,
..Default::default()
},
);
}
}
self.sysvars.len()
}
}
#[derive(Clone, Debug)]
pub enum AccountPatch {
Full(Arc<Account>),
Diff {
lamports: u64,
patches: Vec<(u32, u64)>,
},
}
impl AccountPatch {
pub fn estimated_heap_bytes(&self) -> usize {
match self {
AccountPatch::Full(arc) => std::mem::size_of::<Account>() + arc.data.len(),
AccountPatch::Diff { patches, .. } => patches.len() * 12, }
}
pub fn is_diff(&self) -> bool {
matches!(self, AccountPatch::Diff { .. })
}
pub fn reconstruct(&self, initial_account: Option<&Account>) -> Option<Account> {
match self {
AccountPatch::Full(arc) => Some((**arc).clone()),
AccountPatch::Diff { lamports, patches } => {
let init = initial_account?;
let mut acct = init.clone();
acct.lamports = *lamports;
let data = &mut acct.data;
for &(word_idx, val) in patches {
let offset = word_idx as usize * 8;
let end = (offset + 8).min(data.len());
let bytes = val.to_le_bytes();
data[offset..end].copy_from_slice(&bytes[..end - offset]);
}
Some(acct)
}
}
}
}
#[derive(Clone)]
pub struct CompactDelta {
pub(crate) accounts: FastHashMap<Pubkey, AccountPatch>,
pub(crate) sysvars: Vec<(Pubkey, Option<Account>)>,
}
impl CompactDelta {
pub fn tombstone() -> Self {
Self {
accounts: FastHashMap::default(),
sysvars: Vec::new(),
}
}
pub fn empty(clock: Clock) -> Self {
let clock_data = bincode::serialize(&clock).unwrap_or_default();
Self {
accounts: FastHashMap::default(),
sysvars: vec![(
Clock::id(),
Some(Account {
lamports: 1,
data: clock_data,
owner: Pubkey::from_str_const("Sysvar1111111111111111111111111111111111111"),
executable: false,
rent_epoch: 0,
}),
)],
}
}
pub fn from_changed(
initial: &SvmSnapshot,
changed: FastHashMap<Pubkey, Arc<Account>>,
svm: &LiteSVM,
) -> Self {
let mut accounts = FastHashMap::default();
accounts.reserve(changed.len());
for (pk, arc_acct) in changed {
let patch = match initial.accounts.get(&pk) {
Some(init_arc)
if init_arc.data.len() == arc_acct.data.len()
&& init_arc.owner == arc_acct.owner
&& init_arc.executable == arc_acct.executable =>
{
let init_data = &init_arc.data;
let cur_data = &arc_acct.data;
let mut patches = Vec::new();
let num_full_words = cur_data.len() / 8;
for wi in 0..num_full_words {
let off = wi * 8;
let init_word =
u64::from_le_bytes(init_data[off..off + 8].try_into().unwrap());
let cur_word =
u64::from_le_bytes(cur_data[off..off + 8].try_into().unwrap());
if init_word != cur_word {
patches.push((wi as u32, cur_word));
}
}
let tail_start = num_full_words * 8;
if tail_start < cur_data.len() {
let mut init_tail = [0u8; 8];
let mut cur_tail = [0u8; 8];
let tail_len = cur_data.len() - tail_start;
init_tail[..tail_len].copy_from_slice(&init_data[tail_start..]);
cur_tail[..tail_len].copy_from_slice(&cur_data[tail_start..]);
if init_tail != cur_tail {
patches.push((num_full_words as u32, u64::from_le_bytes(cur_tail)));
}
}
AccountPatch::Diff {
lamports: arc_acct.lamports,
patches,
}
}
_ => {
AccountPatch::Full(arc_acct)
}
};
accounts.insert(pk, patch);
}
let sysvars = SvmSnapshot::take_sysvars(svm);
Self { accounts, sysvars }
}
pub fn from_dirty(
initial: &SvmSnapshot,
svm: &LiteSVM,
dirty_accounts: &FastHashSet<Pubkey>,
) -> Self {
let mut changed: FastHashMap<Pubkey, Arc<Account>> = FastHashMap::default();
for pk in dirty_accounts {
match svm.get_account(pk) {
Some(acct) => {
changed.insert(*pk, Arc::new(acct));
}
None => {
changed.insert(
*pk,
Arc::new(Account {
lamports: 0,
..Default::default()
}),
);
}
}
}
Self::from_changed(initial, changed, svm)
}
pub fn from_patches(svm: &LiteSVM, patches: FastHashMap<Pubkey, AccountPatch>) -> Self {
let sysvars = SvmSnapshot::take_sysvars(svm);
Self {
accounts: patches,
sysvars,
}
}
pub fn child_delta(
parent: &CompactDelta,
new_patches: FastHashMap<Pubkey, AccountPatch>,
dirty_accounts: &FastHashSet<Pubkey>,
svm: &LiteSVM,
) -> Self {
Self::child_delta_inner(parent, new_patches, dirty_accounts, svm)
}
pub fn child_delta_from(
parent: &CompactDelta,
exec_delta: CompactDelta,
dirty_accounts: &FastHashSet<Pubkey>,
svm: &LiteSVM,
) -> Self {
Self::child_delta_inner(parent, exec_delta.accounts, dirty_accounts, svm)
}
fn child_delta_inner(
parent: &CompactDelta,
new_patches: FastHashMap<Pubkey, AccountPatch>,
dirty_accounts: &FastHashSet<Pubkey>,
svm: &LiteSVM,
) -> Self {
let mut accounts = FastHashMap::default();
accounts.reserve(parent.accounts.len() + new_patches.len());
for (pk, patch) in &parent.accounts {
if !dirty_accounts.contains(pk) {
accounts.insert(*pk, patch.clone());
}
}
for (pk, patch) in new_patches {
accounts.insert(pk, patch);
}
let sysvars = SvmSnapshot::take_sysvars(svm);
Self { accounts, sysvars }
}
pub fn keys(&self) -> impl Iterator<Item = &Pubkey> {
self.accounts.keys()
}
pub fn account_count(&self) -> usize {
self.accounts.len()
}
pub fn estimated_heap_bytes(&self) -> usize {
let map_overhead = self.accounts.len() * 80;
let patch_data: usize = self
.accounts
.values()
.map(|p| p.estimated_heap_bytes())
.sum();
let sysvar_data: usize = self
.sysvars
.iter()
.map(|(_, opt)| match opt {
Some(a) => std::mem::size_of::<Account>() + a.data.len(),
None => 0,
})
.sum();
map_overhead + patch_data + sysvar_data
}
pub fn clock(&self) -> Clock {
let clock_id = Clock::id();
for (id, account) in &self.sysvars {
if id == &clock_id {
if let Some(acct) = account {
if let Ok(clock) = bincode::deserialize::<Clock>(&acct.data) {
return clock;
}
}
}
}
Clock::default()
}
pub fn reconstruct_account(&self, initial: &SvmSnapshot, pubkey: &Pubkey) -> Option<Account> {
let patch = self.accounts.get(pubkey)?;
patch.reconstruct(initial.accounts.get(pubkey).map(|a| a.as_ref()))
}
pub fn restore_selective(
&self,
initial: &SvmSnapshot,
svm: &mut LiteSVM,
divergent_keys: &FastHashSet<Pubkey>,
) -> usize {
let mut count = 0;
for pubkey in divergent_keys {
if self.accounts.contains_key(pubkey) {
continue; }
match initial.accounts.get(pubkey) {
Some(original) => {
let _ = svm.set_account(*pubkey, (**original).clone());
}
None => {
if let Some(existing) = svm.get_account(pubkey) {
if existing.executable {
continue;
}
}
let _ = svm.set_account(
*pubkey,
Account {
lamports: 0,
..Default::default()
},
);
}
}
count += 1;
}
for (pubkey, patch) in &self.accounts {
match patch {
AccountPatch::Full(arc) => {
let _ = svm.set_account(*pubkey, (**arc).clone());
}
AccountPatch::Diff { lamports, patches } => {
if let Some(init_acct) = initial.accounts.get(pubkey) {
let mut acct = (**init_acct).clone();
acct.lamports = *lamports;
let data = &mut acct.data;
for &(word_idx, val) in patches {
let offset = word_idx as usize * 8;
let end = (offset + 8).min(data.len());
let bytes = val.to_le_bytes();
data[offset..end].copy_from_slice(&bytes[..end - offset]);
}
let _ = svm.set_account(*pubkey, acct);
}
}
}
count += 1;
}
self.restore_sysvars(svm);
count
}
pub fn restore_selective_from(
&self,
initial: &SvmSnapshot,
svm: &mut LiteSVM,
divergent_keys: &FastHashSet<Pubkey>,
prev_delta: &CompactDelta,
prev_exec_dirty: &FastHashSet<Pubkey>,
) -> usize {
let mut count = 0;
for pubkey in divergent_keys {
if self.accounts.contains_key(pubkey) {
continue;
}
match initial.accounts.get(pubkey) {
Some(original) => {
let _ = svm.set_account(*pubkey, (**original).clone());
}
None => {
if let Some(existing) = svm.get_account(pubkey) {
if existing.executable {
continue;
}
}
let _ = svm.set_account(
*pubkey,
Account {
lamports: 0,
..Default::default()
},
);
}
}
count += 1;
}
for (pubkey, next_patch) in &self.accounts {
if !prev_exec_dirty.contains(pubkey) {
if let Some(prev_patch) = prev_delta.accounts.get(pubkey) {
if patches_equal(prev_patch, next_patch) {
continue; }
}
}
match next_patch {
AccountPatch::Full(arc) => {
let _ = svm.set_account(*pubkey, (**arc).clone());
}
AccountPatch::Diff { lamports, patches } => {
if let Some(init_acct) = initial.accounts.get(pubkey) {
let mut acct = (**init_acct).clone();
acct.lamports = *lamports;
let data = &mut acct.data;
for &(word_idx, val) in patches {
let offset = word_idx as usize * 8;
let end = (offset + 8).min(data.len());
let bytes = val.to_le_bytes();
data[offset..end].copy_from_slice(&bytes[..end - offset]);
}
let _ = svm.set_account(*pubkey, acct);
}
}
}
count += 1;
}
self.restore_sysvars(svm);
count
}
pub fn accounts_report_info(&self) -> Vec<(String, usize)> {
self.accounts
.iter()
.map(|(pk, patch)| {
let pk_str = pk.to_string();
let short = format!(
"{}..{}",
&pk_str[..4],
&pk_str[pk_str.len().saturating_sub(4)..]
);
let bytes = patch.estimated_heap_bytes();
(short, bytes)
})
.collect()
}
pub fn sysvar_data_bytes(&self) -> usize {
self.sysvars
.iter()
.map(|(_, opt)| opt.as_ref().map(|a| a.data.len()).unwrap_or(0))
.sum()
}
fn restore_sysvars(&self, svm: &mut LiteSVM) {
for (sysvar, account) in &self.sysvars {
if let Some(acct) = account {
let _ = svm.set_account(*sysvar, acct.clone());
} else {
let _ = svm.set_account(
*sysvar,
Account {
lamports: 0,
..Default::default()
},
);
}
}
}
}
fn patches_equal(a: &AccountPatch, b: &AccountPatch) -> bool {
match (a, b) {
(AccountPatch::Full(a_arc), AccountPatch::Full(b_arc)) => Arc::ptr_eq(a_arc, b_arc),
(
AccountPatch::Diff {
lamports: la,
patches: pa,
},
AccountPatch::Diff {
lamports: lb,
patches: pb,
},
) => la == lb && pa == pb,
_ => false,
}
}
#[inline]
pub fn value_bucket(val: u64) -> u8 {
match val {
0 => 0,
1 => 1,
2..=9 => 2,
10..=99 => 3,
100..=999 => 4,
1_000..=9_999 => 5,
10_000..=99_999 => 6,
100_000..=999_999 => 7,
1_000_000..=9_999_999 => 8,
10_000_000..=99_999_999 => 9,
100_000_000..=999_999_999 => 10,
1_000_000_000..=9_999_999_999 => 11,
10_000_000_000..=99_999_999_999 => 12,
100_000_000_000..=999_999_999_999 => 13,
1_000_000_000_000..=9_999_999_999_999 => 14,
_ => 15,
}
}
#[inline]
pub fn slot_bucket(val: u64) -> u8 {
match val {
0 => 0,
1..=9 => val as u8, 10..=99 => 9 + (val / 10) as u8, 100..=999 => 18 + (val / 100) as u8, 1_000..=9_999 => 27 + (val / 1_000) as u8, 10_000..=99_999 => 36 + (val / 10_000) as u8, 100_000..=999_999 => 45 + (val / 100_000) as u8, 1_000_000..=9_999_999 => 54 + (val / 1_000_000) as u8, _ => 64, }
}
#[inline]
pub fn slot_diff_bucket(diff: u64) -> u8 {
match diff {
0..=9 => diff as u8, 10..=99 => 9 + (diff / 10) as u8, 100..=999 => 18 + (diff / 100) as u8, 1_000..=9_999 => 27 + (diff / 1_000) as u8, 10_000..=99_999 => 36 + (diff / 10_000) as u8, 100_000..=999_999 => 45 + (diff / 100_000) as u8, _ => 55, }
}
pub(super) const FINGERPRINT_BITS: u32 = 18;
pub fn compute_state_fingerprint_from_snapshot(
svm: &LiteSVM,
dirty: &DirtyTracker,
initial: &SvmSnapshot,
) -> u64 {
let mut hasher = FxHasher::default();
let clock: Clock = svm.get_sysvar();
let initial_clock = initial.clock();
let slot_diff = clock.slot.saturating_sub(initial_clock.slot);
slot_diff_bucket(slot_diff).hash(&mut hasher);
clock.epoch.hash(&mut hasher);
if let Some(target) = dirty.clock_target_slot {
target.hash(&mut hasher);
}
if dirty.dirty_accounts().is_empty() {
return hasher.finish();
}
let mut sorted_dirty: Vec<&Pubkey> = dirty.dirty_accounts().iter().collect();
sorted_dirty.sort();
(sorted_dirty.len() as u64).hash(&mut hasher);
for pubkey in &sorted_dirty {
let account = svm.get_account(pubkey);
let lamports = account.as_ref().map(|a| a.lamports).unwrap_or(0);
let data = account.as_ref().map(|a| a.data.as_slice()).unwrap_or(&[]);
pubkey.hash(&mut hasher);
value_bucket(lamports).hash(&mut hasher);
value_bucket(data.len() as u64).hash(&mut hasher);
let init_data = initial
.accounts
.get(pubkey)
.map(|a| a.data.as_slice())
.unwrap_or(&[]);
let min_len = data.len().min(init_data.len());
let mut i = 0usize;
while i < min_len {
if data[i] != init_data[i] {
let start = i;
while i < min_len && data[i] != init_data[i] {
i += 1;
}
let val = read_region_value(&data[start..i]);
(start as u32, value_bucket(val)).hash(&mut hasher);
} else {
i += 1;
}
}
if data.len() > min_len {
let mut i = min_len;
while i < data.len() {
if data[i] != 0 {
let start = i;
while i < data.len() && data[i] != 0 {
i += 1;
}
let val = read_region_value(&data[start..i]);
(start as u32, value_bucket(val)).hash(&mut hasher);
} else {
i += 1;
}
}
}
if data.len() != init_data.len() {
(min_len as u32, value_bucket(data.len() as u64)).hash(&mut hasher);
}
}
hasher.finish()
}
pub unsafe fn fingerprint_and_collect_changed(
svm: &LiteSVM,
dirty: &DirtyTracker,
initial: &SvmSnapshot,
field_bitmap_ptr: *mut u8,
field_bitmap_len: usize,
) -> (u64, FastHashMap<Pubkey, AccountPatch>, u32) {
let do_field_novelty = !field_bitmap_ptr.is_null();
let total_bits = if do_field_novelty {
field_bitmap_len * 8
} else {
0
};
let mut field_novel_count: u32 = 0;
let mut hasher = FxHasher::default();
let mut changed: FastHashMap<Pubkey, AccountPatch> = FastHashMap::default();
let clock: Clock = svm.get_sysvar();
let initial_clock = initial.clock();
let slot_diff = clock.slot.saturating_sub(initial_clock.slot);
let sdb = slot_diff_bucket(slot_diff);
slot_diff_bucket(slot_diff).hash(&mut hasher);
clock.epoch.hash(&mut hasher);
if let Some(target) = dirty.clock_target_slot {
target.hash(&mut hasher);
}
if do_field_novelty && (slot_diff > 0 || clock.epoch != initial_clock.epoch) {
field_novel_count += check_and_set_bit_atomic(
field_bitmap_ptr,
total_bits,
field_hash(CLOCK_TYPE_KEY, 0, sdb),
);
field_novel_count += check_and_set_bit_atomic(
field_bitmap_ptr,
total_bits,
field_hash(CLOCK_TYPE_KEY, 1, value_bucket(clock.epoch)),
);
if let Some(target) = dirty.clock_target_slot {
field_novel_count += check_and_set_bit_atomic(
field_bitmap_ptr,
total_bits,
field_hash(CLOCK_TYPE_KEY, 2, slot_bucket(target)),
);
}
}
if dirty.dirty_accounts().is_empty() {
return (hasher.finish(), changed, field_novel_count);
}
let mut sorted_dirty: Vec<&Pubkey> = dirty.dirty_accounts().iter().collect();
sorted_dirty.sort();
(sorted_dirty.len() as u64).hash(&mut hasher);
for pubkey in &sorted_dirty {
let account = svm.get_account(pubkey);
let lamports = account.as_ref().map(|a| a.lamports).unwrap_or(0);
let data = account.as_ref().map(|a| a.data.as_slice()).unwrap_or(&[]);
pubkey.hash(&mut hasher);
value_bucket(lamports).hash(&mut hasher);
value_bucket(data.len() as u64).hash(&mut hasher);
let init_account = initial.accounts.get(pubkey);
let init_data = init_account.map(|a| a.data.as_slice()).unwrap_or(&[]);
let init_lamports = init_account.map(|a| a.lamports).unwrap_or(0);
let same_size = data.len() == init_data.len();
let type_key = pubkey_key(pubkey);
if do_field_novelty {
if slot_diff > 0 {
field_novel_count += check_and_set_bit_atomic(
field_bitmap_ptr,
total_bits,
field_hash(type_key, LAMPORTS_SENTINEL - 1, sdb),
);
}
field_novel_count += check_and_set_bit_atomic(
field_bitmap_ptr,
total_bits,
field_hash(type_key, LAMPORTS_SENTINEL, value_bucket(lamports)),
);
}
let min_len = data.len().min(init_data.len());
let mut any_diff = false;
let mut i = 0usize;
while i < min_len {
if data[i] != init_data[i] {
any_diff = true;
let start = i;
while i < min_len && data[i] != init_data[i] {
i += 1;
}
let val = read_region_value(&data[start..i]);
let vb = value_bucket(val);
(start as u32, vb).hash(&mut hasher);
if do_field_novelty {
field_novel_count += check_and_set_bit_atomic(
field_bitmap_ptr,
total_bits,
field_hash(type_key, start as u32, vb),
);
}
} else {
i += 1;
}
}
if data.len() > min_len {
any_diff = true;
let mut i = min_len;
while i < data.len() {
if data[i] != 0 {
let start = i;
while i < data.len() && data[i] != 0 {
i += 1;
}
let val = read_region_value(&data[start..i]);
let vb = value_bucket(val);
(start as u32, vb).hash(&mut hasher);
if do_field_novelty {
field_novel_count += check_and_set_bit_atomic(
field_bitmap_ptr,
total_bits,
field_hash(type_key, start as u32, vb),
);
}
} else {
i += 1;
}
}
}
if !same_size {
any_diff = true;
(min_len as u32, value_bucket(data.len() as u64)).hash(&mut hasher);
if do_field_novelty {
field_novel_count += check_and_set_bit_atomic(
field_bitmap_ptr,
total_bits,
field_hash(type_key, min_len as u32, value_bucket(data.len() as u64)),
);
}
}
if lamports != init_lamports {
any_diff = true;
}
let cur_owner = account.as_ref().map(|a| a.owner).unwrap_or_default();
let init_owner = init_account.map(|a| a.owner).unwrap_or_default();
let cur_executable = account.as_ref().map(|a| a.executable).unwrap_or(false);
let init_executable = init_account.map(|a| a.executable).unwrap_or(false);
let metadata_changed = cur_owner != init_owner || cur_executable != init_executable;
if metadata_changed {
any_diff = true;
}
if do_field_novelty {
let mut id_hasher = FxHasher::default();
pubkey.hash(&mut id_hasher);
value_bucket(lamports).hash(&mut id_hasher);
value_bucket(data.len() as u64).hash(&mut id_hasher);
field_novel_count +=
check_and_set_bit_atomic(field_bitmap_ptr, total_bits, id_hasher.finish());
if slot_diff > 0 {
let mut id_clock_hasher = FxHasher::default();
pubkey.hash(&mut id_clock_hasher);
value_bucket(lamports).hash(&mut id_clock_hasher);
sdb.hash(&mut id_clock_hasher);
field_novel_count += check_and_set_bit_atomic(
field_bitmap_ptr,
total_bits,
id_clock_hasher.finish(),
);
}
}
if any_diff {
let patch = if same_size && init_account.is_some() && !metadata_changed {
let mut patches = Vec::new();
let num_full_words = data.len() / 8;
for wi in 0..num_full_words {
let off = wi * 8;
let init_word = u64::from_le_bytes(init_data[off..off + 8].try_into().unwrap());
let cur_word = u64::from_le_bytes(data[off..off + 8].try_into().unwrap());
if init_word != cur_word {
patches.push((wi as u32, cur_word));
}
}
let tail_start = num_full_words * 8;
if tail_start < data.len() {
let mut init_tail = [0u8; 8];
let mut cur_tail = [0u8; 8];
let tail_len = data.len() - tail_start;
init_tail[..tail_len].copy_from_slice(&init_data[tail_start..]);
cur_tail[..tail_len].copy_from_slice(&data[tail_start..]);
if init_tail != cur_tail {
patches.push((num_full_words as u32, u64::from_le_bytes(cur_tail)));
}
}
Some(AccountPatch::Diff { lamports, patches })
} else {
None };
if let Some(patch) = patch {
changed.insert(**pubkey, patch);
} else if let Some(acct) = account {
changed.insert(**pubkey, AccountPatch::Full(Arc::new(acct)));
} else {
changed.insert(
**pubkey,
AccountPatch::Full(Arc::new(Account {
lamports: 0,
..Default::default()
})),
);
}
}
}
if do_field_novelty && sorted_dirty.len() > 1 {
let mut set_hasher = FxHasher::default();
for pk in &sorted_dirty {
pk.hash(&mut set_hasher);
}
field_novel_count +=
check_and_set_bit_atomic(field_bitmap_ptr, total_bits, set_hasher.finish());
if slot_diff > 0 {
let mut set_clock_hasher = FxHasher::default();
for pk in &sorted_dirty {
pk.hash(&mut set_clock_hasher);
}
sdb.hash(&mut set_clock_hasher);
field_novel_count +=
check_and_set_bit_atomic(field_bitmap_ptr, total_bits, set_clock_hasher.finish());
}
}
(hasher.finish(), changed, field_novel_count)
}
#[inline]
fn pubkey_key(pubkey: &Pubkey) -> u64 {
u64::from_le_bytes(pubkey.to_bytes()[0..8].try_into().unwrap())
}
pub const FIELD_NOVELTY_BITMAP_SIZE: usize = 1 << 17;
const LAMPORTS_SENTINEL: u32 = u32::MAX;
const CLOCK_TYPE_KEY: u64 = u64::MAX - 1;
#[inline]
fn read_region_value(region: &[u8]) -> u64 {
match region.len() {
0 => 0,
1 => region[0] as u64,
2 => u16::from_le_bytes(region[0..2].try_into().unwrap()) as u64,
3..=4 => {
let mut buf = [0u8; 4];
buf[..region.len()].copy_from_slice(region);
u32::from_le_bytes(buf) as u64
}
5..=8 => {
let mut buf = [0u8; 8];
buf[..region.len()].copy_from_slice(region);
u64::from_le_bytes(buf)
}
_ => {
let mut h = FxHasher::default();
region.hash(&mut h);
h.finish()
}
}
}
#[inline]
fn field_hash(type_key: u64, offset: u32, bucket: u8) -> u64 {
let mut h = FxHasher::default();
type_key.hash(&mut h);
offset.hash(&mut h);
bucket.hash(&mut h);
h.finish()
}
#[inline]
unsafe fn check_and_set_bit_atomic(bitmap_ptr: *mut u8, total_bits: usize, hash: u64) -> u32 {
use std::sync::atomic::{AtomicU8, Ordering};
let idx = hash as usize % total_bits;
let byte_ptr = bitmap_ptr.add(idx / 8) as *const AtomicU8;
let bit_mask = 1u8 << (idx % 8);
let prev = (*byte_ptr).fetch_or(bit_mask, Ordering::Relaxed);
if prev & bit_mask == 0 {
1
} else {
0
}
}
pub unsafe fn check_field_novelty(
svm: &LiteSVM,
dirty: &super::DirtyTracker,
initial: &SvmSnapshot,
bitmap_ptr: *mut u8,
bitmap_len: usize,
) -> u32 {
debug_assert_eq!(
bitmap_len, FIELD_NOVELTY_BITMAP_SIZE,
"check_field_novelty: bitmap_len ({}) must equal FIELD_NOVELTY_BITMAP_SIZE ({})",
bitmap_len, FIELD_NOVELTY_BITMAP_SIZE
);
let mut novel_count: u32 = 0;
let total_bits = bitmap_len * 8;
let clock: Clock = svm.get_sysvar();
let initial_clock = initial.clock();
let slot_diff = clock.slot.saturating_sub(initial_clock.slot);
let sdb = slot_diff_bucket(slot_diff);
if slot_diff > 0 || clock.epoch != initial_clock.epoch {
novel_count +=
check_and_set_bit_atomic(bitmap_ptr, total_bits, field_hash(CLOCK_TYPE_KEY, 0, sdb));
novel_count += check_and_set_bit_atomic(
bitmap_ptr,
total_bits,
field_hash(CLOCK_TYPE_KEY, 1, value_bucket(clock.epoch)),
);
if let Some(target) = dirty.clock_target_slot {
novel_count += check_and_set_bit_atomic(
bitmap_ptr,
total_bits,
field_hash(CLOCK_TYPE_KEY, 2, slot_bucket(target)),
);
}
}
let mut __dirty_pubkeys: Vec<&Pubkey> = Vec::with_capacity(dirty.dirty_accounts().len());
for pubkey in dirty.dirty_accounts() {
let account = svm.get_account(pubkey);
let cur_data = account.as_ref().map(|a| a.data.as_slice()).unwrap_or(&[]);
let cur_lamports = account.as_ref().map(|a| a.lamports).unwrap_or(0);
let type_key = pubkey_key(pubkey);
__dirty_pubkeys.push(pubkey);
if slot_diff > 0 {
novel_count += check_and_set_bit_atomic(
bitmap_ptr,
total_bits,
field_hash(type_key, LAMPORTS_SENTINEL - 1, sdb),
);
}
novel_count += check_and_set_bit_atomic(
bitmap_ptr,
total_bits,
field_hash(type_key, LAMPORTS_SENTINEL, value_bucket(cur_lamports)),
);
let init_data = initial
.accounts
.get(pubkey)
.map(|a| a.data.as_slice())
.unwrap_or(&[]);
let min_len = cur_data.len().min(init_data.len());
let mut i = 0usize;
while i < min_len {
if cur_data[i] != init_data[i] {
let start = i;
while i < min_len && cur_data[i] != init_data[i] {
i += 1;
}
let val = read_region_value(&cur_data[start..i]);
novel_count += check_and_set_bit_atomic(
bitmap_ptr,
total_bits,
field_hash(type_key, start as u32, value_bucket(val)),
);
} else {
i += 1;
}
}
if cur_data.len() > min_len {
let mut i = min_len;
while i < cur_data.len() {
if cur_data[i] != 0 {
let start = i;
while i < cur_data.len() && cur_data[i] != 0 {
i += 1;
}
let val = read_region_value(&cur_data[start..i]);
novel_count += check_and_set_bit_atomic(
bitmap_ptr,
total_bits,
field_hash(type_key, start as u32, value_bucket(val)),
);
} else {
i += 1;
}
}
}
if cur_data.len() != init_data.len() {
novel_count += check_and_set_bit_atomic(
bitmap_ptr,
total_bits,
field_hash(
type_key,
min_len as u32,
value_bucket(cur_data.len() as u64),
),
);
}
{
let mut id_hasher = FxHasher::default();
pubkey.hash(&mut id_hasher);
value_bucket(cur_lamports).hash(&mut id_hasher);
value_bucket(cur_data.len() as u64).hash(&mut id_hasher);
novel_count += check_and_set_bit_atomic(bitmap_ptr, total_bits, id_hasher.finish());
}
if slot_diff > 0 {
let mut id_clock_hasher = FxHasher::default();
pubkey.hash(&mut id_clock_hasher);
value_bucket(cur_lamports).hash(&mut id_clock_hasher);
sdb.hash(&mut id_clock_hasher);
novel_count +=
check_and_set_bit_atomic(bitmap_ptr, total_bits, id_clock_hasher.finish());
}
}
if __dirty_pubkeys.len() > 1 {
__dirty_pubkeys.sort();
let mut set_hasher = FxHasher::default();
for pk in &__dirty_pubkeys {
pk.hash(&mut set_hasher);
}
novel_count += check_and_set_bit_atomic(bitmap_ptr, total_bits, set_hasher.finish());
if slot_diff > 0 {
let mut set_clock_hasher = FxHasher::default();
for pk in &__dirty_pubkeys {
pk.hash(&mut set_clock_hasher);
}
sdb.hash(&mut set_clock_hasher);
novel_count +=
check_and_set_bit_atomic(bitmap_ptr, total_bits, set_clock_hasher.finish());
}
}
novel_count
}