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
strip = true
[lib]
name = "coordinator"
path = "src/main.rs"
# [lib]
# name = "coordinator"
# 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
}
#[derive(Serialize, PartialEq, Debug, Validate)]
#[derive(Serialize, PartialEq, Debug, Validate, Deserialize)]
pub struct BondRequirementResponse {
pub bond_address: String,
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
@ -124,8 +125,7 @@ pub struct OffersRequest {
pub struct PublicOffer {
pub amount_sat: u64,
pub offer_id_hex: String,
pub required_bond_amount_sat: u64,
pub bond_locking_address: String,
pub bond_requirements: BondRequirementResponse,
}
#[derive(Deserialize, Serialize, Debug)]

View File

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

View File

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

View File

@ -13,10 +13,11 @@ pub struct OrderRequest {
// coordinator answer to maker step 1
// direct Json answer to step 1 (same request)
#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, Serialize, Clone)]
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 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
@ -76,8 +77,7 @@ pub struct PublicOffers {
pub struct PublicOffer {
pub amount_sat: u64,
pub offer_id_hex: String,
pub required_bond_amount_sat: u64,
pub bond_locking_address: String, // its probably bad privacy to make the locking address static
pub bond_requirements: BondRequirementResponse,
}
// 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)?;
// 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) =
trading_wallet.get_escrow_psbt_inputs(offer_conditions.locking_amount_sat as i64)?;
trading_wallet.get_escrow_psbt_inputs(input_amount)?;
debug!(
"Submitting maker bond: {:#?}",

View File

@ -69,7 +69,7 @@ pub fn run_maker(maker_config: &TraderSettings) -> Result<()> {
)?;
// submit signed payout psbt back to coordinator
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 {
warn!("Trader unsatisfied. Initiating escrow mode.");
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
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
} else {
error!("Trader unsatisfied. Initiating escrow mode.");

View File

@ -12,18 +12,23 @@ impl ActiveOffer {
taker_config: &TraderSettings,
offer: &PublicOffer,
) -> 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
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
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) =
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 {
robohash_hex: taker_config.robosats_robohash_hex.clone(),

View File

@ -99,6 +99,7 @@ mod tests {
let bond_target = BondRequirementResponse {
bond_address: "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx".to_string(),
locking_amount_sat: 10000,
escrow_locking_input_amount_without_trade_sum: 10000,
};
let trader_input = TraderSettings {
electrum_endpoint: "ssl://mempool.space:40002".to_string(),
@ -123,6 +124,7 @@ mod tests {
let bond_target = BondRequirementResponse {
bond_address: "invalid_address".to_string(),
locking_amount_sat: 10000,
escrow_locking_input_amount_without_trade_sum: 10000,
};
let trader_input = TraderSettings {
electrum_endpoint: "ssl://mempool.space:40002".to_string(),
@ -148,6 +150,7 @@ mod tests {
bond_address: "bc1p5d7rjq7g6rdk2yhzks9smlaqtedr4dekq08ge8ztwac72sfr9rusxg3297"
.to_string(),
locking_amount_sat: 10000,
escrow_locking_input_amount_without_trade_sum: 10000,
};
let trader_input = TraderSettings {
electrum_endpoint: "ssl://mempool.space:40002".to_string(),
@ -172,6 +175,7 @@ mod tests {
let bond_target = BondRequirementResponse {
bond_address: "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx".to_string(),
locking_amount_sat: 10000000000, // Very high amount
escrow_locking_input_amount_without_trade_sum: 10000,
};
let trader_input = TraderSettings {
electrum_endpoint: "ssl://mempool.space:40002".to_string(),
@ -196,6 +200,7 @@ mod tests {
let bond_target = BondRequirementResponse {
bond_address: "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx".to_string(),
locking_amount_sat: 0,
escrow_locking_input_amount_without_trade_sum: 10000,
};
let trader_input = TraderSettings {
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)
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();
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.
///
/// 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
/// for signing, ensuring that it has not been accessed for signing before.
@ -39,7 +39,7 @@ pub struct MuSigData {
pub secret_key: MusigSecretKey,
}
// secret nonce has to be used only one time!
/// nonce must not be used more than once
#[derive(Debug)]
pub struct MusigNonce {
secret_nonce: SecNonce,