Skip to content

fix(drive)!: an evonode's token claim covers only the epochs it read (PV14) - #5015

Merged
QuantumExplorer merged 1 commit into
v4.2-devfrom
claude/angry-mirzakhani-4aed17
Sep 26, 2026
Merged

QuantumExplorer merged 1 commit into
v4.2-devfrom
claude/angry-mirzakhani-4aed17

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Sep 26, 2026 •

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

A token can pay a perpetual distribution to EvonodesByParticipation. Each claim weighs every cycle by the claimant's share of the blocks proposed in the epochs the cycle spans, read from the finalized epoch infos of those epochs.

The claim starts from the evonode's last paid moment, or from the start of the distribution on its first claim, and runs up to 128 cycles (32,767 for a fixed amount). It reads at most drive_abci.query.max_returned_elements (100) finalized epochs, the bound #4150 put on get_finalized_epoch_infos. But it evaluated its whole range whatever it had read:

  • Any function other than a fixed amount: a cycle whose epoch was not read yields MissingEpochInfo, and the claim fails as an internal error. Nothing is paid and the last paid moment does not move, so every later claim fails the same way. An evonode more than 100 epochs behind (about 2.5 years on mainnet, 4 days on testnet) could never claim the token again.
  • A fixed amount: the claimant's share of the blocks in the epochs read was applied to the whole range, and the last paid moment moved to its end.

A claim reaching an epoch with no finalized info yet behaved the same way. The previous epoch's info is written by the fee distribution of the first block of the next epoch, after that block's transitions, and an epoch in which no block was produced never gets one.

Related: #4767 is another configuration that leaves evonode rewards unclaimable (a block- or time-based distribution paired with this recipient). This PR does not touch that pairing. It does cover the cycle-length case described below, which validation allows for this recipient.

What was done?

Drive::evonode_participation_rewards, a new versioned method under drive.methods.token.distribution. It returns the amount and the moment the claim pays through, which the claim transformer now stores as the last paid moment.

  • v0 (every version up to 13) is the transformer's code, moved verbatim.
  • v1 (DRIVE_TOKEN_METHOD_VERSIONS_V2, protocol version 14 only) changes four things:
    • It covers only the epochs it read. It reads the finalized epochs after the cycle start of the last paid moment, up to the claim's max_cycle_moment. It pays through the last whole cycle ending at or before the last epoch read. An evonode further behind is paid over several claims, each continuing from the moment the previous one stored.
    • It reads at least one whole cycle. The read limit is the new SystemLimits::max_evonode_reward_claim_epochs (100, backfilled into every table), or the interval when a cycle is longer. Epoch validation admits any interval from 1 to 65,535 for this recipient. Without this, a cycle of more than 100 epochs could never be paid.
    • An epoch without finalized info before the last one read counts as an epoch without blocks. Such an epoch will never get one: the fee distribution finalizes epochs in order and skips the ones in which no block was produced (epochs before protocol version 9 were never finalized either). It adds no blocks to either side of the ratio, and a cycle without any block pays nobody.
    • A claim that read no whole cycle earns nothing. An example is a claim in the first block of an epoch whose only new epoch is not finalized yet. It is refused, paid, with InvalidTokenClaimNoCurrentRewards, instead of failing as an internal error.

Drive::get_finalized_epoch_infos takes its limit as a parameter. The gRPC query handler and the v0 claim pass the same drive_abci.query.max_returned_elements they used before.

The epoch-info read stays unbilled, as before. v1 reads no more than v0 for any cycle up to 100 epochs, and never past max_cycle_moment. For a cycle longer than 100 epochs it reads that one cycle, and only once the cycle has finished.

PV14 changelog item 48 in v14.rs, after #5013's item 47.

Before / after

An evonode never claimed a token paying every epoch since epoch 0. It proposed 2 of every 10 blocks before epoch 100 and 8 of 10 from it. It claims at epoch 151, with epochs 0 to 150 finalized.

