Skip to content
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

Support setting a shielded address as refund target #4233

Open
wants to merge 22 commits into
base: main
Choose a base branch
from

Conversation

yito88
Copy link
Member

@yito88 yito88 commented Jan 13, 2025

Describe your changes

Closes #4168

Based on #4134

Checklist before merging

  • If this PR has some consensus breaking changes, I added the corresponding breaking:: labels
    • This will require 2 reviewers to approve the changes
  • If this PR requires changes to the docs or specs, a corresponding PR is opened in the namada-docs repo
    • Relevant PR if applies:
  • If this PR affects services such as namada-indexer or namada-masp-indexer, a corresponding PR is opened in that repo
    • Relevant PR if applies:

@yito88 yito88 force-pushed the yuji/ibc-shielded-refund branch from bbd65d4 to 9ced974 Compare January 13, 2025 16:18
Copy link

codecov bot commented Jan 13, 2025

Codecov Report

Attention: Patch coverage is 44.18605% with 600 lines in your changes missing coverage. Please review.

Project coverage is 74.40%. Comparing base (c0b9f68) to head (9ced974).

Files with missing lines Patch % Lines
crates/ibc/src/lib.rs 30.71% 185 Missing ⚠️
crates/ibc/src/context/middlewares/pfm_mod.rs 52.63% 180 Missing ⚠️
crates/sdk/src/tx.rs 0.00% 79 Missing ⚠️
crates/sdk/src/masp.rs 22.58% 48 Missing ⚠️
crates/ibc/src/context/nft_transfer_mod.rs 5.26% 36 Missing ⚠️
crates/ibc/src/msg.rs 48.88% 23 Missing ⚠️
crates/ibc/src/context/common.rs 50.00% 14 Missing ⚠️
crates/ibc/src/context/transfer_mod.rs 63.15% 14 Missing ⚠️
crates/sdk/src/rpc.rs 0.00% 12 Missing ⚠️
crates/ibc/src/context/token_transfer.rs 63.63% 8 Missing ⚠️
... and 1 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4233      +/-   ##
==========================================
- Coverage   74.59%   74.40%   -0.19%     
==========================================
  Files         342      344       +2     
  Lines      108724   109587     +863     
==========================================
+ Hits        81101    81543     +442     
- Misses      27623    28044     +421     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@@ -3218,3 +3288,29 @@ fn packet_forward_memo(
})
.expect("Test failed")
}

fn gen_refund_target(test: &Test, alias: &str) -> Result<()> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should really be a disposable address but we don't have the right support for it in the wallet. I'll add a note to #4040

@@ -104,7 +104,7 @@ impl Display for MaspTxId {
Serialize,
Deserialize,
)]
pub struct MaspEpoch(Epoch);
pub struct MaspEpoch(pub Epoch);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been made public to extract the inner Epoch when constructing the refund storage key. Can't we instead implement KeySeg on MaspEpoch? Otherwise could we come up with a getter to extract the inner epoch from here? I'd like to avoid exposing the inner type as the whole point of this type was to ensure safe operations on it

MsgEnvelope::Packet(PacketMsg::Ack(msg)) => {
let refund_info = recv_info_from_packet(&msg.packet, true)
.map_err(StorageError::new)?;
accum = apply_refund_msg(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it ok to apply the refund message without checking if the ack is successful or not?

@grarco
Copy link
Collaborator

grarco commented Jan 21, 2025

What happens if the shielded refund transaction fails? In the wasm code of the ibc transaction we don't explicitly handle this case. I tried looking at ICS20 but I couldn't find anything in terms of what the refund operation should guarantee or not

Comment on lines 246 to +247
transfer: None,
refund_masp_tx: None,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A refund masp tx should only exist if a transfer does too. This should be reflected in the layout of the MsgTransfer struct. Right now you can represent a MsgTransfer with a refund_masp_tx but no transfer.

@@ -104,7 +104,7 @@ impl Display for MaspTxId {
Serialize,
Deserialize,
)]
pub struct MaspEpoch(Epoch);
pub struct MaspEpoch(pub Epoch);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm more in favor of adding an accessor method rather than making this public, to discourage building invalid instances of this type.

Comment on lines +639 to +640
// Shielded refund only happens if MsgAcknowledgement or MsgTimeout is
// successful
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this comment is a bit confusing. it should only happen in case a timeout is triggered, or we receive an ack failure

Comment on lines +743 to +745
if let Some((_, (epoch, refund_masp_tx))) =
msg.transfer.as_ref().zip(msg.refund_masp_tx)
{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For instance, make transfer: Option<Either<MaspTransaction, (MaspTransaction, Refund)>>, or Option<(MaspTransaction, Option<Refund>)>

Comment on lines +305 to +306
fn try_get_refund_masp_tx<Transfer: BorshDeserialize>(
storage: &S,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method (or another one) should delete the refund shielding tx from storage. The refund tx is only needed in storage until an ack/timeout is received.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In shielded sync, the shielding tx could be retrieved from the IBC transfer transaction. It can't apply immediately because we don't know if the transfer hasn't failed at that time.
I tried to retrieve the refund shielding tx from storage in the PR for now. (But I have to fix to delete it when the ack is successful.)
Is it possible to have a kind of pending state of the IBC shielded transfer in shielded sync?

Copy link
Collaborator

@sug0 sug0 Jan 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if extract_masp_tx only returned the MASP tx in case the IBC packet was a timeout or ack failure? This way shielded sync wouldn't attempt to apply notes that could be in a pending state. This doesn't require many changes to the client code, but it might require reworking your current node code a bit...

Oh but I guess that still wouldn't solve the original problem, we can't delete the note from storage since we need to fetch during shielded sync. Perhaps we should give up on storing the shielding tx in storage, and carry it in a memo instead?

let packet_data = serde_json::from_slice::<PacketData>(
&msg.packet.data,
)
Some(IbcMessage::Envelope(envelope)) => match *envelope {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Some(IbcMessage::Envelope(envelope)) => match *envelope {
// These events are emitted on the receiver
Some(IbcMessage::Envelope(envelope)) => match *envelope {

Comment on lines +642 to +643
// If there is a transfer to the IBC account, then deduplicate the
// balance increase since we already account for it below
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what is being duplicated. Could you clarify, please?

Comment on lines +644 to +645
let token = convert_to_address(ibc_trace).into_storage_result()?;
let delta = ValueSum::from_pair(token, amount);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be moved inside the if-expr below, since they are only used there

Comment on lines +839 to +845
.refund_masp_tx(
&msg.packet.port_id_on_a,
&msg.packet.chan_id_on_a,
msg.packet.seq_on_a,
masp_epoch,
)
.map_err(|e| Error::Context(Box::new(e)))?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps we could delete the refund tx when we call refund_masp_tx

packet.seq_on_a,
masp_epoch,
);
if !storage.has_key(&refund_masp_tx_key).ok()? {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One note on this: a masp epoch change will trigger a failure of the shielded refund tx only if any of the involved assets is part of the conversion tree. If none of them is, then the tx will succeed at any epoch (provided that the assets have not entered the conversion tree in the meantime): so maybe we should try the shielded refund anyway before defaulting to the the transparent address (or maybe provide the MasEpoch field as an option and always try when None).

An additional note, the masp Transaction could also carry an expiration in the form of a block height: this could cause a rejection by the MASP vp. We should double check how that is handled when the refund tx is created cause for obvious reasons it cannot match the expiration of the original unshielding tx

Copy link
Member Author

@yito88 yito88 Jan 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we check if the assets have not entered the conversion tree in the transaction execution?
In the IBC logic, it just transfers(or mints) tokens from the IBC address to the MASP address. We can fall back the target to the transparent address if we know the shielded refund will fail at that time.

@@ -86,6 +98,8 @@ pub struct MsgNftTransfer<Transfer> {
pub message: IbcMsgNftTransfer,
/// Shieleded transfer for MASP transaction
pub transfer: Option<Transfer>,
/// MASP transaction for refund
pub refund_masp_tx: Option<(MaspEpoch, MaspTransaction)>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is where we probably want to remove the epoch or at least make it optional

@@ -36,6 +37,8 @@ pub struct MsgTransfer<Transfer> {
pub message: IbcMsgTransfer,
/// Shieleded transfer for MASP transaction
pub transfer: Option<Transfer>,
/// MASP transaction for refund
pub refund_masp_tx: Option<(MaspEpoch, MaspTransaction)>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as below

context,
masp_transfer_data,
None,
args.tx.expiration.to_datetime(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we shouldn't use the same expiration we use for the transaction also for the refund tx. I don't see why anyone might want to set an expiration on a refunding tx so we could just avoid attaching an expiration at all in this case or we should try to come up with a clever way to estimate the delay we expect on the ibc route (if this is even possible)

@@ -2781,8 +2783,11 @@ pub async fn build_ibc_transfer(
// The refund target should be given or created if the source is shielded.
// Otherwise, the refund target should be None.
assert!(
(args.source.spending_key().is_some() && refund_target.is_some())
|| (args.source.address().is_some() && refund_target.is_none())
(args.source.spending_key().is_some()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we return an error instead if panicking in the sdk?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Shielded refund design
4 participants