-
Notifications
You must be signed in to change notification settings - Fork 624
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
Add versions to packet and create Eureka packet commitment #6986
Conversation
@@ -122,6 +129,10 @@ message Packet { | |||
ibc.core.client.v1.Height timeout_height = 7 [(gogoproto.nullable) = false]; | |||
// block timestamp (in nanoseconds) after which the packet times out | |||
uint64 timeout_timestamp = 8; | |||
// which IBC version to use to commit this packet | |||
IBCVersion ibc_version = 9; |
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't we store ibcversion under client id prefixed key in state and look it up when necessary instead of putting it in packet? We could just write a single byte value for it and migrating away from it when classic is dropped would be easier? This would need us to forbid new connections/channels on a eureka client tho I believe.
Just a thought that came to me initially looking at this packet field.
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 we could do it this way, and I am open to that. There are a couple reasons I like having the IBC Version in the packet.
One minor reason is that we could use the same client for Eureka and Classic. Of course, we could disable this if we choose to in other ways.
The major reason is that I think having these versions explicitly defined in the Packet will make it easier for us to upgrade in the future. If we have ProtocolVersion stored on a per-client basis, the entire chain and all applications on it have to make a synchronous decision to move to the next protocol. If it's a per-packet basis, one application can decide to upgrade if the sender supports the new version. If the receiver doesn't then it just doesn't get received and we time out. i think in general having version in the packet and being permissive to version-related failures to relay will make the system more easily upgradable over time than a prenegotiated version.
cc: @colin-axner
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 this is an enum that is not actually being hashed into the commitment. So it is really taking a small amount of space in the packet. The packet is also only in the transaction bytes, it is not stored on chain except for a couple edge-cases.
// NOTE: sdk.Uint64ToBigEndian sets the uint64 to a slice of length 8. | ||
func CommitPacket(cdc codec.BinaryCodec, packet Packet) []byte { |
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 codec was never used in a version of IBC. Think it was used in early pre-launch implementations but got changed before launch. the function signature never changed tho
// which IBC version to use to commit this packet | ||
IBCVersion protocol_version = 9; | ||
// version which application should use to process the packet data | ||
string app_version = 10; |
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'm fine with this to move forward, but I feel the app_version should be inside the packet data
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 we can do that in a multi packet data version
// This will be treated as a classic packet | ||
IBC_VERSION_UNSPECIFIED = 0; |
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, should we assign classic to 0? Would be encoding compliant with the existing structure, I'm fairly sure (empty fields not encoded), but it doesn't matter so much since this is just go api
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.
Proto makes me suffix this enum with UNSPECIFIED. idk if it can be overridden.
Agreed, this is just inside the chain doesn't go over the protocol so it doesn't matter
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.
ORDER_NONE_UNSPECIFIED = 0 [(gogoproto.enumvalue_customname) = "NONE"]; |
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.
// which IBC version to use to commit this packet | ||
IBCVersion protocol_version = 9; |
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 idea here being rather than introspecting the channel-id, we have the sender specify a version?
For other reviewers, you should think of the packet type as our go api and the commitment structure as the spec requirement. 2 ibc implementations could use different internal packet types and still be spec compliant
…a/add-version-to-packet
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 can't spot anything wired, LGTM!
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.
love the encompassing commit packet, left some initial comments/qs
timeoutBuf := sdk.Uint64ToBigEndian(packet.GetTimeoutTimestamp()) | ||
|
||
// only hash the timeout height if it is non-zero. This will allow us to remove it entirely in the future | ||
if !reflect.DeepEqual(timeoutHeight, clienttypes.ZeroHeight()) { |
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 height have an Eq
in its interface we could use?
@@ -35,6 +55,51 @@ func CommitPacket(cdc codec.BinaryCodec, packet Packet) []byte { | |||
return hash[:] | |||
} | |||
|
|||
// CommitEurekaPacket returns the Eureka packet commitment bytes. The commitment consists of: | |||
// sha256_hash(timeout_timestamp + timeout_height.RevisionNumber + timeout_height.RevisionHeight + sha256_hash(data)) |
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.
mention hasing of version?
// NOTE: sdk.Uint64ToBigEndian sets the uint64 to a slice of length 8. | ||
func CommitPacket(cdc codec.BinaryCodec, packet Packet) []byte { | ||
func CommitClassicPacket(packet Packet) []byte { |
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 two called by CommitPacket
should now be able to be made private no?
@@ -64,6 +64,7 @@ var ( | |||
disabledTimeout = clienttypes.ZeroHeight() | |||
validPacketData = []byte("testdata") | |||
unknownPacketData = []byte("unknown") | |||
validVersion = "ics-20" |
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.
guessing this is passed someplace as packet app version?
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.
in packet_test.go, just put the vars in one place rather than splitting them across files. We already use these vars in that test file
buf = append(buf, destinationHash[:]...) | ||
|
||
// hash the version only if it is nonempty | ||
if packet.AppVersion != "" { |
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.
where would have empty app version? do we need validation for this field?
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 don't see any version validation in the handshakes.
I'm not sure if we should be allowing it to be empty. Maybe we shouldn't, but i was thinking a blank version would imply use your default version. So if transfer receives a blank version, it tries to unmarshal data into ics20-v1 packet for example
IBC_VERSION_CLASSIC = 1; | ||
// IBC version 2 implements the Eureka protocol | ||
IBC_VERSION_EUREKA = 2; |
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.
looked over this with @chatton since it overlaps with our work. He made a good point that Eureka name is leaking into the codebase. I think long term, referring to this as v1 and v2 packet commitment structures would be more intuitive. Otherwise you keep adding codenames for every version (which I find to be very confusing)
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.
Agreed, switched to v2 for Eureka and v1 for classic. With this context, I think it still makes sense to leave the 0 enum as UNSPECIFIED but treat it as a v1 packet. What do you think?
I prefer that to zero indexing the IBC_Version_1 enum
// NOTE: The commitment MUSTs be a fixed length preimage to prevent relayers from being able | ||
// to malleate the packet fields and create a commitment hash that matches the original packet. | ||
func CommitPacket(packet Packet) []byte { | ||
switch packet.ProtocolVersion { |
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 we aren't proving the protocol version a relayer could provide the opposite version that a packet was sent on. Unsure how likely the impact would be, but certainly creates some opportunity
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 it was a eureka packet would fail on chan lookup and if a classic on counterparty lookup? this is assuming we don't allow chans/conns on eureka clients though, unsure for allowing them.
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 only matter if there was a hash collision between classic packets and eureka packets. Since we changed the commitment algorithm, I don't think that's possible here.
The same exact packet fields with the only thing changed being the ProtocolVersion would lead to different commitments, since the commitment algorithm is different even for the existing fields (and we newly hash dest identifiers)
And additionally two different packets hashing to the same hash by swapping out the ProtocolVersion would break the sha256 preimage collision so we don't need to worry about that.
I have thought about the issue of future maintainer adding a third version. And this third version commits packets in the exact same way as v2. In this case, if the future maintainer is not careful, they can allow relayers to modify the version on receive.
I could signal the possibility of that footgun here by explicitly hashing protocol version. But that would only be a hint for right behavior, since future maintainers may not do it (if they don't change commitment hashes in between the next versions, they will need to hash in the protocol version)
destinationHash := sha256.Sum256([]byte(packet.GetDestChannel())) | ||
buf = append(buf, destinationHash[:]...) | ||
|
||
// hash the version only if it is nonempty |
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.
why? What's wrong with hashing the version even if it is empty
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 could do this, thought to save on the compute if hash is empty. More relevant for VM implementations
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 it is better not to have branching in the code when not necessary. A single hash isn't gonna make or break it. So can we hash even if it is empty?
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, sweet!
Quality Gate passed for 'ibc-go'Issues Measures |
Merging to not block future PRs. I believe I've answered people's questions. If there are followups, please open new issues or discuss on the eureka channel |
Description
TODO:
closes: #6971 #6972
Before we can merge this PR, please make sure that all the following items have been
checked off. If any of the checklist items are not applicable, please leave them but
write a little note why.
docs/
).godoc
comments.Files changed
in the GitHub PR explorer.SonarCloud Report
in the comment section below once CI passes.