use std::fmt;
use hex::{FromHex, ToHex};
use zebra_chain::{block::Height, transaction};
use crate::{FromDisk, IntoDisk, TransactionLocation};
use super::block::TRANSACTION_LOCATION_DISK_BYTES;
#[cfg(any(test, feature = "proptest-impl"))]
use proptest_derive::Arbitrary;
#[cfg(test)]
mod tests;
pub type SaplingScanningKey = String;
#[derive(Copy, Clone, Eq, PartialEq)]
#[cfg_attr(
any(test, feature = "proptest-impl"),
derive(Arbitrary, Default, serde::Serialize, serde::Deserialize)
)]
pub struct SaplingScannedResult(
#[cfg_attr(any(test, feature = "proptest-impl"), serde(with = "hex"))] [u8; 32],
);
impl fmt::Display for SaplingScannedResult {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.encode_hex::<String>())
}
}
impl fmt::Debug for SaplingScannedResult {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("SaplingScannedResult")
.field(&self.encode_hex::<String>())
.finish()
}
}
impl ToHex for &SaplingScannedResult {
fn encode_hex<T: FromIterator<char>>(&self) -> T {
self.bytes_in_display_order().encode_hex()
}
fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
self.bytes_in_display_order().encode_hex_upper()
}
}
impl ToHex for SaplingScannedResult {
fn encode_hex<T: FromIterator<char>>(&self) -> T {
(&self).encode_hex()
}
fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
(&self).encode_hex_upper()
}
}
impl FromHex for SaplingScannedResult {
type Error = <[u8; 32] as FromHex>::Error;
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
let result = <[u8; 32]>::from_hex(hex)?;
Ok(Self::from_bytes_in_display_order(result))
}
}
impl From<SaplingScannedResult> for transaction::Hash {
fn from(scanned_result: SaplingScannedResult) -> Self {
transaction::Hash::from_bytes_in_display_order(&scanned_result.0)
}
}
impl From<transaction::Hash> for SaplingScannedResult {
fn from(hash: transaction::Hash) -> Self {
SaplingScannedResult(hash.bytes_in_display_order())
}
}
impl SaplingScannedResult {
pub fn from_bytes_in_display_order(bytes: [u8; 32]) -> Self {
Self(bytes)
}
pub fn bytes_in_display_order(&self) -> [u8; 32] {
self.0
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary, Default))]
pub struct SaplingScannedDatabaseEntry {
pub index: SaplingScannedDatabaseIndex,
pub value: Option<SaplingScannedResult>,
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary, Default))]
pub struct SaplingScannedDatabaseIndex {
pub sapling_key: SaplingScanningKey,
pub tx_loc: TransactionLocation,
}
impl SaplingScannedDatabaseIndex {
pub const fn min() -> Self {
Self {
sapling_key: String::new(),
tx_loc: TransactionLocation::MIN,
}
}
pub fn min_for_key(sapling_key: &SaplingScanningKey) -> Self {
Self {
sapling_key: sapling_key.clone(),
tx_loc: TransactionLocation::MIN,
}
}
pub fn max_for_key(sapling_key: &SaplingScanningKey) -> Self {
Self {
sapling_key: sapling_key.clone(),
tx_loc: TransactionLocation::MAX,
}
}
pub fn min_for_key_and_height(sapling_key: &SaplingScanningKey, height: Height) -> Self {
Self {
sapling_key: sapling_key.clone(),
tx_loc: TransactionLocation::min_for_height(height),
}
}
pub fn max_for_key_and_height(sapling_key: &SaplingScanningKey, height: Height) -> Self {
Self {
sapling_key: sapling_key.clone(),
tx_loc: TransactionLocation::max_for_height(height),
}
}
}
impl IntoDisk for SaplingScanningKey {
type Bytes = Vec<u8>;
fn as_bytes(&self) -> Self::Bytes {
SaplingScanningKey::as_bytes(self).to_vec()
}
}
impl FromDisk for SaplingScanningKey {
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
SaplingScanningKey::from_utf8(bytes.as_ref().to_vec())
.expect("only valid UTF-8 strings are written to the database")
}
}
impl IntoDisk for SaplingScannedDatabaseIndex {
type Bytes = Vec<u8>;
fn as_bytes(&self) -> Self::Bytes {
let mut bytes = Vec::new();
bytes.extend(self.sapling_key.as_bytes());
bytes.extend(self.tx_loc.as_bytes());
bytes
}
}
impl FromDisk for SaplingScannedDatabaseIndex {
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
let bytes = bytes.as_ref();
let (sapling_key, tx_loc) = bytes.split_at(bytes.len() - TRANSACTION_LOCATION_DISK_BYTES);
Self {
sapling_key: SaplingScanningKey::from_bytes(sapling_key),
tx_loc: TransactionLocation::from_bytes(tx_loc),
}
}
}
impl IntoDisk for Option<SaplingScannedResult> {
type Bytes = Vec<u8>;
fn as_bytes(&self) -> Self::Bytes {
let mut bytes = Vec::new();
if let Some(result) = self.as_ref() {
bytes.extend(result.bytes_in_display_order());
}
bytes
}
}
impl FromDisk for Option<SaplingScannedResult> {
#[allow(clippy::unwrap_in_result)]
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
let bytes = bytes.as_ref();
if bytes.is_empty() {
None
} else {
Some(SaplingScannedResult::from_bytes_in_display_order(
bytes
.try_into()
.expect("unexpected incorrect SaplingScannedResult data length"),
))
}
}
}