daml/docs/source/daml-script/index.rst
Richard Kapolnai f3caa5919d
rename "gRPC API" to "gRPC Ledger API" (#8046)
* rename "gRPC API" to "gRPC Ledger API"
an incremental step towards #8029
changelog_begin
changelog_end

* fix section underline
2020-11-24 16:34:07 +01:00

379 lines
16 KiB
ReStructuredText
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

.. Copyright (c) 2020 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
.. SPDX-License-Identifier: Apache-2.0
DAML Script
###########
.. toctree::
:hidden:
api/index
DAML scenarios provide a simple way for testing DAML models
and getting quick feedback in DAML studio. However, scenarios are run
in a special process and do not interact with an actual ledger. This
means that you cannot use scenarios to test other ledger clients,
e.g., your UI or :doc:`DAML triggers </triggers/index>`.
DAML Script addresses this problem by providing you with an API with
the simplicity of DAML scenarios and all the benefits such as being
able to reuse your DAML types and logic while running against an actual
ledger in addition to allowing you to experiment in :ref:`DAML Studio <scenario-script-results>`.
This means that you can use it to test automation logic, your
UI but also for :ref:`ledger initialization <script-ledger-initialization>`
where scenarios cannot be used (with the exception of :doc:`/tools/sandbox`).
You can also use DAML Script interactively using :doc:`/daml-repl/index`.
Usage
=====
Our example for this tutorial consists of 2 templates.
First, we have a template called ``Coin``:
.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- COIN_TEMPLATE_BEGIN
:end-before: -- COIN_TEMPLATE_END
This template represents a coin issued to ``owner`` by ``issuer``.
``Coin`` has both the ``owner`` and the ``issuer`` as signatories.
Second, we have a template called ``CoinProposal``:
.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- COIN_PROPOSAL_TEMPLATE_BEGIN
:end-before: -- COIN_PROPOSAL_TEMPLATE_END
``CoinProposal`` is only signed by the ``issuer`` and it provides a
single ``Accept`` choice which, when exercised by the controller will
create the corresponding ``Coin``.
Having defined the templates, we can now move on to write DAML scripts
that operate on these templates. To get access to the API used to implement DAML scripts, you need to add the ``daml-script``
library to the ``dependencies`` field in ``daml.yaml``.
.. literalinclude:: ./template-root/daml.yaml.template
:start-after: # script-dependencies-begin
:end-before: # script-dependencies-end
We also enable the ``ApplicativeDo`` extension. We will
see below why this is useful.
.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- DAML_SCRIPT_HEADER_BEGIN
:end-before: -- DAML_SCRIPT_HEADER_END
Since on an actual ledger parties cannot be arbitrary strings, we
define a record containing all the parties that we will use in our
script so that we can easily swap them out.
.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- LEDGER_PARTIES_BEGIN
:end-before: -- LEDGER_PARTIES_END
Let us now write a function to initialize the ledger with 3
``CoinProposal`` contracts and accept 2 of them. This function takes the
``LedgerParties`` as an argument and return something of type ``Script
()`` which is DAML scripts equivalent of ``Scenario ()``.
.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- INITIALIZE_SIGNATURE_BEGIN
:end-before: -- INITIALIZE_SIGNATURE_END
First we create the proposals. To do so, we use the ``submit``
function to submit a transaction. The first argument is the party
submitting the transaction. In our case, we want all proposals to be
created by the bank so we use ``parties.bank``. The second argument
must be of type ``Commands a`` so in our case ``Commands (ContractId
CoinProposal, ContractId CoinProposal, ContractId CoinProposal)``
corresponding to the 3 proposals that we create. ``Commands`` is
similar to ``Update`` which is used in the ``submit`` function in
scenarios. However, ``Commands`` requires that the individual commands
do not depend on each other. This matches the restriction on the
Ledger API where a transaction consists of a list of commands. Using
``ApplicativeDo`` we can still use ``do``-notation as long as we
respect this and the last statement in the ``do``-block is of the form
``return expr`` or ``pure expr``.
In ``Commands`` we use ``createCmd`` instead of
``create`` and ``exerciseCmd`` instead of ``exercise``.
.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- INITIALIZE_PROPOSAL_BEGIN
:end-before: -- INITIALIZE_PROPOSAL_END
Now that we have created the ``CoinProposal``\ s, we want ``Alice`` and
``Bob`` to accept the proposal while the ``Bank`` will ignore the
proposal that it has created for itself. To do so we use separate
``submit`` statements for ``Alice`` and ``Bob`` and call
``exerciseCmd``.
.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- INITIALIZE_ACCEPT_BEGIN
:end-before: -- INITIALIZE_ACCEPT_END
Finally, we call ``pure ()`` on the last line of our script to match
the type ``Script ()``.
.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- INITIALIZE_PURE_BEGIN
:end-before: -- INITIALIZE_PURE_END
Party management
----------------
We have now defined a way to initialize the ledger so we can write a
test that checks that the contracts that we expect exist afterwards.
First, we define the signature of our test. We will create the parties
used here in the test, so it does not take any arguments.
.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- TEST_SIGNATURE_BEGIN
:end-before: -- TEST_SIGNATURE_END
Now, we create the parties using the ``allocateParty`` function. This
uses the party management service to create new parties with the given
display name. Note that the display name does not identify a party
uniquely. If you call ``allocateParty`` twice with the same display
name, it will create 2 different parties. This is very convenient for
testing since a new party cannot see any old contracts on the ledger
so using new parties for each test removes the need to reset the
ledger.
.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- TEST_ALLOCATE_BEGIN
:end-before: -- TEST_ALLOCATE_END
We now call the ``initialize`` function that we defined before on the
parties that we have just allocated.
.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- TEST_INITIALIZE_BEGIN
:end-before: -- TEST_INITIALIZE_END
Another option for getting access to the relevant party ids is to use
``listKnownParties`` to pick out the party with a given display
name. This is mainly useful in demo scenarios because display names
are not guaranteed to be unique.
.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- INITIALIZE_QUERY_BEGIN
:end-before: -- INITIALIZE_QUERY_END
Queries
-------
To verify the contracts on the ledger, we use the ``query``
function. We pass it the type of the template and a party. It will
then give us all active contracts of the given type visible to the
party. In our example, we expect to see one active ``CoinProposal``
for ``bank`` and one ``Coin`` contract for each of ``Alice`` and
``Bob``. We get back list of ``(ContractId t, t)`` pairs from
``query``. In our tests, we do not need the contract ids, so we throw
them away using ``map snd``.
.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- TEST_QUERIES_BEGIN
:end-before: -- TEST_QUERIES_END
Running a Script
----------------
To run our script, we first build it with ``daml build`` and then run
it by pointing to the DAR, the name of our script, the host and
port our ledger is running on and the time mode of the ledger.
``daml script --dar .daml/dist/script-example-0.0.1.dar --script-name ScriptExample:test --ledger-host localhost --ledger-port 6865``
Up to now, we have worked with parties that we have allocated in the
test. We can also pass in the path to a file containing
the input in the :doc:`/json-api/lf-value-specification`.
.. literalinclude:: ./template-root/ledger-parties.json
:language: daml
We can then initialize our ledger passing in the json file via ``--input-file``.
``daml script --dar .daml/dist/script-example-0.0.1.dar --script-name ScriptExample:initialize --ledger-host localhost --ledger-port 6865 --input-file ledger-parties.json``
If you open Navigator, you can now see the contracts that have been created.
While we will not use it here, there is also an ``--output-file``
option that you can use to write the result of a script to a file
using the DAML-LF JSON encoding. This is particularly useful if you need to consume
the result from another program.
.. _script-ledger-initialization:
Using DAML Script for Ledger Initialization
===========================================
You can use DAML script to initialize a ledger on startup. To do so,
specify an ``init-script: ScriptExample:initializeFixed`` field in
your ``daml.yaml``. This will automatically be picked up by ``daml
start`` and used to initialize sandbox. Since it is often useful to
create a party with a specific party identifier during development, you can
use the ``allocatePartyWithHint`` function which accepts not only the
display name but also a hint for the party identifier. On Sandbox, the hint
will be used directly as the party identifier of the newly allocated
party. This allows us to implement ``initializeFixed`` as a small
wrapper around the ``initialize`` function we defined above:
.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- INITIALIZE_FIXED_BEGIN
:end-before: -- INITIALIZE_FIXED_END
Migrating from Scenarios
------------------------
Existing scenarios that you used for ledger initialization can be
translated to DAML script but there are a few things to keep in mind:
#. You need to add ``daml-script`` to the list of dependencies in your
``daml.yaml``.
#. You need to import the ``Daml.Script`` module.
#. Calls to ``create``, ``exercise``, ``exerciseByKey`` and
``createAndExercise`` need to be suffixed with ``Cmd``, e.g.,
``createCmd``.
#. Instead of specifying a ``scenario`` field in your ``daml.yaml``,
you need to specify an ``init-script`` field. The initialization
script is specified via ``Module:identifier`` for both fields.
#. In DAML script, ``submit`` and ``submitMustFail`` are limited to
the functionality provided by the ledger API: A list of independent
commands consisting of ``createCmd``, ``exerciseCmd``,
``createAndExerciseCmd`` and ``exerciseByKeyCmd``. There are two
issues you might run into when migrating an existing scenario:
#. Your commands depend on each other, e.g., you use the result of
a ``create`` within a following command in the same
``submit``. In this case, you have two options: If it is not
important that they are part of a single transaction, split
them into multiple calls to ``submit``. If you do need them to
be within the same transaction, you can move the logic to a
choice and call that using ``createAndExerciseCmd``.
#. You use something that is not part of the 4 ledger API command
types, e.g., ``fetch``. For ``fetch`` and ``fetchByKey``, you
can instead use ``queryContractId`` and ``queryContractKey``
with the caveat that they do not run within the same
transaction. Other types of ``Update`` statements can be moved
to a choice that you call via ``createAndExerciseCmd``.
#. Instead of Scenarios ``getParty``, DAML Script provides you with
``allocateParty`` and ``allocatePartyWithHint``. There are a few
important differences:
#. Allocating a party always gives you back a new party (or
fails). If you have multiple calls to ``getParty`` with the same
string and expect to get back the same party, you should instead
allocate the party once at the beginning and pass it along to
the rest of the code.
#. If you want to allocate a party with a specific party id, you
can use ``allocatePartyWithHint x (PartyIdHint x)`` as a replacement for `getParty x`. Note that
while this is supported in DAML Studio and DAML for PostgreSQL, other
ledgers can behave differently and ignore the party id hint or
interpret it another way. Try to not rely on any specific
party id.
#. Instead of ``pass`` and ``passToDate``, DAML Script provides
``passTime`` and ``setTime``.
.. _daml-script-distributed:
Using DAML Script in Distributed Topologies
===========================================
So far, we have run DAML script against a single participant node. It
is also more possible to run it in a setting where different parties
are hosted on different participant nodes. To do so, pass the
``--participant-config participants.json`` file to ``daml script``
instead of ``--ledger-host`` and ``ledger-port``. The file should be of the format
.. literalinclude:: ./participants-example.json
:language: json
This will define a participant called ``one``, a default participant
and it defines that the party ``alice`` is on participant
``one``. Whenever you submit something as party, we will use the
participant for that party or if none is specified
``default_participant``. If ``default_participant`` is not specified,
using a party with an unspecified participant is an error.
``allocateParty`` will also use the ``default_participant``. If you
want to allocate a party on a specific participant, you can use
``allocatePartyOn`` which accepts the participant name as an extra
argument.
.. _daml-script-auth:
Running DAML Script against Ledgers with Authorization
======================================================
To run DAML Script against a ledger that verifies authorization,
you need to specify an access token. There are two ways of doing that:
1. Specify a single access token via ``--access-token-file
path/to/jwt``. This token will then be used for all requests so it
must provide claims for all parties that you use in your script.
2. If you need multiple tokens, e.g., because you only have
single-party tokens you can use the ``access_token`` field in the
participant config specified via ``--participant-config``. The
section on
:ref:`using DAML Script in distributed topologies <daml-script-distributed>`
contains an example. Note that you can specify the same participant
twice if you want different auth tokens.
If you specify both ``--access-token-file`` and
``--participant-config``, the participant config takes precedence and
the token from the file will be used for any participant that does not
have a token specified in the config.
.. _daml-script-json-api:
Running DAML Script against the HTTP JSON API
=============================================
In some cases, you only have access to the
:doc:`HTTP JSON API </json-api/index>` but not to the gRPC of a ledger, e.g., on
`project:DABL <https://projectdabl.com>`_. For this usecase, DAML
script can be run against the JSON API. Note that if you do have
access to the gRPC Ledger API, running DAML script against the JSON API does
not have any advantages.
To run DAML script against the JSON API you have to pass the ``--json-api`` parameter to ``daml script``. There are a few differences and limitations compared to running DAML Script against the gRPC Ledger API:
#. When running against the JSON API, the ``--host`` argument has to
contain an ``http://`` or ``https://`` prefix, e.g., ``daml
script --host http://localhost --port 7575 --json-api``.
#. The JSON API only supports single-command submissions. This means
that within a single call to ``submit`` you can only execute one
ledger API command, e.g., one ``createCmd`` or one ``exerciseCmd``.
#. The JSON API requires authorization tokens even when it is run
against a ledger that doesn't verify authorization. The section on
:ref:`authorization <daml-script-auth>` describes how to specify
the tokens.
#. The tokens must contain exactly one party in ``actAs`` and/or
``readAs``. This party will be used for ``submit`` and
``query``. Passing a party as the argument to ``submit`` and
``query`` that is different from the party in the token is an
error.
#. If you use multiple parties within your DAML Script, you need to
specify one token per party.
#. ``getTime`` will always return the Unix epoch in static time mode
since the time service is not exposed via the JSON API.
#. ``setTime`` is not supported and will throw a runtime error.