Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add creation slot to TransactionId and rework some stuff #1414

Merged
merged 27 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 4 additions & 114 deletions sdk/src/types/block/block_id.rs
Original file line number Diff line number Diff line change
@@ -1,117 +1,7 @@
// Copyright 2020-2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use super::{slot::SlotIndex, ConvertTo};
use crate::types::block::Error;

impl_id!(pub BlockHash, 32, "The hash of a [`Block`].");

impl BlockHash {
#[cfg(target_endian = "little")]
pub fn with_slot_index(self, slot_index: SlotIndex) -> BlockId {
BlockId { hash: self, slot_index }
}

#[cfg(target_endian = "big")]
pub fn with_slot_index(self, slot_index: SlotIndex) -> BlockId {
BlockId {
hash: self,
slot_index: slot_index.to_le().into(),
}
}
}

/// A block identifier.
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Debug, packable::Packable)]
#[packable(unpack_error = Error)]
#[repr(C)]
pub struct BlockId {
pub(crate) hash: BlockHash,
// IMPORTANT: On big-endian systems this value is misrepresented because it is transmuted directly
// from bytes, so the getter below handles that conversion. Do not access it directly.
slot_index: SlotIndex,
}

impl BlockId {
/// The length of a [`BlockId`]
pub const LENGTH: usize = 36;

pub fn new(bytes: [u8; Self::LENGTH]) -> Self {
unsafe { core::mem::transmute(bytes) }
}

/// Returns the [`BlockId`]'s hash part.
pub fn hash(&self) -> &BlockHash {
&self.hash
}

/// Returns the [`BlockId`]'s slot index part.
#[cfg(target_endian = "little")]
pub fn slot_index(&self) -> SlotIndex {
self.slot_index
}

/// Returns the [`BlockId`]'s slot index part.
#[cfg(target_endian = "big")]
pub fn slot_index(&self) -> SlotIndex {
self.slot_index.to_le().into()
}
}

impl AsRef<[u8]> for BlockId {
fn as_ref(&self) -> &[u8] {
unsafe { core::mem::transmute::<_, &[u8; Self::LENGTH]>(self) }
}
}

impl core::str::FromStr for BlockId {
type Err = Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::new(prefix_hex::decode(s).map_err(Error::Hex)?))
}
}

impl core::fmt::Display for BlockId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
prefix_hex::encode(self.as_ref()).fmt(f)
}
}

impl TryFrom<&alloc::string::String> for BlockId {
type Error = Error;

fn try_from(s: &alloc::string::String) -> Result<Self, Self::Error> {
core::str::FromStr::from_str(s.as_str())
}
}

impl TryFrom<&str> for BlockId {
type Error = Error;

fn try_from(s: &str) -> Result<Self, Self::Error> {
core::str::FromStr::from_str(s)
}
}

impl ConvertTo<BlockId> for &alloc::string::String {
fn convert(self) -> Result<BlockId, Error> {
self.try_into()
}
}

impl ConvertTo<BlockId> for &str {
fn convert(self) -> Result<BlockId, Error> {
self.try_into()
}
}

