use crate::{
block::{error::BlockRecoveryError, header::BlockHeader, RecoveredBlock},
transaction::signed::{RecoveryError, SignedTransaction},
Block, BlockBody, GotExpected, InMemorySize, SealedHeader,
};
use alloc::vec::Vec;
use alloy_consensus::BlockHeader as _;
use alloy_eips::{eip1898::BlockWithParent, BlockNumHash};
use alloy_primitives::{Address, BlockHash, Sealable, Sealed, B256};
use alloy_rlp::{Decodable, Encodable};
use bytes::BufMut;
use core::ops::Deref;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SealedBlock<B: Block> {
header: SealedHeader<B::Header>,
body: B::Body,
}
impl<B: Block> SealedBlock<B> {
pub fn seal_slow(block: B) -> Self {
let hash = block.header().hash_slow();
Self::new_unchecked(block, hash)
}
#[inline]
pub fn new_unchecked(block: B, hash: BlockHash) -> Self {
let (header, body) = block.split();
Self { header: SealedHeader::new(header, hash), body }
}
pub fn new_unhashed(block: B) -> Self {
let (header, body) = block.split();
Self { header: SealedHeader::new_unhashed(header), body }
}
pub fn seal_parts(header: B::Header, body: B::Body) -> Self {
Self::seal_slow(B::new(header, body))
}
pub fn from_parts_unhashed(header: B::Header, body: B::Body) -> Self {
Self::new_unhashed(B::new(header, body))
}
pub fn from_parts_unchecked(header: B::Header, body: B::Body, hash: BlockHash) -> Self {
Self::new_unchecked(B::new(header, body), hash)
}
pub fn from_sealed_parts(header: SealedHeader<B::Header>, body: B::Body) -> Self {
let (header, hash) = header.split();
Self::from_parts_unchecked(header, body, hash)
}
#[inline]
pub fn hash_ref(&self) -> &BlockHash {
self.header.hash_ref()
}
#[inline]
pub fn hash(&self) -> B256 {
self.header.hash()
}
#[doc(alias = "into_components")]
pub fn split(self) -> (B, BlockHash) {
let (header, hash) = self.header.split();
(B::new(header, self.body), hash)
}
pub fn into_block(self) -> B {
self.unseal()
}
pub fn unseal(self) -> B {
let header = self.header.unseal();
B::new(header, self.body)
}
pub fn clone_block(&self) -> B {
B::new(self.header.clone_header(), self.body.clone())
}
pub const fn with_senders(self, senders: Vec<Address>) -> RecoveredBlock<B> {
RecoveredBlock::new_sealed(self, senders)
}
pub fn try_with_senders(
self,
senders: Vec<Address>,
) -> Result<RecoveredBlock<B>, BlockRecoveryError<Self>> {
RecoveredBlock::try_recover_sealed_with_senders(self, senders)
}
pub fn try_with_senders_unchecked(
self,
senders: Vec<Address>,
) -> Result<RecoveredBlock<B>, BlockRecoveryError<Self>> {
RecoveredBlock::try_recover_sealed_with_senders_unchecked(self, senders)
}
pub fn try_recover(self) -> Result<RecoveredBlock<B>, BlockRecoveryError<Self>> {
RecoveredBlock::try_recover_sealed(self)
}
pub fn try_recover_unchecked(self) -> Result<RecoveredBlock<B>, BlockRecoveryError<Self>> {
RecoveredBlock::try_recover_sealed_unchecked(self)
}
pub const fn header(&self) -> &B::Header {
self.header.header()
}
pub const fn body(&self) -> &B::Body {
&self.body
}
pub fn rlp_length(&self) -> usize {
B::rlp_length(self.header(), self.body())
}
pub fn senders(&self) -> Result<Vec<Address>, RecoveryError> {
self.body().recover_signers()
}
pub fn num_hash(&self) -> BlockNumHash {
BlockNumHash::new(self.number(), self.hash())
}
pub fn block_with_parent(&self) -> BlockWithParent {
BlockWithParent { parent: self.parent_hash(), block: self.num_hash() }
}
pub const fn sealed_header(&self) -> &SealedHeader<B::Header> {
&self.header
}
pub fn sealed_header_ref(&self) -> SealedHeader<&B::Header> {
SealedHeader::new(self.header(), self.hash())
}
pub fn clone_sealed_header(&self) -> SealedHeader<B::Header> {
self.header.clone()
}
pub fn into_sealed_header(self) -> SealedHeader<B::Header> {
self.header
}
pub fn into_header(self) -> B::Header {
self.header.unseal()
}
pub fn into_body(self) -> B::Body {
self.body
}
pub fn split_header_body(self) -> (B::Header, B::Body) {
let header = self.header.unseal();
(header, self.body)
}
pub fn split_sealed_header_body(self) -> (SealedHeader<B::Header>, B::Body) {
(self.header, self.body)
}
#[inline]
pub fn blob_versioned_hashes_iter(&self) -> impl Iterator<Item = &B256> + '_ {
self.body().blob_versioned_hashes_iter()
}
#[inline]
pub fn transaction_count(&self) -> usize {
self.body().transaction_count()
}
pub fn ensure_transaction_root_valid(&self) -> Result<(), GotExpected<B256>> {
let calculated_root = self.body().calculate_tx_root();
if self.header().transactions_root() != calculated_root {
return Err(GotExpected {
got: calculated_root,
expected: self.header().transactions_root(),
})
}
Ok(())
}
}
impl<B> From<B> for SealedBlock<B>
where
B: Block,
{
fn from(block: B) -> Self {
Self::seal_slow(block)
}
}
impl<B> Default for SealedBlock<B>
where
B: Block + Default,
{
fn default() -> Self {
Self::seal_slow(Default::default())
}
}
impl<B: Block> InMemorySize for SealedBlock<B> {
#[inline]
fn size(&self) -> usize {
self.body.size() + self.header.size()
}
}
impl<B: Block> Deref for SealedBlock<B> {
type Target = B::Header;
fn deref(&self) -> &Self::Target {
self.header()
}
}
impl<B: Block> Encodable for SealedBlock<B> {
fn encode(&self, out: &mut dyn BufMut) {
self.clone().into_block().encode(out);
}
}
impl<B: Block> Decodable for SealedBlock<B> {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
let block = B::decode(buf)?;
Ok(Self::seal_slow(block))
}
}
impl<B: Block> From<SealedBlock<B>> for Sealed<B> {
fn from(value: SealedBlock<B>) -> Self {
let (block, hash) = value.split();
Self::new_unchecked(block, hash)
}
}
impl<B: Block> From<Sealed<B>> for SealedBlock<B> {
fn from(value: Sealed<B>) -> Self {
let (block, hash) = value.into_parts();
Self::new_unchecked(block, hash)
}
}
impl<T, H> SealedBlock<alloy_consensus::Block<T, H>>
where
T: Decodable + SignedTransaction,
H: BlockHeader,
{
pub fn decode_sealed(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
let sealed = alloy_consensus::Block::<T, H>::decode_sealed(buf)?;
let (block, hash) = sealed.into_parts();
Ok(Self::new_unchecked(block, hash))
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl<'a, B> arbitrary::Arbitrary<'a> for SealedBlock<B>
where
B: Block + arbitrary::Arbitrary<'a>,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let block = B::arbitrary(u)?;
Ok(Self::seal_slow(block))
}
}
#[cfg(any(test, feature = "test-utils"))]
impl<B: crate::test_utils::TestBlock> SealedBlock<B> {
pub const fn header_mut(&mut self) -> &mut B::Header {
self.header.header_mut()
}
pub fn set_hash(&mut self, hash: BlockHash) {
self.header.set_hash(hash)
}
pub const fn body_mut(&mut self) -> &mut B::Body {
&mut self.body
}
pub fn set_parent_hash(&mut self, hash: BlockHash) {
self.header.set_parent_hash(hash)
}
pub fn set_block_number(&mut self, number: alloy_primitives::BlockNumber) {
self.header.set_block_number(number)
}
pub fn set_timestamp(&mut self, timestamp: u64) {
self.header.set_timestamp(timestamp)
}
pub fn set_state_root(&mut self, state_root: alloy_primitives::B256) {
self.header.set_state_root(state_root)
}
pub fn set_difficulty(&mut self, difficulty: alloy_primitives::U256) {
self.header.set_difficulty(difficulty)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_rlp::{Decodable, Encodable};
#[test]
fn test_sealed_block_rlp_roundtrip() {
let header = alloy_consensus::Header {
number: 42,
gas_limit: 30_000_000,
gas_used: 21_000,
timestamp: 1_000_000,
base_fee_per_gas: Some(1_000_000_000),
..Default::default()
};
let tx = alloy_consensus::TxLegacy {
chain_id: Some(1),
nonce: 0,
gas_price: 21_000_000_000,
gas_limit: 21_000,
to: alloy_primitives::TxKind::Call(Address::ZERO),
value: alloy_primitives::U256::from(100),
input: alloy_primitives::Bytes::default(),
};
let tx_signed =
alloy_consensus::TxEnvelope::Legacy(alloy_consensus::Signed::new_unchecked(
tx,
alloy_primitives::Signature::test_signature(),
B256::ZERO,
));
let body = alloy_consensus::BlockBody {
transactions: vec![tx_signed],
ommers: vec![],
withdrawals: Some(Default::default()),
};
let block = alloy_consensus::Block::new(header, body);
let sealed_block = SealedBlock::seal_slow(block);
let mut encoded = Vec::new();
sealed_block.encode(&mut encoded);
let decoded = SealedBlock::<
alloy_consensus::Block<alloy_consensus::TxEnvelope, alloy_consensus::Header>,
>::decode(&mut encoded.as_slice())
.expect("Failed to decode sealed block");
assert_eq!(sealed_block.hash(), decoded.hash());
assert_eq!(sealed_block.header().number, decoded.header().number);
assert_eq!(sealed_block.header().state_root, decoded.header().state_root);
assert_eq!(sealed_block.body().transactions.len(), decoded.body().transactions.len());
}
#[test]
fn test_decode_sealed_produces_correct_hash() {
let header = alloy_consensus::Header {
number: 42,
gas_limit: 30_000_000,
gas_used: 21_000,
timestamp: 1_000_000,
base_fee_per_gas: Some(1_000_000_000),
..Default::default()
};
let tx = alloy_consensus::TxLegacy {
chain_id: Some(1),
nonce: 0,
gas_price: 21_000_000_000,
gas_limit: 21_000,
to: alloy_primitives::TxKind::Call(Address::ZERO),
value: alloy_primitives::U256::from(100),
input: alloy_primitives::Bytes::default(),
};
let tx_signed =
alloy_consensus::TxEnvelope::Legacy(alloy_consensus::Signed::new_unchecked(
tx,
alloy_primitives::Signature::test_signature(),
B256::ZERO,
));
let body = alloy_consensus::BlockBody {
transactions: vec![tx_signed],
ommers: vec![],
withdrawals: Some(Default::default()),
};
let block = alloy_consensus::Block::new(header, body);
let expected_hash = block.header.hash_slow();
let mut encoded = Vec::new();
block.encode(&mut encoded);
let decoded =
SealedBlock::<alloy_consensus::Block<alloy_consensus::TxEnvelope>>::decode_sealed(
&mut encoded.as_slice(),
)
.expect("Failed to decode sealed block");
assert_eq!(decoded.hash(), expected_hash);
assert_eq!(decoded.header().number, 42);
assert_eq!(decoded.body().transactions.len(), 1);
}
#[test]
fn test_sealed_block_from_sealed() {
let header = alloy_consensus::Header::default();
let body = alloy_consensus::BlockBody::<alloy_consensus::TxEnvelope>::default();
let block = alloy_consensus::Block::new(header, body);
let hash = block.header.hash_slow();
let sealed: Sealed<alloy_consensus::Block<alloy_consensus::TxEnvelope>> =
Sealed::new_unchecked(block.clone(), hash);
let sealed_block: SealedBlock<alloy_consensus::Block<alloy_consensus::TxEnvelope>> =
SealedBlock::from(sealed);
assert_eq!(sealed_block.hash(), hash);
assert_eq!(sealed_block.header().number, block.header.number);
}
}