Enhances staking info and validator management#269
Conversation
- Show current epoch with validators, weight, slashed - Show previous epoch with inflation, claimed, unclaimed, slashed - Add --epoch option to query specific epoch data - Clean text output instead of JSON - Update docs with new output format
- Add `staking validators` command showing validator set table with: - Moniker, address, self stake, delegated stake - Pending deposits/withdrawals with amounts - Weight percentage (voting power) - Status (active, quarantined, banned, primed, pending) - Owner/operator role indicators - Add `staking validator-history` command showing: - Slash events (idleness penalties) with percentage - Reward events (ValidatorPrime) with validator/delegator rewards - Timestamps, epochs, and block numbers - Summary with totals - Update staking commands to use positional args (backwards compatible): - `validator-info 0x...` instead of `--validator 0x...` - Same for deposit, exit, claim, prime, delegator commands - Add slashed data to epoch-info output
Only show truly pending deposits (epoch + 2 > current) and withdrawals (epoch + 7 > current) instead of all historical ones. Also add quarantined-validators and banned-validators commands to docs.
WalkthroughThis PR updates staking CLI UX: adds Changes
Sequence DiagramsequenceDiagram
actor CLI as CLI User
participant Cmd as CLI Command
participant Action as ValidatorHistoryAction
participant Client as PublicClient
participant Contracts as Staking/Slashing Contracts
participant Table as CLI Table Renderer
CLI->>Cmd: run `validator-history [validator]`
Cmd->>Action: execute(options)
Action->>Action: resolve network/config & validate validator
Action->>Client: create public client
Action->>Contracts: resolve staking & slashing contract addresses
par Fetch events
Action->>Contracts: fetch slash events for validator
Action->>Contracts: fetch reward events (all)
end
Action->>Action: filter reward events for validator
Action->>Action: collect unique blockNumbers
Action->>Client: batch fetch block timestamps
Client-->>Action: return timestamps
Action->>Action: attach timestamps, convert logs → typed events
Action->>Action: sort events (desc), apply limit, compute totals
Action->>Table: format rows (Time, Epoch, Type, Details, TxId/Block)
Table-->>CLI: display table
Action-->>CLI: print summary (counts, totals, hint if truncated)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
README.md (1)
351-353: Minor inconsistency: Example uses old option syntax.Line 312 shows the updated positional argument syntax
validator-info [validator], but this example still uses--validator 0x.... Consider updating for consistency:# Check validator info - genlayer staking validator-info --validator 0x... + genlayer staking validator-info 0x...
🧹 Nitpick comments (6)
src/commands/staking/stakingInfo.ts (2)
214-217: Consider validating the epoch parameter.If a user passes an invalid string (e.g.,
--epoch abc),BigInt(options.epoch)will throw an unhandled exception. Consider adding validation:// If specific epoch requested, show just that epoch's data if (options.epoch !== undefined) { + const epochNum = (() => { + try { + const n = BigInt(options.epoch); + if (n < 0n) throw new Error("Epoch must be non-negative"); + return n; + } catch { + this.failSpinner(`Invalid epoch number: ${options.epoch}`); + return null; + } + })(); + if (epochNum === null) return; - const epochNum = BigInt(options.epoch); const epochData = await client.getEpochData(epochNum);
444-454: Hardcoded weight formula constants may drift from contract.The weight calculation replicates the contract formula with hardcoded
ALPHA = 0.6andBETA = 0.5. If these change in the contract, this display will show incorrect weights.Consider either:
- Adding a comment noting where these values come from and when they should be updated
- Fetching these from the contract if exposed
+ // Weight formula mirrors staking contract parameters. + // If contract changes ALPHA/BETA, update here accordingly. + // See: contracts/Staking.sol or equivalent const ALPHA = 0.6; const BETA = 0.5;src/commands/staking/index.ts (4)
13-13: Use@/*import alias for the new import (per repo guidelines).
import {ValidatorHistoryAction, ValidatorHistoryOptions} from "./validatorHistory";should follow the@/*alias convention (and ideally this file should be migrated consistently in a follow-up).
49-64: Avoidconsole.error+process.exit(1)in action handlers; DRY the repeated “required validator” logic.This pattern is repeated many times and makes testing/error handling harder than necessary. Prefer Commander’s
command.error(...)(or throw) and extract a small helper to resolve/validate positional-vs-option args.Example pattern (apply similarly across commands):
- .action(async (validatorArg: string | undefined, options: ValidatorDepositOptions) => { - const validator = validatorArg || options.validator; - if (!validator) { - console.error("Error: validator address is required"); - process.exit(1); - } + .action(async (validatorArg: string | undefined, options: ValidatorDepositOptions, command: Command) => { + const validator = validatorArg ?? options.validator; + if (!validator) command.error("Error: validator address is required", {exitCode: 1}); const action = new ValidatorDepositAction(); await action.execute({...options, validator}); });Also consider adjusting the
optionstypes in these handlers: many of the*Optionsinterfaces havevalidator: stringrequired, but with positional args + deprecated--validator, the parsed Commanderoptions.validatoris effectively optional at the handler boundary.Also applies to: 66-83, 84-118, 119-137, 138-163, 166-183, 185-202, 204-221, 238-255
259-266: Parse/validate--epochat the Commander layer (better UX + fewer downstream checks).Consider an option parser (or explicit validation) so
epoch-info --epoch foofails fast with a clear message (and so negative/out-of-range values are rejected consistently).
313-328:validator-historycommand registration looks good; consider a small help/validation polish.Given the action refuses localnet (no
eth_getLogs), you may want to mention that constraint in the command description or option help text; also consider validating--from-block/--limitas integers in the CLI layer for quicker feedback.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (6)
README.md(3 hunks)docs/validator-guide.md(1 hunks)package.json(1 hunks)src/commands/staking/index.ts(4 hunks)src/commands/staking/stakingInfo.ts(4 hunks)src/commands/staking/validatorHistory.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src/commands/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
All CLI action classes must extend
BaseActionfromsrc/lib/actions/BaseAction.tswhich provides GenLayer client initialization, keystore management, spinner/logging utilities, and user prompts
Files:
src/commands/staking/stakingInfo.tssrc/commands/staking/validatorHistory.tssrc/commands/staking/index.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use
@/*path alias to reference./src/*and@@/tests/*path alias to reference./tests/*in imports
Files:
src/commands/staking/stakingInfo.tssrc/commands/staking/validatorHistory.tssrc/commands/staking/index.ts
src/commands/*/index.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Commands must be organized in
src/commands/<domain>/index.tswith each file exporting aninitialize*Commands(program)function
Files:
src/commands/staking/index.ts
🧠 Learnings (7)
📚 Learning: 2025-07-10T23:50:34.628Z
Learnt from: epsjunior
Repo: genlayerlabs/genlayer-cli PR: 239
File: package.json:60-66
Timestamp: 2025-07-10T23:50:34.628Z
Learning: In the genlayer-cli project, dotenv is used with manual parsing via dotenv.parse() rather than automatic loading via dotenv.config(), so warnings about implicit .env.local auto-loading changes in dotenv v17 are not applicable to this project.
Applied to files:
package.json
📚 Learning: 2025-12-03T23:03:32.323Z
Learnt from: CR
Repo: genlayerlabs/genlayer-cli PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T23:03:32.323Z
Learning: Use `npm install` to install dependencies, `npm run dev` for watch mode development build using esbuild, `npm run build` for production build, and `node dist/index.js` to run CLI from source
Applied to files:
package.json
📚 Learning: 2025-09-03T13:20:22.582Z
Learnt from: epsjunior
Repo: genlayerlabs/genlayer-cli PR: 253
File: tests/actions/code.test.ts:78-84
Timestamp: 2025-09-03T13:20:22.582Z
Learning: In genlayer-cli, the CLI framework handles unhandled promise rejections globally, so errors outside try/catch blocks are still displayed to users appropriately.
Applied to files:
package.json
📚 Learning: 2025-12-03T23:03:32.323Z
Learnt from: CR
Repo: genlayerlabs/genlayer-cli PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T23:03:32.323Z
Learning: Applies to src/commands/**/*.ts : All CLI action classes must extend `BaseAction` from `src/lib/actions/BaseAction.ts` which provides GenLayer client initialization, keystore management, spinner/logging utilities, and user prompts
Applied to files:
src/commands/staking/stakingInfo.tssrc/commands/staking/index.ts
📚 Learning: 2025-12-03T23:03:32.323Z
Learnt from: CR
Repo: genlayerlabs/genlayer-cli PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T23:03:32.323Z
Learning: When adding commands, create action class extending `BaseAction` in `src/commands/<domain>/<action>.ts`, export action options interface, register in domain's `index.ts` via Commander, and add tests in `tests/commands/<domain>.test.ts` and `tests/actions/<action>.test.ts`
Applied to files:
src/commands/staking/index.ts
📚 Learning: 2025-12-03T23:03:32.323Z
Learnt from: CR
Repo: genlayerlabs/genlayer-cli PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T23:03:32.323Z
Learning: Applies to src/commands/*/index.ts : Commands must be organized in `src/commands/<domain>/index.ts` with each file exporting an `initialize*Commands(program)` function
Applied to files:
src/commands/staking/index.ts
📚 Learning: 2025-12-03T23:03:32.323Z
Learnt from: CR
Repo: genlayerlabs/genlayer-cli PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T23:03:32.323Z
Learning: Applies to src/index.ts : Main entry point is `src/index.ts` which initializes Commander program and registers all command groups
Applied to files:
src/commands/staking/index.ts
🧬 Code graph analysis (3)
src/commands/staking/stakingInfo.ts (1)
src/commands/staking/StakingAction.ts (2)
StakingConfig(12-17)formatAmount(145-147)
src/commands/staking/validatorHistory.ts (1)
src/commands/staking/StakingAction.ts (2)
StakingConfig(12-17)StakingAction(19-196)
src/commands/staking/index.ts (9)
src/commands/staking/validatorDeposit.ts (2)
ValidatorDepositOptions(5-8)ValidatorDepositAction(10-48)src/commands/staking/validatorClaim.ts (2)
ValidatorClaimOptions(5-7)ValidatorClaimAction(9-43)src/commands/staking/setOperator.ts (2)
SetOperatorOptions(5-8)SetOperatorAction(10-46)src/commands/staking/setIdentity.ts (2)
SetIdentityOptions(6-17)SetIdentityAction(19-78)src/commands/staking/delegatorJoin.ts (2)
DelegatorJoinOptions(5-8)DelegatorJoinAction(10-44)src/commands/staking/delegatorExit.ts (2)
DelegatorExitOptions(4-7)DelegatorExitAction(9-56)src/commands/staking/delegatorClaim.ts (2)
DelegatorClaimOptions(4-7)DelegatorClaimAction(9-41)src/commands/staking/stakingInfo.ts (2)
StakingInfoOptions(10-12)StakingInfoAction(14-566)src/commands/staking/validatorHistory.ts (2)
ValidatorHistoryOptions(30-34)ValidatorHistoryAction(56-259)
🔇 Additional comments (14)
src/commands/staking/stakingInfo.ts (4)
2-4: LGTM - Imports are appropriate for the new functionality.
449-454: LGTM - Precision is adequate for display percentages.Using floating-point for the weight calculation is acceptable since it's only for display purposes (showing relative percentages) rather than financial calculations.
501-515: LGTM - Pending filter logic is consistent with other methods.The filtering of truly pending deposits/withdrawals using
ACTIVATION_DELAY_EPOCHSandUNBONDING_PERIOD_EPOCHSconstants is consistent with the same logic ingetValidatorInfoandgetStakeInfo.
351-565: LGTM - Well-structured validator listing implementation.The implementation follows good practices:
- Parallel data fetching for performance
- Batch processing to avoid rate limiting
- Graceful handling of missing signer address
- Consistent error handling pattern
docs/validator-guide.md (1)
91-113: LGTM - Documentation accurately reflects the new epoch-info output.The updated example clearly shows the enhanced human-readable format with current/previous epoch details and staking requirements, matching the implementation in
stakingInfo.ts.src/commands/staking/validatorHistory.ts (6)
77-96: LGTM - Good early validation and fallback handling.The method properly:
- Checks for unsupported localnet early
- Falls back to signer address when validator not specified
- Validates the address is actually a validator before expensive log fetching
145-156: LGTM - Efficient batch fetching of block timestamps.Batching with
Promise.allis appropriate. Consider that if a single block fetch fails, the entire batch will fail. For robustness, you might wantPromise.allSettled, but this is likely fine for the typical case.
158-175: LGTM - Event transformation is correct.The type assertions for
log.argsare necessary due to viem's generic types. The fallback toDate(0)for missing timestamps is a safe default.
190-254: LGTM - Clear and informative display output.The output handling is well done:
- Graceful empty state handling
- Informative table with colored status indicators
- Summary shows totals from all events (not just limited display)
- Clear indication when results are truncated
61-75: [Rewritten review comment]
[Classification tag]
8-28: These event ABIs are hardcoded; verify they match the deployed staking and slashing contracts before deployment.The
SLASH_EVENT_ABIandREWARD_EVENT_ABIare used bypublicClient.getLogs()to fetch and parse events from deployed contracts. If these ABIs become out of sync with the actual contract implementations (e.g., after a contract upgrade or if field definitions change), event parsing will fail or produce incorrect results. Consider establishing a process to verify ABI compatibility against deployed contracts during QA and before any contract upgrades.package.json (1)
63-63: LGTM - Dependencies align with new features.The cli-table3 addition supports the new tabular validator display (used in
stakingInfo.tsandvalidatorHistory.ts), and the genlayer-js bump to ^0.18.9 includes necessary API additions for the staking commands. cli-table3@^0.6.5 is the latest available version on npm.src/commands/staking/index.ts (2)
301-312: Newstaking validatorscommand wiring looks clean and consistent with existing info commands.Nice addition; the options surface matches the other staking-info style commands.
119-137: No typo found. ThesetOperator.tsfile correctly usesabi.VALIDATOR_WALLET_ABI(all uppercase) on line 26, and theSetOperatorActionclass is properly implemented and imported.Likely an incorrect or invalid review comment.
| // Fetch reward events (not indexed, need to filter client-side) | ||
| const rewardLogs = await publicClient.getLogs({ | ||
| address: stakingAddress, | ||
| event: REWARD_EVENT_ABI, | ||
| fromBlock, | ||
| toBlock: "latest", | ||
| }); | ||
|
|
||
| // Filter rewards to this validator | ||
| const filteredRewardLogs = rewardLogs.filter( | ||
| log => (log.args as any).validator?.toLowerCase() === validatorAddress.toLowerCase() | ||
| ); |
There was a problem hiding this comment.
Performance concern: Unbounded reward log fetching.
Fetching all ValidatorPrime events from block 0 (or fromBlock) with client-side filtering could be expensive on a chain with many epochs. Since the validator field isn't indexed, all reward events must be downloaded.
Consider:
- Using a more recent default
fromBlock(e.g., last N epochs based on epoch duration) - Documenting that
--from-blockshould be used for older validators - Adding a warning when fetching from genesis
+ // Fetch reward events (not indexed, need to filter client-side)
+ // Note: This fetches ALL reward events which could be slow on long chains
const rewardLogs = await publicClient.getLogs({Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/commands/staking/validatorHistory.ts around lines 126 to 137, the code
unconditionally fetches all reward logs (potentially from genesis) then filters
client-side which can be extremely expensive; change the logic to use a safe
recent default fromBlock (e.g. compute a block window covering last N epochs
based on epoch duration or use now - X blocks) when none is provided, log a
warning if the user is about to fetch from genesis, and update the CLI
help/documentation to recommend using --from-block for long-lived validators;
implement the default-fromBlock calculation, emit a single warning when
fromBlock <= genesis or not provided, and ensure the --from-block flag remains
honored for older validators.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
tests/actions/staking.test.ts (1)
212-220: Use a fixed timestamp for test determinism.The mock uses
Date.now()to calculate the start timestamp, which makes the test non-deterministic. Tests should use fixed, known values for reproducibility and easier debugging.Apply this diff to use a fixed timestamp:
mockClient.getEpochData.mockResolvedValue({ - start: BigInt(Math.floor(Date.now() / 1000) - 3600), + start: 1704063600n, // Fixed timestamp: 2024-01-01T00:00:00Z - 3600 end: 0n, vcount: 5n, weight: 100000n, inflation: 1000n * BigInt(1e18), claimed: 500n * BigInt(1e18), slashed: 0n, });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
tests/actions/staking.test.ts(3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use
@/*path alias to reference./src/*and@@/tests/*path alias to reference./tests/*in imports
Files:
tests/actions/staking.test.ts
🔇 Additional comments (2)
tests/actions/staking.test.ts (2)
67-69: LGTM!The mock extensions for
getEpochDataandformatStakingAmountproperly support the new epoch-related functionality introduced in this PR.
266-266: The test expectation is correct and accurately matches the implementation. Line 243 ofsrc/commands/staking/stakingInfo.tscallsthis.succeedSpinner("Epoch info")with only a single argument, which is exactly what the test expects on line 266. While other similar methods likegetValidatorInfoandlistActiveValidatorsuse the pattern "{X} retrieved" with an additional data parameter, thegetEpochInfomethod intentionally uses a different pattern. This is not an error in the test.
Improves the staking command suite with enhanced info displays and validator management features.
The validator table includes details like self-stake, delegation stake, pending deposits/withdrawals, weight, and status.
The validator history command fetches and displays slash and reward events for a specified validator, useful for monitoring performance.
The epoch info display now includes both the current and previous epochs, providing a more comprehensive view of the staking status. Also adds specific epoch querying.
Summary by CodeRabbit
New Features
validatorstable view andvalidator-historyreport; new listings for quarantined and banned validators.epoch-infonow shows richer, human-readable current/next/previous epoch metrics, inflation, and stake details.Documentation
Tests
✏️ Tip: You can customize this high-level summary in your review settings.