1use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
2use primitive_types::U256;
3
4use crate::Version;
5
6pub const MAX_NODE_DATA_SIZE: usize = 32 + 4 + 4 + 4 + 4 + 32 + 32 + 32 + 9 + 9 + 9 + 32 + 32 + 9 + 32 + 32 + 9; #[repr(C)]
28#[derive(Debug, Clone, Default)]
29#[cfg_attr(test, derive(PartialEq, Eq))]
30pub struct NodeData {
31 pub consensus_branch_id: u32,
33 pub subtree_commitment: [u8; 32],
35 pub start_time: u32,
37 pub end_time: u32,
39 pub start_target: u32,
41 pub end_target: u32,
43 pub start_sapling_root: [u8; 32],
45 pub end_sapling_root: [u8; 32],
47 pub subtree_total_work: U256,
49 pub start_height: u64,
51 pub end_height: u64,
53 pub sapling_tx: u64,
55}
56
57impl NodeData {
58 pub fn combine(left: &NodeData, right: &NodeData) -> NodeData {
60 crate::V1::combine(left, right)
61 }
62
63 pub(crate) fn combine_inner(
64 subtree_commitment: [u8; 32],
65 left: &NodeData,
66 right: &NodeData,
67 ) -> NodeData {
68 NodeData {
69 consensus_branch_id: left.consensus_branch_id,
70 subtree_commitment,
71 start_time: left.start_time,
72 end_time: right.end_time,
73 start_target: left.start_target,
74 end_target: right.end_target,
75 start_sapling_root: left.start_sapling_root,
76 end_sapling_root: right.end_sapling_root,
77 subtree_total_work: left.subtree_total_work + right.subtree_total_work,
78 start_height: left.start_height,
79 end_height: right.end_height,
80 sapling_tx: left.sapling_tx + right.sapling_tx,
81 }
82 }
83
84 fn write_compact<W: std::io::Write>(w: &mut W, compact: u64) -> std::io::Result<()> {
85 match compact {
86 0..=0xfc => w.write_all(&[compact as u8])?,
87 0xfd..=0xffff => {
88 w.write_all(&[0xfd])?;
89 w.write_u16::<LittleEndian>(compact as u16)?;
90 }
91 0x10000..=0xffff_ffff => {
92 w.write_all(&[0xfe])?;
93 w.write_u32::<LittleEndian>(compact as u32)?;
94 }
95 _ => {
96 w.write_all(&[0xff])?;
97 w.write_u64::<LittleEndian>(compact)?;
98 }
99 }
100 Ok(())
101 }
102
103 fn read_compact<R: std::io::Read>(reader: &mut R) -> std::io::Result<u64> {
104 let result = match reader.read_u8()? {
105 i @ 0..=0xfc => i.into(),
106 0xfd => reader.read_u16::<LittleEndian>()?.into(),
107 0xfe => reader.read_u32::<LittleEndian>()?.into(),
108 _ => reader.read_u64::<LittleEndian>()?,
109 };
110
111 Ok(result)
112 }
113
114 pub fn write<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
116 w.write_all(&self.subtree_commitment)?;
117 w.write_u32::<LittleEndian>(self.start_time)?;
118 w.write_u32::<LittleEndian>(self.end_time)?;
119 w.write_u32::<LittleEndian>(self.start_target)?;
120 w.write_u32::<LittleEndian>(self.end_target)?;
121 w.write_all(&self.start_sapling_root)?;
122 w.write_all(&self.end_sapling_root)?;
123
124 let mut work_buf = [0u8; 32];
125 self.subtree_total_work.to_little_endian(&mut work_buf[..]);
126 w.write_all(&work_buf)?;
127
128 Self::write_compact(w, self.start_height)?;
129 Self::write_compact(w, self.end_height)?;
130 Self::write_compact(w, self.sapling_tx)?;
131 Ok(())
132 }
133
134 pub fn read<R: std::io::Read>(consensus_branch_id: u32, r: &mut R) -> std::io::Result<Self> {
136 let mut data = NodeData {
137 consensus_branch_id,
138 ..Default::default()
139 };
140 r.read_exact(&mut data.subtree_commitment)?;
141 data.start_time = r.read_u32::<LittleEndian>()?;
142 data.end_time = r.read_u32::<LittleEndian>()?;
143 data.start_target = r.read_u32::<LittleEndian>()?;
144 data.end_target = r.read_u32::<LittleEndian>()?;
145 r.read_exact(&mut data.start_sapling_root)?;
146 r.read_exact(&mut data.end_sapling_root)?;
147
148 let mut work_buf = [0u8; 32];
149 r.read_exact(&mut work_buf)?;
150 data.subtree_total_work = U256::from_little_endian(&work_buf);
151
152 data.start_height = Self::read_compact(r)?;
153 data.end_height = Self::read_compact(r)?;
154 data.sapling_tx = Self::read_compact(r)?;
155
156 Ok(data)
157 }
158
159 pub fn to_bytes(&self) -> Vec<u8> {
161 crate::V1::to_bytes(self)
162 }
163
164 pub fn from_bytes<T: AsRef<[u8]>>(consensus_branch_id: u32, buf: T) -> std::io::Result<Self> {
166 crate::V1::from_bytes(consensus_branch_id, buf)
167 }
168
169 pub fn hash(&self) -> [u8; 32] {
171 crate::V1::hash(self)
172 }
173}
174
175#[derive(Debug, Clone, Default)]
177#[cfg_attr(test, derive(PartialEq, Eq))]
178pub struct V2 {
179 pub v1: NodeData,
181 pub start_orchard_root: [u8; 32],
183 pub end_orchard_root: [u8; 32],
185 pub orchard_tx: u64,
187}
188
189impl V2 {
190 pub(crate) fn combine_inner(subtree_commitment: [u8; 32], left: &V2, right: &V2) -> V2 {
191 V2 {
192 v1: NodeData::combine_inner(subtree_commitment, &left.v1, &right.v1),
193 start_orchard_root: left.start_orchard_root,
194 end_orchard_root: right.end_orchard_root,
195 orchard_tx: left.orchard_tx + right.orchard_tx,
196 }
197 }
198
199 pub fn write<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
201 self.v1.write(w)?;
202 w.write_all(&self.start_orchard_root)?;
203 w.write_all(&self.end_orchard_root)?;
204 NodeData::write_compact(w, self.orchard_tx)?;
205 Ok(())
206 }
207
208 pub fn read<R: std::io::Read>(consensus_branch_id: u32, r: &mut R) -> std::io::Result<Self> {
210 let mut data = V2 {
211 v1: NodeData::read(consensus_branch_id, r)?,
212 ..Default::default()
213 };
214 r.read_exact(&mut data.start_orchard_root)?;
215 r.read_exact(&mut data.end_orchard_root)?;
216 data.orchard_tx = NodeData::read_compact(r)?;
217
218 Ok(data)
219 }
220}
221
222#[derive(Debug, Clone, Default)]
228#[cfg_attr(test, derive(PartialEq, Eq))]
229pub struct V3 {
230 pub v2: V2,
232 pub start_ironwood_root: [u8; 32],
238 pub end_ironwood_root: [u8; 32],
244 pub ironwood_tx: u64,
246}
247
248impl V3 {
249 pub(crate) fn combine_inner(subtree_commitment: [u8; 32], left: &V3, right: &V3) -> V3 {
250 V3 {
251 v2: V2::combine_inner(subtree_commitment, &left.v2, &right.v2),
252 start_ironwood_root: left.start_ironwood_root,
253 end_ironwood_root: right.end_ironwood_root,
254 ironwood_tx: left.ironwood_tx + right.ironwood_tx,
255 }
256 }
257
258 pub fn write<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
260 self.v2.write(w)?;
261 w.write_all(&self.start_ironwood_root)?;
262 w.write_all(&self.end_ironwood_root)?;
263 NodeData::write_compact(w, self.ironwood_tx)?;
264 Ok(())
265 }
266
267 pub fn read<R: std::io::Read>(consensus_branch_id: u32, r: &mut R) -> std::io::Result<Self> {
269 let mut data = V3 {
270 v2: V2::read(consensus_branch_id, r)?,
271 ..Default::default()
272 };
273 r.read_exact(&mut data.start_ironwood_root)?;
274 r.read_exact(&mut data.end_ironwood_root)?;
275 data.ironwood_tx = NodeData::read_compact(r)?;
276
277 Ok(data)
278 }
279}
280
281#[cfg(any(test, feature = "test-dependencies"))]
282pub mod testing {
283 use primitive_types::U256;
284 use proptest::array::uniform32;
285 use proptest::prelude::{any, prop_compose};
286
287 use super::NodeData;
288
289 prop_compose! {
290 pub fn arb_node_data()(
291 subtree_commitment in uniform32(any::<u8>()),
292 start_time in any::<u32>(),
293 end_time in any::<u32>(),
294 start_target in any::<u32>(),
295 end_target in any::<u32>(),
296 start_sapling_root in uniform32(any::<u8>()),
297 end_sapling_root in uniform32(any::<u8>()),
298 subtree_total_work in uniform32(any::<u8>()),
299 start_height in any::<u64>(),
300 end_height in any::<u64>(),
301 sapling_tx in any::<u64>(),
302 ) -> NodeData {
303 NodeData {
304 consensus_branch_id: 0,
305 subtree_commitment,
306 start_time,
307 end_time,
308 start_target,
309 end_target,
310 start_sapling_root,
311 end_sapling_root,
312 subtree_total_work: U256::from_little_endian(&subtree_total_work[..]),
313 start_height,
314 end_height,
315 sapling_tx
316 }
317 }
318 }
319}
320
321#[cfg(test)]
322mod tests {
323 use super::testing::arb_node_data;
324 use proptest::prelude::*;
325
326 use primitive_types::U256;
327
328 use crate::{
329 Entry, EntryLink, MAX_ENTRY_SIZE, V1 as HistoryV1, V2 as HistoryV2, V3 as HistoryV3,
330 Version,
331 };
332
333 use super::{MAX_NODE_DATA_SIZE, NodeData, V2, V3};
334
335 fn node_data(start_height: u64, end_height: u64) -> NodeData {
336 NodeData {
337 consensus_branch_id: 1,
338 subtree_commitment: [1; 32],
339 start_time: 2,
340 end_time: 3,
341 start_target: 4,
342 end_target: 5,
343 start_sapling_root: [6; 32],
344 end_sapling_root: [7; 32],
345 subtree_total_work: U256::from(8u64),
346 start_height,
347 end_height,
348 sapling_tx: 9,
349 }
350 }
351
352 fn node_data_v2(start_height: u64, end_height: u64) -> V2 {
353 V2 {
354 v1: node_data(start_height, end_height),
355 start_orchard_root: [10; 32],
356 end_orchard_root: [11; 32],
357 orchard_tx: 12,
358 }
359 }
360
361 fn node_data_v3(start_height: u64, end_height: u64) -> V3 {
362 V3 {
363 v2: node_data_v2(start_height, end_height),
364 start_ironwood_root: [13; 32],
365 end_ironwood_root: [14; 32],
366 ironwood_tx: 15,
367 }
368 }
369
370 fn max_node_data() -> NodeData {
371 NodeData {
372 consensus_branch_id: u32::MAX,
373 subtree_commitment: [1; 32],
374 start_time: u32::MAX,
375 end_time: u32::MAX,
376 start_target: u32::MAX,
377 end_target: u32::MAX,
378 start_sapling_root: [2; 32],
379 end_sapling_root: [3; 32],
380 subtree_total_work: U256::MAX,
381 start_height: u64::MAX,
382 end_height: u64::MAX,
383 sapling_tx: u64::MAX,
384 }
385 }
386
387 fn max_node_data_v2() -> V2 {
388 V2 {
389 v1: max_node_data(),
390 start_orchard_root: [4; 32],
391 end_orchard_root: [5; 32],
392 orchard_tx: u64::MAX,
393 }
394 }
395
396 fn max_node_data_v3() -> V3 {
397 V3 {
398 v2: max_node_data_v2(),
399 start_ironwood_root: [6; 32],
400 end_ironwood_root: [7; 32],
401 ironwood_tx: u64::MAX,
402 }
403 }
404
405 fn v1_fixture_bytes() -> Vec<u8> {
406 let mut expected = vec![];
407 expected.extend_from_slice(&[1; 32]);
408 expected.extend_from_slice(&2u32.to_le_bytes());
409 expected.extend_from_slice(&3u32.to_le_bytes());
410 expected.extend_from_slice(&4u32.to_le_bytes());
411 expected.extend_from_slice(&5u32.to_le_bytes());
412 expected.extend_from_slice(&[6; 32]);
413 expected.extend_from_slice(&[7; 32]);
414 expected.extend_from_slice(&8u64.to_le_bytes());
415 expected.extend_from_slice(&[0; 24]);
416 expected.push(1);
417 expected.push(2);
418 expected.push(9);
419 expected
420 }
421
422 fn v2_fixture_bytes() -> Vec<u8> {
423 let mut expected = v1_fixture_bytes();
424 expected.extend_from_slice(&[10; 32]);
425 expected.extend_from_slice(&[11; 32]);
426 expected.push(12);
427 expected
428 }
429
430 fn v3_fixture_bytes() -> Vec<u8> {
431 let mut expected = v2_fixture_bytes();
432 expected.extend_from_slice(&[13; 32]);
433 expected.extend_from_slice(&[14; 32]);
434 expected.push(15);
435 expected
436 }
437
438 proptest! {
439 #[test]
440 fn serialization_round_trip(node_data in arb_node_data()) {
441 assert_eq!(NodeData::from_bytes(0, node_data.to_bytes()).unwrap(), node_data);
442 }
443 }
444
445 #[test]
446 fn v1_and_v2_serialization_fixtures_are_stable() {
447 assert_eq!(HistoryV1::to_bytes(&node_data(1, 2)), v1_fixture_bytes());
448 assert_eq!(HistoryV2::to_bytes(&node_data_v2(1, 2)), v2_fixture_bytes());
449 }
450
451 #[test]
452 fn v3_serialization_round_trip() {
453 let node_data = node_data_v3(1, 2);
454
455 assert_eq!(
456 HistoryV3::from_bytes(1, v3_fixture_bytes()).unwrap(),
457 node_data
458 );
459 assert_eq!(HistoryV3::to_bytes(&node_data), v3_fixture_bytes());
460 }
461
462 #[test]
463 fn max_serialized_sizes_cover_all_versions() {
464 assert_eq!(HistoryV1::to_bytes(&max_node_data()).len(), 171);
465 assert_eq!(HistoryV2::to_bytes(&max_node_data_v2()).len(), 244);
466 let max_v3_bytes = HistoryV3::to_bytes(&max_node_data_v3());
467 assert_eq!(max_v3_bytes.len(), 317);
468 assert_eq!(
469 HistoryV3::from_bytes(u32::MAX, max_v3_bytes).unwrap(),
470 max_node_data_v3()
471 );
472 assert_eq!(MAX_NODE_DATA_SIZE, 317);
473
474 let entry = Entry::<HistoryV3>::new(
475 max_node_data_v3(),
476 EntryLink::Stored(u32::MAX),
477 EntryLink::Stored(u32::MAX),
478 );
479 let mut encoded = vec![];
480 entry.write(&mut encoded).unwrap();
481 assert_eq!(encoded.len(), MAX_ENTRY_SIZE);
482 }
483
484 #[test]
485 fn v3_combine_tracks_ironwood_fields() {
486 let mut left = node_data_v3(1, 1);
487 left.start_ironwood_root = [16; 32];
488 left.end_ironwood_root = [17; 32];
489 left.ironwood_tx = 18;
490
491 let mut right = node_data_v3(2, 2);
492 right.start_ironwood_root = [19; 32];
493 right.end_ironwood_root = [20; 32];
494 right.ironwood_tx = 21;
495
496 let combined = HistoryV3::combine(&left, &right);
497
498 assert_eq!(combined.v2.v1.start_height, 1);
499 assert_eq!(combined.v2.v1.end_height, 2);
500 assert_eq!(combined.start_ironwood_root, [16; 32]);
501 assert_eq!(combined.end_ironwood_root, [20; 32]);
502 assert_eq!(combined.ironwood_tx, 39);
503 }
504
505 #[test]
506 fn v3_combine_hash_commits_to_ironwood_fields() {
507 let left = node_data_v3(1, 1);
508 let right = node_data_v3(2, 2);
509 let base_hash = HistoryV3::combine(&left, &right).v2.v1.subtree_commitment;
510
511 let mut changed_start_root = left.clone();
512 changed_start_root.start_ironwood_root[0] ^= 1;
513 assert_ne!(
514 HistoryV3::combine(&changed_start_root, &right)
515 .v2
516 .v1
517 .subtree_commitment,
518 base_hash
519 );
520
521 let mut changed_left_end_root = left.clone();
522 changed_left_end_root.end_ironwood_root[0] ^= 1;
523 assert_ne!(
524 HistoryV3::combine(&changed_left_end_root, &right)
525 .v2
526 .v1
527 .subtree_commitment,
528 base_hash
529 );
530
531 let mut changed_right_start_root = right.clone();
532 changed_right_start_root.start_ironwood_root[0] ^= 1;
533 assert_ne!(
534 HistoryV3::combine(&left, &changed_right_start_root)
535 .v2
536 .v1
537 .subtree_commitment,
538 base_hash
539 );
540
541 let mut changed_end_root = right.clone();
542 changed_end_root.end_ironwood_root[0] ^= 1;
543 assert_ne!(
544 HistoryV3::combine(&left, &changed_end_root)
545 .v2
546 .v1
547 .subtree_commitment,
548 base_hash
549 );
550
551 let mut changed_tx_count = left.clone();
552 changed_tx_count.ironwood_tx += 1;
553 assert_ne!(
554 HistoryV3::combine(&changed_tx_count, &right)
555 .v2
556 .v1
557 .subtree_commitment,
558 base_hash
559 );
560
561 let mut redistributed_left_tx = left.clone();
562 let mut redistributed_right_tx = right;
563 redistributed_left_tx.ironwood_tx += 1;
564 redistributed_right_tx.ironwood_tx -= 1;
565 assert_ne!(
566 HistoryV3::combine(&redistributed_left_tx, &redistributed_right_tx)
567 .v2
568 .v1
569 .subtree_commitment,
570 base_hash
571 );
572 }
573}