tempo_payload_types/
attrs.rs

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/// A handle for a payload interrupt flag.
14///
15/// Can be fired using [`InterruptHandle::interrupt`].
16#[derive(Debug, Clone, Default)]
17pub struct InterruptHandle(Arc<atomic::AtomicBool>);
18
19impl InterruptHandle {
20    /// Turns on the interrupt flag on the associated payload.
21    pub fn interrupt(&self) {
22        self.0.store(true, Ordering::Relaxed);
23    }
24
25    /// Returns whether the interrupt flag is set.
26    pub fn is_interrupted(&self) -> bool {
27        self.0.load(Ordering::Relaxed)
28    }
29}
30
31/// Container type for all components required to build a payload.
32///
33/// The `TempoPayloadBuilderAttributes` has an additional feature of interrupting payload.
34///
35/// It also carries DKG data to be included in the block's extra_data field.
36#[derive(derive_more::Debug, Clone)]
37pub struct TempoPayloadBuilderAttributes {
38    inner: EthPayloadBuilderAttributes,
39    interrupt: InterruptHandle,
40    timestamp_millis_part: u64,
41    /// DKG ceremony data to include in the block's extra_data header field.
42    ///
43    /// This is empty when no DKG data is available (e.g., when the DKG manager
44    /// hasn't produced ceremony outcomes yet, or when DKG operations fail).
45    extra_data: Bytes,
46    #[debug(skip)]
47    subblocks: Arc<dyn Fn() -> Vec<RecoveredSubBlock> + Send + Sync + 'static>,
48}
49
50impl TempoPayloadBuilderAttributes {
51    /// Creates new `TempoPayloadBuilderAttributes` with `inner` attributes.
52    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    /// Returns the extra data to be included in the block header.
79    pub fn extra_data(&self) -> &Bytes {
80        &self.extra_data
81    }
82
83    /// Returns the `interrupt` flag. If true, it marks that a payload is requested to stop
84    /// processing any more transactions.
85    pub fn is_interrupted(&self) -> bool {
86        self.interrupt.0.load(Ordering::Relaxed)
87    }
88
89    /// Returns a cloneable [`InterruptHandle`] for turning on the `interrupt` flag.
90    pub fn interrupt_handle(&self) -> &InterruptHandle {
91        &self.interrupt
92    }
93
94    /// Returns the milliseconds portion of the timestamp.
95    pub fn timestamp_millis_part(&self) -> u64 {
96        self.timestamp_millis_part
97    }
98
99    /// Returns the timestamp in milliseconds.
100    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    /// Returns the subblocks.
108    pub fn subblocks(&self) -> Vec<RecoveredSubBlock> {
109        (self.subblocks)()
110    }
111}
112
113// Required by reth's e2e-test-utils for integration tests.
114// The test utilities need to convert from standard Ethereum payload attributes
115// to custom chain-specific attributes.
116impl 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/// Tempo RPC payload attributes configuration.
183#[derive(Debug, Clone, Serialize, Deserialize, derive_more::Deref, derive_more::DerefMut)]
184#[serde(rename_all = "camelCase")]
185pub struct TempoPayloadAttributes {
186    /// Inner [`EthPayloadAttributes`].
187    #[serde(flatten)]
188    #[deref]
189    #[deref_mut]
190    pub inner: EthPayloadAttributes,
191
192    /// Milliseconds portion of the timestamp.
193    #[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
215    #[test]
216    fn test_attributes_without_extra_data() {
217        let parent = B256::default();
218        let id = PayloadId::default();
219        let recipient = Address::default();
220        let timestamp_millis = 1000;
221
222        let attrs = TempoPayloadBuilderAttributes::new(
223            id,
224            parent,
225            recipient,
226            timestamp_millis,
227            Bytes::default(),
228            Vec::new,
229        );
230
231        assert_eq!(attrs.extra_data(), &Bytes::default());
232        assert_eq!(attrs.parent(), parent);
233        assert_eq!(attrs.suggested_fee_recipient(), recipient);
234        assert_eq!(attrs.timestamp(), 1); // 1000 ms / 1000 = 1 second
235    }
236
237    #[test]
238    fn test_attributes_with_extra_data() {
239        let parent = B256::default();
240        let id = PayloadId::default();
241        let recipient = Address::default();
242        let timestamp_millis = 1000;
243        let extra_data = Bytes::from(vec![1, 2, 3, 4, 5]);
244
245        let attrs = TempoPayloadBuilderAttributes::new(
246            id,
247            parent,
248            recipient,
249            timestamp_millis,
250            extra_data.clone(),
251            Vec::new,
252        );
253
254        assert_eq!(attrs.extra_data(), &extra_data);
255        assert_eq!(attrs.parent(), parent);
256        assert_eq!(attrs.suggested_fee_recipient(), recipient);
257    }
258
259    #[test]
260    fn test_attributes_with_empty_extra_data() {
261        let parent = B256::default();
262        let id = PayloadId::default();
263        let recipient = Address::default();
264        let timestamp_millis = 1000;
265
266        let attrs = TempoPayloadBuilderAttributes::new(
267            id,
268            parent,
269            recipient,
270            timestamp_millis,
271            Bytes::default(),
272            Vec::new,
273        );
274
275        assert_eq!(attrs.extra_data(), &Bytes::default());
276    }
277}