1use crate::merkle::error::Error;
2use crate::{Side, block_range, internal_hash};
3use chia_protocol::Bytes32;
4#[cfg(feature = "py-bindings")]
5use chia_py_streamable_macro::{PyJsonDict, PyStreamable};
6use chia_streamable_macro::Streamable;
7use chia_traits::Streamable;
8#[cfg(feature = "py-bindings")]
9use pyo3::{Bound, FromPyObject, IntoPyObject, PyAny, PyErr, Python, pyclass, pymethods};
10use std::ops::Range;
11
12pub type TreeIndexType = u32;
13
14#[cfg_attr(
15 feature = "py-bindings",
16 pyclass(from_py_object),
17 derive(PyJsonDict, PyStreamable)
18)]
19#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Streamable)]
20#[cfg(feature = "py-bindings")]
24pub struct TreeIndex(#[pyo3(get, name = "raw")] pub TreeIndexType);
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Streamable)]
27#[cfg(not(feature = "py-bindings"))]
28pub struct TreeIndex(pub TreeIndexType);
29
30#[cfg(feature = "py-bindings")]
31#[pymethods]
32impl TreeIndex {
33 #[new]
34 pub fn py_new(raw: TreeIndexType) -> Self {
35 Self(raw)
36 }
37}
38
39impl std::fmt::Display for TreeIndex {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 self.0.fmt(f)
42 }
43}
44
45#[cfg_attr(
46 feature = "py-bindings",
47 derive(FromPyObject, IntoPyObject, PyJsonDict),
48 pyo3(transparent)
49)]
50#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Streamable)]
51pub struct Parent(pub Option<TreeIndex>);
52
53#[cfg_attr(
54 feature = "py-bindings",
55 derive(FromPyObject, IntoPyObject, PyJsonDict),
56 pyo3(transparent)
57)]
58#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
59#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Streamable)]
60pub struct Hash(pub Bytes32);
61
62#[cfg_attr(
66 feature = "py-bindings",
67 pyclass(from_py_object),
68 derive(PyJsonDict, PyStreamable)
69)]
70#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
71#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Streamable)]
72#[cfg(feature = "py-bindings")]
76pub struct KeyId(#[pyo3(get, name = "raw")] pub i64);
77
78#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
79#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Streamable)]
80#[cfg(not(feature = "py-bindings"))]
81pub struct KeyId(pub i64);
82
83#[cfg(feature = "py-bindings")]
84#[pymethods]
85impl KeyId {
86 #[new]
87 pub fn py_new(raw: i64) -> Self {
88 Self(raw)
89 }
90}
91
92#[cfg_attr(
93 feature = "py-bindings",
94 pyclass(from_py_object),
95 derive(PyJsonDict, PyStreamable)
96)]
97#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
98#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Streamable)]
99#[cfg(feature = "py-bindings")]
103pub struct ValueId(#[pyo3(get, name = "raw")] pub i64);
104
105#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
106#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Streamable)]
107#[cfg(not(feature = "py-bindings"))]
108pub struct ValueId(pub i64);
109
110#[cfg(feature = "py-bindings")]
111#[pymethods]
112impl ValueId {
113 #[new]
114 pub fn py_new(raw: i64) -> Self {
115 Self(raw)
116 }
117}
118
119impl std::fmt::Display for ValueId {
120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121 self.0.fmt(f)
122 }
123}
124
125impl std::fmt::Display for KeyId {
126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127 self.0.fmt(f)
128 }
129}
130
131const METADATA_RANGE: Range<usize> = 0..METADATA_SIZE;
133pub const METADATA_SIZE: usize = 2;
134pub const DATA_SIZE: usize = 53;
136pub const BLOCK_SIZE: usize = METADATA_SIZE + DATA_SIZE;
137
138pub type BlockBytes = [u8; BLOCK_SIZE];
139type MetadataBytes = [u8; METADATA_SIZE];
140type DataBytes = [u8; DATA_SIZE];
141
142const DATA_RANGE: Range<usize> = METADATA_SIZE..METADATA_SIZE + DATA_SIZE;
143
144pub(crate) fn streamable_from_bytes_ignore_extra_bytes<T>(
145 bytes: &[u8],
146) -> Result<T, chia_traits::chia_error::Error>
147where
148 T: Streamable,
149{
150 let mut cursor = std::io::Cursor::new(bytes);
151 T::parse::<false>(&mut cursor)
152}
153
154#[repr(u8)]
155#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Streamable)]
156pub enum NodeType {
157 Internal = 0,
158 Leaf = 1,
159}
160
161#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, Streamable)]
162pub struct NodeMetadata {
163 pub node_type: NodeType,
165 pub dirty: bool,
166}
167
168#[cfg_attr(
169 feature = "py-bindings",
170 pyclass(get_all, from_py_object),
171 derive(PyJsonDict, PyStreamable)
172)]
173#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Streamable)]
174pub struct InternalNode {
175 pub hash: Hash,
176 pub parent: Parent,
177 pub left: TreeIndex,
178 pub right: TreeIndex,
179}
180
181impl InternalNode {
182 pub fn sibling_index(&self, index: TreeIndex) -> Result<TreeIndex, Error> {
183 if index == self.right {
184 Ok(self.left)
185 } else if index == self.left {
186 Ok(self.right)
187 } else {
188 Err(Error::IndexIsNotAChild(index))
189 }
190 }
191
192 pub fn get_sibling_side(&self, index: TreeIndex) -> Result<Side, Error> {
193 if self.left == index {
194 Ok(Side::Right)
195 } else if self.right == index {
196 Ok(Side::Left)
197 } else {
198 Err(Error::IndexIsNotAChild(index))
199 }
200 }
201}
202
203#[cfg_attr(
204 feature = "py-bindings",
205 pyclass(get_all, from_py_object),
206 derive(PyJsonDict, PyStreamable)
207)]
208#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Streamable)]
209pub struct LeafNode {
210 pub hash: Hash,
211 pub parent: Parent,
212 pub key: KeyId,
213 pub value: ValueId,
214}
215
216#[derive(Clone, Copy, Debug, PartialEq, Eq)]
218pub enum Node {
219 Internal(InternalNode),
220 Leaf(LeafNode),
221}
222
223impl Node {
224 pub fn parent(&self) -> Parent {
225 match self {
226 Node::Internal(node) => node.parent,
227 Node::Leaf(node) => node.parent,
228 }
229 }
230
231 pub fn set_parent(&mut self, parent: Parent) {
232 match self {
233 Node::Internal(node) => node.parent = parent,
234 Node::Leaf(node) => node.parent = parent,
235 }
236 }
237
238 pub fn hash(&self) -> Hash {
239 match self {
240 Node::Internal(node) => node.hash,
241 Node::Leaf(node) => node.hash,
242 }
243 }
244
245 pub fn set_hash(&mut self, hash: Hash) {
246 match self {
247 Node::Internal(node) => node.hash = hash,
248 Node::Leaf(node) => node.hash = hash,
249 }
250 }
251
252 pub fn from_bytes(
253 metadata: &NodeMetadata,
254 blob: &DataBytes,
255 ) -> Result<Self, chia_traits::chia_error::Error> {
256 Ok(match metadata.node_type {
257 NodeType::Internal => Node::Internal(streamable_from_bytes_ignore_extra_bytes(blob)?),
258 NodeType::Leaf => Node::Leaf(streamable_from_bytes_ignore_extra_bytes(blob)?),
259 })
260 }
261
262 pub fn to_bytes(&self) -> Result<DataBytes, Error> {
263 let mut base = match self {
264 Node::Internal(node) => node.to_bytes(),
265 Node::Leaf(node) => node.to_bytes(),
266 }
267 .map_err(Error::Streaming)?;
268 assert!(base.len() <= DATA_SIZE);
269 base.resize(DATA_SIZE, 0);
270 Ok(base
271 .as_slice()
272 .try_into()
273 .expect("padding was added above, might be too large"))
274 }
275
276 pub fn expect_leaf(&self, message: &str) -> LeafNode {
277 let Node::Leaf(leaf) = self else {
278 let message = message.replace("<<self>>", &format!("{self:?}"));
279 panic!("{}", message)
280 };
281
282 *leaf
283 }
284
285 pub fn expect_internal(&self, message: &str) -> InternalNode {
286 let Node::Internal(internal) = self else {
287 let message = message.replace("<<self>>", &format!("{self:?}"));
288 panic!("{}", message)
289 };
290
291 *internal
292 }
293
294 pub fn try_into_leaf(self) -> Result<LeafNode, Error> {
295 match self {
296 Node::Leaf(leaf) => Ok(leaf),
297 Node::Internal(internal) => Err(Error::NodeNotALeaf(internal)),
298 }
299 }
300}
301
302#[cfg(feature = "py-bindings")]
303impl<'py> IntoPyObject<'py> for Node {
304 type Target = PyAny;
305 type Output = Bound<'py, Self::Target>;
306 type Error = PyErr;
307
308 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
309 match self {
310 Node::Internal(node) => Ok(node.into_pyobject(py)?.into_any()),
311 Node::Leaf(node) => Ok(node.into_pyobject(py)?.into_any()),
312 }
313 }
314}
315
316#[derive(Clone, Copy, Debug, PartialEq)]
318pub struct Block {
319 pub metadata: NodeMetadata,
321 pub node: Node,
322}
323
324impl Block {
325 pub fn to_bytes(&self) -> Result<BlockBytes, Error> {
326 let mut blob: BlockBytes = [0; BLOCK_SIZE];
327 blob[METADATA_RANGE].copy_from_slice(&self.metadata.to_bytes().map_err(Error::Streaming)?);
328 blob[DATA_RANGE].copy_from_slice(&self.node.to_bytes()?);
329
330 Ok(blob)
331 }
332
333 pub fn from_bytes(blob: BlockBytes) -> Result<Self, Error> {
334 let metadata_blob: MetadataBytes = blob[METADATA_RANGE].try_into().unwrap();
335 let data_blob: DataBytes = blob[DATA_RANGE].try_into().unwrap();
336 let metadata =
337 NodeMetadata::from_bytes(&metadata_blob).map_err(Error::FailedLoadingMetadata)?;
338 let node = Node::from_bytes(&metadata, &data_blob).map_err(Error::FailedLoadingNode)?;
339
340 Ok(Block { metadata, node })
341 }
342
343 pub fn update_hash(&mut self, left: &Hash, right: &Hash) {
344 self.node.set_hash(internal_hash(left, right));
345 self.metadata.dirty = false;
346 }
347}
348
349pub fn try_get_block(blob: &[u8], index: TreeIndex) -> Result<Block, Error> {
350 let range = block_range(index);
351 let block_bytes: BlockBytes = blob
352 .get(range)
353 .ok_or(Error::BlockIndexOutOfBounds(index))?
354 .try_into()
355 .expect("used block_range() so should be correct length");
356
357 Block::from_bytes(block_bytes)
358}