fix(scenarios): encode finite upper bounds as exclusive, drop the closure branch from the time specs - #247
Open
nau wants to merge 1 commit into
Open
fix(scenarios): encode finite upper bounds as exclusive, drop the closure branch from the time specs#247nau wants to merge 1 commit into
nau wants to merge 1 commit into
Conversation
…sure branch from the time specs The ledger builds every PlutusV3 script context with a closed finite lower bound (PV1.lowerBound) and an open finite upper bound (PV1.strictUpperBound); see transValidityInterval in Cardano.Ledger.Conway.TxInfo. A finite inclusive upper bound never reaches a validator. The htlc, two_party_escrow and linear_vesting specs still required validators to branch on the closure flag, and the fixtures used point intervals [t, t] with an inclusive upper bound, so every submission had to carry a dead branch and CAPE measured it. - set_valid_range now encodes to_time as UpperBound (Finite t) False, the ledger's invalidHereafter; from_time stays LowerBound (Finite t) True. - Every fixture with a finite upper bound t is re-encoded as to_time = t + 1, so every converted bound value and every expected outcome is unchanged. - The three "Note on time semantics" paragraphs state the ledger guarantee instead of the closure conversion table. The infinite-bound tests stay. - Schema and builder docs describe from_time/to_time as inclusive/exclusive. Verified: Hspec suite passes (117 examples); cape submission verify passes 25/25, 47/47 and 29/29 on all 23 non-preview submissions of the three scenarios. metrics.json files are not regenerated here: claim/deposit happy-path budgets move by about +165k CPU (or -140k to -203k CPU for the asdata/plain variants); linear_vesting budgets are unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The
htlc,two_party_escrowandlinear_vestingscenarios each carry a "Note on time semantics" paragraph that requires a validator to handle the closure flag of thetxInfoValidRangebounds:The fixtures then use point intervals
[t, t], i.e. a finite inclusive upper bound, for every claim and deposit test. The ledger never produces that shape in a PlutusV3 script context (details below). So the inclusive branch is unreachable on-chain, yet every submission has to implement it to pass the fixtures, and CAPE measures it. The three scenarios are the only ones that useset_valid_range.What the ledger guarantees
CAPE contexts are PlutusV3, and V3 exists only from the Conway era on. In the Conway era the script context of every Plutus version is built by
transValidityIntervalineras/conway/impl/src/Cardano/Ledger/Conway/TxInfo.hs(cardano-ledgercardano-ledger-conway-1.22.1.0, the mainnet PV11 pin), lines 793–810:The V1, V2 and V3
EraPlutusTxInfo ... ConwayErainstances all call it (same file, lines 407, 444, 483), and the Dijkstra era reuses it (eras/dijkstra/impl/src/Cardano/Ledger/Dijkstra/TxInfo.hs, lines 251, 310, 349, 445). The helpers are defined in plutus-ledger-apiplutus-ledger-api/src/PlutusLedgerApi/V1/Interval.hs(tag1.63.0.0):So for every context the ledger hands to a V3 script:
LowerBound (Finite t) True(inclusive), wheretisinvalid_before;UpperBound (Finite t) False(exclusive), wheretisinvalid_hereafter(the txttl);NegInf/PosInf.Scope note: the older era-generic translation in
eras/alonzo/impl/src/Cardano/Ledger/Alonzo/Plutus/TxInfo.hs(lines 229–243), used by the Alonzo and Babbage eras, translates an upper-bound-only interval withPV1.to, which yields an inclusiveupperBound(Interval.hs line 360). That is the one historical case of a finite inclusive upper bound, and it applies to pre-Conway eras only (the dispatch moved from protocol versions to eras in cardano-ledger alonzo 1.14.0.0 / conway 1.20.0.0, which removedhardforkConwayTranslateUpperBoundForPlutusScripts). It cannot occur in a PlutusV3 context. The both-bounds case isstrictUpperBoundin every era.Why it matters for a benchmark
Constrwalk and a conditional on every claim/deposit, and a submission that skips it fails the current fixtures. CAPE exists to compare compilers on realistic script contexts; forcing dead code into every submission skews the numbers it reports.*_infinite_*_boundtests stay.The change
Semantics preserved. With exclusivity guaranteed,
upperBound = t − 1forFinite tis unconditional andlowerBound = tis the raw finite lower bound. Every fixture with a finite inclusive upper boundtis re-encoded as(Finite (t + 1), False); lower bounds already are(Finite t, True). Under this pairing every converted bound value, and therefore every expected pass/fail outcome, is unchanged, and a submission that still implements the closure branch computes the same value on its exclusive path.lib/Cape/ScriptContextBuilder.hs:set_valid_rangenow encodesto_timeasUpperBound (Finite t) False.from_timestaysLowerBound (Finite t) True, omitted bounds stayNegInf/PosInf. The JSON DSL can no longer express a validity interval the ledger would not produce.from_time/to_timemap 1:1 ontoinvalid_before/invalid_hereafter.schemas/cape-tests.schema.json,lib/Cape/Tests.hs: describefrom_timeas the inclusive lower bound andto_timeas the exclusive upper bound.test/Cape/ScriptContextBuilderSpec.hs: expectations follow the builder.scenarios/*/cape-tests.json: every finiteto_timebumped by one (table below). Lower bounds and the infinite-bound tests are untouched.scenarios/*/*.md: the three "Note on time semantics" paragraphs state the ledger guarantee instead of the closure conversion table, the[t, t]fixture sentences are rewritten, and the interval notation in the three*_infinite_lower_bounddescriptions follows the new encoding. The upper/lower bound choice per path (claim and deposit read the upper bound, refund and unlock read the lower bound) is unchanged. The stale htlc sentence about a follow-up issue now points at Unify validity-range semantics across Linear Vesting and Two-Party Escrow scenarios (post-HTLC follow-up) #171.Fixture table
Encodedis thePOSIXTimeRangethe builder produces.Valueis the converted bound the spec compares (upperBound = t − 1for an exclusiveFinite t).successful_claimbaseline,claim_well_before_timeoutand every claim check that inherits it[Finite 50 T, Finite 50 T](no)[Finite 50 T, Finite 51 F)claim_just_before_timeout[99 T, 99 T](no)[99 T, 100 F)claim_at_timeout[100 T, 100 T](no)[100 T, 101 F)claim_after_timeout[150 T, 150 T](no)[150 T, 151 F)claim_infinite_upper_bound[50 T, PosInf T](yes)successful_refundbaseline,refund_just_after_timeout(101),refund_before_timeout(50),refund_at_timeout(100)[t T, PosInf T](yes)refund_infinite_lower_bound[NegInf T, 200 T](no)[NegInf T, 201 F)successful_depositbaseline and every deposit check that inherits it[1000 T, 1000 T](no)[1000 T, 1001 F)depositTime1000, unchangeddeposit_infinite_upper_bound[1000 T, PosInf T](yes)from_time2801, 3000, 3600, 5000, 2000, 900, 1800)[t T, PosInf T](yes)refund_infinite_lower_bound[NegInf T, 3000 T](no)[NegInf T, 3001 F)refund_with_no_time_rangealways(yes)from_time11, 101, 50, 91, 25, 200, 5, 10, 100)[t T, PosInf T](yes)partial_unlock_infinite_lower_bound[NegInf T, 90 T](no)[NegInf T, 91 F)full_unlock_infinite_lower_bound[NegInf T, 200 T](no)[NegInf T, 201 F)No fixture changes outcome.
Submissions impact and verification
cape submission verifyon every non-preview submission of the three scenarios (23 directories: htlc 9, two_party_escrow 7, linear_vesting 7): all pass with the same counts as before, 25/25, 47/47 and 29/29. The 12_previewdirectories were not re-run (they needmeasure-preview); they compile the same sources as their production counterparts.linear_vesting: no CPU/mem change on any evaluation (only the two infinite-lower-bound checks were re-encoded, and they reject before the closure is read).htlcandtwo_party_escrow, submissions that branch on the closure flag (Plinth 1.45/1.64/1.65, Scalus 0.16/0.17/0.18.2): claim / deposit happy paths +165 208 CPU, +402 mem (the exclusive path does one extrasubtractInteger);claim_at_timeout,claim_after_timeout,claim_double_satisfaction+101 208 CPU. Roughly 35 lovelace per claim at mainnet prices._asdata/_plainvariants go the other way: happy paths −139 174 to −203 174 CPU, and some checks move by larger amounts because the lazy field walk takes a different path.metrics.jsonfiles are therefore stale by those amounts forhtlcandtwo_party_escrow(aggregates included:cpu_units.summoves by twice the claim delta for htlc, by the deposit delta for two_party_escrow). CI'scape submission verify --allrewritesmetrics.jsonbut does not gate on its content, so this PR passes CI as is. I did not regenerate them here because the_previewvariants need the second build and a partial regeneration would leave the set inconsistent; a regeneration of all 35 directories can follow in one pass.Related
Out of scope
*_infinite_upper_bound/*_infinite_lower_boundtests: an omitted bound is a legal transaction, so the finiteness checks stay.metrics.json: follow-ups, per submission.refund_at_exact_deadlinein two_party_escrow usesfrom_time = 1800): untouched.