lock inputs via strategies and unlock early due to payment anxiety#8
lock inputs via strategies and unlock early due to payment anxiety#8Mshehu5 wants to merge 1 commit intopayjoin:masterfrom
Conversation
425cef2 to
bb8c115
Compare
9c8ae45 to
b314b63
Compare
| fn unspent_coins(&self) -> impl Iterator<Item = OutputHandle<'a>> + '_ { | ||
| self.potentially_spendable_txos().filter(|o| { | ||
| !self.info().unconfirmed_spends.contains(&o.outpoint()) | ||
| // TODO Startegies should inform which inputs can be spendable. |
There was a problem hiding this comment.
I realize this may have been misleading. Wallets should mark utxos as locked as secondary information (WalletInfo). Perhaps a better name for it could be: "used_utxos". This information should not live in the strategy struct
src/wallet.rs
Outdated
| fn is_payment_due_soon(&self, po_id: &PaymentObligationId) -> bool { | ||
| let po = po_id.with(self.sim).data(); | ||
| let time_to_deadline = po.deadline.0.saturating_sub(self.sim.current_timestep.0); | ||
| let anxiety_window = | ||
| (po.deadline.0.saturating_sub(po.reveal_time.0) / PAYMENT_ANXIETY_THRESHOLD).max(1); | ||
| time_to_deadline <= anxiety_window | ||
| } |
There was a problem hiding this comment.
This should be implemented in action.rs and should live with the rest of the scoring logic.
The way I think this should go
- Wallets should score handling an action with every utxo they have (even ones used in other payments that are not confirmed)
- If I use a UTXO that is locked but it means I handle a "more valuable" payment I should prefer to do that. The two costs should be evaluated against each other.
For example, Alice has only one UTXO and needs to pay Bob but there is ample time so she starts a Payjoin. UTXO a is locked in a payjoin that Alice started. She is waiting for Bob to respond to her. After a couple timesteps a higher priority tx shows up and Alice would prefer to spend the UTXO on that payment obligation. The cost function should inform the wallet that abandoning the payjoin is more valuable.
And this "every utxo" scaffolding will set us up nicely for subset sum portion of the cost function.
5899898 to
ea4a800
Compare
|
Heads up @Mshehu5 CI is failing |
arminsabouri
left a comment
There was a problem hiding this comment.
AbandonedPayjoin is defintely the way to go with this! Nice work.
I just had a few bikeshed comments and a comment about how wallet info is meant to work
| tx | ||
| } | ||
|
|
||
| fn abandon_payjoin(&mut self, payment_obligation_id: &PaymentObligationId) { |
There was a problem hiding this comment.
Maybe rename or add comment that this is a fallback.
src/wallet.rs
Outdated
| self.info_mut() | ||
| .unconfirmed_txos_in_payjoins | ||
| .insert(input.outpoint, *bulletin_board_id); | ||
| self.info_mut().used_utxos.insert(input.outpoint); |
There was a problem hiding this comment.
the "used" terminology implies they were used and spent. Perhaps better name would be in_use ?
src/actions.rs
Outdated
| // Same urgency curve as PaymentObligationHandledOutcome: | ||
| let points = [ | ||
| (0.0, 2.0 * payment_obligation_utility_factor), | ||
| (2.0, payment_obligation_utility_factor), | ||
| (5.0, 0.0), | ||
| ]; | ||
| let utility = piecewise_linear(self.time_left as f64, &points); | ||
| let score = self.amount_freed * utility; | ||
| debug!("AbandonPayjoinOutcome score: {:?}", score); |
There was a problem hiding this comment.
This makes sense as an outcome but can we just call the unilateral payment obligation score method instead of dup code?
src/actions.rs
Outdated
| #[derive(Debug)] | ||
| pub(crate) struct AbandonPayjoinOutcome { | ||
| time_left: i32, | ||
| amount_freed: f64, |
There was a problem hiding this comment.
Should this be amount handled?
There was a problem hiding this comment.
Or is it something else?
There was a problem hiding this comment.
currently changed it to unblocked_payment_amount
but maybe realesed_payment_amount or payment_amount might be better
| // Remove from initiated or received payjoins map | ||
| self.info_mut() | ||
| .initiated_payjoins | ||
| .remove(payment_obligation_id); | ||
| self.info_mut() | ||
| .received_payjoins | ||
| .remove(payment_obligation_id); |
There was a problem hiding this comment.
Perhaps a TODO or somethign to address in this PR.
info is a vec. The latest info is mean to capture the latest state. Its a vec so we can analyze the transcript of the simulation as it runs. So perhaps we can create a append a new wallet info and update the latest info index in wallet data.
There was a problem hiding this comment.
This comment still seems unaddressed
|
feel free to request review whenever this pr is ready |
255b8f9 to
3167196
Compare
| fn cost(&self, payment_obligation_weight: f64) -> ActionCost { | ||
| let points = [ | ||
| (0.0, 2.0 * payment_obligation_weight), | ||
| (2.0, payment_obligation_weight), | ||
| (5.0, 0.0), | ||
| ]; | ||
| let utility = piecewise_linear(self.time_left as f64, &points); | ||
| let cost = -(self.unblocked_payment_amount * utility); | ||
| debug!("AbandonPayjoinOutcome cost: {:?}", cost); | ||
| ActionCost(cost) |
There was a problem hiding this comment.
Not sure if this is the best way to implement this should amount be a factor ? or should it just be time left ?
There was a problem hiding this comment.
hm this is tricky. Whatever we end up choosing here we will use in our multiparty version so this is important to nail down. The cost seems like its two fold (for the sender)
- The cost of missing a payment obligation which you abstracted away in
payment_obligation_urgency_score. - The cost of losing some privacy. Which we can ignore for now.
if you satisfy our other payment obligation using the existing UTXOs AND the payment obligatoin is not expiring then there should be no strong reason to fallback.
arminsabouri
left a comment
There was a problem hiding this comment.
The scope of this PR is high. Its trying to do alot of things at once. And the scope is only increasing.
I would suggest we tighten and make a smaller PR for just the two party payjoin case.
Concretly if a sender's payment obligatoin is still not handled and its getting close to the deadline we fallback -- just broadcast the unilateral tx. No need to manage internal wallet info state or mark utxo as locked.
| } | ||
| } | ||
|
|
||
| /// Score of abandoning a pending payjoin. |
There was a problem hiding this comment.
| /// Score of abandoning a pending payjoin. | |
| /// Score of abandoning a pending payjoin. | |
| /// And using the inputs in this payjoin as inputs to a seperate tx |
| /// Set of multi-party payjoin sessions that this wallet is participating in | ||
| pub(crate) active_multi_party_payjoins: HashMap<BulletinBoardId, MultiPartyPayjoinSession>, | ||
| /// UTXOs currently in use by interactive protocols (payjoins, multi-party sessions). | ||
| pub(crate) in_use_utxos: OrdSet<Outpoint>, |
There was a problem hiding this comment.
Is this not the same thing as the keys of L54?
| let next_info = self.info().clone(); | ||
| let next_info_id = WalletInfoId(self.sim.wallet_info.len()); | ||
| self.sim.wallet_info.push(next_info); |
There was a problem hiding this comment.
Is the prev and next wallet info not the same thing? Its just cloning the current wallet info. I think you want to pass in a new wallet info.
| fn cost(&self, payment_obligation_weight: f64) -> ActionCost { | ||
| let points = [ | ||
| (0.0, 2.0 * payment_obligation_weight), | ||
| (2.0, payment_obligation_weight), | ||
| (5.0, 0.0), | ||
| ]; | ||
| let utility = piecewise_linear(self.time_left as f64, &points); | ||
| let cost = -(self.unblocked_payment_amount * utility); | ||
| debug!("AbandonPayjoinOutcome cost: {:?}", cost); | ||
| ActionCost(cost) |
There was a problem hiding this comment.
hm this is tricky. Whatever we end up choosing here we will use in our multiparty version so this is important to nail down. The cost seems like its two fold (for the sender)
- The cost of missing a payment obligation which you abstracted away in
payment_obligation_urgency_score. - The cost of losing some privacy. Which we can ignore for now.
if you satisfy our other payment obligation using the existing UTXOs AND the payment obligatoin is not expiring then there should be no strong reason to fallback.
| // Clear in_use_utxos so scoring evaluates all UTXOs, including ones | ||
| // committed to pending interactive protocols. |
There was a problem hiding this comment.
I don't think we want to clear these out. because it will be ignoring the cost of falling back on a payjoin
| // Remove from initiated or received payjoins map | ||
| self.info_mut() | ||
| .initiated_payjoins | ||
| .remove(payment_obligation_id); | ||
| self.info_mut() | ||
| .received_payjoins | ||
| .remove(payment_obligation_id); |
There was a problem hiding this comment.
This comment still seems unaddressed
| self.participate_in_multi_party_payjoin(bulletin_board_id); | ||
| } | ||
| Action::AbandonPayjoin(po_id) => { | ||
| self.abandon_payjoin(po_id); |
There was a problem hiding this comment.
It seems like this should be fallback not abandon and nothign happens.
The sender still should send the payment. Unless there is a more pressing payment that needs that UTXO but that doesnt seem like its being considered here.
this PR aims to address two TODO in unspent coin wallet.rs L159-160
// TODO Startegies should inform which inputs can be spendable.
// TODO: these inputs should unlock if the payjoin is expired or the associated payment obligation is due soon (i.e payment anxiety)
coins are now locked when used by a strategy and unlock when expired or due to payment anxiety(payment obligation almost due) which is currently implemented as if less than 10% of your deadline window remains this will unlock the coins. This threshold can be changed as is just a value