@@ -13,7 +13,7 @@ use crate::{
13
13
OpEthApiError , SequencerClient ,
14
14
} ;
15
15
use alloy_consensus:: BlockHeader ;
16
- use alloy_primitives:: U256 ;
16
+ use alloy_primitives:: { B256 , U256 } ;
17
17
use eyre:: WrapErr ;
18
18
use op_alloy_network:: Optimism ;
19
19
pub use receipt:: { OpReceiptBuilder , OpReceiptFieldsBuilder } ;
@@ -23,8 +23,8 @@ use reth_evm::ConfigureEvm;
23
23
use reth_node_api:: { FullNodeComponents , FullNodeTypes , HeaderTy , NodeTypes } ;
24
24
use reth_node_builder:: rpc:: { EthApiBuilder , EthApiCtx } ;
25
25
use reth_optimism_flashblocks:: {
26
- ExecutionPayloadBaseV1 , FlashBlockCompleteSequenceRx , FlashBlockService , PendingBlockRx ,
27
- WsFlashBlockStream ,
26
+ ExecutionPayloadBaseV1 , FlashBlockBuildInfo , FlashBlockCompleteSequenceRx , FlashBlockService ,
27
+ InProgressFlashBlockRx , PendingBlockRx , PendingFlashBlock , WsFlashBlockStream ,
28
28
} ;
29
29
use reth_rpc:: eth:: { core:: EthApiInner , DevSigner } ;
30
30
use reth_rpc_eth_api:: {
@@ -43,10 +43,18 @@ use reth_tasks::{
43
43
pool:: { BlockingTaskGuard , BlockingTaskPool } ,
44
44
TaskSpawner ,
45
45
} ;
46
- use std:: { fmt, fmt:: Formatter , marker:: PhantomData , sync:: Arc , time:: Instant } ;
47
- use tokio:: sync:: watch;
46
+ use std:: {
47
+ fmt:: { self , Formatter } ,
48
+ marker:: PhantomData ,
49
+ sync:: Arc ,
50
+ time:: Duration ,
51
+ } ;
52
+ use tokio:: { sync:: watch, time} ;
48
53
use tracing:: info;
49
54
55
+ /// Maximum duration to wait for a fresh flashblock when one is being built.
56
+ const MAX_FLASHBLOCK_WAIT_DURATION : Duration = Duration :: from_millis ( 50 ) ;
57
+
50
58
/// Adapter for [`EthApiInner`], which holds all the data required to serve core `eth_` API.
51
59
pub type EthApiNodeBackend < N , Rpc > = EthApiInner < N , Rpc > ;
52
60
@@ -79,13 +87,15 @@ impl<N: RpcNodeCore, Rpc: RpcConvert> OpEthApi<N, Rpc> {
79
87
min_suggested_priority_fee : U256 ,
80
88
pending_block_rx : Option < PendingBlockRx < N :: Primitives > > ,
81
89
flashblock_rx : Option < FlashBlockCompleteSequenceRx > ,
90
+ in_progress_rx : Option < InProgressFlashBlockRx > ,
82
91
) -> Self {
83
92
let inner = Arc :: new ( OpEthApiInner {
84
93
eth_api,
85
94
sequencer_client,
86
95
min_suggested_priority_fee,
87
96
pending_block_rx,
88
97
flashblock_rx,
98
+ in_progress_rx,
89
99
} ) ;
90
100
Self { inner }
91
101
}
@@ -109,15 +119,57 @@ impl<N: RpcNodeCore, Rpc: RpcConvert> OpEthApi<N, Rpc> {
109
119
self . inner . flashblock_rx . as_ref ( ) . map ( |rx| rx. resubscribe ( ) )
110
120
}
111
121
122
+ /// Returns information about the flashblock currently being built, if any.
123
+ fn flashblock_build_info ( & self ) -> Option < FlashBlockBuildInfo > {
124
+ self . inner . in_progress_rx . as_ref ( ) . and_then ( |rx| * rx. borrow ( ) )
125
+ }
126
+
127
+ /// Extracts pending block if it matches the expected parent hash.
128
+ fn extract_matching_block (
129
+ & self ,
130
+ block : Option < & PendingFlashBlock < N :: Primitives > > ,
131
+ parent_hash : B256 ,
132
+ ) -> Option < PendingBlock < N :: Primitives > > {
133
+ block. filter ( |b| b. block ( ) . parent_hash ( ) == parent_hash) . map ( |b| b. pending . clone ( ) )
134
+ }
135
+
112
136
/// Build a [`OpEthApi`] using [`OpEthApiBuilder`].
113
137
pub const fn builder ( ) -> OpEthApiBuilder < Rpc > {
114
138
OpEthApiBuilder :: new ( )
115
139
}
116
140
141
+ /// Awaits a fresh flashblock if one is being built, otherwise returns current.
142
+ async fn flashblock (
143
+ & self ,
144
+ parent_hash : B256 ,
145
+ ) -> eyre:: Result < Option < PendingBlock < N :: Primitives > > > {
146
+ let Some ( rx) = self . inner . pending_block_rx . as_ref ( ) else { return Ok ( None ) } ;
147
+
148
+ // Check if a flashblock is being built
149
+ if let Some ( build_info) = self . flashblock_build_info ( ) {
150
+ let current_index = rx. borrow ( ) . as_ref ( ) . map ( |b| b. last_flashblock_index ) ;
151
+
152
+ // Check if this is the first flashblock or the next consecutive index
153
+ let is_next_index = current_index. is_none_or ( |idx| build_info. index == idx + 1 ) ;
154
+
155
+ // Wait only for relevant flashblocks: matching parent and next in sequence
156
+ if build_info. parent_hash == parent_hash && is_next_index {
157
+ let mut rx_clone = rx. clone ( ) ;
158
+ // Wait up to MAX_FLASHBLOCK_WAIT_DURATION for a new flashblock to arrive
159
+ let _ = time:: timeout ( MAX_FLASHBLOCK_WAIT_DURATION , rx_clone. changed ( ) ) . await ;
160
+ }
161
+ }
162
+
163
+ // Fall back to current block
164
+ Ok ( self . extract_matching_block ( rx. borrow ( ) . as_ref ( ) , parent_hash) )
165
+ }
166
+
117
167
/// Returns a [`PendingBlock`] that is built out of flashblocks.
118
168
///
119
169
/// If flashblocks receiver is not set, then it always returns `None`.
120
- pub fn pending_flashblock ( & self ) -> eyre:: Result < Option < PendingBlock < N :: Primitives > > >
170
+ ///
171
+ /// It may wait up to 50ms for a fresh flashblock if one is currently being built.
172
+ pub async fn pending_flashblock ( & self ) -> eyre:: Result < Option < PendingBlock < N :: Primitives > > >
121
173
where
122
174
OpEthApiError : FromEvmError < N :: Evm > ,
123
175
Rpc : RpcConvert < Primitives = N :: Primitives > ,
@@ -128,21 +180,7 @@ impl<N: RpcNodeCore, Rpc: RpcConvert> OpEthApi<N, Rpc> {
128
180
PendingBlockEnvOrigin :: DerivedFromLatest ( parent) => parent,
129
181
} ;
130
182
131
- let Some ( rx) = self . inner . pending_block_rx . as_ref ( ) else { return Ok ( None ) } ;
132
- let pending_block = rx. borrow ( ) ;
133
- let Some ( pending_block) = pending_block. as_ref ( ) else { return Ok ( None ) } ;
134
-
135
- let now = Instant :: now ( ) ;
136
-
137
- // Is the pending block not expired and latest is its parent?
138
- if pending. evm_env . block_env . number == U256 :: from ( pending_block. block ( ) . number ( ) ) &&
139
- parent. hash ( ) == pending_block. block ( ) . parent_hash ( ) &&
140
- now <= pending_block. expires_at
141
- {
142
- return Ok ( Some ( pending_block. pending . clone ( ) ) ) ;
143
- }
144
-
145
- Ok ( None )
183
+ self . flashblock ( parent. hash ( ) ) . await
146
184
}
147
185
}
148
186
@@ -330,6 +368,8 @@ pub struct OpEthApiInner<N: RpcNodeCore, Rpc: RpcConvert> {
330
368
///
331
369
/// If set, then it provides sequences of flashblock built.
332
370
flashblock_rx : Option < FlashBlockCompleteSequenceRx > ,
371
+ /// Receiver that signals when a flashblock is being built
372
+ in_progress_rx : Option < InProgressFlashBlockRx > ,
333
373
}
334
374
335
375
impl < N : RpcNodeCore , Rpc : RpcConvert > fmt:: Debug for OpEthApiInner < N , Rpc > {
@@ -465,24 +505,28 @@ where
465
505
None
466
506
} ;
467
507
468
- let rxs = if let Some ( ws_url) = flashblocks_url {
469
- info ! ( target: "reth:cli" , %ws_url, "Launching flashblocks service" ) ;
470
- let ( tx, pending_block_rx) = watch:: channel ( None ) ;
471
- let stream = WsFlashBlockStream :: new ( ws_url) ;
472
- let service = FlashBlockService :: new (
473
- stream,
474
- ctx. components . evm_config ( ) . clone ( ) ,
475
- ctx. components . provider ( ) . clone ( ) ,
476
- ctx. components . task_executor ( ) . clone ( ) ,
477
- ) ;
478
- let flashblock_rx = service. subscribe_block_sequence ( ) ;
479
- ctx. components . task_executor ( ) . spawn ( Box :: pin ( service. run ( tx) ) ) ;
480
- Some ( ( pending_block_rx, flashblock_rx) )
481
- } else {
482
- None
483
- } ;
508
+ let ( pending_block_rx, flashblock_rx, in_progress_rx) =
509
+ if let Some ( ws_url) = flashblocks_url {
510
+ info ! ( target: "reth:cli" , %ws_url, "Launching flashblocks service" ) ;
511
+
512
+ let ( tx, pending_rx) = watch:: channel ( None ) ;
513
+ let stream = WsFlashBlockStream :: new ( ws_url) ;
514
+ let service = FlashBlockService :: new (
515
+ stream,
516
+ ctx. components . evm_config ( ) . clone ( ) ,
517
+ ctx. components . provider ( ) . clone ( ) ,
518
+ ctx. components . task_executor ( ) . clone ( ) ,
519
+ ) ;
520
+
521
+ let flashblock_rx = service. subscribe_block_sequence ( ) ;
522
+ let in_progress_rx = service. subscribe_in_progress ( ) ;
523
+
524
+ ctx. components . task_executor ( ) . spawn ( Box :: pin ( service. run ( tx) ) ) ;
484
525
485
- let ( pending_block_rx, flashblock_rx) = rxs. unzip ( ) ;
526
+ ( Some ( pending_rx) , Some ( flashblock_rx) , Some ( in_progress_rx) )
527
+ } else {
528
+ ( None , None , None )
529
+ } ;
486
530
487
531
let eth_api = ctx. eth_api_builder ( ) . with_rpc_converter ( rpc_converter) . build_inner ( ) ;
488
532
@@ -492,6 +536,7 @@ where
492
536
U256 :: from ( min_suggested_priority_fee) ,
493
537
pending_block_rx,
494
538
flashblock_rx,
539
+ in_progress_rx,
495
540
) )
496
541
}
497
542
}
0 commit comments