@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.
@@ -0,0 +1,615 @@
1
+ use std::collections::BTreeMap;
2
+
3
+ use alloy_sol_types::SolEvent;
4
+ use rayon::{
5
+ iter::{IntoParallelRefMutIterator, ParallelIterator},
6
+ slice::ParallelSliceMut,
7
+ };
8
+ use revm::{
9
+ context::result::ExecutionResult,
10
+ database::{DatabaseCommitExt, WrapDatabaseRef},
11
+ primitives::{Address, B256, map::HashMap},
12
+ };
13
+
14
+ use crate::{
15
+ db::{CommitData, CommitKey, Error, GenesisInfo, PendingCommit, PersistentDB},
16
+ state_changes::{self, AccountMergeInfo, AccountUpdate},
17
+ };
18
+
19
+ #[derive(Clone, Debug, Default)]
20
+ pub struct StateCommit {
21
+ pub key: CommitKey,
22
+ pub change_set: state_changes::StateChangeset,
23
+ pub results: BTreeMap<B256, (ExecutionResult, u64)>,
24
+ }
25
+
26
+ pub fn build_commit(pending_commit: &mut PendingCommit) -> Result<StateCommit, crate::db::Error> {
27
+ assert!(pending_commit.built_commit.is_none());
28
+ let mut state_builder = revm::database::State::builder()
29
+ .with_cached_prestate(std::mem::take(&mut pending_commit.cache))
30
+ .build();
31
+
32
+ state_builder.transition_state = Some(std::mem::take(&mut pending_commit.transitions));
33
+ state_builder
34
+ .merge_transitions(revm::database::states::bundle_state::BundleRetention::PlainState);
35
+
36
+ let bundle = state_builder.take_bundle();
37
+ let mut change_set = state_changes::bundle_into_change_set(bundle);
38
+
39
+ change_set.legacy_attributes = std::mem::take(&mut pending_commit.legacy_attributes);
40
+ change_set.legacy_cold_wallets = std::mem::take(&mut pending_commit.legacy_cold_wallets);
41
+ change_set.merged_legacy_cold_wallets =
42
+ std::mem::take(&mut pending_commit.merged_legacy_cold_wallets)
43
+ .into_iter()
44
+ .filter_map(|(key, legacy)| legacy.map(|v| (key, v)))
45
+ .collect();
46
+
47
+ let mut state_commit = StateCommit {
48
+ key: pending_commit.key,
49
+ change_set,
50
+ results: std::mem::take(&mut pending_commit.results),
51
+ };
52
+
53
+ finalize(&mut state_commit);
54
+
55
+ Ok(state_commit)
56
+ }
57
+
58
+ pub fn apply_rewards(
59
+ db: &mut PersistentDB,
60
+ pending: &mut PendingCommit,
61
+ rewards: HashMap<Address, u128>,
62
+ ) -> Result<(), crate::db::Error> {
63
+ let mut state = revm::database::State::builder()
64
+ .with_bundle_update()
65
+ .with_cached_prestate(std::mem::take(&mut pending.cache))
66
+ .with_database(WrapDatabaseRef(&db))
67
+ .build();
68
+
69
+ state
70
+ .increment_balances(rewards)
71
+ .map_err(|err| crate::db::Error::State(format!("increment balances err={}", err)))?;
72
+
73
+ if let Some(transition_state) = state.transition_state.take() {
74
+ // println!("transition state {:#?}", transition_state);
75
+ pending
76
+ .transitions
77
+ .add_transitions(transition_state.transitions.into_iter());
78
+ }
79
+
80
+ pending.cache = std::mem::take(&mut state.cache);
81
+ // println!("cache {:#?}", pending.cache.accounts);
82
+
83
+ Ok(())
84
+ }
85
+
86
+ pub fn commit_to_db(
87
+ db: &mut PersistentDB,
88
+ mut pending_commit: PendingCommit,
89
+ commit_data: Option<CommitData>,
90
+ ) -> Result<Vec<AccountUpdate>, crate::db::Error> {
91
+ let genesis_info = db.genesis_info.clone();
92
+ let mut commit = match pending_commit.built_commit {
93
+ Some(commit) => commit,
94
+ None => build_commit(&mut pending_commit)?,
95
+ };
96
+
97
+ commit_with_resize_retry(|| db.commit(&mut commit, &commit_data), || db.resize())?;
98
+
99
+ Ok(collect_dirty_accounts(commit, &genesis_info))
100
+ }
101
+
102
+ /// Maximum number of resize-and-retry attempts after an initial `DbFull` on commit.
103
+ const MAX_RESIZE_RETRIES: usize = 3;
104
+
105
+ fn commit_with_resize_retry(
106
+ mut try_commit: impl FnMut() -> Result<(), Error>,
107
+ mut resize: impl FnMut() -> Result<(), Error>,
108
+ ) -> Result<(), Error> {
109
+ for _ in 0..=MAX_RESIZE_RETRIES {
110
+ match try_commit() {
111
+ Ok(()) => return Ok(()),
112
+ Err(Error::DbFull) => resize()?,
113
+ Err(err) => return Err(err),
114
+ }
115
+ }
116
+ Err(Error::DbFull)
117
+ }
118
+
119
+ fn finalize(state: &mut StateCommit) {
120
+ state.change_set.accounts.par_sort_unstable_by_key(|a| a.0);
121
+ state.change_set.contracts.par_sort_unstable_by_key(|a| a.0);
122
+
123
+ state
124
+ .change_set
125
+ .storage
126
+ .par_iter_mut()
127
+ .for_each(|s| s.storage.par_sort_unstable_by_key(|slot| slot.0));
128
+
129
+ state
130
+ .change_set
131
+ .storage
132
+ .par_sort_unstable_by_key(|a| a.address);
133
+ }
134
+
135
+ fn collect_dirty_accounts(
136
+ commit: StateCommit,
137
+ genesis_info: &Option<GenesisInfo>,
138
+ ) -> Vec<AccountUpdate> {
139
+ let mut dirty_accounts = HashMap::with_capacity(commit.change_set.accounts.len());
140
+
141
+ for (address, account) in commit.change_set.accounts {
142
+ if let Some(account) = account {
143
+ dirty_accounts.insert(
144
+ address,
145
+ AccountUpdate {
146
+ address,
147
+ balance: account.balance,
148
+ nonce: account.nonce,
149
+ vote: None,
150
+ unvote: None,
151
+ username: None,
152
+ username_resigned: false,
153
+ merge_info: commit
154
+ .change_set
155
+ .merged_legacy_cold_wallets
156
+ .get(&address)
157
+ .map(|value| AccountMergeInfo {
158
+ legacy_address: value.1,
159
+ transaction_hash: value.0,
160
+ }),
161
+ },
162
+ );
163
+ }
164
+ }
165
+
166
+ if let Some(info) = genesis_info {
167
+ for (receipt, _) in commit.results.values() {
168
+ match receipt {
169
+ ExecutionResult::Success { logs, .. } => {
170
+ for log in logs {
171
+ match log.address {
172
+ _ if log.address == info.validator_contract => {
173
+ // Attempt to decode the log as a Voted event
174
+ if let Ok(event) = crate::events::Voted::decode_log(&log) {
175
+ // println!(
176
+ // "Voted event (from={:?} to={:?})",
177
+ // event.data.voter, event.data.validator,
178
+ // );
179
+
180
+ dirty_accounts.get_mut(&event.voter).and_then(|account| {
181
+ account.vote = Some(event.validator);
182
+ account.unvote = None; // cancel out any previous unvote if one happened in same commit
183
+ Some(account)
184
+ });
185
+
186
+ continue;
187
+ }
188
+
189
+ // Attempt to decode the log as a Unvoted event
190
+ if let Ok(event) = crate::events::Unvoted::decode_log(&log) {
191
+ // println!(
192
+ // "Unvoted event (from={:?} removed vote={:?})",
193
+ // event.data.voter, event.data.validator,
194
+ // );
195
+
196
+ dirty_accounts.get_mut(&event.voter).and_then(|account| {
197
+ account.unvote = Some(event.validator);
198
+ account.vote = None; // cancel out any previous vote if one happened in same commit
199
+ Some(account)
200
+ });
201
+
202
+ continue;
203
+ }
204
+ }
205
+ _ if log.address == info.username_contract => {
206
+ // Attempt to decode log as a UsernameRegistered event
207
+ if let Ok(event) =
208
+ crate::events::UsernameRegistered::decode_log(&log)
209
+ {
210
+ dirty_accounts.get_mut(&event.addr).and_then(|account| {
211
+ account.username = Some(event.username.clone());
212
+ account.username_resigned = false; // cancel out any previous resignation if one happened in same commit
213
+ Some(account)
214
+ });
215
+ continue;
216
+ }
217
+
218
+ // Attempt to decode log as a UsernameResigned event
219
+ if let Ok(event) = crate::events::UsernameResigned::decode_log(&log)
220
+ {
221
+ dirty_accounts.get_mut(&event.addr).and_then(|account| {
222
+ account.username = None; // cancel out any previous registration if one happened in same commit
223
+ account.username_resigned = true;
224
+ Some(account)
225
+ });
226
+ continue;
227
+ }
228
+ }
229
+ _ => (), // ignore
230
+ }
231
+ }
232
+
233
+ //
234
+ }
235
+ ExecutionResult::Revert { .. } | ExecutionResult::Halt { .. } => (), // ignore
236
+ }
237
+ }
238
+ }
239
+
240
+ dirty_accounts.into_values().collect()
241
+ }
242
+
243
+ #[cfg(test)]
244
+ mod tests {
245
+ use std::collections::BTreeMap;
246
+
247
+ use super::commit_with_resize_retry;
248
+ use crate::{
249
+ db::{Error, GenesisInfo, PendingCommit, PersistentDB},
250
+ events,
251
+ state_changes::{AccountMergeInfo, AccountUpdate, StateChangeset},
252
+ state_commit::{StateCommit, apply_rewards, collect_dirty_accounts},
253
+ };
254
+ use crate::{
255
+ legacy::{LegacyAccountAttributes, LegacyAddress},
256
+ state_changes::StorageChangeset,
257
+ };
258
+ use alloy_primitives::{Address, Log, U256, address};
259
+ use alloy_primitives::{B256, b256};
260
+ use alloy_sol_types::SolEvent;
261
+ use bytes::Bytes;
262
+ use revm::{
263
+ context::result::{ExecutionResult, Output, ResultGas, SuccessReason},
264
+ primitives::HashMap,
265
+ };
266
+ use revm::{database::states::StorageSlot, state::AccountInfo};
267
+
268
+ #[test]
269
+ fn test_collect_dirty_accounts() {
270
+ let mut change_set = StateChangeset::default();
271
+ change_set.accounts.push((
272
+ address!("0000000000000000000000000000000000000001"),
273
+ Some(AccountInfo::from_balance(U256::from(1))),
274
+ ));
275
+ change_set.accounts.push((
276
+ address!("0000000000000000000000000000000000000002"),
277
+ Some(AccountInfo::from_balance(U256::from(1))),
278
+ ));
279
+
280
+ let genesis_info = GenesisInfo {
281
+ account: address!("0000000000000000000000000000000000000001"),
282
+ deployer_account: address!("0000000000000000000000000000000000000002"),
283
+ validator_contract: address!("0000000000000000000000000000000000000003"),
284
+ username_contract: address!("0000000000000000000000000000000000000004"),
285
+ initial_block_number: 0,
286
+ initial_supply: U256::from(1_000_000),
287
+ };
288
+
289
+ let storage = vec![
290
+ (
291
+ U256::from(1),
292
+ StorageSlot::new_changed(U256::ZERO, U256::from(1234)),
293
+ ),
294
+ (
295
+ U256::from(2),
296
+ StorageSlot::new_changed(U256::ZERO, U256::from(5678)),
297
+ ),
298
+ ];
299
+
300
+ change_set.storage.push(StorageChangeset {
301
+ address: address!("0000000000000000000000000000000000000002"),
302
+ storage,
303
+ ..Default::default()
304
+ });
305
+
306
+ change_set.legacy_attributes.insert(
307
+ address!("0000000000000000000000000000000000000001"),
308
+ LegacyAccountAttributes {
309
+ legacy_nonce: Some(5),
310
+ second_public_key: Some("".into()),
311
+ ..Default::default()
312
+ },
313
+ );
314
+
315
+ let legacy_address: LegacyAddress =
316
+ "DJmvhhiQFSrEQCq9FUxvcLcpcBjx7K3yLt".try_into().unwrap();
317
+ change_set.legacy_cold_wallets.insert(
318
+ legacy_address.clone(),
319
+ crate::legacy::LegacyColdWallet {
320
+ address: legacy_address.clone(),
321
+ balance: U256::from(255),
322
+ legacy_attributes: LegacyAccountAttributes {
323
+ legacy_nonce: Some(3),
324
+ ..Default::default()
325
+ },
326
+ ..Default::default()
327
+ },
328
+ );
329
+
330
+ change_set.merged_legacy_cold_wallets.insert(
331
+ address!("0000000000000000000000000000000000000001"),
332
+ (
333
+ b256!("0000000000000000000000000000000000000000000000000000000000000001"),
334
+ legacy_address,
335
+ ),
336
+ );
337
+
338
+ let mut results = BTreeMap::<B256, (ExecutionResult, u64)>::new();
339
+
340
+ results.insert(
341
+ b256!("0000000000000000000000000000000000000000000000000000000000000001"),
342
+ (
343
+ ExecutionResult::Success {
344
+ reason: SuccessReason::Stop,
345
+ gas: ResultGas::new_with_state_gas(30000, 30000, 0, 0),
346
+ logs: vec![
347
+ Log {
348
+ address: genesis_info.validator_contract,
349
+ data: events::Voted {
350
+ validator: address!("0000000000000000000000000000000000000002"),
351
+ voter: address!("0000000000000000000000000000000000000001"),
352
+ }
353
+ .encode_log_data(),
354
+ },
355
+ Log {
356
+ address: genesis_info.validator_contract,
357
+ data: events::Unvoted {
358
+ validator: address!("0000000000000000000000000000000000000004"),
359
+ voter: address!("0000000000000000000000000000000000000002"),
360
+ }
361
+ .encode_log_data(),
362
+ },
363
+ Log {
364
+ address: genesis_info.username_contract,
365
+ data: events::UsernameRegistered {
366
+ addr: address!("0000000000000000000000000000000000000001"),
367
+ username: "test".into(),
368
+ previousUsername: "".into(),
369
+ }
370
+ .encode_log_data(),
371
+ },
372
+ Log {
373
+ address: genesis_info.username_contract,
374
+ data: events::UsernameResigned {
375
+ addr: address!("0000000000000000000000000000000000000002"),
376
+ username: "resigned".into(),
377
+ }
378
+ .encode_log_data(),
379
+ },
380
+ Log {
381
+ address: genesis_info.validator_contract,
382
+ ..Default::default()
383
+ },
384
+ Log {
385
+ address: genesis_info.username_contract,
386
+ ..Default::default()
387
+ },
388
+ Log {
389
+ address: address!("0000000000000000000000000000000000000000"),
390
+ ..Default::default()
391
+ },
392
+ ],
393
+ output: Output::Create(
394
+ alloy_primitives::Bytes(Bytes::new()),
395
+ Some(address!("0000000000000000000000000000000000000001")),
396
+ ),
397
+ },
398
+ 0,
399
+ ),
400
+ );
401
+
402
+ results.insert(
403
+ b256!("0000000000000000000000000000000000000000000000000000000000000002"),
404
+ (
405
+ ExecutionResult::Revert {
406
+ gas: ResultGas::new_with_state_gas(30000, 30000, 0, 0),
407
+ logs: vec![],
408
+ output: alloy_primitives::Bytes(Bytes::new()),
409
+ },
410
+ 0,
411
+ ),
412
+ );
413
+
414
+ let state = StateCommit {
415
+ change_set,
416
+ results,
417
+ ..Default::default()
418
+ };
419
+
420
+ let mut account_updates = collect_dirty_accounts(state, &Some(genesis_info));
421
+ account_updates.sort_by_key(|k| k.address);
422
+
423
+ assert_eq!(
424
+ account_updates,
425
+ vec![
426
+ AccountUpdate {
427
+ address: address!("0000000000000000000000000000000000000001"),
428
+ balance: U256::ONE,
429
+ nonce: 0,
430
+ vote: Some(address!("0000000000000000000000000000000000000002")),
431
+ unvote: None,
432
+ username: Some("test".into()),
433
+ username_resigned: false,
434
+ merge_info: Some(AccountMergeInfo {
435
+ legacy_address: "DJmvhhiQFSrEQCq9FUxvcLcpcBjx7K3yLt".try_into().unwrap(),
436
+ transaction_hash: b256!(
437
+ "0000000000000000000000000000000000000000000000000000000000000001"
438
+ )
439
+ })
440
+ },
441
+ AccountUpdate {
442
+ address: address!("0000000000000000000000000000000000000002"),
443
+ balance: U256::ONE,
444
+ nonce: 0,
445
+ vote: None,
446
+ unvote: Some(address!("0000000000000000000000000000000000000004")),
447
+ username: None,
448
+ username_resigned: true,
449
+ merge_info: None
450
+ }
451
+ ]
452
+ );
453
+ }
454
+
455
+ #[test]
456
+ fn test_apply_rewards() {
457
+ let path = tempfile::Builder::new()
458
+ .prefix("evm.mdb")
459
+ .tempdir()
460
+ .unwrap();
461
+
462
+ let mut db = PersistentDB::new(crate::db::PersistentDBOptions::new(
463
+ path.path().to_path_buf(),
464
+ ))
465
+ .expect("database");
466
+ let mut pending = PendingCommit::default();
467
+
468
+ let account1 = revm::primitives::address!("bd6f65c58a46427af4b257cbe231d0ed69ed5508");
469
+ let account2 = revm::primitives::address!("ad6f65c58a46427af4b257cbe231d0ed69ed5508");
470
+
471
+ let mut rewards = HashMap::<Address, u128>::default();
472
+ rewards.insert(account1, 1234);
473
+ rewards.insert(account2, 0);
474
+
475
+ let result = apply_rewards(&mut db, &mut pending, rewards);
476
+ assert!(result.is_ok());
477
+
478
+ let cache_account1 = pending.cache.accounts.get(&account1).expect("account1");
479
+ assert!(cache_account1.account.is_some());
480
+ assert_eq!(
481
+ cache_account1.status,
482
+ revm::database::AccountStatus::InMemoryChange
483
+ );
484
+
485
+ let cache_account2 = pending.cache.accounts.get(&account2).expect("account2");
486
+ assert!(cache_account2.account.is_none());
487
+ assert_eq!(
488
+ cache_account2.status,
489
+ revm::database::AccountStatus::LoadedNotExisting
490
+ );
491
+
492
+ let transition_account1 = pending
493
+ .transitions
494
+ .transitions
495
+ .get(&account1)
496
+ .expect("transition_account1");
497
+ assert!(transition_account1.info.is_some());
498
+ assert_eq!(
499
+ transition_account1.status,
500
+ revm::database::AccountStatus::InMemoryChange
501
+ );
502
+ assert_eq!(transition_account1.storage_was_destroyed, false);
503
+
504
+ let transition_account2 = pending.transitions.transitions.get(&account2);
505
+ assert_eq!(transition_account2, None);
506
+ }
507
+
508
+ #[test]
509
+ fn commit_succeeds_without_resizing() {
510
+ let mut commits = 0;
511
+ let mut resizes = 0;
512
+
513
+ let result = commit_with_resize_retry(
514
+ || {
515
+ commits += 1;
516
+ Ok(())
517
+ },
518
+ || {
519
+ resizes += 1;
520
+ Ok(())
521
+ },
522
+ );
523
+
524
+ assert!(result.is_ok());
525
+ assert_eq!(commits, 1); // committed on the first attempt
526
+ assert_eq!(resizes, 0); // never had to grow the map
527
+ }
528
+
529
+ #[test]
530
+ fn commit_recovers_after_one_resize() {
531
+ let mut commits = 0;
532
+ let mut resizes = 0;
533
+
534
+ let result = commit_with_resize_retry(
535
+ || {
536
+ commits += 1;
537
+ if commits == 1 {
538
+ Err(Error::DbFull)
539
+ } else {
540
+ Ok(())
541
+ }
542
+ },
543
+ || {
544
+ resizes += 1;
545
+ Ok(())
546
+ },
547
+ );
548
+
549
+ assert!(result.is_ok());
550
+ assert_eq!(commits, 2); // one DbFull, then success
551
+ assert_eq!(resizes, 1);
552
+ }
553
+
554
+ #[test]
555
+ fn commit_recovers_after_two_resizes() {
556
+ let mut commits = 0;
557
+ let mut resizes = 0;
558
+
559
+ let result = commit_with_resize_retry(
560
+ || {
561
+ commits += 1;
562
+ if commits <= 2 {
563
+ Err(Error::DbFull)
564
+ } else {
565
+ Ok(())
566
+ }
567
+ },
568
+ || {
569
+ resizes += 1;
570
+ Ok(())
571
+ },
572
+ );
573
+
574
+ assert!(result.is_ok());
575
+ assert_eq!(commits, 3); // two DbFull, then success
576
+ assert_eq!(resizes, 2);
577
+ }
578
+
579
+ #[test]
580
+ fn commit_gives_up_after_max_retries() {
581
+ let mut commits = 0;
582
+ let mut resizes = 0;
583
+
584
+ let result = commit_with_resize_retry(
585
+ || {
586
+ commits += 1;
587
+ Err(Error::DbFull) // never fits, no matter how often we grow
588
+ },
589
+ || {
590
+ resizes += 1;
591
+ Ok(())
592
+ },
593
+ );
594
+
595
+ assert!(matches!(result, Err(Error::DbFull)));
596
+ assert_eq!(commits, 4); // initial attempt + MAX_RESIZE_RETRIES (3)
597
+ assert_eq!(resizes, 4); // a resize follows every DbFull
598
+ }
599
+
600
+ #[test]
601
+ fn commit_propagates_non_dbfull_error_without_resizing() {
602
+ let mut resizes = 0;
603
+
604
+ let result = commit_with_resize_retry(
605
+ || Err(Error::Lock),
606
+ || {
607
+ resizes += 1;
608
+ Ok(())
609
+ },
610
+ );
611
+
612
+ assert!(matches!(result, Err(Error::Lock)));
613
+ assert_eq!(resizes, 0); // non-DbFull errors return immediately
614
+ }
615
+ }