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 5 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
46 changes: 25 additions & 21 deletions sdk/src/types/block/block_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,26 @@ 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 {
pub fn with_slot_index(self, slot_index: impl Into<SlotIndex>) -> BlockId {
BlockId {
hash: self,
slot_index: slot_index.to_le().into(),
slot_index: slot_index.into().to_le_bytes(),
}
}
}

/// A block identifier.
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Debug, packable::Packable)]
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, 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,
slot_index: [u8; core::mem::size_of::<SlotIndex>()],
}

impl BlockId {
/// The length of a [`BlockId`]
pub const LENGTH: usize = 36;
pub const LENGTH: usize = BlockHash::LENGTH + core::mem::size_of::<SlotIndex>();

pub fn new(bytes: [u8; Self::LENGTH]) -> Self {
unsafe { core::mem::transmute(bytes) }
Expand All @@ -46,15 +38,18 @@ impl BlockId {
}

/// 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()
unsafe {
#[cfg(target_endian = "little")]
{
core::mem::transmute(self.slot_index)
}

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

Expand All @@ -72,6 +67,15 @@ impl core::str::FromStr for BlockId {
}
}

impl core::fmt::Debug for BlockId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("BlockId")
.field("hash", &self.hash)
.field("slot_index", &self.slot_index())
.finish()
}
}

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)
Expand Down
11 changes: 1 addition & 10 deletions sdk/src/types/block/output/output_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,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 +67,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 Down
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 @@ -12,7 +12,7 @@ use packable::{error::UnpackError, packer::Packer, unpacker::Unpacker, Packable,
pub(crate) use self::essence::{ContextInputCount, InputCount, OutputCount};
pub use self::{
essence::{RegularTransactionEssence, RegularTransactionEssenceBuilder, TransactionEssence},
transaction_id::TransactionId,
transaction_id::{TransactionHash, TransactionId},
};
use crate::types::block::{protocol::ProtocolParameters, unlock::Unlocks, Error};

Expand Down Expand Up @@ -51,7 +51,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
124 changes: 119 additions & 5 deletions sdk/src/types/block/payload/transaction/transaction_id.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,125 @@
// 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, slot::SlotIndex, ConvertTo, Error};

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

impl TransactionHash {
pub fn with_slot_index(self, slot_index: impl Into<SlotIndex>) -> TransactionId {
TransactionId {
hash: self,
slot_index: slot_index.into().to_le_bytes(),
}
}
}

/// A transaction identifier.
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, packable::Packable)]
#[packable(unpack_error = Error)]
#[repr(C)]
pub struct TransactionId {
pub(crate) hash: TransactionHash,
slot_index: [u8; core::mem::size_of::<SlotIndex>()],
}

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

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

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

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

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

/// 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)
}
}

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

impl core::str::FromStr for TransactionId {
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::Debug for TransactionId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("TransactionId")
.field("hash", &self.hash)
.field("slot_index", &self.slot_index())
.finish()
}
}

