finish monitoring bonds function refactor

This commit is contained in:
f321x
2024-07-12 16:37:22 +02:00
parent d99439b52f
commit 6552ce559d
4 changed files with 82 additions and 50 deletions

View File

@ -484,6 +484,7 @@ dependencies = [
"rand", "rand",
"reqwest", "reqwest",
"serde", "serde",
"sha2",
"sqlx", "sqlx",
"tokio", "tokio",
"tower", "tower",

View File

@ -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

View File

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

View File

@ -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> {