diff --git a/app/fork_test.go b/app/fork_test.go new file mode 100644 index 00000000..a510f84a --- /dev/null +++ b/app/fork_test.go @@ -0,0 +1,152 @@ +package app + +import ( + "fmt" + "testing" + "time" + + "cosmossdk.io/math" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/require" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var oneEnternityLater = time.Date(9999, 9, 9, 9, 9, 9, 9, time.UTC) + +func TestFork(t *testing.T) { + realio := Setup(false, nil) + + ctx := realio.BaseApp.NewContext(false, tmproto.Header{Height: int64(ForkHeight)}) + stakingKeeper := realio.StakingKeeper + + timeKey := time.Date(2024, 4, 1, 1, 1, 1, 1, time.UTC) + + duplicativeUnbondingDelegation := stakingtypes.UnbondingDelegation{ + DelegatorAddress: "test_del_1", + ValidatorAddress: "test_val_1", + Entries: []stakingtypes.UnbondingDelegationEntry{ + stakingtypes.NewUnbondingDelegationEntry(int64(ForkHeight), timeKey, math.OneInt()), + }, + } + + stakingKeeper.InsertUBDQueue(ctx, duplicativeUnbondingDelegation, timeKey) + stakingKeeper.InsertUBDQueue(ctx, duplicativeUnbondingDelegation, timeKey) + + duplicativeRedelegation := stakingtypes.Redelegation{ + DelegatorAddress: "test_del_1", + ValidatorSrcAddress: "test_val_1", + ValidatorDstAddress: "test_val_2", + Entries: []stakingtypes.RedelegationEntry{ + stakingtypes.NewRedelegationEntry(int64(ForkHeight), timeKey, math.OneInt(), sdk.OneDec()), + }, + } + stakingKeeper.InsertRedelegationQueue(ctx, duplicativeRedelegation, timeKey) + stakingKeeper.InsertRedelegationQueue(ctx, duplicativeRedelegation, timeKey) + stakingKeeper.InsertRedelegationQueue(ctx, duplicativeRedelegation, timeKey) + + duplicativeVal := stakingtypes.Validator{ + OperatorAddress: "test_op", + UnbondingHeight: int64(ForkHeight), + UnbondingTime: timeKey, + } + + stakingKeeper.InsertUnbondingValidatorQueue(ctx, duplicativeVal) + stakingKeeper.InsertUnbondingValidatorQueue(ctx, duplicativeVal) + + require.True(t, checkDuplicateUBDQueue(ctx, *realio)) + require.True(t, checkDuplicateRelegationQueue(ctx, *realio)) + require.True(t, checkDuplicateValQueue(ctx, *realio)) + + realio.ScheduleForkUpgrade(ctx) + + require.False(t, checkDuplicateUBDQueue(ctx, *realio)) + require.False(t, checkDuplicateRelegationQueue(ctx, *realio)) + require.False(t, checkDuplicateValQueue(ctx, *realio)) + + dvPairs := stakingKeeper.GetUBDQueueTimeSlice(ctx, timeKey) + require.Equal(t, dvPairs[0].DelegatorAddress, duplicativeUnbondingDelegation.DelegatorAddress) + require.Equal(t, dvPairs[0].ValidatorAddress, duplicativeUnbondingDelegation.ValidatorAddress) + + triplets := stakingKeeper.GetRedelegationQueueTimeSlice(ctx, timeKey) + require.Equal(t, triplets[0].DelegatorAddress, duplicativeRedelegation.DelegatorAddress) + require.Equal(t, triplets[0].ValidatorDstAddress, duplicativeRedelegation.ValidatorDstAddress) + require.Equal(t, triplets[0].ValidatorSrcAddress, duplicativeRedelegation.ValidatorSrcAddress) + + vals := stakingKeeper.GetUnbondingValidators(ctx, timeKey, int64(ForkHeight)) + require.Equal(t, vals[0], duplicativeVal.OperatorAddress) + +} + +func checkDuplicateUBDQueue(ctx sdk.Context, realio RealioNetwork) bool { + ubdIter := realio.StakingKeeper.UBDQueueIterator(ctx, oneEnternityLater) + defer ubdIter.Close() + + for ; ubdIter.Valid(); ubdIter.Next() { + timeslice := stakingtypes.DVPairs{} + value := ubdIter.Value() + realio.appCodec.MustUnmarshal(value, ×lice) + if checkDuplicateUBD(timeslice.Pairs) { + return true + } + } + return false +} + +func checkDuplicateUBD(eles []stakingtypes.DVPair) bool { + unique_eles := map[string]bool{} + for _, ele := range eles { + unique_eles[ele.String()] = true + } + fmt.Println(eles, "eles") + + return len(unique_eles) != len(eles) +} + +func checkDuplicateRelegationQueue(ctx sdk.Context, realio RealioNetwork) bool { + redeIter := realio.StakingKeeper.RedelegationQueueIterator(ctx, oneEnternityLater) + defer redeIter.Close() + + for ; redeIter.Valid(); redeIter.Next() { + timeslice := stakingtypes.DVVTriplets{} + value := redeIter.Value() + realio.appCodec.MustUnmarshal(value, ×lice) + if checkDuplicateRedelegation(timeslice.Triplets) { + return true + } + } + return false +} + +func checkDuplicateRedelegation(eles []stakingtypes.DVVTriplet) bool { + unique_eles := map[string]bool{} + for _, ele := range eles { + unique_eles[ele.String()] = true + } + + return len(unique_eles) != len(eles) +} + +func checkDuplicateValQueue(ctx sdk.Context, realio RealioNetwork) bool { + valsIter := realio.StakingKeeper.ValidatorQueueIterator(ctx, oneEnternityLater, 9999) + defer valsIter.Close() + + for ; valsIter.Valid(); valsIter.Next() { + timeslice := stakingtypes.ValAddresses{} + value := valsIter.Value() + realio.appCodec.MustUnmarshal(value, ×lice) + if checkDuplicateValAddr(timeslice.Addresses) { + return true + } + } + return false +} +func checkDuplicateValAddr(eles []string) bool { + unique_eles := map[string]bool{} + for _, ele := range eles { + unique_eles[ele] = true + } + + return len(unique_eles) != len(eles) +} diff --git a/app/forks.go b/app/forks.go index 52abf2d8..865c5f48 100644 --- a/app/forks.go +++ b/app/forks.go @@ -1,9 +1,15 @@ package app import ( + "sort" + "time" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) +var ForkHeight = 5989487 + // ScheduleForkUpgrade executes any necessary fork logic for based upon the current // block height and chain ID (mainnet or testnet). It sets an upgrade plan once // the chain reaches the pre-defined upgrade height. @@ -12,7 +18,14 @@ import ( // // 1. Release a non-breaking patch version so that the chain can set the scheduled upgrade plan at upgrade-height. // 2. Release the software defined in the upgrade-info -func (app *RealioNetwork) ScheduleForkUpgrade(_ sdk.Context) { +func (app *RealioNetwork) ScheduleForkUpgrade(ctx sdk.Context) { + if ctx.BlockHeight() == 5989487 { + + // remove duplicate UnbondingQueueKey + removeDuplicateValueUnbondingQueueKey(app, ctx) + removeDuplicateValueRedelegationQueueKey(app, ctx) + removeDuplicateUnbondingValidator(app, ctx) + } // NOTE: there are no testnet forks for the existing versions // if !types.IsMainnet(ctx.ChainID()) { // return @@ -43,3 +56,118 @@ func (app *RealioNetwork) ScheduleForkUpgrade(_ sdk.Context) { // ) //} } + +func removeDuplicateValueRedelegationQueueKey(app *RealioNetwork, ctx sdk.Context) { + // Get Staking keeper, codec and staking store + sk := app.StakingKeeper + cdc := app.AppCodec() + store := ctx.KVStore(app.keys[stakingtypes.ModuleName]) + + // remove duplicate UnbondingQueueKey + ubdTime := sk.UnbondingTime(ctx) + currTime := ctx.BlockTime() + + redelegationTimesliceIterator := sk.RedelegationQueueIterator(ctx, currTime.Add(ubdTime)) // make sure to iterate all queue + defer redelegationTimesliceIterator.Close() + + for ; redelegationTimesliceIterator.Valid(); redelegationTimesliceIterator.Next() { + timeslice := stakingtypes.DVVTriplets{} + value := redelegationTimesliceIterator.Value() + cdc.MustUnmarshal(value, ×lice) + + triplets := removeDuplicateDVVTriplets(timeslice.Triplets) + bz := cdc.MustMarshal(&stakingtypes.DVVTriplets{Triplets: triplets}) + + store.Set(redelegationTimesliceIterator.Key(), bz) + } + +} + +func removeDuplicateDVVTriplets(triplets []stakingtypes.DVVTriplet) []stakingtypes.DVVTriplet { + var list []stakingtypes.DVVTriplet + for _, item := range triplets { + if !containsDVVTriplets(list, item) { + list = append(list, item) + } + } + return list +} + +func containsDVVTriplets(s []stakingtypes.DVVTriplet, e stakingtypes.DVVTriplet) bool { + for _, a := range s { + if a.DelegatorAddress == e.DelegatorAddress && + a.ValidatorSrcAddress == e.ValidatorSrcAddress && + a.ValidatorDstAddress == e.ValidatorDstAddress { + return true + } + } + return false +} + +func removeDuplicateUnbondingValidator(app *RealioNetwork, ctx sdk.Context) { + valIter := app.StakingKeeper.ValidatorQueueIterator(ctx, time.Date(9999, 9, 9, 9, 9, 9, 9, time.UTC), 99999999999999) + defer valIter.Close() + + for ; valIter.Valid(); valIter.Next() { + addrs := stakingtypes.ValAddresses{} + app.appCodec.MustUnmarshal(valIter.Value(), &addrs) + + vals := map[string]bool{} + for _, valAddr := range addrs.Addresses { + vals[valAddr] = true + } + + unique_addrs := []string{} + for valAddr, _ := range vals { + unique_addrs = append(unique_addrs, valAddr) + } + sort.Strings(unique_addrs) + + ctx.KVStore(app.GetKey(stakingtypes.StoreKey)).Set(valIter.Key(), app.appCodec.MustMarshal(&stakingtypes.ValAddresses{Addresses: unique_addrs})) + } +} + +func removeDuplicateValueUnbondingQueueKey(app *RealioNetwork, ctx sdk.Context) { + // Get Staking keeper, codec and staking store + sk := app.StakingKeeper + cdc := app.AppCodec() + store := ctx.KVStore(app.keys[stakingtypes.ModuleName]) + + // remove duplicate UnbondingQueueKey + ubdTime := sk.UnbondingTime(ctx) + currTime := ctx.BlockTime() + + unbondingTimesliceIterator := sk.UBDQueueIterator(ctx, currTime.Add(ubdTime)) // make sure to iterate all queue + defer unbondingTimesliceIterator.Close() + + for ; unbondingTimesliceIterator.Valid(); unbondingTimesliceIterator.Next() { + timeslice := stakingtypes.DVPairs{} + value := unbondingTimesliceIterator.Value() + cdc.MustUnmarshal(value, ×lice) + + dvPairs := removeDuplicatesDVPairs(timeslice.Pairs) + bz := cdc.MustMarshal(&stakingtypes.DVPairs{Pairs: dvPairs}) + + store.Set(unbondingTimesliceIterator.Key(), bz) + } +} + +func removeDuplicatesDVPairs(dvPairs []stakingtypes.DVPair) []stakingtypes.DVPair { + var list []stakingtypes.DVPair + for _, item := range dvPairs { + if !containsDVPairs(list, item) { + list = append(list, item) + } + } + return list +} + +func containsDVPairs(s []stakingtypes.DVPair, e stakingtypes.DVPair) bool { + for _, a := range s { + if a.DelegatorAddress == e.DelegatorAddress && + a.ValidatorAddress == e.ValidatorAddress { + return true + } + } + return false +}