finish first implementation of escrow contract

This commit is contained in:
f321x
2024-07-29 17:43:03 +02:00
parent fc9b3d1a5d
commit 22148c545d
9 changed files with 118 additions and 44 deletions

View File

@ -6,3 +6,4 @@ BDK_DB_PATH="./dbs/bdk-wallet" # Path to the BDK Sled database (no .db postfix)
WALLET_XPRV="tprv8ZgxMBicQKsPdHuCSjhQuSZP1h6ZTeiRqREYS5guGPdtL7D1uNLpnJmb2oJep99Esq1NbNZKVJBNnD2ZhuXSK7G5eFmmcx73gsoa65e2U32" WALLET_XPRV="tprv8ZgxMBicQKsPdHuCSjhQuSZP1h6ZTeiRqREYS5guGPdtL7D1uNLpnJmb2oJep99Esq1NbNZKVJBNnD2ZhuXSK7G5eFmmcx73gsoa65e2U32"
PUNISHMENT_ENABLED=1 # enable punishment for misbehaving traders PUNISHMENT_ENABLED=1 # enable punishment for misbehaving traders
PORT=9999 # port for the coordinator to listen on PORT=9999 # port for the coordinator to listen on
COORDINATOR_FEERATE=1 # coordinator fee in percent of the trade amount

View File

@ -144,12 +144,7 @@ pub async fn handle_taker_bond(
}; };
if let Err(e) = database if let Err(e) = database
.add_taker_info_and_move_table( .add_taker_info_and_move_table(payload, &escrow_output_data)
payload,
&escrow_output_data.escrow_output_descriptor,
&escrow_output_data.escrow_tx_fee_address,
&escrow_output_data.coordinator_xonly_escrow_pk,
)
.await .await
{ {
return Err(BondError::CoordinatorError(e.to_string())); return Err(BondError::CoordinatorError(e.to_string()));
@ -158,6 +153,9 @@ pub async fn handle_taker_bond(
Ok(OfferTakenResponse { Ok(OfferTakenResponse {
escrow_output_descriptor: escrow_output_data.escrow_output_descriptor, escrow_output_descriptor: escrow_output_data.escrow_output_descriptor,
escrow_tx_fee_address: escrow_output_data.escrow_tx_fee_address, escrow_tx_fee_address: escrow_output_data.escrow_tx_fee_address,
escrow_amount_maker_sat: escrow_output_data.escrow_amount_maker_sat,
escrow_amount_taker_sat: escrow_output_data.escrow_amount_taker_sat,
escrow_fee_sat_per_participant: escrow_output_data.escrow_fee_sat_per_participant,
}) })
} }
@ -167,11 +165,18 @@ pub async fn get_offer_status_maker(
) -> Result<OfferTakenResponse, FetchOffersError> { ) -> Result<OfferTakenResponse, FetchOffersError> {
let database = &coordinator.coordinator_db; let database = &coordinator.coordinator_db;
let (descriptor, fee_address) = match database let EscrowPsbt {
escrow_output_descriptor,
escrow_tx_fee_address,
escrow_amount_maker_sat,
escrow_amount_taker_sat,
escrow_fee_sat_per_participant,
..
} = match database
.fetch_escrow_output_information(&payload.offer_id_hex) .fetch_escrow_output_information(&payload.offer_id_hex)
.await .await
{ {
Ok(Some(descriptor_and_fee_addr)) => (descriptor_and_fee_addr.0, descriptor_and_fee_addr.1), Ok(Some(escrow_psbt_data)) => escrow_psbt_data,
Ok(None) => { Ok(None) => {
return Err(FetchOffersError::NoOffersAvailable); return Err(FetchOffersError::NoOffersAvailable);
} }
@ -180,8 +185,11 @@ pub async fn get_offer_status_maker(
} }
}; };
Ok(OfferTakenResponse { Ok(OfferTakenResponse {
escrow_output_descriptor: descriptor, escrow_output_descriptor,
escrow_tx_fee_address: fee_address, escrow_tx_fee_address,
escrow_amount_maker_sat,
escrow_amount_taker_sat,
escrow_fee_sat_per_participant,
}) })
} }

View File

