mirror of
https://github.com/RoboSats/taptrade-core.git
synced 2025-07-21 18:23:43 +00:00
finish monitoring bonds function refactor
This commit is contained in:
1
taptrade-cli-demo/coordinator/Cargo.lock
generated
1
taptrade-cli-demo/coordinator/Cargo.lock
generated
@ -484,6 +484,7 @@ dependencies = [
|
|||||||
"rand",
|
"rand",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
|
"sha2",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower",
|
"tower",
|
||||||
|
@ -23,6 +23,7 @@ tokio = { version = "1.38.0", features = ["full", "test-util", "rt"] }
|
|||||||
tower = "0.4.13"
|
tower = "0.4.13"
|
||||||
log = "0.4.22"
|
log = "0.4.22"
|
||||||
env_logger = "0.11.3"
|
env_logger = "0.11.3"
|
||||||
|
sha2 = "0.10.8"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
// prevent querying the db all the time.
|
// prevent querying the db all the time.
|
||||||
// Also needs to implement punishment logic in case a fraud is detected.
|
// Also needs to implement punishment logic in case a fraud is detected.
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Table {
|
pub enum Table {
|
||||||
@ -20,19 +21,27 @@ pub struct MonitoringBond {
|
|||||||
pub table: Table,
|
pub table: Table,
|
||||||
}
|
}
|
||||||
|
|
||||||
// the current implementation only publishes the bond and removes the offer from the db
|
impl MonitoringBond {
|
||||||
// in a more advanced implementation we could increase the transaction fee (cpfp) and
|
// used a hash of bond instead of txid to prevent issues when a valid txid can't be generated
|
||||||
// continue monitoring the bond transaction until a confirmation happens for maximum pain
|
// due to missing fields etc. (crate error)
|
||||||
// in case the trader is actively malicious and did not just accidentally invalidate the bond
|
pub fn id(&self) -> Result<Vec<u8>> {
|
||||||
// we could directly forward bond sats to the other parties payout address in case it is a taken trade
|
Ok(sha256(&hex::decode(&self.bond_tx_hex)?))
|
||||||
async fn punish_trader(coordinator: &Coordinator, bond: &MonitoringBond) -> Result<()> {
|
}
|
||||||
// publish bond
|
|
||||||
coordinator
|
|
||||||
.coordinator_wallet
|
|
||||||
.publish_bond_tx_hex(&bond.bond_tx_hex)?; // can be made async with esplora backend if we figure out the compilation error of bdk
|
|
||||||
|
|
||||||
// remove offer from db/orderbook
|
// the current implementation only publishes the bond and removes the offer from the db
|
||||||
Ok(())
|
// in a more advanced implementation we could increase the transaction fee (cpfp) and
|
||||||
|
// continue monitoring the bond transaction until a confirmation happens for maximum pain
|
||||||
|
// in case the trader is actively malicious and did not just accidentally invalidate the bond
|
||||||
|
// we could directly forward bond sats to the other parties payout address in case it is a taken trade
|
||||||
|
async fn punish(&self, coordinator: &Coordinator) -> Result<()> {
|
||||||
|
// publish bond
|
||||||
|
coordinator
|
||||||
|
.coordinator_wallet
|
||||||
|
.publish_bond_tx_hex(&self.bond_tx_hex)?; // can be made async with esplora backend if we figure out the compilation error of bdk
|
||||||
|
|
||||||
|
// remove offer from db/orderbook
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn monitor_bonds(coordinator: Arc<Coordinator>) -> Result<()> {
|
pub async fn monitor_bonds(coordinator: Arc<Coordinator>) -> Result<()> {
|
||||||
@ -47,7 +56,7 @@ pub async fn monitor_bonds(coordinator: Arc<Coordinator>) -> Result<()> {
|
|||||||
.await?;
|
.await?;
|
||||||
debug!("Monitoring active bonds: {}", bonds.len());
|
debug!("Monitoring active bonds: {}", bonds.len());
|
||||||
// verify all bonds and initiate punishment if necessary
|
// verify all bonds and initiate punishment if necessary
|
||||||
for (bond, error) in validation_results {
|
for (_, (bond, error)) in validation_results {
|
||||||
warn!("Bond validation failed: {:?}", error);
|
warn!("Bond validation failed: {:?}", error);
|
||||||
match env::var("PUNISHMENT_ENABLED")
|
match env::var("PUNISHMENT_ENABLED")
|
||||||
.unwrap_or_else(|_| "0".to_string())
|
.unwrap_or_else(|_| "0".to_string())
|
||||||
@ -55,7 +64,7 @@ pub async fn monitor_bonds(coordinator: Arc<Coordinator>) -> Result<()> {
|
|||||||
{
|
{
|
||||||
"1" => {
|
"1" => {
|
||||||
dbg!("Punishing trader for bond violation: {:?}", error);
|
dbg!("Punishing trader for bond violation: {:?}", error);
|
||||||
punish_trader(&coordinator, &bond).await?;
|
bond.punish(&coordinator).await?;
|
||||||
}
|
}
|
||||||
"0" => {
|
"0" => {
|
||||||
dbg!("Punishment disabled, ignoring bond violation: {:?}", error);
|
dbg!("Punishment disabled, ignoring bond violation: {:?}", error);
|
||||||
@ -68,3 +77,10 @@ pub async fn monitor_bonds(coordinator: Arc<Coordinator>) -> Result<()> {
|
|||||||
tokio::time::sleep(tokio::time::Duration::from_secs(15)).await;
|
tokio::time::sleep(tokio::time::Duration::from_secs(15)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sha256(data: &[u8]) -> Vec<u8> {
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(data);
|
||||||
|
let result = hasher.finalize();
|
||||||
|
result.to_vec()
|
||||||
|
}
|
||||||
|
@ -12,7 +12,7 @@ use bdk::{
|
|||||||
wallet::verify::*,
|
wallet::verify::*,
|
||||||
KeychainKind, SyncOptions, Wallet,
|
KeychainKind, SyncOptions, Wallet,
|
||||||
};
|
};
|
||||||
use std::str::FromStr;
|
use std::{collections::HashMap, str::FromStr};
|
||||||
use std::{fmt, ops::Deref};
|
use std::{fmt, ops::Deref};
|
||||||
use utils::*;
|
use utils::*;
|
||||||
// use verify_tx::*;
|
// use verify_tx::*;
|
||||||
@ -90,7 +90,8 @@ impl<D: bdk::database::BatchDatabase> CoordinatorWallet<D> {
|
|||||||
.validate_bonds(Arc::new(vec![dummy_monitoring_bond]))
|
.validate_bonds(Arc::new(vec![dummy_monitoring_bond]))
|
||||||
.await?;
|
.await?;
|
||||||
if !invalid_bond.is_empty() {
|
if !invalid_bond.is_empty() {
|
||||||
return Err(anyhow!(invalid_bond[0].1.to_string()));
|
let (_, error) = invalid_bond.values().next().unwrap();
|
||||||
|
return Err(anyhow!(error.to_string()));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -102,10 +103,9 @@ impl<D: bdk::database::BatchDatabase> CoordinatorWallet<D> {
|
|||||||
pub async fn validate_bonds(
|
pub async fn validate_bonds(
|
||||||
&self,
|
&self,
|
||||||
bonds: Arc<Vec<MonitoringBond>>,
|
bonds: Arc<Vec<MonitoringBond>>,
|
||||||
) -> Result<Vec<(MonitoringBond, anyhow::Error)>> {
|
) -> Result<HashMap<Vec<u8>, (MonitoringBond, anyhow::Error)>> {
|
||||||
let mut invalid_bonds: Vec<(MonitoringBond, anyhow::Error)> = Vec::new();
|
let mut invalid_bonds: HashMap<Vec<u8>, (MonitoringBond, anyhow::Error)> = HashMap::new();
|
||||||
let blockchain = &*self.backend;
|
let blockchain = &*self.backend;
|
||||||
|
|
||||||
{
|
{
|
||||||
let wallet = self.wallet.lock().await;
|
let wallet = self.wallet.lock().await;
|
||||||
for bond in bonds.as_ref().iter() {
|
for bond in bonds.as_ref().iter() {
|
||||||
@ -116,7 +116,7 @@ impl<D: bdk::database::BatchDatabase> CoordinatorWallet<D> {
|
|||||||
// we need to test this with signed and invalid/unsigned transactions
|
// we need to test this with signed and invalid/unsigned transactions
|
||||||
// checks signatures and inputs
|
// checks signatures and inputs
|
||||||
if let Err(e) = verify_tx(&tx, &*wallet.database(), blockchain) {
|
if let Err(e) = verify_tx(&tx, &*wallet.database(), blockchain) {
|
||||||
invalid_bonds.push((bond.clone(), anyhow!(e)));
|
invalid_bonds.insert(bond.id()?, (bond.clone(), anyhow!(e)));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,10 +124,13 @@ impl<D: bdk::database::BatchDatabase> CoordinatorWallet<D> {
|
|||||||
input_sum = match tx.input_sum(blockchain, &*wallet.database()) {
|
input_sum = match tx.input_sum(blockchain, &*wallet.database()) {
|
||||||
Ok(amount) => {
|
Ok(amount) => {
|
||||||
if amount < bond.requirements.min_input_sum_sat {
|
if amount < bond.requirements.min_input_sum_sat {
|
||||||
invalid_bonds.push((
|
invalid_bonds.insert(
|
||||||
bond.clone(),
|
bond.id()?,
|
||||||
anyhow!("Bond input sum too small: {}", amount),
|
(
|
||||||
));
|
bond.clone(),
|
||||||
|
anyhow!("Bond input sum too small: {}", amount),
|
||||||
|
),
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
amount
|
amount
|
||||||
@ -140,10 +143,13 @@ impl<D: bdk::database::BatchDatabase> CoordinatorWallet<D> {
|
|||||||
match tx.bond_output_sum(&bond.requirements.bond_address) {
|
match tx.bond_output_sum(&bond.requirements.bond_address) {
|
||||||
Ok(amount) => {
|
Ok(amount) => {
|
||||||
if amount < bond.requirements.locking_amount_sat {
|
if amount < bond.requirements.locking_amount_sat {
|
||||||
invalid_bonds.push((
|
invalid_bonds.insert(
|
||||||
bond.clone(),
|
bond.id()?,
|
||||||
anyhow!("Bond output sum too small: {}", amount),
|
(
|
||||||
));
|
bond.clone(),
|
||||||
|
anyhow!("Bond output sum too small: {}", amount),
|
||||||
|
),
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
amount
|
amount
|
||||||
@ -153,23 +159,27 @@ impl<D: bdk::database::BatchDatabase> CoordinatorWallet<D> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
if ((input_sum - tx.all_output_sum()) / tx.vsize() as u64) < 200 {
|
if ((input_sum - tx.all_output_sum()) / tx.vsize() as u64) < 200 {
|
||||||
invalid_bonds.push((
|
invalid_bonds.insert(
|
||||||
bond.clone(),
|
bond.id()?,
|
||||||
anyhow!(
|
(
|
||||||
"Bond fee rate too low: {}",
|
bond.clone(),
|
||||||
(input_sum - tx.all_output_sum()) / tx.vsize() as u64
|
anyhow!(
|
||||||
|
"Bond fee rate too low: {}",
|
||||||
|
(input_sum - tx.all_output_sum()) / tx.vsize() as u64
|
||||||
|
),
|
||||||
),
|
),
|
||||||
));
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// let invalid_bonds = Arc::new(invalid_bonds);
|
|
||||||
// let json_rpc_client = self.json_rpc_client.clone();
|
// now test all bonds with bitcoin core rpc testmempoolaccept
|
||||||
// let mempool_accept_future = tokio::task::spawn_blocking(move || {
|
let json_rpc_client = self.json_rpc_client.clone();
|
||||||
// test_mempool_accept_bonds(json_rpc_client, bonds, &mut invalid_bonds)
|
let mempool_accept_future =
|
||||||
// });
|
tokio::task::spawn_blocking(move || test_mempool_accept_bonds(json_rpc_client, bonds));
|
||||||
// mempool_accept_future.await??;
|
let invalid_bonds_testmempoolaccept = mempool_accept_future.await??;
|
||||||
|
invalid_bonds.extend(invalid_bonds_testmempoolaccept.into_iter());
|
||||||
|
|
||||||
debug!("validate_bond_tx_hex(): Bond validation done.");
|
debug!("validate_bond_tx_hex(): Bond validation done.");
|
||||||
Ok(invalid_bonds)
|
Ok(invalid_bonds)
|
||||||
@ -202,8 +212,9 @@ fn search_monitoring_bond_by_txid(
|
|||||||
fn test_mempool_accept_bonds(
|
fn test_mempool_accept_bonds(
|
||||||
json_rpc_client: Arc<Client>,
|
json_rpc_client: Arc<Client>,
|
||||||
bonds: Arc<Vec<MonitoringBond>>,
|
bonds: Arc<Vec<MonitoringBond>>,
|
||||||
invalid_bonds: &mut Vec<(MonitoringBond, anyhow::Error)>,
|
) -> Result<HashMap<Vec<u8>, (MonitoringBond, anyhow::Error)>> {
|
||||||
) -> Result<()> {
|
let mut invalid_bonds: HashMap<Vec<u8>, (MonitoringBond, anyhow::Error)> = HashMap::new();
|
||||||
|
|
||||||
let raw_bonds: Vec<String> = bonds
|
let raw_bonds: Vec<String> = bonds
|
||||||
.iter()
|
.iter()
|
||||||
.map(|bond| bond.bond_tx_hex.clone().raw_hex()) // Assuming `raw_hex()` returns a String or &str
|
.map(|bond| bond.bond_tx_hex.clone().raw_hex()) // Assuming `raw_hex()` returns a String or &str
|
||||||
@ -215,17 +226,20 @@ fn test_mempool_accept_bonds(
|
|||||||
if !res.allowed {
|
if !res.allowed {
|
||||||
let invalid_bond: MonitoringBond =
|
let invalid_bond: MonitoringBond =
|
||||||
search_monitoring_bond_by_txid(&bonds, &res.txid.to_string())?;
|
search_monitoring_bond_by_txid(&bonds, &res.txid.to_string())?;
|
||||||
invalid_bonds.push((
|
invalid_bonds.insert(
|
||||||
invalid_bond,
|
invalid_bond.id()?,
|
||||||
anyhow!(
|
(
|
||||||
"Bond not accepted by testmempoolaccept: {:?}",
|
invalid_bond,
|
||||||
res.reject_reason
|
anyhow!(
|
||||||
.unwrap_or("rejected by testmempoolaccept".to_string())
|
"Bond not accepted by testmempoolaccept: {:?}",
|
||||||
|
res.reject_reason
|
||||||
|
.unwrap_or("rejected by testmempoolaccept".to_string())
|
||||||
|
),
|
||||||
),
|
),
|
||||||
));
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(invalid_bonds)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for CoordinatorWallet<Tree> {
|
impl fmt::Debug for CoordinatorWallet<Tree> {
|
||||||
|
Reference in New Issue
Block a user