trader signature submission call, bugfixes, stop creating payout psbt twice

This commit is contained in:
fbock
2024-08-19 14:52:52 +02:00
parent e9140d5821
commit b5c083b4ab
11 changed files with 155 additions and 58 deletions

View File

@ -244,8 +244,12 @@ async fn poll_final_payout(
error!("Database error fetching final payout: {e}");
Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response())
}
_ => {
error!("Unknown error handling poll_final_payout()");
Err(RequestError::CoordinatorError(e)) => {
error!("Coordinator error handling final payout: {e}");
Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response())
}
e => {
error!("Unknown error handling poll_final_payout(): {:?}", e);
Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response())
}
}
@ -274,8 +278,8 @@ async fn submit_payout_signature(
// error!("Database error fetching final payout: {e}");
// Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response())
// }
_ => {
error!("Unknown error handling submit_payout_signature()");
e => {
error!("Unknown error handling submit_payout_signature(): {:?}", e);
Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response())
}
}

View File

@ -66,7 +66,7 @@ impl KeyspendContext {
wallet::aggregate_musig_pubkeys(maker_pk, taker_pk)?;
let agg_nonce: MusigAggNonce =
coordinator_utils::agg_hex_musig_nonces(maker_nonce, taker_nonce)?;
let keyspend_psbt = PartiallySignedTransaction::from_str(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)?;

View File

@ -13,11 +13,10 @@ pub async fn process_order(
let wallet = &coordinator.coordinator_wallet;
let database = &coordinator.coordinator_db;
let bond_address = wallet.get_new_address().await?;
let locking_amount_sat = offer.amount_satoshi * offer.bond_ratio as u64 / 100;
let locking_amount_sat = offer.amount_satoshi * u64::from(offer.bond_ratio) / 100;
let bond_requirements = BondRequirementResponse {
bond_address,
bond_address: wallet.get_new_address().await?,
locking_amount_sat,
};
@ -272,17 +271,36 @@ pub async fn handle_final_payout(
.await
.map_err(|e| RequestError::Database(e.to_string()))?;
let payout_keyspend_psbt_hex = coordinator
.coordinator_wallet
.assemble_keyspend_payout_psbt(&escrow_payout_data)
let payout_keyspend_psbt_hex = if let Some(payout_psbt_hex) = database
.fetch_keyspend_payout_psbt(&payload.offer_id_hex)
.await
.map_err(|e| RequestError::CoordinatorError(e.to_string()))?;
database
.insert_keyspend_payout_psbt(&payload.offer_id_hex, &payout_keyspend_psbt_hex)
.await
.map_err(|e| RequestError::Database(e.to_string()))?;
.map_err(|e| RequestError::Database(e.to_string()))?
{
payout_psbt_hex
} else {
if !database
.toggle_processing(&payload.offer_id_hex)
.await
.map_err(|e| RequestError::Database(e.to_string()))?
{
return Ok(PayoutProcessingResult::NotReady);
}
let payout_keyspend_psbt_hex = coordinator
.coordinator_wallet
.assemble_keyspend_payout_psbt(&escrow_payout_data)
.await
.map_err(|e| RequestError::CoordinatorError(e.to_string()))?;
database
.insert_keyspend_payout_psbt(&payload.offer_id_hex, &payout_keyspend_psbt_hex)
.await
.map_err(|e| RequestError::Database(e.to_string()))?;
database
.toggle_processing(&payload.offer_id_hex)
.await
.map_err(|e| RequestError::Database(e.to_string()))?;
payout_keyspend_psbt_hex
};
return Ok(PayoutProcessingResult::ReadyPSBT(PayoutResponse {
payout_psbt_hex: payout_keyspend_psbt_hex,
agg_musig_nonce_hex: escrow_payout_data.agg_musig_nonce.to_string(),
@ -327,7 +345,7 @@ pub async fn handle_payout_signature(
coordinator: Arc<Coordinator>,
) -> Result<bool, RequestError> {
let database = &coordinator.coordinator_db;
let _wallet = &coordinator.coordinator_wallet;
// let _wallet = &coordinator.coordinator_wallet;
check_offer_and_confirmation(&payload.offer_id_hex, &payload.robohash_hex, database).await?;
@ -340,16 +358,16 @@ pub async fn handle_payout_signature(
.await
.map_err(|e| RequestError::Database(e.to_string()))?;
let keyspend_information = if let Some(keyspend_context) = database
let keyspend_information = match database
.fetch_keyspend_payout_information(&payload.offer_id_hex)
.await
.map_err(|e| RequestError::Database(e.to_string()))?
{
keyspend_context
} else {
return Ok(false);
Some(context) => context,
None => return Ok(false),
};
dbg!("Keyspend info: {}", keyspend_information);
debug!("Keyspend info: {:?}", keyspend_information);
warn!("Use musig2 validate partial sig to validate sigs before using to blame users providing wrong sigs");
Ok(true)

View File

@ -1,6 +1,8 @@
#[cfg(test)]
mod db_tests;
use axum::routing::trace;
use super::*;
#[derive(Clone, Debug)]
@ -144,7 +146,8 @@ impl CoordinatorDB {
escrow_amount_taker_sat INTEGER,
escrow_fee_per_participant INTEGER,
escrow_output_descriptor TEXT,
payout_transaction_psbt_hex TEXT
payout_transaction_psbt_hex TEXT,
processing INTEGER NOT NULL
)", // escrow_psbt_is_confirmed will be set 1 once the escrow psbt is confirmed onchain
)
.execute(&db_pool)
@ -374,8 +377,8 @@ impl CoordinatorDB {
bond_ratio, offer_duration_ts, bond_address_maker, bond_address_taker, bond_amount_sat, bond_tx_hex_maker,
bond_tx_hex_taker, payout_address_maker, payout_address_taker, taproot_xonly_pubkey_hex_maker, taproot_xonly_pubkey_hex_taker, musig_pub_nonce_hex_maker, musig_pubkey_compressed_hex_maker,
musig_pub_nonce_hex_taker, musig_pubkey_compressed_hex_taker, escrow_psbt_hex, escrow_psbt_txid, escrow_output_descriptor, escrow_psbt_is_confirmed, escrow_ongoing,
escrow_taproot_pk_coordinator, escrow_amount_maker_sat, escrow_amount_taker_sat, escrow_fee_per_participant)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
escrow_taproot_pk_coordinator, escrow_amount_maker_sat, escrow_amount_taker_sat, escrow_fee_per_participant, processing)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
)
.bind(public_offer.offer_id)
.bind(public_offer.robohash_maker)
@ -406,6 +409,7 @@ impl CoordinatorDB {
.bind(escrow_tx_data.escrow_amount_maker_sat as i64)
.bind(escrow_tx_data.escrow_amount_taker_sat as i64)
.bind(escrow_tx_data.escrow_fee_sat_per_participant as i64)
.bind(0)
.execute(&*self.db_pool)
.await?;
@ -912,24 +916,24 @@ impl CoordinatorDB {
let is_already_there = match is_maker {
true => {
let status = sqlx::query(
"SELECT musig_partial_sig_maker FROM taken_offers WHERE offer_id = ?",
"SELECT musig_partial_sig_hex_maker FROM taken_offers WHERE offer_id = ?",
)
.bind(offer_id_hex)
.fetch_one(&*self.db_pool)
.await?;
status
.get::<Option<String>, _>("musig_partial_sig_maker")
.get::<Option<String>, _>("musig_partial_sig_hex_maker")
.is_some()
}
false => {
let status = sqlx::query(
"SELECT musig_partial_sig_taker FROM taken_offers WHERE offer_id = ?",
"SELECT musig_partial_sig_hex_taker FROM taken_offers WHERE offer_id = ?",
)
.bind(offer_id_hex)
.fetch_one(&*self.db_pool)
.await?;
status
.get::<Option<String>, _>("musig_partial_sig_taker")
.get::<Option<String>, _>("musig_partial_sig_hex_taker")
.is_some()
}
};
@ -939,9 +943,9 @@ impl CoordinatorDB {
return Err(anyhow!("Partial sig already submitted"));
} else {
let query = if is_maker {
"UPDATE taken_offers SET musig_partial_sig_maker = ? WHERE offer_id = ?"
"UPDATE taken_offers SET musig_partial_sig_hex_maker = ? WHERE offer_id = ?"
} else {
"UPDATE taken_offers SET musig_partial_sig_taker = ? WHERE offer_id = ?"
"UPDATE taken_offers SET musig_partial_sig_hex_taker = ? WHERE offer_id = ?"
};
sqlx::query(query)
.bind(partial_sig_hex)
@ -957,13 +961,13 @@ impl CoordinatorDB {
offer_id_hex: &str,
) -> Result<Option<KeyspendContext>> {
let row = sqlx::query(
"SELECT musig_partial_sig_maker, musig_partial_sig_taker,
"SELECT musig_partial_sig_hex_maker, musig_partial_sig_hex_taker,
musig_pubkey_compressed_hex_maker, musig_pubkey_compressed_hex_taker, musig_pub_nonce_hex_maker, musig_pub_nonce_hex_taker,
payout_transaction_psbt_hex FROM taken_offers WHERE offer_id = ?",
).bind(offer_id_hex).fetch_one(&*self.db_pool).await?;
let maker_sig: Option<String> = row.try_get("musig_partial_sig_maker")?;
let taker_sig: Option<String> = row.try_get("musig_partial_sig_taker")?;
let maker_sig: Option<String> = row.try_get("musig_partial_sig_hex_maker")?;
let taker_sig: Option<String> = row.try_get("musig_partial_sig_hex_taker")?;
let maker_pubkey: String = row.try_get("musig_pubkey_compressed_hex_maker")?;
let taker_pubkey: String = row.try_get("musig_pubkey_compressed_hex_taker")?;
@ -987,4 +991,35 @@ impl CoordinatorDB {
Ok(None)
}
}
pub async fn fetch_keyspend_payout_psbt(&self, offer_id_hex: &str) -> Result<Option<String>> {
let row =
sqlx::query("SELECT payout_transaction_psbt_hex FROM taken_offers WHERE offer_id = ?")
.bind(offer_id_hex)
.fetch_one(&*self.db_pool)
.await?;
let payout_psbt: Option<String> = row.try_get("payout_transaction_psbt_hex")?;
Ok(payout_psbt)
}
pub async fn toggle_processing(&self, offer_id: &str) -> Result<bool> {
let result = sqlx::query(
r#"
UPDATE taken_offers
SET processing = CASE
WHEN processing = 0 THEN 1
ELSE 0
END
WHERE offer_id = ?
RETURNING processing
"#,
)
.bind(offer_id)
.fetch_one(&*self.db_pool)
.await?;
trace!("Toggled processing status for offer {}", offer_id);
Ok(result.get::<i64, _>(0) == 1)
}
}

View File

@ -30,16 +30,16 @@ pub fn aggregate_musig_pubkeys(
maker_musig_pubkey: &str,
taker_musig_pubkey: &str,
) -> Result<KeyAggContext> {
debug!(
"Aggregating musig pubkeys: {} and {}",
maker_musig_pubkey, taker_musig_pubkey
);
let pubkeys: [MuSig2PubKey; 2] = [
MuSig2PubKey::from_str(maker_musig_pubkey).context("Error parsing musig pk 1")?,
MuSig2PubKey::from_str(taker_musig_pubkey).context("Error parsing musig pk 2")?,
];
let key_agg_ctx = KeyAggContext::new(pubkeys).context("Error aggregating musig pubkeys")?;
debug!(
"Aggregating musig pubkeys: {} and {} to {:?}",
maker_musig_pubkey, taker_musig_pubkey, key_agg_ctx
);
Ok(key_agg_ctx)
}

View File

@ -1,8 +1,16 @@
use bdk::FeeRate;
/// construction of the transaction spending the escrow output after a successfull trade as keyspend transaction
use super::*;
fn get_tx_fees_abs_sat(blockchain_backend: &RpcBlockchain) -> Result<(u64, u64)> {
let feerate = blockchain_backend.estimate_fee(6)?;
let feerate = match blockchain_backend.estimate_fee(6) {
Ok(feerate) => feerate,
Err(e) => {
error!("Failed to estimate fee: {}. Using fallback 40 sat/vb`", e);
FeeRate::from_sat_per_vb(40.0)
}
};
let keyspend_payout_tx_size_vb = 140; // ~, always 1 input, 2 outputs
let tx_fee_abs = feerate.fee_vb(keyspend_payout_tx_size_vb);
@ -10,13 +18,6 @@ fn get_tx_fees_abs_sat(blockchain_backend: &RpcBlockchain) -> Result<(u64, u64)>
Ok((tx_fee_abs, tx_fee_abs / 2))
}
// pub fn aggregate_partial_signatures(
// maker_sig_hex: &str,
// taker_sig_hex: &str,
// ) -> anyhow::Result<String> {
// Ok(())
// }
impl<D: bdk::database::BatchDatabase> CoordinatorWallet<D> {
fn get_escrow_utxo(
&self,

View File

@ -17,8 +17,8 @@ else
bitcoin-cli -regtest -datadir="/home/bitcoin/.bitcoin" -rpcwallet="coordinator_wallet" -generate 101
fi
# Generate a block every 120 seconds
# Generate a block every 10 seconds
while true; do
bitcoin-cli -regtest -datadir="/home/bitcoin/.bitcoin" -rpcwallet="coordinator_wallet" -generate 1
sleep 120
done
sleep 10
done

View File

@ -121,3 +121,10 @@ pub struct TradeObligationsUnsatisfied {
pub robohash_hex: String,
pub offer_id_hex: String,
}
#[derive(Debug, Serialize)]
pub struct PayoutSignatureRequest {
pub partial_sig_hex: String,
pub offer_id_hex: String,
pub robohash_hex: String,
}

View File

@ -292,10 +292,12 @@ impl IsOfferReadyRequest {
}
}
let payout_response: PayoutResponse = res.json()?;
let final_psbt = PartiallySignedTransaction::from_str(&payout_response.payout_psbt_hex)?;
let final_psbt = PartiallySignedTransaction::deserialize(&hex::decode(
&payout_response.payout_psbt_hex,
)?)?;
let agg_nonce = AggNonce::from_str(&payout_response.agg_musig_nonce_hex)
.map_err(|e| anyhow!("Error parsing agg nonce: {}", e))?;
let agg_pubk_ctx = KeyAggContext::from_hex(&payout_response.agg_musig_nonce_hex)
let agg_pubk_ctx = KeyAggContext::from_hex(&payout_response.agg_musig_pubkey_ctx_hex)
.map_err(|e| anyhow!("Error parsing agg pubkey ctx: {}", e))?;
Ok((final_psbt, agg_nonce, agg_pubk_ctx))
}
@ -325,3 +327,29 @@ impl TradeObligationsUnsatisfied {
Ok(())
}
}
impl PayoutSignatureRequest {
pub fn send(trader_config: &TraderSettings, signature: &str, offer_id_hex: &str) -> Result<()> {
let request = Self {
robohash_hex: trader_config.robosats_robohash_hex.clone(),
offer_id_hex: offer_id_hex.to_string(),
partial_sig_hex: signature.to_string(),
};
let client = reqwest::blocking::Client::new();
let res = client
.post(format!(
"{}{}",
trader_config.coordinator_endpoint, "/submit-payout-signature"
))
.json(&request)
.send()?;
if res.status() != 200 && res.status() != 202 {
return Err(anyhow!(
"Submitting trade obligations unsatisfied failed. Status: {}",
res.status()
));
}
Ok(())
}
}

View File

@ -22,6 +22,7 @@ use bdk::{
database::MemoryDatabase,
wallet::AddressInfo,
};
use communication::api::PayoutSignatureRequest;
use reqwest::header::ACCEPT_LANGUAGE;
use std::{str::FromStr, thread, time::Duration};
@ -54,16 +55,16 @@ pub fn run_maker(maker_config: &TraderSettings) -> Result<()> {
let (payout_keyspend_psbt, agg_pub_nonce, agg_pubk_ctx) =
IsOfferReadyRequest::poll_payout(maker_config, &offer)?;
debug!("Payout PSBT received: {}", &payout_keyspend_psbt);
let signed_payout_psbt = wallet
let signature = wallet
.validate_payout_psbt(&payout_keyspend_psbt)?
.sign_keyspend_payout_psbt(
.create_keyspend_payout_signature(
payout_keyspend_psbt,
agg_pubk_ctx,
agg_pub_nonce,
offer.used_musig_config,
)?;
// submit signed payout psbt back to coordinator
panic!("Payout to be implemented!");
PayoutSignatureRequest::send(&maker_config, &signature, &offer.offer_id_hex)?;
} else {
error!("Trade failed. Initiating escrow mode.");
TradeObligationsUnsatisfied::request_escrow(&offer.offer_id_hex, maker_config)?;
@ -96,14 +97,17 @@ pub fn run_taker(taker_config: &TraderSettings) -> Result<()> {
IsOfferReadyRequest::poll_payout(taker_config, &accepted_offer)?;
debug!("Received payout psbt: {}", &payout_keyspend_psbt);
let signed_payout_psbt = wallet
let signature = wallet
.validate_payout_psbt(&payout_keyspend_psbt)?
.sign_keyspend_payout_psbt(
.create_keyspend_payout_signature(
payout_keyspend_psbt,
agg_pubk_ctx,
agg_pub_nonce,
accepted_offer.used_musig_config,
)?;
// submit partial signature back to coordinator
PayoutSignatureRequest::send(&taker_config, &signature, &accepted_offer.offer_id_hex)?;
// here we need to handle if the other party is not cooperating
} else {
error!("Trade failed.");

View File

@ -213,7 +213,7 @@ impl TradingWallet {
/// creates a partial signature to spend the keyspend path of the escrow output
/// which will be returned to the coordinator for aggregation
pub fn sign_keyspend_payout_psbt(
pub fn create_keyspend_payout_signature(
&self,
validated_payout_psbt: PartiallySignedTransaction,
key_agg_context: KeyAggContext,