bee_block/output/
output_id.rs

1// Copyright 2020-2021 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use core::str::FromStr;
5
6use crypto::hashes::{blake2b::Blake2b256, Digest};
7use packable::{bounded::BoundedU16, PackableExt};
8
9use crate::{output::OUTPUT_INDEX_RANGE, payload::transaction::TransactionId, Error};
10
11pub(crate) type OutputIndex = BoundedU16<{ *OUTPUT_INDEX_RANGE.start() }, { *OUTPUT_INDEX_RANGE.end() }>;
12
13/// The identifier of an [`Output`](crate::output::Output).
14#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, packable::Packable)]
15#[packable(unpack_error = Error)]
16pub struct OutputId {
17    transaction_id: TransactionId,
18    #[packable(unpack_error_with = Error::InvalidInputOutputIndex)]
19    index: OutputIndex,
20}
21
22impl OutputId {
23    /// The length of a [`OutputId`].
24    pub const LENGTH: usize = TransactionId::LENGTH + core::mem::size_of::<u16>();
25
26    /// Creates a new [`OutputId`].
27    pub fn new(transaction_id: TransactionId, index: u16) -> Result<Self, Error> {
28        index
29            .try_into()
30            .map(|index| Self { transaction_id, index })
31            .map_err(Error::InvalidInputOutputIndex)
32    }
33
34    /// Returns the [`TransactionId`] of an [`OutputId`].
35    #[inline(always)]
36    pub fn transaction_id(&self) -> &TransactionId {
37        &self.transaction_id
38    }
39
40    /// Returns the index of an [`OutputId`].
41    #[inline(always)]
42    pub fn index(&self) -> u16 {
43        self.index.get()
44    }
45
46    /// Splits an [`OutputId`] into its [`TransactionId`] and index.
47    #[inline(always)]
48    pub fn split(self) -> (TransactionId, u16) {
49        (self.transaction_id, self.index())
50    }
51
52    /// Hash the [`OutputId`] with BLAKE2b-256.
53    #[inline(always)]
54    pub fn hash(self) -> [u8; 32] {
55        Blake2b256::digest(&self.pack_to_vec()).into()
56    }
57}
58
59#[cfg(feature = "serde")]
60string_serde_impl!(OutputId);
61
62impl TryFrom<[u8; OutputId::LENGTH]> for OutputId {
63    type Error = Error;
64
65    fn try_from(bytes: [u8; OutputId::LENGTH]) -> Result<Self, Self::Error> {
66        let (transaction_id, index) = bytes.split_at(TransactionId::LENGTH);
67
68        Self::new(
69            // Unwrap is fine because size is already known and valid.
70            From::<[u8; TransactionId::LENGTH]>::from(transaction_id.try_into().unwrap()),
71            // Unwrap is fine because size is already known and valid.
72            u16::from_le_bytes(index.try_into().unwrap()),
73        )
74    }
75}
76
77impl FromStr for OutputId {
78    type Err = Error;
79
80    fn from_str(s: &str) -> Result<Self, Self::Err> {
81        let bytes: [u8; OutputId::LENGTH] = prefix_hex::decode(s).map_err(Error::HexError)?;
82        Self::try_from(bytes)
83    }
84}
85
86impl core::fmt::Display for OutputId {
87    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
88        let mut buffer = [0u8; OutputId::LENGTH];
89        let (transaction_id, index) = buffer.split_at_mut(TransactionId::LENGTH);
90        transaction_id.copy_from_slice(self.transaction_id.as_ref());
91        index.copy_from_slice(&self.index().to_le_bytes());
92        write!(f, "{}", prefix_hex::encode(buffer))
93    }
94}
95
96impl core::fmt::Debug for OutputId {
97    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
98        write!(f, "OutputId({})", self)
99    }
100}
101
102#[cfg(feature = "inx")]
103mod inx {
104    use super::*;
105
106    impl From<OutputId> for ::inx::proto::OutputId {
107        fn from(value: OutputId) -> Self {
108            Self {
109                id: value.pack_to_vec(),
110            }
111        }
112    }
113
114    impl TryFrom<::inx::proto::OutputId> for OutputId {
115        type Error = crate::error::inx::InxError;
116
117        fn try_from(value: ::inx::proto::OutputId) -> Result<Self, Self::Error> {
118            let bytes: [u8; OutputId::LENGTH] =
119                value.id.try_into().map_err(|e| Self::Error::InvalidId("OutputId", e))?;
120            OutputId::try_from(bytes).map_err(Self::Error::Block)
121        }
122    }
123}