Skip to content

feat: support ACP-236 auto-renewed staking commands#28

Open
anishnar wants to merge 1 commit into
mainfrom
anish/acp236-add-auto-renewed-validator
Open

feat: support ACP-236 auto-renewed staking commands#28
anishnar wants to merge 1 commit into
mainfrom
anish/acp236-add-auto-renewed-validator

Conversation

@anishnar

@anishnar anishnar commented Jun 3, 2026

Copy link
Copy Markdown

Summary

  • add platform validator add-auto-renewed for ACP-236 auto-renewed validator creation
  • add platform validator set-auto-config for next-cycle config updates and graceful cycle-end exit
  • remove the manual reward-auto command because RewardAutoRenewedValidatorTx is consensus-issued only
  • fetch validatorAuthority from platform.getCurrentValidators for SetAutoRenewedValidatorConfigTx signing
  • use native AvalancheGo ACP-236 wallet builders for AddAutoRenewedValidatorTx and SetAutoRenewedValidatorConfigTx
  • pin AvalancheGo to the helicon-devnet pseudo-version v1.14.3-0.20260603151011-1339ef45dc6c
  • add deterministic command, P-Chain, and devnetcompat coverage for ACP-236 field mapping, serialization, authority lookup, issue behavior, and validation errors

Notes

  • RewardAutoRenewedValidatorTx is issued by the consensus engine at cycle end, analogous to classic reward validator handling. It is not a user-facing CLI command and is not expected to succeed through manual issueTx.
  • add-auto-renewed now calls the native NewAddAutoRenewedValidatorTx builder instead of sizing fees through AddPermissionlessValidatorTx, avoiding underfunding risk under dynamic fees.
  • set-auto-config loads the active validator's validatorAuthority owner from current validators and signs the owner-auth path with that state.
  • This PR still depends on unreleased AvalancheGo ACP-236 APIs from helicon-devnet, so it should wait for upstream release readiness before merge.

Closes #20

Devnet E2E

  • network ID: 76
  • RPC: https://api.avax-dev.network
  • prior add-auto-renewed accepted: 2eZGX56X7v67fHBvaLo7pn6N8sqHH5BwXNziCqymaVWDVo9NPM
  • prior set-auto-config accepted: 2StJDneDaDZLD3vDntzqPRX5C2698tDCGsZ2RperLaFdoSJavb
  • post-regression-test add-auto-renewed accepted: 2jcpUhCU8yJBg7PKWVB5BstVBrcNbi81D3SKsouMHXTZPgkPNt
  • post-regression-test set-auto-config accepted: 9XjDvMtUpsvwsSiet66CARZXcziC1es7ih81vQfV49EkcWpEL
  • current-validator check after the CLI set showed weight: 2000000000000, period: 172800, autoCompoundRewardShares: 400000, and the expected validatorAuthority
  • reward-auto is intentionally not part of the user E2E flow because it is node-issued/internal

Tests

  • go test ./cmd ./pkg/pchain
  • go test -tags devnetcompat ./pkg/pchain
  • go test ./...
  • git diff --check
  • GOTOOLCHAIN=go1.25.9+auto go run golang.org/x/vuln/cmd/govulncheck@v1.2.0 ./...
  • go run . validator add-auto-renewed --help
  • go run . validator set-auto-config --help

@anishnar anishnar changed the title Add auto-renewed validator command scaffold Support ACP-236 auto-renewed staking commands Jun 4, 2026
@anishnar anishnar changed the title Support ACP-236 auto-renewed staking commands feat: support ACP-236 auto-renewed staking commands Jun 4, 2026
Comment thread cmd/validator.go Fixed

@owenwahlgren owenwahlgren left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: ACP-236 auto-renewed staking commands

Thanks for this — the Go itself is high quality: clean DI seams (the validator*Fn function vars), config structs, %w error wrapping, no secret logging, and three solid layers of tests (command validation, pchain unit, build-tagged devnetcompat serialization). My concerns are mostly architectural and merge-readiness, not code style. Requesting changes on the three high-severity items below.