@ -139,7 +139,11 @@ impl CoordinatorDB {
taker_happy INTEGER, taker_happy INTEGER,
escrow_ongoing INTEGER NOT NULL, escrow_ongoing INTEGER NOT NULL,
escrow_winner_robohash TEXT, escrow_winner_robohash TEXT,
escrow_taproot_pk_coordinator TEXT escrow_taproot_pk_coordinator TEXT,
escrow_amount_maker_sat INTEGER,
escrow_amount_taker_sat INTEGER,
escrow_fee_per_participant INTEGER,
escrow_output_descriptor TEXT
)", // escrow_psbt_is_confirmed will be set 1 once the escrow psbt is confirmed onchain )", // escrow_psbt_is_confirmed will be set 1 once the escrow psbt is confirmed onchain
) )
.execute(&db_pool) .execute(&db_pool)
@ -353,9 +357,7 @@ impl CoordinatorDB {
pub async fn add_taker_info_and_move_table( pub async fn add_taker_info_and_move_table(
&self, &self,
trade_and_taker_info: &OfferPsbtRequest, trade_and_taker_info: &OfferPsbtRequest,
escrow_output_descriptor: &str, escrow_tx_data: &EscrowPsbt,
escrow_tx_fee_address: &str,
escrow_taproot_pk_coordinator: &str,
) -> Result<()> { ) -> Result<()> {
let public_offer = self let public_offer = self
.fetch_and_delete_offer_from_public_offers_table( .fetch_and_delete_offer_from_public_offers_table(
@ -368,8 +370,8 @@ impl CoordinatorDB {
bond_ratio, offer_duration_ts, bond_address_maker, bond_address_taker, bond_amount_sat, bond_tx_hex_maker, 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_pubkey_hex_maker, taproot_pubkey_hex_taker, musig_pub_nonce_hex_maker, musig_pubkey_hex_maker, bond_tx_hex_taker, payout_address_maker, payout_address_taker, taproot_pubkey_hex_maker, taproot_pubkey_hex_taker, musig_pub_nonce_hex_maker, musig_pubkey_hex_maker,
musig_pub_nonce_hex_taker, musig_pubkey_hex_taker, escrow_output_descriptor, escrow_tx_fee_address, escrow_psbt_is_confirmed, escrow_ongoing, musig_pub_nonce_hex_taker, musig_pubkey_hex_taker, escrow_output_descriptor, escrow_tx_fee_address, escrow_psbt_is_confirmed, escrow_ongoing,
escrow_taproot_pk_coordinator) escrow_taproot_pk_coordinator, escrow_amount_maker_sat, escrow_amount_taker_sat, escrow_fee_per_participant)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
) )
.bind(public_offer.offer_id) .bind(public_offer.offer_id)
.bind(public_offer.robohash_maker) .bind(public_offer.robohash_maker)
@ -391,11 +393,14 @@ impl CoordinatorDB {
.bind(public_offer.musig_pubkey_hex_maker) .bind(public_offer.musig_pubkey_hex_maker)
.bind(trade_and_taker_info.trade_data.musig_pub_nonce_hex.clone()) .bind(trade_and_taker_info.trade_data.musig_pub_nonce_hex.clone())
.bind(trade_and_taker_info.trade_data.musig_pubkey_hex.clone()) .bind(trade_and_taker_info.trade_data.musig_pubkey_hex.clone())
.bind(escrow_output_descriptor) .bind(&escrow_tx_data.escrow_output_descriptor)
.bind(escrow_tx_fee_address) .bind(&escrow_tx_data.escrow_tx_fee_address)
.bind(0) .bind(0)
.bind(0) .bind(0)
.bind(escrow_taproot_pk_coordinator) .bind(&escrow_tx_data.coordinator_xonly_escrow_pk)
.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)
.execute(&*self.db_pool) .execute(&*self.db_pool)
.await?; .await?;
@ -405,9 +410,11 @@ impl CoordinatorDB {
pub async fn fetch_escrow_output_information( pub async fn fetch_escrow_output_information(
&self, &self,
offer_id_hex: &str, offer_id_hex: &str,
) -> Result<Option<(String, String)>> { ) -> Result<Option<EscrowPsbt>> {
let offer = sqlx::query( let offer = sqlx::query(
"SELECT escrow_output_descriptor, escrow_tx_fee_address FROM taken_offers WHERE offer_id = ?", "SELECT escrow_output_descriptor, escrow_tx_fee_address, escrow_amount_maker_sat,
escrow_amount_taker_sat, escrow_fee_per_participant, escrow_taproot_pk_coordinator
FROM taken_offers WHERE offer_id = ?",
) )
.bind(offer_id_hex) .bind(offer_id_hex)
.fetch_optional(&*self.db_pool) .fetch_optional(&*self.db_pool)
@ -416,9 +423,23 @@ impl CoordinatorDB {
Some(offer) => offer, Some(offer) => offer,
None => return Ok(None), None => return Ok(None),
}; };
let descriptor = offer.try_get::<String, _>("escrow_output_descriptor")?; let escrow_output_descriptor = offer.try_get::<String, _>("escrow_output_descriptor")?;
let fee_address = offer.try_get::<String, _>("escrow_tx_fee_address")?; let escrow_tx_fee_address = offer.try_get::<String, _>("escrow_tx_fee_address")?;
Ok(Some((descriptor, fee_address))) let escrow_amount_maker_sat = offer.try_get::<i64, _>("escrow_amount_maker_sat")? as u64;
let escrow_amount_taker_sat = offer.try_get::<i64, _>("escrow_amount_taker_sat")? as u64;
let escrow_fee_sat_per_participant =
offer.try_get::<i64, _>("escrow_fee_per_participant")? as u64;
let coordinator_xonly_escrow_pk =
offer.try_get::<String, _>("escrow_taproot_pk_coordinator")?;
Ok(Some(EscrowPsbt {
escrow_output_descriptor,
escrow_tx_fee_address,
coordinator_xonly_escrow_pk,
escrow_amount_maker_sat,
escrow_amount_taker_sat,
escrow_fee_sat_per_participant,
}))
} }
// returns a hashmap of RoboHash, MonitoringBond for the monitoring loop // returns a hashmap of RoboHash, MonitoringBond for the monitoring loop
@ -689,4 +710,32 @@ impl CoordinatorDB {
musig_pubkey_compressed_hex_taker, musig_pubkey_compressed_hex_taker,
}) })
} }
pub async fn get_escrow_tx_amounts(
&self,
trade_id: &str,
coordinator_feerate: f64,
) -> Result<(u64, u64, u64)> {
let row = sqlx::query(
"SELECT amount_sat, is_buy_order, bond_amount_sat FROM active_maker_offers WHERE offer_id = ?",
).bind(trade_id).fetch_one(&*self.db_pool).await?;
let amount_sat: u64 = row.get("amount_sat");
let is_buy_order: bool = 1 == row.get::<i64, _>("is_buy_order");
let bond_amount_sat: u64 = row.get("bond_amount_sat");
let escrow_fee_per_participant: u64 = (amount_sat as f64 * coordinator_feerate) as u64;
let (escrow_amount_maker_sat, escrow_amount_taker_sat) = if is_buy_order {
(amount_sat + bond_amount_sat, bond_amount_sat)
} else {
(bond_amount_sat, amount_sat + bond_amount_sat)
};
Ok((
escrow_amount_maker_sat,
escrow_amount_taker_sat,
escrow_fee_per_participant,
))
}
} }

