1use std::{cmp::max, fmt::Debug};
9
10use serde::{Deserialize, Serialize};
11
12use zebra_chain::{
13 amount::{self, Amount, NonNegative},
14 block::Height,
15 parameters::NetworkKind,
16 serialization::{ZcashDeserializeInto, ZcashSerialize},
17 transparent::{self, Address::*},
18};
19
20use crate::service::finalized_state::disk_format::{
21 block::{TransactionIndex, TransactionLocation, TRANSACTION_LOCATION_DISK_BYTES},
22 expand_zero_be_bytes, truncate_zero_be_bytes, FromDisk, IntoDisk,
23};
24
25#[cfg(any(test, feature = "proptest-impl"))]
26use proptest_derive::Arbitrary;
27
28#[cfg(any(test, feature = "proptest-impl"))]
29mod arbitrary;
30
31pub const BALANCE_DISK_BYTES: usize = 8;
33
34pub const OUTPUT_INDEX_DISK_BYTES: usize = 3;
38
39pub const MAX_ON_DISK_OUTPUT_INDEX: OutputIndex =
51 OutputIndex((1 << (OUTPUT_INDEX_DISK_BYTES * 8)) - 1);
52
53pub const OUTPUT_LOCATION_DISK_BYTES: usize =
58 TRANSACTION_LOCATION_DISK_BYTES + OUTPUT_INDEX_DISK_BYTES;
59
60#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
64pub struct OutputIndex(u32);
65
66impl OutputIndex {
67 pub fn from_index(output_index: u32) -> OutputIndex {
71 OutputIndex(output_index)
72 }
73
74 pub fn index(&self) -> u32 {
76 self.0
77 }
78
79 #[allow(dead_code)]
81 pub fn from_usize(output_index: usize) -> OutputIndex {
82 OutputIndex(
83 output_index
84 .try_into()
85 .expect("the maximum valid index fits in the inner type"),
86 )
87 }
88
89 #[allow(dead_code)]
91 pub fn as_usize(&self) -> usize {
92 self.0
93 .try_into()
94 .expect("the maximum valid index fits in usize")
95 }
96
97 #[allow(dead_code)]
99 pub fn from_u64(output_index: u64) -> OutputIndex {
100 OutputIndex(
101 output_index
102 .try_into()
103 .expect("the maximum u64 index fits in the inner type"),
104 )
105 }
106
107 #[allow(dead_code)]
109 pub fn as_u64(&self) -> u64 {
110 self.0.into()
111 }
112}
113
114#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
119#[cfg_attr(
120 any(test, feature = "proptest-impl"),
121 derive(Arbitrary, Serialize, Deserialize)
122)]
123pub struct OutputLocation {
124 transaction_location: TransactionLocation,
126
127 output_index: OutputIndex,
129}
130
131impl OutputLocation {
132 #[allow(dead_code)]
134 pub fn from_usize(
135 height: Height,
136 transaction_index: usize,
137 output_index: usize,
138 ) -> OutputLocation {
139 OutputLocation {
140 transaction_location: TransactionLocation::from_usize(height, transaction_index),
141 output_index: OutputIndex::from_usize(output_index),
142 }
143 }
144
145 pub fn from_outpoint(
151 transaction_location: TransactionLocation,
152 outpoint: &transparent::OutPoint,
153 ) -> OutputLocation {
154 OutputLocation::from_output_index(transaction_location, outpoint.index)
155 }
156
157 pub fn from_output_index(
161 transaction_location: TransactionLocation,
162 output_index: u32,
163 ) -> OutputLocation {
164 OutputLocation {
165 transaction_location,
166 output_index: OutputIndex::from_index(output_index),
167 }
168 }
169
170 pub fn height(&self) -> Height {
172 self.transaction_location.height
173 }
174
175 pub fn transaction_index(&self) -> TransactionIndex {
177 self.transaction_location.index
178 }
179
180 pub fn output_index(&self) -> OutputIndex {
182 self.output_index
183 }
184
185 pub fn transaction_location(&self) -> TransactionLocation {
187 self.transaction_location
188 }
189
190 #[cfg(any(test, feature = "proptest-impl"))]
192 #[allow(dead_code)]
193 pub fn height_mut(&mut self) -> &mut Height {
194 &mut self.transaction_location.height
195 }
196}
197
198pub type AddressLocation = OutputLocation;
209
210#[derive(Copy, Clone, Debug, Eq, PartialEq)]
220#[cfg_attr(
221 any(test, feature = "proptest-impl"),
222 derive(Arbitrary, Serialize, Deserialize)
223)]
224pub struct AddressBalanceLocation {
225 balance: Amount<NonNegative>,
227
228 location: AddressLocation,
230}
231
232impl AddressBalanceLocation {
233 pub fn new(first_output: OutputLocation) -> AddressBalanceLocation {
238 AddressBalanceLocation {
239 balance: Amount::zero(),
240 location: first_output,
241 }
242 }
243
244 pub fn balance(&self) -> Amount<NonNegative> {
246 self.balance
247 }
248
249 pub fn balance_mut(&mut self) -> &mut Amount<NonNegative> {
251 &mut self.balance
252 }
253
254 pub fn receive_output(
256 &mut self,
257 unspent_output: &transparent::Output,
258 ) -> Result<(), amount::Error> {
259 self.balance = (self.balance + unspent_output.value())?;
260
261 Ok(())
262 }
263
264 pub fn spend_output(
266 &mut self,
267 spent_output: &transparent::Output,
268 ) -> Result<(), amount::Error> {
269 self.balance = (self.balance - spent_output.value())?;
270
271 Ok(())
272 }
273
274 pub fn address_location(&self) -> AddressLocation {
276 self.location
277 }
278
279 #[cfg(any(test, feature = "proptest-impl"))]
281 #[allow(dead_code)]
282 pub fn height_mut(&mut self) -> &mut Height {
283 &mut self.location.transaction_location.height
284 }
285}
286
287#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
296#[cfg_attr(
297 any(test, feature = "proptest-impl"),
298 derive(Arbitrary, Serialize, Deserialize)
299)]
300pub struct AddressUnspentOutput {
301 address_location: AddressLocation,
303
304 unspent_output_location: OutputLocation,
306}
307
308impl AddressUnspentOutput {
309 pub fn new(
312 address_location: AddressLocation,
313 unspent_output_location: OutputLocation,
314 ) -> AddressUnspentOutput {
315 AddressUnspentOutput {
316 address_location,
317 unspent_output_location,
318 }
319 }
320
321 pub fn address_iterator_start(address_location: AddressLocation) -> AddressUnspentOutput {
332 let zero_output_location = OutputLocation::from_usize(Height(0), 0, 0);
334
335 AddressUnspentOutput {
336 address_location,
337 unspent_output_location: zero_output_location,
338 }
339 }
340
341 pub fn address_iterator_next(&mut self) {
351 self.unspent_output_location.output_index.0 += 1;
356 }
357
358 pub fn address_location(&self) -> AddressLocation {
362 self.address_location
363 }
364
365 pub fn unspent_output_location(&self) -> OutputLocation {
367 self.unspent_output_location
368 }
369
370 #[cfg(any(test, feature = "proptest-impl"))]
372 #[allow(dead_code)]
373 pub fn address_location_mut(&mut self) -> &mut AddressLocation {
374 &mut self.address_location
375 }
376
377 #[cfg(any(test, feature = "proptest-impl"))]
379 #[allow(dead_code)]
380 pub fn unspent_output_location_mut(&mut self) -> &mut OutputLocation {
381 &mut self.unspent_output_location
382 }
383}
384
385#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
394#[cfg_attr(
395 any(test, feature = "proptest-impl"),
396 derive(Arbitrary, Serialize, Deserialize)
397)]
398pub struct AddressTransaction {
399 address_location: AddressLocation,
401
402 transaction_location: TransactionLocation,
404}
405
406impl AddressTransaction {
407 pub fn new(
410 address_location: AddressLocation,
411 transaction_location: TransactionLocation,
412 ) -> AddressTransaction {
413 AddressTransaction {
414 address_location,
415 transaction_location,
416 }
417 }
418
419 pub fn address_iterator_range(
434 address_location: AddressLocation,
435 query: std::ops::RangeInclusive<Height>,
436 ) -> std::ops::RangeInclusive<AddressTransaction> {
437 let first_utxo_location = address_location.transaction_location();
442
443 let query_start_location = TransactionLocation::from_index(*query.start(), 0);
445 let query_end_location = TransactionLocation::from_index(*query.end(), u16::MAX);
446
447 let addr_tx = |tx_loc| AddressTransaction::new(address_location, tx_loc);
448
449 addr_tx(max(first_utxo_location, query_start_location))..=addr_tx(query_end_location)
450 }
451
452 #[allow(dead_code)]
462 pub fn address_iterator_next(&mut self) {
463 self.transaction_location.index.0 += 1;
468 }
469
470 pub fn address_location(&self) -> AddressLocation {
474 self.address_location
475 }
476
477 pub fn transaction_location(&self) -> TransactionLocation {
479 self.transaction_location
480 }
481
482 #[cfg(any(test, feature = "proptest-impl"))]
484 #[allow(dead_code)]
485 pub fn address_location_mut(&mut self) -> &mut AddressLocation {
486 &mut self.address_location
487 }
488
489 #[cfg(any(test, feature = "proptest-impl"))]
491 #[allow(dead_code)]
492 pub fn transaction_location_mut(&mut self) -> &mut TransactionLocation {
493 &mut self.transaction_location
494 }
495}
496
497fn address_variant(address: &transparent::Address) -> u8 {
501 use NetworkKind::*;
502 match (address.network_kind(), address) {
506 (Mainnet, PayToPublicKeyHash { .. }) => 0,
507 (Mainnet, PayToScriptHash { .. }) => 1,
508 (Testnet | Regtest, PayToPublicKeyHash { .. }) => 2,
512 (Testnet | Regtest, PayToScriptHash { .. }) => 3,
513 }
514}
515
516impl IntoDisk for transparent::Address {
517 type Bytes = [u8; 21];
518
519 fn as_bytes(&self) -> Self::Bytes {
520 let variant_bytes = vec![address_variant(self)];
521 let hash_bytes = self.hash_bytes().to_vec();
522
523 [variant_bytes, hash_bytes].concat().try_into().unwrap()
524 }
525}
526
527#[cfg(any(test, feature = "proptest-impl"))]
528impl FromDisk for transparent::Address {
529 fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
530 let (address_variant, hash_bytes) = disk_bytes.as_ref().split_at(1);
531
532 let address_variant = address_variant[0];
533 let hash_bytes = hash_bytes.try_into().unwrap();
534
535 let network = if address_variant < 2 {
536 NetworkKind::Mainnet
537 } else {
538 NetworkKind::Testnet
539 };
540
541 if address_variant % 2 == 0 {
542 transparent::Address::from_pub_key_hash(network, hash_bytes)
543 } else {
544 transparent::Address::from_script_hash(network, hash_bytes)
545 }
546 }
547}
548
549impl IntoDisk for Amount<NonNegative> {
550 type Bytes = [u8; BALANCE_DISK_BYTES];
551
552 fn as_bytes(&self) -> Self::Bytes {
553 self.to_bytes()
554 }
555}
556
557impl FromDisk for Amount<NonNegative> {
558 fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
559 let array = bytes.as_ref().try_into().unwrap();
560 Amount::from_bytes(array).unwrap()
561 }
562}
563
564impl IntoDisk for OutputIndex {
565 type Bytes = [u8; OUTPUT_INDEX_DISK_BYTES];
566
567 fn as_bytes(&self) -> Self::Bytes {
568 let mem_bytes = self.index().to_be_bytes();
569
570 let disk_bytes = truncate_zero_be_bytes(&mem_bytes, OUTPUT_INDEX_DISK_BYTES);
571
572 match disk_bytes {
573 Some(b) => b.try_into().unwrap(),
574 None => {
584 #[cfg(test)]
585 {
586 use zebra_chain::serialization::TrustedPreallocate;
587 assert!(
588 u64::from(MAX_ON_DISK_OUTPUT_INDEX.0)
589 > zebra_chain::transparent::Output::max_allocation(),
590 "increased block size requires database output index format change",
591 );
592 }
593
594 truncate_zero_be_bytes(
595 &MAX_ON_DISK_OUTPUT_INDEX.0.to_be_bytes(),
596 OUTPUT_INDEX_DISK_BYTES,
597 )
598 .expect("max on disk output index is valid")
599 .try_into()
600 .unwrap()
601 }
602 }
603 }
604}
605
606impl FromDisk for OutputIndex {
607 fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
608 let mem_len = u32::BITS / 8;
609 let mem_len = mem_len.try_into().unwrap();
610
611 let mem_bytes = expand_zero_be_bytes(disk_bytes.as_ref(), mem_len);
612 let mem_bytes = mem_bytes.try_into().unwrap();
613 OutputIndex::from_index(u32::from_be_bytes(mem_bytes))
614 }
615}
616
617impl IntoDisk for OutputLocation {
618 type Bytes = [u8; OUTPUT_LOCATION_DISK_BYTES];
619
620 fn as_bytes(&self) -> Self::Bytes {
621 let transaction_location_bytes = self.transaction_location().as_bytes().to_vec();
622 let output_index_bytes = self.output_index().as_bytes().to_vec();
623
624 [transaction_location_bytes, output_index_bytes]
625 .concat()
626 .try_into()
627 .unwrap()
628 }
629}
630
631impl FromDisk for OutputLocation {
632 fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
633 let (transaction_location_bytes, output_index_bytes) = disk_bytes
634 .as_ref()
635 .split_at(TRANSACTION_LOCATION_DISK_BYTES);
636
637 let transaction_location = TransactionLocation::from_bytes(transaction_location_bytes);
638 let output_index = OutputIndex::from_bytes(output_index_bytes);
639
640 OutputLocation {
641 transaction_location,
642 output_index,
643 }
644 }
645}
646
647impl IntoDisk for AddressBalanceLocation {
648 type Bytes = [u8; BALANCE_DISK_BYTES + OUTPUT_LOCATION_DISK_BYTES];
649
650 fn as_bytes(&self) -> Self::Bytes {
651 let balance_bytes = self.balance().as_bytes().to_vec();
652 let address_location_bytes = self.address_location().as_bytes().to_vec();
653
654 [balance_bytes, address_location_bytes]
655 .concat()
656 .try_into()
657 .unwrap()
658 }
659}
660
661impl FromDisk for AddressBalanceLocation {
662 fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
663 let (balance_bytes, address_location_bytes) =
664 disk_bytes.as_ref().split_at(BALANCE_DISK_BYTES);
665
666 let balance = Amount::from_bytes(balance_bytes.try_into().unwrap()).unwrap();
667 let address_location = AddressLocation::from_bytes(address_location_bytes);
668
669 let mut address_balance_location = AddressBalanceLocation::new(address_location);
670 *address_balance_location.balance_mut() = balance;
671
672 address_balance_location
673 }
674}
675
676impl IntoDisk for transparent::Output {
677 type Bytes = Vec<u8>;
678
679 fn as_bytes(&self) -> Self::Bytes {
680 self.zcash_serialize_to_vec().unwrap()
681 }
682}
683
684impl FromDisk for transparent::Output {
685 fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
686 bytes.as_ref().zcash_deserialize_into().unwrap()
687 }
688}
689
690impl IntoDisk for AddressUnspentOutput {
691 type Bytes = [u8; OUTPUT_LOCATION_DISK_BYTES + OUTPUT_LOCATION_DISK_BYTES];
692
693 fn as_bytes(&self) -> Self::Bytes {
694 let address_location_bytes = self.address_location().as_bytes();
695 let unspent_output_location_bytes = self.unspent_output_location().as_bytes();
696
697 [address_location_bytes, unspent_output_location_bytes]
698 .concat()
699 .try_into()
700 .unwrap()
701 }
702}
703
704impl FromDisk for AddressUnspentOutput {
705 fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
706 let (address_location_bytes, unspent_output_location_bytes) =
707 disk_bytes.as_ref().split_at(OUTPUT_LOCATION_DISK_BYTES);
708
709 let address_location = AddressLocation::from_bytes(address_location_bytes);
710 let unspent_output_location = AddressLocation::from_bytes(unspent_output_location_bytes);
711
712 AddressUnspentOutput::new(address_location, unspent_output_location)
713 }
714}
715
716impl IntoDisk for AddressTransaction {
717 type Bytes = [u8; OUTPUT_LOCATION_DISK_BYTES + TRANSACTION_LOCATION_DISK_BYTES];
718
719 fn as_bytes(&self) -> Self::Bytes {
720 let address_location_bytes: [u8; OUTPUT_LOCATION_DISK_BYTES] =
721 self.address_location().as_bytes();
722 let transaction_location_bytes: [u8; TRANSACTION_LOCATION_DISK_BYTES] =
723 self.transaction_location().as_bytes();
724
725 address_location_bytes
726 .iter()
727 .copied()
728 .chain(transaction_location_bytes.iter().copied())
729 .collect::<Vec<u8>>()
730 .try_into()
731 .expect("concatenation of fixed-sized arrays should have the correct size")
732 }
733}
734
735impl FromDisk for AddressTransaction {
736 fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
737 let (address_location_bytes, transaction_location_bytes) =
738 disk_bytes.as_ref().split_at(OUTPUT_LOCATION_DISK_BYTES);
739
740 let address_location = AddressLocation::from_bytes(address_location_bytes);
741 let transaction_location = TransactionLocation::from_bytes(transaction_location_bytes);
742
743 AddressTransaction::new(address_location, transaction_location)
744 }
745}