🔴 High / Blockers

1. reward-auto is invalid by design — remove it.
RewardAutoRenewedValidatorTx is issued automatically by the consensus engine at the end of each validation cycle (same model as the classic RewardValidatorTx). It has no inputs/credentials and cannot be issued by a user wallet via the mempool/API — now or after devnet "supports" the type. The PR description frames the unsupported transaction type rejection as a temporary devnet gap ("cannot be accepted until devnet AvalancheGo supports that tx path"), but it's a permanent architectural constraint. That framing should be corrected so nobody later tries to "fix" it by waiting on devnet.

Please remove the whole path:

  • validatorRewardAutoCmd + its init() registration (cmd/validator.go)
  • valRewardAutoTxID / valRewardAutoTime vars and flag defs
  • parseRewardAutoTimestamp + TestParseRewardAutoTimestamp (cmd/helpers_test.go)
  • RewardAutoRenewedValidator / RewardAutoRenewedValidatorConfig / issueRewardAutoRenewedValidatorTx and the validatorRewardAutoRenewedValidatorFn seam (pkg/pchain/pchain.go, cmd/validator.go)
  • the reward-auto reset lines in resetValidatorAutoCommandState

That leaves add-auto-renewed and set-auto-config, which are genuinely user-issuable — a cleaner, defensible surface.

2. Pinning main to an unreleased avalanchego devnet branch.
avalanchego v1.14.3-0.20260602193739-919446e8501f (plus matching graft/coreth, graft/evm, libevm) is a sae-devnet-5 pseudo-version. Merging to main couples the entire CLI — every existing command — to a pre-release SDK branch and makes a clean tagged release impossible. This should stay on a feature branch (or behind a build tag) until ACP-236 lands in an actual avalanchego release. At minimum, the PR should explicitly call out that main would knowingly track a devnet branch.

3. Fee under-funding risk in add-auto-renewed on Fuji/Mainnet.
issueAddAutoRenewedValidatorTx (pkg/pchain/pchain.go) builds an AddPermissionlessValidatorTx purely to drive UTXO selection + stake outputs, then repackages BaseTx/StakeOuts into a larger AddAutoRenewedValidatorTx (extra fields: ValidatorAuthority, AutoCompoundRewardShares, Period, ValidatorNodeID, DelegationShares). Under Etna dynamic fees the burned amount was sized for the smaller tx's complexity, but the node charges on the actual (bigger) tx bytes. It passed on devnet almost certainly because devnet gas price is at the floor; on Fuji/Mainnet under load this can be rejected as underfunded. Please verify the fee delta is covered (buffer, or confirm the builder over-selects) and validate on Fuji before this is trusted with mainnet funds.


🟡 Medium

4. SetAutoRenewedValidatorConfig is structurally fragile. It borrows NewDisableL1ValidatorTx to mint the auth, copies authTx.DisableAuth into SetAutoRenewedValidatorConfigTx.Auth, and newPWalletWithOwner hand-builds a backend with an owners map keyed by txID → authority. This works only because the disable-L1 builder resolves the auth owner from the backend's owners map. If avalanchego changes how that builder sources the disable owner (e.g. reads chain state), this breaks silently. Reasonable workaround for missing wallet helpers, but please add a comment explaining why NewDisableL1ValidatorTx is borrowed and a TODO to swap to a native builder once the SDK exposes one.

5. GetAutoRenewedValidatorAuthority fetches all current validators, then linear-scans. The request sends empty args, so on Mainnet that's a multi-thousand-entry JSON payload to find one txID. The hand-rolled JSON structs are justified (the typed client likely doesn't expose validatorAuthority yet), but consider a nodeIDs/subnetID filter if the API supports narrowing.

