Skip to content

Commit 8f5983b

Browse files
authored
Merge pull request #125 from stejbac/refactor-protocol-trade-model
Refactor trade model & pass contract-forming txids to RPC client
2 parents 5f8399a + 5bc8971 commit 8f5983b

10 files changed

Lines changed: 477 additions & 462 deletions

File tree

protocol/src/protocol_musig_adaptor.rs

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use bdk_electrum::BdkElectrumClient;
55
use bdk_electrum::electrum_client::Client;
66
use bdk_wallet::bitcoin::bip32::Xpriv;
77
use bdk_wallet::bitcoin::{
8-
Address, Amount, FeeRate, Network, OutPoint, Psbt, ScriptBuf, Transaction, TxOut, Txid,
8+
Address, Amount, FeeRate, Network, OutPoint, Psbt, Script, ScriptBuf, Transaction, TxOut, Txid,
99
relative,
1010
};
1111
use bdk_wallet::template::{Bip86, DescriptorTemplate as _};
@@ -333,7 +333,7 @@ impl BMPProtocol {
333333
ProtocolRole::Buyer => self.p_tik.peers_key_share(),
334334
}?.pub_key();
335335
// given the DepositTx, we can create SwapTx for Alice.
336-
self.swap_tx.build(&self.q_tik, adaptor_point, &self.deposit_tx, bob.swap_script.as_ref())?;
336+
self.swap_tx.build(&self.q_tik, adaptor_point, &self.deposit_tx, bob.swap_script.as_deref())?;
337337
// let start the signing process for SwapTx already.
338338
let swap_pub_nonce = self.swap_tx.get_pub_nonce(); // could be one round earlier, if we solve secure nonce generation
339339

