Skip to content

Commit 238eed6

Browse files
committed
Test coverage for payload limits scaling and inheritance
1 parent 1852e6f commit 238eed6

File tree

6 files changed

+503
-32
lines changed

6 files changed

+503
-32
lines changed

bin/flashblocks/grafana/standard.json

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@
110110
},
111111
"gridPos": {
112112
"h": 4,
113-
"w": 2,
113+
"w": 3,
114114
"x": 0,
115115
"y": 1
116116
},
@@ -182,8 +182,8 @@
182182
},
183183
"gridPos": {
184184
"h": 4,
185-
"w": 3,
186-
"x": 2,
185+
"w": 4,
186+
"x": 3,
187187
"y": 1
188188
},
189189
"id": 37,
@@ -218,7 +218,7 @@
218218
"refId": "A"
219219
}
220220
],
221-
"title": "Payload Job Deadline",
221+
"title": "Payload Deadline (block time)",
222222
"type": "stat"
223223
},
224224
{
@@ -248,7 +248,7 @@
248248
"gridPos": {
249249
"h": 4,
250250
"w": 3,
251-
"x": 5,
251+
"x": 7,
252252
"y": 1
253253
},
254254
"id": 18,
@@ -316,8 +316,8 @@
316316
},
317317
"gridPos": {
318318
"h": 4,
319-
"w": 4,
320-
"x": 8,
319+
"w": 3,
320+
"x": 10,
321321
"y": 1
322322
},
323323
"id": 5,
@@ -352,7 +352,7 @@
352352
"refId": "A"
353353
}
354354
],
355-
"title": "Total Transactions Count",
355+
"title": "Transactions Built",
356356
"type": "stat"
357357
},
358358
{
@@ -382,8 +382,8 @@
382382
},
383383
"gridPos": {
384384
"h": 4,
385-
"w": 4,
386-
"x": 12,
385+
"w": 3,
386+
"x": 13,
387387
"y": 1
388388
},
389389
"id": 6,
@@ -621,7 +621,7 @@
621621
"refId": "A"
622622
}
623623
],
624-
"title": "Failed Payload Jobs",
624+
"title": "Failed Jobs",
625625
"type": "stat"
626626
},
627627
{
@@ -3216,7 +3216,6 @@
32163216
"timepicker": {},
32173217
"timezone": "browser",
32183218
"title": "Flashblock - Standard Builder",
3219-
"uid": "5211c89f-e5bc-4e58-8210-237af32b0d89",
3220-
"version": 4,
3219+
"version": 5,
32213220
"weekStart": ""
32223221
}

src/pipelines/mod.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,15 @@ impl<P: Platform> Pipeline<P> {
119119
}
120120

121121
/// Sets payload building limits for the pipeline.
122+
///
123+
/// Here we can either use an instance of `LimitsFactory` that generates
124+
/// limits dynamically according to a user-defined logic, or we can use a
125+
/// fixed `Limits` instance.
122126
#[must_use]
123-
pub fn with_limits<L: LimitsFactory<P>>(self, limits: L) -> Self {
127+
pub fn with_limits<T, L: IntoLimitsFactory<P, T>>(self, limits: L) -> Self {
124128
let mut this = self;
125-
this.limits = Some(Box::new(limits) as Box<dyn LimitsFactory<P>>);
129+
let factory = limits.into_limits_factory();
130+
this.limits = Some(Box::new(factory) as Box<dyn LimitsFactory<P>>);
126131
this
127132
}
128133
}

