perf(linear-vesting): optimize validator with raw BI.* data extraction#7658
perf(linear-vesting): optimize validator with raw BI.* data extraction#7658
Conversation
Replace high-level pattern matching with raw BI.unsafeDataAsConstr, BI.head/tail for data extraction, BI.caseList' for list traversal, and BI.caseInteger for redeemer dispatch. Use standard Haskell multi-way if guards instead of builtinIf pattern. CPU: 30.4M → 11.4M (62% reduction) MEM: 129K → 48K (63% reduction)
b080a9f to
19eb906
Compare
| credFields = BI.snd credCon | ||
| in if BI.equalsInteger credTag 0 | ||
| then BI.unsafeDataAsB (BI.head credFields) | ||
| else traceError "Expected PubKeyCredential" |
There was a problem hiding this comment.
This doesn't need the constructor tag check, it doesn't impact the security invariants of the contract as we require the public key hash to be present in txInfoSignatories which guarantees that it is indeed a valid public key hash (the ledger enforces this).
This can be rewritten (more efficiently) as:
BI.unsafeDataAsB $ BI.head $ BI.snd $ BI.unsafeDataAsConstr $ BI.head (BI.snd (BI.unsafeDataAsConstr addr))Same with the other similar functions (ie. getScriptHashFromAddress). Even more-so for getScriptHashFromAddress because the ledger invariants already guarantee that the argument that we call this on inputAddress must be an address where the first field has the encoding Constr 1 [scriptHash] because inputAddress is the address of the input being validated by this script execution, which guarantees that it is indeed a script input and thus that the payment credential has constructor index 1 (is a ScriptCredential).
This is a micro-optimization probably so feel free to ignore it.
This check would be necessary if this contract had a minting policy for authentication tokens for creating vesting positions (in which case we would impose this check amongst other checks to enforce that the beneficiary address is indeed a valid public key address with the correct constructor index and a single bytestring field that has the length of 28) AND the validator allowed anyone to claim the vested amount provided they send an output with that amount to the beneficiary address. This would be required because in such a scenario, if the constructor index of the beneficiary address was incorrect then the check that txOutAddressAsData satisfyingOutput == beneficiaryAddress would be unsatisfiable if beneficiaryAddress was not strictly enforced to have canonical builtin data encoding that matches the ledger (ie. if it had constructor tag 3, it would be unsatisfiable because there is no way to construct a tx such that the output in the script context has an address with constructor tag 3).
There was a problem hiding this comment.
Good call: applied in fc4db8b. Both getScriptHashFromAddress and getPubKeyHashFromAddress now skip the tag check entirely, since as you pointed out the ledger guarantees the correct credential type in both cases. Shaved off another ~360K CPU.
Remove constructor tag verification from getScriptHashFromAddress and getPubKeyHashFromAddress. The ledger guarantees the correct credential type in both cases: the validated input is always a script address, and the beneficiary hash is checked against txInfoSignatories. Saves ~360K CPU / ~1.9K MEM.
zliu41
left a comment
There was a problem hiding this comment.
Please add this to the validation benchmark. We need more better-optimized scripts in the validation benchmark.
Context
BI.*data extractionApproach
Replaced the entire validation logic in
LinearVesting.Validatorwith hand-writtenBI.*operations that bypass the high-levelPlutusLedgerApipattern matching infrastructure. Instead of deserializingScriptContext,TxInfo, and related types into Haskell ADTs, the optimized code works directly withBuiltinDatausingBI.unsafeDataAsConstr,BI.head, andBI.tailto extract fields positionally.Uses standard Haskell multi-way if guards for control flow rather than the
builtinIflambda/unit pattern, based on experiments in PRs #7583 and #7584 which confirmed guards produce slightly better results (~5% CPU / ~7% MEM improvement).The
VestingDatumandVestingRedeemertype definitions and TH splices are preserved unchanged to maintain compatibility withTest.hs, which constructs test data using the typed API.Changes
Validator optimization
fromBuiltinData-basedScriptContextparsing with directBI.unsafeDataAsConstrfield extraction inuntypedValidatortxSignedBywithtxSignedBy'usingBI.caseList'over raw signatory listList.findwithfindInputByOutRefandfindOutputByAddressusingBI.caseList'assetClassValueOfwithvalueOfthat walks theBI.unsafeDataAsMapstructure directlygetLowerInclusiveTimeRangepattern match withlowerInclusiveTimethat reads constructor tags positionallygetScriptHashFromAddress,getPubKeyHashFromAddress,getSpendingInfohelpers for raw credential extraction (no tag checks — ledger invariants guarantee correct credential types)BI.caseIntegerfor redeemer dispatch instead offromBuiltinData-basedVestingRedeemerparsingImports cleanup
PlutusTx.Prelude,PlutusLedgerApi.Data.V3(full),PlutusLedgerApi.V3.Data.Contexts,PlutusTx.Data.ListPlutusTx.Bool,PlutusTx.Builtins.Internal,PlutusTx.TraceGolden files
.golden.eval,.golden.pir,.golden.uplcto reflect optimized outputResults