kabu_defi_market/
pool_loader_actor.rs

1use kabu_core_components::Component;
2use reth_tasks::TaskExecutor;
3use std::collections::{BTreeMap, HashMap};
4use std::marker::PhantomData;
5use std::sync::Arc;
6use tokio::sync::{RwLock, broadcast};
7
8use alloy_network::Network;
9use alloy_primitives::Address;
10use alloy_provider::Provider;
11use alloy_rpc_types::TransactionRequest;
12use eyre::{Result, eyre};
13use tracing::{debug, error, info};
14
15use kabu_core_blockchain::{Blockchain, BlockchainState};
16use kabu_node_debug_provider::DebugProviderExt;
17use kabu_types_events::{LoomTask, MarketEvents};
18use kabu_types_market::MarketState;
19use kabu_types_market::SwapDirection;
20use kabu_types_market::required_state::RequiredStateReader;
21use kabu_types_market::{Market, PoolClass, PoolId, PoolLoaders, PoolWrapper, PoolsLoadingConfig};
22
23use kabu_types_blockchain::get_touched_addresses;
24use reth_node_types::NodePrimitives;
25use revm::{Database, DatabaseCommit, DatabaseRef};
26use tokio::sync::Semaphore;
27
28const MAX_CONCURRENT_TASKS: usize = 20;
29
30pub async fn pool_loader_worker<P, PL, N, DB>(
31    client: P,
32    pool_loaders: Arc<PoolLoaders<PL, N>>,
33    pools_config: PoolsLoadingConfig,
34    market: Arc<RwLock<Market>>,
35    market_state: Arc<RwLock<MarketState<DB>>>,
36    tasks_rx: broadcast::Sender<LoomTask>,
37    market_events_tx: broadcast::Sender<MarketEvents>,
38) -> Result<()>
39where
40    N: Network<TransactionRequest = TransactionRequest>,
41    P: Provider<N> + DebugProviderExt<N> + Send + Sync + Clone + 'static,
42    PL: Provider<N> + Send + Sync + Clone + 'static,
43    DB: Database + DatabaseRef + DatabaseCommit + Send + Sync + Clone + 'static,
44{
45    let mut processed_pools = HashMap::new();
46    let semaphore = std::sync::Arc::new(Semaphore::new(pools_config.threads().unwrap_or(MAX_CONCURRENT_TASKS)));
47
48    let mut tasks_receiver = tasks_rx.subscribe();
49    loop {
50        if let Ok(task) = tasks_receiver.recv().await {
51            let LoomTask::FetchAndAddPools(pools) = task;
52
53            for (pool_id, pool_class) in pools {
54                // Check if pool already exists
55                if processed_pools.insert(pool_id, true).is_some() {
56                    continue;
57                }
58
59                let sema_clone = semaphore.clone();
60                let client_clone = client.clone();
61                let market_clone = market.clone();
62                let market_state = market_state.clone();
63                let pool_loaders_clone = pool_loaders.clone();
64                let market_events_tx_clone = market_events_tx.clone();
65
66                tokio::task::spawn(async move {
67                    match sema_clone.acquire().await {
68                        Ok(permit) => {
69                            match fetch_and_add_pool_by_pool_id(
70                                client_clone,
71                                market_clone,
72                                market_state,
73                                pool_loaders_clone,
74                                pool_id,
75                                pool_class,
76                            )
77                            .await
78                            {
79                                Ok((pool_id, swap_path_idx_vec)) => {
80                                    info!(%pool_id, %pool_class, "Pool loaded successfully");
81                                    if let Err(e) = market_events_tx_clone.send(MarketEvents::NewPoolLoaded { pool_id, swap_path_idx_vec })
82                                    {
83                                        error!("Failed to send market event: {}", e);
84                                    }
85                                }
86                                Err(error) => {
87                                    error!(%error, %pool_id, %pool_class, "failed fetch_and_add_pool_by_address");
88                                }
89                            }
90
91                            drop(permit);
92                        }
93                        Err(error) => {
94                            error!(%error, "failed acquire semaphore");
95                        }
96                    }
97                });
98            }
99        }
100    }
101}
102
103/// Fetch pool data, add it to the market and fetch the required state
104pub async fn fetch_and_add_pool_by_pool_id<P, PL, N, DB, LDT>(
105    client: P,
106    market: Arc<RwLock<Market>>,
107    market_state: Arc<RwLock<MarketState<DB>>>,
108    pool_loaders: Arc<PoolLoaders<PL, N, LDT>>,
109    pool_id: PoolId,
110    pool_class: PoolClass,
111) -> Result<(PoolId, Vec<usize>)>
112where
113    N: Network<TransactionRequest = alloy_rpc_types::TransactionRequest>,
114    P: Provider<N> + DebugProviderExt<N> + Send + Sync + Clone + 'static,
115    PL: Provider<N> + Send + Sync + Clone + 'static,
116    DB: DatabaseRef + Database + DatabaseCommit + Send + Sync + Clone + 'static,
117    LDT: NodePrimitives + 'static,
118{
119    debug!(%pool_id, %pool_class, "Fetching pool");
120
121    let pool = pool_loaders.load_pool_without_provider(pool_id, &pool_class).await?;
122    fetch_state_and_add_pool::<P, N, DB, LDT>(client, market.clone(), market_state.clone(), pool).await
123}
124
125pub async fn fetch_state_and_add_pool<P, N, DB, LDT>(
126    client: P,
127    market: Arc<RwLock<Market>>,
128    market_state: Arc<RwLock<MarketState<DB>>>,
129    pool_wrapped: PoolWrapper,
130) -> Result<(PoolId, Vec<usize>)>
131where
132    N: Network<TransactionRequest = alloy_rpc_types::TransactionRequest>,
133    P: Provider<N> + DebugProviderExt<N> + Send + Sync + Clone + 'static,
134    DB: Database + DatabaseRef + DatabaseCommit + Send + Sync + Clone + 'static,
135    LDT: NodePrimitives,
136{
137    match pool_wrapped.get_state_required() {
138        Ok(required_state) => match RequiredStateReader::fetch_calls_and_slots::<N, P>(client, required_state, None).await {
139            Ok(state) => {
140                let pool_address = pool_wrapped.get_address();
141                {
142                    let updated_addresses = get_touched_addresses(&state);
143
144                    let mut market_state_write_guard = market_state.write().await;
145                    market_state_write_guard.apply_geth_update(state);
146                    // TODO : Fix disable cells
147                    let address = match pool_address {
148                        PoolId::Address(addr) => addr,
149                        PoolId::B256(_) => Address::ZERO,
150                    };
151                    market_state_write_guard.config.disable_cell_vec(address, pool_wrapped.get_read_only_cell_vec());
152
153                    let pool_tokens = pool_wrapped.get_tokens();
154
155                    for updated_address in updated_addresses {
156                        if !pool_tokens.contains(&updated_address) {
157                            market_state_write_guard.config.add_force_insert(updated_address);
158                        }
159                    }
160
161                    drop(market_state_write_guard);
162                }
163
164                let directions_vec = pool_wrapped.get_swap_directions();
165                let pool_manager_cells = pool_wrapped.get_pool_manager_cells();
166                let pool_id = pool_wrapped.get_pool_id();
167
168                let mut directions_tree: BTreeMap<PoolWrapper, Vec<SwapDirection>> = BTreeMap::new();
169                directions_tree.insert(pool_wrapped.clone(), directions_vec);
170
171                let start_time = std::time::Instant::now();
172                let mut market_write_guard = market.write().await;
173                debug!(elapsed = start_time.elapsed().as_micros(), "market_guard market.write acquired");
174                // Ignore error if pool already exists because it was maybe already added by e.g. db pool loader
175                let _ = market_write_guard.add_pool(pool_wrapped);
176
177                let swap_paths = market_write_guard.build_swap_path_vec(&directions_tree)?;
178                let swap_paths_added = market_write_guard.add_paths(swap_paths);
179
180                for (pool_manager_address, cells_vec) in pool_manager_cells {
181                    for cell in cells_vec {
182                        market_write_guard.add_pool_manager_cell(pool_manager_address, pool_id, cell)
183                    }
184                }
185
186                debug!(elapsed = start_time.elapsed().as_micros(),  market = %market_write_guard, "market_guard path added");
187
188                drop(market_write_guard);
189                debug!(elapsed = start_time.elapsed().as_micros(), "market_guard market.write releases");
190
191                Ok((pool_id, swap_paths_added))
192            }
193            Err(e) => {
194                error!("{}", e);
195                Err(e)
196            }
197        },
198        Err(e) => {
199            error!("{}", e);
200            Err(e)
201        }
202    }
203}
204
205pub struct PoolLoaderActor<P, PL, N, DB>
206where
207    N: Network,
208    P: Provider<N> + Send + Sync + Clone + 'static,
209    PL: Provider<N> + Send + Sync + Clone + 'static,
210    DB: Database + DatabaseRef + DatabaseCommit + Send + Sync + Clone + Default + 'static,
211{
212    client: P,
213    pool_loaders: Arc<PoolLoaders<PL, N>>,
214    pools_config: PoolsLoadingConfig,
215
216    market: Option<Arc<RwLock<Market>>>,
217
218    market_state: Option<Arc<RwLock<MarketState<DB>>>>,
219
220    tasks_rx: Option<broadcast::Sender<LoomTask>>,
221
222    market_events_channel_tx: Option<broadcast::Sender<MarketEvents>>,
223    _n: PhantomData<N>,
224}
225
226impl<P, PL, N, DB> PoolLoaderActor<P, PL, N, DB>
227where
228    N: Network,
229    P: Provider<N> + Send + Sync + Clone + 'static,
230    PL: Provider<N> + Send + Sync + Clone + 'static,
231    DB: Database + DatabaseRef + DatabaseCommit + Send + Sync + Clone + Default + 'static,
232{
233    pub fn new(client: P, pool_loaders: Arc<PoolLoaders<PL, N>>, pools_config: PoolsLoadingConfig) -> Self {
234        Self {
235            client,
236            pool_loaders,
237            pools_config,
238            market: None,
239            market_state: None,
240            tasks_rx: None,
241            market_events_channel_tx: None,
242            _n: PhantomData,
243        }
244    }
245
246    pub fn on_bc<LDT: NodePrimitives>(self, bc: &Blockchain, state: &BlockchainState<DB, LDT>) -> Self {
247        Self {
248            market: Some(bc.market()),
249            market_state: Some(state.market_state_commit()),
250            tasks_rx: Some(bc.tasks_channel()),
251            market_events_channel_tx: Some(bc.market_events_channel()),
252            ..self
253        }
254    }
255}
256
257impl<P, PL, N, DB> Component for PoolLoaderActor<P, PL, N, DB>
258where
259    N: Network<TransactionRequest = TransactionRequest>,
260    P: Provider<N> + DebugProviderExt<N> + Send + Sync + Clone + 'static,
261    PL: Provider<N> + Send + Sync + Clone + 'static,
262    DB: Database + DatabaseRef + DatabaseCommit + Default + Send + Sync + Clone + 'static,
263{
264    fn spawn(self, executor: TaskExecutor) -> Result<()> {
265        let name = self.name();
266
267        let tasks_rx = self.tasks_rx.ok_or_else(|| eyre!("tasks_rx not set"))?;
268
269        executor.spawn_critical(name, async move {
270            if let Err(e) = pool_loader_worker(
271                self.client.clone(),
272                self.pool_loaders.clone(),
273                self.pools_config.clone(),
274                self.market.clone().unwrap(),
275                self.market_state.clone().unwrap(),
276                tasks_rx,
277                self.market_events_channel_tx.clone().unwrap(),
278            )
279            .await
280            {
281                error!("Pool loader worker failed: {}", e);
282            }
283        });
284
285        Ok(())
286    }
287    fn name(&self) -> &'static str {
288        "PoolLoaderActor"
289    }
290}