6. Silent period truncation at the library layer. uint64(cfg.Period / time.Second) truncates sub-second input. The cmd layer guards this (parseAutoRenewPeriod), but pkg/pchain is importable as a library, so a direct caller gets silent truncation. Consider validating Period % time.Second == 0 inside the pchain functions too.


🟢 Low / Nits

  • --stake help says "(min 2000)" on add-auto-renewed, but the command enforces netConfig.MinValidatorStake (1 on Local/Fuji). Mirrors the existing add flag, so pre-existing, but misleading on testnets.
  • parseRewardAutoTimestamp's timestamp > uint64(1<<63-1) is correct (Go binds << tighter than -), but math.MaxInt64 would read more clearly. (Moot if #1 removes this function.)

✅ Strengths

  • Test coverage is excellent — table-driven command validation, injected-function pchain unit tests, and //go:build devnetcompat tests that actually run SyntacticVerify + round-trip serialize. Right way to test tx construction without a live node.
  • Clean DRY refactor: feeToShares now delegates to fractionToShares with no behavior change, covered by TestFractionToShares.
  • Follows project conventions: input validation before tx building, %w wrapping, no panics, no key/secret logging, DI matching the existing getNetworkConfig/loadPChainWallet pattern.

Verdict: The code is well-built and well-tested; the blockers are release-integrity issues, not code quality. #1 (a tx users can't issue) and #2 (devnet-branch dependency) should be resolved before merge to main, and #3 needs a real Fuji validation. Medium items are fine as follow-ups with TODO comments.

@owenwahlgren