View File

@ -36,6 +36,7 @@ pub struct CoordinatorWallet<D: bdk::database::BatchDatabase> {
pub backend: Arc<RpcBlockchain>, pub backend: Arc<RpcBlockchain>,
pub json_rpc_client: Arc<bdk::bitcoincore_rpc::Client>, pub json_rpc_client: Arc<bdk::bitcoincore_rpc::Client>,
pub mempool: Arc<MempoolHandler>, pub mempool: Arc<MempoolHandler>,
pub coordinator_feerate: f64,
} }
#[derive(Debug)] #[derive(Debug)]
@ -43,6 +44,9 @@ pub struct EscrowPsbt {
pub escrow_output_descriptor: String, pub escrow_output_descriptor: String,
pub escrow_tx_fee_address: String, pub escrow_tx_fee_address: String,
pub coordinator_xonly_escrow_pk: String, pub coordinator_xonly_escrow_pk: String,
pub escrow_amount_maker_sat: u64,
pub escrow_amount_taker_sat: u64,
pub escrow_fee_sat_per_participant: u64,
} }
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone)]
@ -98,6 +102,7 @@ pub async fn init_coordinator_wallet() -> Result<CoordinatorWallet<sled::Tree>>
backend: Arc::new(backend), backend: Arc::new(backend),
json_rpc_client, json_rpc_client,
mempool: Arc::new(mempool), mempool: Arc::new(mempool),
coordinator_feerate: env::var("COORDINATOR_FEERATE")?.parse::<f64>()?,
}) })
} }
@ -241,11 +246,17 @@ impl<D: bdk::database::BatchDatabase> CoordinatorWallet<D> {
let escrow_output_descriptor = let escrow_output_descriptor =
build_escrow_transaction_output_descriptor(&escrow_pubkeys, &coordinator_escrow_pk)?; build_escrow_transaction_output_descriptor(&escrow_pubkeys, &coordinator_escrow_pk)?;
let escrow_tx_fee_address = self.get_new_address().await?; let escrow_tx_fee_address = self.get_new_address().await?;
let (escrow_amount_maker_sat, escrow_amount_taker_sat, escrow_fee_sat_per_participant) = db
.get_escrow_tx_amounts(trade_id, self.coordinator_feerate)
.await?;
Ok(EscrowPsbt { Ok(EscrowPsbt {
escrow_output_descriptor, escrow_output_descriptor,
escrow_tx_fee_address, escrow_tx_fee_address,
coordinator_xonly_escrow_pk: coordinator_escrow_pk.to_string(), coordinator_xonly_escrow_pk: coordinator_escrow_pk.to_string(),
escrow_amount_maker_sat,
escrow_amount_taker_sat,
escrow_fee_sat_per_participant,
}) })
} }

