-
Notifications
You must be signed in to change notification settings - Fork 17
refactor: improve governance participation logic #538
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR refactors the governance participation calculation logic to properly handle orchestrator activation and deactivation cycles, ensuring that only proposals from periods when an orchestrator was active are counted toward their participation rate. The refactoring also improves caching behavior by using cache-and-network fetch policy consistently.
Changes:
- Replaced the generic
transcoderActivatedEventsquery with a specifictranscoderActivationHistoryquery that fetches both activation and deactivation events - Created a new
useGovernanceParticipationhook that encapsulates the logic for calculating governance participation based on activation windows - Refactored
OrchestratingViewcomponent to use the new hook instead of inline query logic
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| queries/transcoderActivationHistory.graphql | New GraphQL query that fetches both activation and deactivation events for a delegate in chronological order |
| queries/transcoderActivatedEvents.graphql | Removed generic query that only fetched activation events with flexible parameters |
| hooks/useGovernanceParticipation.tsx | New hook implementing window-based participation calculation logic with activation/deactivation tracking |
| components/OrchestratingView/index.tsx | Simplified to use new hook, extracted participation calculation to improve readability |
| apollo/subgraph.ts | Updated generated TypeScript types reflecting query changes and added JSDoc formatting improvements |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Fix participation logic so it works when orchestrators are activated and deactivated. Also improve caching behavoir.
49365fa to
a400198
Compare
Co-authored-by: copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
b5e04d4 to
16251e8
Compare
Ensure pending proposals are ommited in the participation calculation.
|
@ECWireless ready for your review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for (const { type, round } of timeline) { | ||
| if (type === "activation") { | ||
| if (start === null) { | ||
| start = round; |
Copilot
AI
Feb 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The buildActiveWindows function silently ignores activation events that occur when a transcoder is already active (when start !== null). While this may be the expected behavior if the protocol doesn't allow duplicate activations, this silent handling could mask data quality issues.
For robustness, consider either:
- Adding a comment documenting this expected behavior
- Adding validation to warn if unexpected duplicate activations are encountered
- Throwing an error if this case should never occur in practice
This will make the code more maintainable and help future developers understand the assumptions.
| start = round; | |
| start = round; | |
| } else { | |
| // Unexpected duplicate activation while a window is already active. | |
| // This is ignored to preserve existing behavior, but logged for visibility. | |
| // If the activation/deactivation protocol guarantees no duplicates, this | |
| // may indicate a data quality issue upstream. | |
| // eslint-disable-next-line no-console | |
| console.warn( | |
| "buildActiveWindows: ignoring duplicate activation event", | |
| { activeStartRound: start, activationRound: round } | |
| ); |
| if (start === null) { | ||
| start = round; | ||
| } | ||
| } else if (start !== null && round >= start) { |
Copilot
AI
Feb 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The buildActiveWindows function may not correctly handle deactivation events that occur when start === null (i.e., before any activation). The condition on line 59 checks start !== null && round >= start, so a deactivation event with start === null is simply ignored.
While this might be the intended behavior (deactivation without activation is meaningless), it could silently hide data integrity issues in the underlying events. Consider adding validation or a warning when encountering a deactivation without a prior activation.
| } else if (start !== null && round >= start) { | |
| } else if (type === "deactivation") { | |
| if (start === null) { | |
| console.warn( | |
| "buildActiveWindows: received deactivation without a prior activation", | |
| { deactivationRound: round } | |
| ); | |
| continue; | |
| } | |
| if (round < start) { | |
| console.warn( | |
| "buildActiveWindows: received deactivation before the current activation start", | |
| { activationStartRound: start, deactivationRound: round } | |
| ); | |
| continue; | |
| } |
| round: Number(d.deactivationRound), | ||
| type: "deactivation" as const, | ||
| })), | ||
| ].sort((a, b) => a.round - b.round || (a.type === "deactivation" ? -1 : 1)); |
Copilot
AI
Feb 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sort tiebreaker at line 49 prioritizes deactivation over activation when both events occur in the same round: (a.type === "deactivation" ? -1 : 1). This means that if a transcoder is both activated and deactivated in the same round, the deactivation is processed first.
This could lead to counterintuitive behavior. If the same round has both an activation and deactivation event:
- With current logic: deactivation comes first, so the activation creates a window starting at that round
- Expected behavior might be: activation comes first, creating a zero-length or instant window
Consider documenting this behavior or revisiting whether this is the correct handling for same-round activation/deactivation events.
| ].sort((a, b) => a.round - b.round || (a.type === "deactivation" ? -1 : 1)); | |
| ].sort((a, b) => { | |
| if (a.round !== b.round) { | |
| return a.round - b.round; | |
| } | |
| if (a.type === b.type) { | |
| return 0; | |
| } | |
| // When in the same round, process activation events before deactivation events | |
| return a.type === "activation" ? -1 : 1; | |
| }); |
| const isActiveProposal = (voteStart: string, currentRoundId?: string) => | ||
| currentRoundId ? Number(voteStart) <= Number(currentRoundId) : true; | ||
|
|
Copilot
AI
Feb 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The isActiveProposal function returns true when currentRoundId is undefined (line 74). This means all proposals are considered active if the current round ID is not provided. While this might be intentional fallback behavior, it could lead to incorrect participation calculations if the current round ID is unexpectedly missing.
Consider documenting this behavior or making it more explicit with a comment, especially since this affects how "eligible" proposals are counted.
| const isActiveProposal = (voteStart: string, currentRoundId?: string) => | |
| currentRoundId ? Number(voteStart) <= Number(currentRoundId) : true; | |
| const isActiveProposal = (voteStart: string, currentRoundId?: string) => { | |
| // If currentRoundId is not provided, treat all proposals as active. | |
| // This affects how "eligible" proposals are counted in participation metrics. | |
| if (currentRoundId === undefined) { | |
| return true; | |
| } | |
| return Number(voteStart) <= Number(currentRoundId); | |
| }; |
Fix participation logic so it works when orchestrators are activated and deactivated. Also improve caching behavoir.