Skip to content

Commit 3fb4271

Browse files
committed
Scan for po's handled in a cospend
Mark po's as success if they are handled in the a cospend.
1 parent a08523b commit 3fb4271

5 files changed

Lines changed: 121 additions & 107 deletions

File tree

mppj/results.json

Lines changed: 25 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,19 @@
11
{
22
"total_payment_obligations": 10,
3-
"percentage_payment_obligations_missed": 1.0,
4-
"total_block_weight_wu": 16132,
5-
"average_fee_cost_sats": 143,
6-
"dust_utxo_count": 5,
3+
"percentage_payment_obligations_missed": 0.8,
4+
"total_block_weight_wu": 12742,
5+
"average_fee_cost_sats": 471,
6+
"dust_utxo_count": 1,
77
"utxo_size_distribution_sats": [
88
152,
9-
152,
10-
152,
11-
152,
12-
152,
13-
821,
14-
821,
15-
821,
16-
821,
17-
821,
18-
821,
19-
821,
209
821,
21-
8422,
22-
4999991468,
23-
4999996768,
24-
4999997024,
25-
4999999027,
26-
4999999289,
10+
4483,
11+
8521,
12+
11851,
13+
4999988039,
14+
4999991369,
15+
4999995407,
16+
4999998886,
2717
5000000000,
2818
5000000000,
2919
5000000000,
@@ -78,39 +68,40 @@
7868
5000000000,
7969
5000000000,
8070
5000000000,
81-
5000000110,
82-
5000000141,
83-
5000000141,
84-
5000000141,
85-
5000000141
71+
5000000000,
72+
5000000000,
73+
5000000000,
74+
5000000000,
75+
5000000000,
76+
5000000471
8677
],
8778
"wallet_utxo_stats": [
8879
{
8980
"wallet_id": 0,
9081
"dust_count": 0,
91-
"total_count": 47,
82+
"total_count": 48,
9283
"p50_sats": 5000000000,
93-
"p90_sats": 5000000110
84+
"p90_sats": 5000000000
9485
},
9586
{
9687
"wallet_id": 1,
97-
"dust_count": 5,
98-
"total_count": 10,
99-
"p50_sats": 152,
88+
"dust_count": 1,
89+
"total_count": 6,
90+
"p50_sats": 5000000000,
10091
"p90_sats": 5000000000
10192
},
10293
{
10394
"wallet_id": 2,
10495
"dust_count": 0,
105-
"total_count": 7,
96+
"total_count": 8,
10697
"p50_sats": 5000000000,
10798
"p90_sats": 5000000000
10899
},
109100
{
110101
"wallet_id": 3,
111102
"dust_count": 0,
112-
"total_count": 14,
113-
"p50_sats": 821,
103+
"total_count": 7,
104+
"p50_sats": 5000000000,
114105
"p90_sats": 5000000000
115106
}
116107
]

src/actions.rs

Lines changed: 57 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -99,23 +99,23 @@ pub(crate) struct ParticipateMultiPartyPayjoinOutcome {
9999
base_cost: f64,
100100
}
101101

102-
#[derive(Debug)]
103-
pub(crate) struct RespondToPayjoinOutcome {
104-
/// Base cost: fee_paid + amount handled. In sats
105-
base_cost: f64,
106-
}
107-
108-
impl RespondToPayjoinOutcome {
109-
fn cost(&self) -> ActionCost {
110-
// Responding to a payjoin should always be better than unilaterally spending at this point
111-
// As there is no interaction cost. TODO in the future we will want to model the cost of doing
112-
// the last round of interaction with the counterparty as a function of rounds remaining.
113-
ActionCost(0.0)
114-
}
115-
}
116-
117-
#[derive(Debug)]
118-
// TODO: model the participation utility as a linear function of the progression of the session
102+
#[derive(Debug)]
103+
pub(crate) struct RespondToPayjoinOutcome {
104+
/// Base cost: fee_paid + amount handled. In sats
105+
base_cost: f64,
106+
}
107+
108+
impl RespondToPayjoinOutcome {
109+
fn cost(&self) -> ActionCost {
110+
// Responding to a payjoin should always be better than unilaterally spending at this point
111+
// As there is no interaction cost. TODO in the future we will want to model the cost of doing
112+
// the last round of interaction with the counterparty as a function of rounds remaining.
113+
ActionCost(0.0)
114+
}
115+
}
116+
117+
#[derive(Debug)]
118+
// TODO: model the participation utility as a linear function of the progression of the session
119119
pub(crate) struct AcceptCospendOutcome;
120120

121121
impl AcceptCospendOutcome {
@@ -449,51 +449,51 @@ impl Strategy for BatchSpender {
449449
}
450450

451451
#[derive(Debug, Clone)]
452-
pub(crate) struct MultipartyPayjoinInitiatorStrategy;
453-
454-
impl Strategy for MultipartyPayjoinInitiatorStrategy {
455-
fn enumerate_candidate_actions(&self, state: &WalletView) -> Vec<Action> {
456-
if state.payment_obligations.is_empty() {
457-
return vec![Action::Wait];
458-
}
459-
// TODO: if the sesion is on going do not intiate a new one
460-
// TODO: this is scaffolding for now, peers in the future will evaluate if they should initiate a multi-party payjoin given the number of payment obligations
461-
if state.wallet_id != WalletId(0) {
462-
return vec![Action::Wait];
463-
}
464-
let receivers = state
465-
.payment_obligations
466-
.iter()
467-
.map(|po| po.to)
468-
.collect::<HashSet<_>>();
469-
if receivers.len() < 2 {
470-
return vec![Action::Wait];
471-
}
472-
473-
// TODO: only one multi-party payjoin session can be active at a time FOR NOW
474-
let mut actions = vec![];
452+
pub(crate) struct MultipartyPayjoinInitiatorStrategy;
453+
454+
impl Strategy for MultipartyPayjoinInitiatorStrategy {
455+
fn enumerate_candidate_actions(&self, state: &WalletView) -> Vec<Action> {
456+
if state.payment_obligations.is_empty() {
457+
return vec![Action::Wait];
458+
}
459+
// TODO: if the sesion is on going do not intiate a new one
460+
// TODO: this is scaffolding for now, peers in the future will evaluate if they should initiate a multi-party payjoin given the number of payment obligations
461+
if state.wallet_id != WalletId(0) {
462+
return vec![Action::Wait];
463+
}
464+
let receivers = state
465+
.payment_obligations
466+
.iter()
467+
.map(|po| po.to)
468+
.collect::<HashSet<_>>();
469+
if receivers.len() < 2 {
470+
return vec![Action::Wait];
471+
}
472+
473+
// TODO: only one multi-party payjoin session can be active at a time FOR NOW
474+
let mut actions = vec![];
475475
if !state.active_cospends.is_empty() {
476-
// If we have an active session we should actively participate in it
476+
// If we have an active session we should actively participate in it
477477
debug_assert!(state.active_cospends.len() <= 1);
478478
for bulletin_board_id in state.active_cospends.iter() {
479479
actions.push(Action::ContinueParticipateInCospend(*bulletin_board_id));
480-
}
481-
return actions;
482-
}
483-
480+
}
481+
return actions;
482+
}
483+
484484
actions.push(Action::CreateCospendProposal(
485-
state.payment_obligations.iter().map(|po| po.id).collect(),
486-
));
487-
488-
actions
489-
}
490-
491-
fn clone_box(&self) -> Box<dyn Strategy> {
492-
Box::new(self.clone())
493-
}
494-
}
495-
496-
#[derive(Debug, Clone)]
485+
state.payment_obligations.iter().map(|po| po.id).collect(),
486+
));
487+
488+
actions
489+
}
490+
491+
fn clone_box(&self) -> Box<dyn Strategy> {
492+
Box::new(self.clone())
493+
}
494+
}
495+
496+
#[derive(Debug, Clone)]
497497
pub(crate) struct MakerStrategy;
498498

