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

Modify IBC client governance unfreezing to reflect ADR changes #8405

Merged
merged 55 commits into from
Feb 16, 2021
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
a654f27
update proto files
colin-axner Jan 19, 2021
9aa500c
Merge branch 'master' of github.com:cosmos/cosmos-sdk into colin/8197…
colin-axner Jan 19, 2021
51b5001
Merge branch 'master' of github.com:cosmos/cosmos-sdk into colin/8197…
colin-axner Jan 19, 2021
a47dbbb
make proto-gen
colin-axner Jan 19, 2021
73b2821
update clienttypes
colin-axner Jan 21, 2021
8d27dcf
update localhost and solo machine
colin-axner Jan 21, 2021
fcb97ff
refactor tm client proposal handling
colin-axner Jan 21, 2021
0e93fb6
Merge branch 'master' into colin/8197-unfreeze-clients-gov-prop
colin-axner Jan 21, 2021
bbd6d26
copy metadata
colin-axner Jan 21, 2021
0282996
Merge branch 'colin/8197-unfreeze-clients-gov-prop' of github.com:cos…
colin-axner Jan 21, 2021
ce4fe37
self review fixes
colin-axner Jan 22, 2021
c4cb7de
update 02-client keeper tests
colin-axner Jan 22, 2021
a7ef263
fix 02-client type tests
colin-axner Jan 22, 2021
d15f901
fix localhost and solomachine tests
colin-axner Jan 22, 2021
0c6d98d
begin updating tm tests
colin-axner Jan 26, 2021
fa28622
partially fix tm tests
colin-axner Jan 28, 2021
4c6688c
increase codecov
colin-axner Jan 29, 2021
a041987
add more tests
colin-axner Jan 29, 2021
eefccd5
Merge branch 'master' of github.com:cosmos/cosmos-sdk into colin/8197…
colin-axner Jan 29, 2021
a5bc8d5
add changelog
colin-axner Jan 29, 2021
861b1cb
update specs
colin-axner Jan 29, 2021
908e5a0
add docs
colin-axner Jan 29, 2021
51905db
Merge branch 'master' into colin/8197-unfreeze-clients-gov-prop
colin-axner Feb 1, 2021
c74a5af
fix test
colin-axner Feb 1, 2021
dbe1341
Merge branch 'colin/8197-unfreeze-clients-gov-prop' of github.com:cos…
colin-axner Feb 1, 2021
0c96f99
modify adr
colin-axner Feb 1, 2021
0fda643
allow modified chain-ids
colin-axner Feb 2, 2021
f17b5e5
Merge branch 'master' of github.com:cosmos/cosmos-sdk into colin/8197…
colin-axner Feb 2, 2021
e141f19
add CLI command
colin-axner Feb 2, 2021
18b82ba
fix typos
colin-axner Feb 2, 2021
2369e07
Merge branch 'master' into colin/8197-unfreeze-clients-gov-prop
colin-axner Feb 2, 2021
bc6a359
fix lint
colin-axner Feb 2, 2021
2b55583
Merge branch 'colin/8197-unfreeze-clients-gov-prop' of github.com:cos…
colin-axner Feb 2, 2021
ef2fb8a
Apply suggestions from code review
colin-axner Feb 2, 2021
c9bca48
update docs, rm example
colin-axner Feb 5, 2021
6a96b6d
Merge branch 'colin/8197-unfreeze-clients-gov-prop' of github.com:cos…
colin-axner Feb 5, 2021
78d04f4
Update docs/ibc/proposals.md
colin-axner Feb 8, 2021
20b7a5d
merge master
colin-axner Feb 8, 2021
696b5f2
update height checks to reflect chain-id changes cc @AdityaSripal
colin-axner Feb 8, 2021
0d60013
Merge branch 'master' into colin/8197-unfreeze-clients-gov-prop
colin-axner Feb 10, 2021
b14ea6c
Apply suggestions from code review
colin-axner Feb 11, 2021
bbb4acf
Apply suggestions from code review
colin-axner Feb 11, 2021
10dfe43
address most of @AdityaSripal suggestions
colin-axner Feb 11, 2021
1edf9ab
Merge branch 'master' of github.com:cosmos/cosmos-sdk into colin/8197…
colin-axner Feb 11, 2021
eb5823c
Merge branch 'colin/8197-unfreeze-clients-gov-prop' of github.com:cos…
colin-axner Feb 11, 2021
e01e8c5
Merge branch 'master' of github.com:cosmos/cosmos-sdk into colin/8197…
colin-axner Feb 12, 2021
1869fbd
update docs per review suggestions
colin-axner Feb 12, 2021
84ebde6
Update x/ibc/core/02-client/types/proposal.go
colin-axner Feb 12, 2021
912fdf5
add proposal handler
colin-axner Feb 12, 2021
613cf69
Merge branch 'colin/8197-unfreeze-clients-gov-prop' of github.com:cos…
colin-axner Feb 12, 2021
b7834c6
register proposal type
colin-axner Feb 12, 2021
1ba2c3d
register proposal on codec
colin-axner Feb 12, 2021
9fae3ce
fix routing
colin-axner Feb 12, 2021
26837e2
Merge branch 'master' into colin/8197-unfreeze-clients-gov-prop
colin-axner Feb 16, 2021
a5c09a7
Merge branch 'colin/8197-unfreeze-clients-gov-prop' of github.com:cos…
colin-axner Feb 16, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

