fix input amounts trough additional api request field in BondRequirementsResponse

This commit is contained in:
fbock
2024-08-28 10:58:55 +02:00
parent 658644f1e6
commit abf4d7e60f
12 changed files with 77 additions and 57 deletions

View File

@ -35,6 +35,6 @@ lto = true
opt-level = 3 opt-level = 3
strip = true strip = true
[lib] # [lib]
name = "coordinator" # name = "coordinator"
path = "src/main.rs" # path = "src/main.rs"

View File

@ -86,10 +86,11 @@ pub struct OfferRequest {
pub offer_duration_ts: u64, // unix timestamp how long the offer should stay available pub offer_duration_ts: u64, // unix timestamp how long the offer should stay available
} }
#[derive(Serialize, PartialEq, Debug, Validate)] #[derive(Serialize, PartialEq, Debug, Validate, Deserialize)]
pub struct BondRequirementResponse { pub struct BondRequirementResponse {
pub bond_address: String, pub bond_address: String,
pub locking_amount_sat: u64, // min amount of the bond output in sat pub locking_amount_sat: u64, // min amount of the bond output in sat
pub escrow_locking_input_amount_without_trade_sum: u64,
} }
// maker step 2 // maker step 2
@ -124,8 +125,7 @@ pub struct OffersRequest {
pub struct PublicOffer { pub struct PublicOffer {
pub amount_sat: u64, pub amount_sat: u64,
pub offer_id_hex: String, pub offer_id_hex: String,
pub required_bond_amount_sat: u64, pub bond_requirements: BondRequirementResponse,
pub bond_locking_address: String,
} }
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]

View File

@ -15,31 +15,23 @@ pub async fn process_order(
let wallet = &coordinator.coordinator_wallet; let wallet = &coordinator.coordinator_wallet;
let database = &coordinator.coordinator_db; let database = &coordinator.coordinator_db;
// the client also uses this amount to select inputs for the escrow psbt, this could be separated let bond_amount = (offer.bond_ratio as u64 * offer.amount_satoshi) / 100;
// to make the required bond amount lower without causing the client to return too small inputs let coordinator_fee = ((coordinator.coordinator_wallet.coordinator_feerate
// 5000 is the abs tx fee used in the escrow psbt per trader * offer.amount_satoshi as f64)
panic!("This is borked"); / 100.0) as u64;
let coordinator_feerate = (coordinator.coordinator_wallet.coordinator_feerate let absolute_tx_fee = 5000;
* offer.amount_satoshi as f64) as u64 // 5000 is a buffer
/ 100; let escrow_locking_input_amount_without_trade_sum =
let locking_amount_sat = match offer.is_buy_order { bond_amount + coordinator_fee + absolute_tx_fee + 5000;
true => {
5000 + coordinator_feerate + offer.amount_satoshi * u64::from(offer.bond_ratio) / 100
}
false => {
(offer.amount_satoshi * u64::from(offer.bond_ratio) / 100)
+ offer.amount_satoshi
+ 5000 + coordinator_feerate
}
};
trace!( trace!(
"Offer amount: {}, Locking amount: {}", "Offer amount: {}, Locking amount: {}",
offer.amount_satoshi, offer.amount_satoshi,
locking_amount_sat bond_amount
); );
let bond_requirements = BondRequirementResponse { let bond_requirements = BondRequirementResponse {
bond_address: wallet.get_new_address().await?, bond_address: wallet.get_new_address().await?,
locking_amount_sat, locking_amount_sat: bond_amount,
escrow_locking_input_amount_without_trade_sum,
}; };
database database

View File

