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

Update #264 - updates genesis and genesis tests #382

Merged
merged 49 commits into from
Nov 18, 2022
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
ba651e3
reformat consumer genesis test
sainoe Jul 18, 2022
a87495d
remove validator fill in of ExportAppStateAndValidators
sainoe Jul 19, 2022
e3919b5
checkpoint, testing export genesis consumer
sainoe Jul 25, 2022
3dcd610
test consumer export
sainoe Jul 27, 2022
a815a15
make pass the tests
sainoe Jul 29, 2022
b25f968
fix export height to valset update id in consumer
sainoe Aug 2, 2022
4a06760
pass the tests
sainoe Aug 3, 2022
7b48c09
pass the tests
sainoe Aug 3, 2022
1378a52
* Update the provider and consumer export/init genesis with the new C…
sainoe Aug 11, 2022
c3cfeb4
remove pendingVSCPackets
sainoe Aug 12, 2022
0e548e4
remove references in create consumer chain proposal setters and getters
sainoe Aug 22, 2022
9a70d95
merge with remotes
sainoe Aug 22, 2022
524a0aa
fix unchecked errors
sainoe Aug 22, 2022
2a670e0
Merge branch 'main' into sainoe/export-genesis
sainoe Sep 29, 2022
65dc723
fix iterator bug
sainoe Sep 29, 2022
c711ecb
fix last nits
sainoe Sep 29, 2022
a4e83d8
Merge remote-tracking branch 'upstream/main' into sainoe/export-genesis
sainoe Sep 29, 2022
ecb643a
fix linter
sainoe Sep 30, 2022
06b5df5
Fix conflicts and make pass the tests
sainoe Oct 3, 2022
4ec85d5
format provider genesis tests
sainoe Oct 3, 2022
8017c29
format consumer genesis tests
sainoe Oct 3, 2022
d62dc16
Merge branch 'main' into sainoe/export-genesis
jtremback Oct 4, 2022
f1a415e
clarify consumer keeper genesis
sainoe Oct 6, 2022
9ea6c5a
push missing merge commit
sainoe Oct 6, 2022
5f03831
remove unused test helpers
sainoe Oct 7, 2022
03c643c
Merge branch 'main' into sainoe/export-genesis-2
shaspitz Oct 7, 2022
eb59e50
Feat: update consumer init and export genesis
sainoe Nov 9, 2022
de20341
Revert "Feat: update consumer init and export genesis"
sainoe Nov 9, 2022
c856708
merge main
sainoe Nov 9, 2022
d3b5bef
* Add LastTransmissionBlockHeight to consumer genesis proto
sainoe Nov 7, 2022
c292da4
Update consumer init
sainoe Nov 9, 2022
74aa7e9
Update consumer genesis export
sainoe Nov 9, 2022
19e4b93
fix last nits
sainoe Nov 9, 2022
0599369
Fix consumer InitGenesis
sainoe Nov 10, 2022
de4a73e
Update comments in genesis.proto
sainoe Nov 10, 2022
30b141c
format consumer genesis test
sainoe Nov 10, 2022
0399487
update comments
sainoe Nov 10, 2022
255c96d
Update provider genesis comments
sainoe Nov 14, 2022
1d06cd7
fix small lint errs
shaspitz Nov 14, 2022
26ec9e8
Merge branch 'main' into sainoe/export-genesis-2
shaspitz Nov 14, 2022
ebd590b
Merge branch 'main' into sainoe/export-genesis-2
shaspitz Nov 15, 2022
d0d5db1
* Update consumer genesis validation
sainoe Nov 15, 2022
4aca97e
Document consumer genesis validation
sainoe Nov 17, 2022
fd9dc10
Document consumer genesis validation
sainoe Nov 17, 2022
50ae321
Merge branch 'main' into sainoe/export-genesis-2
sainoe Nov 18, 2022
af1aebb
Update after #448 merge
sainoe Nov 18, 2022
6d93504
Merge branch 'sainoe/export-genesis-2' into export-2-upstream
sainoe Nov 18, 2022
504c44c
Merge branch 'main' into sainoe/export-genesis-2
shaspitz Nov 18, 2022
aff5156
Update x/ccv/consumer/types/genesis.go
jtremback Nov 18, 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
15 changes: 9 additions & 6 deletions proto/interchain_security/ccv/consumer/v1/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,31 @@ import "tendermint/abci/types.proto";
// GenesisState defines the CCV consumer chain genesis state
message GenesisState {
Params params = 1 [ (gogoproto.nullable) = false ];
string provider_client_id = 2; // empty for a completely new chain
string provider_channel_id = 3; // empty for a completely new chain
string provider_client_id = 2; // empty for a new chain, filled in on restart.
string provider_channel_id = 3; // empty for a new chain, filled in on restart.
bool new_chain = 4; // true for new chain GenesisState, false for chain restart.
// ProviderClientState filled in on new chain, nil on restart.
ibc.lightclients.tendermint.v1.ClientState provider_client_state = 5;
// ProviderConsensusState filled in on new chain, nil on restart.
ibc.lightclients.tendermint.v1.ConsensusState provider_consensus_state = 6;
// MaturingPackets nil on new chain, filled on restart.
// MaturingPackets nil on new chain, filled in on restart.
repeated MaturingVSCPacket maturing_packets = 7
[ (gogoproto.nullable) = false ];
// InitialValset filled in on new chain and on restart.
repeated .tendermint.abci.ValidatorUpdate initial_val_set = 8
[ (gogoproto.nullable) = false ];
// HeightToValsetUpdateId nil on new chain, filled on restart.
// HeightToValsetUpdateId nil on new chain, filled in on restart.
repeated HeightToValsetUpdateID height_to_valset_update_id = 9
[ (gogoproto.nullable) = false ];
// OutstandingDowntimes nil on new chain, filled on restart.
// OutstandingDowntimes nil on new chain, filled in on restart.
repeated OutstandingDowntime outstanding_downtime_slashing = 10
[ (gogoproto.nullable) = false ];
// PendingSlashRequests filled in on new chain, nil on restart.
// PendingSlashRequests nil on new chain, filled in on restart.
interchain_security.ccv.consumer.v1.SlashRequests pending_slash_requests = 11
[ (gogoproto.nullable) = false ];
// LastTransmissionBlockHeight nil on new chain, filled in on restart.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this being used for a zero height restart or a restart containing the current block height? Or both?

Copy link
Contributor

Choose a reason for hiding this comment

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

If this genesis restore was being used on a zero height restart, then that might result in it taking a really long time to send out a distribution... not sure

Copy link
Contributor Author

@sainoe sainoe Nov 15, 2022

Choose a reason for hiding this comment

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

You're right. A zero height restart could take a problematic long time to distribute due to this line:

if (curHeight - ltbh.Height) < bpdt {

Actually LastTransmissionBlockHeight should never be greater than the current block height.

interchain_security.ccv.consumer.v1.LastTransmissionBlockHeight last_transmission_block_height = 12
[ (gogoproto.nullable) = false ];
}

// MaturingVSCPacket defines the genesis information for the
Expand Down
2 changes: 1 addition & 1 deletion proto/interchain_security/ccv/provider/v1/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ message GenesisState {

// consumer chain
message ConsumerState {
// ChannelID defines the chain ID for the consumer chain
// ChainID defines the chain ID for the consumer chain
string chain_id = 1;
// ChannelID defines the IBC channel ID for the consumer chain
string channel_id = 2;
Expand Down
2 changes: 1 addition & 1 deletion tests/difference/core/driver/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ func (b *Builder) createConsumerGenesis(tmConfig *ibctesting.TendermintConfig) *
consumertypes.DefaultHistoricalEntries,
consumertypes.DefaultConsumerUnbondingPeriod,
)
return consumertypes.NewInitialGenesisState(providerClient, providerConsState, valUpdates, consumertypes.SlashRequests{}, params)
return consumertypes.NewInitialGenesisState(providerClient, providerConsState, valUpdates, params)
}

func (b *Builder) createLink() {
Expand Down
8 changes: 1 addition & 7 deletions tests/e2e/channel_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,6 @@ func (suite *CCVTestSuite) TestConsumerGenesis() {

genesis := consumerKeeper.ExportGenesis(suite.consumerChain.GetContext())

// Confirm that client and cons state are exported from consumer keeper properly
Copy link
Contributor

Choose a reason for hiding this comment

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

Was this deletion intentional?

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. The client and last consensus states are exported by the IBC module during the genesis export. A consumer genesis should only have these states fill in for a new chain start.

consumerEndpointClientState, consumerEndpointConsState := suite.GetConsumerEndpointClientAndConsState()
suite.Require().Equal(consumerEndpointClientState, genesis.ProviderClientState)
suite.Require().Equal(consumerEndpointConsState, genesis.ProviderConsensusState)

suite.Require().NotPanics(func() {
consumerKeeper.InitGenesis(suite.consumerChain.GetContext(), genesis)
// reset suite to reset provider client
Expand All @@ -39,9 +34,8 @@ func (suite *CCVTestSuite) TestConsumerGenesis() {

clientId, ok := consumerKeeper.GetProviderClientID(ctx)
suite.Require().True(ok)
clientState, ok := suite.consumerApp.GetIBCKeeper().ClientKeeper.GetClientState(ctx, clientId)
_, ok = suite.consumerApp.GetIBCKeeper().ClientKeeper.GetClientState(ctx, clientId)
suite.Require().True(ok)
suite.Require().Equal(genesis.ProviderClientState, clientState, "client state not set correctly after InitGenesis")

suite.SetupCCVChannel()

Expand Down
16 changes: 16 additions & 0 deletions testutil/keeper/expectations.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types"
"github.com/golang/mock/gomock"

host "github.com/cosmos/ibc-go/v3/modules/core/24-host"
ccv "github.com/cosmos/interchain-security/x/ccv/types"

extra "github.com/oxyno-zeta/gomock-extra-matcher"
Expand Down Expand Up @@ -88,3 +89,18 @@ func GetMocksForStopConsumerChain(ctx sdk.Context, mocks *MockedKeepers) []*gomo
mocks.MockChannelKeeper.EXPECT().ChanCloseInit(ctx, ccv.ProviderPortID, "channelID", dummyCap).Times(1),
}
}

func ExpectLatestConsensusStateMock(ctx sdk.Context, mocks MockedKeepers, clientID string, consState *ibctmtypes.ConsensusState) *gomock.Call {
return mocks.MockClientKeeper.EXPECT().
GetLatestClientConsensusState(ctx, clientID).Return(consState, true).Times(1)
}

func ExpectCreateClientMock(ctx sdk.Context, mocks MockedKeepers, clientID string, clientState *ibctmtypes.ClientState, consState *ibctmtypes.ConsensusState) *gomock.Call {
return mocks.MockClientKeeper.EXPECT().CreateClient(ctx, clientState, consState).Return(clientID, nil).Times(1)
}

func ExpectGetCapabilityMock(ctx sdk.Context, mocks MockedKeepers) *gomock.Call {
return mocks.MockScopedKeeper.EXPECT().GetCapability(
ctx, host.PortPath(ccv.ConsumerPortID),
).Return(nil, true).Times(1)
}
16 changes: 0 additions & 16 deletions testutil/keeper/unit_test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ package keeper

import (
"testing"
time "time"

tmtypes "github.com/tendermint/tendermint/types"

"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
Expand All @@ -27,8 +24,6 @@ import (
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"

clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types"
commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types"
ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types"
)

// Parameters needed to instantiate an in-memory keeper
Expand Down Expand Up @@ -187,17 +182,6 @@ func GenPubKey() (crypto.PubKey, error) {
return cryptocodec.ToTmPubKeyInterface(privKey.PrivKey.PubKey())
}

func GetClientState(chainID string) *ibctmtypes.ClientState {
return ibctmtypes.NewClientState(chainID, ibctmtypes.DefaultTrustLevel, 0, 0,
time.Second*10, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(),
[]string{"upgrade", "upgradedIBCState"}, true, true)
}

func GetConsensusState(clientID string, timestamp time.Time, vals ...*tmtypes.Validator) *ibctmtypes.ConsensusState {
return ibctmtypes.NewConsensusState(timestamp, commitmenttypes.NewMerkleRoot([]byte("apphash")),
tmtypes.NewValidatorSet(vals).Hash()[:])
}

// SetupForStoppingConsumerChain registers expected mock calls and corresponding state setup
// which asserts that a consumer chain was properly stopped from StopConsumerChain().
func SetupForStoppingConsumerChain(t *testing.T, ctx sdk.Context,
Expand Down
145 changes: 85 additions & 60 deletions x/ccv/consumer/keeper/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ import (
)

// InitGenesis initializes the CCV consumer state and binds to PortID.
// The three states in which a consumer chain can start/restart:
//
// 1. A client to the provider was never created, i.e. a new consumer chain is started for the first time.
// 2. A consumer chain restarts after a client to the provider was created, but the CCV channel handshake is still in progress
// 3. A consumer chain restarts after the CCV channel handshake was completed.
func (k Keeper) InitGenesis(ctx sdk.Context, state *consumertypes.GenesisState) []abci.ValidatorUpdate {
k.SetParams(ctx, state.Params)
// TODO: Remove enabled flag and find a better way to setup e2e tests
Expand All @@ -37,39 +42,51 @@ func (k Keeper) InitGenesis(ctx sdk.Context, state *consumertypes.GenesisState)
}

// initialValSet is checked in NewChain case by ValidateGenesis
// start a new chain
if state.NewChain {
// Create the provider client in InitGenesis for new consumer chain. CCV Handshake must be established with this client id.
// create the provider client in InitGenesis for new consumer chain. CCV Handshake must be established with this client id.
clientID, err := k.clientKeeper.CreateClient(ctx, state.ProviderClientState, state.ProviderConsensusState)
if err != nil {
panic(err)
}

// Set default value for valset update ID
k.SetHeightValsetUpdateID(ctx, uint64(ctx.BlockHeight()), uint64(0))
// set provider client id.
k.SetProviderClientID(ctx, clientID)
} else {
// verify that latest consensus state on provider client matches the initial validator set of restarted chain
// thus, IBC genesis MUST run before CCV consumer genesis
consState, ok := k.clientKeeper.GetLatestClientConsensusState(ctx, state.ProviderClientId)
if !ok {
panic("consensus state for provider client not found. MUST run IBC genesis before CCV consumer genesis")
}
tmConsState, ok := consState.(*ibctmtypes.ConsensusState)
if !ok {
panic(fmt.Sprintf("consensus state has wrong type. expected: %T, got: %T", &ibctmtypes.ConsensusState{}, consState))
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Why the switch? Were they not working in the other order?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wdym?

// ensure that initial validator set is same as initial consensus state on provider client.
// this will be verified by provider module on channel handshake.
vals, err := tmtypes.PB2TM.ValidatorUpdates(state.InitialValSet)
if err != nil {
// set default value for valset update ID
k.SetHeightValsetUpdateID(ctx, uint64(ctx.BlockHeight()), uint64(0))
} else {
// verify genesis initial valset against the latest consensus state
// IBC genesis MUST run before CCV consumer genesis
if err := k.verifyGenesisInitValset(ctx, state); err != nil {
panic(err)
}
valSet := tmtypes.NewValidatorSet(vals)
// chain restarts without the CCV channel established
if state.ProviderChannelId == "" {
k.SetPendingSlashRequests(ctx, state.PendingSlashRequests)

// chain restarts with the CCV channel established
} else {
Copy link
Contributor

Choose a reason for hiding this comment

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

So i guess you don't even have to deal with the case where the handshake is in progress because it will just take care of itself?

Copy link
Contributor Author

@sainoe sainoe Nov 15, 2022

Choose a reason for hiding this comment

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

This case is detected when the channelID is empty and only these states are getting populated:

// chain restarts without the CCV channel established
if state.ProviderChannelId == "" {
k.SetPendingSlashRequests(ctx, state.PendingSlashRequests)

// set height to valset update id mapping
for _, h2v := range state.HeightToValsetUpdateId {
k.SetHeightValsetUpdateID(ctx, h2v.Height, h2v.ValsetUpdateId)
}
// set provider client id
k.SetProviderClientID(ctx, state.ProviderClientId)
}
// populate cross chain validators states with initial valset
k.ApplyCCValidatorChanges(ctx, state.InitialValSet)

Note that the client to the provider is already created and imported by the IBC module genesis in this case.

// set provider channel ID
k.SetProviderChannel(ctx, state.ProviderChannelId)
// set all unbonding sequences
for _, mp := range state.MaturingPackets {
k.SetPacketMaturityTime(ctx, mp.VscId, mp.MaturityTime)
}
// set outstanding downtime slashing requests
for _, od := range state.OutstandingDowntimeSlashing {
consAddr, err := sdk.ConsAddressFromBech32(od.ValidatorConsensusAddress)
if err != nil {
panic(err)
}
k.SetOutstandingDowntime(ctx, consAddr)
}
// set last transmission block height
err := k.SetLastTransmissionBlockHeight(ctx, state.LastTransmissionBlockHeight)
Comment on lines +82 to +83
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this code work for a zero height restart? Does it even need to work for a zero height restart? note sure...

if err != nil {
panic(fmt.Sprintf("could not set last transmission block height: %v", err))
}

if !bytes.Equal(tmConsState.NextValidatorsHash, valSet.Hash()) {
panic("initial validator set does not match last consensus state of the provider client")
}

// set height to valset update id mapping
Expand All @@ -79,12 +96,7 @@ func (k Keeper) InitGenesis(ctx sdk.Context, state *consumertypes.GenesisState)

// set provider client id
k.SetProviderClientID(ctx, state.ProviderClientId)
// set provider channel id.
k.SetProviderChannel(ctx, state.ProviderChannelId)
// set all unbonding sequences
for _, mp := range state.MaturingPackets {
k.SetPacketMaturityTime(ctx, mp.VscId, mp.MaturityTime)
}

}

// populate cross chain validators states with initial valset
Expand All @@ -93,16 +105,15 @@ func (k Keeper) InitGenesis(ctx sdk.Context, state *consumertypes.GenesisState)
return state.InitialValSet
}

// ExportGenesis exports the CCV consumer state. If the channel has already been established, then we export
// provider chain. Otherwise, this is still considered a new chain and we export latest client state.
// ExportGenesis returns the CCV consumer module's exported genesis
func (k Keeper) ExportGenesis(ctx sdk.Context) (genesis *consumertypes.GenesisState) {
params := k.GetParams(ctx)
if !params.Enabled {
return consumertypes.DefaultGenesisState()
}

// export the current validator set
valset, err := k.GetValidatorUpdates(ctx)
valset, err := k.GetCurrentValidatorsAsABCIUpdates(ctx)
if err != nil {
panic(fmt.Sprintf("fail to retrieve the validator set: %s", err))
}
Expand All @@ -124,16 +135,6 @@ func (k Keeper) ExportGenesis(ctx sdk.Context) (genesis *consumertypes.GenesisSt
return false
})

heightToVCIDs := []types.HeightToValsetUpdateID{}
k.IterateHeightToValsetUpdateID(ctx, func(height, vscID uint64) bool {
hv := types.HeightToValsetUpdateID{
Height: height,
ValsetUpdateId: vscID,
}
heightToVCIDs = append(heightToVCIDs, hv)
return true
})

outstandingDowntimes := []types.OutstandingDowntime{}
k.IterateOutstandingDowntime(ctx, func(addr string) bool {
od := types.OutstandingDowntime{
Expand All @@ -148,36 +149,60 @@ func (k Keeper) ExportGenesis(ctx sdk.Context) (genesis *consumertypes.GenesisSt
channelID,
maturingPackets,
valset,
heightToVCIDs,
k.GetHeightToValsetUpdateIDs(ctx),
consumertypes.SlashRequests{},
outstandingDowntimes,
consumertypes.LastTransmissionBlockHeight{},
params,
)
} else {
clientID, ok := k.GetProviderClientID(ctx)
// if provider clientID and channelID don't exist on the consumer chain, then CCV protocol is disabled for this chain
// return a disabled genesis state
// if provider clientID and channelID don't exist on the consumer chain,
// then CCV protocol is disabled for this chain return a default genesis state
if !ok {
return consumertypes.DefaultGenesisState()
}
cs, ok := k.clientKeeper.GetClientState(ctx, clientID)
if !ok {
panic("provider client not set on already running consumer chain")
}
tmCs, ok := cs.(*ibctmtypes.ClientState)
if !ok {
panic("provider client consensus state is not tendermint client state")
}
consState, ok := k.clientKeeper.GetLatestClientConsensusState(ctx, clientID)
if !ok {
panic("provider consensus state not set on already running consumer chain")
}
tmConsState, ok := consState.(*ibctmtypes.ConsensusState)
if !ok {
panic("provider consensus state is not tendermint consensus state")
}

// export client states and pending slashing requests into a new chain genesis
genesis = consumertypes.NewInitialGenesisState(tmCs, tmConsState, valset, k.GetPendingSlashRequests(ctx), params)
genesis = consumertypes.NewRestartGenesisState(
clientID,
"",
nil,
valset,
k.GetHeightToValsetUpdateIDs(ctx),
k.GetPendingSlashRequests(ctx),
nil,
consumertypes.LastTransmissionBlockHeight{},
params,
)
}

return
}

// verifyGenesisInitValset verifies the latest consensus state on provider client matches
// the initial validator set of restarted chain thus
func (k Keeper) verifyGenesisInitValset(ctx sdk.Context, genState *consumertypes.GenesisState) error {

consState, ok := k.clientKeeper.GetLatestClientConsensusState(ctx, genState.ProviderClientId)
if !ok {
return fmt.Errorf("consensus state for provider client not found. MUST run IBC genesis before CCV consumer genesis")
}
tmConsState, ok := consState.(*ibctmtypes.ConsensusState)
if !ok {
return fmt.Errorf(fmt.Sprintf("consensus state has wrong type. expected: %T, got: %T", &ibctmtypes.ConsensusState{}, consState))
}

// ensure that initial validator set is same as initial consensus state on provider client.
// this will be verified by provider module on channel handshake.
vals, err := tmtypes.PB2TM.ValidatorUpdates(genState.InitialValSet)
if err != nil {
return err
}
valSet := tmtypes.NewValidatorSet(vals)

if !bytes.Equal(tmConsState.NextValidatorsHash, valSet.Hash()) {
return fmt.Errorf("initial validator set does not match last consensus state of the provider client")
}
return nil
}
Loading