Linear (500, +100 per epoch) Fixed amount (1,000 per epoch)
Before (v13): claim at 151 InternalError("... missing epoch info for epochs 100..=100"), nothing paid; the same at every later epoch Pays 30,000 (all 150 epochs at the share of epochs 1 to 99), last paid moment 150. Its blocks earned 60,600.
After (v14): first claim at 151 Pays epochs 1 to 100 Pays 20,600 (epochs 1 to 100)
After: second claim Pays epochs 101 to 150 Pays 40,000 (epochs 101 to 150)
After: third claim InvalidTokenClaimNoCurrentRewards, paid InvalidTokenClaimNoCurrentRewards, paid

A fixed amount weighs the range one claim covers as one span, by its total blocks, as before. With the same number of blocks in every epoch, as above, that equals the per-epoch shares exactly.

In-place changes to shipped generations

  • get_finalized_epoch_infos v0 (every protocol version) reads its limit from a new parameter instead of from platform_version.drive_abci.query.max_returned_elements. Every caller that existed before (the query handler and the claim through evonode_participation_rewards v0) passes that same value, so the output is identical.
  • The token claim transformer (TokenClaimTransitionActionV0, every protocol version): the EvonodesByParticipation branch moved verbatim into evonode_participation_rewards v0, which versions up to 13 select. v0 returns max_cycle_moment as the moment paid through, which is the moment the transformer stored before, so the outcome is identical up to 13. The v13 tests below pin it.

How Has This Been Tested?

The failures were reproduced first, at protocol version 13 and at 14 before the fix, with drive-abci claims through process_raw_state_transitions:

  • a linear function 150 epochs behind failed with the internal error above;
  • a fixed amount paid 30,000 instead of 60,600;
  • a first claim on a distribution begun at epoch 30 failed at epoch 150 and again at 161.

New tests in batch/tests/token/distribution/perpetual/epoch_based.rs:

  • should_pay_an_evonode_150_epochs_behind_over_two_claims_from_protocol_version_14, for linear and fixed amount: 100 epochs then 50, together equal to the sum of the per-epoch shares, then nothing left.
  • should_pay_an_evonode_that_never_claimed_a_distribution_begun_long_ago_from_protocol_version_14.
  • should_pay_an_evonode_claim_within_the_read_limit_the_same_at_protocol_versions_13_and_14, for both functions.
  • should_pay_multi_epoch_cycles_through_the_last_whole_cycle_read_from_protocol_version_14: interval 3, 33 cycles then 19.
  • should_read_a_whole_cycle_longer_than_the_read_limit_from_protocol_version_14: interval 120.
  • should_count_an_epoch_without_blocks_as_no_participation_from_protocol_version_14: v13 still fails as an internal error.
  • should_leave_an_epoch_not_finalized_yet_to_a_later_claim_from_protocol_version_14: v13 fails (linear) or pays the epoch at the share of the epochs before it (fixed amount).
  • should_keep_failing_or_mispaying_evonode_claims_past_the_epochs_read_at_protocol_version_13: pins v13.

Mutations, each run against the whole module:

  • With the PV14 table set back to v0, the six v14 behaviour tests fail and the v13 pins still pass.
  • Without the whole-cycle read, only the 120-epoch test fails.
  • With a missing epoch reported as None, only the epoch-without-blocks test fails.

Also run:

  • cargo test -p drive-abci --lib -- token finalized_epoch (442 passed);
  • cargo test -p drive-abci --test strategy_tests token_tests (6 passed; the evonode fixed and linear chains keep their amounts);
  • cargo test -p drive --lib -- credit_pools tokens finalized_epoch verify::system (266 passed);
  • cargo test -p platform-version;
  • cargo clippy -p drive -p drive-abci -p dpp --all-features --all-targets -- -D warnings;
  • cargo fmt --all -- --check.

No fee pin moved: the epoch-info read is unbilled in both versions, so no _protocol_version_13 twin tests were needed.

Breaking Changes

Consensus change at protocol version 14. From that version, an EvonodesByParticipation claim can pay a different amount and store a different last paid moment. Claims that failed as internal errors are paid, or refused as a paid consensus error. Protocol version 13 and earlier are unchanged.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed
  • If I added or changed GroveDB structure, I described it in the area's structure.rs, regenerated grovedb-structure.json, and checked the structure viewer link posted on this pull request

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