View File

@ -45,6 +45,7 @@ async fn new_test_wallet(wallet_xprv: &str) -> CoordinatorWallet<MemoryDatabase>
backend: Arc::new(backend), backend: Arc::new(backend),
json_rpc_client: Arc::clone(&json_rpc_client), json_rpc_client: Arc::clone(&json_rpc_client),
mempool: Arc::new(MempoolHandler::new(json_rpc_client).await), mempool: Arc::new(MempoolHandler::new(json_rpc_client).await),
coordinator_feerate: env::var("COORDINATOR_FEERATE").unwrap().parse().unwrap(),
} }
} }

View File

@ -65,7 +65,7 @@ impl OfferPsbtRequest {
offer: &PublicOffer, offer: &PublicOffer,
trade_data: BondSubmissionRequest, trade_data: BondSubmissionRequest,
taker_config: &TraderSettings, taker_config: &TraderSettings,
) -> Result<PartiallySignedTransaction> { ) -> Result<OfferTakenResponse> {
let request = OfferPsbtRequest { let request = OfferPsbtRequest {
offer: offer.clone(), offer: offer.clone(),
trade_data, trade_data,
@ -82,8 +82,6 @@ impl OfferPsbtRequest {
.json::<OfferTakenResponse>()?; .json::<OfferTakenResponse>()?;
debug!("Trader received escrow psbt"); debug!("Trader received escrow psbt");
let psbt_bytes = hex::decode(res.trade_psbt_hex_to_sign)?; Ok(res)
let psbt = PartiallySignedTransaction::deserialize(&psbt_bytes)?;
Ok(psbt)
} }
} }

View File

