Skip to content

Commit

Permalink
Move SpWallet from sp client library to dana
Browse files Browse the repository at this point in the history
  • Loading branch information
cygnet3 committed Sep 18, 2024
1 parent 0353f19 commit f9f432b
Show file tree
Hide file tree
Showing 10 changed files with 549 additions and 25 deletions.
2 changes: 1 addition & 1 deletion rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion rust/src/api/psbt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ use log::info;
use pushtx::Network;
use sp_client::bitcoin;
use sp_client::bitcoin::{consensus::encode::serialize_hex, OutPoint, Psbt};
use sp_client::spclient::{SpClient, SpWallet};
use sp_client::spclient::SpClient;

use crate::wallet::spwallet::SpWallet;

use super::structs::{Amount, OwnedOutput, Recipient};
use anyhow::{anyhow, Error, Result};
Expand Down
24 changes: 13 additions & 11 deletions rust/src/api/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use std::{collections::HashMap, str::FromStr};
use serde::{Deserialize, Serialize};
use sp_client::bitcoin::{self, absolute::Height, OutPoint, Txid};

use crate::wallet;

type SpendingTxId = String;
type MinedInBlock = String;

Expand Down Expand Up @@ -154,21 +156,21 @@ pub struct RecordedTransactionOutgoing {
pub change: Amount,
}

