diff --git a/src/api/account.rs b/src/api/account.rs index aae12cb..aa9e727 100644 --- a/src/api/account.rs +++ b/src/api/account.rs @@ -206,8 +206,12 @@ pub fn new_diversified_address(ua_type: u8) -> anyhow::Result { db.store_diversifier(c.id_account, &new_diversifier_index)?; let orchard_keys = db.get_orchard(c.id_account)?; - if ua_type == 2 || orchard_keys.is_none() { // sapling only - return Ok(encode_payment_address(c.chain.network().hrp_sapling_payment_address(), &pa)); + if ua_type == 2 || orchard_keys.is_none() { + // sapling only + return Ok(encode_payment_address( + c.chain.network().hrp_sapling_payment_address(), + &pa, + )); } let orchard_keys = orchard_keys.unwrap(); @@ -371,11 +375,7 @@ pub fn derive_keys( /// * t, s, o: include transparent, sapling, orchard receivers? /// /// The address depends on the UA settings and may include transparent, sapling & orchard receivers -pub fn get_unified_address( - coin: u8, - id_account: u32, - address_type: u8, -) -> anyhow::Result { +pub fn get_unified_address(coin: u8, id_account: u32, address_type: u8) -> anyhow::Result { let c = CoinConfig::get(coin); let db = c.db()?; let tpe = UnifiedAddressType { diff --git a/src/api/dart_ffi.rs b/src/api/dart_ffi.rs index 1301e8e..43fa19a 100644 --- a/src/api/dart_ffi.rs +++ b/src/api/dart_ffi.rs @@ -414,7 +414,8 @@ pub async unsafe extern "C" fn get_taddr_balance(coin: u8, id_account: u32) -> C pub async unsafe extern "C" fn transfer_pools( coin: u8, account: u32, - from_pool: u8, to_pool: u8, + from_pool: u8, + to_pool: u8, amount: u64, fee_included: bool, memo: *mut c_char, @@ -423,9 +424,18 @@ pub async unsafe extern "C" fn transfer_pools( ) -> CResult<*mut c_char> { from_c_str!(memo); let res = async move { - let tx_plan = crate::api::payment_v2::transfer_pools(coin, account, from_pool, to_pool, - amount, fee_included, - &memo, split_amount, confirmations).await?; + let tx_plan = crate::api::payment_v2::transfer_pools( + coin, + account, + from_pool, + to_pool, + amount, + fee_included, + &memo, + split_amount, + confirmations, + ) + .await?; let tx_plan = serde_json::to_string(&tx_plan)?; Ok::<_, anyhow::Error>(tx_plan) }; diff --git a/src/api/payment_v2.rs b/src/api/payment_v2.rs index 7f8d93e..6df720d 100644 --- a/src/api/payment_v2.rs +++ b/src/api/payment_v2.rs @@ -32,7 +32,9 @@ pub async fn build_tx_plan( let mut recipient_fee = false; for r in recipients { if r.fee_included { - if recipient_fee { return Err(TransactionBuilderError::DuplicateRecipientFee) } + if recipient_fee { + return Err(TransactionBuilderError::DuplicateRecipientFee); + } recipient_fee = true; } } @@ -154,8 +156,17 @@ pub async fn build_max_tx( Err(TransactionBuilderError::TxTooComplex) } -pub async fn transfer_pools(coin: u8, account: u32, from_pool: u8, to_pool: u8, amount: u64, - fee_included: bool, memo: &str, split_amount: u64, confirmations: u32) -> anyhow::Result { +pub async fn transfer_pools( + coin: u8, + account: u32, + from_pool: u8, + to_pool: u8, + amount: u64, + fee_included: bool, + memo: &str, + split_amount: u64, + confirmations: u32, +) -> anyhow::Result { let address = get_unified_address(coin, account, to_pool)?; // get our own unified address let recipient = RecipientMemo { address, @@ -165,14 +176,37 @@ pub async fn transfer_pools(coin: u8, account: u32, from_pool: u8, to_pool: u8, max_amount_per_note: split_amount, }; let last_height = get_latest_height().await?; - let tx_plan = build_tx_plan(coin, account, last_height, slice::from_ref(&recipient), - !from_pool, confirmations).await?; + let tx_plan = build_tx_plan( + coin, + account, + last_height, + slice::from_ref(&recipient), + !from_pool, + confirmations, + ) + .await?; Ok(tx_plan) } /// Make a transaction that shields the transparent balance -pub async fn shield_taddr(coin: u8, account: u32, amount: u64, confirmations: u32) -> anyhow::Result { - let tx_plan = transfer_pools(coin, account, 1, 6, amount, true, "Shield Transparent Balance", 0, confirmations).await?; +pub async fn shield_taddr( + coin: u8, + account: u32, + amount: u64, + confirmations: u32, +) -> anyhow::Result { + let tx_plan = transfer_pools( + coin, + account, + 1, + 6, + amount, + true, + "Shield Transparent Balance", + 0, + confirmations, + ) + .await?; let tx_id = sign_and_broadcast(coin, account, &tx_plan).await?; log::info!("TXID: {}", tx_id); Ok(tx_id) diff --git a/src/db.rs b/src/db.rs index e417941..21d0aac 100644 --- a/src/db.rs +++ b/src/db.rs @@ -651,8 +651,7 @@ impl DbAdapter { let note = r?; notes.push(note); } - } - else { + } else { let mut statement = self.connection.prepare( "SELECT id_note, diversifier, value, rcm, rho, witness FROM received_notes r, orchard_witnesses w WHERE spent IS NULL AND account = ?2 AND rho IS NOT NULL AND (r.excluded IS NULL OR NOT r.excluded) AND w.height = ?1 @@ -707,36 +706,54 @@ impl DbAdapter { pub fn purge_old_witnesses(&mut self, height: u32) -> anyhow::Result<()> { log::debug!("+purge_old_witnesses"); - let min_height: Option = self.connection.query_row( - "SELECT MAX(height) FROM blocks WHERE height <= ?1", - params![height], + const BLOCKS_PER_HOUR: u32 = 60*60/75; + const BLOCKS_PER_DAY: u32 = 24*BLOCKS_PER_HOUR; + const BLOCKS_PER_MONTH: u32 = 30*BLOCKS_PER_DAY; + for i in 1..=24 { // 1 checkpoint per hour + self.prune_interval(height - i*BLOCKS_PER_HOUR, height - (i-1)*BLOCKS_PER_HOUR)?; + } + for i in 2..=30 { // 1 checkpoint per day + self.prune_interval(height - i*BLOCKS_PER_DAY, height - (i-1)*BLOCKS_PER_DAY)?; + } + for i in 2..=12 { // 1 checkpoint per 30 days + self.prune_interval(height - i*BLOCKS_PER_MONTH, height - (i-1)*BLOCKS_PER_MONTH)?; + } + + log::debug!("-purge_old_witnesses"); + Ok(()) + } + + // Only keep the oldest checkpoint in [low, high) + fn prune_interval(&mut self, low: u32, high: u32) -> anyhow::Result<()> { + log::info!("prune_interval {} {}", low, high); + let keep_height: Option = self.connection.query_row( + "SELECT MIN(height) FROM blocks WHERE height >= ?1 AND height < ?2", + params![low, high], |row| row.get(0), )?; - - // Leave at least one sapling witness - if let Some(min_height) = min_height { - log::debug!("Purging witnesses older than {}", min_height); + if let Some(keep_height) = keep_height { + log::info!("keep {}", keep_height); let transaction = self.connection.transaction()?; transaction.execute( - "DELETE FROM sapling_witnesses WHERE height < ?1", - params![min_height], + "DELETE FROM sapling_witnesses WHERE height >= ?1 AND height < ?2 AND height != ?3", + params![low, high, keep_height], )?; transaction.execute( - "DELETE FROM orchard_witnesses WHERE height < ?1", - params![min_height], + "DELETE FROM orchard_witnesses WHERE height >= ?1 AND height < ?2 AND height != ?3", + params![low, high, keep_height], )?; - transaction.execute("DELETE FROM blocks WHERE height < ?1", params![min_height])?; + transaction.execute("DELETE FROM blocks WHERE height >= ?1 AND height < ?2 AND height != ?3", + params![low, high, keep_height])?; transaction.execute( - "DELETE FROM sapling_tree WHERE height < ?1", - params![min_height], + "DELETE FROM sapling_tree WHERE height >= ?1 AND height < ?2 AND height != ?3", + params![low, high, keep_height], )?; transaction.execute( - "DELETE FROM orchard_tree WHERE height < ?1", - params![min_height], + "DELETE FROM orchard_tree WHERE height >= ?1 AND height < ?2 AND height != ?3", + params![low, high, keep_height], )?; transaction.commit()?; } - log::debug!("-purge_old_witnesses"); Ok(()) } diff --git a/src/key2.rs b/src/key2.rs index ca71501..c917029 100644 --- a/src/key2.rs +++ b/src/key2.rs @@ -1,7 +1,11 @@ use crate::coinconfig::CoinConfig; use anyhow::anyhow; use bip39::{Language, Mnemonic, Seed}; -use zcash_client_backend::encoding::{decode_extended_full_viewing_key, decode_extended_spending_key, decode_payment_address, decode_transparent_address, encode_extended_full_viewing_key, encode_extended_spending_key, encode_payment_address}; +use zcash_client_backend::encoding::{ + decode_extended_full_viewing_key, decode_extended_spending_key, decode_payment_address, + decode_transparent_address, encode_extended_full_viewing_key, encode_extended_spending_key, + encode_payment_address, +}; use zcash_client_backend::keys::UnifiedFullViewingKey; use zcash_primitives::consensus::{Network, Parameters}; use zcash_primitives::zip32::{ChildIndex, ExtendedFullViewingKey, ExtendedSpendingKey}; @@ -79,17 +83,15 @@ pub fn is_valid_address(coin: u8, address: &str) -> bool { let network = c.chain.network(); if decode_payment_address(network.hrp_sapling_payment_address(), address).is_ok() { true - } - else if let Ok(Some(_)) = decode_transparent_address( + } else if let Ok(Some(_)) = decode_transparent_address( &network.b58_pubkey_address_prefix(), &network.b58_script_address_prefix(), - address) { + address, + ) { true - } - else if zcash_client_backend::address::RecipientAddress::decode(network, address).is_some() { + } else if zcash_client_backend::address::RecipientAddress::decode(network, address).is_some() { true - } - else { + } else { false } } diff --git a/src/main/rpc.rs b/src/main/rpc.rs index a1ecff5..8c65697 100644 --- a/src/main/rpc.rs +++ b/src/main/rpc.rs @@ -15,8 +15,8 @@ use thiserror::Error; use warp_api_ffi::api::payment_uri::PaymentURI; use warp_api_ffi::api::recipient::{Recipient, RecipientMemo, RecipientShort}; use warp_api_ffi::{ - build_tx, get_secret_keys, AccountData, AccountRec, CoinConfig, KeyPack, - RaptorQDrops, TransactionPlan, TxRec, + build_tx, get_secret_keys, AccountData, AccountRec, CoinConfig, KeyPack, RaptorQDrops, + TransactionPlan, TxRec, }; lazy_static! { @@ -187,11 +187,8 @@ pub fn get_address() -> Result { #[get("/unified_address?&&")] pub fn get_unified_address(t: u8, s: u8, o: u8) -> Result { let c = CoinConfig::get_active(); - let address = warp_api_ffi::api::account::get_unified_address( - c.coin, - c.id_account, - t & s << 1 & o << 2, - )?; + let address = + warp_api_ffi::api::account::get_unified_address(c.coin, c.id_account, t & s << 1 & o << 2)?; Ok(address) } diff --git a/src/note_selection/optimize.rs b/src/note_selection/optimize.rs index 57be86d..4830c4a 100644 --- a/src/note_selection/optimize.rs +++ b/src/note_selection/optimize.rs @@ -35,10 +35,7 @@ pub fn group_orders(orders: &[Order], fee: u64) -> Result<(Vec, Order } } let amount = order.amount(fee)?; - order_info.push(OrderInfo { - group_type, - amount, - }); + order_info.push(OrderInfo { group_type, amount }); } let mut t0 = 0u64; @@ -190,7 +187,7 @@ pub fn fill( order_infos: &[OrderInfo], amounts: &OrderGroupAmounts, allocation: &FundAllocation, - fee: u64 + fee: u64, ) -> Result> { assert_eq!(orders.len(), order_infos.len()); let mut fills = vec![]; diff --git a/src/note_selection/ser.rs b/src/note_selection/ser.rs index 903331d..f161f4d 100644 --- a/src/note_selection/ser.rs +++ b/src/note_selection/ser.rs @@ -6,10 +6,7 @@ use zcash_primitives::memo::MemoBytes; #[derive(Serialize, Deserialize)] #[serde(remote = "MemoBytes")] -pub struct MemoBytesProxy( - #[serde(getter = "get_memo_bytes")] - pub String, -); +pub struct MemoBytesProxy(#[serde(getter = "get_memo_bytes")] pub String); fn get_memo_bytes(memo: &MemoBytes) -> String { hex::encode(memo.as_slice()) diff --git a/src/note_selection/types.rs b/src/note_selection/types.rs index a8f4026..5a3d746 100644 --- a/src/note_selection/types.rs +++ b/src/note_selection/types.rs @@ -201,7 +201,14 @@ impl Destination { } impl Order { - pub fn new(network: &Network, id: u32, address: &str, amount: u64, take_fee: bool, memo: MemoBytes) -> Self { + pub fn new( + network: &Network, + id: u32, + address: &str, + amount: u64, + take_fee: bool, + memo: MemoBytes, + ) -> Self { let destinations = decode(network, address).unwrap(); Order { id, @@ -214,10 +221,11 @@ impl Order { pub fn amount(&self, fee: u64) -> Result { if self.take_fee { - if self.raw_amount < fee { return Err(TransactionBuilderError::RecipientCannotPayFee) } + if self.raw_amount < fee { + return Err(TransactionBuilderError::RecipientCannotPayFee); + } Ok(self.raw_amount - fee) - } - else { + } else { Ok(self.raw_amount) } } diff --git a/src/scan.rs b/src/scan.rs index 070e7e1..24f5309 100644 --- a/src/scan.rs +++ b/src/scan.rs @@ -207,9 +207,8 @@ async fn sync_async_inner<'a>( } } - let mut db = db_builder.build()?; + let db = db_builder.build()?; db.store_block_timestamp(last_height, &last_hash, last_timestamp)?; - db.purge_old_witnesses(last_height - 500)?; height = last_height; let cb = progress_callback.lock().await; cb(progress.clone()); @@ -220,6 +219,8 @@ async fn sync_async_inner<'a>( if get_tx { get_transaction_details(coin).await?; } + let mut db = db_builder.build()?; + db.purge_old_witnesses(height)?; Ok(()) }