kabu_strategy_backrun/
pending_tx_state_change_processor.rs

1use alloy_consensus::Transaction as _;
2use alloy_consensus::transaction::TxHashRef;
3use alloy_eips::BlockNumberOrTag;
4use alloy_evm::EvmEnv;
5use alloy_network::Network;
6use alloy_primitives::{Address, BlockNumber, TxHash, U256};
7use alloy_provider::Provider;
8use alloy_rpc_types::state::StateOverride;
9use alloy_rpc_types::{BlockOverrides, TransactionInput, TransactionRequest};
10use alloy_rpc_types_trace::geth::GethDebugTracingCallOptions;
11use eyre::{Result, eyre};
12use kabu_core_components::Component;
13use kabu_evm_db::KabuDBError;
14use lazy_static::lazy_static;
15use revm::context::{BlockEnv, CfgEnv};
16use revm::context_interface::block::BlobExcessGasAndPrice;
17use revm::{Database, DatabaseCommit, DatabaseRef};
18use std::collections::HashMap;
19use std::collections::hash_map::Entry;
20use std::marker::PhantomData;
21use std::sync::Arc;
22use tokio::sync::{RwLock, broadcast};
23use tracing::{debug, error, warn};
24
25use kabu_node_debug_provider::DebugProviderExt;
26use kabu_types_blockchain::{GethStateUpdateVec, Mempool, TRACING_CALL_OPTS, debug_trace_call_diff};
27use kabu_types_events::{MarketEvents, MempoolEvents, StateUpdateEvent};
28use kabu_types_market::{Market, MarketState};
29use kabu_types_market::{accounts_vec_len, storage_vec_len};
30use reth_node_types::NodePrimitives;
31use reth_primitives_traits::SignerRecoverable;
32use reth_tasks::TaskExecutor;
33
34use super::affected_pools_code::{get_affected_pools_from_code, is_pool_code};
35use super::affected_pools_state::get_affected_pools_from_state_update;
36
37lazy_static! {
38    static ref COINBASE: Address = "0x1f9090aaE28b8a3dCeaDf281B0F12828e676c326".parse().unwrap();
39}
40
41/// Process a pending tx from the mempool
42#[allow(clippy::too_many_arguments)]
43pub async fn pending_tx_state_change_task<P, N, DB, LDT>(
44    client: P,
45    tx_hash: TxHash,
46    market: Arc<RwLock<Market>>,
47    mempool: Arc<RwLock<Mempool<LDT>>>,
48    market_state: Arc<RwLock<MarketState<DB>>>,
49    affecting_tx: Arc<RwLock<HashMap<TxHash, bool>>>,
50    cur_block_number: BlockNumber,
51    cur_block_time: u64,
52    cur_next_base_fee: u64,
53    cur_state_override: StateOverride,
54    state_updates_broadcaster: broadcast::Sender<StateUpdateEvent<DB, LDT>>,
55) -> Result<()>
56where
57    N: Network<TransactionRequest = alloy_rpc_types::TransactionRequest>,
58    P: Provider<N> + DebugProviderExt<N> + Send + Sync + Clone + 'static,
59    DB: DatabaseRef + Database + DatabaseCommit + Clone + Send + Sync + 'static,
60    LDT: NodePrimitives + 'static,
61{
62    let mut state_update_vec: GethStateUpdateVec = Vec::new();
63    let mut state_required_vec: GethStateUpdateVec = Vec::new();
64
65    let mut merged_state_update_vec: GethStateUpdateVec = Vec::new();
66
67    let mempool_tx = match mempool.read().await.get_tx_by_hash(&tx_hash).cloned() {
68        Some(tx) => tx,
69        None => return Err(eyre!("MEMPOOL_TX_NOT_FOUND")),
70    };
71
72    let tx = match mempool_tx.tx.clone() {
73        Some(tx) => tx,
74        None => return Err(eyre!("NO_TX_IN_MEMPOOL")),
75    };
76
77    let source = mempool_tx.source.clone();
78
79    // Convert transaction to TransactionRequest
80    // Extract the signer using recover_signer() for the from address
81    let from = tx.recover_signer().map_err(|e| eyre!("Failed to recover signer: {}", e))?;
82    let transaction_request = TransactionRequest::default()
83        .from(from)
84        .to(tx.to().unwrap_or_default())
85        .value(tx.value())
86        .input(TransactionInput::new(tx.input().clone()))
87        .nonce(tx.nonce())
88        .gas_limit(tx.gas_limit())
89        .max_fee_per_gas(tx.max_fee_per_gas())
90        .max_priority_fee_per_gas(tx.max_priority_fee_per_gas().unwrap_or_default());
91
92    // let transaction_type = transaction_request.transaction_type.unwrap_or_default();
93    // if transaction_type == LEGACY_TX_TYPE_ID || transaction_type == EIP2930_TX_TYPE_ID {
94    //     match transaction_request.gas_price {
95    //         Some(g) => {
96    //             if g < cur_next_base_fee as u128 {
97    //                 transaction_request.set_gas_price(cur_next_base_fee as u128);
98    //             }
99    //         }
100    //         None => {
101    //             error!(
102    //                 "No gas price for gas_price={:?}, max_fee_per_gas={:?}, max_priority_fee_per_gas={:?}, hash={:?}",
103    //                 transaction_request.gas_price,
104    //                 transaction_request.max_fee_per_gas,
105    //                 transaction_request.max_priority_fee_per_gas,
106    //                 mempool_tx.tx_hash
107    //             );
108    //             return Err(eyre!("NO_GAS_PRICE"));
109    //         }
110    //     }
111    // } else if transaction_type == EIP1559_TX_TYPE_ID {
112    //     match transaction_request.max_fee_per_gas {
113    //         Some(g) => {
114    //             if g < cur_next_base_fee as u128 {
115    //                 transaction_request.set_max_fee_per_gas(cur_next_base_fee as u128);
116    //             }
117    //         }
118    //         None => {
119    //             error!(
120    //                 "No base fee for gas_price={:?}, max_fee_per_gas={:?}, max_priority_fee_per_gas={:?}, hash={:?}",
121    //                 transaction_request.gas_price,
122    //                 transaction_request.max_fee_per_gas,
123    //                 transaction_request.max_priority_fee_per_gas,
124    //                 mempool_tx.tx_hash
125    //             );
126    //             return Err(eyre!("NO_BASE_FEE"));
127    //         }
128    //     }
129    // } else if transaction_type == EIP4844_TX_TYPE_ID {
130    //     // ignore blob tx
131    //     debug!("Ignore EIP4844 transaction: hash={:?}", mempool_tx.tx_hash);
132    //     return Ok(());
133    // } else {
134    //     warn!("Unknown transaction type: type={}, hash={:?}", transaction_type, mempool_tx.tx_hash);
135    //     return Err(eyre!("UNKNOWN_TX_TYPE"));
136    // }
137
138    let call_opts: GethDebugTracingCallOptions = GethDebugTracingCallOptions {
139        block_overrides: Some(BlockOverrides {
140            number: Some(U256::from(cur_block_number)),
141            time: Some(cur_block_time),
142            coinbase: Some(*COINBASE),
143            base_fee: Some(U256::from(cur_next_base_fee)),
144            ..Default::default()
145        }),
146        state_overrides: Some(cur_state_override.clone()),
147        ..TRACING_CALL_OPTS.clone()
148    };
149
150    if !(*affecting_tx.read().await.get(&tx_hash).unwrap_or(&true)) {
151        return Err(eyre!("NON_AFFECTING_TX"));
152    }
153
154    let diff_trace_result =
155        debug_trace_call_diff(client.clone(), transaction_request, BlockNumberOrTag::Latest.into(), Some(call_opts)).await;
156    match diff_trace_result {
157        Ok((pre, post)) => {
158            state_required_vec.push(pre.clone());
159            state_update_vec.push(post.clone());
160
161            merged_state_update_vec.push(pre);
162            merged_state_update_vec.push(post);
163        }
164        Err(error) => {
165            let tx_hash = *tx.tx_hash();
166            mempool.write().await.set_failed(tx_hash);
167            debug!(block=cur_block_number, %tx_hash, %error, "debug_trace_call error for");
168        }
169    }
170
171    let affected_pools = get_affected_pools_from_state_update(market.clone(), &state_update_vec).await;
172
173    let accounts_len = accounts_vec_len(&state_update_vec);
174    let storage_len = storage_vec_len(&state_update_vec);
175
176    debug!(%tx_hash, %source, pools = affected_pools.len(), accounts = accounts_len, storage = storage_len, "Mempool affected pools");
177
178    affecting_tx.write().await.insert(tx_hash, !affected_pools.is_empty());
179
180    // Use the passed in block number and time (already incremented in worker)
181    let next_block_number = cur_block_number;
182    let next_block_timestamp = cur_block_time;
183
184    if !affected_pools.is_empty() {
185        let cur_state_db = market_state.read().await.state_db.clone();
186        let request = StateUpdateEvent::new(
187            next_block_number,
188            next_block_timestamp,
189            cur_next_base_fee,
190            cur_state_db,
191            state_update_vec,
192            Some(state_required_vec.clone()),
193            affected_pools,
194            vec![tx_hash],
195            vec![mempool_tx.tx.clone().unwrap()],
196            "pending_tx_searcher".to_string(),
197            9000,
198        );
199        if let Err(e) = state_updates_broadcaster.send(request) {
200            error!("state_updates_broadcaster : {}", e)
201        }
202    }
203
204    if is_pool_code(&merged_state_update_vec) {
205        let evm_env = EvmEnv {
206            cfg_env: CfgEnv::default(),
207            block_env: BlockEnv {
208                number: U256::from(next_block_number),
209                timestamp: U256::from(next_block_timestamp),
210                basefee: cur_next_base_fee,
211                blob_excess_gas_and_price: Some(BlobExcessGasAndPrice { excess_blob_gas: 0, blob_gasprice: 0 }),
212                ..Default::default()
213            },
214        };
215        match get_affected_pools_from_code(client, market.clone(), &merged_state_update_vec, &evm_env).await {
216            Ok(affected_pools) => {
217                match affecting_tx.write().await.entry(tx_hash) {
218                    Entry::Occupied(mut v) => {
219                        if !v.get() {
220                            v.insert(!affected_pools.is_empty());
221                        }
222                    }
223                    Entry::Vacant(v) => {
224                        v.insert(!affected_pools.is_empty());
225                    }
226                };
227
228                debug!("Mempool code pools {} {} update len : {}", tx_hash, source, affected_pools.len());
229
230                {
231                    // Use the passed in block number and time (already incremented in worker)
232                    let block_number = cur_block_number;
233                    let block_timestamp = cur_block_time;
234
235                    if !affected_pools.is_empty() {
236                        let cur_state_db = market_state.read().await.state_db.clone();
237
238                        let request = StateUpdateEvent::new(
239                            block_number,
240                            block_timestamp,
241                            cur_next_base_fee,
242                            cur_state_db,
243                            merged_state_update_vec,
244                            None,
245                            affected_pools,
246                            vec![tx_hash],
247                            vec![mempool_tx.tx.unwrap()],
248                            "poolcode_searcher".to_string(),
249                            3000,
250                        );
251                        if let Err(e) = state_updates_broadcaster.send(request) {
252                            error!("state_updates_broadcaster : {}", e)
253                        }
254                    }
255                }
256            }
257            Err(e) => {
258                debug!("code affected pools error : {e}")
259            }
260        }
261    }
262    Ok(())
263}
264
265#[allow(clippy::too_many_arguments)]
266pub async fn pending_tx_state_change_worker<P, N, DB, LDT>(
267    client: P,
268    market: Arc<RwLock<Market>>,
269    mempool: Arc<RwLock<Mempool<LDT>>>,
270    market_state: Arc<RwLock<MarketState<DB>>>,
271    mempool_events_rx: broadcast::Sender<MempoolEvents>,
272    market_events_rx: broadcast::Sender<MarketEvents>,
273    state_updates_broadcaster: broadcast::Sender<StateUpdateEvent<DB, LDT>>,
274) -> Result<()>
275where
276    N: Network<TransactionRequest = alloy_rpc_types::TransactionRequest>,
277    P: Provider<N> + DebugProviderExt<N> + Send + Sync + Clone + 'static,
278    DB: DatabaseRef + Database + DatabaseCommit + Clone + Send + Sync + 'static,
279    LDT: NodePrimitives + 'static,
280{
281    let mut mempool_events_receiver = mempool_events_rx.subscribe();
282    let mut market_events_receiver = market_events_rx.subscribe();
283
284    let affecting_tx: Arc<RwLock<HashMap<TxHash, bool>>> = Arc::new(RwLock::new(HashMap::new()));
285    let mut cur_next_base_fee = 0;
286    let mut cur_block_number: Option<BlockNumber> = None;
287    let mut cur_block_time: Option<u64> = None;
288    let mut cur_state_override: StateOverride = StateOverride::default();
289
290    loop {
291        tokio::select! {
292            msg = market_events_receiver.recv() => {
293                if let Ok(msg) = msg {
294                    let market_event_msg : MarketEvents = msg;
295                    if let MarketEvents::BlockHeaderUpdate{ block_number, block_hash, timestamp, base_fee, next_base_fee } = market_event_msg {
296                        debug!("Block header update {} {} base_fee {} ", block_number, block_hash, base_fee);
297                        cur_block_number = Some( block_number + 1);
298                        cur_block_time = Some(timestamp + 12 );
299                        cur_next_base_fee = next_base_fee;
300
301                        for _counter in 0..5  {
302                            if let Ok(msg) = market_events_receiver.recv().await
303                                && matches!(msg, MarketEvents::BlockStateUpdate{..} ) {
304                                    // TODO: Get state override from elsewhere if needed
305                                    cur_state_override = StateOverride::default();
306                                    debug!("Block state update received {} {}", block_number, block_hash);
307                                    break;
308                            }
309                        }
310                    }
311                }
312            }
313            msg = mempool_events_receiver.recv() => {
314                if let Ok(msg) = msg {
315                    let mempool_event_msg : MempoolEvents = msg;
316                    if let MempoolEvents::MempoolActualTxUpdate{ tx_hash }  = mempool_event_msg {
317                        if cur_block_number.is_none() {
318                            warn!("Did not received block header update yet!");
319                            continue;
320                        }
321
322                        tokio::task::spawn(
323                            pending_tx_state_change_task(
324                                client.clone(),
325                                tx_hash,
326                                market.clone(),
327                                mempool.clone(),
328                                market_state.clone(),
329                                affecting_tx.clone(),
330                                cur_block_number.unwrap_or_default(),
331                                cur_block_time.unwrap_or_default(),
332                                cur_next_base_fee,
333                                cur_state_override.clone(),
334                                state_updates_broadcaster.clone(),
335                            )
336                        );
337                    }
338                }
339            }
340        }
341    }
342}
343
344pub struct PendingTxStateChangeProcessorComponent<P, N, DB: Clone + Send + Sync + 'static, LDT: NodePrimitives + 'static> {
345    client: P,
346
347    market: Option<Arc<RwLock<Market>>>,
348
349    mempool: Option<Arc<RwLock<Mempool<LDT>>>>,
350
351    market_state: Option<Arc<RwLock<MarketState<DB>>>>,
352
353    market_events_rx: Option<broadcast::Sender<MarketEvents>>,
354
355    mempool_events_rx: Option<broadcast::Sender<MempoolEvents>>,
356
357    state_updates_tx: Option<broadcast::Sender<StateUpdateEvent<DB, LDT>>>,
358    _n: PhantomData<N>,
359}
360
361impl<P, N, DB, LDT> PendingTxStateChangeProcessorComponent<P, N, DB, LDT>
362where
363    N: Network,
364    P: Provider<N> + DebugProviderExt<N> + Send + Sync + Clone + 'static,
365    DB: DatabaseRef + Send + Sync + Clone + 'static,
366    LDT: NodePrimitives + 'static,
367{
368    pub fn new(client: P) -> PendingTxStateChangeProcessorComponent<P, N, DB, LDT> {
369        PendingTxStateChangeProcessorComponent {
370            client,
371            market: None,
372            mempool: None,
373            market_state: None,
374            market_events_rx: None,
375            mempool_events_rx: None,
376            state_updates_tx: None,
377            _n: PhantomData,
378        }
379    }
380
381    pub fn with_channels(
382        self,
383        market_events_rx: broadcast::Sender<MarketEvents>,
384        mempool_events_rx: broadcast::Sender<MempoolEvents>,
385        state_updates_tx: broadcast::Sender<StateUpdateEvent<DB, LDT>>,
386    ) -> Self {
387        Self {
388            market_events_rx: Some(market_events_rx),
389            mempool_events_rx: Some(mempool_events_rx),
390            state_updates_tx: Some(state_updates_tx),
391            ..self
392        }
393    }
394
395    pub fn with_market(self, market: Arc<RwLock<Market>>) -> Self {
396        Self { market: Some(market), ..self }
397    }
398
399    pub fn with_mempool(self, mempool: Option<Arc<RwLock<Mempool<LDT>>>>) -> Self {
400        Self { mempool, ..self }
401    }
402
403    pub fn with_market_state(self, market_state: Arc<RwLock<MarketState<DB>>>) -> Self {
404        Self { market_state: Some(market_state), ..self }
405    }
406}
407
408impl<P, N, DB, LDT> Component for PendingTxStateChangeProcessorComponent<P, N, DB, LDT>
409where
410    N: Network<TransactionRequest = alloy_rpc_types::TransactionRequest>,
411    P: Provider<N> + DebugProviderExt<N> + Send + Sync + Clone + 'static,
412    DB: DatabaseRef<Error = KabuDBError> + Database<Error = KabuDBError> + DatabaseCommit + Send + Sync + Clone + Default + 'static,
413    LDT: NodePrimitives + 'static,
414{
415    fn spawn(self, executor: TaskExecutor) -> Result<()> {
416        let name = self.name();
417
418        let mempool_events_rx = self.mempool_events_rx.ok_or_else(|| eyre!("mempool_events_rx not set"))?;
419        let market_events_rx = self.market_events_rx.ok_or_else(|| eyre!("market_events_rx not set"))?;
420        let state_updates_tx = self.state_updates_tx.ok_or_else(|| eyre!("state_updates_tx not set"))?;
421        let market = self.market.ok_or_else(|| eyre!("market not set"))?;
422        let mempool = self.mempool.ok_or_else(|| eyre!("mempool not set"))?;
423        let market_state = self.market_state.ok_or_else(|| eyre!("market_state not set"))?;
424
425        executor.spawn_critical(name, async move {
426            if let Err(e) = pending_tx_state_change_worker(
427                self.client.clone(),
428                market,
429                mempool,
430                market_state,
431                mempool_events_rx,
432                market_events_rx,
433                state_updates_tx,
434            )
435            .await
436            {
437                error!("pending_tx_state_change_worker failed: {}", e);
438            }
439        });
440
441        Ok(())
442    }
443    fn name(&self) -> &'static str {
444        "PendingTxStateChangeProcessorComponent"
445    }
446}