Skip to content

Conversation

@rickstaa
Copy link
Member

@rickstaa rickstaa commented Feb 3, 2026

Fix participation logic so it works when orchestrators are activated and deactivated. Also improve caching behavoir.

@rickstaa rickstaa requested a review from ECWireless as a code owner February 3, 2026 20:59
@vercel
Copy link

vercel bot commented Feb 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
explorer-arbitrum-one Ready Ready Preview, Comment Feb 5, 2026 10:35am

Request Review

Copy link
Contributor

Copilot AI left a 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 transcoderActivatedEvents query with a specific transcoderActivationHistory query that fetches both activation and deactivation events
  • Created a new useGovernanceParticipation hook that encapsulates the logic for calculating governance participation based on activation windows
  • Refactored OrchestratingView component 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.

Base automatically changed from 310-showcase-orchestrator-voting-activity-in-the-ui to main February 4, 2026 03:16
Fix participation logic so it works when orchestrators are activated and
deactivated. Also improve caching behavoir.
Co-authored-by: copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Ensure pending proposals are ommited in the participation calculation.
@rickstaa rickstaa requested a review from Copilot February 5, 2026 10:33
@rickstaa
Copy link
Member Author

rickstaa commented Feb 5, 2026

@ECWireless ready for your review.

Copy link
Contributor

Copilot AI left a 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;
Copy link

Copilot AI Feb 5, 2026

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:

  1. Adding a comment documenting this expected behavior
  2. Adding validation to warn if unexpected duplicate activations are encountered
  3. 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.

Suggested change
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 }
);

Copilot uses AI. Check for mistakes.
if (start === null) {
start = round;
}
} else if (start !== null && round >= start) {
Copy link

Copilot AI Feb 5, 2026

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.

Suggested change
} 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;
}

Copilot uses AI. Check for mistakes.
round: Number(d.deactivationRound),
type: "deactivation" as const,
})),
].sort((a, b) => a.round - b.round || (a.type === "deactivation" ? -1 : 1));
Copy link

Copilot AI Feb 5, 2026

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.

Suggested change
].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;
});

Copilot uses AI. Check for mistakes.
Comment on lines +73 to +75
const isActiveProposal = (voteStart: string, currentRoundId?: string) =>
currentRoundId ? Number(voteStart) <= Number(currentRoundId) : true;

Copy link

Copilot AI Feb 5, 2026

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.

Suggested change
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);
};

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant