1use std::{cmp::max, fmt::Debug};
9
10use serde::{Deserialize, Serialize};
11
12use zebra_chain::{
13 amount::{self, Amount, Constraint, NegativeAllowed, 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)]
212#[cfg_attr(
213 any(test, feature = "proptest-impl"),
214 derive(Arbitrary, Serialize, Deserialize),
215 serde(bound = "C: Constraint + Clone")
216)]
217pub struct AddressBalanceLocationInner<C: Constraint + Copy + std::fmt::Debug> {
218 balance: Amount<C>,
220
221 received: u64,
223
224 location: AddressLocation,
226}
227
228impl<C: Constraint + Copy + std::fmt::Debug> AddressBalanceLocationInner<C> {
229 fn new(first_output: OutputLocation) -> Self {
234 Self {
235 balance: Amount::zero(),
236 received: 0,
237 location: first_output,
238 }
239 }
240
241 pub fn balance(&self) -> Amount<C> {
243 self.balance
244 }
245
246 pub fn received(&self) -> u64 {
248 self.received
249 }
250
251 pub fn balance_mut(&mut self) -> &mut Amount<C> {
253 &mut self.balance
254 }
255
256 pub fn received_mut(&mut self) -> &mut u64 {
258 &mut self.received
259 }
260
261 pub fn address_location(&self) -> AddressLocation {
263 self.location
264 }
265
266 #[cfg(any(test, feature = "proptest-impl"))]
268 #[allow(dead_code)]
269 pub fn height_mut(&mut self) -> &mut Height {
270 &mut self.location.transaction_location.height
271 }
272}
273
274impl<C: Constraint + Copy + std::fmt::Debug> std::ops::Add for AddressBalanceLocationInner<C> {
275 type Output = Result<Self, amount::Error>;
276
277 fn add(self, rhs: Self) -> Self::Output {
278 Ok(AddressBalanceLocationInner {
279 balance: (self.balance + rhs.balance)?,
280 received: self.received.saturating_add(rhs.received),
281 location: self.location.min(rhs.location),
282 })
283 }
284}
285
286pub struct AddressBalanceLocationChange(AddressBalanceLocationInner<NegativeAllowed>);
289
290impl AddressBalanceLocationChange {
291 pub fn new(location: AddressLocation) -> Self {
295 Self(AddressBalanceLocationInner::new(location))
296 }
297
298 #[allow(clippy::unwrap_in_result)]
300 pub fn receive_output(
301 &mut self,
302 unspent_output: &transparent::Output,
303 ) -> Result<(), amount::Error> {
304 self.balance = (self
305 .balance
306 .zatoshis()
307 .checked_add(unspent_output.value().zatoshis()))
308 .expect("adding two Amounts is always within an i64")
309 .try_into()?;
310 self.received = self.received.saturating_add(unspent_output.value().into());
311 Ok(())
312 }
313
314 #[allow(clippy::unwrap_in_result)]
316 pub fn spend_output(
317 &mut self,
318 spent_output: &transparent::Output,
319 ) -> Result<(), amount::Error> {
320 self.balance = (self
321 .balance
322 .zatoshis()
323 .checked_sub(spent_output.value().zatoshis()))
324 .expect("subtracting two Amounts is always within an i64")
325 .try_into()?;
326
327 Ok(())
328 }
329}
330
331impl std::ops::Deref for AddressBalanceLocationChange {
332 type Target = AddressBalanceLocationInner<NegativeAllowed>;
333
334 fn deref(&self) -> &Self::Target {
335 &self.0
336 }
337}
338
339impl std::ops::DerefMut for AddressBalanceLocationChange {
340 fn deref_mut(&mut self) -> &mut Self::Target {
341 &mut self.0
342 }
343}
344
345impl std::ops::Add for AddressBalanceLocationChange {
346 type Output = Result<Self, amount::Error>;
347
348 fn add(self, rhs: Self) -> Self::Output {
349 (self.0 + rhs.0).map(Self)
350 }
351}
352
353#[derive(Copy, Clone, Debug, Eq, PartialEq)]
363#[cfg_attr(
364 any(test, feature = "proptest-impl"),
365 derive(Arbitrary, Serialize, Deserialize)
366)]
367pub struct AddressBalanceLocation(AddressBalanceLocationInner<NonNegative>);
368
369impl AddressBalanceLocation {
370 pub fn new(first_output: OutputLocation) -> Self {
374 Self(AddressBalanceLocationInner::new(first_output))
375 }
376
377 pub fn into_new_change(self) -> AddressBalanceLocationChange {
380 AddressBalanceLocationChange::new(self.location)
381 }
382}
383
384impl std::ops::Deref for AddressBalanceLocation {
385 type Target = AddressBalanceLocationInner<NonNegative>;
386
387 fn deref(&self) -> &Self::Target {
388 &self.0
389 }
390}
391
392impl std::ops::DerefMut for AddressBalanceLocation {
393 fn deref_mut(&mut self) -> &mut Self::Target {
394 &mut self.0
395 }
396}
397
398impl std::ops::Add for AddressBalanceLocation {
399 type Output = Result<Self, amount::Error>;
400
401 fn add(self, rhs: Self) -> Self::Output {
402 (self.0 + rhs.0).map(Self)
403 }
404}
405
406#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
415#[cfg_attr(
416 any(test, feature = "proptest-impl"),
417 derive(Arbitrary, Serialize, Deserialize)
418)]
419pub struct AddressUnspentOutput {
420 address_location: AddressLocation,
422
423 unspent_output_location: OutputLocation,
425}
426
427impl AddressUnspentOutput {
428 pub fn new(
431 address_location: AddressLocation,
432 unspent_output_location: OutputLocation,
433 ) -> AddressUnspentOutput {
434 AddressUnspentOutput {
435 address_location,
436 unspent_output_location,
437 }
438 }
439
440 pub fn address_iterator_start(address_location: AddressLocation) -> AddressUnspentOutput {
451 let zero_output_location = OutputLocation::from_usize(Height(0), 0, 0);
453
454 AddressUnspentOutput {
455 address_location,
456 unspent_output_location: zero_output_location,
457 }
458 }
459
460 pub fn address_iterator_next(&mut self) {
470 self.unspent_output_location.output_index.0 += 1;
475 }
476
477 pub fn address_location(&self) -> AddressLocation {
481 self.address_location
482 }
483
484 pub fn unspent_output_location(&self) -> OutputLocation {
486 self.unspent_output_location
487 }
488
489 #[cfg(any(test, feature = "proptest-impl"))]
491 #[allow(dead_code)]
492 pub fn address_location_mut(&mut self) -> &mut AddressLocation {
493 &mut self.address_location
494 }
495
496 #[cfg(any(test, feature = "proptest-impl"))]
498 #[allow(dead_code)]
499 pub fn unspent_output_location_mut(&mut self) -> &mut OutputLocation {
500 &mut self.unspent_output_location
501 }
502}
503
504#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
513#[cfg_attr(
514 any(test, feature = "proptest-impl"),
515 derive(Arbitrary, Serialize, Deserialize)
516)]
517pub struct AddressTransaction {
518 address_location: AddressLocation,
520
521 transaction_location: TransactionLocation,
523}
524
525impl AddressTransaction {
526 pub fn new(
529 address_location: AddressLocation,
530 transaction_location: TransactionLocation,
531 ) -> AddressTransaction {
532 AddressTransaction {
533 address_location,
534 transaction_location,
535 }
536 }
537
538 pub fn address_iterator_range(
553 address_location: AddressLocation,
554 query: std::ops::RangeInclusive<Height>,
555 ) -> std::ops::RangeInclusive<AddressTransaction> {
556 let first_utxo_location = address_location.transaction_location();
561
562 let query_start_location = TransactionLocation::from_index(*query.start(), 0);
564 let query_end_location = TransactionLocation::from_index(*query.end(), u16::MAX);
565
566 let addr_tx = |tx_loc| AddressTransaction::new(address_location, tx_loc);
567
568 addr_tx(max(first_utxo_location, query_start_location))..=addr_tx(query_end_location)
569 }
570
571 #[allow(dead_code)]
581 pub fn address_iterator_next(&mut self) {
582 self.transaction_location.index.0 += 1;
587 }
588
589 pub fn address_location(&self) -> AddressLocation {
593 self.address_location
594 }
595
596 pub fn transaction_location(&self) -> TransactionLocation {
598 self.transaction_location
599 }
600
601 #[cfg(any(test, feature = "proptest-impl"))]
603 #[allow(dead_code)]
604 pub fn address_location_mut(&mut self) -> &mut AddressLocation {
605 &mut self.address_location
606 }
607
608 #[cfg(any(test, feature = "proptest-impl"))]
610 #[allow(dead_code)]
611 pub fn transaction_location_mut(&mut self) -> &mut TransactionLocation {
612 &mut self.transaction_location
613 }
614}
615
616fn address_variant(address: &transparent::Address) -> u8 {
620 use NetworkKind::*;
621 match (address.network_kind(), address) {
625 (Mainnet, PayToPublicKeyHash { .. }) => 0,
626 (Mainnet, PayToScriptHash { .. }) => 1,
627 (Testnet | Regtest, PayToPublicKeyHash { .. }) => 2,
631 (Testnet | Regtest, PayToScriptHash { .. }) => 3,
632 (Mainnet, Tex { .. }) => 4,
634 (Testnet | Regtest, Tex { .. }) => 5,
635 }
636}
637
638impl IntoDisk for transparent::Address {
639 type Bytes = [u8; 21];
640
641 fn as_bytes(&self) -> Self::Bytes {
642 let variant_bytes = vec![address_variant(self)];
643 let hash_bytes = self.hash_bytes().to_vec();
644
645 [variant_bytes, hash_bytes].concat().try_into().unwrap()
646 }
647}
648
649#[cfg(any(test, feature = "proptest-impl"))]
650impl FromDisk for transparent::Address {
651 fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
652 let (address_variant, hash_bytes) = disk_bytes.as_ref().split_at(1);
653
654 let address_variant = address_variant[0];
655 let hash_bytes = hash_bytes.try_into().unwrap();
656
657 let network = if address_variant < 2 {
658 NetworkKind::Mainnet
659 } else {
660 NetworkKind::Testnet
661 };
662
663 if address_variant % 2 == 0 {
664 transparent::Address::from_pub_key_hash(network, hash_bytes)
665 } else {
666 transparent::Address::from_script_hash(network, hash_bytes)
667 }
668 }
669}
670
671impl<C: Constraint> IntoDisk for Amount<C> {
672 type Bytes = [u8; BALANCE_DISK_BYTES];
673
674 fn as_bytes(&self) -> Self::Bytes {
675 self.to_bytes()
676 }
677}
678
679impl FromDisk for Amount<NonNegative> {
680 fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
681 let array = bytes.as_ref().try_into().unwrap();
682 Amount::from_bytes(array).unwrap()
683 }
684}
685
686impl IntoDisk for OutputIndex {
687 type Bytes = [u8; OUTPUT_INDEX_DISK_BYTES];
688
689 fn as_bytes(&self) -> Self::Bytes {
690 let mem_bytes = self.index().to_be_bytes();
691
692 let disk_bytes = truncate_zero_be_bytes(&mem_bytes, OUTPUT_INDEX_DISK_BYTES);
693
694 match disk_bytes {
695 Some(b) => b.try_into().unwrap(),
696 None => {
706 #[cfg(test)]
707 {
708 use zebra_chain::serialization::TrustedPreallocate;
709 assert!(
710 u64::from(MAX_ON_DISK_OUTPUT_INDEX.0)
711 > zebra_chain::transparent::Output::max_allocation(),
712 "increased block size requires database output index format change",
713 );
714 }
715
716 truncate_zero_be_bytes(
717 &MAX_ON_DISK_OUTPUT_INDEX.0.to_be_bytes(),
718 OUTPUT_INDEX_DISK_BYTES,
719 )
720 .expect("max on disk output index is valid")
721 .try_into()
722 .unwrap()
723 }
724 }
725 }
726}
727
728impl FromDisk for OutputIndex {
729 fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
730 let mem_len = u32::BITS / 8;
731 let mem_len = mem_len.try_into().unwrap();
732
733 let mem_bytes = expand_zero_be_bytes(disk_bytes.as_ref(), mem_len);
734 let mem_bytes = mem_bytes.try_into().unwrap();
735 OutputIndex::from_index(u32::from_be_bytes(mem_bytes))
736 }
737}
738
739impl IntoDisk for OutputLocation {
740 type Bytes = [u8; OUTPUT_LOCATION_DISK_BYTES];
741
742 fn as_bytes(&self) -> Self::Bytes {
743 let transaction_location_bytes = self.transaction_location().as_bytes().to_vec();
744 let output_index_bytes = self.output_index().as_bytes().to_vec();
745
746 [transaction_location_bytes, output_index_bytes]
747 .concat()
748 .try_into()
749 .unwrap()
750 }
751}
752
753impl FromDisk for OutputLocation {
754 fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
755 let (transaction_location_bytes, output_index_bytes) = disk_bytes
756 .as_ref()
757 .split_at(TRANSACTION_LOCATION_DISK_BYTES);
758
759 let transaction_location = TransactionLocation::from_bytes(transaction_location_bytes);
760 let output_index = OutputIndex::from_bytes(output_index_bytes);
761
762 OutputLocation {
763 transaction_location,
764 output_index,
765 }
766 }
767}
768
769impl<C: Constraint + Copy + std::fmt::Debug> IntoDisk for AddressBalanceLocationInner<C> {
770 type Bytes = [u8; BALANCE_DISK_BYTES + OUTPUT_LOCATION_DISK_BYTES + size_of::<u64>()];
771
772 fn as_bytes(&self) -> Self::Bytes {
773 let balance_bytes = self.balance().as_bytes().to_vec();
774 let address_location_bytes = self.address_location().as_bytes().to_vec();
775 let received_bytes = self.received().to_le_bytes().to_vec();
776
777 [balance_bytes, address_location_bytes, received_bytes]
778 .concat()
779 .try_into()
780 .unwrap()
781 }
782}
783
784impl IntoDisk for AddressBalanceLocation {
785 type Bytes = [u8; BALANCE_DISK_BYTES + OUTPUT_LOCATION_DISK_BYTES + size_of::<u64>()];
786
787 fn as_bytes(&self) -> Self::Bytes {
788 self.0.as_bytes()
789 }
790}
791
792impl IntoDisk for AddressBalanceLocationChange {
793 type Bytes = [u8; BALANCE_DISK_BYTES + OUTPUT_LOCATION_DISK_BYTES + size_of::<u64>()];
794
795 fn as_bytes(&self) -> Self::Bytes {
796 self.0.as_bytes()
797 }
798}
799
800impl<C: Constraint + Copy + std::fmt::Debug> FromDisk for AddressBalanceLocationInner<C> {
801 fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
802 let (balance_bytes, rest) = disk_bytes.as_ref().split_at(BALANCE_DISK_BYTES);
803 let (address_location_bytes, rest) = rest.split_at(BALANCE_DISK_BYTES);
804 let (received_bytes, _) = rest.split_at_checked(size_of::<u64>()).unwrap_or_default();
805
806 let balance = Amount::from_bytes(balance_bytes.try_into().unwrap()).unwrap();
807 let address_location = AddressLocation::from_bytes(address_location_bytes);
808 let received = u64::from_le_bytes(received_bytes.try_into().unwrap_or_default());
812
813 let mut address_balance_location = Self::new(address_location);
814 *address_balance_location.balance_mut() = balance;
815 *address_balance_location.received_mut() = received;
816
817 address_balance_location
818 }
819}
820
821impl FromDisk for AddressBalanceLocation {
822 fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
823 Self(AddressBalanceLocationInner::from_bytes(disk_bytes))
824 }
825}
826
827impl FromDisk for AddressBalanceLocationChange {
828 fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
829 Self(AddressBalanceLocationInner::from_bytes(disk_bytes))
830 }
831}
832
833impl IntoDisk for transparent::Output {
834 type Bytes = Vec<u8>;
835
836 fn as_bytes(&self) -> Self::Bytes {
837 self.zcash_serialize_to_vec().unwrap()
838 }
839}
840
841impl FromDisk for transparent::Output {
842 fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
843 bytes.as_ref().zcash_deserialize_into().unwrap()
844 }
845}
846
847impl IntoDisk for AddressUnspentOutput {
848 type Bytes = [u8; OUTPUT_LOCATION_DISK_BYTES + OUTPUT_LOCATION_DISK_BYTES];
849
850 fn as_bytes(&self) -> Self::Bytes {
851 let address_location_bytes = self.address_location().as_bytes();
852 let unspent_output_location_bytes = self.unspent_output_location().as_bytes();
853
854 [address_location_bytes, unspent_output_location_bytes]
855 .concat()
856 .try_into()
857 .unwrap()
858 }
859}
860
861impl FromDisk for AddressUnspentOutput {
862 fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
863 let (address_location_bytes, unspent_output_location_bytes) =
864 disk_bytes.as_ref().split_at(OUTPUT_LOCATION_DISK_BYTES);
865
866 let address_location = AddressLocation::from_bytes(address_location_bytes);
867 let unspent_output_location = AddressLocation::from_bytes(unspent_output_location_bytes);
868
869 AddressUnspentOutput::new(address_location, unspent_output_location)
870 }
871}
872
873impl IntoDisk for AddressTransaction {
874 type Bytes = [u8; OUTPUT_LOCATION_DISK_BYTES + TRANSACTION_LOCATION_DISK_BYTES];
875
876 fn as_bytes(&self) -> Self::Bytes {
877 let address_location_bytes: [u8; OUTPUT_LOCATION_DISK_BYTES] =
878 self.address_location().as_bytes();
879 let transaction_location_bytes: [u8; TRANSACTION_LOCATION_DISK_BYTES] =
880 self.transaction_location().as_bytes();
881
882 address_location_bytes
883 .iter()
884 .copied()
885 .chain(transaction_location_bytes.iter().copied())
886 .collect::<Vec<u8>>()
887 .try_into()
888 .expect("concatenation of fixed-sized arrays should have the correct size")
889 }
890}
891
892impl FromDisk for AddressTransaction {
893 fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
894 let (address_location_bytes, transaction_location_bytes) =
895 disk_bytes.as_ref().split_at(OUTPUT_LOCATION_DISK_BYTES);
896
897 let address_location = AddressLocation::from_bytes(address_location_bytes);
898 let transaction_location = TransactionLocation::from_bytes(transaction_location_bytes);
899
900 AddressTransaction::new(address_location, transaction_location)
901 }
902}