src/platform/limits.rs

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,274 @@ impl Limits {
9797
}
9898
}
9999
}
100+
101+
/// Convinience trait that allows API users to either used a factory type or a
102+
/// concrete limits value in [`Pipeline::with_limits`].
103+
pub trait IntoLimitsFactory<P: Platform, Marker = ()> {
104+
/// Convert the type into a limits factory.
105+
fn into_limits_factory(self) -> impl LimitsFactory<P>;
106+
}
107+
108+
impl<T: LimitsFactory<P>, P: Platform> IntoLimitsFactory<P, Variant<0>> for T {
109+
fn into_limits_factory(self) -> impl LimitsFactory<P> {
110+
self
111+
}
112+
}
113+
114+
impl<P: Platform> IntoLimitsFactory<P, Variant<1>> for Limits {
115+
fn into_limits_factory(self) -> impl LimitsFactory<P> {
116+
struct FixedLimits(Limits);
117+
impl<P: Platform> LimitsFactory<P> for FixedLimits {
118+
fn create(&self, _: &BlockContext<P>, _: Option<&Limits>) -> Limits {
119+
self.0.clone()
120+
}
121+
}
122+
123+
FixedLimits(self)
124+
}
125+
}
126+
127+
#[cfg(test)]
128+
mod tests {
129+
use {
130+
super::*,
131+
crate::test_utils::*,
132+
core::{
133+
num::NonZero,
134+
sync::atomic::{AtomicU32, Ordering},
135+
},
136+
std::sync::Arc,
137+
};
138+
139+
assert_is_dyn_safe!(LimitsFactory<P>, P: Platform);
140+
141+
#[derive(Debug)]
142+
struct TestStep {
143+
must_run: bool,
144+
iter: AtomicU32,
145+
break_after: AtomicU32,
146+
147+
minimum_iterations: Option<u32>,
148+
maximum_iterations: Option<u32>,
149+
expected_gas_limit: Option<u64>,
150+
expected_deadline: Option<Duration>,
151+
sleep_on_step: Option<Duration>,
152+
}
153+
154+
impl Default for TestStep {
155+
fn default() -> Self {
156+
Self {
157+
must_run: false,
158+
iter: AtomicU32::new(0),
159+
break_after: AtomicU32::new(1),
160+
expected_gas_limit: None,
161+
expected_deadline: None,
162+
minimum_iterations: None,
163+
maximum_iterations: None,
164+
sleep_on_step: None,
165+
}
166+
}
167+
}
168+
169+
impl TestStep {
170+
#[allow(dead_code)]
171+
pub fn break_after(self, iterations: u32) -> Self {
172+
self.break_after.store(iterations, Ordering::Relaxed);
173+
self
174+
}
175+
176+
pub fn expect_gas_limit(mut self, gas_limit: u64) -> Self {
177+
self.expected_gas_limit = Some(gas_limit);
178+
self
179+
}
180+
181+
pub fn expect_deadline(mut self, deadline: Duration) -> Self {
182+
self.expected_deadline = Some(deadline);
183+
self
184+
}
185+
186+
pub fn expect_minimum_iterations(mut self, iterations: u32) -> Self {
187+
self.minimum_iterations = Some(iterations);
188+
self
189+
}
190+
191+
pub fn expect_maximum_iterations(mut self, iterations: u32) -> Self {
192+
self.maximum_iterations = Some(iterations);
193+
self
194+
}
195+
196+
pub fn sleep_on_step(mut self, duration: Duration) -> Self {
197+
self.sleep_on_step = Some(duration);
198+
self
199+
}
200+
201+
#[track_caller]
202+
pub fn must_run(mut self) -> Self {
203+
self.must_run = true;
204+
self
205+
}
206+
}
207+
208+
impl Drop for TestStep {
209+
fn drop(&mut self) {
210+
assert!(
211+
!self.must_run || self.iter.load(Ordering::Relaxed) > 0,
212+
"Step was expected to run, but did not."
213+
);
214+
215+
if let Some(minimum_iterations) = self.minimum_iterations {
216+
assert!(
217+
self.iter.load(Ordering::Relaxed) >= minimum_iterations,
218+
"Expected step to run at least {minimum_iterations} times, but ran \
219+
{} times.",
220+
self.iter.load(Ordering::Relaxed)
221+
);
222+
}
223+
224+
if let Some(maximum_iterations) = self.maximum_iterations {
225+
assert!(
226+
self.iter.load(Ordering::Relaxed) <= maximum_iterations,
227+
"Expected step to run at most {maximum_iterations} times, but ran \
228+
{} times.",
229+
self.iter.load(Ordering::Relaxed)
230+
);
231+
}
232+
}
233+
}
234+
235+
impl<P: Platform> Step<P> for TestStep {
236+
async fn step(
237+
self: Arc<Self>,
238+
payload: Checkpoint<P>,
239+
ctx: StepContext<P>,
240+
) -> ControlFlow<P> {
241+
if let Some(sleep_duration) = self.sleep_on_step {
242+
tokio::time::sleep(sleep_duration).await;
243+
}
244+
245+
let break_after = self.break_after.load(Ordering::Relaxed);
246+
if self.iter.fetch_add(1, Ordering::Relaxed) >= break_after {
247+
return ControlFlow::Break(payload);
248+
}
249+
250+
if ctx.deadline_reached() {
251+
return ControlFlow::Break(payload);
252+
}
253+
254+
if let Some(expected_gas_limit) = self.expected_gas_limit {
255+
assert_eq!(
256+
ctx.limits().gas_limit,
257+
expected_gas_limit,
258+
"Expected gas limit to be {expected_gas_limit}, but got {}",
259+
ctx.limits().gas_limit
260+
);
261+
}
262+
263+
if let Some(expected_deadline) = self.expected_deadline {
264+
assert_eq!(
265+
ctx.limits().deadline,
266+
Some(expected_deadline),
267+
"Expected deadline to be {expected_deadline:?}, but got {:?}",
268+
ctx.limits().deadline
269+
);
270+
}
271+
272+
ControlFlow::Ok(payload)
273+
}
274+
}
275+
276+
#[rblib_test(Ethereum, Optimism)]
277+
async fn one_level_flat<P: TestablePlatform>() -> eyre::Result<()> {
278+
let pipeline = Pipeline::<P>::default()
279+
.with_step(
280+
TestStep::default()
281+
.expect_gas_limit(1000)
282+
.expect_deadline(Duration::from_millis(500))
283+
.must_run(),
284+
)
285+
.with_limits(
286+
Limits::with_gas_limit(1000).with_deadline(Duration::from_millis(500)),
287+
);
288+
P::create_test_node(pipeline).await?.next_block().await?;
289+
Ok(())
290+
}
291+
292+
#[rblib_test(Ethereum, Optimism)]
293+
async fn scale_fraction<P: TestablePlatform>() -> eyre::Result<()> {
294+
let pipeline = Pipeline::<P>::default()
295+
.with_pipeline(
296+
Once,
297+
(TestStep::default()
298+
.expect_gas_limit(500)
299+
.expect_deadline(Duration::from_millis(250))
300+
.must_run(),)
301+
.with_limits(
302+
Scaled::default()
303+
.gas(Fraction(1, NonZero::new(2).unwrap()))
304+
.deadline(Fraction(1, NonZero::new(2).unwrap())),
305+
),
306+
)
307+
.with_limits(
308+
Limits::with_gas_limit(1000).with_deadline(Duration::from_millis(500)),
309+
);
310+
P::create_test_node(pipeline).await?.next_block().await?;
311+
Ok(())
312+
}
313+
314+
#[rblib_test(Ethereum, Optimism)]
315+
async fn scale_minus<P: TestablePlatform>() -> eyre::Result<()> {
316+
let pipeline = Pipeline::<P>::default()
317+
.with_pipeline(
318+
Once,
319+
(TestStep::default()
320+
.expect_gas_limit(900)
321+
.expect_deadline(Duration::from_millis(400))
322+
.must_run(),)
323+
.with_limits(
324+
Scaled::default()
325+
.gas(Minus(100))
326+
.deadline(Minus(Duration::from_millis(100))),
327+
),
328+
)
329+
.with_limits(
330+
Limits::with_gas_limit(1000).with_deadline(Duration::from_millis(500)),
331+
);
332+
P::create_test_node(pipeline).await?.next_block().await?;
333+
Ok(())
334+
}
335+
336+
/// two levels of scaling
337+
#[rblib_test(Ethereum, Optimism)]
338+
async fn deadline_approaches<P: TestablePlatform>() -> eyre::Result<()> {
339+
let pipeline = Pipeline::<P>::default()
340+
.with_pipeline(
341+
Once,
342+
Pipeline::default()
343+
.with_pipeline(
344+
Once,
345+
(TestStep::default()
346+
.expect_gas_limit(400)
347+
.expect_deadline(Duration::from_millis(300))
348+
.expect_minimum_iterations(2)
349+
.expect_maximum_iterations(3)
350+
.sleep_on_step(Duration::from_millis(100))
351+
.must_run(),)
352+
.with_limits(
353+
Scaled::default()
354+
.gas(Minus(100))
355+
.deadline(Minus(Duration::from_millis(100))),
356+
),
357+
)
358+
.with_limits(
359+
Scaled::default()
360+
.gas(Fraction(1, NonZero::new(2).unwrap()))
361+
.deadline(Fraction(1, NonZero::new(2).unwrap())),
362+
),
363+
)
364+
.with_limits(
365+
Limits::with_gas_limit(1000).with_deadline(Duration::from_millis(800)),
366+
);
367+
P::create_test_node(pipeline).await?.next_block().await?;
368+
Ok(())
369+
}
370+
}

0 commit comments

Comments
 (0)