fix: align SDK consumers with the resolution-kernel train - #212
Conversation
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.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
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.
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
left a comment
There was a problem hiding this comment.
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.
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_FINALIZEis 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
NoOp/Unspecified, and decision identity remains inactive.resolutionAction === "Finalize"is the sole finalization capability.decidedandfinalized.defaultConsensusMaxRotationsfor every appeal round; explicit arrays, including[0], remain authoritative.submitAppeal(bytes32),topUpAndSubmitAppeal(bytes32, FeesDistribution), andfinalizeTransaction(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.npm run check:chains: no drift againstgenlayer-networks/main.git diff --check: clean.Current head:
8f72796efa6f1f52d420957bd253771c8583ca14.The exact composed behavior is qualified by genlayerlabs/genlayer-e2e#748.