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 verifiable upgrade type and validate basic functions #3451

Conversation

chatton
Copy link
Contributor

@chatton chatton commented Apr 13, 2023

Description

closes: #3445

This PR also introduces a helper function to check if a connection is open. Other checks can be refactored in a follow up.

Follow up Items after this PR is merged:

  • Add ValidateBasic tests for Upgrade Types
  • Determine the correct usage of latest_sequence_send field.
  • Implement this suggestion

Commit Message / Changelog Entry

N/A part of feature branch


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 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/) or specification (x/<module>/spec/).
  • Added relevant godoc comments.
  • Provide a commit message to be used for the changelog entry in the PR description for review.
  • Re-reviewed Files changed in the Github PR explorer.
  • Review Codecov Report in the comment section below once CI passes.

@chatton chatton marked this pull request as ready for review April 13, 2023 11:05
return errorsmod.Wrap(ErrInvalidUpgrade, "upgrade timeout cannot be empty")
}

// TODO: determine if last packet sequence sent can be 0?
Copy link
Contributor

Choose a reason for hiding this comment

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

leave this todo in? I remember we talked about it being sensible if its zero (i.e channel which has just opened) but maybe it makes sense to leave it around for another pair of eyes to check?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes I think 0 should technically be a valid value (no packets sent yet), maybe @colin-axner / @AdityaSripal could confirm if we should let 0 be an accepted value here?

Copy link
Member

Choose a reason for hiding this comment

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

Packet sequences start at 1. But iirc from yesterday this value can be determined within the state machine in WriteChanUpgradeInit() as opposed to expecting a user provided value

Copy link
Contributor Author

Choose a reason for hiding this comment

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

validate basic of the message should then check for >= 1 right? Even if it is determined within the state machine it should still be validated here if possible I think.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we should remove this function? I think we should have MsgUpgradeInit and MsgUpgradeTry use the UpgradeFields and UpgradeTimeout directly rather than wrapping with Upgrade. I suggest this because it seems unnecessary to expose a field populated by the state machine to the relayer. This would render this ValidateBasic unnecessary as the msg should do basic validation on the fields and timeout and then the state machine would construct the upgrade type with the fields, timeout and retrieved packet sequence send. I guess we could keep if for the situation where the entire counterparty upgrade type is provided in the TRY and ACK, in this situation the sequence should not equal 0.

My vote is to modify the init/try field for self upgrade to use the fields/timeout directly in the msg type and add a if sequence != 0 check to be used to verify the fully formed counterparty upgrade

Copy link
Member

Choose a reason for hiding this comment

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

sounds good to me

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I'm happy enough with that approach 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, just realising the message modification will cause a bigger diff with the OnChanInit keeper func which is being addressed in a follow up. @colin-axner are you happy if the message structure gets updated (and this validate basic removed) in a follow up?

Copy link
Contributor

Choose a reason for hiding this comment

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

Absolutely!

Copy link
Member

@damiannolan damiannolan left a comment

Choose a reason for hiding this comment

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

Looks great. I left some suggestions, mostly around naming

modules/core/04-channel/types/upgrade.go Outdated Show resolved Hide resolved
return errorsmod.Wrap(ErrInvalidUpgrade, "upgrade timeout cannot be empty")
}

// TODO: determine if last packet sequence sent can be 0?
Copy link
Member

Choose a reason for hiding this comment

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

Packet sequences start at 1. But iirc from yesterday this value can be determined within the state machine in WriteChanUpgradeInit() as opposed to expecting a user provided value

proto/ibc/core/channel/v1/upgrade.proto Outdated Show resolved Hide resolved
return errorsmod.Wrap(types.ErrInvalidChannelOrdering, "channel ordering must be a subset of the new ordering")
}

return k.connectionKeeper.CheckIsOpen(ctx, proposedUpgrade.ConnectionHops[0])
Copy link
Member

@damiannolan damiannolan Apr 13, 2023

Choose a reason for hiding this comment

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

