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 get_storage(&self, address: &Address, index: &U256) -> Option<U256> {
161 self.inner
162 .read()
163 .unwrap()
164 .accounts
165 .get_storage(address, index)
166 }
167
168 pub fn update_state(
178 &mut self,
179 updates: &HashMap<Address, StateUpdate>,
180 block: BlockHeader,
181 ) -> Result<HashMap<Address, StateUpdate>, PreCachedDBError> {
182 let mut write_guard = self.write_inner()?;
185
186 let mut revert_updates = HashMap::new();
187 write_guard.block = Some(block);
188
189 for (address, update_info) in updates.iter() {
190 let mut revert_entry = StateUpdate::default();
191
192 if let Some(current_account) = write_guard
193 .accounts
194 .get_account_info(address)
195 {
196 revert_entry.balance = Some(current_account.balance);
197 }
198
199 if update_info.storage.is_some() {
200 let mut revert_storage = HashMap::default();
201 for index in update_info
202 .storage
203 .as_ref()
204 .unwrap()
205 .keys()
206 {
207 if let Some(s) = write_guard
208 .accounts
209 .get_storage(address, index)
210 {
211 revert_storage.insert(*index, s);
212 }
213 }
214 revert_entry.storage = Some(revert_storage);
215 }
216 revert_updates.insert(*address, revert_entry);
217 write_guard
218 .accounts
219 .update_account(address, update_info);
220 }
221
222 Ok(revert_updates)
223 }
224
225 #[cfg(test)]
226 pub fn get_account_storage(&self) -> Result<AccountStorage, PreCachedDBError> {
227 self.read_inner()
228 .map(|guard| guard.accounts.clone())
229 }
230
231 pub fn block_number(&self) -> Result<Option<u64>, PreCachedDBError> {
233 self.read_inner().map(|guard| {
234 guard
235 .block
236 .as_ref()
237 .map(|header| header.number)
238 })
239 }
240
241 pub fn clear(&self) -> Result<(), PreCachedDBError> {
243 let mut write_guard = self.write_inner()?;
244 write_guard.accounts.clear();
245 write_guard.block = None;
246 Ok(())
247 }
248
249 fn read_inner(&self) -> Result<RwLockReadGuard<'_, PreCachedDBInner>, PreCachedDBError> {
250 self.inner
251 .read()
252 .map_err(|_| PreCachedDBError::Fatal("Tycho state db lock poisoned".into()))
253 }
254
255 fn write_inner(&self) -> Result<RwLockWriteGuard<'_, PreCachedDBInner>, PreCachedDBError> {
256 self.inner
257 .write()
258 .map_err(|_| PreCachedDBError::Fatal("Tycho state db lock poisoned".into()))
259 }
260}
261
262impl EngineDatabaseInterface for PreCachedDB {
263 type Error = PreCachedDBError;
264
265 fn init_account(
276 &self,
277 address: Address,
278 account: AccountInfo,
279 permanent_storage: Option<HashMap<U256, U256>>,
280 _mocked: bool,
281 ) -> Result<(), <Self as EngineDatabaseInterface>::Error> {
282 if account.code.is_none() && account.code_hash != KECCAK_EMPTY {
283 warn!("Code is None for account {address} but code hash is not KECCAK_EMPTY");
284 } else if account.code.is_some() && account.code_hash == KECCAK_EMPTY {
285 warn!("Code is Some for account {address} but code hash is KECCAK_EMPTY");
286 }
287
288 self.write_inner()?
289 .accounts
290 .init_account(address, account, permanent_storage, true);
291
292 Ok(())
293 }
294
295 fn clear_temp_storage(&mut self) -> Result<(), <Self as EngineDatabaseInterface>::Error> {
297 debug!("Temp storage in TychoDB is never set, nothing to clear");
298
299 Ok(())
300 }
301
302 fn get_current_block(&self) -> Option<BlockHeader> {
303 self.inner.read().unwrap().block.clone()
304 }
305}
306
307impl DatabaseRef for PreCachedDB {
308 type Error = PreCachedDBError;
309 fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
322 self.read_inner()?
323 .accounts
324 .get_account_info(&address)
325 .map(|acc| Some(acc.clone()))
326 .ok_or(PreCachedDBError::MissingAccount(address))
327 }
328
329 fn code_by_hash_ref(&self, code_hash: B256) -> Result<Bytecode, Self::Error> {
330 Err(PreCachedDBError::Fatal(format!("Code by hash not supported: {code_hash}")))
331 }
332
333 fn storage_ref(&self, address: Address, index: U256) -> Result<U256, Self::Error> {
348 debug!(%address, %index, "Requested storage of account");
349 let read_guard = self.read_inner()?;
350 if let Some(storage_value) = read_guard
351 .accounts
352 .get_storage(&address, &index)
353 {
354 debug!(%address, %index, %storage_value, "Got value locally");
355 Ok(storage_value)
356 } else {
357 if read_guard
359 .accounts
360 .account_present(&address)
361 {
362 debug!(%address, %index, "Account found, but slot is zero");
365 Ok(U256::ZERO)
366 } else {
367 debug!(%address, %index, "Account not found");
369 Err(PreCachedDBError::MissingAccount(address))
370 }
371 }
372 }
373
374 fn block_hash_ref(&self, _number: u64) -> Result<B256, Self::Error> {
376 match self.read_inner()?.block.clone() {
377 Some(header) => Ok(B256::from_slice(&header.hash)),
378 None => Ok(B256::default()),
379 }
380 }
381}
382
383#[cfg(test)]
384mod tests {
385 use std::{error::Error, str::FromStr};
386
387 use revm::primitives::U256;
388 use rstest::{fixture, rstest};
389 use tycho_common::Bytes;
390
391 use super::*;
392 use crate::evm::tycho_models::{AccountUpdate, Chain, ChangeType};
393
394 #[fixture]
395 pub fn mock_db() -> PreCachedDB {
396 PreCachedDB {
397 inner: Arc::new(RwLock::new(PreCachedDBInner {
398 accounts: AccountStorage::new(),
399 block: None,
400 })),
401 }
402 }
403
404 #[rstest]
405 #[tokio::test]
406 async fn test_account_get_acc_info(mock_db: PreCachedDB) -> Result<(), Box<dyn Error>> {
407 let mock_acc_address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc")?;
410 mock_db
411 .init_account(mock_acc_address, AccountInfo::default(), None, false)
412 .expect("Account init should succeed");
413
414 let acc_info = mock_db
415 .basic_ref(mock_acc_address)
416 .unwrap()
417 .unwrap();
418
419 assert_eq!(
420 mock_db
421 .basic_ref(mock_acc_address)
422 .unwrap()
423 .unwrap(),
424 acc_info
425 );
426 Ok(())
427 }
428
429 #[rstest]
430 fn test_account_storage(mock_db: PreCachedDB) -> Result<(), Box<dyn Error>> {
431 let mock_acc_address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc")?;
432 let storage_address = U256::from(1);
433 let mut permanent_storage: HashMap<U256, U256> = HashMap::new();
434 permanent_storage.insert(storage_address, U256::from(10));
435 mock_db
436 .init_account(mock_acc_address, AccountInfo::default(), Some(permanent_storage), false)
437 .expect("Account init should succeed");
438
439 let storage = mock_db
440 .storage_ref(mock_acc_address, storage_address)
441 .unwrap();
442
443 assert_eq!(storage, U256::from(10));
444 Ok(())
445 }
446
447 #[rstest]
448 fn test_account_storage_zero(mock_db: PreCachedDB) -> Result<(), Box<dyn Error>> {
449 let mock_acc_address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc")?;
450 let storage_address = U256::from(1);
451 mock_db
452 .init_account(mock_acc_address, AccountInfo::default(), None, false)
453 .expect("Account init should succeed");
454
455 let storage = mock_db
456 .storage_ref(mock_acc_address, storage_address)
457 .unwrap();
458
459 assert_eq!(storage, U256::ZERO);
460 Ok(())
461 }
462
463 #[rstest]
464 #[should_panic(
465 expected = "called `Result::unwrap()` on an `Err` value: MissingAccount(0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc)"
466 )]
467 fn test_account_storage_missing(mock_db: PreCachedDB) {
468 let mock_acc_address =
469 Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc").unwrap();
470 let storage_address = U256::from(1);
471
472 mock_db
474 .storage_ref(mock_acc_address, storage_address)
475 .unwrap();
476 }
477
478 #[rstest]
479 #[tokio::test]
480 async fn test_update_state(mut mock_db: PreCachedDB) -> Result<(), Box<dyn Error>> {
481 let address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc")?;
482 mock_db
483 .init_account(address, AccountInfo::default(), None, false)
484 .expect("Account init should succeed");
485
486 let mut new_storage = HashMap::default();
487 let new_storage_value_index = U256::from_limbs_slice(&[123]);
488 new_storage.insert(new_storage_value_index, new_storage_value_index);
489 let new_balance = U256::from_limbs_slice(&[500]);
490 let update = StateUpdate { storage: Some(new_storage), balance: Some(new_balance) };
491 let new_block = BlockHeader {
492 number: 1,
493 hash: Bytes::from_str(
494 "0xc6b994ec855fb2b31013c7ae65074406fac46679b5b963469104e0bfeddd66d9",
495 )
496 .unwrap(),
497 timestamp: 123,
498 ..Default::default()
499 };
500 let mut updates = HashMap::default();
501 updates.insert(address, update);
502
503 mock_db
504 .update_state(&updates, new_block)
505 .expect("State update should succeed");
506
507 assert_eq!(
508 mock_db
509 .get_storage(&address, &new_storage_value_index)
510 .unwrap(),
511 new_storage_value_index
512 );
513 let account_info = mock_db
514 .basic_ref(address)
515 .unwrap()
516 .unwrap();
517 assert_eq!(account_info.balance, new_balance);
518 let block = mock_db
519 .inner
520 .read()
521 .unwrap()
522 .block
523 .clone()
524 .expect("block is Some");
525 assert_eq!(block.number, 1);
526
527 Ok(())
528 }
529
530 #[rstest]
531 #[tokio::test]
532 async fn test_block_number_getter(mut mock_db: PreCachedDB) -> Result<(), Box<dyn Error>> {
533 let address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc")?;
534 mock_db
535 .init_account(address, AccountInfo::default(), None, false)
536 .expect("Account init should succeed");
537
538 let new_block = BlockHeader {
539 number: 1,
540 hash: Bytes::from_str(
541 "0xc6b994ec855fb2b31013c7ae65074406fac46679b5b963469104e0bfeddd66d9",
542 )
543 .unwrap(),
544 timestamp: 123,
545 ..Default::default()
546 };
547 let updates = HashMap::default();
548
549 mock_db
550 .update_state(&updates, new_block)
551 .expect("State update should succeed");
552
553 let block_number = mock_db.block_number();
554 assert_eq!(block_number.unwrap().unwrap(), 1);
555
556 Ok(())
557 }
558
559 #[rstest]
560 #[tokio::test]
561 async fn test_update() {
562 let mock_db = PreCachedDB {
563 inner: Arc::new(RwLock::new(PreCachedDBInner {
564 accounts: AccountStorage::new(),
565 block: None,
566 })),
567 };
568
569 let account_update = AccountUpdate::new(
570 Address::from_str("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D").unwrap(),
571 Chain::Ethereum,
572 HashMap::new(),
573 Some(U256::from(500)),
574 Some(Vec::<u8>::new()),
575 ChangeType::Creation,
576 );
577
578 let new_block = BlockHeader {
579 number: 1,
580 hash: Bytes::from_str(
581 "0xc6b994ec855fb2b31013c7ae65074406fac46679b5b963469104e0bfeddd66d9",
582 )
583 .unwrap(),
584 timestamp: 123,
585 ..Default::default()
586 };
587
588 mock_db
589 .update(vec![account_update], Some(new_block))
590 .unwrap();
591
592 let account_info = mock_db
593 .basic_ref(Address::from_str("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D").unwrap())
594 .unwrap()
595 .unwrap();
596
597 assert_eq!(
598 account_info,
599 AccountInfo {
600 nonce: 0,
601 balance: U256::from(500),
602 code_hash: B256::from_str(
603 "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
604 )
605 .unwrap(),
606 code: Some(Bytecode::default()),
607 }
608 );
609
610 assert_eq!(
611 mock_db
612 .inner
613 .read()
614 .unwrap()
615 .block
616 .clone()
617 .expect("block is Some")
618 .number,
619 1
620 );
621 }
622
623 #[ignore]
639 #[rstest]
640 fn test_tycho_db_connection() {
641 tracing_subscriber::fmt()
642 .with_env_filter("debug")
643 .init();
644
645 let ambient_contract =
646 Address::from_str("0xaaaaaaaaa24eeeb8d57d431224f73832bc34f688").unwrap();
647
648 let db = PreCachedDB::new().expect("db should initialize");
649
650 let acc_info = db
651 .basic_ref(ambient_contract)
652 .unwrap()
653 .unwrap();
654
655 debug!(?acc_info, "Account info");
656 }
657}