Skip to content

fix: align SDK consumers with the resolution-kernel train - #212

Open
kirilaa wants to merge 12 commits into
v2-devfrom
fix/consensus-train-surface
Open

fix: align SDK consumers with the resolution-kernel train#212
kirilaa wants to merge 12 commits into
v2-devfrom
fix/consensus-train-surface

Conversation

@kirilaa

@kirilaa kirilaa commented Aug 25, 2026

Copy link
Copy Markdown

Delivery context

Depends-On: genlayerlabs/genlayer-consensus#1307
Depends-On: genlayerlabs/genlayer-node#1800

Problem and outcome

This SDK line targets the resolution-kernel/v0.6 train. Older deployments use their matching older SDK; TransactionStatus.READY_TO_FINALIZE is removed.

The train and current Studio are separate release surfaces. Contract networks use the train's exact decision-bound lifecycle, appeal, and finalization methods. Current Studio keeps its native decision-free methods and degrades the advanced lifecycle to stored status only when the lifecycle RPC is genuinely absent. The SDK does not depend on Studio parity work to land this train.

Consumer contract

  • Ordinary reads expose a non-projecting lifecycle derived from stored state.
  • The advanced lifecycle exposes exact stored/projected status, resolution action/source, decision identity, and evaluation time when the target proves them.
  • On current Studio without the lifecycle RPC, projection repeats stored status, resolution is NoOp/Unspecified, and decision identity remains inactive.
  • resolutionAction === "Finalize" is the sole finalization capability.
  • Receipt waiting uses consumer stages decided and finalized.
  • Omitted rotation budgets fund defaultConsensusMaxRotations for every appeal round; explicit arrays, including [0], remain authoritative.
  • Current Studio uses native submitAppeal(bytes32), topUpAndSubmitAppeal(bytes32, FeesDistribution), and finalizeTransaction(bytes32); it does not expose the train's authoritative appeal quote.

Implementation and validation

  • npm test -- --run: 179 passing, no type errors.
  • npm run build: passing.
  • API docs regenerated.
  • npm run check:chains: no drift against genlayer-networks/main.
  • git diff --check: clean.

Current head: 8f72796efa6f1f52d420957bd253771c8583ca14.

The exact composed behavior is qualified by genlayerlabs/genlayer-e2e#748.

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

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: 095451c6-48af-4d4d-b4b1-37d7139f5fbe

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 changed the title fix: read the consensus surfaces the resolution-kernel train exposes fix: align SDK consumers with the resolution-kernel train Aug 27, 2026
kirilaa and others added 6 commits August 27, 2026 23:31
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.
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.
kirilaa and others added 2 commits August 28, 2026 08:22
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.
@MuncleUscles
MuncleUscles marked this pull request as ready for review August 28, 2026 13:39

@MuncleUscles MuncleUscles left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed the train/current-Studio split and lifecycle surface on this exact head. Native tests, build, generated docs, and chain drift checks are green. Approving; composed behavior remains gated by the stack E2E.

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