use crate::{ Address, Bytes, GotExpected, Header, SealedHeader, TransactionSigned, TransactionSignedEcRecovered, Withdrawals, B256, }; pub use alloy_eips::eip1898::{ BlockHashOrNumber, BlockId, BlockNumHash, BlockNumberOrTag, ForkBlock, RpcBlockHash, }; use alloy_rlp::{RlpDecodable, RlpEncodable}; use derive_more::{Deref, DerefMut}; #[cfg(any(test, feature = "arbitrary"))] use proptest::prelude::prop_compose; #[cfg(any(test, feature = "arbitrary"))] pub use reth_primitives_traits::test_utils::{generate_valid_header, valid_header_strategy}; use reth_primitives_traits::Requests; use serde::{Deserialize, Serialize}; #[cfg(not(feature = "std"))] use alloc::vec::Vec; // HACK(onbjerg): we need this to always set `requests` to `None` since we might otherwise generate // a block with `None` withdrawals and `Some` requests, in which case we end up trying to decode the // requests as withdrawals #[cfg(any(feature = "arbitrary", test))] prop_compose! { pub fn empty_requests_strategy()(_ in 0..1) -> Option { None } } /// Ethereum full block. /// /// Withdrawals can be optionally included at the end of the RLP encoded message. #[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(rlp, 25))] #[derive( Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, Deref, RlpEncodable, RlpDecodable, )] #[rlp(trailing)] pub struct Block { /// Block header. #[deref] pub header: Header, /// Transactions in this block. pub body: Vec, /// Ommers/uncles header. pub ommers: Vec
, /// Block withdrawals. pub withdrawals: Option, /// Block requests. pub requests: Option, } impl Block { /// Calculate the header hash and seal the block so that it can't be changed. pub fn seal_slow(self) -> SealedBlock { SealedBlock { header: self.header.seal_slow(), body: self.body, ommers: self.ommers, withdrawals: self.withdrawals, requests: self.requests, } } /// Seal the block with a known hash. /// /// WARNING: This method does not perform validation whether the hash is correct. pub fn seal(self, hash: B256) -> SealedBlock { SealedBlock { header: self.header.seal(hash), body: self.body, ommers: self.ommers, withdrawals: self.withdrawals, requests: self.requests, } } /// Expensive operation that recovers transaction signer. See [`SealedBlockWithSenders`]. pub fn senders(&self) -> Option> { TransactionSigned::recover_signers(&self.body, self.body.len()) } /// Transform into a [`BlockWithSenders`]. /// /// # Panics /// /// If the number of senders does not match the number of transactions in the block /// and the signer recovery for one of the transactions fails. /// /// Note: this is expected to be called with blocks read from disk. #[track_caller] pub fn with_senders_unchecked(self, senders: Vec
) -> BlockWithSenders { self.try_with_senders_unchecked(senders).expect("stored block is valid") } /// Transform into a [`BlockWithSenders`] using the given senders. /// /// If the number of senders does not match the number of transactions in the block, this falls /// back to manually recovery, but _without ensuring that the signature has a low `s` value_. /// See also [`TransactionSigned::recover_signer_unchecked`] /// /// Returns an error if a signature is invalid. #[track_caller] pub fn try_with_senders_unchecked( self, senders: Vec
, ) -> Result { let senders = if self.body.len() == senders.len() { senders } else { let Some(senders) = TransactionSigned::recover_signers_unchecked(&self.body, self.body.len()) else { return Err(self) }; senders }; Ok(BlockWithSenders { block: self, senders }) } /// **Expensive**. Transform into a [`BlockWithSenders`] by recovering senders in the contained /// transactions. /// /// Returns `None` if a transaction is invalid. pub fn with_recovered_senders(self) -> Option { let senders = self.senders()?; Some(BlockWithSenders { block: self, senders }) } /// Returns whether or not the block contains any blob transactions. #[inline] pub fn has_blob_transactions(&self) -> bool { self.body.iter().any(|tx| tx.is_eip4844()) } /// Returns whether or not the block contains any EIP-7702 transactions. #[inline] pub fn has_eip7702_transactions(&self) -> bool { self.body.iter().any(|tx| tx.is_eip7702()) } /// Returns an iterator over all blob transactions of the block #[inline] pub fn blob_transactions_iter(&self) -> impl Iterator + '_ { self.body.iter().filter(|tx| tx.is_eip4844()) } /// Returns only the blob transactions, if any, from the block body. #[inline] pub fn blob_transactions(&self) -> Vec<&TransactionSigned> { self.blob_transactions_iter().collect() } /// Returns an iterator over all blob versioned hashes from the block body. #[inline] pub fn blob_versioned_hashes_iter(&self) -> impl Iterator + '_ { self.blob_transactions_iter() .filter_map(|tx| tx.as_eip4844().map(|blob_tx| &blob_tx.blob_versioned_hashes)) .flatten() } /// Returns all blob versioned hashes from the block body. #[inline] pub fn blob_versioned_hashes(&self) -> Vec<&B256> { self.blob_versioned_hashes_iter().collect() } /// Calculates a heuristic for the in-memory size of the [`Block`]. #[inline] pub fn size(&self) -> usize { self.header.size() + // take into account capacity self.body.iter().map(TransactionSigned::size).sum::() + self.body.capacity() * core::mem::size_of::() + self.ommers.iter().map(Header::size).sum::() + self.ommers.capacity() * core::mem::size_of::
() + self.withdrawals.as_ref().map_or(core::mem::size_of::>(), Withdrawals::total_size) } } #[cfg(any(test, feature = "arbitrary"))] impl<'a> arbitrary::Arbitrary<'a> for Block { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { // first generate up to 100 txs let transactions = (0..100) .map(|_| TransactionSigned::arbitrary(u)) .collect::>>()?; // then generate up to 2 ommers let ommers = (0..2).map(|_| Header::arbitrary(u)).collect::>>()?; Ok(Self { header: u.arbitrary()?, body: transactions, ommers, // for now just generate empty requests, see HACK above requests: u.arbitrary()?, withdrawals: u.arbitrary()?, }) } } /// Sealed block with senders recovered from transactions. #[derive(Debug, Clone, PartialEq, Eq, Default, Deref, DerefMut)] pub struct BlockWithSenders { /// Block #[deref] #[deref_mut] pub block: Block, /// List of senders that match the transactions in the block pub senders: Vec
, } impl BlockWithSenders { /// New block with senders. Return none if len of tx and senders does not match pub fn new(block: Block, senders: Vec
) -> Option { (block.body.len() == senders.len()).then_some(Self { block, senders }) } /// Seal the block with a known hash. /// /// WARNING: This method does not perform validation whether the hash is correct. #[inline] pub fn seal(self, hash: B256) -> SealedBlockWithSenders { let Self { block, senders } = self; SealedBlockWithSenders { block: block.seal(hash), senders } } /// Calculate the header hash and seal the block with senders so that it can't be changed. #[inline] pub fn seal_slow(self) -> SealedBlockWithSenders { SealedBlockWithSenders { block: self.block.seal_slow(), senders: self.senders } } /// Split Structure to its components #[inline] pub fn into_components(self) -> (Block, Vec
) { (self.block, self.senders) } /// Returns an iterator over all transactions in the block. #[inline] pub fn transactions(&self) -> impl Iterator + '_ { self.block.body.iter() } /// Returns an iterator over all transactions and their sender. #[inline] pub fn transactions_with_sender( &self, ) -> impl Iterator + '_ { self.senders.iter().zip(self.block.body.iter()) } /// Consumes the block and returns the transactions of the block. #[inline] pub fn into_transactions(self) -> Vec { self.block.body } /// Returns an iterator over all transactions in the chain. #[inline] pub fn into_transactions_ecrecovered( self, ) -> impl Iterator { self.block.body.into_iter().zip(self.senders).map(|(tx, sender)| tx.with_signer(sender)) } } /// Sealed Ethereum full block. /// /// Withdrawals can be optionally included at the end of the RLP encoded message. #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] #[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(rlp, 32))] #[derive( Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, Deref, DerefMut, RlpEncodable, RlpDecodable, )] #[rlp(trailing)] pub struct SealedBlock { /// Locked block header. #[deref] #[deref_mut] pub header: SealedHeader, /// Transactions with signatures. pub body: Vec, /// Ommer/uncle headers pub ommers: Vec
, /// Block withdrawals. pub withdrawals: Option, /// Block requests. pub requests: Option, } impl SealedBlock { /// Create a new sealed block instance using the sealed header and block body. #[inline] pub fn new(header: SealedHeader, body: BlockBody) -> Self { let BlockBody { transactions, ommers, withdrawals, requests } = body; Self { header, body: transactions, ommers, withdrawals, requests } } /// Header hash. #[inline] pub const fn hash(&self) -> B256 { self.header.hash() } /// Splits the sealed block into underlying components #[inline] pub fn split(self) -> (SealedHeader, Vec, Vec
) { (self.header, self.body, self.ommers) } /// Splits the [`BlockBody`] and [`SealedHeader`] into separate components #[inline] pub fn split_header_body(self) -> (SealedHeader, BlockBody) { ( self.header, BlockBody { transactions: self.body, ommers: self.ommers, withdrawals: self.withdrawals, requests: self.requests, }, ) } /// Returns an iterator over all blob transactions of the block #[inline] pub fn blob_transactions_iter(&self) -> impl Iterator + '_ { self.body.iter().filter(|tx| tx.is_eip4844()) } /// Returns only the blob transactions, if any, from the block body. #[inline] pub fn blob_transactions(&self) -> Vec<&TransactionSigned> { self.blob_transactions_iter().collect() } /// Returns an iterator over all blob versioned hashes from the block body. #[inline] pub fn blob_versioned_hashes_iter(&self) -> impl Iterator + '_ { self.blob_transactions_iter() .filter_map(|tx| tx.as_eip4844().map(|blob_tx| &blob_tx.blob_versioned_hashes)) .flatten() } /// Returns all blob versioned hashes from the block body. #[inline] pub fn blob_versioned_hashes(&self) -> Vec<&B256> { self.blob_versioned_hashes_iter().collect() } /// Expensive operation that recovers transaction signer. See [`SealedBlockWithSenders`]. pub fn senders(&self) -> Option> { TransactionSigned::recover_signers(&self.body, self.body.len()) } /// Seal sealed block with recovered transaction senders. pub fn seal_with_senders(self) -> Option { self.try_seal_with_senders().ok() } /// Seal sealed block with recovered transaction senders. pub fn try_seal_with_senders(self) -> Result { match self.senders() { Some(senders) => Ok(SealedBlockWithSenders { block: self, senders }), None => Err(self), } } /// Transform into a [`SealedBlockWithSenders`]. /// /// # Panics /// /// If the number of senders does not match the number of transactions in the block /// and the signer recovery for one of the transactions fails. #[track_caller] pub fn with_senders_unchecked(self, senders: Vec
) -> SealedBlockWithSenders { self.try_with_senders_unchecked(senders).expect("stored block is valid") } /// Transform into a [`SealedBlockWithSenders`] using the given senders. /// /// If the number of senders does not match the number of transactions in the block, this falls /// back to manually recovery, but _without ensuring that the signature has a low `s` value_. /// See also [`TransactionSigned::recover_signer_unchecked`] /// /// Returns an error if a signature is invalid. #[track_caller] pub fn try_with_senders_unchecked( self, senders: Vec
, ) -> Result { let senders = if self.body.len() == senders.len() { senders } else { let Some(senders) = TransactionSigned::recover_signers_unchecked(&self.body, self.body.len()) else { return Err(self) }; senders }; Ok(SealedBlockWithSenders { block: self, senders }) } /// Unseal the block pub fn unseal(self) -> Block { Block { header: self.header.unseal(), body: self.body, ommers: self.ommers, withdrawals: self.withdrawals, requests: self.requests, } } /// Calculates a heuristic for the in-memory size of the [`SealedBlock`]. #[inline] pub fn size(&self) -> usize { self.header.size() + // take into account capacity self.body.iter().map(TransactionSigned::size).sum::() + self.body.capacity() * core::mem::size_of::() + self.ommers.iter().map(Header::size).sum::() + self.ommers.capacity() * core::mem::size_of::
() + self.withdrawals.as_ref().map_or(core::mem::size_of::>(), Withdrawals::total_size) } /// Calculates the total gas used by blob transactions in the sealed block. pub fn blob_gas_used(&self) -> u64 { self.blob_transactions().iter().filter_map(|tx| tx.blob_gas_used()).sum() } /// Returns whether or not the block contains any blob transactions. #[inline] pub fn has_blob_transactions(&self) -> bool { self.body.iter().any(|tx| tx.is_eip4844()) } /// Returns whether or not the block contains any eip-7702 transactions. #[inline] pub fn has_eip7702_transactions(&self) -> bool { self.body.iter().any(|tx| tx.is_eip7702()) } /// Ensures that the transaction root in the block header is valid. /// /// The transaction root is the Keccak 256-bit hash of the root node of the trie structure /// populated with each transaction in the transactions list portion of the block. /// /// # Returns /// /// Returns `Ok(())` if the calculated transaction root matches the one stored in the header, /// indicating that the transactions in the block are correctly represented in the trie. /// /// Returns `Err(error)` if the transaction root validation fails, providing a `GotExpected` /// error containing the calculated and expected roots. pub fn ensure_transaction_root_valid(&self) -> Result<(), GotExpected> { let calculated_root = crate::proofs::calculate_transaction_root(&self.body); if self.header.transactions_root != calculated_root { return Err(GotExpected { got: calculated_root, expected: self.header.transactions_root, }) } Ok(()) } /// Returns a vector of transactions RLP encoded with [`TransactionSigned::encode_enveloped`]. pub fn raw_transactions(&self) -> Vec { self.body.iter().map(|tx| tx.envelope_encoded()).collect() } } impl From for Block { fn from(block: SealedBlock) -> Self { block.unseal() } } /// Sealed block with senders recovered from transactions. #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, Deref, DerefMut)] pub struct SealedBlockWithSenders { /// Sealed block #[deref] #[deref_mut] pub block: SealedBlock, /// List of senders that match transactions from block. pub senders: Vec
, } impl SealedBlockWithSenders { /// New sealed block with sender. Return none if len of tx and senders does not match pub fn new(block: SealedBlock, senders: Vec
) -> Option { (block.body.len() == senders.len()).then_some(Self { block, senders }) } /// Split Structure to its components #[inline] pub fn into_components(self) -> (SealedBlock, Vec
) { (self.block, self.senders) } /// Returns the unsealed [`BlockWithSenders`] #[inline] pub fn unseal(self) -> BlockWithSenders { let Self { block, senders } = self; BlockWithSenders { block: block.unseal(), senders } } /// Returns an iterator over all transactions in the block. #[inline] pub fn transactions(&self) -> impl Iterator + '_ { self.block.body.iter() } /// Returns an iterator over all transactions and their sender. #[inline] pub fn transactions_with_sender( &self, ) -> impl Iterator + '_ { self.senders.iter().zip(self.block.body.iter()) } /// Consumes the block and returns the transactions of the block. #[inline] pub fn into_transactions(self) -> Vec { self.block.body } /// Returns an iterator over all transactions in the chain. #[inline] pub fn into_transactions_ecrecovered( self, ) -> impl Iterator { self.block.body.into_iter().zip(self.senders).map(|(tx, sender)| tx.with_signer(sender)) } } /// A response to `GetBlockBodies`, containing bodies if any bodies were found. /// /// Withdrawals can be optionally included at the end of the RLP encoded message. #[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(rlp, 10))] #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, RlpEncodable, RlpDecodable, )] #[rlp(trailing)] pub struct BlockBody { /// Transactions in the block pub transactions: Vec, /// Uncle headers for the given block pub ommers: Vec
, /// Withdrawals in the block. pub withdrawals: Option, /// Requests in the block. pub requests: Option, } impl BlockBody { /// Create a [`Block`] from the body and its header. // todo(onbjerg): should this not just take `self`? its used in one place pub fn create_block(&self, header: Header) -> Block { Block { header, body: self.transactions.clone(), ommers: self.ommers.clone(), withdrawals: self.withdrawals.clone(), requests: self.requests.clone(), } } /// Calculate the transaction root for the block body. pub fn calculate_tx_root(&self) -> B256 { crate::proofs::calculate_transaction_root(&self.transactions) } /// Calculate the ommers root for the block body. pub fn calculate_ommers_root(&self) -> B256 { crate::proofs::calculate_ommers_root(&self.ommers) } /// Calculate the withdrawals root for the block body, if withdrawals exist. If there are no /// withdrawals, this will return `None`. pub fn calculate_withdrawals_root(&self) -> Option { self.withdrawals.as_ref().map(|w| crate::proofs::calculate_withdrawals_root(w)) } /// Calculate the requests root for the block body, if requests exist. If there are no /// requests, this will return `None`. pub fn calculate_requests_root(&self) -> Option { self.requests.as_ref().map(|r| crate::proofs::calculate_requests_root(&r.0)) } /// Calculates a heuristic for the in-memory size of the [`BlockBody`]. #[inline] pub fn size(&self) -> usize { self.transactions.iter().map(TransactionSigned::size).sum::() + self.transactions.capacity() * core::mem::size_of::() + self.ommers.iter().map(Header::size).sum::() + self.ommers.capacity() * core::mem::size_of::
() + self.withdrawals .as_ref() .map_or(core::mem::size_of::>(), Withdrawals::total_size) } } impl From for BlockBody { fn from(block: Block) -> Self { Self { transactions: block.body, ommers: block.ommers, withdrawals: block.withdrawals, requests: block.requests, } } } #[cfg(any(test, feature = "arbitrary"))] impl<'a> arbitrary::Arbitrary<'a> for BlockBody { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { // first generate up to 100 txs let transactions = (0..100) .map(|_| TransactionSigned::arbitrary(u)) .collect::>>()?; // then generate up to 2 ommers let ommers = (0..2).map(|_| Header::arbitrary(u)).collect::>>()?; // for now just generate empty requests, see HACK above Ok(Self { transactions, ommers, requests: None, withdrawals: u.arbitrary()? }) } } #[cfg(test)] mod tests { use super::{BlockNumberOrTag::*, *}; use crate::hex_literal::hex; use alloy_eips::eip1898::HexStringMissingPrefixError; use alloy_rlp::{Decodable, Encodable}; use std::str::FromStr; /// Check parsing according to EIP-1898. #[test] fn can_parse_blockid_u64() { let num = serde_json::json!( {"blockNumber": "0xaf"} ); let id = serde_json::from_value::(num); assert_eq!(id.unwrap(), BlockId::from(175)); } #[test] fn can_parse_block_hash() { let block_hash = B256::from_str("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") .unwrap(); let block_hash_json = serde_json::json!( { "blockHash": "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"} ); let id = serde_json::from_value::(block_hash_json).unwrap(); assert_eq!(id, BlockId::from(block_hash,)); } #[test] fn can_parse_block_hash_with_canonical() { let block_hash = B256::from_str("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") .unwrap(); let block_id = BlockId::Hash(RpcBlockHash::from_hash(block_hash, Some(true))); let block_hash_json = serde_json::json!( { "blockHash": "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", "requireCanonical": true } ); let id = serde_json::from_value::(block_hash_json).unwrap(); assert_eq!(id, block_id) } #[test] fn can_parse_blockid_tags() { let tags = [("latest", Latest), ("finalized", Finalized), ("safe", Safe), ("pending", Pending)]; for (value, tag) in tags { let num = serde_json::json!({ "blockNumber": value }); let id = serde_json::from_value::(num); assert_eq!(id.unwrap(), BlockId::from(tag)) } } #[test] fn repeated_keys_is_err() { let num = serde_json::json!({"blockNumber": 1, "requireCanonical": true, "requireCanonical": false}); assert!(serde_json::from_value::(num).is_err()); let num = serde_json::json!({"blockNumber": 1, "requireCanonical": true, "blockNumber": 23}); assert!(serde_json::from_value::(num).is_err()); } /// Serde tests #[test] fn serde_blockid_tags() { let block_ids = [Latest, Finalized, Safe, Pending].map(BlockId::from); for block_id in &block_ids { let serialized = serde_json::to_string(&block_id).unwrap(); let deserialized: BlockId = serde_json::from_str(&serialized).unwrap(); assert_eq!(deserialized, *block_id) } } #[test] fn serde_blockid_number() { let block_id = BlockId::from(100u64); let serialized = serde_json::to_string(&block_id).unwrap(); let deserialized: BlockId = serde_json::from_str(&serialized).unwrap(); assert_eq!(deserialized, block_id) } #[test] fn serde_blockid_hash() { let block_id = BlockId::from(B256::default()); let serialized = serde_json::to_string(&block_id).unwrap(); let deserialized: BlockId = serde_json::from_str(&serialized).unwrap(); assert_eq!(deserialized, block_id) } #[test] fn serde_blockid_hash_from_str() { let val = "\"0x898753d8fdd8d92c1907ca21e68c7970abd290c647a202091181deec3f30a0b2\""; let block_hash: B256 = serde_json::from_str(val).unwrap(); let block_id: BlockId = serde_json::from_str(val).unwrap(); assert_eq!(block_id, BlockId::Hash(block_hash.into())); } #[test] fn serde_rpc_payload_block_tag() { let payload = r#"{"method":"eth_call","params":[{"to":"0xebe8efa441b9302a0d7eaecc277c09d20d684540","data":"0x45848dfc"},"latest"],"id":1,"jsonrpc":"2.0"}"#; let value: serde_json::Value = serde_json::from_str(payload).unwrap(); let block_id_param = value.pointer("/params/1").unwrap(); let block_id: BlockId = serde_json::from_value::(block_id_param.clone()).unwrap(); assert_eq!(BlockId::Number(BlockNumberOrTag::Latest), block_id); } #[test] fn serde_rpc_payload_block_object() { let example_payload = r#"{"method":"eth_call","params":[{"to":"0xebe8efa441b9302a0d7eaecc277c09d20d684540","data":"0x45848dfc"},{"blockHash": "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"}],"id":1,"jsonrpc":"2.0"}"#; let value: serde_json::Value = serde_json::from_str(example_payload).unwrap(); let block_id_param = value.pointer("/params/1").unwrap().to_string(); let block_id: BlockId = serde_json::from_str::(&block_id_param).unwrap(); let hash = B256::from_str("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") .unwrap(); assert_eq!(BlockId::from(hash), block_id); let serialized = serde_json::to_string(&BlockId::from(hash)).unwrap(); assert_eq!("{\"blockHash\":\"0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3\"}", serialized) } #[test] fn serde_rpc_payload_block_number() { let example_payload = r#"{"method":"eth_call","params":[{"to":"0xebe8efa441b9302a0d7eaecc277c09d20d684540","data":"0x45848dfc"},{"blockNumber": "0x0"}],"id":1,"jsonrpc":"2.0"}"#; let value: serde_json::Value = serde_json::from_str(example_payload).unwrap(); let block_id_param = value.pointer("/params/1").unwrap().to_string(); let block_id: BlockId = serde_json::from_str::(&block_id_param).unwrap(); assert_eq!(BlockId::from(0u64), block_id); let serialized = serde_json::to_string(&BlockId::from(0u64)).unwrap(); assert_eq!("\"0x0\"", serialized) } #[test] #[should_panic] fn serde_rpc_payload_block_number_duplicate_key() { let payload = r#"{"blockNumber": "0x132", "blockNumber": "0x133"}"#; let parsed_block_id = serde_json::from_str::(payload); parsed_block_id.unwrap(); } #[test] fn serde_rpc_payload_block_hash() { let payload = r#"{"blockHash": "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"}"#; let parsed = serde_json::from_str::(payload).unwrap(); let expected = BlockId::from( B256::from_str("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") .unwrap(), ); assert_eq!(parsed, expected); } #[test] fn encode_decode_raw_block() { let bytes = hex!("f90288f90218a0fe21bb173f43067a9f90cfc59bbb6830a7a2929b5de4a61f372a9db28e87f9aea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a061effbbcca94f0d3e02e5bd22e986ad57142acabf0cb3d129a6ad8d0f8752e94a0d911c25e97e27898680d242b7780b6faef30995c355a2d5de92e6b9a7212ad3aa0056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008003834c4b408252081e80a00000000000000000000000000000000000000000000000000000000000000000880000000000000000842806be9da056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421f869f86702842806be9e82520894658bdf435d810c91414ec09147daa6db624063798203e880820a95a040ce7918eeb045ebf8c8b1887ca139d076bda00fa828a07881d442a72626c42da0156576a68e456e295e4c9cf67cf9f53151f329438916e0f24fc69d6bbb7fbacfc0c0"); let bytes_buf = &mut bytes.as_ref(); let block = Block::decode(bytes_buf).unwrap(); let mut encoded_buf = Vec::new(); block.encode(&mut encoded_buf); assert_eq!(bytes[..], encoded_buf); } #[test] fn serde_blocknumber_non_0xprefix() { let s = "\"2\""; let err = serde_json::from_str::(s).unwrap_err(); assert_eq!(err.to_string(), HexStringMissingPrefixError::default().to_string()); } #[test] fn block_with_senders() { let mut block = Block::default(); let sender = Address::random(); block.body.push(TransactionSigned::default()); assert_eq!(BlockWithSenders::new(block.clone(), vec![]), None); assert_eq!( BlockWithSenders::new(block.clone(), vec![sender]), Some(BlockWithSenders { block: block.clone(), senders: vec![sender] }) ); let sealed = block.seal_slow(); assert_eq!(SealedBlockWithSenders::new(sealed.clone(), vec![]), None); assert_eq!( SealedBlockWithSenders::new(sealed.clone(), vec![sender]), Some(SealedBlockWithSenders { block: sealed, senders: vec![sender] }) ); } }