Skip to content

Commit 180ce94

Browse files
committed
feat: allow for accountsdb checksum computation
1 parent ff7a295 commit 180ce94

File tree

8 files changed

+202
-84
lines changed

8 files changed

+202
-84
lines changed

Cargo.lock

Lines changed: 8 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ serde_json = "1.0"
140140
serde_with = "3.16"
141141
serial_test = "3.2"
142142
sha3 = "0.10.8"
143-
solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "6eae52b" }
143+
solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "3ff1b2ea" }
144144
solana-account-decoder = { version = "2.2" }
145145
solana-account-decoder-client-types = { version = "2.2" }
146146
solana-account-info = { version = "2.2" }
@@ -211,6 +211,10 @@ tonic-build = "0.9.2"
211211
tracing = "0.1"
212212
tracing-log = { version = "0.2", features = ["log-tracer"] }
213213
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
214+
twox-hash = { version = "2.1", default-features = false, features = [
215+
"xxhash3_64",
216+
"alloc",
217+
] }
214218
url = "2.5.0"
215219

216220
# SPL Token crates used across the workspace
@@ -231,7 +235,7 @@ version = "0.22.0"
231235
# some solana dependencies have solana-storage-proto as dependency
232236
# we need to patch them with our version, because they use protobuf-src v1.1.0
233237
# and we use protobuf-src v2.1.1. Otherwise compilation fails
234-
solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "6eae52b" }
238+
solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "3ff1b2ea" }
235239
solana-storage-proto = { path = "./storage-proto" }
236240
solana-svm = { git = "https://github.com/magicblock-labs/magicblock-svm.git", rev = "bdbaac86" }
237241
# Fork is used to enable `disable_manual_compaction` usage

magicblock-accounts-db/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ thiserror = { workspace = true }
2323
tracing = { workspace = true }
2424
magicblock-config = { workspace = true }
2525

26+
# misc
27+
twox-hash = { workspace = true }
28+
2629
[dev-dependencies]
2730
tempfile = { workspace = true }
2831
tracing-subscriber = { workspace = true }

magicblock-accounts-db/src/lib.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{fs, path::Path, sync::Arc, thread};
1+
use std::{fs, hash::Hasher, path::Path, sync::Arc, thread};
22

33
use error::{AccountsDbError, LogErr};
44
use index::{
@@ -13,6 +13,7 @@ use solana_account::{
1313
use solana_pubkey::Pubkey;
1414
use storage::AccountsStorage;
1515
use tracing::{error, info, warn};
16+
use twox_hash::xxhash3_64;
1617

1718
use crate::{snapshot::SnapshotManager, traits::AccountsBank};
1819

@@ -374,6 +375,26 @@ impl AccountsDb {
374375
pub fn write_lock(&self) -> GlobalSyncLock {
375376
self.write_lock.clone()
376377
}
378+
379+
/// Computes a deterministic checksum of all active accounts.
380+
///
381+
/// Iterates all accounts in key-sorted order (via LMDB) and hashes both
382+
/// pubkey and serialized account data using xxHash3. Returns a 64-bit hash
383+
/// suitable for verifying state consistency across nodes.
384+
///
385+
/// Acquires the write lock to ensure a consistent snapshot of the state.
386+
pub fn checksum(&self) -> u64 {
387+
let _locked = self.write_lock.write();
388+
let mut hasher = xxhash3_64::Hasher::new();
389+
for (pubkey, acc) in self.iter_all() {
390+
let Some(borrowed) = acc.as_borrowed() else {
391+
continue;
392+
};
393+
hasher.write(pubkey.as_ref());
394+
hasher.write(borrowed.buffer());
395+
}
396+
hasher.finish()
397+
}
377398
}
378399

379400
impl AccountsBank for AccountsDb {

magicblock-accounts-db/src/storage.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -371,10 +371,9 @@ impl AccountsStorage {
371371
Ok(())
372372
}
373373

374-
/// Returns the total expected size of the file in bytes (Header + Data).
374+
/// Returns the total occupied size of the storage file in bytes.
375375
pub(crate) fn size_bytes(&self) -> u64 {
376-
(self.header().capacity_blocks as u64 * self.block_size as u64)
377-
+ METADATA_STORAGE_SIZE as u64
376+
self.active_segment().len() as u64
378377
}
379378

380379
pub(crate) fn block_size(&self) -> usize {

magicblock-accounts-db/src/tests.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,63 @@ fn test_database_reset() {
464464
assert_eq!(adb_reset.account_count(), 0);
465465
}
466466

467+
#[test]
468+
fn test_checksum_deterministic_across_dbs() {
469+
// Two independent DBs with identical accounts must produce identical checksums
470+
let dir1 = tempfile::tempdir().unwrap();
471+
let dir2 = tempfile::tempdir().unwrap();
472+
let config = AccountsDbConfig::default();
473+
474+
let db1 = AccountsDb::new(&config, dir1.path(), 0).unwrap();
475+
let db2 = AccountsDb::new(&config, dir2.path(), 0).unwrap();
476+
477+
// Insert same accounts into both DBs
478+
for i in 0..50 {
479+
let pubkey = Pubkey::new_unique();
480+
let mut account = AccountSharedData::new(LAMPORTS, SPACE, &OWNER);
481+
account.data_as_mut_slice()[..8].copy_from_slice(&(i as u64).to_le_bytes());
482+
db1.insert_account(&pubkey, &account).unwrap();
483+
db2.insert_account(&pubkey, &account).unwrap();
484+
}
485+
486+
assert_eq!(db1.checksum(), db2.checksum(), "checksums must match for identical state");
487+
}
488+
489+
#[test]
490+
fn test_checksum_detects_state_change() {
491+
let env = TestEnv::new();
492+
493+
// Create initial state
494+
let mut accounts: Vec<_> = (0..20)
495+
.map(|_| {
496+
let acc = env.create_and_insert_account();
497+
(acc.pubkey, acc.account)
498+
})
499+
.collect();
500+
501+
let original_checksum = env.checksum();
502+
503+
// Modify a single account's data
504+
accounts[5].1.data_as_mut_slice()[0] ^= 0xFF;
505+
env.insert_account(&accounts[5].0, &accounts[5].1).unwrap();
506+
507+
assert_ne!(
508+
env.checksum(),
509+
original_checksum,
510+
"checksum must detect single account modification"
511+
);
512+
513+
// Modify lamports on a different account
514+
accounts[10].1.set_lamports(1_000_000);
515+
env.insert_account(&accounts[10].0, &accounts[10].1).unwrap();
516+
517+
assert_ne!(
518+
env.checksum(),
519+
original_checksum,
520+
"checksum must detect lamport change"
521+
);
522+
}
523+
467524
// ==============================================================
468525
// TEST UTILITIES
469526
// ==============================================================

0 commit comments

Comments
 (0)