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

chore: remove GetHeight from ClientMessage interface #1285

Merged
51 changes: 27 additions & 24 deletions modules/core/02-client/keeper/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,6 @@ func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, clientMsg exporte
return err
}

// Marshal the ClientMessage as an Any and encode the resulting bytes to hex.
// This prevents the event value from containing invalid UTF-8 characters
// which may cause data to be lost when JSON encoding/decoding.
clientMsgStr := hex.EncodeToString(types.MustMarshalClientMessage(k.cdc, clientMsg))
damiannolan marked this conversation as resolved.
Show resolved Hide resolved

// set default consensus height with header height
consensusHeight := clientMsg.GetHeight()

foundMisbehaviour := clientState.CheckForMisbehaviour(ctx, k.cdc, clientStore, clientMsg)
if foundMisbehaviour {
clientState.UpdateStateOnMisbehaviour(ctx, k.cdc, clientStore, clientMsg)
Expand All @@ -96,29 +88,40 @@ func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, clientMsg exporte
)
}()

EmitSubmitMisbehaviourEventOnUpdate(ctx, clientID, clientState.ClientType(), consensusHeight, clientMsgStr)
EmitSubmitMisbehaviourEvent(ctx, clientID, clientState)

return nil
}

clientState.UpdateState(ctx, k.cdc, clientStore, clientMsg)
consensusHeights, err := clientState.UpdateState(ctx, k.cdc, clientStore, clientMsg)
if err != nil {
return err
}

k.Logger(ctx).Info("client state updated", "client-id", clientID, "height", consensusHeight.String())
if len(consensusHeights) != 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 think not emitting UpdateClientEvent on a successful MsgUpdateClient is problematic. I may be wrong though. Would be good to check with relayers what the best expected functionality is for dup updates

The main odd thing is if you query for UpdateClient events, this message wouldn't come up. To me, it'd make more sense if the event was emitted, but indicated it was a duplicate update

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'm happy to change this to return the height from the ClientState implementation on duplicate updates.
However, I don't think we can provide information about duplicate updates in the events, that would only be possible if emitting from 07-tendermint for example, but as we discussed before they should be emitted from 02-client in a somewhat standardised fashion.

If we're in agreement, I can update this PR to return the height on duplicates and also make the changes to remove the error return value and panic instead.

Wdyt?

Copy link
Contributor

Choose a reason for hiding this comment

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

I was thinking if consensus_heights is empty, we should just return "" in the consensus heights attribute. Thoughts?

In favor of removing error return 👍

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 updated and removed the error return, and also returning the height on duplicate header updates from 07!

k.Logger(ctx).Info("client state updated", "client-id", clientID, "heights", consensusHeights)

defer func() {
telemetry.IncrCounterWithLabels(
[]string{"ibc", "client", "update"},
1,
[]metrics.Label{
telemetry.NewLabel(types.LabelClientType, clientState.ClientType()),
telemetry.NewLabel(types.LabelClientID, clientID),
telemetry.NewLabel(types.LabelUpdateType, "msg"),
},
)
}()
defer func() {
telemetry.IncrCounterWithLabels(
[]string{"ibc", "client", "update"},
1,
[]metrics.Label{
telemetry.NewLabel(types.LabelClientType, clientState.ClientType()),
telemetry.NewLabel(types.LabelClientID, clientID),
telemetry.NewLabel(types.LabelUpdateType, "msg"),
},
)
}()

// emitting events in the keeper emits for both begin block and handler client updates
EmitUpdateClientEvent(ctx, clientID, clientState.ClientType(), consensusHeight, clientMsgStr)
// Marshal the ClientMessage as an Any and encode the resulting bytes to hex.
// This prevents the event value from containing invalid UTF-8 characters
// which may cause data to be lost when JSON encoding/decoding.
clientMsgStr := hex.EncodeToString(types.MustMarshalClientMessage(k.cdc, clientMsg))

// emitting events in the keeper emits for both begin block and handler client updates
EmitUpdateClientEvent(ctx, clientID, clientState.ClientType(), consensusHeights, clientMsgStr)

}

return nil
}
Expand Down
16 changes: 12 additions & 4 deletions modules/core/02-client/keeper/events.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package keeper