impl core::ops::Deref for BlockId {
type Target = [u8; Self::LENGTH];

fn deref(&self) -> &Self::Target {
unsafe { core::mem::transmute::<_, &[u8; Self::LENGTH]>(self) }
}
}
#[cfg(feature = "serde")]
string_serde_impl!(BlockId);
impl_id_with_slot!(
pub BlockHash, 32, "The hash of a [`Block`].",
pub BlockId, "A [`Block`] identifier."
thibault-martinez marked this conversation as resolved.
Show resolved Hide resolved
);
18 changes: 7 additions & 11 deletions sdk/src/types/block/context_input/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,30 +69,26 @@ mod tests {

#[test]
fn test_commitment() {
let commitment: ContextInput = serde_json::from_str(
r#"
let commitment: ContextInput = serde_json::from_value(serde_json::json!(
{
"type": 0,
"commitmentId": "0xedf5f572c58ddf4b4f9567d82bf96689cc68b730df796d822b4b9fb643f5efda4f9567d82bf96689"
"commitmentId": "0xedf5f572c58ddf4b4f9567d82bf96689cc68b730df796d822b4b9fb643f5efda4f9567d8"
}
"#,
)
))
.unwrap();
assert!(commitment.is_commitment());
assert_eq!(
commitment.as_commitment().commitment_id().to_string(),
"0xedf5f572c58ddf4b4f9567d82bf96689cc68b730df796d822b4b9fb643f5efda4f9567d82bf96689"
"0xedf5f572c58ddf4b4f9567d82bf96689cc68b730df796d822b4b9fb643f5efda4f9567d8"
);

// Test wrong type returns error.
let commitment_deserialization_result: Result<ContextInput, _> = serde_json::from_str(
r#"
let commitment_deserialization_result: Result<ContextInput, _> = serde_json::from_value(serde_json::json!(
{
"type": 2,
"commitmentId": "0xedf5f572c58ddf4b4f9567d82bf96689cc68b730df796d822b4b9fb643f5efda4f9567d82bf96689"
"commitmentId": "0xedf5f572c58ddf4b4f9567d82bf96689cc68b730df796d822b4b9fb643f5efda4f9567d8"
}
"#,
);
));
assert!(commitment_deserialization_result.is_err());
}

Expand Down
121 changes: 121 additions & 0 deletions sdk/src/types/block/macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,127 @@ macro_rules! impl_id {
#[cfg(feature = "serde")]
pub(crate) use impl_id;

#[macro_export(local_inner_macros)]
macro_rules! impl_id_with_slot {
($hash_vis:vis $hash_name:ident, $hash_length:literal, $hash_doc:literal, $id_vis:vis $id_name:ident, $id_doc:literal) => {
impl_id!($hash_vis $hash_name, $hash_length, $hash_doc);

impl $hash_name {
pub fn with_slot_index(self, slot_index: impl Into<$crate::types::block::slot::SlotIndex>) -> $id_name {
$id_name {
hash: self,
slot_index: slot_index.into().to_le_bytes(),
}
}
}

#[doc = $id_doc]
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, packable::Packable)]
#[packable(unpack_error = $crate::types::block::Error)]
#[repr(C)]
$id_vis struct $id_name {
pub(crate) hash: $hash_name,
slot_index: [u8; core::mem::size_of::<$crate::types::block::slot::SlotIndex>()],
}

impl $id_name {
#[doc = core::concat!("The length of a [`", core::stringify!($id_name),"`].")]
pub const LENGTH: usize = $hash_name::LENGTH + core::mem::size_of::<$crate::types::block::slot::SlotIndex>();

pub fn new(bytes: [u8; Self::LENGTH]) -> Self {
unsafe { core::mem::transmute(bytes) }
}

#[doc = core::concat!("Returns the [`", core::stringify!($id_name),"`]'s hash part.")]
pub fn hash(&self) -> &$hash_name {
&self.hash
}

#[doc = core::concat!("Returns the [`", core::stringify!($id_name),"`]'s slot index part.")]
pub fn slot_index(&self) -> $crate::types::block::slot::SlotIndex {
unsafe {
#[cfg(target_endian = "little")]
{
core::mem::transmute(self.slot_index)
}

#[cfg(target_endian = "big")]
{
core::mem::transmute(self.slot_index.to_le())
}
}
}
}

impl AsRef<[u8]> for $id_name {
fn as_ref(&self) -> &[u8] {
unsafe { core::mem::transmute::<_, &[u8; Self::LENGTH]>(self) }
}
}

impl core::str::FromStr for $id_name {
type Err = $crate::types::block::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::new(prefix_hex::decode(s).map_err($crate::types::block::Error::Hex)?))
}
}

impl core::fmt::Debug for $id_name {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct(core::stringify!($id_name))
.field("id", &alloc::string::ToString::to_string(self))
.field("slot_index", &self.slot_index())
.finish()
}
}

impl core::fmt::Display for $id_name {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
prefix_hex::encode(self.as_ref()).fmt(f)
}
}

impl TryFrom<&alloc::string::String> for $id_name {
type Error = $crate::types::block::Error;

fn try_from(s: &alloc::string::String) -> Result<Self, Self::Error> {
core::str::FromStr::from_str(s.as_str())
}
}

impl TryFrom<&str> for $id_name {
type Error = $crate::types::block::Error;

fn try_from(s: &str) -> Result<Self, Self::Error> {
core::str::FromStr::from_str(s)
}
}

impl $crate::types::block::ConvertTo<$id_name> for &alloc::string::String {
fn convert(self) -> Result<$id_name, $crate::types::block::Error> {
self.try_into()
}
}