impl From<sp_client::spclient::RecordedTransaction> for RecordedTransaction {
fn from(value: sp_client::spclient::RecordedTransaction) -> Self {
impl From<wallet::recorded::RecordedTransaction> for RecordedTransaction {
fn from(value: wallet::recorded::RecordedTransaction) -> Self {
match value {
sp_client::spclient::RecordedTransaction::Incoming(incoming) => {
wallet::recorded::RecordedTransaction::Incoming(incoming) => {
Self::Incoming(incoming.into())
}

sp_client::spclient::RecordedTransaction::Outgoing(outgoing) => {
wallet::recorded::RecordedTransaction::Outgoing(outgoing) => {
Self::Outgoing(outgoing.into())
}
}
}
}

impl From<RecordedTransaction> for sp_client::spclient::RecordedTransaction {
impl From<RecordedTransaction> for wallet::recorded::RecordedTransaction {
fn from(value: RecordedTransaction) -> Self {
match value {
RecordedTransaction::Incoming(incoming) => Self::Incoming(incoming.into()),
Expand All @@ -177,8 +179,8 @@ impl From<RecordedTransaction> for sp_client::spclient::RecordedTransaction {
}
}

impl From<sp_client::spclient::RecordedTransactionIncoming> for RecordedTransactionIncoming {
fn from(value: sp_client::spclient::RecordedTransactionIncoming) -> Self {
impl From<wallet::recorded::RecordedTransactionIncoming> for RecordedTransactionIncoming {
fn from(value: wallet::recorded::RecordedTransactionIncoming) -> Self {
let confirmed_at = value.confirmed_at.map(|height| height.to_consensus_u32());

Self {
Expand All @@ -189,7 +191,7 @@ impl From<sp_client::spclient::RecordedTransactionIncoming> for RecordedTransact
}
}

impl From<RecordedTransactionIncoming> for sp_client::spclient::RecordedTransactionIncoming {
impl From<RecordedTransactionIncoming> for wallet::recorded::RecordedTransactionIncoming {
fn from(value: RecordedTransactionIncoming) -> Self {
let confirmed_at = value
.confirmed_at
Expand All @@ -203,8 +205,8 @@ impl From<RecordedTransactionIncoming> for sp_client::spclient::RecordedTransact
}
}

impl From<sp_client::spclient::RecordedTransactionOutgoing> for RecordedTransactionOutgoing {
fn from(value: sp_client::spclient::RecordedTransactionOutgoing) -> Self {
impl From<wallet::recorded::RecordedTransactionOutgoing> for RecordedTransactionOutgoing {
fn from(value: wallet::recorded::RecordedTransactionOutgoing) -> Self {
let confirmed_at = value.confirmed_at.map(|height| height.to_consensus_u32());

Self {
Expand All @@ -221,7 +223,7 @@ impl From<sp_client::spclient::RecordedTransactionOutgoing> for RecordedTransact
}
}

impl From<RecordedTransactionOutgoing> for sp_client::spclient::RecordedTransactionOutgoing {
impl From<RecordedTransactionOutgoing> for wallet::recorded::RecordedTransactionOutgoing {
fn from(value: RecordedTransactionOutgoing) -> Self {
let confirmed_at = value
.confirmed_at
Expand Down
18 changes: 11 additions & 7 deletions rust/src/api/wallet.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
use std::str::FromStr;

use crate::{
blindbit,
wallet::spwallet::{derive_keys_from_seed, SpWallet},
};
use anyhow::{Error, Result};
use reqwest::Url;
use sp_client::bitcoin::{
absolute::Height,
secp256k1::{PublicKey, SecretKey},
Network, OutPoint, Txid,
use sp_client::{
bitcoin::{
absolute::Height,
secp256k1::{PublicKey, SecretKey},
Network, OutPoint, Txid,
},
spclient::{SpClient, SpendKey},
};
use sp_client::spclient::{derive_keys_from_seed, SpClient, SpWallet, SpendKey};

use crate::blindbit;

use super::structs::{Amount, Recipient, WalletStatus};

Expand Down
11 changes: 6 additions & 5 deletions rust/src/blindbit/logic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,17 @@ use sp_client::bitcoin::{
secp256k1::{PublicKey, Scalar},
Amount, BlockHash, OutPoint, Txid, XOnlyPublicKey,
};
use sp_client::silentpayments::receiving::Label;
use sp_client::spclient::{OutputSpendStatus, OwnedOutput, SpClient};
use sp_client::{
silentpayments::receiving::Label,
spclient::{OutputList, SpWallet},
};

use crate::stream::{send_scan_progress, send_scan_result, ScanProgress};
use crate::{
blindbit::client::{BlindbitClient, UtxoResponse},
stream::ScanResult,
wallet::outputslist::OutputList,
};
use crate::{
stream::{send_scan_progress, send_scan_result, ScanProgress},
wallet::spwallet::SpWallet,
};

use super::client::FilterResponse;
Expand Down
1 change: 1 addition & 0 deletions rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ mod blindbit;
mod frb_generated; /* AUTO INJECTED BY flutter_rust_bridge. This line may not be accurate, and you can change it according to your needs. */
mod logger;
mod stream;
mod wallet;
3 changes: 3 additions & 0 deletions rust/src/wallet/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod outputslist;
pub mod recorded;
pub mod spwallet;
172 changes: 172 additions & 0 deletions rust/src/wallet/outputslist.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
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<OutPoint, OwnedOutput>,
}

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::<HashMap<OutPoint, OwnedOutput>>();
self.outputs = new_outputs;
}

pub fn to_outpoints_list(&self) -> HashMap<OutPoint, OwnedOutput> {
self.outputs.clone()
}

pub fn extend_from(&mut self, new: HashMap<OutPoint, OwnedOutput>) {
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<OutPoint, OwnedOutput> {
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)
}
}
27 changes: 27 additions & 0 deletions rust/src/wallet/recorded.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use serde::{Deserialize, Serialize};
use sp_client::{
bitcoin::{absolute::Height, Amount, OutPoint, Txid},
spclient::Recipient,
};

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub enum RecordedTransaction {
Incoming(RecordedTransactionIncoming),
Outgoing(RecordedTransactionOutgoing),
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct RecordedTransactionIncoming {
pub txid: Txid,
pub amount: Amount,
pub confirmed_at: Option<Height>,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct RecordedTransactionOutgoing {
pub txid: Txid,
pub spent_outpoints: Vec<OutPoint>,
pub recipients: Vec<Recipient>,
pub confirmed_at: Option<Height>,
pub change: Amount,
}
Loading

0 comments on commit f9f432b

Please sign in to comment.