fix: Scalus 0.17 upgrade + correct chain cost-model application in JulcTransactionEvaluator#39
Merged
Merged
Conversation
Upgrade Scalus to 0.17.0 and adapt the Scalus VM integration to its updated API. Key PlutusV3 custom cost models by the integer language id expected by MachineParams.fromCostModels, fixing the runtime "key not found: 2" failure when using chain-supplied V3 cost model params. Pass the new evaluateDeBruijnedTerm tracing argument explicitly as false to preserve the previous non-tracing evaluation behavior
…t-model setup and evaluation JulcTransactionEvaluator configured the chain cost model on one provider instance but evaluated on another. setCostModelParams ran on the instance created by getOrCreateVm()/JulcVm.create() (its own ServiceLoader lookup), while evaluation ran on JulcVm.withProvider(getProvider(), ...) — a separate ServiceLoader lookup yielding a different instance. Cost-model params are stored per provider instance, so the evaluating VM never received the chain cost model and silently fell back to its built-in default. This over-estimated the script budget on any protocol version whose cost model differs from the built-in default (e.g. PV11: 65,563,707 vs the correct 65,559,542 CPU). It was masked on PV10 because the default happened to match. Since QuickTx bakes the evaluator's ExUnits into the transaction, every tx built through this evaluator carried the wrong budget. Affected all backends (Java, Truffle, Scalus). Resolve the VM provider once via getProvider() and use that same instance for both setCostModelParams and evaluateWithArgs, passing the resolved script's language explicitly. Remove the now-unused vm field, getOrCreateVm(), and the JulcVm import. Document the evaluator's (unchanged) non-thread-safe contract. Add a regression test that supplies two differing PlutusV3 cost models and asserts the evaluated CPU budget tracks the supplied model rather than the built-in default. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Summary
Two related fixes that make JuLC apply the chain's Plutus V3 cost model correctly when evaluating transactions, rather than silently falling back to built-in default. Together they correct script-budget (ExUnits) over-estimation on Plutus V11 (vanRossem) and any chain with custom cost-model params.
JulcTransactionEvaluatorso cost-model setup and evaluation use the same VM provider instance.Why
On a PV11 devnet,
JulcTransactionEvaluatorreported 65,563,707 CPU for a script, while the node'sevaluateTxand JuLC's own VMs (invoked correctly) reported 65,559,542 CPU — a constant +4,165 gap (memory identical). SinceQuickTx.buildAndSign(...)bakes the evaluator's ExUnits into the transaction, every transaction built through this evaluator carried a wrong-for-the-protocol budget. It was masked on PV10 because the built-in default happened to match. Two independent root causes had to be fixed for the chain cost model to reach the evaluating VM:1. Scalus 0.17 integration
ScalusVmProvidermishandled custom V3 cost models and protocol versions:CostModelsmap key: keyed by theLanguage.PlutusV3enum object, butMachineParams.fromCostModels()looks the model up by integer language id (languageId() == ordinal() == 2) → runtimekey not found: 2. Now keyed byLanguage.PlutusV3.languageId().protocolMajorVersion >= 10 ? plominPV : changPVcollapsed PV11 → PV10.PlutusVMgates PV11 semantics (e.g. case-on-builtins) onprotocolVersion >= vanRossemPV, so this silently disabled them. Now maps directly:new MajorProtocolVersion(protocolMajorVersion).evaluateDeBruijnedTermgained a 4thtracingboolean; passfalseto preserve prior non-tracing behaviour.scalus_3andscalus-bloxbean-cardano-client-lib_3to0.17.0, andcardano-client-libto0.7.2.2. Single VM provider instance
JulcTransactionEvaluatorconfigured the cost model on one provider instance but evaluated on another:getOrCreateVm()→JulcVm.create()did its ownServiceLoaderlookup → instance A;setCostModelParams(...)ran on A.JulcVm.withProvider(getProvider(), lang)— a separateServiceLoaderlookup → instance B; evaluation ran on B.Cost-model params are stored per provider instance, so B never received the chain cost model and fell back to its built-in (PV10-shaped) default. Affected all backends (Java, Truffle, Scalus).
Fix: resolve the provider once via
getProvider()and use that same instance for bothsetCostModelParamsandevaluateWithArgs, passing the resolved script's language explicitly. Remove the now-unusedvmfield,getOrCreateVm(), and theJulcVmimport. Document the evaluator's (unchanged) non-thread-safe contract.Behavioral change
Transactions built via this evaluator now use the chain's cost model for ExUnits on non-PV10 protocol versions (and any custom cost models), instead of the built-in default. Budgets/fees will change accordingly on such chains
(correctly).
Tests
evaluateTx_usesSuppliedCostModel_notBuiltInDefault: supplies two differing PlutusV3 cost models (one with every cost doubled) and asserts the evaluated CPU budget tracks the supplied model — pre-fix both runs produced the same built-in-default budget and the test fails.JulcTransactionEvaluatorTest: 19/19 pass.EscrowBudgetComparisonTest, julc-examples, Yaci DevKit PV11): JuLC Java VM, JuLC Scalus VM, and the DevKitevaluateTxendpoint now all report 65,559,542 / 208,684; strict-equality and 3-way comparison pass.Out of scope (tracked separately)
JulcTransactionEvaluatorthread-safety is documented, not enforced; making per-call cost-model config atomic (or per-call rather than instance state) is a follow-up.