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 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
103pub 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 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 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}