From 2414e692be6960e10b733b2673b48729d8073987 Mon Sep 17 00:00:00 2001 From: cygnet Date: Thu, 19 Sep 2024 00:06:16 +0200 Subject: [PATCH 01/14] Merge outpoints list with Sp Wallet --- rust/Cargo.lock | 2 +- rust/src/api/wallet.rs | 17 ++-- rust/src/blindbit/logic.rs | 23 ++--- rust/src/wallet/mod.rs | 1 - rust/src/wallet/outputslist.rs | 172 ------------------------------- rust/src/wallet/spwallet.rs | 181 +++++++++++++++++++++++---------- 6 files changed, 146 insertions(+), 250 deletions(-) delete mode 100644 rust/src/wallet/outputslist.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index c68502c..e650872 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1535,7 +1535,7 @@ dependencies = [ [[package]] name = "sp_client" version = "0.1.0" -source = "git+https://github.com/cygnet3/sp-client?branch=master#1e9fc52fe3f7971c2f5935eaea8c386f7c5f4e05" +source = "git+https://github.com/cygnet3/sp-client?branch=master#9908afd45e5ea51e2a8629fd606f64f3ce0178ab" dependencies = [ "anyhow", "bitcoin", diff --git a/rust/src/api/wallet.rs b/rust/src/api/wallet.rs index 626044c..1bbff02 100644 --- a/rust/src/api/wallet.rs +++ b/rust/src/api/wallet.rs @@ -77,10 +77,7 @@ pub async fn setup( )) } } - let mut sp_wallet = SpWallet::new(sp_client, None, vec![]).unwrap(); - - sp_wallet.get_mut_outputs().set_birthday(birthday); - sp_wallet.reset_to_birthday(); + let sp_wallet = SpWallet::new(sp_client, birthday).unwrap(); Ok(serde_json::to_string(&sp_wallet).unwrap()) } @@ -90,8 +87,7 @@ pub async fn setup( #[flutter_rust_bridge::frb(sync)] pub fn change_birthday(encoded_wallet: String, birthday: u32) -> Result { let mut wallet: SpWallet = serde_json::from_str(&encoded_wallet)?; - let outputs = wallet.get_mut_outputs(); - outputs.set_birthday(birthday); + wallet.set_birthday(birthday); wallet.reset_to_birthday(); Ok(serde_json::to_string(&wallet).unwrap()) } @@ -123,9 +119,9 @@ pub fn get_wallet_info(encoded_wallet: String) -> Result { Ok(WalletStatus { address: wallet.get_client().get_receiving_address(), network: wallet.get_client().get_network().to_core_arg().to_owned(), - balance: wallet.get_outputs().get_balance().to_sat(), - birthday: wallet.get_outputs().get_birthday(), - last_scan: wallet.get_outputs().get_last_scan(), + balance: wallet.get_balance().to_sat(), + birthday: wallet.get_birthday(), + last_scan: wallet.get_last_scan(), tx_history: wallet .get_tx_history() .into_iter() @@ -133,7 +129,6 @@ pub fn get_wallet_info(encoded_wallet: String) -> Result { .collect(), outputs: wallet .get_outputs() - .to_outpoints_list() .into_iter() .map(|(outpoint, output)| (outpoint.to_string(), output.into())) .collect(), @@ -149,7 +144,7 @@ pub fn mark_outpoints_spent( let mut wallet: SpWallet = serde_json::from_str(&encoded_wallet)?; for outpoint in spent { - wallet.get_mut_outputs().mark_spent( + wallet.mark_spent( OutPoint::from_str(&outpoint)?, Txid::from_str(&spent_by)?, true, diff --git a/rust/src/blindbit/logic.rs b/rust/src/blindbit/logic.rs index 8a9310b..56b4866 100644 --- a/rust/src/blindbit/logic.rs +++ b/rust/src/blindbit/logic.rs @@ -20,7 +20,6 @@ use sp_client::spclient::{OutputSpendStatus, OwnedOutput, SpClient}; use crate::{ blindbit::client::{BlindbitClient, UtxoResponse}, stream::ScanResult, - wallet::outputslist::OutputList, }; use crate::{ stream::{send_scan_progress, send_scan_result, ScanProgress}, @@ -49,7 +48,7 @@ pub async fn scan_blocks( ) -> Result<()> { let blindbit_client = get_blindbit_client(host_url); - let last_scan = sp_wallet.get_outputs().get_last_scan(); + let last_scan = sp_wallet.get_last_scan(); let tip_height = blindbit_client.block_height().await?; // 0 means scan to tip @@ -114,7 +113,8 @@ pub async fn scan_blocks( tweaks, new_utxo_filter, spent_filter, - sp_wallet, + sp_wallet.get_client(), + sp_wallet.get_owned_outpoints(), &blindbit_client, ) .await?; @@ -132,7 +132,7 @@ pub async fn scan_blocks( } if send_update { - sp_wallet.get_mut_outputs().update_last_scan(blkheight); + sp_wallet.set_last_scan(blkheight); let wallet_str = serde_json::to_string(&sp_wallet)?; @@ -156,14 +156,15 @@ pub async fn process_block( tweaks: Vec, new_utxo_filter: FilterResponse, spent_filter: FilterResponse, - sp_wallet: &SpWallet, + sp_client: &SpClient, + owned_outpoints: Vec, blindbit_client: &BlindbitClient, ) -> Result<(HashMap, Vec)> { let outs = process_block_outputs( blkheight, tweaks, new_utxo_filter, - sp_wallet.get_client(), + sp_client, blindbit_client, ) .await?; @@ -171,7 +172,7 @@ pub async fn process_block( let ins = process_block_inputs( blkheight, spent_filter, - sp_wallet.get_outputs(), + owned_outpoints, blindbit_client, ) .await?; @@ -233,7 +234,7 @@ pub async fn process_block_outputs( pub async fn process_block_inputs( blkheight: u32, spent_filter: FilterResponse, - outputs: &OutputList, + outputs: Vec, blindbit_client: &BlindbitClient, ) -> Result> { let mut res = vec![]; @@ -359,13 +360,11 @@ pub fn check_block_outputs( pub fn get_input_hashes( blkhash: BlockHash, - outputs: &OutputList, + outpoints: Vec, ) -> Result> { - let owned = outputs.to_outpoints_list(); - let mut map: HashMap<[u8; 8], OutPoint> = HashMap::new(); - for (outpoint, _) in owned { + for outpoint in outpoints { let mut arr = [0u8; 68]; arr[..32].copy_from_slice(&outpoint.txid.to_raw_hash().to_byte_array()); arr[32..36].copy_from_slice(&outpoint.vout.to_le_bytes()); diff --git a/rust/src/wallet/mod.rs b/rust/src/wallet/mod.rs index 735323b..1b4afb9 100644 --- a/rust/src/wallet/mod.rs +++ b/rust/src/wallet/mod.rs @@ -1,3 +1,2 @@ -pub mod outputslist; pub mod recorded; pub mod spwallet; diff --git a/rust/src/wallet/outputslist.rs b/rust/src/wallet/outputslist.rs deleted file mode 100644 index c8374c2..0000000 --- a/rust/src/wallet/outputslist.rs +++ /dev/null @@ -1,172 +0,0 @@ -use std::{collections::HashMap, io::Write}; - -use anyhow::{Error, Result}; -use serde::{Deserialize, Serialize}; -use sp_client::{ - bitcoin::{hashes::Hash, secp256k1::PublicKey, Amount, BlockHash, OutPoint, Txid}, - silentpayments::{self, utils::SilentPaymentAddress}, - spclient::{OutputSpendStatus, OwnedOutput, SpClient}, -}; - -type WalletFingerprint = [u8; 8]; - -#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)] -pub struct OutputList { - pub wallet_fingerprint: WalletFingerprint, - birthday: u32, - last_scan: u32, - outputs: HashMap, -} - -impl OutputList { - pub fn new(scan_pk: PublicKey, spend_pk: PublicKey, birthday: u32) -> Self { - // take a fingerprint of the wallet by hashing its keys - let mut engine = silentpayments::bitcoin_hashes::sha256::HashEngine::default(); - engine - .write_all(&scan_pk.serialize()) - .expect("Failed to write scan_pk to engine"); - engine - .write_all(&spend_pk.serialize()) - .expect("Failed to write spend_pk to engine"); - let hash = silentpayments::bitcoin_hashes::sha256::Hash::from_engine(engine); - let mut wallet_fingerprint = [0u8; 8]; - wallet_fingerprint.copy_from_slice(&hash.to_byte_array()[..8]); - let outputs = HashMap::new(); - Self { - wallet_fingerprint, - outputs, - birthday, - last_scan: birthday, - } - } - - pub fn check_fingerprint(&self, client: &SpClient) -> bool { - let sp_address: SilentPaymentAddress = client.get_receiving_address().try_into().unwrap(); - let new = Self::new(sp_address.get_scan_key(), sp_address.get_spend_key(), 0); - new.wallet_fingerprint == self.wallet_fingerprint - } - - pub fn get_birthday(&self) -> u32 { - self.birthday - } - - pub fn get_last_scan(&self) -> u32 { - self.last_scan - } - - pub fn set_birthday(&mut self, new_birthday: u32) { - self.birthday = new_birthday; - } - - pub fn update_last_scan(&mut self, scan_height: u32) { - self.last_scan = scan_height; - } - - pub(crate) fn reset_to_height(&mut self, height: u32) { - let new_outputs = self - .to_outpoints_list() - .into_iter() - .filter(|(_, o)| o.blockheight < height) - .collect::>(); - self.outputs = new_outputs; - } - - pub fn to_outpoints_list(&self) -> HashMap { - self.outputs.clone() - } - - pub fn extend_from(&mut self, new: HashMap) { - self.outputs.extend(new); - } - - pub fn get_balance(&self) -> Amount { - self.outputs - .iter() - .filter(|(_, o)| o.spend_status == OutputSpendStatus::Unspent) - .fold(Amount::from_sat(0), |acc, x| acc + x.1.amount) - } - - #[allow(dead_code)] - pub fn to_spendable_list(&self) -> HashMap { - self.to_outpoints_list() - .into_iter() - .filter(|(_, o)| o.spend_status == OutputSpendStatus::Unspent) - .collect() - } - - pub fn get_outpoint(&self, outpoint: OutPoint) -> Result<(OutPoint, OwnedOutput)> { - let output = self - .to_outpoints_list() - .get_key_value(&outpoint) - .ok_or_else(|| Error::msg("Outpoint not in list"))? - .1 - .to_owned(); - - Ok((outpoint, output)) - } - - pub fn mark_spent( - &mut self, - outpoint: OutPoint, - spending_tx: Txid, - force_update: bool, - ) -> Result<()> { - let (outpoint, mut output) = self.get_outpoint(outpoint)?; - - match output.spend_status { - OutputSpendStatus::Unspent => { - let tx_hex = spending_tx.to_string(); - output.spend_status = OutputSpendStatus::Spent(tx_hex); - self.outputs.insert(outpoint, output); - Ok(()) - } - OutputSpendStatus::Spent(tx_hex) => { - // We may want to fail if that's the case, or force update if we know what we're doing - if force_update { - let tx_hex = spending_tx.to_string(); - output.spend_status = OutputSpendStatus::Spent(tx_hex); - self.outputs.insert(outpoint, output); - Ok(()) - } else { - Err(Error::msg(format!( - "Output already spent by transaction {}", - tx_hex - ))) - } - } - OutputSpendStatus::Mined(block) => Err(Error::msg(format!( - "Output already mined in block {}", - block - ))), - } - } - - /// Mark the output as being spent in block `mined_in_block` - /// We don't really need to check the previous status, if it's in a block there's nothing we can do - pub fn mark_mined(&mut self, outpoint: OutPoint, mined_in_block: BlockHash) -> Result<()> { - let (outpoint, mut output) = self.get_outpoint(outpoint)?; - - let block_hex = mined_in_block.to_string(); - output.spend_status = OutputSpendStatus::Mined(block_hex); - self.outputs.insert(outpoint, output); - Ok(()) - } - - /// Revert the outpoint status to Unspent, regardless of the current status - /// This could be useful on some rare occurrences, like a transaction falling out of mempool after a while - /// Watch out we also reverse the mined state, use with caution - #[allow(dead_code)] - pub fn revert_spent_status(&mut self, outpoint: OutPoint) -> Result<()> { - let (outpoint, mut output) = self.get_outpoint(outpoint)?; - - if output.spend_status != OutputSpendStatus::Unspent { - output.spend_status = OutputSpendStatus::Unspent; - self.outputs.insert(outpoint, output); - } - Ok(()) - } - - pub fn get_mut(&mut self, outpoint: &OutPoint) -> Option<&mut OwnedOutput> { - self.outputs.get_mut(outpoint) - } -} diff --git a/rust/src/wallet/spwallet.rs b/rust/src/wallet/spwallet.rs index da4402a..4f46d4f 100644 --- a/rust/src/wallet/spwallet.rs +++ b/rust/src/wallet/spwallet.rs @@ -14,57 +14,43 @@ use anyhow::{Error, Result}; use sp_client::silentpayments::utils as sp_utils; use sp_client::spclient::{OutputSpendStatus, OwnedOutput, Recipient, SpClient}; -use super::outputslist::OutputList; use super::recorded::{ RecordedTransaction, RecordedTransactionIncoming, RecordedTransactionOutgoing, }; +type WalletFingerprint = [u8; 8]; + #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct SpWallet { client: SpClient, - outputs: OutputList, + wallet_fingerprint: WalletFingerprint, tx_history: Vec, + birthday: u32, + last_scan: u32, + outputs: HashMap, } impl SpWallet { - pub fn new( - client: SpClient, - outputs: Option, - tx_history: Vec, - ) -> Result { - if let Some(existing_outputs) = outputs { - if existing_outputs.check_fingerprint(&client) { - Ok(Self { - client, - outputs: existing_outputs, - tx_history, - }) - } else { - Err(Error::msg("outputs don't match client")) - } - } else { - // Create a new outputs list - let outputs = OutputList::new( - client.get_scan_key().public_key(&Secp256k1::signing_only()), - client.get_spend_key().into(), - 0, - ); - Ok(Self { - client, - outputs, - tx_history, - }) - } + pub fn new(client: SpClient, birthday: u32) -> Result { + let wallet_fingerprint = client.get_client_fingerprint()?; + let last_scan = birthday; + let tx_history = vec![]; + let outputs = HashMap::new(); + + Ok(Self { + client, + birthday, + wallet_fingerprint, + last_scan, + tx_history, + outputs, + }) } pub fn get_client(&self) -> &SpClient { &self.client } - pub fn get_outputs(&self) -> &OutputList { - &self.outputs - } - pub fn get_tx_history(&self) -> Vec { self.tx_history.clone() } @@ -74,8 +60,35 @@ impl SpWallet { &mut self.client } - pub fn get_mut_outputs(&mut self) -> &mut OutputList { - &mut self.outputs + pub fn get_birthday(&self) -> u32 { + self.birthday + } + + pub fn set_birthday(&mut self, new_birthday: u32) { + self.birthday = new_birthday; + } + + pub fn get_last_scan(&self) -> u32 { + self.last_scan + } + + pub fn set_last_scan(&mut self, new_scan: u32) { + self.last_scan = new_scan; + } + + pub fn get_outputs(self) -> HashMap { + self.outputs.clone() + } + + pub fn get_owned_outpoints(&self) -> Vec { + self.outputs.keys().cloned().collect() + } + + pub fn get_balance(&self) -> Amount { + self.outputs + .iter() + .filter(|(_, o)| o.spend_status == OutputSpendStatus::Unspent) + .fold(Amount::from_sat(0), |acc, x| acc + x.1.amount) } #[allow(dead_code)] @@ -89,21 +102,17 @@ impl SpWallet { let txid = tx.txid(); for i in 0..tx.output.len() { - if self - .get_outputs() - .get_outpoint(OutPoint { - txid, - vout: i as u32, - }) - .is_ok() - { + if self.outputs.contains_key(&OutPoint { + txid, + vout: i as u32, + }) { return Err(Error::msg("Transaction already scanned")); } } for input in tx.input.iter() { - if let Ok((_, output)) = self.get_outputs().get_outpoint(input.previous_output) { - match output.spend_status { + if let Some(output) = self.outputs.get_mut(&input.previous_output) { + match output.spend_status.clone() { OutputSpendStatus::Spent(tx) => { if tx == txid.to_string() { return Err(Error::msg("Transaction already scanned")); @@ -158,7 +167,7 @@ impl SpWallet { } } let mut res = new_outputs.clone(); - self.outputs.extend_from(new_outputs); + self.outputs.extend(new_outputs); let txid = tx.txid().to_string(); // update outputs that we own and that are spent @@ -229,7 +238,7 @@ impl SpWallet { fn reset_to_height(&mut self, blkheight: u32) { // reset known outputs to height - self.outputs.reset_to_height(blkheight); + self.outputs.retain(|_, o| o.blockheight < blkheight); // reset tx history to height self.tx_history.retain(|tx| match tx { @@ -243,9 +252,8 @@ impl SpWallet { } pub fn reset_to_birthday(&mut self) { - self.reset_to_height(self.outputs.get_birthday()); - - self.outputs.update_last_scan(self.outputs.get_birthday()); + self.reset_to_height(self.birthday); + self.last_scan = self.birthday; } pub fn record_block_outputs( @@ -269,7 +277,7 @@ impl SpWallet { } // add outputs to known outputs - self.outputs.extend_from(found_outputs); + self.outputs.extend(found_outputs); } pub fn record_block_inputs( @@ -281,11 +289,78 @@ impl SpWallet { for outpoint in found_inputs { // this may confirm the same tx multiple times, but this shouldn't be a problem self.confirm_recorded_outgoing_transaction(outpoint, blkheight)?; - self.outputs.mark_mined(outpoint, blkhash)?; + self.mark_mined(outpoint, blkhash)?; } Ok(()) } + + pub fn mark_spent( + &mut self, + outpoint: OutPoint, + spending_tx: Txid, + force_update: bool, + ) -> Result<()> { + let output = self + .outputs + .get_mut(&outpoint) + .ok_or(Error::msg("Outpoint not in list"))?; + + match &output.spend_status { + OutputSpendStatus::Unspent => { + let tx_hex = spending_tx.to_string(); + output.spend_status = OutputSpendStatus::Spent(tx_hex); + //self.outputs.insert(outpoint, output); + Ok(()) + } + OutputSpendStatus::Spent(tx_hex) => { + // We may want to fail if that's the case, or force update if we know what we're doing + if force_update { + let tx_hex = spending_tx.to_string(); + output.spend_status = OutputSpendStatus::Spent(tx_hex); + //self.outputs.insert(outpoint, output); + Ok(()) + } else { + Err(Error::msg(format!( + "Output already spent by transaction {}", + tx_hex + ))) + } + } + OutputSpendStatus::Mined(block) => Err(Error::msg(format!( + "Output already mined in block {}", + block + ))), + } + } + + /// Mark the output as being spent in block `mined_in_block` + /// We don't really need to check the previous status, if it's in a block there's nothing we can do + pub fn mark_mined(&mut self, outpoint: OutPoint, mined_in_block: BlockHash) -> Result<()> { + let output = self + .outputs + .get_mut(&outpoint) + .ok_or(Error::msg("Outpoint not in list"))?; + + let block_hex = mined_in_block.to_string(); + output.spend_status = OutputSpendStatus::Mined(block_hex); + //self.outputs.insert(outpoint, output); + Ok(()) + } + + /// Revert the outpoint status to Unspent, regardless of the current status + /// This could be useful on some rare occurrences, like a transaction falling out of mempool after a while + /// Watch out we also reverse the mined state, use with caution + #[allow(dead_code)] + pub fn revert_spent_status(&mut self, outpoint: OutPoint) -> Result<()> { + let output = self + .outputs + .get_mut(&outpoint) + .ok_or(Error::msg("Outpoint not in list"))?; + + output.spend_status = OutputSpendStatus::Unspent; + Ok(()) + } } pub fn derive_keys_from_seed(seed: &[u8; 64], network: Network) -> Result<(SecretKey, SecretKey)> { From 9c70bd1930d91f660ef2cf32e965c25a3cf2d089 Mon Sep 17 00:00:00 2001 From: cygnet Date: Thu, 19 Sep 2024 23:23:30 +0200 Subject: [PATCH 02/14] Move scanning logic to wallet --- rust/src/api/chain.rs | 3 +- rust/src/api/psbt.rs | 12 +- rust/src/api/wallet.rs | 24 +-- rust/src/blindbit/logic.rs | 395 ------------------------------------ rust/src/blindbit/mod.rs | 5 +- rust/src/wallet/mod.rs | 1 + rust/src/wallet/scan.rs | 371 +++++++++++++++++++++++++++++++++ rust/src/wallet/spwallet.rs | 49 +---- 8 files changed, 398 insertions(+), 462 deletions(-) delete mode 100644 rust/src/blindbit/logic.rs create mode 100644 rust/src/wallet/scan.rs diff --git a/rust/src/api/chain.rs b/rust/src/api/chain.rs index ec06901..e84842d 100644 --- a/rust/src/api/chain.rs +++ b/rust/src/api/chain.rs @@ -4,6 +4,7 @@ use crate::blindbit; pub async fn get_chain_height(blindbit_url: String) -> anyhow::Result { let url = Url::parse(&blindbit_url)?; + let blindbit_client = blindbit::BlindbitClient::new(url); - blindbit::logic::get_chain_height(url).await + blindbit_client.block_height().await } diff --git a/rust/src/api/psbt.rs b/rust/src/api/psbt.rs index 1bc9dbc..54fd89d 100644 --- a/rust/src/api/psbt.rs +++ b/rust/src/api/psbt.rs @@ -28,9 +28,7 @@ pub fn create_new_psbt( let recipients = recipients.into_iter().map(Into::into).collect(); - let (psbt, change_idx) = wallet - .get_client() - .create_new_psbt(inputs, recipients, None)?; + let (psbt, change_idx) = wallet.client.create_new_psbt(inputs, recipients, None)?; Ok((psbt.to_string(), change_idx)) } @@ -50,11 +48,9 @@ pub fn fill_sp_outputs(encoded_wallet: String, psbt: String) -> Result { let wallet: SpWallet = serde_json::from_str(&encoded_wallet)?; let mut psbt = Psbt::from_str(&psbt)?; - let partial_secret = wallet.get_client().get_partial_secret_from_psbt(&psbt)?; + let partial_secret = wallet.client.get_partial_secret_from_psbt(&psbt)?; - wallet - .get_client() - .fill_sp_outputs(&mut psbt, partial_secret)?; + wallet.client.fill_sp_outputs(&mut psbt, partial_secret)?; Ok(psbt.to_string()) } @@ -68,7 +64,7 @@ pub fn sign_psbt(encoded_wallet: String, psbt: String, finalize: bool) -> Result let mut aux_rand = [0u8; 32]; rng.fill_bytes(&mut aux_rand); - let mut signed = wallet.get_client().sign_psbt(psbt, &aux_rand)?; + let mut signed = wallet.client.sign_psbt(psbt, &aux_rand)?; if finalize { SpClient::finalize_psbt(&mut signed)?; diff --git a/rust/src/api/wallet.rs b/rust/src/api/wallet.rs index 1bbff02..289e27d 100644 --- a/rust/src/api/wallet.rs +++ b/rust/src/api/wallet.rs @@ -87,7 +87,7 @@ pub async fn setup( #[flutter_rust_bridge::frb(sync)] pub fn change_birthday(encoded_wallet: String, birthday: u32) -> Result { let mut wallet: SpWallet = serde_json::from_str(&encoded_wallet)?; - wallet.set_birthday(birthday); + wallet.birthday = birthday; wallet.reset_to_birthday(); Ok(serde_json::to_string(&wallet).unwrap()) } @@ -106,10 +106,12 @@ pub async fn scan_to_tip( encoded_wallet: String, ) -> Result<()> { let blindbit_url = Url::parse(&blindbit_url)?; + let blindbit_client = blindbit::BlindbitClient::new(blindbit_url); let mut wallet: SpWallet = serde_json::from_str(&encoded_wallet)?; - blindbit::logic::scan_blocks(blindbit_url, 0, dust_limit, &mut wallet).await?; + wallet.scan_blocks(&blindbit_client, 0, dust_limit).await?; + Ok(()) } @@ -117,18 +119,14 @@ pub async fn scan_to_tip( pub fn get_wallet_info(encoded_wallet: String) -> Result { let wallet: SpWallet = serde_json::from_str(&encoded_wallet)?; Ok(WalletStatus { - address: wallet.get_client().get_receiving_address(), - network: wallet.get_client().get_network().to_core_arg().to_owned(), + address: wallet.client.get_receiving_address(), + network: wallet.client.get_network().to_core_arg().to_owned(), balance: wallet.get_balance().to_sat(), - birthday: wallet.get_birthday(), - last_scan: wallet.get_last_scan(), - tx_history: wallet - .get_tx_history() - .into_iter() - .map(Into::into) - .collect(), + birthday: wallet.birthday, + last_scan: wallet.last_scan, + tx_history: wallet.tx_history.into_iter().map(Into::into).collect(), outputs: wallet - .get_outputs() + .outputs .into_iter() .map(|(outpoint, output)| (outpoint.to_string(), output.into())) .collect(), @@ -200,7 +198,7 @@ pub fn add_incoming_tx_to_history( #[flutter_rust_bridge::frb(sync)] pub fn show_mnemonic(encoded_wallet: String) -> Result> { let wallet: SpWallet = serde_json::from_str(&encoded_wallet)?; - let mnemonic = wallet.get_client().get_mnemonic(); + let mnemonic = wallet.client.get_mnemonic(); Ok(mnemonic) } diff --git a/rust/src/blindbit/logic.rs b/rust/src/blindbit/logic.rs deleted file mode 100644 index 56b4866..0000000 --- a/rust/src/blindbit/logic.rs +++ /dev/null @@ -1,395 +0,0 @@ -use std::{ - collections::HashMap, - time::{Duration, Instant}, -}; - -use anyhow::{Error, Result}; -use futures::{stream, StreamExt}; -use log::info; -use reqwest::Url; -use sp_client::bitcoin::{ - absolute::Height, - bip158::BlockFilter, - hashes::{sha256, Hash}, - secp256k1::{PublicKey, Scalar}, - Amount, BlockHash, OutPoint, Txid, XOnlyPublicKey, -}; -use sp_client::silentpayments::receiving::Label; -use sp_client::spclient::{OutputSpendStatus, OwnedOutput, SpClient}; - -use crate::{ - blindbit::client::{BlindbitClient, UtxoResponse}, - stream::ScanResult, -}; -use crate::{ - stream::{send_scan_progress, send_scan_result, ScanProgress}, - wallet::spwallet::SpWallet, -}; - -use super::client::FilterResponse; - -const CONCURRENT_FILTER_REQUESTS: usize = 200; - -fn get_blindbit_client(host_url: Url) -> BlindbitClient { - BlindbitClient::new(host_url) -} - -pub async fn get_chain_height(host_url: Url) -> Result { - let blindbit_client = get_blindbit_client(host_url); - - blindbit_client.block_height().await -} - -pub async fn scan_blocks( - host_url: Url, - mut n_blocks_to_scan: u32, - dust_limit: u32, - sp_wallet: &mut SpWallet, -) -> Result<()> { - let blindbit_client = get_blindbit_client(host_url); - - let last_scan = sp_wallet.get_last_scan(); - let tip_height = blindbit_client.block_height().await?; - - // 0 means scan to tip - if n_blocks_to_scan == 0 { - n_blocks_to_scan = tip_height - last_scan; - } - - let start = last_scan + 1; - let end = if last_scan + n_blocks_to_scan <= tip_height { - last_scan + n_blocks_to_scan - } else { - tip_height - }; - - if start > end { - info!("scan_blocks called with start > end: {} > {}", start, end); - return Ok(()); - } - - info!("start: {} end: {}", start, end); - let start_time: Instant = Instant::now(); - let mut update_time: Instant = start_time; - - let range = start..=end; - - let mut data = stream::iter(range) - .map(|n| { - let bb_client = &blindbit_client; - async move { - let tweaks = bb_client.tweak_index(n, dust_limit).await.unwrap(); - let new_utxo_filter = bb_client.filter_new_utxos(n).await.unwrap(); - let spent_filter = bb_client.filter_spent(n).await.unwrap(); - let blkhash = new_utxo_filter.block_hash; - (n, blkhash, tweaks, new_utxo_filter, spent_filter) - } - }) - .buffered(CONCURRENT_FILTER_REQUESTS); - - while let Some((blkheight, blkhash, tweaks, new_utxo_filter, spent_filter)) = data.next().await - { - let mut send_update = false; - - // send update if we are currently at the final block - if blkheight == end { - send_update = true; - } - - // send update after 30 seconds since last update - if update_time.elapsed() > Duration::from_secs(30) { - send_update = true; - update_time = Instant::now(); - } - - send_scan_progress(ScanProgress { - start, - current: blkheight, - end, - }); - - let (found_outputs, found_inputs) = process_block( - blkheight, - tweaks, - new_utxo_filter, - spent_filter, - sp_wallet.get_client(), - sp_wallet.get_owned_outpoints(), - &blindbit_client, - ) - .await?; - - if !found_outputs.is_empty() { - send_update = true; - let height = Height::from_consensus(blkheight)?; - sp_wallet.record_block_outputs(height, found_outputs); - } - - if !found_inputs.is_empty() { - send_update = true; - let height = Height::from_consensus(blkheight)?; - sp_wallet.record_block_inputs(height, blkhash, found_inputs)?; - } - - if send_update { - sp_wallet.set_last_scan(blkheight); - - let wallet_str = serde_json::to_string(&sp_wallet)?; - - send_scan_result(ScanResult { - updated_wallet: wallet_str, - }); - } - } - - // time elapsed for the scan - info!( - "Blindbit scan complete in {} seconds", - start_time.elapsed().as_secs() - ); - - Ok(()) -} - -pub async fn process_block( - blkheight: u32, - tweaks: Vec, - new_utxo_filter: FilterResponse, - spent_filter: FilterResponse, - sp_client: &SpClient, - owned_outpoints: Vec, - blindbit_client: &BlindbitClient, -) -> Result<(HashMap, Vec)> { - let outs = process_block_outputs( - blkheight, - tweaks, - new_utxo_filter, - sp_client, - blindbit_client, - ) - .await?; - - let ins = process_block_inputs( - blkheight, - spent_filter, - owned_outpoints, - blindbit_client, - ) - .await?; - - Ok((outs, ins)) -} - -pub async fn process_block_outputs( - blkheight: u32, - tweaks: Vec, - new_utxo_filter: FilterResponse, - sp_client: &SpClient, - blindbit_client: &BlindbitClient, -) -> Result> { - let mut res = HashMap::new(); - - if !tweaks.is_empty() { - let secrets_map = sp_client.get_script_to_secret_map(tweaks).unwrap(); - - //last_scan = last_scan.max(n as u32); - let candidate_spks: Vec<&[u8; 34]> = secrets_map.keys().collect(); - - //get block gcs & check match - let blkfilter = BlockFilter::new(&hex::decode(new_utxo_filter.data)?); - let blkhash = new_utxo_filter.block_hash; - - let matched_outputs = check_block_outputs(blkfilter, blkhash, candidate_spks)?; - - //if match: fetch and scan utxos - if matched_outputs { - info!("matched outputs on: {}", blkheight); - let utxos = blindbit_client.utxos(blkheight).await?; - let found = scan_utxos(utxos, secrets_map, &sp_client).await?; - - if !found.is_empty() { - for (label, utxo, tweak) in found { - let outpoint = OutPoint { - txid: utxo.txid, - vout: utxo.vout, - }; - - let out = OwnedOutput { - blockheight: blkheight, - tweak: hex::encode(tweak.to_be_bytes()), - amount: Amount::from_sat(utxo.value), - script: utxo.scriptpubkey.to_hex_string(), - label: label.map(|l| l.as_string()), - spend_status: OutputSpendStatus::Unspent, - }; - - res.insert(outpoint, out); - } - } - } - } - Ok(res) -} - -pub async fn process_block_inputs( - blkheight: u32, - spent_filter: FilterResponse, - outputs: Vec, - blindbit_client: &BlindbitClient, -) -> Result> { - let mut res = vec![]; - - let blkhash = spent_filter.block_hash; - - // first get the 8-byte hashes used to construct the input filter - let input_hashes_map = get_input_hashes(blkhash, outputs)?; - - // check against filter - let blkfilter = BlockFilter::new(&hex::decode(spent_filter.data)?); - let matched_inputs = check_block_inputs( - blkfilter, - blkhash, - input_hashes_map.keys().cloned().collect(), - )?; - - // if match: download spent data, collect the outpoints that are spent - if matched_inputs { - info!("matched inputs on: {}", blkheight); - let spent = blindbit_client.spent_index(blkheight).await?.data; - - for spent in spent { - let hex: &[u8] = spent.hex.as_ref(); - - if let Some(outpoint) = input_hashes_map.get(hex) { - res.push(*outpoint) - } - } - } - Ok(res) -} - -pub async fn scan_utxos( - utxos: Vec, - secrets_map: HashMap<[u8; 34], PublicKey>, - sp_client: &SpClient, -) -> Result, UtxoResponse, Scalar)>> { - let mut res: Vec<(Option