Skip to content

Fix excluded addresses re-inclusion#59

Merged
TxCorpi0x merged 10 commits into
masterfrom
mehdi/pse-excluded-addresses-fix
Jan 21, 2026
Merged

Fix excluded addresses re-inclusion#59
TxCorpi0x merged 10 commits into
masterfrom
mehdi/pse-excluded-addresses-fix

Conversation

@TxCorpi0x

@TxCorpi0x TxCorpi0x commented Jan 15, 2026

Copy link
Copy Markdown
Contributor

Description

This pull request significantly improves the handling and testing of excluded delegator addresses in the PSE module. The changes ensure that excluded addresses do not accumulate or receive rewards, and that their state is managed correctly throughout their lifecycle, including re-inclusion and undelegation. The integration and unit tests are enhanced to thoroughly validate these behaviors. Additionally, the hooks and keeper logic are updated for correctness and clarity.

Key changes include:

Exclusion Logic and Hooks

  • Updated AfterDelegationModified and BeforeDelegationRemoved hooks to prevent score accumulation for excluded addresses and to ensure DelegationTimeEntry is only managed for non-excluded addresses. Removal of delegation time entry now applies to all addresses, ensuring proper cleanup. [1] [2] [3] [4]
  • In DistributeCommunityPSE, clarified and simplified the clearing of account score snapshots after distribution, with comments explaining unconditional clearing for safety.

Integration and Unit Testing Enhancements

  • Major rewrite and expansion of the TestPSEDistribution integration test to:
    • Set up delegators and validators with progressive amounts,
    • Exclude and re-include a delegator, verifying reward distribution and event emission at each stage,
    • Test full undelegation for a re-included delegator.
  • Added a comprehensive unit test Test_ExcludedAddress_FullLifecycle covering the full lifecycle of an excluded address, including score accumulation, exclusion, reward distribution, undelegation, re-delegation, re-inclusion, and fresh score accumulation.
  • Updated imports in distribute_test.go to support new test logic.

Miscellaneous

  • Added missing imports for error handling and Cosmos SDK modules in params.go.

These changes collectively make the exclusion and re-inclusion logic for delegators more robust and thoroughly tested, reducing the risk of incorrect reward distribution or state inconsistencies.

Reviewers checklist:

  • Try to write more meaningful comments with clear actions to be taken.
  • Nit-picking should be unblocking. Focus on core issues.

Authors checklist

  • Provide a concise and meaningful description
  • Review the code yourself first, before making the PR.
  • Annotate your PR in places that require explanation.
  • Think and try to split the PR to smaller PR if it is big.

This change is Reviewable

@TxCorpi0x TxCorpi0x requested a review from a team as a code owner January 15, 2026 10:13
@TxCorpi0x TxCorpi0x requested review from masihyeganeh, metalarm10, miladz68 and ysv and removed request for a team January 15, 2026 10:13

@ysv ysv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@ysv reviewed all commit messages and made 1 comment.
Reviewable status: 0 of 3 files reviewed, 1 unresolved discussion (waiting on @masihyeganeh, @metalarm10, @miladz68, and @TxCorpi0x).


x/pse/keeper/distribute.go line 70 at r1 (raw file):

		// Only remove snapshots for non-excluded addresses. Excluded addresses keep their snapshots
		// to maintain score history for potential future inclusion.
		if _, excluded := excludedAddrMap[delStr]; !excluded {

I tend to disagree with this fix.
In this case address for excluded address will keep increasing between multiple PSE distributions. And if such address is removed from exclude list it will have huge score which is sum of score for multiple PSE iterations.

I'm also not sure how this fixes bug which we had

@TxCorpi0x TxCorpi0x left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@TxCorpi0x made 1 comment.
Reviewable status: 0 of 3 files reviewed, 1 unresolved discussion (waiting on @masihyeganeh, @metalarm10, @miladz68, and @ysv).


x/pse/keeper/distribute.go line 70 at r1 (raw file):

Previously, ysv (Yaroslav Savchuk) wrote…

I tend to disagree with this fix.
In this case address for excluded address will keep increasing between multiple PSE distributions. And if such address is removed from exclude list it will have huge score which is sum of score for multiple PSE iterations.

I'm also not sure how this fixes bug which we had

The fix is related to undelegation hook failure; there is a problem in the logic, it prevents undelagation command of staking module to fail because of missing item in snapshots.

But related to that problem you mentioned, Currently the scoreMap is being filtered and excluded addresses will be removed from score addition step. This behaviour is the existing behaviour in master branch.
Generally, i think we should prevent excluded addresses from receiving the score (current code) and we do not need to prevent already created score to be rewarded, in fact exclusion should happen only on score calculation, but while the distribution we should distribute already calculated score (even if it is in the list of excluded addresses now)

@ysv ysv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@ysv reviewed 1 file and all commit messages, made 4 comments, and resolved 1 discussion.
Reviewable status: 1 of 5 files reviewed, 4 unresolved discussions (waiting on @masihyeganeh, @metalarm10, @miladz68, and @TxCorpi0x).


x/pse/keeper/hooks.go line 52 at r2 (raw file):

	}); err != nil {
		return err
	}

maybe we should move this to be executed after isExcludedAddress check ?

This way both DelegationTimeEntry and AccountScoreSnapshot will always be empty for excluded address.
Because I can see that you remove it in params.go

Code quote:

	// Update DelegationTimeEntry for all addresses (needed for delegation tracking)
	if err := h.k.SetDelegationTimeEntry(ctx, valAddr, delAddr, types.DelegationTimeEntry{
		LastChangedUnixSec: blockTimeUnixSeconds,
		Shares:             delegation.Shares,
	}); err != nil {
		return err
	}

x/pse/keeper/hooks.go line 63 at r2 (raw file):

	} else if isExcludedAddress(delAddr, params.ExcludedAddresses, h.k.addressCodec) {
		return nil
	}

maybe it makes sense to replace whole this block with single call to isExcludedAddress function ?
Like this:

if excluded, err := isExcludedAddress {
}

the main idea is to move GetParams & !errors.Is(err, collections.ErrNotFound) into isExcludedAddress also.

Code quote:

	params, err := h.k.GetParams(ctx)
	if err != nil {
		// During genesis, params might not be initialized yet - treat all as non-excluded
		if !errors.Is(err, collections.ErrNotFound) {
			return err
		}
	} else if isExcludedAddress(delAddr, params.ExcludedAddresses, h.k.addressCodec) {
		return nil
	}

x/pse/keeper/hooks.go line 93 at r2 (raw file):

	// Remove DelegationTimeEntry for all addresses
	if err := h.k.RemoveDelegationTimeEntry(ctx, valAddr, delAddr); err != nil {

should we make this removal optional ?
For cases when it is called for excluded address


integration-tests/modules/pse_test.go line 33 at r2 (raw file):

)