Personally I prefer just doing the check explicitly in here rather than adding another expected keeper interface method but happy to keep this as is if there's consensus on adding this instead.
My vote would be for doing the check directly here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The idea is that this could be used in many other places, it also covers the not found case. Quickly looking it looks like we have around 7 other duplicate checks.

My preference would be to re-use this helper

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with @damiannolan. I think also the CheckIsOpen function should anyway return a boolean (unless we rename it to CheckExistsAndIsOpen, since it is actually checking two things). In my opinion I would just do the explicit check here.

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah there was a bit of bike-shedding over the name/scope of this function between us. Initially we had it as IsOpen with a single boolean return but that didn't make it as self contained as we'd like. Returning the error allowed it to basically also check for any required error conditions and return them, simplifying the validation, hence the rename to CheckIsOpen.

Cian did say this function might not fly with everyone though 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm happy to just remove the function entirely and do the explicit checking where needed! 👍

modules/core/04-channel/keeper/keeper.go Outdated Show resolved Hide resolved
modules/core/04-channel/types/expected_keepers.go Outdated Show resolved Hide resolved
proto/ibc/core/channel/v1/upgrade.proto Outdated Show resolved Hide resolved
Copy link
Contributor

@charleenfei charleenfei left a comment

Choose a reason for hiding this comment

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

so much easier to review 🚀

Copy link
Contributor

@crodriguezvega crodriguezvega left a comment

Choose a reason for hiding this comment

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

Thank you, @chatton. I have left a bunch of suggestions.

modules/core/03-connection/keeper/keeper.go Outdated Show resolved Hide resolved
return errorsmod.Wrap(types.ErrInvalidChannelOrdering, "channel ordering must be a subset of the new ordering")
}

return k.connectionKeeper.CheckIsOpen(ctx, proposedUpgrade.ConnectionHops[0])
Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with @damiannolan. I think also the CheckIsOpen function should anyway return a boolean (unless we rename it to CheckExistsAndIsOpen, since it is actually checking two things). In my opinion I would just do the explicit check here.

}

if strings.TrimSpace(muf.Version) == "" {
return errorsmod.Wrap(ErrInvalidUpgrade, "proposed upgrade version cannot be empty")
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
return errorsmod.Wrap(ErrInvalidUpgrade, "proposed upgrade version cannot be empty")
return errorsmod.Wrap(ErrInvalidVersion, "version cannot be empty")

message Upgrade {
UpgradeFields upgrade_fields = 1 [(gogoproto.nullable) = false];
UpgradeTimeout timeout = 2 [(gogoproto.nullable) = false];
uint64 last_packet_sent = 3;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: last implies that there will be no more, while latest is the most recent but there will be more.

Suggested change
uint64 last_packet_sent = 3;
uint64 latest_packet_sent = 3;

Copy link
Member

@damiannolan damiannolan Apr 14, 2023

Choose a reason for hiding this comment

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

We could probably also make this work as next_sequence_send or without the offset prev_sequence_send / latest_sequence_send, which may be more conformant to the existing 04-channel primitives

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I like latest_sequence_send

uint64 last_packet_sent = 3;
}

// ModifiableUpgradeFields are the fields in a channel end which may be changed
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// ModifiableUpgradeFields are the fields in a channel end which may be changed
// UpgradeFields are the fields in a channel end which may be changed

expPass: false,
},
{
name: "attempt upgrade when connection is not set",
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
name: "attempt upgrade when connection is not set",
name: "fails when connection is not set",

path.EndpointA.SetConnection(connection)
},
expPass: false,
},
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess we should add test cases for empty channel version, packet sent and the upgrade timeout.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think these should be tested in a separate test that tests validate basic. This PR is already quite large, are you happy if we create a follow up PR to add tests for ValidateBasic of UpgradeFields and Upgrade?

expPass: false,
},
{
name: "attempt upgrade when connection is not open",
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
name: "attempt upgrade when connection is not open",
name: "fails when connection is not open",

}