PR Hygiene · a04ff07

  • Bots — coderabbitai not yet · thepastaclaw not yet — /skip-bots proceeds without the ones not yet reported
  • Self-review — post /self-reviewed once the bots are done
  • Within your 5 open PRs
  • Build running
  • Approvals — you own every area touched; none needed

When every box is checked the PR Hygiene check passes and this can merge.

…(PV14)

An EvonodesByParticipation claim read at most 100 finalized epochs but
evaluated its whole range, up to 128 cycles (32,767 for a fixed amount).
Past the epochs read, a function other than a fixed amount failed as an
internal error and the last paid moment never advanced; a fixed amount
applied the share of the epochs read to the whole range. A claim reaching
an epoch not finalized yet did the same.

evonode_participation_rewards v1 (DRIVE_TOKEN_METHOD_VERSIONS_V2) pays
through the last whole cycle it read, reads at least one whole cycle
(SystemLimits::max_evonode_reward_claim_epochs, 100, or the interval),
and counts an epoch without finalized info before the last one read as
an epoch without blocks. v0 is the transformer's code moved verbatim.
get_finalized_epoch_infos takes its limit from the caller.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 26, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 49 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository: dashpay/platform/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 4f606831-fd9a-433e-a1ac-43a34aec0922

📥 Commits

Reviewing files that changed from the base of the PR and between f6504e5 and a04ff07.

📒 Files selected for processing (21)
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/distribution/perpetual/epoch_based.rs
  • packages/rs-drive-abci/src/query/system/finalized_epoch_infos/v0/mod.rs
  • packages/rs-drive-abci/tests/strategy_tests/test_cases/token_tests.rs
  • packages/rs-drive/src/drive/credit_pools/epochs/get_finalized_epoch_info/mod.rs
  • packages/rs-drive/src/drive/credit_pools/epochs/get_finalized_epoch_info/v0/mod.rs
  • packages/rs-drive/src/drive/tokens/distribution/evonode_participation_rewards/mod.rs
  • packages/rs-drive/src/drive/tokens/distribution/evonode_participation_rewards/v0/mod.rs
  • packages/rs-drive/src/drive/tokens/distribution/evonode_participation_rewards/v1/mod.rs
  • packages/rs-drive/src/drive/tokens/distribution/mod.rs
  • packages/rs-drive/src/state_transition_action/batch/batched_transition/token_transition/token_claim_transition_action/v0/transformer.rs
  • packages/rs-platform-version/src/version/drive_versions/drive_token_method_versions/mod.rs
  • packages/rs-platform-version/src/version/drive_versions/drive_token_method_versions/v1.rs
  • packages/rs-platform-version/src/version/drive_versions/drive_token_method_versions/v2.rs
  • packages/rs-platform-version/src/version/drive_versions/v9.rs
  • packages/rs-platform-version/src/version/mocks/v2_test.rs
  • packages/rs-platform-version/src/version/system_limits/mod.rs
  • packages/rs-platform-version/src/version/system_limits/v1.rs
  • packages/rs-platform-version/src/version/system_limits/v2.rs
  • packages/rs-platform-version/src/version/system_limits/v3.rs
  • packages/rs-platform-version/src/version/system_limits/v4.rs
  • packages/rs-platform-version/src/version/v14.rs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the waiting-bots Waiting for the review bots to report on this head label Sep 26, 2026
@github-actions github-actions Bot added this to the v4.2.0 milestone Sep 26, 2026
@thepastaclaw

thepastaclaw commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

🕓 Queued for automated review — 2nd in line, estimated start in ~10 min (commit a04ff07)
Estimated review time once started: ~40 min (two-phase automated review; median of recent runs).

  • Request priority review — click to move this review to the front of the queue.

@QuantumExplorer

Copy link
Copy Markdown
Member Author

Reviewed

@QuantumExplorer
QuantumExplorer merged commit 472b84f into v4.2-dev Sep 26, 2026
36 of 37 checks passed
@QuantumExplorer
QuantumExplorer deleted the claude/angry-mirzakhani-4aed17 branch September 26, 2026 04:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-bots Waiting for the review bots to report on this head

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants