Skip to content

fix: read the consensus surfaces the resolution-kernel train exposes - #212

Draft
kirilaa wants to merge 2 commits into
v2-devfrom
fix/consensus-train-surface
Draft

fix: read the consensus surfaces the resolution-kernel train exposes#212
kirilaa wants to merge 2 commits into
v2-devfrom
fix/consensus-train-surface

Conversation

@kirilaa

@kirilaa kirilaa commented Aug 25, 2026

Copy link
Copy Markdown

Depends-On: genlayerlabs/genlayer-consensus#1307

Public SDK — flagged for DXP review. This changes decoded status
numbering and drops two contract reads from the staking surface. The
compatibility question is called out under Chain compatibility below and is
the part most worth a second opinion.

Observed break

The dev-env E2E stack fails before any scenario completes, inside the SDK:

Function "getTransactionData" not found on ABI
  at waitForConsensusReceipt (genlayer-e2e .../tooling/js-driver.ts:477)

genlayer-e2e run
32882620666
genlayer-explorer, genlayer-cli and genlayer-wallet on dev-env. The
studio stack passes throughout, because getTransactionData short-circuits on
isStudio and never reaches the contract read.

The harness builds its chain from the live deployment's ABIs, so the ABI it
hands the SDK is the train's. The SDK asks it for functions the train removed.

What the train changed

SDK call On the train
ConsensusData.getTransactionData(txId, timestamp) removed — stored record is getStoredTransactionData(txId), projection is getTransactionLifecycle
Staking.activeValidators() removed — joined registry is paged
Staking.activeValidatorsCount() removed — takes all of getEpochInfo() with it
Staking.validatorView(addr) 12 fields → 9 (registry tree pointers left/right/parent gone)
TransactionStatus ordinals ReadyToFinalize removed at 11; 12/13/14 shift down

activeValidators() is gone because committee capacity is 1,543 and an
address[] that long overruns the return-size limit.

Approach

Transaction read is capability-detected. The chain's own ABI is already in
hand (chain.consensusDataContract.abi), so the read picks getStoredTransactionData
when the chain offers it and keeps the old call otherwise. A testnet on the
current deployment is unaffected.

Paged registry walk. 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. A short page means it shrank; stop and
let the next read see it settled.

Decoder. It already normalised txData/txCalldata and
numOfInitialValidators/initialRotations; it now also accepts observedAt
for currentTimestamp, which it reads unconditionally — so an unrecognised
name was a crash, not a missing field.

Status numbering. ReadyToFinalize stopped being a stored status;
readiness is the resolution kernel's verdict now. Left alone, a
ValidatorsTimeout transaction decoded as "READY_TO_FINALIZE" — no error,
just the wrong answer. The TransactionStatus 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 genuinely partial and its type now says so.

Chain compatibility — the DXP question

Narrower than first written, after checking the pre-train ABIs directly.

Safe on both surfaces. The paged registry reads are not new: pre-train
StakingReadFacet already exposes validatorsJoinedCount and
getValidatorsJoined, and activeValidatorsCount is there too. So
getActiveValidators, getActiveValidatorsCount and getEpochInfo work
against a current-deployment chain as well as the train. activeValidators
(the one that is gone on the train) lived on StakingConfig pre-train, which
is why the old call worked there.

The transaction read degrades per-chain — it detects against the chain's
own ABI, so nothing changes for a testnet.

One hard pre-train break: validatorView. The ABI here is now the train's
nine fields, and a pre-train chain returns twelve — left, right, parent
still lead the struct. Both call sites read by name, so a pre-train chain
decodes them from the wrong slots. There is no chain ABI to detect against for
this one: STAKING_ABI is the SDK's own bundled copy.

One semantic break: status ordinals. A pre-train chain's 11 is
ReadyToFinalize; this maps it to ValidatorsTimeout.

Both are fine if v2-dev ships alongside the train landing in v0.6-dev, and
not fine if this branch must keep serving today's testnets first. I have not
guessed — happy to add a probe for validatorView if DXP wants one.

src/chains/* is untouched: it is generated from genlayer-networks, and
npm run check:chains reports no drift.

Validation

npm run build (incl. DTS), npm run lint, npx vitest run --typecheck
(126 passed, 0 type errors), npm run check:chains — all clean. The repo's own
transaction-enum tests pinned the old ordinals and are re-pointed to the
train's.

Acceptance

The dev-env E2E stack reaches its scenarios against the train. Lands with the
train, not before it — against current v0.6-dev consensus these entrypoints
still exist.

Companion adaptations: genlayerlabs/genlayer-node#1800,
genlayerlabs/genlayer-dev-env#138, genlayerlabs/genlayer-cli#412.

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.
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.
@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

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.

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