From 29961af392a0160b4cf4bde53377dcb6c391023d Mon Sep 17 00:00:00 2001 From: NicolasCantu Date: Tue, 8 Apr 2025 16:03:12 +0200 Subject: [PATCH] Refactoring to update to latest common --- src/commit.rs | 147 ++++++++++++++++++++++++++++++++++---------------- src/faucet.rs | 95 ++++++++++++++++---------------- src/main.rs | 87 ++++++++++-------------------- src/scan.rs | 66 +++++++++++------------ 4 files changed, 206 insertions(+), 189 deletions(-) diff --git a/src/commit.rs b/src/commit.rs index 559a980..2438fee 100644 --- a/src/commit.rs +++ b/src/commit.rs @@ -6,10 +6,10 @@ use std::{ use anyhow::{Error, Result}; use bitcoincore_rpc::bitcoin::hex::DisplayHex; -use sdk_common::pcd::{Member, Roles}; +use sdk_common::{pcd::Member, silentpayments::sign_transaction, sp_client::{silentpayments::SilentPaymentAddress, RecipientAddress}}; use sdk_common::serialization::{OutPointMemberMap, OutPointProcessMap}; use sdk_common::silentpayments::create_transaction; -use sdk_common::sp_client::spclient::Recipient; +use sdk_common::sp_client::{FeeRate, Recipient}; use sdk_common::network::{AnkFlag, CommitMessage, HandshakeMessage}; use sdk_common::sp_client::bitcoin::{Amount, OutPoint}; use sdk_common::process::{lock_processes, Process, ProcessState}; @@ -41,14 +41,14 @@ pub(crate) fn handle_commit_request(commit_msg: CommitMessage) -> Result Result { let merkle_root_bin = pcd_commitment.create_merkle_tree()?.root().unwrap(); - if let Ok(pairing_process_id) = handle_member_list(&commit_msg.roles, commit_msg.process_id) { + if let Ok(pairing_process_id) = handle_member_list(&commit_msg) { dump_cached_members()?; // Send a handshake message to every connected client if let Some(new_member) = lock_members().unwrap().get(&pairing_process_id) { let our_sp_address = WALLET .get() .ok_or(Error::msg("Wallet not initialized"))? - .get_wallet()? - .get_client() + .lock_anyhow()? + .get_sp_client() .get_receiving_address(); let mut new_member_map = HashMap::new(); new_member_map.insert(pairing_process_id, new_member.clone()); let init_msg = HandshakeMessage::new( - our_sp_address, + our_sp_address.into(), OutPointMemberMap(new_member_map), OutPointProcessMap(HashMap::new()), ); @@ -124,22 +124,28 @@ pub fn lock_members() -> Result>, .lock_anyhow() } -fn handle_member_list(roles: &Roles, process_id: OutPoint) -> Result { +fn handle_member_list(commit_msg: &CommitMessage) -> Result { //Check if there is one role with one member - if roles.len() != 1 { + if commit_msg.roles.len() != 1 { return Err(Error::msg("Process is not a pairing process")); } - if let Some(pairing_role) = roles.get("pairing") { - if pairing_role.members.len() == 1 { - if let Some(member) = pairing_role.members.get(0) { - let mut memberlist = lock_members()?; - memberlist.insert( - process_id, - member.to_owned()); - return Ok(process_id); - } + if let Some(pairing_role) = commit_msg.roles.get("pairing") { + if !pairing_role.members.is_empty() { + return Err(Error::msg("Process is not a pairing process")); } + } else { + return Err(Error::msg("Process is not a pairing process")); + } + + if let Some(paired_addresses) = commit_msg.public_data.get("pairedAddresses") { + let paired_addresses: Vec = serde_json::from_value(paired_addresses.clone())?; + let mut memberlist = lock_members()?; + memberlist.insert( + commit_msg.process_id, + Member::new(paired_addresses.iter().map(|a| a.as_str().try_into().unwrap()).collect()) + ); + return Ok(commit_msg.process_id); } Err(Error::msg("Process is not a pairing process")) @@ -253,7 +259,8 @@ fn process_validation(updated_process: &mut Process, commit_msg: &CommitMessage) let state_to_validate = updated_process .get_state_for_id(&new_state_id)?; - state_to_validate.is_valid(updated_process.get_latest_commited_state())?; + let members = lock_members()?.clone(); + state_to_validate.is_valid(updated_process.get_latest_commited_state(), &OutPointMemberMap(members))?; let commited_in = commit_new_transaction(updated_process, state_to_validate.clone())?; @@ -268,26 +275,26 @@ fn commit_new_transaction( let sp_wallet = WALLET .get() .ok_or(Error::msg("Wallet not initialized"))? - .get_wallet()?; + .lock_anyhow()?; let commitment_payload = Vec::from(state_to_commit.state_id); let mut recipients = vec![]; recipients.push(Recipient { - address: sp_wallet.get_client().get_receiving_address(), + address: RecipientAddress::SpAddress(sp_wallet.get_sp_client().get_receiving_address()), amount: Amount::from_sat(1000), - nb_outputs: 1, }); + // TODO not sure if this is still used // If the process is a pairing, we add another output that directly pays the owner of the process // We can find out simply by looking at the members list if let Some(member) = lock_members()?.get(&updated_process.get_process_id().unwrap()) { // We just pick one of the devices of the member at random en pay to it, member can then share the private key between all devices // For now we take the first address + let address: SilentPaymentAddress = member.get_addresses().get(0).unwrap().as_str().try_into()?; recipients.push(Recipient { - address: member.get_addresses().iter().next().unwrap().to_string(), + address: RecipientAddress::SpAddress(address), amount: Amount::from_sat(1000), - nb_outputs: 1 }); } // This output is used to generate publicly available public keys without having to go through too many loops @@ -305,19 +312,32 @@ fn commit_new_transaction( return Err(Error::msg(format!("Missing next commitment outpoint for process {}", updated_process.get_process_id()?))); }; - let psbt = create_transaction( - vec![next_commited_in], - &freezed_utxos, - &sp_wallet, + let unspent_outputs = sp_wallet.get_unspent_outputs(); + let mut available_outpoints = vec![]; + // We push the next_commited_in at the top of the available outpoints + if let Some(output) = unspent_outputs.get(&next_commited_in) { + available_outpoints.push((next_commited_in, output.clone())); + } + + // We filter out freezed utxos + for (outpoint, output) in unspent_outputs { + if !freezed_utxos.contains(&outpoint) { + available_outpoints.push((outpoint, output)); + } + } + + let unsigned_transaction = create_transaction( + available_outpoints, + sp_wallet.get_sp_client(), recipients, Some(commitment_payload), - fee_rate, - None, + FeeRate::from_sat_per_vb(fee_rate.to_sat() as f32), )?; - let new_tx = psbt.extract_tx()?; + let final_tx = sign_transaction(sp_wallet.get_sp_client(), unsigned_transaction)?; - let txid = daemon.broadcast(&new_tx)?; + daemon.test_mempool_accept(&final_tx)?; + let txid = daemon.broadcast(&final_tx)?; let commited_in = OutPoint::new(txid, 0); freezed_utxos.insert(commited_in); @@ -337,23 +357,34 @@ mod tests { use bitcoincore_rpc::bitcoin::consensus::deserialize; use bitcoincore_rpc::bitcoin::hex::DisplayHex; use sdk_common::pcd::Member; + use sdk_common::pcd::Pcd; + use sdk_common::pcd::PcdCommitments; use sdk_common::pcd::RoleDefinition; + use sdk_common::pcd::Roles; use sdk_common::pcd::ValidationRule; use sdk_common::process::CACHEDPROCESSES; - use sdk_common::sp_client::silentpayments::utils::SilentPaymentAddress; - use serde_json::json; + use sdk_common::sp_client::silentpayments::SilentPaymentAddress; use mockall::predicate::*; use mockall::mock; + use std::collections::BTreeMap; use std::collections::HashMap; + use std::path::PathBuf; + use std::str::FromStr; use std::sync::Mutex; use bitcoincore_rpc::bitcoin::*; use crate::daemon::RpcCall; + use crate::DiskStorage; + use crate::StateFile; use std::sync::OnceLock; use sdk_common::sp_client::bitcoin::consensus::serialize; + use sdk_common::sp_client::bitcoin::hex::FromHex; use serde_json::{Map, Value}; const LOCAL_ADDRESS: &str = "sprt1qq222dhaxlzmjft2pa7qtspw2aw55vwfmtnjyllv5qrsqwm3nufxs6q7t88jf9asvd7rxhczt87de68du3jhem54xvqxy80wc6ep7lauxacsrq79v"; const INIT_TRANSACTION: &str = "02000000000102b01b832bf34cf87583c628839c5316546646dcd4939e339c1d83e693216cdfa00100000000fdffffffdd1ca865b199accd4801634488fca87e0cf81b36ee7e9bec526a8f922539b8670000000000fdffffff0200e1f505000000001600140798fac9f310cefad436ea928f0bdacf03a11be544e0f5050000000016001468a66f38e7c2c9e367577d6fad8532ae2c728ed2014043764b77de5041f80d19e3d872f205635f87486af015c00d2a3b205c694a0ae1cbc60e70b18bcd4470abbd777de63ae52600aba8f5ad1334cdaa6bcd931ab78b0140b56dd8e7ac310d6dcbc3eff37f111ced470990d911b55cd6ff84b74b579c17d0bba051ec23b738eeeedba405a626d95f6bdccb94c626db74c57792254bfc5a7c00000000"; + const TMP_WALLET: &str = "/tmp/.4nk/wallet"; + const TMP_PROCESSES: &str = "/tmp/.4nk/processes"; + const TMP_MEMBERS: &str = "/tmp/.4nk/members"; // Define the mock for Daemon with the required methods mock! { @@ -476,27 +507,51 @@ mod tests { println!("Initialized CACHEDPROCESSES"); } + + if STORAGE.get().is_none() { + let wallet_file = StateFile::new(PathBuf::from_str(TMP_WALLET).unwrap()); + let processes_file = StateFile::new(PathBuf::from_str(TMP_PROCESSES).unwrap()); + let members_file = StateFile::new(PathBuf::from_str(TMP_MEMBERS).unwrap()); + + wallet_file.create().unwrap(); + processes_file.create().unwrap(); + members_file.create().unwrap(); + + let disk_storage = DiskStorage { + wallet_file, + processes_file, + members_file + }; + STORAGE + .set(Mutex::new(disk_storage)) + .expect("STORAGE should initialize only once"); + + println!("Initialized STORAGE"); + } } fn mock_commit_msg(process_id: OutPoint) -> CommitMessage { - let field_name = "roles".to_owned(); - let member = Member::new(vec![SilentPaymentAddress::try_from(LOCAL_ADDRESS).unwrap()]).unwrap(); - let validation_rule = ValidationRule::new(1.0, vec![field_name.clone()], 1.0).unwrap(); + let field_names = ["a".to_owned(), "b".to_owned(), "pub_a".to_owned(), "roles".to_owned()]; + let pairing_id = OutPoint::from_str("b0c8378ee68e9a73836b04423ddb6de9fc0e2e715e04ffe6aa34117bb1025f01:0").unwrap(); + let member = Member::new(vec![SilentPaymentAddress::try_from(LOCAL_ADDRESS).unwrap()]); + let validation_rule = ValidationRule::new(1.0, Vec::from(field_names), 1.0).unwrap(); let role_def = RoleDefinition { - members: vec![member], + members: vec![pairing_id], validation_rules: vec![validation_rule], storages: vec![], }; - let roles = BTreeMap::from([(String::from("role_name"), role_def)]); - let pcd_commitment = json!({field_name: "b30212b9649054b71f938fbe0d1c08e72de95bdb12b8008082795c6e9c4ad26a"}); + let roles = Roles::new(BTreeMap::from([(String::from("role_name"), role_def)])); + let public_data = Pcd::new(BTreeMap::from([("pub_a".to_owned(), Value::Null)])); + let clear_state = Pcd::new(BTreeMap::from([("a".to_owned(), Value::Null), ("b".to_owned(), Value::Null)])); + let pcd_commitments = PcdCommitments::new(&process_id, &Pcd::new(public_data.clone().into_iter().chain(clear_state).collect()), &roles).unwrap(); let commit_msg = CommitMessage { process_id, - roles: roles.clone(), - public_data: BTreeMap::new(), + roles, + public_data, validation_tokens: vec![], - pcd_commitment: pcd_commitment.clone(), + pcd_commitment: pcd_commitments, error: None, }; @@ -537,7 +592,6 @@ mod tests { let new_state = ProcessState { commited_in: process_id, pcd_commitment, - encrypted_pcd: Value::Object(roles_map), ..Default::default() }; let target = vec![&empty_state, &new_state]; @@ -576,7 +630,6 @@ mod tests { let new_state = ProcessState { commited_in: process_id, pcd_commitment, - encrypted_pcd: Value::Object(roles_map), ..Default::default() }; let empty_state = ProcessState { diff --git a/src/faucet.rs b/src/faucet.rs index ddafc6a..b2d1dc4 100644 --- a/src/faucet.rs +++ b/src/faucet.rs @@ -2,6 +2,7 @@ use std::{collections::HashMap, str::FromStr}; use bitcoincore_rpc::bitcoin::secp256k1::PublicKey; use bitcoincore_rpc::json::{self as bitcoin_json}; +use sdk_common::silentpayments::sign_transaction; use sdk_common::sp_client::bitcoin::secp256k1::{ rand::thread_rng, Keypair, Message as Secp256k1Message, Secp256k1, ThirtyTwoByteHash, }; @@ -24,14 +25,13 @@ use sdk_common::{ use sdk_common::sp_client::silentpayments::sending::generate_recipient_pubkeys; use sdk_common::sp_client::silentpayments::utils::sending::calculate_partial_secret; -use sdk_common::sp_client::silentpayments::utils::SilentPaymentAddress; -use sdk_common::sp_client::spclient::Recipient; +use sdk_common::sp_client::{FeeRate, OwnedOutput, Recipient, RecipientAddress}; use anyhow::{Error, Result}; use crate::lock_freezed_utxos; use crate::scan::check_transaction_alone; -use crate::{scan::compute_partial_tweak_to_transaction, MutexExt, DAEMON, FAUCET_AMT, WALLET}; +use crate::{scan::compute_partial_tweak_to_transaction, MutexExt, DAEMON, FAUCET_AMT, WALLET, SilentPaymentAddress}; fn spend_from_core(dest: XOnlyPublicKey) -> Result<(Transaction, Amount)> { let core = DAEMON @@ -64,56 +64,51 @@ fn spend_from_core(dest: XOnlyPublicKey) -> Result<(Transaction, Amount)> { } fn faucet_send(sp_address: SilentPaymentAddress, commitment: &str) -> Result<(Transaction, PublicKey)> { - let sp_wallet = WALLET.get().ok_or(Error::msg("Wallet not initialized"))?.get_wallet()?; - // do we have a sp output available ? - let available_outpoints = sp_wallet.get_outputs().to_spendable_list(); + let sp_wallet = WALLET.get().ok_or(Error::msg("Wallet not initialized"))?.lock_anyhow()?; - let available_amt = available_outpoints + let fee_estimate = DAEMON + .get() + .ok_or(Error::msg("DAEMON not initialized"))? + .lock_anyhow()? + .estimate_fee(6) + .unwrap_or(Amount::from_sat(1000)) + .checked_div(1000) + .unwrap(); + + log::debug!("fee estimate for 6 blocks: {}", fee_estimate); + + let recipient = Recipient { + address: RecipientAddress::SpAddress(sp_address), + amount: FAUCET_AMT, + }; + + let freezed_utxos = lock_freezed_utxos()?; + + // We filter out the freezed utxos from available list + let available_outpoints: Vec<(OutPoint, OwnedOutput)> = sp_wallet.get_outputs() .iter() - .fold(Amount::from_sat(0), |acc, (_, x)| acc + x.amount); - - // If we don't have at least 4 times the amount we need to send, we take some reserves out - if available_amt > FAUCET_AMT.checked_mul(4).unwrap() { - let mut total_amt = Amount::from_sat(0); - let mut inputs = HashMap::new(); - for (outpoint, output) in available_outpoints { - total_amt += output.amount; - inputs.insert(outpoint, output); - if total_amt >= FAUCET_AMT { - break; + .filter_map(|(outpoint, output)| { + if !freezed_utxos.contains(&outpoint) { + Some((*outpoint, output.clone())) + } else { + None } - } + }) + .collect(); - let recipient = Recipient { - address: sp_address.into(), - amount: FAUCET_AMT, - nb_outputs: 1, - }; + // If we had mandatory inputs, we would make sure to put them at the top of the list + // We don't care for faucet though - let fee_estimate = DAEMON - .get() - .ok_or(Error::msg("DAEMON not initialized"))? - .lock_anyhow()? - .estimate_fee(6) - .unwrap_or(Amount::from_sat(1000)) - .checked_div(1000) - .unwrap(); - - log::debug!("fee estimate for 6 blocks: {}", fee_estimate); - - let freezed_utxos = lock_freezed_utxos()?; - - let signed_psbt = create_transaction( - vec![], - &freezed_utxos, - &sp_wallet, + // We try to pay the faucet amount + if let Ok(unsigned_transaction) = create_transaction( + available_outpoints, + sp_wallet.get_sp_client(), vec![recipient], Some(Vec::from_hex(commitment).unwrap()), - fee_estimate, - None - )?; - - let final_tx = signed_psbt.extract_tx()?; + FeeRate::from_sat_per_vb(fee_estimate.to_sat() as f32), + ) + { + let final_tx = sign_transaction(sp_wallet.get_sp_client(), unsigned_transaction)?; let partial_tweak = compute_partial_tweak_to_transaction(&final_tx)?; @@ -121,13 +116,15 @@ fn faucet_send(sp_address: SilentPaymentAddress, commitment: &str) -> Result<(Tr .get() .ok_or(Error::msg("DAEMON not initialized"))? .lock_anyhow()?; + // First check that mempool accept it + daemon.test_mempool_accept(&final_tx)?; let txid = daemon.broadcast(&final_tx)?; log::debug!("Sent tx {}", txid); // We immediately add the new tx to our wallet to prevent accidental double spend check_transaction_alone(sp_wallet, &final_tx, &partial_tweak)?; - Ok((final_tx, partial_tweak)) + Ok((final_tx, partial_tweak)) } else { // let's try to spend directly from the mining address let secp = Secp256k1::signing_only(); @@ -170,7 +167,7 @@ fn faucet_send(sp_address: SilentPaymentAddress, commitment: &str) -> Result<(Tr .get(0) .expect("Failed to generate keys") .to_owned(); - let change_sp_address = sp_wallet.get_client().get_receiving_address(); + let change_sp_address = sp_wallet.get_sp_client().get_receiving_address(); let change_output_key: XOnlyPublicKey = generate_recipient_pubkeys(vec![change_sp_address], partial_secret)? .into_values() @@ -240,7 +237,9 @@ fn faucet_send(sp_address: SilentPaymentAddress, commitment: &str) -> Result<(Tr .get() .ok_or(Error::msg("DAEMON not initialized"))? .lock_anyhow()?; + // We don't worry about core_tx being refused by core daemon.broadcast(&core_tx)?; + daemon.test_mempool_accept(&faucet_tx)?; let txid = daemon.broadcast(&faucet_tx)?; log::debug!("Sent tx {}", txid); } diff --git a/src/main.rs b/src/main.rs index d7068c4..32f0f0d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,28 +10,27 @@ use std::{ sync::{Mutex, MutexGuard, OnceLock}, }; -use bitcoincore_rpc::json::{self as bitcoin_json}; +use bitcoincore_rpc::{bitcoin::secp256k1::SecretKey, json::{self as bitcoin_json}}; use commit::{lock_members, MEMBERLIST}; use futures_util::{future, pin_mut, stream::TryStreamExt, FutureExt, StreamExt}; use log::{debug, error, warn}; use message::{broadcast_message, process_message, BroadcastType, MessageCache, MESSAGECACHE}; use scan::{check_transaction_alone, compute_partial_tweak_to_transaction}; -use sdk_common::{network::HandshakeMessage, pcd::Member, process::{lock_processes, Process, CACHEDPROCESSES}, serialization::{OutPointMemberMap, OutPointProcessMap}, sp_client::{bitcoin::{ +use sdk_common::{network::HandshakeMessage, pcd::Member, process::{lock_processes, Process, CACHEDPROCESSES}, serialization::{OutPointMemberMap, OutPointProcessMap}, silentpayments::SpWallet, sp_client::{bitcoin::{ consensus::deserialize, hex::{DisplayHex, FromHex}, Amount, Network, Transaction, -}, silentpayments::utils::SilentPaymentAddress}, MutexExt}; +}, silentpayments::SilentPaymentAddress, OwnedOutput}, MutexExt}; use sdk_common::sp_client::{ bitcoin::OutPoint, bitcoin::secp256k1::rand::{thread_rng, Rng}, - spclient::SpWallet, + SpClient, SpendKey }; use sdk_common::{ error::AnkError, network::{AnkFlag, NewTxMessage}, }; -use sdk_common::sp_client::spclient::{derive_keys_from_seed, SpClient, SpendKey}; use serde_json::Value; use tokio::net::{TcpListener, TcpStream}; use tokio::sync::mpsc::{unbounded_channel, UnboundedSender}; @@ -127,7 +126,7 @@ impl StateFile { #[derive(Debug)] pub struct DiskStorage { - // wallet_file: StateFile, + pub wallet_file: StateFile, pub processes_file: StateFile, pub members_file: StateFile, } @@ -136,30 +135,14 @@ pub static STORAGE: OnceLock> = OnceLock::new(); const FAUCET_AMT: Amount = Amount::from_sat(10_000); -#[derive(Debug)] -struct SilentPaymentWallet { - sp_wallet: Mutex, - storage: Mutex, -} - -impl SilentPaymentWallet { - pub fn get_wallet(&self) -> Result> { - self.sp_wallet.lock_anyhow() - } - - // This is terrible design - pub fn save(&self, unlocked_wallet: MutexGuard) -> Result<()> { - let value = serde_json::from_str(&serde_json::to_string(&unlocked_wallet.clone())?)?; - self.storage.lock_anyhow()?.save(&value) - } -} - -pub(crate) static WALLET: OnceLock = OnceLock::new(); +pub(crate) static WALLET: OnceLock> = OnceLock::new(); fn handle_new_tx_request(new_tx_msg: &NewTxMessage) -> Result<()> { let tx = deserialize::(&Vec::from_hex(&new_tx_msg.transaction)?)?; - // we try to broadcast it - DAEMON.get().unwrap().lock_anyhow()?.broadcast(&tx).map_err(|e| AnkError::NewTxError(e.to_string()))?; + + let daemon = DAEMON.get().unwrap().lock_anyhow()?; + daemon.test_mempool_accept(&tx)?; + daemon.broadcast(&tx)?; Ok(()) } @@ -247,8 +230,8 @@ fn create_new_tx_message(transaction: Vec) -> Result { let partial_tweak = compute_partial_tweak_to_transaction(&tx)?; - let sp_wallet = WALLET.get().ok_or(Error::msg("Wallet not initialized"))?; - check_transaction_alone(sp_wallet.get_wallet()?, &tx, &partial_tweak)?; + let sp_wallet = WALLET.get().ok_or_else(|| Error::msg("Wallet not initialized"))?.lock_anyhow()?; + check_transaction_alone(sp_wallet, &tx, &partial_tweak)?; Ok(NewTxMessage::new( transaction.to_lower_hex_string(), @@ -368,35 +351,26 @@ async fn main() -> Result<()> { // Create a new wallet file if it doesn't exist or fails to load wallet_file.create()?; - // Generate a seed and derive keys - let mut seed = [0u8; 64]; - thread_rng().fill(&mut seed); - - let (scan_sk, spend_sk) = derive_keys_from_seed(&seed, config.network) - .expect("Couldn't generate a new sp_wallet"); + let mut rng = thread_rng(); let new_client = SpClient::new( - config.wallet_name.clone(), - scan_sk, - SpendKey::Secret(spend_sk), - None, + SecretKey::new(&mut rng), + SpendKey::Secret(SecretKey::new(&mut rng)), config.network, ) .expect("Failed to create a new SpClient"); - // Initialize a new wallet with the generated client - let mut wallet = SpWallet::new(new_client, None, vec![])?; + let mut sp_wallet = SpWallet::new(new_client); // Set birthday and update scan information - let outputs = wallet.get_mut_outputs(); - outputs.set_birthday(current_tip); - outputs.update_last_scan(current_tip); + sp_wallet.set_birthday(current_tip); + sp_wallet.set_last_scan(current_tip); // Save the newly created wallet to disk - let json = serde_json::to_value(wallet.clone())?; + let json = serde_json::to_value(sp_wallet.clone())?; wallet_file.save(&json)?; - wallet + sp_wallet } }; @@ -437,31 +411,25 @@ async fn main() -> Result<()> { *freezed_utxos = utxo_to_freeze; } - let our_sp_address = sp_wallet.get_client().get_receiving_address(); + let our_sp_address = sp_wallet.get_sp_client().get_receiving_address(); log::info!( - "Using wallet {} with address {}", - sp_wallet.get_client().label, + "Using wallet with address {}", our_sp_address, ); log::info!( "Found {} outputs for a total balance of {}", - sp_wallet.get_outputs().to_spendable_list().len(), - sp_wallet.get_outputs().get_balance() + sp_wallet.get_outputs().len(), + sp_wallet.get_balance() ); - let last_scan = sp_wallet.get_outputs().get_last_scan(); + let last_scan = sp_wallet.get_last_scan(); WALLET - .set(SilentPaymentWallet { - sp_wallet: Mutex::new(sp_wallet), - storage: Mutex::new(wallet_file), - }) + .set(Mutex::new(sp_wallet)) .expect("Failed to initialize WALLET"); - WALLET.get().unwrap().save(WALLET.get().unwrap().get_wallet()?).unwrap(); - CACHEDPROCESSES .set(Mutex::new(cached_processes)) .expect("Failed to initialize CACHEDPROCESSES"); @@ -471,6 +439,7 @@ async fn main() -> Result<()> { .expect("Failed to initialize MEMBERLIST"); let storage = DiskStorage { + wallet_file, processes_file, members_file, }; @@ -495,7 +464,7 @@ async fn main() -> Result<()> { // Let's spawn the handling of each connection in a separate task. while let Ok((stream, addr)) = listener.accept().await { - tokio::spawn(handle_connection(stream, addr, our_sp_address.as_str().try_into().unwrap())); + tokio::spawn(handle_connection(stream, addr, our_sp_address)); } Ok(()) diff --git a/src/scan.rs b/src/scan.rs index 12e889c..3eb2d42 100644 --- a/src/scan.rs +++ b/src/scan.rs @@ -3,8 +3,9 @@ use std::str::FromStr; use std::sync::MutexGuard; use anyhow::{Error, Result}; +use bitcoincore_rpc::bitcoin::absolute::Height; use electrum_client::ElectrumApi; -use hex::FromHex; +use sdk_common::silentpayments::SpWallet; use sdk_common::sp_client::bitcoin::bip158::BlockFilter; use sdk_common::sp_client::bitcoin::hex::DisplayHex; use sdk_common::sp_client::bitcoin::secp256k1::{All, PublicKey, Scalar, Secp256k1, SecretKey}; @@ -13,10 +14,10 @@ use sdk_common::sp_client::silentpayments::receiving::Receiver; use sdk_common::sp_client::silentpayments::utils::receiving::{ calculate_tweak_data, get_pubkey_from_input, }; -use sdk_common::sp_client::spclient::{OutputSpendStatus, OwnedOutput, SpWallet}; +use sdk_common::sp_client::{OutputSpendStatus, OwnedOutput}; use tokio::time::Instant; -use crate::{electrumclient, MutexExt, DAEMON, WALLET}; +use crate::{electrumclient, MutexExt, DAEMON, STORAGE, WALLET}; pub fn compute_partial_tweak_to_transaction(tx: &Transaction) -> Result { let daemon = DAEMON.get().ok_or(Error::msg("DAEMON not initialized"))?; @@ -85,7 +86,7 @@ fn get_script_to_secret_map( } pub fn check_transaction_alone(mut wallet: MutexGuard, tx: &Transaction, tweak_data: &PublicKey) -> Result> { - let updates = match wallet.update_wallet_with_transaction(tx, 0, *tweak_data) { + let updates = match wallet.update_with_transaction(tx, tweak_data, 0) { Ok(updates) => updates, Err(e) => { log::debug!("Error while checking transaction: {}", e); @@ -94,7 +95,8 @@ pub fn check_transaction_alone(mut wallet: MutexGuard, tx: &Transactio }; if updates.len() > 0 { - WALLET.get().unwrap().save(wallet)?; + let storage = STORAGE.get().ok_or_else(|| Error::msg("Failed to get STORAGE"))?; + storage.lock_anyhow()?.wallet_file.save(&serde_json::to_value(wallet.clone())?)?; } Ok(updates) @@ -167,6 +169,7 @@ fn scan_block_outputs( .collect(); let ours = sp_receiver.scan_transaction(&secret.unwrap(), xonlykeys?)?; + let height = Height::from_consensus(blkheight as u32)?; for (label, map) in ours { res.extend(p2tr_outs.iter().filter_map(|(i, o)| { match XOnlyPublicKey::from_slice(&o.script_pubkey.as_bytes()[2..]) { @@ -188,10 +191,10 @@ fn scan_block_outputs( return Some(( outpoint, OwnedOutput { - blockheight: blkheight as u32, - tweak: hex::encode(tweak.secret_bytes()), + blockheight: height, + tweak: tweak.secret_bytes(), amount: o.value, - script: hex::encode(o.script_pubkey.as_bytes()), + script: o.script_pubkey.clone(), label: label_str, spend_status: OutputSpendStatus::Unspent, }, @@ -213,7 +216,7 @@ fn scan_block_outputs( } fn scan_block_inputs( - our_outputs: HashMap, + our_outputs: &HashMap, txdata: Vec, ) -> Result> { let mut found = vec![]; @@ -234,8 +237,7 @@ pub fn scan_blocks(mut n_blocks_to_scan: u32, electrum_url: &str) -> anyhow::Res log::info!("Starting a rescan"); let electrum_client = electrumclient::create_electrum_client(electrum_url)?; - let sp_wallet = WALLET.get().ok_or(Error::msg("Wallet not initialized"))?; - let mut wallet = sp_wallet.get_wallet()?; + let mut sp_wallet = WALLET.get().ok_or(Error::msg("Wallet not initialized"))?.lock_anyhow()?; let core = DAEMON .get() @@ -243,7 +245,7 @@ pub fn scan_blocks(mut n_blocks_to_scan: u32, electrum_url: &str) -> anyhow::Res .lock_anyhow()?; let secp = Secp256k1::new(); - let scan_height = wallet.get_outputs().get_last_scan(); + let scan_height = sp_wallet.get_last_scan(); let tip_height: u32 = core.get_current_height()?.try_into()?; // 0 means scan to tip @@ -270,15 +272,14 @@ pub fn scan_blocks(mut n_blocks_to_scan: u32, electrum_url: &str) -> anyhow::Res let mut tweak_data_map = electrum_client.sp_tweaks(start as usize)?; - let scan_sk = wallet.get_client().get_scan_key(); + let scan_sk = sp_wallet.get_sp_client().get_scan_key(); - let sp_receiver = wallet.get_client().sp_receiver.clone(); let start_time = Instant::now(); for (blkheight, blkhash, blkfilter) in filters { - let spk2secret = match tweak_data_map.remove(&(&blkheight)) { + let spk2secret = match tweak_data_map.remove(&blkheight) { Some(tweak_data_vec) => { - get_script_to_secret_map(&sp_receiver, tweak_data_vec, scan_sk.into(), &secp)? + get_script_to_secret_map(&sp_wallet.get_sp_client().sp_receiver, tweak_data_vec, scan_sk.into(), &secp)? } None => HashMap::new(), }; @@ -287,42 +288,38 @@ pub fn scan_blocks(mut n_blocks_to_scan: u32, electrum_url: &str) -> anyhow::Res let candidate_spks: Vec<&[u8; 34]> = spk2secret.keys().collect(); // check if owned inputs are spent - let our_outputs: HashMap = - wallet.get_outputs().to_outpoints_list(); - - let owned_spks: Result>> = our_outputs + let owned_spks: Vec> = sp_wallet.get_outputs() .iter() .map(|(_, output)| { - let script = Vec::from_hex(&output.script).map_err(|e| Error::new(e)); + let script = output.script.to_bytes(); script }) .collect(); - let matched = check_block(blkfilter, blkhash, candidate_spks, owned_spks?)?; + let matched = check_block(blkfilter, blkhash, candidate_spks, owned_spks)?; if matched { let blk = core.get_block(blkhash)?; // scan block for new outputs, and add them to our list let utxo_created_in_block = - scan_block_outputs(&sp_receiver, &blk.txdata, blkheight.into(), spk2secret)?; + scan_block_outputs(&sp_wallet.get_sp_client().sp_receiver, &blk.txdata, blkheight.into(), spk2secret)?; if !utxo_created_in_block.is_empty() { - wallet + sp_wallet .get_mut_outputs() - .extend_from(utxo_created_in_block); + .extend(utxo_created_in_block); } // update the list of outputs just in case // utxos may be created and destroyed in the same block - let updated_outputs: HashMap = - wallet.get_outputs().to_outpoints_list(); - // search inputs and mark as mined - let utxo_destroyed_in_block = scan_block_inputs(updated_outputs, blk.txdata)?; + let utxo_destroyed_in_block = scan_block_inputs(sp_wallet.get_outputs(), blk.txdata)?; if !utxo_destroyed_in_block.is_empty() { - let outputs = wallet.get_mut_outputs(); + let outputs = sp_wallet.get_mut_outputs(); for outpoint in utxo_destroyed_in_block { - outputs.mark_mined(outpoint, blkhash)?; + if let Some(output) = outputs.get_mut(&outpoint) { + output.spend_status = OutputSpendStatus::Mined(blkhash.to_string()); + } } } } @@ -335,10 +332,9 @@ pub fn scan_blocks(mut n_blocks_to_scan: u32, electrum_url: &str) -> anyhow::Res ); // update last_scan height - wallet - .get_mut_outputs() - .update_last_scan(end); - WALLET.get().unwrap().save(wallet)?; + sp_wallet + .set_last_scan(end); + STORAGE.get().unwrap().lock_anyhow()?.wallet_file.save(&serde_json::to_value(sp_wallet.clone())?)?; Ok(()) }