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

02-client: merge misbehavior & header interfaces #1107

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
8f62a47
refactor: allow the mock module to be used multiple times as base ibc…
colin-axner Feb 10, 2022
843b459
feat: adding Pack/Unpack acknowledgement helper fns (#895)
seantking Feb 10, 2022
fb2f041
imp: support custom keys for testing (#893)
fedekunze Feb 10, 2022
c27d5b5
chore: add ParsePacketFromEvents testing helper function (#904)
colin-axner Feb 14, 2022
afa2d90
fix: correctly claim capability for mock module, handle genesis expor…
colin-axner Feb 15, 2022
d31f92d
docs: update migration docs for upgrade proposal in relation to ICS27…
colin-axner Feb 15, 2022
f442721
chore(ica): add trail of bits audit report (#903)
crodriguezvega Feb 16, 2022
20dd5ca
testing: adding multiple sender accounts for testing purposes (#935)
seantking Feb 17, 2022
98f4d3a
Create test chain with multiple validators (#942)
AdityaSripal Feb 17, 2022
52e3ae5
add changelog entry for SDK bump
crodriguezvega Feb 18, 2022
d48f576
fix: classify client states without consensus states as expired (#941)
timlind Feb 22, 2022
01cd4ad
chore: fix broken link (#972)
colin-axner Feb 22, 2022
9fcf254
add backport actions for v1.3.x and v2.1.x (#958)
crodriguezvega Feb 22, 2022
08d38d4
Revert "feat: adding Pack/Unpack acknowledgement helper fns (#895)" (…
colin-axner Feb 23, 2022
ef34765
chore: update migration docs (#985)
colin-axner Feb 23, 2022
556cc01
chore: fix mispelled words (#991)
seantking Feb 24, 2022
04ab3cb
fix: remove go mod tidy from proto-gen script (#989)
seantking Feb 24, 2022
4545154
bug: support base denoms with slashes (#978)
crodriguezvega Feb 28, 2022
9d8be7c
upgrade ics23 to v0.7 (#948)
crodriguezvega Feb 28, 2022
6d6888b
ibctesting: make `testing.T` public (#1020)
fedekunze Feb 28, 2022
e1be19b
add changelog entry for #941
crodriguezvega Mar 1, 2022
f994d1e
fix package import (#1007)
crodriguezvega Mar 1, 2022
147e0f1
feat: Add a function to initialize the ICS27 module via an upgrade pr…
colin-axner Mar 1, 2022
f71a505
docs: add missing args to NewKeeper in integration docs (#1038)
daeMOn63 Mar 3, 2022
a55ca88
small fixes for v2 to v3 migration (#1016)
crodriguezvega Mar 4, 2022
029c6e9
add missing slash
crodriguezvega Mar 4, 2022
7ae90c2
build(deps): bump actions/checkout from 2.4.0 to 3 (#1045)
dependabot[bot] Mar 7, 2022
fc452ac
call packet.GetSequence() rather than passing the func as argument (#…
joe-bowman Mar 7, 2022
13df199
Add counterpartyChannelID param to IBCModule.OnChanOpenAck (#1086)
catShaark Mar 9, 2022
90a7e5f
fix mirgation docs (#1091)
colin-axner Mar 9, 2022
a7563c9
fix: handle testing update client errors (#1094)
fedekunze Mar 9, 2022
f0b94df
replace channel keeper with IBC keeper in AnteDecorator (#950)
crodriguezvega Mar 10, 2022
d40f1da
add backport rules for v1.4.x and v2.2.x (#1085)
crodriguezvega Mar 10, 2022
c32e4be
ibctesting: custom voting power reduction for testing (#939)
fedekunze Mar 11, 2022
7ca5875
merging Header & Misbehavior interfaces into ClientMessage & fixing a…
seantking Mar 11, 2022
d59c886
fix: update related functions
seantking Mar 11, 2022
44bf918
Merge branch '02-client-refactor' into sean/issue#875-merge-misbehvio…
seantking Mar 11, 2022
c345354
chore: comments
seantking Mar 11, 2022
671ab6b
Update modules/core/02-client/types/encoding.go
seantking Mar 15, 2022
b682545
chore: changelog
seantking Mar 15, 2022
46d070c
chore: comment
seantking Mar 15, 2022
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (testing) [\#892](https://github.com/cosmos/ibc-go/pull/892) IBC Mock modules store the scoped keeper and portID within the IBCMockApp. They also maintain reference to the AppModule to update the AppModule's list of IBC applications it references. Allows for the mock module to be reused as a base application in middleware stacks.
* (channel) [\#882](https://github.com/cosmos/ibc-go/pull/882) The `WriteAcknowledgement` API now takes `exported.Acknowledgement` instead of a byte array
* (modules/core/ante) [\#950](https://github.com/cosmos/ibc-go/pull/950) Replaces the channel keeper with the IBC keeper in the IBC `AnteDecorator` in order to execute the entire message and be able to reject redundant messages that are in the same block as the non-redundant messages.
* (modules/core/exported) [\#1107](https://github.com/cosmos/ibc-go/pull/1107) Merging the `Header` and `Misbehaviour` interfaces into a single `ClientMessage` type


### State Machine Breaking

Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/adr-027-ibc-wasm.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ packaged inside a payload which is json serialized and passed to `callContract`
array of bytes returned by the smart contract. This data is deserialized and passed as return argument.

```go
func (c *ClientState) CheckProposedHeaderAndUpdateState(context sdk.Context, marshaler codec.BinaryMarshaler, store sdk.KVStore, header exported.Header) (exported.ClientState, exported.ConsensusState, error) {
func (c *ClientState) CheckProposedHeaderAndUpdateState(context sdk.Context, marshaler codec.BinaryMarshaler, store sdk.KVStore, header exported.ClientMessage) (exported.ClientState, exported.ConsensusState, error) {
// get consensus state corresponding to client state to check if the client is expired
consensusState, err := GetConsensusState(store, marshaler, c.LatestHeight)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions modules/core/02-client/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func NewUpdateClientCmd() *cobra.Command {

cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry)

var header exported.Header
var header exported.ClientMessage
headerContentOrFileName := args[1]
if err := cdc.UnmarshalInterfaceJSON([]byte(headerContentOrFileName), &header); err != nil {

Expand Down Expand Up @@ -141,7 +141,7 @@ func NewSubmitMisbehaviourCmd() *cobra.Command {
}
cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry)

var misbehaviour exported.Misbehaviour
var misbehaviour exported.ClientMessage
clientID := args[0]
misbehaviourContentOrFileName := args[1]
if err := cdc.UnmarshalInterfaceJSON([]byte(misbehaviourContentOrFileName), &misbehaviour); err != nil {
Expand Down
6 changes: 3 additions & 3 deletions modules/core/02-client/keeper/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func (k Keeper) CreateClient(
}

// UpdateClient updates the consensus state and the state root from a provided header.
func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, header exported.Header) error {
func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, header exported.ClientMessage) error {
clientState, found := k.GetClientState(ctx, clientID)
if !found {
return sdkerrors.Wrapf(types.ErrClientNotFound, "cannot update client with ID %s", clientID)
Expand Down Expand Up @@ -85,7 +85,7 @@ func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, header exported.H
// Marshal the Header 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.
headerStr = hex.EncodeToString(types.MustMarshalHeader(k.cdc, header))
headerStr = hex.EncodeToString(types.MustMarshalClientMessage(k.cdc, header))
// set default consensus height with header height
consensusHeight = header.GetHeight()

Expand Down Expand Up @@ -188,7 +188,7 @@ func (k Keeper) UpgradeClient(ctx sdk.Context, clientID string, upgradedClient e

// CheckMisbehaviourAndUpdateState checks for client misbehaviour and freezes the
// client if so.
func (k Keeper) CheckMisbehaviourAndUpdateState(ctx sdk.Context, clientID string, misbehaviour exported.Misbehaviour) error {
func (k Keeper) CheckMisbehaviourAndUpdateState(ctx sdk.Context, clientID string, misbehaviour exported.ClientMessage) error {
clientState, found := k.GetClientState(ctx, clientID)
if !found {
return sdkerrors.Wrapf(types.ErrClientNotFound, "cannot check misbehaviour for client with ID %s", clientID)
Expand Down
2 changes: 1 addition & 1 deletion modules/core/02-client/keeper/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,7 @@ func (suite *KeeperTestSuite) TestUpdateClientEventEmission() {
bz, err := hex.DecodeString(string(attr.Value))
suite.Require().NoError(err)

emittedHeader, err := types.UnmarshalHeader(suite.chainA.App.AppCodec(), bz)
emittedHeader, err := types.UnmarshalClientMessage(suite.chainA.App.AppCodec(), bz)
suite.Require().NoError(err)
suite.Require().Equal(header, emittedHeader)
}
Expand Down
4 changes: 2 additions & 2 deletions modules/core/02-client/legacy/v100/solomachine.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,14 @@ func (cs ClientState) ExportMetadata(_ sdk.KVStore) []exported.GenesisMetadata {

// CheckHeaderAndUpdateState panics!
func (cs *ClientState) CheckHeaderAndUpdateState(
_ sdk.Context, _ codec.BinaryCodec, _ sdk.KVStore, _ exported.Header,
_ sdk.Context, _ codec.BinaryCodec, _ sdk.KVStore, _ exported.ClientMessage,
) (exported.ClientState, exported.ConsensusState, error) {
panic("legacy solo machine is deprecated!")
}

// CheckMisbehaviourAndUpdateState panics!
func (cs ClientState) CheckMisbehaviourAndUpdateState(
_ sdk.Context, _ codec.BinaryCodec, _ sdk.KVStore, _ exported.Misbehaviour,
_ sdk.Context, _ codec.BinaryCodec, _ sdk.KVStore, _ exported.ClientMessage,
) (exported.ClientState, error) {
panic("legacy solo machine is deprecated!")
}
Expand Down
60 changes: 14 additions & 46 deletions modules/core/02-client/types/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) {
)
registry.RegisterInterface(
"ibc.core.client.v1.Header",
(*exported.Header)(nil),
(*exported.ClientMessage)(nil),
)
registry.RegisterInterface(
"ibc.core.client.v1.Height",
Expand All @@ -32,7 +32,7 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) {
)
registry.RegisterInterface(
"ibc.core.client.v1.Misbehaviour",
(*exported.Misbehaviour)(nil),
(*exported.ClientMessage)(nil),
)
registry.RegisterImplementations(
(*govtypes.Content)(nil),
Expand Down Expand Up @@ -124,66 +124,34 @@ func UnpackConsensusState(any *codectypes.Any) (exported.ConsensusState, error)
return consensusState, nil
}

// PackHeader constructs a new Any packed with the given header value. It returns
// an error if the header can't be casted to a protobuf message or if the concrete
// PackClientMessage constructs a new Any packed with the given value. It returns
// an error if the value can't be casted to a protobuf message or if the concrete
// implemention is not registered to the protobuf codec.
func PackHeader(header exported.Header) (*codectypes.Any, error) {
msg, ok := header.(proto.Message)
func PackClientMessage(clientMessage exported.ClientMessage) (*codectypes.Any, error) {
msg, ok := clientMessage.(proto.Message)
if !ok {
return nil, sdkerrors.Wrapf(sdkerrors.ErrPackAny, "cannot proto marshal %T", header)
return nil, sdkerrors.Wrapf(sdkerrors.ErrPackAny, "cannot proto marshal %T", clientMessage)
}

anyHeader, err := codectypes.NewAnyWithValue(msg)
any, err := codectypes.NewAnyWithValue(msg)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrPackAny, err.Error())
}

return anyHeader, nil
return any, nil
}

// UnpackHeader unpacks an Any into a Header. It returns an error if the
// consensus state can't be unpacked into a Header.
func UnpackHeader(any *codectypes.Any) (exported.Header, error) {
// UnpackClientMessage unpacks an Any into a ClientMessage. It returns an error if the
// consensus state can't be unpacked into a ClientMessage.
func UnpackClientMessage(any *codectypes.Any) (exported.ClientMessage, error) {
if any == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrUnpackAny, "protobuf Any message cannot be nil")
}

header, ok := any.GetCachedValue().(exported.Header)
clientMessage, ok := any.GetCachedValue().(exported.ClientMessage)
if !ok {
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnpackAny, "cannot unpack Any into Header %T", any)
}

return header, nil
}

// PackMisbehaviour constructs a new Any packed with the given misbehaviour value. It returns
// an error if the misbehaviour can't be casted to a protobuf message or if the concrete
// implemention is not registered to the protobuf codec.
func PackMisbehaviour(misbehaviour exported.Misbehaviour) (*codectypes.Any, error) {
msg, ok := misbehaviour.(proto.Message)
if !ok {
return nil, sdkerrors.Wrapf(sdkerrors.ErrPackAny, "cannot proto marshal %T", misbehaviour)
}

anyMisbhaviour, err := codectypes.NewAnyWithValue(msg)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrPackAny, err.Error())
}

return anyMisbhaviour, nil
}

// UnpackMisbehaviour unpacks an Any into a Misbehaviour. It returns an error if the
// Any can't be unpacked into a Misbehaviour.
func UnpackMisbehaviour(any *codectypes.Any) (exported.Misbehaviour, error) {
if any == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrUnpackAny, "protobuf Any message cannot be nil")
}

misbehaviour, ok := any.GetCachedValue().(exported.Misbehaviour)
if !ok {
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnpackAny, "cannot unpack Any into Misbehaviour %T", any)
}

return misbehaviour, nil
return clientMessage, nil
}
61 changes: 7 additions & 54 deletions modules/core/02-client/types/codec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,11 @@ func (suite *TypesTestSuite) TestPackConsensusState() {
}
}

func (suite *TypesTestSuite) TestPackHeader() {
func (suite *TypesTestSuite) TestPackClientMessage() {
testCases := []struct {
name string
header exported.Header
expPass bool
name string
clientMessage exported.ClientMessage
expPass bool
}{
{
"solo machine header",
Expand All @@ -142,7 +142,7 @@ func (suite *TypesTestSuite) TestPackHeader() {
testCasesAny := []caseAny{}

for _, tc := range testCases {
clientAny, err := types.PackHeader(tc.header)
clientAny, err := types.PackClientMessage(tc.clientMessage)
if tc.expPass {
suite.Require().NoError(err, tc.name)
} else {
Expand All @@ -153,57 +153,10 @@ func (suite *TypesTestSuite) TestPackHeader() {
}

for i, tc := range testCasesAny {
cs, err := types.UnpackHeader(tc.any)
cs, err := types.UnpackClientMessage(tc.any)
if tc.expPass {
suite.Require().NoError(err, tc.name)
suite.Require().Equal(testCases[i].header, cs, tc.name)
} else {
suite.Require().Error(err, tc.name)
}
}
}

func (suite *TypesTestSuite) TestPackMisbehaviour() {
testCases := []struct {
name string
misbehaviour exported.Misbehaviour
expPass bool
}{
{
"solo machine misbehaviour",
ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2).CreateMisbehaviour(),
true,
},
{
"tendermint misbehaviour",
ibctmtypes.NewMisbehaviour("tendermint", suite.chainA.LastHeader, suite.chainA.LastHeader),
true,
},
{
"nil",
nil,
false,
},
}

testCasesAny := []caseAny{}

for _, tc := range testCases {
clientAny, err := types.PackMisbehaviour(tc.misbehaviour)
if tc.expPass {
suite.Require().NoError(err, tc.name)
} else {
suite.Require().Error(err, tc.name)
}

testCasesAny = append(testCasesAny, caseAny{tc.name, clientAny, tc.expPass})
}

for i, tc := range testCasesAny {
cs, err := types.UnpackMisbehaviour(tc.any)
if tc.expPass {
suite.Require().NoError(err, tc.name)
suite.Require().Equal(testCases[i].misbehaviour, cs, tc.name)
suite.Require().Equal(testCases[i].clientMessage, cs, tc.name)
} else {
suite.Require().Error(err, tc.name)
}
Expand Down
24 changes: 12 additions & 12 deletions modules/core/02-client/types/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,29 +86,29 @@ func UnmarshalConsensusState(cdc codec.BinaryCodec, bz []byte) (exported.Consens
return consensusState, nil
}

// MarshalHeader protobuf serializes a Header interface
func MarshalHeader(cdc codec.BinaryCodec, h exported.Header) ([]byte, error) {
return cdc.MarshalInterface(h)
// MarshalClientMessage protobuf serializes a ClientMessage interface
func MarshalClientMessage(cdc codec.BinaryCodec, clientMessage exported.ClientMessage) ([]byte, error) {
return cdc.MarshalInterface(clientMessage)
}

// MustMarshalHeader attempts to encode a Header object and returns the
// MustMarshalClientMessage attempts to encode a ClientMessage object and returns the
// raw encoded bytes. It panics on error.
func MustMarshalHeader(cdc codec.BinaryCodec, header exported.Header) []byte {
bz, err := MarshalHeader(cdc, header)
func MustMarshalClientMessage(cdc codec.BinaryCodec, clientMessage exported.ClientMessage) []byte {
bz, err := MarshalClientMessage(cdc, clientMessage)
if err != nil {
panic(fmt.Errorf("failed to encode header: %w", err))
panic(fmt.Errorf("failed to encode ClientMessage: %w", err))
}

return bz
}

// UnmarshalHeader returns a Header interface from raw proto encoded header bytes.
// UnmarshalClientMessage returns a ClientMessage interface from raw proto encoded header bytes.
// An error is returned upon decoding failure.
func UnmarshalHeader(cdc codec.BinaryCodec, bz []byte) (exported.Header, error) {
var header exported.Header
if err := cdc.UnmarshalInterface(bz, &header); err != nil {
func UnmarshalClientMessage(cdc codec.BinaryCodec, bz []byte) (exported.ClientMessage, error) {
var clientMessage exported.ClientMessage
if err := cdc.UnmarshalInterface(bz, &clientMessage); err != nil {
return nil, err
}

return header, nil
return clientMessage, nil
}
7 changes: 3 additions & 4 deletions modules/core/02-client/types/encoding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,17 @@ func (suite *TypesTestSuite) TestMarshalHeader() {
}

// marshal header
bz, err := types.MarshalHeader(cdc, h)
bz, err := types.MarshalClientMessage(cdc, h)
suite.Require().NoError(err)

// unmarshal header
newHeader, err := types.UnmarshalHeader(cdc, bz)
newHeader, err := types.UnmarshalClientMessage(cdc, bz)
suite.Require().NoError(err)

suite.Require().Equal(h, newHeader)

// use invalid bytes
invalidHeader, err := types.UnmarshalHeader(cdc, []byte("invalid bytes"))
invalidHeader, err := types.UnmarshalClientMessage(cdc, []byte("invalid bytes"))
suite.Require().Error(err)
suite.Require().Nil(invalidHeader)

}
16 changes: 8 additions & 8 deletions modules/core/02-client/types/msgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ func (msg MsgCreateClient) UnpackInterfaces(unpacker codectypes.AnyUnpacker) err

// NewMsgUpdateClient creates a new MsgUpdateClient instance
//nolint:interfacer
func NewMsgUpdateClient(id string, header exported.Header, signer string) (*MsgUpdateClient, error) {
anyHeader, err := PackHeader(header)
func NewMsgUpdateClient(id string, header exported.ClientMessage, signer string) (*MsgUpdateClient, error) {
anyHeader, err := PackClientMessage(header)
if err != nil {
return nil, err
}
Expand All @@ -123,7 +123,7 @@ func (msg MsgUpdateClient) ValidateBasic() error {
if err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err)
}
header, err := UnpackHeader(msg.Header)
header, err := UnpackClientMessage(msg.Header)
if err != nil {
return err
}
Expand All @@ -147,7 +147,7 @@ func (msg MsgUpdateClient) GetSigners() []sdk.AccAddress {

// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces
func (msg MsgUpdateClient) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error {
var header exported.Header
var header exported.ClientMessage
return unpacker.UnpackAny(msg.Header, &header)
}

Expand Down Expand Up @@ -229,8 +229,8 @@ func (msg MsgUpgradeClient) UnpackInterfaces(unpacker codectypes.AnyUnpacker) er

// NewMsgSubmitMisbehaviour creates a new MsgSubmitMisbehaviour instance.
//nolint:interfacer
func NewMsgSubmitMisbehaviour(clientID string, misbehaviour exported.Misbehaviour, signer string) (*MsgSubmitMisbehaviour, error) {
anyMisbehaviour, err := PackMisbehaviour(misbehaviour)
func NewMsgSubmitMisbehaviour(clientID string, misbehaviour exported.ClientMessage, signer string) (*MsgSubmitMisbehaviour, error) {
anyMisbehaviour, err := PackClientMessage(misbehaviour)
if err != nil {
return nil, err
}
Expand All @@ -248,7 +248,7 @@ func (msg MsgSubmitMisbehaviour) ValidateBasic() error {
if err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err)
}
misbehaviour, err := UnpackMisbehaviour(msg.Misbehaviour)
misbehaviour, err := UnpackClientMessage(msg.Misbehaviour)
if err != nil {
return err
}
Expand All @@ -270,6 +270,6 @@ func (msg MsgSubmitMisbehaviour) GetSigners() []sdk.AccAddress {

// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces
func (msg MsgSubmitMisbehaviour) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error {
var misbehaviour exported.Misbehaviour
var misbehaviour exported.ClientMessage
return unpacker.UnpackAny(msg.Misbehaviour, &misbehaviour)
}
Loading