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

Give contracts finer control on IBC packet acknowledgement #1721

Closed
larry0x opened this issue Apr 26, 2023 · 16 comments · Fixed by #2130
Closed

Give contracts finer control on IBC packet acknowledgement #1721

larry0x opened this issue Apr 26, 2023 · 16 comments · Fixed by #2130
Milestone

Comments

@larry0x
Copy link
Contributor

larry0x commented Apr 26, 2023

I'm attempting to implement something similar to the packet-forward-middleware as a CosmWasm contract. Unfortunately, as a Go module, PFM is able to control how to write acknowledgement for a packet in a way not available to CW contracts.

How PFM works

Consider a transfer of USDC following the route Osmosis → Noble → Juno. Let's call Osmosis → Noble packet1 and Noble → Juno packet2.

Why this is not possible for CW contracts

My suggestion

  • Make the data field in IbcReceiveResponse optional:

      pub struct IbcReceiveResponse<T = Empty> {
    -     pub acknowledgement: Binary,
    +     pub acknowledgement: Option<Binary>,
          pub messages: Vec<SubMsg<T>>,
          pub attributes: Vec<Attribute>,
          pub events: Vec<Event>,
      }

    and only write an ack if the data is Some.

  • Add a new IbcMsg::WriteAcknowledgement binding which invokes ics4Wrapper.WriteAcknowledgement.

These would allow contracts to completely replicate the behavior of packet-forward-middleware.

@larry0x larry0x changed the title Give contract finer control on IBC packet acknowledgement Give contracts finer control on IBC packet acknowledgement Apr 26, 2023
@alpe
Copy link
Contributor

alpe commented Apr 27, 2023

This is an interesting idea. Thanks for bringing this up!
Do I assume correct that in order to keep connections to Osmosis and Juno, you would have 2 contracts on Noble that can interact with each other?
The change to IbcReceiveResponse is a breaking change unfortunately. But let's move this to CosmWasm repo for discussion.

@alpe
Copy link
Contributor

alpe commented Apr 27, 2023

Note: with the current impl in wasmd, the submessages returned with IbcReceiveResponse may create response data that "overwrite" the nil ack.

@larry0x
Copy link
Contributor Author

larry0x commented Apr 27, 2023

Do I assume correct that in order to keep connections to Osmosis and Juno, you would have 2 contracts on Noble that can interact with each other?

A single contract can do it, just like the single ibc-transfer module can maintain channels with multiple chains.

@larry0x
Copy link
Contributor Author

larry0x commented Apr 27, 2023

Note: with the current impl in wasmd, the submessages returned with IbcReceiveResponse may create response data that "overwrite" the nil ack.

Yep, Polytone and my project (ICS-999) already uses this trick.

However, the ack is always written during the execution of the MsgRecvPacket.

What packet-forward-middleware does, is to not write an ack for packet1 at all. It instead dispatches another packet (packet2) to the destination chain (Juno in my example). When Juno sends the ack for packet2 back to Noble, the middleware finally writes the ack for packet1.

@webmaster128
Copy link
Member

webmaster128 commented Jun 13, 2023

Note: with the current impl in wasmd, the submessages returned with IbcReceiveResponse may create response data that "overwrite" the nil ack.

Yep, Polytone and my project (ICS-999) already uses this trick.

How does this trick work? This may help shape an API. How is it possible to write a "the nil ack"? What is a nil ack? An ack with empty bytes?

@larry0x
Copy link
Contributor Author

larry0x commented Jun 13, 2023

How is it possible to write a "the nil ack"? What is a "the nil ack"? An ack with empty bytes?

In ibc-go's porttypes.Middleware interface, the OnRecvPacket function is supposed to return an ibcexported.Acknowledgement. The IBC transport layer will then write this ack. However, if you return a nil instead, the transport layer will interpret this as "this IBC app doesn't want to write an ack at all" and does not write an ack.

This is the behavior that PFM utilizes: https://github.com/strangelove-ventures/packet-forward-middleware/blob/v6.0.2/router/ibc_middleware.go#L225-L227