owenwahlgren commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Hey @anishnar — full pass done. Functionally solid; main asks before merge:

  1. Rebase onto refactor: replace func-typed injection seams in pkg/pchain with interfaces #30 once merged — it replaces the func-typed injection seams in pkg/pchain with small issuer interfaces. Apply the same pattern to issueAddAutoRenewedValidatorTx / issueSetAutoRenewedValidatorConfigTx (or skip injection entirely and unit-test the pure cfg→args mapping).
  2. Drop the ...Fn package globals in cmd/validator.go — test the pchain layer instead of rewiring the existing commands' production paths.
  3. go.mod — the helicon-devnet avalanchego pseudo-version needs to be a tagged release before merge (as you noted).
  4. Smaller: filter GetAutoRenewedValidatorAuthority by nodeID instead of fetching the full validator set; avoid the triple network fetch in set-auto-config; add a max-period bound; add Help/MissingArgs e2e cases; use reward.PercentDenominator; drop the CI Go bump (covered by fix: upgrade Go to 1.25.11 and x/net to resolve stdlib vulnerabilities #31).

Happy to pair on the rebase. 🙏

owenwahlgren added a commit that referenced this pull request Jun 10, 2026
…faces (#30)

* refactor: replace func-typed injection seams in pkg/pchain with interfaces

The issue*Tx helpers took the wallet call as a function-typed parameter
so unit tests could stub it. With multi-parameter builders the inline
function types grew longer than the helper bodies (and the pattern was
recently copied into an even larger 10-parameter signature in #28).

Replace each seam with a single-method issuer interface mirroring the
corresponding avalanchego wallet/chain/p method, so w.PWallet()
satisfies them directly and tests stub a small struct instead of a
closure. No behavior change: exported API, error messages, and
wrapping are identical. Also gofmt's five files that were unformatted
on main (same whitespace fixes as #29).

* fix: upgrade Go to 1.25.11 and golang.org/x deps to resolve stdlib vulnerabilities

govulncheck fails CI on main: GO-2026-5037 (crypto/x509), GO-2026-4971
(net), GO-2026-4918 (net/http + golang.org/x/net) are reachable from
keystore key generation, wallet dialing, and network ID lookup. All are
fixed in go1.25.10/1.25.11 and x/net v0.53.0.

- go.mod: go 1.25.9 -> 1.25.11; x/net v0.47.0 -> v0.53.0 (pulls
  x/crypto, x/sys, x/term, x/text et al. forward)
- ci.yml: bump setup-go and GOTOOLCHAIN pins to 1.25.11

govulncheck now reports 0 reachable vulnerabilities.

---------

Co-authored-by: Martin Eckardt <m.eckardt@outlook.com>
owenwahlgren added a commit that referenced this pull request Jun 15, 2026
…faces

The issue*Tx helpers took the wallet call as a function-typed parameter
so unit tests could stub it. With multi-parameter builders the inline
function types grew longer than the helper bodies (and the pattern was
recently copied into an even larger 10-parameter signature in #28).

Replace each seam with a single-method issuer interface mirroring the
corresponding avalanchego wallet/chain/p method, so w.PWallet()
satisfies them directly and tests stub a small struct instead of a
closure. No behavior change: exported API, error messages, and
wrapping are identical. Also gofmt's five files that were unformatted
on main (same whitespace fixes as #29).
owenwahlgren added a commit that referenced this pull request Jun 15, 2026
Add `validator add-auto-renewed` and `validator set-auto-config`, built on
the interface-based issuer seams from the pkg/pchain refactor.

Addressing review feedback on #28:
- replace the func-typed injection seams for the auto-renewed issuers with
  small single-method interfaces (autoRenewedValidatorTxIssuer,
  setAutoRenewedValidatorConfigTxIssuer), matching the rest of pkg/pchain;
  the native pwallet IssueAddAutoRenewedValidatorTx / IssueSetAutoRenewed-
  ValidatorConfigTx methods satisfy them
- drop the validator*Fn package globals in cmd/validator.go; the commands
  call the production pchain functions directly and coverage lives in the
  pchain layer + e2e Help/MissingArgs cases
- narrow GetAutoRenewedValidatorAuthority via the getCurrentValidators
  nodeIDs filter (new optional --node-id) instead of fetching the full set
- build the owner-authorized wallet once via wallet.NewWalletFromKeychain-
  WithOwner, removing the redundant P-Chain state fetch in set-auto-config
- bound the auto-renewal period by network MaxStakeDuration
- use reward.PercentDenominator instead of a literal 1_000_000

The avalanchego dependency still tracks the unreleased helicon-devnet
pseudo-version because the ACP-236 tx types and wallet builders are not yet
in a tagged release; this PR should wait for upstream release readiness.

Co-authored-by: anishnar <anish@avalabs.org>
@owenwahlgren owenwahlgren force-pushed the anish/acp236-add-auto-renewed-validator branch from 636ecf1 to 7bb028b Compare June 15, 2026 16:45
owenwahlgren added a commit that referenced this pull request Jun 15, 2026
Add `validator add-auto-renewed` and `validator set-auto-config`, built on
the interface-based issuer seams from the pkg/pchain refactor.

Addressing review feedback on #28:
- replace the func-typed injection seams for the auto-renewed issuers with
  small single-method interfaces (autoRenewedValidatorTxIssuer,
  setAutoRenewedValidatorConfigTxIssuer), matching the rest of pkg/pchain;
  the native pwallet IssueAddAutoRenewedValidatorTx / IssueSetAutoRenewed-
  ValidatorConfigTx methods satisfy them
- drop the validator*Fn package globals in cmd/validator.go; the commands
  call the production pchain functions directly and coverage lives in the
  pchain layer + e2e Help/MissingArgs cases
- narrow GetAutoRenewedValidatorAuthority via the getCurrentValidators
  nodeIDs filter (new optional --node-id) instead of fetching the full set
- build the owner-authorized wallet once via wallet.NewWalletFromKeychain-
  WithOwner, removing the redundant P-Chain state fetch in set-auto-config
- bound the auto-renewal period by network MaxStakeDuration
- use reward.PercentDenominator instead of a literal 1_000_000

The avalanchego dependency still tracks the unreleased helicon-devnet
pseudo-version because the ACP-236 tx types and wallet builders are not yet
in a tagged release; this PR should wait for upstream release readiness.

Co-authored-by: anishnar <anish@avalabs.org>
@owenwahlgren owenwahlgren force-pushed the anish/acp236-add-auto-renewed-validator branch from 7bb028b to 74c7ece Compare June 15, 2026 17:01
Add `validator add-auto-renewed` and `validator set-auto-config`, built on
the interface-based issuer seams from the pkg/pchain refactor.

Addressing review feedback on #28:
- replace the func-typed injection seams for the auto-renewed issuers with
  small single-method interfaces (autoRenewedValidatorTxIssuer,
  setAutoRenewedValidatorConfigTxIssuer), matching the rest of pkg/pchain;
  the native pwallet IssueAddAutoRenewedValidatorTx / IssueSetAutoRenewed-
  ValidatorConfigTx methods satisfy them
- drop the validator*Fn package globals in cmd/validator.go; the commands
  call the production pchain functions directly and coverage lives in the
  pchain layer + e2e Help/MissingArgs cases
- narrow GetAutoRenewedValidatorAuthority via the getCurrentValidators
  nodeIDs filter (new optional --node-id) instead of fetching the full set
- build the owner-authorized wallet once via wallet.NewWalletFromKeychain-
  WithOwner, removing the redundant P-Chain state fetch in set-auto-config
- bound the auto-renewal period by network MaxStakeDuration
- use reward.PercentDenominator instead of a literal 1_000_000

The avalanchego dependency still tracks the unreleased helicon-devnet
pseudo-version because the ACP-236 tx types and wallet builders are not yet
in a tagged release; this PR should wait for upstream release readiness.

Co-authored-by: anishnar <anish@avalabs.org>
@owenwahlgren owenwahlgren force-pushed the anish/acp236-add-auto-renewed-validator branch from 74c7ece to 0f2f75e Compare June 15, 2026 17:08
@owenwahlgren

Copy link
Copy Markdown
Collaborator

Pushed a pass addressing the review feedback (rebased onto main, which now includes the #30 issuer-interface refactor). CI is green; the diff is now purely the ACP-236 feature.

Review checklist

  1. Interface seams for the auto-renewed issuers — replaced the func-typed injection with two single-method interfaces (autoRenewedValidatorTxIssuer, setAutoRenewedValidatorConfigTxIssuer), matching the rest of pkg/pchain. The native wallet methods IssueAddAutoRenewedValidatorTx / IssueSetAutoRenewedValidatorConfigTx satisfy them, so the Builder+IssueUnsignedTx split is gone.
  2. Dropped the validator*Fn package globals in cmd/validator.go — the commands call the production pchain functions directly. Coverage moved to the pchain layer (stubbed interface tests + httptest-backed authority lookup) plus e2e Help/MissingArgs cases.
  3. go.mod — still pins the unreleased helicon-devnet avalanchego pseudo-version (the ACP-236 tx types/builders aren't in a tagged release yet). This remains the merge blocker; needs an upstream tag first.
  4. Smaller items:
    • GetAutoRenewedValidatorAuthority now narrows via the getCurrentValidators nodeIDs filter (new optional --node-id on set-auto-config) instead of fetching the full set.
    • The owner-authorized wallet is built once via wallet.NewWalletFromKeychainWithOwner, removing the redundant P-Chain state fetch in set-auto-config.
    • Added a max-period bound (MaxStakeDuration) on both commands.
    • Added Help/MissingArgs e2e cases for add-auto-renewed and set-auto-config.
    • fractionToShares uses reward.PercentDenominator instead of the literal 1_000_000.
    • Dropped the CI Go bump (covered by fix: upgrade Go to 1.25.11 and x/net to resolve stdlib vulnerabilities #31); .github/ diff vs main is now empty.

Note: the owner-backed wallet is still required for set-auto-config because the public builder resolves the config owner from the backend's owners map (builder.authorizebackend.GetOwner), not from chain state — documented inline.

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.

Support ACP-236 auto-renewed staking P-Chain transaction types

3 participants