Refactoring to update to latest common

This commit is contained in:
NicolasCantu 2025-04-08 16:03:12 +02:00 committed by Nicolas Cantu
parent 193d49caa7
commit 29961af392
4 changed files with 206 additions and 189 deletions

View File

@ -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<OutPoin
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_process_map = HashMap::new();
let new_process = processes.get(&commit_msg.process_id).unwrap().clone();
new_process_map.insert(commit_msg.process_id, new_process);
let init_msg = HandshakeMessage::new(
our_sp_address,
our_sp_address.to_string(),
OutPointMemberMap(HashMap::new()),
OutPointProcessMap(new_process_map),
);
@ -70,20 +70,20 @@ fn handle_new_process(commit_msg: &CommitMessage) -> Result<Process> {
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<MutexGuard<'static, HashMap<OutPoint, Member>>,
.lock_anyhow()
}
fn handle_member_list(roles: &Roles, process_id: OutPoint) -> Result<OutPoint> {
fn handle_member_list(commit_msg: &CommitMessage) -> Result<OutPoint> {
//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<String> = 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 {

View File

@ -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);
}

View File

@ -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<Mutex<DiskStorage>> = OnceLock::new();
const FAUCET_AMT: Amount = Amount::from_sat(10_000);
#[derive(Debug)]
struct SilentPaymentWallet {
sp_wallet: Mutex<SpWallet>,
storage: Mutex<StateFile>,
}
impl SilentPaymentWallet {
pub fn get_wallet(&self) -> Result<MutexGuard<SpWallet>> {
self.sp_wallet.lock_anyhow()
}
// This is terrible design
pub fn save(&self, unlocked_wallet: MutexGuard<SpWallet>) -> 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<SilentPaymentWallet> = OnceLock::new();
pub(crate) static WALLET: OnceLock<Mutex<SpWallet>> = OnceLock::new();
fn handle_new_tx_request(new_tx_msg: &NewTxMessage) -> Result<()> {
let tx = deserialize::<Transaction>(&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<u8>) -> Result<NewTxMessage> {
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(())

View File

@ -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<PublicKey> {
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<SpWallet>, tx: &Transaction, tweak_data: &PublicKey) -> Result<HashMap<OutPoint, OwnedOutput>> {
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<SpWallet>, 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<OutPoint, OwnedOutput>,
our_outputs: &HashMap<OutPoint, OwnedOutput>,
txdata: Vec<Transaction>,
) -> Result<Vec<OutPoint>> {
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<OutPoint, OwnedOutput> =
wallet.get_outputs().to_outpoints_list();
let owned_spks: Result<Vec<Vec<u8>>> = our_outputs
let owned_spks: Vec<Vec<u8>> = 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<OutPoint, OwnedOutput> =
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(())
}