-
Notifications
You must be signed in to change notification settings - Fork 94
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
fix(legacy-refunds): support going the spending path on refund #2280
base: dev
Are you sure you want to change the base?
Conversation
modularize these funcs and introdcue a new error type to filter errors and possible retrys. tests are not yet adapted.
not finished and on successful checking swaps are removed. these tests were used to check that we error on recover funds when: - swap is not finished yet: there is not reason not to try recover funds still - swap was successful (determined the existance of taker/maker payment spend): we should still attempt to recover funds in this case as the tx might be re-orged or some weird thing happned. there is no downside of retrying. also test_recover_funds_maker_swap_maker_payment_refunded was used to test that we check local data to determine that the swap failed. again this isn't the followed approach here. even if we store maker_payment_refund tx locally (so we should have sent it when the swap was running) we will still attempt to run recover funds and look for the tx on-chain. this test could have been adapted but then it looks exactly like test_recover_funds_maker_payment_refund_already_refunded, so there is no point of that.
namely, we do try to refund (maker payment) or spend (taker payment), which ever is possible. we might want to change refund_maker_payment name to recover or something
same as what's done with the maker. thought much more useful here because on the maker side we can't really miss spending the taker's payment while getting our own payment spent :p
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.
Thanks for the PR, looks cleaner. My only note is regarding error handling.
}, | ||
Err(e) => return ERR!("Error {} when trying to find taker payment spend", e), | ||
Err(e) => return Err(RecoverSwapError::Temporary(e)), |
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.
we won't get error tracing anymore from this fn. Please return MmError<RecoverSwapError>
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.
but the returned error is a string. how would tracing work here? or you mean change the error from search_for_swap_tx_spend_other
?
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.
not just String, location is also prepended
https://docs.rs/gstuff/latest/gstuff/macro.ERR.html
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.
yeah i see ERR! does prepend the current location, but i mean it doesn't get the trace, only the line in this function i think.
we can revert that though for the sake of RPC usage.
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.
Yeah, but why not MmError? I mean instead of reverting?
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.
Yeah, but why not MmError? I mean instead of reverting?
Legacy code doesnt support MmError, it requires String in a lot of places
Well @mariocynicys could use MmError for internal function and in general function, which returns String, convert mmerr to string, but its better to just come back to ERR macro and wrap errors in internal function
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.
there's already an old issue to replace macros from gstuff with MmError
generally..
#1250
You can simply map to String
in the function caller if it requires returning String
as Error
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 agree that explicit error with enum variants in recover_funds
function simplifies logic handled in refund payment functions
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.
Yeah, but why not MmError? I mean instead of reverting?
ah sure, i forgot the original comment was for MmError xD
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.
if self.finished_at.load(Ordering::Relaxed) == 0 { | ||
return ERR!("Swap must be finished before recover funds attempt"); | ||
} | ||
pub async fn recover_funds(&self) -> Result<RecoveredSwap, RecoverSwapError> { |
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.
same as above
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.
@mariocynicys could you please fix PR lint |
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.
Thanks for this fix, here my notes
// We recovered the swap successfully. | ||
Ok(recovered_swap) => match recovered_swap { | ||
// We recovered the swap by refunding the maker payment. | ||
RecoveredSwap { | ||
action: RecoveredSwapAction::RefundedMyPayment, | ||
coin: _, | ||
transaction, | ||
} => { | ||
let tx_ident = TransactionIdentifier { | ||
tx_hex: transaction.tx_hex().into(), | ||
tx_hash: transaction.tx_hash_as_bytes(), | ||
}; | ||
info!("Maker payment refund tx {:02x}", tx_ident.tx_hash); | ||
return Ok((Some(MakerSwapCommand::FinalizeMakerPaymentRefund), vec![ | ||
MakerSwapEvent::MakerPaymentRefunded(Some(tx_ident)), | ||
])); | ||
}, | ||
// We recovered the swap by proceeding forward and spending the taker payment. The swap wasn't actually a failure. | ||
// Roll back to confirming the taker payment spend. | ||
RecoveredSwap { | ||
action: RecoveredSwapAction::SpentOtherPayment, | ||
coin: _, | ||
transaction, | ||
} => { | ||
let tx_ident = TransactionIdentifier { | ||
tx_hex: transaction.tx_hex().into(), | ||
tx_hash: transaction.tx_hash_as_bytes(), | ||
}; | ||
info!("Refund canceled. Taker payment spend tx {:02x}", tx_ident.tx_hash); | ||
// TODO: We prepared for refund but didn't finalize refund. This must be breaking something for lightning. | ||
return Ok((Some(MakerSwapCommand::ConfirmTakerPaymentSpend), vec![ | ||
MakerSwapEvent::TakerPaymentSpent(tx_ident), | ||
MakerSwapEvent::TakerPaymentSpendConfirmStarted, | ||
])); | ||
}, |
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.
Can be simplified using closure and return match
// We recovered the swap successfully.
Ok(recovered_swap) => {
let tx_ident = |tx: TransactionEnum| TransactionIdentifier {
tx_hex: tx.tx_hex().into(),
tx_hash: tx.tx_hash_as_bytes(),
};
// We recovered the swap by refunding the maker payment.
return match recovered_swap.action {
RecoveredSwapAction::RefundedMyPayment => {
let ident = tx_ident(recovered_swap.transaction);
info!("Maker payment refund tx {:02x}", ident.tx_hash);
Ok((Some(MakerSwapCommand::FinalizeMakerPaymentRefund), vec![
MakerSwapEvent::MakerPaymentRefunded(Some(ident)),
]))
},
// We recovered the swap by proceeding forward and spending the taker payment. The swap wasn't actually a failure.
// Roll back to confirming the taker payment spend.
RecoveredSwapAction::SpentOtherPayment => {
let ident = tx_ident(recovered_swap.transaction);
info!("Refund canceled. Taker payment spend tx {:02x}", ident.tx_hash);
// TODO: We prepared for refund but didn't finalize refund. This must be breaking something for lightning.
Ok((Some(MakerSwapCommand::ConfirmTakerPaymentSpend), vec![
MakerSwapEvent::TakerPaymentSpent(ident),
MakerSwapEvent::TakerPaymentSpendConfirmStarted,
]))
},
};
},
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.
}, | ||
// The swap will be automatically recovered after the specified locktime. | ||
RecoverSwapError::AutoRecoverableAfter(locktime) => { | ||
let maker_payment = self.r().maker_payment.clone().unwrap().tx_hex; |
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.
Better use maker_payment.as_ref()
and clone tx_hex
let maker_payment = self.r().maker_payment.as_ref().unwrap().tx_hex.clone()
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.
// We recovered the swap successfully. | ||
Ok(recovered_swap) => match recovered_swap { | ||
// We recovered the swap by refunding the taker payment. | ||
RecoveredSwap { | ||
action: RecoveredSwapAction::RefundedMyPayment, | ||
coin: _, | ||
transaction, | ||
} => { | ||
let tx_ident = TransactionIdentifier { | ||
tx_hex: transaction.tx_hex().into(), | ||
tx_hash: transaction.tx_hash_as_bytes(), | ||
}; | ||
info!("Taker payment refund tx {:02x}", tx_ident.tx_hash); | ||
return Ok((Some(TakerSwapCommand::FinalizeTakerPaymentRefund), vec![ | ||
TakerSwapEvent::TakerPaymentRefunded(Some(tx_ident)), | ||
])); | ||
}, | ||
// We recovered the swap by proceeding forward and spending the maker payment. The swap wasn't actually a failure. | ||
// Roll back to confirming the maker payment spend. | ||
RecoveredSwap { | ||
action: RecoveredSwapAction::SpentOtherPayment, | ||
coin: _, | ||
transaction, | ||
} => { | ||
let tx_ident = TransactionIdentifier { | ||
tx_hex: transaction.tx_hex().into(), | ||
tx_hash: transaction.tx_hash_as_bytes(), | ||
}; | ||
info!("Refund canceled. Taker payment spend tx {:02x}", tx_ident.tx_hash); | ||
// TODO: We prepared for refund but didn't finalize refund. This must be breaking something for lightning. | ||
// We better find a way to rollback the state machine and remove erroneous events, | ||
// the swap at this point will be marked as errored but in fact it recovered from the error. | ||
return Ok((Some(TakerSwapCommand::ConfirmMakerPaymentSpend), vec![ | ||
TakerSwapEvent::MakerPaymentSpent(tx_ident), | ||
])); | ||
}, |
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.
Also can be simplified with closure and return match
// We recovered the swap successfully.
Ok(recovered_swap) => {
let tx_ident = |transaction: TransactionEnum| TransactionIdentifier {
tx_hex: transaction.tx_hex().into(),
tx_hash: transaction.tx_hash_as_bytes(),
};
return match recovered_swap.action {
// We recovered the swap by refunding the taker payment.
RecoveredSwapAction::RefundedMyPayment => {
let ident = tx_ident(recovered_swap.transaction);
info!("Taker payment refund tx {:02x}", ident.tx_hash);
Ok((Some(TakerSwapCommand::FinalizeTakerPaymentRefund), vec![
TakerSwapEvent::TakerPaymentRefunded(Some(ident)),
]))
},
// We recovered the swap by proceeding forward and spending the maker payment. The swap wasn't actually a failure.
// Roll back to confirming the maker payment spend.
RecoveredSwapAction::SpentOtherPayment => {
let ident = tx_ident(recovered_swap.transaction);
info!("Refund canceled. Taker payment spend tx {:02x}", ident.tx_hash);
// TODO: We prepared for refund but didn't finalize refund. This must be breaking something for lightning.
// We better find a way to rollback the state machine and remove erroneous events,
// the swap at this point will be marked as errored but in fact it recovered from the error.
Ok((Some(TakerSwapCommand::ConfirmMakerPaymentSpend), vec![
TakerSwapEvent::MakerPaymentSpent(ident),
]))
},
};
},
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.
aha i see,
you could also run tx_ident outside the match since it's the exact same code for both branches.
i did it this way though since it fits more with the style of the error routes so i thought it will be easier to read. sometimes i lean for readability & style and ignore other aspects :)
but sense u mentioned it, i guess i looks off, so let's combine the combinable code outside the match then.
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.
}, | ||
// The swap will be automatically recovered after the specified locktime. | ||
RecoverSwapError::AutoRecoverableAfter(locktime) => { | ||
let taker_payment = self.r().taker_payment.clone().unwrap().tx_hex; |
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.
Its better to use as_ref for taker_payment
let taker_payment = self.r().taker_payment.as_ref().unwrap().tx_hex.clone();
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.
pub async fn recover_funds(&self) -> Result<RecoveredSwap, String> { | ||
async fn try_spend_taker_payment(selfi: &MakerSwap, secret_hash: &[u8]) -> Result<TransactionEnum, String> { | ||
pub async fn recover_funds(&self) -> Result<RecoveredSwap, RecoverSwapError> { | ||
async fn try_spend_taker_payment(selfi: &MakerSwap) -> Result<TransactionEnum, RecoverSwapError> { |
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.
By the way, why not move try_spend_taker_payment
function out of the recover_funds
function?
Right now, there is a large function nested inside another large function, and both only require &self
.
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 think this scopes the helper function better since it's not used elsewhere. we can consider putting helpers on the impl Struct
scope if that impl
scope doesn't have too many methods already, but this one has tons of methods so scoping the helper inside recover_funds
would make more sense.
sorry, but amending this would break the links i just shared :). thanks for your undertanding, reader.
This PR attempts to follow a more robust recovery process by attempting both refunding OR spending if the swap goes the ugly way.
This is done by replacing the refund-only logic with refund-or-spend-logic (recovery,
recover_funds
).Also
recover_funds
has been adapted to return more structured error types for information about retrials and such. Also removing some pre-checks that makes us fail early based on local data (e.g. is_swap_finished), as we should still query the rpc to make sure our recovery tx isn't lost or something.