use std::{cmp::max, collections::HashMap, fmt::Debug};
use zebra_chain::{
amount::{self, Amount, Constraint, NegativeAllowed, NonNegative},
block::Height,
parameters::NetworkKind,
serialization::{ZcashDeserializeInto, ZcashSerialize},
transparent::{self, Address::*, OutputIndex},
};
use crate::service::finalized_state::disk_format::{
block::{TransactionIndex, TransactionLocation, TRANSACTION_LOCATION_DISK_BYTES},
expand_zero_be_bytes, truncate_zero_be_bytes, FromDisk, IntoDisk,
};
#[cfg(any(test, feature = "proptest-impl"))]
use proptest_derive::Arbitrary;
pub const BALANCE_DISK_BYTES: usize = 8;
pub const OUTPUT_INDEX_DISK_BYTES: usize = 3;
pub const MAX_ON_DISK_OUTPUT_INDEX: OutputIndex =
OutputIndex::from_index((1 << (OUTPUT_INDEX_DISK_BYTES * 8)) - 1);
pub const OUTPUT_LOCATION_DISK_BYTES: usize =
TRANSACTION_LOCATION_DISK_BYTES + OUTPUT_INDEX_DISK_BYTES;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(
any(test, feature = "proptest-impl"),
derive(Arbitrary, serde::Serialize, serde::Deserialize)
)]
pub struct OutputLocation {
transaction_location: TransactionLocation,
output_index: OutputIndex,
}
impl OutputLocation {
#[allow(dead_code)]
pub fn from_usize(
height: Height,
transaction_index: usize,
output_index: usize,
) -> OutputLocation {
OutputLocation {
transaction_location: TransactionLocation::from_usize(height, transaction_index),
output_index: OutputIndex::from_usize(output_index),
}
}
pub fn from_outpoint(
transaction_location: TransactionLocation,
outpoint: &transparent::OutPoint,
) -> OutputLocation {
OutputLocation::from_output_index(transaction_location, outpoint.index)
}
pub fn from_output_index(
transaction_location: TransactionLocation,
output_index: u32,
) -> OutputLocation {
OutputLocation {
transaction_location,
output_index: OutputIndex::from_index(output_index),
}
}
pub fn height(&self) -> Height {
self.transaction_location.height
}
pub fn transaction_index(&self) -> TransactionIndex {
self.transaction_location.index
}
pub fn output_index(&self) -> OutputIndex {
self.output_index
}
pub fn transaction_location(&self) -> TransactionLocation {
self.transaction_location
}
#[cfg(any(test, feature = "proptest-impl"))]
#[allow(dead_code)]
pub fn height_mut(&mut self) -> &mut Height {
&mut self.transaction_location.height
}
}
pub type AddressLocation = OutputLocation;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(
any(test, feature = "proptest-impl"),
derive(Arbitrary, serde::Serialize, serde::Deserialize),
serde(bound = "C: Constraint + Clone")
)]
pub struct AddressBalanceLocationInner<C: Constraint + Copy + std::fmt::Debug> {
balance: Amount<C>,
received: u64,
location: AddressLocation,
}
impl<C: Constraint + Copy + std::fmt::Debug> AddressBalanceLocationInner<C> {
pub(crate) fn new(first_output: OutputLocation) -> Self {
Self {
balance: Amount::zero(),
received: 0,
location: first_output,
}
}
pub fn balance(&self) -> Amount<C> {
self.balance
}
pub fn received(&self) -> u64 {
self.received
}
pub fn balance_mut(&mut self) -> &mut Amount<C> {
&mut self.balance
}
pub fn received_mut(&mut self) -> &mut u64 {
&mut self.received
}
pub fn address_location(&self) -> AddressLocation {
self.location
}
#[cfg(any(test, feature = "proptest-impl"))]
#[allow(dead_code)]
pub fn height_mut(&mut self) -> &mut Height {
&mut self.location.transaction_location.height
}
#[allow(clippy::unwrap_in_result)]
pub fn receive_output(
&mut self,
unspent_output: &transparent::Output,
) -> Result<(), amount::Error> {
self.balance = (self
.balance
.zatoshis()
.checked_add(unspent_output.value().zatoshis()))
.expect("ops handling taddr balances must not overflow")
.try_into()?;
self.received = self.received.saturating_add(unspent_output.value().into());
Ok(())
}
#[allow(clippy::unwrap_in_result)]
pub fn spend_output(
&mut self,
spent_output: &transparent::Output,
) -> Result<(), amount::Error> {
self.balance = (self
.balance
.zatoshis()
.checked_sub(spent_output.value().zatoshis()))
.expect("ops handling taddr balances must not underflow")
.try_into()?;
Ok(())
}
}
impl<C: Constraint + Copy + std::fmt::Debug> std::ops::Add for AddressBalanceLocationInner<C> {
type Output = Result<Self, amount::Error>;
fn add(self, rhs: Self) -> Self::Output {
Ok(AddressBalanceLocationInner {
balance: (self.balance + rhs.balance)?,
received: self.received.saturating_add(rhs.received),
location: self.location.min(rhs.location),
})
}
}
impl From<AddressBalanceLocationInner<NonNegative>> for AddressBalanceLocation {
fn from(value: AddressBalanceLocationInner<NonNegative>) -> Self {
Self(value)
}
}
impl From<AddressBalanceLocationInner<NegativeAllowed>> for AddressBalanceLocationChange {
fn from(value: AddressBalanceLocationInner<NegativeAllowed>) -> Self {
Self(value)
}
}
pub struct AddressBalanceLocationChange(AddressBalanceLocationInner<NegativeAllowed>);
pub enum AddressBalanceLocationUpdates {
Merge(HashMap<transparent::Address, AddressBalanceLocationChange>),
Insert(HashMap<transparent::Address, AddressBalanceLocation>),
}
impl From<HashMap<transparent::Address, AddressBalanceLocation>> for AddressBalanceLocationUpdates {
fn from(value: HashMap<transparent::Address, AddressBalanceLocation>) -> Self {
Self::Insert(value)
}
}
impl From<HashMap<transparent::Address, AddressBalanceLocationChange>>
for AddressBalanceLocationUpdates
{
fn from(value: HashMap<transparent::Address, AddressBalanceLocationChange>) -> Self {
Self::Merge(value)
}
}
impl AddressBalanceLocationChange {
pub fn new(location: AddressLocation) -> Self {
Self(AddressBalanceLocationInner::new(location))
}
}
impl std::ops::Deref for AddressBalanceLocationChange {
type Target = AddressBalanceLocationInner<NegativeAllowed>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for AddressBalanceLocationChange {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl std::ops::Add for AddressBalanceLocationChange {
type Output = Result<Self, amount::Error>;
fn add(self, rhs: Self) -> Self::Output {
(self.0 + rhs.0).map(Self)
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(
any(test, feature = "proptest-impl"),
derive(Arbitrary, serde::Serialize, serde::Deserialize)
)]
pub struct AddressBalanceLocation(AddressBalanceLocationInner<NonNegative>);
impl AddressBalanceLocation {
pub fn new(first_output: OutputLocation) -> Self {
Self(AddressBalanceLocationInner::new(first_output))
}
pub fn into_new_change(self) -> AddressBalanceLocationChange {
AddressBalanceLocationChange::new(self.location)
}
}
impl std::ops::Deref for AddressBalanceLocation {
type Target = AddressBalanceLocationInner<NonNegative>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for AddressBalanceLocation {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl std::ops::Add for AddressBalanceLocation {
type Output = Result<Self, amount::Error>;
fn add(self, rhs: Self) -> Self::Output {
(self.0 + rhs.0).map(Self)
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
#[cfg_attr(
any(test, feature = "proptest-impl"),
derive(Arbitrary, serde::Serialize, serde::Deserialize)
)]
pub struct AddressUnspentOutput {
address_location: AddressLocation,
unspent_output_location: OutputLocation,
}
impl AddressUnspentOutput {
pub fn new(
address_location: AddressLocation,
unspent_output_location: OutputLocation,
) -> AddressUnspentOutput {
AddressUnspentOutput {
address_location,
unspent_output_location,
}
}
pub fn address_iterator_start(address_location: AddressLocation) -> AddressUnspentOutput {
let zero_output_location = OutputLocation::from_usize(Height(0), 0, 0);
AddressUnspentOutput {
address_location,
unspent_output_location: zero_output_location,
}
}
pub fn address_iterator_next(&mut self) {
self.unspent_output_location.output_index += 1;
}
pub fn address_location(&self) -> AddressLocation {
self.address_location
}
pub fn unspent_output_location(&self) -> OutputLocation {
self.unspent_output_location
}
#[cfg(any(test, feature = "proptest-impl"))]
#[allow(dead_code)]
pub fn address_location_mut(&mut self) -> &mut AddressLocation {
&mut self.address_location
}
#[cfg(any(test, feature = "proptest-impl"))]
#[allow(dead_code)]
pub fn unspent_output_location_mut(&mut self) -> &mut OutputLocation {
&mut self.unspent_output_location
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
#[cfg_attr(
any(test, feature = "proptest-impl"),
derive(Arbitrary, serde::Serialize, serde::Deserialize)
)]
pub struct AddressTransaction {
address_location: AddressLocation,
transaction_location: TransactionLocation,
}
impl AddressTransaction {
pub fn new(
address_location: AddressLocation,
transaction_location: TransactionLocation,
) -> AddressTransaction {
AddressTransaction {
address_location,
transaction_location,
}
}
pub fn address_iterator_range(
address_location: AddressLocation,
query: std::ops::RangeInclusive<Height>,
) -> std::ops::RangeInclusive<AddressTransaction> {
let first_utxo_location = address_location.transaction_location();
let query_start_location = TransactionLocation::from_index(*query.start(), 0);
let query_end_location = TransactionLocation::from_index(*query.end(), u16::MAX);
let addr_tx = |tx_loc| AddressTransaction::new(address_location, tx_loc);
addr_tx(max(first_utxo_location, query_start_location))..=addr_tx(query_end_location)
}
#[allow(dead_code)]
pub fn address_iterator_next(&mut self) {
self.transaction_location.index.0 += 1;
}
pub fn address_location(&self) -> AddressLocation {
self.address_location
}
pub fn transaction_location(&self) -> TransactionLocation {
self.transaction_location
}
#[cfg(any(test, feature = "proptest-impl"))]
#[allow(dead_code)]
pub fn address_location_mut(&mut self) -> &mut AddressLocation {
&mut self.address_location
}
#[cfg(any(test, feature = "proptest-impl"))]
#[allow(dead_code)]
pub fn transaction_location_mut(&mut self) -> &mut TransactionLocation {
&mut self.transaction_location
}
}
fn address_variant(address: &transparent::Address) -> u8 {
use NetworkKind::*;
match (address.network_kind(), address) {
(Mainnet, PayToPublicKeyHash { .. }) => 0,
(Mainnet, PayToScriptHash { .. }) => 1,
(Testnet | Regtest, PayToPublicKeyHash { .. }) => 2,
(Testnet | Regtest, PayToScriptHash { .. }) => 3,
(Mainnet, Tex { .. }) => 4,
(Testnet | Regtest, Tex { .. }) => 5,
}
}
impl IntoDisk for transparent::Address {
type Bytes = [u8; 21];
fn as_bytes(&self) -> Self::Bytes {
let variant_bytes = vec![address_variant(self)];
let hash_bytes = self.hash_bytes().to_vec();
[variant_bytes, hash_bytes].concat().try_into().unwrap()
}
}
#[cfg(any(test, feature = "proptest-impl"))]
impl FromDisk for transparent::Address {
fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
let (address_variant, hash_bytes) = disk_bytes.as_ref().split_at(1);
let address_variant = address_variant[0];
let hash_bytes = hash_bytes.try_into().unwrap();
let network = if address_variant < 2 {
NetworkKind::Mainnet
} else {
NetworkKind::Testnet
};
if address_variant % 2 == 0 {
transparent::Address::from_pub_key_hash(network, hash_bytes)
} else {
transparent::Address::from_script_hash(network, hash_bytes)
}
}
}
impl<C: Constraint> IntoDisk for Amount<C> {
type Bytes = [u8; BALANCE_DISK_BYTES];
fn as_bytes(&self) -> Self::Bytes {
self.to_bytes()
}
}
impl FromDisk for Amount<NonNegative> {
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
let array = bytes.as_ref().try_into().unwrap();
Amount::from_bytes(array).unwrap()
}
}
impl IntoDisk for OutputIndex {
type Bytes = [u8; OUTPUT_INDEX_DISK_BYTES];
fn as_bytes(&self) -> Self::Bytes {
let mem_bytes = self.index().to_be_bytes();
let disk_bytes = truncate_zero_be_bytes(&mem_bytes, OUTPUT_INDEX_DISK_BYTES);
match disk_bytes {
Some(b) => b.try_into().unwrap(),
None => {
#[cfg(test)]
{
use zebra_chain::serialization::TrustedPreallocate;
assert!(
u64::from(MAX_ON_DISK_OUTPUT_INDEX.index())
> zebra_chain::transparent::Output::max_allocation(),
"increased block size requires database output index format change",
);
}
truncate_zero_be_bytes(
&MAX_ON_DISK_OUTPUT_INDEX.index().to_be_bytes(),
OUTPUT_INDEX_DISK_BYTES,
)
.expect("max on disk output index is valid")
.try_into()
.unwrap()
}
}
}
}
impl FromDisk for OutputIndex {
fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
const MEM_LEN: usize = size_of::<u32>();
let mem_bytes = expand_zero_be_bytes::<MEM_LEN>(disk_bytes.as_ref());
OutputIndex::from_index(u32::from_be_bytes(mem_bytes))
}
}
impl IntoDisk for OutputLocation {
type Bytes = [u8; OUTPUT_LOCATION_DISK_BYTES];
fn as_bytes(&self) -> Self::Bytes {
let transaction_location_bytes = self.transaction_location().as_bytes().to_vec();
let output_index_bytes = self.output_index().as_bytes().to_vec();
[transaction_location_bytes, output_index_bytes]
.concat()
.try_into()
.unwrap()
}
}
impl FromDisk for OutputLocation {
fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
let (transaction_location_bytes, output_index_bytes) = disk_bytes
.as_ref()
.split_at(TRANSACTION_LOCATION_DISK_BYTES);
let transaction_location = TransactionLocation::from_bytes(transaction_location_bytes);
let output_index = OutputIndex::from_bytes(output_index_bytes);
OutputLocation {
transaction_location,
output_index,
}
}
}
impl<C: Constraint + Copy + std::fmt::Debug> IntoDisk for AddressBalanceLocationInner<C> {
type Bytes = [u8; BALANCE_DISK_BYTES + OUTPUT_LOCATION_DISK_BYTES + size_of::<u64>()];
fn as_bytes(&self) -> Self::Bytes {
let balance_bytes = self.balance().as_bytes().to_vec();
let address_location_bytes = self.address_location().as_bytes().to_vec();
let received_bytes = self.received().to_le_bytes().to_vec();
[balance_bytes, address_location_bytes, received_bytes]
.concat()
.try_into()
.unwrap()
}
}
impl IntoDisk for AddressBalanceLocation {
type Bytes = [u8; BALANCE_DISK_BYTES + OUTPUT_LOCATION_DISK_BYTES + size_of::<u64>()];
fn as_bytes(&self) -> Self::Bytes {
self.0.as_bytes()
}
}
impl IntoDisk for AddressBalanceLocationChange {
type Bytes = [u8; BALANCE_DISK_BYTES + OUTPUT_LOCATION_DISK_BYTES + size_of::<u64>()];
fn as_bytes(&self) -> Self::Bytes {
self.0.as_bytes()
}
}
impl<C: Constraint + Copy + std::fmt::Debug> FromDisk for AddressBalanceLocationInner<C> {
fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
let (balance_bytes, rest) = disk_bytes.as_ref().split_at(BALANCE_DISK_BYTES);
let (address_location_bytes, rest) = rest.split_at(BALANCE_DISK_BYTES);
let (received_bytes, _) = rest.split_at_checked(size_of::<u64>()).unwrap_or_default();
let balance = Amount::from_bytes(balance_bytes.try_into().unwrap()).unwrap();
let address_location = AddressLocation::from_bytes(address_location_bytes);
let received = u64::from_le_bytes(received_bytes.try_into().unwrap_or_default());
let mut address_balance_location = Self::new(address_location);
*address_balance_location.balance_mut() = balance;
*address_balance_location.received_mut() = received;
address_balance_location
}
}
impl FromDisk for AddressBalanceLocation {
fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
Self(AddressBalanceLocationInner::from_bytes(disk_bytes))
}
}
impl FromDisk for AddressBalanceLocationChange {
fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
Self(AddressBalanceLocationInner::from_bytes(disk_bytes))
}
}
impl IntoDisk for transparent::Output {
type Bytes = Vec<u8>;
fn as_bytes(&self) -> Self::Bytes {
self.zcash_serialize_to_vec().unwrap()
}
}
impl FromDisk for transparent::Output {
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
bytes.as_ref().zcash_deserialize_into().unwrap()
}
}
impl IntoDisk for AddressUnspentOutput {
type Bytes = [u8; OUTPUT_LOCATION_DISK_BYTES + OUTPUT_LOCATION_DISK_BYTES];
fn as_bytes(&self) -> Self::Bytes {
let address_location_bytes = self.address_location().as_bytes();
let unspent_output_location_bytes = self.unspent_output_location().as_bytes();
[address_location_bytes, unspent_output_location_bytes]
.concat()
.try_into()
.unwrap()
}
}
impl FromDisk for AddressUnspentOutput {
fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
let (address_location_bytes, unspent_output_location_bytes) =
disk_bytes.as_ref().split_at(OUTPUT_LOCATION_DISK_BYTES);
let address_location = AddressLocation::from_bytes(address_location_bytes);
let unspent_output_location = AddressLocation::from_bytes(unspent_output_location_bytes);
AddressUnspentOutput::new(address_location, unspent_output_location)
}
}
impl IntoDisk for AddressTransaction {
type Bytes = [u8; OUTPUT_LOCATION_DISK_BYTES + TRANSACTION_LOCATION_DISK_BYTES];
fn as_bytes(&self) -> Self::Bytes {
let address_location_bytes: [u8; OUTPUT_LOCATION_DISK_BYTES] =
self.address_location().as_bytes();
let transaction_location_bytes: [u8; TRANSACTION_LOCATION_DISK_BYTES] =
self.transaction_location().as_bytes();
address_location_bytes
.iter()
.copied()
.chain(transaction_location_bytes.iter().copied())
.collect::<Vec<u8>>()
.try_into()
.expect("concatenation of fixed-sized arrays should have the correct size")
}
}
impl FromDisk for AddressTransaction {
fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
let (address_location_bytes, transaction_location_bytes) =
disk_bytes.as_ref().split_at(OUTPUT_LOCATION_DISK_BYTES);
let address_location = AddressLocation::from_bytes(address_location_bytes);
let transaction_location = TransactionLocation::from_bytes(transaction_location_bytes);
AddressTransaction::new(address_location, transaction_location)
}
}