@@ -377,8 +377,7 @@ impl BMPProtocol {
377377
assert_eq!(bob.p_agg, *self.p_tik.aggregated_key()?.pub_key(), "Bob is sending the wrong P' for his aggregated key.");
378378
assert_eq!(bob.q_agg, *self.q_tik.aggregated_key()?.pub_key(), "Bob is sending the wrong Q' for his aggregated key.");
379379

380-
// let txid = self.deposit_tx.transfer_sig_and_broadcast(&mut self.ctx, bob.deposit_tx_merged)?;
381-
let txid = self.deposit_tx.tx()?.compute_txid();
380+
let txid = self.deposit_tx.txid()?;
382381
// here we are building the partial signature of the SwapTx, note that there is only one SwapTx (for Alice)
383382
let swap_part_sig = self.swap_tx.build_partial_sig(bob.swap_pub_nonce)?;
384383

@@ -464,8 +463,7 @@ impl RedirectTx {
464463
let fee_rate = FeeRate::from_sat_per_vb_unchecked(10); // TODO: feerates shall come from pricenodes
465464

466465
let escrow_amount = warn_tx.builder.escrow()?.prevout.value;
467-
let available_amount_msat = RedirectTxBuilder::available_amount_msat(escrow_amount, fee_rate)
468-
.ok_or(anyhow::anyhow!("Overflow computing available amount for receivers"))?;
466+
let available_amount_msat = RedirectTxBuilder::available_amount_msat(escrow_amount, fee_rate)?;
469467

470468
let receivers = Receiver::compute_receivers_from_shares(receiver_shares, available_amount_msat, fee_rate)
471469
.ok_or(anyhow::anyhow!("Could not compute receiver list"))?;
@@ -582,15 +580,9 @@ impl WarningTx {
582580

583581
pub fn signed_tx(&self) -> anyhow::Result<&Transaction> { Ok(self.builder.signed_tx()?) }
584582

585-
pub fn funds_as_outpoint(&self) -> OutPoint {
586-
OutPoint::new(self.unsigned_tx().unwrap().compute_txid(), 0)
587-
}
583+
pub fn funds_as_outpoint(&self) -> OutPoint { self.builder.escrow().unwrap().outpoint }
588584

589-
pub fn funds_as_output(&self) -> TxOut {
590-
let w = self.unsigned_tx().unwrap();
591-
let wout: &Vec<TxOut> = w.output.as_ref();
592-
wout[0].clone()
593-
}
585+
pub fn funds_as_output(&self) -> TxOut { self.builder.escrow().unwrap().prevout }
594586

595587
pub fn new(role: ProtocolRole) -> Self {
596588
Self {
@@ -614,17 +606,17 @@ impl WarningTx {
614606
ProtocolRole::Buyer => p_tik
615607
}.with_taproot_tweak(None)?;
616608

617-
let tx = self.builder
609+
let txid = self.builder
618610
.set_buyer_input(deposit_tx.builder.buyer_payout()?.clone())
619611
.set_seller_input(deposit_tx.builder.seller_payout()?.clone())
620612
.set_escrow_address(key_spend.p2tr_address(Network::Regtest))
621613
.set_anchor_address(Address::from_script(self.anchor_spend.as_ref().unwrap(), Network::Regtest)?) // TODO: Improve.
622614
.set_lock_time(relative::LockTime::ZERO)
623615
.set_fee_rate(FeeRate::from_sat_per_vb_unchecked(10)) // TODO: feerates shall come from pricenodes
624616
.compute_unsigned_tx()?
625-
.unsigned_tx()?;
617+
.txid()?;
626618

627-
dbg!(ctx.role, self.role, tx.compute_txid());
619+
dbg!(ctx.role, self.role, txid);
628620
Ok(())
629621
}
630622

@@ -700,15 +692,15 @@ impl SwapTx {
700692
}
701693

702694
// round 1
703-
pub fn build(&mut self, q_tik: &KeyCtx, p_a: Point, deposit_tx: &DepositTx, swap_spend_opt: Option<&ScriptBuf>) -> anyhow::Result<()> {
695+
pub fn build(&mut self, q_tik: &KeyCtx, p_a: Point, deposit_tx: &DepositTx, swap_spend_opt: Option<&Script>) -> anyhow::Result<()> {
704696
self.fund_sig.set_tweaked_key_ctx(q_tik.with_taproot_tweak(None)?);
705697
// SwapTx is asymmetric, both parties need to agree on P_a being the public adaptor
706698
// P_a is the Public key which Alice (the seller) contributes to 2of2 Multisig to lock the deposit and trade amount in the DepositTx
707699
// if secret key of P_a is revealed to Bob, then we has both partial keys to it and is able to spend it.
708700
self.fund_sig.set_adaptor_point(p_a)?;
709701
self.fund_sig.init_my_nonce_share()?;
710702
let Some(use_spend) = (match self.role {
711-
ProtocolRole::Seller => self.swap_spend.as_ref(),
703+
ProtocolRole::Seller => self.swap_spend.as_deref(),
712704
ProtocolRole::Buyer => swap_spend_opt,
713705
}) else { panic!("No spend-condition from role {:?}", self.role) };
714706

@@ -781,7 +773,7 @@ impl DepositTx {
781773

782774
fn merged_psbt(&self) -> anyhow::Result<&Psbt> { Ok(self.builder.psbt()?) }
783775

784-
fn tx(&self) -> anyhow::Result<&Transaction> { Ok(&self.merged_psbt()?.unsigned_tx) }
776+
fn txid(&self) -> anyhow::Result<Txid> { Ok(*self.builder.txid()?) }
785777

786778
pub fn generate_part_tx(&mut self, ctx: &mut BMPContext) -> anyhow::Result<Psbt> {
787779
self.builder
@@ -837,13 +829,13 @@ impl DepositTx {
837829
Ok(deposit_txid)
838830
}
839831

840-
fn _get_outpoint_for(self, script: &ScriptBuf) -> anyhow::Result<OutPoint> {
841-
let tx = self.tx()?;
832+
fn _get_outpoint_for(self, script: &Script) -> anyhow::Result<OutPoint> {
833+
let tx = &self.merged_psbt()?.unsigned_tx;
842834

843835
for (index, output) in tx.output.iter().enumerate() {
844836
if output.script_pubkey == *script {
845837
return Ok(OutPoint {
846-
txid: tx.compute_txid(),
838+
txid: self.txid()?,
847839
vout: u32::try_from(index)?,
848840
});
849841
}

protocol/src/psbt.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,7 @@ impl<Cs: Iterator<Item=TxOutput>, As: Iterator<Item=Address>> TradeWallet for Mo
8080
output.extend(trade_fee_receivers.iter().map(TxOut::from));
8181
let mut change_output = TxOut { value: Amount::ZERO, script_pubkey: self.new_address()?.script_pubkey() };
8282

83-
let mut cost_msat = Receiver::total_output_cost_msat(trade_fee_receivers, fee_rate, 2)
84-
.ok_or(TransactionErrorKind::Overflow)?
83+
let mut cost_msat = Receiver::total_output_cost_msat(trade_fee_receivers, fee_rate, 2)?
8584
.checked_add(deposit_amount_msat)
8685
.ok_or(TransactionErrorKind::Overflow)?
8786
.checked_add(fee_cost_msat(HALF_DEPOSIT_TX_BASE_WEIGHT)?)

protocol/src/receiver.rs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use bdk_wallet::bitcoin::address::{NetworkChecked, NetworkUnchecked, NetworkVali
44
use bdk_wallet::bitcoin::amount::CheckedSum as _;
55
use bdk_wallet::bitcoin::{Address, Amount, FeeRate, Network, TxOut, Weight};
66

7-
use crate::transaction::Result;
7+
use crate::transaction::{Result, TransactionErrorKind};
88

99
// Receivers paid less than this absolute satoshi amount are excluded:
1010
const MIN_OUTPUT_AMOUNT: Amount = Amount::from_sat(1000);
@@ -32,21 +32,23 @@ impl Receiver {
3232
amount_msat.checked_add(fee_msat)
3333
}
3434

35-
pub fn total_output_cost_msat<'a, I>(receivers: I, fee_rate: FeeRate, extra_output_num: u16) -> Option<u64>
35+
pub fn total_output_cost_msat<'a, I>(receivers: I, fee_rate: FeeRate, extra_output_num: u16) -> Result<u64>
3636
where I: IntoIterator<Item=&'a Self>
3737
{
38-
let mut cost = 0u64;
39-
let mut num = extra_output_num;
40-
for receiver in receivers {
41-
cost = cost.checked_add(receiver.output_cost_msat(fee_rate)?)?;
42-
// Fail if more than 65535 outputs, which will never happen for a standard tx:
43-
num = num.checked_add(1)?;
44-
}
45-
if num > 252 {
46-
// For more than 252 outputs, we get a 3-byte length encoding instead of 1, adding 8 wu.
47-
cost = cost.checked_add(fee_rate.to_sat_per_kwu().checked_mul(8)?)?;
48-
}
49-
Some(cost)
38+
(|| {
39+
let mut cost = 0u64;
40+
let mut num = extra_output_num;
41+
for receiver in receivers {
42+
cost = cost.checked_add(receiver.output_cost_msat(fee_rate)?)?;
43+
// Fail if more than 65535 outputs, which will never happen for a standard tx:
44+
num = num.checked_add(1)?;
45+
}
46+
if num > 252 {
47+
// For >252 outputs, we get a 3-byte length encoding instead of 1, adding 8 wu.
48+
cost = cost.checked_add(fee_rate.to_sat_per_kwu().checked_mul(8)?)?;
49+
}
50+
Some(cost)
51+
})().ok_or(TransactionErrorKind::Overflow)
5052
}
5153

5254
// TODO: Consider returning a `Result<T>` instead of an `Option<T>` to distinguish overflows

protocol/src/transaction.rs

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use bdk_wallet::bitcoin::taproot::{Signature, TAPROOT_ANNEX_PREFIX, TaprootBuild
1010
use bdk_wallet::bitcoin::transaction::Version;
1111
use bdk_wallet::bitcoin::{
1212
Address, Amount, FeeRate, Network, OutPoint, Psbt, ScriptBuf, Sequence, TapNodeHash,
13-
TapSighash, TapSighashType, Transaction, TxIn, TxOut, Weight, Witness, XOnlyPublicKey,
13+
TapSighash, TapSighashType, Transaction, TxIn, TxOut, Txid, Weight, Witness, XOnlyPublicKey,
1414
absolute, relative, script, secp256k1,
1515
};
1616
use paste::paste;
@@ -93,9 +93,12 @@ impl NetworkParams for Network {
9393

9494
macro_rules! make_getter {
9595
($field_name:ident: $field_type:ident) => {
96+
make_getter!($field_name: $field_type: $field_type);
97+
};
98+
($field_name:ident: $field_type:ident: $base_field_type:ident) => {
9699
paste! {
97100
pub fn $field_name(&self) -> Result<&$field_type> {
98-
self.$field_name.as_ref().ok_or(TransactionErrorKind::[<Missing $field_type>])
101+
self.$field_name.as_ref().ok_or(TransactionErrorKind::[<Missing $base_field_type>])
99102
}
100103
}
101104
};
@@ -253,9 +256,9 @@ impl DepositTxBuilder {
253256
make_getter_setter!(fee_rate: FeeRate);
254257
make_getter_setter!(buyers_half_psbt: Psbt);
255258
make_getter_setter!(sellers_half_psbt: Psbt);
256-
make_getter!(psbt: Psbt);
257-
make_getter!(buyer_payout: TxOutput);
258-
make_getter!(seller_payout: TxOutput);
259+
make_getter!(psbt: Psbt: Transaction);
260+
make_getter!(buyer_payout: TxOutput: Transaction);
261+
make_getter!(seller_payout: TxOutput: Transaction);
259262

260263
fn sellers_trade_deposit(&self) -> Result<Amount> {
261264
self.trade_amount()?.checked_add(*self.sellers_security_deposit()?)
@@ -316,6 +319,8 @@ impl DepositTxBuilder {
316319
Ok(self)
317320
}
318321

322+
pub fn txid(&self) -> Result<&Txid> { Ok(&self.buyer_payout()?.outpoint.txid) }
323+
319324
pub fn sign_buyer_inputs(&mut self, trade_wallet: &(impl TradeWallet + ?Sized)) -> Result<&mut Self> {
320325
self.sign_matching_inputs(trade_wallet, &prevout_set(self.buyers_half_psbt()?))
321326
}
@@ -329,14 +334,14 @@ impl DepositTxBuilder {
329334
trade_wallet: &(impl TradeWallet + ?Sized),
330335
prevouts: &BTreeSet<OutPoint>,
331336
) -> Result<&mut Self> {
332-
let psbt = self.psbt.as_mut().ok_or(TransactionErrorKind::MissingPsbt)?;
337+
let psbt = self.psbt.as_mut().ok_or(TransactionErrorKind::MissingTransaction)?;
333338
trade_wallet.sign_selected_inputs(psbt, &|o| prevouts.contains(o))?;
334339
Ok(self)
335340
}
336341

337342
pub fn combine_psbts(&mut self, other: Psbt) -> Result<&mut Self> {
338343
// TODO: We may need to do some validation of the provided PSBT.
339-
self.psbt.as_mut().ok_or(TransactionErrorKind::MissingPsbt)?.combine(other)?;
344+
self.psbt.as_mut().ok_or(TransactionErrorKind::MissingTransaction)?.combine(other)?;
340345
Ok(self)
341346
}
342347

@@ -357,6 +362,7 @@ pub struct WarningTxBuilder {
357362
// Derived fields:
358363
unsigned_tx: Option<Transaction>,
359364
signed_tx: Option<Transaction>,
365+
txid: Option<Txid>,
360366
}
361367

362368
impl WarningTxBuilder {
@@ -370,19 +376,20 @@ impl WarningTxBuilder {
370376
make_getter_setter!(seller_input_signature: Signature);
371377
make_getter!(unsigned_tx: Transaction);
372378
make_getter!(signed_tx: Transaction);
379+
make_getter!(txid: Txid: Transaction);
373380

374-
pub fn escrow_amount(input_amounts: impl IntoIterator<Item=Amount>, fee_rate: FeeRate) -> Option<Amount> {
375-
input_amounts.into_iter().checked_sum()?
381+
pub fn escrow_amount(input_amounts: impl IntoIterator<Item=Amount>, fee_rate: FeeRate) -> Result<Amount> {
382+
(|| input_amounts.into_iter().checked_sum()?
376383
.checked_sub(ANCHOR_AMOUNT)?
377384
.checked_sub(fee_rate.checked_mul_by_weight(SIGNED_WARNING_TX_WEIGHT)?)
385+
)().ok_or(TransactionErrorKind::Overflow)
378386
}
379387

380388
pub fn compute_unsigned_tx(&mut self) -> Result<&mut Self> {
381389
let escrow_output = TxOut {
382390
value: Self::escrow_amount(
383391
[self.buyer_input()?.prevout.value, self.seller_input()?.prevout.value],
384-
*self.fee_rate()?,
385-
).ok_or(TransactionErrorKind::Overflow)?,
392+
*self.fee_rate()?)?,
386393
script_pubkey: self.escrow_address()?.script_pubkey(),
387394
};
388395
let anchor_output = TxOut {
@@ -395,14 +402,13 @@ impl WarningTxBuilder {
395402
input: self.tx_ins(*self.lock_time()?)?.to_vec(),
396403
output: vec![escrow_output, anchor_output],
397404
};
398-
self.unsigned_tx.get_or_insert(tx);
405+
self.txid = Some(self.unsigned_tx.get_or_insert(tx).compute_txid());
399406
Ok(self)
400407
}
401408

402409
pub fn escrow(&self) -> Result<TxOutput> {
403-
let txid = self.unsigned_tx()?.compute_txid();
404410
let output = self.unsigned_tx()?.output[0].clone();
405-
Ok(TxOutput::new(OutPoint::new(txid, 0), output))
411+
Ok(TxOutput::new(OutPoint::new(*self.txid()?, 0), output))
406412
}
407413

408414
pub fn buyer_input_sighash(&self) -> Result<TapSighash> {
@@ -437,6 +443,7 @@ pub struct RedirectTxBuilder {
437443
// Derived fields:
438444
unsigned_tx: Option<Transaction>,
439445
signed_tx: Option<Transaction>,
446+
txid: Option<Txid>,
440447
}
441448

442449
impl RedirectTxBuilder {
@@ -447,14 +454,17 @@ impl RedirectTxBuilder {
447454
make_getter_setter!(input_signature: Signature);
448455
make_getter!(unsigned_tx: Transaction);
449456
make_getter!(signed_tx: Transaction);
457+
make_getter!(txid: Txid: Transaction);
450458

451-
pub fn available_amount_msat(escrow_amount: Amount, fee_rate: FeeRate) -> Option<u64> {
452-
let redirection_tx_base_fee = fee_rate.to_sat_per_kwu()
453-
.checked_mul(SIGNED_REDIRECT_TX_BASE_WEIGHT.to_wu())?;
454-
escrow_amount
455-
.checked_sub(ANCHOR_AMOUNT)?
456-
.to_sat().checked_mul(1000)?
457-
.checked_sub(redirection_tx_base_fee)
459+
pub fn available_amount_msat(escrow_amount: Amount, fee_rate: FeeRate) -> Result<u64> {
460+
(|| {
461+
let redirection_tx_base_fee = fee_rate.to_sat_per_kwu()
462+
.checked_mul(SIGNED_REDIRECT_TX_BASE_WEIGHT.to_wu())?;
463+
escrow_amount
464+
.checked_sub(ANCHOR_AMOUNT)?
465+
.to_sat().checked_mul(1000)?
466+
.checked_sub(redirection_tx_base_fee)
467+
})().ok_or(TransactionErrorKind::Overflow)
458468
}
459469

460470
pub fn compute_unsigned_tx(&mut self) -> Result<&mut Self> {
@@ -470,7 +480,7 @@ impl RedirectTxBuilder {
470480
input: self.tx_ins(*self.lock_time()?)?.to_vec(),
471481
output,
472482
};
473-
self.unsigned_tx.get_or_insert(tx);
483+
self.txid = Some(self.unsigned_tx.get_or_insert(tx).compute_txid());
474484
Ok(self)
475485
}
476486

rpc/build.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
2828
"ReceiverAddressAndAmount", "PartialSignaturesRequest", "DepositTxSignatureRequest",
2929
"PublishDepositTxRequest", "SubscribeTxConfirmationStatusRequest"
3030
])
31+
.serde_serialized_type("ContractualTxIds", &[
32+
rev_hex("depositTxId"), rev_hex("buyersWarningTxId"), rev_hex("sellersWarningTxId"),
33+
rev_hex("buyersRedirectTxId"), rev_hex("sellersRedirectTxId")
34+
])
3135
.serde_serialized_type("PubKeySharesRequest", &[
3236
enum_field("myRole", "Role")
3337
])

rpc/src/main/java/bisq/TradeProtocolClient.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ private void setupTakerIsBuyerTrade(String buyerTradeId, String sellerTradeId) {
128128

129129
var sellerDepositPsbt = stub.signDepositTx(DepositTxSignatureRequest.newBuilder()
130130
.setTradeId(sellerTradeId)
131-
.setPeersPartialSignatures(buyerPartialSignatureMessage)
131+
// Don't include contract-forming (Deposit/Warning/Redirect) txids, as they are ignored:
132+
.setPeersPartialSignatures(buyerPartialSignatureMessage.toBuilder().clearContractualTxIds())
132133
.build());
133134
System.out.println("Got reply: " + sellerDepositPsbt);
134135

@@ -141,7 +142,8 @@ private void setupTakerIsBuyerTrade(String buyerTradeId, String sellerTradeId) {
141142

142143
var buyerDepositPsbt = stub.signDepositTx(DepositTxSignatureRequest.newBuilder()
143144
.setTradeId(buyerTradeId)
144-
.setPeersPartialSignatures(sellerPartialSignatureMessage)
145+
// Don't include contract-forming (Deposit/Warning/Redirect) txids, as they are ignored:
146+
.setPeersPartialSignatures(sellerPartialSignatureMessage.toBuilder().clearContractualTxIds())
145147
.build());
146148
System.out.println("Got reply: " + buyerDepositPsbt);
147149

@@ -219,7 +221,8 @@ private void setupTakerIsSellerTrade(String buyerTradeId, String sellerTradeId)
219221

220222
var buyerDepositPsbt = stub.signDepositTx(DepositTxSignatureRequest.newBuilder()
221223
.setTradeId(buyerTradeId)
222-
.setPeersPartialSignatures(sellerPartialSignatureMessage)
224+
// Don't include contract-forming (Deposit/Warning/Redirect) txids, as they are ignored:
225+
.setPeersPartialSignatures(sellerPartialSignatureMessage.toBuilder().clearContractualTxIds())
223226
.build());
224227
System.out.println("Got reply: " + buyerDepositPsbt);
225228

@@ -232,7 +235,8 @@ private void setupTakerIsSellerTrade(String buyerTradeId, String sellerTradeId)
232235

233236
var sellerDepositPsbt = stub.signDepositTx(DepositTxSignatureRequest.newBuilder()
234237
.setTradeId(sellerTradeId)
235-
.setPeersPartialSignatures(buyerPartialSignatureMessage)
238+
// Don't include contract-forming (Deposit/Warning/Redirect) txids, as they are ignored:
239+
.setPeersPartialSignatures(buyerPartialSignatureMessage.toBuilder().clearContractualTxIds())
236240
.build());
237241
System.out.println("Got reply: " + sellerDepositPsbt);
238242

0 commit comments

Comments
 (0)