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