diff --git a/app/upgrades/v4/rollapp_state_info_next_proposer.go b/app/upgrades/v4/rollapp_state_info_next_proposer.go new file mode 100644 index 000000000..fedb2f458 --- /dev/null +++ b/app/upgrades/v4/rollapp_state_info_next_proposer.go @@ -0,0 +1,46 @@ +package v4 + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/dymensionxyz/gerr-cosmos/gerrc" + + rollappkeeper "github.com/dymensionxyz/dymension/v3/x/rollapp/keeper" + sequencerkeeper "github.com/dymensionxyz/dymension/v3/x/sequencer/keeper" +) + +// migrateRollappStateInfoNextProposer should be called +// - after migrateSequencerIndices: it uses the proposer index +// - before migrateRollappFinalizationQueue: it uses the old queue to reduce store reads +func migrateRollappStateInfoNextProposer(ctx sdk.Context, rk *rollappkeeper.Keeper, sk *sequencerkeeper.Keeper) error { + // use old queue since it requires less store reads + q := rk.GetAllBlockHeightToFinalizationQueue(ctx) + + // map rollappID to proposer address to avoid multiple reads from the sequencer keeper + // safe since the map is used only for existence checks + rollappProposers := make(map[string]string) + + for _, queue := range q { + for _, stateInfoIndex := range queue.FinalizationQueue { + // get current proposer address + proposer, ok := rollappProposers[stateInfoIndex.RollappId] + if !ok { + p := sk.GetProposer(ctx, stateInfoIndex.RollappId) + if p.Sentinel() { + return gerrc.ErrInternal.Wrapf("proposer cannot be sentinel: rollappID %s", stateInfoIndex.RollappId) + } + rollappProposers[stateInfoIndex.RollappId] = p.Address + proposer = p.Address + } + + // update state info + stateInfo, found := rk.GetStateInfo(ctx, stateInfoIndex.RollappId, stateInfoIndex.Index) + if !found { + return gerrc.ErrInternal.Wrapf("state info not found: rollappID %s, index %d", stateInfoIndex.RollappId, stateInfoIndex.Index) + } + stateInfo.NextProposer = proposer + rk.SetStateInfo(ctx, stateInfo) + } + } + + return nil +} diff --git a/app/upgrades/v4/upgrade.go b/app/upgrades/v4/upgrade.go index 6d1648a31..f5047c990 100644 --- a/app/upgrades/v4/upgrade.go +++ b/app/upgrades/v4/upgrade.go @@ -96,6 +96,10 @@ func CreateUpgradeHandler( return nil, err } + if err := migrateRollappStateInfoNextProposer(ctx, keepers.RollappKeeper, keepers.SequencerKeeper); err != nil { + return nil, err + } + if err := migrateRollappFinalizationQueue(ctx, keepers.RollappKeeper); err != nil { return nil, err } diff --git a/app/upgrades/v4/upgrade_test.go b/app/upgrades/v4/upgrade_test.go index e03b4978e..3001e4b42 100644 --- a/app/upgrades/v4/upgrade_test.go +++ b/app/upgrades/v4/upgrade_test.go @@ -146,6 +146,8 @@ func (s *UpgradeTestSuite) TestUpgrade() { // Check rollapp finalization queue s.validateRollappFinalizationQueue() + s.validateNonFinalizedStateInfos() + s.validateStreamerMigration() return @@ -333,51 +335,67 @@ func (s *UpgradeTestSuite) validateRollappFinalizationQueue() { { CreationHeight: 1, FinalizationQueue: []rollapptypes.StateInfoIndex{ - {RollappId: "rollapp1", Index: 1}, - {RollappId: "rollapp1", Index: 2}, + {RollappId: rollappIDFromIdx(1), Index: 1}, + {RollappId: rollappIDFromIdx(1), Index: 2}, }, - RollappId: "rollapp1", + RollappId: rollappIDFromIdx(1), }, { CreationHeight: 1, FinalizationQueue: []rollapptypes.StateInfoIndex{ - {RollappId: "rollapp2", Index: 1}, - {RollappId: "rollapp2", Index: 2}, + {RollappId: rollappIDFromIdx(2), Index: 1}, + {RollappId: rollappIDFromIdx(2), Index: 2}, }, - RollappId: "rollapp2", + RollappId: rollappIDFromIdx(2), }, { CreationHeight: 1, FinalizationQueue: []rollapptypes.StateInfoIndex{ - {RollappId: "rollapp3", Index: 1}, + {RollappId: rollappIDFromIdx(3), Index: 1}, }, - RollappId: "rollapp3", + RollappId: rollappIDFromIdx(3), }, { CreationHeight: 2, FinalizationQueue: []rollapptypes.StateInfoIndex{ - {RollappId: "rollapp1", Index: 3}, + {RollappId: rollappIDFromIdx(1), Index: 3}, }, - RollappId: "rollapp1", + RollappId: rollappIDFromIdx(1), }, { CreationHeight: 2, FinalizationQueue: []rollapptypes.StateInfoIndex{ - {RollappId: "rollapp3", Index: 2}, + {RollappId: rollappIDFromIdx(3), Index: 2}, }, - RollappId: "rollapp3", + RollappId: rollappIDFromIdx(3), }, { CreationHeight: 3, FinalizationQueue: []rollapptypes.StateInfoIndex{ - {RollappId: "rollapp3", Index: 3}, - {RollappId: "rollapp3", Index: 4}, + {RollappId: rollappIDFromIdx(3), Index: 3}, + {RollappId: rollappIDFromIdx(3), Index: 4}, }, - RollappId: "rollapp3", + RollappId: rollappIDFromIdx(3), }, }, queue) } +func (s *UpgradeTestSuite) validateNonFinalizedStateInfos() { + queue, err := s.App.RollappKeeper.GetEntireFinalizationQueue(s.Ctx) + s.Require().NoError(err) + + for _, q := range queue { + proposer := s.App.SequencerKeeper.GetProposer(s.Ctx, q.RollappId) + for _, stateInfoIndex := range q.FinalizationQueue { + stateInfo, found := s.App.RollappKeeper.GetStateInfo(s.Ctx, stateInfoIndex.RollappId, stateInfoIndex.Index) + s.Require().True(found) + + // Verify that all non-finalized state infos contain the correct proposer (the same that's set in x/sequencer) + s.Require().Equal(proposer.Address, stateInfo.NextProposer) + } + } +} + func (s *UpgradeTestSuite) seedAndStoreRollapps(numRollapps int) { for _, rollapp := range s.seedRollapps(numRollapps) { s.App.RollappKeeper.SetRollapp(s.Ctx, rollapp) @@ -448,27 +466,27 @@ func (s *UpgradeTestSuite) seedRollappFinalizationQueue() { q1 := rollapptypes.BlockHeightToFinalizationQueue{ CreationHeight: 1, FinalizationQueue: []rollapptypes.StateInfoIndex{ - {RollappId: "rollapp1", Index: 1}, - {RollappId: "rollapp1", Index: 2}, - {RollappId: "rollapp2", Index: 1}, - {RollappId: "rollapp2", Index: 2}, - {RollappId: "rollapp3", Index: 1}, + {RollappId: rollappIDFromIdx(1), Index: 1}, + {RollappId: rollappIDFromIdx(1), Index: 2}, + {RollappId: rollappIDFromIdx(2), Index: 1}, + {RollappId: rollappIDFromIdx(2), Index: 2}, + {RollappId: rollappIDFromIdx(3), Index: 1}, }, RollappId: "", } q2 := rollapptypes.BlockHeightToFinalizationQueue{ CreationHeight: 2, FinalizationQueue: []rollapptypes.StateInfoIndex{ - {RollappId: "rollapp1", Index: 3}, - {RollappId: "rollapp3", Index: 2}, + {RollappId: rollappIDFromIdx(1), Index: 3}, + {RollappId: rollappIDFromIdx(3), Index: 2}, }, RollappId: "", } q3 := rollapptypes.BlockHeightToFinalizationQueue{ CreationHeight: 3, FinalizationQueue: []rollapptypes.StateInfoIndex{ - {RollappId: "rollapp3", Index: 3}, - {RollappId: "rollapp3", Index: 4}, + {RollappId: rollappIDFromIdx(3), Index: 3}, + {RollappId: rollappIDFromIdx(3), Index: 4}, }, RollappId: "", } @@ -476,18 +494,43 @@ func (s *UpgradeTestSuite) seedRollappFinalizationQueue() { s.App.RollappKeeper.SetBlockHeightToFinalizationQueue(s.Ctx, q1) s.App.RollappKeeper.SetBlockHeightToFinalizationQueue(s.Ctx, q2) s.App.RollappKeeper.SetBlockHeightToFinalizationQueue(s.Ctx, q3) + + stateInfos := []rollapptypes.StateInfo{ + generateStateInfo(1, 1), + generateStateInfo(1, 2), + generateStateInfo(1, 3), + generateStateInfo(2, 1), + generateStateInfo(2, 2), + generateStateInfo(3, 1), + generateStateInfo(3, 2), + generateStateInfo(3, 3), + generateStateInfo(3, 4), + } + + for _, stateInfo := range stateInfos { + s.App.RollappKeeper.SetStateInfo(s.Ctx, stateInfo) + } +} + +func generateStateInfo(rollappIdx, stateIdx int) rollapptypes.StateInfo { + return rollapptypes.StateInfo{ + StateInfoIndex: rollapptypes.StateInfoIndex{ + RollappId: rollappIDFromIdx(rollappIdx), + Index: uint64(stateIdx), + }, + } } func TestReformatFinalizationQueue(t *testing.T) { q := rollapptypes.BlockHeightToFinalizationQueue{ CreationHeight: 1, FinalizationQueue: []rollapptypes.StateInfoIndex{ - {RollappId: "rollapp1", Index: 1}, - {RollappId: "rollapp1", Index: 2}, - {RollappId: "rollapp1", Index: 3}, - {RollappId: "rollapp2", Index: 1}, - {RollappId: "rollapp2", Index: 2}, - {RollappId: "rollapp3", Index: 1}, + {RollappId: rollappIDFromIdx(1), Index: 1}, + {RollappId: rollappIDFromIdx(1), Index: 2}, + {RollappId: rollappIDFromIdx(1), Index: 3}, + {RollappId: rollappIDFromIdx(2), Index: 1}, + {RollappId: rollappIDFromIdx(2), Index: 2}, + {RollappId: rollappIDFromIdx(3), Index: 1}, }, RollappId: "", // empty for old-style queues } @@ -498,26 +541,26 @@ func TestReformatFinalizationQueue(t *testing.T) { { CreationHeight: 1, FinalizationQueue: []rollapptypes.StateInfoIndex{ - {RollappId: "rollapp1", Index: 1}, - {RollappId: "rollapp1", Index: 2}, - {RollappId: "rollapp1", Index: 3}, + {RollappId: rollappIDFromIdx(1), Index: 1}, + {RollappId: rollappIDFromIdx(1), Index: 2}, + {RollappId: rollappIDFromIdx(1), Index: 3}, }, - RollappId: "rollapp1", + RollappId: rollappIDFromIdx(1), }, { CreationHeight: 1, FinalizationQueue: []rollapptypes.StateInfoIndex{ - {RollappId: "rollapp2", Index: 1}, - {RollappId: "rollapp2", Index: 2}, + {RollappId: rollappIDFromIdx(2), Index: 1}, + {RollappId: rollappIDFromIdx(2), Index: 2}, }, - RollappId: "rollapp2", + RollappId: rollappIDFromIdx(2), }, { CreationHeight: 1, FinalizationQueue: []rollapptypes.StateInfoIndex{ - {RollappId: "rollapp3", Index: 1}, + {RollappId: rollappIDFromIdx(3), Index: 1}, }, - RollappId: "rollapp3", + RollappId: rollappIDFromIdx(3), }, }, newQueues) } diff --git a/proto/dymensionxyz/dymension/rollapp/state_info.proto b/proto/dymensionxyz/dymension/rollapp/state_info.proto index 4352368e6..037381f67 100644 --- a/proto/dymensionxyz/dymension/rollapp/state_info.proto +++ b/proto/dymensionxyz/dymension/rollapp/state_info.proto @@ -54,7 +54,8 @@ message StateInfo { (gogoproto.moretags) = "yaml:\"created_at\"" ]; - // next sequencer is the bech32-encoded address of the next sequencer after the current sequencer + // NextProposer is the bech32-encoded address of the proposer that we expect to see in the next state info. + // Most of the time NextProposer is the current proposer. In case of rotation it is changed to the successor. string nextProposer = 11; } diff --git a/x/rollapp/types/state_info.pb.go b/x/rollapp/types/state_info.pb.go index c68fd6856..716bc59c7 100644 --- a/x/rollapp/types/state_info.pb.go +++ b/x/rollapp/types/state_info.pb.go @@ -112,7 +112,8 @@ type StateInfo struct { BDs BlockDescriptors `protobuf:"bytes,9,opt,name=BDs,proto3" json:"BDs"` // created_at is the timestamp at which the StateInfo was created CreatedAt time.Time `protobuf:"bytes,10,opt,name=created_at,json=createdAt,proto3,stdtime" json:"created_at" yaml:"created_at"` - // next sequencer is the bech32-encoded address of the next sequencer after the current sequencer + // NextProposer is the bech32-encoded address of the proposer that we expect to see in the next state info. + // Most of the time NextProposer is the current proposer. In case of rotation it is changed to the successor. NextProposer string `protobuf:"bytes,11,opt,name=nextProposer,proto3" json:"nextProposer,omitempty"` }