mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-04-28 03:00:18 -04:00
Implement custom script subscribe for btc
This commit is contained in:
90
Cargo.lock
generated
90
Cargo.lock
generated
@@ -420,6 +420,46 @@ version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "base64-compat"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a8d4d2746f89841e49230dd26917df1876050f95abafafbe34f47cb534b88d7"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bdk"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecf7e997526ceefbab7dd99fc0da6834ed8853bd051f53523415ed1dc82b870d"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bdk-macros",
|
||||
"bitcoin",
|
||||
"electrum-client",
|
||||
"js-sys",
|
||||
"log",
|
||||
"miniscript",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sled",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bdk-macros"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81c1980e50ae23bb6efa9283ae8679d6ea2c6fa6a99fe62533f65f4a25a1a56c"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.30",
|
||||
"quote 1.0.10",
|
||||
"syn 1.0.80",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bech32"
|
||||
version = "0.8.1"
|
||||
@@ -486,6 +526,7 @@ version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a41df6ad9642c5c15ae312dd3d074de38fd3eb7cc87ad4ce10f90292a83fe4d"
|
||||
dependencies = [
|
||||
"base64-compat",
|
||||
"bech32",
|
||||
"bitcoin_hashes",
|
||||
"secp256k1",
|
||||
@@ -1255,16 +1296,17 @@ dependencies = [
|
||||
name = "darkfi"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-channel",
|
||||
"async-executor",
|
||||
"async-native-tls",
|
||||
"async-std",
|
||||
"async-trait",
|
||||
"async-tungstenite",
|
||||
"bdk",
|
||||
"bellman",
|
||||
"bimap",
|
||||
"bitcoin",
|
||||
"bitcoin_hashes",
|
||||
"bitvec 0.18.5",
|
||||
"blake2b_simd",
|
||||
"blake2s_simd",
|
||||
@@ -1275,7 +1317,6 @@ dependencies = [
|
||||
"crypto_api_chachapoly",
|
||||
"dirs 4.0.0",
|
||||
"easy-parallel",
|
||||
"electrum-client",
|
||||
"ff",
|
||||
"futures 0.3.17",
|
||||
"group",
|
||||
@@ -1715,6 +1756,16 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
@@ -1852,6 +1903,15 @@ dependencies = [
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fxhash"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.12.4"
|
||||
@@ -2507,6 +2567,16 @@ version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
|
||||
|
||||
[[package]]
|
||||
name = "miniscript"
|
||||
version = "6.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d69450033bf162edf854d4aacaff82ca5ef34fa81f6cf69e1c81a103f0834997"
|
||||
dependencies = [
|
||||
"bitcoin",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.4.4"
|
||||
@@ -3767,6 +3837,22 @@ version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
|
||||
|
||||
[[package]]
|
||||
name = "sled"
|
||||
version = "0.34.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"crossbeam-epoch 0.9.5",
|
||||
"crossbeam-utils 0.8.5",
|
||||
"fs2",
|
||||
"fxhash",
|
||||
"libc",
|
||||
"log",
|
||||
"parking_lot 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.7.0"
|
||||
|
||||
@@ -70,16 +70,16 @@ spl-token = {version = "3.2.0", features = ["no-entrypoint"], optional = true}
|
||||
spl-associated-token-account = {version = "1.0.3", features = ["no-entrypoint"], optional = true}
|
||||
|
||||
## Cashier Bitcoin Dependencies
|
||||
bitcoin = {version = "0.27.0", optional = true}
|
||||
bitcoin_hashes = "0.10.0"
|
||||
anyhow = "1.0.44"
|
||||
bdk = {version = "0.12.0", optional = true}
|
||||
bitcoin = {version = "0.27.0", optional = true }
|
||||
secp256k1 = {version = "0.20.3", default-features = false, features = ["rand-std"], optional = true}
|
||||
electrum-client = {version = "0.8.0", optional = true}
|
||||
|
||||
## Cashier Ethereum Dependencies
|
||||
hash-db = {version = "0.15.2", optional = true}
|
||||
keccak-hasher = {version = "0.15.3", optional = true}
|
||||
|
||||
[features]
|
||||
btc = ["bitcoin", "secp256k1", "electrum-client"]
|
||||
btc = ["bdk", "bitcoin", "secp256k1"]
|
||||
sol = ["solana-sdk", "solana-client", "spl-token", "spl-associated-token-account"]
|
||||
eth = ["keccak-hasher", "hash-db"]
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
use async_std::sync::Arc;
|
||||
use std::convert::From;
|
||||
use async_std::sync::{Arc, Mutex};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::convert::{From, TryFrom, TryInto};
|
||||
use std::cmp::max;
|
||||
use std::fmt;
|
||||
use std::ops::Add;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use anyhow::Context;
|
||||
use async_executor::Executor;
|
||||
use async_trait::async_trait;
|
||||
|
||||
@@ -11,12 +16,18 @@ use bitcoin::blockdata::{
|
||||
transaction::{OutPoint, SigHashType, Transaction, TxIn, TxOut},
|
||||
};
|
||||
use bitcoin::consensus::encode::serialize_hex;
|
||||
use bitcoin::hash_types::PubkeyHash as BtcPubKeyHash;
|
||||
use bitcoin::hash_types::{PubkeyHash as BtcPubKeyHash, Txid};
|
||||
use bitcoin::network::constants::Network;
|
||||
use bitcoin::util::address::Address;
|
||||
use bitcoin::util::ecdsa::{PrivateKey as BtcPrivKey, PublicKey as BtcPubKey};
|
||||
use bitcoin::util::psbt::serialize::Serialize;
|
||||
use electrum_client::{Client as ElectrumClient, ElectrumApi, GetBalanceRes};
|
||||
use bdk::electrum_client::{
|
||||
Client as ElectrumClient,
|
||||
ElectrumApi,
|
||||
GetBalanceRes,
|
||||
GetHistoryRes,
|
||||
HeaderNotification
|
||||
};
|
||||
use log::*;
|
||||
use secp256k1::{
|
||||
constants::{PUBLIC_KEY_SIZE, SECRET_KEY_SIZE},
|
||||
@@ -37,6 +48,40 @@ pub type PrivKey = BtcPrivKey;
|
||||
|
||||
const KEYPAIR_LENGTH: usize = SECRET_KEY_SIZE + PUBLIC_KEY_SIZE;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
|
||||
pub struct BlockHeight(u32);
|
||||
|
||||
impl From<BlockHeight> for u32 {
|
||||
fn from(height: BlockHeight) -> Self {
|
||||
height.0
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<HeaderNotification> for BlockHeight {
|
||||
type Error = BtcFailed;
|
||||
fn try_from(value: HeaderNotification) -> BtcResult<Self> {
|
||||
Ok(Self(
|
||||
value
|
||||
.height
|
||||
.try_into()
|
||||
.context("Failed to fit usize into u32")?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<u32> for BlockHeight {
|
||||
type Output = BlockHeight;
|
||||
fn add(self, rhs: u32) -> Self::Output {
|
||||
BlockHeight(self.0 + rhs)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum ExpiredTimelocks {
|
||||
None,
|
||||
Cancel,
|
||||
Punish,
|
||||
}
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Keypair {
|
||||
secret: SecretKey,
|
||||
@@ -155,17 +200,163 @@ impl Account {
|
||||
Script::new_p2pkh(&btc_pubkey_hash)
|
||||
}
|
||||
}
|
||||
fn print_status_change(txid: Txid, old: Option<ScriptStatus>, new: ScriptStatus) -> ScriptStatus {
|
||||
match (old, new) {
|
||||
(None, new_status) => {
|
||||
debug!(target: "BTC BRIDGE", "Found relevant Bitcoin transaction: {:?} {:?}", txid, new_status);
|
||||
}
|
||||
(Some(old_status), new_status) if old_status != new_status => {
|
||||
debug!(target: "BTC BRIDGE", "Bitcoin transaction status changed: {:?} {} {}", txid, new_status, old_status);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
new
|
||||
}
|
||||
|
||||
fn sync_interval(avg_block_time: Duration) -> Duration {
|
||||
max(avg_block_time / 10, Duration::from_secs(1))
|
||||
}
|
||||
pub struct Client {
|
||||
electrum: ElectrumClient,
|
||||
subscriptions: Arc<Mutex<Vec<Script>>>,
|
||||
latest_block_height: BlockHeight,
|
||||
last_sync: Instant,
|
||||
sync_interval: Duration,
|
||||
script_history: BTreeMap<Script, Vec<GetHistoryRes>>,
|
||||
|
||||
}
|
||||
impl Client {
|
||||
pub fn new(electrum_url: &str) -> BtcResult<Self> {
|
||||
|
||||
let config = bdk::electrum_client::ConfigBuilder::default()
|
||||
.retry(5)
|
||||
.build();
|
||||
let client = ElectrumClient::from_config(electrum_url, config)?;
|
||||
|
||||
let electrum = ElectrumClient::new(electrum_url)
|
||||
.map_err(|err| crate::Error::from(super::BtcFailed::from(err)))?;
|
||||
|
||||
let latest_block = electrum
|
||||
.block_headers_subscribe()?;
|
||||
|
||||
//testnet avg block time
|
||||
let interval = sync_interval(Duration::from_secs(300));
|
||||
|
||||
Ok(Self {
|
||||
electrum: electrum,
|
||||
subscriptions: Arc::new(Mutex::new(Vec::new())),
|
||||
latest_block_height: BlockHeight::try_from(latest_block)
|
||||
.map_err(|_| crate::Error::TryFromError)?,
|
||||
last_sync: Instant::now(),
|
||||
sync_interval: interval,
|
||||
script_history: Default::default(),
|
||||
})
|
||||
}
|
||||
fn update_state(&mut self) -> Result<()> {
|
||||
let now = Instant::now();
|
||||
if now < self.last_sync + self.sync_interval {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.last_sync = now;
|
||||
self.update_latest_block()?;
|
||||
self.update_script_histories()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn update_latest_block(&mut self) -> BtcResult<()> {
|
||||
let latest_block = self
|
||||
.electrum
|
||||
.block_headers_subscribe()?;
|
||||
let latest_block_height = BlockHeight::try_from(latest_block)
|
||||
.map_err(|err| crate::Error::from(super::BtcFailed::from(err)))?;
|
||||
|
||||
if latest_block_height > self.latest_block_height {
|
||||
// debug!( target: "BTC BRIDGE", "{} {}"
|
||||
// u32::from(latest_block_height),
|
||||
// "Got notification for new block"
|
||||
// );
|
||||
self.latest_block_height = latest_block_height;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_script_histories(&mut self) -> BtcResult<()> {
|
||||
let histories = self
|
||||
.electrum
|
||||
.batch_script_get_history(self.script_history.keys())?;
|
||||
|
||||
if histories.len() != self.script_history.len() {
|
||||
debug!(
|
||||
"Expected {} history entries, received {}",
|
||||
self.script_history.len(),
|
||||
histories.len()
|
||||
);
|
||||
}
|
||||
|
||||
let scripts = self.script_history.keys().cloned();
|
||||
let histories = histories.into_iter();
|
||||
|
||||
self.script_history = scripts.zip(histories).collect::<BTreeMap<_, _>>();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn status_of_script<T>(&mut self, tx: &T) -> BtcResult<ScriptStatus>
|
||||
where
|
||||
T: Watchable,
|
||||
{
|
||||
let txid = tx.id();
|
||||
let script = tx.script();
|
||||
|
||||
|
||||
if !self.script_history.contains_key(&script) {
|
||||
self.script_history.insert(script.clone(), vec![]);
|
||||
}
|
||||
|
||||
self.update_state()?;
|
||||
|
||||
let history = self.script_history.entry(script).or_default();
|
||||
|
||||
let history_of_tx = history
|
||||
.iter()
|
||||
.filter(|entry| entry.tx_hash == txid)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
match history_of_tx.as_slice() {
|
||||
[] => Ok(ScriptStatus::Unseen),
|
||||
[remaining @ .., last] => {
|
||||
if !remaining.is_empty() {
|
||||
debug!("Found more than a single history entry for script. This is highly unexpected and those history entries will be ignored")
|
||||
}
|
||||
|
||||
if last.height <= 0 {
|
||||
Ok(ScriptStatus::InMempool)
|
||||
} else {
|
||||
Ok(ScriptStatus::Confirmed(
|
||||
Confirmed::from_inclusion_and_latest_block(
|
||||
u32::try_from(last.height)
|
||||
.map_err(|_| crate::Error::TryFromError)?,
|
||||
u32::from(self.latest_block_height),
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
pub struct BtcClient {
|
||||
main_account: Account,
|
||||
client: Arc<Mutex<Client>>,
|
||||
notify_channel: (
|
||||
async_channel::Sender<TokenNotification>,
|
||||
async_channel::Receiver<TokenNotification>,
|
||||
),
|
||||
client: Arc<ElectrumClient>,
|
||||
network: Network,
|
||||
}
|
||||
|
||||
impl BtcClient {
|
||||
pub async fn new(main_keypair: Keypair, network: &str) -> Result<Arc<Self>> {
|
||||
//TODO
|
||||
@@ -181,19 +372,18 @@ impl BtcClient {
|
||||
|
||||
let main_account = Account::new(&main_keypair, network);
|
||||
|
||||
let electrum_client = ElectrumClient::new(url)
|
||||
.map_err(|err| crate::Error::from(super::BtcFailed::from(err)))?;
|
||||
|
||||
Ok(Arc::new(Self {
|
||||
main_account,
|
||||
client: Arc::new(Mutex::new(Client::new(url)?)),
|
||||
notify_channel,
|
||||
client: Arc::new(electrum_client),
|
||||
network,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
async fn handle_subscribe_request(
|
||||
self: Arc<Self>,
|
||||
tx: impl Watchable + Send + 'static,
|
||||
btc_keys: Account,
|
||||
drk_pub_key: jubjub::SubgroupPoint,
|
||||
) -> BtcResult<()> {
|
||||
@@ -201,51 +391,54 @@ impl BtcClient {
|
||||
target: "BTC BRIDGE",
|
||||
"Handle subscribe request"
|
||||
);
|
||||
let client = &self.client;
|
||||
|
||||
let keys_clone = btc_keys.clone();
|
||||
// p2pkh script
|
||||
let script = keys_clone.script_pubkey;
|
||||
let client = self.client.clone();
|
||||
let electrum = &client.lock().await.electrum;
|
||||
let script = tx.script();
|
||||
let txid = tx.id();
|
||||
|
||||
// Check if we're already subscribed
|
||||
if client.lock().await
|
||||
.subscriptions.lock().await.contains(&script) {
|
||||
return Ok(());
|
||||
}
|
||||
//Fetch any current balance
|
||||
let prev_balance = client.script_get_balance(&script)?;
|
||||
let prev_balance = electrum.script_get_balance(&script)?;
|
||||
|
||||
let cur_balance: GetBalanceRes;
|
||||
//let status = client.script_subscribe(&script)?;
|
||||
let mut last_status = None;
|
||||
|
||||
|
||||
let status = client.script_subscribe(&script)?;
|
||||
|
||||
loop {
|
||||
let current_status = client.script_pop(&script)?;
|
||||
debug!(target: "BTC BRIDGE", "script status: {:?}", status);
|
||||
debug!(target: "BTC BRIDGE", "current_script status: {:?}", current_status);
|
||||
if current_status == status {
|
||||
async_std::task::sleep(Duration::from_secs(5)).await;
|
||||
debug!(
|
||||
target: "BTC BRIDGE",
|
||||
"ScriptPubKey status has not changed, amtucfd: {}, amtcfd: {}",
|
||||
client.script_get_balance(&script)?.unconfirmed,
|
||||
client.script_get_balance(&script)?.confirmed
|
||||
);
|
||||
continue;
|
||||
}
|
||||
async_std::task::sleep(Duration::from_secs(5)).await;
|
||||
//let current_status = client.script_pop(&script)?;
|
||||
|
||||
match current_status {
|
||||
Some(_) => {
|
||||
// Script has a notification update
|
||||
debug!(target: "BTC BRIDGE", "ScripPubKey notify update");
|
||||
//TODO: unsubscribe is never successful
|
||||
//let _ = client.script_unsubscribe(&script)?;
|
||||
break;
|
||||
}
|
||||
None => {
|
||||
return Err(BtcFailed::ElectrumError(
|
||||
"ScriptPubKey was not found".to_string(),
|
||||
));
|
||||
let new_status = match client.lock().await.status_of_script(&tx) {
|
||||
Ok(new_status) => new_status,
|
||||
Err(error) => {
|
||||
debug!(target: "BTC BRIDGE", "Failed to get status of script: {:#}", error);
|
||||
return Err(BtcFailed::BtcError("Failed to get status of script".to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
last_status = Some(print_status_change(txid, last_status, new_status));
|
||||
|
||||
match new_status {
|
||||
ScriptStatus::Unseen => continue,
|
||||
ScriptStatus::InMempool => continue,
|
||||
ScriptStatus::Confirmed(inner) => {
|
||||
let confirmations = inner.confirmations();
|
||||
debug!(target: "BTC BRIDGE", "Received confirmed tx: {:#}", confirmations);
|
||||
if confirmations > 0 {
|
||||
break
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
} // Endloop
|
||||
|
||||
cur_balance = client.script_get_balance(&script)?;
|
||||
cur_balance = electrum.script_get_balance(&script)?;
|
||||
|
||||
let send_notification = self.notify_channel.0.clone();
|
||||
|
||||
@@ -271,17 +464,18 @@ impl BtcClient {
|
||||
.map_err(Error::from)?;
|
||||
|
||||
debug!(target: "BTC BRIDGE", "Received {} btc", ui_amnt);
|
||||
let _ = self.send_btc_to_main_wallet(amnt as u64, btc_keys)?;
|
||||
let _ = self.send_btc_to_main_wallet(amnt as u64, btc_keys).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_btc_to_main_wallet(self: Arc<Self>, amount: u64, btc_keys: Account) -> BtcResult<()> {
|
||||
async fn send_btc_to_main_wallet(self: Arc<Self>, amount: u64, btc_keys: Account) -> BtcResult<()> {
|
||||
debug!(target: "BTC BRIDGE", "Sending {} BTC to main wallet", amount);
|
||||
let client = &self.client;
|
||||
let client = self.client.lock().await;
|
||||
let electrum = &client.electrum;
|
||||
let keys_clone = btc_keys.clone();
|
||||
let script = keys_clone.script_pubkey;
|
||||
let utxo = client.script_list_unspent(&script)?;
|
||||
let utxo = electrum.script_list_unspent(&script)?;
|
||||
|
||||
let mut inputs = Vec::new();
|
||||
let mut amounts: u64 = 0;
|
||||
@@ -311,13 +505,10 @@ impl BtcClient {
|
||||
version: 2,
|
||||
};
|
||||
|
||||
//TODO: Better handling of fees, don't cast to u64
|
||||
let tx_size = transaction.get_size();
|
||||
//Estimate fee for getting in next block
|
||||
|
||||
let fee_per_kb = client.estimate_fee(1)?;
|
||||
let _fee = tx_size as f64 * fee_per_kb * 100000_f64;
|
||||
//let value = amounts - fee as u64;
|
||||
|
||||
let transaction = Transaction {
|
||||
input: inputs,
|
||||
@@ -346,11 +537,12 @@ impl BtcClient {
|
||||
debug!(target: "BTC BRIDGE", "Signed tx: {:?}",
|
||||
serialize_hex(&signed_tx));
|
||||
|
||||
let txid = client.transaction_broadcast_raw(&signed_tx.serialize().to_vec())?;
|
||||
let txid = electrum.transaction_broadcast_raw(&signed_tx.serialize().to_vec())?;
|
||||
|
||||
debug!(target: "BTC BRIDGE", "Sent {} satoshi to main wallet, txid: {}", amount, txid);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -367,12 +559,22 @@ impl NetworkClient for BtcClient {
|
||||
let private_key = serialize(&keypair);
|
||||
let public_key = btc_keys.address.to_string();
|
||||
|
||||
let keys_clone = btc_keys.clone();
|
||||
let script = keys_clone.script_pubkey;
|
||||
|
||||
let txid = self.client.lock().await.electrum
|
||||
.script_get_history(&script).unwrap()[0].tx_hash;
|
||||
|
||||
// start scheduler for checking balance
|
||||
debug!(target: "BRIDGE BITCOIN", "Subscribing for deposit");
|
||||
|
||||
executor
|
||||
.spawn(async move {
|
||||
let result = self.handle_subscribe_request(btc_keys, drk_pub_key).await;
|
||||
let result = self.handle_subscribe_request(
|
||||
(txid, script),
|
||||
btc_keys,
|
||||
drk_pub_key
|
||||
).await;
|
||||
if let Err(e) = result {
|
||||
error!(target: "BTC BRIDGE SUBSCRIPTION","{}", e.to_string());
|
||||
}
|
||||
@@ -397,9 +599,20 @@ impl NetworkClient for BtcClient {
|
||||
let btc_keys = Account::new(&keypair, self.network);
|
||||
let public_key = btc_keys.address.to_string();
|
||||
|
||||
let keys_clone = btc_keys.clone();
|
||||
let script = keys_clone.script_pubkey;
|
||||
|
||||
//Ugly
|
||||
let txid = self.client.lock().await.electrum.
|
||||
script_get_history(&script).unwrap()[0].tx_hash;
|
||||
|
||||
executor
|
||||
.spawn(async move {
|
||||
let result = self.handle_subscribe_request(btc_keys, drk_pub_key).await;
|
||||
let result = self.handle_subscribe_request(
|
||||
(txid, script),
|
||||
btc_keys,
|
||||
drk_pub_key
|
||||
).await;
|
||||
if let Err(e) = result {
|
||||
error!(target: "BTC BRIDGE SUBSCRIPTION","{}", e.to_string());
|
||||
}
|
||||
@@ -418,14 +631,14 @@ impl NetworkClient for BtcClient {
|
||||
amount: u64,
|
||||
) -> Result<()> {
|
||||
// address is not a btc address, so derive the btc address
|
||||
let client = &self.client;
|
||||
let electrum = &self.client.lock().await.electrum;
|
||||
let public_key = deserialize(&address)?;
|
||||
let script_pubkey = Account::derive_btc_script_pubkey(public_key, self.network);
|
||||
|
||||
let main_script_pubkey = &self.main_account.script_pubkey;
|
||||
|
||||
let main_utxo = client
|
||||
.script_list_unspent(main_script_pubkey)
|
||||
let main_utxo = electrum
|
||||
.script_list_unspent(&main_script_pubkey)
|
||||
.map_err(|e| Error::from(BtcFailed::from(e)))?;
|
||||
|
||||
let transaction = Transaction {
|
||||
@@ -455,7 +668,7 @@ impl NetworkClient for BtcClient {
|
||||
&self.main_account.keypair.context,
|
||||
)?;
|
||||
|
||||
let txid = client
|
||||
let txid = electrum
|
||||
.transaction_broadcast_raw(&signed_tx.serialize().to_vec())
|
||||
.map_err(|e| Error::from(BtcFailed::from(e)))?;
|
||||
|
||||
@@ -502,6 +715,97 @@ pub fn sign_transaction(
|
||||
output: tx.output,
|
||||
})
|
||||
}
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum ScriptStatus {
|
||||
Unseen,
|
||||
InMempool,
|
||||
Confirmed(Confirmed),
|
||||
}
|
||||
|
||||
impl ScriptStatus {
|
||||
pub fn from_confirmations(confirmations: u32) -> Self {
|
||||
match confirmations {
|
||||
0 => Self::InMempool,
|
||||
confirmations => Self::Confirmed(Confirmed::new(confirmations - 1)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct Confirmed {
|
||||
depth: u32,
|
||||
}
|
||||
|
||||
impl Confirmed {
|
||||
pub fn new(depth: u32) -> Self {
|
||||
Self { depth }
|
||||
}
|
||||
pub fn from_inclusion_and_latest_block(inclusion_height: u32, latest_block: u32) -> Self {
|
||||
let depth = latest_block.saturating_sub(inclusion_height);
|
||||
|
||||
Self { depth }
|
||||
}
|
||||
|
||||
pub fn confirmations(&self) -> u32 {
|
||||
self.depth + 1
|
||||
}
|
||||
|
||||
pub fn meets_target<T>(&self, target: T) -> bool
|
||||
where
|
||||
u32: PartialOrd<T>,
|
||||
{
|
||||
self.confirmations() >= target
|
||||
}
|
||||
}
|
||||
|
||||
impl ScriptStatus {
|
||||
// Check if the script has any confirmations.
|
||||
pub fn is_confirmed(&self) -> bool {
|
||||
matches!(self, ScriptStatus::Confirmed(_))
|
||||
}
|
||||
|
||||
// Check if the script has met the given confirmation target.
|
||||
pub fn is_confirmed_with<T>(&self, target: T) -> bool
|
||||
where
|
||||
u32: PartialOrd<T>,
|
||||
{
|
||||
match self {
|
||||
ScriptStatus::Confirmed(inner) => inner.meets_target(target),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_been_seen(&self) -> bool {
|
||||
matches!(self, ScriptStatus::InMempool | ScriptStatus::Confirmed(_))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ScriptStatus {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ScriptStatus::Unseen => write!(f, "unseen"),
|
||||
ScriptStatus::InMempool => write!(f, "in mempool"),
|
||||
ScriptStatus::Confirmed(inner) => {
|
||||
write!(f, "confirmed with {} blocks", inner.confirmations())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Watchable {
|
||||
fn id(&self) -> Txid;
|
||||
fn script(&self) -> Script;
|
||||
}
|
||||
|
||||
impl Watchable for (Txid, Script) {
|
||||
fn id(&self) -> Txid {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn script(&self) -> Script {
|
||||
self.1.clone()
|
||||
}
|
||||
}
|
||||
impl Encodable for bitcoin::Transaction {
|
||||
fn encode<S: std::io::Write>(&self, s: S) -> Result<usize> {
|
||||
let tx = self.serialize();
|
||||
@@ -605,7 +909,6 @@ pub enum BtcFailed {
|
||||
KeypairError(String),
|
||||
Notification(String),
|
||||
}
|
||||
|
||||
impl std::error::Error for BtcFailed {}
|
||||
|
||||
impl std::fmt::Display for BtcFailed {
|
||||
@@ -649,8 +952,8 @@ impl From<bitcoin::util::address::Error> for BtcFailed {
|
||||
BtcFailed::BadBtcAddress(err.to_string())
|
||||
}
|
||||
}
|
||||
impl From<electrum_client::Error> for BtcFailed {
|
||||
fn from(err: electrum_client::Error) -> BtcFailed {
|
||||
impl From<bdk::electrum_client::Error> for BtcFailed {
|
||||
fn from(err: bdk::electrum_client::Error) -> BtcFailed {
|
||||
BtcFailed::ElectrumError(err.to_string())
|
||||
}
|
||||
}
|
||||
@@ -660,6 +963,12 @@ impl From<bitcoin::util::key::Error> for BtcFailed {
|
||||
BtcFailed::DecodeAndEncodeError(err.to_string())
|
||||
}
|
||||
}
|
||||
impl From<anyhow::Error> for BtcFailed {
|
||||
fn from(err: anyhow::Error) -> BtcFailed {
|
||||
BtcFailed::DecodeAndEncodeError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub type BtcResult<T> = std::result::Result<T, BtcFailed>;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user