kabu_strategy_merger/
samepath_merger_actor.rs

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 mut ret : Vec<&TxComposeData> = Vec::new();
46    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        // Manually construct TransactionRequest from signed tx
97        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            // set tx context for evm
164            let tx = &stuffing_states[*tx_idx].0;
165
166            // Manually construct TransactionRequest from signed tx
167            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            // TODO: EVM transact functionality is placeholder for now
187
188            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            // TODO: Consume DB properly
227            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            // TODO: Update optimize_with_in_amount to work with KabuEVMWrapper
250            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 affecting_tx: HashMap<TxHash, bool> = HashMap::new();
299    //let mut cur_base_fee: u128 = 0;
300    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                        //cur_base_fee = base_fee;
316                        *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                                    // State override now comes from the market state update itself
325                                    // TODO: Get state override from provider if needed
326                                    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    //encoder: SwapStepEncoder,
394    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}