impl core::fmt::Display for TransactionId {
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 TransactionId {
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 TransactionId {
type Error = Error;

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

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

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

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

fn deref(&self) -> &Self::Target {
unsafe { core::mem::transmute::<_, &[u8; Self::LENGTH]>(self) }
}
}
#[cfg(feature = "serde")]
string_serde_impl!(TransactionId);
10 changes: 6 additions & 4 deletions sdk/src/wallet/account/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,7 @@ fn serialize() {
unlock::{ReferenceUnlock, SignatureUnlock, Unlock, Unlocks},
};

const TRANSACTION_ID: &str = "0x24a1f46bdb6b2bf38f1c59f73cdd4ae5b418804bb231d76d06fbf246498d5883";
const TRANSACTION_ID: &str = "0x24a1f46bdb6b2bf38f1c59f73cdd4ae5b418804bb231d76d06fbf246498d588300000000";
const ED25519_ADDRESS: &str = "0xe594f9a895c0e0a6760dd12cffc2c3d1e1cbf7269b328091f96ce3d0dd550b75";
const ED25519_PUBLIC_KEY: &str = "0x1da5ddd11ba3f961acab68fafee3177d039875eaa94ac5fdbff8b53f0c50bfb9";
const ED25519_SIGNATURE: &str = "0xc6a40edf9a089f42c18f4ebccb35fe4b578d93b879e99b87f63573324a710d3456b03fb6d1fcc027e6401cbd9581f790ee3ed7a3f68e9c225fcb9f1cd7b7110d";
Expand Down Expand Up @@ -659,8 +659,10 @@ fn serialize() {
let tx_payload = TransactionPayload::new(essence, unlocks).unwrap();

let incoming_transaction = Transaction {
transaction_id: TransactionId::from_str("0x131fc4cb8f315ae36ae3bf6a4e4b3486d5f17581288f1217410da3e0700d195a")
.unwrap(),
transaction_id: TransactionId::from_str(
"0x131fc4cb8f315ae36ae3bf6a4e4b3486d5f17581288f1217410da3e0700d195a00000000",
)
.unwrap(),
payload: tx_payload,
block_id: None,
network_id: 0,
Expand All @@ -673,7 +675,7 @@ fn serialize() {

let mut incoming_transactions = HashMap::new();
incoming_transactions.insert(
TransactionId::from_str("0x131fc4cb8f315ae36ae3bf6a4e4b3486d5f17581288f1217410da3e0700d195a").unwrap(),
TransactionId::from_str("0x131fc4cb8f315ae36ae3bf6a4e4b3486d5f17581288f1217410da3e0700d195a00000000").unwrap(),
incoming_transaction,
);

Expand Down
4 changes: 2 additions & 2 deletions sdk/src/wallet/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ mod tests {
0,
WalletEvent::TransactionInclusion(TransactionInclusionEvent {
transaction_id: TransactionId::from_str(
"0x2289d9981fb23cc5f4f6c2742685eeb480f8476089888aa886a18232bad81989",
"0x2289d9981fb23cc5f4f6c2742685eeb480f8476089888aa886a18232bad8198900000000",
)
.expect("invalid tx id"),
inclusion_state: InclusionState::Confirmed,
Expand All @@ -184,7 +184,7 @@ mod tests {
0,
WalletEvent::TransactionInclusion(TransactionInclusionEvent {
transaction_id: TransactionId::from_str(
"0x2289d9981fb23cc5f4f6c2742685eeb480f8476089888aa886a18232bad81989",
"0x2289d9981fb23cc5f4f6c2742685eeb480f8476089888aa886a18232bad8198900000000",
)
.expect("invalid tx id"),
inclusion_state: InclusionState::Confirmed,
Expand Down
7 changes: 5 additions & 2 deletions sdk/src/wallet/storage/participation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ impl StorageManager {
#[cfg(test)]
mod tests {
use super::*;
use crate::{types::block::payload::transaction::TransactionId, wallet::storage::adapter::memory::Memory};
use crate::{types::block::payload::transaction::TransactionHash, wallet::storage::adapter::memory::Memory};

#[tokio::test]
async fn insert_get_remove_participation_event() {
Expand Down Expand Up @@ -155,7 +155,10 @@ mod tests {
);

let outputs_participation = std::iter::once((
OutputId::new(TransactionId::new([3; 32]), 0).unwrap(),
TransactionHash::new([3; 32])
.with_slot_index(0)
.with_output_index(0)
.unwrap(),
OutputStatusResponse::mock(),
))
.collect::<HashMap<_, _>>();
Expand Down
45 changes: 39 additions & 6 deletions sdk/tests/client/input_signing_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ fn input_signing_data_conversion() {
output,
output_metadata: OutputMetadata::new(
BlockId::from_str("0xedf5f572c58ddf4b4f9567d82bf96689cc68b730df796d822b4b9fb643f5efda00000000").unwrap(),
OutputId::from_str("0xbce525324af12eda02bf7927e92cea3a8e8322d0f41966271443e6c3b245a4400000").unwrap(),
OutputId::from_str("0xbce525324af12eda02bf7927e92cea3a8e8322d0f41966271443e6c3b245a440000000000000")
.unwrap(),
false,
Some(
SlotCommitmentId::from_str(
Expand All @@ -48,7 +49,8 @@ fn input_signing_data_conversion() {
.unwrap(),
),
Some(
TransactionId::from_str("0x24a1f46bdb6b2bf38f1c59f73cdd4ae5b418804bb231d76d06fbf246498d5883").unwrap(),
TransactionId::from_str("0x24a1f46bdb6b2bf38f1c59f73cdd4ae5b418804bb231d76d06fbf246498d588300000000")
.unwrap(),
),
Some(
SlotCommitmentId::from_str(
Expand All @@ -71,14 +73,45 @@ fn input_signing_data_conversion() {
InputSigningData::try_from_dto_with_params(input_signing_data_dto.clone(), &protocol_parameters).unwrap();
assert_eq!(input_signing_data, restored_input_signing_data);

let input_signing_data_dto_str = r#"{"output":{"type":0,"amount":"1000000","mana":"0","unlockConditions":[{"type":0,"address":{"type":0,"pubKeyHash":"0x7ffec9e1233204d9c6dce6812b1539ee96af691ca2e4d9065daa85907d33e5d3"}}]},"outputMetadata":{"blockId":"0xedf5f572c58ddf4b4f9567d82bf96689cc68b730df796d822b4b9fb643f5efda00000000","transactionId":"0xbce525324af12eda02bf7927e92cea3a8e8322d0f41966271443e6c3b245a440","outputIndex":0,"isSpent":false,"commitmentIdSpent":"0xedf5f572c58ddf4b4f9567d82bf96689cc68b730df796d822b4b9fb643f5efda4f9567d82bf96689","transactionIdSpent":"0x24a1f46bdb6b2bf38f1c59f73cdd4ae5b418804bb231d76d06fbf246498d5883","includedCommitmentId":"0xedf5f572c58ddf4b4f9567d82bf96689cc68b730df796d822b4b9fb643f5efda4f9567d82bf96689","latestCommitmentId":"0xedf5f572c58ddf4b4f9567d82bf96689cc68b730df796d822b4b9fb643f5efda4f9567d82bf96689"},"chain":{"coinType":4219,"account":0,"change":0,"addressIndex":0}}"#;
let input_signing_data_dto_json = serde_json::json!({
"output": {
"type": 0,
"amount": "1000000",
"mana": "0",
"unlockConditions": [
{
"type": 0,
"address": {
"type": 0,
"pubKeyHash": "0x7ffec9e1233204d9c6dce6812b1539ee96af691ca2e4d9065daa85907d33e5d3"
}
}
]
},
"outputMetadata": {
"blockId": "0xedf5f572c58ddf4b4f9567d82bf96689cc68b730df796d822b4b9fb643f5efda00000000",
"transactionId": "0xbce525324af12eda02bf7927e92cea3a8e8322d0f41966271443e6c3b245a44000000000",
"outputIndex": 0,
"isSpent": false,
"commitmentIdSpent": "0xedf5f572c58ddf4b4f9567d82bf96689cc68b730df796d822b4b9fb643f5efda4f9567d82bf96689",
"transactionIdSpent": "0x24a1f46bdb6b2bf38f1c59f73cdd4ae5b418804bb231d76d06fbf246498d588300000000",
"includedCommitmentId": "0xedf5f572c58ddf4b4f9567d82bf96689cc68b730df796d822b4b9fb643f5efda4f9567d82bf96689",
"latestCommitmentId": "0xedf5f572c58ddf4b4f9567d82bf96689cc68b730df796d822b4b9fb643f5efda4f9567d82bf96689"},
"chain": {
"coinType": 4219,
"account": 0,
"change": 0,
"addressIndex": 0
}
}
);
assert_eq!(
serde_json::to_string(&input_signing_data_dto).unwrap(),
input_signing_data_dto_str
serde_json::to_value(&input_signing_data_dto).unwrap(),
input_signing_data_dto_json
);

let restored_input_signing_data_dto =
serde_json::from_str::<InputSigningDataDto>(input_signing_data_dto_str).unwrap();
serde_json::from_value::<InputSigningDataDto>(input_signing_data_dto_json).unwrap();
assert_eq!(restored_input_signing_data_dto.chain.as_ref(), Some(&bip44_chain));

let restored_input_signing_data =
Expand Down
Loading