-
Notifications
You must be signed in to change notification settings - Fork 586
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
Add verifiable upgrade type and validate basic functions #3451
Conversation
return errorsmod.Wrap(ErrInvalidUpgrade, "upgrade timeout cannot be empty") | ||
} | ||
|
||
// TODO: determine if last packet sequence sent can be 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.
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?
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 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?
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.
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
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.
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.
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.
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
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.
sounds good to me
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 I'm happy enough with that approach 👍
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, 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?
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.
Absolutely!
…e-upgrade-type-and-validate-basic-functions
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.
Looks great. I left some suggestions, mostly around naming
return errorsmod.Wrap(ErrInvalidUpgrade, "upgrade timeout cannot be empty") | ||
} | ||
|
||
// TODO: determine if last packet sequence sent can be 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.
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
return errorsmod.Wrap(types.ErrInvalidChannelOrdering, "channel ordering must be a subset of the new ordering") | ||
} | ||
|
||
return k.connectionKeeper.CheckIsOpen(ctx, proposedUpgrade.ConnectionHops[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.
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.
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 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
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 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.
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 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 😄
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 happy to just remove the function entirely and do the explicit checking where needed! 👍
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.
so much easier to review 🚀
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.
Thank you, @chatton. I have left a bunch of suggestions.
return errorsmod.Wrap(types.ErrInvalidChannelOrdering, "channel ordering must be a subset of the new ordering") | ||
} | ||
|
||
return k.connectionKeeper.CheckIsOpen(ctx, proposedUpgrade.ConnectionHops[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.
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") |
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.
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; |
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.
nit: last
implies that there will be no more, while latest
is the most recent but there will be more.
uint64 last_packet_sent = 3; | |
uint64 latest_packet_sent = 3; |
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 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
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 like latest_sequence_send
uint64 last_packet_sent = 3; | ||
} | ||
|
||
// ModifiableUpgradeFields are the fields in a channel end which may be changed |
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.
// 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", |
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.
name: "attempt upgrade when connection is not set", | |
name: "fails when connection is not set", |
path.EndpointA.SetConnection(connection) | ||
}, | ||
expPass: false, | ||
}, |
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 guess we should add test cases for empty channel version, packet sent and the upgrade timeout.
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 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", |
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.
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 { |
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.
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{ |
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.
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; |
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.
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 |
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.
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
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! 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}) { |
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 wonder if there is a variable we can use for []Order{ORDERED, UNORDERED}
? Such as SupportedOrderings
? Can be done in followup
// - 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 { |
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.
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? |
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.
sounds good to me
// 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]; |
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.
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.
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, lets remove Upgrade.ValidateBasic()
and update msgs to include UpgradeFields
and UpgradeTimeout
in follow ups.
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:
latest_sequence_send
field.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.
docs/
) or specification (x/<module>/spec/
).godoc
comments.Files changed
in the Github PR explorer.Codecov Report
in the comment section below once CI passes.