func TestPSEDistribution(t *testing.T) {

I need to review this test carefully one more time but also I suggest to test this differently.
Can we test this by querying user score ?
E.g

  1. address has XX score
  2. address added to excluded_list
  3. query address => has 0 score
  4. remove from exclude_list
  5. query address (e.g next block after removed from exclude list) => score is credited again starting from 0

We can also query score for excluded address before/after distribution and it should always be 0

@ysv ysv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@ysv made 1 comment.
Reviewable status: 1 of 5 files reviewed, 4 unresolved discussions (waiting on @masihyeganeh, @metalarm10, @miladz68, and @TxCorpi0x).


integration-tests/modules/pse_test.go line 33 at r2 (raw file):
maybe try to do this via unit-tests

  1. address has XX score
  2. address added to excluded_list
  3. query address => has 0 score
  4. remove from exclude_list
  5. query address (e.g next block after removed from exclude list) => score is credited again starting from 0

@ysv ysv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@ysv made 1 comment.
Reviewable status: 1 of 5 files reviewed, 4 unresolved discussions (waiting on @masihyeganeh, @metalarm10, @miladz68, and @TxCorpi0x).


integration-tests/modules/pse_test.go line 33 at r2 (raw file):

Previously, ysv (Yaroslav Savchuk) wrote…

maybe try to do this via unit-tests

  1. address has XX score
  2. address added to excluded_list
  3. query address => has 0 score
  4. remove from exclude_list
  5. query address (e.g next block after removed from exclude list) => score is credited again starting from 0

agreed to just add unit test for score queries for now

@TxCorpi0x TxCorpi0x left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@TxCorpi0x made 4 comments.
Reviewable status: 0 of 5 files reviewed, 4 unresolved discussions (waiting on @masihyeganeh, @metalarm10, @miladz68, and @ysv).


integration-tests/modules/pse_test.go line 33 at r2 (raw file):

Previously, ysv (Yaroslav Savchuk) wrote…

agreed to just add unit test for score queries for now

Integration test updated for better readability,
Unit test Updated with the requested steps.


x/pse/keeper/hooks.go line 52 at r2 (raw file):

Previously, ysv (Yaroslav Savchuk) wrote…

maybe we should move this to be executed after isExcludedAddress check ?

This way both DelegationTimeEntry and AccountScoreSnapshot will always be empty for excluded address.
Because I can see that you remove it in params.go

Done, Moved


x/pse/keeper/hooks.go line 63 at r2 (raw file):

Previously, ysv (Yaroslav Savchuk) wrote…

maybe it makes sense to replace whole this block with single call to isExcludedAddress function ?
Like this:

if excluded, err := isExcludedAddress {
}

the main idea is to move GetParams & !errors.Is(err, collections.ErrNotFound) into isExcludedAddress also.

Done
Defined method for keeper according to discussion.


x/pse/keeper/hooks.go line 93 at r2 (raw file):

Previously, ysv (Yaroslav Savchuk) wrote…

should we make this removal optional ?
For cases when it is called for excluded address

It was unnecessary change, reverted

@TxCorpi0x TxCorpi0x changed the title Fix excluded addresses periods inconsistencies Fix excluded addresses re-inclusion Jan 21, 2026

@ysv ysv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@ysv reviewed 3 files and all commit messages, made 3 comments, and resolved 4 discussions.
Reviewable status: 3 of 5 files reviewed, 3 unresolved discussions (waiting on @masihyeganeh, @metalarm10, @miladz68, and @TxCorpi0x).


x/pse/keeper/distribute.go line 45 at r3 (raw file):

	// Clear all account score snapshots.
	// Excluded addresses should not have snapshots (cleared when added to exclusion list),
	// but we clear unconditionally for simplicity and safety.

nit: but we clear unconditionally for all addresses.

Code quote:

but we clear unconditionally for simplicity and safety

x/pse/keeper/hooks.go line 104 at r3 (raw file):

	newScore := lastScore.Add(addedScore)

	// Remove DelegationTimeEntry for all addresses, including excluded ones

comment is wrong including excluded ones


x/pse/keeper/params.go line 171 at r3 (raw file):

// IsExcludedAddress checks if the given address is in the excluded addresses list.
// Returns false if params are not initialized (e.g., during genesis).
func (k Keeper) IsExcludedAddress(ctx context.Context, addr sdk.AccAddress) bool {

In my opinion this method should return (bool, error)
Because there are a few errors which are outside of scope of this function and should be handled on higher level.

Usage I invision:

isExcluded, err := h.k.IsExcludedAddress(ctx, delAddr)
if err != nil {
return err 
}
if isExcluded {
  return nil 
}

@TxCorpi0x TxCorpi0x left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@TxCorpi0x made 2 comments and resolved 1 discussion.
Reviewable status: 0 of 5 files reviewed, 2 unresolved discussions (waiting on @masihyeganeh, @metalarm10, @miladz68, and @ysv).


x/pse/keeper/hooks.go line 104 at r3 (raw file):

Previously, ysv (Yaroslav Savchuk) wrote…

comment is wrong including excluded ones

Done.


x/pse/keeper/params.go line 171 at r3 (raw file):

Previously, ysv (Yaroslav Savchuk) wrote…

In my opinion this method should return (bool, error)
Because there are a few errors which are outside of scope of this function and should be handled on higher level.

Usage I invision:

isExcluded, err := h.k.IsExcludedAddress(ctx, delAddr)
if err != nil {
return err 
}
if isExcluded {
  return nil 
}

Done.

@ysv ysv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@ysv reviewed 5 files and all commit messages, and resolved 2 discussions.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on @masihyeganeh, @metalarm10, and @miladz68).

@TxCorpi0x TxCorpi0x merged commit 6ce9341 into master Jan 21, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants