use rusqlite::named_params;
use zcash_protocol::consensus::BlockHeight;
use crate::error::SqliteClientError;
#[cfg(feature = "transparent-inputs")]
use {
super::{
capture_policy_generation, check_transparent_policy_generation, durable_policy,
grants_private_authority, projection, resolve_mode,
},
crate::{
AccountRef, AccountUuid,
wallet::{
Account, chain_tip_height, encoding::KeyScope, fully_scanned_height, get_account,
get_block_hash, transparent::get_legacy_transparent_address,
},
},
std::collections::{BTreeMap, BTreeSet},
transparent::{
address::TransparentAddress,
bundle::OutPoint,
keys::{IncomingViewingKey as _, NonHardenedChildIndex, TransparentKeyScope},
},
zcash_client_backend::data_api::{
Account as _,
transparent_ledger::{
AccountLifecycle, AddressRange, CandidateBlocker, CandidateRecovery, ChainPoint,
CommitOutcome, CommitRejection, IntegrityFailure, InvalidCommit,
MAX_RECOVERY_IDENTIFIER_LEN, PageRequest, PendingPage, PublicationAnchor, ReceiveEvent,
RecoveryBlocker, RecoveryRevision, RefusedCommit, SpendEvent, StaleCommit,
TransparentAddressWatchSet, TransparentLedgerCommit, TransparentLedgerMode,
WatchOrigin, WatchedAddress,
},
},
zcash_keys::{
address::Address,
keys::{
ReceiverRequirement::{Allow, Require},
UnifiedAddressRequest,
transparent::gap_limits::GapLimits,
},
},
zcash_primitives::{block::BlockHash, transaction::TxId},
zcash_protocol::{consensus, value::Zatoshis},
zcash_script::script,
};
#[cfg(feature = "transparent-inputs")]
mod account_ledger;
#[cfg(feature = "transparent-inputs")]
mod commit;
#[cfg(feature = "transparent-inputs")]
mod coverage;
#[cfg(feature = "transparent-inputs")]
mod diagnostics;
#[cfg(feature = "transparent-inputs")]
mod events;
#[cfg(feature = "transparent-inputs")]
mod ledger_activation;
mod lifecycle;
#[cfg(feature = "transparent-inputs")]
mod metadata;
#[cfg(feature = "transparent-inputs")]
mod ownership;
#[cfg(feature = "transparent-inputs")]
mod recovery_addresses;
#[cfg(feature = "transparent-inputs")]
mod recovery_suspension;
#[cfg(feature = "transparent-inputs")]
mod revisions;
#[cfg(feature = "transparent-inputs")]
use account_ledger::AccountLedger;
#[cfg(feature = "transparent-inputs")]
pub(crate) use account_ledger::{account_ledger, ledger_blockers};
#[cfg(all(
feature = "transparent-inputs",
any(test, feature = "test-dependencies")
))]
pub(crate) use commit::qualify_revision;
#[cfg(feature = "transparent-inputs")]
pub(crate) use commit::{CommitTrust, apply_commit};
#[cfg(feature = "transparent-inputs")]
pub(crate) use diagnostics::candidate_recovery;
#[cfg(feature = "transparent-inputs")]
use diagnostics::{placed_receives, placed_spends, recovery_status};
#[cfg(feature = "transparent-inputs")]
pub(crate) use ledger_activation::activate_recovered_ledger;
pub(crate) use lifecycle::{clear_pending_pages, truncate_recovery};
#[cfg(feature = "transparent-inputs")]
pub(crate) use lifecycle::{forget_ledger, forget_reattributed_script};
#[cfg(all(feature = "transparent-inputs", feature = "transparent-key-import"))]
pub(crate) use ownership::forget_other_candidates;
#[cfg(feature = "transparent-inputs")]
pub(crate) use recovery_addresses::watch_set;
#[cfg(feature = "transparent-inputs")]
use super::atomically;
#[cfg(feature = "transparent-inputs")]
use commit::account_lifecycle;
#[cfg(feature = "transparent-inputs")]
use events::{CoverageSupport, apply_receive, apply_spend, open_page, record_range};
#[cfg(feature = "transparent-inputs")]
use recovery_addresses::{AccountRecoveryAddresses, WINDOW_LIMIT, WINDOW_SCOPES};
#[cfg(feature = "transparent-inputs")]
use recovery_suspension::{
account_recovery_is_suspended, source_recovery_is_suspended, suspend_recovery,
};
#[cfg(feature = "transparent-inputs")]
fn script_bytes(address: &TransparentAddress) -> Vec<u8> {
transparent::address::Script::from(address.script()).0.0
}
#[cfg(feature = "transparent-inputs")]
fn address_from_script(bytes: Vec<u8>) -> Result<TransparentAddress, SqliteClientError> {
script::FromChain::parse(&script::Code(bytes))
.ok()
.and_then(|script| TransparentAddress::from_script_from_chain(&script))
.ok_or_else(|| {
SqliteClientError::CorruptedData("recovery script is not a standard address".into())
})
}
#[cfg(feature = "transparent-inputs")]
fn reject(rejection: CommitRejection) -> SqliteClientError {
SqliteClientError::TransparentLedgerCommitRejected(rejection)
}
#[cfg(feature = "transparent-inputs")]
fn local_target(conn: &rusqlite::Connection) -> Result<Option<ChainPoint>, SqliteClientError> {
let Some(height) = fully_scanned_height(conn)? else {
return Ok(None);
};
Ok(get_block_hash(conn, height)?.map(|hash| ChainPoint { height, hash }))
}
#[cfg(feature = "transparent-inputs")]
fn is_local_block(
conn: &rusqlite::Connection,
point: &ChainPoint,
) -> Result<bool, SqliteClientError> {
Ok(get_block_hash(conn, point.height)? == Some(point.hash))
}
#[cfg(feature = "transparent-inputs")]
fn height(value: u32) -> BlockHeight {
BlockHeight::from_u32(value)
}
#[cfg(feature = "transparent-inputs")]
fn read_revision(
row: &rusqlite::Row<'_>,
offset: usize,
) -> Result<RecoveryRevision, SqliteClientError> {
Ok(RecoveryRevision {
source: row.get(offset)?,
revision: row.get(offset + 1)?,
lineage: u64::try_from(row.get::<_, i64>(offset + 2)?)
.map_err(|_| SqliteClientError::CorruptedData("negative revision lineage".into()))?,
sealed: row.get(offset + 3)?,
publication: PublicationAnchor {
height: height(row.get(offset + 4)?),
hash: BlockHash::try_from_slice(&row.get::<_, Vec<u8>>(offset + 5)?).ok_or_else(
|| SqliteClientError::CorruptedData("invalid publication hash".into()),
)?,
},
})
}
#[cfg(feature = "transparent-inputs")]
fn read_hash(bytes: Vec<u8>) -> Result<BlockHash, SqliteClientError> {
BlockHash::try_from_slice(&bytes)
.ok_or_else(|| SqliteClientError::CorruptedData("invalid block hash".into()))
}
#[cfg(all(test, feature = "transparent-inputs"))]
mod tests {
use std::collections::BTreeMap;
use transparent::{
address::TransparentAddress,
keys::{NonHardenedChildIndex, TransparentKeyScope},
};
use zcash_client_backend::data_api::transparent_ledger::WatchOrigin;
use super::recovery_addresses::note_origin;
#[test]
fn derivation_outranks_a_standalone_import() {
let address = TransparentAddress::PublicKeyHash([1; 20]);
let derived = WatchOrigin::Derived {
scope: TransparentKeyScope::EXTERNAL,
index: NonHardenedChildIndex::ZERO,
};
for order in [
[WatchOrigin::Standalone, derived],
[derived, WatchOrigin::Standalone],
] {
let mut addresses = BTreeMap::new();
for origin in order {
note_origin(&mut addresses, address, origin);
}
assert_eq!(addresses[&address], derived);
}
}
}