ic_web3_rs/types/
block.rs

1use crate::types::{Bytes, H160, H2048, H256, H64, U256, U64};
2use serde::{de::Error, ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer};
3
4/// The block header type returned from RPC calls.
5#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
6pub struct BlockHeader {
7    /// Hash of the block
8    pub hash: Option<H256>,
9    /// Hash of the parent
10    #[serde(rename = "parentHash")]
11    pub parent_hash: H256,
12    /// Hash of the uncles
13    #[serde(rename = "sha3Uncles")]
14    pub uncles_hash: H256,
15    /// Miner/author's address.
16    #[serde(rename = "miner", default, deserialize_with = "null_to_default")]
17    pub author: H160,
18    /// State root hash
19    #[serde(rename = "stateRoot")]
20    pub state_root: H256,
21    /// Transactions root hash
22    #[serde(rename = "transactionsRoot")]
23    pub transactions_root: H256,
24    /// Transactions receipts root hash
25    #[serde(rename = "receiptsRoot")]
26    pub receipts_root: H256,
27    /// Block number. None if pending.
28    pub number: Option<U64>,
29    /// Gas Used
30    #[serde(rename = "gasUsed")]
31    pub gas_used: U256,
32    /// Gas Limit
33    #[serde(rename = "gasLimit")]
34    pub gas_limit: U256,
35    /// Base fee per unit of gas (if past London)
36    #[serde(rename = "baseFeePerGas", skip_serializing_if = "Option::is_none")]
37    pub base_fee_per_gas: Option<U256>,
38    /// Extra data
39    #[serde(rename = "extraData")]
40    pub extra_data: Bytes,
41    /// Logs bloom
42    #[serde(rename = "logsBloom")]
43    pub logs_bloom: H2048,
44    /// Timestamp
45    pub timestamp: U256,
46    /// Difficulty
47    pub difficulty: U256,
48    /// Mix Hash
49    #[serde(rename = "mixHash")]
50    pub mix_hash: Option<H256>,
51    /// Nonce
52    pub nonce: Option<H64>,
53}
54
55/// The block type returned from RPC calls.
56/// This is generic over a `TX` type.
57#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)]
58pub struct Block<TX> {
59    /// Hash of the block
60    pub hash: Option<H256>,
61    /// Hash of the parent
62    #[serde(rename = "parentHash")]
63    pub parent_hash: H256,
64    /// Hash of the uncles
65    #[serde(rename = "sha3Uncles")]
66    pub uncles_hash: H256,
67    /// Miner/author's address.
68    #[serde(rename = "miner", default, deserialize_with = "null_to_default")]
69    pub author: H160,
70    /// State root hash
71    #[serde(rename = "stateRoot")]
72    pub state_root: H256,
73    /// Transactions root hash
74    #[serde(rename = "transactionsRoot")]
75    pub transactions_root: H256,
76    /// Transactions receipts root hash
77    #[serde(rename = "receiptsRoot")]
78    pub receipts_root: H256,
79    /// Block number. None if pending.
80    pub number: Option<U64>,
81    /// Gas Used
82    #[serde(rename = "gasUsed")]
83    pub gas_used: U256,
84    /// Gas Limit
85    #[serde(rename = "gasLimit")]
86    pub gas_limit: U256,
87    /// Base fee per unit of gas (if past London)
88    #[serde(rename = "baseFeePerGas", skip_serializing_if = "Option::is_none")]
89    pub base_fee_per_gas: Option<U256>,
90    /// Extra data
91    #[serde(rename = "extraData")]
92    pub extra_data: Bytes,
93    /// Logs bloom
94    #[serde(rename = "logsBloom")]
95    pub logs_bloom: Option<H2048>,
96    /// Timestamp
97    pub timestamp: U256,
98    /// Difficulty
99    pub difficulty: U256,
100    /// Total difficulty
101    #[serde(rename = "totalDifficulty")]
102    pub total_difficulty: Option<U256>,
103    /// Seal fields
104    #[serde(default, rename = "sealFields")]
105    pub seal_fields: Vec<Bytes>,
106    /// Uncles' hashes
107    pub uncles: Vec<H256>,
108    /// Transactions
109    pub transactions: Vec<TX>,
110    /// Size in bytes
111    pub size: Option<U256>,
112    /// Mix Hash
113    #[serde(rename = "mixHash")]
114    pub mix_hash: Option<H256>,
115    /// Nonce
116    pub nonce: Option<H64>,
117}
118
119fn null_to_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
120where
121    T: Default + Deserialize<'de>,
122    D: Deserializer<'de>,
123{
124    let option = Option::deserialize(deserializer)?;
125    Ok(option.unwrap_or_default())
126}
127
128/// Block Number
129#[derive(Copy, Clone, Debug, PartialEq)]
130pub enum BlockNumber {
131    /// Latest block
132    Latest,
133    /// Earliest block (genesis)
134    Earliest,
135    /// Pending block (not yet part of the blockchain)
136    Pending,
137    /// Block by number from canon chain
138    Number(U64),
139}
140
141impl<T: Into<U64>> From<T> for BlockNumber {
142    fn from(num: T) -> Self {
143        BlockNumber::Number(num.into())
144    }
145}
146
147impl Serialize for BlockNumber {
148    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
149    where
150        S: Serializer,
151    {
152        match *self {
153            BlockNumber::Number(ref x) => serializer.serialize_str(&format!("0x{:x}", x)),
154            BlockNumber::Latest => serializer.serialize_str("latest"),
155            BlockNumber::Earliest => serializer.serialize_str("earliest"),
156            BlockNumber::Pending => serializer.serialize_str("pending"),
157        }
158    }
159}
160
161impl<'a> Deserialize<'a> for BlockNumber {
162    fn deserialize<D>(deserializer: D) -> Result<BlockNumber, D::Error>
163    where
164        D: Deserializer<'a>,
165    {
166        let value = String::deserialize(deserializer)?;
167        match value.as_str() {
168            "latest" => Ok(BlockNumber::Latest),
169            "earliest" => Ok(BlockNumber::Earliest),
170            "pending" => Ok(BlockNumber::Pending),
171            _ if value.starts_with("0x") => U64::from_str_radix(&value[2..], 16)
172                .map(BlockNumber::Number)
173                .map_err(|e| D::Error::custom(format!("invalid block number: {}", e))),
174            _ => Err(D::Error::custom("invalid block number: missing 0x prefix".to_string())),
175        }
176    }
177}
178
179/// Block Identifier
180#[derive(Copy, Clone, Debug, PartialEq)]
181pub enum BlockId {
182    /// By Hash
183    Hash(H256),
184    /// By Number
185    Number(BlockNumber),
186}
187
188impl Serialize for BlockId {
189    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
190    where
191        S: Serializer,
192    {
193        match *self {
194            BlockId::Hash(ref x) => {
195                let mut s = serializer.serialize_struct("BlockIdEip1898", 1)?;
196                s.serialize_field("blockHash", &format!("{:?}", x))?;
197                s.end()
198            }
199            BlockId::Number(ref num) => num.serialize(serializer),
200        }
201    }
202}
203
204impl From<U64> for BlockId {
205    fn from(num: U64) -> Self {
206        BlockNumber::Number(num).into()
207    }
208}
209
210impl From<BlockNumber> for BlockId {
211    fn from(num: BlockNumber) -> Self {
212        BlockId::Number(num)
213    }
214}
215
216impl From<H256> for BlockId {
217    fn from(hash: H256) -> Self {
218        BlockId::Hash(hash)
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225    use serde_json::Value;
226
227    #[test]
228    fn block_miner() {
229        let mut json = serde_json::json!(
230        {
231            "miner": "0x0000000000000000000000000000000000000001",
232            "number": "0x1b4",
233            "hash": "0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331",
234            "parentHash": "0x9646252be9520f6e71339a8df9c55e4d7619deeb018d2a3f2d21fc165dde5eb5",
235            "mixHash": "0x1010101010101010101010101010101010101010101010101010101010101010",
236            "nonce": "0x0000000000000000",
237            "sealFields": [
238              "0xe04d296d2460cfb8472af2c5fd05b5a214109c25688d3704aed5484f9a7792f2",
239              "0x0000000000000042"
240            ],
241            "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
242            "logsBloom":  "0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331",
243            "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
244            "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
245            "stateRoot": "0xd5855eb08b3387c0af375e9cdb6acfc05eb8f519e419b874b6ff2ffda7ed1dff",
246            "difficulty": "0x27f07",
247            "totalDifficulty": "0x27f07",
248            "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000",
249            "size": "0x27f07",
250            "gasLimit": "0x9f759",
251            "minGasPrice": "0x9f759",
252            "gasUsed": "0x9f759",
253            "timestamp": "0x54e34e8e",
254            "transactions": [],
255            "uncles": []
256          }
257        );
258
259        let block: Block<()> = serde_json::from_value(json.clone()).unwrap();
260        assert_eq!(block.author, H160::from_low_u64_be(1));
261        assert!(block.base_fee_per_gas.is_none());
262
263        // Null miner
264        // We test this too because it was observed that Infura nodes behave this way even though it
265        // goes against the ethrpc documentation.
266        json.as_object_mut().unwrap().insert("miner".to_string(), Value::Null);
267        let block: Block<()> = serde_json::from_value(json.clone()).unwrap();
268        assert_eq!(block.author, Default::default());
269
270        // No miner
271        json.as_object_mut().unwrap().remove("miner");
272        let block: Block<()> = serde_json::from_value(json).unwrap();
273        assert_eq!(block.author, Default::default());
274    }
275
276    #[test]
277    fn post_london_block() {
278        let json = serde_json::json!(
279        {
280            "baseFeePerGas": "0x7",
281            "miner": "0x0000000000000000000000000000000000000001",
282            "number": "0x1b4",
283            "hash": "0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331",
284            "parentHash": "0x9646252be9520f6e71339a8df9c55e4d7619deeb018d2a3f2d21fc165dde5eb5",
285            "mixHash": "0x1010101010101010101010101010101010101010101010101010101010101010",
286            "nonce": "0x0000000000000000",
287            "sealFields": [
288              "0xe04d296d2460cfb8472af2c5fd05b5a214109c25688d3704aed5484f9a7792f2",
289              "0x0000000000000042"
290            ],
291            "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
292            "logsBloom":  "0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331",
293            "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
294            "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
295            "stateRoot": "0xd5855eb08b3387c0af375e9cdb6acfc05eb8f519e419b874b6ff2ffda7ed1dff",
296            "difficulty": "0x27f07",
297            "totalDifficulty": "0x27f07",
298            "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000",
299            "size": "0x27f07",
300            "gasLimit": "0x9f759",
301            "minGasPrice": "0x9f759",
302            "gasUsed": "0x9f759",
303            "timestamp": "0x54e34e8e",
304            "transactions": [],
305            "uncles": []
306          }
307        );
308
309        let block: Block<()> = serde_json::from_value(json).unwrap();
310        assert_eq!(block.base_fee_per_gas, Some(U256::from(7)));
311    }
312
313    #[test]
314    fn serialize_deserialize_block_number() {
315        // BlockNumber::Latest
316        let serialized = serde_json::to_value(BlockNumber::Latest).unwrap();
317        assert_eq!(serialized, "latest");
318        let deserialized = serde_json::from_value::<BlockNumber>(serialized).unwrap();
319        assert_eq!(deserialized, BlockNumber::Latest);
320
321        // BlockNumber::Earliest
322        let serialized = serde_json::to_value(BlockNumber::Earliest).unwrap();
323        assert_eq!(serialized, "earliest");
324        let deserialized = serde_json::from_value::<BlockNumber>(serialized).unwrap();
325        assert_eq!(deserialized, BlockNumber::Earliest);
326
327        // BlockNumber::Pending
328        let serialized = serde_json::to_value(BlockNumber::Pending).unwrap();
329        assert_eq!(serialized, "pending");
330        let deserialized = serde_json::from_value::<BlockNumber>(serialized).unwrap();
331        assert_eq!(deserialized, BlockNumber::Pending);
332
333        // BlockNumber::Number
334        let serialized = serde_json::to_value(BlockNumber::Number(100.into())).unwrap();
335        assert_eq!(serialized, "0x64");
336        let deserialized = serde_json::from_value::<BlockNumber>(serialized).unwrap();
337        assert_eq!(deserialized, BlockNumber::Number(100.into()));
338        let deserialized = serde_json::from_value::<BlockNumber>("64".into());
339        assert_eq!(
340            deserialized.unwrap_err().to_string(),
341            "invalid block number: missing 0x prefix"
342        );
343    }
344}