1use std::{cmp::max, collections::HashMap, fmt::Debug};
9
10use zebra_chain::{
11 amount::{self, Amount, Constraint, NegativeAllowed, NonNegative},
12 block::Height,
13 parameters::NetworkKind,
14 serialization::{ZcashDeserializeInto, ZcashSerialize},
15 transparent::{self, Address::*, OutputIndex},
16};
17
18use crate::service::finalized_state::disk_format::{
19 block::{TransactionIndex, TransactionLocation, TRANSACTION_LOCATION_DISK_BYTES},
20 expand_zero_be_bytes, truncate_zero_be_bytes, FromDisk, IntoDisk,
21};
22
23#[cfg(any(test, feature = "proptest-impl"))]
24use proptest_derive::Arbitrary;
25
26pub const BALANCE_DISK_BYTES: usize = 8;
28
29pub const OUTPUT_INDEX_DISK_BYTES: usize = 3;
33
34pub const MAX_ON_DISK_OUTPUT_INDEX: OutputIndex =
46 OutputIndex::from_index((1 << (OUTPUT_INDEX_DISK_BYTES * 8)) - 1);
47
48pub const OUTPUT_LOCATION_DISK_BYTES: usize =
53 TRANSACTION_LOCATION_DISK_BYTES + OUTPUT_INDEX_DISK_BYTES;
54
55#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
62#[cfg_attr(
63 any(test, feature = "proptest-impl"),
64 derive(Arbitrary, serde::Serialize, serde::Deserialize)
65)]
66pub struct OutputLocation {
67 transaction_location: TransactionLocation,
69
70 output_index: OutputIndex,
72}
73
74impl OutputLocation {
75 #[allow(dead_code)]
77 pub fn from_usize(
78 height: Height,
79 transaction_index: usize,
80 output_index: usize,
81 ) -> OutputLocation {
82 OutputLocation {
83 transaction_location: TransactionLocation::from_usize(height, transaction_index),
84 output_index: OutputIndex::from_usize(output_index),
85 }
86 }
87
88 pub fn from_outpoint(
94 transaction_location: TransactionLocation,
95 outpoint: &transparent::OutPoint,
96 ) -> OutputLocation {
97 OutputLocation::from_output_index(transaction_location, outpoint.index)
98 }
99
100 pub fn from_output_index(
104 transaction_location: TransactionLocation,
105 output_index: u32,
106 ) -> OutputLocation {
107 OutputLocation {
108 transaction_location,
109 output_index: OutputIndex::from_index(output_index),
110 }
111 }
112
113 pub fn height(&self) -> Height {
115 self.transaction_location.height
116 }
117
118 pub fn transaction_index(&self) -> TransactionIndex {
120 self.transaction_location.index
121 }
122
123 pub fn output_index(&self) -> OutputIndex {
125 self.output_index
126 }
127
128 pub fn transaction_location(&self) -> TransactionLocation {
130 self.transaction_location
131 }
132
133 #[cfg(any(test, feature = "proptest-impl"))]
135 #[allow(dead_code)]
136 pub fn height_mut(&mut self) -> &mut Height {
137 &mut self.transaction_location.height
138 }
139}
140
141pub type AddressLocation = OutputLocation;
152
153#[derive(Copy, Clone, Debug, Eq, PartialEq)]
155#[cfg_attr(
156 any(test, feature = "proptest-impl"),
157 derive(Arbitrary, serde::Serialize, serde::Deserialize),
158 serde(bound = "C: Constraint + Clone")
159)]
160pub struct AddressBalanceLocationInner<C: Constraint + Copy + std::fmt::Debug> {
161 balance: Amount<C>,
163
164 received: u64,
166
167 location: AddressLocation,
169}
170
171impl<C: Constraint + Copy + std::fmt::Debug> AddressBalanceLocationInner<C> {
172 pub(crate) fn new(first_output: OutputLocation) -> Self {
177 Self {
178 balance: Amount::zero(),
179 received: 0,
180 location: first_output,
181 }
182 }
183
184 pub fn balance(&self) -> Amount<C> {
186 self.balance
187 }
188
189 pub fn received(&self) -> u64 {
191 self.received
192 }
193
194 pub fn balance_mut(&mut self) -> &mut Amount<C> {
196 &mut self.balance
197 }
198
199 pub fn received_mut(&mut self) -> &mut u64 {
201 &mut self.received
202 }
203
204 pub fn address_location(&self) -> AddressLocation {
206 self.location
207 }
208
209 #[cfg(any(test, feature = "proptest-impl"))]
211 #[allow(dead_code)]
212 pub fn height_mut(&mut self) -> &mut Height {
213 &mut self.location.transaction_location.height
214 }
215
216 #[allow(clippy::unwrap_in_result)]
218 pub fn receive_output(
219 &mut self,
220 unspent_output: &transparent::Output,
221 ) -> Result<(), amount::Error> {
222 self.balance = (self
223 .balance
224 .zatoshis()
225 .checked_add(unspent_output.value().zatoshis()))
226 .expect("ops handling taddr balances must not overflow")
227 .try_into()?;
228 self.received = self.received.saturating_add(unspent_output.value().into());
229 Ok(())
230 }
231
232 #[allow(clippy::unwrap_in_result)]
234 pub fn spend_output(
235 &mut self,
236 spent_output: &transparent::Output,
237 ) -> Result<(), amount::Error> {
238 self.balance = (self
239 .balance
240 .zatoshis()
241 .checked_sub(spent_output.value().zatoshis()))
242 .expect("ops handling taddr balances must not underflow")
243 .try_into()?;
244
245 Ok(())
246 }
247}
248
249impl<C: Constraint + Copy + std::fmt::Debug> std::ops::Add for AddressBalanceLocationInner<C> {
250 type Output = Result<Self, amount::Error>;
251
252 fn add(self, rhs: Self) -> Self::Output {
253 Ok(AddressBalanceLocationInner {
254 balance: (self.balance + rhs.balance)?,
255 received: self.received.saturating_add(rhs.received),
256 location: self.location.min(rhs.location),
269 })
270 }
271}
272
273impl From<AddressBalanceLocationInner<NonNegative>> for AddressBalanceLocation {
274 fn from(value: AddressBalanceLocationInner<NonNegative>) -> Self {
275 Self(value)
276 }
277}
278
279impl From<AddressBalanceLocationInner<NegativeAllowed>> for AddressBalanceLocationChange {
280 fn from(value: AddressBalanceLocationInner<NegativeAllowed>) -> Self {
281 Self(value)
282 }
283}
284
285pub struct AddressBalanceLocationChange(AddressBalanceLocationInner<NegativeAllowed>);
288
289pub enum AddressBalanceLocationUpdates {
291 Merge(HashMap<transparent::Address, AddressBalanceLocationChange>),
293 Insert(HashMap<transparent::Address, AddressBalanceLocation>),
295}
296
297impl From<HashMap<transparent::Address, AddressBalanceLocation>> for AddressBalanceLocationUpdates {
298 fn from(value: HashMap<transparent::Address, AddressBalanceLocation>) -> Self {
299 Self::Insert(value)
300 }
301}
302
303impl From<HashMap<transparent::Address, AddressBalanceLocationChange>>
304 for AddressBalanceLocationUpdates
305{
306 fn from(value: HashMap<transparent::Address, AddressBalanceLocationChange>) -> Self {
307 Self::Merge(value)
308 }
309}
310
311impl AddressBalanceLocationChange {
312 pub fn new(location: AddressLocation) -> Self {
316 Self(AddressBalanceLocationInner::new(location))
317 }
318}
319
320impl std::ops::Deref for AddressBalanceLocationChange {
321 type Target = AddressBalanceLocationInner<NegativeAllowed>;
322
323 fn deref(&self) -> &Self::Target {
324 &self.0
325 }
326}
327
328impl std::ops::DerefMut for AddressBalanceLocationChange {
329 fn deref_mut(&mut self) -> &mut Self::Target {
330 &mut self.0
331 }
332}
333
334impl std::ops::Add for AddressBalanceLocationChange {
335 type Output = Result<Self, amount::Error>;
336
337 fn add(self, rhs: Self) -> Self::Output {
338 (self.0 + rhs.0).map(Self)
339 }
340}
341
342#[derive(Copy, Clone, Debug, Eq, PartialEq)]
352#[cfg_attr(
353 any(test, feature = "proptest-impl"),
354 derive(Arbitrary, serde::Serialize, serde::Deserialize)
355)]
356pub struct AddressBalanceLocation(AddressBalanceLocationInner<NonNegative>);
357
358impl AddressBalanceLocation {
359 pub fn new(first_output: OutputLocation) -> Self {
363 Self(AddressBalanceLocationInner::new(first_output))
364 }
365
366 pub fn into_new_change(self) -> AddressBalanceLocationChange {
369 AddressBalanceLocationChange::new(self.location)
370 }
371}
372
373impl std::ops::Deref for AddressBalanceLocation {
374 type Target = AddressBalanceLocationInner<NonNegative>;
375
376 fn deref(&self) -> &Self::Target {
377 &self.0
378 }
379}
380
381impl std::ops::DerefMut for AddressBalanceLocation {
382 fn deref_mut(&mut self) -> &mut Self::Target {
383 &mut self.0
384 }
385}
386
387impl std::ops::Add for AddressBalanceLocation {
388 type Output = Result<Self, amount::Error>;
389
390 fn add(self, rhs: Self) -> Self::Output {
391 (self.0 + rhs.0).map(Self)
392 }
393}
394
395#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
404#[cfg_attr(
405 any(test, feature = "proptest-impl"),
406 derive(Arbitrary, serde::Serialize, serde::Deserialize)
407)]
408pub struct AddressUnspentOutput {
409 address_location: AddressLocation,
411
412 unspent_output_location: OutputLocation,
414}
415
416impl AddressUnspentOutput {
417 pub fn new(
420 address_location: AddressLocation,
421 unspent_output_location: OutputLocation,
422 ) -> AddressUnspentOutput {
423 AddressUnspentOutput {
424 address_location,
425 unspent_output_location,
426 }
427 }
428
429 pub fn address_iterator_start(address_location: AddressLocation) -> AddressUnspentOutput {
440 let zero_output_location = OutputLocation::from_usize(Height(0), 0, 0);
442
443 AddressUnspentOutput {
444 address_location,
445 unspent_output_location: zero_output_location,
446 }
447 }
448
449 pub fn address_iterator_next(&mut self) {
459 self.unspent_output_location.output_index += 1;
464 }
465
466 pub fn address_location(&self) -> AddressLocation {
470 self.address_location
471 }
472
473 pub fn unspent_output_location(&self) -> OutputLocation {
475 self.unspent_output_location
476 }
477
478 #[cfg(any(test, feature = "proptest-impl"))]
480 #[allow(dead_code)]
481 pub fn address_location_mut(&mut self) -> &mut AddressLocation {
482 &mut self.address_location
483 }
484
485 #[cfg(any(test, feature = "proptest-impl"))]
487 #[allow(dead_code)]
488 pub fn unspent_output_location_mut(&mut self) -> &mut OutputLocation {
489 &mut self.unspent_output_location
490 }
491}
492
493#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
502#[cfg_attr(
503 any(test, feature = "proptest-impl"),
504 derive(Arbitrary, serde::Serialize, serde::Deserialize)
505)]
506pub struct AddressTransaction {
507 address_location: AddressLocation,
509
510 transaction_location: TransactionLocation,
512}
513
514impl AddressTransaction {
515 pub fn new(
518 address_location: AddressLocation,
519 transaction_location: TransactionLocation,
520 ) -> AddressTransaction {
521 AddressTransaction {
522 address_location,
523 transaction_location,
524 }
525 }
526
527 pub fn address_iterator_range(
542 address_location: AddressLocation,
543 query: std::ops::RangeInclusive<Height>,
544 ) -> std::ops::RangeInclusive<AddressTransaction> {
545 let first_utxo_location = address_location.transaction_location();
550
551 let query_start_location = TransactionLocation::from_index(*query.start(), 0);
553 let query_end_location = TransactionLocation::from_index(*query.end(), u16::MAX);
554
555 let addr_tx = |tx_loc| AddressTransaction::new(address_location, tx_loc);
556
557 addr_tx(max(first_utxo_location, query_start_location))..=addr_tx(query_end_location)
558 }
559
560 #[allow(dead_code)]
570 pub fn address_iterator_next(&mut self) {
571 self.transaction_location.index.0 += 1;
576 }
577
578 pub fn address_location(&self) -> AddressLocation {
582 self.address_location
583 }
584
585 pub fn transaction_location(&self) -> TransactionLocation {
587 self.transaction_location
588 }
589
590 #[cfg(any(test, feature = "proptest-impl"))]
592 #[allow(dead_code)]
593 pub fn address_location_mut(&mut self) -> &mut AddressLocation {
594 &mut self.address_location
595 }
596
597 #[cfg(any(test, feature = "proptest-impl"))]
599 #[allow(dead_code)]
600 pub fn transaction_location_mut(&mut self) -> &mut TransactionLocation {
601 &mut self.transaction_location
602 }
603}
604
605fn address_variant(address: &transparent::Address) -> u8 {
609 use NetworkKind::*;
610 match (address.network_kind(), address) {
614 (Mainnet, PayToPublicKeyHash { .. }) => 0,
615 (Mainnet, PayToScriptHash { .. }) => 1,
616 (Testnet | Regtest, PayToPublicKeyHash { .. }) => 2,
620 (Testnet | Regtest, PayToScriptHash { .. }) => 3,
621 (Mainnet, Tex { .. }) => 4,
623 (Testnet | Regtest, Tex { .. }) => 5,
624 }
625}
626
627impl IntoDisk for transparent::Address {
628 type Bytes = [u8; 21];
629
630 fn as_bytes(&self) -> Self::Bytes {
631 let variant_bytes = vec![address_variant(self)];
632 let hash_bytes = self.hash_bytes().to_vec();
633
634 [variant_bytes, hash_bytes].concat().try_into().unwrap()
635 }
636}
637
638#[cfg(any(test, feature = "proptest-impl"))]
639impl FromDisk for transparent::Address {
640 fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
641 let (address_variant, hash_bytes) = disk_bytes.as_ref().split_at(1);
642
643 let address_variant = address_variant[0];
644 let hash_bytes = hash_bytes.try_into().unwrap();
645
646 let network = if address_variant < 2 {
647 NetworkKind::Mainnet
648 } else {
649 NetworkKind::Testnet
650 };
651
652 if address_variant % 2 == 0 {
653 transparent::Address::from_pub_key_hash(network, hash_bytes)
654 } else {
655 transparent::Address::from_script_hash(network, hash_bytes)
656 }
657 }
658}
659
660impl<C: Constraint> IntoDisk for Amount<C> {
661 type Bytes = [u8; BALANCE_DISK_BYTES];
662
663 fn as_bytes(&self) -> Self::Bytes {
664 self.to_bytes()
665 }
666}
667
668impl FromDisk for Amount<NonNegative> {
669 fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
670 let array = bytes.as_ref().try_into().unwrap();
671 Amount::from_bytes(array).unwrap()
672 }
673}
674
675impl IntoDisk for OutputIndex {
676 type Bytes = [u8; OUTPUT_INDEX_DISK_BYTES];
677
678 fn as_bytes(&self) -> Self::Bytes {
679 let mem_bytes = self.index().to_be_bytes();
680
681 let disk_bytes = truncate_zero_be_bytes(&mem_bytes, OUTPUT_INDEX_DISK_BYTES);
682
683 match disk_bytes {
684 Some(b) => b.try_into().unwrap(),
685 None => {
695 #[cfg(test)]
696 {
697 use zebra_chain::serialization::TrustedPreallocate;
698 assert!(
699 u64::from(MAX_ON_DISK_OUTPUT_INDEX.index())
700 > zebra_chain::transparent::Output::max_allocation(),
701 "increased block size requires database output index format change",
702 );
703 }
704
705 truncate_zero_be_bytes(
706 &MAX_ON_DISK_OUTPUT_INDEX.index().to_be_bytes(),
707 OUTPUT_INDEX_DISK_BYTES,
708 )
709 .expect("max on disk output index is valid")
710 .try_into()
711 .unwrap()
712 }
713 }
714 }
715}
716
717impl FromDisk for OutputIndex {
718 fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
719 const MEM_LEN: usize = size_of::<u32>();
720
721 let mem_bytes = expand_zero_be_bytes::<MEM_LEN>(disk_bytes.as_ref());
722 OutputIndex::from_index(u32::from_be_bytes(mem_bytes))
723 }
724}
725
726impl IntoDisk for OutputLocation {
727 type Bytes = [u8; OUTPUT_LOCATION_DISK_BYTES];
728
729 fn as_bytes(&self) -> Self::Bytes {
730 let transaction_location_bytes = self.transaction_location().as_bytes().to_vec();
731 let output_index_bytes = self.output_index().as_bytes().to_vec();
732
733 [transaction_location_bytes, output_index_bytes]
734 .concat()
735 .try_into()
736 .unwrap()
737 }
738}
739
740impl FromDisk for OutputLocation {
741 fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
742 let (transaction_location_bytes, output_index_bytes) = disk_bytes
743 .as_ref()
744 .split_at(TRANSACTION_LOCATION_DISK_BYTES);
745
746 let transaction_location = TransactionLocation::from_bytes(transaction_location_bytes);
747 let output_index = OutputIndex::from_bytes(output_index_bytes);
748
749 OutputLocation {
750 transaction_location,
751 output_index,
752 }
753 }
754}
755
756impl<C: Constraint + Copy + std::fmt::Debug> IntoDisk for AddressBalanceLocationInner<C> {
757 type Bytes = [u8; BALANCE_DISK_BYTES + OUTPUT_LOCATION_DISK_BYTES + size_of::<u64>()];
758
759 fn as_bytes(&self) -> Self::Bytes {
760 let balance_bytes = self.balance().as_bytes().to_vec();
761 let address_location_bytes = self.address_location().as_bytes().to_vec();
762 let received_bytes = self.received().to_le_bytes().to_vec();
763
764 [balance_bytes, address_location_bytes, received_bytes]
765 .concat()
766 .try_into()
767 .unwrap()
768 }
769}
770
771impl IntoDisk for AddressBalanceLocation {
772 type Bytes = [u8; BALANCE_DISK_BYTES + OUTPUT_LOCATION_DISK_BYTES + size_of::<u64>()];
773
774 fn as_bytes(&self) -> Self::Bytes {
775 self.0.as_bytes()
776 }
777}
778
779impl IntoDisk for AddressBalanceLocationChange {
780 type Bytes = [u8; BALANCE_DISK_BYTES + OUTPUT_LOCATION_DISK_BYTES + size_of::<u64>()];
781
782 fn as_bytes(&self) -> Self::Bytes {
783 self.0.as_bytes()
784 }
785}
786
787impl<C: Constraint + Copy + std::fmt::Debug> FromDisk for AddressBalanceLocationInner<C> {
788 fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
789 let (balance_bytes, rest) = disk_bytes.as_ref().split_at(BALANCE_DISK_BYTES);
790 let (address_location_bytes, rest) = rest.split_at(BALANCE_DISK_BYTES);
791 let (received_bytes, _) = rest.split_at_checked(size_of::<u64>()).unwrap_or_default();
792
793 let balance = Amount::from_bytes(balance_bytes.try_into().unwrap()).unwrap();
794 let address_location = AddressLocation::from_bytes(address_location_bytes);
795 let received = u64::from_le_bytes(received_bytes.try_into().unwrap_or_default());
799
800 let mut address_balance_location = Self::new(address_location);
801 *address_balance_location.balance_mut() = balance;
802 *address_balance_location.received_mut() = received;
803
804 address_balance_location
805 }
806}
807
808impl FromDisk for AddressBalanceLocation {
809 fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
810 Self(AddressBalanceLocationInner::from_bytes(disk_bytes))
811 }
812}
813
814impl FromDisk for AddressBalanceLocationChange {
815 fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
816 Self(AddressBalanceLocationInner::from_bytes(disk_bytes))
817 }
818}
819
820impl IntoDisk for transparent::Output {
821 type Bytes = Vec<u8>;
822
823 fn as_bytes(&self) -> Self::Bytes {
824 self.zcash_serialize_to_vec().unwrap()
825 }
826}
827
828impl FromDisk for transparent::Output {
829 fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
830 bytes.as_ref().zcash_deserialize_into().unwrap()
831 }
832}
833
834impl IntoDisk for AddressUnspentOutput {
835 type Bytes = [u8; OUTPUT_LOCATION_DISK_BYTES + OUTPUT_LOCATION_DISK_BYTES];
836
837 fn as_bytes(&self) -> Self::Bytes {
838 let address_location_bytes = self.address_location().as_bytes();
839 let unspent_output_location_bytes = self.unspent_output_location().as_bytes();
840
841 [address_location_bytes, unspent_output_location_bytes]
842 .concat()
843 .try_into()
844 .unwrap()
845 }
846}
847
848impl FromDisk for AddressUnspentOutput {
849 fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
850 let (address_location_bytes, unspent_output_location_bytes) =
851 disk_bytes.as_ref().split_at(OUTPUT_LOCATION_DISK_BYTES);
852
853 let address_location = AddressLocation::from_bytes(address_location_bytes);
854 let unspent_output_location = AddressLocation::from_bytes(unspent_output_location_bytes);
855
856 AddressUnspentOutput::new(address_location, unspent_output_location)
857 }
858}
859
860impl IntoDisk for AddressTransaction {
861 type Bytes = [u8; OUTPUT_LOCATION_DISK_BYTES + TRANSACTION_LOCATION_DISK_BYTES];
862
863 fn as_bytes(&self) -> Self::Bytes {
864 let address_location_bytes: [u8; OUTPUT_LOCATION_DISK_BYTES] =
865 self.address_location().as_bytes();
866 let transaction_location_bytes: [u8; TRANSACTION_LOCATION_DISK_BYTES] =
867 self.transaction_location().as_bytes();
868
869 address_location_bytes
870 .iter()
871 .copied()
872 .chain(transaction_location_bytes.iter().copied())
873 .collect::<Vec<u8>>()
874 .try_into()
875 .expect("concatenation of fixed-sized arrays should have the correct size")
876 }
877}
878
879impl FromDisk for AddressTransaction {
880 fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
881 let (address_location_bytes, transaction_location_bytes) =
882 disk_bytes.as_ref().split_at(OUTPUT_LOCATION_DISK_BYTES);
883
884 let address_location = AddressLocation::from_bytes(address_location_bytes);
885 let transaction_location = TransactionLocation::from_bytes(transaction_location_bytes);
886
887 AddressTransaction::new(address_location, transaction_location)
888 }
889}