You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit a7adc8a
Browse filesBrowse the repository at this point in the historyBrowse files
fix: stop silently dropping citizens from the relayer work list (#13)
* fix: stop silently dropping citizens from the relayer work list
Mainnet round 110 locked its relayer reward pool permanently: 8528 expected
weighted actions vs 8128 completed, isRewardClaimable(110) == false, 4285 B3TR
that no relayer can ever claim. Rounds 111 and 112 went the same way.
The gap is exactly 100 navigator citizens x (3 vote + 1 claim). They were not
ineligible and their navigator was healthy - it had set valid round-110
preferences with all 13 apps eligible and percentages summing to 10000.
Simulating castNavigatorVote(citizen, 110) at historical revisions succeeds at
every block from just after those preferences were set through 100 blocks before
the deadline, a ~3.6 day window. The contract was ready; no relayer ever called
it. The same cohort voted fine in rounds 108, 109 and 112.
Four defects here can each produce that signature:
1. Thor's /accounts/* halts a batch at the first reverting clause and returns a
SHORT array. batchSimulate iterated results.length, so one bad clause dropped
every citizen after it in a 100-clause chunk, with no error and no log. The
added regression test reproduces round 110's shape exactly: 120 citizens with
one reverting clause at index 0 yielded 20 survivors before the fix, 119
after. simulateAllClauses now resumes past a revert so it only ever costs its
own clause.
2. A failed citizen fetch left citizenUsers at 0, which is indistinguishable
from "this network has no citizens", so both citizen voting phases were
skipped for the whole cycle - and the error was not even logged when the
message contained "reverted". The summary now carries citizenFetchFailed and
always logs. The citizen phases are wrapped so a failure there cannot starve
the claim phases that run after them.
3. ExitAnnounced removed all of a navigator's citizens on announcement. An
exiting navigator is not dead: verified on mainnet, castNavigatorVote
succeeded all round for a navigator with isExiting=true / isDeactivated=false,
and that navigator accounts for 7 of round 110's missing citizens. Even for a
genuinely deactivated navigator the citizens must stay, because the on-chain
skip path is what reduces expected actions - dropping them means nobody ever
makes the call. Citizens who really are undelegated are filtered out
authoritatively by getNavigatorsForCitizens(citizens, snapshot).
4. CYCLE_RETRIES was nodePool.length, so any deployment setting NODE_URL got
exactly one attempt with no backoff. Floored at 3.
Verified with a DRY_RUN cycle against mainnet round 113: the citizen work list
is 180 and getTotalDelegatedCitizensAtTimepoint(snapshot) is 180; auto-voters
2154 against 2154 on-chain.
Two delegation-replay tests asserted the old removal behaviour as intentional
"lazy invalidation". They are inverted here deliberately, with the reasoning
recorded in the tests.
Caveat: I cannot confirm the operator of the relayer that serves citizens runs
this exact code, and I could not reproduce a truncated chunk against a live node
today. These are reference-implementation defects consistent with the observed
on-chain signature - hardening, not a proven root-cause fix.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016EvtKnHKXcJbRcLp9D7vk9
* fix: contain the citizen governance phase too
The previous commit wrapped only the allocation cycle. A throw in
runCitizenGovernanceVoteCycle still propagated out of runActiveRoundVotingCycles,
aborted runAllCycles, and starved both claim phases — exactly the failure the
containment exists to prevent, left half-open.
Each citizen phase now has its own catch, so a failing allocation cycle doesn't
cost the governance votes either, or vice versa.
The two new tests fail without this change ("promise rejected 'governor
unreachable' instead of resolving").
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016EvtKnHKXcJbRcLp9D7vk9
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
0 commit comments