1use super::transaction::RpcTransaction;
2use ethrex_common::{
3 H256, serde_utils,
4 types::{Block, BlockBody, BlockHash, BlockHeader, BlockNumber, Withdrawal},
5};
6use ethrex_rlp::encode::RLPEncode;
7
8use crate::utils::RpcErr;
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Serialize, Deserialize)]
12#[serde(rename_all = "camelCase")]
13pub struct RpcBlock {
14 pub hash: H256,
15 #[serde(with = "serde_utils::u64::hex_str")]
16 pub size: u64,
17 #[serde(flatten)]
18 pub header: BlockHeader,
19 #[serde(flatten)]
20 pub body: BlockBodyWrapper,
21}
22
23#[derive(Debug, Serialize, Deserialize)]
24#[serde(untagged)]
25pub enum BlockBodyWrapper {
26 Full(FullBlockBody),
27 OnlyHashes(OnlyHashesBlockBody),
28}
29
30#[derive(Debug, Serialize, Deserialize)]
31pub struct FullBlockBody {
32 pub transactions: Vec<RpcTransaction>,
33 pub uncles: Vec<H256>,
34 pub withdrawals: Vec<Withdrawal>,
35}
36
37#[derive(Debug, Serialize, Deserialize)]
38pub struct OnlyHashesBlockBody {
39 pub transactions: Vec<H256>,
41 pub uncles: Vec<H256>,
42 pub withdrawals: Vec<Withdrawal>,
43}
44
45impl TryInto<Block> for RpcBlock {
46 type Error = String;
47
48 fn try_into(self) -> Result<Block, Self::Error> {
49 let block_body = if let BlockBodyWrapper::Full(body) = self.body {
50 body
51 } else {
52 return Err("Expected full block body from RPC".to_owned());
53 };
54
55 let transactions = block_body.transactions.into_iter().map(|t| t.tx).collect();
56
57 Ok(Block {
58 header: self.header,
59 body: BlockBody {
60 transactions,
61 ommers: Vec::new(),
62 withdrawals: Some(block_body.withdrawals),
63 },
64 })
65 }
66}
67
68impl RpcBlock {
69 pub fn build(
70 header: BlockHeader,
71 body: BlockBody,
72 hash: H256,
73 full_transactions: bool,
74 ) -> Result<RpcBlock, RpcErr> {
75 let size = Block::new(header.clone(), body.clone()).length();
76 let body_wrapper = if full_transactions {
77 BlockBodyWrapper::Full(FullBlockBody::from_body(body, header.number, hash)?)
78 } else {
79 BlockBodyWrapper::OnlyHashes(OnlyHashesBlockBody {
80 transactions: body.transactions.iter().map(|t| t.hash()).collect(),
81 uncles: body.ommers.iter().map(|ommer| ommer.hash()).collect(),
82 withdrawals: body.withdrawals.unwrap_or_default(),
83 })
84 };
85
86 Ok(RpcBlock {
87 hash,
88 size: size as u64,
89 header,
90 body: body_wrapper,
91 })
92 }
93}
94
95impl FullBlockBody {
96 pub fn from_body(
97 body: BlockBody,
98 block_number: BlockNumber,
99 block_hash: BlockHash,
100 ) -> Result<FullBlockBody, RpcErr> {
101 let mut transactions = Vec::new();
102 for (index, tx) in body.transactions.iter().enumerate() {
103 transactions.push(RpcTransaction::build(
104 tx.clone(),
105 Some(block_number),
106 Some(block_hash),
107 Some(index),
108 )?);
109 }
110 Ok(FullBlockBody {
111 transactions,
112 uncles: body.ommers.iter().map(|ommer| ommer.hash()).collect(),
113 withdrawals: body.withdrawals.unwrap_or_default(),
114 })
115 }
116}
117#[cfg(test)]
118mod test {
119
120 use bytes::Bytes;
121 use ethrex_common::{
122 Address, Bloom, H256, U256,
123 constants::EMPTY_KECCAK_HASH,
124 types::{EIP1559Transaction, Transaction, TxKind},
125 };
126 use std::str::FromStr;
127
128 use super::*;
129
130 #[test]
131 fn serialize_block() {
132 let block_header = BlockHeader {
133 parent_hash: H256::from_str(
134 "0x48e29e7357408113a4166e04e9f1aeff0680daa2b97ba93df6512a73ddf7a154",
135 )
136 .unwrap(),
137 ommers_hash: H256::from_str(
138 "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
139 )
140 .unwrap(),
141 coinbase: Address::from_str("0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba").unwrap(),
142 state_root: H256::from_str(
143 "0x9de6f95cb4ff4ef22a73705d6ba38c4b927c7bca9887ef5d24a734bb863218d9",
144 )
145 .unwrap(),
146 transactions_root: H256::from_str(
147 "0x578602b2b7e3a3291c3eefca3a08bc13c0d194f9845a39b6f3bcf843d9fed79d",
148 )
149 .unwrap(),
150 receipts_root: H256::from_str(
151 "0x035d56bac3f47246c5eed0e6642ca40dc262f9144b582f058bc23ded72aa72fa",
152 )
153 .unwrap(),
154 logs_bloom: Bloom::from([0; 256]),
155 difficulty: U256::zero(),
156 number: 1,
157 gas_limit: 0x016345785d8a0000,
158 gas_used: 0xa8de,
159 timestamp: 0x03e8,
160 extra_data: Bytes::new(),
161 prev_randao: H256::zero(),
162 nonce: 0x0000000000000000,
163 base_fee_per_gas: Some(0x07),
164 withdrawals_root: Some(
165 H256::from_str(
166 "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
167 )
168 .unwrap(),
169 ),
170 blob_gas_used: Some(0x00),
171 excess_blob_gas: Some(0x00),
172 parent_beacon_block_root: Some(H256::zero()),
173 requests_hash: Some(*EMPTY_KECCAK_HASH),
174 ..Default::default()
175 };
176
177 let tx = EIP1559Transaction {
178 nonce: 0,
179 max_fee_per_gas: 78,
180 max_priority_fee_per_gas: 17,
181 to: TxKind::Call(Address::from_slice(
182 &hex::decode("6177843db3138ae69679A54b95cf345ED759450d").unwrap(),
183 )),
184 value: 3000000000000000_u64.into(),
185 data: Bytes::from_static(b"0x1568"),
186 signature_r: U256::from_str_radix(
187 "151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65d",
188 16,
189 )
190 .unwrap(),
191 signature_s: U256::from_str_radix(
192 "64c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4",
193 16,
194 )
195 .unwrap(),
196 signature_y_parity: false,
197 chain_id: 3151908,
198 gas_limit: 63000,
199 access_list: vec![(
200 Address::from_slice(
201 &hex::decode("6177843db3138ae69679A54b95cf345ED759450d").unwrap(),
202 ),
203 vec![],
204 )],
205 ..Default::default()
206 };
207
208 let block_body = BlockBody {
209 transactions: vec![Transaction::EIP1559Transaction(tx)],
210 ommers: vec![],
211 withdrawals: Some(vec![]),
212 };
213 let hash = block_header.hash();
214
215 let block = RpcBlock::build(block_header, block_body, hash, true).unwrap();
216 let expected_block = r#"{"hash":"0x94fb81ef7259ad4cef032745a2a5254babe26037f2850d320b872692f7c60178","size":"0x2f7","parentHash":"0x48e29e7357408113a4166e04e9f1aeff0680daa2b97ba93df6512a73ddf7a154","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba","stateRoot":"0x9de6f95cb4ff4ef22a73705d6ba38c4b927c7bca9887ef5d24a734bb863218d9","transactionsRoot":"0x578602b2b7e3a3291c3eefca3a08bc13c0d194f9845a39b6f3bcf843d9fed79d","receiptsRoot":"0x035d56bac3f47246c5eed0e6642ca40dc262f9144b582f058bc23ded72aa72fa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x16345785d8a0000","gasUsed":"0xa8de","timestamp":"0x3e8","extraData":"0x","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x7","withdrawalsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","blobGasUsed":"0x0","excessBlobGas":"0x0","parentBeaconBlockRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","requestsHash":"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470","transactions":[{"type":"0x2","nonce":"0x0","to":"0x6177843db3138ae69679a54b95cf345ed759450d","gas":"0xf618","value":"0xaa87bee538000","input":"0x307831353638","maxPriorityFeePerGas":"0x11","maxFeePerGas":"0x4e","gasPrice":"0x4e","accessList":[{"address":"0x6177843db3138ae69679a54b95cf345ed759450d","storageKeys":[]}],"chainId":"0x301824","yParity":"0x0","v":"0x0","r":"0x151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65d","s":"0x64c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4","blockNumber":"0x1","blockHash":"0x94fb81ef7259ad4cef032745a2a5254babe26037f2850d320b872692f7c60178","from":"0x35af8ea983a3ba94c655e19b82b932a30d6b9558","hash":"0x0b8c8f37731d9493916b06d666c3fd5dee2c3bbda06dfe866160d717e00dda91","transactionIndex":"0x0"}],"uncles":[],"withdrawals":[]}"#;
217 assert_eq!(serde_json::to_string(&block).unwrap(), expected_block)
218 }
219}