iotexproject / iotexproject/iotex-core
[rewarding/archive] pendingVoterRewardDelegates() is unreadable at historical heights on an archive node
- 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
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