Skip to main content

alloy_rpc_types_engine/
testing.rs

1//! Testing namespace types for building a block in a single call.
2//!
3//! This follows the `testing_buildBlockV1` specification.
4
5use crate::PayloadAttributes;
6use alloc::vec::Vec;
7use alloy_primitives::{Bytes, B256};
8
9/// Capability string for `testing_buildBlockV1`.
10pub const TESTING_BUILD_BLOCK_V1: &str = "testing_buildBlockV1";
11
12/// Request payload for `testing_buildBlockV1`.
13///
14/// See the [Execution API `testing_buildBlockV1` specification][spec].
15///
16/// [spec]: https://github.com/ethereum/execution-apis/blob/main/src/testing/testing_buildBlockV1.yaml
17#[derive(Clone, Debug, Default, PartialEq, Eq)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize))]
19#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
20pub struct TestingBuildBlockRequestV1 {
21    /// Parent block hash of the block to build.
22    pub parent_block_hash: B256,
23    /// Payload attributes.
24    pub payload_attributes: PayloadAttributes,
25    /// Raw signed transactions to force-include in order.
26    pub transactions: Vec<Bytes>,
27    /// Optional extra data for the block header.
28    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
29    pub extra_data: Option<Bytes>,
30}
31
32impl TestingBuildBlockRequestV1 {
33    /// Consumes the request and returns the positional JSON-RPC parameters.
34    pub fn into_params(self) -> (B256, PayloadAttributes, Vec<Bytes>, Option<Bytes>) {
35        (self.parent_block_hash, self.payload_attributes, self.transactions, self.extra_data)
36    }
37}
38
39#[cfg(feature = "serde")]
40/// Deserializes from either the camelCase object form produced by [`serde::Serialize`] or the
41/// positional JSON-RPC params form defined by the execution-apis spec.
42///
43/// `transactions: null` and omitted transactions are both represented as an empty transaction
44/// vector because this type stores transactions as `Vec<Bytes>`.
45impl<'de> serde::Deserialize<'de> for TestingBuildBlockRequestV1 {
46    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
47    where
48        D: serde::Deserializer<'de>,
49    {
50        #[derive(serde::Deserialize)]
51        #[serde(rename_all = "camelCase")]
52        struct RequestObject {
53            parent_block_hash: B256,
54            payload_attributes: PayloadAttributes,
55            #[serde(default)]
56            transactions: Option<Vec<Bytes>>,
57            #[serde(default)]
58            extra_data: Option<Bytes>,
59        }
60
61        #[derive(serde::Deserialize)]
62        #[serde(untagged)]
63        enum Request {
64            Object(RequestObject),
65            Params4(B256, PayloadAttributes, Option<Vec<Bytes>>, Option<Bytes>),
66            Params3(B256, PayloadAttributes, Option<Vec<Bytes>>),
67            Params2(B256, PayloadAttributes),
68        }
69
70        Ok(match Request::deserialize(deserializer)? {
71            Request::Object(request) => Self {
72                parent_block_hash: request.parent_block_hash,
73                payload_attributes: request.payload_attributes,
74                transactions: request.transactions.unwrap_or_default(),
75                extra_data: request.extra_data,
76            },
77            Request::Params4(parent_block_hash, payload_attributes, transactions, extra_data) => {
78                Self {
79                    parent_block_hash,
80                    payload_attributes,
81                    transactions: transactions.unwrap_or_default(),
82                    extra_data,
83                }
84            }
85            Request::Params3(parent_block_hash, payload_attributes, transactions) => Self {
86                parent_block_hash,
87                payload_attributes,
88                transactions: transactions.unwrap_or_default(),
89                extra_data: None,
90            },
91            Request::Params2(parent_block_hash, payload_attributes) => Self {
92                parent_block_hash,
93                payload_attributes,
94                transactions: Vec::new(),
95                extra_data: None,
96            },
97        })
98    }
99}
100
101#[cfg(all(test, feature = "serde"))]
102mod tests {
103    use super::*;
104    use alloy_primitives::Address;
105    use serde_json::json;
106    use similar_asserts::assert_eq;
107
108    fn parent_block_hash() -> B256 {
109        "0xaf51811799f22260e5b4e1f95504dae760505f102dcb2e9ca7d897d8a40124a1".parse().unwrap()
110    }
111
112    fn payload_attributes_json() -> serde_json::Value {
113        json!({
114            "parentBeaconBlockRoot": B256::ZERO,
115            "prevRandao": B256::ZERO,
116            "suggestedFeeRecipient": Address::ZERO,
117            "timestamp": "0x1ce",
118            "withdrawals": [],
119        })
120    }
121
122    fn payload_attributes() -> PayloadAttributes {
123        serde_json::from_value(payload_attributes_json()).unwrap()
124    }
125
126    #[test]
127    fn deserialize_testing_build_block_request_object() {
128        let expected = TestingBuildBlockRequestV1 {
129            parent_block_hash: parent_block_hash(),
130            payload_attributes: payload_attributes(),
131            transactions: vec![Bytes::from(vec![0x12, 0x34])],
132            extra_data: Some(Bytes::default()),
133        };
134        let value = serde_json::to_value(&expected).unwrap();
135
136        let actual: TestingBuildBlockRequestV1 = serde_json::from_value(value).unwrap();
137
138        assert_eq!(actual, expected);
139    }
140
141    #[test]
142    fn deserialize_testing_build_block_request_params_with_null_transactions() {
143        let value = json!([
144            "0xaf51811799f22260e5b4e1f95504dae760505f102dcb2e9ca7d897d8a40124a1",
145            payload_attributes_json(),
146            null,
147            "0x"
148        ]);
149
150        let actual: TestingBuildBlockRequestV1 = serde_json::from_value(value).unwrap();
151
152        assert_eq!(
153            actual,
154            TestingBuildBlockRequestV1 {
155                parent_block_hash: parent_block_hash(),
156                payload_attributes: payload_attributes(),
157                transactions: Vec::new(),
158                extra_data: Some(Bytes::default()),
159            }
160        );
161    }
162
163    #[test]
164    fn deserialize_testing_build_block_request_params_with_transactions() {
165        let value = json!([
166            "0xaf51811799f22260e5b4e1f95504dae760505f102dcb2e9ca7d897d8a40124a1",
167            payload_attributes_json(),
168            ["0x1234"]
169        ]);
170
171        let actual: TestingBuildBlockRequestV1 = serde_json::from_value(value).unwrap();
172
173        assert_eq!(
174            actual,
175            TestingBuildBlockRequestV1 {
176                parent_block_hash: parent_block_hash(),
177                payload_attributes: payload_attributes(),
178                transactions: vec![Bytes::from(vec![0x12, 0x34])],
179                extra_data: None,
180            }
181        );
182    }
183}