diff --git a/app/ante/ante_options.go b/app/ante/ante_options.go index 84bf48b64..02728e053 100644 --- a/app/ante/ante_options.go +++ b/app/ante/ante_options.go @@ -1,6 +1,7 @@ package ante import ( + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth/ante" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" @@ -16,11 +17,17 @@ import ( txfeeskeeper "github.com/osmosis-labs/osmosis/v15/x/txfees/keeper" ) +// FeeMarketKeeper defines the expected keeper interface used on the AnteHandler +type FeeMarketKeeper interface { + ethante.FeeMarketKeeper + GetMinGasPrice(ctx sdk.Context) (minGasPrice sdk.Dec) +} + type HandlerOptions struct { AccountKeeper *authkeeper.AccountKeeper BankKeeper bankkeeper.Keeper IBCKeeper *ibckeeper.Keeper - FeeMarketKeeper ethante.FeeMarketKeeper + FeeMarketKeeper FeeMarketKeeper EvmKeeper ethante.EVMKeeper FeegrantKeeper ante.FeegrantKeeper TxFeesKeeper *txfeeskeeper.Keeper diff --git a/app/ante/handlers.go b/app/ante/handlers.go index 0c993207c..8897891ae 100644 --- a/app/ante/handlers.go +++ b/app/ante/handlers.go @@ -37,7 +37,7 @@ func newEthAnteHandler(options HandlerOptions) sdk.AnteHandler { // newLegacyCosmosAnteHandlerEip712 creates an AnteHandler to process legacy EIP-712 // transactions, as defined by the presence of an ExtensionOptionsWeb3Tx extension. func newLegacyCosmosAnteHandlerEip712(options HandlerOptions) sdk.AnteHandler { - mempoolFeeDecorator := txfeesante.NewMempoolFeeDecorator(*options.TxFeesKeeper) + mempoolFeeDecorator := txfeesante.NewMempoolFeeDecorator(*options.TxFeesKeeper, options.FeeMarketKeeper) deductFeeDecorator := txfeesante.NewDeductFeeDecorator(*options.TxFeesKeeper, options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper) return sdk.ChainAnteDecorators( @@ -86,7 +86,7 @@ func newLegacyCosmosAnteHandlerEip712(options HandlerOptions) sdk.AnteHandler { } func newCosmosAnteHandler(options HandlerOptions) sdk.AnteHandler { - mempoolFeeDecorator := txfeesante.NewMempoolFeeDecorator(*options.TxFeesKeeper) + mempoolFeeDecorator := txfeesante.NewMempoolFeeDecorator(*options.TxFeesKeeper, options.FeeMarketKeeper) deductFeeDecorator := txfeesante.NewDeductFeeDecorator(*options.TxFeesKeeper, options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper) return sdk.ChainAnteDecorators( diff --git a/go.mod b/go.mod index c39ca8847..2544e58ee 100644 --- a/go.mod +++ b/go.mod @@ -239,10 +239,10 @@ replace ( cosmossdk.io/api => cosmossdk.io/api v0.3.1 // use dymension forks - github.com/evmos/ethermint => github.com/dymensionxyz/ethermint v0.22.0-dymension-v1.0.0 + github.com/evmos/ethermint => github.com/dymensionxyz/ethermint v0.22.0-dymension-v1.0.0.0.20241202124813-c7838a77b8f6 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/osmosis-labs/osmosis/osmomath => github.com/dymensionxyz/osmosis/osmomath v0.0.6-dymension-v0.1.0.20240820121212-c0e21fa21e43 - github.com/osmosis-labs/osmosis/v15 => github.com/dymensionxyz/osmosis/v15 v15.2.1-0.20241121091134-5930c0d433a8 + github.com/osmosis-labs/osmosis/v15 => github.com/dymensionxyz/osmosis/v15 v15.2.1-0.20241202152541-b4fc63e3fe20 // broken goleveldb github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 diff --git a/go.sum b/go.sum index 5ada6ab18..53b4269fb 100644 --- a/go.sum +++ b/go.sum @@ -500,14 +500,14 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dvsekhvalnov/jose2go v1.6.0 h1:Y9gnSnP4qEI0+/uQkHvFXeD2PLPJeXEL+ySMEA2EjTY= github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= -github.com/dymensionxyz/ethermint v0.22.0-dymension-v1.0.0 h1:pND0fMCE5xv2A5eyXdSCI8+a+XaxvCNvC0+4Ac7TsTg= -github.com/dymensionxyz/ethermint v0.22.0-dymension-v1.0.0/go.mod h1:aokD0im7cUMMtR/khzNsmcGtINtxCpBfcgRvJdmLymA= +github.com/dymensionxyz/ethermint v0.22.0-dymension-v1.0.0.0.20241202124813-c7838a77b8f6 h1:YHircCv0aQSIz0T8N/C8upYHfAlsY8y80hAsxqkWtfA= +github.com/dymensionxyz/ethermint v0.22.0-dymension-v1.0.0.0.20241202124813-c7838a77b8f6/go.mod h1:aokD0im7cUMMtR/khzNsmcGtINtxCpBfcgRvJdmLymA= github.com/dymensionxyz/gerr-cosmos v1.1.0 h1:IW/P7HCB/iP9kgk3VXaWUoMoyx3vD76YO6p1fnubHVc= github.com/dymensionxyz/gerr-cosmos v1.1.0/go.mod h1:n+0olxPogzWqFKba45mCpvrHLGmeS8W9UZjggHnWk6c= github.com/dymensionxyz/osmosis/osmomath v0.0.6-dymension-v0.1.0.20240820121212-c0e21fa21e43 h1:EskhZ6ILN3vwJ6l8gPWPZ49RFSB52WghT5v+pmzrNCI= github.com/dymensionxyz/osmosis/osmomath v0.0.6-dymension-v0.1.0.20240820121212-c0e21fa21e43/go.mod h1:SdGCL9CZb14twRAJUSzb7bRE0OoopRpF2Hnd1UhJpFU= -github.com/dymensionxyz/osmosis/v15 v15.2.1-0.20241121091134-5930c0d433a8 h1:AwKgKV4uBZcylJarkT+W8K5FFjH/0tu3q4pzGRJsv+A= -github.com/dymensionxyz/osmosis/v15 v15.2.1-0.20241121091134-5930c0d433a8/go.mod h1:sXttKj99Ke160CvjID+5hvOG3TEF/K1k/Eqa37EhRCc= +github.com/dymensionxyz/osmosis/v15 v15.2.1-0.20241202152541-b4fc63e3fe20 h1:HQitz73Icw+YHPg56m284yYQVwf1XhKqfbbduX5SGes= +github.com/dymensionxyz/osmosis/v15 v15.2.1-0.20241202152541-b4fc63e3fe20/go.mod h1:sXttKj99Ke160CvjID+5hvOG3TEF/K1k/Eqa37EhRCc= github.com/dymensionxyz/sdk-utils v0.2.12 h1:wrcof+IP0AJQ7vvMRVpSekNNwa6B7ghAspHRjp/k+Lk= github.com/dymensionxyz/sdk-utils v0.2.12/go.mod h1:it9owYOpnIe17+ftTATQNDN4z+mBQx20/2Jm8SK15Rk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= diff --git a/ibctesting/light_client_test.go b/ibctesting/light_client_test.go index ef4c0164c..3e461f632 100644 --- a/ibctesting/light_client_test.go +++ b/ibctesting/light_client_test.go @@ -467,14 +467,12 @@ func (s *lightClientSuite) TestAfterUpdateState_OptimisticUpdateExists_NotCompat // - trigger rollback // - validate rollback: // - check if the client is frozen -// - validate IsHardForkingInProgress returns true // - validate client updates are blocked // - validate future consensus states are cleared // // - resolve hard fork // - validate client is unfrozen and hard fork is resolved // - validate the client is updated -// - validate the client is not in hard forking state // // - validate client updates are allowed func (s *lightClientSuite) TestAfterUpdateState_Rollback() { @@ -517,9 +515,14 @@ func (s *lightClientSuite) TestAfterUpdateState_Rollback() { // get number of consensus states before rollback csBeforeRollback := s.hubApp().IBCKeeper.ClientKeeper.GetAllConsensusStates(s.hubCtx())[0].ConsensusStates - // Trigger rollback - rollbackHeight := uint64(s.rollappChain().LastHeader.Header.Height) - 5 - err := s.hubApp().LightClientKeeper.RollbackCanonicalClient(s.hubCtx(), s.rollappChain().ChainID, rollbackHeight) + // Trigger rollback / simulate fork + nRolledBack := uint64(5) + lastValidHeight := uint64(s.rollappChain().LastHeader.Header.Height) - nRolledBack + newRevisionHeight := lastValidHeight + 1 + ra := s.hubApp().RollappKeeper.MustGetRollapp(s.hubCtx(), s.rollappChain().ChainID) + ra.Revisions = append(ra.Revisions, rollapptypes.Revision{StartHeight: newRevisionHeight, Number: 1}) + s.hubApp().RollappKeeper.SetRollapp(s.hubCtx(), ra) + err := s.hubApp().LightClientKeeper.RollbackCanonicalClient(s.hubCtx(), s.rollappChain().ChainID, lastValidHeight) s.Require().NoError(err) clientState, found := s.hubApp().IBCKeeper.ClientKeeper.GetClientState(s.hubCtx(), s.path.EndpointA.ClientID) @@ -530,15 +533,12 @@ func (s *lightClientSuite) TestAfterUpdateState_Rollback() { // Check if the client is frozen s.True(!tmClientState.FrozenHeight.IsZero(), "Client should be frozen after rollback") - // Check if IsHardForkingInProgress returns true - s.True(s.hubApp().LightClientKeeper.IsHardForkingInProgress(s.hubCtx(), s.rollappChain().ChainID), "Rollapp should be in hard forking state") - // Validate future consensus states are cleared csAfterRollback := s.hubApp().IBCKeeper.ClientKeeper.GetAllConsensusStates(s.hubCtx())[0].ConsensusStates s.Require().Less(len(csAfterRollback), len(csBeforeRollback), "Consensus states should be cleared after rollback") for height := uint64(0); height <= uint64(s.rollappChain().LastHeader.Header.Height); height++ { _, found := s.hubApp().IBCKeeper.ClientKeeper.GetClientConsensusState(s.hubCtx(), s.path.EndpointA.ClientID, clienttypes.NewHeight(1, height)) - if height > rollbackHeight { + if height >= newRevisionHeight { s.False(found, "Consensus state should be cleared for height %d", height) } } @@ -547,7 +547,7 @@ func (s *lightClientSuite) TestAfterUpdateState_Rollback() { cnt := 0 for _, height := range signerHeights { _, err := s.hubApp().LightClientKeeper.GetSigner(s.hubCtx(), s.path.EndpointA.ClientID, uint64(height)) - if height > int64(rollbackHeight) { + if height >= int64(lastValidHeight) { s.Error(err, "Signer should be removed for height %d", height) } else { s.NoError(err, "Signer should not be removed for height %d", height) @@ -578,6 +578,8 @@ func (s *lightClientSuite) TestAfterUpdateState_Rollback() { s.ErrorIs(err, types.ErrorHardForkInProgress) // submit a state info update to resolve the hard fork + + bds.BD = bds.BD[len(bds.BD)-int(nRolledBack):] blockDescriptors := &rollapptypes.BlockDescriptors{BD: bds.BD} msgUpdateState := rollapptypes.NewMsgUpdateState( s.hubChain().SenderAccount.GetAddress().String(), @@ -587,16 +589,15 @@ func (s *lightClientSuite) TestAfterUpdateState_Rollback() { uint64(len(bds.BD)), blockDescriptors, ) + msgUpdateState.RollappRevision = 1 _, err = s.rollappMsgServer().UpdateState(s.hubCtx(), msgUpdateState) - s.Require().NoError(err) + s.Require().NoError(err, "update state") // Test resolve hard fork clientState, found = s.hubApp().IBCKeeper.ClientKeeper.GetClientState(s.hubCtx(), s.path.EndpointA.ClientID) s.True(found) // Verify that the client is unfrozen and hard fork is resolved s.True(clientState.(*ibctm.ClientState).FrozenHeight.IsZero(), "Client should be unfrozen after hard fork resolution") - // Verify that the client is not in hard forking state - s.False(s.hubApp().LightClientKeeper.IsHardForkingInProgress(s.hubCtx(), s.rollappChain().ChainID), "Rollapp should not be in hard forking state") // Verify that the client is updated with the height of the first block descriptor s.Require().Equal(bds.BD[0].Height, clientState.GetLatestHeight().GetRevisionHeight()) _, ok = s.hubApp().IBCKeeper.ClientKeeper.GetLatestClientConsensusState(s.hubCtx(), s.path.EndpointA.ClientID) @@ -604,5 +605,10 @@ func (s *lightClientSuite) TestAfterUpdateState_Rollback() { // validate client updates are no longer blocked s.coordinator.CommitBlock(s.rollappChain()) + + // a bit of a hack to make sure the ibc go testing framework can update, since we can't get inside to pass a revision + ra.Revisions = nil + s.hubApp().RollappKeeper.SetRollapp(s.hubCtx(), ra) + s.NoError(s.path.EndpointA.UpdateClient()) } diff --git a/proto/dymensionxyz/dymension/lightclient/genesis.proto b/proto/dymensionxyz/dymension/lightclient/genesis.proto index 98bc6c591..fb0b9c3e2 100644 --- a/proto/dymensionxyz/dymension/lightclient/genesis.proto +++ b/proto/dymensionxyz/dymension/lightclient/genesis.proto @@ -17,7 +17,6 @@ message HeaderSignerEntry { message GenesisState { repeated CanonicalClient canonical_clients = 1 [ (gogoproto.nullable) = false ]; repeated HeaderSignerEntry header_signers = 3 [ (gogoproto.nullable) = false ]; - repeated string hard_fork_keys = 4; } message CanonicalClient { diff --git a/testutil/keeper/lightclient.go b/testutil/keeper/lightclient.go index 7a67c1e3b..77b4fb9d4 100644 --- a/testutil/keeper/lightclient.go +++ b/testutil/keeper/lightclient.go @@ -182,6 +182,10 @@ func NewMockSequencerKeeper(sequencers map[string]*sequencertypes.Sequencer) *Mo type MockRollappKeeper struct{} +func (m *MockRollappKeeper) IsFirstHeightOfLatestFork(ctx sdk.Context, rollappId string, revision, height uint64) bool { + return false +} + func (m *MockRollappKeeper) GetLatestHeight(ctx sdk.Context, rollappId string) (uint64, bool) { panic("implement me") } diff --git a/x/delayedack/keeper/fraud.go b/x/delayedack/keeper/fraud.go index 3b8d0d840..eecb23fe2 100644 --- a/x/delayedack/keeper/fraud.go +++ b/x/delayedack/keeper/fraud.go @@ -13,11 +13,11 @@ import ( var _ rollapptypes.RollappHooks = &Keeper{} -func (k Keeper) OnHardFork(ctx sdk.Context, rollappID string, newRevisionHeight uint64) error { +func (k Keeper) OnHardFork(ctx sdk.Context, rollappID string, lastValidHeight uint64) error { logger := ctx.Logger().With("module", "DelayedAckMiddleware") // Get all the pending packets from fork height inclusive - rollappPendingPackets := k.ListRollappPackets(ctx, types.PendingByRollappIDFromHeight(rollappID, newRevisionHeight)) + rollappPendingPackets := k.ListRollappPackets(ctx, types.PendingByRollappIDFromHeight(rollappID, lastValidHeight+1)) // Iterate over all the pending packets and revert them for _, rollappPacket := range rollappPendingPackets { diff --git a/x/delayedack/keeper/fraud_test.go b/x/delayedack/keeper/fraud_test.go index 2495f41b1..900565c57 100644 --- a/x/delayedack/keeper/fraud_test.go +++ b/x/delayedack/keeper/fraud_test.go @@ -35,7 +35,7 @@ func (suite *DelayedAckTestSuite) TestHandleFraud() { suite.Require().Nil(err) // call fraud on the 4 packet - err = keeper.OnHardFork(ctx, rollappId, 4) + err = keeper.OnHardFork(ctx, rollappId, 3) suite.Require().NoError(err) // expected result: diff --git a/x/dymns/keeper/hooks.go b/x/dymns/keeper/hooks.go index 9fb8acc19..ee9f33adc 100644 --- a/x/dymns/keeper/hooks.go +++ b/x/dymns/keeper/hooks.go @@ -71,7 +71,7 @@ func (h rollappHooks) BeforeUpdateState(_ sdk.Context, _ string, _ string, _ boo return nil } -func (h rollappHooks) AfterUpdateState(_ sdk.Context, _ string, _ *rollapptypes.StateInfo) error { +func (h rollappHooks) AfterUpdateState(ctx sdk.Context, stateInfo *rollapptypes.StateInfoMeta) error { return nil } diff --git a/x/lightclient/ante/ibc_msg_update_client.go b/x/lightclient/ante/ibc_msg_update_client.go index 4b94bbcd0..7b61c2a65 100644 --- a/x/lightclient/ante/ibc_msg_update_client.go +++ b/x/lightclient/ante/ibc_msg_update_client.go @@ -63,11 +63,6 @@ func (i IBCMessagesDecorator) HandleMsgUpdateClient(ctx sdk.Context, msg *ibccli return gerrc.ErrInternal.Wrapf("get rollapp from sequencer: rollapp: %s", seq.RollappId) } - // cannot update the LC unless fork is resolved (after receiving state post fork state update) - if i.k.IsHardForkingInProgress(ctx, rollapp.RollappId) { - return types.ErrorHardForkInProgress - } - // this disallows LC updates from previous revisions but should be fine since new state roots can be used to prove // state older than the one in the current state root. if header.Header.Version.App != rollapp.LatestRevision().Number { diff --git a/x/lightclient/ante/ibc_msgs_test.go b/x/lightclient/ante/ibc_msgs_test.go index 460ee0b0f..c47f95198 100644 --- a/x/lightclient/ante/ibc_msgs_test.go +++ b/x/lightclient/ante/ibc_msgs_test.go @@ -17,8 +17,11 @@ type MockRollappKeeper struct { stateInfos map[string]map[uint64]rollapptypes.StateInfo } +func (m *MockRollappKeeper) IsFirstHeightOfLatestFork(ctx sdk.Context, rollappId string, revision, height uint64) bool { + panic("implement me") +} + func (m *MockRollappKeeper) GetLatestHeight(ctx sdk.Context, rollappId string) (uint64, bool) { - // TODO implement me panic("implement me") } diff --git a/x/lightclient/keeper/client_store.go b/x/lightclient/keeper/client_store.go index 5e91401b8..291b2297b 100644 --- a/x/lightclient/keeper/client_store.go +++ b/x/lightclient/keeper/client_store.go @@ -22,6 +22,13 @@ func getClientState(clientStore sdk.KVStore, cdc codec.BinaryCodec) exported.Cli return clienttypes.MustUnmarshalClientState(cdc, bz) } +// must be tendermint! +func getClientStateTM(clientStore sdk.KVStore, cdc codec.BinaryCodec) *ibctm.ClientState { + c := getClientState(clientStore, cdc) + tmClientState, _ := c.(*ibctm.ClientState) + return tmClientState +} + // setClientState stores the client state func setClientState(clientStore sdk.KVStore, cdc codec.BinaryCodec, clientState exported.ClientState) { key := host.ClientStateKey() diff --git a/x/lightclient/keeper/genesis.go b/x/lightclient/keeper/genesis.go index 155e9a290..7b6834c1c 100644 --- a/x/lightclient/keeper/genesis.go +++ b/x/lightclient/keeper/genesis.go @@ -19,18 +19,13 @@ func (k Keeper) InitGenesis(ctx sdk.Context, genesisState types.GenesisState) { panic(err) } } - for _, rollappID := range genesisState.HardForkKeys { - k.SetHardForkInProgress(ctx, rollappID) - } } func (k Keeper) ExportGenesis(ctx sdk.Context) types.GenesisState { clients := k.GetAllCanonicalClients(ctx) - hardForkKeys := k.ListHardForkKeys(ctx) ret := types.GenesisState{ CanonicalClients: clients, - HardForkKeys: hardForkKeys, } if err := k.headerSigners.Walk(ctx, nil, diff --git a/x/lightclient/keeper/genesis_test.go b/x/lightclient/keeper/genesis_test.go index ac0efff28..9a843af2e 100644 --- a/x/lightclient/keeper/genesis_test.go +++ b/x/lightclient/keeper/genesis_test.go @@ -19,7 +19,6 @@ func TestInitGenesis(t *testing.T) { keeper.InitGenesis(ctx, types.GenesisState{ CanonicalClients: clients, - HardForkKeys: []string{"rollapp-1", "rollapp-2"}, }) ibc, found := keeper.GetCanonicalClient(ctx, "rollapp-1") @@ -28,10 +27,6 @@ func TestInitGenesis(t *testing.T) { ibc, found = keeper.GetCanonicalClient(ctx, "rollapp-2") require.True(t, found) require.Equal(t, "client-2", ibc) - hfks := keeper.ListHardForkKeys(ctx) - require.Len(t, hfks, 2) - require.Equal(t, "rollapp-1", hfks[0]) - require.Equal(t, "rollapp-2", hfks[1]) } func TestExportGenesis(t *testing.T) { @@ -39,8 +34,6 @@ func TestExportGenesis(t *testing.T) { keeper.SetCanonicalClient(ctx, "rollapp-1", "client-1") keeper.SetCanonicalClient(ctx, "rollapp-2", "client-2") - keeper.SetHardForkInProgress(ctx, "rollapp-1") - keeper.SetHardForkInProgress(ctx, "rollapp-2") genesis := keeper.ExportGenesis(ctx) @@ -49,9 +42,6 @@ func TestExportGenesis(t *testing.T) { require.Equal(t, "client-2", genesis.CanonicalClients[1].IbcClientId) require.Equal(t, "rollapp-1", genesis.CanonicalClients[0].RollappId) require.Equal(t, "rollapp-2", genesis.CanonicalClients[1].RollappId) - require.Len(t, genesis.HardForkKeys, 2) - require.Equal(t, "rollapp-1", genesis.HardForkKeys[0]) - require.Equal(t, "rollapp-2", genesis.HardForkKeys[1]) } func TestImportExportGenesis(t *testing.T) { @@ -80,7 +70,6 @@ func TestImportExportGenesis(t *testing.T) { Height: 43, }, }, - HardForkKeys: []string{"rollapp-1", "rollapp-2"}, } k.InitGenesis(ctx, g) diff --git a/x/lightclient/keeper/hook_listener.go b/x/lightclient/keeper/hook_listener.go index 45966964d..c3dd0b4e0 100644 --- a/x/lightclient/keeper/hook_listener.go +++ b/x/lightclient/keeper/hook_listener.go @@ -30,40 +30,35 @@ func (k Keeper) RollappHooks() rollapptypes.RollappHooks { // AfterUpdateState is called after a state update is made to a rollapp. // This hook checks if the rollapp has a canonical IBC light client and if the Consensus state is compatible with the state update // and punishes the sequencer if it is not -func (hook rollappHook) AfterUpdateState( - ctx sdk.Context, - rollappId string, - stateInfo *rollapptypes.StateInfo, -) error { +func (hook rollappHook) AfterUpdateState(ctx sdk.Context, stateInfoM *rollapptypes.StateInfoMeta) error { if !hook.k.Enabled() { return nil } + rollappID := stateInfoM.Rollapp + stateInfo := &stateInfoM.StateInfo - client, ok := hook.k.GetCanonicalClient(ctx, rollappId) + client, ok := hook.k.GetCanonicalClient(ctx, rollappID) if !ok { return nil } - // first state after hardfork, should reset the client to active state - if hook.k.IsHardForkingInProgress(ctx, rollappId) { - err := hook.k.ResolveHardFork(ctx, rollappId) - if err != nil { - return errorsmod.Wrap(err, "resolve hard fork") - } - return nil + if hook.k.rollappKeeper.IsFirstHeightOfLatestFork(ctx, rollappID, stateInfoM.Revision, stateInfo.GetStartHeight()) { + return errorsmod.Wrap(hook.k.ResolveHardFork(ctx, rollappID), "resolve hard fork") } - // TODO: check hard fork in progress here - seq, err := hook.k.SeqK.RealSequencer(ctx, stateInfo.Sequencer) if err != nil { return errorsmod.Wrap(errors.Join(gerrc.ErrInternal, err), "get sequencer for state info") } - // [hStart-1..,hEnd) is correct because we compare against a next validators hash - for h := stateInfo.GetStartHeight() - 1; h < stateInfo.GetLatestHeight(); h++ { - if err := hook.validateOptimisticUpdate(ctx, rollappId, client, seq, stateInfo, h); err != nil { - return errorsmod.Wrap(err, "validate optimistic update") + // [hStart-1..,hEnd] is correct because we compare against a next validators hash + // for all heights in the range [hStart-1..hEnd), but do not for hEnd + for h := stateInfo.GetStartHeight() - 1; h <= stateInfo.GetLatestHeight(); h++ { + if err := hook.validateOptimisticUpdate(ctx, rollappID, client, seq, stateInfo, h); err != nil { + if errors.Is(err, types.ErrNextValHashMismatch) && h == stateInfo.GetLatestHeight() { + continue + } + return errorsmod.Wrapf(err, "validate optimistic update: height: %d", h) } } @@ -92,7 +87,7 @@ func (hook rollappHook) validateOptimisticUpdate( } expectBD, err := hook.getBlockDescriptor(ctx, rollapp, cache, h) if err != nil { - return err + return errorsmod.Wrap(err, "get block descriptor") } expect := types.RollappState{ BlockDescriptor: expectBD, @@ -101,7 +96,7 @@ func (hook rollappHook) validateOptimisticUpdate( err = types.CheckCompatibility(*got, expect) if err != nil { - return errors.Join(gerrc.ErrFault, err) + return errorsmod.Wrap(err, "check compatibility") } // everything is fine diff --git a/x/lightclient/keeper/hook_listener_test.go b/x/lightclient/keeper/hook_listener_test.go index bfdc85589..43398f56d 100644 --- a/x/lightclient/keeper/hook_listener_test.go +++ b/x/lightclient/keeper/hook_listener_test.go @@ -22,9 +22,6 @@ func TestAfterUpdateState(t *testing.T) { prepare func(ctx sdk.Context, k lightClientKeeper.Keeper) input expectErr bool }{ - // TODO: tests need expanding - // At least the following need to be added - // - Client with all cons states after the state update will not be canonical { name: "canonical client does not exist for rollapp", prepare: func(ctx sdk.Context, k lightClientKeeper.Keeper) input { @@ -37,7 +34,7 @@ func TestAfterUpdateState(t *testing.T) { }, { - name: "both states are not compatible - slash the sequencer who signed", + name: "incompatible", prepare: func(ctx sdk.Context, k lightClientKeeper.Keeper) input { k.SetCanonicalClient(ctx, keepertest.DefaultRollapp, keepertest.CanonClientID) err := k.SaveSigner(ctx, keepertest.Alice.Address, keepertest.CanonClientID, 2) @@ -115,7 +112,11 @@ func TestAfterUpdateState(t *testing.T) { keeper, ctx := keepertest.LightClientKeeper(t) input := tc.prepare(ctx, *keeper) - err := keeper.RollappHooks().AfterUpdateState(ctx, input.rollappId, input.stateInfo) + err := keeper.RollappHooks().AfterUpdateState(ctx, &rollapptypes.StateInfoMeta{ + StateInfo: *input.stateInfo, + Revision: 0, /// TODO: + Rollapp: keepertest.DefaultRollapp, + }) if tc.expectErr { require.Error(t, err) } else { diff --git a/x/lightclient/keeper/keeper.go b/x/lightclient/keeper/keeper.go index ad2c1c685..74ea57fd9 100644 --- a/x/lightclient/keeper/keeper.go +++ b/x/lightclient/keeper/keeper.go @@ -9,7 +9,6 @@ import ( errorsmod "cosmossdk.io/errors" "github.com/cometbft/cometbft/libs/log" "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/store/prefix" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" ibcclienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" @@ -153,31 +152,6 @@ func (k Keeper) ExpectedClientState(context.Context, *types.QueryExpectedClientS return &types.QueryExpectedClientStateResponse{ClientState: anyClient}, nil } -func (k Keeper) SetHardForkInProgress(ctx sdk.Context, rollappID string) { - ctx.KVStore(k.storeKey).Set(types.HardForkKey(rollappID), []byte{0x01}) -} - -// remove the hardfork key from the store -func (k Keeper) setHardForkResolved(ctx sdk.Context, rollappID string) { - ctx.KVStore(k.storeKey).Delete(types.HardForkKey(rollappID)) -} - -// checks if rollapp is hard forking -func (k Keeper) IsHardForkingInProgress(ctx sdk.Context, rollappID string) bool { - return ctx.KVStore(k.storeKey).Has(types.HardForkKey(rollappID)) -} - -func (k Keeper) ListHardForkKeys(ctx sdk.Context) []string { - prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.HardForkPrefix) - iter := prefixStore.Iterator(nil, nil) - defer iter.Close() // nolint: errcheck - var ret []string - for ; iter.Valid(); iter.Next() { - ret = append(ret, string(iter.Key())) - } - return ret -} - func (k Keeper) pruneSigners(ctx sdk.Context, client string, h uint64, isAbove bool) error { var rng *collections.PairRange[string, uint64] if isAbove { diff --git a/x/lightclient/keeper/rollback.go b/x/lightclient/keeper/rollback.go index b5fbfae71..f88c0e569 100644 --- a/x/lightclient/keeper/rollback.go +++ b/x/lightclient/keeper/rollback.go @@ -12,11 +12,11 @@ import ( ibctm "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" ) -func (hook rollappHook) OnHardFork(ctx sdk.Context, rollappId string, newRevisionHeight uint64) error { - return hook.k.RollbackCanonicalClient(ctx, rollappId, newRevisionHeight) +func (hook rollappHook) OnHardFork(ctx sdk.Context, rollappId string, lastValidHeight uint64) error { + return hook.k.RollbackCanonicalClient(ctx, rollappId, lastValidHeight) } -func (k Keeper) RollbackCanonicalClient(ctx sdk.Context, rollappId string, newRevisionHeight uint64) error { +func (k Keeper) RollbackCanonicalClient(ctx sdk.Context, rollappId string, lastValidHeight uint64) error { client, found := k.GetCanonicalClient(ctx, rollappId) if !found { return gerrc.ErrFailedPrecondition.Wrap("canonical client not found") @@ -26,7 +26,7 @@ func (k Keeper) RollbackCanonicalClient(ctx sdk.Context, rollappId string, newRe // iterate over all consensus states and metadata in the client store IterateConsensusStateDescending(cs, func(h exported.Height) bool { // iterate until we pass the new revision height - if h.GetRevisionHeight() < newRevisionHeight { + if h.GetRevisionHeight() <= lastValidHeight { return true } @@ -37,18 +37,17 @@ func (k Keeper) RollbackCanonicalClient(ctx sdk.Context, rollappId string, newRe return false }) - // clean the optimistic updates valset - err := k.PruneSignersAbove(ctx, client, newRevisionHeight-1) + // we DO want to prune the signer of the last valid height: + // the only reason we didn't do it before was because we were waiting for next validators hash + // but now we don't care about that + err := k.PruneSignersAbove(ctx, client, lastValidHeight-1) if err != nil { return errorsmod.Wrap(err, "prune signers above") } - // marks that hard fork is in progress - k.SetHardForkInProgress(ctx, rollappId) - // freeze the client // it will be released after the hardfork is resolved (on the next state update) - k.freezeClient(cs, newRevisionHeight) + k.freezeClient(cs, lastValidHeight) return nil } @@ -57,11 +56,21 @@ func (k Keeper) RollbackCanonicalClient(ctx sdk.Context, rollappId string, newRe // and adding consensus states based on the block descriptors // CONTRACT: canonical client is already set, state info exists func (k Keeper) ResolveHardFork(ctx sdk.Context, rollappID string) error { - client, _ := k.GetCanonicalClient(ctx, rollappID) // already checked in the caller - clientStore := k.ibcClientKeeper.ClientStore(ctx, client) + clientID, _ := k.GetCanonicalClient(ctx, rollappID) // already checked in the caller + clientStore := k.ibcClientKeeper.ClientStore(ctx, clientID) stateinfo, _ := k.rollappKeeper.GetLatestStateInfo(ctx, rollappID) // already checked in the caller + height := stateinfo.StartHeight + // sanity check + client := getClientStateTM(clientStore, k.cdc) + clientHeight := client.GetLatestHeight().GetRevisionHeight() + if height <= clientHeight { + return gerrc.ErrInternal.Wrapf("client latest height not less than new latest height: new: %d, client: %d", + height, clientHeight, + ) + } + bd := stateinfo.BDs.BD[0] // get the valHash of this sequencer @@ -69,8 +78,6 @@ func (k Keeper) ResolveHardFork(ctx sdk.Context, rollappID string) error { proposer, _ := k.SeqK.RealSequencer(ctx, stateinfo.Sequencer) valHash, _ := proposer.ValsetHash() - // unfreeze the client and set the latest height - k.resetClientToValidState(clientStore, height) // add consensus states based on the block descriptors cs := ibctm.ConsensusState{ Timestamp: bd.Timestamp, @@ -81,26 +88,25 @@ func (k Keeper) ResolveHardFork(ctx sdk.Context, rollappID string) error { setConsensusState(clientStore, k.cdc, clienttypes.NewHeight(1, height), &cs) setConsensusMetadata(ctx, clientStore, clienttypes.NewHeight(1, height)) - k.setHardForkResolved(ctx, rollappID) + k.unfreezeClient(clientStore, height) + return nil } // freezeClient freezes the client by setting the frozen height to the current height func (k Keeper) freezeClient(clientStore sdk.KVStore, height uint64) { - c := getClientState(clientStore, k.cdc) - tmClientState, _ := c.(*ibctm.ClientState) + tmClientState := getClientStateTM(clientStore, k.cdc) // freeze the client - tmClientState.FrozenHeight = clienttypes.NewHeight(1, height) + tmClientState.FrozenHeight = ibctm.FrozenHeight tmClientState.LatestHeight = clienttypes.NewHeight(1, height) setClientState(clientStore, k.cdc, tmClientState) } // freezeClient freezes the client by setting the frozen height to the current height -func (k Keeper) resetClientToValidState(clientStore sdk.KVStore, height uint64) { - c := getClientState(clientStore, k.cdc) - tmClientState, _ := c.(*ibctm.ClientState) +func (k Keeper) unfreezeClient(clientStore sdk.KVStore, height uint64) { + tmClientState := getClientStateTM(clientStore, k.cdc) // unfreeze the client and set the latest height tmClientState.FrozenHeight = clienttypes.ZeroHeight() diff --git a/x/lightclient/types/errors.go b/x/lightclient/types/errors.go index c4f3b1517..6124262a5 100644 --- a/x/lightclient/types/errors.go +++ b/x/lightclient/types/errors.go @@ -6,11 +6,8 @@ import ( ) var ( - ErrStateRootsMismatch = errorsmod.Wrap(gerrc.ErrFailedPrecondition, "block descriptor state root does not match tendermint header app hash") - ErrValidatorHashMismatch = errorsmod.Wrap(gerrc.ErrFailedPrecondition, "next validator hash does not match the sequencer for h+1") - ErrTimestampMismatch = errorsmod.Wrap(gerrc.ErrFailedPrecondition, "block descriptor timestamp does not match tendermint header timestamp") - ErrSequencerNotFound = errorsmod.Wrap(gerrc.ErrNotFound, "sequencer for given valhash") - ErrorMissingClientState = errorsmod.Wrap(gerrc.ErrInternal, "client state was expected, but not found") - ErrorInvalidClientType = errorsmod.Wrap(gerrc.ErrInternal, "client state is not a tendermint client") - ErrorHardForkInProgress = errorsmod.Wrap(gerrc.ErrFailedPrecondition, "update light client until forking is finished") + ErrStateRootMismatch = errorsmod.Wrap(gerrc.ErrFault, "block descriptor state root does not match tendermint header app hash") + ErrNextValHashMismatch = errorsmod.Wrap(gerrc.ErrFault, "next validator hash on light client cons state does not match the sequencer for h+1 from the state info") + ErrTimestampMismatch = errorsmod.Wrap(gerrc.ErrFault, "block descriptor timestamp does not match tendermint header timestamp") + ErrorHardForkInProgress = errorsmod.Wrap(gerrc.ErrFailedPrecondition, "update light client while fork in progress") ) diff --git a/x/lightclient/types/expected_keepers.go b/x/lightclient/types/expected_keepers.go index 3bca788b2..e9995d46c 100644 --- a/x/lightclient/types/expected_keepers.go +++ b/x/lightclient/types/expected_keepers.go @@ -23,6 +23,7 @@ type RollappKeeperExpected interface { FindStateInfoByHeight(ctx sdk.Context, rollappId string, height uint64) (*rollapptypes.StateInfo, error) GetLatestStateInfo(ctx sdk.Context, rollappId string) (rollapptypes.StateInfo, bool) SetRollapp(ctx sdk.Context, rollapp rollapptypes.Rollapp) + IsFirstHeightOfLatestFork(ctx sdk.Context, rollappId string, revision, height uint64) bool } type IBCClientKeeperExpected interface { diff --git a/x/lightclient/types/genesis.pb.go b/x/lightclient/types/genesis.pb.go index 94c4e0e5b..737c834f9 100644 --- a/x/lightclient/types/genesis.pb.go +++ b/x/lightclient/types/genesis.pb.go @@ -88,7 +88,6 @@ func (m *HeaderSignerEntry) GetHeight() uint64 { type GenesisState struct { CanonicalClients []CanonicalClient `protobuf:"bytes,1,rep,name=canonical_clients,json=canonicalClients,proto3" json:"canonical_clients"` HeaderSigners []HeaderSignerEntry `protobuf:"bytes,3,rep,name=header_signers,json=headerSigners,proto3" json:"header_signers"` - HardForkKeys []string `protobuf:"bytes,4,rep,name=hard_fork_keys,json=hardForkKeys,proto3" json:"hard_fork_keys,omitempty"` } func (m *GenesisState) Reset() { *m = GenesisState{} } @@ -138,13 +137,6 @@ func (m *GenesisState) GetHeaderSigners() []HeaderSignerEntry { return nil } -func (m *GenesisState) GetHardForkKeys() []string { - if m != nil { - return m.HardForkKeys - } - return nil -} - type CanonicalClient struct { RollappId string `protobuf:"bytes,1,opt,name=rollapp_id,json=rollappId,proto3" json:"rollapp_id,omitempty"` IbcClientId string `protobuf:"bytes,2,opt,name=ibc_client_id,json=ibcClientId,proto3" json:"ibc_client_id,omitempty"` @@ -208,32 +200,30 @@ func init() { } var fileDescriptor_5520440548912168 = []byte{ - // 395 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x52, 0xcd, 0xca, 0xd3, 0x40, - 0x14, 0x4d, 0xbe, 0x94, 0x0f, 0x33, 0xdf, 0x8f, 0xed, 0x20, 0x12, 0x14, 0x63, 0x09, 0x2e, 0x02, - 0x42, 0x22, 0x16, 0xc1, 0xad, 0x2d, 0xfe, 0x14, 0x77, 0xa9, 0x2b, 0x37, 0x21, 0x99, 0xb9, 0x4d, - 0x86, 0xa6, 0x33, 0x71, 0x66, 0x2a, 0x8d, 0x2b, 0x1f, 0xc1, 0xc7, 0xea, 0xb2, 0x4b, 0x57, 0x22, - 0xed, 0x8b, 0x48, 0x7e, 0x2c, 0x6d, 0x45, 0x74, 0x37, 0xe7, 0xcc, 0x3d, 0x9c, 0x7b, 0x0e, 0x17, - 0x3d, 0xa3, 0xd5, 0x12, 0xb8, 0x62, 0x82, 0xaf, 0xab, 0x2f, 0xe1, 0x01, 0x84, 0x05, 0xcb, 0x72, - 0x4d, 0x0a, 0x06, 0x5c, 0x87, 0x19, 0x70, 0x50, 0x4c, 0x05, 0xa5, 0x14, 0x5a, 0x60, 0xef, 0x58, - 0x11, 0x1c, 0x40, 0x70, 0xa4, 0x78, 0x70, 0x2f, 0x13, 0x99, 0x68, 0xc6, 0xc3, 0xfa, 0xd5, 0x2a, - 0xbd, 0x15, 0x1a, 0xbc, 0x83, 0x84, 0x82, 0x9c, 0xb1, 0x8c, 0x83, 0x7c, 0xcd, 0xb5, 0xac, 0xf0, - 0x53, 0x34, 0x50, 0xf0, 0x69, 0x05, 0x9c, 0x80, 0x8c, 0x13, 0x4a, 0x25, 0x28, 0xe5, 0x98, 0x43, - 0xd3, 0xb7, 0xa3, 0xfe, 0xe1, 0xe3, 0x55, 0xcb, 0xe3, 0x87, 0xc8, 0x6e, 0x1d, 0x62, 0x46, 0x9d, - 0x8b, 0x66, 0xe8, 0x4e, 0x4b, 0x4c, 0x29, 0xbe, 0x8f, 0x2e, 0x73, 0xa8, 0x97, 0x70, 0xac, 0xa1, - 0xe9, 0xf7, 0xa2, 0x0e, 0x79, 0x5f, 0x2f, 0xd0, 0xf5, 0xdb, 0x36, 0xc2, 0x4c, 0x27, 0x1a, 0xf0, - 0x1c, 0x0d, 0x48, 0xc2, 0x05, 0x67, 0x24, 0x29, 0xe2, 0x56, 0x5e, 0x5b, 0x5a, 0xfe, 0xd5, 0xf3, - 0x51, 0xf0, 0xef, 0x74, 0xc1, 0xe4, 0xb7, 0x78, 0xd2, 0xe0, 0x71, 0x6f, 0xf3, 0xe3, 0xb1, 0x11, - 0xf5, 0xc9, 0x29, 0xad, 0x70, 0x8a, 0x6e, 0xf3, 0x26, 0x6f, 0xac, 0x9a, 0xc0, 0xca, 0xb1, 0x1a, - 0x93, 0x17, 0xff, 0x63, 0xf2, 0x47, 0x53, 0x9d, 0xcd, 0x4d, 0x7e, 0xf4, 0xa1, 0xf0, 0x13, 0x74, - 0x9b, 0x27, 0x92, 0xc6, 0x73, 0x21, 0x17, 0xf1, 0x02, 0x2a, 0xe5, 0xf4, 0x86, 0x96, 0x6f, 0x47, - 0xd7, 0x35, 0xfb, 0x46, 0xc8, 0xc5, 0x7b, 0xa8, 0x94, 0xf7, 0x01, 0xdd, 0x3d, 0x5b, 0x1a, 0x3f, - 0x42, 0x48, 0x8a, 0xa2, 0x48, 0xca, 0xb2, 0xee, 0xb2, 0x2d, 0xdc, 0xee, 0x98, 0x29, 0xc5, 0x1e, - 0xba, 0x61, 0x29, 0x89, 0xcf, 0xdb, 0xbe, 0x62, 0x29, 0x99, 0x74, 0x85, 0x8f, 0xa3, 0xcd, 0xce, - 0x35, 0xb7, 0x3b, 0xd7, 0xfc, 0xb9, 0x73, 0xcd, 0x6f, 0x7b, 0xd7, 0xd8, 0xee, 0x5d, 0xe3, 0xfb, - 0xde, 0x35, 0x3e, 0xbe, 0xcc, 0x98, 0xce, 0x57, 0x69, 0x40, 0xc4, 0x32, 0xfc, 0xcb, 0x81, 0x7d, - 0x1e, 0x85, 0xeb, 0x93, 0x2b, 0xd3, 0x55, 0x09, 0x2a, 0xbd, 0x6c, 0x4e, 0x65, 0xf4, 0x2b, 0x00, - 0x00, 0xff, 0xff, 0x68, 0x01, 0x4e, 0x62, 0x98, 0x02, 0x00, 0x00, + // 363 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x52, 0x4f, 0x4b, 0xfb, 0x40, + 0x14, 0x4c, 0x7e, 0x2d, 0xe5, 0xd7, 0xad, 0xd5, 0x76, 0x11, 0x09, 0x8a, 0xb1, 0xe4, 0x54, 0x10, + 0x12, 0xb1, 0x08, 0x5e, 0x6d, 0x11, 0xed, 0x35, 0xf5, 0xe4, 0x25, 0x24, 0x9b, 0x67, 0xb2, 0x90, + 0xee, 0xc6, 0xec, 0x56, 0x1a, 0x3f, 0x85, 0x1f, 0xab, 0xc7, 0x1e, 0xc5, 0x83, 0x48, 0xfb, 0x45, + 0x24, 0x7f, 0x2c, 0x6d, 0x45, 0xf4, 0xb6, 0x33, 0xfb, 0x86, 0x79, 0x33, 0x3c, 0x74, 0xe6, 0xa7, + 0x63, 0x60, 0x82, 0x72, 0x36, 0x4d, 0x9f, 0xad, 0x15, 0xb0, 0x22, 0x1a, 0x84, 0x92, 0x44, 0x14, + 0x98, 0xb4, 0x02, 0x60, 0x20, 0xa8, 0x30, 0xe3, 0x84, 0x4b, 0x8e, 0x8d, 0x75, 0x85, 0xb9, 0x02, + 0xe6, 0x9a, 0xe2, 0x70, 0x3f, 0xe0, 0x01, 0xcf, 0xc7, 0xad, 0xec, 0x55, 0x28, 0x8d, 0x09, 0x6a, + 0xdf, 0x82, 0xeb, 0x43, 0x32, 0xa2, 0x01, 0x83, 0xe4, 0x9a, 0xc9, 0x24, 0xc5, 0xa7, 0xa8, 0x2d, + 0xe0, 0x71, 0x02, 0x8c, 0x40, 0xe2, 0xb8, 0xbe, 0x9f, 0x80, 0x10, 0x9a, 0xda, 0x51, 0xbb, 0x75, + 0xbb, 0xb5, 0xfa, 0xb8, 0x2a, 0x78, 0x7c, 0x84, 0xea, 0x85, 0x83, 0x43, 0x7d, 0xed, 0x5f, 0x3e, + 0xf4, 0xbf, 0x20, 0x86, 0x3e, 0x3e, 0x40, 0xb5, 0x10, 0xb2, 0x25, 0xb4, 0x4a, 0x47, 0xed, 0x56, + 0xed, 0x12, 0x19, 0x6f, 0x2a, 0xda, 0xb9, 0x29, 0x22, 0x8c, 0xa4, 0x2b, 0x01, 0x3f, 0xa0, 0x36, + 0x71, 0x19, 0x67, 0x94, 0xb8, 0x91, 0x53, 0xc8, 0x33, 0xcb, 0x4a, 0xb7, 0x71, 0xde, 0x33, 0x7f, + 0x4f, 0x67, 0x0e, 0xbe, 0xc4, 0x83, 0x1c, 0xf7, 0xab, 0xb3, 0xf7, 0x13, 0xc5, 0x6e, 0x91, 0x4d, + 0x5a, 0x60, 0x0f, 0xed, 0x86, 0x79, 0x5e, 0x47, 0xe4, 0x81, 0x85, 0x56, 0xc9, 0x4d, 0x2e, 0xfe, + 0x62, 0xf2, 0xad, 0xa9, 0xd2, 0xa6, 0x19, 0xae, 0x7d, 0x08, 0xe3, 0x0e, 0xed, 0x6d, 0xad, 0x83, + 0x8f, 0x11, 0x4a, 0x78, 0x14, 0xb9, 0x71, 0x9c, 0xb5, 0x54, 0x54, 0x59, 0x2f, 0x99, 0xa1, 0x8f, + 0x0d, 0xd4, 0xa4, 0x1e, 0x71, 0xb6, 0x7b, 0x6c, 0x50, 0x8f, 0x0c, 0xca, 0x2a, 0xfb, 0xf6, 0x6c, + 0xa1, 0xab, 0xf3, 0x85, 0xae, 0x7e, 0x2c, 0x74, 0xf5, 0x65, 0xa9, 0x2b, 0xf3, 0xa5, 0xae, 0xbc, + 0x2e, 0x75, 0xe5, 0xfe, 0x32, 0xa0, 0x32, 0x9c, 0x78, 0x26, 0xe1, 0x63, 0xeb, 0x87, 0xd3, 0x79, + 0xea, 0x59, 0xd3, 0x8d, 0xfb, 0x91, 0x69, 0x0c, 0xc2, 0xab, 0xe5, 0x47, 0xd0, 0xfb, 0x0c, 0x00, + 0x00, 0xff, 0xff, 0xb7, 0x25, 0x65, 0x00, 0x72, 0x02, 0x00, 0x00, } func (m *HeaderSignerEntry) Marshal() (dAtA []byte, err error) { @@ -298,15 +288,6 @@ func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if len(m.HardForkKeys) > 0 { - for iNdEx := len(m.HardForkKeys) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.HardForkKeys[iNdEx]) - copy(dAtA[i:], m.HardForkKeys[iNdEx]) - i = encodeVarintGenesis(dAtA, i, uint64(len(m.HardForkKeys[iNdEx]))) - i-- - dAtA[i] = 0x22 - } - } if len(m.HeaderSigners) > 0 { for iNdEx := len(m.HeaderSigners) - 1; iNdEx >= 0; iNdEx-- { { @@ -424,12 +405,6 @@ func (m *GenesisState) Size() (n int) { n += 1 + l + sovGenesis(uint64(l)) } } - if len(m.HardForkKeys) > 0 { - for _, s := range m.HardForkKeys { - l = len(s) - n += 1 + l + sovGenesis(uint64(l)) - } - } return n } @@ -686,38 +661,6 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field HardForkKeys", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenesis - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenesis - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenesis - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.HardForkKeys = append(m.HardForkKeys, string(dAtA[iNdEx:postIndex])) - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenesis(dAtA[iNdEx:]) diff --git a/x/lightclient/types/keys.go b/x/lightclient/types/keys.go index fe49e6e16..c09497bb2 100644 --- a/x/lightclient/types/keys.go +++ b/x/lightclient/types/keys.go @@ -13,7 +13,7 @@ const ( var ( RollappClientKey = []byte{0x01} canonicalClientKey = []byte{0x04} - HardForkPrefix = []byte{0x05} + _ = []byte{0x05} HeaderSignersPrefixKey = collections.NewPrefix("headerSigners/") ClientHeightToSigner = collections.NewPrefix("clientHeightToSigner/") ) @@ -29,9 +29,3 @@ func CanonicalClientKey(clientID string) []byte { key = append(key, []byte(clientID)...) return key } - -func HardForkKey(rollappID string) []byte { - key := HardForkPrefix - key = append(key, []byte(rollappID)...) - return key -} diff --git a/x/lightclient/types/state.go b/x/lightclient/types/state.go index bf6b5af52..56c8ccd0a 100644 --- a/x/lightclient/types/state.go +++ b/x/lightclient/types/state.go @@ -20,18 +20,26 @@ import ( func CheckCompatibility(ibcState ibctm.ConsensusState, raState RollappState) error { // Check if block descriptor state root matches IBC block header app hash if !bytes.Equal(ibcState.Root.GetHash(), raState.BlockDescriptor.StateRoot) { - return errorsmod.Wrap(ErrStateRootsMismatch, "block descriptor state root does not match tendermint header app hash") + return errorsmod.Wrapf(ErrStateRootMismatch, "ibc state root: %s, block descriptor state root: %s", ibcState.Root, raState.BlockDescriptor.StateRoot) } + // timestamp is optional here to support 2D rollapp upgrade. + if !raState.BlockDescriptor.Timestamp.IsZero() && !ibcState.Timestamp.Equal(raState.BlockDescriptor.Timestamp) { + return errorsmod.Wrapf(ErrTimestampMismatch, "ibc timestamp: %s, block descriptor timestamp: %s", ibcState.Timestamp, raState.BlockDescriptor.Timestamp) + } + if err := compareNextValHash(ibcState, raState); err != nil { + return errorsmod.Wrap(err, "compare next val hash") + } + return nil +} + +func compareNextValHash(ibcState ibctm.ConsensusState, raState RollappState) error { // Check if the nextValidatorHash matches for the sequencer for h+1 block descriptor hash, err := raState.NextBlockSequencer.ValsetHash() if err != nil { - return errors.Join(err, gerrc.ErrInternal.Wrap("val set hash")) + return errors.Join(err, gerrc.ErrInternal.Wrap("next block seq val set hash")) } if !bytes.Equal(ibcState.NextValidatorsHash, hash) { - return errorsmod.Wrap(ErrValidatorHashMismatch, "cons state next validator hash does not match the state info hash for sequencer for h+1") - } - if !raState.BlockDescriptor.Timestamp.IsZero() && !ibcState.Timestamp.Equal(raState.BlockDescriptor.Timestamp) { - return errorsmod.Wrap(ErrTimestampMismatch, "block descriptor timestamp does not match tendermint header timestamp") + return ErrNextValHashMismatch } return nil } diff --git a/x/lightclient/types/state_test.go b/x/lightclient/types/state_test.go index f797c90be..1e43d26aa 100644 --- a/x/lightclient/types/state_test.go +++ b/x/lightclient/types/state_test.go @@ -53,7 +53,7 @@ func TestCheckCompatibility(t *testing.T) { raState: invalidRootRaState, } }, - err: types.ErrStateRootsMismatch, + err: types.ErrStateRootMismatch, }, { name: "nextValidatorHash does not match the sequencer who submitted the next block descriptor", @@ -65,7 +65,7 @@ func TestCheckCompatibility(t *testing.T) { raState: validRollappState, } }, - err: types.ErrValidatorHashMismatch, + err: types.ErrNextValHashMismatch, }, { name: "timestamps is empty. ignore timestamp check", diff --git a/x/rollapp/keeper/fraud_proposal.go b/x/rollapp/keeper/fraud_proposal.go index c993e27bc..4f2234485 100644 --- a/x/rollapp/keeper/fraud_proposal.go +++ b/x/rollapp/keeper/fraud_proposal.go @@ -57,7 +57,7 @@ func (k Keeper) SubmitRollappFraud(goCtx context.Context, msg *types.MsgRollappF // it will revert the future pending states to the specified height // and increment the revision number // will fail if state already finalized - err := k.HardFork(ctx, msg.RollappId, msg.FraudHeight) + err := k.HardFork(ctx, msg.RollappId, msg.FraudHeight-1) if err != nil { err = errorsmod.Wrap(err, "hard fork") ctx.Logger().Error("Fraud proposal.", err) diff --git a/x/rollapp/keeper/fraud_proposal_test.go b/x/rollapp/keeper/fraud_proposal_test.go index 91bfe6500..68efba3da 100644 --- a/x/rollapp/keeper/fraud_proposal_test.go +++ b/x/rollapp/keeper/fraud_proposal_test.go @@ -69,7 +69,7 @@ func (s *RollappTestSuite) TestSubmitRollappFraud() { } // Force a fork at height 50 - err = s.k().HardFork(s.Ctx, rollappId, 50) + err = s.k().HardFork(s.Ctx, rollappId, 49) s.Require().NoError(err) lastHeight = 50 @@ -81,7 +81,7 @@ func (s *RollappTestSuite) TestSubmitRollappFraud() { } // Force another fork at height 120 - err = s.k().HardFork(s.Ctx, rollappId, 120) + err = s.k().HardFork(s.Ctx, rollappId, 119) s.Require().NoError(err) // assert revision correctness diff --git a/x/rollapp/keeper/hard_fork.go b/x/rollapp/keeper/hard_fork.go index 0bd8ebae0..ceabf4a9a 100644 --- a/x/rollapp/keeper/hard_fork.go +++ b/x/rollapp/keeper/hard_fork.go @@ -13,21 +13,22 @@ import ( ) // HardFork handles the fraud evidence submitted by the user. -func (k Keeper) HardFork(ctx sdk.Context, rollappID string, newRevisionHeight uint64) error { +func (k Keeper) HardFork(ctx sdk.Context, rollappID string, lastValidHeight uint64) error { rollapp, found := k.GetRollapp(ctx, rollappID) if !found { return gerrc.ErrNotFound } - if !k.ForkAllowed(ctx, rollappID, newRevisionHeight-1) { + if !k.ForkAllowed(ctx, rollappID, lastValidHeight) { return gerrc.ErrFailedPrecondition.Wrap("fork not allowed") } - lastValidHeight, err := k.RevertPendingStates(ctx, rollappID, newRevisionHeight) + lastValidHeight, err := k.RevertPendingStates(ctx, rollappID, lastValidHeight+1) if err != nil { return errorsmod.Wrap(err, "revert pending states") } - newRevisionHeight = lastValidHeight + 1 + + newRevisionHeight := lastValidHeight + 1 // update revision number rollapp.BumpRevision(newRevisionHeight) @@ -38,7 +39,7 @@ func (k Keeper) HardFork(ctx sdk.Context, rollappID string, newRevisionHeight ui k.SetRollapp(ctx, rollapp) // handle the sequencers, clean delayed packets, handle light client - err = k.hooks.OnHardFork(ctx, rollappID, newRevisionHeight) + err = k.hooks.OnHardFork(ctx, rollappID, lastValidHeight) if err != nil { return errorsmod.Wrap(err, "hard fork callback") } @@ -153,10 +154,10 @@ func (k Keeper) UpdateLastStateInfo(ctx sdk.Context, stateInfo *types.StateInfo, func (k Keeper) HardForkToLatest(ctx sdk.Context, rollappID string) error { lastBatch, ok := k.GetLatestStateInfo(ctx, rollappID) if !ok { - return errorsmod.Wrapf(gerrc.ErrFailedPrecondition, "can't hard fork, no state info found") + return errorsmod.Wrapf(gerrc.ErrFailedPrecondition, "no last batch") } // we invoke a hard fork on the last posted batch without reverting any states - return k.HardFork(ctx, rollappID, lastBatch.GetLatestHeight()+1) + return k.HardFork(ctx, rollappID, lastBatch.GetLatestHeight()) } func mapKeysToSlice(m map[string]struct{}) []string { @@ -199,6 +200,12 @@ func (k Keeper) pruneFinalizationsAbove(ctx sdk.Context, rollappID string, lastS } return nil } +// is the first of the latest fork? +func (k Keeper) IsFirstHeightOfLatestFork(ctx sdk.Context, rollappId string, revision, height uint64) bool { + rollapp := k.MustGetRollapp(ctx, rollappId) + latest := rollapp.LatestRevision().Number + return rollapp.DidFork() && rollapp.IsRevisionStartHeight(revision, height) && revision == latest +} // is forking to the latest height going to violate assumptions? func (k Keeper) ForkLatestAllowed(ctx sdk.Context, rollapp string) bool { diff --git a/x/rollapp/keeper/hard_fork_test.go b/x/rollapp/keeper/hard_fork_test.go index 9e52856f7..8c8390a20 100644 --- a/x/rollapp/keeper/hard_fork_test.go +++ b/x/rollapp/keeper/hard_fork_test.go @@ -82,7 +82,7 @@ func (s *RollappTestSuite) TestHardFork() { // finalize some of the states s.k().FinalizeRollappStates(s.Ctx.WithBlockHeight(int64(initialHeight + tc.statesFinalized))) - err = s.k().HardFork(s.Ctx, rollappId, tc.fraudHeight) + err = s.k().HardFork(s.Ctx, rollappId, tc.fraudHeight-1) if tc.expectError { s.Require().Error(err) } else { @@ -102,7 +102,7 @@ func (s *RollappTestSuite) TestHardFork_InvalidRollapp() { _, err := s.PostStateUpdate(*ctx, rollapp, proposer, 1, uint64(10)) s.Require().Nil(err) - err = s.k().HardFork(*ctx, "invalidRollapp", 2) + err = s.k().HardFork(*ctx, "invalidRollapp", 1) s.Require().Error(err) } @@ -134,7 +134,7 @@ func (s *RollappTestSuite) TestHardFork_AlreadyFinalized() { s.Require().Nil(err) s.Require().Equal(common.Status_FINALIZED, stateInfo.Status) - err = s.k().HardFork(*ctx, rollapp, 2) + err = s.k().HardFork(*ctx, rollapp, 1) s.Require().NotNil(err) } diff --git a/x/rollapp/keeper/msg_server_update_state.go b/x/rollapp/keeper/msg_server_update_state.go index f9f423ee5..546107445 100644 --- a/x/rollapp/keeper/msg_server_update_state.go +++ b/x/rollapp/keeper/msg_server_update_state.go @@ -112,9 +112,13 @@ func (k msgServer) UpdateState(goCtx context.Context, msg *types.MsgUpdateState) k.SetStateInfo(ctx, *stateInfo) // call the after-update-state hook - // currently used by `x/lightclient` to validate the state update in regards to the light client + // currently used by `x/lightclient` to validate the state update against consensus states // x/sequencer will complete the rotation if needed - err = k.hooks.AfterUpdateState(ctx, msg.RollappId, stateInfo) + err = k.hooks.AfterUpdateState(ctx, &types.StateInfoMeta{ + StateInfo: *stateInfo, + Revision: msg.RollappRevision, + Rollapp: msg.RollappId, + }) if err != nil { return nil, errorsmod.Wrap(err, "hook: after update state") } diff --git a/x/rollapp/types/hooks.go b/x/rollapp/types/hooks.go index 8893b5fd3..da5ea8c25 100644 --- a/x/rollapp/types/hooks.go +++ b/x/rollapp/types/hooks.go @@ -12,7 +12,7 @@ import ( // RollappHooks event hooks for rollapp object (noalias) type RollappHooks interface { BeforeUpdateState(ctx sdk.Context, seqAddr, rollappId string, lastStateUpdateBySequencer bool) error // Must be called when a rollapp's state changes - AfterUpdateState(ctx sdk.Context, rollappID string, stateInfo *StateInfo) error // Must be called when a rollapp's state changes + AfterUpdateState(ctx sdk.Context, stateInfo *StateInfoMeta) error // Must be called when a rollapp's state changes AfterStateFinalized(ctx sdk.Context, rollappID string, stateInfo *StateInfo) error // Must be called when a rollapp's state changes RollappCreated(ctx sdk.Context, rollappID, alias string, creator sdk.AccAddress) error AfterTransfersEnabled(ctx sdk.Context, rollappID, rollappIBCDenom string) error @@ -40,9 +40,9 @@ func (h MultiRollappHooks) BeforeUpdateState(ctx sdk.Context, seqAddr, rollappId return nil } -func (h MultiRollappHooks) AfterUpdateState(ctx sdk.Context, rollappID string, stateInfo *StateInfo) error { +func (h MultiRollappHooks) AfterUpdateState(ctx sdk.Context, stateInfo *StateInfoMeta) error { for i := range h { - err := h[i].AfterUpdateState(ctx, rollappID, stateInfo) + err := h[i].AfterUpdateState(ctx, stateInfo) if err != nil { return err } @@ -60,9 +60,9 @@ func (h MultiRollappHooks) AfterStateFinalized(ctx sdk.Context, rollappID string return nil } -func (h MultiRollappHooks) OnHardFork(ctx sdk.Context, rollappID string, newRevisionHeight uint64) error { +func (h MultiRollappHooks) OnHardFork(ctx sdk.Context, rollappID string, lastValidHeight uint64) error { for i := range h { - err := h[i].OnHardFork(ctx, rollappID, newRevisionHeight) + err := h[i].OnHardFork(ctx, rollappID, lastValidHeight) if err != nil { return err } @@ -100,7 +100,7 @@ func (StubRollappCreatedHooks) RollappCreated(sdk.Context, string, string, sdk.A } func (StubRollappCreatedHooks) BeforeUpdateState(sdk.Context, string, string, bool) error { return nil } -func (StubRollappCreatedHooks) AfterUpdateState(sdk.Context, string, *StateInfo) error { +func (StubRollappCreatedHooks) AfterUpdateState(sdk.Context, *StateInfoMeta) error { return nil } func (StubRollappCreatedHooks) OnHardFork(sdk.Context, string, uint64) error { return nil } diff --git a/x/rollapp/types/rollapp.go b/x/rollapp/types/rollapp.go index 1abc075ff..5f257a6ef 100644 --- a/x/rollapp/types/rollapp.go +++ b/x/rollapp/types/rollapp.go @@ -9,7 +9,6 @@ import ( errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/dymensionxyz/dymension/v3/testutil/sample" ) @@ -129,6 +128,7 @@ func (r Rollapp) LatestRevision() Revision { return r.Revisions[len(r.Revisions)-1] } +// TODO: rollapp type method should be more robust https://github.com/dymensionxyz/dymension/issues/1596 func (r Rollapp) GetRevisionForHeight(h uint64) Revision { for i := len(r.Revisions) - 1; i >= 0; i-- { if r.Revisions[i].StartHeight <= h { @@ -138,6 +138,15 @@ func (r Rollapp) GetRevisionForHeight(h uint64) Revision { return Revision{} } +func (r Rollapp) IsRevisionStartHeight(revision, height uint64) bool { + rev := r.GetRevisionForHeight(height) + return rev.Number == revision && rev.StartHeight == height +} + +func (r Rollapp) DidFork() bool { + return 1 < len(r.Revisions) +} + func (r *Rollapp) BumpRevision(nextRevisionStartHeight uint64) { r.Revisions = append(r.Revisions, Revision{ Number: r.LatestRevision().Number + 1, diff --git a/x/rollapp/types/state_info.go b/x/rollapp/types/state_info.go index 535a28a25..807dede92 100644 --- a/x/rollapp/types/state_info.go +++ b/x/rollapp/types/state_info.go @@ -79,3 +79,9 @@ func (s *StateInfo) GetEvents() []sdk.Attribute { } return eventAttributes } + +type StateInfoMeta struct { + StateInfo + Revision uint64 + Rollapp string +} diff --git a/x/sequencer/keeper/hook_listener.go b/x/sequencer/keeper/hook_listener.go index ce47dc3c8..97ef5d419 100644 --- a/x/sequencer/keeper/hook_listener.go +++ b/x/sequencer/keeper/hook_listener.go @@ -36,14 +36,14 @@ func (hook rollappHook) BeforeUpdateState(ctx sdk.Context, seqAddr, rollappId st } // AfterUpdateState checks if rotation is completed and the nextProposer is changed -func (hook rollappHook) AfterUpdateState(ctx sdk.Context, rollappID string, stateInfo *rollapptypes.StateInfo) error { +func (hook rollappHook) AfterUpdateState(ctx sdk.Context, stateInfo *rollapptypes.StateInfoMeta) error { // no proposer changed - no op if stateInfo.Sequencer == stateInfo.NextProposer { return nil } // handle proposer rotation completion - proposer := hook.k.GetProposer(ctx, rollappID) + proposer := hook.k.GetProposer(ctx, stateInfo.Rollapp) err := hook.k.OnProposerLastBlock(ctx, proposer) if err != nil { return errorsmod.Wrap(err, "on proposer last block")