diff --git a/sdk/src/client/api/block_builder/mod.rs b/sdk/src/client/api/block_builder/mod.rs index 0ef773c69c..e48e7e4e43 100644 --- a/sdk/src/client/api/block_builder/mod.rs +++ b/sdk/src/client/api/block_builder/mod.rs @@ -51,7 +51,7 @@ impl ClientInner { }); Ok(BlockWrapper::new( - self.get_protocol_parameters().await?, + &self.get_protocol_parameters().await?, issuing_time, commitment.id(), latest_finalized_slot, diff --git a/sdk/src/types/block/core/wrapper.rs b/sdk/src/types/block/core/wrapper.rs index ee3731a5ab..215346784a 100644 --- a/sdk/src/types/block/core/wrapper.rs +++ b/sdk/src/types/block/core/wrapper.rs @@ -4,6 +4,7 @@ use core::mem::size_of; use crypto::hashes::{blake2b::Blake2b256, Digest}; +use getset::{CopyGetters, Getters}; use packable::{ error::{UnexpectedEOF, UnpackError, UnpackErrorExt}, packer::{Packer, SlicePacker}, @@ -20,12 +21,13 @@ use crate::types::block::{ Block, Error, IssuerId, }; -/// Represent the object that nodes gossip around the network. -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct BlockWrapper { - /// Protocol parameters of the network to which this block belongs. - // TODO Not fully sure this is worth the small UX improvement, needs to be discussed more. - protocol_params: ProtocolParameters, +#[derive(Clone, Debug, Eq, PartialEq, CopyGetters)] +#[getset(get_copy = "pub")] +pub struct BlockHeader { + /// Protocol version of the network to which this block belongs. + protocol_version: u8, + /// The identifier of the network to which this block belongs. + network_id: u64, /// The time at which the block was issued. It is a Unix timestamp in nanoseconds. issuing_time: u64, /// The identifier of the slot to which this block commits. @@ -34,10 +36,91 @@ pub struct BlockWrapper { latest_finalized_slot: SlotIndex, /// The identifier of the account that issued this block. issuer_id: IssuerId, +} + +impl BlockHeader { + /// The length of the block header. + pub const LENGTH: usize = + size_of::() + 2 * size_of::() + SlotCommitmentId::LENGTH + size_of::() + IssuerId::LENGTH; + + pub(crate) fn hash(&self) -> [u8; 32] { + let mut bytes = [0u8; Self::LENGTH]; + + self.pack(&mut SlicePacker::new(&mut bytes)).unwrap(); + Blake2b256::digest(bytes).into() + } +} + +impl Packable for BlockHeader { + type UnpackError = Error; + type UnpackVisitor = ProtocolParameters; + + fn pack(&self, packer: &mut P) -> Result<(), P::Error> { + self.protocol_version.pack(packer)?; + self.network_id.pack(packer)?; + self.issuing_time.pack(packer)?; + self.slot_commitment_id.pack(packer)?; + self.latest_finalized_slot.pack(packer)?; + self.issuer_id.pack(packer)?; + + Ok(()) + } + + fn unpack( + unpacker: &mut U, + protocol_params: &Self::UnpackVisitor, + ) -> Result> { + let protocol_version = u8::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + + if VERIFY && protocol_version != protocol_params.version() { + return Err(UnpackError::Packable(Error::ProtocolVersionMismatch { + expected: protocol_params.version(), + actual: protocol_version, + })); + } + + let network_id = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + + if VERIFY && network_id != protocol_params.network_id() { + return Err(UnpackError::Packable(Error::NetworkIdMismatch { + expected: protocol_params.network_id(), + actual: network_id, + })); + } + + let issuing_time = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + + let slot_commitment_id = SlotCommitmentId::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + + let latest_finalized_slot = SlotIndex::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + + let issuer_id = IssuerId::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + + Ok(Self { + protocol_version, + network_id, + issuing_time, + slot_commitment_id, + latest_finalized_slot, + issuer_id, + }) + } +} + +/// Represent the object that nodes gossip around the network. +#[derive(Clone, Debug, Eq, PartialEq, Getters, CopyGetters)] +pub struct BlockWrapper { + #[getset(skip)] + header: BlockHeader, /// The inner block. + #[getset(get = "pub")] block: Block, /// The block signature, used to validate issuance capabilities. + #[getset(get_copy = "pub")] signature: Signature, + /// The identifier of the block. + #[getset(get_copy = "pub")] + id: BlockId, } impl BlockWrapper { @@ -45,17 +128,11 @@ impl BlockWrapper { pub const LENGTH_MIN: usize = 46; /// The maximum number of bytes in a block. pub const LENGTH_MAX: usize = 32768; - /// The length of the block header. - pub const HEADER_LENGTH: usize = size_of::() - + 2 * size_of::() - + size_of::() - + size_of::() - + size_of::(); /// Creates a new [`BlockWrapper`]. #[inline(always)] pub fn new( - protocol_params: ProtocolParameters, + protocol_params: &ProtocolParameters, issuing_time: u64, slot_commitment_id: SlotCommitmentId, latest_finalized_slot: SlotIndex, @@ -63,104 +140,70 @@ impl BlockWrapper { block: impl Into, signature: impl Into, ) -> Self { - Self { - protocol_params, + let block = block.into(); + let signature = signature.into(); + let header = BlockHeader { + protocol_version: protocol_params.version(), + network_id: protocol_params.network_id(), issuing_time, slot_commitment_id, latest_finalized_slot, issuer_id, - block: block.into(), - signature: signature.into(), + }; + Self { + id: Self::block_id(&header, &block, &signature, protocol_params), + header, + block, + signature, } } /// Returns the protocol version of a [`BlockWrapper`]. #[inline(always)] pub fn protocol_version(&self) -> u8 { - self.protocol_params.version() - } - - /// Returns the protocol parameters of a [`BlockWrapper`]. - #[inline(always)] - pub fn protocol_parameters(&self) -> &ProtocolParameters { - &self.protocol_params + self.header.protocol_version() } /// Returns the network id of a [`BlockWrapper`]. #[inline(always)] pub fn network_id(&self) -> u64 { - self.protocol_params.network_id() + self.header.network_id() } /// Returns the issuing time of a [`BlockWrapper`]. #[inline(always)] pub fn issuing_time(&self) -> u64 { - self.issuing_time + self.header.issuing_time() } /// Returns the slot commitment ID of a [`BlockWrapper`]. #[inline(always)] pub fn slot_commitment_id(&self) -> SlotCommitmentId { - self.slot_commitment_id + self.header.slot_commitment_id() } /// Returns the latest finalized slot of a [`BlockWrapper`]. #[inline(always)] pub fn latest_finalized_slot(&self) -> SlotIndex { - self.latest_finalized_slot + self.header.latest_finalized_slot() } /// Returns the issuer ID of a [`BlockWrapper`]. #[inline(always)] pub fn issuer_id(&self) -> IssuerId { - self.issuer_id - } - - /// Returns the [`Block`] of a [`BlockWrapper`]. - #[inline(always)] - pub fn block(&self) -> &Block { - &self.block - } - - /// Returns the signature of a [`BlockWrapper`]. - #[inline(always)] - pub fn signature(&self) -> &Signature { - &self.signature - } - - pub(crate) fn pack_header(&self, packer: &mut P) -> Result<(), P::Error> { - self.protocol_version().pack(packer)?; - self.network_id().pack(packer)?; - self.issuing_time.pack(packer)?; - self.slot_commitment_id.pack(packer)?; - self.latest_finalized_slot.pack(packer)?; - self.issuer_id.pack(packer)?; - - Ok(()) - } - - pub(crate) fn header_hash(&self) -> [u8; 32] { - let mut bytes = [0u8; Self::HEADER_LENGTH]; - - self.pack_header(&mut SlicePacker::new(&mut bytes)).unwrap(); - Blake2b256::digest(bytes).into() - } - - /// Computes the block hash. - pub fn hash(&self) -> BlockHash { - let id = [ - &self.header_hash()[..], - &self.block.hash()[..], - &self.signature.pack_to_vec(), - ] - .concat(); - BlockHash::new(Blake2b256::digest(id).into()) + self.header.issuer_id() } /// Computes the block identifier. - pub fn id(&self) -> BlockId { - self.hash() - .with_slot_index(self.protocol_parameters().slot_index(self.issuing_time())) + pub fn block_id( + block_header: &BlockHeader, + block: &Block, + signature: &Signature, + protocol_params: &ProtocolParameters, + ) -> BlockId { + let id = [&block_header.hash()[..], &block.hash()[..], &signature.pack_to_vec()].concat(); + let block_hash = BlockHash::new(Blake2b256::digest(id).into()); + block_hash.with_slot_index(protocol_params.slot_index(block_header.issuing_time())) } /// Unpacks a [`BlockWrapper`] from a sequence of bytes doing syntactical checks and verifying that @@ -208,7 +251,7 @@ impl Packable for BlockWrapper { type UnpackVisitor = ProtocolParameters; fn pack(&self, packer: &mut P) -> Result<(), P::Error> { - self.pack_header(packer)?; + self.header.pack(packer)?; self.block.pack(packer)?; self.signature.pack(packer)?; @@ -221,59 +264,32 @@ impl Packable for BlockWrapper { ) -> Result> { let start_opt = unpacker.read_bytes(); - let protocol_version = u8::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - - if VERIFY && protocol_version != protocol_params.version() { - return Err(UnpackError::Packable(Error::ProtocolVersionMismatch { - expected: protocol_params.version(), - actual: protocol_version, - })); - } - - let network_id = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - - if VERIFY && network_id != protocol_params.network_id() { - return Err(UnpackError::Packable(Error::NetworkIdMismatch { - expected: protocol_params.network_id(), - actual: network_id, - })); - } - - let issuing_time = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - - let slot_commitment_id = SlotCommitmentId::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - - let latest_finalized_slot = SlotIndex::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - - let issuer_id = IssuerId::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + let header = BlockHeader::unpack::<_, VERIFY>(unpacker, protocol_params)?; let block = Block::unpack::<_, VERIFY>(unpacker, protocol_params)?; let signature = Signature::unpack::<_, VERIFY>(unpacker, &())?; + let wrapper = Self { + id: Self::block_id(&header, &block, &signature, protocol_params), + header, + block, + signature, + }; + if VERIFY { - let block_len = if let (Some(start), Some(end)) = (start_opt, unpacker.read_bytes()) { + let wrapper_len = if let (Some(start), Some(end)) = (start_opt, unpacker.read_bytes()) { end - start } else { - block.packed_len() + wrapper.packed_len() }; - if block_len > Self::LENGTH_MAX { - return Err(UnpackError::Packable(Error::InvalidBlockLength(block_len))); + if wrapper_len > Self::LENGTH_MAX { + return Err(UnpackError::Packable(Error::InvalidBlockWrapperLength(wrapper_len))); } } - let block_wrapper = Self { - protocol_params: protocol_params.clone(), - issuing_time, - slot_commitment_id, - latest_finalized_slot, - issuer_id, - block, - signature, - }; - - Ok(block_wrapper) + Ok(wrapper) } } @@ -308,10 +324,10 @@ pub(crate) mod dto { Self { protocol_version: value.protocol_version(), network_id: value.network_id(), - issuing_time: value.issuing_time, - slot_commitment_id: value.slot_commitment_id, - latest_finalized_slot: value.latest_finalized_slot, - issuer_id: value.issuer_id, + issuing_time: value.issuing_time(), + slot_commitment_id: value.slot_commitment_id(), + latest_finalized_slot: value.latest_finalized_slot(), + issuer_id: value.issuer_id(), block: BlockDto::from(&value.block), signature: value.signature, } @@ -335,12 +351,12 @@ pub(crate) mod dto { } Ok(BlockWrapper::new( - protocol_params.clone(), + &protocol_params, dto.issuing_time, dto.slot_commitment_id, dto.latest_finalized_slot, dto.issuer_id, - Block::try_from_dto_with_params(dto.block, protocol_params)?, + Block::try_from_dto_with_params(dto.block, &protocol_params)?, dto.signature, )) } diff --git a/sdk/src/types/block/error.rs b/sdk/src/types/block/error.rs index 66468f1c4a..fa060a803f 100644 --- a/sdk/src/types/block/error.rs +++ b/sdk/src/types/block/error.rs @@ -80,7 +80,7 @@ pub enum Error { InvalidInputCount(>::Error), InvalidInputOutputIndex(>::Error), InvalidBech32Hrp(String), - InvalidBlockLength(usize), + InvalidBlockWrapperLength(usize), InvalidStateMetadataLength(>::Error), InvalidManaValue(u64), InvalidMetadataFeatureLength(>::Error), @@ -248,7 +248,7 @@ impl fmt::Display for Error { Self::InvalidInputKind(k) => write!(f, "invalid input kind: {k}"), Self::InvalidInputCount(count) => write!(f, "invalid input count: {count}"), Self::InvalidInputOutputIndex(index) => write!(f, "invalid input or output index: {index}"), - Self::InvalidBlockLength(length) => write!(f, "invalid block length {length}"), + Self::InvalidBlockWrapperLength(length) => write!(f, "invalid block wrapper length {length}"), Self::InvalidStateMetadataLength(length) => write!(f, "invalid state metadata length: {length}"), Self::InvalidManaValue(mana) => write!(f, "invalid mana value: {mana}"), Self::InvalidMetadataFeatureLength(length) => { diff --git a/sdk/src/types/block/rand/block.rs b/sdk/src/types/block/rand/block.rs index 188ecf92ef..9cfe76b614 100644 --- a/sdk/src/types/block/rand/block.rs +++ b/sdk/src/types/block/rand/block.rs @@ -37,7 +37,7 @@ pub fn rand_basic_block_builder_with_strong_parents(strong_parents: StrongParent } /// Generates a random block wrapper. -pub fn rand_block_wrapper_with_block(protocol_params: ProtocolParameters, block: impl Into) -> BlockWrapper { +pub fn rand_block_wrapper_with_block(protocol_params: &ProtocolParameters, block: impl Into) -> BlockWrapper { BlockWrapper::new( protocol_params, rand_number(), @@ -50,7 +50,7 @@ pub fn rand_block_wrapper_with_block(protocol_params: ProtocolParameters, block: } /// Generates a random block wrapper. -pub fn rand_block_wrapper(protocol_params: ProtocolParameters) -> BlockWrapper { +pub fn rand_block_wrapper(protocol_params: &ProtocolParameters) -> BlockWrapper { rand_block_wrapper_with_block( protocol_params, rand_basic_block_builder_with_strong_parents(rand_strong_parents()) diff --git a/sdk/tests/types/block.rs b/sdk/tests/types/block.rs index 4b82f80c5c..bc007e9be1 100644 --- a/sdk/tests/types/block.rs +++ b/sdk/tests/types/block.rs @@ -89,7 +89,7 @@ use packable::PackableExt; #[test] fn pack_unpack_valid() { let protocol_parameters = protocol_parameters(); - let block = rand_block_wrapper(protocol_parameters.clone()); + let block = rand_block_wrapper(&protocol_parameters); let packed_block = block.pack_to_vec(); assert_eq!(packed_block.len(), block.packed_len()); @@ -106,7 +106,7 @@ fn getters() { let payload = Payload::from(rand_tagged_data_payload()); let block = rand_block_wrapper_with_block( - protocol_parameters.clone(), + &protocol_parameters, rand_basic_block_builder_with_strong_parents(parents.clone()) .with_payload(payload.clone()) .finish()