1use reth_tasks::TaskExecutor;
2use std::collections::HashMap;
3use std::marker::PhantomData;
4use std::ops::Deref;
5use std::sync::Arc;
6use tokio::sync::{RwLock, broadcast};
7
8use alloy_consensus::Transaction as _;
9use alloy_consensus::transaction::TxHashRef;
10use alloy_eips::BlockNumberOrTag;
11use alloy_network::Network;
12use alloy_primitives::{Address, TxHash, U256};
13use alloy_provider::Provider;
14use alloy_rpc_types::state::StateOverride;
15use alloy_rpc_types::{BlockOverrides, TransactionInput, TransactionRequest};
16use alloy_rpc_types_trace::geth::GethDebugTracingCallOptions;
17use eyre::{Result, eyre};
18use kabu_core_components::Component;
19use kabu_evm_db::{DatabaseHelpers, KabuDBError};
20use kabu_evm_utils::evm_env::tx_req_to_env;
21use kabu_evm_utils::evm_transact;
22use kabu_node_debug_provider::DebugProviderExt;
23use kabu_types_blockchain::{GethStateUpdate, GethStateUpdateVec, TRACING_CALL_OPTS, debug_trace_call_pre_state};
24use kabu_types_entities::{DataFetcher, FetchState};
25use kabu_types_events::{MarketEvents, MessageSwapCompose, SwapComposeData, SwapComposeMessage, TxComposeData};
26use kabu_types_market::MarketState;
27use kabu_types_swap::Swap;
28use lazy_static::lazy_static;
29use reth_node_types::NodePrimitives;
30use reth_primitives_traits::SignerRecoverable;
31use revm::context::{BlockEnv, ContextTr};
32use revm::context_interface::block::BlobExcessGasAndPrice;
33use revm::{Context, Database, DatabaseCommit, DatabaseRef, MainBuilder, MainContext};
34use tokio::sync::broadcast::error::RecvError;
35use tracing::{debug, error, info, trace};
36
37lazy_static! {
38 static ref COINBASE: Address = "0x1f9090aaE28b8a3dCeaDf281B0F12828e676c326".parse().unwrap();
39}
40
41fn get_merge_list<'a, DB: Clone + 'static>(
42 request: &SwapComposeData<DB>,
43 swap_paths: &'a HashMap<TxHash, Vec<SwapComposeData<DB>>>,
44) -> Vec<&'a SwapComposeData<DB>> {
45 let swap_line = if let Swap::BackrunSwapLine(swap_line) = &request.swap {
47 swap_line
48 } else {
49 return Vec::new();
50 };
51
52 let swap_stuffing_hash = request.first_stuffing_hash();
53
54 let mut ret: Vec<&SwapComposeData<DB>> = swap_paths
55 .iter()
56 .filter_map(|(k, v)| {
57 if *k != swap_stuffing_hash {
58 v.iter().find(|a| if let Swap::BackrunSwapLine(a_line) = &a.swap { a_line.path == swap_line.path } else { false })
59 } else {
60 None
61 }
62 })
63 .collect();
64
65 ret.sort_by(|a, b| b.swap.arb_profit_eth().cmp(&a.swap.arb_profit_eth()));
66
67 ret
68}
69
70async fn same_path_merger_task<P, N, DB, NP>(
71 client: P,
72 stuffing_txes: Vec<NP::SignedTx>,
73 pre_states: Arc<RwLock<DataFetcher<TxHash, GethStateUpdate>>>,
74 market_state: Arc<RwLock<MarketState<DB>>>,
75 call_opts: GethDebugTracingCallOptions,
76 request: SwapComposeData<DB, NP>,
77 swap_request_tx: broadcast::Sender<MessageSwapCompose<DB, NP>>,
78) -> Result<()>
79where
80 N: Network<TransactionRequest = TransactionRequest>,
81 P: Provider<N> + DebugProviderExt<N> + Send + Sync + Clone + 'static,
82 DB: Database<Error = KabuDBError> + DatabaseRef<Error = KabuDBError> + DatabaseCommit + Send + Sync + Clone + 'static,
83 NP: NodePrimitives,
84{
85 debug!("same_path_merger_task stuffing_txs len {}", stuffing_txes.len());
86
87 let mut prestate_guard = pre_states.write().await;
88
89 let mut stuffing_state_locks: Vec<(NP::SignedTx, FetchState<GethStateUpdate>)> = Vec::new();
90
91 for tx in stuffing_txes.into_iter() {
92 let client_clone = client.clone();
93 let tx_hash: TxHash = *tx.tx_hash();
94 let call_opts_clone = call_opts.clone();
95
96 let from = tx.recover_signer().map_err(|e| eyre!("Failed to recover signer: {}", e))?;
98 let tx_request = TransactionRequest::default()
99 .from(from)
100 .to(tx.to().unwrap_or_default())
101 .value(tx.value())
102 .input(TransactionInput::new(tx.input().clone()))
103 .nonce(tx.nonce())
104 .gas_limit(tx.gas_limit())
105 .max_fee_per_gas(tx.max_fee_per_gas())
106 .max_priority_fee_per_gas(tx.max_priority_fee_per_gas().unwrap_or_default());
107
108 let lock = prestate_guard
109 .fetch(tx_hash, |_tx_hash| async move {
110 debug_trace_call_pre_state(client_clone, tx_request, BlockNumberOrTag::Latest.into(), Some(call_opts_clone)).await
111 })
112 .await;
113
114 stuffing_state_locks.push((tx, lock));
115 }
116
117 drop(prestate_guard);
118
119 let mut stuffing_states: Vec<(NP::SignedTx, GethStateUpdate)> = Vec::new();
120
121 for (tx, lock) in stuffing_state_locks.into_iter() {
122 if let FetchState::Fetching(lock) = lock
123 && let Some(t) = lock.read().await.deref()
124 {
125 stuffing_states.push((tx, t.clone()));
126 }
127 }
128
129 let mut tx_order: Vec<usize> = (0..stuffing_states.len()).collect();
130
131 let mut changing: Option<usize> = None;
132 let mut counter = 0;
133
134 let db_org = market_state.read().await.state_db.clone();
135
136 let rdb: Option<DB> = loop {
137 counter += 1;
138 if counter > 10 {
139 break None;
140 }
141
142 let mut ok = true;
143
144 let tx_and_state: Vec<&(NP::SignedTx, GethStateUpdate)> = tx_order.iter().map(|i| stuffing_states.get(*i).unwrap()).collect();
145
146 let states: GethStateUpdateVec = tx_and_state.iter().map(|(_tx, state)| state.clone()).collect();
147
148 let mut db = db_org.clone();
149
150 DatabaseHelpers::apply_geth_state_update_vec(&mut db, states);
151
152 let block_env = BlockEnv {
153 number: U256::from(request.tx_compose.next_block_number),
154 timestamp: U256::from(request.tx_compose.next_block_timestamp),
155 basefee: request.tx_compose.next_block_base_fee,
156 blob_excess_gas_and_price: Some(BlobExcessGasAndPrice { excess_blob_gas: 0, blob_gasprice: 0 }),
157 ..Default::default()
158 };
159
160 let mut evm = Context::mainnet().with_db(db).with_block(block_env).build_mainnet();
161
162 for (idx, tx_idx) in tx_order.clone().iter().enumerate() {
163 let tx = &stuffing_states[*tx_idx].0;
165
166 let from = match tx.recover_signer() {
168 Ok(addr) => addr,
169 Err(e) => {
170 error!("Failed to recover signer: {}", e);
171 ok = false;
172 break;
173 }
174 };
175 let tx_req = TransactionRequest::default()
176 .from(from)
177 .to(tx.to().unwrap_or_default())
178 .value(tx.value())
179 .input(TransactionInput::new(tx.input().clone()))
180 .nonce(tx.nonce())
181 .gas_limit(tx.gas_limit())
182 .max_fee_per_gas(tx.max_fee_per_gas())
183 .max_priority_fee_per_gas(tx.max_priority_fee_per_gas().unwrap_or_default());
184 let tx_env = tx_req_to_env(tx_req);
185
186 match evm_transact(&mut evm, tx_env) {
189 Ok(_c) => {
190 trace!("Transaction {} committed successfully", idx);
191 }
192 Err(e) => {
193 error!("Transaction {} commit error: {}", idx, e);
194 match changing {
195 Some(changing_idx) => {
196 if (changing_idx == idx && idx == 0) || (changing_idx == idx - 1) {
197 tx_order.remove(changing_idx);
198 trace!("Removing Some {idx} {changing_idx}");
199 changing = None;
200 } else if idx < tx_order.len() && idx > 0 {
201 tx_order.swap(idx, idx - 1);
202 trace!("Swapping Some {idx} {changing_idx}");
203 changing = Some(idx - 1)
204 }
205 }
206 None => {
207 if idx > 0 {
208 trace!("Swapping None {idx}");
209 tx_order.swap(idx, idx - 1);
210 changing = Some(idx - 1)
211 } else {
212 trace!("Removing None {idx}");
213 tx_order.remove(0);
214 changing = None
215 }
216 }
217 }
218 ok = false;
219 break;
220 }
221 }
222 }
223
224 if ok {
225 debug!("Transaction sequence found {tx_order:?}");
226 let db = evm.ctx.db_ref().clone();
228 break Some(db);
229 }
230 };
231
232 if tx_order.len() < 2 {
233 return Err(eyre!("NOT_MERGED"));
234 }
235
236 if let Some(db) = rdb {
237 let _block_env = BlockEnv {
238 number: U256::from(request.tx_compose.next_block_number),
239 timestamp: U256::from(request.tx_compose.next_block_timestamp),
240 basefee: request.tx_compose.next_block_base_fee,
241 blob_excess_gas_and_price: Some(BlobExcessGasAndPrice { excess_blob_gas: 0, blob_gasprice: 0 }),
242 ..Default::default()
243 };
244
245 if let Swap::BackrunSwapLine(swap_line) = request.swap.clone() {
246 let first_token = swap_line.get_first_token().unwrap();
247 let _amount_in = first_token.calc_token_value_from_eth(U256::from(10).pow(U256::from(17))).unwrap();
248
249 match Ok::<(), eyre::Error>(()) {
251 Ok(_r) => {
252 let encode_request = MessageSwapCompose::<DB, NP>::prepare(SwapComposeData {
253 tx_compose: TxComposeData {
254 stuffing_txs_hashes: tx_order.iter().map(|i| *stuffing_states[*i].0.tx_hash()).collect(),
255 stuffing_txs: tx_order.iter().map(|i| stuffing_states[*i].0.clone()).collect(),
256 ..request.tx_compose
257 },
258 swap: Swap::BackrunSwapLine(swap_line.clone()),
259 origin: Some("samepath_merger".to_string()),
260 tips_pct: None,
261 poststate: Some(db),
262 poststate_update: None,
263 ..request
264 });
265
266 if let Err(e) = swap_request_tx.send(encode_request) {
267 error!("{}", e)
268 }
269 info!("+++ Calculation finished {swap_line}");
270 }
271 Err(e) => {
272 error!("optimization error : {e:?}")
273 }
274 }
275 }
276 }
277
278 trace!("same_path_merger_task stuffing_states len {}", stuffing_states.len());
279
280 Ok(())
281}
282
283async fn same_path_merger_worker<
284 N: Network<TransactionRequest = TransactionRequest>,
285 P: Provider<N> + DebugProviderExt<N> + Send + Sync + Clone + 'static,
286 DB: DatabaseRef<Error = KabuDBError> + Database<Error = KabuDBError> + DatabaseCommit + Send + Sync + Clone + 'static,
287>(
288 client: P,
289 market_state: Arc<RwLock<MarketState<DB>>>,
290 mut market_events_rx: broadcast::Receiver<MarketEvents>,
291 mut compose_channel_rx: broadcast::Receiver<MessageSwapCompose<DB>>,
292 compose_channel_tx: broadcast::Sender<MessageSwapCompose<DB>>,
293) {
294 let mut swap_paths: HashMap<TxHash, Vec<SwapComposeData<DB>>> = HashMap::new();
295
296 let prestate = Arc::new(RwLock::new(DataFetcher::<TxHash, GethStateUpdate>::new()));
297
298 let mut cur_next_base_fee: u64 = 0;
301 let mut cur_block_number: Option<alloy_primitives::BlockNumber> = None;
302 let mut cur_block_time: Option<u64> = None;
303 let mut cur_state_override: StateOverride = StateOverride::default();
304
305 loop {
306 tokio::select! {
307 msg = market_events_rx.recv() => {
308 if let Ok(msg) = msg {
309 let market_event_msg : MarketEvents = msg;
310 if let MarketEvents::BlockHeaderUpdate{block_number, block_hash, base_fee, next_base_fee, timestamp} = market_event_msg {
311 debug!("Block header update {} {} base_fee {} ", block_number, block_hash, base_fee);
312 cur_block_number = Some( block_number + 1);
313 cur_block_time = Some(timestamp + 12 );
314 cur_next_base_fee = next_base_fee;
315 *prestate.write().await = DataFetcher::<TxHash, GethStateUpdate>::new();
317 swap_paths = HashMap::new();
318
319 let new_block_hash = block_hash;
320
321 for _counter in 0..5 {
322 if let Ok(MarketEvents::BlockStateUpdate{block_hash}) = market_events_rx.recv().await
323 && new_block_hash == block_hash {
324 cur_state_override = StateOverride::default();
327 debug!("Block state update received {} {}", block_number, block_hash);
328 break;
329 }
330 }
331 }
332 }
333 }
334
335
336 msg = compose_channel_rx.recv() => {
337 let msg : Result<MessageSwapCompose<DB>, RecvError> = msg;
338 match msg {
339 Ok(compose_request)=>{
340 if let SwapComposeMessage::Ready(sign_request) = compose_request.inner()
341 && sign_request.tx_compose.stuffing_txs_hashes.len() == 1
342 && let Swap::BackrunSwapLine( _swap_line ) = &sign_request.swap {
343 let stuffing_tx_hash = sign_request.first_stuffing_hash();
344
345 let requests_vec = get_merge_list(sign_request, &swap_paths);
346 if !requests_vec.is_empty() {
347
348 let mut stuffing_txs = vec![sign_request.tx_compose.stuffing_txs[0].clone()];
349 stuffing_txs.extend( requests_vec.iter().map(|r| r.tx_compose.stuffing_txs[0].clone() ).collect::<Vec<_>>());
350 let client_clone = client.clone();
351 let prestate_clone = prestate.clone();
352
353 let call_opts : GethDebugTracingCallOptions = GethDebugTracingCallOptions{
354 block_overrides : Some(BlockOverrides {
355 number : Some( U256::from(cur_block_number.unwrap_or_default())),
356 time : Some(cur_block_time.unwrap_or_default()),
357 coinbase : Some(*COINBASE),
358 base_fee : Some(U256::from(cur_next_base_fee)),
359 ..Default::default()
360 }),
361 state_overrides : Some(cur_state_override.clone()),
362 ..TRACING_CALL_OPTS.clone()
363 };
364
365 tokio::task::spawn(
366 same_path_merger_task(
367 client_clone,
368 stuffing_txs,
369 prestate_clone,
370 market_state.clone(),
371 call_opts,
372 sign_request.clone(),
373 compose_channel_tx.clone()
374 )
375 );
376 }
377 let e = swap_paths.entry(stuffing_tx_hash).or_default();
378 e.push( sign_request.clone() );
379 }
380 },
381 Err(e)=>{
382 error!("{e}")
383 }
384 }
385 }
386 }
387 }
388}
389
390#[derive(Clone)]
391pub struct SamePathMergerComponent<P, N, DB: Send + Sync + Clone + 'static> {
392 client: P,
393 market_state: Option<Arc<RwLock<MarketState<DB>>>>,
395 market_events: Option<broadcast::Sender<MarketEvents>>,
396 compose_channel_rx: Option<broadcast::Sender<MessageSwapCompose<DB>>>,
397 compose_channel_tx: Option<broadcast::Sender<MessageSwapCompose<DB>>>,
398 _n: PhantomData<N>,
399}
400
401impl<P, N, DB> SamePathMergerComponent<P, N, DB>
402where
403 N: Network,
404 P: Provider<N> + DebugProviderExt<N> + Send + Sync + Clone + 'static,
405 DB: DatabaseRef<Error = KabuDBError> + DatabaseRef<Error = KabuDBError> + DatabaseCommit + Send + Sync + Clone + 'static,
406{
407 pub fn new(client: P) -> Self {
408 Self { client, market_state: None, market_events: None, compose_channel_rx: None, compose_channel_tx: None, _n: PhantomData }
409 }
410
411 pub fn with_market_state(self, market_state: Arc<RwLock<MarketState<DB>>>) -> Self {
412 Self { market_state: Some(market_state), ..self }
413 }
414
415 pub fn with_market_events_channel(self, market_events: broadcast::Sender<MarketEvents>) -> Self {
416 Self { market_events: Some(market_events), ..self }
417 }
418
419 pub fn with_compose_channel(self, compose_channel: broadcast::Sender<MessageSwapCompose<DB>>) -> Self {
420 Self { compose_channel_rx: Some(compose_channel.clone()), compose_channel_tx: Some(compose_channel), ..self }
421 }
422}
423
424impl<P, N, DB> Component for SamePathMergerComponent<P, N, DB>
425where
426 N: Network<TransactionRequest = TransactionRequest>,
427 P: Provider<N> + DebugProviderExt<N> + Send + Sync + Clone + 'static,
428 DB: DatabaseRef<Error = KabuDBError> + Database<Error = KabuDBError> + DatabaseCommit + Send + Sync + Clone + 'static,
429{
430 fn spawn(self, executor: TaskExecutor) -> Result<()> {
431 let name = self.name();
432
433 let market_events_rx = self.market_events.ok_or_else(|| eyre!("market_events not set"))?.subscribe();
434 let compose_channel_rx = self.compose_channel_rx.ok_or_else(|| eyre!("compose_channel_rx not set"))?.subscribe();
435 let compose_channel_tx = self.compose_channel_tx.ok_or_else(|| eyre!("compose_channel_tx not set"))?;
436 let market_state = self.market_state.ok_or_else(|| eyre!("market_state not set"))?;
437
438 executor.spawn_critical(
439 name,
440 same_path_merger_worker(self.client.clone(), market_state, market_events_rx, compose_channel_rx, compose_channel_tx),
441 );
442
443 Ok(())
444 }
445
446 fn name(&self) -> &'static str {
447 "SamePathMergerComponent"
448 }
449}