pub(super) mod funding;
pub(super) mod lifecycle;
pub(super) mod reservations;
pub(crate) use funding::record_funded_refund_keys;
pub use funding::verify_refund_funding_proposal;
pub use reservations::{OperationOutcome, ReceiveDeposit};
use super::{OneTimeKey, account_key, restore::queue};
use crate::{AccountUuid, WalletDb, error::SqliteClientError, util::Clock};
use rand_core::Rng;
use rusqlite::{Connection, OptionalExtension};
use std::borrow::{Borrow, BorrowMut};
use zakura_one_time_ivk::swap::Observation;
use zcash_protocol::{
consensus::{BlockHeight, Parameters},
memo::MemoBytes,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ReservationPolicy {
Disabled,
Gap,
Limit,
Stale,
FundingHistory,
ReceiveHistory,
}
impl<C: Borrow<Connection>, P: Parameters, CL, R> WalletDb<C, P, CL, R> {
pub fn refund_funding_memo(
&self,
account: AccountUuid,
index: u64,
deposit: &str,
) -> Result<MemoBytes, SqliteClientError> {
funding::funding_memo(self.conn.borrow(), &self.params, account, index, deposit)
}
}
impl<C: BorrowMut<Connection>, P: Parameters, CL: Clock, R: Rng> WalletDb<C, P, CL, R> {
pub fn reserve_refund_key(
&mut self,
account: AccountUuid,
tip: BlockHeight,
) -> Result<Result<OneTimeKey, ReservationPolicy>, SqliteClientError> {
self.transactionally(|db| {
if !enabled(db.conn.0, db.params, account)? {
return Ok(Err(ReservationPolicy::Disabled));
}
funding::reserve(db.conn.0, db.params, account, tip)
})
}
pub fn record_refund_operation(
&mut self,
account: AccountUuid,
index: u64,
deposit: &str,
deadline: i64,
now: i64,
) -> Result<(), SqliteClientError> {
self.transactionally(|db| {
if !enabled(db.conn.0, db.params, account)? {
return Err(super::invalid("private receivers disabled"));
}
funding::record_operation(db.conn.0, db.params, account, index, deposit, deadline, now)
})
}
pub fn prepare_receive_reservation(
&mut self,
account: AccountUuid,
now: i64,
) -> Result<Result<OneTimeKey, ReservationPolicy>, SqliteClientError> {
self.transactionally(|db| {
if !enabled(db.conn.0, db.params, account)? {
return Ok(Err(ReservationPolicy::Disabled));
}
reservations::reap(db.conn.0, db.params, account, now)?;
queue::ensure_window(db.conn.0, db.params, account)?;
reservations::prepare(db.conn.0, db.params, account, now)
})
}
pub fn begin_receive_operation(
&mut self,
account: AccountUuid,
index: u64,
deadline: i64,
now: i64,
) -> Result<Result<String, ReservationPolicy>, SqliteClientError> {
let mut id = [0; 16];
self.rng.fill_bytes(&mut id);
let request = hex::encode(id);
self.transactionally(|db| {
if !enabled(db.conn.0, db.params, account)? {
return Ok(Err(ReservationPolicy::Disabled));
}
reservations::begin(db.conn.0, account, index, &request, deadline, now)
.map(|begun| begun.map(|()| request))
})
}
pub fn finish_receive_operation(
&mut self,
account: AccountUuid,
request: &str,
outcome: &OperationOutcome,
) -> Result<(), SqliteClientError> {
self.transactionally(|db| reservations::finish(db.conn.0, account, request, outcome))
}
pub fn start_receive_operation(
&mut self,
account: AccountUuid,
request: &str,
now: i64,
) -> Result<Result<ReceiveDeposit, ReservationPolicy>, SqliteClientError> {
self.transactionally(|db| {
if !enabled(db.conn.0, db.params, account)? {
return Ok(Err(ReservationPolicy::Disabled));
}
reservations::start(db.conn.0, account, request, now)
})
}
pub fn record_operation_status(
&mut self,
account: AccountUuid,
deposit: &str,
memo: Option<&str>,
observation: Observation,
now: i64,
) -> Result<bool, SqliteClientError> {
self.transactionally(|db| {
reservations::record_status(
db.conn.0,
db.params,
account,
(deposit, memo),
observation,
now,
)
})
}
}
const RESERVED: &str = "(k.reserved_at IS NOT NULL AND k.released_at IS NULL)";
fn enabled<P: Parameters>(
conn: &Connection,
params: &P,
account: AccountUuid,
) -> Result<bool, SqliteClientError> {
let (owner, _) = account_key(conn, params, account)?;
Ok(conn
.query_row(
"SELECT enabled FROM ironwood_one_time_recovery WHERE account_id = ?1",
[owner.0],
|r| r.get(0),
)
.optional()?
.unwrap_or(false))
}
#[cfg(test)]
mod tests;