impl $crate::types::block::ConvertTo<$id_name> for &str {
fn convert(self) -> Result<$id_name, $crate::types::block::Error> {
self.try_into()
}
}

impl core::ops::Deref for $id_name {
type Target = [u8; Self::LENGTH];

fn deref(&self) -> &Self::Target {
unsafe { core::mem::transmute::<_, &[u8; Self::LENGTH]>(self) }
}
}
#[cfg(feature = "serde")]
string_serde_impl!($id_name);
}
}

/// Convenience macro to serialize types to string via serde.
#[macro_export]
#[cfg(feature = "serde")]
Expand Down
20 changes: 8 additions & 12 deletions sdk/src/types/block/output/output_id.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright 2020-2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use alloc::string::ToString;
use core::str::FromStr;

use crypto::hashes::{blake2b::Blake2b256, Digest};
Expand All @@ -21,7 +22,7 @@ pub struct OutputId {

impl OutputId {
/// The length of a [`OutputId`].
pub const LENGTH: usize = TransactionId::LENGTH + core::mem::size_of::<u16>();
pub const LENGTH: usize = TransactionId::LENGTH + core::mem::size_of::<OutputIndex>();

/// Creates a new [`OutputId`].
pub fn new(transaction_id: TransactionId, index: u16) -> Result<Self, Error> {
Expand All @@ -43,15 +44,6 @@ impl OutputId {
self.index.get()
}

/// Creates a null [`OutputId`].
pub fn null() -> Self {
Self {
transaction_id: TransactionId::null(),
// Unwrap is fine because index is already known and valid.
index: 0u16.try_into().unwrap(),
}
}

/// Splits an [`OutputId`] into its [`TransactionId`] and index.
#[inline(always)]
pub fn split(self) -> (TransactionId, u16) {
Expand All @@ -76,7 +68,7 @@ impl TryFrom<[u8; Self::LENGTH]> for OutputId {

Self::new(
// Unwrap is fine because size is already known and valid.
From::<[u8; TransactionId::LENGTH]>::from(transaction_id.try_into().unwrap()),
TransactionId::new(transaction_id.try_into().unwrap()),
// Unwrap is fine because size is already known and valid.
u16::from_le_bytes(index.try_into().unwrap()),
)
Expand All @@ -103,6 +95,10 @@ impl core::fmt::Display for OutputId {

impl core::fmt::Debug for OutputId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "OutputId({self})")
f.debug_struct("OutputId")
.field("id", &self.to_string())
.field("transaction_id", &self.transaction_id)
thibault-martinez marked this conversation as resolved.
Show resolved Hide resolved
.field("output_index", &self.index)
.finish()
}
}
4 changes: 2 additions & 2 deletions sdk/src/types/block/payload/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub use self::{
RegularTransactionEssence, RegularTransactionEssenceBuilder, TransactionCapabilities,
TransactionCapabilityFlag, TransactionEssence,
},
transaction_id::TransactionId,
transaction_id::{TransactionHash, TransactionId},
};
use crate::types::block::{protocol::ProtocolParameters, unlock::Unlocks, Error};

Expand Down Expand Up @@ -54,7 +54,7 @@ impl TransactionPayload {
hasher.update(Self::KIND.to_le_bytes());
hasher.update(self.pack_to_vec());

TransactionId::new(hasher.finalize().into())
TransactionHash::new(hasher.finalize().into()).with_slot_index(self.essence.creation_slot())
}
}

Expand Down
17 changes: 11 additions & 6 deletions sdk/src/types/block/payload/transaction/transaction_id.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
// Copyright 2020-2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

impl_id!(
pub TransactionId,
32,
"A transaction identifier, the BLAKE2b-256 hash of the transaction bytes. See <https://www.blake2.net/> for more information."
use crate::types::block::{output::OutputId, Error};

impl_id_with_slot!(
pub TransactionHash, 32, "The hash of a [`TransactionPayload`].",
pub TransactionId, "A [`TransactionPayload`] identifier."
);

#[cfg(feature = "serde")]
string_serde_impl!(TransactionId);
impl TransactionId {
/// Creates an [`OutputId`] from this [`TransactionId`] and an output index.
pub fn with_output_index(self, index: u16) -> Result<OutputId, Error> {
thibault-martinez marked this conversation as resolved.
Show resolved Hide resolved
OutputId::new(self, index)
}
}
Loading