From 9c2a72628619471f2c5d86c40b53bb498a88aec6 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Fri, 23 Jan 2026 18:51:44 +0530 Subject: [PATCH 1/8] feat: added bank, account, distribution module types in expected_keepers.go of uvalidator module --- x/uvalidator/types/expected_keepers.go | 29 ++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/x/uvalidator/types/expected_keepers.go b/x/uvalidator/types/expected_keepers.go index e6b8cb79..aad78806 100644 --- a/x/uvalidator/types/expected_keepers.go +++ b/x/uvalidator/types/expected_keepers.go @@ -3,6 +3,8 @@ package types import ( "context" + "cosmossdk.io/core/address" + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) @@ -11,6 +13,7 @@ import ( type StakingKeeper interface { GetValidator(ctx context.Context, addr sdk.ValAddress) (validator stakingtypes.Validator, err error) GetAllValidators(ctx context.Context) (validators []stakingtypes.Validator, err error) + ValidatorByConsAddr(context.Context, sdk.ConsAddress) (stakingtypes.ValidatorI, error) } // SlashingKeeper defines the expected interface for the slashing module. @@ -23,3 +26,29 @@ type UtssKeeper interface { GetCurrentTssParticipants(ctx context.Context) ([]string, error) HasOngoingTss(ctx context.Context) (bool, error) } + +type AccountKeeper interface { + AddressCodec() address.Codec + GetAccount(ctx context.Context, addr sdk.AccAddress) sdk.AccountI + GetModuleAddress(name string) sdk.AccAddress + GetModuleAccount(ctx context.Context, name string) sdk.ModuleAccountI +} + +// BankKeeper defines the expected interface needed to retrieve account balances. +type BankKeeper interface { + GetAllBalances(ctx context.Context, addr sdk.AccAddress) sdk.Coins + + SpendableCoins(ctx context.Context, addr sdk.AccAddress) sdk.Coins + + SendCoinsFromModuleToModule(ctx context.Context, senderModule, recipientModule string, amt sdk.Coins) error + SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error + SendCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error + + BlockedAddr(addr sdk.AccAddress) bool +} + +type DistributionKeeper interface { + FundCommunityPool(ctx context.Context, amount sdk.Coins, sender sdk.AccAddress) error + GetCommunityTax(ctx context.Context) (math.LegacyDec, error) + AllocateTokensToValidator(ctx context.Context, val stakingtypes.ValidatorI, tokens sdk.DecCoins) error +} From db76c7475cdc395c9662f3072a73a062e458063f Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Fri, 23 Jan 2026 18:52:58 +0530 Subject: [PATCH 2/8] feat: added bank, account, distribution module types in depinject.go --- x/uvalidator/depinject.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/x/uvalidator/depinject.go b/x/uvalidator/depinject.go index e4803687..5d4fe107 100755 --- a/x/uvalidator/depinject.go +++ b/x/uvalidator/depinject.go @@ -43,9 +43,12 @@ type ModuleInputs struct { StoreService store.KVStoreService AddressCodec address.Codec - StakingKeeper stakingkeeper.Keeper - SlashingKeeper slashingkeeper.Keeper - UtssKeeper types.UtssKeeper + BankKeeper types.BankKeeper + AuthKeeper types.AccountKeeper + DistributionKeeper types.DistributionKeeper + StakingKeeper stakingkeeper.Keeper + SlashingKeeper slashingkeeper.Keeper + UtssKeeper types.UtssKeeper } type ModuleOutputs struct { @@ -58,8 +61,8 @@ type ModuleOutputs struct { func ProvideModule(in ModuleInputs) ModuleOutputs { govAddr := authtypes.NewModuleAddress(govtypes.ModuleName).String() - k := keeper.NewKeeper(in.Cdc, in.StoreService, log.NewLogger(os.Stderr), govAddr, in.StakingKeeper, in.SlashingKeeper, in.UtssKeeper) - m := NewAppModule(in.Cdc, k, in.StakingKeeper, in.SlashingKeeper, in.UtssKeeper) + k := keeper.NewKeeper(in.Cdc, in.StoreService, log.NewLogger(os.Stderr), govAddr, in.BankKeeper, in.AuthKeeper, in.DistributionKeeper, in.StakingKeeper, in.SlashingKeeper, in.UtssKeeper) + m := NewAppModule(in.Cdc, k, in.BankKeeper, in.AuthKeeper, in.DistributionKeeper, in.StakingKeeper, in.SlashingKeeper, in.UtssKeeper) return ModuleOutputs{Module: m, Keeper: k, Out: depinject.Out{}} } From b25be126867f81449edbd92653fbeda88a4c6fd7 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Fri, 23 Jan 2026 18:53:53 +0530 Subject: [PATCH 3/8] feat: added bank, account and distribution modules in uvalidator module --- app/app.go | 8 ++++++-- x/uvalidator/keeper/keeper.go | 23 ++++++++++++++------- x/uvalidator/keeper/keeper_test.go | 5 +++-- x/uvalidator/module.go | 33 ++++++++++++++++++++++-------- 4 files changed, 49 insertions(+), 20 deletions(-) diff --git a/app/app.go b/app/app.go index 9e95a7f9..b803deee 100755 --- a/app/app.go +++ b/app/app.go @@ -253,6 +253,7 @@ var maccPerms = map[string][]string{ feemarkettypes.ModuleName: nil, erc20types.ModuleName: {authtypes.Minter, authtypes.Burner}, uexecutortypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + uvalidatortypes.ModuleName: nil, } var ( @@ -750,6 +751,9 @@ func NewChainApp( runtime.NewKVStoreService(keys[uvalidatortypes.StoreKey]), logger, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + app.BankKeeper, + app.AccountKeeper, + app.DistrKeeper, app.StakingKeeper, app.SlashingKeeper, &app.UtssKeeper, @@ -1038,7 +1042,7 @@ func NewChainApp( uexecutor.NewAppModule(appCodec, app.UexecutorKeeper, app.EVMKeeper, app.FeeMarketKeeper, app.BankKeeper, app.AccountKeeper, app.UregistryKeeper, app.UtxverifierKeeper, app.UvalidatorKeeper), utxverifier.NewAppModule(appCodec, app.UtxverifierKeeper, app.UregistryKeeper), uregistry.NewAppModule(appCodec, app.UregistryKeeper, app.EVMKeeper), - uvalidator.NewAppModule(appCodec, app.UvalidatorKeeper, app.StakingKeeper, app.SlashingKeeper, &app.UtssKeeper), + uvalidator.NewAppModule(appCodec, app.UvalidatorKeeper, app.BankKeeper, app.AccountKeeper, app.DistrKeeper, app.StakingKeeper, app.SlashingKeeper, &app.UtssKeeper), utss.NewAppModule(appCodec, app.UtssKeeper, app.UvalidatorKeeper), ) @@ -1070,6 +1074,7 @@ func NewChainApp( feemarkettypes.ModuleName, evmtypes.ModuleName, // NOTE: EVM BeginBlocker must come after FeeMarket BeginBlocker + uvalidatortypes.ModuleName, distrtypes.ModuleName, slashingtypes.ModuleName, evidencetypes.ModuleName, @@ -1088,7 +1093,6 @@ func NewChainApp( uexecutortypes.ModuleName, utxverifiertypes.ModuleName, uregistrytypes.ModuleName, - uvalidatortypes.ModuleName, utsstypes.ModuleName, ) diff --git a/x/uvalidator/keeper/keeper.go b/x/uvalidator/keeper/keeper.go index 65b80430..63b85bf9 100755 --- a/x/uvalidator/keeper/keeper.go +++ b/x/uvalidator/keeper/keeper.go @@ -33,9 +33,12 @@ type Keeper struct { ExpiredBallotIDs collections.KeySet[string] // set of ballot IDs that have expired (not yet pruned) FinalizedBallotIDs collections.KeySet[string] // set of ballot IDs that are PASSED or REJECTED - stakingKeeper types.StakingKeeper - slashingKeeper types.SlashingKeeper - utssKeeper types.UtssKeeper + StakingKeeper types.StakingKeeper + SlashingKeeper types.SlashingKeeper + UtssKeeper types.UtssKeeper + BankKeeper types.BankKeeper + AuthKeeper types.AccountKeeper + DistributionKeeper types.DistributionKeeper authority string hooks types.UValidatorHooks @@ -47,6 +50,9 @@ func NewKeeper( storeService storetypes.KVStoreService, logger log.Logger, authority string, + bankKeeper types.BankKeeper, + authKeeper types.AccountKeeper, + distributionKeeper types.DistributionKeeper, stakingKeeper types.StakingKeeper, slashingKeeper types.SlashingKeeper, utssKeeper types.UtssKeeper, @@ -92,10 +98,13 @@ func NewKeeper( collections.StringKey, ), - authority: authority, - stakingKeeper: stakingKeeper, - slashingKeeper: slashingKeeper, - utssKeeper: utssKeeper, + authority: authority, + StakingKeeper: stakingKeeper, + SlashingKeeper: slashingKeeper, + UtssKeeper: utssKeeper, + BankKeeper: bankKeeper, + AuthKeeper: authKeeper, + DistributionKeeper: distributionKeeper, } return k diff --git a/x/uvalidator/keeper/keeper_test.go b/x/uvalidator/keeper/keeper_test.go index 4a1854ca..4a7c71da 100755 --- a/x/uvalidator/keeper/keeper_test.go +++ b/x/uvalidator/keeper/keeper_test.go @@ -21,6 +21,7 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" mintkeeper "github.com/cosmos/cosmos-sdk/x/mint/keeper" minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" @@ -88,10 +89,10 @@ func SetupTest(t *testing.T) *testFixture { registerBaseSDKModules(logger, f, encCfg, keys, accountAddressCodec, validatorAddressCodec, consensusAddressCodec) // Setup Keeper. - f.k = keeper.NewKeeper(encCfg.Codec, runtime.NewKVStoreService(keys[types.ModuleName]), logger, f.govModAddr, f.stakingKeeper, slashingKeeper.Keeper{}, mockUtssKeeper{}) + f.k = keeper.NewKeeper(encCfg.Codec, runtime.NewKVStoreService(keys[types.ModuleName]), logger, f.govModAddr, f.bankkeeper, f.accountkeeper, distrkeeper.Keeper{}, f.stakingKeeper, slashingKeeper.Keeper{}, mockUtssKeeper{}) f.msgServer = keeper.NewMsgServerImpl(f.k) f.queryServer = keeper.NewQuerier(f.k) - f.appModule = module.NewAppModule(encCfg.Codec, f.k, f.stakingKeeper, slashingKeeper.Keeper{}, mockUtssKeeper{}) + f.appModule = module.NewAppModule(encCfg.Codec, f.k, f.bankkeeper, f.accountkeeper, distrkeeper.Keeper{}, f.stakingKeeper, slashingKeeper.Keeper{}, mockUtssKeeper{}) return f } diff --git a/x/uvalidator/module.go b/x/uvalidator/module.go index 6baaba9f..c24dc10e 100755 --- a/x/uvalidator/module.go +++ b/x/uvalidator/module.go @@ -45,26 +45,35 @@ type AppModuleBasic struct { type AppModule struct { AppModuleBasic - keeper keeper.Keeper - stakingKeeper types.StakingKeeper - slashingKeeper types.SlashingKeeper - utssKeeper types.UtssKeeper + keeper keeper.Keeper + bankKeeper types.BankKeeper + authKeeper types.AccountKeeper + distributionKeeper types.DistributionKeeper + stakingKeeper types.StakingKeeper + slashingKeeper types.SlashingKeeper + utssKeeper types.UtssKeeper } // NewAppModule constructor func NewAppModule( cdc codec.Codec, keeper keeper.Keeper, + bankKeeper types.BankKeeper, + authKeeper types.AccountKeeper, + distributionKeeper types.DistributionKeeper, stakingKeeper types.StakingKeeper, slashingKeeper types.SlashingKeeper, utssKeeper types.UtssKeeper, ) *AppModule { return &AppModule{ - AppModuleBasic: AppModuleBasic{cdc: cdc}, - keeper: keeper, - stakingKeeper: stakingKeeper, - slashingKeeper: slashingKeeper, - utssKeeper: utssKeeper, + AppModuleBasic: AppModuleBasic{cdc: cdc}, + keeper: keeper, + bankKeeper: bankKeeper, + authKeeper: authKeeper, + distributionKeeper: distributionKeeper, + stakingKeeper: stakingKeeper, + slashingKeeper: slashingKeeper, + utssKeeper: utssKeeper, } } @@ -172,3 +181,9 @@ func (a AppModule) migrateToV2() module.MigrationHandler { func (a AppModule) ConsensusVersion() uint64 { return ConsensusVersion } + +func (a AppModule) BeginBlock(ctx context.Context) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) + + return BeginBlocker(sdkCtx, a.keeper) +} From b28cb1d3b50a7bf2b1545214ab1affbf8826983b Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Fri, 23 Jan 2026 18:54:51 +0530 Subject: [PATCH 4/8] refactor: added a helper method to get if a UV is active --- .../keeper/msg_add_universal_validator.go | 2 +- .../keeper/msg_remove_universal_validator.go | 2 +- x/uvalidator/keeper/msg_server.go | 2 +- .../keeper/msg_update_universal_validator.go | 2 +- .../msg_update_universal_validator_status.go | 2 +- x/uvalidator/keeper/validator.go | 27 +++++++++++++++++++ 6 files changed, 32 insertions(+), 5 deletions(-) diff --git a/x/uvalidator/keeper/msg_add_universal_validator.go b/x/uvalidator/keeper/msg_add_universal_validator.go index 307f3016..224ab3f6 100644 --- a/x/uvalidator/keeper/msg_add_universal_validator.go +++ b/x/uvalidator/keeper/msg_add_universal_validator.go @@ -28,7 +28,7 @@ func (k Keeper) AddUniversalValidator( } // Ensure validator exists in staking module - validator, err := k.stakingKeeper.GetValidator(sdkCtx, valAddr) + validator, err := k.StakingKeeper.GetValidator(sdkCtx, valAddr) if err != nil { return fmt.Errorf("core validator not found: %w", err) } diff --git a/x/uvalidator/keeper/msg_remove_universal_validator.go b/x/uvalidator/keeper/msg_remove_universal_validator.go index 84aecf93..f682a768 100644 --- a/x/uvalidator/keeper/msg_remove_universal_validator.go +++ b/x/uvalidator/keeper/msg_remove_universal_validator.go @@ -47,7 +47,7 @@ func (k Keeper) RemoveUniversalValidator( case types.UVStatus_UV_STATUS_PENDING_JOIN: // Check if validator is part of the current TSS process - currentParticipants, err := k.utssKeeper.GetCurrentTssParticipants(ctx) + currentParticipants, err := k.UtssKeeper.GetCurrentTssParticipants(ctx) if err != nil { return fmt.Errorf("failed to fetch current TSS participants: %w", err) } diff --git a/x/uvalidator/keeper/msg_server.go b/x/uvalidator/keeper/msg_server.go index 7d4da277..9b371554 100755 --- a/x/uvalidator/keeper/msg_server.go +++ b/x/uvalidator/keeper/msg_server.go @@ -88,7 +88,7 @@ func (ms msgServer) UpdateUniversalValidator(ctx context.Context, msg *types.Msg // Find validator controlled by this account valAddr := sdk.ValAddress(signerAcc) - validator, err := ms.k.stakingKeeper.GetValidator(ctx, valAddr) + validator, err := ms.k.StakingKeeper.GetValidator(ctx, valAddr) if err != nil { return nil, errors.Wrap(err, "signer is not a validator operator") } diff --git a/x/uvalidator/keeper/msg_update_universal_validator.go b/x/uvalidator/keeper/msg_update_universal_validator.go index 7806df21..bb6a9ab3 100644 --- a/x/uvalidator/keeper/msg_update_universal_validator.go +++ b/x/uvalidator/keeper/msg_update_universal_validator.go @@ -23,7 +23,7 @@ func (k Keeper) UpdateUniversalValidator( } // Ensure validator exists in staking module - _, err = k.stakingKeeper.GetValidator(sdkCtx, valAddr) + _, err = k.StakingKeeper.GetValidator(sdkCtx, valAddr) if err != nil { return fmt.Errorf("core validator not found: %w", err) } diff --git a/x/uvalidator/keeper/msg_update_universal_validator_status.go b/x/uvalidator/keeper/msg_update_universal_validator_status.go index b2b64968..984b984f 100644 --- a/x/uvalidator/keeper/msg_update_universal_validator_status.go +++ b/x/uvalidator/keeper/msg_update_universal_validator_status.go @@ -17,7 +17,7 @@ func (k Keeper) UpdateUniversalValidatorStatus( ) error { sdkCtx := sdk.UnwrapSDKContext(ctx) - isOngoingTSS, err := k.utssKeeper.HasOngoingTss(ctx) + isOngoingTSS, err := k.UtssKeeper.HasOngoingTss(ctx) if err != nil { return fmt.Errorf("failed to check TSS state: %w", err) } diff --git a/x/uvalidator/keeper/validator.go b/x/uvalidator/keeper/validator.go index 94a26fc0..dfdaef99 100644 --- a/x/uvalidator/keeper/validator.go +++ b/x/uvalidator/keeper/validator.go @@ -125,3 +125,30 @@ func (k Keeper) GetUniversalValidator( return val, true, nil } + +// IsActiveUniversalValidator returns true if the given validator address is +// currently registered as an active universal validator. +func (k Keeper) IsActiveUniversalValidator( + ctx context.Context, + validatorOperatorAddr string, +) (bool, error) { + valAddr, err := sdk.ValAddressFromBech32(validatorOperatorAddr) + if err != nil { + return false, err + } + + exists, err := k.UniversalValidatorSet.Has(ctx, valAddr) + if err != nil { + return false, err + } + if !exists { + return false, nil + } + + uv, err := k.UniversalValidatorSet.Get(ctx, valAddr) + if err != nil { + return false, fmt.Errorf("failed to get universal validator: %w", err) + } + + return uv.LifecycleInfo.CurrentStatus == types.UVStatus_UV_STATUS_ACTIVE, nil +} From 3978a0181faafab1dcb043eb512629daa196dc7c Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Fri, 23 Jan 2026 18:55:20 +0530 Subject: [PATCH 5/8] refactor: added a helper method to get if a UV is active --- x/uvalidator/keeper/voting.go | 6 +++--- x/uvalidator/types/msg_add_universal_validator.go | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/x/uvalidator/keeper/voting.go b/x/uvalidator/keeper/voting.go index 9327abaa..635d8ea2 100644 --- a/x/uvalidator/keeper/voting.go +++ b/x/uvalidator/keeper/voting.go @@ -42,7 +42,7 @@ func (k Keeper) IsBondedUniversalValidator(ctx context.Context, universalValidat } // Ensure the universal validator exists in the staking module - validator, err := k.stakingKeeper.GetValidator(ctx, valAddr) + validator, err := k.StakingKeeper.GetValidator(ctx, valAddr) if err != nil { return false, fmt.Errorf("core validator not found: %w", err) } @@ -75,7 +75,7 @@ func (k Keeper) IsTombstonedUniversalValidator(ctx context.Context, universalVal } // Query the validator - validator, err := k.stakingKeeper.GetValidator(sdkCtx, valAddr) + validator, err := k.StakingKeeper.GetValidator(sdkCtx, valAddr) if err != nil { return false, fmt.Errorf("core validator not found: %w", err) } @@ -86,7 +86,7 @@ func (k Keeper) IsTombstonedUniversalValidator(ctx context.Context, universalVal return false, fmt.Errorf("failed to get consensus address: %w", err) } - return k.slashingKeeper.IsTombstoned(sdkCtx, consAddress), nil + return k.SlashingKeeper.IsTombstoned(sdkCtx, consAddress), nil } func (k Keeper) VoteOnBallot( diff --git a/x/uvalidator/types/msg_add_universal_validator.go b/x/uvalidator/types/msg_add_universal_validator.go index db388637..a55b0523 100644 --- a/x/uvalidator/types/msg_add_universal_validator.go +++ b/x/uvalidator/types/msg_add_universal_validator.go @@ -3,6 +3,7 @@ package types import ( "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) var ( @@ -52,5 +53,9 @@ func (msg *MsgAddUniversalValidator) ValidateBasic() error { return errors.Wrap(err, "invalid core validator address") } + if msg.Network == nil { + return errors.Wrap(sdkerrors.ErrInvalidRequest, "network info is required") + } + return msg.Network.ValidateBasic() } From ad9db45a63fbb166a1303fb5e4511c23a4dd7414 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Fri, 23 Jan 2026 18:56:00 +0530 Subject: [PATCH 6/8] feat: added a BeginBlocker in abci.go in uvalidator module to allocate the boosted rewards of a UV --- x/uvalidator/abci.go | 131 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 x/uvalidator/abci.go diff --git a/x/uvalidator/abci.go b/x/uvalidator/abci.go new file mode 100644 index 00000000..d4b6e207 --- /dev/null +++ b/x/uvalidator/abci.go @@ -0,0 +1,131 @@ +package module + +import ( + "context" + + "time" + + "cosmossdk.io/math" + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cosmos/cosmos-sdk/telemetry" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/pushchain/push-chain-node/x/uvalidator/keeper" + "github.com/pushchain/push-chain-node/x/uvalidator/types" +) + +func BeginBlocker(ctx sdk.Context, uvalidatorKeeper keeper.Keeper) error { + defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) + + // determine the total power signing the block + var previousTotalPower int64 + for _, voteInfo := range ctx.VoteInfos() { + previousTotalPower += voteInfo.Validator.Power + } + + // full amount will be allocated to community pool by distribution module itself + if previousTotalPower == 0 { + return nil + } + + height := ctx.BlockHeight() + if height > 1 { + if err := AllocateTokens(ctx, previousTotalPower, ctx.VoteInfos(), uvalidatorKeeper); err != nil { + return err + } + } + + return nil +} + +// AllocateTokens performs reward and fee distribution to all validators based +// on the F1 fee distribution specification. +func AllocateTokens(ctx context.Context, totalPreviousPower int64, bondedVotes []abci.VoteInfo, k keeper.Keeper) error { + // fetch and clear the collected fees for distribution, since this is + // called in BeginBlock, collected fees will be from the previous block + // (and distributed to the previous proposer) + feeCollector := k.AuthKeeper.GetModuleAccount(ctx, authtypes.FeeCollectorName) + feesCollectedInt := k.BankKeeper.GetAllBalances(ctx, feeCollector.GetAddress()) + if feesCollectedInt.IsZero() { + return nil + } + feesCollected := sdk.NewDecCoinsFromCoins(feesCollectedInt...) + + // transfer collected fees to the uvalidator module account + err := k.BankKeeper.SendCoinsFromModuleToModule(ctx, authtypes.FeeCollectorName, types.ModuleName, feesCollectedInt) + if err != nil { + return err + } + + // First: calculate effective total power (standard + boost for UVs) + effectiveTotalPower := math.LegacyZeroDec() + for _, vote := range bondedVotes { + validator, err := k.StakingKeeper.ValidatorByConsAddr(ctx, vote.Validator.Address) + if err != nil { + return err + } + + isUV, err := k.IsActiveUniversalValidator(ctx, validator.GetOperator()) + if err != nil { + return err + } + + power := math.LegacyNewDec(vote.Validator.Power) + if isUV { + power = power.Mul(math.LegacyMustNewDecFromStr("1.148")) + } + + effectiveTotalPower = effectiveTotalPower.Add(power) + } + + // Allocate 0.148x to the UVs proportionally using effective power + allocated := sdk.NewDecCoins() + for _, vote := range bondedVotes { + validator, err := k.StakingKeeper.ValidatorByConsAddr(ctx, vote.Validator.Address) + if err != nil { + return err + } + + power := math.LegacyNewDec(vote.Validator.Power) + + isUV, err := k.IsActiveUniversalValidator(ctx, validator.GetOperator()) + if err != nil { + return err + } + + if isUV { + power = power.Mul(math.LegacyMustNewDecFromStr("0.148")) + } else { + continue + } + + powerFraction := power.QuoTruncate(effectiveTotalPower) + reward := feesCollected.MulDecTruncate(powerFraction) + + err = k.DistributionKeeper.AllocateTokensToValidator(ctx, validator, reward) + if err != nil { + return err + } + + allocated = allocated.Add(reward...) + } + + // Calculate and return remaining + extraCoins, extraChange := allocated.TruncateDecimal() + remainingCoins, _ := feesCollectedInt.SafeSub(extraCoins...) + + remainingDec := sdk.NewDecCoinsFromCoins(remainingCoins...) + remainingDec = remainingDec.Add(extraChange...) + remainingFinal, _ := remainingDec.TruncateDecimal() + + if !remainingFinal.IsZero() { + err = k.BankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, authtypes.FeeCollectorName, remainingFinal) + if err != nil { + return err + } + } + + // Distribution's BeginBlocker will now run and allocate rest amount of fees proportionally to the voting power + return nil +} From de846195c82222a4d9843b09b3e668a543e35727 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Fri, 23 Jan 2026 19:00:44 +0530 Subject: [PATCH 7/8] refactor: added 1.148 value in a variable --- x/uvalidator/abci.go | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/x/uvalidator/abci.go b/x/uvalidator/abci.go index d4b6e207..055cfe41 100644 --- a/x/uvalidator/abci.go +++ b/x/uvalidator/abci.go @@ -2,7 +2,6 @@ package module import ( "context" - "time" "cosmossdk.io/math" @@ -15,6 +14,14 @@ import ( "github.com/pushchain/push-chain-node/x/uvalidator/types" ) +// BoostMultiplier is the factor applied to UV power when calculating effective total power. +// This inflates the denominator so non-UVs get diluted. +const BoostMultiplier = "1.148" + +// ExtraBoostPortion is the fractional part of the boost (0.148) +// used when allocating only the incremental boost to UVs. +const ExtraBoostPortion = "0.148" + func BeginBlocker(ctx sdk.Context, uvalidatorKeeper keeper.Keeper) error { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) @@ -73,7 +80,7 @@ func AllocateTokens(ctx context.Context, totalPreviousPower int64, bondedVotes [ power := math.LegacyNewDec(vote.Validator.Power) if isUV { - power = power.Mul(math.LegacyMustNewDecFromStr("1.148")) + power = power.Mul(math.LegacyMustNewDecFromStr(BoostMultiplier)) } effectiveTotalPower = effectiveTotalPower.Add(power) @@ -87,19 +94,19 @@ func AllocateTokens(ctx context.Context, totalPreviousPower int64, bondedVotes [ return err } - power := math.LegacyNewDec(vote.Validator.Power) - isUV, err := k.IsActiveUniversalValidator(ctx, validator.GetOperator()) if err != nil { return err } - if isUV { - power = power.Mul(math.LegacyMustNewDecFromStr("0.148")) - } else { + if !isUV { continue } + // Use only the extra portion for UV allocation + power := math.LegacyNewDec(vote.Validator.Power) + power = power.Mul(math.LegacyMustNewDecFromStr(ExtraBoostPortion)) + powerFraction := power.QuoTruncate(effectiveTotalPower) reward := feesCollected.MulDecTruncate(powerFraction) @@ -111,7 +118,7 @@ func AllocateTokens(ctx context.Context, totalPreviousPower int64, bondedVotes [ allocated = allocated.Add(reward...) } - // Calculate and return remaining + // Calculate and return remaining (including any truncation change) extraCoins, extraChange := allocated.TruncateDecimal() remainingCoins, _ := feesCollectedInt.SafeSub(extraCoins...) From 1a35b1493b5f95d275ee6bf59a474e1638d7f66a Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Fri, 23 Jan 2026 19:04:10 +0530 Subject: [PATCH 8/8] refactor: added a NOTE for community tax in BeginBlocker of uvalidator module --- x/uvalidator/abci.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/x/uvalidator/abci.go b/x/uvalidator/abci.go index 055cfe41..05f07bef 100644 --- a/x/uvalidator/abci.go +++ b/x/uvalidator/abci.go @@ -14,6 +14,11 @@ import ( "github.com/pushchain/push-chain-node/x/uvalidator/types" ) +// NOTE: +// This logic works well when community tax = 0. +// When community tax > 0, UVs effectively get boosted from the full fee amount, +// and community tax is only applied to the remaining (after UV boost allocation), reducing the community pool share. + // BoostMultiplier is the factor applied to UV power when calculating effective total power. // This inflates the denominator so non-UVs get diluted. const BoostMultiplier = "1.148"