1use alloy_primitives::{Address, B256, Bytes};
2use alloy_rpc_types_engine::PayloadId;
3use alloy_rpc_types_eth::Withdrawals;
4use reth_ethereum_engine_primitives::{EthPayloadAttributes, EthPayloadBuilderAttributes};
5use reth_node_api::{PayloadAttributes, PayloadBuilderAttributes};
6use serde::{Deserialize, Serialize};
7use std::{
8 convert::Infallible,
9 sync::{Arc, atomic, atomic::Ordering},
10};
11use tempo_primitives::RecoveredSubBlock;
12
13#[derive(Debug, Clone, Default)]
17pub struct InterruptHandle(Arc<atomic::AtomicBool>);
18
19impl InterruptHandle {
20 pub fn interrupt(&self) {
22 self.0.store(true, Ordering::Relaxed);
23 }
24
25 pub fn is_interrupted(&self) -> bool {
27 self.0.load(Ordering::Relaxed)
28 }
29}
30
31#[derive(derive_more::Debug, Clone)]
37pub struct TempoPayloadBuilderAttributes {
38 inner: EthPayloadBuilderAttributes,
39 interrupt: InterruptHandle,
40 timestamp_millis_part: u64,
41 extra_data: Bytes,
46 #[debug(skip)]
47 subblocks: Arc<dyn Fn() -> Vec<RecoveredSubBlock> + Send + Sync + 'static>,
48}
49
50impl TempoPayloadBuilderAttributes {
51 pub fn new(
53 id: PayloadId,
54 parent: B256,
55 suggested_fee_recipient: Address,
56 timestamp_millis: u64,
57 extra_data: Bytes,
58 subblocks: impl Fn() -> Vec<RecoveredSubBlock> + Send + Sync + 'static,
59 ) -> Self {
60 let (seconds, millis) = (timestamp_millis / 1000, timestamp_millis % 1000);
61 Self {
62 inner: EthPayloadBuilderAttributes {
63 id,
64 parent,
65 timestamp: seconds,
66 suggested_fee_recipient,
67 prev_randao: B256::ZERO,
68 withdrawals: Withdrawals::default(),
69 parent_beacon_block_root: Some(B256::ZERO),
70 },
71 interrupt: InterruptHandle::default(),
72 timestamp_millis_part: millis,
73 extra_data,
74 subblocks: Arc::new(subblocks),
75 }
76 }
77
78 pub fn extra_data(&self) -> &Bytes {
80 &self.extra_data
81 }
82
83 pub fn is_interrupted(&self) -> bool {
86 self.interrupt.0.load(Ordering::Relaxed)
87 }
88
89 pub fn interrupt_handle(&self) -> &InterruptHandle {
91 &self.interrupt
92 }
93
94 pub fn timestamp_millis_part(&self) -> u64 {
96 self.timestamp_millis_part
97 }
98
99 pub fn timestamp_millis(&self) -> u64 {
101 self.inner
102 .timestamp()
103 .saturating_mul(1000)
104 .saturating_add(self.timestamp_millis_part)
105 }
106
107 pub fn subblocks(&self) -> Vec<RecoveredSubBlock> {
109 (self.subblocks)()
110 }
111}
112
113impl From<EthPayloadBuilderAttributes> for TempoPayloadBuilderAttributes {
117 fn from(inner: EthPayloadBuilderAttributes) -> Self {
118 Self {
119 inner,
120 interrupt: InterruptHandle::default(),
121 timestamp_millis_part: 0,
122 extra_data: Bytes::default(),
123 subblocks: Arc::new(Vec::new),
124 }
125 }
126}
127
128impl PayloadBuilderAttributes for TempoPayloadBuilderAttributes {
129 type RpcPayloadAttributes = TempoPayloadAttributes;
130 type Error = Infallible;
131
132 fn try_new(
133 parent: B256,
134 rpc_payload_attributes: Self::RpcPayloadAttributes,
135 version: u8,
136 ) -> Result<Self, Self::Error>
137 where
138 Self: Sized,
139 {
140 let TempoPayloadAttributes {
141 inner,
142 timestamp_millis_part,
143 } = rpc_payload_attributes;
144 Ok(Self {
145 inner: EthPayloadBuilderAttributes::try_new(parent, inner, version)?,
146 interrupt: InterruptHandle::default(),
147 timestamp_millis_part,
148 extra_data: Bytes::default(),
149 subblocks: Arc::new(Vec::new),
150 })
151 }
152
153 fn payload_id(&self) -> alloy_rpc_types_engine::payload::PayloadId {
154 self.inner.payload_id()
155 }
156
157 fn parent(&self) -> B256 {
158 self.inner.parent()
159 }
160
161 fn timestamp(&self) -> u64 {
162 self.inner.timestamp()
163 }
164
165 fn parent_beacon_block_root(&self) -> Option<B256> {
166 self.inner.parent_beacon_block_root()
167 }
168
169 fn suggested_fee_recipient(&self) -> Address {
170 self.inner.suggested_fee_recipient()
171 }
172
173 fn prev_randao(&self) -> B256 {
174 self.inner.prev_randao()
175 }
176
177 fn withdrawals(&self) -> &Withdrawals {
178 self.inner.withdrawals()
179 }
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize, derive_more::Deref, derive_more::DerefMut)]
184#[serde(rename_all = "camelCase")]
185pub struct TempoPayloadAttributes {
186 #[serde(flatten)]
188 #[deref]
189 #[deref_mut]
190 pub inner: EthPayloadAttributes,
191
192 #[serde(with = "alloy_serde::quantity")]
194 pub timestamp_millis_part: u64,
195}
196
197impl PayloadAttributes for TempoPayloadAttributes {
198 fn timestamp(&self) -> u64 {
199 self.inner.timestamp()
200 }
201
202 fn withdrawals(&self) -> Option<&Vec<alloy_rpc_types_eth::Withdrawal>> {
203 self.inner.withdrawals()
204 }
205
206 fn parent_beacon_block_root(&self) -> Option<B256> {
207 self.inner.parent_beacon_block_root()
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214 use alloy_rpc_types_eth::Withdrawal;
215
216 trait TestExt: Sized {
217 fn random() -> Self;
218 fn with_timestamp(self, millis: u64) -> Self;
219 fn with_subblocks(
220 self,
221 f: impl Fn() -> Vec<RecoveredSubBlock> + Send + Sync + 'static,
222 ) -> Self;
223 }
224
225 impl TestExt for TempoPayloadBuilderAttributes {
226 fn random() -> Self {
227 Self::new(
228 PayloadId::default(),
229 B256::random(),
230 Address::random(),
231 1000,
232 Bytes::default(),
233 Vec::new,
234 )
235 }
236
237 fn with_timestamp(mut self, millis: u64) -> Self {
238 self.inner.timestamp = millis / 1000;
239 self.timestamp_millis_part = millis % 1000;
240 self
241 }
242
243 fn with_subblocks(
244 mut self,
245 f: impl Fn() -> Vec<RecoveredSubBlock> + Send + Sync + 'static,
246 ) -> Self {
247 self.subblocks = Arc::new(f);
248 self
249 }
250 }
251
252 #[test]
253 fn test_interrupt_handle() {
254 let handle = InterruptHandle::default();
256 assert!(!handle.is_interrupted());
257
258 handle.interrupt();
260 assert!(handle.is_interrupted());
261
262 let handle2 = handle.clone();
264 assert!(handle2.is_interrupted());
265
266 let fresh = InterruptHandle::default();
268 let cloned = fresh.clone();
269 assert!(!cloned.is_interrupted());
270 fresh.interrupt();
271 assert!(cloned.is_interrupted()); handle.interrupt();
275 handle.interrupt();
276 assert!(handle.is_interrupted());
277 }
278
279 #[test]
280 fn test_builder_attributes_construction() {
281 let parent = B256::random();
282 let id = PayloadId::new([1, 2, 3, 4, 5, 6, 7, 8]);
283 let recipient = Address::random();
284 let extra_data = Bytes::from(vec![1, 2, 3, 4, 5]);
285 let timestamp_millis = 1500; let attrs = TempoPayloadBuilderAttributes::new(
289 id,
290 parent,
291 recipient,
292 timestamp_millis,
293 extra_data.clone(),
294 Vec::new,
295 );
296 assert_eq!(attrs.extra_data(), &extra_data);
297 assert_eq!(attrs.parent(), parent);
298 assert_eq!(attrs.suggested_fee_recipient(), recipient);
299 assert_eq!(attrs.payload_id(), id);
300 assert_eq!(attrs.timestamp(), 1);
301 assert_eq!(attrs.timestamp_millis_part(), 500);
302
303 assert_eq!(attrs.prev_randao(), B256::ZERO);
305 assert_eq!(attrs.parent_beacon_block_root(), Some(B256::ZERO));
306 assert!(attrs.withdrawals().is_empty());
307
308 let attrs2 = TempoPayloadBuilderAttributes::new(
310 id,
311 parent,
312 recipient,
313 timestamp_millis + 500, Bytes::default(),
315 Vec::new,
316 );
317 assert_eq!(attrs2.extra_data(), &Bytes::default());
318 assert_eq!(attrs2.timestamp(), 2);
319 assert_eq!(attrs2.timestamp_millis_part(), 0);
320 }
321
322 #[test]
323 fn test_builder_attributes_interrupt_integration() {
324 let attrs = TempoPayloadBuilderAttributes::random();
325
326 assert!(!attrs.is_interrupted());
328
329 let handle = attrs.interrupt_handle().clone();
331 handle.interrupt();
332
333 assert!(attrs.is_interrupted());
335 assert!(handle.is_interrupted());
336
337 let handle2 = attrs.interrupt_handle();
339 assert!(handle2.is_interrupted());
340 }
341
342 #[test]
343 fn test_builder_attributes_timestamp_handling() {
344 let attrs = TempoPayloadBuilderAttributes::random().with_timestamp(3000);
346 assert_eq!(attrs.timestamp(), 3);
347 assert_eq!(attrs.timestamp_millis_part(), 0);
348 assert_eq!(attrs.timestamp_millis(), 3000);
349
350 let attrs = TempoPayloadBuilderAttributes::random().with_timestamp(3999);
352 assert_eq!(attrs.timestamp(), 3);
353 assert_eq!(attrs.timestamp_millis_part(), 999);
354 assert_eq!(attrs.timestamp_millis(), 3999);
355
356 let attrs = TempoPayloadBuilderAttributes::random().with_timestamp(0);
358 assert_eq!(attrs.timestamp(), 0);
359 assert_eq!(attrs.timestamp_millis_part(), 0);
360 assert_eq!(attrs.timestamp_millis(), 0);
361
362 let large_ts = u64::MAX / 1000 * 1000;
364 let attrs = TempoPayloadBuilderAttributes::random().with_timestamp(large_ts + 500);
365 assert_eq!(attrs.timestamp_millis_part(), 500);
366 assert!(attrs.timestamp_millis() >= large_ts);
367 }
368
369 #[test]
370 fn test_builder_attributes_subblocks() {
371 use std::sync::atomic::AtomicUsize;
372
373 let call_count = Arc::new(AtomicUsize::new(0));
374 let count_clone = call_count.clone();
375
376 let attrs = TempoPayloadBuilderAttributes::random().with_subblocks(move || {
377 count_clone.fetch_add(1, Ordering::SeqCst);
378 Vec::new()
379 });
380
381 assert_eq!(call_count.load(Ordering::SeqCst), 0);
383 let _ = attrs.subblocks();
384 assert_eq!(call_count.load(Ordering::SeqCst), 1);
385 let _ = attrs.subblocks();
386 assert_eq!(call_count.load(Ordering::SeqCst), 2);
387 }
388
389 #[test]
390 fn test_from_eth_payload_builder_attributes() {
391 let eth_attrs = EthPayloadBuilderAttributes {
392 id: PayloadId::new([9, 8, 7, 6, 5, 4, 3, 2]),
393 parent: B256::random(),
394 timestamp: 1000,
395 suggested_fee_recipient: Address::random(),
396 prev_randao: B256::random(),
397 withdrawals: Withdrawals::new(vec![Withdrawal {
398 index: 1,
399 validator_index: 2,
400 address: Address::random(),
401 amount: 100,
402 }]),
403 parent_beacon_block_root: Some(B256::random()),
404 };
405
406 let tempo_attrs: TempoPayloadBuilderAttributes = eth_attrs.clone().into();
407
408 assert_eq!(tempo_attrs.payload_id(), eth_attrs.id);
410 assert_eq!(tempo_attrs.parent(), eth_attrs.parent);
411 assert_eq!(tempo_attrs.timestamp(), eth_attrs.timestamp);
412 assert_eq!(
413 tempo_attrs.suggested_fee_recipient(),
414 eth_attrs.suggested_fee_recipient
415 );
416 assert_eq!(tempo_attrs.prev_randao(), eth_attrs.prev_randao);
417 assert_eq!(tempo_attrs.withdrawals().len(), 1);
418 assert_eq!(
419 tempo_attrs.parent_beacon_block_root(),
420 eth_attrs.parent_beacon_block_root
421 );
422
423 assert_eq!(tempo_attrs.timestamp_millis_part(), 0);
425 assert_eq!(tempo_attrs.extra_data(), &Bytes::default());
426 assert!(!tempo_attrs.is_interrupted());
427 assert!(tempo_attrs.subblocks().is_empty());
428 }
429
430 #[test]
431 fn test_try_new_from_rpc_attributes() {
432 let rpc_attrs = TempoPayloadAttributes {
433 inner: EthPayloadAttributes {
434 timestamp: 100,
435 prev_randao: B256::random(),
436 suggested_fee_recipient: Address::random(),
437 withdrawals: Some(vec![]),
438 parent_beacon_block_root: Some(B256::random()),
439 },
440 timestamp_millis_part: 750,
441 };
442
443 let parent = B256::random();
444 let result = TempoPayloadBuilderAttributes::try_new(parent, rpc_attrs, 3);
445 assert!(result.is_ok());
446
447 let attrs = result.unwrap();
448 assert_eq!(attrs.parent(), parent);
449 assert_eq!(attrs.timestamp(), 100);
450 assert_eq!(attrs.timestamp_millis_part(), 750);
451 assert_eq!(attrs.timestamp_millis(), 100_750);
452 assert_eq!(attrs.extra_data(), &Bytes::default());
453 assert!(!attrs.is_interrupted());
454 }
455
456 #[test]
457 fn test_tempo_payload_attributes_serde() {
458 let timestamp = 1234567890;
459 let timestamp_millis_part = 999;
460 let attrs = TempoPayloadAttributes {
461 inner: EthPayloadAttributes {
462 timestamp,
463 prev_randao: B256::ZERO,
464 suggested_fee_recipient: Address::random(),
465 withdrawals: Some(vec![]),
466 parent_beacon_block_root: Some(B256::random()),
467 },
468 timestamp_millis_part,
469 };
470
471 let json = serde_json::to_string(&attrs).unwrap();
473 assert!(json.contains("timestampMillisPart"));
474
475 let deserialized: TempoPayloadAttributes = serde_json::from_str(&json).unwrap();
476 assert_eq!(deserialized.inner.timestamp, timestamp);
477 assert_eq!(deserialized.timestamp_millis_part, timestamp_millis_part);
478
479 assert_eq!(attrs.timestamp, timestamp);
481
482 let mut attrs = attrs;
484 attrs.timestamp = 123;
485 assert_eq!(attrs.inner.timestamp, 123);
486 }
487
488 #[test]
489 fn test_tempo_payload_attributes_trait_impl() {
490 let withdrawal_addr = Address::random();
491 let beacon_root = B256::random();
492
493 let attrs = TempoPayloadAttributes {
494 inner: EthPayloadAttributes {
495 timestamp: 9999,
496 prev_randao: B256::ZERO,
497 suggested_fee_recipient: Address::random(),
498 withdrawals: Some(vec![Withdrawal {
499 index: 0,
500 validator_index: 1,
501 address: withdrawal_addr,
502 amount: 500,
503 }]),
504 parent_beacon_block_root: Some(beacon_root),
505 },
506 timestamp_millis_part: 123,
507 };
508
509 assert_eq!(PayloadAttributes::timestamp(&attrs), 9999);
511 assert_eq!(attrs.withdrawals().unwrap().len(), 1);
512 assert_eq!(attrs.withdrawals().unwrap()[0].address, withdrawal_addr);
513 assert_eq!(attrs.parent_beacon_block_root(), Some(beacon_root));
514
515 let attrs_none = TempoPayloadAttributes {
517 inner: EthPayloadAttributes {
518 timestamp: 1,
519 prev_randao: B256::ZERO,
520 suggested_fee_recipient: Address::random(),
521 withdrawals: None,
522 parent_beacon_block_root: None,
523 },
524 timestamp_millis_part: 0,
525 };
526 assert!(attrs_none.withdrawals().is_none());
527 assert!(attrs_none.parent_beacon_block_root().is_none());
528 }
529}