Skip to content

ci: verify upstream PR 226 exact source - #1

Closed
maho0638 wants to merge 25 commits into
mainfrom
diag/validate-pr226-20261012
Closed

maho0638 wants to merge 25 commits into
mainfrom
diag/validate-pr226-20261012

Conversation

@maho0638

Copy link
Copy Markdown
Owner

Temporary fork-only verification for upstream PR genlayerlabs#226; source code is identical to its upstream PR head, plus a diagnostic GitHub Actions workflow. Do not merge. Close after collecting CI results.

MuncleUscles and others added 25 commits June 11, 2026 17:09
…y key = bytes32(0) (genlayerlabs#190)

The wildcard sentinel previously shared bytes32(0) with GenVM's natural
empty-method-name key (deploy, emit_transfer), making deploy-specific
Mode-2 allocations impossible. Per cross-team decision, the wildcard
moves to the untagged keccak256 of empty bytes
(0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470),
which is provably outside the derived-key space. DEPLOY_CALL_KEY drops
the bytes32(1) sentinel (GenVM ask withdrawn) and now equals
CALL_KEY_UNNAMED = bytes32(0), matching what GenVM actually emits.

Must land together with the consensus CALL_KEY_WILDCARD constant and the
node Mode-2 tree decode change in v0.6.
…layerlabs#195)

* feat(sdk): developer-NFT read/claim actions

Expose the on-chain developer-NFT reward flow: getDeveloperNft,
getClaimableRewardsFromFees, getClaimableRewardsFromInflation, claimNft
rewards/epochs. NFTMinter ABI + AddressManager-based address resolution.
Mirrors existing action patterns; unit tests added. (Codex-generated;
build + 90 tests green. Base is fix/callkey-wildcard-sentinel — rebase onto
v2-dev before PR.)

* fix(nonce): coerce eth_getTransactionCount hex string to number

getCurrentNonce returned the RPC's hex quantity string while the declared
return type (and callers) expect a number. On the local-account signing path
the raw string flows into viem's transaction serializer, which encodes the
ASCII characters as the nonce bytes — producing an unmineable transaction and
an infinite receipt wait. Affects any local-account consensus call (appeals,
top-ups) on network backends.
* feat: add vesting delegator actions

* feat(vesting): validator-leg actions (join/deposit/exit/claim, operator transfer, identity, wallet reads)

Implements the CON-607 Vesting.sol validator surface (consensus branch
con-607-vesting-lock-then-validate): vestingValidatorJoin/Deposit/Exit/
Claim, operator-transfer initiate/complete/cancel, setIdentity, and
getValidatorWallets/validatorWalletCount/validatorDeposited/
isValidatorWallet reads. Replaces the #1154 stub in vesting/validator.ts.
…ke) (genlayerlabs#194)

* feat: getCurrentEpoch + isValidatorBelowMinStake epoch-zero helpers

Convenience reads for epoch-0-aware tooling: getCurrentEpoch() (bootstrap epoch
where min stake is not enforced) and isValidatorBelowMinStake() so callers can
present a below-min validator as bootstrapping vs faulted while epoch is 0.
Additive; existing signatures unchanged.

* fix(types): add epoch-zero fields to ValidatorInfo

getValidatorInfo returns currentEpoch, validatorMinStake,
validatorMinStakeRaw, and belowMin, but the ValidatorInfo interface
was never extended to include them, breaking the dts build
(TS2353) and failing the drift, test, and smoke checks.
…bs#196)

This file is a temporary artifact emitted by `vitest --typecheck` and
deleted/regenerated on each run, so committing it produced spurious
working-tree changes on every test run.

It was accidentally committed in genlayerlabs#145 (34de39f). Remove it from tracking
and ignore `tsconfig.vitest-temp*.json` so the tree stays clean.
genlayerlabs#200)

B1: executeWrite in staking + vesting now branches on account.type === "local".
The local-key path (getTransactionCount -> prepareTransactionRequest -> sign ->
sendRawTransaction) is unchanged and stays the regression gate. Address-only /
injected-provider accounts route through client.request eth_sendTransaction
(wallet manages nonce + signing), mirroring the proven IC provider lane in
src/contracts/actions.ts. Shared preflight simulate + gas estimate and the
post-mine receipt wait / revert diagnostics are unchanged around the branch.

B2: new accountActions.transfer({to, value}) for native GEN transfers, local-key
only, mirroring the executeWrite local lane. accountActions now takes publicClient.

Unblocks CLI browser-wallet writes routing through the SDK.
…ng) (genlayerlabs#192)

The v0.6 genvm-manager renamed the method-call calldata map key from
"method" to "" (empty string). The empty key sorts first in canonical
calldata map encoding (keys ordered by unicode-codepoint array; the empty
array precedes any non-empty key), so the method name becomes a prefix of
the encoded binary calldata.

makeCalldataObject now writes the method name under ret[""]. The decoder is
unchanged: it builds a generic Map from raw key bytes and already reads
whatever key is present. No back-compat toggle — there is no calldata
versioning mechanism in this repo, so this is a hard cutover.

The genvm-manager still auto-remaps "method" -> "" but logs an error each
time; this migration stops that error.

BREAKING CHANGE: method-call calldata now uses the "" key instead of
"method". Encoded bytes for every write/read/view/schema call change. Peers
must run the v0.6 genvm-manager (or a node/studio build that expects the ""
key).
* fix: support proof-bearing vesting validator joins

* fix: require operator proofs for validator joins
… on its 4 companions) (genlayerlabs#208)

* Reapply "fix: support proof-bearing validator joins (genlayerlabs#206)" (genlayerlabs#207)

This reverts commit 7666f0b.

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

CON-715 removed ValidatorWalletBlueprint.setOperator in favour of
initiateOperatorTransfer + completeOperatorTransfer. The SDK only spoke the
single-call surface, so rotating an operator against a consensus deployment
that carries the change fails: the selector no longer exists, so viem reverts
with no reason and the Python SDK reports ABIFunctionNotFound.

Adds both calls plus cancelOperatorTransfer and the getPendingOperator view,
and keeps setOperator for deployments that still expose it.

The proof binding is the subtle part. validatorJoin's possession proof is
verified by the ValidatorWalletFactory and is bound to it; rotation is verified
by the wallet, so the registrar is the wallet's own address
(PubKeyUtils.validateWithPossession(pubKey, address(this), owner(), proof)).
createOperatorRegistration already takes the registrar as a parameter, so no
change was needed there — but a join proof silently fails to verify for a
rotation, which is the easy mistake to make. getOperatorTransferContext 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 local error instead
of an onlyOwner revert. A test pins that a join-bound proof does not verify for
rotation while a wallet-bound one does.

Callers still need updating; this only adds the surface they need:
genlayer-e2e's driver interface, genlayer-cli's staking set-operator, and
genlayer-py's staking_set_operator all remain single-call.

Verified: npx tsc --noEmit, npm test -- --run (123/123, no type errors),
npm run build.

* fix(staking): resolve the Claim/Commit layout from the chain

CON-715 widened both staking structs — Claim gained `offset`, Commit gained
`outstanding`/`priced`/`fragmented` and narrowed several members — while
keeping the same function names and arguments. Static tuples decode
positionally, so reading a post-CON-715 chain with the shape in STAKING_ABI
does not fail: `commit.input` picks up `claim.commit`. Pending deposits came
back as small indices instead of amounts, which is why the e2e delegator
scenarios asserted 0.1 GEN and got "0" or "2".

Both layouts are deployed — consensus v0.6-dev has the old one, #1290 the new —
so neither shape can simply replace the other. STAKING_COMMIT_VIEWS_CURRENT_ABI
adds the post-CON-715 shape for the four struct-returning views, and
readCommitView resolves which one the chain speaks, once, then caches it for
the client: getStakeInfo loops over every pending entry and must not re-probe
per entry.

The probe only works in one direction, and the tests pin that: decoding a
legacy response with the current shape throws (the response is short), while
decoding a current response with the legacy shape succeeds and lies. So the
current shape is always attempted first and a decode *failure* identifies a
legacy chain. If that asymmetry ever breaks, the probe would quietly start
reporting wrong balances again — hence the test asserting the exact misread
value rather than merely "not equal".

Also drops duplicate ValidatorWalletBlueprint entries: the ABI already carried
initiateOperatorTransfer/completeOperatorTransfer/cancelOperatorTransfer/
getPendingOperator, with initiateOperatorTransfer declared as taking an address
rather than the pubkey and possession proof it actually takes.

Verified: npx tsc --noEmit, npm test -- --run (126/126, no type errors),
npm run build.

---------

Co-authored-by: kirilaa <kirilantevski@gmail.com>
…bs#212)

* fix: read the consensus surfaces the resolution-kernel train exposes

Four entrypoints the SDK calls were removed or reshaped by the train. The
first one is what fails in E2E today, on the dev-env stack, before any
scenario completes:

  getTransactionData(txId, timestamp) -> not found on ABI

That read answered with a projection evaluated at a caller-supplied clock.
The train splits the two apart: the stored record is
getStoredTransactionData(txId) and the projection lives behind
getTransactionLifecycle. Chains upgrade independently, so the transaction
read now picks whichever the chain's OWN abi offers rather than assuming --
the dev-env harness builds its chain from the live deployment, so its abi is
the train's while a testnet's is not.

The stored record renames three fields. The decoder already normalised two of
them (txData/txCalldata, numOfInitialValidators/initialRotations); it now
also accepts observedAt for currentTimestamp, which it reads unconditionally,
so an unrecognised name was a crash rather than a missing field.

activeValidators() is gone because committee capacity is 1,543 and an
address[] that long overruns the return-size limit. The joined registry is
paged instead: read validatorsJoinedCount() first so a registry that grows
underneath the walk cannot spin the loop, then getValidatorsJoined 64 at a
time -- the size the paged reads are written around, and the one
genlayer-node uses for the same walk. activeValidatorsCount() went with it,
which took all of getEpochInfo() down with a single read.

validatorView lost its registry tree pointers (left, right, parent), so the
struct is resynced to the nine fields the train returns; both call sites read
it by name, so nothing else moves. validatorsRoot() is removed from the abi
as well -- nothing here calls it, and leaving a declaration for a function
that no longer exists only invites a revert.

The status map is renumbered with them. ReadyToFinalize stopped being a
stored status -- readiness is the resolution kernel's verdict now -- and
removing it at ordinal 11 shifts the three above it down. Left alone, a
ValidatorsTimeout transaction decoded as "READY_TO_FINALIZE": no error, just
the wrong answer. The enum member stays, because the node still reports that
state; what changed is that no chain value decodes to it, so the name->number
map is now partial and says so in its type.

The repo's own tests pinned the old numbering and are re-pointed to the
train's.

* fix: resync validatorViewPrimed and validatorViewPrePrimed

The earlier commit resynced validatorView and missed its two siblings, which
return the same struct and drifted the same way: the train dropped the
registry tree pointers (left, right, parent), so a twelve-field decode runs
off the end of a nine-field return.

It surfaces as a bounds error rather than a wrong value --
"Position 319 is out of bounds (0 < position < 288)" from
validatorViewPrimed -- because the reader walks past the encoded tail.

* fix: align SDK consumers with train contracts

* fix: route studio chains around the train lifecycle reads

The train adaptation made the appeal and finalization actions read
getTransactionLifecycle from ConsensusData unconditionally. Studio chains
(localnet, studionet) run the studio-embedded consensus, which predates the
train: that ConsensusData exposes no getTransactionLifecycle and no
estimateLatestAppealCharge, so the read decodes a short tuple and viem raises
"Position 63 is out of bounds (0 < position < 32)".

This failed the studio suites of e2e run 33112369501, including
051_fee_lifecycle.feature ("submitAppeal records an appeal bond without
expanding the primary fee budget").

Guard each affected action on client.chain.isStudio before any train read,
mirroring transactionActions.getTransaction, and reinstate the pre-train call
shape recovered from the v2-dev base:

- appealTransaction encodes submitAppeal(bytes32) against the chain's own
  ConsensusMain ABI, with the caller's value defaulting to zero. The base
  resolved the value through FeeManager/RoundsStorage, both of which carry no
  address on studio, so it already degraded to zero there.
- topUpAndSubmitAppeal encodes topUpAndSubmitAppeal(bytes32, FeesDistribution)
  without a decision id.
- finalizeTransaction encodes finalizeTransaction(bytes32).
- getAppealCharge and getMinAppealBond report the missing quote surface, which
  is the error the base already raised on studio.
- resolveTransactions and finalizeDecisions are train-only additions with no
  base equivalent, so they report the gap and point at finalizeTransaction.

canAppeal and the rounds-storage reads already returned their base error on
studio: their missing-contract guards run before any train read, so they are
left alone. finalizeIdlenessTxs keeps its migration error; studio's
ConsensusMain ABI never carried that entrypoint, so the base did not support it
there either.

The train path is unchanged for non-studio chains.

* feat(transactions)!: layer consumer and protocol lifecycles

Keep the default transaction model on materialized state with a small discriminated lifecycle and stored-state wait helpers. Move projection, resolution source/action, and active decision identity behind the advanced namespace using one normalized schema on both Studio and contract networks.\n\nRemove ReadyToFinalize and duplicate canFinalize exposure. Finalize remains a protocol action.\n\nValidation: npm test -- --run; npm run lint; npm run build; npm run docs; git diff --check.

* fix(staking): restore complete public action surface

* fix(types): stop merging unrelated transaction results

* fix(appeals): bind Studio calls to decisions

* fix(appeals): use train ABI on Studio networks

* fix(lifecycle): fall back to the Studio status when the RPC is absent

The train surface routes Studio chains through the node RPC
gen_getTransactionLifecycle: advanced.getTransactionLifecycle reads it
directly, and _readLifecycleIdentity reads it for every appeal and
finalization. The genlayer-studio backend deployed from studio main does
not implement that method, so viem raises MethodNotFoundRpcError and the
whole flow dies before it reaches anything the transaction can answer.

Keep the node-RPC-first design and degrade only when the endpoint says it
does not implement the method (viem's typed error, JSON-RPC -32601, or a
message that reports the gap in text). The fallback synthesizes the
lifecycle from the Studio consumer surface the SDK already uses,
client.getTransaction, which proves the exact stored status and nothing
else: the projection repeats the stored status, the resolution is
NoOp/Unspecified, and the decision identity stays inactive. When the
status cannot be read, the original RPC error is re-raised rather than
reported as a lifecycle.

_readLifecycleIdentity returns the same conservative identity, so a
Studio chain without the RPC reports no decision instead of a fabricated
one. A fabricated decision id would be signed into submitAppeal or
finalizeTransaction as expectedDecisionId, and every consumer of that
identity refuses to act on an inactive decision, which is the honest
outcome for a decision the SDK cannot read.

This unblocks the Studio flows that never touch a decision. It does not
make the four appeal scenarios of 051_fee_lifecycle pass: those need a
real active decision id on Studio, which only the backend can supply.

* fix(studio): preserve the current consensus surface

* fix(fees): fund default consensus rotations in estimates

---------

Co-authored-by: Edgars <edgars@genlayerlabs.com>
Co-authored-by: kirilaa <kiril@yeager.ai>
* fix(fees): mirror consensus deposit quote

* fix(studio): bind decision lifecycle actions

Use Studio's lifecycle and appeal-charge RPCs to bind appeal, combined top-up/appeal, and finalization to the active decision. Keep Studio ABI exports aligned with the v0.6 consensus surface and cover quoting and guarded selectors.

* fix(fees): encode internal message price caps

* fix(studio): surface mined envelope reverts

* fix(fees): encode schedule-free topups

* fix(transactions): discover message payment events

* test(appeals): lock configured contract routing

Verify that canAppeal reads the active decision from the separately configured Appeals contract at the lifecycle snapshot. This catches local-chain interface drift before cross-stack E2E.

* fix(appeals): admit unfunded rounds safely

Route the public appeal action through topUpAndSubmitAppeal, whose consensus-derived schedule extension is valid for both pre-funded and unfunded rounds. Keep the direct submitAppeal selector out of the high-level path so a full bond-plus-funding quote cannot still fail AppealRoundNotPermitted.\n\nCover quoted and caller-provided values on deployed Consensus and Studio, and refresh generated API docs.
Add the dedicated Studio dev chain preset and make prerelease npm publication use a non-latest channel.
Remove the CI-suppression marker from release commits and directly test the real release configuration against GitHub skip directives.
…cer (genlayerlabs#220)

* fix(staking): follow the CON-471 ValidatorStake.live -> hasUnclaimedRewards rename

genlayer-consensus renames `ValidatorStake.live` to `hasUnclaimedRewards`
(the flag marks unclaimed inflation rewards, not node liveness). Update
the staking ABI tuple, the `ValidatorView` types and the view mapper, and
the fixtures/smoke assertion that read the old key.

Depends-On: genlayerlabs/genlayer-consensus#1403

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KVkzRWuHBntyhom3xqHEAG

* fix(consumers): align staking and lifecycle reads with the migration producer

Reconcile the staking ABI subset against consensus c4749a420, retain both reward-flag states independently of bans, and preserve uint256 claim amounts and execution generation. Add producer-derived fixtures and document pre-release package delivery and remaining integration requirements.

* fix(transactions): discover canonical children across deferred delivery

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: kirilaa <kirilantevski@gmail.com>
…layerlabs#223)

Read FeeManager receiptWrapperBytes and quote propose-receipt gas with the effective gasPrice. Plain eth_call can expose tx.gasprice=0, which makes the Nitro rollup gas model revert. Avoid the zero-price messageFeeParamsBudgetFloor view and use the configured receipt wrapper plus Consensus #1692 terminal reserve. Recognize chain-derived pricing even when its plain-call quote is zero.

Mirror the same floor in Studio when its RPC omits the authoritative value. Validation: 202 Vitest tests and typecheck, production build, changed-file ESLint, and diff whitespace check.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-12T08:18:07.437130Z cae5e54 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@maho0638 maho0638 closed this Oct 12, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cae5e549f5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

},
},
// The stable Studio explorer does not index this preview deployment.
blockExplorers: undefined,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Omit the explorer field when no explorer exists

When connect("studioDevnet") tries to add the newly exported chain to a wallet, this undefined explorer is converted by src/wallet/connect.ts into blockExplorerUrls: [undefined] (serialized as [null]). Wallets such as MetaMask validate every supplied explorer URL and reject the entire wallet_addEthereumChain request, preventing first-time connections to Studio Devnet. Omit blockExplorerUrls when the chain has no explorer rather than supplying an array with an invalid element.

Useful? React with 👍 / 👎.

Comment thread src/vesting/actions.ts

let hash: `0x${string}`;
if (account.type === "local") {
const nonce = await publicClient.getTransactionCount({address: account.address as ViemAddress});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Read the pending nonce for vesting writes

When two local-key vesting writes are submitted before the first is mined, this call uses viem's default latest block tag, so both transactions can receive the same nonce and the second is rejected as already known or as an underpriced replacement. The new transfer path already requests blockTag: "pending" for this reason; the shared vesting write path should do the same.

Useful? React with 👍 / 👎.

Comment thread src/contracts/actions.ts
Comment on lines +1321 to +1325
const functionName = hasAbiFunction(consensusMainContract.abi, "getAddressManager")
? "getAddressManager"
: hasAbiFunction(consensusMainContract.abi, "addressManager")
? "addressManager"
: undefined;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Resolve the AddressManager on Studio presets

On the built-in localnet, studionet, and new studioDevnet presets, consensusMainContract.abi contains neither getAddressManager nor addressManager, so every newly added developer-NFT method fails here with the local “missing AddressManager getter” error before making an RPC request. The staking and vesting paths already call getAddressManager through a minimal dedicated ABI; use the same approach here instead of requiring the static preset ABI to advertise the getter.

Useful? React with 👍 / 👎.

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.

4 participants