ergotree_ir/ergo_tree/
tree_header.rs

1//! ErgoTree header
2
3use derive_more::From;
4use thiserror::Error;
5
6use crate::serialization::sigma_byte_reader::SigmaByteRead;
7use crate::serialization::sigma_byte_writer::SigmaByteWrite;
8
9/// Currently we define meaning for only first byte, which may be extended in future versions.
10///    7  6  5  4  3  2  1  0
11///  -------------------------
12///  |  |  |  |  |  |  |  |  |
13///  -------------------------
14///  Bit 7 == 1 if the header contains more than 1 byte (default == 0)
15///  Bit 6 - reserved for GZIP compression (should be 0)
16///  Bit 5 == 1 - reserved for context dependent costing (should be = 0)
17///  Bit 4 == 1 if constant segregation is used for this ErgoTree (default = 0)
18///  (see <https://github.com/ScorexFoundation/sigmastate-interpreter/issues/264>)
19///  Bit 3 == 1 if size of the whole tree is serialized after the header byte (default = 0)
20///  Bits 2-0 - language version (current version == 0)
21///
22///  Currently we don't specify interpretation for the second and other bytes of the header.
23///  We reserve the possibility to extend header by using Bit 7 == 1 and chain additional bytes as in VLQ.
24///  Once the new bytes are required, a new version of the language should be created and implemented.
25///  That new language will give an interpretation for the new bytes.
26#[derive(PartialEq, Eq, Debug, Clone)]
27pub struct ErgoTreeHeader {
28    version: ErgoTreeVersion,
29    is_constant_segregation: bool,
30    has_size: bool,
31}
32
33impl ErgoTreeHeader {
34    /// Serialization
35    pub fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
36        w.put_u8(self.serialized())
37    }
38    /// Deserialization
39    pub fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, ErgoTreeHeaderError> {
40        let header_byte = r
41            .get_u8()
42            .map_err(|e| ErgoTreeHeaderError::IoError(e.to_string()))?;
43
44        ErgoTreeHeader::new(header_byte)
45    }
46}
47
48impl ErgoTreeHeader {
49    const CONSTANT_SEGREGATION_FLAG: u8 = 0b0001_0000;
50    const HAS_SIZE_FLAG: u8 = 0b0000_1000;
51
52    /// Parse from byte
53    pub fn new(header_byte: u8) -> Result<Self, ErgoTreeHeaderError> {
54        let version = ErgoTreeVersion::parse_version(header_byte)?;
55        let has_size = header_byte & Self::HAS_SIZE_FLAG != 0;
56        let is_constant_segregation = header_byte & Self::CONSTANT_SEGREGATION_FLAG != 0;
57        Ok(ErgoTreeHeader {
58            version,
59            is_constant_segregation,
60            has_size,
61        })
62    }
63
64    /// Serialize to byte
65    pub fn serialized(&self) -> u8 {
66        let mut header_byte: u8 = self.version.0;
67        if self.is_constant_segregation {
68            header_byte |= Self::CONSTANT_SEGREGATION_FLAG;
69        }
70        if self.has_size {
71            header_byte |= Self::HAS_SIZE_FLAG;
72        }
73        header_byte
74    }
75
76    /// Return a header with version set to 0 and constant segregation flag set to the given value
77    pub fn v0(constant_segregation: bool) -> Self {
78        ErgoTreeHeader {
79            version: ErgoTreeVersion::V0,
80            is_constant_segregation: constant_segregation,
81            has_size: false,
82        }
83    }
84
85    /// Return a header with version set to 1 (with size flag set) and constant segregation flag set to the given value
86    pub fn v1(constant_segregation: bool) -> Self {
87        ErgoTreeHeader {
88            version: ErgoTreeVersion::V1,
89            is_constant_segregation: constant_segregation,
90            has_size: true,
91        }
92    }
93
94    /// Returns true if constant segregation flag is set
95    pub fn is_constant_segregation(&self) -> bool {
96        self.is_constant_segregation
97    }
98
99    /// Returns true if size flag is set
100    pub fn has_size(&self) -> bool {
101        self.has_size
102    }
103
104    /// Returns ErgoTree version
105    pub fn version(&self) -> &ErgoTreeVersion {
106        &self.version
107    }
108}
109
110/// Header parsing error
111#[derive(Error, PartialEq, Eq, Debug, Clone, From)]
112pub enum ErgoTreeHeaderError {
113    /// Invalid version
114    #[error("Invalid version: {0}")]
115    VersionError(ErgoTreeVersionError),
116    /// IO error
117    #[error("IO error: {0}")]
118    IoError(String),
119}
120
121/// ErgoTree version 0..=7, should fit in 3 bits
122#[derive(PartialEq, Eq, Debug, Clone)]
123pub struct ErgoTreeVersion(u8);
124
125impl ErgoTreeVersion {
126    /// Header mask to extract version bits.
127    pub const VERSION_MASK: u8 = 0x07;
128    /// Version 0
129    pub const V0: Self = ErgoTreeVersion(0);
130    /// Version 1 (size flag is mandatory)
131    pub const V1: Self = ErgoTreeVersion(1);
132
133    /// Returns a value of the version bits from the given header byte.
134    pub fn parse_version(header_byte: u8) -> Result<Self, ErgoTreeVersionError> {
135        let version = header_byte & ErgoTreeVersion::VERSION_MASK;
136        if version <= 1 {
137            Ok(ErgoTreeVersion(version))
138        } else {
139            Err(ErgoTreeVersionError::InvalidVersion(version))
140        }
141    }
142}
143
144/// Version parsing error
145#[derive(Error, PartialEq, Eq, Debug, Clone, From)]
146pub enum ErgoTreeVersionError {
147    /// Invalid version
148    #[error("Invalid version: {0}")]
149    InvalidVersion(u8),
150}