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

Add versions to packet and create Eureka packet commitment #6986

Merged
merged 8 commits into from
Aug 1, 2024

Conversation

AdityaSripal
Copy link
Member

@AdityaSripal AdityaSripal commented Jul 29, 2024

Description

TODO:

  • Use IBC Version to switch between the two hashing functions
  • Get feedback from port router team on new hashing function
  • Merge into single entry point for packet msg handler
  • Parameterize the commitment function. This should be done in a separate PR after some discussion and is not necessary for initial release

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.

  • Targeted PR against the correct branch (see CONTRIBUTING.md).
  • Linked to GitHub issue with discussion and accepted design, OR link to spec that describes this work.
  • Code follows the module structure standards and Go style guide.
  • Wrote unit and integration tests.
  • Updated relevant documentation (docs/).
  • Added relevant godoc comments.
  • Provide a conventional commit message to follow the repository standards.
  • Include a descriptive changelog entry when appropriate. This may be left to the discretion of the PR reviewers. (e.g. chores should be omitted from changelog)
  • Re-reviewed Files changed in the GitHub PR explorer.
  • Review SonarCloud Report in the comment section below once CI passes.

@@ -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;
Copy link
Contributor

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.

Copy link
Member Author

@AdityaSripal AdityaSripal Jul 30, 2024

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

Copy link
Member Author

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.

@AdityaSripal AdityaSripal requested a review from DimitrisJim July 30, 2024 10:34
// NOTE: sdk.Uint64ToBigEndian sets the uint64 to a slice of length 8.
func CommitPacket(cdc codec.BinaryCodec, packet Packet) []byte {
Copy link
Member Author

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

@AdityaSripal AdityaSripal changed the title add versions to packet and separate commitment function Add versions to packet and create Eureka packet commitment Jul 30, 2024
@AdityaSripal AdityaSripal marked this pull request as ready for review July 30, 2024 10:47
// 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;
Copy link
Contributor

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

Copy link
Member Author

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

Comment on lines +100 to +101
// This will be treated as a classic packet
IBC_VERSION_UNSPECIFIED = 0;
Copy link
Contributor

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

Copy link
Member Author

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

Copy link
Contributor

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"];

Copy link
Member Author

Choose a reason for hiding this comment

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

Comment on lines +140 to +141
// which IBC version to use to commit this packet
IBCVersion protocol_version = 9;
Copy link
Contributor

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

@AdityaSripal AdityaSripal requested a review from srdtrk July 30, 2024 15:36
Copy link
Contributor

@sangier sangier left a 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!

Copy link
Contributor

@DimitrisJim DimitrisJim left a 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()) {
Copy link
Contributor

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))
Copy link
Contributor

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 {
Copy link
Contributor

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"
Copy link
Contributor

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?

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 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 != "" {
Copy link
Contributor

@DimitrisJim DimitrisJim Jul 31, 2024

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?

Copy link
Member Author

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

Comment on lines 103 to 105
IBC_VERSION_CLASSIC = 1;
// IBC version 2 implements the Eureka protocol
IBC_VERSION_EUREKA = 2;
Copy link
Contributor

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)

Copy link
Member Author

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 {
Copy link
Contributor

@colin-axner colin-axner Jul 31, 2024

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

Copy link
Contributor

@DimitrisJim DimitrisJim Jul 31, 2024

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.

Copy link
Member Author

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)

modules/core/04-channel/types/packet.go Outdated Show resolved Hide resolved
destinationHash := sha256.Sum256([]byte(packet.GetDestChannel()))
buf = append(buf, destinationHash[:]...)

// hash the version only if it is nonempty
Copy link
Member

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

Copy link
Member Author

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

Copy link
Member

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?

Copy link
Contributor

@DimitrisJim DimitrisJim left a comment

Choose a reason for hiding this comment

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

lgtm, sweet!

Copy link

sonarqubecloud bot commented Aug 1, 2024

@AdityaSripal
Copy link
Member Author

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

@AdityaSripal AdityaSripal merged commit 44215f7 into feat/ibc-eureka Aug 1, 2024
63 of 65 checks passed
@AdityaSripal AdityaSripal deleted the aditya/add-version-to-packet branch August 1, 2024 13:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Done 🥳
Status: Done
Development

Successfully merging this pull request may close these issues.

Implement new packet hashing for IBC Eureka Introduce Version field into the Packet struct
5 participants