iotexproject / iotexproject/iotex-core

[rewarding/archive] pendingVoterRewardDelegates() is unreadable at historical heights on an archive node

Open
#4,998 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
1.6k
Forks
382
Avg merge
4d 22h
Merged PRs (30d)
17

Description

## Summary

`pendingVoterRewardDelegates()` cannot be served at a historical block height on an
archive node. It returns `db.ErrNotSupported` while every other IIP-59 read-state
method answers historical queries normally.

```
$ eth_call pendingVoterRewardDelegates() @ 0xe1
{"code":5,"message":"erigon store does not support ordered range scan: not supported"}

$ eth_call pendingVoterRewardDelegates() @ latest
0x...0003 0x0ddf... 0x80fe... 0xc340... # fine
```

## Cause

The method enumerates the pending-pool index by an ordered range scan over the V2
state-key prefix. `ErigonWorkingSetStore.States` rejects any scan outright:

https://github.com/iotexproject/iotex-core/blob/rc1_2.5.0/state/factory/erigonstore/workingsetstore_erigon.go#L384-L388

```go
func (store *ErigonWorkingSetStore) States(ns string, obj any, keys [][]byte, scan *db.RangeScan) (state.Iterator, error) {
if scan != nil {
return nil, errors.Wrap(db.ErrNotSupported, "erigon store does not support ordered range scan")
}
```

The refusal itself is deliberate and correct — erigon's object storage is addressed by
contract slot, so it cannot honour `[min, max)` ordering and returning a differently
ordered answer would be worse. The gap is that the IIP-59 pool index has no
non-scan path to fall back to when the read is historical.

## Scope

Not a consensus issue. The drain does not depend on this method, and a failed range
scan during a drain is classified non-settleable (`voter_reward_errors.go`), so it
fails the block rather than letting nodes diverge — that part behaves correctly.

What it blocks is **historical review**: "which candidates had a pending pool at
height H" cannot be answered from a retained archive.

## Reproduction

Any archive node with IIP-59 activated. Verified on `rc1_2.5.0`
(`e3bfc422e0684d22e1abdaa15c98097165f3cef8`) with a 9-node local nightly cluster,
querying an isolated archive node built from a stopped chain's data directory.

Per-method result at a historical block tag:

| method | historical | latest |
|---|---|---|
| `voterRewardDistribution` | ok | ok |
| `pendingVoterReward` | ok | ok |
| `delegateRewardSnapshot` | ok | ok |
| `delegatePayoutAddress` | ok | ok |
| `voterRewardDestination` | ok | ok |
| `pendingVoterRewardDelegates` | **ErrNotSupported** | ok |

## Not a regression

Present identically before this branch — the same stub with the same line exists on
the earlier tested commit `1c9a36b`. Filing it because IIP-59 is the first feature
that depends on a prefix scan of the pool index, which is what makes the limitation
observable.

## Suggested resolution

Either give the historical path a non-erigon fallback for this one read, or document
it as a known limitation of archive retention and keep the method `latest`-only.
Whichever is chosen, the archive acceptance criteria should state it explicitly —
right now this is the single item preventing a clean archive-retention sign-off.

Contributor guide

Open the contributing guide

Research direction

Start with pendingVoterRewardDelegates() and the historical read path, then inspect state/factory/erigonstore/workingsetstore_erigon.go around States and voter_reward_errors.go. Compare the other IIP-59 historical methods and determine whether a non-Erigon fallback or an explicit latest-only limitation is appropriate; done means the archive acceptance criteria and behavior are explicit and verified.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
backend-api-design, blockchain, databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.