Repository navigation
fix(drive)!: an evonode's token claim covers only the epochs it read (PV14) - #5015
Conversation
…(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>
|
Warning Review limit reachedNext included review available in 49 minutes. View limit detailsLimit 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. Review configuration: ⚙️ Run configurationConfiguration used: Repository: dashpay/platform/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (21)
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. Comment |
|
🕓 Queued for automated review — 2nd in line, estimated start in ~10 min (commit a04ff07)
|
|
Reviewed |
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 onget_finalized_epoch_infos. But it evaluated its whole range whatever it had read: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 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 underdrive.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.DRIVE_TOKEN_METHOD_VERSIONS_V2, protocol version 14 only) changes four things: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.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.InvalidTokenClaimNoCurrentRewards, instead of failing as an internal error.Drive::get_finalized_epoch_infostakes its limit as a parameter. The gRPC query handler and the v0 claim pass the samedrive_abci.query.max_returned_elementsthey 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.
InternalError("... missing epoch info for epochs 100..=100"), nothing paid; the same at every later epochInvalidTokenClaimNoCurrentRewards, paidInvalidTokenClaimNoCurrentRewards, paidA 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_infosv0 (every protocol version) reads its limit from a new parameter instead of fromplatform_version.drive_abci.query.max_returned_elements. Every caller that existed before (the query handler and the claim throughevonode_participation_rewardsv0) passes that same value, so the output is identical.TokenClaimTransitionActionV0, every protocol version): theEvonodesByParticipationbranch moved verbatim intoevonode_participation_rewardsv0, which versions up to 13 select. v0 returnsmax_cycle_momentas 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: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:
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_13twin tests were needed.Breaking Changes
Consensus change at protocol version 14. From that version, an
EvonodesByParticipationclaim 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:
structure.rs, regeneratedgrovedb-structure.json, and checked the structure viewer link posted on this pull requestFor repository code-owners and collaborators only
🤖 Generated with Claude Code
PR Hygiene ·
a04ff07/skip-botsproceeds without the ones not yet reported/self-reviewedonce the bots are doneWhen every box is checked the
PR Hygienecheck passes and this can merge.