How does this trick work?

The trick is this: at the end of the the ibc_packet_receive entry point, the contract returns a submsg, as well as a nil ack:

pub fn packet_receive(
    deps: DepsMut,
    env: Env,
    packet: IbcPacket,
) -> Result<IbcReceiveResponse, ContractError> {
    // ...

    // we don't do set_ack on the IbcReceiveResponse here,
    // so the ack is empty bytes by default.
    Ok(IbcReceiveResponse::new()
        .add_submessage(SubMsg::reply_always(
            // ...
        )))
}

Why? Because we don't know the content of the ack yet. We need to execute the submessage first, and based on the result of the submessage determine what to put in the ack.

In the reply of that submessage, we set data in the response:

Ok(Response::new().set_data(
    // ...
))

The wasm module will interpret this data as the ack.


Tbh, I think this is a somewhat confusing behavior.

Here's a similar opinion found on twitter: https://twitter.com/0xArt3miX/status/1667968274410225673

I think it'd be much clearer to

  • make the acknowlegement field optional in the IbcReceiveResponse
    • if it's empty bytes Some(Binary(vec![])), wasm module interprets it as the contract wants to write an ack of zero byte
    • if it's None, wasm module interprets it as the contract doesn't want to write an ack at all
  • introduce an IbcMsg::WriteAcknowledgement binding, which allows contracts to explicitly write acks

@webmaster128 webmaster128 transferred this issue from CosmWasm/wasmd Jun 13, 2023
@webmaster128
Copy link
Member

Thank you! Moved this over to cosmwasm as suggested by Alex. Here we can change the contract APIs and then integrate.

Independent of the current override behaviour it makes sense to make writing the ack optional and allow writing the acknowledgement separately. When we are doing that it should be possible to write the ack in a different block, which means some security considerations must be done to avoid writing someone else's ack.

remove the acknowlegement field in the IbcReceiveResponse

I don't think we should make this the default. In the majority of cases you want to write an ack. #1649 goes in the opposite direction and makes setting it required so you don't forget. But IbcReceiveResponse::new(Some(ack)) / IbcReceiveResponse::new(None) is an option.

@larry0x
Copy link
Contributor Author

larry0x commented Jun 13, 2023

@webmaster128 Edited my response above with a better explanation

remove the acknowlegement field in the IbcReceiveResponse

Edited, I no longer suggest removing it, but rather make it an Option<Binary>

@larry0x
Copy link
Contributor Author

larry0x commented Jun 13, 2023

But IbcReceiveResponse::new(Some(ack)) / IbcReceiveResponse::new(None) is an option

This makes sense, like it

@webmaster128 webmaster128 added this to the 2.0.0 milestone Jun 13, 2023
@webmaster128
Copy link
Member

🤝

Will do some more checks with Alex and especially look into the long polling use case. But overall this sounds solid.

@webmaster128
Copy link
Member

In ICS-004: Writing acknowledgements you find additional background around async achnowledgements, which is what is requested here.

@webmaster128
Copy link
Member

In the first release of CosmWasm 2.0, we make writing the ack optional as this is the breaking change (#1892). This does not yet allow you to write it later. The API for writing it later will be created later on. Removing this from the 2.0 milestone to reflect this breakdown.

@hussein-aitlahcen
Copy link
Contributor

This feature would be a big help!

@webmaster128
Copy link
Member

What's the use case?

@hussein-aitlahcen
Copy link
Contributor

hussein-aitlahcen commented Apr 16, 2024

What's the use case?

We have a cosmwasm implementation acting as a bridge between ICS20 and a Solidity contract (generalization of ICS20 allowing multi-denom transfer and custom encoding for packets to be efficient on EVM). It's 99% compatible with ICS20, PFM being the only exception here, as described by @larry0x. Hence, the memo is unusable for us, right now

@webmaster128
Copy link
Member

If this feature is important to you, please have a look into #2130 to ensure we are not missing something. Thanks!

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

Successfully merging a pull request may close this issue.

4 participants