use crate::types::{Bytes, H160, H2048, H256, H64, U256, U64};
use serde::{de::Error, ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer};
/// The block header type returned from RPC calls.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct BlockHeader {
/// Hash of the block
pub hash: Option<H256>,
/// Hash of the parent
#[serde(rename = "parentHash")]
pub parent_hash: H256,
/// Hash of the uncles
#[serde(rename = "sha3Uncles")]
#[cfg_attr(feature = "allow-missing-fields", serde(default))]
pub uncles_hash: H256,
/// Miner/author's address.
#[serde(rename = "miner", default, deserialize_with = "null_to_default")]
pub author: H160,
/// State root hash
#[serde(rename = "stateRoot")]
pub state_root: H256,
/// Transactions root hash
#[serde(rename = "transactionsRoot")]
pub transactions_root: H256,
/// Transactions receipts root hash
#[serde(rename = "receiptsRoot")]
pub receipts_root: H256,
/// Block number. None if pending.
pub number: Option<U64>,
/// Gas Used
#[serde(rename = "gasUsed")]
pub gas_used: U256,
/// Gas Limit
#[serde(rename = "gasLimit")]
#[cfg_attr(feature = "allow-missing-fields", serde(default))]
pub gas_limit: U256,
/// Base fee per unit of gas (if past London)
#[serde(rename = "baseFeePerGas", skip_serializing_if = "Option::is_none")]
pub base_fee_per_gas: Option<U256>,
/// Extra data
#[serde(rename = "extraData")]
pub extra_data: Bytes,
/// Logs bloom
#[serde(rename = "logsBloom")]
pub logs_bloom: H2048,
/// Timestamp
pub timestamp: U256,
/// Difficulty
#[cfg_attr(feature = "allow-missing-fields", serde(default))]
pub difficulty: U256,
/// Mix Hash
#[serde(rename = "mixHash")]
pub mix_hash: Option<H256>,
/// Nonce
pub nonce: Option<H64>,
}
/// The block type returned from RPC calls.
/// This is generic over a `TX` type.
#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)]
pub struct Block<TX> {
/// Hash of the block
pub hash: Option<H256>,
/// Hash of the parent
#[serde(rename = "parentHash")]
pub parent_hash: H256,
/// Hash of the uncles
#[serde(rename = "sha3Uncles")]
#[cfg_attr(feature = "allow-missing-fields", serde(default))]
pub uncles_hash: H256,
/// Miner/author's address.
#[serde(rename = "miner", default, deserialize_with = "null_to_default")]
pub author: H160,
/// State root hash
#[serde(rename = "stateRoot")]
pub state_root: H256,
/// Transactions root hash
#[serde(rename = "transactionsRoot")]
pub transactions_root: H256,
/// Transactions receipts root hash
#[serde(rename = "receiptsRoot")]
pub receipts_root: H256,
/// Block number. None if pending.
pub number: Option<U64>,
/// Gas Used
#[serde(rename = "gasUsed")]
pub gas_used: U256,
/// Gas Limit
#[serde(rename = "gasLimit")]
#[cfg_attr(feature = "allow-missing-fields", serde(default))]
pub gas_limit: U256,
/// Base fee per unit of gas (if past London)
#[serde(rename = "baseFeePerGas", skip_serializing_if = "Option::is_none")]
pub base_fee_per_gas: Option<U256>,
/// Extra data
#[serde(rename = "extraData")]
pub extra_data: Bytes,
/// Logs bloom
#[serde(rename = "logsBloom")]
pub logs_bloom: Option<H2048>,
/// Timestamp
pub timestamp: U256,
/// Difficulty
#[cfg_attr(feature = "allow-missing-fields", serde(default))]
pub difficulty: U256,
/// Total difficulty
#[serde(rename = "totalDifficulty")]
pub total_difficulty: Option<U256>,
/// Seal fields
#[serde(default, rename = "sealFields")]
pub seal_fields: Vec<Bytes>,
/// Uncles' hashes
#[cfg_attr(feature = "allow-missing-fields", serde(default))]
pub uncles: Vec<H256>,
/// Transactions
pub transactions: Vec<TX>,
/// Size in bytes
pub size: Option<U256>,
/// Mix Hash
#[serde(rename = "mixHash")]
pub mix_hash: Option<H256>,
/// Nonce
pub nonce: Option<H64>,
}
fn null_to_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
T: Default + Deserialize<'de>,
D: Deserializer<'de>,
{
let option = Option::deserialize(deserializer)?;
Ok(option.unwrap_or_default())
}
/// Block Number
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum BlockNumber {
/// Finalized block
Finalized,
/// Safe block
Safe,
/// Latest block
Latest,
/// Earliest block (genesis)
Earliest,
/// Pending block (not yet part of the blockchain)
Pending,
/// Block by number from canon chain
Number(U64),
}
impl<T: Into<U64>> From<T> for BlockNumber {
fn from(num: T) -> Self {
BlockNumber::Number(num.into())
}
}
impl Serialize for BlockNumber {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match *self {
BlockNumber::Number(ref x) => serializer.serialize_str(&format!("0x{:x}", x)),
BlockNumber::Latest => serializer.serialize_str("latest"),
BlockNumber::Earliest => serializer.serialize_str("earliest"),
BlockNumber::Pending => serializer.serialize_str("pending"),
BlockNumber::Finalized => serializer.serialize_str("finalized"),
BlockNumber::Safe => serializer.serialize_str("safe"),
}
}
}
impl<'a> Deserialize<'a> for BlockNumber {
fn deserialize<D>(deserializer: D) -> Result<BlockNumber, D::Error>
where
D: Deserializer<'a>,
{
let value = String::deserialize(deserializer)?;
match value.as_str() {
"latest" => Ok(BlockNumber::Latest),
"earliest" => Ok(BlockNumber::Earliest),
"pending" => Ok(BlockNumber::Pending),
"finalized" => Ok(BlockNumber::Finalized),
"safe" => Ok(BlockNumber::Safe),
_ if value.starts_with("0x") => U64::from_str_radix(&value[2..], 16)
.map(BlockNumber::Number)
.map_err(|e| D::Error::custom(format!("invalid block number: {}", e))),
_ => Err(D::Error::custom("invalid block number: missing 0x prefix".to_string())),
}
}
}
/// Block Identifier
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum BlockId {
/// By Hash
Hash(H256),
/// By Number
Number(BlockNumber),
}
impl Serialize for BlockId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match *self {
BlockId::Hash(ref x) => {
let mut s = serializer.serialize_struct("BlockIdEip1898", 1)?;
s.serialize_field("blockHash", &format!("{:?}", x))?;
s.end()
}
BlockId::Number(ref num) => num.serialize(serializer),
}
}
}
impl From<U64> for BlockId {
fn from(num: U64) -> Self {
BlockNumber::Number(num).into()
}
}
impl From<BlockNumber> for BlockId {
fn from(num: BlockNumber) -> Self {
BlockId::Number(num)
}
}
impl From<H256> for BlockId {
fn from(hash: H256) -> Self {
BlockId::Hash(hash)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::Value;
#[test]
fn block_miner() {
let mut json = serde_json::json!(
{
"miner": "0x0000000000000000000000000000000000000001",
"number": "0x1b4",
"hash": "0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331",
"parentHash": "0x9646252be9520f6e71339a8df9c55e4d7619deeb018d2a3f2d21fc165dde5eb5",
"mixHash": "0x1010101010101010101010101010101010101010101010101010101010101010",
"nonce": "0x0000000000000000",
"sealFields": [
"0xe04d296d2460cfb8472af2c5fd05b5a214109c25688d3704aed5484f9a7792f2",
"0x0000000000000042"
],
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"logsBloom": "0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331",
"transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"stateRoot": "0xd5855eb08b3387c0af375e9cdb6acfc05eb8f519e419b874b6ff2ffda7ed1dff",
"difficulty": "0x27f07",
"totalDifficulty": "0x27f07",
"extraData": "0x0000000000000000000000000000000000000000000000000000000000000000",
"size": "0x27f07",
"gasLimit": "0x9f759",
"minGasPrice": "0x9f759",
"gasUsed": "0x9f759",
"timestamp": "0x54e34e8e",
"transactions": [],
"uncles": []
}
);
let block: Block<()> = serde_json::from_value(json.clone()).unwrap();
assert_eq!(block.author, H160::from_low_u64_be(1));
assert!(block.base_fee_per_gas.is_none());
// Null miner
// We test this too because it was observed that Infura nodes behave this way even though it
// goes against the ethrpc documentation.
json.as_object_mut().unwrap().insert("miner".to_string(), Value::Null);
let block: Block<()> = serde_json::from_value(json.clone()).unwrap();
assert_eq!(block.author, Default::default());
// No miner
json.as_object_mut().unwrap().remove("miner");
let block: Block<()> = serde_json::from_value(json).unwrap();
assert_eq!(block.author, Default::default());
}
#[test]
fn post_london_block() {
let json = serde_json::json!(
{
"baseFeePerGas": "0x7",
"miner": "0x0000000000000000000000000000000000000001",
"number": "0x1b4",
"hash": "0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331",
"parentHash": "0x9646252be9520f6e71339a8df9c55e4d7619deeb018d2a3f2d21fc165dde5eb5",
"mixHash": "0x1010101010101010101010101010101010101010101010101010101010101010",
"nonce": "0x0000000000000000",
"sealFields": [
"0xe04d296d2460cfb8472af2c5fd05b5a214109c25688d3704aed5484f9a7792f2",
"0x0000000000000042"
],
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"logsBloom": "0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331",
"transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"stateRoot": "0xd5855eb08b3387c0af375e9cdb6acfc05eb8f519e419b874b6ff2ffda7ed1dff",
"difficulty": "0x27f07",
"totalDifficulty": "0x27f07",
"extraData": "0x0000000000000000000000000000000000000000000000000000000000000000",
"size": "0x27f07",
"gasLimit": "0x9f759",
"minGasPrice": "0x9f759",
"gasUsed": "0x9f759",
"timestamp": "0x54e34e8e",
"transactions": [],
"uncles": []
}
);
let block: Block<()> = serde_json::from_value(json).unwrap();
assert_eq!(block.base_fee_per_gas, Some(U256::from(7)));
}
#[test]
fn serialize_deserialize_block_number() {
// BlockNumber::Latest
let serialized = serde_json::to_value(BlockNumber::Latest).unwrap();
assert_eq!(serialized, "latest");
let deserialized = serde_json::from_value::<BlockNumber>(serialized).unwrap();
assert_eq!(deserialized, BlockNumber::Latest);
// BlockNumber::Earliest
let serialized = serde_json::to_value(BlockNumber::Earliest).unwrap();
assert_eq!(serialized, "earliest");
let deserialized = serde_json::from_value::<BlockNumber>(serialized).unwrap();
assert_eq!(deserialized, BlockNumber::Earliest);
// BlockNumber::Pending
let serialized = serde_json::to_value(BlockNumber::Pending).unwrap();
assert_eq!(serialized, "pending");
let deserialized = serde_json::from_value::<BlockNumber>(serialized).unwrap();
assert_eq!(deserialized, BlockNumber::Pending);
// BlockNumber::Finalized
let serialized = serde_json::to_value(BlockNumber::Finalized).unwrap();
assert_eq!(serialized, "finalized");
let deserialized = serde_json::from_value::<BlockNumber>(serialized).unwrap();
assert_eq!(deserialized, BlockNumber::Finalized);
// BlockNumber::Safe
let serialized = serde_json::to_value(BlockNumber::Safe).unwrap();
assert_eq!(serialized, "safe");
let deserialized = serde_json::from_value::<BlockNumber>(serialized).unwrap();
assert_eq!(deserialized, BlockNumber::Safe);
// BlockNumber::Number
let serialized = serde_json::to_value(BlockNumber::Number(100.into())).unwrap();
assert_eq!(serialized, "0x64");
let deserialized = serde_json::from_value::<BlockNumber>(serialized).unwrap();
assert_eq!(deserialized, BlockNumber::Number(100.into()));
let deserialized = serde_json::from_value::<BlockNumber>("64".into());
assert_eq!(
deserialized.unwrap_err().to_string(),
"invalid block number: missing 0x prefix"
);
}
}