Skip to content

feat(staking): support the two-step operator rotation - #104

Merged
MuncleUscles merged 1 commit into
v0.19-devfrom
feat/proof-bearing-operator-transfer
Aug 18, 2026
Merged

MuncleUscles merged 1 commit into
v0.19-devfrom
feat/proof-bearing-operator-transfer

Conversation

@kirilaa

@kirilaa kirilaa commented Aug 18, 2026

Copy link
Copy Markdown

Depends-On: genlayerlabs/genlayer-consensus#1290

What

Adds initiate_operator_transfer / complete_operator_transfer (plus
cancel_operator_transfer and the get_pending_operator view), and the
proof-of-possession helper they need.

Why

CON-715 removed ValidatorWalletBlueprint.setOperator in favour of a two-step
rotation. Against consensus #1290 the Python SDK fails at the first step:

ABIFunctionNotFound: ("The function 'setOperator' was not found in this ",
                      "contract's abi.")

That is currently red on every e2e run for this closure — 045_validator_set_operator
and 079_operator_key_rotation in the genlayer-py (dev-env) lane, with the
same failure reproducing in the js and cli lanes for the same reason.

set_operator is kept, since consensus v0.6-dev still exposes it and the
default e2e matrix pins that branch.

The proof binding

This SDK had no possession-proof support, so genlayer_py.staking.operator_registration
is new. It mirrors genlayer-js's createOperatorRegistration: keccak over
abi.encode(domain, chainId, registrar, owner, pubKey[0], pubKey[1]), signed
EIP-191 by the operator key.

The registrar differs by flow, and getting it wrong fails silently — the proof
just does not verify:

Flow Verified by Registrar
validatorJoin ValidatorWalletFactory the factory
initiateOperatorTransfer the wallet the wallet

get_operator_transfer_context builds the wallet-bound context and reads
owner() from the wallet rather than assuming the caller is the owner, so a
mismatch surfaces as a clear error instead of an onlyOwner revert.

Cross-language vector

Both SDKs sign proofs the same contract verifies, so the test asserts the
exact bytes genlayer-js asserts — domain hash, message hash, and signature —
not merely internal consistency. A divergence here would be a real
interoperability break.

Known gap, deliberately not in this PR

validator_join still encodes the retired validatorJoin() /
validatorJoin(address) overloads that CON-666 replaced with
validatorJoin(uint256[2],bytes). It is the same migration and now has the
helper it needs, but it changes a public signature, so it deserves its own
change and its own review.

Validation

  • pytest tests/unit — 141 passing.

CON-715 removed ValidatorWalletBlueprint.setOperator in favour of
initiateOperatorTransfer + completeOperatorTransfer. Against a consensus
deployment carrying that change, set_operator fails with
ABIFunctionNotFound: the function 'setOperator' was not found in this
contract's abi.

Adds the two calls plus cancel_operator_transfer and the get_pending_operator
view, and keeps set_operator for deployments that still expose it.

This SDK had no proof-of-possession support at all, so the encoding is new
here: genlayer_py.staking.operator_registration mirrors genlayer-js's
createOperatorRegistration — keccak over abi.encode(domain, chainId, registrar,
owner, pubKey[0], pubKey[1]), signed EIP-191 by the operator key. Both SDKs
sign proofs the same contract verifies, so the test pins the exact vector
genlayer-js asserts (domain hash, message hash and signature bytes) rather than
only checking internal consistency.

The registrar differs by flow and is the easy thing to get wrong: the factory
verifies a validator join, the wallet verifies a rotation. get_operator_transfer_context
builds the wallet-bound context and reads owner() from the wallet rather than
assuming the caller is the owner, and initiate_operator_transfer verifies the
proof locally so a factory-bound one fails with a clear message instead of an
opaque revert. A test pins that a join proof does not verify for a rotation.

Not addressed here: validator_join still encodes the retired validatorJoin() /
validatorJoin(address) overloads, which CON-666 replaced with
validatorJoin(uint256[2],bytes). That is the same migration and now has the
helper it needs, but it changes a public signature, so it wants its own change.

Verified: pytest tests/unit — 141 passing.
@coderabbitai

coderabbitai Bot commented Aug 18, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e3370f84-e174-4746-85e7-c20ef7b683cd

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@MuncleUscles
MuncleUscles marked this pull request as ready for review August 18, 2026 13:14
@MuncleUscles
MuncleUscles merged commit 2a689c0 into v0.19-dev Aug 18, 2026
13 of 15 checks passed
@MuncleUscles
MuncleUscles deleted the feat/proof-bearing-operator-transfer branch August 18, 2026 14:13
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.

2 participants