1use alloy_consensus::BlockHeader;
2use alloy_primitives::{Address, U256};
3use eyre::{Result, eyre};
4use kabu_core_components::Component;
5use reth_node_types::{HeaderTy, NodeTypesWithDB};
6use reth_provider::{BlockNumReader, HeaderProvider};
7use reth_tasks::TaskExecutor;
8use revm::{Database, DatabaseCommit, DatabaseRef};
9use tokio::sync::{broadcast, broadcast::error::RecvError};
10use tracing::{debug, error};
11
12use kabu_evm_db::KabuDBError;
13use kabu_types_events::{MarketEvents, MessageSwapCompose, SwapComposeData, SwapComposeMessage};
14use kabu_types_swap::{Swap, SwapStep};
15use revm::context::BlockEnv;
16use revm::context_interface::block::BlobExcessGasAndPrice;
17
18async fn arb_swap_steps_optimizer_task<
19 DB: DatabaseRef<Error = KabuDBError> + Database<Error = KabuDBError> + DatabaseCommit + Send + Sync + Clone,
20>(
21 compose_channel_tx: broadcast::Sender<MessageSwapCompose<DB>>,
22 _state_db: DB,
23 block_number: u64,
24 block_timestamp: u64,
25 request: SwapComposeData<DB>,
26) -> Result<()> {
27 debug!("Step Simulation started");
28
29 if let Swap::BackrunSwapSteps((sp0, sp1)) = request.swap {
30 let start_time = chrono::Local::now();
31
32 let _block_env = BlockEnv {
33 number: U256::from(block_number + 1),
34 timestamp: U256::from(block_timestamp + 12),
35 blob_excess_gas_and_price: Some(BlobExcessGasAndPrice { excess_blob_gas: 0, blob_gasprice: 0 }),
36 ..Default::default()
37 };
38
39 match Ok::<(SwapStep, SwapStep), eyre::Error>((sp0.clone(), sp1.clone())) {
41 Ok((s0, s1)) => {
42 let encode_request = MessageSwapCompose::prepare(SwapComposeData {
43 origin: Some("merger_searcher".to_string()),
44 tips_pct: None,
45 swap: Swap::BackrunSwapSteps((s0, s1)),
46 ..request
47 });
48 compose_channel_tx.send(encode_request).map_err(|_| eyre!("CANNOT_SEND"))?;
49 }
50 Err(e) => {
51 error!("Optimization error:{}", e);
52 return Err(eyre!("OPTIMIZATION_ERROR"));
53 }
54 }
55 debug!("Step Optimization finished {} + {} {}", &sp0, &sp1, chrono::Local::now() - start_time);
56 } else {
57 error!("Incorrect swap_type");
58 return Err(eyre!("INCORRECT_SWAP_TYPE"));
59 }
60
61 Ok(())
62}
63
64async fn arb_swap_path_merger_worker<
65 N: NodeTypesWithDB,
66 R: BlockNumReader + HeaderProvider<Header = HeaderTy<N>> + Send + Sync + Clone + 'static,
67 DB: DatabaseRef<Error = KabuDBError> + Database<Error = KabuDBError> + DatabaseCommit + Send + Sync + Clone + 'static,
68>(
69 provider: R,
70 multicaller_address: Address,
71 market_events_rx: broadcast::Sender<MarketEvents>,
72 compose_channel_rx: broadcast::Sender<MessageSwapCompose<DB>>,
73 compose_channel_tx: broadcast::Sender<MessageSwapCompose<DB>>,
74) -> Result<()>
75where
76 HeaderTy<N>: BlockHeader,
77{
78 let mut market_events_receiver = market_events_rx.subscribe();
79 let mut compose_channel_receiver = compose_channel_rx.subscribe();
80
81 let mut ready_requests: Vec<SwapComposeData<DB>> = Vec::new();
82 let mut processed_swaps = std::collections::HashSet::new();
83
84 loop {
85 tokio::select! {
86 msg = market_events_receiver.recv() => {
87 let msg : Result<MarketEvents, RecvError> = msg;
88 match msg {
89 Ok(event) => {
90 match event {
91 MarketEvents::BlockHeaderUpdate{..} =>{
92 debug!("Cleaning ready requests");
93 ready_requests = Vec::new();
94 processed_swaps.clear();
95 }
96 MarketEvents::BlockStateUpdate{..}=>{
97 debug!("State updated");
98 }
100 _=>{}
101 }
102 }
103 Err(e)=>{error!("{}", e)}
104 }
105
106 },
107 msg = compose_channel_receiver.recv() => {
108 let msg : Result<MessageSwapCompose<DB>, RecvError> = msg;
109 match msg {
110 Ok(swap) => {
111
112 let compose_data = match swap.inner() {
113 SwapComposeMessage::Ready(data) => data,
114 _=>continue,
115 };
116
117 let swap_path = match &compose_data.swap {
118 Swap::BackrunSwapLine(path) => path,
119 _=>continue,
120 };
121
122 let swap_key = format!("{:?}", compose_data.swap);
124
125 if processed_swaps.contains(&swap_key) {
127 debug!("Skipping duplicate swap in merger");
128 continue;
129 }
130
131 for req in ready_requests.iter() {
132
133 let req_swap = match &req.swap {
134 Swap::BackrunSwapLine(path)=>path,
135 _ => continue,
136 };
137
138 if !compose_data.same_stuffing(&req.tx_compose.stuffing_txs_hashes) {
140 continue
141 };
142
143 if !SwapStep::can_merge_swap_paths(req_swap, swap_path) {
145 continue;
146 }
147
148 match SwapStep::merge_swap_paths( req_swap.clone(), swap_path.clone(), multicaller_address ){
149 Ok((sp0, sp1)) => {
150 let (block_number, block_timestamp) = match provider.last_block_number() {
152 Ok(block_num) => {
153 match provider.sealed_header(block_num) {
154 Ok(Some(sealed)) => {
155 let header = sealed.header();
156 (header.number(), header.timestamp())
157 },
158 Ok(None) => {
159 error!("No header for block {}", block_num);
160 continue;
161 }
162 Err(e) => {
163 error!("Failed to get header for block {}: {}", block_num, e);
164 continue;
165 }
166 }
167 }
168 Err(e) => {
169 error!("Failed to get last block number: {}", e);
170 continue;
171 }
172 };
173
174 let request = SwapComposeData{
175 swap : Swap::BackrunSwapSteps((sp0,sp1)),
176 ..compose_data.clone()
177 };
178
179 if let Some(db) = compose_data.poststate.clone() {
180 let db_clone = db.clone();
181
182 let compose_channel_clone = compose_channel_tx.clone();
183 tokio::task::spawn( async move {
184 arb_swap_steps_optimizer_task(
185 compose_channel_clone,
186 db_clone,
187 block_number,
188 block_timestamp,
189 request
190 ).await
191 });
192 }
193 break; }
195 Err(e)=>{
196 error!("SwapPath merge error : {} {}", ready_requests.len(), e);
197 }
198 }
199 }
200 processed_swaps.insert(swap_key);
201 ready_requests.push(compose_data.clone());
202 ready_requests.sort_by(|r0,r1| r1.swap.arb_profit().cmp(&r0.swap.arb_profit()) );
203
204 if ready_requests.len() > 20 {
206 ready_requests.truncate(20);
207 }
208
209 }
210 Err(e)=>{error!("{}",e)}
211 }
212 }
213 }
214 }
215}
216
217#[derive(Clone)]
218pub struct ArbSwapPathMergerComponent<N, R, DB>
219where
220 N: NodeTypesWithDB,
221 R: BlockNumReader + HeaderProvider<Header = HeaderTy<N>> + Send + Sync + Clone + 'static,
222 DB: Send + Sync + Clone + 'static,
223{
224 provider: R,
225 multicaller_address: Address,
226 market_events: Option<broadcast::Sender<MarketEvents>>,
227 compose_channel_rx: Option<broadcast::Sender<MessageSwapCompose<DB>>>,
228 compose_channel_tx: Option<broadcast::Sender<MessageSwapCompose<DB>>>,
229 _phantom: std::marker::PhantomData<N>,
230}
231
232impl<N, R, DB> ArbSwapPathMergerComponent<N, R, DB>
233where
234 N: NodeTypesWithDB,
235 R: BlockNumReader + HeaderProvider<Header = HeaderTy<N>> + Send + Sync + Clone + 'static,
236 DB: DatabaseRef + Send + Sync + Clone + 'static,
237{
238 pub fn new(provider: R, multicaller_address: Address) -> ArbSwapPathMergerComponent<N, R, DB> {
239 ArbSwapPathMergerComponent {
240 provider,
241 multicaller_address,
242 market_events: None,
243 compose_channel_rx: None,
244 compose_channel_tx: None,
245 _phantom: std::marker::PhantomData,
246 }
247 }
248
249 pub fn with_market_events_channel(self, market_events: broadcast::Sender<MarketEvents>) -> Self {
250 Self { market_events: Some(market_events), ..self }
251 }
252
253 pub fn with_compose_channel(self, compose_channel: broadcast::Sender<MessageSwapCompose<DB>>) -> Self {
254 Self { compose_channel_rx: Some(compose_channel.clone()), compose_channel_tx: Some(compose_channel), ..self }
255 }
256}
257
258impl<N, R, DB> Component for ArbSwapPathMergerComponent<N, R, DB>
259where
260 N: NodeTypesWithDB,
261 R: BlockNumReader + HeaderProvider<Header = HeaderTy<N>> + Send + Sync + Clone + 'static,
262 DB: DatabaseRef<Error = KabuDBError> + Database<Error = KabuDBError> + DatabaseCommit + Send + Sync + Clone + 'static,
263 HeaderTy<N>: BlockHeader,
264{
265 fn spawn(self, executor: TaskExecutor) -> Result<()> {
266 let name = self.name();
267
268 let compose_channel_rx = self.compose_channel_rx.ok_or_else(|| eyre!("compose_channel_rx not set"))?;
269 let compose_channel_tx = self.compose_channel_tx.ok_or_else(|| eyre!("compose_channel_tx not set"))?;
270 let market_events_rx = self.market_events.ok_or_else(|| eyre!("market_events not set"))?;
271
272 executor.spawn_critical(name, async move {
273 if let Err(e) = arb_swap_path_merger_worker::<N, _, _>(
274 self.provider,
275 self.multicaller_address,
276 market_events_rx,
277 compose_channel_rx,
278 compose_channel_tx,
279 )
280 .await
281 {
282 error!("Arb swap path merger worker failed: {}", e);
283 }
284 });
285
286 Ok(())
287 }
288
289 fn name(&self) -> &'static str {
290 "ArbSwapPathMergerComponent"
291 }
292}
293
294#[cfg(test)]
295mod test {
296 use alloy_primitives::{Address, U256};
297 use kabu_evm_db::KabuDB;
298 use kabu_types_events::SwapComposeData;
299 use kabu_types_market::SwapPath;
300 use kabu_types_market::Token;
301 use kabu_types_swap::{Swap, SwapAmountType, SwapLine};
302 use std::sync::Arc;
303
304 #[test]
305 pub fn test_sort() {
306 let mut ready_requests: Vec<SwapComposeData<KabuDB>> = Vec::new();
307 let token = Arc::new(Token::new(Address::random()));
308
309 let sp0 = SwapLine {
310 path: SwapPath {
311 tokens: vec![token.clone(), token.clone()],
312 pools: vec![],
313 disabled: false,
314 disabled_pool: Default::default(),
315 score: None,
316 },
317 amount_in: SwapAmountType::Set(U256::from(1)),
318 amount_out: SwapAmountType::Set(U256::from(2)),
319 ..Default::default()
320 };
321 ready_requests.push(SwapComposeData { swap: Swap::BackrunSwapLine(sp0), ..SwapComposeData::default() });
322
323 let sp1 = SwapLine {
324 path: SwapPath {
325 tokens: vec![token.clone(), token.clone()],
326 pools: vec![],
327 disabled: false,
328 disabled_pool: Default::default(),
329 score: None,
330 },
331 amount_in: SwapAmountType::Set(U256::from(10)),
332 amount_out: SwapAmountType::Set(U256::from(20)),
333 ..Default::default()
334 };
335 ready_requests.push(SwapComposeData { swap: Swap::BackrunSwapLine(sp1), ..SwapComposeData::default() });
336
337 let sp2 = SwapLine {
338 path: SwapPath {
339 tokens: vec![token.clone(), token.clone()],
340 pools: vec![],
341 disabled: false,
342 disabled_pool: Default::default(),
343 score: None,
344 },
345 amount_in: SwapAmountType::Set(U256::from(3)),
346 amount_out: SwapAmountType::Set(U256::from(5)),
347 ..Default::default()
348 };
349 ready_requests.push(SwapComposeData { swap: Swap::BackrunSwapLine(sp2), ..SwapComposeData::default() });
350
351 ready_requests.sort_by(|a, b| a.swap.arb_profit().cmp(&b.swap.arb_profit()));
352
353 assert_eq!(ready_requests[0].swap.arb_profit(), U256::from(1));
354 assert_eq!(ready_requests[1].swap.arb_profit(), U256::from(2));
355 assert_eq!(ready_requests[2].swap.arb_profit(), U256::from(10));
356 }
357}