* (x/{bank,distrib,gov,slashing,staking}) [\#8363](https://github.com/cosmos/cosmos-sdk/issues/8363) Store keys have been modified to allow for variable-length addresses.
* (x/ibc) [\#8266](https://github.com/cosmos/cosmos-sdk/issues/8266) Add amino JSON for IBC messages in order to support Ledger text signing.
* (x/ibc) [\#8405](https://github.com/cosmos/cosmos-sdk/pull/8405) Refactor IBC client update governance proposals to use a substitute client to update a frozen or expired client.
* (x/evidence) [\#8502](https://github.com/cosmos/cosmos-sdk/pull/8502) `HandleEquivocationEvidence` persists the evidence to state.

### Improvements
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ We elect not to deal with chains which have actually halted, which is necessaril
1. Add a new governance proposal type, `ClientUpdateProposal`, in the `x/ibc` module
1. Extend the base `Proposal` with two client identifiers (`string`) and an initial height ('exported.Height').
1. The first client identifier is the proposed client to be updated. This client must be either frozen or expired.
1. The second client is a substitute client. It carries all the state for the client which may be updated. It must have identitical client and chain parameters to the client which may be updated (except for latest height and frozen height). It should be continually updated during the voting period.
1. The second client is a substitute client. It carries all the state for the client which may be updated. It must have identitical client and chain parameters to the client which may be updated (except for latest height, frozen height, and chain-id). It should be continually updated during the voting period.
1. The initial height represents the starting height consensus states which will be copied from the substitute client to the frozen/expired client.
1. If this governance proposal passes, the client on trial will be updated with all the state of the substitute, if and only if:
1. `allow_governance_override_after_expiry` is true and the client has expired (`Expired()` returns true)
Expand Down
13 changes: 8 additions & 5 deletions docs/core/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -7901,17 +7901,20 @@ client.
<a name="ibc.core.client.v1.ClientUpdateProposal"></a>

### ClientUpdateProposal
ClientUpdateProposal is a governance proposal. If it passes, the client is
updated with the provided header. The update may fail if the header is not
valid given certain conditions specified by the client implementation.
ClientUpdateProposal is a governance proposal. If it passes, the substitute client's
consensus states starting from the 'initial height' are copied over to the subjects
client state. The proposal handler may fail if the subject and the substitute do not
match in client and chain parameters (with exception to latest height, frozen height, and chain-id).
The updated client must also be valid (cannot be expired).


| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `title` | [string](#string) | | the title of the update proposal |
| `description` | [string](#string) | | the description of the proposal |
| `client_id` | [string](#string) | | the client identifier for the client to be updated if the proposal passes |
| `header` | [google.protobuf.Any](#google.protobuf.Any) | | the header used to update the client if the proposal passes |
| `subject_client_id` | [string](#string) | | the client identifier for the client to be updated if the proposal passes |
| `substitute_client_id` | [string](#string) | | the substitute client identifier for the client standing in for the subject client |
| `initial_height` | [Height](#ibc.core.client.v1.Height) | | the intital height to copy consensus states from the substitute to the subject |



Expand Down
1 change: 1 addition & 0 deletions docs/ibc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This repository contains reference documentation for the IBC protocol integratio
2. [Integration](./integration.md)
3. [Customization](./custom.md)
4. [Relayer](./relayer.md)
5. [Governance Proposals](./proposals.md)

After reading about IBC, head on to the [Building Modules
documentation](../building-modules/README.md) to learn more about the process of building modules.
38 changes: 38 additions & 0 deletions docs/ibc/proposals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<!--
order: 5
-->

# Governance Proposals

In uncommon situations, a highly valued client may become frozen due to uncontrollable
cwgoes marked this conversation as resolved.
Show resolved Hide resolved
circumstances. A highly valued client might have hundreds of channels being actively used.
Some of those channels might have a significant amount of locked tokens used for ICS 20.

If the one third of the validator set the chain the client represents decides to collude,
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
they can sign off on two valid but conflicting headers each signed by the other one third
of the honest validator sets. The light client can now be updated with two valid, but conflicting
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
headers at the same height. The light client cannot know which header is trustworthy and therefore
evidence of such misbehaviour is likely to be submitted resulting in a frozen light client.

Frozen light clients cannot be updated under any circumstance except via a governance proposal.
Since validators can arbitarily agree to make state transitions that defy the written code, a
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand this sentence, what is the relation? Do you mean that the security model would be the same?

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 was just trying to refer to the security model outlined in the ADR. Why this feature is safe and why it requires a governance proposal. Is there a way I could clarify this better?

Copy link
Contributor

Choose a reason for hiding this comment

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

"a quorum validators can sign arbitrary state roots which may not be valid executions of the state machine"

something like this is clearer I think

governance proposal has been added to ease the complexity of unfreezing or updating clients
which have become "stuck". Unfreezing clients, re-enables all of the channels built upon that
client. This may result in recovery of otherwise lost funds.

Tendermint light clients may also become expired if the trusting period has passed since their
last update. If a chain undergoes an unplanned upgrade, there may be no commitment to that upgrade
signed by the validator set before the chain-id changes. In this situation, the validator set of
the last valid update for the light client is never expected to produce another valid header since
the chain-id has changed, which will ultimately lead the on-chain light client to become expired.
Copy link
Member

Choose a reason for hiding this comment

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

Can this be two separate paragraphs since they're two separate situations?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What is two separate situations? I was intending to show that unplanned upgrades results in client expiry

Copy link
Contributor

Choose a reason for hiding this comment

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

I also don't understand the question

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, do you mean that they could be expired just because no headers have been submitted even if there was no unplanned upgrade?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, that should be clarified.

Copy link
Member

Choose a reason for hiding this comment

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

The case when a light client is expired is a different situation from when the chain undergoes an unplanned upgrades.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated to separate general expiry from expiry due to unplanned upgrade by the counterparty chain


In the case that a highly valued light client is frozen, expired, or rendered non-updateable, a
Copy link
Member

Choose a reason for hiding this comment

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

In the cases where the client is frozen or had an unplanned upgrade, there may be many clients of the counterparty chain that are now non-updatable. The proposal should be able to specify multiple subject-client-ids as a string array. So that a single proposal can unfreeze all clients of the chain in question.

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 agree this seems practical, but I have some concerns:

  • gas usage, is there a limit to gas used by a governance proposal? There could be a lot of consensus states and copying a lot of consensus states for multiple clients could be problematic in practice (especially if no one tests or simulates the results beforehand)
  • the original ADR for this feature only specifies updating a single client, I believe because this should be used in rare cases

As I stated below, I think the trusting period needs to remain the same. It seems unlikely to me that there would exist identical clients that both need to be updated, since if they are identical, only 1 is needed

Copy link
Member

Choose a reason for hiding this comment

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

  • gas usage is an issue. I don't know when this would be executed. I guess at the end of a block? There's a general block gas limit. Though I don't know what happens to Endblock calculations if they go over it.
  • I believe trusting period doesn't need to stay the same. But if we decide otherwise, then we can stick to a single subject client

governance proposal may be submitted to update this client, known as the subject client. The
proposal includes the client identifier for the subject, the client identifier for a substitute
client, and an initial height to reference the substitute client from. Light client implementations
may implement custom updating logic, but in most cases, the subject will be updated with information
from the substitute client, if the proposal passes. The substitute client is used as a "stand in"
while the subject is on trial. It is best practice to create a substitute client *after* the subject
has become frozen to avoid the substitute from also becoming frozen. An active substitute client
allows headers to be submitted during the voting period to prevent accidental expiry once the proposal
passes.
16 changes: 10 additions & 6 deletions proto/ibc/core/client/v1/client.proto
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,23 @@ message ClientConsensusStates {
[(gogoproto.moretags) = "yaml:\"consensus_states\"", (gogoproto.nullable) = false];
}

// ClientUpdateProposal is a governance proposal. If it passes, the client is
// updated with the provided header. The update may fail if the header is not
// valid given certain conditions specified by the client implementation.
// ClientUpdateProposal is a governance proposal. If it passes, the substitute client's
// consensus states starting from the 'initial height' are copied over to the subjects
// client state. The proposal handler may fail if the subject and the substitute do not
// match in client and chain parameters (with exception to latest height, frozen height, and chain-id).
// The updated client must also be valid (cannot be expired).
message ClientUpdateProposal {
option (gogoproto.goproto_getters) = false;
// the title of the update proposal
string title = 1;
// the description of the proposal
string description = 2;
// the client identifier for the client to be updated if the proposal passes
string client_id = 3 [(gogoproto.moretags) = "yaml:\"client_id\""];
// the header used to update the client if the proposal passes
google.protobuf.Any header = 4;
string subject_client_id = 3 [(gogoproto.moretags) = "yaml:\"subject_client_id\""];
Copy link
Member

Choose a reason for hiding this comment

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

This should be repeated string

// the substitute client identifier for the client standing in for the subject client
string substitute_client_id = 4 [(gogoproto.moretags) = "yaml:\"susbtitute_client_id\""];
// the intital height to copy consensus states from the substitute to the subject
Height initial_height = 5 [(gogoproto.moretags) = "yaml:\"initial_height\"", (gogoproto.nullable) = false];
}

// Height is a monotonically increasing data type
Expand Down
69 changes: 69 additions & 0 deletions x/ibc/core/02-client/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import (
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/version"
govcli "github.com/cosmos/cosmos-sdk/x/gov/client/cli"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
"github.com/cosmos/cosmos-sdk/x/ibc/core/02-client/types"
"github.com/cosmos/cosmos-sdk/x/ibc/core/exported"
)
Expand Down Expand Up @@ -244,3 +247,69 @@ func NewUpgradeClientCmd() *cobra.Command {

return cmd
}

// NewCmdSubmitUpdateClientProposal implements a command handler for submitting an update IBC client proposal transaction.
func NewCmdSubmitUpdateClientProposal() *cobra.Command {
cmd := &cobra.Command{
Use: "update-client [subject-client-id] [substitute-client-id] [initial-height] [flags]",
Args: cobra.ExactArgs(3),
Short: "Submit an update IBC client proposal",
Long: "Submit an update IBC client proposal along with an initial deposit.\n" +
"Please specify a subject client identifier you want to update..\n" +
"Please specify the substitute client the subject client will use and the initial height to reference the substitute client's state.",
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

title, err := cmd.Flags().GetString(govcli.FlagTitle)
if err != nil {
return err
}

description, err := cmd.Flags().GetString(govcli.FlagDescription)
if err != nil {
return err
}

subjectClientID := args[0]
substituteClientID := args[1]

initialHeight, err := types.ParseHeight(args[2])
if err != nil {
return err
}

content := types.NewClientUpdateProposal(title, description, subjectClientID, substituteClientID, initialHeight)

from := clientCtx.GetFromAddress()

depositStr, err := cmd.Flags().GetString(govcli.FlagDeposit)
if err != nil {
return err
}
deposit, err := sdk.ParseCoinsNormalized(depositStr)
if err != nil {
return err
}

msg, err := govtypes.NewMsgSubmitProposal(content, deposit, from)
if err != nil {
return err
}

if err = msg.ValidateBasic(); err != nil {
return err
}

return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
},
}

cmd.Flags().String(govcli.FlagTitle, "", "title of proposal")
cmd.Flags().String(govcli.FlagDescription, "", "description of proposal")
cmd.Flags().String(govcli.FlagDeposit, "", "deposit of proposal")

return cmd
}
41 changes: 25 additions & 16 deletions x/ibc/core/02-client/keeper/proposal.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,49 @@ import (
"github.com/cosmos/cosmos-sdk/x/ibc/core/exported"
)

// ClientUpdateProposal will try to update the client with the new header if and only if
// the proposal passes. The localhost client is not allowed to be modified with a proposal.
// ClientUpdateProposal will retrieve the subject and substitute client.
// The initial height must be greater than the latest height of the subject
// client. A callback will occur to the subject client state with the client
// prefixed store being provided for both the subject and the substitute client.
// The localhost client is not allowed to be modified with a proposal. The IBC
// client implementations are responsible for validating the parameters of the
// subtitute (enusring they match the subject's parameters) as well as copying
// the necessary consensus states from the subtitute to the subject client
// store.
func (k Keeper) ClientUpdateProposal(ctx sdk.Context, p *types.ClientUpdateProposal) error {
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 thought of trying to enforce subject and substitute client types being equal here but just doing ClientType() doesn't add any more security from a malicious light client implementation, and if the light client implementation is malicious, it could just mess with other clients of its own type.

It is up to governance never to pass a vote where the subject belongs to a malicious light client implementation. Ideally, best practice (which I will document/recommend) would be to create an entirely new substitute client after the subject became frozen/expired. This way no other connection/channels are relying on the substitute.

if p.ClientId == exported.Localhost {
if p.SubjectClientId == exported.Localhost || p.SubstituteClientId == exported.Localhost {
return sdkerrors.Wrap(types.ErrInvalidUpdateClientProposal, "cannot update localhost client with proposal")
}

clientState, found := k.GetClientState(ctx, p.ClientId)
subjectClientState, found := k.GetClientState(ctx, p.SubjectClientId)
if !found {
return sdkerrors.Wrapf(types.ErrClientNotFound, "cannot update client with ID %s", p.ClientId)
return sdkerrors.Wrapf(types.ErrClientNotFound, "subject client with ID %s", p.SubjectClientId)
}

header, err := types.UnpackHeader(p.Header)
if err != nil {
return err
if subjectClientState.GetLatestHeight().GTE(p.InitialHeight) {
return sdkerrors.Wrapf(types.ErrInvalidHeight, "subject client state latest height is greater or equal to initial height (%s >= %s)", subjectClientState.GetLatestHeight(), p.InitialHeight)
}

substituteClientState, found := k.GetClientState(ctx, p.SubstituteClientId)
if !found {
return sdkerrors.Wrapf(types.ErrClientNotFound, "substitute client with ID %s", p.SubstituteClientId)
}

clientState, consensusState, err := clientState.CheckProposedHeaderAndUpdateState(ctx, k.cdc, k.ClientStore(ctx, p.ClientId), header)
clientState, err := subjectClientState.CheckSubstituteAndUpdateState(ctx, k.cdc, k.ClientStore(ctx, p.SubjectClientId), k.ClientStore(ctx, p.SubstituteClientId), substituteClientState, p.InitialHeight)
if err != nil {
return err
}
k.SetClientState(ctx, p.SubjectClientId, clientState)

k.SetClientState(ctx, p.ClientId, clientState)
k.SetClientConsensusState(ctx, p.ClientId, header.GetHeight(), consensusState)

k.Logger(ctx).Info("client updated after governance proposal passed", "client-id", p.ClientId, "height", clientState.GetLatestHeight().String())
k.Logger(ctx).Info("client updated after governance proposal passed", "client-id", p.SubjectClientId, "height", clientState.GetLatestHeight().String())

defer func() {
telemetry.IncrCounterWithLabels(
[]string{"ibc", "client", "update"},
1,
[]metrics.Label{
telemetry.NewLabel("client-type", clientState.ClientType()),
telemetry.NewLabel("client-id", p.ClientId),
telemetry.NewLabel("client-id", p.SubjectClientId),
telemetry.NewLabel("update-type", "proposal"),
},
)
Expand All @@ -53,9 +62,9 @@ func (k Keeper) ClientUpdateProposal(ctx sdk.Context, p *types.ClientUpdatePropo
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeUpdateClientProposal,
sdk.NewAttribute(types.AttributeKeyClientID, p.ClientId),
sdk.NewAttribute(types.AttributeKeySubjectClientID, p.SubjectClientId),
sdk.NewAttribute(types.AttributeKeyClientType, clientState.ClientType()),
sdk.NewAttribute(types.AttributeKeyConsensusHeight, header.GetHeight().String()),
sdk.NewAttribute(types.AttributeKeyConsensusHeight, clientState.GetLatestHeight().String()),
),
)

Expand Down
Loading