Skip to main content

alloy_rpc_types_beacon/
relay.rs

1//! Flashbots relay API types.
2//!
3//! See also <https://flashbots.github.io/relay-specs/>
4
5use crate::{requests::ExecutionRequestsV4, BlsPublicKey, BlsSignature};
6use alloy_primitives::{Address, B256, U256};
7use alloy_rpc_types_engine::{
8    BlobsBundleV1, BlobsBundleV2, ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3,
9};
10use serde::{Deserialize, Serialize};
11use serde_with::{serde_as, DisplayFromStr};
12
13/// Represents an entry of the `/relay/v1/builder/validators` endpoint
14#[serde_as]
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16pub struct Validator {
17    /// The slot number for the validator entry.
18    #[serde_as(as = "DisplayFromStr")]
19    pub slot: u64,
20    /// The index of the validator.
21    #[serde_as(as = "DisplayFromStr")]
22    pub validator_index: u64,
23    /// Details of the validator registration.
24    pub entry: ValidatorRegistration,
25}
26
27/// Details of a validator registration.
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29pub struct ValidatorRegistration {
30    /// The registration message.
31    pub message: ValidatorRegistrationMessage,
32    /// The signature for the registration.
33    pub signature: BlsSignature,
34}
35
36/// Represents the message of a validator registration.
37#[serde_as]
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
39pub struct ValidatorRegistrationMessage {
40    /// The fee recipient's address.
41    pub fee_recipient: Address,
42
43    /// The gas limit for the registration.
44    #[serde_as(as = "DisplayFromStr")]
45    pub gas_limit: u64,
46
47    /// The timestamp of the registration.
48    #[serde_as(as = "DisplayFromStr")]
49    pub timestamp: u64,
50
51    /// The public key of the validator.
52    pub pubkey: BlsPublicKey,
53}
54
55/// Represents public information about a block sent by a builder to the relay, or from the relay to
56/// the proposer.
57///
58/// Depending on the context, value might represent the claimed value by a builder (not necessarily
59/// a value confirmed by the relay).
60#[serde_as]
61#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
62#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
63#[cfg_attr(feature = "ssz", derive(tree_hash_derive::TreeHash))]
64pub struct BidTrace {
65    /// The slot associated with the block.
66    #[serde_as(as = "DisplayFromStr")]
67    pub slot: u64,
68    /// The parent hash of the block.
69    pub parent_hash: B256,
70    /// The hash of the block.
71    pub block_hash: B256,
72    /// The public key of the builder.
73    pub builder_pubkey: BlsPublicKey,
74    /// The public key of the proposer.
75    pub proposer_pubkey: BlsPublicKey,
76    /// The recipient of the proposer's fee.
77    pub proposer_fee_recipient: Address,
78    /// The gas limit associated with the block.
79    #[serde_as(as = "DisplayFromStr")]
80    pub gas_limit: u64,
81    /// The gas used within the block.
82    #[serde_as(as = "DisplayFromStr")]
83    pub gas_used: u64,
84    /// The value associated with the block.
85    #[serde_as(as = "DisplayFromStr")]
86    pub value: U256,
87}
88
89/// SignedBidTrace is a BidTrace with a signature
90#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
91#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
92pub struct SignedBidTrace {
93    /// The BidTrace message associated with the submission.
94    pub message: BidTrace,
95    /// The signature associated with the submission.
96    pub signature: BlsSignature,
97}
98
99/// Submission for the `/relay/v1/builder/blocks` endpoint (Bellatrix).
100///
101/// <https://github.com/attestantio/go-builder-client/blob/e54c7fffd418d88414fad808dde3ed2ac863a7f8/api/deneb/submitblockrequest.go#L13>
102#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
103#[serde(deny_unknown_fields)]
104#[cfg_attr(feature = "ssz", derive(ssz_derive::Decode, ssz_derive::Encode))]
105pub struct SignedBidSubmissionV1 {
106    /// The BidTrace message associated with the submission.
107    pub message: BidTrace,
108    /// The execution payload for the submission.
109    #[serde(with = "crate::payload::beacon_payload_v1")]
110    pub execution_payload: ExecutionPayloadV1,
111    /// The signature associated with the submission.
112    pub signature: BlsSignature,
113}
114
115/// Submission for the `/relay/v1/builder/blocks` endpoint (Capella).
116///
117/// Also known as `CapellaSubmitBlockRequest`.
118///
119/// <https://github.com/attestantio/go-builder-client/blob/e54c7fffd418d88414fad808dde3ed2ac863a7f8/api/capella/submitblockrequest.go#L13>
120#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
121#[serde(deny_unknown_fields)]
122#[cfg_attr(feature = "ssz", derive(ssz_derive::Decode, ssz_derive::Encode))]
123pub struct SignedBidSubmissionV2 {
124    /// The BidTrace message associated with the submission.
125    pub message: BidTrace,
126    /// The execution payload for the submission.
127    #[serde(with = "crate::payload::beacon_payload_v2")]
128    pub execution_payload: ExecutionPayloadV2,
129    /// The signature associated with the submission.
130    pub signature: BlsSignature,
131}
132
133/// Submission for the `/relay/v1/builder/blocks` endpoint (Deneb).
134///
135/// Also known as `DenebSubmitBlockRequest`.
136///
137/// <https://github.com/attestantio/go-builder-client/blob/e54c7fffd418d88414fad808dde3ed2ac863a7f8/api/deneb/submitblockrequest.go#L13>
138#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
139#[serde(deny_unknown_fields)]
140#[cfg_attr(feature = "ssz", derive(ssz_derive::Decode, ssz_derive::Encode))]
141pub struct SignedBidSubmissionV3 {
142    /// The BidTrace message associated with the submission.
143    pub message: BidTrace,
144    /// The execution payload for the submission.
145    #[serde(with = "crate::payload::beacon_payload_v3")]
146    pub execution_payload: ExecutionPayloadV3,
147    /// The Deneb block bundle for this bid.
148    pub blobs_bundle: BlobsBundleV1,
149    /// The signature associated with the submission.
150    pub signature: BlsSignature,
151}
152
153/// Submission for the `/relay/v1/builder/blocks` endpoint (Electra).
154///
155///
156/// Also known as `ElectraSubmitBlockRequest`.
157#[serde_as]
158#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
159#[serde(deny_unknown_fields)]
160#[cfg_attr(feature = "ssz", derive(ssz_derive::Decode, ssz_derive::Encode))]
161pub struct SignedBidSubmissionV4 {
162    /// The [`BidTrace`] message associated with the submission.
163    pub message: BidTrace,
164    /// The execution payload for the submission.
165    #[serde(with = "crate::payload::beacon_payload_v3")]
166    pub execution_payload: ExecutionPayloadV3,
167    /// The Electra block bundle for this bid.
168    pub blobs_bundle: BlobsBundleV1,
169    /// The Pectra execution requests for this bid.
170    pub execution_requests: ExecutionRequestsV4,
171    /// The signature associated with the submission.
172    pub signature: BlsSignature,
173}
174
175/// Submission for the `/relay/v1/builder/blocks` endpoint (Fulu).
176///
177///
178/// Also known as `FuluSubmitBlockRequest`.
179#[serde_as]
180#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
181#[serde(deny_unknown_fields)]
182#[cfg_attr(feature = "ssz", derive(ssz_derive::Decode, ssz_derive::Encode))]
183pub struct SignedBidSubmissionV5 {
184    /// The [`BidTrace`] message associated with the submission.
185    pub message: BidTrace,
186    /// The execution payload for the submission.
187    #[serde(with = "crate::payload::beacon_payload_v3")]
188    pub execution_payload: ExecutionPayloadV3,
189    /// The Fulu block bundle for this bid.
190    pub blobs_bundle: BlobsBundleV2,
191    /// The Pectra execution requests for this bid.
192    pub execution_requests: ExecutionRequestsV4,
193    /// The signature associated with the submission.
194    pub signature: BlsSignature,
195}
196
197/// Represents all versions of signed bid submissions (submit block requests).
198///
199/// Note: The fields are ordered starting with the most recent version so that the
200/// untagged, transparent decoding prioritises the newest version.
201#[derive(Debug, Clone, Serialize, Deserialize)]
202#[serde(untagged)]
203#[cfg_attr(feature = "ssz", derive(ssz_derive::Decode, ssz_derive::Encode))]
204#[cfg_attr(feature = "ssz", ssz(enum_behaviour = "transparent"))]
205pub enum SubmitBlockRequest {
206    /// Fulu [`SignedBidSubmissionV5`].
207    Fulu(SignedBidSubmissionV5),
208    /// Electra [`SignedBidSubmissionV4`].
209    Electra(SignedBidSubmissionV4),
210    /// Deneb [`SignedBidSubmissionV3`].
211    Deneb(SignedBidSubmissionV3),
212    /// Capella [`SignedBidSubmissionV2`].
213    Capella(SignedBidSubmissionV2),
214}
215
216impl SubmitBlockRequest {
217    /// Returns the [`SignedBidSubmissionV2`] if this is [`Self::Capella`]
218    pub const fn as_capella(&self) -> Option<&SignedBidSubmissionV2> {
219        match self {
220            Self::Capella(submission) => Some(submission),
221            _ => None,
222        }
223    }
224
225    /// Returns the [`SignedBidSubmissionV3`] if this is [`Self::Deneb`]
226    pub const fn as_deneb(&self) -> Option<&SignedBidSubmissionV3> {
227        match self {
228            Self::Deneb(submission) => Some(submission),
229            _ => None,
230        }
231    }
232
233    /// Returns the [`SignedBidSubmissionV4`] if this is [`Self::Electra`]
234    pub const fn as_electra(&self) -> Option<&SignedBidSubmissionV4> {
235        match self {
236            Self::Electra(submission) => Some(submission),
237            _ => None,
238        }
239    }
240
241    /// Returns the [`SignedBidSubmissionV5`] if this is [`Self::Fulu`]
242    pub const fn as_fulu(&self) -> Option<&SignedBidSubmissionV5> {
243        match self {
244            Self::Fulu(submission) => Some(submission),
245            _ => None,
246        }
247    }
248
249    /// Returns the underlying [`BidTrace`].
250    pub const fn bid_trace(&self) -> &BidTrace {
251        match self {
252            Self::Capella(req) => &req.message,
253            Self::Deneb(req) => &req.message,
254            Self::Electra(req) => &req.message,
255            Self::Fulu(req) => &req.message,
256        }
257    }
258}
259
260impl From<SignedBidSubmissionV2> for SubmitBlockRequest {
261    fn from(value: SignedBidSubmissionV2) -> Self {
262        Self::Capella(value)
263    }
264}
265impl From<SignedBidSubmissionV3> for SubmitBlockRequest {
266    fn from(value: SignedBidSubmissionV3) -> Self {
267        Self::Deneb(value)
268    }
269}
270impl From<SignedBidSubmissionV4> for SubmitBlockRequest {
271    fn from(value: SignedBidSubmissionV4) -> Self {
272        Self::Electra(value)
273    }
274}
275impl From<SignedBidSubmissionV5> for SubmitBlockRequest {
276    fn from(value: SignedBidSubmissionV5) -> Self {
277        Self::Fulu(value)
278    }
279}
280
281/// Query for the `/relay/v1/builder/blocks` endpoint
282#[serde_as]
283#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
284pub struct SubmitBlockRequestQuery {
285    /// If set to 1, opt into bid cancellations.
286    #[serde(skip_serializing_if = "Option::is_none")]
287    #[serde_as(as = "Option<serde_with::BoolFromInt>")]
288    pub cancellations: Option<bool>,
289}
290
291impl SubmitBlockRequestQuery {
292    /// Opt into bid cancellations.
293    pub const fn cancellations() -> Self {
294        Self { cancellations: Some(true) }
295    }
296}
297
298/// A Request to validate a [`SignedBidSubmissionV1`]
299///
300/// <https://github.com/flashbots/builder/blob/03ee71cf0a344397204f65ff6d3a917ee8e06724/eth/block-validation/api.go#L132-L136>
301#[serde_as]
302#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
303pub struct BuilderBlockValidationRequest {
304    /// The request to be validated.
305    #[serde(flatten)]
306    pub request: SignedBidSubmissionV1,
307    /// The registered gas limit for the validation request.
308    #[serde_as(as = "DisplayFromStr")]
309    pub registered_gas_limit: u64,
310}
311
312/// A Request to validate a [`SignedBidSubmissionV2`]
313///
314/// <https://github.com/flashbots/builder/blob/03ee71cf0a344397204f65ff6d3a917ee8e06724/eth/block-validation/api.go#L204-L208>
315#[serde_as]
316#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
317pub struct BuilderBlockValidationRequestV2 {
318    /// The request to be validated.
319    #[serde(flatten)]
320    pub request: SignedBidSubmissionV2,
321    /// The registered gas limit for the validation request.
322    #[serde_as(as = "DisplayFromStr")]
323    pub registered_gas_limit: u64,
324}
325
326/// A Request to validate a [`SignedBidSubmissionV3`]
327///
328/// <https://github.com/flashbots/builder/blob/7577ac81da21e760ec6693637ce2a81fe58ac9f8/eth/block-validation/api.go#L198-L202>
329#[serde_as]
330#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
331pub struct BuilderBlockValidationRequestV3 {
332    /// The request to be validated.
333    #[serde(flatten)]
334    pub request: SignedBidSubmissionV3,
335    /// The registered gas limit for the validation request.
336    #[serde_as(as = "DisplayFromStr")]
337    pub registered_gas_limit: u64,
338    /// The parent beacon block root for the validation request.
339    pub parent_beacon_block_root: B256,
340}
341
342/// A Request to validate a [`SignedBidSubmissionV4`]
343#[serde_as]
344#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
345pub struct BuilderBlockValidationRequestV4 {
346    /// The request to be validated.
347    #[serde(flatten)]
348    pub request: SignedBidSubmissionV4,
349    /// The registered gas limit for the validation request.
350    #[serde_as(as = "DisplayFromStr")]
351    pub registered_gas_limit: u64,
352    /// The parent beacon block root for the validation request.
353    pub parent_beacon_block_root: B256,
354}
355
356impl BuilderBlockValidationRequestV4 {
357    /// Converts this validation request to [`alloy_rpc_types_engine::ExecutionData`].
358    ///
359    /// Extracts the execution payload and creates the appropriate sidecar with versioned hashes and
360    /// execution requests.
361    #[cfg(all(feature = "sha2", feature = "ssz"))]
362    pub fn into_execution_data(self) -> alloy_rpc_types_engine::ExecutionData {
363        // Compute versioned hashes from blob commitments
364        let versioned_hashes = self
365            .request
366            .blobs_bundle
367            .commitments
368            .iter()
369            .map(|commitment| alloy_eips::eip4844::kzg_to_versioned_hash(commitment.as_slice()))
370            .collect();
371
372        // Create Cancun payload fields
373        let cancun_fields = alloy_rpc_types_engine::CancunPayloadFields {
374            parent_beacon_block_root: self.parent_beacon_block_root,
375            versioned_hashes,
376        };
377
378        // Convert execution requests to Requests type
379        let prague_fields = alloy_rpc_types_engine::PraguePayloadFields::new(
380            self.request.execution_requests.to_requests(),
381        );
382
383        // Create the execution payload sidecar
384        let sidecar =
385            alloy_rpc_types_engine::ExecutionPayloadSidecar::v4(cancun_fields, prague_fields);
386
387        alloy_rpc_types_engine::ExecutionData::new(self.request.execution_payload.into(), sidecar)
388    }
389}
390
391#[cfg(all(feature = "sha2", feature = "ssz"))]
392impl From<BuilderBlockValidationRequestV4> for alloy_rpc_types_engine::ExecutionData {
393    fn from(request: BuilderBlockValidationRequestV4) -> Self {
394        request.into_execution_data()
395    }
396}
397
398/// A Request to validate a [`SignedBidSubmissionV5`]
399#[serde_as]
400#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
401pub struct BuilderBlockValidationRequestV5 {
402    /// The request to be validated.
403    #[serde(flatten)]
404    pub request: SignedBidSubmissionV5,
405    /// The registered gas limit for the validation request.
406    #[serde_as(as = "DisplayFromStr")]
407    pub registered_gas_limit: u64,
408    /// The parent beacon block root for the validation request.
409    pub parent_beacon_block_root: B256,
410}
411
412impl BuilderBlockValidationRequestV5 {
413    /// Converts this validation request to [`alloy_rpc_types_engine::ExecutionData`].
414    ///
415    /// Extracts the execution payload and creates the appropriate sidecar with versioned hashes and
416    /// execution requests.
417    #[cfg(all(feature = "sha2", feature = "ssz"))]
418    pub fn into_execution_data(self) -> alloy_rpc_types_engine::ExecutionData {
419        // Compute versioned hashes from blob commitments
420        let versioned_hashes = self
421            .request
422            .blobs_bundle
423            .commitments
424            .iter()
425            .map(|commitment| alloy_eips::eip4844::kzg_to_versioned_hash(commitment.as_slice()))
426            .collect();
427
428        // Create Cancun payload fields
429        let cancun_fields = alloy_rpc_types_engine::CancunPayloadFields {
430            parent_beacon_block_root: self.parent_beacon_block_root,
431            versioned_hashes,
432        };
433
434        // Convert execution requests to Requests type
435        let prague_fields = alloy_rpc_types_engine::PraguePayloadFields::new(
436            self.request.execution_requests.to_requests(),
437        );
438
439        // Create the execution payload sidecar
440        let sidecar =
441            alloy_rpc_types_engine::ExecutionPayloadSidecar::v4(cancun_fields, prague_fields);
442
443        alloy_rpc_types_engine::ExecutionData::new(self.request.execution_payload.into(), sidecar)
444    }
445}
446
447#[cfg(all(feature = "sha2", feature = "ssz"))]
448impl From<BuilderBlockValidationRequestV5> for alloy_rpc_types_engine::ExecutionData {
449    fn from(request: BuilderBlockValidationRequestV5) -> Self {
450        request.into_execution_data()
451    }
452}
453
454/// Response type for the GET `/relay/v1/data/bidtraces/builder_blocks_received`
455///
456/// Provides [BidTrace]s for payloads that were delivered to proposers.
457/// Only submissions that were successfully verified.
458#[serde_as]
459#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
460#[allow(missing_docs)]
461pub struct BuilderBlockReceived {
462    #[serde_as(as = "DisplayFromStr")]
463    pub slot: u64,
464    pub parent_hash: B256,
465    pub block_hash: B256,
466    pub builder_pubkey: BlsPublicKey,
467    pub proposer_pubkey: BlsPublicKey,
468    pub proposer_fee_recipient: Address,
469    #[serde_as(as = "DisplayFromStr")]
470    pub gas_limit: u64,
471    #[serde_as(as = "DisplayFromStr")]
472    pub gas_used: u64,
473    #[serde_as(as = "DisplayFromStr")]
474    pub value: U256,
475    #[serde_as(as = "DisplayFromStr")]
476    pub num_tx: u64,
477    #[serde_as(as = "DisplayFromStr")]
478    pub block_number: u64,
479    #[serde_as(as = "DisplayFromStr")]
480    pub timestamp: u64,
481    #[serde_as(as = "DisplayFromStr")]
482    pub timestamp_ms: u64,
483    #[serde(default)]
484    pub optimistic_submission: bool,
485}
486
487/// Query for the GET `/relay/v1/data/bidtraces/proposer_payload_delivered`
488///
489/// Provides [BidTrace]s for payloads that were delivered to proposers.
490#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
491pub struct ProposerPayloadsDeliveredQuery {
492    /// A specific slot
493    #[serde(skip_serializing_if = "Option::is_none")]
494    pub slot: Option<u64>,
495    /// Maximum number of entries (200 max)
496    #[serde(skip_serializing_if = "Option::is_none")]
497    pub limit: Option<u64>,
498    /// Search for a specific blockhash
499    #[serde(skip_serializing_if = "Option::is_none")]
500    pub block_hash: Option<B256>,
501    /// Search for a specific EL block number
502    #[serde(skip_serializing_if = "Option::is_none")]
503    pub block_number: Option<u64>,
504    /// Filter results by a proposer public key
505    #[serde(skip_serializing_if = "Option::is_none")]
506    pub proposer_pubkey: Option<BlsPublicKey>,
507    /// Filter results by a builder public key
508    #[serde(skip_serializing_if = "Option::is_none")]
509    pub builder_pubkey: Option<BlsPublicKey>,
510    /// How to order results
511    #[serde(skip_serializing_if = "Option::is_none")]
512    pub order_by: Option<OrderBy>,
513}
514
515impl ProposerPayloadsDeliveredQuery {
516    /// Sets the specific slot
517    pub const fn slot(mut self, slot: u64) -> Self {
518        self.slot = Some(slot);
519        self
520    }
521
522    /// Sets the maximum number of entries (200 max)
523    pub const fn limit(mut self, limit: u64) -> Self {
524        self.limit = Some(limit);
525        self
526    }
527
528    /// Sets the specific blockhash
529    pub const fn block_hash(mut self, block_hash: B256) -> Self {
530        self.block_hash = Some(block_hash);
531        self
532    }
533
534    /// Sets the specific EL block number
535    pub const fn block_number(mut self, block_number: u64) -> Self {
536        self.block_number = Some(block_number);
537        self
538    }
539
540    /// Sets the proposer public key
541    pub const fn proposer_pubkey(mut self, proposer_pubkey: BlsPublicKey) -> Self {
542        self.proposer_pubkey = Some(proposer_pubkey);
543        self
544    }
545
546    /// Sets the builder public key
547    pub const fn builder_pubkey(mut self, builder_pubkey: BlsPublicKey) -> Self {
548        self.builder_pubkey = Some(builder_pubkey);
549        self
550    }
551
552    /// Configures how to order results
553    pub const fn order_by(mut self, order_by: OrderBy) -> Self {
554        self.order_by = Some(order_by);
555        self
556    }
557
558    /// Order results by descending value (highest value first)
559    pub const fn order_by_desc(self) -> Self {
560        self.order_by(OrderBy::Desc)
561    }
562
563    /// Order results by ascending value (lowest value first)
564    pub const fn order_by_asc(self) -> Self {
565        self.order_by(OrderBy::Asc)
566    }
567}
568
569/// Response for the GET `/relay/v1/data/bidtraces/proposer_payload_delivered`
570///
571/// Represents a payload that was delivered to proposers
572#[serde_as]
573#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
574pub struct ProposerPayloadDelivered {
575    /// The slot this payload belongs to.
576    #[serde_as(as = "DisplayFromStr")]
577    pub slot: u64,
578    /// Parent hash of the payload.
579    pub parent_hash: B256,
580    /// Hash of the payload..
581    pub block_hash: B256,
582    /// Builder's pubkey.
583    pub builder_pubkey: BlsPublicKey,
584    /// Proposer's BLS pubkey.
585    pub proposer_pubkey: BlsPublicKey,
586    /// The fee recipient of the payload.
587    pub proposer_fee_recipient: Address,
588    /// Gas limit used by the payload.
589    #[serde_as(as = "DisplayFromStr")]
590    pub gas_limit: u64,
591    /// Gas used by the payload.
592    #[serde_as(as = "DisplayFromStr")]
593    pub gas_used: u64,
594    /// The payload's value.
595    #[serde_as(as = "DisplayFromStr")]
596    pub value: U256,
597    /// The block number of that payload.
598    #[serde_as(as = "DisplayFromStr")]
599    pub block_number: u64,
600    /// The number of transactions in that payload.
601    #[serde_as(as = "DisplayFromStr")]
602    pub num_tx: u64,
603}
604
605/// Sort results in either ascending or descending values.
606///
607/// - `-value` - descending value (highest value first)
608/// - `value` - ascending value (lowest value first)
609#[derive(
610    Default, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize,
611)]
612pub enum OrderBy {
613    /// Sort result by descending value (highest value first)
614    #[default]
615    #[serde(rename = "-value")]
616    Desc,
617    /// Sort result by ascending value (lowest value first)
618    #[serde(rename = "value")]
619    Asc,
620}
621
622/// Query for the GET `/relay/v1/data/bidtraces/builder_blocks_received` endpoint.
623/// This endpoint provides BidTraces for builder block submissions that match the query and were
624/// verified successfully.
625#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
626pub struct BuilderBlocksReceivedQuery {
627    /// A specific slot
628    #[serde(skip_serializing_if = "Option::is_none")]
629    pub slot: Option<u64>,
630    /// Maximum number of entries (200 max)
631    #[serde(skip_serializing_if = "Option::is_none")]
632    pub limit: Option<u64>,
633    /// Search for a specific blockhash
634    #[serde(skip_serializing_if = "Option::is_none")]
635    pub block_hash: Option<B256>,
636    /// Search for a specific EL block number
637    #[serde(skip_serializing_if = "Option::is_none")]
638    pub block_number: Option<u64>,
639    /// Search for a specific builder public key.
640    #[serde(skip_serializing_if = "Option::is_none")]
641    pub builder_pubkey: Option<BlsPublicKey>,
642}
643
644impl BuilderBlocksReceivedQuery {
645    /// Sets the specific slot
646    pub const fn slot(mut self, slot: u64) -> Self {
647        self.slot = Some(slot);
648        self
649    }
650
651    /// Sets the maximum number of entries (200 max)
652    pub const fn limit(mut self, limit: u64) -> Self {
653        self.limit = Some(limit);
654        self
655    }
656
657    /// Sets the specific blockhash
658    pub const fn block_hash(mut self, block_hash: B256) -> Self {
659        self.block_hash = Some(block_hash);
660        self
661    }
662
663    /// Sets the specific EL block number
664    pub const fn block_number(mut self, block_number: u64) -> Self {
665        self.block_number = Some(block_number);
666        self
667    }
668
669    /// Sets the specific builder public key
670    pub const fn builder_pubkey(mut self, builder_pubkey: BlsPublicKey) -> Self {
671        self.builder_pubkey = Some(builder_pubkey);
672        self
673    }
674}
675
676/// Error types for the relay.
677pub mod error {
678    use super::*;
679
680    /// Error thrown by the `validateBuilderSubmission` endpoints if the message differs from
681    /// payload.
682    #[derive(Debug, thiserror::Error)]
683    pub enum ValidateBuilderSubmissionEqualityError {
684        /// Thrown if parent hash mismatches
685        #[error("incorrect ParentHash {actual}, expected {expected}")]
686        IncorrectParentHash {
687            /// The expected parent hash
688            expected: B256,
689            /// The actual parent hash
690            actual: B256,
691        },
692        /// Thrown if block hash mismatches
693        #[error("incorrect BlockHash {actual}, expected {expected}")]
694        IncorrectBlockHash {
695            /// The expected block hash
696            expected: B256,
697            /// The actual block hash
698            actual: B256,
699        },
700        /// Thrown if gas limit mismatches
701        #[error("incorrect GasLimit {actual}, expected {expected}")]
702        IncorrectGasLimit {
703            /// The expected gas limit
704            expected: u64,
705            /// The actual gas limit
706            actual: u64,
707        },
708        /// Thrown if gas used mismatches
709        #[error("incorrect GasUsed {actual}, expected {expected}")]
710        IncorrectGasUsed {
711            /// The expected gas used
712            expected: u64,
713            /// The actual gas used
714            actual: u64,
715        },
716    }
717}
718
719#[cfg(test)]
720mod tests {
721    use super::*;
722    use similar_asserts::assert_eq;
723
724    #[test]
725    fn serde_validator() {
726        let s = r#"[{"slot":"7689441","validator_index":"748813","entry":{"message":{"fee_recipient":"0xbe87be8ac54fb2a4ecb8d7935d0fc80f72c28f9f","gas_limit":"30000000","timestamp":"1688333351","pubkey":"0xb56ff6826cfa6b82fc6c2974988b1576fe5c34bd6c672f911e1d3eec1134822581d6d68f68992ad1f945b0c80468d941"},"signature":"0x8b42028d248f5a2fd41ab425408470ffde1d941ee83db3d9bde583feb22413608673dc27930383893410ef05e52ed8cf0e0291d8ed111189a065f9598176d1c51cabeaba8f628b2f92626bb58d2068292eb7682673a31473d0cdbe278e67c723"}},{"slot":"7689443","validator_index":"503252","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1680328764","pubkey":"0xa8ac80cd889110f407fd6e42d08c71faf158aa917c4c5f5d65bfcea7c4ae5231df9e14721c1980e18233fb1e79316cf6"},"signature":"0xa3e0e3190acc0be2c3a5f9c75cea5d79bebbb955f3f477794e8d7c0cbf73cd61fa0b0c3bfeb5cd9ba53a60c7bf9958640a63ecbd355a8ddb717f2ac8f413dbe4865cbae46291cb590410fa51471df9eaccb3602718df4c17f8eee750df8b5491"}},{"slot":"7689445","validator_index":"792322","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1691185175","pubkey":"0xa25455216e254c96d7ddfc029da033f6de715e91ab09b2fb6a97613b13a8e9189c43f0eaabd671b3c9aab051f6ff0630"},"signature":"0xb63ea72aac017cdfaa7b0fb9cbaffbe3c72b8ce2dd986b6e21834fc2f0accf9184e301de6c5f066bb7075d3f5d020a9e169dee80998685e20553c197ab27ef4d7b0a19f062796a228308ef33a01d0a08dbe35b02e7dca762e247c84e5ea9d170"}},{"slot":"7689446","validator_index":"888141","entry":{"message":{"fee_recipient":"0x73b9067aeeeab2e6263951ad9637971145566ba6","gas_limit":"30000000","timestamp":"1692606575","pubkey":"0x921b0309fffd798599f9564103f9ab34ebe2a3ea17ab39439e5e033ec5affb925a5ad65c0863a1d0f84365e4c1eec952"},"signature":"0x8a88068c7926d81348b773977050d093450b673af1762c0f0b416e4fcc76f277f2f117138780e62e49f5ac02d13ba5ed0e2d76df363003c4ff7ad40713ed3ef6aa3eb57580c8a3e1e6fe7245db700e541d1f2a7e88ec426c3fba82fa91b647a4"}},{"slot":"7689451","validator_index":"336979","entry":{"message":{"fee_recipient":"0xebec795c9c8bbd61ffc14a6662944748f299cacf","gas_limit":"30000000","timestamp":"1680621366","pubkey":"0x8b46eb0f36a51fcfab66910380be4d68c6323291eada9f68ad628a798a9c21ed858d62f1b15c08484b13c9cdcd0fc657"},"signature":"0x910afc415aed14a0c49cc1c2d29743018a69e517de84cee9e4ff2ea21a0d3b95c25b0cd59b8a2539fbe8e73d5e53963a0afbcf9f86db4de4ba03aa1f80d9dc1ecca18e597829e5e9269ce08b99ff347eba43c0d9c87c174f3a30422d4de800c8"}},{"slot":"7689452","validator_index":"390650","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1678460723","pubkey":"0x854464f0b798d1510a0f76a77190f15e9e67d5ac348647f5fe906539cf4ff7101fb1463f4c408b72e6fae9cfbd21ffd3"},"signature":"0xb5c3aa515cdf723f03fafd092150d6fc5453f6bcec873194927f64f65aa96241f4c5ed417e0676163c5a07af0d63f83811268096e520af3b6a5d5031e619609a0999efc03cc94a30a1175e5e5a52c66d868ebb527669be27a7b81920e44c511a"}},{"slot":"7689453","validator_index":"316626","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1680791443","pubkey":"0xa7a4ecdc53283698af917eaf0ba68e350f5cd136c2b12fa10e59f1e1fd15130da1889a28975777887d84638c85e9036c"},"signature":"0xa3f7604dafd63d9a22d66c228d7d90e83a69d9fa4b920dceeb2e7d43ef49307645725f7c4890fefce18ba41d36b4d23c09cef5109827c7fb3bc78f8526ba0bfeceb950a6f0b5d5d8ad1c8dc740267f053b4f9113141c27e1528c8557b9175e3f"}},{"slot":"7689455","validator_index":"733684","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1688895576","pubkey":"0xb2731b8efe43d723c4b621bd23e48dd00e1fe7816414e708f3607da6f17f5ba5c172ceac005591cb3e28320ae6aa81cf"},"signature":"0x85c8fd5201705413e004b0b4ef773c28d95864deab2e7698da6ea8b27c46867d03e50dae4ad523cebc1ea6205b7925810347e14f8db5396d11200c0cd099faefe254bc2844b29bf15d4d62e15f876f08ee53e2cd33ceee698d69f4f70e8eac82"}},{"slot":"7689457","validator_index":"950865","entry":{"message":{"fee_recipient":"0x8b733fe59d1190b3efa5be3f21a574ef2ab0c62b","gas_limit":"30000000","timestamp":"1696575191","pubkey":"0xa7c8fbb503de34fb92d43490533c35f0418a52ff5834462213950217b592f975caa3ac1daa3f1cdd89362d6f48ef46c1"},"signature":"0x9671727de015aa343db8a8068e27b555b59b6dcfc9b7f8b47bce820fe60cd1179dfdfc91270918edf40a918b513e5a16069e23aaf1cdd37fce0d63e3ade7ca9270ed4a64f70eb64915791e47074bf76aa3225ebd727336444b44826f2cf2a002"}},{"slot":"7689459","validator_index":"322181","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1677755315","pubkey":"0x9381507f58bd51e0ce662e6bb27796e416988bd1f64f219f8cffd2137e933a5fb4c8196ca80c653fd5f69ef378f038aa"},"signature":"0xaeb27e1b729dded42d28efcfadfc6691851749ccb19779f488a37a31e12a56cfd856e81d251fe0e7868aa539f66116b11216a9599bd51a06f386d2255381fcd9d7c698df980964a7e54428cee458e28e3ca5078db92e6837cba72276e7af3334"}},{"slot":"7689461","validator_index":"482285","entry":{"message":{"fee_recipient":"0x6d2e03b7effeae98bd302a9f836d0d6ab0002766","gas_limit":"30000000","timestamp":"1680010474","pubkey":"0x940c6e411db07f151c7c647cb09842f5979db8d89c11c3c1a08894588f1d561e59bc15bd085dc9a025aac52b1cf83d73"},"signature":"0xb4b9fab65a2c01208fd17117b83d59b1e0bb92ede9f7ac3f48f55957551b36add2c3d880e76118fecf1e01496bc3b065194ae6bcb317f1c583f70e2a67cf2a28f4f68d80fc3697941d3700468ac29aafd0118778112d253eb3c31d6bcbdc0c13"}},{"slot":"7689466","validator_index":"98883","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1680479471","pubkey":"0x8d50dad3c465c2c5cd327fd725e239915c0ba43adfdc106909665222c43b2705e9db77f8102308445ae5301131d2a843"},"signature":"0x8a597b9c3160f12bed715a0311134ce04875d05050eb6a349fcc470a610f039ce5a07eebf5332db4f2126b77ebdd1beb0df83784e01061489333aba009ecdb9767e61933f78d2fd96520769afffa3be4455d8dfc6de3fb1b2cee2133f8dd15cf"}},{"slot":"7689470","validator_index":"533204","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1695122498","pubkey":"0x88b31798f15c2857200c60ac923c5a310c204edbce0d49c755f3f0c65e688ab065870385a5b2b18972917c566ecc02a4"},"signature":"0x96033c3ac9d7083d889a723ecd99c79cb2ab3caebeac5435d2319fd300b796ca4f6261eca211b0dbb6df98ce23eba75b04203e026c5aee373d2ba579904ea06284ff58df7bd45ea9a4d9cc9b24ef15ee57e8894193d1c6c8883dace63feb77b7"}},{"slot":"7689471","validator_index":"129205","entry":{"message":{"fee_recipient":"0xed33259a056f4fb449ffb7b7e2ecb43a9b5685bf","gas_limit":"30000000","timestamp":"1695122497","pubkey":"0xb83dc440882e55185ef74631329f954e0f2d547e4f8882bd4470698d1883754eb9b5ee17a091de9730c80d5d1982f6e7"},"signature":"0xabc4d7cc48c2b4608ba49275620837a543e8a6a79d65395c9fca9794442acacf6df2fb1aca71f85472c521c4cf5797f702bd8adef32d7cd38d98334a0a11f8d197a702fa70f48d8512676e64e55a98914a0acc89b60a37046efb646f684a3917"}},{"slot":"7689472","validator_index":"225708","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1680528109","pubkey":"0xa35ae9c944764f4abb247471ad4744b5acec3156a5ec0e72f67a4417268c7c0a8c2775a9639e70ef345176c41b3cd1ba"},"signature":"0x96dd275d5eadd7648e0b8ef44070da65bd2e8d597b51e292d1f775af36595300dcd69e466b51e422f938e695c9cbacd71249d7dfadc9fdf358170082645f63a2ddc6fd82e6a68100460b6beac7603af09012ef06705900410204e8adb6c08d21"}},{"slot":"7689473","validator_index":"959108","entry":{"message":{"fee_recipient":"0xf197c6f2ac14d25ee2789a73e4847732c7f16bc9","gas_limit":"30000000","timestamp":"1696666607","pubkey":"0xaa4c7bc4848f7ea9939112c726e710db5603bc562ef006b6bf5f4644f31b9ab4daf8e3ff72f0140c077f756073dbe3bd"},"signature":"0x8de420ab9db85a658c2ba5feb59020d1e5c0c5e385f55cb877304a348609ad64ec3f3d7be1c6e847df8687bf9c045f2c06e56b4302a04df07801fbcccaf32dbeaeb854680536e13c7c2dc9372272fbf65651298bfb12bdedb58ddda3de5b26c2"}},{"slot":"7689477","validator_index":"258637","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1693935108","pubkey":"0x944bad9bc9ad0fa7b287b1e85e3bf0a5e246c871f2ce62c974d46b2968f853bdc06c22933e2be0549967343d4b01310b"},"signature":"0x906badf5ea9b63a210e7e5baa2f9b4871c9176a26b55282b148fb6eb3f433a41cabe61be612b02c1d6c071f13a374ee118b1fe71d0461c12a9595e0ed458123b0a1bbfef389aec8954803af60eca8ae982f767aa2f7f4c051f38ef630eaef8bf"}},{"slot":"7689479","validator_index":"641748","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1685364312","pubkey":"0xaf548fad3364238b9a909dc56735249b91e0fd5e330f65aa9072fe7f73024b4d8febc7cc2bd401ad8ace9a1043440b22"},"signature":"0xb36cb46aeb188463e9fec2f89b6dcb2e52489af7c852915011ff625fb24d82ded781ae864ccbd354cbbed1f02037a67a152fecc2735b598ab31c4620e7151dd20eb761c620c49dcb31704f43b631979fc545be4292bc0f193a1919255db7a5b8"}},{"slot":"7689481","validator_index":"779837","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1687790504","pubkey":"0xa39f287b3195ecaeb1c18c4f957d881a86a31f809078fe1d7525acfa751b7b0c43f369813d63d72fdd35d0e58f73bea9"},"signature":"0xb0bd69f16df95d09f02768c0625eb1d99dd6385a7db8aa72606a4b0b05a36add5671b02c554914e087f302bf9e17698f0b551a5b517ebdad68118f672bf80ea8de118d9e06808a39cf107bbc13a0cdfbfd0d5e1daf39ad4d66364a0047609dea"}},{"slot":"7689484","validator_index":"903816","entry":{"message":{"fee_recipient":"0xebec795c9c8bbd61ffc14a6662944748f299cacf","gas_limit":"30000000","timestamp":"1695066839","pubkey":"0xa1401efd4b681f3123da1c5de0f786e8c7879ceebc399227db994debf416e198ec25afecd1ee443808affd93143d183e"},"signature":"0x90234ccb98ca78ba35ae5925af7eb985c3cf6fd5f94291f881f315cf1072ab45a7dd812af52d8aede2f08d8179a5a7eb02b2b01fc8a2a792ef7077010df9f444866a03b8ec4798834dc9af8ff55fcd52f399b41d9dd9b0959d456f24aa38ac3c"}},{"slot":"7689485","validator_index":"364451","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1678669663","pubkey":"0x879658976e272aafab49be7105b27f9dea07e79f386dc4a165e17b082059c4b5628d8677203cd809a9332580d9cc28fe"},"signature":"0x8a3013425fd933630521a88a96dcc51e82f8f23cc5c243102415f7782799d39a3450bc5dc6b8a59331f78199e7729896186e700b166841195057ed6efbbd36a5a352cbe6a69ecbec27d74f9b2336f290e19940623a9712b70b59724f879f4e77"}},{"slot":"7689487","validator_index":"641598","entry":{"message":{"fee_recipient":"0x2370d6d6a4e6de417393d54abb144e740f662e01","gas_limit":"30000000","timestamp":"1683216978","pubkey":"0xa226a5c9121aee6a0e20759d877386bb50cb07de978eb89cb0a09dd9d7159117e4d610b3b80233c48bd84a1b9df5f1b2"},"signature":"0x983a0e5721dc39ae3bc35a83999f964ff7840e25e1033f033d51f40408acd07b4f8bda2bbd27f9fe793dd26e2dfe150c03577d0e2ff16d88cef0fb3bb849602d7287aac89199a4b39b1903a8dd9cd9e206ff68c391732fc6e6ef5ff2c89cb439"}},{"slot":"7689490","validator_index":"954682","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1696695974","pubkey":"0xadbe2aeecfc01016938dc0a90986d36975acdd1e3cbb3461bb917a7eaf260c1a387867e47f3d9a1dd56778f552a0ed6a"},"signature":"0x85dea1b1b01ecaf3240571ecddcfc4eaa06b4e23b1c2cc6db646164716f96b8ad46bf0687f5bb840a7468514ac18439205bfb16941cdafc6853c4c65271cd113be72f9428469d0815a7169c70ae37709a19ad669e709d6a9cfd90bc471309bc6"}},{"slot":"7689496","validator_index":"833362","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1690132607","pubkey":"0xa4e9ee836facfaf67dab10c59c10ed6d3a0f007799129f13b009be56ed87ad6b08b30b315f38b6cc38f2fdb747dac587"},"signature":"0xb73b40c9766004d6471b3355fc6ffa765a442c0687852687ed89120cdcebf03c37ed2c087fd254492b2b7f11c2446ec5116e40f442367196af967e6905ca5fb333a2b3a9705c0d302817038199b43c1dd36124fe6085d610c491176d1d5d0cff"}},{"slot":"7689498","validator_index":"202233","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1678891455","pubkey":"0x8cb0401c024bb74a481b9253ce61af57ad263e7ab542166922c13e46d76c2593b4735a85e5fbaba9d1cd63d8996981e1"},"signature":"0xb9364c714be2c11651b8c6854b0fc5872d8b109fa1c8b67e4e1bf71555a364e3003a88d98292a09487d20b3e89a0716c1030a559ce70aeef582fab3d6820fde678249d3952c809c53e56940cc74ba6fcc112bb94adf72d41451e5e69788f98da"}},{"slot":"7689500","validator_index":"380674","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1693299181","pubkey":"0x9801236e1e78426d7b4be81f828419bd1aac3b2041ebed2af9683eca259e9915c6f5d45d9e3021a8218f8b6a73550ee4"},"signature":"0x98961cb2f920898e656ddaf66d11bcfd808179cf8f313687260eb760bd065f1f5ae79a37587575a1c6c46671845c63e409cc01bca97823adc0e6dbbc280327df886df4fb8aa7e1d310311bc80e29fed90a6ae3346017d1b5d20b32beed8fd477"}},{"slot":"7689502","validator_index":"486859","entry":{"message":{"fee_recipient":"0x0b91a6bafdae6ae32d864ed9a0e883a5ca9a02dd","gas_limit":"30000000","timestamp":"1680003847","pubkey":"0x99225f70bb2c9310835a367e95b34c0d6021b5ec9bf350f4d8f0fc4dce34c9c16f4299b788ad0d27001b0efd2712d494"},"signature":"0x86ab16d4b4e686b20d1bb9d532975961acd797e02d9c9e48d13805ec2ba71df9e69a63c3b254b8e640fcc26f651ad243155430095caa6c5770b52039f1d6a9312e0d8f9dd2fb4fe2d35d372075a93b14e745be91e7eb1f28f0f5bf2c62f7584e"}},{"slot":"7689503","validator_index":"70348","entry":{"message":{"fee_recipient":"0x6d2e03b7effeae98bd302a9f836d0d6ab0002766","gas_limit":"30000000","timestamp":"1680011532","pubkey":"0x820dd8b5396377da3f2d4972d4c94fbad401bbf4b3a56e570a532f77c1802f2cc310bf969bb6aa96d90ea2708c562ed6"},"signature":"0xb042608d02c4ca053c6b9e22804af99421c47eda96ce585d1df6f37cbf59cfd6830a3592f6de543232135c42bb3da9cd13ecf5aea2e3b802114dc08877e9023a7cf6d75e28ca30d1df3f3c98bddd36b4f521e63895179a7e8c3752f5cbc681ea"}}]"#;
727
728        let validators: Vec<Validator> = serde_json::from_str(s).unwrap();
729        let json: serde_json::Value = serde_json::from_str(s).unwrap();
730
731        assert_eq!(json, serde_json::to_value(validators).unwrap());
732    }
733
734    #[test]
735    fn bellatrix_bid_submission() {
736        let s = r#"{"message":{"slot":"1","parent_hash":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","block_hash":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","builder_pubkey":"0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a", "proposer_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a","proposer_fee_recipient":"0xabcf8e0d4e9587369b2301d0790347320302cc09","gas_limit":"1","gas_used":"1","value":"1"},"execution_payload":{"parent_hash":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","fee_recipient":"0xabcf8e0d4e9587369b2301d0790347320302cc09","state_root":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","receipts_root":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","logs_bloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prev_randao":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","block_number":"1","gas_limit":"1","gas_used":"1","timestamp":"1","extra_data":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","base_fee_per_gas":"1","block_hash":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","transactions":["0x02f878831469668303f51d843b9ac9f9843b9aca0082520894c93269b73096998db66be0441e836d873535cb9c8894a19041886f000080c001a031cc29234036afbf9a1fb9476b463367cb1f957ac0b919b69bbc798436e604aaa018c4e9c3914eb27aadd0b91e10b18655739fcf8c1fc398763a9f1beecb8ddc86"]},"signature":"0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"}"#;
737
738        let bid = serde_json::from_str::<SignedBidSubmissionV1>(s).unwrap();
739        let json: serde_json::Value = serde_json::from_str(s).unwrap();
740        assert_eq!(json, serde_json::to_value(bid).unwrap());
741    }
742
743    #[test]
744    fn capella_bid_submission() {
745        let s = r#"{"message":{"slot":"1","parent_hash":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","block_hash":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","builder_pubkey":"0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a", "proposer_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a","proposer_fee_recipient":"0xabcf8e0d4e9587369b2301d0790347320302cc09","gas_limit":"1","gas_used":"1","value":"1"},"execution_payload":{"parent_hash":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","fee_recipient":"0xabcf8e0d4e9587369b2301d0790347320302cc09","state_root":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","receipts_root":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","logs_bloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prev_randao":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","block_number":"1","gas_limit":"1","gas_used":"1","timestamp":"1","extra_data":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","base_fee_per_gas":"1","block_hash":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","transactions":["0x02f878831469668303f51d843b9ac9f9843b9aca0082520894c93269b73096998db66be0441e836d873535cb9c8894a19041886f000080c001a031cc29234036afbf9a1fb9476b463367cb1f957ac0b919b69bbc798436e604aaa018c4e9c3914eb27aadd0b91e10b18655739fcf8c1fc398763a9f1beecb8ddc86"],"withdrawals":[{"index":"1","validator_index":"1","address":"0xabcf8e0d4e9587369b2301d0790347320302cc09","amount":"32000000000"}]},"signature":"0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"}"#;
746
747        let bid = serde_json::from_str::<SignedBidSubmissionV2>(s).unwrap();
748        let json: serde_json::Value = serde_json::from_str(s).unwrap();
749        assert_eq!(json, serde_json::to_value(bid).unwrap());
750    }
751
752    #[test]
753    fn deneb_bid_submission() {
754        let s = r#"{"message":{"slot":"1","parent_hash":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","block_hash":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","builder_pubkey":"0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a", "proposer_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a","proposer_fee_recipient":"0xabcf8e0d4e9587369b2301d0790347320302cc09","gas_limit":"1","gas_used":"1","value":"1"},"execution_payload":{"parent_hash":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","fee_recipient":"0xabcf8e0d4e9587369b2301d0790347320302cc09","state_root":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","receipts_root":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","logs_bloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prev_randao":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","block_number":"1","gas_limit":"1","gas_used":"1","timestamp":"1","extra_data":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","base_fee_per_gas":"1","block_hash":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","transactions":["0x02f878831469668303f51d843b9ac9f9843b9aca0082520894c93269b73096998db66be0441e836d873535cb9c8894a19041886f000080c001a031cc29234036afbf9a1fb9476b463367cb1f957ac0b919b69bbc798436e604aaa018c4e9c3914eb27aadd0b91e10b18655739fcf8c1fc398763a9f1beecb8ddc86"],"withdrawals":[{"index":"1","validator_index":"1","address":"0xabcf8e0d4e9587369b2301d0790347320302cc09","amount":"32000000000"}], "blob_gas_used":"1","excess_blob_gas":"1"},"blobs_bundle":{"commitments":[],"proofs":[],"blobs":[]},"signature":"0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"}"#;
755
756        let bid = serde_json::from_str::<SignedBidSubmissionV3>(s).unwrap();
757        let json: serde_json::Value = serde_json::from_str(s).unwrap();
758        assert_eq!(json, serde_json::to_value(bid).unwrap());
759    }
760
761    #[test]
762    fn electra_bid_submission() {
763        let s = include_str!("examples/relay_builder_block_validation_request_v4.json");
764
765        let bid = serde_json::from_str::<SignedBidSubmissionV4>(s).unwrap();
766        let json: serde_json::Value = serde_json::from_str(s).unwrap();
767        assert_eq!(json, serde_json::to_value(bid).unwrap());
768    }
769
770    #[cfg(feature = "ssz")]
771    #[test]
772    fn capella_bid_submission_ssz() {
773        use ssz::{Decode, Encode};
774
775        let bytes = include_bytes!("examples/relay_signed_bid_submission_capella.ssz").to_vec();
776        let bid = SignedBidSubmissionV2::from_ssz_bytes(&bytes).unwrap();
777        assert_eq!(bytes, bid.as_ssz_bytes());
778    }
779
780    #[test]
781    fn serde_builder_block_received() {
782        let s = r#"{
783    "slot": "1",
784    "parent_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
785    "block_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
786    "builder_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
787    "proposer_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
788    "proposer_fee_recipient": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
789    "gas_limit": "1",
790    "gas_used": "1",
791    "value": "1",
792    "block_number": "1",
793    "num_tx": "1",
794    "timestamp": "1",
795    "timestamp_ms": "1"
796  }"#;
797        let block: BuilderBlockReceived = serde_json::from_str(s).unwrap();
798        let to_json: serde_json::Value = serde_json::to_value(block.clone()).unwrap();
799        let block2: BuilderBlockReceived = serde_json::from_value(to_json).unwrap();
800        assert_eq!(block, block2);
801    }
802
803    #[test]
804    fn test_can_parse_validation_request_body() {
805        const VALIDATION_REQUEST_BODY: &str = include_str!("examples/relay_single_payload.json");
806
807        let _validation_request_body: BuilderBlockValidationRequest =
808            serde_json::from_str(VALIDATION_REQUEST_BODY).unwrap();
809    }
810
811    #[test]
812    fn test_serde_proposer_payload_delivered() {
813        let s = r#"{
814    "slot": "1",
815    "parent_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
816    "block_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
817    "builder_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
818    "proposer_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
819    "proposer_fee_recipient": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
820    "gas_limit": "1",
821    "gas_used": "1",
822    "value": "1",
823    "block_number": "1",
824    "num_tx": "1"
825  }"#;
826        let payload: ProposerPayloadDelivered = serde_json::from_str(s).unwrap();
827        let json: serde_json::Value = serde_json::from_str(s).unwrap();
828        let to_json: serde_json::Value = serde_json::to_value(payload).unwrap();
829        assert_eq!(json, to_json);
830    }
831
832    #[cfg(all(feature = "sha2", feature = "ssz"))]
833    #[test]
834    fn test_builder_block_validation_request_v4_to_execution_data() {
835        // Use the existing test data for SignedBidSubmissionV4 and wrap it in a validation request
836        let bid_submission_json =
837            include_str!("examples/relay_builder_block_validation_request_v4.json");
838        let bid_submission: SignedBidSubmissionV4 =
839            serde_json::from_str(bid_submission_json).unwrap();
840
841        let request = BuilderBlockValidationRequestV4 {
842            request: bid_submission,
843            registered_gas_limit: 30000000,
844            parent_beacon_block_root: B256::from_slice(&[0x12; 32]),
845        };
846
847        let expected_payload = request.request.execution_payload.clone();
848        let expected_parent_beacon_block_root = request.parent_beacon_block_root;
849        let expected_hashes: Vec<_> = request
850            .request
851            .blobs_bundle
852            .commitments
853            .iter()
854            .map(|commitment| alloy_eips::eip4844::kzg_to_versioned_hash(commitment.as_slice()))
855            .collect();
856
857        let execution_data = request.into_execution_data();
858
859        // Verify the execution payload is V3
860        match &execution_data.payload {
861            alloy_rpc_types_engine::ExecutionPayload::V3(payload) => {
862                assert_eq!(payload, &expected_payload);
863            }
864            _ => panic!("Expected ExecutionPayload::V3"),
865        }
866
867        // Verify the sidecar contains the correct parent beacon block root
868        assert_eq!(
869            execution_data.sidecar.parent_beacon_block_root(),
870            Some(expected_parent_beacon_block_root)
871        );
872
873        // Verify versioned hashes are computed correctly
874        let cancun_fields = execution_data.sidecar.cancun().unwrap();
875        assert_eq!(cancun_fields.parent_beacon_block_root, expected_parent_beacon_block_root);
876        assert_eq!(cancun_fields.versioned_hashes, expected_hashes);
877
878        // Verify execution requests are present
879        assert!(execution_data.sidecar.prague().is_some());
880    }
881
882    #[cfg(all(feature = "sha2", feature = "ssz"))]
883    #[test]
884    fn test_builder_block_validation_request_v4_from_trait() {
885        let bid_submission_json =
886            include_str!("examples/relay_builder_block_validation_request_v4.json");
887        let bid_submission: SignedBidSubmissionV4 =
888            serde_json::from_str(bid_submission_json).unwrap();
889
890        let request = BuilderBlockValidationRequestV4 {
891            request: bid_submission,
892            registered_gas_limit: 30000000,
893            parent_beacon_block_root: B256::from_slice(&[0x12; 32]),
894        };
895
896        let execution_data_owned: alloy_rpc_types_engine::ExecutionData = request.clone().into();
897
898        let execution_data_method = request.into_execution_data();
899        assert_eq!(execution_data_owned.payload, execution_data_method.payload);
900        assert_eq!(
901            execution_data_owned.sidecar.parent_beacon_block_root(),
902            execution_data_method.sidecar.parent_beacon_block_root()
903        );
904    }
905
906    #[cfg(all(feature = "sha2", feature = "ssz"))]
907    #[test]
908    fn test_builder_block_validation_request_v5_to_execution_data() {
909        // Create a test SignedBidSubmissionV5 based on the V4 test data structure
910        let bid_submission_v4_json =
911            include_str!("examples/relay_builder_block_validation_request_v4.json");
912        let bid_submission_v4: SignedBidSubmissionV4 =
913            serde_json::from_str(bid_submission_v4_json).unwrap();
914
915        // Convert to V5 structure (same data, but with BlobsBundleV2)
916        let bid_submission_v5 = SignedBidSubmissionV5 {
917            message: bid_submission_v4.message.clone(),
918            execution_payload: bid_submission_v4.execution_payload.clone(),
919            blobs_bundle: alloy_rpc_types_engine::BlobsBundleV2 {
920                commitments: bid_submission_v4.blobs_bundle.commitments.clone(),
921                proofs: bid_submission_v4.blobs_bundle.proofs.clone(),
922                blobs: bid_submission_v4.blobs_bundle.blobs.clone(),
923            },
924            execution_requests: bid_submission_v4.execution_requests.clone(),
925            signature: bid_submission_v4.signature,
926        };
927
928        let request = BuilderBlockValidationRequestV5 {
929            request: bid_submission_v5,
930            registered_gas_limit: 30000000,
931            parent_beacon_block_root: B256::from_slice(&[0x13; 32]),
932        };
933
934        // Store references before calling to_execution_data() which consumes self
935        let expected_payload = request.request.execution_payload.clone();
936        let expected_parent_beacon_block_root = request.parent_beacon_block_root;
937        let expected_hashes: Vec<_> = request
938            .request
939            .blobs_bundle
940            .commitments
941            .iter()
942            .map(|commitment| alloy_eips::eip4844::kzg_to_versioned_hash(commitment.as_slice()))
943            .collect();
944
945        // Test the convenience method
946        let execution_data = request.into_execution_data();
947
948        // Verify the execution payload is V3
949        match &execution_data.payload {
950            alloy_rpc_types_engine::ExecutionPayload::V3(payload) => {
951                assert_eq!(payload, &expected_payload);
952            }
953            _ => panic!("Expected ExecutionPayload::V3"),
954        }
955
956        // Verify the sidecar contains the correct parent beacon block root
957        assert_eq!(
958            execution_data.sidecar.parent_beacon_block_root(),
959            Some(expected_parent_beacon_block_root)
960        );
961
962        // Verify versioned hashes are computed correctly
963        let cancun_fields = execution_data.sidecar.cancun().unwrap();
964        assert_eq!(cancun_fields.parent_beacon_block_root, expected_parent_beacon_block_root);
965        assert_eq!(cancun_fields.versioned_hashes, expected_hashes);
966
967        // Verify execution requests are present
968        assert!(execution_data.sidecar.prague().is_some());
969    }
970
971    #[cfg(all(feature = "sha2", feature = "ssz"))]
972    #[test]
973    fn test_builder_block_validation_request_v5_from_trait() {
974        // Create a test SignedBidSubmissionV5 based on the V4 test data structure
975        let bid_submission_v4_json =
976            include_str!("examples/relay_builder_block_validation_request_v4.json");
977        let bid_submission_v4: SignedBidSubmissionV4 =
978            serde_json::from_str(bid_submission_v4_json).unwrap();
979
980        // Convert to V5 structure (same data, but with BlobsBundleV2)
981        let bid_submission_v5 = SignedBidSubmissionV5 {
982            message: bid_submission_v4.message.clone(),
983            execution_payload: bid_submission_v4.execution_payload.clone(),
984            blobs_bundle: alloy_rpc_types_engine::BlobsBundleV2 {
985                commitments: bid_submission_v4.blobs_bundle.commitments.clone(),
986                proofs: bid_submission_v4.blobs_bundle.proofs.clone(),
987                blobs: bid_submission_v4.blobs_bundle.blobs.clone(),
988            },
989            execution_requests: bid_submission_v4.execution_requests.clone(),
990            signature: bid_submission_v4.signature,
991        };
992
993        let request = BuilderBlockValidationRequestV5 {
994            request: bid_submission_v5,
995            registered_gas_limit: 30000000,
996            parent_beacon_block_root: B256::from_slice(&[0x13; 32]),
997        };
998
999        let execution_data_owned: alloy_rpc_types_engine::ExecutionData = request.clone().into();
1000
1001        let execution_data_method = request.into_execution_data();
1002        assert_eq!(execution_data_owned.payload, execution_data_method.payload);
1003        assert_eq!(
1004            execution_data_owned.sidecar.parent_beacon_block_root(),
1005            execution_data_method.sidecar.parent_beacon_block_root()
1006        );
1007    }
1008}