bee_block/payload/
mod.rs

1// Copyright 2020-2021 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4//! The payload module defines the core data types for representing block payloads.
5
6pub mod milestone;
7pub mod tagged_data;
8pub mod transaction;
9pub mod treasury_transaction;
10
11use alloc::boxed::Box;
12use core::ops::Deref;
13
14use packable::{
15    error::{UnpackError, UnpackErrorExt},
16    packer::Packer,
17    unpacker::Unpacker,
18    Packable, PackableExt,
19};
20
21pub(crate) use self::{
22    milestone::{MilestoneMetadataLength, MilestoneOptionCount, ReceiptFundsCount, SignatureCount},
23    tagged_data::{TagLength, TaggedDataLength},
24    transaction::{InputCount, OutputCount},
25};
26pub use self::{
27    milestone::{MilestoneOptions, MilestonePayload},
28    tagged_data::TaggedDataPayload,
29    transaction::TransactionPayload,
30    treasury_transaction::TreasuryTransactionPayload,
31};
32use crate::{protocol::ProtocolParameters, Error};
33
34/// A generic payload that can represent different types defining block payloads.
35#[derive(Clone, Debug, Eq, PartialEq)]
36#[cfg_attr(
37    feature = "serde",
38    derive(serde::Serialize, serde::Deserialize),
39    serde(tag = "type", content = "data")
40)]
41pub enum Payload {
42    /// A transaction payload.
43    Transaction(Box<TransactionPayload>),
44    /// A milestone payload.
45    Milestone(Box<MilestonePayload>),
46    /// A treasury transaction payload.
47    TreasuryTransaction(Box<TreasuryTransactionPayload>),
48    /// A tagged data payload.
49    TaggedData(Box<TaggedDataPayload>),
50}
51
52impl From<TransactionPayload> for Payload {
53    fn from(payload: TransactionPayload) -> Self {
54        Self::Transaction(Box::new(payload))
55    }
56}
57
58impl From<MilestonePayload> for Payload {
59    fn from(payload: MilestonePayload) -> Self {
60        Self::Milestone(Box::new(payload))
61    }
62}
63
64impl From<TreasuryTransactionPayload> for Payload {
65    fn from(payload: TreasuryTransactionPayload) -> Self {
66        Self::TreasuryTransaction(Box::new(payload))
67    }
68}
69
70impl From<TaggedDataPayload> for Payload {
71    fn from(payload: TaggedDataPayload) -> Self {
72        Self::TaggedData(Box::new(payload))
73    }
74}
75
76impl Payload {
77    /// Returns the payload kind of a `Payload`.
78    pub fn kind(&self) -> u32 {
79        match self {
80            Self::Transaction(_) => TransactionPayload::KIND,
81            Self::Milestone(_) => MilestonePayload::KIND,
82            Self::TreasuryTransaction(_) => TreasuryTransactionPayload::KIND,
83            Self::TaggedData(_) => TaggedDataPayload::KIND,
84        }
85    }
86}
87
88impl Packable for Payload {
89    type UnpackError = Error;
90    type UnpackVisitor = ProtocolParameters;
91
92    fn pack<P: Packer>(&self, packer: &mut P) -> Result<(), P::Error> {
93        match self {
94            Payload::Transaction(transaction) => {
95                TransactionPayload::KIND.pack(packer)?;
96                transaction.pack(packer)
97            }
98            Payload::Milestone(milestone) => {
99                MilestonePayload::KIND.pack(packer)?;
100                milestone.pack(packer)
101            }
102            Payload::TreasuryTransaction(treasury_transaction) => {
103                TreasuryTransactionPayload::KIND.pack(packer)?;
104                treasury_transaction.pack(packer)
105            }
106            Payload::TaggedData(tagged_data) => {
107                TaggedDataPayload::KIND.pack(packer)?;
108                tagged_data.pack(packer)
109            }
110        }?;
111
112        Ok(())
113    }
114
115    fn unpack<U: Unpacker, const VERIFY: bool>(
116        unpacker: &mut U,
117        visitor: &Self::UnpackVisitor,
118    ) -> Result<Self, UnpackError<Self::UnpackError, U::Error>> {
119        Ok(match u32::unpack::<_, VERIFY>(unpacker, &()).coerce()? {
120            TransactionPayload::KIND => {
121                Payload::from(TransactionPayload::unpack::<_, VERIFY>(unpacker, visitor).coerce()?)
122            }
123            MilestonePayload::KIND => Payload::from(MilestonePayload::unpack::<_, VERIFY>(unpacker, visitor).coerce()?),
124            TreasuryTransactionPayload::KIND => {
125                Payload::from(TreasuryTransactionPayload::unpack::<_, VERIFY>(unpacker, visitor).coerce()?)
126            }
127            TaggedDataPayload::KIND => Payload::from(TaggedDataPayload::unpack::<_, VERIFY>(unpacker, &()).coerce()?),
128            k => return Err(Error::InvalidPayloadKind(k)).map_err(UnpackError::Packable),
129        })
130    }
131}
132
133/// Representation of an optional [`Payload`].
134/// Essentially an `Option<Payload>` with a different [`Packable`] implementation, to conform to specs.
135#[derive(Clone, Debug, Eq, PartialEq)]
136#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
137pub struct OptionalPayload(Option<Payload>);
138
139impl OptionalPayload {
140    fn pack_ref<P: Packer>(payload: &Payload, packer: &mut P) -> Result<(), P::Error> {
141        (payload.packed_len() as u32).pack(packer)?;
142        payload.pack(packer)
143    }
144}
145
146impl Deref for OptionalPayload {
147    type Target = Option<Payload>;
148
149    fn deref(&self) -> &Self::Target {
150        &self.0
151    }
152}
153
154impl Packable for OptionalPayload {
155    type UnpackError = Error;
156    type UnpackVisitor = ProtocolParameters;
157
158    fn pack<P: Packer>(&self, packer: &mut P) -> Result<(), P::Error> {
159        match &self.0 {
160            None => 0u32.pack(packer),
161            Some(payload) => Self::pack_ref(payload, packer),
162        }
163    }
164
165    fn unpack<U: Unpacker, const VERIFY: bool>(
166        unpacker: &mut U,
167        visitor: &Self::UnpackVisitor,
168    ) -> Result<Self, UnpackError<Self::UnpackError, U::Error>> {
169        let len = u32::unpack::<_, VERIFY>(unpacker, &()).coerce()? as usize;
170
171        if len > 0 {
172            unpacker.ensure_bytes(len)?;
173
174            let start_opt = unpacker.read_bytes();
175
176            let payload = Payload::unpack::<_, VERIFY>(unpacker, visitor)?;
177
178            let actual_len = if let (Some(start), Some(end)) = (start_opt, unpacker.read_bytes()) {
179                end - start
180            } else {
181                payload.packed_len()
182            };
183
184            if len != actual_len {
185                Err(UnpackError::Packable(Error::InvalidPayloadLength {
186                    expected: len,
187                    actual: actual_len,
188                }))
189            } else {
190                Ok(Self(Some(payload)))
191            }
192        } else {
193            Ok(Self(None))
194        }
195    }
196}
197
198// FIXME: does this break any invariant about the Payload length?
199impl From<Option<Payload>> for OptionalPayload {
200    fn from(option: Option<Payload>) -> Self {
201        Self(option)
202    }
203}
204
205#[allow(clippy::from_over_into)]
206impl Into<Option<Payload>> for OptionalPayload {
207    fn into(self) -> Option<Payload> {
208        self.0
209    }
210}
211
212#[cfg(feature = "dto")]
213#[allow(missing_docs)]
214pub mod dto {
215    use serde::{Deserialize, Serialize};
216
217    use super::*;
218    pub use super::{
219        milestone::dto::MilestonePayloadDto, tagged_data::dto::TaggedDataPayloadDto,
220        transaction::dto::TransactionPayloadDto, treasury_transaction::dto::TreasuryTransactionPayloadDto,
221    };
222    use crate::error::dto::DtoError;
223
224    /// Describes all the different payload types.
225    #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
226    #[serde(untagged)]
227    pub enum PayloadDto {
228        Transaction(Box<TransactionPayloadDto>),
229        Milestone(Box<MilestonePayloadDto>),
230        TreasuryTransaction(Box<TreasuryTransactionPayloadDto>),
231        TaggedData(Box<TaggedDataPayloadDto>),
232    }
233
234    impl From<TransactionPayloadDto> for PayloadDto {
235        fn from(payload: TransactionPayloadDto) -> Self {
236            Self::Transaction(Box::new(payload))
237        }
238    }
239
240    impl From<MilestonePayloadDto> for PayloadDto {
241        fn from(payload: MilestonePayloadDto) -> Self {
242            Self::Milestone(Box::new(payload))
243        }
244    }
245
246    impl From<TreasuryTransactionPayloadDto> for PayloadDto {
247        fn from(payload: TreasuryTransactionPayloadDto) -> Self {
248            Self::TreasuryTransaction(Box::new(payload))
249        }
250    }
251
252    impl From<TaggedDataPayloadDto> for PayloadDto {
253        fn from(payload: TaggedDataPayloadDto) -> Self {
254            Self::TaggedData(Box::new(payload))
255        }
256    }
257
258    impl From<&Payload> for PayloadDto {
259        fn from(value: &Payload) -> Self {
260            match value {
261                Payload::Transaction(p) => PayloadDto::Transaction(Box::new(TransactionPayloadDto::from(p.as_ref()))),
262                Payload::Milestone(p) => PayloadDto::Milestone(Box::new(MilestonePayloadDto::from(p.as_ref()))),
263                Payload::TreasuryTransaction(p) => {
264                    PayloadDto::TreasuryTransaction(Box::new(TreasuryTransactionPayloadDto::from(p.as_ref())))
265                }
266                Payload::TaggedData(p) => PayloadDto::TaggedData(Box::new(TaggedDataPayloadDto::from(p.as_ref()))),
267            }
268        }
269    }
270
271    impl Payload {
272        pub fn try_from_dto(value: &PayloadDto, protocol_parameters: &ProtocolParameters) -> Result<Payload, DtoError> {
273            Ok(match value {
274                PayloadDto::Transaction(p) => {
275                    Payload::from(TransactionPayload::try_from_dto(p.as_ref(), protocol_parameters)?)
276                }
277                PayloadDto::Milestone(p) => {
278                    Payload::from(MilestonePayload::try_from_dto(p.as_ref(), protocol_parameters)?)
279                }
280                PayloadDto::TreasuryTransaction(p) => Payload::from(TreasuryTransactionPayload::try_from_dto(
281                    p.as_ref(),
282                    protocol_parameters.token_supply(),
283                )?),
284                PayloadDto::TaggedData(p) => Payload::from(TaggedDataPayload::try_from(p.as_ref())?),
285            })
286        }
287    }
288}