SCU: a command can run under a package version the client was not compiled against

One observation from running Smart Contract Upgrade on a sandbox, with the exact errors, in case it saves someone a debugging session.

Setup: dpm 1.0.22, Daml SDK 3.4.11, single-participant dpm sandbox (its log banner says Starting Canton version 3.5.18), Daml Script 3.4.11 and the JSON API. Two versions of one package, 0.1.0 and 0.2.0, the second adding a trailing Optional field to one template; dpm upgrade-check --both passes for the pair.

The rule itself is documented: with no package_id_selection_preference, the participant resolves the package name to the highest vetted version. What the documentation does not show is how that looks from the client.

After 0.2.0 was vetted, a Daml Script compiled against 0.1.0 submitted an exercise with no preference. The ledger executed the 0.2.0 code and the resulting contract carried the 0.2.0 package id. The only failure was on the client, when the script runner could not translate the result: Failed to translate create argument: Lookup(NotFound(Package(00a85a8b...))). The ledger side effect happened under a version the client had never seen, and the client’s error says nothing about versions.

The mirror case: a script compiled against 0.2.0 and run before 0.2.0 was uploaded was not rejected. The participant resolved the name to the only vetted version, 0.1.0, executed that, and again only the runner failed, with Lookup(NotFound(Package(3b3ac851...))). So “package not vetted” never appears as an error in this configuration.

Pinning works as documented: packagePreference in Daml Script, packageIdSelectionPreference on the JSON API. All of this is on one participant; we have not looked at how it behaves across participants.

One question, mostly for the Digital Asset folks here: is there a recommended way for a client to state the version it was compiled against, so that a mismatch fails at submission rather than executing under another version and failing only on result translation?

Not from DA so they’ll still confirm, but yes.

In Daml Script, that’s exerciseExactCmd and the other *ExactCmd variants. The plain commands strip the package id and send a by-name template id, which is why the participant gets to pick.

The exact variants keep the id of the package the script was compiled against, so there is nothing to resolve: it runs under that version, and if the participant doesn’t have it you get NOT_FOUND: PACKAGE_NOT_FOUND at submission instead of the translation error.

Same effect as your packagePreference pin, but the id comes from the DAR instead of being passed in.

@crisog thanks, that was the missing piece. We ran it on a fresh sandbox (Canton 3.4.11 this time, Daml Script 3.4.11, a package whose 0.2.0 adds one trailing Optional field):

  • A 0.2.0 script using createExactCmd against a ledger with only 0.1.0 vetted failed at submission: NOT_FOUND: PACKAGE_NOT_FOUND ... Couldn't find package cc1d3bc4... while looking for template or interface cc1d3bc4...:Demo:Counter.
  • A 0.1.0 script using exerciseExactCmd after 0.2.0 was vetted ran under 0.1.0, and the new contract kept the 0.1.0 package id.
  • On the JSON API the pin comes from the template id: <package-id>:Demo:Counter ran under 0.1.0, #scu-exact-demo:Demo:Counter under 0.2.0.

Two things we did not expect. A plain createCmd from the 0.2.0 script, with the new field left empty and only 0.1.0 vetted, failed nowhere: the contract was created under 0.1.0 and the script reported success. So a create is quieter than the exercise case in the first post. And the pin has a cost: exerciseExactCmd from the 0.1.0 script on a contract created under 0.2.0 with the new field set failed with INTERPRETATION_UPGRADE_ERROR_TRANSLATION_FAILED ... to a value of type Demo:Counter@e703b7d1 fails. Pinning turns a silent version switch into a hard failure once the ledger holds contracts the pinned version cannot read.

For the DA folks: for a production client that must not run under a version it was not built against, is the package-id template id the intended tool, or packageIdSelectionPreference?