Skip to main content

reth_e2e_test_utils/
payload.rs

1use eyre::eyre;
2use futures_util::StreamExt;
3use reth_node_api::{PayloadAttributes, PayloadKind};
4use reth_payload_builder::{PayloadBuilderHandle, PayloadId};
5use reth_payload_builder_primitives::Events;
6use reth_payload_primitives::PayloadTypes;
7use tokio_stream::wrappers::BroadcastStream;
8
9/// Helper for payload operations
10#[derive(derive_more::Debug)]
11pub struct PayloadTestContext<T: PayloadTypes> {
12    /// Events of all payload jobs of the node since the context was created.
13    ///
14    /// The harness does not read the stream, so it also yields the events of the payloads built by
15    /// [`NodeTestContext::new_payload`]. The payload builder only buffers the most recent events,
16    /// so a reader that falls behind receives a lagged error first.
17    ///
18    /// [`NodeTestContext::new_payload`]: crate::node::NodeTestContext::new_payload
19    pub payload_event_stream: BroadcastStream<Events<T>>,
20    payload_builder: PayloadBuilderHandle<T>,
21    /// Timestamp of the last generated payload attributes, or the starting point if none were
22    /// generated yet.
23    ///
24    /// [`Self::next_attributes`] increments it before generating the next attributes. Prefer
25    /// [`NodeTestContext::set_next_payload_timestamp`] over setting it directly.
26    ///
27    /// [`NodeTestContext::set_next_payload_timestamp`]: crate::node::NodeTestContext::set_next_payload_timestamp
28    pub timestamp: u64,
29    #[debug(skip)]
30    attributes_generator: Box<dyn Fn(u64) -> T::PayloadAttributes + Send + Sync>,
31}
32
33impl<T: PayloadTypes> PayloadTestContext<T> {
34    /// Creates a new payload helper
35    pub async fn new(
36        payload_builder: PayloadBuilderHandle<T>,
37        attributes_generator: impl Fn(u64) -> T::PayloadAttributes + Send + Sync + 'static,
38    ) -> eyre::Result<Self> {
39        let payload_events = payload_builder.subscribe().await?;
40        let payload_event_stream = payload_events.into_stream();
41        // Cancun timestamp
42        Ok(Self {
43            payload_event_stream,
44            payload_builder,
45            timestamp: 1710338135,
46            attributes_generator: Box::new(attributes_generator),
47        })
48    }
49
50    /// Generates the next payload attributes
51    pub fn next_attributes(&mut self) -> T::PayloadAttributes {
52        self.timestamp += 1;
53        (self.attributes_generator)(self.timestamp)
54    }
55
56    /// Asserts that the next event is a payload attributes event
57    pub async fn expect_attr_event(&mut self, attrs: T::PayloadAttributes) -> eyre::Result<()> {
58        let first_event = self.payload_event_stream.next().await.unwrap()?;
59        if let Events::Attributes(attr) = first_event {
60            assert_eq!(attrs.timestamp(), attr.timestamp());
61        } else {
62            panic!("Expect first event as payload attributes.")
63        }
64        Ok(())
65    }
66
67    /// Resolves the payload job with the given id like [`Self::resolve_payload`] and discards the
68    /// payload.
69    ///
70    /// # Panics
71    ///
72    /// If the payload job can not be resolved.
73    pub async fn wait_for_built_payload(&self, payload_id: PayloadId) {
74        if let Err(err) = self.resolve_payload(payload_id).await {
75            panic!("{err}")
76        }
77    }
78
79    /// Expects the next event to be a built payload event or panics
80    pub async fn expect_built_payload(&mut self) -> eyre::Result<T::BuiltPayload> {
81        let second_event = self.payload_event_stream.next().await.unwrap()?;
82        if let Events::BuiltPayload(payload) = second_event {
83            Ok(payload)
84        } else {
85            panic!("Expect a built payload event.");
86        }
87    }
88
89    /// Resolves the payload job with the given id and returns its payload.
90    ///
91    /// The job is resolved with [`PayloadKind::WaitForPending`], so if it has not built a payload
92    /// yet, this waits for its build in progress instead of racing an empty payload against it like
93    /// `engine_getPayload` does. The payload can still contain no transactions, e.g. if the pool
94    /// had none when the build started. This has no timeout of its own.
95    ///
96    /// Returns an error if the payload builder has no job with the given id, e.g. because the job
97    /// reached its deadline, or if the job fails to build a payload.
98    pub async fn resolve_payload(&self, payload_id: PayloadId) -> eyre::Result<T::BuiltPayload> {
99        self.payload_builder
100            .resolve_kind(payload_id, PayloadKind::WaitForPending)
101            .await
102            .ok_or_else(|| eyre!("payload builder has no payload job {payload_id}"))?
103            .map_err(|err| eyre!("failed to resolve payload job {payload_id}: {err}"))
104    }
105}