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(
152 &self,
153 account_updates: Vec<AccountUpdate>,
154 ) -> Result<(), PreCachedDBError> {
155 let mut write_guard = self.write_inner()?;
156
157 for update in account_updates {
158 if matches!(update.change, ChangeType::Creation) {
159 let code =
160 Bytecode::new_raw(AlloyBytes::from(update.code.clone().ok_or_else(|| {
161 error!(%update.address, "MissingCode");
162 PreCachedDBError::BadUpdate("MissingCode".into(), Box::new(update.clone()))
163 })?));
164 let balance = update.balance.unwrap_or(U256::ZERO);
165
166 write_guard.accounts.overwrite_account(
167 update.address,
168 AccountInfo::new(balance, 0, code.hash_slow(), code),
169 Some(update.slots.clone()),
170 true,
171 );
172 } else {
173 warn!(%update.address, "force_update_accounts called with non-Creation update; ignoring");
174 }
175 }
176 Ok(())
177 }
178
179 pub fn get_storage(&self, address: &Address, index: &U256) -> Option<U256> {
195 self.inner
196 .read()
197 .unwrap()
198 .accounts
199 .get_storage(address, index)
200 }
201
202 pub fn update_state(
212 &mut self,
213 updates: &HashMap<Address, StateUpdate>,
214 block: BlockHeader,
215 ) -> Result<HashMap<Address, StateUpdate>, PreCachedDBError> {
216 let mut write_guard = self.write_inner()?;
219
220 let mut revert_updates = HashMap::new();
221 write_guard.block = Some(block);
222
223 for (address, update_info) in updates.iter() {
224 let mut revert_entry = StateUpdate::default();
225
226 if let Some(current_account) = write_guard
227 .accounts
228 .get_account_info(address)
229 {
230 revert_entry.balance = Some(current_account.balance);
231 }
232
233 if let Some(storage) = &update_info.storage {
234 let mut revert_storage = HashMap::default();
235 for index in storage.keys() {
236 if let Some(s) = write_guard
237 .accounts
238 .get_storage(address, index)
239 {
240 revert_storage.insert(*index, s);
241 }
242 }
243 revert_entry.storage = Some(revert_storage);
244 }
245 revert_updates.insert(*address, revert_entry);
246 write_guard
247 .accounts
248 .update_account(address, update_info);
249 }
250
251 Ok(revert_updates)
252 }
253
254 #[cfg(test)]
255 pub fn get_account_storage(&self) -> Result<AccountStorage, PreCachedDBError> {
256 self.read_inner()
257 .map(|guard| guard.accounts.clone())
258 }
259
260 pub fn block_number(&self) -> Result<Option<u64>, PreCachedDBError> {
262 self.read_inner().map(|guard| {
263 guard
264 .block
265 .as_ref()
266 .map(|header| header.number)
267 })
268 }
269
270 pub fn clear(&self) -> Result<(), PreCachedDBError> {
272 let mut write_guard = self.write_inner()?;
273 write_guard.accounts.clear();
274 write_guard.block = None;
275 Ok(())
276 }
277
278 fn read_inner(&self) -> Result<RwLockReadGuard<'_, PreCachedDBInner>, PreCachedDBError> {
279 self.inner
280 .read()
281 .map_err(|_| PreCachedDBError::Fatal("Tycho state db lock poisoned".into()))
282 }
283
284 fn write_inner(&self) -> Result<RwLockWriteGuard<'_, PreCachedDBInner>, PreCachedDBError> {
285 self.inner
286 .write()
287 .map_err(|_| PreCachedDBError::Fatal("Tycho state db lock poisoned".into()))
288 }
289}
290
291impl EngineDatabaseInterface for PreCachedDB {
292 type Error = PreCachedDBError;
293
294 fn init_account(
305 &self,
306 address: Address,
307 account: AccountInfo,
308 permanent_storage: Option<HashMap<U256, U256>>,
309 _mocked: bool,
310 ) -> Result<(), <Self as EngineDatabaseInterface>::Error> {
311 if account.code.is_none() && account.code_hash != KECCAK_EMPTY {
312 warn!("Code is None for account {address} but code hash is not KECCAK_EMPTY");
313 } else if account.code.is_some() && account.code_hash == KECCAK_EMPTY {
314 warn!("Code is Some for account {address} but code hash is KECCAK_EMPTY");
315 }
316
317 self.write_inner()?
318 .accounts
319 .init_account(address, account, permanent_storage, true);
320
321 Ok(())
322 }
323
324 fn clear_temp_storage(&mut self) -> Result<(), <Self as EngineDatabaseInterface>::Error> {
326 debug!("Temp storage in TychoDB is never set, nothing to clear");
327
328 Ok(())
329 }
330
331 fn get_current_block(&self) -> Option<BlockHeader> {
332 self.inner.read().unwrap().block.clone()
333 }
334}
335
336impl DatabaseRef for PreCachedDB {
337 type Error = PreCachedDBError;
338 fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
351 self.read_inner()?
352 .accounts
353 .get_account_info(&address)
354 .map(|acc| Some(acc.clone()))
355 .ok_or(PreCachedDBError::MissingAccount(address))
356 }
357
358 fn code_by_hash_ref(&self, code_hash: B256) -> Result<Bytecode, Self::Error> {
359 Err(PreCachedDBError::Fatal(format!("Code by hash not supported: {code_hash}")))
360 }
361
362 fn storage_ref(&self, address: Address, index: U256) -> Result<U256, Self::Error> {
377 debug!(%address, %index, "Requested storage of account");
378 let read_guard = self.read_inner()?;
379 if let Some(storage_value) = read_guard
380 .accounts
381 .get_storage(&address, &index)
382 {
383 debug!(%address, %index, %storage_value, "Got value locally");
384 Ok(storage_value)
385 } else {
386 if read_guard
388 .accounts
389 .account_present(&address)
390 {
391 debug!(%address, %index, "Account found, but slot is zero");
394 Ok(U256::ZERO)
395 } else {
396 debug!(%address, %index, "Account not found");
398 Err(PreCachedDBError::MissingAccount(address))
399 }
400 }
401 }
402
403 fn block_hash_ref(&self, _number: u64) -> Result<B256, Self::Error> {
405 match self.read_inner()?.block.clone() {
406 Some(header) => Ok(B256::from_slice(&header.hash)),
407 None => Ok(B256::default()),
408 }
409 }
410}
411
412#[cfg(test)]
413mod tests {
414 use std::{error::Error, str::FromStr};
415
416 use revm::primitives::U256;
417 use rstest::{fixture, rstest};
418 use tycho_common::Bytes;
419
420 use super::*;
421 use crate::evm::tycho_models::{AccountUpdate, Chain, ChangeType};
422
423 #[fixture]
424 pub fn mock_db() -> PreCachedDB {
425 PreCachedDB {
426 inner: Arc::new(RwLock::new(PreCachedDBInner {
427 accounts: AccountStorage::new(),
428 block: None,
429 })),
430 }
431 }
432
433 #[rstest]
434 #[tokio::test]
435 async fn test_account_get_acc_info(mock_db: PreCachedDB) -> Result<(), Box<dyn Error>> {
436 let mock_acc_address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc")?;
439 mock_db
440 .init_account(mock_acc_address, AccountInfo::default(), None, false)
441 .expect("Account init should succeed");
442
443 let acc_info = mock_db
444 .basic_ref(mock_acc_address)
445 .unwrap()
446 .unwrap();
447
448 assert_eq!(
449 mock_db
450 .basic_ref(mock_acc_address)
451 .unwrap()
452 .unwrap(),
453 acc_info
454 );
455 Ok(())
456 }
457
458 #[rstest]
459 fn test_account_storage(mock_db: PreCachedDB) -> Result<(), Box<dyn Error>> {
460 let mock_acc_address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc")?;
461 let storage_address = U256::from(1);
462 let mut permanent_storage: HashMap<U256, U256> = HashMap::new();
463 permanent_storage.insert(storage_address, U256::from(10));
464 mock_db
465 .init_account(mock_acc_address, AccountInfo::default(), Some(permanent_storage), false)
466 .expect("Account init should succeed");
467
468 let storage = mock_db
469 .storage_ref(mock_acc_address, storage_address)
470 .unwrap();
471
472 assert_eq!(storage, U256::from(10));
473 Ok(())
474 }
475
476 #[rstest]
477 fn test_account_storage_zero(mock_db: PreCachedDB) -> Result<(), Box<dyn Error>> {
478 let mock_acc_address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc")?;
479 let storage_address = U256::from(1);
480 mock_db
481 .init_account(mock_acc_address, AccountInfo::default(), None, false)
482 .expect("Account init should succeed");
483
484 let storage = mock_db
485 .storage_ref(mock_acc_address, storage_address)
486 .unwrap();
487
488 assert_eq!(storage, U256::ZERO);
489 Ok(())
490 }
491
492 #[rstest]
493 #[should_panic(
494 expected = "called `Result::unwrap()` on an `Err` value: MissingAccount(0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc)"
495 )]
496 fn test_account_storage_missing(mock_db: PreCachedDB) {
497 let mock_acc_address =
498 Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc").unwrap();
499 let storage_address = U256::from(1);
500
501 mock_db
503 .storage_ref(mock_acc_address, storage_address)
504 .unwrap();
505 }
506
507 #[rstest]
508 #[tokio::test]
509 async fn test_update_state(mut mock_db: PreCachedDB) -> Result<(), Box<dyn Error>> {
510 let address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc")?;
511 mock_db
512 .init_account(address, AccountInfo::default(), None, false)
513 .expect("Account init should succeed");
514
515 let mut new_storage = HashMap::default();
516 let new_storage_value_index = U256::from_limbs_slice(&[123]);
517 new_storage.insert(new_storage_value_index, new_storage_value_index);
518 let new_balance = U256::from_limbs_slice(&[500]);
519 let update = StateUpdate { storage: Some(new_storage), balance: Some(new_balance) };
520 let new_block = BlockHeader {
521 number: 1,
522 hash: Bytes::from_str(
523 "0xc6b994ec855fb2b31013c7ae65074406fac46679b5b963469104e0bfeddd66d9",
524 )
525 .unwrap(),
526 timestamp: 123,
527 ..Default::default()
528 };
529 let mut updates = HashMap::default();
530 updates.insert(address, update);
531
532 mock_db
533 .update_state(&updates, new_block)
534 .expect("State update should succeed");
535
536 assert_eq!(
537 mock_db
538 .get_storage(&address, &new_storage_value_index)
539 .unwrap(),
540 new_storage_value_index
541 );
542 let account_info = mock_db
543 .basic_ref(address)
544 .unwrap()
545 .unwrap();
546 assert_eq!(account_info.balance, new_balance);
547 let block = mock_db
548 .inner
549 .read()
550 .unwrap()
551 .block
552 .clone()
553 .expect("block is Some");
554 assert_eq!(block.number, 1);
555
556 Ok(())
557 }
558
559 #[rstest]
560 #[tokio::test]
561 async fn test_block_number_getter(mut mock_db: PreCachedDB) -> Result<(), Box<dyn Error>> {
562 let address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc")?;
563 mock_db
564 .init_account(address, AccountInfo::default(), None, false)
565 .expect("Account init should succeed");
566
567 let new_block = BlockHeader {
568 number: 1,
569 hash: Bytes::from_str(
570 "0xc6b994ec855fb2b31013c7ae65074406fac46679b5b963469104e0bfeddd66d9",
571 )
572 .unwrap(),
573 timestamp: 123,
574 ..Default::default()
575 };
576 let updates = HashMap::default();
577
578 mock_db
579 .update_state(&updates, new_block)
580 .expect("State update should succeed");
581
582 let block_number = mock_db.block_number();
583 assert_eq!(block_number.unwrap().unwrap(), 1);
584
585 Ok(())
586 }
587
588 #[rstest]
589 #[tokio::test]
590 async fn test_update() {
591 let mock_db = PreCachedDB {
592 inner: Arc::new(RwLock::new(PreCachedDBInner {
593 accounts: AccountStorage::new(),
594 block: None,
595 })),
596 };
597
598 let account_update = AccountUpdate::new(
599 Address::from_str("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D").unwrap(),
600 Chain::Ethereum,
601 HashMap::new(),
602 Some(U256::from(500)),
603 Some(Vec::<u8>::new()),
604 ChangeType::Creation,
605 );
606
607 let new_block = BlockHeader {
608 number: 1,
609 hash: Bytes::from_str(
610 "0xc6b994ec855fb2b31013c7ae65074406fac46679b5b963469104e0bfeddd66d9",
611 )
612 .unwrap(),
613 timestamp: 123,
614 ..Default::default()
615 };
616
617 mock_db
618 .update(vec![account_update], Some(new_block))
619 .unwrap();
620
621 let account_info = mock_db
622 .basic_ref(Address::from_str("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D").unwrap())
623 .unwrap()
624 .unwrap();
625
626 assert_eq!(
627 account_info,
628 AccountInfo {
629 nonce: 0,
630 balance: U256::from(500),
631 code_hash: B256::from_str(
632 "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
633 )
634 .unwrap(),
635 code: Some(Bytecode::default()),
636 }
637 );
638
639 assert_eq!(
640 mock_db
641 .inner
642 .read()
643 .unwrap()
644 .block
645 .clone()
646 .expect("block is Some")
647 .number,
648 1
649 );
650 }
651
652 #[rstest]
653 fn test_force_update_accounts_overwrites_existing(mock_db: PreCachedDB) {
654 let address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc").unwrap();
655
656 mock_db
658 .init_account(address, AccountInfo::default(), None, true)
659 .expect("placeholder init should succeed");
660
661 let storage_slot = U256::from(1);
663 let storage_value = U256::from(42);
664 let mut slots = HashMap::new();
665 slots.insert(storage_slot, storage_value);
666 let update = AccountUpdate::new(
667 address,
668 Chain::Ethereum,
669 slots,
670 Some(U256::from(100)),
671 Some(Vec::<u8>::new()),
672 ChangeType::Creation,
673 );
674 mock_db
675 .force_update_accounts(vec![update])
676 .expect("force update should succeed");
677
678 let info = mock_db
679 .basic_ref(address)
680 .unwrap()
681 .unwrap();
682 assert_eq!(info.balance, U256::from(100));
683 assert_eq!(
684 mock_db
685 .get_storage(&address, &storage_slot)
686 .unwrap(),
687 storage_value
688 );
689 }
690
691 #[rstest]
692 fn test_force_update_accounts_non_creation_ignored(mock_db: PreCachedDB) {
693 let address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc").unwrap();
694 let original_balance = U256::from(10);
695
696 mock_db
697 .init_account(
698 address,
699 AccountInfo { balance: original_balance, ..Default::default() },
700 None,
701 true,
702 )
703 .expect("init should succeed");
704
705 let update = AccountUpdate::new(
707 address,
708 Chain::Ethereum,
709 HashMap::new(),
710 Some(U256::from(999)),
711 None,
712 ChangeType::Update,
713 );
714 mock_db
715 .force_update_accounts(vec![update])
716 .expect("force update should succeed");
717
718 let info = mock_db
720 .basic_ref(address)
721 .unwrap()
722 .unwrap();
723 assert_eq!(info.balance, original_balance);
724 }
725
726 #[ignore]
742 #[rstest]
743 fn test_tycho_db_connection() {
744 tracing_subscriber::fmt()
745 .with_env_filter("debug")
746 .init();
747
748 let ambient_contract =
749 Address::from_str("0xaaaaaaaaa24eeeb8d57d431224f73832bc34f688").unwrap();
750
751 let db = PreCachedDB::new().expect("db should initialize");
752
753 let acc_info = db
754 .basic_ref(ambient_contract)
755 .unwrap()
756 .unwrap();
757
758 debug!(?acc_info, "Account info");
759 }
760}