1use std::{
2 collections::HashMap,
3 sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard},
4};
5
6use alloy::primitives::{Address, Bytes as AlloyBytes, B256, U256};
7use revm::{
8 context::DBErrorMarker,
9 primitives::KECCAK_EMPTY,
10 state::{AccountInfo, Bytecode},
11 DatabaseRef,
12};
13use thiserror::Error;
14use tracing::{debug, error, instrument, warn};
15use tycho_client::feed::BlockHeader;
16
17use crate::evm::{
18 account_storage::{AccountStorage, StateUpdate},
19 engine_db::engine_db_interface::EngineDatabaseInterface,
20 tycho_models::{AccountUpdate, ChangeType},
21};
22
23#[derive(Error, Debug)]
24pub enum TychoClientError {
25 #[error("Failed to parse URI: {0}. Error: {1}")]
26 UriParsing(String, String),
27 #[error("Failed to format request: {0}")]
28 FormatRequest(String),
29 #[error("Unexpected HTTP client error: {0}")]
30 HttpClient(String),
31 #[error("Failed to parse response: {0}")]
32 ParseResponse(String),
33}
34
35#[derive(Error, Debug)]
36pub enum PreCachedDBError {
37 #[error("Account {0} not found")]
38 MissingAccount(Address),
39 #[error("Bad account update: {0} - {1:?}")]
40 BadUpdate(String, Box<AccountUpdate>),
41 #[error("Block needs to be set")]
42 BlockNotSet(),
43 #[error("Tycho Client error: {0}")]
44 TychoClientError(#[from] TychoClientError),
45 #[error("{0}")]
46 Fatal(String),
47}
48
49impl DBErrorMarker for PreCachedDBError {}
50
51#[derive(Clone, Debug)]
52pub struct PreCachedDBInner {
53 accounts: AccountStorage,
55 block: Option<BlockHeader>,
57}
58
59#[derive(Clone, Debug)]
60pub struct PreCachedDB {
61 pub inner: Arc<RwLock<PreCachedDBInner>>,
67}
68
69impl PreCachedDB {
70 pub fn new() -> Result<Self, PreCachedDBError> {
72 Ok(PreCachedDB {
73 inner: Arc::new(RwLock::new(PreCachedDBInner {
74 accounts: AccountStorage::new(),
75 block: None,
76 })),
77 })
78 }
79
80 #[instrument(skip_all)]
81 pub fn update(
82 &self,
83 account_updates: Vec<AccountUpdate>,
84 block: Option<BlockHeader>,
85 ) -> Result<(), PreCachedDBError> {
86 let mut write_guard = self.write_inner()?;
89
90 write_guard.block = block;
91
92 for update in account_updates {
93 match update.change {
94 ChangeType::Update => {
95 debug!(%update.address, "Updating account");
96
97 write_guard.accounts.update_account(
100 &update.address,
101 &StateUpdate {
102 storage: Some(update.slots.clone()),
103 balance: update.balance,
104 },
105 );
106 }
107 ChangeType::Deletion => {
108 debug!(%update.address, "Deleting account");
109
110 warn!(%update.address, "Deletion not implemented");
111 }
112 ChangeType::Creation => {
113 debug!(%update.address, "Creating account");
114
115 let code = Bytecode::new_raw(AlloyBytes::from(
117 update.code.clone().ok_or_else(|| {
118 error!(%update.address, "MissingCode");
119 PreCachedDBError::BadUpdate(
120 "MissingCode".into(),
121 Box::new(update.clone()),
122 )
123 })?,
124 ));
125 let balance = update.balance.unwrap_or(U256::ZERO);
127
128 write_guard.accounts.init_account(
130 update.address,
131 AccountInfo::new(balance, 0, code.hash_slow(), code),
132 Some(update.slots.clone()),
133 true, );
136 }
137 ChangeType::Unspecified => {
138 warn!(%update.address, "Unspecified change type");
139 }
140 }
141 }
142 Ok(())
143 }
144
145 pub fn force_update_accounts(
150 &self,
151 account_updates: Vec<AccountUpdate>,
152 ) -> Result<(), PreCachedDBError> {
153 let mut write_guard = self.write_inner()?;
154
155 for update in account_updates {
156 if matches!(update.change, ChangeType::Creation) {
157 let code =
158 Bytecode::new_raw(AlloyBytes::from(update.code.clone().ok_or_else(|| {
159 error!(%update.address, "MissingCode");
160 PreCachedDBError::BadUpdate("MissingCode".into(), Box::new(update.clone()))
161 })?));
162 let balance = update.balance.unwrap_or(U256::ZERO);
163
164 write_guard.accounts.overwrite_account(
165 update.address,
166 AccountInfo::new(balance, 0, code.hash_slow(), code),
167 Some(update.slots.clone()),
168 true,
169 );
170 } else {
171 warn!(%update.address, "force_update_accounts called with non-Creation update; ignoring");
172 }
173 }
174 Ok(())
175 }
176
177 pub fn get_storage(&self, address: &Address, index: &U256) -> Option<U256> {
193 self.inner
194 .read()
195 .unwrap()
196 .accounts
197 .get_storage(address, index)
198 }
199
200 pub fn update_state(
210 &mut self,
211 updates: &HashMap<Address, StateUpdate>,
212 block: BlockHeader,
213 ) -> Result<HashMap<Address, StateUpdate>, PreCachedDBError> {
214 let mut write_guard = self.write_inner()?;
217
218 let mut revert_updates = HashMap::new();
219 write_guard.block = Some(block);
220
221 for (address, update_info) in updates.iter() {
222 let mut revert_entry = StateUpdate::default();
223
224 if let Some(current_account) = write_guard
225 .accounts
226 .get_account_info(address)
227 {
228 revert_entry.balance = Some(current_account.balance);
229 }
230
231 if let Some(storage) = &update_info.storage {
232 let mut revert_storage = HashMap::default();
233 for index in storage.keys() {
234 if let Some(s) = write_guard
235 .accounts
236 .get_storage(address, index)
237 {
238 revert_storage.insert(*index, s);
239 }
240 }
241 revert_entry.storage = Some(revert_storage);
242 }
243 revert_updates.insert(*address, revert_entry);
244 write_guard
245 .accounts
246 .update_account(address, update_info);
247 }
248
249 Ok(revert_updates)
250 }
251
252 #[cfg(test)]
253 pub fn get_account_storage(&self) -> Result<AccountStorage, PreCachedDBError> {
254 self.read_inner()
255 .map(|guard| guard.accounts.clone())
256 }
257
258 pub fn block_number(&self) -> Result<Option<u64>, PreCachedDBError> {
260 self.read_inner().map(|guard| {
261 guard
262 .block
263 .as_ref()
264 .map(|header| header.number)
265 })
266 }
267
268 pub fn clear(&self) -> Result<(), PreCachedDBError> {
270 let mut write_guard = self.write_inner()?;
271 write_guard.accounts.clear();
272 write_guard.block = None;
273 Ok(())
274 }
275
276 fn read_inner(&self) -> Result<RwLockReadGuard<'_, PreCachedDBInner>, PreCachedDBError> {
277 self.inner
278 .read()
279 .map_err(|_| PreCachedDBError::Fatal("Tycho state db lock poisoned".into()))
280 }
281
282 fn write_inner(&self) -> Result<RwLockWriteGuard<'_, PreCachedDBInner>, PreCachedDBError> {
283 self.inner
284 .write()
285 .map_err(|_| PreCachedDBError::Fatal("Tycho state db lock poisoned".into()))
286 }
287}
288
289impl EngineDatabaseInterface for PreCachedDB {
290 type Error = PreCachedDBError;
291
292 fn init_account(
303 &self,
304 address: Address,
305 account: AccountInfo,
306 permanent_storage: Option<HashMap<U256, U256>>,
307 _mocked: bool,
308 ) -> Result<(), <Self as EngineDatabaseInterface>::Error> {
309 if account.code.is_none() && account.code_hash != KECCAK_EMPTY {
310 warn!("Code is None for account {address} but code hash is not KECCAK_EMPTY");
311 } else if account.code.is_some() && account.code_hash == KECCAK_EMPTY {
312 warn!("Code is Some for account {address} but code hash is KECCAK_EMPTY");
313 }
314
315 self.write_inner()?
316 .accounts
317 .init_account(address, account, permanent_storage, true);
318
319 Ok(())
320 }
321
322 fn clear_temp_storage(&mut self) -> Result<(), <Self as EngineDatabaseInterface>::Error> {
324 debug!("Temp storage in TychoDB is never set, nothing to clear");
325
326 Ok(())
327 }
328
329 fn get_current_block(&self) -> Option<BlockHeader> {
330 self.inner.read().unwrap().block.clone()
331 }
332}
333
334impl DatabaseRef for PreCachedDB {
335 type Error = PreCachedDBError;
336 fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
349 self.read_inner()?
350 .accounts
351 .get_account_info(&address)
352 .map(|acc| Some(acc.clone()))
353 .ok_or(PreCachedDBError::MissingAccount(address))
354 }
355
356 fn code_by_hash_ref(&self, code_hash: B256) -> Result<Bytecode, Self::Error> {
357 Err(PreCachedDBError::Fatal(format!("Code by hash not supported: {code_hash}")))
358 }
359
360 fn storage_ref(&self, address: Address, index: U256) -> Result<U256, Self::Error> {
375 debug!(%address, %index, "Requested storage of account");
376 let read_guard = self.read_inner()?;
377 if let Some(storage_value) = read_guard
378 .accounts
379 .get_storage(&address, &index)
380 {
381 debug!(%address, %index, %storage_value, "Got value locally");
382 Ok(storage_value)
383 } else {
384 if read_guard
386 .accounts
387 .account_present(&address)
388 {
389 debug!(%address, %index, "Account found, but slot is zero");
392 Ok(U256::ZERO)
393 } else {
394 debug!(%address, %index, "Account not found");
396 Err(PreCachedDBError::MissingAccount(address))
397 }
398 }
399 }
400
401 fn block_hash_ref(&self, _number: u64) -> Result<B256, Self::Error> {
403 match self.read_inner()?.block.clone() {
404 Some(header) => Ok(B256::from_slice(&header.hash)),
405 None => Ok(B256::default()),
406 }
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use std::{error::Error, str::FromStr};
413
414 use revm::primitives::U256;
415 use rstest::{fixture, rstest};
416 use tycho_common::Bytes;
417
418 use super::*;
419 use crate::evm::tycho_models::{AccountUpdate, Chain, ChangeType};
420
421 #[fixture]
422 pub fn mock_db() -> PreCachedDB {
423 PreCachedDB {
424 inner: Arc::new(RwLock::new(PreCachedDBInner {
425 accounts: AccountStorage::new(),
426 block: None,
427 })),
428 }
429 }
430
431 #[rstest]
432 #[tokio::test]
433 async fn test_account_get_acc_info(mock_db: PreCachedDB) -> Result<(), Box<dyn Error>> {
434 let mock_acc_address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc")?;
437 mock_db
438 .init_account(mock_acc_address, AccountInfo::default(), None, false)
439 .expect("Account init should succeed");
440
441 let acc_info = mock_db
442 .basic_ref(mock_acc_address)
443 .unwrap()
444 .unwrap();
445
446 assert_eq!(
447 mock_db
448 .basic_ref(mock_acc_address)
449 .unwrap()
450 .unwrap(),
451 acc_info
452 );
453 Ok(())
454 }
455
456 #[rstest]
457 fn test_account_storage(mock_db: PreCachedDB) -> Result<(), Box<dyn Error>> {
458 let mock_acc_address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc")?;
459 let storage_address = U256::from(1);
460 let mut permanent_storage: HashMap<U256, U256> = HashMap::new();
461 permanent_storage.insert(storage_address, U256::from(10));
462 mock_db
463 .init_account(mock_acc_address, AccountInfo::default(), Some(permanent_storage), false)
464 .expect("Account init should succeed");
465
466 let storage = mock_db
467 .storage_ref(mock_acc_address, storage_address)
468 .unwrap();
469
470 assert_eq!(storage, U256::from(10));
471 Ok(())
472 }
473
474 #[rstest]
475 fn test_account_storage_zero(mock_db: PreCachedDB) -> Result<(), Box<dyn Error>> {
476 let mock_acc_address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc")?;
477 let storage_address = U256::from(1);
478 mock_db
479 .init_account(mock_acc_address, AccountInfo::default(), None, false)
480 .expect("Account init should succeed");
481
482 let storage = mock_db
483 .storage_ref(mock_acc_address, storage_address)
484 .unwrap();
485
486 assert_eq!(storage, U256::ZERO);
487 Ok(())
488 }
489
490 #[rstest]
491 #[should_panic(
492 expected = "called `Result::unwrap()` on an `Err` value: MissingAccount(0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc)"
493 )]
494 fn test_account_storage_missing(mock_db: PreCachedDB) {
495 let mock_acc_address =
496 Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc").unwrap();
497 let storage_address = U256::from(1);
498
499 mock_db
501 .storage_ref(mock_acc_address, storage_address)
502 .unwrap();
503 }
504
505 #[rstest]
506 #[tokio::test]
507 async fn test_update_state(mut mock_db: PreCachedDB) -> Result<(), Box<dyn Error>> {
508 let address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc")?;
509 mock_db
510 .init_account(address, AccountInfo::default(), None, false)
511 .expect("Account init should succeed");
512
513 let mut new_storage = HashMap::default();
514 let new_storage_value_index = U256::from_limbs_slice(&[123]);
515 new_storage.insert(new_storage_value_index, new_storage_value_index);
516 let new_balance = U256::from_limbs_slice(&[500]);
517 let update = StateUpdate { storage: Some(new_storage), balance: Some(new_balance) };
518 let new_block = BlockHeader {
519 number: 1,
520 hash: Bytes::from_str(
521 "0xc6b994ec855fb2b31013c7ae65074406fac46679b5b963469104e0bfeddd66d9",
522 )
523 .unwrap(),
524 timestamp: 123,
525 ..Default::default()
526 };
527 let mut updates = HashMap::default();
528 updates.insert(address, update);
529
530 mock_db
531 .update_state(&updates, new_block)
532 .expect("State update should succeed");
533
534 assert_eq!(
535 mock_db
536 .get_storage(&address, &new_storage_value_index)
537 .unwrap(),
538 new_storage_value_index
539 );
540 let account_info = mock_db
541 .basic_ref(address)
542 .unwrap()
543 .unwrap();
544 assert_eq!(account_info.balance, new_balance);
545 let block = mock_db
546 .inner
547 .read()
548 .unwrap()
549 .block
550 .clone()
551 .expect("block is Some");
552 assert_eq!(block.number, 1);
553
554 Ok(())
555 }
556
557 #[rstest]
558 #[tokio::test]
559 async fn test_block_number_getter(mut mock_db: PreCachedDB) -> Result<(), Box<dyn Error>> {
560 let address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc")?;
561 mock_db
562 .init_account(address, AccountInfo::default(), None, false)
563 .expect("Account init should succeed");
564
565 let new_block = BlockHeader {
566 number: 1,
567 hash: Bytes::from_str(
568 "0xc6b994ec855fb2b31013c7ae65074406fac46679b5b963469104e0bfeddd66d9",
569 )
570 .unwrap(),
571 timestamp: 123,
572 ..Default::default()
573 };
574 let updates = HashMap::default();
575
576 mock_db
577 .update_state(&updates, new_block)
578 .expect("State update should succeed");
579
580 let block_number = mock_db.block_number();
581 assert_eq!(block_number.unwrap().unwrap(), 1);
582
583 Ok(())
584 }
585
586 #[rstest]
587 #[tokio::test]
588 async fn test_update() {
589 let mock_db = PreCachedDB {
590 inner: Arc::new(RwLock::new(PreCachedDBInner {
591 accounts: AccountStorage::new(),
592 block: None,
593 })),
594 };
595
596 let account_update = AccountUpdate::new(
597 Address::from_str("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D").unwrap(),
598 Chain::Ethereum,
599 HashMap::new(),
600 Some(U256::from(500)),
601 Some(Vec::<u8>::new()),
602 ChangeType::Creation,
603 );
604
605 let new_block = BlockHeader {
606 number: 1,
607 hash: Bytes::from_str(
608 "0xc6b994ec855fb2b31013c7ae65074406fac46679b5b963469104e0bfeddd66d9",
609 )
610 .unwrap(),
611 timestamp: 123,
612 ..Default::default()
613 };
614
615 mock_db
616 .update(vec![account_update], Some(new_block))
617 .unwrap();
618
619 let account_info = mock_db
620 .basic_ref(Address::from_str("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D").unwrap())
621 .unwrap()
622 .unwrap();
623
624 assert_eq!(
625 account_info,
626 AccountInfo {
627 nonce: 0,
628 balance: U256::from(500),
629 code_hash: B256::from_str(
630 "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
631 )
632 .unwrap(),
633 code: Some(Bytecode::default()),
634 }
635 );
636
637 assert_eq!(
638 mock_db
639 .inner
640 .read()
641 .unwrap()
642 .block
643 .clone()
644 .expect("block is Some")
645 .number,
646 1
647 );
648 }
649
650 #[rstest]
651 fn test_force_update_accounts_overwrites_existing(mock_db: PreCachedDB) {
652 let address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc").unwrap();
653
654 mock_db
656 .init_account(address, AccountInfo::default(), None, true)
657 .expect("placeholder init should succeed");
658
659 let storage_slot = U256::from(1);
661 let storage_value = U256::from(42);
662 let mut slots = HashMap::new();
663 slots.insert(storage_slot, storage_value);
664 let update = AccountUpdate::new(
665 address,
666 Chain::Ethereum,
667 slots,
668 Some(U256::from(100)),
669 Some(Vec::<u8>::new()),
670 ChangeType::Creation,
671 );
672 mock_db
673 .force_update_accounts(vec![update])
674 .expect("force update should succeed");
675
676 let info = mock_db
677 .basic_ref(address)
678 .unwrap()
679 .unwrap();
680 assert_eq!(info.balance, U256::from(100));
681 assert_eq!(
682 mock_db
683 .get_storage(&address, &storage_slot)
684 .unwrap(),
685 storage_value
686 );
687 }
688
689 #[rstest]
690 fn test_force_update_accounts_non_creation_ignored(mock_db: PreCachedDB) {
691 let address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc").unwrap();
692 let original_balance = U256::from(10);
693
694 mock_db
695 .init_account(
696 address,
697 AccountInfo { balance: original_balance, ..Default::default() },
698 None,
699 true,
700 )
701 .expect("init should succeed");
702
703 let update = AccountUpdate::new(
705 address,
706 Chain::Ethereum,
707 HashMap::new(),
708 Some(U256::from(999)),
709 None,
710 ChangeType::Update,
711 );
712 mock_db
713 .force_update_accounts(vec![update])
714 .expect("force update should succeed");
715
716 let info = mock_db
718 .basic_ref(address)
719 .unwrap()
720 .unwrap();
721 assert_eq!(info.balance, original_balance);
722 }
723
724 #[ignore]
740 #[rstest]
741 fn test_tycho_db_connection() {
742 tracing_subscriber::fmt()
743 .with_env_filter("debug")
744 .init();
745
746 let ambient_contract =
747 Address::from_str("0xaaaaaaaaa24eeeb8d57d431224f73832bc34f688").unwrap();
748
749 let db = PreCachedDB::new().expect("db should initialize");
750
751 let acc_info = db
752 .basic_ref(ambient_contract)
753 .unwrap()
754 .unwrap();
755
756 debug!(?acc_info, "Account info");
757 }
758}