499499
impl Strategy for MakerStrategy {

src/blocks.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,20 @@ impl<'a> BroadcastSetHandleMut<'a> {
204204
{
205205
info.handled_payment_obligations
206206
.extend(payment_obligation_ids.iter().map(|id| *id));
207+
208+
for (_, mppj_session) in info.active_multi_party_payjoins.iter() {
209+
if mppj_session
210+
.tx_template
211+
.inputs
212+
.iter()
213+
.any(|i| i.outpoint == input.data().outpoint)
214+
{
215+
info.handled_payment_obligations.extend(
216+
mppj_session.payment_obligation_ids.iter().copied(),
217+
);
218+
break;
219+
}
220+
}
207221
}
208222
})
209223
}

src/tx_contruction.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ pub(crate) struct MultiPartyPayjoinSession {
2222
pub(crate) tx_template: TxData,
2323
/// The state of the session
2424
pub(crate) state: TxConstructionState,
25+
/// True if this wallet created the session (taker/initiator), false for makers/participants
26+
pub(crate) is_initiator: bool,
2527
}
2628

2729
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -30,7 +32,7 @@ pub(crate) enum TxConstructionState {
3032
SentInputs,
3133
SentOutputs,
3234
SentReadyToSign,
33-
Success(TxId),
35+
Success(Option<TxId>),
3436
}
3537