import (
"strings"

sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/cosmos/ibc-go/v3/modules/core/02-client/types"
Expand All @@ -24,13 +26,19 @@ func EmitCreateClientEvent(ctx sdk.Context, clientID string, clientState exporte
}

// EmitUpdateClientEvent emits an update client event
func EmitUpdateClientEvent(ctx sdk.Context, clientID string, clientType string, consensusHeight exported.Height, clientMsgStr string) {
func EmitUpdateClientEvent(ctx sdk.Context, clientID string, clientType string, consensusHeights []exported.Height, clientMsgStr string) {
var consensusHeightStr []string
for _, height := range consensusHeights {
consensusHeightStr = append(consensusHeightStr, height.String())
}

ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeUpdateClient,
sdk.NewAttribute(types.AttributeKeyClientID, clientID),
sdk.NewAttribute(types.AttributeKeyClientType, clientType),
sdk.NewAttribute(types.AttributeKeyConsensusHeight, consensusHeight.String()),
sdk.NewAttribute(types.AttributeKeyConsensusHeight, consensusHeights[0].String()),
Copy link
Contributor

Choose a reason for hiding this comment

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

hmm, I think we need a len(consensusHeights) > 0 check

Copy link
Member Author

Choose a reason for hiding this comment

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

This was being checked in the UpdateClient() method of keeper.go.

if len(consensusHeights) != 0 {
    // emit events, otherwise no-op & return nil
}

return nil

Copy link
Contributor

Choose a reason for hiding this comment

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

ah yes. The only strange part is that this function assumes you pass in a non empty array of consensus heights

Unrelated, could we add // deprecated next to the consensus height attribute

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 backport consensusHeights attribute to older releases? That way relayers could switch to it without waiting for all chains to upgrade to this version

Copy link
Member Author

@damiannolan damiannolan Apr 26, 2022

Choose a reason for hiding this comment

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

The only strange part is that this function assumes you pass in a non empty array of consensus heights

Agree, it is kind of strange. I'll adapt to check len(consensusHeights) inside event emission func and remove the check from the UpdateClient keeper method.

Should we backport consensusHeights attribute to older releases?

Sounds like a good idea, but this would require a separate PR to main, correct?

Edit: I'll also add deprecated comment in-line 👍 Now that I'm looking at this once again, AttributeKeyConsensusHeight is used for create and upgrade events as a single height value, and should probably be maintained. In which case, I think the deprecation notice should only be here, solely for the update events and not in types/events.go

Copy link
Contributor

Choose a reason for hiding this comment

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

good catch!

sdk.NewAttribute(types.AttributeKeyConsensusHeight, strings.Join(consensusHeightStr, ",")),
sdk.NewAttribute(types.AttributeKeyHeader, clientMsgStr),
),
sdk.NewEvent(
Expand Down Expand Up @@ -80,14 +88,14 @@ func EmitSubmitMisbehaviourEvent(ctx sdk.Context, clientID string, clientState e
}

// EmitSubmitMisbehaviourEventOnUpdate emits a client misbehaviour event on a client update event
func EmitSubmitMisbehaviourEventOnUpdate(ctx sdk.Context, clientID string, clientType string, consensusHeight exported.Height, headerStr string) {
func EmitSubmitMisbehaviourEventOnUpdate(ctx sdk.Context, clientID string, clientType string, consensusHeight exported.Height, clientMsgStr string) {
damiannolan marked this conversation as resolved.
Show resolved Hide resolved
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeSubmitMisbehaviour,
sdk.NewAttribute(types.AttributeKeyClientID, clientID),
sdk.NewAttribute(types.AttributeKeyClientType, clientType),
sdk.NewAttribute(types.AttributeKeyConsensusHeight, consensusHeight.String()),
sdk.NewAttribute(types.AttributeKeyHeader, headerStr),
sdk.NewAttribute(types.AttributeKeyHeader, clientMsgStr),
),
)
}
2 changes: 1 addition & 1 deletion modules/core/02-client/legacy/v100/solomachine.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func (cs *ClientState) VerifyClientMessage(
}

// UpdateState panis!
func (cs *ClientState) UpdateState(_ sdk.Context, _ codec.BinaryCodec, _ sdk.KVStore, _ exported.ClientMessage) error {
func (cs *ClientState) UpdateState(_ sdk.Context, _ codec.BinaryCodec, _ sdk.KVStore, _ exported.ClientMessage) ([]exported.Height, error) {
panic("legacy solo machine is deprecated!")
}

Expand Down
11 changes: 6 additions & 5 deletions modules/core/02-client/types/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import (

// IBC client events
const (
AttributeKeyClientID = "client_id"
AttributeKeySubjectClientID = "subject_client_id"
AttributeKeyClientType = "client_type"
AttributeKeyConsensusHeight = "consensus_height"
AttributeKeyHeader = "header"
AttributeKeyClientID = "client_id"
AttributeKeySubjectClientID = "subject_client_id"
AttributeKeyClientType = "client_type"
AttributeKeyConsensusHeight = "consensus_height"
damiannolan marked this conversation as resolved.
Show resolved Hide resolved
AttributeKeyConsensusHeights = "consensus_heights"
AttributeKeyHeader = "header"
)

// IBC client events vars
Expand Down
6 changes: 3 additions & 3 deletions modules/core/exported/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ type ClientState interface {
VerifyClientMessage(ctx sdk.Context, cdc codec.BinaryCodec, clientStore sdk.KVStore, clientMsg ClientMessage) error

// UpdateState updates and stores as necessary any associated information for an IBC client, such as the ClientState and corresponding ConsensusState.
// An error is returned if ClientMessage is of type Misbehaviour
UpdateState(sdk.Context, codec.BinaryCodec, sdk.KVStore, ClientMessage) error
// Upon successful update, a list of consensus heights is returned.
// An error is returned if ClientMessage is of type Misbehaviour. It assumes the ClientMessage has already been verified.
UpdateState(sdk.Context, codec.BinaryCodec, sdk.KVStore, ClientMessage) ([]Height, error)
colin-axner marked this conversation as resolved.
Show resolved Hide resolved

// Update and Misbehaviour functions
CheckSubstituteAndUpdateState(ctx sdk.Context, cdc codec.BinaryCodec, subjectClientStore, substituteClientStore sdk.KVStore, substituteClient ClientState) (ClientState, error)
Expand Down Expand Up @@ -203,7 +204,6 @@ type ConsensusState interface {
type ClientMessage interface {
proto.Message

GetHeight() Height
ClientType() string
ValidateBasic() error
}
Expand Down
7 changes: 4 additions & 3 deletions modules/light-clients/06-solomachine/types/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,11 @@ func (cs ClientState) verifyMisbehaviour(ctx sdk.Context, cdc codec.BinaryCodec,
}

// UpdateState updates the consensus state to the new public key and an incremented sequence.
func (cs ClientState) UpdateState(ctx sdk.Context, cdc codec.BinaryCodec, clientStore sdk.KVStore, clientMsg exported.ClientMessage) error {
// A list containing the updated consensus height is returned.
func (cs ClientState) UpdateState(ctx sdk.Context, cdc codec.BinaryCodec, clientStore sdk.KVStore, clientMsg exported.ClientMessage) ([]exported.Height, error) {
smHeader, ok := clientMsg.(*Header)
if !ok {
return sdkerrors.Wrapf(clienttypes.ErrInvalidClientType, "expected %T got %T", Header{}, clientMsg)
return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidClientType, "expected %T got %T", Header{}, clientMsg)
}

// create new solomachine ConsensusState
Expand All @@ -99,7 +100,7 @@ func (cs ClientState) UpdateState(ctx sdk.Context, cdc codec.BinaryCodec, client

clientStore.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(cdc, &cs))

return nil
return []exported.Height{smHeader.GetHeight()}, nil
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
}

// CheckForMisbehaviour returns true for type Misbehaviour (passed VerifyClientMessage check), otherwise returns false
Expand Down
6 changes: 5 additions & 1 deletion modules/light-clients/06-solomachine/types/update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -441,11 +441,14 @@ func (suite *SoloMachineTestSuite) TestUpdateState() {
suite.Run(tc.name, func() {
tc.setup() // setup test

err := clientState.UpdateState(suite.chainA.GetContext(), suite.chainA.Codec, suite.store, clientMsg)
consensusHeights, err := clientState.UpdateState(suite.chainA.GetContext(), suite.chainA.Codec, suite.store, clientMsg)

if tc.expPass {
suite.Require().NoError(err)

suite.Require().Len(consensusHeights, 1)
suite.Require().Equal(clienttypes.NewHeight(0, clientMsg.(*types.Header).Sequence), consensusHeights[0])
colin-axner marked this conversation as resolved.
Show resolved Hide resolved

clientStateBz := suite.store.Get(host.ClientStateKey())
suite.Require().NotEmpty(clientStateBz)

Expand All @@ -457,6 +460,7 @@ func (suite *SoloMachineTestSuite) TestUpdateState() {
suite.Require().Equal(clientMsg.(*types.Header).NewDiversifier, newClientState.(*types.ClientState).ConsensusState.Diversifier)
suite.Require().Equal(clientMsg.(*types.Header).Timestamp, newClientState.(*types.ClientState).ConsensusState.Timestamp)
} else {
suite.Require().Empty(consensusHeights)
suite.Require().Error(err)
}

Expand Down
10 changes: 6 additions & 4 deletions modules/light-clients/07-tendermint/types/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,19 +154,20 @@ func (cs *ClientState) verifyHeader(
// If we are updating to a past height, a consensus state is created for that height to be persisted in client store
// If we are updating to a future height, the consensus state is created and the client state is updated to reflect
// the new latest height
// A list containing the updated consensus height is returned.
// UpdateState must only be used to update within a single revision, thus header revision number and trusted height's revision
// number must be the same. To update to a new revision, use a separate upgrade path
// UpdateState will prune the oldest consensus state if it is expired.
func (cs ClientState) UpdateState(ctx sdk.Context, cdc codec.BinaryCodec, clientStore sdk.KVStore, clientMsg exported.ClientMessage) error {
func (cs ClientState) UpdateState(ctx sdk.Context, cdc codec.BinaryCodec, clientStore sdk.KVStore, clientMsg exported.ClientMessage) ([]exported.Height, error) {
header, ok := clientMsg.(*Header)
if !ok {
return sdkerrors.Wrapf(clienttypes.ErrInvalidClientType, "expected type %T, got %T", &Header{}, header)
return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidClientType, "expected type %T, got %T", &Header{}, header)
}

// check for duplicate update
if consensusState, _ := GetConsensusState(clientStore, cdc, header.GetHeight()); consensusState != nil {
// perform no-op
return nil
return []exported.Height{}, nil
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
}

cs.pruneOldestConsensusState(ctx, cdc, clientStore)
Expand All @@ -175,6 +176,7 @@ func (cs ClientState) UpdateState(ctx sdk.Context, cdc codec.BinaryCodec, client
if height.GT(cs.LatestHeight) {
cs.LatestHeight = height
}

consensusState := &ConsensusState{
Timestamp: header.GetTime(),
Root: commitmenttypes.NewMerkleRoot(header.Header.GetAppHash()),
Expand All @@ -186,7 +188,7 @@ func (cs ClientState) UpdateState(ctx sdk.Context, cdc codec.BinaryCodec, client
setConsensusState(clientStore, cdc, consensusState, header.GetHeight())
setConsensusMetadata(ctx, clientStore, header.GetHeight())

return nil
return []exported.Height{height}, nil
}

// pruneOldestConsensusState will retrieve the earliest consensus state for this clientID and check if it is expired. If it is,
Expand Down
43 changes: 32 additions & 11 deletions modules/light-clients/07-tendermint/types/update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ func (suite *TendermintTestSuite) TestUpdateState() {
path *ibctesting.Path
clientMessage exported.ClientMessage
clientStore sdk.KVStore
consensusHeights []exported.Height
pruneHeight clienttypes.Height
prevClientState exported.ClientState
prevConsensusState exported.ConsensusState
Expand All @@ -318,10 +319,14 @@ func (suite *TendermintTestSuite) TestUpdateState() {
}{
{
"success with height later than latest height", func() {
suite.Require().True(path.EndpointA.GetClientState().GetLatestHeight().LT(clientMessage.GetHeight()))
tmHeader, ok := clientMessage.(*types.Header)
suite.Require().True(ok)
suite.Require().True(path.EndpointA.GetClientState().GetLatestHeight().LT(tmHeader.GetHeight()))
},
func() {
suite.Require().True(path.EndpointA.GetClientState().GetLatestHeight().EQ(clientMessage.GetHeight())) // new update, updated client state should have changed
tmHeader, ok := clientMessage.(*types.Header)
suite.Require().True(ok)
suite.Require().True(path.EndpointA.GetClientState().GetLatestHeight().EQ(tmHeader.GetHeight())) // new update, updated client state should have changed
}, true,
},
{
Expand All @@ -332,7 +337,9 @@ func (suite *TendermintTestSuite) TestUpdateState() {
err := path.EndpointA.UpdateClient()
suite.Require().NoError(err)

suite.Require().True(path.EndpointA.GetClientState().GetLatestHeight().GT(clientMessage.GetHeight()))
tmHeader, ok := clientMessage.(*types.Header)
suite.Require().True(ok)
suite.Require().True(path.EndpointA.GetClientState().GetLatestHeight().GT(tmHeader.GetHeight()))

prevClientState = path.EndpointA.GetClientState()
},
Expand All @@ -349,14 +356,20 @@ func (suite *TendermintTestSuite) TestUpdateState() {
// use the same header which just updated the client
clientMessage, err = path.EndpointA.Chain.ConstructUpdateTMClientHeader(path.EndpointA.Counterparty.Chain, path.EndpointA.ClientID)
suite.Require().NoError(err)
suite.Require().Equal(path.EndpointA.GetClientState().GetLatestHeight(), clientMessage.GetHeight())

tmHeader, ok := clientMessage.(*types.Header)
suite.Require().True(ok)
suite.Require().Equal(path.EndpointA.GetClientState().GetLatestHeight(), tmHeader.GetHeight())

prevClientState = path.EndpointA.GetClientState()
prevConsensusState = path.EndpointA.GetConsensusState(clientMessage.GetHeight())
prevConsensusState = path.EndpointA.GetConsensusState(tmHeader.GetHeight())
},
func() {
suite.Require().Equal(path.EndpointA.GetClientState(), prevClientState)
suite.Require().Equal(path.EndpointA.GetConsensusState(clientMessage.GetHeight()), prevConsensusState)

tmHeader, ok := clientMessage.(*types.Header)
suite.Require().True(ok)
suite.Require().Equal(path.EndpointA.GetConsensusState(tmHeader.GetHeight()), prevConsensusState)
}, true,
},
{
Expand Down Expand Up @@ -385,7 +398,9 @@ func (suite *TendermintTestSuite) TestUpdateState() {
suite.Require().NoError(err)
},
func() {
suite.Require().True(path.EndpointA.GetClientState().GetLatestHeight().EQ(clientMessage.GetHeight())) // new update, updated client state should have changed
tmHeader, ok := clientMessage.(*types.Header)
suite.Require().True(ok)
suite.Require().True(path.EndpointA.GetClientState().GetLatestHeight().EQ(tmHeader.GetHeight())) // new update, updated client state should have changed

// ensure consensus state was pruned
_, found := path.EndpointA.Chain.GetConsensusState(path.EndpointA.ClientID, pruneHeight)
Expand All @@ -396,7 +411,9 @@ func (suite *TendermintTestSuite) TestUpdateState() {
"invalid ClientMessage type", func() {
clientMessage = &types.Misbehaviour{}
},
func() {}, false,
func() {
suite.Require().Empty(consensusHeights)
}, false,
},
}
for _, tc := range testCases {
Expand All @@ -419,7 +436,7 @@ func (suite *TendermintTestSuite) TestUpdateState() {
clientState := path.EndpointA.GetClientState()

clientStore = suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID)
err = clientState.UpdateState(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), clientStore, clientMessage)
consensusHeights, err = clientState.UpdateState(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), clientStore, clientMessage)

if tc.expPass {
suite.Require().NoError(err)
Expand Down Expand Up @@ -556,7 +573,9 @@ func (suite *TendermintTestSuite) TestCheckForMisbehaviour() {
NextValidatorsHash: header.Header.NextValidatorsHash,
}

suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), path.EndpointA.ClientID, clientMessage.GetHeight(), consensusState)
tmHeader, ok := clientMessage.(*types.Header)
suite.Require().True(ok)
suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), path.EndpointA.ClientID, tmHeader.GetHeight(), consensusState)
},
false,
},
Expand All @@ -572,7 +591,9 @@ func (suite *TendermintTestSuite) TestCheckForMisbehaviour() {
NextValidatorsHash: header.Header.NextValidatorsHash,
}

suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), path.EndpointA.ClientID, clientMessage.GetHeight(), consensusState)
tmHeader, ok := clientMessage.(*types.Header)
suite.Require().True(ok)
suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), path.EndpointA.ClientID, tmHeader.GetHeight(), consensusState)
},
true,
},
Expand Down