diff --git a/taptrade-cli-demo/coordinator/src/communication/mod.rs b/taptrade-cli-demo/coordinator/src/communication/mod.rs index 0c74dc3..2224ad5 100755 --- a/taptrade-cli-demo/coordinator/src/communication/mod.rs +++ b/taptrade-cli-demo/coordinator/src/communication/mod.rs @@ -123,23 +123,7 @@ async fn submit_escrow_psbt( Ok(StatusCode::NOT_ACCEPTABLE.into_response()) } _ => Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response()), - // Err(RequestError::NotFound) => { - // info!("Offer for escrow psbt not found"); - // Ok(StatusCode::NOT_FOUND.into_response()) - // } - // Err(RequestError::NotConfirmed) => { - // info!("Offer for escrow psbt not confirmed"); - // Ok(StatusCode::NOT_ACCEPTABLE.into_response()) - // } - // Err(RequestError::Database(e)) => { - // error!("Database error fetching escrow psbt: {e}"); - // Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response()) - // } } - // check if psbt is correct, valid and signed - // publish psbt if it is correct - // return 200 if everything is correct - // return 400 if something is wrong } /// Will get polled by the traders once they submitted their PSBT part. The coorinator will return status code 200 once he received both PSBTs and they got mined, @@ -163,6 +147,7 @@ async fn poll_escrow_confirmation( } } +/// gets called if the trader is happy and does not want to initiate escrow async fn submit_obligation_confirmation( Extension(coordinator): Extension>, Json(payload): Json, @@ -190,8 +175,8 @@ async fn submit_obligation_confirmation( // or -// gets called if one of the traders wants to initiate escrow (e.g. claiming they didn't receive the fiat) -// before timeout ends, just sets the maker unhappy and escrow onging flag in the db +/// gets called if one of the traders wants to initiate escrow (e.g. claiming they didn't receive the fiat) +/// before timeout ends, just sets the maker unhappy and escrow onging flag in the db async fn request_escrow( Extension(coordinator): Extension>, Json(payload): Json, @@ -255,6 +240,7 @@ async fn poll_final_payout( } } +/// recieves the partial signature for the keyspend payout transaction async fn submit_payout_signature( Extension(coordinator): Extension>, Json(payload): Json, @@ -266,18 +252,6 @@ async fn submit_payout_signature( // this was the first signature Ok(false) => Ok(StatusCode::ACCEPTED.into_response()), - // Err(RequestError::NotConfirmed) => { - // info!("Offer tx for final payout not confirmed"); - // Ok(StatusCode::NOT_ACCEPTABLE.into_response()) - // } - // Err(RequestError::NotFound) => { - // info!("Offer for final payout not found"); - // Ok(StatusCode::NOT_FOUND.into_response()) - // } - // Err(RequestError::Database(e)) => { - // error!("Database error fetching final payout: {e}"); - // Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response()) - // } e => { error!("Unknown error handling submit_payout_signature(): {:?}", e); Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response()) @@ -285,6 +259,7 @@ async fn submit_payout_signature( } } +/// testing endpoint async fn test_api() -> &'static str { "Hello, World!" } @@ -312,7 +287,7 @@ pub async fn api_server(coordinator: Arc) -> Result<()> { let port: u16 = env::var("PORT") .unwrap_or_else(|_| "9999".to_string()) .parse()?; - info!("Listening on {}", port); + info!("Coordinator is listening on port {}", port); let addr = SocketAddr::from(([127, 0, 0, 1], port)); let tcp = TcpListener::bind(&addr).await.unwrap(); axum::serve(tcp, app).await?; diff --git a/taptrade-cli-demo/coordinator/src/coordinator/coordinator_utils.rs b/taptrade-cli-demo/coordinator/src/coordinator/coordinator_utils.rs index ff41135..4bb6ad6 100644 --- a/taptrade-cli-demo/coordinator/src/coordinator/coordinator_utils.rs +++ b/taptrade-cli-demo/coordinator/src/coordinator/coordinator_utils.rs @@ -19,6 +19,8 @@ pub struct PayoutData { pub aggregated_musig_pubkey_ctx_hex: String, } +/// KeyspendContext contains all data neccessary to create the +/// signed keyspend payout transaction #[derive(Debug, Clone)] pub struct KeyspendContext { pub agg_sig: LiftedSignature, @@ -27,7 +29,10 @@ pub struct KeyspendContext { pub keyspend_psbt: PartiallySignedTransaction, } +/// takes two hex encoded pub musig nonces (serialized according to musig2 crate) and +/// aggregates them into a single MusigAggNonce pub fn agg_hex_musig_nonces(maker_nonce: &str, taker_nonce: &str) -> Result { + // decode the hex strings into MusigPubNonces let musig_pub_nonce_maker = match MusigPubNonce::from_hex(maker_nonce) { Ok(musig_pub_nonce_maker) => musig_pub_nonce_maker, Err(e) => { @@ -47,12 +52,14 @@ pub fn agg_hex_musig_nonces(maker_nonce: &str, taker_nonce: &str) -> Result anyhow::Result { + // obtain the tweak scalar from the escrow output descriptor let tweak = get_keyspend_tweak_scalar(descriptor)?; + // aggregate the maker and taker musig pubkeys with the tweak let agg_keyspend_pk: musig2::KeyAggContext = aggregate_musig_pubkeys_with_tweak(maker_pk, taker_pk, tweak)?; + // aggregate the public nonces let agg_nonce: MusigAggNonce = coordinator_utils::agg_hex_musig_nonces(maker_nonce, taker_nonce)?; + // deserialize the keyspend psbt let keyspend_psbt = PartiallySignedTransaction::deserialize(&hex::decode(keyspend_psbt)?)?; let partial_maker_sig = PartialSignature::from_hex(maker_sig)?; let partial_taker_sig = PartialSignature::from_hex(taker_sig)?; let partial_signatures = vec![partial_maker_sig, partial_taker_sig]; - // let msg = keyspend_psbt. + // obtain the message to sign (taproot key spend signature hash) let msg = { let mut sig_hash_cache = SighashCache::new(keyspend_psbt.unsigned_tx.clone()); + // it should only contain one utxo, the escrow locking UTXO let utxo = keyspend_psbt .iter_funding_utxos() .next() @@ -110,6 +122,7 @@ impl KeyspendContext { } } +/// get the scalar used to tweak the keyspend output key from the escrow output descriptor fn get_keyspend_tweak_scalar(descriptor: &str) -> Result { let tr_descriptor: Descriptor = bdk::descriptor::Descriptor::from_str(descriptor)?; @@ -130,6 +143,7 @@ fn get_keyspend_tweak_scalar(descriptor: &str) -> Result String { +/// generates a random order id of size `size` bytes and returns it as hex encoded string +pub fn generate_random_order_id(size: usize) -> String { // Generate `len` random bytes let bytes: Vec = rand::thread_rng() .sample_iter(&rand::distributions::Standard) - .take(len) + .take(size) .collect(); // Convert bytes to hex string hex::encode(bytes) } +/// does check both that a robhash and offerID are actually existing in the database +/// and that the escrow locking transaction is confirmed pub async fn check_offer_and_confirmation( offer_id_hex: &str, robohash_hex: &str, diff --git a/taptrade-cli-demo/coordinator/src/coordinator/mempool_monitoring.rs b/taptrade-cli-demo/coordinator/src/coordinator/mempool_monitoring.rs index a0f1e96..0f95f22 100644 --- a/taptrade-cli-demo/coordinator/src/coordinator/mempool_monitoring.rs +++ b/taptrade-cli-demo/coordinator/src/coordinator/mempool_monitoring.rs @@ -87,6 +87,8 @@ pub struct MempoolHandler { } impl MempoolHandler { + /// creates a new mempool handler connected to a json rpc client which spawns + /// a new tokio thread which keeps track of the mempool state pub async fn new(json_rpc_client: Arc) -> Self { let mempool = Arc::new(Mempool::new(json_rpc_client)); let mempool_clone = Arc::clone(&mempool); @@ -101,6 +103,7 @@ impl MempoolHandler { } } + /// called to look for UTXOs in the mempool pub async fn lookup_mempool_inputs( &self, bonds: &Vec, @@ -125,6 +128,7 @@ impl MempoolHandler { Ok(bonds_to_punish) } + /// kills the mempool thread, needed to prevent long running when ending the program, especially in tests pub async fn shutdown(&self) { if let Some(sender) = self.shutdown_sender.lock().await.take() { let _ = sender.send(()); // Ignore the result, as the receiver might have been dropped diff --git a/taptrade-cli-demo/coordinator/src/coordinator/tx_confirmation_monitoring.rs b/taptrade-cli-demo/coordinator/src/coordinator/tx_confirmation_monitoring.rs index ffcbb76..1dd0e3d 100644 --- a/taptrade-cli-demo/coordinator/src/coordinator/tx_confirmation_monitoring.rs +++ b/taptrade-cli-demo/coordinator/src/coordinator/tx_confirmation_monitoring.rs @@ -1,5 +1,6 @@ use super::*; +/// fetches confirmations of the txids in unconfirmed_txids using core rpc fn get_confirmations( unconfirmed_txids: Vec, coordinator: Arc, @@ -58,6 +59,9 @@ fn get_confirmations( Ok(now_confirmed_txs) } +/// pulls txids of unconfirmed escrow transactions from the database, checks +/// for confirmations using the bitcoin core rpc api and updates the database +/// entries with the confirmation pub async fn update_transaction_confirmations(coordinator: Arc) { loop { tokio::time::sleep(std::time::Duration::from_secs(30)).await; @@ -78,6 +82,7 @@ pub async fn update_transaction_confirmations(coordinator: Arc) { continue; } let coordinator_clone = Arc::clone(&coordinator); + // spawn blocking because the core rpc call is blocking let newly_confirmed_txids = match tokio::task::spawn_blocking(move || { get_confirmations(unconfirmed_transactions, coordinator_clone) }) diff --git a/taptrade-cli-demo/coordinator/src/main.rs b/taptrade-cli-demo/coordinator/src/main.rs index d421608..9c133de 100755 --- a/taptrade-cli-demo/coordinator/src/main.rs +++ b/taptrade-cli-demo/coordinator/src/main.rs @@ -110,7 +110,7 @@ async fn main() -> Result<()> { coordinator_wallet: Arc::new(init_coordinator_wallet().await?), }); - // begin monitoring bonds as separate tokio taks which runs concurrently + // begin monitoring bonds as separate tokio task which runs concurrently let coordinator_ref = Arc::clone(&coordinator); tokio::spawn(async move { loop { diff --git a/taptrade-cli-demo/coordinator/src/wallet/escrow_psbt.rs b/taptrade-cli-demo/coordinator/src/wallet/escrow_psbt.rs index 2f21749..a28ac23 100644 --- a/taptrade-cli-demo/coordinator/src/wallet/escrow_psbt.rs +++ b/taptrade-cli-demo/coordinator/src/wallet/escrow_psbt.rs @@ -12,6 +12,7 @@ pub struct EscrowPsbtConstructionData { } impl EscrowPsbtConstructionData { + /// get the sum of the input utxos pub fn input_sum(&self) -> Result { let mut input_sum = 0; for input in &self.escrow_input_utxos { @@ -26,6 +27,7 @@ impl EscrowPsbtConstructionData { } } +/// aggregate hex encoded musig pubkeys to a KeyAggContext without tweaking pub fn aggregate_musig_pubkeys( maker_musig_pubkey: &str, taker_musig_pubkey: &str, @@ -43,7 +45,7 @@ pub fn aggregate_musig_pubkeys( Ok(key_agg_ctx) } -/// this function builds the escrow output with all possible spending conditions +/// this function builds the escrow output descriptor with all possible spending conditions pub fn build_escrow_transaction_output_descriptor( maker_escrow_data: &EscrowPsbtConstructionData, taker_escrow_data: &EscrowPsbtConstructionData, @@ -113,8 +115,8 @@ pub fn build_escrow_transaction_output_descriptor( Ok(descriptor) // then spend to descriptor.address(Network::Regtest) } -// pub fn assemble_escrow_psbts( impl CoordinatorWallet { + /// assemble the escrow locking transaction as psbt and return it with relevant associated data pub async fn create_escrow_psbt( &self, db: &Arc, @@ -149,32 +151,9 @@ impl CoordinatorWallet { .await?; let (escrow_psbt, details) = { - // maybe we can generate a address/taproot pk directly from the descriptor without a new wallet? - // let temp_wallet = Wallet::new( - // &escrow_output_descriptor, - // None, - // bitcoin::Network::Regtest, - // MemoryDatabase::new(), - // )?; - + // get address for escrow output from the descriptor let escrow_address = escrow_output_descriptor.address(bdk::bitcoin::Network::Regtest)?; - // dbg!("building untweaked address now"); - // let agg_untweaked: musig2::secp256k1::PublicKey = aggregate_musig_pubkeys( - // &maker_psbt_input_data.musig_pubkey_compressed_hex, - // &taker_psbt_input_data.musig_pubkey_compressed_hex, - // )? - // .aggregated_pubkey_untweaked(); - // let xonly_hex = agg_untweaked.x_only_public_key().0.to_string(); - // let bdk_xonly = bdk::bitcoin::key::XOnlyPublicKey::from_str(&xonly_hex)?; - // let output_key = TweakedPublicKey::dangerous_assume_tweaked(bdk_xonly); - // let escrow_address = bdk::bitcoin::Address::p2tr_tweaked(output_key, Network::Regtest); - // escrow_output_descriptor = Descriptor::::new_tr(bdk_xonly, None) - // .context("Error assembling escrow output descriptor")?; - - // dummy escrow address for testing the psbt signing flow - // let escrow_address = - // Address::from_str(self.get_new_address().await?.as_str())?.assume_checked(); // using absolute fee for now, in production we should come up with a way to determine the tx weight // upfront and substract the fee from the change outputs (10k == ~30/sat vbyte) diff --git a/taptrade-cli-demo/coordinator/src/wallet/mod.rs b/taptrade-cli-demo/coordinator/src/wallet/mod.rs index 8ed6adf..a914692 100644 --- a/taptrade-cli-demo/coordinator/src/wallet/mod.rs +++ b/taptrade-cli-demo/coordinator/src/wallet/mod.rs @@ -37,6 +37,7 @@ pub struct BondRequirements { pub min_input_sum_sat: u64, } +/// sets up the coordinator bdk wallet from the env variables pub async fn init_coordinator_wallet() -> Result> { let wallet_xprv = ExtendedPrivKey::from_str( &env::var("WALLET_XPRV").context("loading WALLET_XPRV from .env failed")?, @@ -49,7 +50,7 @@ pub async fn init_coordinator_wallet() -> Result Result Result CoordinatorWallet { + /// shutdown function to end the mempool task pub async fn shutdown(&self) { debug!("Shutting down wallet"); self.mempool.shutdown().await; } + /// get a new address of the coordinator wallet pub async fn get_new_address(&self) -> Result { let wallet = self.wallet.lock().await; let address = wallet.get_address(bdk::wallet::AddressIndex::New)?; Ok(address.address.to_string()) } + /// used to validate submitted bond transactions using the same logic as in the continoous monitoring + /// puts the bond in a dummy MonitoringBond struct use the existing logic pub async fn validate_bond_tx_hex( &self, bond_tx_hex: &str, @@ -263,6 +267,7 @@ fn test_mempool_accept_bonds( ) -> Result, (MonitoringBond, anyhow::Error)>> { let mut invalid_bonds: HashMap, (MonitoringBond, anyhow::Error)> = HashMap::new(); + // split bonds into chunks of 25 to avoid hitting the maxmimum allowed size of the rpc call let raw_bonds: Vec> = bonds .iter() .map(|bond| bond.bond_tx_hex.clone().raw_hex()) diff --git a/taptrade-cli-demo/coordinator/src/wallet/wallet_utils.rs b/taptrade-cli-demo/coordinator/src/wallet/wallet_utils.rs index bfd7cd7..0993247 100644 --- a/taptrade-cli-demo/coordinator/src/wallet/wallet_utils.rs +++ b/taptrade-cli-demo/coordinator/src/wallet/wallet_utils.rs @@ -5,6 +5,7 @@ pub struct PsbtInput { pub utxo: bdk::bitcoin::OutPoint, } +/// implements functions required for bond transactions on the bdk::bitcoin::Transaction struct pub trait BondTx { fn input_sum(&self, blockchain: &B, db: &D) -> Result; fn bond_output_sum(&self, bond_address: &str) -> Result; @@ -57,6 +58,7 @@ impl BondTx for Transaction { } } +/// converts a csv string of bincode binary serialized, hex encoded bdk psbt inputs to a vector of PsbtInput pub fn csv_hex_to_bdk_input(inputs_csv_hex: &str) -> Result> { let mut inputs: Vec = Vec::new(); for input_hex in inputs_csv_hex.split(',') {