@ -31,13 +31,13 @@ pub fn run_maker(maker_config: &TraderSettings) -> Result<()> {
info!("Maker offer created: {:#?}", &offer); info!("Maker offer created: {:#?}", &offer);
let escrow_psbt_requirements = offer.wait_until_taken(maker_config)?; let escrow_psbt_requirements = offer.wait_until_taken(maker_config)?;
let escrow_psbt = wallet.get_escrow_psbt(escrow_psbt_requirements, maker_config); let escrow_psbt = wallet.get_escrow_psbt(escrow_psbt_requirements, maker_config)?;
// .validate_maker_psbt(&escrow_contract_psbt)? // .validate_maker_psbt(&escrow_contract_psbt)?
// .sign_escrow_psbt(&mut escrow_contract_psbt)?; // .sign_escrow_psbt(&mut escrow_contract_psbt)?;
// submit signed escrow psbt back to coordinator // submit signed escrow psbt back to coordinator
PsbtSubmissionRequest::submit_escrow_psbt( PsbtSubmissionRequest::submit_escrow_psbt(
&escrow_contract_psbt, &escrow_psbt,
offer.offer_id_hex.clone(), offer.offer_id_hex.clone(),
maker_config, maker_config,
)?; )?;

View File

@ -29,13 +29,12 @@ impl ActiveOffer {
taker_config, taker_config,
&trading_wallet.taproot_pubkey, &trading_wallet.taproot_pubkey,
)?; )?;
let mut escrow_contract_psbt = let escrow_contract_requirements =
OfferPsbtRequest::taker_request(offer, bond_submission_request, taker_config)?; OfferPsbtRequest::taker_request(offer, bond_submission_request, taker_config)?;
// now we have to verify, sign and submit the escrow psbt again // now we have to verify, sign and submit the escrow psbt again
trading_wallet let escrow_contract_psbt =
.validate_taker_psbt(&escrow_contract_psbt)? trading_wallet.get_escrow_psbt(escrow_contract_requirements, taker_config)?;
.sign_escrow_psbt(&mut escrow_contract_psbt)?;
// submit signed escrow psbt back to coordinator // submit signed escrow psbt back to coordinator
PsbtSubmissionRequest::submit_escrow_psbt( PsbtSubmissionRequest::submit_escrow_psbt(

View File

@ -26,6 +26,7 @@ use bdk::{
FeeRate, KeychainKind, SignOptions, SyncOptions, Wallet, FeeRate, KeychainKind, SignOptions, SyncOptions, Wallet,
}; };
use bond::Bond; use bond::Bond;
use cli::OfferType;
use musig2::MuSigData; use musig2::MuSigData;
use std::str::FromStr; use std::str::FromStr;
use wallet_utils::get_seed; use wallet_utils::get_seed;
@ -88,12 +89,10 @@ impl TradingWallet {
Ok((bond, musig_data, payout_address)) Ok((bond, musig_data, payout_address))
} }
pub async fn get_escrow_psbt( pub fn get_escrow_psbt(
&self, &self,
escrow_psbt_requirements: OfferTakenResponse, escrow_psbt_requirements: OfferTakenResponse,
trader_config: &TraderSettings, trader_config: &TraderSettings,
escrow_amount: u64,
coordinator_fee_amount: u64,
) -> Result<PartiallySignedTransaction> { ) -> Result<PartiallySignedTransaction> {
let fee_output = Address::from_str(&escrow_psbt_requirements.escrow_tx_fee_address)? let fee_output = Address::from_str(&escrow_psbt_requirements.escrow_tx_fee_address)?
.assume_checked() .assume_checked()
@ -108,11 +107,19 @@ impl TradingWallet {
temp_wallet.get_address(AddressIndex::New)?.script_pubkey() temp_wallet.get_address(AddressIndex::New)?.script_pubkey()
}; };
self.wallet.sync(&self.backend, SyncOptions::default())?; self.wallet.sync(&self.backend, SyncOptions::default())?;
let escrow_amount_sat = match trader_config.trade_type {
OfferType::Buy(_) => escrow_psbt_requirements.escrow_amount_taker_sat,
OfferType::Sell(_) => escrow_psbt_requirements.escrow_amount_maker_sat,
};
let (mut psbt, details) = { let (mut psbt, details) = {
let mut builder = self.wallet.build_tx(); let mut builder = self.wallet.build_tx();
builder builder
.add_recipient(escrow_output, escrow_amount) .add_recipient(escrow_output, escrow_amount_sat)
.add_recipient(fee_output, coordinator_fee_amount) .add_recipient(
fee_output,
escrow_psbt_requirements.escrow_fee_sat_per_participant,
)
.fee_rate(FeeRate::from_sat_per_vb(10.0)); .fee_rate(FeeRate::from_sat_per_vb(10.0));
builder.finish()? builder.finish()?
}; };
@ -124,11 +131,11 @@ impl TradingWallet {
// validate that the taker psbt references the correct inputs and amounts // validate that the taker psbt references the correct inputs and amounts
// taker input should be the same as in the previous bond transaction. // taker input should be the same as in the previous bond transaction.
// input amount should be the bond amount when buying, // input amount should be the bond amount when buying,
pub fn validate_taker_psbt(&self, psbt: &PartiallySignedTransaction) -> Result<&Self> { // pub fn validate_taker_psbt(&self, psbt: &PartiallySignedTransaction) -> Result<&Self> {
error!("IMPLEMENT TAKER PSBT VALIDATION!"); // error!("IMPLEMENT TAKER PSBT VALIDATION!");
// tbd once the trade psbt is implemented on coordinator side // // tbd once the trade psbt is implemented on coordinator side
Ok(self) // Ok(self)
} // }
// pub fn sign_escrow_psbt(&self, escrow_psbt: &mut PartiallySignedTransaction) -> Result<&Self> { // pub fn sign_escrow_psbt(&self, escrow_psbt: &mut PartiallySignedTransaction) -> Result<&Self> {
// let finalized = self.wallet.sign(escrow_psbt, SignOptions::default())?; // let finalized = self.wallet.sign(escrow_psbt, SignOptions::default())?;