@ -18,6 +18,7 @@ struct AwaitingBondOffer {
offer_duration_ts: u64, offer_duration_ts: u64,
bond_address: String, bond_address: String,
bond_amount_sat: u64, bond_amount_sat: u64,
escrow_locking_input_amount_without_trade_sum: u64,
} }
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
@ -78,7 +79,8 @@ impl CoordinatorDB {
bond_ratio INTEGER NOT NULL, bond_ratio INTEGER NOT NULL,
offer_duration_ts INTEGER NOT NULL, offer_duration_ts INTEGER NOT NULL,
bond_address TEXT NOT NULL, bond_address TEXT NOT NULL,
bond_amount_sat INTEGER NOT NULL bond_amount_sat INTEGER NOT NULL,
escrow_locking_input_amount_without_trade_sum INTEGER NOT NULL
)", )",
) )
.execute(&db_pool) .execute(&db_pool)
@ -96,6 +98,7 @@ impl CoordinatorDB {
offer_duration_ts INTEGER NOT NULL, offer_duration_ts INTEGER NOT NULL,
bond_address TEXT NOT NULL, bond_address TEXT NOT NULL,
bond_amount_sat INTEGER NOT NULL, bond_amount_sat INTEGER NOT NULL,
escrow_locking_input_amount_without_trade_sum INTEGER,
bond_tx_hex TEXT NOT NULL, bond_tx_hex TEXT NOT NULL,
payout_address TEXT NOT NULL, payout_address TEXT NOT NULL,
change_address_maker TEXT NOT NULL, change_address_maker TEXT NOT NULL,
@ -169,8 +172,8 @@ impl CoordinatorDB {
) -> Result<()> { ) -> Result<()> {
sqlx::query( sqlx::query(
"INSERT OR REPLACE INTO maker_requests (robohash, is_buy_order, amount_sat, "INSERT OR REPLACE INTO maker_requests (robohash, is_buy_order, amount_sat,
bond_ratio, offer_duration_ts, bond_address, bond_amount_sat) bond_ratio, offer_duration_ts, bond_address, bond_amount_sat, escrow_locking_input_amount_without_trade_sum)
VALUES (?, ?, ?, ?, ?, ?, ?)", VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
) )
.bind(hex::decode(&order.robohash_hex)?) .bind(hex::decode(&order.robohash_hex)?)
.bind(bool_to_sql_int(order.is_buy_order)) .bind(bool_to_sql_int(order.is_buy_order))
@ -179,6 +182,7 @@ impl CoordinatorDB {
.bind(order.offer_duration_ts as i64) .bind(order.offer_duration_ts as i64)
.bind(bond_requirements.bond_address.clone()) .bind(bond_requirements.bond_address.clone())
.bind(bond_requirements.locking_amount_sat as i64) .bind(bond_requirements.locking_amount_sat as i64)
.bind(bond_requirements.escrow_locking_input_amount_without_trade_sum as i64)
.execute(&*self.db_pool) .execute(&*self.db_pool)
.await?; .await?;
@ -207,7 +211,7 @@ impl CoordinatorDB {
robohash_hex: &str, robohash_hex: &str,
) -> Result<AwaitingBondOffer> { ) -> Result<AwaitingBondOffer> {
let fetched_values = sqlx::query_as::<_, (Vec<u8>, bool, i64, u8, i64, String, i64)> ( let fetched_values = sqlx::query_as::<_, (Vec<u8>, bool, i64, u8, i64, String, i64)> (
"SELECT robohash, is_buy_order, amount_sat, bond_ratio, offer_duration_ts, bond_address, bond_amount_sat FROM maker_requests WHERE robohash = ?", "SELECT robohash, is_buy_order, amount_sat, bond_ratio, offer_duration_ts, bond_address, bond_amount_sat, escrow_locking_input_amount_without_trade_sum FROM maker_requests WHERE robohash = ?",
) )
.bind(hex::decode(robohash_hex)?) .bind(hex::decode(robohash_hex)?)
.fetch_one(&*self.db_pool) .fetch_one(&*self.db_pool)
@ -226,6 +230,7 @@ impl CoordinatorDB {
offer_duration_ts: fetched_values.4 as u64, offer_duration_ts: fetched_values.4 as u64,
bond_address: fetched_values.5, bond_address: fetched_values.5,
bond_amount_sat: fetched_values.6 as u64, bond_amount_sat: fetched_values.6 as u64,
escrow_locking_input_amount_without_trade_sum: fetched_values.6 as u64,
}; };
debug!( debug!(
"Deleted offer from maker_requests table. Fetched offer: {:#?}", "Deleted offer from maker_requests table. Fetched offer: {:#?}",
@ -252,8 +257,8 @@ impl CoordinatorDB {
sqlx::query( sqlx::query(
"INSERT OR REPLACE INTO active_maker_offers (offer_id, robohash, is_buy_order, amount_sat, "INSERT OR REPLACE INTO active_maker_offers (offer_id, robohash, is_buy_order, amount_sat,
bond_ratio, offer_duration_ts, bond_address, bond_amount_sat, bond_tx_hex, payout_address, taproot_pubkey_hex_maker, musig_pub_nonce_hex, musig_pubkey_hex, taker_bond_address, bond_ratio, offer_duration_ts, bond_address, bond_amount_sat, bond_tx_hex, payout_address, taproot_pubkey_hex_maker, musig_pub_nonce_hex, musig_pubkey_hex, taker_bond_address,
change_address_maker, escrow_inputs_hex_maker_csv) change_address_maker, escrow_inputs_hex_maker_csv, escrow_locking_input_amount_without_trade_sum)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
) )
.bind(offer_id) .bind(offer_id)
.bind(hex::decode(&data.robohash_hex)?) .bind(hex::decode(&data.robohash_hex)?)
@ -271,6 +276,7 @@ impl CoordinatorDB {
.bind(taker_bond_address) .bind(taker_bond_address)
.bind(data.client_change_address.clone()) .bind(data.client_change_address.clone())
.bind(data.bdk_psbt_inputs_hex_csv.clone()) .bind(data.bdk_psbt_inputs_hex_csv.clone())
.bind(remaining_offer_information.escrow_locking_input_amount_without_trade_sum as i64)
.execute(&*self.db_pool) .execute(&*self.db_pool)
.await?; .await?;
@ -287,8 +293,8 @@ impl CoordinatorDB {
"Fetching suitable offers from db. Specification: {:#?}", "Fetching suitable offers from db. Specification: {:#?}",
requested_offer requested_offer
); );
let fetched_offers = sqlx::query_as::<_, (String, i64, i64, String)> ( let fetched_offers = sqlx::query_as::<_, (String, i64, i64, String, i64)> (
"SELECT offer_id, amount_sat, bond_amount_sat, taker_bond_address FROM active_maker_offers WHERE is_buy_order = ? AND amount_sat BETWEEN ? AND ?", "SELECT offer_id, amount_sat, bond_amount_sat, taker_bond_address, escrow_locking_input_amount_without_trade_sum FROM active_maker_offers WHERE is_buy_order = ? AND amount_sat BETWEEN ? AND ?",
) )
.bind(requested_offer.buy_offers) .bind(requested_offer.buy_offers)
.bind(requested_offer.amount_min_sat as i64) .bind(requested_offer.amount_min_sat as i64)
@ -299,11 +305,16 @@ impl CoordinatorDB {
let available_offers: Vec<PublicOffer> = fetched_offers let available_offers: Vec<PublicOffer> = fetched_offers
.into_iter() .into_iter()
.map( .map(
|(offer_id_hex, amount_sat, bond_amount_sat, bond_address_taker)| PublicOffer { |(offer_id_hex, amount_sat, bond_amount_sat, bond_address_taker, min_inputs)| {
PublicOffer {
offer_id_hex, offer_id_hex,
amount_sat: amount_sat as u64, amount_sat: amount_sat as u64,
required_bond_amount_sat: bond_amount_sat as u64, bond_requirements: BondRequirementResponse {
bond_locking_address: bond_address_taker, bond_address: bond_address_taker,
locking_amount_sat: bond_amount_sat as u64,
escrow_locking_input_amount_without_trade_sum: min_inputs as u64,
},
}
}, },
) )
.collect(); .collect();
@ -400,7 +411,7 @@ impl CoordinatorDB {
.bind(public_offer.bond_ratio) .bind(public_offer.bond_ratio)
.bind(public_offer.offer_duration_ts) .bind(public_offer.offer_duration_ts)
.bind(public_offer.bond_address_maker) .bind(public_offer.bond_address_maker)
.bind(trade_and_taker_info.offer.bond_locking_address.clone()) .bind(trade_and_taker_info.offer.bond_requirements.bond_address.clone())
.bind(public_offer.bond_amount_sat) .bind(public_offer.bond_amount_sat)
.bind(public_offer.bond_tx_hex_maker) .bind(public_offer.bond_tx_hex_maker)
.bind(trade_and_taker_info.trade_data.signed_bond_hex.clone()) .bind(trade_and_taker_info.trade_data.signed_bond_hex.clone())

View File

@ -13,8 +13,8 @@ hex = "0.4.3"
log = "0.4.21" log = "0.4.21"
musig2 = "0.0.11" musig2 = "0.0.11"
rand_core = "0.6.4" rand_core = "0.6.4"
reqwest = { version = "0.12.4", features = ["blocking", "json"] } reqwest = { version = "0.12", features = ["blocking", "json"] }
serde = "1.0.203" serde = "1.0"
sha2 = "0.10.8" sha2 = "0.10.8"
[profile.release] [profile.release]

View File

@ -13,10 +13,11 @@ pub struct OrderRequest {
// coordinator answer to maker step 1 // coordinator answer to maker step 1
// direct Json answer to step 1 (same request) // direct Json answer to step 1 (same request)
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize, Serialize, Clone)]
pub struct BondRequirementResponse { pub struct BondRequirementResponse {
pub bond_address: String, // address the bond ha/workspaces/taptrade-core/taptrade-cli-demo/trader/src/communications to be locked to pub bond_address: String, // address the bond ha/workspaces/taptrade-core/taptrade-cli-demo/trader/src/communications to be locked to
pub locking_amount_sat: u64, // min amount of the bond output in sat pub locking_amount_sat: u64, // min amount of the bond output in sat
pub escrow_locking_input_amount_without_trade_sum: u64, // minimum required amount of input to the escrow tx
} }
// maker step 2 // maker step 2
@ -76,8 +77,7 @@ pub struct PublicOffers {
pub struct PublicOffer { pub struct PublicOffer {
pub amount_sat: u64, pub amount_sat: u64,
pub offer_id_hex: String, pub offer_id_hex: String,
pub required_bond_amount_sat: u64, pub bond_requirements: BondRequirementResponse,
pub bond_locking_address: String, // its probably bad privacy to make the locking address static
} }
// request to receive the escrow psbt to sign for the specified offer to take it // request to receive the escrow psbt to sign for the specified offer to take it

View File

@ -20,8 +20,14 @@ impl ActiveOffer {
trading_wallet.trade_onchain_assembly(&offer_conditions, maker_config)?; trading_wallet.trade_onchain_assembly(&offer_conditions, maker_config)?;
// get necessary data for the coordinator to assemble the escrow locking psbt (inputs owned by maker, change address) // get necessary data for the coordinator to assemble the escrow locking psbt (inputs owned by maker, change address)
let input_amount = if maker_config.trade_type.is_buy_order() {
offer_conditions.escrow_locking_input_amount_without_trade_sum
} else {
offer_conditions.escrow_locking_input_amount_without_trade_sum
+ maker_config.trade_type.value()
};
let (psbt_inputs_hex_csv, escrow_change_address) = let (psbt_inputs_hex_csv, escrow_change_address) =
trading_wallet.get_escrow_psbt_inputs(offer_conditions.locking_amount_sat as i64)?; trading_wallet.get_escrow_psbt_inputs(input_amount)?;
debug!( debug!(
"Submitting maker bond: {:#?}", "Submitting maker bond: {:#?}",

View File

@ -69,7 +69,7 @@ pub fn run_maker(maker_config: &TraderSettings) -> Result<()> {
)?; )?;
// submit signed payout psbt back to coordinator // submit signed payout psbt back to coordinator
PayoutSignatureRequest::send(maker_config, &signature, &offer.offer_id_hex)?; PayoutSignatureRequest::send(maker_config, &signature, &offer.offer_id_hex)?;
// now the coordinator will broadcast the payout transaction and the trade should be finished debug!("now the coordinator will broadcast the payout transaction and the trade should be finished");
} else { } else {
warn!("Trader unsatisfied. Initiating escrow mode."); warn!("Trader unsatisfied. Initiating escrow mode.");
TradeObligationsUnsatisfied::request_escrow(&offer.offer_id_hex, maker_config)?; TradeObligationsUnsatisfied::request_escrow(&offer.offer_id_hex, maker_config)?;
@ -122,7 +122,7 @@ pub fn run_taker(taker_config: &TraderSettings) -> Result<()> {
// submit partial signature back to coordinator // submit partial signature back to coordinator
PayoutSignatureRequest::send(taker_config, &signature, &accepted_offer.offer_id_hex)?; PayoutSignatureRequest::send(taker_config, &signature, &accepted_offer.offer_id_hex)?;
// now the coordinator will broadcast the payout transaction and the trade should be finished debug!("now the coordinator will broadcast the payout transaction and the trade should be finished");
// here we need to handle if the other party is not cooperating // here we need to handle if the other party is not cooperating
} else { } else {
error!("Trader unsatisfied. Initiating escrow mode."); error!("Trader unsatisfied. Initiating escrow mode.");

View File

@ -12,18 +12,23 @@ impl ActiveOffer {
taker_config: &TraderSettings, taker_config: &TraderSettings,
offer: &PublicOffer, offer: &PublicOffer,
) -> Result<ActiveOffer> { ) -> Result<ActiveOffer> {
let bond_requirements = BondRequirementResponse {
bond_address: offer.bond_locking_address.clone(),
locking_amount_sat: offer.required_bond_amount_sat,
};
// assembly of the Bond transaction and generation of MuSig data and payout address // assembly of the Bond transaction and generation of MuSig data and payout address
let (bond, mut musig_data, payout_address) = let (bond, mut musig_data, payout_address) =
trading_wallet.trade_onchain_assembly(&bond_requirements, taker_config)?; trading_wallet.trade_onchain_assembly(&offer.bond_requirements, taker_config)?;
// get inputs and a change address necessary for the coordinator to assemble the escrow locking psbt // get inputs and a change address necessary for the coordinator to assemble the escrow locking psbt
let input_amount = if taker_config.trade_type.is_buy_order() {
offer
.bond_requirements
.escrow_locking_input_amount_without_trade_sum
} else {
offer
.bond_requirements
.escrow_locking_input_amount_without_trade_sum
+ taker_config.trade_type.value()
};
let (bdk_psbt_inputs_hex_csv, client_change_address) = let (bdk_psbt_inputs_hex_csv, client_change_address) =
trading_wallet.get_escrow_psbt_inputs(bond_requirements.locking_amount_sat as i64)?; trading_wallet.get_escrow_psbt_inputs(input_amount)?;
let bond_submission_request = BondSubmissionRequest { let bond_submission_request = BondSubmissionRequest {
robohash_hex: taker_config.robosats_robohash_hex.clone(), robohash_hex: taker_config.robosats_robohash_hex.clone(),

View File

@ -99,6 +99,7 @@ mod tests {
let bond_target = BondRequirementResponse { let bond_target = BondRequirementResponse {
bond_address: "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx".to_string(), bond_address: "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx".to_string(),
locking_amount_sat: 10000, locking_amount_sat: 10000,
escrow_locking_input_amount_without_trade_sum: 10000,
}; };
let trader_input = TraderSettings { let trader_input = TraderSettings {
electrum_endpoint: "ssl://mempool.space:40002".to_string(), electrum_endpoint: "ssl://mempool.space:40002".to_string(),
@ -123,6 +124,7 @@ mod tests {
let bond_target = BondRequirementResponse { let bond_target = BondRequirementResponse {
bond_address: "invalid_address".to_string(), bond_address: "invalid_address".to_string(),
locking_amount_sat: 10000, locking_amount_sat: 10000,
escrow_locking_input_amount_without_trade_sum: 10000,
}; };
let trader_input = TraderSettings { let trader_input = TraderSettings {
electrum_endpoint: "ssl://mempool.space:40002".to_string(), electrum_endpoint: "ssl://mempool.space:40002".to_string(),
@ -148,6 +150,7 @@ mod tests {
bond_address: "bc1p5d7rjq7g6rdk2yhzks9smlaqtedr4dekq08ge8ztwac72sfr9rusxg3297" bond_address: "bc1p5d7rjq7g6rdk2yhzks9smlaqtedr4dekq08ge8ztwac72sfr9rusxg3297"
.to_string(), .to_string(),
locking_amount_sat: 10000, locking_amount_sat: 10000,
escrow_locking_input_amount_without_trade_sum: 10000,
}; };
let trader_input = TraderSettings { let trader_input = TraderSettings {
electrum_endpoint: "ssl://mempool.space:40002".to_string(), electrum_endpoint: "ssl://mempool.space:40002".to_string(),
@ -172,6 +175,7 @@ mod tests {
let bond_target = BondRequirementResponse { let bond_target = BondRequirementResponse {
bond_address: "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx".to_string(), bond_address: "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx".to_string(),
locking_amount_sat: 10000000000, // Very high amount locking_amount_sat: 10000000000, // Very high amount
escrow_locking_input_amount_without_trade_sum: 10000,
}; };
let trader_input = TraderSettings { let trader_input = TraderSettings {
electrum_endpoint: "ssl://mempool.space:40002".to_string(), electrum_endpoint: "ssl://mempool.space:40002".to_string(),
@ -196,6 +200,7 @@ mod tests {
let bond_target = BondRequirementResponse { let bond_target = BondRequirementResponse {
bond_address: "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx".to_string(), bond_address: "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx".to_string(),
locking_amount_sat: 0, locking_amount_sat: 0,
escrow_locking_input_amount_without_trade_sum: 10000,
}; };
let trader_input = TraderSettings { let trader_input = TraderSettings {
electrum_endpoint: "ssl://mempool.space:40002".to_string(), electrum_endpoint: "ssl://mempool.space:40002".to_string(),

View File

@ -112,7 +112,8 @@ impl TradingWallet {
} }
/// returns suitable inputs (binary encoded using bincode, hex serialized, csv formatted) and a change address for the assembly of the escrow psbt (coordinator side) /// returns suitable inputs (binary encoded using bincode, hex serialized, csv formatted) and a change address for the assembly of the escrow psbt (coordinator side)
pub fn get_escrow_psbt_inputs(&self, mut amount_sat: i64) -> Result<(String, String)> { pub fn get_escrow_psbt_inputs(&self, amount_sat: u64) -> Result<(String, String)> {
let mut amount_sat = amount_sat as i64; // convert to signed int for subtraction
let mut inputs: Vec<String> = Vec::new(); let mut inputs: Vec<String> = Vec::new();
self.wallet.sync(&self.backend, SyncOptions::default())?; self.wallet.sync(&self.backend, SyncOptions::default())?;

View File

@ -7,7 +7,7 @@
/// nonce, as well as flags indicating whether it has been accessed for signing or sharing. /// nonce, as well as flags indicating whether it has been accessed for signing or sharing.
/// ///
/// The `generate` function in the `MusigNonce` implementation generates a new `MusigNonce` with a /// The `generate` function in the `MusigNonce` implementation generates a new `MusigNonce` with a
/// secret nonce based on the current timestamp. /// secret nonce based on the os rng salted with the current timestamp.
/// ///
/// The `get_sec_for_signing` function in the `MusigNonce` implementation returns the secret nonce /// The `get_sec_for_signing` function in the `MusigNonce` implementation returns the secret nonce
/// for signing, ensuring that it has not been accessed for signing before. /// for signing, ensuring that it has not been accessed for signing before.
@ -39,7 +39,7 @@ pub struct MuSigData {
pub secret_key: MusigSecretKey, pub secret_key: MusigSecretKey,
} }
// secret nonce has to be used only one time! /// nonce must not be used more than once
#[derive(Debug)] #[derive(Debug)]
pub struct MusigNonce { pub struct MusigNonce {
secret_nonce: SecNonce, secret_nonce: SecNonce,