// ValidateBasic performs a basic validation of the proposed upgrade fields
func (muf UpgradeFields) ValidateBasic() error {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
func (muf UpgradeFields) ValidateBasic() error {
func (uf UpgradeFields) ValidateBasic() error {

// - the proposed version is non-empty (checked in ModifiableUpgradeFields.ValidateBasic())
// - the proposed connection hops are not open
func (k Keeper) ValidateUpgradeFields(ctx sdk.Context, proposedUpgrade types.UpgradeFields, existingChannel types.Channel) error {
currentFields := types.UpgradeFields{
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we have maybe a helper function that creates an UpgradeFields object from a channel? Something like func From(channel types.Channel): UpgradeFields

message Upgrade {
UpgradeFields fields = 1 [(gogoproto.nullable) = false];
UpgradeTimeout timeout = 2 [(gogoproto.nullable) = false];
uint64 latest_sequence_send = 3;
Copy link
Contributor

@crodriguezvega crodriguezvega Apr 17, 2023

Choose a reason for hiding this comment

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

Just trying to understand how the flushing of in-flight packets will work, so excuses if I am missing something.

If we have 2 chains (A and B) and A initiates the upgrade (i.e. executes MsgChannelUpgradeInit) this latest_sequence_send in the message can be used by chain A to know that it needs to block further sending of packets. But how can it be used by chain B to block sends? Doesn't chain B keep track of its own next_sequence_send that does not necessarily match the next_sequence_send on chain A?

@@ -7,7 +7,7 @@ require (
github.com/cosmos/cosmos-sdk v0.47.0
github.com/cosmos/gogoproto v1.4.6
github.com/cosmos/ibc-go/v7 v7.0.0
github.com/cosmos/interchain-accounts v0.5.0
Copy link
Contributor Author

@chatton chatton Apr 17, 2023

Choose a reason for hiding this comment

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

there was an error I'm not quite sure I can explain happening suddenly

On branch cian/issue#3445-add-verifiable-upgrade-type-and-validate-basic-functions
        downloaded: h1:b9P3j2ZPziqYUYzb7zHVucZg84EfiGY+T+fPgszVxfI=                                                         [135/1987]
        sum.golang.org: h1:0Pzv5xHq+TVY7ExRIsrzJ33+t22TUoJ1k8UgSxvv1X4=

SECURITY ERROR
This download does NOT match the one reported by the checksum server.
The bits may have been replaced on the origin server, or an attacker may
have intercepted the download attempt.

For more information, see 'go help module-auth'.

github.com/cosmos/ibc-go/e2e/testsuite imports
        github.com/strangelove-ventures/interchaintest/v7/chain/cosmos imports
        github.com/cosmos/cosmos-sdk/x/upgrade imports
        github.com/cosmos/cosmos-sdk/x/upgrade/client/cli imports
        github.com/cosmos/cosmos-sdk/x/upgrade/plan imports
        github.com/hashicorp/go-getter imports
        cloud.google.com/go/storage tested by
        cloud.google.com/go/storage.test imports
        cloud.google.com/go/httpreplay imports
        cloud.google.com/go/httpreplay/internal/proxy imports
        github.com/google/martian/v3/mitm: github.com/cosmos/interchain-accounts@v0.5.0: verifying go.mod: checksum mismatch
        downloaded: h1:b9P3j2ZPziqYUYzb7zHVucZg84EfiGY+T+fPgszVxfI=
        sum.golang.org: h1:0Pzv5xHq+TVY7ExRIsrzJ33+t22TUoJ1k8UgSxvv1X4=

SECURITY ERROR
This download does NOT match the one reported by the checksum server.
The bits may have been replaced on the origin server, or an attacker may
have intercepted the download attempt.

For more information, see 'go help module-auth'.

github.com/cosmos/ibc-go/e2e/testsuite imports
        github.com/strangelove-ventures/interchaintest/v7/chain/cosmos imports
        github.com/cosmos/cosmos-sdk/x/upgrade imports
        github.com/cosmos/cosmos-sdk/x/upgrade/client/cli imports
        github.com/cosmos/cosmos-sdk/x/upgrade/plan imports
        github.com/hashicorp/go-getter imports
        github.com/aws/aws-sdk-go/service/s3 imports
        github.com/aws/aws-sdk-go/aws/awsutil imports
        github.com/jmespath/go-jmespath tested by
        github.com/jmespath/go-jmespath.test imports
        github.com/jmespath/go-jmespath/internal/testify/assert: github.com/cosmos/interchain-accounts@v0.5.0: verifying go.mod: check
sum mismatch
        downloaded: h1:b9P3j2ZPziqYUYzb7zHVucZg84EfiGY+T+fPgszVxfI=
        sum.golang.org: h1:0Pzv5xHq+TVY7ExRIsrzJ33+t22TUoJ1k8UgSxvv1X4=

SECURITY ERROR
This download does NOT match the one reported by the checksum server.
The bits may have been replaced on the origin server, or an attacker may
have intercepted the download attempt.

For more information, see 'go help module-auth'.

It looks like some sort of mismatch in v0.5.0 in the one we've imported and the one that is imported in interchaintest.

This error became reproducible locally after I cleaned my local modcache

Copy link
Contributor

@colin-axner colin-axner left a comment

Choose a reason for hiding this comment

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

LGTM! See relevant comments. Thanks for the quick changes! :)


// ValidateBasic performs a basic validation of the proposed upgrade fields
func (uf UpgradeFields) ValidateBasic() error {
if !collections.Contains(uf.Ordering, []Order{ORDERED, UNORDERED}) {
Copy link
Contributor

@colin-axner colin-axner Apr 17, 2023

Choose a reason for hiding this comment

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

I wonder if there is a variable we can use for []Order{ORDERED, UNORDERED}? Such as SupportedOrderings? Can be done in followup

modules/core/04-channel/keeper/keeper.go Show resolved Hide resolved
// - the proposed connection hops do not exist
// - the proposed version is non-empty (checked in UpgradeFields.ValidateBasic())
// - the proposed connection hops are not open
func (k Keeper) ValidateUpgradeFields(ctx sdk.Context, proposedUpgrade types.UpgradeFields, existingChannel types.Channel) error {
Copy link
Member

Choose a reason for hiding this comment

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

nit; wondering if existingChannel -> currentChannel would be better readability, maybe/maybe not

return errorsmod.Wrap(ErrInvalidUpgrade, "upgrade timeout cannot be empty")
}

// TODO: determine if last packet sequence sent can be 0?
Copy link
Member

Choose a reason for hiding this comment

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

sounds good to me

modules/core/04-channel/keeper/keeper.go Show resolved Hide resolved
// to allow the counterparty to block sends after the upgrade has started.
message Upgrade {
UpgradeFields fields = 1 [(gogoproto.nullable) = false];
UpgradeTimeout timeout = 2 [(gogoproto.nullable) = false];
Copy link
Member

Choose a reason for hiding this comment

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

When accessing the fields of timeout we use e.g. upgrade.Timeout.TimeoutHeight and upgrade.Timeout.TimeoutTimestamp.

We could considering dropping timeout_ prefix in the UpgradeTimeout type so that fields are upgrade.Timeout.Height and upgrade.Timeout.Timestamp.

This is out of scope of this PR tho, we can address this later if needs be.

Copy link
Member

@damiannolan damiannolan left a comment

Choose a reason for hiding this comment

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

LGTM, lets remove Upgrade.ValidateBasic() and update msgs to include UpgradeFields and UpgradeTimeout in follow ups.

@chatton chatton merged commit 34e800c into 04-channel-upgrades Apr 18, 2023
@chatton chatton deleted the cian/issue#3445-add-verifiable-upgrade-type-and-validate-basic-functions branch April 18, 2023 08:35
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 this pull request may close these issues.

6 participants