Skip to main content

ethrex_rpc/types/
block.rs

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