use zcash_client_backend::data_api::{
transparent_ledger::{
ChainPoint, LastKnownBalance, LastKnownSource, RecoveryBlocker, RecoveryCompletion,
TransparentAuthority, TransparentLedgerBalance, TransparentLedgerMode,
TransparentLedgerSnapshot,
},
wallet::{ConfirmationsPolicy, TargetHeight},
};
use zcash_protocol::{
consensus::{self, BlockHeight},
value::Zatoshis,
};
use crate::{AccountUuid, error::SqliteClientError, wallet::chain_tip_height};
#[cfg(feature = "transparent-inputs")]
use {
super::recovery,
zcash_client_backend::data_api::{AccountBalance, transparent_ledger::AccountLifecycle},
zcash_keys::keys::transparent::gap_limits::GapLimits,
};
#[cfg(feature = "transparent-inputs")]
fn transparent_balance(
conn: &rusqlite::Connection,
account: AccountUuid,
target_height: TargetHeight,
confirmations_policy: ConfirmationsPolicy,
ledger_only: bool,
) -> Result<Option<TransparentLedgerBalance>, SqliteClientError> {
let mut balances = std::collections::HashMap::<AccountUuid, AccountBalance>::new();
crate::wallet::transparent::add_transparent_account_balances(
conn,
target_height,
confirmations_policy,
ledger_only,
Some(account),
&mut balances,
)?;
let balance = balances.remove(&account).unwrap_or(AccountBalance::ZERO);
Ok(Some(TransparentLedgerBalance {
regular: *balance.unshielded_regular_balance(),
coinbase: *balance.unshielded_coinbase_balance(),
}))
}
#[cfg(not(feature = "transparent-inputs"))]
fn transparent_balance(
_: &rusqlite::Connection,
_: AccountUuid,
_: TargetHeight,
_: ConfirmationsPolicy,
_: bool,
) -> Result<Option<TransparentLedgerBalance>, SqliteClientError> {
Ok(None)
}
struct PrivateLedgerView {
authority: Option<TransparentLedgerBalance>,
last_known: Option<LastKnownBalance>,
blockers: Vec<RecoveryBlocker>,
covered_through: Option<ChainPoint>,
recovered_unverified: Option<Zatoshis>,
}
#[cfg(feature = "transparent-inputs")]
fn private_ledger_view<P: consensus::Parameters>(
conn: &rusqlite::Connection,
params: &P,
gap_limits: &GapLimits,
account: AccountUuid,
tip: BlockHeight,
confirmations_policy: ConfirmationsPolicy,
) -> Result<PrivateLedgerView, SqliteClientError> {
let ledger = recovery::account_ledger(conn, params, gap_limits, account)?;
let target = TargetHeight::from(tip + 1);
let covered_through = match ledger.status.covered_through {
Some(height) => {
crate::wallet::get_block_hash(conn, height)?.map(|hash| ChainPoint { height, hash })
}
None => None,
};
let (authority, last_known, blockers) = if ledger.authorizes_after(tip) {
(
transparent_balance(conn, account, target, confirmations_policy, true)?,
None,
vec![],
)
} else {
let last_known = match ledger.lifecycle {
AccountLifecycle::Active => {
let at = covered_through.filter(|point| point.height == tip);
transparent_balance(conn, account, target, confirmations_policy, true)?.map(
|balance| LastKnownBalance {
balance,
source: LastKnownSource::PrivateLedger,
at,
},
)
}
AccountLifecycle::Candidate => {
legacy_last_known(conn, account, target, confirmations_policy)?
}
};
(
None,
last_known,
recovery::ledger_blockers(conn, &ledger, Some(tip))?,
)
};
Ok(PrivateLedgerView {
authority,
last_known,
blockers,
covered_through,
recovered_unverified: ledger.status.recovered_unverified,
})
}
#[cfg(not(feature = "transparent-inputs"))]
fn private_ledger_view(
conn: &rusqlite::Connection,
account: AccountUuid,
tip: BlockHeight,
confirmations_policy: ConfirmationsPolicy,
) -> Result<PrivateLedgerView, SqliteClientError> {
Ok(PrivateLedgerView {
authority: None,
last_known: legacy_last_known(
conn,
account,
TargetHeight::from(tip + 1),
confirmations_policy,
)?,
blockers: vec![
RecoveryBlocker::TransparentSupportUnavailable,
RecoveryBlocker::PrivateRecoveryUnavailable,
],
covered_through: None,
recovered_unverified: None,
})
}
fn legacy_last_known(
conn: &rusqlite::Connection,
account: AccountUuid,
target: TargetHeight,
confirmations_policy: ConfirmationsPolicy,
) -> Result<Option<LastKnownBalance>, SqliteClientError> {
transparent_balance(conn, account, target, confirmations_policy, false)?
.map(|balance| {
Ok(LastKnownBalance {
balance,
source: last_known_source(conn, account, target, confirmations_policy)?,
at: None,
})
})
.transpose()
}
pub(crate) fn ledger_snapshot<P: consensus::Parameters>(
conn: &rusqlite::Connection,
#[cfg_attr(not(feature = "transparent-inputs"), allow(unused_variables))] params: &P,
#[cfg(feature = "transparent-inputs")] gap_limits: &GapLimits,
account: AccountUuid,
mode: TransparentLedgerMode,
confirmations_policy: ConfirmationsPolicy,
) -> Result<TransparentLedgerSnapshot<AccountUuid>, SqliteClientError> {
let account_exists: bool = conn.query_row(
"SELECT EXISTS (SELECT 1 FROM accounts WHERE uuid = ?1)",
[account.0],
|row| row.get(0),
)?;
if !account_exists {
return Err(SqliteClientError::AccountUnknown);
}
let blocked_snapshot = |blockers| TransparentLedgerSnapshot {
account,
mode,
authority: TransparentAuthority::Unavailable,
authorized: None,
last_known: None,
completion: RecoveryCompletion::Blocked,
blockers,
covered_through: None,
recovered_unverified: None,
};
let Some(tip) = chain_tip_height(conn)? else {
return Ok(blocked_snapshot(vec![RecoveryBlocker::ChainUnknown]));
};
let target = TargetHeight::from(tip + 1);
if mode.retains_public_authority() {
let Some(balance) =
transparent_balance(conn, account, target, confirmations_policy, false)?
else {
return Ok(blocked_snapshot(vec![
RecoveryBlocker::TransparentSupportUnavailable,
]));
};
return Ok(TransparentLedgerSnapshot {
account,
mode,
authority: TransparentAuthority::Public,
authorized: Some(balance),
last_known: None,
completion: RecoveryCompletion::NotApplicable,
blockers: vec![],
covered_through: None,
recovered_unverified: None,
});
}
#[cfg(feature = "transparent-inputs")]
let private_ledger =
private_ledger_view(conn, params, gap_limits, account, tip, confirmations_policy)?;
#[cfg(not(feature = "transparent-inputs"))]
let private_ledger = private_ledger_view(conn, account, tip, confirmations_policy)?;
let (covered_through, recovered_unverified) = (
private_ledger.covered_through,
private_ledger.recovered_unverified,
);
Ok(match private_ledger.authority {
Some(balance) => TransparentLedgerSnapshot {
account,
mode,
authority: TransparentAuthority::Private,
authorized: Some(balance),
last_known: None,
completion: RecoveryCompletion::Complete,
blockers: vec![],
covered_through,
recovered_unverified,
},
None => TransparentLedgerSnapshot {
account,
mode,
authority: TransparentAuthority::Unavailable,
authorized: None,
last_known: private_ledger.last_known,
completion: RecoveryCompletion::Blocked,
blockers: private_ledger.blockers,
covered_through,
recovered_unverified,
},
})
}
#[cfg(feature = "transparent-inputs")]
fn last_known_source(
conn: &rusqlite::Connection,
account: AccountUuid,
target_height: TargetHeight,
confirmations_policy: ConfirmationsPolicy,
) -> Result<LastKnownSource, SqliteClientError> {
use crate::wallet::transparent::{BalanceProvenance, transparent_balance_provenance};
Ok(
match transparent_balance_provenance(conn, account, target_height, confirmations_policy)? {
BalanceProvenance::LegacyPublic => LastKnownSource::LegacyPublic,
BalanceProvenance::IncludesLocalOnly => LastKnownSource::LegacyPublicAndLocal,
},
)
}
#[cfg(not(feature = "transparent-inputs"))]
fn last_known_source(
_: &rusqlite::Connection,
_: AccountUuid,
_: TargetHeight,
_: ConfirmationsPolicy,
) -> Result<LastKnownSource, SqliteClientError> {
Ok(LastKnownSource::LegacyPublic)
}