3638
#[derive(Debug)]

src/wallet.rs

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ define_entity_info!(Wallet, {
5454
/// Set of multi-party payjoin sessions that this wallet is participating in
5555
pub(crate) active_multi_party_payjoins: HashMap<BulletinBoardId, MultiPartyPayjoinSession>,
5656
/// UTXOs registered in the order book by this wallet
57+
// TODO: this should be moved to wallet data
5758
pub(crate) registered_inputs: OrdSet<Outpoint>,
5859
}
5960
);
@@ -248,9 +249,6 @@ impl<'a> WalletHandleMut<'a> {
248249
}
249250

250251
fn create_multi_party_payjoin_session(&mut self, po_ids: &[PaymentObligationId]) {
251-
if self.id.0 != 0 {
252-
return; // TODO For now the first wallet is the leader
253-
}
254252
// First we create the bulletin board
255253
let bulletin_board_id = self.sim.create_bulletin_board();
256254
// Then we invite all members to join
@@ -273,6 +271,7 @@ impl<'a> WalletHandleMut<'a> {
273271
payment_obligation_ids: po_ids.to_owned(),
274272
tx_template,
275273
state: TxConstructionState::SentInputs,
274+
is_initiator: true,
276275
};
277276
self.info_mut()
278277
.active_multi_party_payjoins
@@ -343,22 +342,26 @@ impl<'a> WalletHandleMut<'a> {
343342
TxConstructionState::SentReadyToSign => {
344343
let t = SentReadyToSign::new(self.sim, *bulletin_board_id);
345344
let res = t.have_enough_ready_to_sign();
346-
info!(">>> have enough ready to sign: {:?}, id: {:?}", res, self.id);
347345
if let Some(tx) = res {
348-
// TODO: only the leader should broadcast the tx right now
349-
if self.id.0 != 0 {
346+
if !session.is_initiator {
347+
let mut updated_session = session.clone();
348+
updated_session.state = TxConstructionState::Success(None);
349+
350+
// TODO: update latest wallet info index in wallet data
351+
self.info_mut()
352+
.active_multi_party_payjoins
353+
.insert(*bulletin_board_id, updated_session);
350354
return;
351355
}
352-
println!("tx: {:?}", tx);
353356
let tx_id = self.spend_tx(tx);
354-
log::info!(
355-
"Multi party payjoin session successful with bulletin board id: {:?}",
356-
bulletin_board_id
357-
);
358357
self.broadcast(std::iter::once(tx_id));
359-
// Update session state to success
358+
let po_ids = session.payment_obligation_ids.clone();
359+
self.info_mut()
360+
.txid_to_payment_obligation_ids
361+
.insert(tx_id, po_ids);
362+
// TODO: update latest wallet info index in wallet data
360363
let mut updated_session = session.clone();
361-
updated_session.state = TxConstructionState::Success(tx_id);
364+
updated_session.state = TxConstructionState::Success(Some(tx_id));
362365
self.info_mut()
363366
.active_multi_party_payjoins
364367
.insert(*bulletin_board_id, updated_session);
@@ -441,6 +444,8 @@ impl<'a> WalletHandleMut<'a> {
441444
)
442445
}
443446

447+
// TODO: this should take input the list of order book entries
448+
// Candidate selection should be done by the cost function
444449
pub(crate) fn create_cospend_proposal(&'a mut self, po_ids: &[PaymentObligationId]) {
445450
// TODO: change should be decomposed.
446451
let change_addr = self.new_address();
@@ -461,10 +466,10 @@ impl<'a> WalletHandleMut<'a> {
461466
);
462467

463468
// If no candidates, fall back to a plain batch spend
464-
let Some(_) = candidates.first() else {
469+
if candidates.is_empty() {
465470
self.handle_payment_obligations(po_ids);
466471
return;
467-
};
472+
}
468473

469474
// Collect unique maker wallet IDs from the best candidate
470475
let maker_ids: Vec<WalletId> = candidates
@@ -496,6 +501,7 @@ impl<'a> WalletHandleMut<'a> {
496501
payment_obligation_ids: po_ids.to_owned(),
497502
tx_template,
498503
state: TxConstructionState::SentInputs,
504+
is_initiator: true,
499505
};
500506
self.info_mut()
501507
.active_multi_party_payjoins
@@ -541,6 +547,7 @@ impl<'a> WalletHandleMut<'a> {
541547
tx_template,
542548
// TODO: better state for someone who has not started the session yet
543549
state: TxConstructionState::SentBulletinBoardId,
550+
is_initiator: false,
544551
},
545552
);
546553
// Mark message as processed

0 commit comments

Comments
 (0)