Skip to content

temp - #498

Merged
pankajjagtapp merged 3 commits into
pankaj/feat/non-eigenpod-withdrawal-credentialsfrom
pankaj/temp-consolidation-target-review
Aug 27, 2026
Merged

temp#498
pankajjagtapp merged 3 commits into
pankaj/feat/non-eigenpod-withdrawal-credentialsfrom
pankaj/temp-consolidation-target-review

Conversation

@pankajjagtapp

@pankajjagtapp pankajjagtapp commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Scratch PR for reviewing a diff. Not for merge. Based on
pankaj/feat/non-eigenpod-withdrawal-credentials rather than master, so the
diff is only the three new commits instead of all of #485.

Addresses the review conclusion on #485: "link them all, gate that function via
multisig, and make the change to the consolidation function."

Commits

SHA What
6ffd3d1c test: pin that a live pod constrains the consolidation target
5daa413f feat(enm): require consolidation targets to be EtherFi-owned
80421985 feat(enm): gate linkLegacyValidatorIds behind the operating multisig

Why the target guard exists

EIP-7251's process_consolidation_request requires only that the target hold
0x02 credentials. There is no source/target same-address rule — the only
withdrawal_credentials[12:] check in the spec is against source_address.
A pod vets the target only when the pod is the caller, so a pod-less node
calling the predeploy directly had no target constraint at all.

test_livePod_rejectsAForeignConsolidationTarget records the pre-existing
control on the pod-backed path: mainnet's EigenPod reverts
ValidatorNotActiveInPod on an unproven target. That is what pod-less nodes
were missing.

The guard

Three grounds to accept, deny otherwise:

  • src == target — a switch to compounding, moves no value
  • linked in etherFiNodeFromPubkeyHash — one of ours
  • ACTIVE in the source's own pod — EigenLayer merkle-verified the credentials

Cross-credential consolidation still works, which is the point of the feature:
a target on another node passes the second branch. ACTIVE rather than
!= INACTIVE because WITHDRAWN passes the looser check and the consensus
layer drops a withdrawn target, burning the fee and logging a consolidation
that never happens.

The role gate

The guard trusts etherFiNodeFromPubkeyHash, and linkLegacyValidatorIds
writes that map while verifying nothing about the pair. On the same role as
requestConsolidation, one key could link a validator it owned and then
consolidate into it. Now onlyOperatingMultisig (4-of-7), matching
setProofSubmitter. The power to grow the trusted set is separate from the
power to use it.

test_linkLegacyValidatorIds asserts that directly: an address holding only
EXECUTOR_OPERATIONS_ROLE is rejected.

Deployment order

The ~23k legacy backfill must run before this ships, or every one of ~73
batches needs a 4-of-7 signature. Ops runbook and workflow live in
protocol-ops.

Verification

Full suite 1679/1684. The 5 failures are pre-existing and environmental (a
NotRegistered setUp and 6 suites needing OP_RPC_URL), reproduced with these
changes reverted.

Known limit

The registry branch is trusted bookkeeping, not proof. The backfill's beacon
verification closes that for the legacy set and the pod-ACTIVE branch is
cryptographic, but a future mislink by the Safe would still be accepted. A
Lido-style beacon proof of the target's credentials is the full fix, as a
follow-up.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Note

High Risk
Changes staking consolidation authorization and who can populate the pubkey registry—both directly affect where validator ETH can be directed.

Overview
Closes a consolidation theft vector on pod-less nodes: EIP-7251 only checks that the target has 0x02 credentials, so requestConsolidation now always validates each target before forwarding. Allowed targets are switch-to-compounding (src == target), any pubkey in etherFiNodeFromPubkeyHash, or an ACTIVE validator in the source pod (not merely non-INACTIVE, to avoid withdrawn targets). Anything else reverts with UnknownConsolidationTarget.

linkLegacyValidatorIds is moved from executor ops to onlyOperatingMultisig, so the role that can extend the trusted pubkey map cannot also submit consolidations that rely on it. Tests and fixtures are updated for the new role split and for rejecting foreign consolidation targets on both pod-less and live-pod paths.

Reviewed by Cursor Bugbot for commit 8042198. Bugbot is set up for automated code reviews on this repo. Configure here.

Documents the control this PR removes for pod-less nodes. A live pod reverts
ValidatorNotActiveInPod on a consolidation target it has not proven, so a
pod-backed node cannot consolidate into an outside validator. The pod-less
path applies no target check.

322/322 behaviour tests pass. Not pushed: PR is in audit.
The target decides where a source's balance lands, and nothing below us
checks it. EIP-7251's process_consolidation_request only requires the target
to hold 0x02 credentials; there is no source/target same-address rule. A pod
vets the target only when the pod is the caller, so a pod-less node calling
the predeploy directly had no target constraint at all. One compromised
EXECUTOR_OPERATIONS_ROLE key could have moved every pod-less validator's
stake to a validator it owned.

Accept a target on three grounds, deny otherwise:
  - src == target: a switch to compounding, moves no value.
  - linked in etherFiNodeFromPubkeyHash: one of ours.
  - ACTIVE in the source's own pod: EigenLayer merkle-verified its
    credentials via verifyWithdrawalCredentials.

Cross-credential consolidation still works, which is the point of the
feature: a target on another node passes the second branch. Only targets we
have no record of are rejected.

ACTIVE rather than != INACTIVE: WITHDRAWN passes that looser check, and the
consensus layer drops a withdrawn target, burning the fee and logging a
consolidation that never happens.

Note the registry branch is only as strong as the registry, and
linkLegacyValidatorIds writes it under this same role. Moving that function
to a stricter role is required for this guard to hold against a compromised
key, and is tracked separately.

324/324 behaviour tests pass on a mainnet fork.
Completes the separation the consolidation target guard depends on. That guard
accepts a target when it is linked in etherFiNodeFromPubkeyHash, and
linkLegacyValidatorIds writes that map while verifying nothing about the pair:
it reads DEPRECATED_etherfiNodeAddress[id] and stores whatever pubkey the
caller passed. Sharing EXECUTOR_OPERATIONS_ROLE with requestConsolidation left
one key able to link a validator it owned and then consolidate into it, so the
guard was a speed bump rather than a control.

onlyOperatingMultisig (OPERATION_MULTISIG_ROLE, 4-of-7), matching
setProofSubmitter. The power to grow the trusted set is now separate from the
power to use it: a compromised executor key can still shuffle value between
validators we already own, which is recoverable griefing, but cannot introduce
a new destination.

Timelock was the alternative. Multisig is enough here because the Safe is
already the trust root, and a 2-day delay on linking a newly discovered legacy
key buys nothing.

test_linkLegacyValidatorIds now asserts the separation directly: an address
holding only EXECUTOR_OPERATIONS_ROLE is rejected. It uses a fresh address
because the shared fixture actors hold several roles at once, and startPrank
because a nested role-id read consumes a single-call prank.

Fixtures that link as setup were granted the multisig role. Deployment order
matters: the ~23k legacy backfill must run BEFORE this ships, or every batch
needs a 4-of-7 signature. The ops runbook records that.

Full suite: 1679/1684. The 5 failures are pre-existing and environmental (a
NotRegistered setUp and 6 suites needing OP_RPC_URL), reproduced with these
changes reverted.
@pankajjagtapp
pankajjagtapp merged commit 8042198 into pankaj/feat/non-eigenpod-withdrawal-credentials Aug 27, 2026
2 checks passed
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