Skip to main content

tempo_payload_types/
lib.rs

1//! Tempo payload types.
2
3#![cfg_attr(not(test), warn(unused_crate_dependencies))]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5
6mod attrs;
7mod budget;
8
9use alloy_primitives::{B256, Bytes};
10pub use attrs::TempoPayloadAttributes;
11pub use budget::{
12    MarshalPersistEstimator, ValidationLatencyEstimate, ValidationLatencyEstimator,
13    ValidationLatencyWorkload, marshal_persist_estimate, observe_marshal_persist,
14};
15use std::{
16    sync::{Arc, OnceLock},
17    time::Duration,
18};
19
20use alloy_eips::eip7685::Requests;
21use alloy_primitives::U256;
22use alloy_rpc_types_eth::Withdrawal;
23use reth_ethereum_engine_primitives::EthBuiltPayload;
24use reth_node_api::{BlockBody, ExecutionPayload, PayloadTypes};
25use reth_payload_primitives::{BuiltPayload, BuiltPayloadExecutedBlock};
26use reth_primitives_traits::{AlloyBlockHeader as _, SealedBlock, SealedOrRecoveredBlock};
27use serde::{Deserialize, Serialize};
28use tempo_primitives::{Block, TempoPrimitives};
29
30/// Payload types for Tempo node.
31#[derive(Debug, Clone, Copy, Default)]
32#[non_exhaustive]
33pub struct TempoPayloadTypes;
34
35/// Built payload type for Tempo node.
36///
37/// Wraps [`EthBuiltPayload`] and optionally includes the executed block data
38/// to enable the engine tree fast path (skipping re-execution for self-built payloads).
39#[derive(Debug, Clone)]
40pub struct TempoBuiltPayload {
41    /// The inner built payload.
42    inner: EthBuiltPayload<TempoPrimitives>,
43    /// RLP-encoded EIP-7928 block access list, when generated for this payload.
44    block_access_list: Option<Bytes>,
45    /// The executed block data, used to skip re-execution in the engine tree.
46    executed_block: Option<BuiltPayloadExecutedBlock<TempoPrimitives>>,
47    /// Replayable builder work for this payload.
48    ///
49    /// This excludes proposer-only idle waiting, but includes transaction
50    /// execution and non-interruptible `builder_finish`.
51    validation_work_duration: Duration,
52    /// Time validators are expected to spend validating this payload.
53    validation_latency_duration: Duration,
54    /// Approximate execution block RLP size estimate used for pacing and builder-side limits.
55    execution_block_size_estimate: usize,
56    /// Shared cache for the encoded execution block bytes.
57    execution_block_encoded: EncodedBlock,
58}
59
60impl TempoBuiltPayload {
61    /// Creates a new [`TempoBuiltPayload`].
62    pub fn new(
63        inner: EthBuiltPayload<TempoPrimitives>,
64        block_access_list: Option<Bytes>,
65        executed_block: Option<BuiltPayloadExecutedBlock<TempoPrimitives>>,
66        validation_work_duration: Duration,
67        validation_latency_duration: Duration,
68        execution_block_size_estimate: usize,
69        execution_block_encoded: EncodedBlock,
70    ) -> Self {
71        Self {
72            inner,
73            block_access_list,
74            executed_block,
75            validation_work_duration,
76            validation_latency_duration,
77            execution_block_size_estimate,
78            execution_block_encoded,
79        }
80    }
81
82    /// Converts the built payload into owned execution payload parts.
83    pub fn into_execution_payload(self) -> (SealedBlock<Block>, Option<Bytes>) {
84        (
85            Arc::unwrap_or_clone(self.inner.block_arc().clone()).into_sealed_block(),
86            self.block_access_list,
87        )
88    }
89
90    /// Converts the built payload into consensus block parts without cloning the execution block.
91    pub fn into_consensus_execution_payload(
92        self,
93    ) -> (SealedOrRecoveredBlock<Block>, Option<Bytes>, EncodedBlock) {
94        let execution_block = SealedOrRecoveredBlock::recovered_arc(self.inner.block_arc().clone());
95
96        (
97            execution_block,
98            self.block_access_list,
99            self.execution_block_encoded,
100        )
101    }
102
103    /// Returns the approximate execution block RLP size estimate.
104    pub fn execution_block_size_estimate(&self) -> usize {
105        self.execution_block_size_estimate
106    }
107
108    /// Returns replayable builder work for this payload.
109    pub fn validation_work_duration(&self) -> Duration {
110        self.validation_work_duration
111    }
112
113    /// Returns the time validators are expected to spend validating this payload.
114    pub fn validation_latency_duration(&self) -> Duration {
115        self.validation_latency_duration
116    }
117
118    /// Converts the built payload into [`TempoExecutionData`].
119    pub fn into_execution_data(self) -> TempoExecutionData {
120        let (block, block_access_list, _) = self.into_consensus_execution_payload();
121        TempoExecutionData {
122            block,
123            block_access_list,
124            validator_set: None,
125        }
126    }
127}
128
129impl BuiltPayload for TempoBuiltPayload {
130    type Primitives = TempoPrimitives;
131
132    fn block(&self) -> &SealedBlock<Block> {
133        self.inner.block()
134    }
135
136    fn fees(&self) -> U256 {
137        self.inner.fees()
138    }
139
140    fn executed_block(&self) -> Option<BuiltPayloadExecutedBlock<Self::Primitives>> {
141        self.executed_block.clone()
142    }
143
144    fn requests(&self) -> Option<Requests> {
145        self.inner.requests()
146    }
147
148    fn block_access_list(&self) -> Option<&Bytes> {
149        self.block_access_list.as_ref()
150    }
151}
152
153/// Execution data for Tempo node. Simply wraps a sealed block.
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct TempoExecutionData {
156    /// The built block.
157    pub block: SealedOrRecoveredBlock<Block>,
158    /// RLP-encoded EIP-7928 block access list, when supplied with the payload.
159    pub block_access_list: Option<Bytes>,
160    /// Validator set active at the time this block was built.
161    pub validator_set: Option<Vec<B256>>,
162}
163
164impl ExecutionPayload for TempoExecutionData {
165    fn parent_hash(&self) -> alloy_primitives::B256 {
166        self.block.parent_hash()
167    }
168
169    fn block_hash(&self) -> alloy_primitives::B256 {
170        self.block.hash()
171    }
172
173    fn block_number(&self) -> u64 {
174        self.block.number()
175    }
176
177    fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
178        self.block
179            .body()
180            .withdrawals
181            .as_ref()
182            .map(|withdrawals| &withdrawals.0)
183    }
184
185    fn parent_beacon_block_root(&self) -> Option<alloy_primitives::B256> {
186        self.block.parent_beacon_block_root()
187    }
188
189    fn timestamp(&self) -> u64 {
190        self.block.timestamp()
191    }
192
193    fn transaction_count(&self) -> usize {
194        self.block.body().transaction_count()
195    }
196
197    fn gas_used(&self) -> u64 {
198        self.block.gas_used()
199    }
200
201    fn gas_limit(&self) -> u64 {
202        self.block.gas_limit()
203    }
204
205    fn slot_number(&self) -> Option<u64> {
206        self.block.slot_number()
207    }
208
209    fn block_access_list(&self) -> Option<&Bytes> {
210        self.block_access_list.as_ref()
211    }
212}
213
214impl From<TempoBuiltPayload> for TempoExecutionData {
215    fn from(value: TempoBuiltPayload) -> Self {
216        value.into_execution_data()
217    }
218}
219
220impl PayloadTypes for TempoPayloadTypes {
221    type ExecutionData = TempoExecutionData;
222    type BuiltPayload = TempoBuiltPayload;
223    type PayloadAttributes = TempoPayloadAttributes;
224
225    fn block_to_payload(block: SealedBlock<Block>, bal: Option<Bytes>) -> Self::ExecutionData {
226        TempoExecutionData {
227            block: block.into(),
228            block_access_list: bal,
229            validator_set: None,
230        }
231    }
232}
233
234/// Shared cache for an execution-layer block encoded as RLP bytes.
235///
236/// Clones share the same once-initialized slot for lazy, one-time computation of the encoded bytes.
237/// For example, a payload builder can hand this cache to consumers while a background task encodes
238/// the block as it is prepared for proposal.
239#[derive(Clone, Debug, Default)]
240pub struct EncodedBlock(Arc<OnceLock<Bytes>>);
241
242impl EncodedBlock {
243    pub fn new(bytes: Bytes) -> Self {
244        Self(Arc::new(OnceLock::from(bytes)))
245    }
246
247    /// Returns cached encoded bytes when they are already available.
248    pub fn get(&self) -> Option<&Bytes> {
249        self.0.get()
250    }
251
252    /// Returns cached encoded bytes, encoding `block` first if the cache is empty.
253    pub fn get_or_encode<T>(&self, block: &T) -> &Bytes
254    where
255        T: alloy_rlp::Encodable,
256    {
257        self.get_or_encode_with(|| {
258            let mut encoded = Vec::new();
259            block.encode(&mut encoded);
260            encoded.into()
261        })
262    }
263
264    /// Returns cached encoded bytes, filling the cache with `encode` if it is empty.
265    pub fn get_or_encode_with(&self, encode: impl FnOnce() -> Bytes) -> &Bytes {
266        self.0.get_or_init(encode)
267    }
268}