JIT Costing: The Definitive PR#876
Open
mwaddip wants to merge 25 commits into
Open
Conversation
Introduce the JIT cost accumulator and limit on the interpreter Context (jit_cost / jit_cost_limit fields; add_jit_cost / add_per_item_jit_cost / jit_cost_value / reset_jit_cost; CostLimitExceeded), wire the supporting cost_accum and error plumbing, and initialise the new fields in every Context constructor (arbitrary + wallet::signing::make_context). Remove the dead pre-costing costs.rs stub (zero consumers). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Charge each node's Scala base cost (FixedCost) at eval time: the per-Expr arms (Const=5, Global=5, Context=1), the ~40 single-cost eval ops, GlobalVars (Height=26, Self/Outputs/Inputs/GroupGenerator=10, MinerPubKey=20), method_call, and ConcreteCollection=20. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Charge each builtin-type method's Scala FixedCost in its EVAL_FN: Context (dataInputs/headers/preHeader/lastBlockUtxoRootHash 15, selfBoxIndex/minerPubKey 20, getVarFromInput 10), Box (value 8, getReg 50, tokens 15), Header/PreHeader fields (10 each; Header.checkPow 700), GroupElement, AvlTree (15 each; updateOperations 45, updateDigest 40), and Global generic methods (groupGenerator/xor/fromBigEndianBytes 10, some/none 5, deserialize per-item 100/32/32). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Charge each BinOp's Scala per-kind cost and route equality through DataValueComparer (per-element comparison costs). Includes the folded SigmaProp equality conformance fix: SigmaProp == SigmaProp now charges per Scala equalSigmaBoolean (ProveDlog 174 / ProveDHTuple 690) instead of the flat catch-all 3, matching the JVM equalDataValues on a rare type-valid path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cost reduce_to_crypto: accumulate JIT cost as a delta from the caller's accumulator (cumulative across inputs, enabling per-tx limit enforcement), preserve ConstPlaceholders for cost eval via ErgoTree::proposition_for_cost_eval (+ Constant.resolved / Expr::resolve_placeholders), short-circuit trivial SigmaProp-constant trees (P2PK) at Scala's EvalSigmaPropConstant flat 50, and propagate the accumulated cost through the verifier (crypto_cost). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Enforce the per-transaction JIT cost limit cumulatively across all transaction inputs: derive the cost limit from chain parameters, share one Context accumulator across the per-input reduce_to_crypto calls so their costs sum, and fail verification when the cumulative cost exceeds the limit. Threaded through tx_context (validate), parameters, ergo_transaction, and the ergo-lib-wasm binding. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Charge Coll.indexOf its Scala cost: PerItemCost(20,10,2) over the iterations actually performed, plus the element-type equality cost per comparison via DataValueComparer::eq_with_cost. A bare == previously left both uncharged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduce eval_lambda_1arg, the shared collection-HOF lambda helper that charges ADD_TO_ENV_COST=5 per binding, and route flatMap through it (charged once per input element). Also charge flatMap's output-length PerItemCost(60,10,8) over the flattened result, matching Scala flatMap_eval. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Charge ADD_TO_ENV_COST per value binding in Apply (FuncValue invocation) and BlockValue (val definitions), matching Scala's per-binding AddToEnvironment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Route the collection HOFs map/filter/fold/exists/forall through eval_lambda_1arg (ADD_TO_ENV charged per element) and charge their Scala per-iteration costs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Many/reverse/startsWith/endsWith/get) Charge the Scala PerItemCost for the remaining Coll methods: zip, indices, patch, updated, updateMany, reverse, startsWith, endsWith, get. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Charge SubstConstants its Scala cost scaled by the number of template constants substituted. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Charge Coll.slice its Scala cost over the requested (to - from) range. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Charge SigmaProp.propBytes its Scala cost scaled by the SigmaBoolean proposition node count (adds SigmaBoolean::size()). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Charge And and Blake2b256 their per-collection-element Scala costs, and charge boolean-constant collections N*5 per element to match the Exprs form. Adds empty-collection regression tests (and_empty, calc_blake2b256_empty, bool_constants_coll). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Charge the Scala FixedCost for the v6.0 numeric and UnsignedBigInt methods. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Charge Global.encodeNbits=25 / decodeNbits=50 (FixedCost) and powHit's PowHitCostKind formula 500 + (k+1) * (totalLen/128 + 1) * 7, with regression tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a SigmaByteWriter serialize-cost accumulator (enable_serialize_cost_tracking / serialize_cost / add_put_byte_cost / add_put_numeric_cost / add_put_chunk_cost) and charge the direct DataSerializer types: Boolean/Byte = PutByte, Short/Int/Long and all length prefixes = PutNumeric, String/Coll bytes = PutChunk, Opt tag = PutByte. Wire Global.serialize to meter the writer puts (StartWriterCost 10 + tracked put costs). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Charge the serialize put-cost for GroupElement (PutChunk over GROUP_SIZE), SigmaBoolean/SigmaProp, and BigInt256/UnsignedBigInt256 sigma_serialize. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Charge the serialize put-cost for ErgoBox: the register type-code prefix (TypeCode::sigma_serialize PutByte), creationHeight, value, and TxId, matching Scala's box serialization metering. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Charge the AvlTree serialize put-cost (digest + flags + key/value-length fields) during Global.serialize. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Charge the PutByte cost for the SType fast-path combined type-code arms (Coll/Nested/Option/Tuple-pair over primitives), with a regression test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Charge the Header serialize put-cost by mirroring the JVM put sequence (HeaderWithoutPow + AutolykosSolution; Header lives in ergo-chain-types and can't reach the cost sink), reaching the blessed v6 Global.serialize[Header] = 333. Adds the cross-type serialize_charges_writer_costkinds regression test covering byte/numeric/coll (c17), GroupElement/UnsignedBigInt (c18), AvlTree (c20) and Header. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the ergo-lib cost_parity integration test replaying mainnet blocks 700000-700060 and asserting each transaction's computed JIT cost matches the node's recorded cost (78 vectors), pinning the full costing model to consensus. Includes the headers / transactions / tx_costs test vectors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jun 3, 2026
Scala NumericCastCostKind bills both SBigInt and SUnsignedBigInt at JitCost(30) (others 10); Upcast/Downcast were billing UnsignedBigInt the else-branch 10. Mirror the oracle so the JIT cost of a numeric cast to UnsignedBigInt matches the JVM. 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.
Closes #193.
Ports JIT costing from sigmastate-interpreter (
direct-tree-eval): each IR nodeaccumulates its Scala cost into the interpreter
Contextduring evaluation, theper-transaction cost limit is enforced cumulatively across inputs, and per-element
equality costs (
DataValueComparer) andSigmaByteWriterserialize costs arecharged. A
cost_paritytest replays mainnet blocks 700000–700060 and asserts everytransaction's computed JIT cost equals the node's recorded cost (78 vectors).
Supersedes #854. Same logical change, re-sliced from #854's tangled history into
24 dependency-ordered, individually-green commits — each builds and passes the
ergotree-interpreter/ergotree-irsuites pluscargo clippy --all-features --all-targets -D warnings. One conformance fix is folded in:SigmaProp == SigmaPropnow charges per Scala
equalSigmaBoolean(ProveDlog 174 / ProveDHTuple 690) instead ofthe flat catch-all 3.
Relationship to #858 (lazy constant resolution)
Independent PR, overlapping in
reduce_to_crypto/ConstPlaceholderhandling — whichevermerges first, the other takes a mechanical rebase. This PR resolves segregated
ConstPlaceholderviaConstantPlaceholder::resolved(populated byErgoTree::proposition_for_cost_eval) and charges itJitCost(1); #858 introducesContext.constantslazy resolution. Reconciliation either way: theConstPlaceholderarm ends up charging 1 JitCost and resolving via
ctx.constants, dropping thisPR's
resolved-field machinery as redundant. Worked reconciliation on theergo-node-integrationbranch (9094f693); full rebase recipe pinned on #858.