|
| 1 | +# Enabling new Plutus Core builtins in `plutus-ledger-api` |
| 2 | + |
| 3 | +The process of releasing new builtins (or other Plutus Core features such as new |
| 4 | +AST nodes, like `constr` and `case`) is quite complex and has to take place in |
| 5 | +several stages. |
| 6 | + |
| 7 | +1. We add new builtins to the Plutus codebase and test and cost them, and |
| 8 | +eventually merge the code with the main branch. At this point the new builtins |
| 9 | +are available for use locally via eg `uplc` or Plinth. |
| 10 | + |
| 11 | +2. We also have to expose them to the ledger by adding code in |
| 12 | +`plutus-ledger-api`. This process will be described in greater detail later. |
| 13 | + |
| 14 | +3. Eventually a Plutus release including the new builtins will be integrated |
| 15 | + with the Cardano node, and at some point a new version of the node software will |
| 16 | + be released for public use and will be adopted by node operators. It may take |
| 17 | + some time until a majority of the nodes in the network are using the new |
| 18 | + software, and until that happens it must be impossible to run scripts which use |
| 19 | + the new builtins on the network since nodes may disagree about the validity of |
| 20 | + scripts. Once a majority of nodes are using the updated software a hard fork |
| 21 | + will take place and at that point it will in principle become possible to use |
| 22 | + the new builtins. There are two factors which determine whether it's possible to |
| 23 | + use them: |
| 24 | + |
| 25 | + * During script deserialisation there's a check (see later) which checks if a |
| 26 | + builtin is allowed based on the (major) Protocol Version, which is incremented |
| 27 | + during the hard fork. If an invalid builtin is found then there will be a |
| 28 | + Phase 1 error and the script will not be executed and will not be stored on |
| 29 | + the chain. |
| 30 | + |
| 31 | + * If the script deserialises correctly then it will be executed, and any uses of |
| 32 | + a builtin will be charged for as usual, based on the cost model parameters in |
| 33 | + the protocol parameters; however, it's possible that the cost model parameters |
| 34 | + may not contain an entry for a particular builtin, and if this happens then |
| 35 | + the charge for that builtin will be set to the maximum possible, and if the |
| 36 | + builtin is used then the script budget will be exhausted immediately, causing |
| 37 | + a Phase 2 error. |
| 38 | + |
| 39 | +Thus two things have to happen before a builtin becomes usable: |
| 40 | + |
| 41 | + * A: a protocol parameter update has to happen which updates the cost model |
| 42 | + parameters with costs for the builtin. |
| 43 | + |
| 44 | + * B: a hard fork has to occur which enables the builtin. |
| 45 | + |
| 46 | +Because these events have to be voted upon separately it's not currently |
| 47 | +possible to ensure that they happen simultaneously. |
| 48 | + |
| 49 | +If the parameter update happens first (ie, A happens before B) then until the |
| 50 | +hard fork, scripts using the new builtin(s) will fail during Phase 1 validation; |
| 51 | +when the hard fork happens such scripts will immediately pass both Phase 1 and |
| 52 | +Phase 2 validation. |
| 53 | + |
| 54 | +If the hard fork happens before the parameter update (ie, B happens before A) |
| 55 | +then until the parameter update, scripts using the new builtin(s) will pass the |
| 56 | +Phase 1 checks but will fail during execution; after the parameter update they |
| 57 | +will succeed. |
| 58 | + |
| 59 | +## Updating `plutus-ledger-api` |
| 60 | + |
| 61 | +When a new buitin is added, the `plutus-ledger-api` code and tests must be |
| 62 | +updated to prepare for both A and B. |
| 63 | + |
| 64 | +### Cost model parameters |
| 65 | + |
| 66 | +After a costing function has been determined for a new builtin (see [this |
| 67 | +document](https://github.com/IntersectMBO/plutus/blob/master/plutus-core/cost-model/CostModelGeneration.md)), |
| 68 | +costing information for the builtin will be stored in the files |
| 69 | +`builtinCostModelA.json`, `builtinCostModelB.json`, and `builtinCostModelC.json` |
| 70 | +in `plutus-core/cost-model/data`. Once this has been done it will be possible |
| 71 | +(inside the `plutus` repository) to run scripts using the new builtin using the |
| 72 | +`uplc` command, and script execution will be correctly costed. However, before |
| 73 | +the builtin can be used on the chain the code in `plutus-ledger-api` must be |
| 74 | +updated to make it aware of the parameters determining the relevant costing |
| 75 | +function. |
| 76 | + |
| 77 | +#### Example |
| 78 | +The current costing function for the `multiplyInteger` builtin is represented |
| 79 | +by the following entry in `builtinCostModelC.json`. |
| 80 | + |
| 81 | +``` |
| 82 | + "multiplyInteger": { |
| 83 | + "cpu": { |
| 84 | + "arguments": { |
| 85 | + "intercept": 90434, |
| 86 | + "slope": 519 |
| 87 | + }, |
| 88 | + "type": "multiplied_sizes" |
| 89 | + }, |
| 90 | + "memory": { |
| 91 | + "arguments": { |
| 92 | + "intercept": 0, |
| 93 | + "slope": 1 |
| 94 | + }, |
| 95 | + "type": "added_sizes" |
| 96 | + } |
| 97 | + } |
| 98 | +``` |
| 99 | + |
| 100 | +This says that the CPU cost of calling `multiplyInteger` with arguments `m` and |
| 101 | +`n` is `90434 + 519 * size(m) * size(n)` and the memory cost is `size(m) + |
| 102 | +size(n)`, where sizes are measured in units of 8-byte words. There are similar |
| 103 | +entries for all of the other builtins, and also for the basic operations of the |
| 104 | +CEK machine (see `cekMachineCosts[ABC].json`). The Plutus Core cost model |
| 105 | +consists of all of this information, and the A/B/C variants account for |
| 106 | +differences in costs for different ledger languages (ie, PlutusV1, PlutusV2, and |
| 107 | +PlutusV3) and some historic changes in costing functions. The ledger's view of |
| 108 | +the cost model is simply as three lists of numbers (one list for each variant) |
| 109 | +stored in the protocol parameters, and there is code in `plutus-ledger-api` |
| 110 | +which is used to convert these numbers into our more structured internal |
| 111 | +representation. The numbers are referred to as "cost model parameters". |
| 112 | + |
| 113 | +### Cost model parameter names |
| 114 | +Before executing a script the the ledger retrieves the relevant set of cost |
| 115 | +model parameters from the protocol parameters and calls code in |
| 116 | +`plutus-ledger-api` to update the evaluator's internal representation of the |
| 117 | +cost model (and some caching is done to avoid repeated updates). The update |
| 118 | +process involves a flattened representation of the cost model where each parameter |
| 119 | +has a name obtained from the JSON structure: for example, the four numbers in |
| 120 | +the costing function for `multiplyInteger` are called |
| 121 | +`multiplyInteger-cpu-arguments-intercept`, |
| 122 | +`multiplyInteger-cpu-arguments-slope`, |
| 123 | +`multiplyInteger-memory-arguments-intercept`, and `multiplyInteger-memory-arguments-slope` (see |
| 124 | +[this file](https://github.com/IntersectMBO/plutus/blob/master/doc/notes/cost-model-representations/cost-model-representations.md) |
| 125 | +for more on the different cost model representations). |
| 126 | + |
| 127 | +The three modules `PlutusLedgerAPI.V[123].ParamName` define `ParamName` types |
| 128 | +which enumerate the cost model parameter names for each Plutus ledger language. |
| 129 | +These have one constructor for each cost model parameter, and the names of the |
| 130 | +constructors are obtained from the flattened cost model parameter names by capitalising |
| 131 | +the first letter of the name and replacting all occurrences of `-` by `'`. Thus the |
| 132 | +parameter names for `multiplyInteger` are represented by the following constructors |
| 133 | + |
| 134 | +``` |
| 135 | + | MultiplyInteger'cpu'arguments'intercept |
| 136 | + | MultiplyInteger'cpu'arguments'slope |
| 137 | + | MultiplyInteger'memory'arguments'intercept |
| 138 | + | MultiplyInteger'memory'arguments'slope |
| 139 | +``` |
| 140 | + |
| 141 | +Before a new builtin can be enabled on the chain the `ParamName` types must be |
| 142 | +updated by adding constructors for the parameter names for the costing functions |
| 143 | +for the new builtin **at the end** of the `ParamName` type. Do **NOT** change |
| 144 | +the order of the existing constructors or add any new entries in the middle. |
| 145 | +The order of new cost model parameters doesn't matter, but it's easiest to add |
| 146 | +them in the order in which they appear in the JSON files. |
| 147 | + |
| 148 | +Note that we don't add the actual values of the parameters, which are instead |
| 149 | +obtained from the ledger via the protocol parameters. Values for new cost model |
| 150 | +parameters must be added to the protocol parameters during a parameter update, |
| 151 | +and after the parameter names have been added to the `ParamName` types a |
| 152 | +representation of the cost model parameters suitable for the parameter update |
| 153 | +can be obtained using the `dump-cost-model-parameters` executable. See the |
| 154 | +`--help` option for information about available output formats; the default |
| 155 | +`--json` option should usually be used. |
| 156 | + |
| 157 | +#### Tests |
| 158 | +After extending the `ParamName` types also update the "length" tests in |
| 159 | +`Spec.CostModelParams` and `Spec.Data.CostModelParams` to use the new numbers of |
| 160 | +constructors in the `ParamName` types. |
| 161 | + |
| 162 | +### Available builtins and Plutus Core language versions |
| 163 | + |
| 164 | +Builtins are introduced in batches and each batch is enabled on the chain at a |
| 165 | +hard fork (equivalent to a new major protocol version (PV)). We have also |
| 166 | +introduced different Plutus Code ledge languages (or LLs: these are currently |
| 167 | +PlutusV1, PlutusV2, and PlutusV3) at various hard forks and up to and including |
| 168 | +PV10 it was only possible to enable new builtins for the most recent ledger |
| 169 | +language: for example the `verifyEcdsaSecp256k1Signature` function was |
| 170 | +introduced in PlutusV2 at PV8 and also became available in PlutusV3 when that |
| 171 | +was introduced at PV9, but at the time of writing (PV10) it has never been |
| 172 | +available in PlutusV1. This situation has led to a rather complicated system of |
| 173 | +checks for which builtins are available in which (LL,PV) combinations. |
| 174 | + |
| 175 | +Fortunately, from PV11 onwards all builtins will become available in all Plutus |
| 176 | +ledger languages, and this simplifies the process of adding new builtins to the |
| 177 | +availability check (remember that this is required to account for the fact that |
| 178 | +at a given time different nodes may be running different versions of the node |
| 179 | +software, which may have different builtins available). |
| 180 | + |
| 181 | +The on-chain builtin availability check is performed by the |
| 182 | +`builtinsAvailableIn` function in `PlutusLedgerApi.Common.Versions`. This makes |
| 183 | +use of lists defined in the same module which contain the different batches of |
| 184 | +builtins . Once a batch of builtins has been released, the corresponding list |
| 185 | +must **NOT** subsequently be altered in any way. |
| 186 | + |
| 187 | +When new builtins are added to Plutus Core but we don't yet intend to release |
| 188 | +them, add them to a new batch (say `batch<N>`) and associate them with |
| 189 | +`futurePV` in all Plutus ledger languages in the function `builtinsIntroducedIn` |
| 190 | +in `PlutusLedgerApi.Common.Version`. More builtins can be added to this batch |
| 191 | +until we get to the point where we're preparing to release the entire batch. |
| 192 | + |
| 193 | +When we get to the point where we intend to release a new batch of builtins in |
| 194 | +an approaching hard fork, do the following: |
| 195 | + * Add a variable corresponding to the new PV, for example `somethingPV`, in `PlutusLedgerApi.Common.ProtocolVersions`. |
| 196 | + * Update `newestPV` to be equal to `somethingPV`. |
| 197 | + * Also update `knownPVs` to include `somethingPV`. |
| 198 | + * Update `builtinsIntroducedIn` say that `batch<N>` will be available in `somethingPV`. |
| 199 | + * Update the tests in `Spec.Versions` and `Spec.Data.Versions` to include the |
| 200 | + names of the new builtins in the `testPermittedBuiltins` functions. |
| 201 | + |
| 202 | +It is possible that we may have added a number of new builtins to Plutus Core |
| 203 | +but decide to enable only some subset at the next hard fork. In this case, |
| 204 | +create a new batch `batch<N+1>`, move the builtins aren't to be released into it |
| 205 | +and and associate `batch<N+1>` with `futurePV` in `builtinsIntroducedIn`. Leave |
| 206 | +the builtins which we _do_ plan to release in `batch<N>` and proceed as above. |
| 207 | +Also make sure that the constructors for the builtins in `batch<N>` come before |
| 208 | +the ones for `batch<N+1>` in the `ParamNames` types: this may require |
| 209 | +re-ordering the constructors (but **NOT** the ones for batches before |
| 210 | +`batch<N>`). |
| 211 | + |
| 212 | +**DO NOT** change `testBuiltinAvailabilityCompatibility` in `Spec.Versions` and |
| 213 | +`Spec.Data.Versions`. This checks that old and new versions of |
| 214 | +builtinsAvailableIn agree up to the Plomin hard fork (the start of PV10) and it |
| 215 | +should never need to be changed in future. |
| 216 | + |
| 217 | +There are also some tests in `PlutusLedgerApi.Test.V3.EvaluationContext` that |
| 218 | +probably don't make sense any more and if nothing goes wrong it's probably safe |
| 219 | +to ignore them. These will be reworked or removed at a later date. |
0 commit comments