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"
PUNISHMENT_ENABLED=1 # enable punishment for misbehaving traders
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
.add_taker_info_and_move_table(
payload,
&escrow_output_data.escrow_output_descriptor,
&escrow_output_data.escrow_tx_fee_address,
&escrow_output_data.coordinator_xonly_escrow_pk,
)
.add_taker_info_and_move_table(payload, &escrow_output_data)
.await
{
return Err(BondError::CoordinatorError(e.to_string()));
@ -158,6 +153,9 @@ pub async fn handle_taker_bond(
Ok(OfferTakenResponse {
escrow_output_descriptor: escrow_output_data.escrow_output_descriptor,
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> {
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)
.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) => {
return Err(FetchOffersError::NoOffersAvailable);
}
@ -180,8 +185,11 @@ pub async fn get_offer_status_maker(
}
};
Ok(OfferTakenResponse {
escrow_output_descriptor: descriptor,
escrow_tx_fee_address: fee_address,
escrow_output_descriptor,
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,
escrow_ongoing INTEGER NOT NULL,
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
)
.execute(&db_pool)
@ -353,9 +357,7 @@ impl CoordinatorDB {
pub async fn add_taker_info_and_move_table(
&self,
trade_and_taker_info: &OfferPsbtRequest,
escrow_output_descriptor: &str,
escrow_tx_fee_address: &str,
escrow_taproot_pk_coordinator: &str,
escrow_tx_data: &EscrowPsbt,
) -> Result<()> {
let public_offer = self
.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_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,
escrow_taproot_pk_coordinator)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
escrow_taproot_pk_coordinator, escrow_amount_maker_sat, escrow_amount_taker_sat, escrow_fee_per_participant)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
)
.bind(public_offer.offer_id)
.bind(public_offer.robohash_maker)
@ -391,11 +393,14 @@ impl CoordinatorDB {
.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_pubkey_hex.clone())
.bind(escrow_output_descriptor)
.bind(escrow_tx_fee_address)
.bind(&escrow_tx_data.escrow_output_descriptor)
.bind(&escrow_tx_data.escrow_tx_fee_address)
.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)
.await?;
@ -405,9 +410,11 @@ impl CoordinatorDB {
pub async fn fetch_escrow_output_information(
&self,
offer_id_hex: &str,
) -> Result<Option<(String, String)>> {
) -> Result<Option<EscrowPsbt>> {
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)
.fetch_optional(&*self.db_pool)
@ -416,9 +423,23 @@ impl CoordinatorDB {
Some(offer) => offer,
None => return Ok(None),
};
let descriptor = offer.try_get::<String, _>("escrow_output_descriptor")?;
let fee_address = offer.try_get::<String, _>("escrow_tx_fee_address")?;
Ok(Some((descriptor, fee_address)))
let escrow_output_descriptor = offer.try_get::<String, _>("escrow_output_descriptor")?;
let escrow_tx_fee_address = offer.try_get::<String, _>("escrow_tx_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
@ -689,4 +710,32 @@ impl CoordinatorDB {
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 json_rpc_client: Arc<bdk::bitcoincore_rpc::Client>,
pub mempool: Arc<MempoolHandler>,
pub coordinator_feerate: f64,
}
#[derive(Debug)]
@ -43,6 +44,9 @@ pub struct EscrowPsbt {
pub escrow_output_descriptor: String,
pub escrow_tx_fee_address: 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)]
@ -98,6 +102,7 @@ pub async fn init_coordinator_wallet() -> Result<CoordinatorWallet<sled::Tree>>
backend: Arc::new(backend),
json_rpc_client,
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 =
build_escrow_transaction_output_descriptor(&escrow_pubkeys, &coordinator_escrow_pk)?;
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 {
escrow_output_descriptor,
escrow_tx_fee_address,
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),
json_rpc_client: Arc::clone(&json_rpc_client),
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,
trade_data: BondSubmissionRequest,
taker_config: &TraderSettings,
) -> Result<PartiallySignedTransaction> {
) -> Result<OfferTakenResponse> {
let request = OfferPsbtRequest {
offer: offer.clone(),
trade_data,
@ -82,8 +82,6 @@ impl OfferPsbtRequest {
.json::<OfferTakenResponse>()?;
debug!("Trader received escrow psbt");
let psbt_bytes = hex::decode(res.trade_psbt_hex_to_sign)?;
let psbt = PartiallySignedTransaction::deserialize(&psbt_bytes)?;
Ok(psbt)
Ok(res)
}
}

View File

@ -31,13 +31,13 @@ pub fn run_maker(maker_config: &TraderSettings) -> Result<()> {
info!("Maker offer created: {:#?}", &offer);
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)?
// .sign_escrow_psbt(&mut escrow_contract_psbt)?;
// submit signed escrow psbt back to coordinator
PsbtSubmissionRequest::submit_escrow_psbt(
&escrow_contract_psbt,
&escrow_psbt,
offer.offer_id_hex.clone(),
maker_config,
)?;

View File

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

View File

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