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#[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 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 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 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 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 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}