@mainsail/evm 0.0.1-evm.52 → 0.0.1-evm.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/core/src/db.rs ADDED
@@ -0,0 +1,3311 @@
1
+ use std::{
2
+ borrow::Cow,
3
+ cell::RefCell,
4
+ cmp::Ordering,
5
+ collections::BTreeMap,
6
+ convert::Infallible,
7
+ path::PathBuf,
8
+ sync::{Arc, LazyLock, RwLock, RwLockReadGuard},
9
+ };
10
+
11
+ use alloy_primitives::Bloom;
12
+ use heed::{Comparator, EnvFlags, EnvOpenOptions};
13
+ use revm::{
14
+ Database, DatabaseRef,
15
+ context::{DBErrorMarker, result::ExecutionResult},
16
+ database::{CacheState, TransitionAccount, TransitionState},
17
+ primitives::*,
18
+ state::{AccountInfo, Bytecode},
19
+ };
20
+ use serde::{Deserialize, Serialize};
21
+
22
+ use crate::{
23
+ account::{AccountInfoExtended, StoredAccountInfo},
24
+ bytecode::StoredBytecode,
25
+ compression::CompactBincode,
26
+ historical::{AccountHistory, HistoricalAccountData},
27
+ legacy::{LegacyAccountAttributes, LegacyAddress, LegacyColdWallet},
28
+ logger::{LogLevel, Logger},
29
+ receipt::{TxReceipt, map_execution_result},
30
+ state_changes,
31
+ state_commit::StateCommit,
32
+ };
33
+
34
+ #[derive(Debug)]
35
+ pub(crate) struct AddressWrapper(Address);
36
+ impl heed::BytesEncode<'_> for AddressWrapper {
37
+ type EItem = AddressWrapper;
38
+
39
+ fn bytes_encode(item: &Self::EItem) -> Result<Cow<'_, [u8]>, heed::BoxedError> {
40
+ Ok(Cow::Borrowed(item.0.as_slice()))
41
+ }
42
+ }
43
+
44
+ impl heed::BytesDecode<'_> for AddressWrapper {
45
+ type DItem = AddressWrapper;
46
+
47
+ fn bytes_decode(bytes: &'_ [u8]) -> Result<Self::DItem, heed::BoxedError> {
48
+ Ok(AddressWrapper(Address::from_slice(bytes)))
49
+ }
50
+ }
51
+
52
+ #[derive(Debug)]
53
+ pub(crate) struct LegacyAddressWrapper(LegacyAddress);
54
+ impl heed::BytesEncode<'_> for LegacyAddressWrapper {
55
+ type EItem = LegacyAddressWrapper;
56
+
57
+ fn bytes_encode(item: &Self::EItem) -> Result<Cow<'_, [u8]>, heed::BoxedError> {
58
+ Ok(Cow::Borrowed(item.0.as_slice()))
59
+ }
60
+ }
61
+
62
+ impl heed::BytesDecode<'_> for LegacyAddressWrapper {
63
+ type DItem = LegacyAddressWrapper;
64
+
65
+ fn bytes_decode(bytes: &'_ [u8]) -> Result<Self::DItem, heed::BoxedError> {
66
+ Ok(LegacyAddressWrapper(LegacyAddress::from_slice(bytes)))
67
+ }
68
+ }
69
+
70
+ #[derive(Debug)]
71
+ pub(crate) struct HashWrapper(B256);
72
+ impl heed::BytesEncode<'_> for HashWrapper {
73
+ type EItem = HashWrapper;
74
+
75
+ fn bytes_encode(item: &Self::EItem) -> Result<Cow<'_, [u8]>, heed::BoxedError> {
76
+ Ok(Cow::Borrowed(item.0.as_slice()))
77
+ }
78
+ }
79
+
80
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
81
+ pub struct TransactionKey {
82
+ pub block_number: u64,
83
+ pub index: u16,
84
+ }
85
+
86
+ impl TransactionKey {
87
+ pub fn new(block_number: u64, index: u16) -> Self {
88
+ Self {
89
+ block_number,
90
+ index,
91
+ }
92
+ }
93
+
94
+ // Parses the "<block_number>-<index>" token exchanged across the napi boundary.
95
+ pub fn parse(token: &str) -> Option<Self> {
96
+ let (block_number, index) = token.split_once('-')?;
97
+ Some(Self {
98
+ block_number: block_number.parse().ok()?,
99
+ index: index.parse().ok()?,
100
+ })
101
+ }
102
+
103
+ pub fn to_token(&self) -> String {
104
+ format!("{}-{}", self.block_number, self.index)
105
+ }
106
+ }
107
+
108
+ impl heed::BytesEncode<'_> for TransactionKey {
109
+ type EItem = TransactionKey;
110
+
111
+ fn bytes_encode(item: &Self::EItem) -> Result<Cow<'_, [u8]>, heed::BoxedError> {
112
+ let mut buffer = Vec::with_capacity(10);
113
+ buffer.extend_from_slice(&item.block_number.to_be_bytes());
114
+ buffer.extend_from_slice(&item.index.to_be_bytes());
115
+ Ok(Cow::Owned(buffer))
116
+ }
117
+ }
118
+
119
+ impl heed::BytesDecode<'_> for TransactionKey {
120
+ type DItem = TransactionKey;
121
+
122
+ fn bytes_decode(bytes: &'_ [u8]) -> Result<Self::DItem, heed::BoxedError> {
123
+ let Some((block_number, rest)) = bytes.split_first_chunk::<8>() else {
124
+ return Err("TransactionKey: truncated key".into());
125
+ };
126
+ let Some((index, _)) = rest.split_first_chunk::<2>() else {
127
+ return Err("TransactionKey: truncated key".into());
128
+ };
129
+
130
+ Ok(TransactionKey {
131
+ block_number: u64::from_be_bytes(*block_number),
132
+ index: u16::from_be_bytes(*index),
133
+ })
134
+ }
135
+ }
136
+
137
+ #[derive(Debug)]
138
+ pub(crate) struct StaticStringWrapper(&'static str);
139
+ impl heed::BytesEncode<'_> for StaticStringWrapper {
140
+ type EItem = StaticStringWrapper;
141
+
142
+ fn bytes_encode(item: &Self::EItem) -> Result<Cow<'_, [u8]>, heed::BoxedError> {
143
+ Ok(Cow::Borrowed(item.0.as_bytes()))
144
+ }
145
+ }
146
+
147
+ type HeedBlockNumber = heed::types::U64<heed::byteorder::BigEndian>;
148
+
149
+ #[derive(Debug)]
150
+ pub(crate) struct StorageEntryWrapper(U256, U256);
151
+ impl heed::BytesEncode<'_> for StorageEntryWrapper {
152
+ type EItem = StorageEntryWrapper;
153
+
154
+ fn bytes_encode(item: &Self::EItem) -> Result<Cow<'_, [u8]>, heed::BoxedError> {
155
+ let a = item.0.as_le_bytes();
156
+ let b = item.1.as_le_bytes();
157
+
158
+ let mut combined = Vec::with_capacity(a.len() + b.len());
159
+ combined.extend_from_slice(a.as_ref());
160
+ combined.extend_from_slice(b.as_ref());
161
+
162
+ Ok(Cow::Owned(combined))
163
+ }
164
+ }
165
+
166
+ impl heed::BytesDecode<'_> for StorageEntryWrapper {
167
+ type DItem = StorageEntryWrapper;
168
+
169
+ fn bytes_decode(bytes: &'_ [u8]) -> Result<Self::DItem, heed::BoxedError> {
170
+ let a = U256::from_le_slice(&bytes[0..32]);
171
+ let b = U256::from_le_slice(&bytes[32..]);
172
+ Ok(StorageEntryWrapper(a, b))
173
+ }
174
+ }
175
+
176
+ pub enum StorageEntryDupSortCmp {}
177
+
178
+ impl Comparator for StorageEntryDupSortCmp {
179
+ fn compare(a: &[u8], b: &[u8]) -> Ordering {
180
+ // The compared values are tuples of `StorageEntry` and sorted by the first tuple value (=32 byte)
181
+ // which corresponds to the storage slot location. The second half of the tuple is ignored.
182
+ a[..32].cmp(&b[..32])
183
+ }
184
+ }
185
+
186
+ // txHash -> receipt
187
+ #[derive(Default, Debug, Serialize, Deserialize)]
188
+ pub(crate) struct CommitReceipts {
189
+ tx_receipts: HashMap<B256, TxReceipt>,
190
+ }
191
+
192
+ pub(crate) struct InnerStorage {
193
+ pub accounts: heed::Database<AddressWrapper, CompactBincode<StoredAccountInfo>>,
194
+ pub accounts_history: Option<
195
+ heed::Database<HeedBlockNumber, CompactBincode<BTreeMap<Address, HistoricalAccountData>>>,
196
+ >,
197
+ pub commits: heed::Database<HeedBlockNumber, CompactBincode<CommitReceipts>>,
198
+ pub contracts: heed::Database<HashWrapper, CompactBincode<StoredBytecode>>,
199
+ pub legacy_attributes: heed::Database<AddressWrapper, CompactBincode<LegacyAccountAttributes>>,
200
+ pub legacy_cold_wallets: heed::Database<LegacyAddressWrapper, CompactBincode<LegacyColdWallet>>,
201
+ pub storage: heed::Database<
202
+ AddressWrapper,
203
+ StorageEntryWrapper,
204
+ heed::DefaultComparator,
205
+ StorageEntryDupSortCmp,
206
+ >,
207
+ // Carried over from previous database-service.ts lmdb backend
208
+ pub state: heed::Database<StaticStringWrapper, heed::types::SerdeBincode<Bytes>>,
209
+ pub proofs: heed::Database<HeedBlockNumber, CompactBincode<ProofData>>,
210
+ pub blocks: heed::Database<HeedBlockNumber, CompactBincode<BlockHeaderData>>,
211
+ pub blocks_hash_number: heed::Database<HashWrapper, HeedBlockNumber>,
212
+ pub transactions: heed::Database<TransactionKey, CompactBincode<TransactionData>>,
213
+ pub transactions_hash_key: heed::Database<HashWrapper, TransactionKey>,
214
+ //
215
+ }
216
+
217
+ // A key of (block_number, round, block_hash) used to associate state with a processable unit.
218
+ #[derive(Hash, PartialEq, Eq, Debug, Default, Clone, Copy)]
219
+ pub struct CommitKey(pub u64, pub u64, pub B256);
220
+
221
+ pub type BlsSig = revm::primitives::FixedBytes<96>;
222
+
223
+ #[derive(Default, Debug, Deserialize, Serialize, PartialEq, Eq)]
224
+ pub struct ProofData {
225
+ pub round: u32,
226
+ pub signature: BlsSig,
227
+ pub validator_set: u128,
228
+ }
229
+
230
+ #[derive(Default, Debug, Deserialize, Serialize, PartialEq, Eq)]
231
+ pub struct BlockHeaderData {
232
+ pub version: u8,
233
+ pub timestamp: u64,
234
+ pub number: u32,
235
+ pub round: u32,
236
+ pub hash: B256,
237
+ pub parent_hash: B256,
238
+ pub state_root: B256,
239
+ pub logs_bloom: Bloom,
240
+ pub transactions_root: B256,
241
+ pub transactions_count: u16,
242
+ pub gas_used: u32,
243
+ pub fee: U256,
244
+ pub reward: U256,
245
+ pub payload_size: u32,
246
+ pub proposer: Address,
247
+ }
248
+
249
+ #[derive(Default, Debug, Deserialize, Serialize, PartialEq, Eq)]
250
+ pub struct TransactionData {
251
+ pub from: Address,
252
+ pub sender_public_key: String,
253
+ pub legacy_address: Option<LegacyAddress>,
254
+ pub to: Option<Address>,
255
+ pub gas_limit: u64,
256
+ pub gas_price: u128,
257
+ pub value: U256,
258
+ pub nonce: u64,
259
+ pub data: Bytes,
260
+ pub v: u32,
261
+ pub r: U256,
262
+ pub s: U256,
263
+ pub legacy_second_signature: Option<String>,
264
+ pub tx_hash: B256,
265
+ pub block_number: u32,
266
+ pub index: u32,
267
+ }
268
+
269
+ #[derive(Default, PartialEq, Eq)]
270
+ pub struct CommitData {
271
+ pub proof: ProofData,
272
+ pub header: BlockHeaderData,
273
+ pub transactions: Vec<TransactionData>,
274
+ }
275
+
276
+ #[derive(Clone, Debug, Default)]
277
+ pub struct PendingCommit {
278
+ pub key: CommitKey,
279
+ pub cache: CacheState,
280
+ pub results: BTreeMap<B256, (ExecutionResult, u64)>,
281
+ pub transitions: TransitionState,
282
+
283
+ pub cumulative_gas_used: u64,
284
+
285
+ // Map of legacy attributes
286
+ pub legacy_attributes: BTreeMap<Address, LegacyAccountAttributes>,
287
+
288
+ // Map of legacy cold wallets
289
+ pub legacy_cold_wallets: BTreeMap<LegacyAddress, LegacyColdWallet>,
290
+
291
+ // Keeps track of all merged legacy cold wallets in this commit;
292
+ // If an address is found in the map, then a lookup for presence of cold wallet has been performed.
293
+ // The option indicates whether a corresponding cold wallet has been found and merged. To avoid
294
+ // redundant lookups, any address present in the map is skipped when processing a transaction.
295
+ pub merged_legacy_cold_wallets: BTreeMap<Address, Option<(B256, LegacyAddress)>>,
296
+
297
+ // Optimization to avoid unnecessary (deep) clones of commit data.
298
+ pub built_commit: Option<StateCommit>,
299
+ }
300
+
301
+ #[derive(Clone, Debug, Default, Serialize, PartialEq, Eq)]
302
+ pub struct GenesisInfo {
303
+ pub account: Address,
304
+ pub deployer_account: Address,
305
+ pub validator_contract: Address,
306
+ pub username_contract: Address,
307
+ pub initial_block_number: u64,
308
+ pub initial_supply: U256,
309
+ }
310
+
311
+ pub struct PersistentDB {
312
+ pub(crate) env: heed::Env,
313
+ pub(crate) inner: RefCell<InnerStorage>,
314
+ pub(crate) accounts_history: Option<AccountHistory>,
315
+ resize_lock: Arc<RwLock<()>>,
316
+ logger: Logger,
317
+ pub genesis_info: Option<GenesisInfo>,
318
+ }
319
+
320
+ #[derive(Default)]
321
+ pub struct PersistentDBOptions {
322
+ pub path: PathBuf,
323
+ pub logger: Option<Logger>,
324
+ pub history_size: Option<u64>,
325
+ }
326
+
327
+ impl PersistentDBOptions {
328
+ pub fn new(path: PathBuf) -> Self {
329
+ Self {
330
+ path,
331
+ ..Default::default()
332
+ }
333
+ }
334
+
335
+ pub fn with_logger(mut self, logger: Logger) -> Self {
336
+ self.logger.replace(logger);
337
+ self
338
+ }
339
+
340
+ pub fn with_history_size(mut self, history_size: u64) -> Self {
341
+ self.history_size.replace(history_size);
342
+ self
343
+ }
344
+ }
345
+
346
+ #[derive(thiserror::Error, Debug)]
347
+ pub enum Error {
348
+ #[error("IO error: {0}")]
349
+ IO(#[from] std::io::Error),
350
+ #[error("BytecodeDecode error: {0}")]
351
+ BytecodeDecode(#[from] revm::bytecode::BytecodeDecodeError),
352
+ #[error("heed error: {0}")]
353
+ Heed(#[from] heed::Error),
354
+ #[error("state error: {0}")]
355
+ State(String),
356
+ #[error("db full error")]
357
+ DbFull,
358
+ #[error("bincode error: {0}")]
359
+ Bincode(#[from] bincode::Error),
360
+ #[error("infallible error: {0}")]
361
+ Infallible(#[from] Infallible),
362
+ #[error("Lock error")]
363
+ Lock,
364
+ }
365
+
366
+ impl DBErrorMarker for Error {}
367
+
368
+ static ENV: LazyLock<RwLock<HashMap<PathBuf, (heed::Env, Arc<RwLock<()>>)>>> =
369
+ LazyLock::new(RwLock::default);
370
+
371
+ impl PersistentDB {
372
+ const MAX_DBS: u32 = 12;
373
+
374
+ pub fn new(opts: PersistentDBOptions) -> Result<Self, Error> {
375
+ std::fs::create_dir_all(&opts.path)?;
376
+
377
+ let mut lock = ENV.write().map_err(|_| Error::Lock)?;
378
+
379
+ let (env, resize_lock) = match lock.get(&opts.path) {
380
+ Some((env, resize_lock)) => (env.clone(), resize_lock.clone()),
381
+ None => {
382
+ let mut env_builder = EnvOpenOptions::new();
383
+
384
+ let mut max_dbs = Self::MAX_DBS;
385
+ if opts.history_size.is_some() {
386
+ max_dbs += 1;
387
+ }
388
+
389
+ env_builder.max_dbs(max_dbs);
390
+ env_builder.map_size(1 * MAP_SIZE_UNIT);
391
+ unsafe { env_builder.flags(EnvFlags::NO_SUB_DIR) };
392
+
393
+ let env = unsafe { env_builder.open(opts.path.join("evm.mdb")) }?;
394
+ // One resize gate per env, shared by every instance for this path.
395
+ let resize_lock = Arc::new(RwLock::new(()));
396
+ lock.insert(opts.path.clone(), (env.clone(), resize_lock.clone()));
397
+
398
+ (env, resize_lock)
399
+ }
400
+ };
401
+
402
+ Self::new_with_env(env, resize_lock, opts)
403
+ }
404
+
405
+ pub fn new_with_env(
406
+ env: heed::Env,
407
+ resize_lock: Arc<RwLock<()>>,
408
+ opts: PersistentDBOptions,
409
+ ) -> Result<Self, Error> {
410
+ let real_disk_size = env.real_disk_size()?;
411
+ if real_disk_size >= env.info().map_size as u64 {
412
+ // Ensure initial map size is always larger than disk size. Resize requires exclusive
413
+ // access to the (possibly shared) env, so take the write side of the gate.
414
+ let _resize_guard = resize_lock.write().map_err(|_| Error::Lock)?;
415
+ unsafe { env.resize(next_map_size(real_disk_size as usize))? };
416
+ }
417
+
418
+ // Database creation is a write txn; hold the read side so a concurrent resize on the
419
+ // shared env cannot remap memory underneath it. Dropped right after commit, before
420
+ // `resize_lock` is moved into the struct.
421
+ let init_guard = resize_lock.read().map_err(|_| Error::Lock)?;
422
+
423
+ let tx_env = env.clone();
424
+ let mut wtxn = tx_env.write_txn()?;
425
+
426
+ let accounts = env.create_database::<AddressWrapper, CompactBincode<StoredAccountInfo>>(
427
+ &mut wtxn,
428
+ Some("accounts"),
429
+ )?;
430
+
431
+ let (accounts_history_db, accounts_history) = match opts.history_size {
432
+ Some(history_size) if history_size > 0 => {
433
+ let db = env.create_database::<HeedBlockNumber,CompactBincode<
434
+ BTreeMap<Address, HistoricalAccountData>>>(&mut wtxn, Some("accounts_history")) ?;
435
+ (Some(db), Some(AccountHistory::new(history_size)))
436
+ }
437
+ _ => (None, None),
438
+ };
439
+
440
+ let commits = env.create_database::<HeedBlockNumber, CompactBincode<CommitReceipts>>(
441
+ &mut wtxn,
442
+ Some("commits"),
443
+ )?;
444
+ let contracts = env.create_database::<HashWrapper, CompactBincode<StoredBytecode>>(
445
+ &mut wtxn,
446
+ Some("contracts"),
447
+ )?;
448
+ let legacy_attributes = env
449
+ .create_database::<AddressWrapper, CompactBincode<LegacyAccountAttributes>>(
450
+ &mut wtxn,
451
+ Some("legacy_attributes"),
452
+ )?;
453
+ let legacy_cold_wallets = env
454
+ .create_database::<LegacyAddressWrapper, CompactBincode<LegacyColdWallet>>(
455
+ &mut wtxn,
456
+ Some("legacy_cold_wallets"),
457
+ )?;
458
+ let storage = env
459
+ .database_options()
460
+ .types::<AddressWrapper, StorageEntryWrapper>()
461
+ .name("storage")
462
+ .flags(heed::DatabaseFlags::DUP_SORT)
463
+ .dup_sort_comparator::<StorageEntryDupSortCmp>()
464
+ .create(&mut wtxn)?;
465
+
466
+ // Carried over from previous database-service.ts lmdb backend
467
+ let state = env.create_database::<StaticStringWrapper, heed::types::SerdeBincode<Bytes>>(
468
+ &mut wtxn,
469
+ Some("state"),
470
+ )?;
471
+ let proofs = env.create_database::<HeedBlockNumber, CompactBincode<ProofData>>(
472
+ &mut wtxn,
473
+ Some("proofs"),
474
+ )?;
475
+ let blocks = env.create_database::<HeedBlockNumber, CompactBincode<BlockHeaderData>>(
476
+ &mut wtxn,
477
+ Some("blocks"),
478
+ )?;
479
+ let blocks_hash_number = env.create_database::<HashWrapper, HeedBlockNumber>(
480
+ &mut wtxn,
481
+ Some("blocks_hash_number"),
482
+ )?;
483
+ let transactions = env.create_database::<TransactionKey, CompactBincode<TransactionData>>(
484
+ &mut wtxn,
485
+ Some("transactions"),
486
+ )?;
487
+ let transactions_hash_key = env.create_database::<HashWrapper, TransactionKey>(
488
+ &mut wtxn,
489
+ Some("transactions_hash_key"),
490
+ )?;
491
+
492
+ wtxn.commit()?;
493
+ drop(init_guard);
494
+
495
+ Ok(Self {
496
+ env,
497
+ inner: RefCell::new(InnerStorage {
498
+ accounts,
499
+ accounts_history: accounts_history_db,
500
+ commits,
501
+ contracts,
502
+ legacy_attributes,
503
+ legacy_cold_wallets,
504
+ storage,
505
+ state,
506
+ blocks_hash_number,
507
+ blocks,
508
+ proofs,
509
+ transactions_hash_key,
510
+ transactions,
511
+ }),
512
+ accounts_history,
513
+ resize_lock,
514
+ logger: opts.logger.unwrap_or_default(),
515
+ genesis_info: None,
516
+ })
517
+ }
518
+
519
+ pub fn set_genesis_info(&mut self, genesis_info: GenesisInfo) -> Result<(), Error> {
520
+ self.with_write_txn(|wtxn| {
521
+ let inner = self.inner.borrow_mut();
522
+
523
+ if inner
524
+ .accounts
525
+ .get(wtxn, &AddressWrapper(genesis_info.account))?
526
+ .is_none()
527
+ {
528
+ inner.accounts.put(
529
+ wtxn,
530
+ &AddressWrapper(genesis_info.account),
531
+ &CompactBincode(&StoredAccountInfo::new(
532
+ genesis_info.initial_supply,
533
+ 0,
534
+ KECCAK_EMPTY,
535
+ )),
536
+ )?;
537
+ }
538
+
539
+ Ok(())
540
+ })?;
541
+
542
+ self.genesis_info.replace(genesis_info);
543
+ Ok(())
544
+ }
545
+
546
+ pub fn get_accounts(
547
+ &self,
548
+ offset: u64,
549
+ limit: u64,
550
+ ) -> Result<(Option<u64>, Vec<AccountInfoExtended>), Error> {
551
+ self.with_read_txn(|tx_env| {
552
+ let iter = self
553
+ .inner
554
+ .borrow()
555
+ .accounts
556
+ .iter(tx_env)?
557
+ .skip(offset as usize);
558
+
559
+ let (cursor, mut accounts) = self.get_items(
560
+ iter,
561
+ |item| match item {
562
+ Some(item) => {
563
+ let (address, info) = item?;
564
+ Ok(Some(AccountInfoExtended {
565
+ address: address.0,
566
+ info: AccountInfo {
567
+ balance: info.balance,
568
+ nonce: info.nonce,
569
+ ..Default::default()
570
+ },
571
+ ..Default::default()
572
+ }))
573
+ }
574
+ None => Ok(None),
575
+ },
576
+ offset,
577
+ limit,
578
+ )?;
579
+
580
+ for account in accounts.iter_mut() {
581
+ if let Some(legacy_attributes) = self
582
+ .inner
583
+ .borrow()
584
+ .legacy_attributes
585
+ .get(tx_env, &AddressWrapper(account.address))?
586
+ {
587
+ account.legacy_attributes = legacy_attributes.0;
588
+ }
589
+ }
590
+
591
+ Ok((cursor, accounts))
592
+ })
593
+ }
594
+
595
+ pub fn get_legacy_cold_wallets(
596
+ &self,
597
+ offset: u64,
598
+ limit: u64,
599
+ ) -> Result<(Option<u64>, Vec<LegacyColdWallet>), Error> {
600
+ self.with_read_txn(|tx_env| {
601
+ let iter = self
602
+ .inner
603
+ .borrow()
604
+ .legacy_cold_wallets
605
+ .iter(tx_env)?
606
+ .skip(offset as usize);
607
+
608
+ self.get_items(
609
+ iter,
610
+ |item| match item {
611
+ Some(item) => {
612
+ let (_, legacy_cold_wallet) = item?;
613
+ Ok(Some(legacy_cold_wallet.0))
614
+ }
615
+ None => Ok(None),
616
+ },
617
+ offset,
618
+ limit,
619
+ )
620
+ })
621
+ }
622
+
623
+ pub fn get_receipts(
624
+ &self,
625
+ offset: u64,
626
+ limit: u64,
627
+ ) -> Result<(Option<u64>, Vec<(u64, Vec<(B256, TxReceipt)>)>), Error> {
628
+ self.with_read_txn(|tx_env| {
629
+ let iter = self
630
+ .inner
631
+ .borrow()
632
+ .commits
633
+ .iter(tx_env)?
634
+ .skip(offset as usize);
635
+
636
+ self.get_items(
637
+ iter,
638
+ |item| match item {
639
+ Some(item) => {
640
+ let (block_number, commit) = item?;
641
+ Ok(Some((
642
+ block_number,
643
+ commit.0.tx_receipts.into_iter().collect(),
644
+ )))
645
+ }
646
+ None => Ok(None),
647
+ },
648
+ offset,
649
+ limit,
650
+ )
651
+ })
652
+ }
653
+
654
+ pub fn get_receipts_by_block_number(
655
+ &self,
656
+ block_number: u64,
657
+ ) -> Result<HashMap<B256, TxReceipt>, Error> {
658
+ self.with_read_txn(|tx_env| {
659
+ let commit = self.inner.borrow().commits.get(tx_env, &block_number)?;
660
+
661
+ match commit {
662
+ Some(inner) => Ok(inner.0.tx_receipts),
663
+ None => Ok(Default::default()),
664
+ }
665
+ })
666
+ }
667
+
668
+ pub fn get_receipts_by_block_range(
669
+ &self,
670
+ from_block_number: u64,
671
+ to_block_number: u64,
672
+ ) -> Result<Vec<(u64, Vec<(B256, TxReceipt)>)>, Error> {
673
+ assert!(
674
+ from_block_number <= to_block_number,
675
+ "from_block_number ({from_block_number}) must be <= to_block_number ({to_block_number})"
676
+ );
677
+
678
+ self.with_read_txn(|tx_env| {
679
+ let inner = self.inner.borrow();
680
+ let range = from_block_number..=to_block_number;
681
+
682
+ let capacity = to_block_number.saturating_sub(from_block_number).min(1024) as usize;
683
+ let mut receipts = Vec::with_capacity(capacity);
684
+
685
+ for item in inner.commits.range(&tx_env, &range)? {
686
+ let (block_number, commit) = item?;
687
+ receipts.push((block_number, commit.0.tx_receipts.into_iter().collect()));
688
+ }
689
+
690
+ Ok(receipts)
691
+ })
692
+ }
693
+
694
+ pub fn get_commits_by_block_range(
695
+ &self,
696
+ from_block_number: u64,
697
+ to_block_number: u64,
698
+ max_bytes: u64,
699
+ ) -> Result<Vec<(ProofData, BlockHeaderData, Vec<TransactionData>)>, Error> {
700
+ assert!(
701
+ from_block_number <= to_block_number,
702
+ "from_block_number ({from_block_number}) must be <= to_block_number ({to_block_number})"
703
+ );
704
+ assert!(max_bytes > 0, "max_bytes ({max_bytes}) must be > 0");
705
+
706
+ // Per-commit fixed cost charged against the budget on top of the block's transaction payload,
707
+ // so that a long run of (near-)empty blocks is still bounded by block count, not just bytes.
708
+ const PER_COMMIT_OVERHEAD_BYTES: u64 = 1024;
709
+
710
+ self.with_read_txn(|tx_env| {
711
+ let inner = self.inner.borrow();
712
+
713
+ let capacity = to_block_number.saturating_sub(from_block_number).min(512) as usize;
714
+ let mut commits = Vec::with_capacity(capacity);
715
+ let mut accumulated_bytes: u64 = 0;
716
+
717
+ for item in inner
718
+ .blocks
719
+ .range(tx_env, &(from_block_number..=to_block_number))?
720
+ {
721
+ let (block_number, header) = item?;
722
+ let estimated_bytes = header.0.payload_size as u64 + PER_COMMIT_OVERHEAD_BYTES;
723
+ accumulated_bytes += estimated_bytes;
724
+ if accumulated_bytes > max_bytes {
725
+ break;
726
+ }
727
+
728
+ // Headers and proofs are written together per commit; a missing proof means the end of
729
+ // the available data has been reached.
730
+ let Some(proof) = inner.proofs.get(tx_env, &block_number)? else {
731
+ break;
732
+ };
733
+
734
+ // Collect this block's transactions via a single range scan over its key prefix; the
735
+ // keys sort by (block_number, index), so they arrive in index order.
736
+ let mut transactions = Vec::with_capacity(header.0.transactions_count as usize);
737
+ let tx_from = TransactionKey::new(block_number, 0);
738
+ let tx_to = TransactionKey::new(block_number, u16::MAX);
739
+ for tx_item in inner.transactions.range(tx_env, &(tx_from..=tx_to))? {
740
+ let (_, transaction) = tx_item?;
741
+ transactions.push(transaction.0);
742
+ }
743
+
744
+ commits.push((proof.0, header.0, transactions));
745
+ }
746
+
747
+ Ok(commits)
748
+ })
749
+ }
750
+
751
+ pub fn get_historical_account_info(
752
+ &self,
753
+ block_number: u64,
754
+ address: Address,
755
+ ) -> Result<(Option<AccountInfo>, bool), Error> {
756
+ match self.inner.borrow().accounts_history {
757
+ Some(db) => self.with_read_txn(|tx_env| match self.accounts_history.as_ref() {
758
+ Some(accounts_history) => {
759
+ let (data, missing_fallback) = accounts_history.get_by_block_and_address(
760
+ tx_env,
761
+ &db,
762
+ block_number,
763
+ &address,
764
+ )?;
765
+
766
+ match data {
767
+ Some(data) => Ok((
768
+ Some(AccountInfo {
769
+ balance: data.balance,
770
+ nonce: data.nonce,
771
+ code_hash: data.code_hash,
772
+ ..Default::default()
773
+ }),
774
+ missing_fallback,
775
+ )),
776
+ None => Ok((None, missing_fallback)),
777
+ }
778
+ }
779
+ None => Ok((None, false)),
780
+ }),
781
+ None => Ok((None, false)),
782
+ }
783
+ }
784
+
785
+ pub fn get_legacy_attributes(
786
+ &self,
787
+ address: Address,
788
+ ) -> Result<Option<LegacyAccountAttributes>, Error> {
789
+ self.with_read_txn(|tx_env| {
790
+ Ok(self
791
+ .inner
792
+ .borrow()
793
+ .legacy_attributes
794
+ .get(tx_env, &AddressWrapper(address))?
795
+ .map(|inner| inner.0))
796
+ })
797
+ }
798
+
799
+ pub fn get_legacy_cold_wallet(
800
+ &self,
801
+ address: LegacyAddress,
802
+ ) -> Result<Option<LegacyColdWallet>, Error> {
803
+ self.with_read_txn(|tx_env| {
804
+ Ok(self
805
+ .inner
806
+ .borrow()
807
+ .legacy_cold_wallets
808
+ .get(tx_env, &LegacyAddressWrapper(address))?
809
+ .map(|inner| inner.0))
810
+ })
811
+ }
812
+
813
+ pub fn resize(&self) -> Result<(), Error> {
814
+ // Exclusive access: blocks until every in-flight transaction (across all instances sharing
815
+ // this env in the process) has released its read guard, and prevents new ones from starting
816
+ // until the remap completes. This is what makes the unsafe env.resize() sound.
817
+ let _resize_guard = self.resize_lock.write().map_err(|_| Error::Lock)?;
818
+
819
+ let info = self.env.info();
820
+
821
+ let current_map_size = info.map_size;
822
+
823
+ let next_map_size = next_map_size(current_map_size);
824
+
825
+ self.logger.log(
826
+ LogLevel::Info,
827
+ format!("resizing db {} -> {}", current_map_size, next_map_size),
828
+ );
829
+
830
+ unsafe { self.env.resize(next_map_size)? };
831
+
832
+ Ok(())
833
+ }
834
+
835
+ fn get_items<T, I, F>(
836
+ &self,
837
+ mut iter: impl Iterator<Item = I>,
838
+ map: F,
839
+ offset: u64,
840
+ limit: u64,
841
+ ) -> Result<(Option<u64>, Vec<T>), Error>
842
+ where
843
+ F: Fn(Option<I>) -> Result<Option<T>, Error>,
844
+ {
845
+ let limit = limit as usize;
846
+ let mut items = Vec::with_capacity(limit);
847
+
848
+ loop {
849
+ let item = map(iter.next())?;
850
+ let Some(item) = item else {
851
+ break;
852
+ };
853
+
854
+ items.push(item);
855
+
856
+ if items.len() == limit {
857
+ break;
858
+ }
859
+ }
860
+
861
+ let next = if items.len() == limit {
862
+ // return next offset as there might be more to read
863
+ Some(offset + items.len() as u64)
864
+ } else {
865
+ None
866
+ };
867
+
868
+ Ok((next, items))
869
+ }
870
+ }
871
+
872
+ const MAP_SIZE_UNIT: usize = 1024 * 1024 * 1024; // 1 GB
873
+ fn next_map_size(map_size: usize) -> usize {
874
+ map_size / MAP_SIZE_UNIT * MAP_SIZE_UNIT + MAP_SIZE_UNIT
875
+ }
876
+
877
+ impl PersistentDB {
878
+ fn basic_ref_tx(
879
+ &self,
880
+ txn: &heed::RoTxn,
881
+ address: Address,
882
+ ) -> Result<Option<AccountInfo>, Error> {
883
+ let inner = self.inner.borrow();
884
+
885
+ let basic = inner
886
+ .accounts
887
+ .get(txn, &AddressWrapper(address))?
888
+ .map(|a| a.0.into());
889
+
890
+ Ok(basic)
891
+ }
892
+
893
+ fn code_by_hash_ref_tx(&self, txn: &heed::RoTxn, code_hash: B256) -> Result<Bytecode, Error> {
894
+ let inner = self.inner.borrow();
895
+
896
+ let contract = match inner.contracts.get(txn, &HashWrapper(code_hash))? {
897
+ Some(contract) => contract.0,
898
+ None => Default::default(),
899
+ };
900
+
901
+ Ok(contract.try_into()?)
902
+ }
903
+
904
+ fn storage_ref_tx(
905
+ &self,
906
+ txn: &heed::RoTxn,
907
+ address: Address,
908
+ index: U256,
909
+ ) -> Result<U256, Error> {
910
+ let inner = self.inner.borrow_mut();
911
+
912
+ let mut iter = inner.storage.iter(txn)?;
913
+ let location = &StorageEntryWrapper(index, U256::ZERO);
914
+
915
+ match iter.move_on_key_dup(&AddressWrapper(address), &location)? {
916
+ Some((_, value)) if value.0 == location.0 => Ok(value.1),
917
+ _ => Ok(U256::ZERO),
918
+ }
919
+ }
920
+
921
+ fn block_hash_ref_tx(&self, txn: &heed::RoTxn, number: u64) -> Result<B256, Error> {
922
+ let inner = self.inner.borrow_mut();
923
+
924
+ let data = inner.blocks.get(txn, &number)?;
925
+ match data {
926
+ Some(data) => Ok(data.hash),
927
+ None => Ok(B256::ZERO),
928
+ }
929
+ }
930
+ }
931
+
932
+ impl Database for PersistentDB {
933
+ type Error = Error;
934
+
935
+ fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
936
+ <Self as DatabaseRef>::basic_ref(self, address)
937
+ }
938
+
939
+ fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
940
+ <Self as DatabaseRef>::code_by_hash_ref(self, code_hash)
941
+ }
942
+
943
+ fn storage(&mut self, address: Address, index: U256) -> Result<U256, Self::Error> {
944
+ <Self as DatabaseRef>::storage_ref(self, address, index)
945
+ }
946
+
947
+ fn block_hash(&mut self, number: u64) -> Result<B256, Self::Error> {
948
+ <Self as DatabaseRef>::block_hash_ref(self, number)
949
+ }
950
+ }
951
+
952
+ impl DatabaseRef for PersistentDB {
953
+ type Error = Error;
954
+
955
+ fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
956
+ self.with_read_txn(|txn| self.basic_ref_tx(txn, address))
957
+ }
958
+
959
+ fn code_by_hash_ref(&self, code_hash: B256) -> Result<Bytecode, Self::Error> {
960
+ self.with_read_txn(|txn| self.code_by_hash_ref_tx(txn, code_hash))
961
+ }
962
+
963
+ fn storage_ref(&self, address: Address, index: U256) -> Result<U256, Self::Error> {
964
+ self.with_read_txn(|txn| self.storage_ref_tx(txn, address, index))
965
+ }
966
+
967
+ fn block_hash_ref(&self, number: u64) -> Result<B256, Self::Error> {
968
+ self.with_read_txn(|txn| self.block_hash_ref_tx(txn, number))
969
+ }
970
+ }
971
+
972
+ /// `DatabaseRef` view that serves all reads from one `RoTxn` instead of opening one per read.
973
+ /// Holds the resize gate for the txn's lifetime so the env can't be remapped while it's open.
974
+ pub struct TxnDatabaseReader<'a> {
975
+ db: &'a PersistentDB,
976
+ txn: heed::RoTxn<'a, heed::WithTls>,
977
+ _resize_guard: RwLockReadGuard<'a, ()>,
978
+ }
979
+
980
+ impl<'a> TxnDatabaseReader<'a> {
981
+ pub fn new(db: &'a PersistentDB) -> Result<Self, Error> {
982
+ let resize_guard = db.resize_lock.read().map_err(|_| Error::Lock)?;
983
+ let txn = db.env.read_txn()?;
984
+ Ok(Self {
985
+ db,
986
+ txn,
987
+ _resize_guard: resize_guard,
988
+ })
989
+ }
990
+ }
991
+
992
+ impl DatabaseRef for TxnDatabaseReader<'_> {
993
+ type Error = Error;
994
+ fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Error> {
995
+ self.db.basic_ref_tx(&self.txn, address)
996
+ }
997
+ fn storage_ref(&self, address: Address, index: U256) -> Result<U256, Error> {
998
+ self.db.storage_ref_tx(&self.txn, address, index)
999
+ }
1000
+ fn code_by_hash_ref(&self, hash: B256) -> Result<Bytecode, Error> {
1001
+ self.db.code_by_hash_ref_tx(&self.txn, hash)
1002
+ }
1003
+ fn block_hash_ref(&self, number: u64) -> Result<B256, Error> {
1004
+ self.db.block_hash_ref_tx(&self.txn, number)
1005
+ }
1006
+ }
1007
+
1008
+ impl PersistentDB {
1009
+ pub fn commit(
1010
+ &self,
1011
+ state_commit: &mut StateCommit,
1012
+ commit_data: &Option<CommitData>,
1013
+ ) -> Result<(), Error> {
1014
+ let StateCommit {
1015
+ key,
1016
+ change_set,
1017
+ results,
1018
+ } = state_commit;
1019
+
1020
+ match self.commit_to_db(key, change_set, commit_data, results) {
1021
+ Ok(_) => return Ok(()),
1022
+ Err(err) => match &err {
1023
+ Error::Heed(heed_err) => match heed_err {
1024
+ heed::Error::Mdb(mdb_err) => match mdb_err {
1025
+ heed::MdbError::MapFull => return Err(Error::DbFull),
1026
+ _ => return Err(err),
1027
+ },
1028
+ _ => return Err(err),
1029
+ },
1030
+ _ => return Err(err),
1031
+ },
1032
+ }
1033
+ }
1034
+
1035
+ fn commit_to_db(
1036
+ &self,
1037
+ key: &CommitKey,
1038
+ change_set: &mut state_changes::StateChangeset,
1039
+ commit_data: &Option<CommitData>,
1040
+ results: &BTreeMap<B256, (ExecutionResult, u64)>,
1041
+ ) -> Result<(), Error> {
1042
+ self.with_write_txn(|rwtxn| {
1043
+ if self.is_block_committed(&rwtxn, key.0) {
1044
+ return Err(Error::State("block already committed".into()));
1045
+ }
1046
+
1047
+ let inner = self.inner.borrow_mut();
1048
+
1049
+ let state_changes::StateChangeset {
1050
+ accounts,
1051
+ storage,
1052
+ contracts,
1053
+ legacy_attributes,
1054
+ legacy_cold_wallets,
1055
+ merged_legacy_cold_wallets,
1056
+ } = change_set;
1057
+
1058
+ // Update accounts
1059
+ for (address, account) in accounts.iter() {
1060
+ let address = AddressWrapper(*address);
1061
+
1062
+ if let Some(account) = account {
1063
+ inner.accounts.put(
1064
+ rwtxn,
1065
+ &address,
1066
+ &CompactBincode(&StoredAccountInfo::new(
1067
+ account.balance,
1068
+ account.nonce,
1069
+ account.code_hash,
1070
+ )),
1071
+ )?;
1072
+ } else {
1073
+ inner.accounts.delete(rwtxn, &address)?;
1074
+ }
1075
+ }
1076
+
1077
+ // Update account history
1078
+ if let Some(db) = &inner.accounts_history {
1079
+ self.accounts_history
1080
+ .as_ref()
1081
+ .expect("accounts history")
1082
+ .insert(
1083
+ rwtxn,
1084
+ db,
1085
+ key.0,
1086
+ accounts
1087
+ .iter()
1088
+ .map(|a| (a.0, a.1.clone().unwrap_or_default()))
1089
+ .collect(),
1090
+ )?;
1091
+ }
1092
+
1093
+ // Update legacy attributes
1094
+ for (address, legacy_attributes) in legacy_attributes.into_iter() {
1095
+ let address = AddressWrapper(*address);
1096
+ inner
1097
+ .legacy_attributes
1098
+ .put(rwtxn, &address, &CompactBincode(legacy_attributes))?;
1099
+ }
1100
+
1101
+ // Update legacy cold wallets
1102
+ for (address, legacy_cold_wallets) in legacy_cold_wallets.into_iter() {
1103
+ let address = LegacyAddressWrapper(*address);
1104
+ inner.legacy_cold_wallets.put(
1105
+ rwtxn,
1106
+ &address,
1107
+ &CompactBincode(legacy_cold_wallets),
1108
+ )?;
1109
+ }
1110
+
1111
+ // Update contracts
1112
+ for (hash, bytecode) in contracts.into_iter() {
1113
+ inner.contracts.put(
1114
+ rwtxn,
1115
+ &HashWrapper(*hash),
1116
+ &CompactBincode(&bytecode.clone().into()),
1117
+ )?;
1118
+ }
1119
+
1120
+ // Update storage
1121
+ for state_changes::StorageChangeset {
1122
+ address,
1123
+ wipe_storage,
1124
+ storage,
1125
+ } in storage.into_iter()
1126
+ {
1127
+ let mut iter = inner.storage.iter_mut(rwtxn)?;
1128
+ let address = AddressWrapper(*address);
1129
+
1130
+ if iter.move_on_key(&address)? {
1131
+ if *wipe_storage {
1132
+ // wipe all existing storage for address
1133
+ unsafe { iter.del_current_with_flags(heed::DeleteFlags::NO_DUP_DATA)? };
1134
+ }
1135
+ }
1136
+
1137
+ for value in storage.into_iter() {
1138
+ let new_storage_value = &StorageEntryWrapper(value.0, value.1.present_value());
1139
+
1140
+ if let Some((_, iter_value)) =
1141
+ iter.move_on_key_dup(&address, &new_storage_value)?
1142
+ {
1143
+ // overwrite or delete if key matches
1144
+ if iter_value.0 == value.0 {
1145
+ if value.1.present_value().is_zero() {
1146
+ let success = unsafe { iter.del_current()? };
1147
+ assert!(success);
1148
+ } else if value.1.present_value() != iter_value.1 {
1149
+ unsafe {
1150
+ // overwrite current position of cursor
1151
+ let success = iter.put_current(&address, &new_storage_value)?;
1152
+ assert!(success);
1153
+ }
1154
+ } else {
1155
+ // skip unchanged storage
1156
+ }
1157
+
1158
+ // cursor matched existing entry, move on to next
1159
+ continue;
1160
+ }
1161
+ }
1162
+
1163
+ if value.1.present_value() != U256::ZERO {
1164
+ unsafe {
1165
+ iter.put_current_with_options(
1166
+ heed::PutFlags::NO_DUP_DATA,
1167
+ &address,
1168
+ &new_storage_value,
1169
+ )?;
1170
+ }
1171
+ }
1172
+ }
1173
+ }
1174
+
1175
+ // Mark legacy cold wallets as merged in storage and migrate legacy attributes
1176
+ for (address, legacy) in merged_legacy_cold_wallets {
1177
+ self.logger.log(
1178
+ LogLevel::Info,
1179
+ format!(
1180
+ "Merging legacy cold wallet '{}' with '{}'",
1181
+ legacy.1, address
1182
+ ),
1183
+ );
1184
+
1185
+ let key = &LegacyAddressWrapper(legacy.1);
1186
+ let mut legacy_cold_wallet = inner
1187
+ .legacy_cold_wallets
1188
+ .get(&rwtxn, key)?
1189
+ .expect("legacy cold wallet to be found")
1190
+ .0;
1191
+
1192
+ assert!(legacy_cold_wallet.merge_info.is_none());
1193
+ legacy_cold_wallet.merge_info.replace((legacy.0, *address));
1194
+
1195
+ inner
1196
+ .legacy_cold_wallets
1197
+ .put(rwtxn, key, &CompactBincode(&legacy_cold_wallet))?;
1198
+
1199
+ // The legacy balance has already been applied to the `PendingCommit`,
1200
+ // thus only the legacy attributes need to be moved to a different storage.
1201
+ inner.legacy_attributes.put(
1202
+ rwtxn,
1203
+ &AddressWrapper(*address),
1204
+ &CompactBincode(&legacy_cold_wallet.legacy_attributes),
1205
+ )?;
1206
+ }
1207
+
1208
+ // ========================================
1209
+ //
1210
+ if let Some(commit_data) = commit_data {
1211
+ let CommitData {
1212
+ proof,
1213
+ header,
1214
+ transactions,
1215
+ } = commit_data;
1216
+
1217
+ // Update blocks
1218
+ inner.blocks.put(rwtxn, &key.0, &CompactBincode(header))?;
1219
+ inner
1220
+ .blocks_hash_number
1221
+ .put(rwtxn, &HashWrapper(header.hash), &key.0)?;
1222
+
1223
+ // Update proofs
1224
+ inner.proofs.put(rwtxn, &key.0, &CompactBincode(proof))?;
1225
+
1226
+ // Update transactions
1227
+ for (sequence, _) in transactions.iter().enumerate() {
1228
+ debug_assert!(sequence <= u16::MAX as usize);
1229
+
1230
+ let key = TransactionKey::new(key.0, sequence as u16);
1231
+ let transaction = &transactions[sequence];
1232
+
1233
+ inner.transactions_hash_key.put(
1234
+ rwtxn,
1235
+ &HashWrapper(transaction.tx_hash),
1236
+ &key,
1237
+ )?;
1238
+
1239
+ inner
1240
+ .transactions
1241
+ .put(rwtxn, &key, &CompactBincode(transaction))?;
1242
+ }
1243
+
1244
+ // Update state
1245
+ let total_round_key = StaticStringWrapper("total_round");
1246
+ let current_total_round =
1247
+ read_total_round(inner.state.get(rwtxn, &total_round_key)?);
1248
+
1249
+ inner.state.put(
1250
+ rwtxn,
1251
+ &total_round_key,
1252
+ &Bytes::from_iter((current_total_round + proof.round as u64 + 1).to_le_bytes()),
1253
+ )?;
1254
+ }
1255
+ // ========================================
1256
+
1257
+ // Finalize commit
1258
+ let mut tx_receipts = HashMap::default();
1259
+ for (k, (result, cumulative_gas_used)) in results {
1260
+ let receipt = map_execution_result(result.clone(), *cumulative_gas_used);
1261
+ tx_receipts.insert(k.clone(), receipt);
1262
+ }
1263
+
1264
+ inner.commits.put(
1265
+ rwtxn,
1266
+ &key.0,
1267
+ &CompactBincode(&CommitReceipts { tx_receipts }),
1268
+ )?;
1269
+
1270
+ Ok(())
1271
+ })
1272
+ }
1273
+
1274
+ pub fn is_block_committed(&self, rtxn: &heed::RoTxn, block_number: u64) -> bool {
1275
+ self.inner
1276
+ .borrow()
1277
+ .commits
1278
+ .get(rtxn, &block_number)
1279
+ .is_ok_and(|v| v.is_some())
1280
+ }
1281
+
1282
+ pub fn get_receipt(
1283
+ &self,
1284
+ block_number: u64,
1285
+ tx_hash: B256,
1286
+ ) -> Result<(bool, Option<TxReceipt>), Error> {
1287
+ self.with_read_txn(|rtxn| {
1288
+ let inner = self.inner.borrow();
1289
+
1290
+ match inner.commits.get(rtxn, &block_number)? {
1291
+ Some(receipts) => Ok((true, receipts.tx_receipts.get(&tx_hash).cloned())),
1292
+ None => Ok((false, None)),
1293
+ }
1294
+ })
1295
+ }
1296
+
1297
+ pub fn is_empty(&self) -> Result<bool, Error> {
1298
+ self.with_read_txn(|rtxn| {
1299
+ let inner = self.inner.borrow();
1300
+
1301
+ Ok(inner.blocks.is_empty(rtxn)?)
1302
+ })
1303
+ }
1304
+
1305
+ pub fn get_state(&self) -> Result<(u64, u64), Error> {
1306
+ self.with_read_txn(|rtxn| {
1307
+ let inner = self.inner.borrow();
1308
+
1309
+ let total_round =
1310
+ read_total_round(inner.state.get(rtxn, &StaticStringWrapper("total_round"))?);
1311
+
1312
+ let block_number = match inner.blocks.last(rtxn)? {
1313
+ Some((block_number, _)) => block_number,
1314
+ None => 0,
1315
+ };
1316
+
1317
+ Ok((block_number, total_round))
1318
+ })
1319
+ }
1320
+
1321
+ pub fn get_block_number_by_hash(&self, block_hash: B256) -> Result<Option<u64>, Error> {
1322
+ self.with_read_txn(|rtxn| {
1323
+ let inner = self.inner.borrow();
1324
+
1325
+ Ok(inner
1326
+ .blocks_hash_number
1327
+ .get(rtxn, &HashWrapper(block_hash))?)
1328
+ })
1329
+ }
1330
+
1331
+ pub fn get_proof_data(&self, block_number: u64) -> Result<Option<ProofData>, Error> {
1332
+ self.with_read_txn(|rtxn| {
1333
+ let inner = self.inner.borrow();
1334
+
1335
+ Ok(inner.proofs.get(rtxn, &block_number)?.map(|data| data.0))
1336
+ })
1337
+ }
1338
+
1339
+ pub fn get_block_header_data(
1340
+ &self,
1341
+ block_number: u64,
1342
+ ) -> Result<Option<BlockHeaderData>, Error> {
1343
+ self.with_read_txn(|rtxn| {
1344
+ let inner = self.inner.borrow();
1345
+
1346
+ Ok(inner.blocks.get(rtxn, &block_number)?.map(|data| data.0))
1347
+ })
1348
+ }
1349
+
1350
+ pub fn get_transaction(&self, key: TransactionKey) -> Result<Option<TransactionData>, Error> {
1351
+ self.with_read_txn(|rtxn| {
1352
+ let inner = self.inner.borrow();
1353
+
1354
+ Ok(inner.transactions.get(rtxn, &key)?.map(|data| data.0))
1355
+ })
1356
+ }
1357
+
1358
+ pub fn get_transaction_data(&self, key: String) -> Result<Option<TransactionData>, Error> {
1359
+ match TransactionKey::parse(&key) {
1360
+ Some(key) => self.get_transaction(key),
1361
+ None => Ok(None),
1362
+ }
1363
+ }
1364
+
1365
+ pub fn get_transaction_key_by_hash(&self, tx_hash: B256) -> Result<Option<String>, Error> {
1366
+ self.with_read_txn(|rtxn| {
1367
+ let inner = self.inner.borrow();
1368
+
1369
+ Ok(inner
1370
+ .transactions_hash_key
1371
+ .get(rtxn, &HashWrapper(tx_hash))?
1372
+ .map(|key| key.to_token()))
1373
+ })
1374
+ }
1375
+
1376
+ /// Runs `f` inside a read txn while holding the shared resize guard, so the env can't be remapped
1377
+ /// (mdb_env_set_mapsize) while the txn is live.
1378
+ fn with_read_txn<T>(
1379
+ &self,
1380
+ f: impl FnOnce(&heed::RoTxn) -> Result<T, Error>,
1381
+ ) -> Result<T, Error> {
1382
+ let _resize_guard = self.resize_lock.read().map_err(|_| Error::Lock)?;
1383
+ let txn = self.env.read_txn()?;
1384
+ f(&txn)
1385
+ }
1386
+
1387
+ /// Runs `f` inside a write txn while holding the shared resize guard, so the env can't be remapped
1388
+ /// (mdb_env_set_mapsize) while the txn is live.
1389
+ fn with_write_txn<T>(
1390
+ &self,
1391
+ f: impl FnOnce(&mut heed::RwTxn) -> Result<T, Error>,
1392
+ ) -> Result<T, Error> {
1393
+ let _resize_guard = self.resize_lock.read().map_err(|_| Error::Lock)?;
1394
+ let mut txn = self.env.write_txn()?;
1395
+ let out = f(&mut txn)?;
1396
+ txn.commit()?;
1397
+ Ok(out)
1398
+ }
1399
+ }
1400
+
1401
+ fn read_total_round(item: Option<Bytes>) -> u64 {
1402
+ match item {
1403
+ Some(total_round) => {
1404
+ assert_eq!(total_round.len(), 8);
1405
+ let mut buffer = [0u8; 8];
1406
+ buffer[..8].copy_from_slice(&total_round[..8]);
1407
+ u64::from_le_bytes(buffer)
1408
+ }
1409
+ None => 0,
1410
+ }
1411
+ }
1412
+
1413
+ impl PendingCommit {
1414
+ pub fn new(key: CommitKey) -> Self {
1415
+ Self {
1416
+ key,
1417
+ cache: Default::default(),
1418
+ cumulative_gas_used: Default::default(),
1419
+ results: Default::default(),
1420
+ transitions: Default::default(),
1421
+ legacy_attributes: Default::default(),
1422
+ legacy_cold_wallets: Default::default(),
1423
+ merged_legacy_cold_wallets: Default::default(),
1424
+ built_commit: Default::default(),
1425
+ }
1426
+ }
1427
+
1428
+ pub fn import_account(
1429
+ &mut self,
1430
+ address: Address,
1431
+ info: AccountInfo,
1432
+ legacy_attributes: Option<LegacyAccountAttributes>,
1433
+ ) {
1434
+ let mut state = revm::database::State::builder()
1435
+ .with_bundle_update()
1436
+ .with_cached_prestate(std::mem::take(&mut self.cache))
1437
+ .build();
1438
+
1439
+ let account = state
1440
+ .load_cache_account(address)
1441
+ .expect("load_cache_account");
1442
+
1443
+ let balance = info.balance.try_into().expect("fit u128");
1444
+ let transition_account = account
1445
+ .increment_balance(balance)
1446
+ .unwrap_or_else(|| TransitionAccount::new_empty_eip161(Default::default()));
1447
+
1448
+ let transitions = vec![(address, transition_account)];
1449
+
1450
+ self.transitions.add_transitions(transitions);
1451
+
1452
+ self.cache = std::mem::take(&mut state.cache);
1453
+
1454
+ if let Some(legacy_attributes) = legacy_attributes {
1455
+ self.legacy_attributes.insert(address, legacy_attributes);
1456
+ }
1457
+ }
1458
+ }
1459
+
1460
+ #[cfg(test)]
1461
+ mod tests {
1462
+ use std::collections::BTreeMap;
1463
+
1464
+ use crate::{
1465
+ account::StoredAccountInfo,
1466
+ compression::CompactBincode,
1467
+ db::{
1468
+ AddressWrapper, BlockHeaderData, CommitData, CommitKey, CommitReceipts, HashWrapper,
1469
+ LegacyAddressWrapper, MAP_SIZE_UNIT, PendingCommit, PersistentDB, PersistentDBOptions,
1470
+ ProofData, StaticStringWrapper, StorageEntryWrapper, TransactionData, TransactionKey,
1471
+ TxnDatabaseReader, next_map_size,
1472
+ },
1473
+ historical::HistoricalAccountData,
1474
+ legacy::{LegacyAccountAttributes, LegacyAddress, LegacyColdWallet},
1475
+ logger::Logger,
1476
+ receipt::TxReceipt,
1477
+ state_changes::{StateChangeset, StorageChangeset},
1478
+ state_commit::{StateCommit, build_commit},
1479
+ };
1480
+ use alloy_primitives::{Address, B256, Bytes, U256, address, b256};
1481
+ use revm::{
1482
+ Database, DatabaseRef,
1483
+ context::result::{ExecutionResult, ResultGas, SuccessReason},
1484
+ database::{TransitionState, states::StorageSlot},
1485
+ primitives::HashMap,
1486
+ state::{AccountInfo, Bytecode},
1487
+ };
1488
+
1489
+ use heed::{BytesDecode, BytesEncode, EnvFlags, EnvOpenOptions};
1490
+
1491
+ #[test]
1492
+ fn test_open_db() {
1493
+ let tmp = tempfile::Builder::new()
1494
+ .prefix("evm.mdb")
1495
+ .tempdir()
1496
+ .unwrap();
1497
+
1498
+ assert!(PersistentDB::new(PersistentDBOptions::new(tmp.path().to_path_buf())).is_ok());
1499
+ }
1500
+
1501
+ #[test]
1502
+ fn test_open_db_with_logger() {
1503
+ let tmp = tempfile::Builder::new()
1504
+ .prefix("evm.mdb")
1505
+ .tempdir()
1506
+ .unwrap();
1507
+
1508
+ assert!(
1509
+ PersistentDB::new(
1510
+ PersistentDBOptions::new(tmp.path().to_path_buf()).with_logger(Logger::new(None))
1511
+ )
1512
+ .is_ok()
1513
+ );
1514
+ }
1515
+
1516
+ #[test]
1517
+ fn test_commit_changes() {
1518
+ let mut db = create_temp_database();
1519
+
1520
+ // 1) Lookup empty account
1521
+ let address = address!("bd6f65c58a46427af4b257cbe231d0ed69ed5508");
1522
+ let account = db.basic(address).expect("works");
1523
+ assert_eq!(account, None);
1524
+
1525
+ // 2) Update balance for account
1526
+ let mut state = HashMap::default();
1527
+
1528
+ let mut account = revm::state::Account::new_not_existing(revm::state::TransactionId::ZERO);
1529
+ account.info.balance = U256::from(100);
1530
+ account.status = revm::state::AccountStatus::Touched;
1531
+
1532
+ let code = Bytecode::new();
1533
+ account.info.code_hash = code.hash_slow();
1534
+ account.info.code = Some(code.clone());
1535
+
1536
+ let mut storage = HashMap::default();
1537
+ storage.insert(
1538
+ U256::from(1),
1539
+ revm::database::states::StorageSlot::new_changed(U256::ZERO, U256::from(1234)),
1540
+ );
1541
+ storage.insert(
1542
+ U256::from(2),
1543
+ revm::database::states::StorageSlot::new_changed(U256::ZERO, U256::from(5678)),
1544
+ );
1545
+
1546
+ state.insert(
1547
+ address,
1548
+ revm::database::TransitionAccount {
1549
+ status: revm::database::AccountStatus::InMemoryChange,
1550
+ info: Some(account.info.clone()),
1551
+ previous_status: revm::database::AccountStatus::Loaded,
1552
+ previous_info: None,
1553
+ storage,
1554
+ storage_was_destroyed: false,
1555
+ },
1556
+ );
1557
+
1558
+ crate::state_commit::commit_to_db(
1559
+ &mut db,
1560
+ PendingCommit {
1561
+ key: CommitKey::default(),
1562
+ transitions: TransitionState { transitions: state },
1563
+ ..Default::default()
1564
+ },
1565
+ Default::default(),
1566
+ )
1567
+ .expect("ok");
1568
+
1569
+ // 3) Assert updated storage
1570
+
1571
+ // Balance
1572
+ let account = db.basic(address).expect("works").expect("account info");
1573
+ assert_eq!(account.balance, U256::from(100));
1574
+
1575
+ // Code
1576
+ assert_eq!(account.code_hash, code.hash_slow());
1577
+ let account_code = db.code_by_hash(code.hash_slow()).expect("code");
1578
+ assert_eq!(account_code, code);
1579
+
1580
+ // Storage
1581
+ let mut account_storage = db.storage(address, U256::from(1)).expect("storage");
1582
+ assert_eq!(account_storage, U256::from(1234));
1583
+
1584
+ account_storage = db.storage(address, U256::from(2)).expect("storage");
1585
+ assert_eq!(account_storage, U256::from(5678));
1586
+ }
1587
+
1588
+ #[test]
1589
+ fn test_commit_built() {
1590
+ let mut db = create_temp_database();
1591
+ let mut pending_commit = PendingCommit::default();
1592
+ pending_commit.built_commit = Some(build_commit(&mut pending_commit).unwrap());
1593
+
1594
+ crate::state_commit::commit_to_db(&mut db, pending_commit, Default::default()).unwrap();
1595
+ }
1596
+
1597
+ #[test]
1598
+ fn test_commit_built_without_precomputed_hashes() {
1599
+ let mut db = create_temp_database();
1600
+ let mut pending_commit = PendingCommit::default();
1601
+ pending_commit.built_commit = Some(build_commit(&mut pending_commit).unwrap());
1602
+
1603
+ crate::state_commit::commit_to_db(&mut db, pending_commit, Default::default()).unwrap();
1604
+ }
1605
+
1606
+ #[test]
1607
+ fn test_storage() {
1608
+ let mut db = create_temp_database();
1609
+
1610
+ let address = address!("bd6f65c58a46427af4b257cbe231d0ed69ed5508");
1611
+ let mut state = HashMap::default();
1612
+
1613
+ let mut account = revm::state::Account::new_not_existing(revm::state::TransactionId::ZERO);
1614
+ account.status = revm::state::AccountStatus::Touched;
1615
+
1616
+ let mut storage = HashMap::default();
1617
+
1618
+ storage.insert(
1619
+ U256::from(99),
1620
+ revm::database::states::StorageSlot::new_changed(U256::ZERO, U256::from(99)),
1621
+ );
1622
+ storage.insert(
1623
+ U256::from(1),
1624
+ revm::database::states::StorageSlot::new_changed(U256::ZERO, U256::from(1)),
1625
+ );
1626
+ storage.insert(
1627
+ U256::from(101),
1628
+ revm::database::states::StorageSlot::new_changed(U256::ZERO, U256::from(101)),
1629
+ );
1630
+ storage.insert(
1631
+ U256::from(2),
1632
+ revm::database::states::StorageSlot::new_changed(U256::ZERO, U256::from(2)),
1633
+ );
1634
+ storage.insert(
1635
+ U256::from(4),
1636
+ revm::database::states::StorageSlot::new_changed(U256::ZERO, U256::from(4)),
1637
+ );
1638
+
1639
+ state.insert(
1640
+ address,
1641
+ revm::database::TransitionAccount {
1642
+ status: revm::database::AccountStatus::InMemoryChange,
1643
+ info: Some(account.info.clone()),
1644
+ previous_status: revm::database::AccountStatus::Loaded,
1645
+ previous_info: None,
1646
+ storage,
1647
+ storage_was_destroyed: false,
1648
+ },
1649
+ );
1650
+
1651
+ crate::state_commit::commit_to_db(
1652
+ &mut db,
1653
+ PendingCommit {
1654
+ key: CommitKey::default(),
1655
+ transitions: TransitionState { transitions: state },
1656
+ ..Default::default()
1657
+ },
1658
+ Default::default(),
1659
+ )
1660
+ .expect("ok");
1661
+
1662
+ // Assert storage is sorted
1663
+
1664
+ let indexes = vec![1, 2, 4, 99, 101];
1665
+
1666
+ // Storage
1667
+ for index in indexes {
1668
+ let account_storage = db.storage(address, U256::from(index)).expect("storage");
1669
+ assert_eq!(account_storage, U256::from(index));
1670
+ }
1671
+ }
1672
+
1673
+ #[test]
1674
+ fn test_storage_overwrite() {
1675
+ let mut db = create_temp_database();
1676
+
1677
+ let address = address!("bd6f65c58a46427af4b257cbe231d0ed69ed5508");
1678
+ let mut state = HashMap::default();
1679
+
1680
+ let mut account = revm::state::Account::new_not_existing(revm::state::TransactionId::ZERO);
1681
+ account.status = revm::state::AccountStatus::Touched;
1682
+
1683
+ let mut storage = HashMap::default();
1684
+
1685
+ storage.insert(
1686
+ U256::from(1),
1687
+ revm::database::states::StorageSlot::new_changed(U256::ZERO, U256::from(1)),
1688
+ );
1689
+ storage.insert(
1690
+ U256::from(2),
1691
+ revm::database::states::StorageSlot::new_changed(U256::ZERO, U256::from(2)),
1692
+ );
1693
+
1694
+ state.insert(
1695
+ address,
1696
+ revm::database::TransitionAccount {
1697
+ status: revm::database::AccountStatus::InMemoryChange,
1698
+ info: Some(account.info.clone()),
1699
+ previous_status: revm::database::AccountStatus::Loaded,
1700
+ previous_info: None,
1701
+ storage,
1702
+ storage_was_destroyed: false,
1703
+ },
1704
+ );
1705
+
1706
+ crate::state_commit::commit_to_db(
1707
+ &mut db,
1708
+ PendingCommit {
1709
+ key: CommitKey::default(),
1710
+ transitions: TransitionState { transitions: state },
1711
+ ..Default::default()
1712
+ },
1713
+ Default::default(),
1714
+ )
1715
+ .expect("ok");
1716
+
1717
+ // Assert storage
1718
+ let mut account_storage = db.storage(address, U256::from(1)).expect("storage");
1719
+ assert_eq!(account_storage, U256::from(1));
1720
+ account_storage = db.storage(address, U256::from(2)).expect("storage");
1721
+ assert_eq!(account_storage, U256::from(2));
1722
+
1723
+ // Now overwrite index 1
1724
+ let mut storage = HashMap::default();
1725
+ storage.insert(
1726
+ U256::from(1),
1727
+ revm::database::states::StorageSlot::new_changed(U256::from(1), U256::from(99)),
1728
+ );
1729
+
1730
+ let mut state = HashMap::default();
1731
+ state.insert(
1732
+ address,
1733
+ revm::database::TransitionAccount {
1734
+ status: revm::database::AccountStatus::Changed,
1735
+ info: Some(account.info.clone()),
1736
+ previous_status: revm::database::AccountStatus::Loaded,
1737
+ previous_info: None,
1738
+ storage,
1739
+ storage_was_destroyed: false,
1740
+ },
1741
+ );
1742
+
1743
+ crate::state_commit::commit_to_db(
1744
+ &mut db,
1745
+ PendingCommit {
1746
+ key: CommitKey(1, 0, B256::ZERO),
1747
+ transitions: TransitionState { transitions: state },
1748
+ ..Default::default()
1749
+ },
1750
+ Default::default(),
1751
+ )
1752
+ .expect("ok");
1753
+
1754
+ // Assert storage again
1755
+
1756
+ // - index 1 was overwritte
1757
+ let mut account_storage = db.storage(address, U256::from(1)).expect("storage");
1758
+ assert_eq!(account_storage, U256::from(99));
1759
+
1760
+ // - index 2 remains unchanged
1761
+ account_storage = db.storage(address, U256::from(2)).expect("storage");
1762
+ assert_eq!(account_storage, U256::from(2));
1763
+ }
1764
+
1765
+ #[test]
1766
+ fn test_next_map_size() {
1767
+ let input = vec![0, 1, 2, 3, 4];
1768
+ for i in input {
1769
+ let next = next_map_size(i * MAP_SIZE_UNIT);
1770
+ assert_eq!(next, (i + 1) * MAP_SIZE_UNIT);
1771
+ }
1772
+ }
1773
+
1774
+ #[test]
1775
+ fn test_resize_on_commit() {
1776
+ let create_large_commit = |block_number: u64, n: usize| {
1777
+ let mut buf = vec![0; 32];
1778
+ buf[0..8].copy_from_slice(&block_number.to_le_bytes());
1779
+ let address = Address::from_word(ethers_core::utils::keccak256(buf).into());
1780
+
1781
+ let mut state = HashMap::default();
1782
+
1783
+ let mut account =
1784
+ revm::state::Account::new_not_existing(revm::state::TransactionId::ZERO);
1785
+ account.status = revm::state::AccountStatus::Touched;
1786
+
1787
+ let mut storage = HashMap::default();
1788
+
1789
+ for i in 0..n {
1790
+ storage.insert(
1791
+ U256::from(i + 1),
1792
+ revm::database::states::StorageSlot::new_changed(U256::ZERO, U256::from(1)),
1793
+ );
1794
+ }
1795
+
1796
+ state.insert(
1797
+ address,
1798
+ revm::database::TransitionAccount {
1799
+ status: revm::database::AccountStatus::InMemoryChange,
1800
+ info: Some(account.info.clone()),
1801
+ previous_status: revm::database::AccountStatus::Loaded,
1802
+ previous_info: None,
1803
+ storage,
1804
+ storage_was_destroyed: false,
1805
+ },
1806
+ );
1807
+
1808
+ PendingCommit {
1809
+ key: CommitKey(block_number, 0, B256::ZERO),
1810
+ transitions: TransitionState { transitions: state },
1811
+ ..Default::default()
1812
+ }
1813
+ };
1814
+
1815
+ let path = tempfile::Builder::new()
1816
+ .prefix("evm.mdb")
1817
+ .tempdir()
1818
+ .unwrap();
1819
+
1820
+ let mut env_builder = EnvOpenOptions::new();
1821
+ env_builder.max_dbs(PersistentDB::MAX_DBS);
1822
+ env_builder.map_size(4096 * 10); // start with very small (few kB)
1823
+
1824
+ unsafe { env_builder.flags(EnvFlags::NO_SUB_DIR) };
1825
+
1826
+ let env = unsafe { env_builder.open(path.path().join("evm.mdb")) }.expect("ok");
1827
+
1828
+ let mut db = PersistentDB::new_with_env(
1829
+ env,
1830
+ std::sync::Arc::new(std::sync::RwLock::new(())),
1831
+ Default::default(),
1832
+ )
1833
+ .expect("open");
1834
+ assert_eq!(db.env.info().map_size, 4096 * 10);
1835
+
1836
+ // large commit to trigger a resize
1837
+ crate::state_commit::commit_to_db(
1838
+ &mut db,
1839
+ create_large_commit(0, 1024),
1840
+ Default::default(),
1841
+ )
1842
+ .expect("ok");
1843
+
1844
+ // increased to next MAP_SIZE_UNIT
1845
+ assert_eq!(db.env.info().map_size, MAP_SIZE_UNIT);
1846
+
1847
+ // add more commits without triggering another resize
1848
+ for i in 0..10 {
1849
+ crate::state_commit::commit_to_db(
1850
+ &mut db,
1851
+ create_large_commit(i + 1, 1024),
1852
+ Default::default(),
1853
+ )
1854
+ .expect("ok");
1855
+ assert_eq!(db.env.info().map_size, MAP_SIZE_UNIT);
1856
+ }
1857
+
1858
+ // reopen db with initial env size should automatically resize
1859
+ drop(db);
1860
+
1861
+ let env = unsafe { env_builder.open(path.path().join("evm.mdb")) }.expect("ok");
1862
+ let db = PersistentDB::new_with_env(
1863
+ env,
1864
+ std::sync::Arc::new(std::sync::RwLock::new(())),
1865
+ Default::default(),
1866
+ )
1867
+ .expect("open");
1868
+ assert_eq!(db.env.info().map_size, MAP_SIZE_UNIT);
1869
+ }
1870
+
1871
+ #[test]
1872
+ fn test_read_accounts() {
1873
+ let db = create_temp_database();
1874
+
1875
+ let addresses = [
1876
+ address!("27b1fdb04752bbc536007a920d24acb045561c26"),
1877
+ address!("3599689E6292b81B2d85451025146515070129Bb"),
1878
+ address!("42712D45473476b98452f434e72461577D686318"),
1879
+ address!("52908400098527886E0F7030069857D2E4169EE7"),
1880
+ address!("5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"),
1881
+ address!("6549f4939460DE12611948b3f82b88C3C8975323"),
1882
+ address!("66f9664f97F2b50F62D13eA064982f936dE76657"),
1883
+ address!("8617E340B3D01FA5F11F306F4090FD50E238070D"),
1884
+ address!("88021160C5C792225E4E5452585947470010289D"),
1885
+ address!("D1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb"),
1886
+ address!("dbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB"),
1887
+ address!("de709f2102306220921060314715629080e2fb77"),
1888
+ address!("fB6916095ca1df60bB79Ce92cE3Ea74c37c5d359"),
1889
+ ];
1890
+
1891
+ {
1892
+ let mut wtxn = db.env.write_txn().unwrap();
1893
+
1894
+ for (index, address) in addresses.iter().enumerate() {
1895
+ db.inner
1896
+ .borrow_mut()
1897
+ .accounts
1898
+ .put(
1899
+ &mut wtxn,
1900
+ &AddressWrapper(*address),
1901
+ &CompactBincode(&StoredAccountInfo {
1902
+ balance: U256::from(index),
1903
+ nonce: index as u64,
1904
+ ..Default::default()
1905
+ }),
1906
+ )
1907
+ .unwrap();
1908
+
1909
+ db.inner
1910
+ .borrow_mut()
1911
+ .legacy_attributes
1912
+ .put(
1913
+ &mut wtxn,
1914
+ &AddressWrapper(*address),
1915
+ &CompactBincode(&LegacyAccountAttributes::default()),
1916
+ )
1917
+ .unwrap();
1918
+ }
1919
+ wtxn.commit().unwrap();
1920
+ }
1921
+
1922
+ const LIMIT: u64 = 5;
1923
+ let mut offset = 0;
1924
+
1925
+ let mut read = 0;
1926
+
1927
+ loop {
1928
+ let (next, accounts) = db.get_accounts(offset, LIMIT).unwrap();
1929
+ for _ in accounts {
1930
+ read += 1;
1931
+ }
1932
+
1933
+ if next.is_none() {
1934
+ break;
1935
+ }
1936
+
1937
+ match next {
1938
+ Some(next) => {
1939
+ offset = next;
1940
+ }
1941
+ None => {
1942
+ break;
1943
+ }
1944
+ }
1945
+ }
1946
+
1947
+ assert_eq!(read, addresses.len());
1948
+ }
1949
+
1950
+ #[test]
1951
+ fn test_get_cold_wallets() {
1952
+ let db = create_temp_database();
1953
+
1954
+ let legacy_addresses = [
1955
+ "DBYyh2vXcigrJGUHfvmYxVxEqeH7vomw6x",
1956
+ "D5KU9KrMYXdkEsRbv4y8hvetGbsJwf9z3P",
1957
+ "DJA2sqCbnmR63sD8doGrXrK3fCiqcA4GUw",
1958
+ "DJmvhhiQFSrEQCq9FUxvcLcpcBjx7K3yLt",
1959
+ ];
1960
+
1961
+ {
1962
+ let mut wtxn = db.env.write_txn().unwrap();
1963
+
1964
+ for (index, legacy) in legacy_addresses.iter().enumerate() {
1965
+ let legacy_address: LegacyAddress = (*legacy).try_into().unwrap();
1966
+ db.inner
1967
+ .borrow_mut()
1968
+ .legacy_cold_wallets
1969
+ .put(
1970
+ &mut wtxn,
1971
+ &LegacyAddressWrapper(legacy_address),
1972
+ &CompactBincode(&LegacyColdWallet {
1973
+ address: legacy_address,
1974
+ balance: U256::from(index),
1975
+ legacy_attributes: Default::default(),
1976
+ merge_info: None,
1977
+ }),
1978
+ )
1979
+ .unwrap();
1980
+ }
1981
+ wtxn.commit().unwrap();
1982
+ }
1983
+
1984
+ const LIMIT: u64 = 2;
1985
+ let mut offset = 0;
1986
+
1987
+ let mut read = 0;
1988
+
1989
+ loop {
1990
+ let (next, wallets) = db.get_legacy_cold_wallets(offset, LIMIT).unwrap();
1991
+ for wallet in wallets {
1992
+ read += 1;
1993
+
1994
+ let cold_wallet = db.get_legacy_cold_wallet(wallet.address).unwrap();
1995
+ assert_eq!(cold_wallet, Some(wallet));
1996
+ }
1997
+
1998
+ if next.is_none() {
1999
+ break;
2000
+ }
2001
+
2002
+ match next {
2003
+ Some(next) => {
2004
+ offset = next;
2005
+ }
2006
+ None => {
2007
+ break;
2008
+ }
2009
+ }
2010
+ }
2011
+
2012
+ assert_eq!(read, legacy_addresses.len());
2013
+ }
2014
+
2015
+ #[test]
2016
+ fn test_get_account_history() {
2017
+ let address1 = address!("0000000000000000000000000000000000000001");
2018
+ let address2 = address!("0000000000000000000000000000000000000002");
2019
+
2020
+ {
2021
+ let db = create_temp_database();
2022
+ let history = db.get_historical_account_info(1, address2).unwrap();
2023
+ assert_eq!(history, (None, false));
2024
+ }
2025
+
2026
+ let db = create_temp_database_opts(|opts| {
2027
+ opts.history_size = Some(8);
2028
+ });
2029
+
2030
+ assert!(db.accounts_history.is_some());
2031
+
2032
+ {
2033
+ let mut wtxn = db.env.write_txn().unwrap();
2034
+
2035
+ let mut entries = BTreeMap::default();
2036
+ entries.insert(
2037
+ address1,
2038
+ HistoricalAccountData {
2039
+ balance: U256::from(1),
2040
+ nonce: 0,
2041
+ code_hash: B256::ZERO,
2042
+ },
2043
+ );
2044
+
2045
+ db.inner
2046
+ .borrow_mut()
2047
+ .accounts_history
2048
+ .unwrap()
2049
+ .put(&mut wtxn, &1, &CompactBincode(&entries))
2050
+ .unwrap();
2051
+
2052
+ wtxn.commit().unwrap();
2053
+ }
2054
+
2055
+ let history = db.get_historical_account_info(1, address1).unwrap();
2056
+ assert_eq!(
2057
+ history,
2058
+ (
2059
+ Some(AccountInfo {
2060
+ balance: U256::from(1),
2061
+ nonce: 0,
2062
+ code_hash: B256::ZERO,
2063
+ ..Default::default()
2064
+ }),
2065
+ false
2066
+ )
2067
+ );
2068
+
2069
+ let history = db.get_historical_account_info(1, address2).unwrap();
2070
+ assert_eq!(history, (None, true));
2071
+ }
2072
+
2073
+ #[test]
2074
+ fn test_legacy_address_wrapper() {
2075
+ let legacy_address: LegacyAddress =
2076
+ "DJmvhhiQFSrEQCq9FUxvcLcpcBjx7K3yLt".try_into().unwrap();
2077
+
2078
+ let wrapper = LegacyAddressWrapper(legacy_address);
2079
+ let serialized = <LegacyAddressWrapper as BytesEncode>::bytes_encode(&wrapper).expect("ok");
2080
+
2081
+ let deserialized =
2082
+ <LegacyAddressWrapper as BytesDecode>::bytes_decode(&serialized).expect("ok");
2083
+ assert_eq!(legacy_address, deserialized.0);
2084
+ }
2085
+
2086
+ #[test]
2087
+ fn test_static_string_wrapper() {
2088
+ let string = "test";
2089
+
2090
+ let wrapper = StaticStringWrapper(string);
2091
+ let serialized = <StaticStringWrapper as BytesEncode>::bytes_encode(&wrapper).expect("ok");
2092
+
2093
+ assert_eq!(serialized, &b"test"[..]);
2094
+ }
2095
+
2096
+ #[test]
2097
+ fn test_commit_key() {
2098
+ let key = CommitKey(0, 0, B256::ZERO);
2099
+ let mut pending = PendingCommit::new(key);
2100
+
2101
+ let info = AccountInfo {
2102
+ balance: U256::ONE,
2103
+ nonce: 1,
2104
+ code_hash: b256!("0000000000000000000000000000000000000000000000000000000000000001"),
2105
+ account_id: None,
2106
+ code: None,
2107
+ };
2108
+
2109
+ let attributes = LegacyAccountAttributes {
2110
+ legacy_nonce: Some(0),
2111
+ second_public_key: Some("key".into()),
2112
+ multi_signature: None,
2113
+ };
2114
+
2115
+ pending.import_account(
2116
+ address!("0000000000000000000000000000000000000001"),
2117
+ info,
2118
+ Some(attributes),
2119
+ );
2120
+
2121
+ let info = AccountInfo {
2122
+ balance: U256::ZERO,
2123
+ nonce: 0,
2124
+ code_hash: B256::ZERO,
2125
+ account_id: None,
2126
+ code: None,
2127
+ };
2128
+ pending.import_account(
2129
+ address!("0000000000000000000000000000000000000002"),
2130
+ info,
2131
+ None,
2132
+ );
2133
+
2134
+ assert_eq!(pending.transitions.transitions.len(), 2);
2135
+ assert_eq!(pending.legacy_attributes.len(), 1);
2136
+ }
2137
+
2138
+ #[test]
2139
+ fn test_basic_ref() {
2140
+ let mut db = create_temp_database();
2141
+
2142
+ let genesis = address!("0000000000000000000000000000000000000000");
2143
+ let account = address!("0000000000000000000000000000000000000001");
2144
+ db.set_genesis_info(crate::db::GenesisInfo {
2145
+ account: genesis,
2146
+ initial_supply: U256::from(1_000_000),
2147
+ ..Default::default()
2148
+ })
2149
+ .unwrap();
2150
+
2151
+ let info = db.basic(genesis).unwrap();
2152
+ assert_eq!(
2153
+ info,
2154
+ Some(AccountInfo {
2155
+ balance: U256::from(1_000_000),
2156
+ ..Default::default()
2157
+ })
2158
+ );
2159
+
2160
+ let info = db.basic(account).unwrap();
2161
+ assert_eq!(info, None);
2162
+ }
2163
+
2164
+ #[test]
2165
+ fn test_code_by_hash() {
2166
+ let mut db = create_temp_database();
2167
+
2168
+ let hash = b256!("0000000000000000000000000000000000000000000000000000000000000001");
2169
+
2170
+ assert_eq!(db.code_by_hash(B256::ZERO).unwrap(), Default::default());
2171
+
2172
+ {
2173
+ let mut wtxn = db.env.write_txn().unwrap();
2174
+ let inner = db.inner.borrow_mut();
2175
+ inner
2176
+ .contracts
2177
+ .put(
2178
+ &mut wtxn,
2179
+ &HashWrapper(hash),
2180
+ &CompactBincode(&Bytecode::new_raw(Bytes::from_static(&[0, 1, 2, 3])).into()),
2181
+ )
2182
+ .unwrap();
2183
+
2184
+ wtxn.commit().unwrap();
2185
+ }
2186
+
2187
+ assert_eq!(
2188
+ db.code_by_hash(hash).unwrap().original_byte_slice(),
2189
+ &[0, 1, 2, 3][..]
2190
+ );
2191
+ }
2192
+
2193
+ #[test]
2194
+ fn test_storage_refe() {
2195
+ let mut db = create_temp_database();
2196
+
2197
+ let account = address!("0000000000000000000000000000000000000001");
2198
+
2199
+ assert_eq!(db.storage(account, U256::ZERO).unwrap(), U256::ZERO);
2200
+ assert_eq!(db.storage(account, U256::from(1)).unwrap(), U256::ZERO);
2201
+
2202
+ {
2203
+ let mut wtxn = db.env.write_txn().unwrap();
2204
+ let inner = db.inner.borrow_mut();
2205
+ inner
2206
+ .storage
2207
+ .put(
2208
+ &mut wtxn,
2209
+ &AddressWrapper(account),
2210
+ &StorageEntryWrapper(U256::from(1), U256::from(2)),
2211
+ )
2212
+ .unwrap();
2213
+
2214
+ wtxn.commit().unwrap();
2215
+ }
2216
+
2217
+ assert_eq!(db.storage(account, U256::from(1)).unwrap(), U256::from(2));
2218
+ }
2219
+
2220
+ #[test]
2221
+ fn test_block_hash() {
2222
+ let mut db = create_temp_database();
2223
+
2224
+ let hash = db.block_hash(1).unwrap();
2225
+ assert_eq!(hash, B256::ZERO);
2226
+
2227
+ {
2228
+ let mut wtxn = db.env.write_txn().unwrap();
2229
+ let inner = db.inner.borrow_mut();
2230
+
2231
+ inner
2232
+ .blocks
2233
+ .put(
2234
+ &mut wtxn,
2235
+ &1,
2236
+ &CompactBincode(&BlockHeaderData {
2237
+ hash: b256!(
2238
+ "0000000000000000000000000000000000000000000000000000000000000001"
2239
+ ),
2240
+ ..Default::default()
2241
+ }),
2242
+ )
2243
+ .unwrap();
2244
+
2245
+ wtxn.commit().unwrap();
2246
+ }
2247
+
2248
+ let hash = db.block_hash(1).unwrap();
2249
+ assert_eq!(
2250
+ hash,
2251
+ b256!("0000000000000000000000000000000000000000000000000000000000000001")
2252
+ );
2253
+ }
2254
+
2255
+ #[test]
2256
+ fn test_open_multiple_same_path() {
2257
+ let path = tempfile::Builder::new()
2258
+ .prefix("evm.mdb")
2259
+ .tempdir()
2260
+ .unwrap();
2261
+
2262
+ let db1 =
2263
+ PersistentDB::new(PersistentDBOptions::new(path.path().to_path_buf())).expect("db1");
2264
+
2265
+ let db2 =
2266
+ PersistentDB::new(PersistentDBOptions::new(path.path().to_path_buf())).expect("db2");
2267
+
2268
+ drop(db1);
2269
+ drop(db2);
2270
+
2271
+ assert!(true);
2272
+ }
2273
+
2274
+ #[test]
2275
+ fn test_set_genesis_info() {
2276
+ let mut db = create_temp_database();
2277
+
2278
+ assert_eq!(db.genesis_info, None);
2279
+
2280
+ db.set_genesis_info(Default::default()).expect("ok");
2281
+
2282
+ assert_eq!(db.genesis_info, Some(Default::default()));
2283
+ }
2284
+ #[test]
2285
+ fn test_get_commits_by_block_range() {
2286
+ let db = create_temp_database();
2287
+
2288
+ // Empty range before anything is written.
2289
+ assert!(
2290
+ db.get_commits_by_block_range(1, 3, u64::MAX)
2291
+ .unwrap()
2292
+ .is_empty()
2293
+ );
2294
+
2295
+ // Write blocks 1..=3; block N has N transactions, inserted in reverse sequence order to
2296
+ // prove the reader returns them ordered by (block_number, sequence).
2297
+ {
2298
+ let mut wtxn = db.env.write_txn().unwrap();
2299
+ let inner = db.inner.borrow();
2300
+
2301
+ for block_number in 1u64..=3 {
2302
+ inner
2303
+ .blocks
2304
+ .put(
2305
+ &mut wtxn,
2306
+ &block_number,
2307
+ &CompactBincode(&BlockHeaderData {
2308
+ number: block_number as u32,
2309
+ transactions_count: block_number as u16,
2310
+ ..Default::default()
2311
+ }),
2312
+ )
2313
+ .unwrap();
2314
+
2315
+ inner
2316
+ .proofs
2317
+ .put(
2318
+ &mut wtxn,
2319
+ &block_number,
2320
+ &CompactBincode(&ProofData {
2321
+ round: block_number as u32,
2322
+ ..Default::default()
2323
+ }),
2324
+ )
2325
+ .unwrap();
2326
+
2327
+ for sequence in (0..block_number).rev() {
2328
+ inner
2329
+ .transactions
2330
+ .put(
2331
+ &mut wtxn,
2332
+ &TransactionKey::new(block_number, sequence as u16),
2333
+ &CompactBincode(&TransactionData {
2334
+ block_number: block_number as u32,
2335
+ index: sequence as u32,
2336
+ tx_hash: B256::from(U256::from(block_number * 100 + sequence)),
2337
+ ..Default::default()
2338
+ }),
2339
+ )
2340
+ .unwrap();
2341
+ }
2342
+ }
2343
+
2344
+ wtxn.commit().unwrap();
2345
+ }
2346
+
2347
+ // Full range (unbounded budget): blocks ascending, transactions per block in sequence order.
2348
+ let commits = db.get_commits_by_block_range(1, 3, u64::MAX).unwrap();
2349
+ assert_eq!(commits.len(), 3);
2350
+
2351
+ for (index, (proof, header, transactions)) in commits.iter().enumerate() {
2352
+ let block_number = (index + 1) as u64;
2353
+
2354
+ assert_eq!(header.number, block_number as u32);
2355
+ assert_eq!(proof.round, block_number as u32);
2356
+ assert_eq!(transactions.len(), block_number as usize);
2357
+
2358
+ for (sequence, transaction) in transactions.iter().enumerate() {
2359
+ assert_eq!(transaction.index, sequence as u32);
2360
+ assert_eq!(transaction.block_number, block_number as u32);
2361
+ }
2362
+ }
2363
+
2364
+ // Sub-range returns only the requested block.
2365
+ let commits = db.get_commits_by_block_range(2, 2, u64::MAX).unwrap();
2366
+ assert_eq!(commits.len(), 1);
2367
+ assert_eq!(commits[0].1.number, 2);
2368
+ assert_eq!(commits[0].2.len(), 2);
2369
+
2370
+ // Range extending past the tip stops at the last available block.
2371
+ let commits = db.get_commits_by_block_range(2, 99, u64::MAX).unwrap();
2372
+ assert_eq!(commits.len(), 2);
2373
+
2374
+ // A too tiny byte budget stops early and does not make progress.
2375
+ let commits = db.get_commits_by_block_range(1, 3, 1).unwrap();
2376
+ assert!(commits.is_empty());
2377
+ }
2378
+
2379
+ #[test]
2380
+ #[should_panic(expected = "must be <= to_block_number")]
2381
+ fn test_get_commits_by_block_range_panics_when_from_exceeds_to() {
2382
+ let db = create_temp_database();
2383
+ let _ = db.get_commits_by_block_range(3, 1, u64::MAX);
2384
+ }
2385
+
2386
+ #[test]
2387
+ #[should_panic(expected = "must be > 0")]
2388
+ fn test_get_commits_by_block_range_panics_when_max_bytes_0() {
2389
+ let db = create_temp_database();
2390
+ let _ = db.get_commits_by_block_range(1, 3, 0);
2391
+ }
2392
+
2393
+ #[test]
2394
+ fn test_get_commits_by_block_range_respects_max_bytes() {
2395
+ let db = create_temp_database();
2396
+
2397
+ // A commit's budget cost is its payload_size plus a fixed per-commit overhead. Use a payload
2398
+ // large enough to dominate that overhead so the expected counts below are unambiguous without
2399
+ // coupling the test to the exact overhead constant.
2400
+ const PAYLOAD: u32 = 1_000_000;
2401
+
2402
+ {
2403
+ let mut wtxn = db.env.write_txn().unwrap();
2404
+ let inner = db.inner.borrow();
2405
+
2406
+ for block_number in 1u64..=3 {
2407
+ inner
2408
+ .blocks
2409
+ .put(
2410
+ &mut wtxn,
2411
+ &block_number,
2412
+ &CompactBincode(&BlockHeaderData {
2413
+ number: block_number as u32,
2414
+ payload_size: PAYLOAD,
2415
+ ..Default::default()
2416
+ }),
2417
+ )
2418
+ .unwrap();
2419
+
2420
+ inner
2421
+ .proofs
2422
+ .put(
2423
+ &mut wtxn,
2424
+ &block_number,
2425
+ &CompactBincode(&ProofData::default()),
2426
+ )
2427
+ .unwrap();
2428
+ }
2429
+
2430
+ wtxn.commit().unwrap();
2431
+ }
2432
+
2433
+ let count = |max_bytes: u64| {
2434
+ db.get_commits_by_block_range(1, 3, max_bytes)
2435
+ .unwrap()
2436
+ .len()
2437
+ };
2438
+
2439
+ // The budget bounds how many commits come back: ~1 payload fits one, ~2 two, ~3 all three.
2440
+ const PER_COMMIT_OVERHEAD_BYTES: u64 = 1024;
2441
+ assert_eq!(count(PAYLOAD as u64 + PER_COMMIT_OVERHEAD_BYTES), 1);
2442
+ assert_eq!(count(2 * (PAYLOAD as u64 + PER_COMMIT_OVERHEAD_BYTES)), 2);
2443
+ assert_eq!(count(3 * (PAYLOAD as u64 + PER_COMMIT_OVERHEAD_BYTES)), 3);
2444
+
2445
+ // An unbounded budget returns the whole range.
2446
+ assert_eq!(count(u64::MAX), 3);
2447
+ }
2448
+
2449
+ #[test]
2450
+ fn test_txn_read_db_serves_all_reads() {
2451
+ // TxnReadDb answers every read kind through its single held txn, matching what the
2452
+ // transient DatabaseRef path returns (including the empties for unknown entries).
2453
+ let db = create_temp_database();
2454
+
2455
+ let account = address!("0000000000000000000000000000000000000001");
2456
+ let code = Bytecode::new_raw(Bytes::from_static(&[0, 1, 2, 3]));
2457
+ let code_hash = code.hash_slow();
2458
+ let block_hash = b256!("0000000000000000000000000000000000000000000000000000000000000001");
2459
+
2460
+ {
2461
+ let mut wtxn = db.env.write_txn().unwrap();
2462
+ let inner = db.inner.borrow_mut();
2463
+
2464
+ inner
2465
+ .accounts
2466
+ .put(
2467
+ &mut wtxn,
2468
+ &AddressWrapper(account),
2469
+ &CompactBincode(&StoredAccountInfo::new(U256::from(100), 7, code_hash)),
2470
+ )
2471
+ .unwrap();
2472
+ inner
2473
+ .contracts
2474
+ .put(
2475
+ &mut wtxn,
2476
+ &HashWrapper(code_hash),
2477
+ &CompactBincode(&code.clone().into()),
2478
+ )
2479
+ .unwrap();
2480
+ inner
2481
+ .storage
2482
+ .put(
2483
+ &mut wtxn,
2484
+ &AddressWrapper(account),
2485
+ &StorageEntryWrapper(U256::from(1), U256::from(42)),
2486
+ )
2487
+ .unwrap();
2488
+ inner
2489
+ .blocks
2490
+ .put(
2491
+ &mut wtxn,
2492
+ &1,
2493
+ &CompactBincode(&BlockHeaderData {
2494
+ hash: block_hash,
2495
+ ..Default::default()
2496
+ }),
2497
+ )
2498
+ .unwrap();
2499
+
2500
+ wtxn.commit().unwrap();
2501
+ }
2502
+
2503
+ let read_db = TxnDatabaseReader::new(&db).unwrap();
2504
+
2505
+ let info = read_db.basic_ref(account).unwrap().expect("account");
2506
+ assert_eq!(info.balance, U256::from(100));
2507
+ assert_eq!(info.nonce, 7);
2508
+ assert_eq!(info.code_hash, code_hash);
2509
+
2510
+ assert_eq!(
2511
+ read_db
2512
+ .code_by_hash_ref(code_hash)
2513
+ .unwrap()
2514
+ .original_byte_slice(),
2515
+ &[0, 1, 2, 3][..]
2516
+ );
2517
+ assert_eq!(
2518
+ read_db.storage_ref(account, U256::from(1)).unwrap(),
2519
+ U256::from(42)
2520
+ );
2521
+ assert_eq!(
2522
+ read_db.storage_ref(account, U256::from(2)).unwrap(),
2523
+ U256::ZERO
2524
+ );
2525
+ assert_eq!(read_db.block_hash_ref(1).unwrap(), block_hash);
2526
+
2527
+ // Unknown entries return the documented empties.
2528
+ let other = address!("0000000000000000000000000000000000000002");
2529
+ assert_eq!(read_db.basic_ref(other).unwrap(), None);
2530
+ assert_eq!(read_db.block_hash_ref(2).unwrap(), B256::ZERO);
2531
+ }
2532
+
2533
+ #[test]
2534
+ fn test_commit_persists_transactions_for_range_read() {
2535
+ // Exercises the real write path (commit_to_db via db.commit) end to end, unlike
2536
+ // test_get_commits_by_block_range which writes the transactions DB directly. Guards against a
2537
+ // key mismatch between how commit_to_db writes transactions and how get_commits_by_block_range
2538
+ // scans them.
2539
+ let db = create_temp_database();
2540
+
2541
+ let block_number = 1u64;
2542
+ let transaction_count = 3u16;
2543
+
2544
+ let transactions: Vec<TransactionData> = (0..transaction_count)
2545
+ .map(|index| TransactionData {
2546
+ block_number: block_number as u32,
2547
+ index: index as u32,
2548
+ tx_hash: B256::from(U256::from(100 + index as u64)),
2549
+ ..Default::default()
2550
+ })
2551
+ .collect();
2552
+
2553
+ let mut state_commit = StateCommit {
2554
+ key: CommitKey(block_number, 0, B256::ZERO),
2555
+ change_set: StateChangeset::default(),
2556
+ results: Default::default(),
2557
+ };
2558
+
2559
+ let commit_data = CommitData {
2560
+ proof: ProofData::default(),
2561
+ header: BlockHeaderData {
2562
+ number: block_number as u32,
2563
+ transactions_count: transaction_count,
2564
+ ..Default::default()
2565
+ },
2566
+ transactions,
2567
+ };
2568
+
2569
+ db.commit(&mut state_commit, &Some(commit_data)).unwrap();
2570
+
2571
+ // Read back through the same path findBlocks/restore use.
2572
+ let commits = db
2573
+ .get_commits_by_block_range(block_number, block_number, u64::MAX)
2574
+ .unwrap();
2575
+ assert_eq!(commits.len(), 1);
2576
+ assert_eq!(
2577
+ commits[0].2.len(),
2578
+ transaction_count as usize,
2579
+ "transactions committed via commit_to_db must be read back by get_commits_by_block_range"
2580
+ );
2581
+ }
2582
+
2583
+ #[test]
2584
+ fn test_commit_rejects_already_committed_block() {
2585
+ let db = create_temp_database();
2586
+ let block_number = 1u64;
2587
+
2588
+ let make_commit = || {
2589
+ (
2590
+ StateCommit {
2591
+ key: CommitKey(block_number, 0, B256::ZERO),
2592
+ change_set: StateChangeset::default(),
2593
+ results: Default::default(),
2594
+ },
2595
+ CommitData {
2596
+ proof: ProofData::default(),
2597
+ header: BlockHeaderData {
2598
+ number: block_number as u32,
2599
+ ..Default::default()
2600
+ },
2601
+ transactions: vec![],
2602
+ },
2603
+ )
2604
+ };
2605
+
2606
+ // First commit of the block succeeds.
2607
+ let (mut state_commit, commit_data) = make_commit();
2608
+ db.commit(&mut state_commit, &Some(commit_data)).unwrap();
2609
+
2610
+ // Committing the same block number again is rejected gracefully, not asserted.
2611
+ let (mut state_commit, commit_data) = make_commit();
2612
+ let result = db.commit(&mut state_commit, &Some(commit_data));
2613
+ assert!(
2614
+ matches!(result, Err(crate::db::Error::State(_))),
2615
+ "expected Err(State(block already committed)), got {result:?}"
2616
+ );
2617
+ }
2618
+
2619
+ #[test]
2620
+ fn test_get_receipts() {
2621
+ let db = create_temp_database();
2622
+
2623
+ let receipts = db.get_receipts_by_block_number(1).unwrap();
2624
+ assert!(receipts.is_empty());
2625
+
2626
+ let hash = b256!("0000000000000000000000000000000000000000000000000000000000000001");
2627
+
2628
+ let (_, receipt) = db.get_receipt(1, hash).unwrap();
2629
+ assert_eq!(receipt, None);
2630
+
2631
+ {
2632
+ let mut wtxn = db.env.write_txn().unwrap();
2633
+
2634
+ let mut tx_receipts: HashMap<B256, TxReceipt> = Default::default();
2635
+ tx_receipts.insert(hash, Default::default());
2636
+
2637
+ db.inner
2638
+ .borrow_mut()
2639
+ .commits
2640
+ .put(
2641
+ &mut wtxn,
2642
+ &1,
2643
+ &CompactBincode(&CommitReceipts {
2644
+ tx_receipts,
2645
+ ..Default::default()
2646
+ }),
2647
+ )
2648
+ .unwrap();
2649
+
2650
+ wtxn.commit().unwrap();
2651
+ }
2652
+
2653
+ let receipts = db.get_receipts_by_block_number(1).unwrap();
2654
+ assert_eq!(receipts.len(), 1);
2655
+ assert_eq!(receipts.get(&hash), Some(&Default::default()));
2656
+
2657
+ let (_, receipt) = db.get_receipt(1, hash).unwrap();
2658
+ assert_eq!(receipt, Some(Default::default()));
2659
+ }
2660
+
2661
+ #[test]
2662
+ fn test_read_receipts() {
2663
+ let db = create_temp_database();
2664
+
2665
+ let target_block = 100;
2666
+ let mut total_receipts = 0;
2667
+
2668
+ {
2669
+ let mut wtxn = db.env.write_txn().unwrap();
2670
+
2671
+ fn random_b256(seed: u64, offset: u64) -> B256 {
2672
+ use std::collections::hash_map::DefaultHasher;
2673
+ use std::hash::{Hash, Hasher};
2674
+ let mut hasher = DefaultHasher::new();
2675
+ seed.hash(&mut hasher);
2676
+
2677
+ B256::from(U256::from(hasher.finish() + offset))
2678
+ }
2679
+
2680
+ for i in 0..target_block {
2681
+ let block_number = (i + 1) as u64;
2682
+
2683
+ let receipts: HashMap<B256, TxReceipt> = [
2684
+ (random_b256(block_number, 0), TxReceipt::default()),
2685
+ (random_b256(block_number, 1), TxReceipt::default()),
2686
+ (random_b256(block_number, 2), TxReceipt::default()),
2687
+ (random_b256(block_number, 3), TxReceipt::default()),
2688
+ ]
2689
+ .into_iter()
2690
+ .collect();
2691
+
2692
+ total_receipts += receipts.len();
2693
+
2694
+ db.inner
2695
+ .borrow_mut()
2696
+ .commits
2697
+ .put(
2698
+ &mut wtxn,
2699
+ &block_number,
2700
+ &CompactBincode(&CommitReceipts {
2701
+ tx_receipts: receipts,
2702
+ ..Default::default()
2703
+ }),
2704
+ )
2705
+ .unwrap();
2706
+ }
2707
+ wtxn.commit().unwrap();
2708
+ }
2709
+
2710
+ const LIMIT: u64 = 7;
2711
+ let mut offset = 0;
2712
+
2713
+ let mut read_block_number = 0;
2714
+ let mut read_receipts = 0;
2715
+
2716
+ loop {
2717
+ let (next, items) = db.get_receipts(offset, LIMIT).unwrap();
2718
+ for (block_number, receipts) in items {
2719
+ read_block_number = block_number;
2720
+ read_receipts += receipts.len();
2721
+ }
2722
+
2723
+ if next.is_none() {
2724
+ break;
2725
+ }
2726
+
2727
+ match next {
2728
+ Some(next) => {
2729
+ offset = next;
2730
+ }
2731
+ None => {
2732
+ break;
2733
+ }
2734
+ }
2735
+ }
2736
+
2737
+ assert_eq!(read_block_number, target_block);
2738
+ assert_eq!(read_receipts, total_receipts);
2739
+ }
2740
+
2741
+ #[test]
2742
+ fn test_get_receipts_by_block_range() {
2743
+ let db = create_temp_database();
2744
+
2745
+ // Empty before anything is written.
2746
+ assert!(db.get_receipts_by_block_range(1, 3).unwrap().is_empty());
2747
+
2748
+ // Write blocks 1..=3; block N gets N receipts with distinct hashes.
2749
+ {
2750
+ let mut wtxn = db.env.write_txn().unwrap();
2751
+ let inner = db.inner.borrow();
2752
+
2753
+ for block_number in 1u64..=3 {
2754
+ let mut tx_receipts: HashMap<B256, TxReceipt> = Default::default();
2755
+ for index in 0..block_number {
2756
+ tx_receipts.insert(
2757
+ B256::from(U256::from(block_number * 100 + index)),
2758
+ Default::default(),
2759
+ );
2760
+ }
2761
+
2762
+ inner
2763
+ .commits
2764
+ .put(
2765
+ &mut wtxn,
2766
+ &block_number,
2767
+ &CompactBincode(&CommitReceipts {
2768
+ tx_receipts,
2769
+ ..Default::default()
2770
+ }),
2771
+ )
2772
+ .unwrap();
2773
+ }
2774
+
2775
+ wtxn.commit().unwrap();
2776
+ }
2777
+
2778
+ // Full range: blocks ascending, receipt count per block matches what was written.
2779
+ let receipts = db.get_receipts_by_block_range(1, 3).unwrap();
2780
+ assert_eq!(receipts.len(), 3);
2781
+ for (index, (block_number, block_receipts)) in receipts.iter().enumerate() {
2782
+ assert_eq!(*block_number, (index + 1) as u64);
2783
+ assert_eq!(block_receipts.len(), *block_number as usize);
2784
+ }
2785
+
2786
+ // Sub-range returns only the requested block.
2787
+ let receipts = db.get_receipts_by_block_range(2, 2).unwrap();
2788
+ assert_eq!(receipts.len(), 1);
2789
+ assert_eq!(receipts[0].0, 2);
2790
+ assert_eq!(receipts[0].1.len(), 2);
2791
+
2792
+ // Range extending past the tip stops at the last available block.
2793
+ let receipts = db.get_receipts_by_block_range(2, 99).unwrap();
2794
+ assert_eq!(receipts.len(), 2);
2795
+ assert_eq!(receipts[0].0, 2);
2796
+ assert_eq!(receipts[1].0, 3);
2797
+ }
2798
+
2799
+ #[test]
2800
+ #[should_panic(expected = "must be <= to_block_number")]
2801
+ fn test_get_receipts_by_block_range_panics_when_from_exceeds_to() {
2802
+ let db = create_temp_database();
2803
+ let _ = db.get_receipts_by_block_range(3, 1);
2804
+ }
2805
+
2806
+ #[test]
2807
+ fn test_get_legacy_attributes() {
2808
+ let db = create_temp_database();
2809
+
2810
+ let address = address!("0000000000000000000000000000000000000001");
2811
+
2812
+ assert_eq!(db.get_legacy_attributes(address).unwrap(), None);
2813
+
2814
+ {
2815
+ let mut wtxn = db.env.write_txn().unwrap();
2816
+
2817
+ db.inner
2818
+ .borrow_mut()
2819
+ .legacy_attributes
2820
+ .put(
2821
+ &mut wtxn,
2822
+ &AddressWrapper(address),
2823
+ &CompactBincode(&LegacyAccountAttributes {
2824
+ legacy_nonce: Some(1234),
2825
+ second_public_key: Some("key".into()),
2826
+ multi_signature: None,
2827
+ }),
2828
+ )
2829
+ .unwrap();
2830
+
2831
+ wtxn.commit().unwrap();
2832
+ }
2833
+
2834
+ assert_eq!(
2835
+ db.get_legacy_attributes(address).unwrap(),
2836
+ Some(LegacyAccountAttributes {
2837
+ legacy_nonce: Some(1234),
2838
+ second_public_key: Some("key".into()),
2839
+ multi_signature: None,
2840
+ })
2841
+ );
2842
+ }
2843
+
2844
+ #[test]
2845
+ fn test_is_empty() {
2846
+ let db = create_temp_database();
2847
+
2848
+ assert_eq!(db.is_empty().unwrap(), true);
2849
+
2850
+ {
2851
+ let mut wtxn = db.env.write_txn().unwrap();
2852
+
2853
+ db.inner
2854
+ .borrow_mut()
2855
+ .blocks
2856
+ .put(
2857
+ &mut wtxn,
2858
+ &1,
2859
+ &CompactBincode(&BlockHeaderData {
2860
+ hash: b256!(
2861
+ "0000000000000000000000000000000000000000000000000000000000000001"
2862
+ ),
2863
+ ..Default::default()
2864
+ }),
2865
+ )
2866
+ .unwrap();
2867
+
2868
+ wtxn.commit().unwrap();
2869
+ }
2870
+
2871
+ assert_eq!(db.is_empty().unwrap(), false);
2872
+ }
2873
+
2874
+ #[test]
2875
+ fn test_get_state() {
2876
+ let db = create_temp_database();
2877
+
2878
+ assert_eq!(db.get_state().unwrap(), (0, 0));
2879
+
2880
+ {
2881
+ let mut wtxn = db.env.write_txn().unwrap();
2882
+
2883
+ db.inner
2884
+ .borrow_mut()
2885
+ .state
2886
+ .put(
2887
+ &mut wtxn,
2888
+ &StaticStringWrapper("total_round"),
2889
+ &Bytes::from_iter(999u64.to_le_bytes()),
2890
+ )
2891
+ .unwrap();
2892
+
2893
+ db.inner
2894
+ .borrow_mut()
2895
+ .blocks
2896
+ .put(
2897
+ &mut wtxn,
2898
+ &255,
2899
+ &CompactBincode(&BlockHeaderData {
2900
+ number: 255,
2901
+ hash: b256!(
2902
+ "0000000000000000000000000000000000000000000000000000000000000001"
2903
+ ),
2904
+ ..Default::default()
2905
+ }),
2906
+ )
2907
+ .unwrap();
2908
+
2909
+ wtxn.commit().unwrap();
2910
+ }
2911
+
2912
+ assert_eq!(db.get_state().unwrap(), (255, 999));
2913
+ }
2914
+
2915
+ #[test]
2916
+ fn test_get_block_number_by_hash() {
2917
+ let db = create_temp_database();
2918
+
2919
+ let hash = b256!("0000000000000000000000000000000000000000000000000000000000000001");
2920
+ assert_eq!(db.get_block_number_by_hash(hash).unwrap(), None);
2921
+
2922
+ {
2923
+ let mut wtxn = db.env.write_txn().unwrap();
2924
+
2925
+ db.inner
2926
+ .borrow_mut()
2927
+ .blocks_hash_number
2928
+ .put(&mut wtxn, &HashWrapper(hash), &10)
2929
+ .unwrap();
2930
+
2931
+ wtxn.commit().unwrap();
2932
+ }
2933
+
2934
+ assert_eq!(db.get_block_number_by_hash(hash).unwrap(), Some(10));
2935
+ }
2936
+
2937
+ #[test]
2938
+ fn test_get_block_header_data() {
2939
+ let db = create_temp_database();
2940
+
2941
+ assert_eq!(db.get_block_header_data(1).unwrap(), None);
2942
+
2943
+ let hash = b256!("0000000000000000000000000000000000000000000000000000000000000001");
2944
+ {
2945
+ let mut wtxn = db.env.write_txn().unwrap();
2946
+
2947
+ db.inner
2948
+ .borrow_mut()
2949
+ .blocks
2950
+ .put(
2951
+ &mut wtxn,
2952
+ &1,
2953
+ &CompactBincode(&BlockHeaderData {
2954
+ number: 1,
2955
+ hash,
2956
+ ..Default::default()
2957
+ }),
2958
+ )
2959
+ .unwrap();
2960
+
2961
+ wtxn.commit().unwrap();
2962
+ }
2963
+
2964
+ assert_eq!(
2965
+ db.get_block_header_data(1).unwrap(),
2966
+ Some(BlockHeaderData {
2967
+ hash,
2968
+ number: 1,
2969
+ ..Default::default()
2970
+ })
2971
+ );
2972
+ }
2973
+
2974
+ #[test]
2975
+ fn test_get_proof_data() {
2976
+ let db = create_temp_database();
2977
+
2978
+ assert_eq!(db.get_proof_data(1).unwrap(), None);
2979
+
2980
+ {
2981
+ let mut wtxn = db.env.write_txn().unwrap();
2982
+
2983
+ db.inner
2984
+ .borrow_mut()
2985
+ .proofs
2986
+ .put(
2987
+ &mut wtxn,
2988
+ &1,
2989
+ &CompactBincode(&ProofData {
2990
+ round: 1,
2991
+ validator_set: 1234,
2992
+ ..Default::default()
2993
+ }),
2994
+ )
2995
+ .unwrap();
2996
+
2997
+ wtxn.commit().unwrap();
2998
+ }
2999
+
3000
+ assert_eq!(
3001
+ db.get_proof_data(1).unwrap(),
3002
+ Some(ProofData {
3003
+ round: 1,
3004
+ validator_set: 1234,
3005
+ ..Default::default()
3006
+ })
3007
+ );
3008
+ }
3009
+
3010
+ #[test]
3011
+ fn test_transaction_key_encode_decode_roundtrip() {
3012
+ for (block_number, index) in [(0u64, 0u16), (1, 2), (5, 9999), (u64::MAX, u16::MAX)] {
3013
+ let key = TransactionKey::new(block_number, index);
3014
+ let encoded = <TransactionKey as BytesEncode>::bytes_encode(&key).unwrap();
3015
+ assert_eq!(encoded.len(), 10, "key is 8-byte block + 2-byte index");
3016
+
3017
+ let decoded = <TransactionKey as BytesDecode>::bytes_decode(&encoded).unwrap();
3018
+ assert_eq!(decoded, key);
3019
+ }
3020
+ }
3021
+
3022
+ #[test]
3023
+ fn test_transaction_key_orders_by_block_then_index() {
3024
+ // The transactions DB relies on byte (memcmp) key order matching numeric
3025
+ // (block_number, index) order, so get_commits_by_block_range can scan by block number.
3026
+ // Big-endian encoding is what guarantees this (e.g. block 2 sorts before block 10).
3027
+ let ascending = [
3028
+ TransactionKey::new(0, 0),
3029
+ TransactionKey::new(0, 1),
3030
+ TransactionKey::new(0, u16::MAX),
3031
+ TransactionKey::new(1, 0), // block 1 sorts after every transaction of block 0
3032
+ TransactionKey::new(2, 0),
3033
+ TransactionKey::new(10, 0), // numeric order, not lexicographic on decimal
3034
+ TransactionKey::new(u64::MAX, 0),
3035
+ TransactionKey::new(u64::MAX, u16::MAX),
3036
+ ];
3037
+
3038
+ for window in ascending.windows(2) {
3039
+ let lo = <TransactionKey as BytesEncode>::bytes_encode(&window[0]).unwrap();
3040
+ let hi = <TransactionKey as BytesEncode>::bytes_encode(&window[1]).unwrap();
3041
+ assert!(lo < hi, "encoded keys must sort by (block, index)");
3042
+ // The derived Ord must agree with the on-disk byte order.
3043
+ assert!(window[0] < window[1]);
3044
+ }
3045
+ }
3046
+
3047
+ #[test]
3048
+ fn test_transaction_key_token_roundtrip_and_lenient_parse() {
3049
+ assert_eq!(TransactionKey::new(5, 2).to_token(), "5-2");
3050
+
3051
+ let key = TransactionKey::new(123, 45);
3052
+ assert_eq!(TransactionKey::parse(&key.to_token()), Some(key));
3053
+
3054
+ // Malformed or out-of-range tokens parse to None (treated as "no such transaction").
3055
+ assert_eq!(TransactionKey::parse("nope"), None);
3056
+ assert_eq!(TransactionKey::parse("-5"), None);
3057
+ assert_eq!(TransactionKey::parse("1-2-3"), None);
3058
+ assert_eq!(TransactionKey::parse("1-70000"), None); // index exceeds u16::MAX
3059
+ }
3060
+
3061
+ #[test]
3062
+ fn test_transaction_key_range_bounds_capture_a_block_range() {
3063
+ // Mirrors the scan bounds get_commits_by_block_range builds for [from, to].
3064
+ let from = TransactionKey::new(5, 0);
3065
+ let to = TransactionKey::new(7, u16::MAX);
3066
+
3067
+ for block in 5..=7u64 {
3068
+ for index in [0u16, 1, 1000, u16::MAX] {
3069
+ let key = TransactionKey::new(block, index);
3070
+ assert!(
3071
+ key >= from && key <= to,
3072
+ "{block}-{index} should be within range"
3073
+ );
3074
+ }
3075
+ }
3076
+
3077
+ // The neighbouring blocks fall outside the range on each side.
3078
+ assert!(TransactionKey::new(4, u16::MAX) < from);
3079
+ assert!(TransactionKey::new(8, 0) > to);
3080
+ }
3081
+
3082
+ #[test]
3083
+ fn test_get_transaction_data() {
3084
+ let db = create_temp_database();
3085
+
3086
+ // Lookups go through the "<block>-<index>" token; before anything is written it is absent,
3087
+ // and malformed/out-of-range tokens resolve to None rather than erroring.
3088
+ assert_eq!(db.get_transaction_data("1-0".into()).unwrap(), None);
3089
+ assert_eq!(db.get_transaction_data("not-a-key".into()).unwrap(), None);
3090
+ assert_eq!(db.get_transaction_data("1-70000".into()).unwrap(), None);
3091
+
3092
+ let hash = b256!("0000000000000000000000000000000000000000000000000000000000000001");
3093
+
3094
+ {
3095
+ let mut wtxn = db.env.write_txn().unwrap();
3096
+
3097
+ db.inner
3098
+ .borrow_mut()
3099
+ .transactions
3100
+ .put(
3101
+ &mut wtxn,
3102
+ &TransactionKey::new(1, 0),
3103
+ &CompactBincode(&TransactionData {
3104
+ tx_hash: hash,
3105
+ ..Default::default()
3106
+ }),
3107
+ )
3108
+ .unwrap();
3109
+
3110
+ wtxn.commit().unwrap();
3111
+ }
3112
+
3113
+ assert_eq!(
3114
+ db.get_transaction_data("1-0".into()).unwrap(),
3115
+ Some(TransactionData {
3116
+ tx_hash: hash,
3117
+ ..Default::default()
3118
+ })
3119
+ );
3120
+ }
3121
+
3122
+ #[test]
3123
+ fn test_get_transaction_hash_by_hash() {
3124
+ let db = create_temp_database();
3125
+
3126
+ let hash = b256!("0000000000000000000000000000000000000000000000000000000000000001");
3127
+
3128
+ assert_eq!(db.get_transaction_key_by_hash(hash).unwrap(), None);
3129
+
3130
+ {
3131
+ let mut wtxn = db.env.write_txn().unwrap();
3132
+
3133
+ db.inner
3134
+ .borrow_mut()
3135
+ .transactions_hash_key
3136
+ .put(&mut wtxn, &HashWrapper(hash), &TransactionKey::new(1, 0))
3137
+ .unwrap();
3138
+
3139
+ wtxn.commit().unwrap();
3140
+ }
3141
+
3142
+ // The stored typed key is returned to the napi boundary as its "<block>-<index>" token.
3143
+ assert_eq!(
3144
+ db.get_transaction_key_by_hash(hash).unwrap(),
3145
+ Some("1-0".to_string())
3146
+ );
3147
+ }
3148
+
3149
+ #[test]
3150
+ fn test_commit() {
3151
+ let db = create_temp_database_opts(|opts| {
3152
+ opts.history_size = Some(8);
3153
+ });
3154
+
3155
+ let block_hash = b256!("0000000000000000000000000000000000000000000000000000000000000001");
3156
+ let key = CommitKey(1, 0, block_hash);
3157
+
3158
+ let account1 = address!("0000000000000000000000000000000000000001");
3159
+ let account2 = address!("0000000000000000000000000000000000000002");
3160
+
3161
+ let mut legacy_attributes: BTreeMap<Address, LegacyAccountAttributes> = Default::default();
3162
+ legacy_attributes.insert(
3163
+ account1,
3164
+ LegacyAccountAttributes {
3165
+ legacy_nonce: Some(2),
3166
+ ..Default::default()
3167
+ },
3168
+ );
3169
+ legacy_attributes.insert(
3170
+ account2,
3171
+ LegacyAccountAttributes {
3172
+ legacy_nonce: Some(9),
3173
+ ..Default::default()
3174
+ },
3175
+ );
3176
+
3177
+ let mut legacy_cold_wallets: BTreeMap<LegacyAddress, LegacyColdWallet> = Default::default();
3178
+ let legacy_addresses = [
3179
+ "DBYyh2vXcigrJGUHfvmYxVxEqeH7vomw6x",
3180
+ "D5KU9KrMYXdkEsRbv4y8hvetGbsJwf9z3P",
3181
+ "DJA2sqCbnmR63sD8doGrXrK3fCiqcA4GUw",
3182
+ "DJmvhhiQFSrEQCq9FUxvcLcpcBjx7K3yLt",
3183
+ ];
3184
+
3185
+ for (index, legacy) in legacy_addresses.iter().enumerate() {
3186
+ let legacy_address = (*legacy).try_into().unwrap();
3187
+
3188
+ legacy_cold_wallets.insert(
3189
+ legacy_address,
3190
+ LegacyColdWallet {
3191
+ address: legacy_address,
3192
+ balance: U256::from(index as u64),
3193
+ ..Default::default()
3194
+ },
3195
+ );
3196
+ }
3197
+
3198
+ let mut merged_legacy_cold_wallets: BTreeMap<Address, (B256, LegacyAddress)> =
3199
+ Default::default();
3200
+ merged_legacy_cold_wallets.insert(
3201
+ account1,
3202
+ (
3203
+ b256!("0000000000000000000000000000000000000000000000000000000000000001"),
3204
+ "DBYyh2vXcigrJGUHfvmYxVxEqeH7vomw6x".try_into().unwrap(),
3205
+ ),
3206
+ );
3207
+
3208
+ let mut results: BTreeMap<B256, (ExecutionResult, u64)> = Default::default();
3209
+ results.insert(
3210
+ b256!("1000000000000000000000000000000000000000000000000000000000000000"),
3211
+ (
3212
+ ExecutionResult::Success {
3213
+ reason: SuccessReason::Stop,
3214
+ gas: ResultGas::default(),
3215
+ logs: Default::default(),
3216
+ output: revm::context::result::Output::Call(Default::default()),
3217
+ },
3218
+ 1234,
3219
+ ),
3220
+ );
3221
+
3222
+ let mut state = StateCommit {
3223
+ key,
3224
+ change_set: StateChangeset {
3225
+ accounts: vec![
3226
+ (
3227
+ account1,
3228
+ Some(AccountInfo {
3229
+ balance: U256::from(1),
3230
+ nonce: 1,
3231
+ ..Default::default()
3232
+ }),
3233
+ ),
3234
+ (account2, None),
3235
+ ],
3236
+ storage: vec![
3237
+ StorageChangeset {
3238
+ address: address!("0000000000000000000000000000000000000003"),
3239
+ storage: vec![(U256::from(1), StorageSlot::new(U256::from(2)))],
3240
+ ..Default::default()
3241
+ },
3242
+ StorageChangeset {
3243
+ address: address!("0000000000000000000000000000000000000004"),
3244
+ storage: vec![],
3245
+ wipe_storage: true,
3246
+ },
3247
+ StorageChangeset {
3248
+ address: address!("0000000000000000000000000000000000000003"),
3249
+ storage: vec![(U256::from(1), StorageSlot::new(U256::from(2)))],
3250
+ wipe_storage: true,
3251
+ },
3252
+ StorageChangeset {
3253
+ address: address!("0000000000000000000000000000000000000004"),
3254
+ storage: vec![(U256::from(1), StorageSlot::new(U256::from(2)))],
3255
+ ..Default::default()
3256
+ },
3257
+ StorageChangeset {
3258
+ address: address!("0000000000000000000000000000000000000004"),
3259
+ storage: vec![(U256::from(1), StorageSlot::new(U256::ZERO))],
3260
+ ..Default::default()
3261
+ },
3262
+ StorageChangeset {
3263
+ address: address!("0000000000000000000000000000000000000005"),
3264
+ storage: vec![(U256::from(1), StorageSlot::new(U256::from(2)))],
3265
+ ..Default::default()
3266
+ },
3267
+ StorageChangeset {
3268
+ address: address!("0000000000000000000000000000000000000005"),
3269
+ storage: vec![(U256::from(1), StorageSlot::new(U256::from(2)))],
3270
+ ..Default::default()
3271
+ },
3272
+ ],
3273
+ contracts: vec![(
3274
+ b256!("1000000000000000000000000000000000000000000000000000000000000000"),
3275
+ Bytecode::new_legacy(Bytes(Bytes::from_static(&[1, 2, 3, 4]).into())),
3276
+ )],
3277
+ legacy_attributes,
3278
+ legacy_cold_wallets,
3279
+ merged_legacy_cold_wallets,
3280
+ },
3281
+ results,
3282
+ };
3283
+ let data = CommitData {
3284
+ transactions: vec![TransactionData::default()],
3285
+ ..Default::default()
3286
+ };
3287
+
3288
+ db.commit(&mut state, &Some(data)).unwrap();
3289
+ }
3290
+
3291
+ fn create_temp_database() -> PersistentDB {
3292
+ let db = create_temp_database_opts(|_| {});
3293
+ db
3294
+ }
3295
+
3296
+ fn create_temp_database_opts<F>(callback: F) -> PersistentDB
3297
+ where
3298
+ F: FnOnce(&mut PersistentDBOptions),
3299
+ {
3300
+ let path = tempfile::Builder::new()
3301
+ .prefix("evm.mdb")
3302
+ .tempdir()
3303
+ .unwrap();
3304
+
3305
+ let mut opts = PersistentDBOptions::new(path.path().to_path_buf());
3306
+ callback(&mut opts);
3307
+
3308
+ let db = PersistentDB::new(opts).expect("database");
3309
+ db
3310
+ }
3311
+ }