kabu_defi_health_monitor/
state_health_monitor.rs

1use kabu_core_components::Component;
2use reth_tasks::TaskExecutor;
3use std::collections::HashMap;
4use std::sync::Arc;
5use tokio::sync::{RwLock, broadcast};
6
7use alloy_eips::BlockNumberOrTag;
8use alloy_network::Ethereum;
9use alloy_provider::Provider;
10use chrono::{DateTime, Duration, Local};
11use eyre::Result;
12use tokio::sync::broadcast::Receiver;
13use tokio::sync::broadcast::error::RecvError;
14use tracing::{error, info, warn};
15
16use kabu_core_blockchain::{Blockchain, BlockchainState};
17use kabu_evm_db::DatabaseKabuExt;
18use kabu_types_events::{MarketEvents, MessageTxCompose, TxComposeMessageType};
19use kabu_types_market::{MarketState, PoolId};
20use reth_node_types::NodePrimitives;
21use revm::DatabaseRef;
22
23async fn verify_pool_state_task<P: Provider<Ethereum> + 'static, DB: DatabaseKabuExt>(
24    client: P,
25    address: PoolId,
26    market_state: Arc<RwLock<MarketState<DB>>>,
27) -> Result<()> {
28    info!("Verifying state {address:?}");
29    let address = match address {
30        PoolId::Address(addr) => addr,
31        PoolId::B256(_) => return Err(eyre::eyre!("B256 pool ID not supported for verification")),
32    };
33    let account = market_state.write().await.state_db.load_account(address).cloned()?;
34    let read_only_cell_hash_set = market_state.read().await.config.read_only_cells.get(&address).cloned().unwrap_or_default();
35
36    for (cell, current_value) in account.storage.iter() {
37        if read_only_cell_hash_set.contains(cell) {
38            continue;
39        }
40        match client.get_storage_at(address, *cell).block_id(BlockNumberOrTag::Latest.into()).await {
41            Ok(actual_value) => {
42                if actual_value.is_zero() {
43                    continue;
44                }
45                if *current_value != actual_value {
46                    warn!(
47                        "verify : account storage is different : {address:?} {cell:?} {current_value:#32x} -> {actual_value:#32x} storage size : {}",
48                        account.storage.len()
49                    );
50                    if let Err(e) = market_state.write().await.state_db.insert_account_storage(address, *cell, actual_value) {
51                        error!("{e}");
52                    }
53                }
54            }
55            Err(e) => {
56                error!("Cannot read storage {:?} {:?} : {}", account, cell, e)
57            }
58        }
59    }
60
61    Ok(())
62}
63
64pub async fn state_health_monitor_worker<
65    P: Provider<Ethereum> + Clone + 'static,
66    DB: DatabaseRef + DatabaseKabuExt + Send + Sync + Clone + 'static,
67>(
68    client: P,
69    market_state: Arc<RwLock<MarketState<DB>>>,
70    tx_compose_channel_rx: broadcast::Sender<MessageTxCompose>,
71    market_events_rx: broadcast::Sender<MarketEvents>,
72) -> Result<()> {
73    let mut tx_compose_channel_rx: Receiver<MessageTxCompose> = tx_compose_channel_rx.subscribe();
74    let mut market_events_rx: Receiver<MarketEvents> = market_events_rx.subscribe();
75
76    let mut check_time_map: HashMap<PoolId, DateTime<Local>> = HashMap::new();
77    let mut pool_address_to_verify_vec: Vec<PoolId> = Vec::new();
78
79    loop {
80        tokio::select! {
81            msg = market_events_rx.recv() => {
82                let market_event_msg : Result<MarketEvents, RecvError> = msg;
83                match market_event_msg {
84                    Ok(market_event)=>{
85                        if matches!(market_event, MarketEvents::BlockStateUpdate{..}) {
86                            for pool_address in pool_address_to_verify_vec {
87                                tokio::task::spawn(
88                                    verify_pool_state_task(
89                                        client.clone(),
90                                        pool_address,
91                                        market_state.clone()
92                                    )
93                                );
94                            }
95                            pool_address_to_verify_vec = Vec::new();
96                        }
97                    }
98                    Err(e)=>{error!("market_event_rx error : {e}")}
99                }
100            },
101
102            msg = tx_compose_channel_rx.recv() => {
103                let tx_compose_update : Result<MessageTxCompose, RecvError>  = msg;
104                match tx_compose_update {
105                    Ok(tx_compose_msg)=>{
106                        if let TxComposeMessageType::Sign(sign_request_data)= tx_compose_msg.inner
107                            && let Some(swap) = sign_request_data.swap {
108                                let pool_address_vec =  swap.get_pool_address_vec();
109                                let now = chrono::Local::now();
110                                for pool_address in pool_address_vec {
111                                    if now - check_time_map.get(&pool_address).cloned().unwrap_or(DateTime::<Local>::MIN_UTC.into()) > Duration::seconds(60) {
112                                        check_time_map.insert(pool_address, Local::now());
113                                        if !pool_address_to_verify_vec.contains(&pool_address){
114                                            pool_address_to_verify_vec.push(pool_address)
115                                        }
116                                    }
117                                }
118                        }
119
120                    }
121                    Err(e)=>{
122                        error!("tx_compose_channel_rx : {e}")
123                    }
124                }
125
126            }
127
128        }
129    }
130}
131
132pub struct StateHealthMonitorActor<P, DB: Clone + Send + Sync + 'static> {
133    client: P,
134
135    market_state: Option<Arc<RwLock<MarketState<DB>>>>,
136
137    tx_compose_channel_rx: Option<broadcast::Sender<MessageTxCompose>>,
138
139    market_events_rx: Option<broadcast::Sender<MarketEvents>>,
140}
141
142impl<P, DB> StateHealthMonitorActor<P, DB>
143where
144    P: Provider<Ethereum> + Send + Sync + Clone + 'static,
145    DB: DatabaseRef + DatabaseKabuExt + Send + Sync + Clone + Default + 'static,
146{
147    pub fn new(client: P) -> Self {
148        StateHealthMonitorActor { client, market_state: None, tx_compose_channel_rx: None, market_events_rx: None }
149    }
150
151    pub fn on_bc<NP: NodePrimitives>(self, bc: &Blockchain, state: &BlockchainState<DB, NP>) -> Self {
152        Self {
153            market_state: Some(state.market_state()),
154            tx_compose_channel_rx: Some(bc.tx_compose_channel()),
155            market_events_rx: Some(bc.market_events_channel()),
156            ..self
157        }
158    }
159}
160
161impl<P, DB> Component for StateHealthMonitorActor<P, DB>
162where
163    P: Provider<Ethereum> + Send + Sync + Clone + 'static,
164    DB: DatabaseRef + DatabaseKabuExt + Send + Sync + Clone + 'static,
165{
166    fn spawn(self, executor: TaskExecutor) -> Result<()> {
167        let market_state = self.market_state.clone().ok_or_else(|| eyre::eyre!("market_state not set"))?;
168        let tx_compose_channel_rx = self.tx_compose_channel_rx.clone().ok_or_else(|| eyre::eyre!("tx_compose_channel_rx not set"))?;
169        let market_events_rx = self.market_events_rx.clone().ok_or_else(|| eyre::eyre!("market_events_rx not set"))?;
170
171        executor.spawn(async move {
172            if let Err(e) = state_health_monitor_worker(self.client.clone(), market_state, tx_compose_channel_rx, market_events_rx).await {
173                tracing::error!("State health monitor worker failed: {}", e);
174            }
175        });
176        Ok(())
177    }
178    fn name(&self) -> &'static str {
179        "StateHealthMonitorActor"
180    }
181}