-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
taproot swaps #28
taproot swaps #28
Conversation
a3c38b3
to
2ee3174
Compare
MusigSessionId was renamed to MusigSecRand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks really good. Everything seems to be in place.
Added review comments.
{ | ||
Ok(_) => {} | ||
Err(LockSwapError::AlreadyLocked) => { | ||
return Err(Status::failed_precondition("swap is locked")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this mean we only have one attempt to pay the user? I am thinking of a case where payment failed and user tries again to complete the swap.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is supposed to unlock after the payment, see #28 (comment)
swapd/src/public_server.rs
Outdated
"failed to persist pay result: {:?}", | ||
e | ||
); | ||
} | ||
}; | ||
|
||
Ok(Response::new(GetSwapPaymentReply::default())) | ||
let _ = self.swap_repository.unlock_swap(&swap_state.swap).await; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if we attempted to pay and there was some error before getting into this point? For example if the lightning_client.pay
failed? Will the swap stay locked forever?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would remain locked forever, yes.
The lightning clients should return PaymentResult::Failure
, rather than an error in most circumstances. (Which they don't! TODO!).
The thing with the locking is, I want to avoid races. If this line here is reached, you know the swap was locked by the current call to pay_swap
. So it's safe to unlock in this function. We could add some logic to allow relocking the swap for payment, to allow payment in case of a crash. Which should be fine if there are no htlcs pending. But then the unlock mechanism needs to change, because the 'unlock' just releases the lock, it has no counter. You definitely want to make sure the cooperative refund can't happen while paying.
I don't like this whole database lock, but I don't really see a good way to do this safely, across restarts as well. If we start reasoning from the refund:
- The fact there was a refund definitely needs to be stored. If there ever was a refund issued, you never want to pay again.
- In order to avoid races with refund/pay, the refund and pay have to use the same lock.
If you have a better solution, please share!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking using the lock only for refund means that before we refund we lock and by that we mark the swap as eligible for refund and not eligible for payment anymore. This covers one side of the race.
For the other side (not refunding while payment in progress) perhaps we can use the payment attempts table?
I see that before payment we add an entry there and after we record the results (which is only handled by the preimage monitor so it should fix things after crashes while payment is in progress) so perhaps only allow cooperative refund if there is no payment attempt without result?
swapd/src/public_server.rs
Outdated
{ | ||
Ok(false) => {} | ||
Ok(true) => { | ||
if !was_locked { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems that if the server has some unplanned crash we can end up with the locked state forever?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, in this case, for refund, that seems like the only viable solution though. If you don't know whether you returned a refund signature or not, you have to consider it refunded.
d0365a8
to
2e365e5
Compare
Swaps are now locked by a row in the database. This allows simultaneous refund locks and allows removing payment locks based on their label.
2e365e5
to
e36c0e5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Locking mechanism looks good to me. Added one comment regarding refund unlocking.
Ok(()) | ||
} | ||
|
||
async fn unlock_swap_refund(&self, swap: &Swap, refund_id: &str) -> Result<(), LockSwapError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we need this function. Now that we have reliable locking mechanism do you see a case where we need to unlock the refund?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Theoretically no. I do like the extra safety check in refund_swap
for the has_pending_or_complete_payment
. It shouldn't happen due to the way the locking works, but perhaps a manual intervention or a bug causes the swap to be accidentally refundable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
This PR adds taproot swap support.
Changes in a nutshell:
Note about the musig2 implementation:
There is no musig2 implementation in rust-bitcoin at the moment. There is a PR in rust-secp256k1 though. Combining the version from that PR with the taproot parts of rust-bitcoin goes well, as long as keys are serialized/deserialized to convert the incompatible structs from one package to another.
review notes
Apologies for the huge PR. For review I think the best option is:
swap.proto
public_server.rs
swap_service.rs