-
Notifications
You must be signed in to change notification settings - Fork 404
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
Alex ibc spike2 #231
Alex ibc spike2 #231
Conversation
Okay, some learnings of mine going through this....
Exactly one callback will come from a given packet. If This does make sense for lower-level coding, but for the contracts, it would be nicer to expose something like (The flow and use of returning errors or returning a packet with an error code is explained here: https://docs.cosmos.network/master/ibc/custom.html#receiving-packets) |
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.
Interesting spike.
Added quite some comments there. I think it would be best to take all these ideas and formalize them in #214 before getting too much deeper in the implementation, but doing this spike did brings up a lot of good questions on the specifics.
sdk "github.com/cosmos/cosmos-sdk/types" | ||
) | ||
|
||
type OnReceiveIBCResponse struct { |
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.
These are three clear entrypoints. I would like to make OnAcknowledgeIBCResponse
/ OnTimeoutIBCResponse
=> OnAcknowledgeIBCSuccess
/ OnAcknowledgeIBCError
as I see no difference in handling a timeout from a rejected packet (it failed, undo state changes)
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.
🤔 Success and Error are a bit ambiguous as an Ack can contain an application level error message.
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.
Exactly, the IBC/wasmd entry points make sense for the protocol level.
I would like to use more semantically interesting ones for the contracts.
And yes, I am only thinking of the interface we expose to the contracts, and figure we provide an adaptor between the official IBC and our desired API.
Log []wasmTypes.LogAttribute `json:"log"` | ||
} | ||
|
||
type AcceptChannelResponse struct { |
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.
This would be one more contract callback? for the OnTryInitChannel
or whatever it is called?
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 have used this for OnChanOpenInit
OnChanOpenTry
but channel handshake was only touched in this spike.
My idea was to have the contracts engaged here as well. For example if a contract is end-of-life (burned, escrow released,...) it would make sense to not accept any new channels.
@@ -0,0 +1,18 @@ | |||
package cosmwasm | |||
|
|||
type IBCQuery struct { |
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, didn't even think about the queries.
This is a good point, we should define them well.
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 sure if this is a useful scenario but I thought a contract may want to query all it's channels and broadcast to it's counter parties. Or may close them all (end-of-life scenario)
// IBC channel livecycle | ||
AcceptChannel(hash []byte, params cosmwasm.Env, order channeltypes.Order, version string, connectionHops []string, store prefix.Store, api wasm.GoAPI, querier QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasm.AcceptChannelResponse, uint64, error) | ||
//OnConnect(hash []byte, params cosmwasm.Env, msg []byte, store prefix.Store, api wasm.GoAPI, querier QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasm.OnTimeoutIBCResponse, uint64, error) | ||
//OnClose(hash []byte, params cosmwasm.Env, msg []byte, store prefix.Store, api wasm.GoAPI, querier QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasm.OnTimeoutIBCResponse, uint64, 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.
Hmmm.... we probably do want this one.
I'm not sure when OnConnect
would be called, or what it would be used for. You can get the same info from an IBCQuery and I don't want to give the contracts too much agency to randomly run code in arbitrary lifecycle events.
But if you have a communication channel with another module and it closes, it is probably nice to allow some cleanup? Or will I simply see the channel gone on the next query and receive OnTimeout
events for all pending packets? That may be enough for cleanup, but I could see wanting to do some cleanup.
The issue would be the contract just erroring on OnClose
and never letting the IBC module do the cleanup. Hmmm...
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.
You can get the same info from an IBCQuery
With a query I would get the channel date but I can only act on some other message that would be received later. This callback on the other hand does not need an additional message. I would rather see this as an extension point to let contracts implement their own behaviour and state changes.
|
||
var MockContracts = make(map[string]IBCCallbacks, 0) | ||
|
||
func (k Keeper) AcceptChannel(ctx sdk.Context, contractAddr sdk.AccAddress, order channeltypes.Order, version string, connectionHops []string, ibcInfo cosmwasm.IBCInfo) ([]string, 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.
we want to enforce order == ORDERED
if !ok { // hack for testing without wasmer | ||
panic("not supported") | ||
} | ||
res, gasUsed, execErr := mock.OnReceive(codeInfo.CodeHash, params, payloadData, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas) |
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.
This will need to know what channel the message is coming in on. that is not included in payload data. Any other context you think as useful?
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 have added this to the environment for the contract.
There are also a couple of message related information that are passed there, too.
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.
Make sense.
I wonder a bit when 90% of the time the Environment doesn't have this info.
Or do you propose two Environment types?
The original cosmwasm.Env
that is sent for init/handle/migrate and then what you coded here (maybe rename IBCEnvironment
), which is a superset of cosmwasm.Env
and passed into the ibc_handle
method for example.
This makes it clear what is called when
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.
Ja, why not have different ones as we call different callbacks anyway. Good idea
The IBC PortID should be somehow available to the contract in all environments as well as IBC queries. An outgoing
MsgWasmIBCCall
would start in a non IBC env. (if it works as I assume)
if !ok { | ||
panic("not supported") | ||
} | ||
res, gasUsed, execErr := mock.OnAcknowledgement(codeInfo.CodeHash, params, payloadData, acknowledgement, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas) |
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.
As above, we need to pass minimally the channelID here.
Also, I would love to be able to detect ack as success/error in our go code (in a fixed manner) before sending to the contract
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 also need some way to map back to the original packet that we sent. This is not just an unsolicited acknowledgement (like receive), but an acknowledgement that ties to one specific packet.
In ibc transfer they take the packet metadata (port and channel) and use it to determine 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.
The original payload from the contract is passed in the payloadData
. The contract has it's source data and the ack so that it can match it proper.
(Assuming the data contains an unique identifier. This is also an area where the rust cosmwasm framework may provide some tooling for non natural identifiers)
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 sender's channel/port are in IBCPacketInfo
and passed via Environment
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.
Ah... so payload data is read from the local store from the original input, and isn't something sent from the other side (that is acknowledgement)
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 have not looked deep enough through the IBC impl to find where the origin message comes from in the ack process but it is available to us in the IBC callbacks.
timeoutTimestamp uint64, | ||
msg json.RawMessage, | ||
) error { | ||
func (k Keeper) IBCCallFromContract(ctx sdk.Context, sourcePort, sourceChannel string, sender sdk.AccAddress, timeoutHeight, timeoutTimestamp uint64, msg json.RawMessage) 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.
Cool idea, so this is another CosmosMsg
type that the contract can return.
IBCSendPacket
and IBCOpenChannel
?
Good to look at this code, I need to combine this with the comments on #214 and get a clear API spec'd out.
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 assume that Send and CloseChannel are possible from a contract. The OpenChannel
process is not that clear to me!
A channel is based on a connection and that on a client. Both are out of scope of our module so far. In GoS the relayer would establish them based on configuration.
Technically the contract could store and provide the necessary data but calling out to other chains would mix with the relayers job and feels wrong at this stage.
from the cli:
wasmcli tx ibc channel open-init [port-id] [channel-id] [counterparty-port-id] [counterparty-channel-id] [connection-hops] [flags]
From the spec:
The connectionHops stores the list of connection identifiers, in order, along which packets sent on this channel will travel. At the moment this list must be of length 1. In the future multi-hop channels may be supported.
} | ||
|
||
func (c *mockContract) AcceptChannel(hash []byte, params cosmwasm2.Env, order channeltypes.Order, version string, connectionHops []string, store prefix.Store, api cosmwasm.GoAPI, querier keeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasm2.AcceptChannelResponse, uint64, error) { | ||
if order != channeltypes.ORDERED { |
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.
Hmm... maybe we let the contract use UNORDERED as well?
This is the application level. An error like "ErrInsufficientFunds" in ibc-transfer would be handled there. I think about this as an RPC call where the other side returns some method specific values. The response can be like (ok, error) or more complex. It would also serve as a commit in a DB transaction. Regardless of the payload, the IBC package was delivered successfully. The I assume there can be IBC related errors like "the port is no longer able to accept channels, such as if the Connection to the remote chain has failed, perhaps because a consensus failure was observed" as the third group.
I see this in the cosmwasm rust framework layer. We could provide handlers and other tooling there and keep wasmd as dynamic as possible |
You suggest the mapping of ack -> success/error should happen inside the contract itself, not in the wasm framework? |
Ja, this is application layer logic in my understanding. But we can help the contract developer by providing a rust framework/toolset that comes with some defaults/ conventions to make the real contract code simple and fast to build. |
Closes in favor of #253 |
This is not my branch, but I made a PR so I can comment on this better and get more visibility