기술자료/DAML

DAML 스크립트

socl 2020. 10. 7. 15:35

DAML 시나리오는 DAML 모델을 테스트하고 DAML 스튜디오에서 빠른 피드백을 받을 수 있는 간단한 방법을 제공합니다. 그러나 시나리오는 특별한 프로세스에서 실행되며 실제 원장과 상호작용을 하지 않습니다. 이는 시나리오를 사용하여 다른 원장 클라이언트 (예 : UI 또는 DAML 트리거)를 테스트할 수 없음을 의미합니다.

DAML 스크립트는 DAML 시나리오의 단순성과 실제 원장에 대해 실행하는 동안 DAML 유형 및 로직을 재사용할 수 있는 것과 같은 모든 이점을 제공하는 API를 제공하여이 문제를 해결하고 DAML Studio에서 실험할 수 있습니다. 즉, 자동화 로직과 UI를 테스트하는 데 사용할 수 있을 뿐만 아니라 시나리오를 사용할 수 없는 원장 초기화에도 사용할 수 있습니다 (DAML Sandbox 제외).

DAML REPL을 사용하여 대화식으로 DAML 스크립트를 사용할 수도 있습니다.

사용법

이 자습서의 예제는 2 개의 템플릿으로 구성됩니다.

먼저 Coin 이라는 템플릿이 있습니다.

template Coin
  with
    issuer : Party
    owner : Party
  where
    signatory issuer, owner

이 템플릿은 발행자 (issuer) 가 소유자 (owner) 에게 발행 한 코인을 나타냅니다. 코인은 소유자와 발행인 모두를 서명자로 가지고 있습니다.

둘째, CoinProposal 이라는 템플릿이 있습니다.

template CoinProposal
  with
    coin : Coin
  where
    signatory coin.issuer
    observer coin.owner

    choice Accept : ContractId Coin
      controller coin.owner
      do create coin

CoinProposal 은 발행자 (issuer) 에 의해서만 서명되며 컨트롤러가 행사(exercised)할 때 해당 코인을 생성하는 단일 Accept 선택(choice)을 제공합니다.

템플릿을 정의했으므로 이제 템플릿에서 작동하는 DAML 스크립트를 작성할 수 있습니다. DAML 스크립트를 구현하는 데 사용되는 API에 액세스 하려면 daml.yamldependencies 필드에 daml-script 라이브러리를 추가해야 합니다.

dependencies:
  - daml-prim
  - daml-stdlib
  - daml-script

ApplicativeDo 확장도 활성화합니다. 이것이 왜 유용한 지 아래에서 볼 것입니다.

{-# LANGUAGE ApplicativeDo #-}

module ScriptExample where

import Daml.Script

실제 원장에서 당사자는 임의의 문자열이 될 수 없기 때문에(정의된 명칭) 스크립트에서 사용할 모든 당사자를 포함하는 레코드를 정의하여 쉽게 교환할 수 있습니다.

data LedgerParties = LedgerParties with
  bank : Party
  alice : Party
  bob : Party

이제 3 개의 CoinProposal 계약으로 원장을 초기화하고 그중 2 개를 수락하는 함수를 작성해 보겠습니다. 이 함수는 LedgerParties 를 인수로 사용하고 DAML 스크립트가 Scenario () 와 동일한 유형 인 Script () 유형을 반환합니다.

initialize : LedgerParties -> Script ()
initialize parties = do

먼저 제안서를 작성합니다. 이를 위해 submit 함수를 사용하여 트랜잭션을 제출합니다.
첫 번째 인수는 거래를 제출하는 당사자입니다. 우리의 경우 모든 제안이 은행에서 생성되기를 원하므로 parties.bank 를 사용합니다. 두 번째 인수는 Commands a 유형이어야 합니다. 이 경우 우리가 생성 한 3개의 제안에 해당하는 Commands (ContractId CoinProposal, ContractId CoinProposal, ContractId CoinProposal) 입니다. Commands 은 시나리오에서 submit 기능에 사용되는 Update 와 유사합니다. 그러나 Commands 을 사용하려면 개별 commands가 서로 의존하지 않아야 합니다(do not depend on each other). 이는 트랜잭션이 commands 목록으로 구성되는 Ledger API의 제한과 일치합니다.

ApplicativeDo를 사용하면 이것을 respect 한 do 표기법을 사용할 수 있습니다. Commands에서 createCmd 대신 create 를 사용하고 exercise 대신 exerciseCmd 를 사용합니다.

 (coinProposalAlice, coinProposalBob, coinProposalBank) <- submit parties.bank $ do
    coinProposalAlice <- createCmd (CoinProposal (Coin parties.bank parties.alice))
    coinProposalBob <- createCmd (CoinProposal (Coin parties.bank parties.bob))
    coinProposalBank <- createCmd (CoinProposal (Coin parties.bank parties.bank))
    pure (coinProposalAlice, coinProposalBob, coinProposalBank)

이제 CoinProposals 를 만들었으므로 Alice와 Bob이 제안을 수락하고 Bank는 자체적으로 생성 한 제안을 무시합니다. 이를 위해 Alice와 Bob에 대해 별도의 제출서를 사용하고 exerciseCmd 를 호출합니다.

coinAlice <- submit parties.alice $ exerciseCmd coinProposalAlice Accept
coinBob <- submit parties.bob $ exerciseCmd coinProposalBob Accept

마지막으로 Script () 유형과 일치하도록 스크립트의 마지막 줄에서 pure () 를 호출합니다.

pure ()

이제 원장을 초기화하는 방법을 정의하여 나중에 예상되는 계약이 존재하는지 확인하는 테스트를 작성할 수 있습니다.

먼저 테스트의 서명을 정의합니다. 여기에서 테스트에 사용된 당사자를 만들 것이므로 인수가 필요하지 않습니다.

test : Script ()
test = do

이제 allocateParty 함수를 사용하여 당사자를 만듭니다. 이것은 파티 관리 서비스(party management service)를 사용하여 지정된 이름의 새로운 파티를 만듭니다. 해당 이름은 당사자를 고유하게 식별하지 않습니다. 동일한 이름으로 allocateParty 를 두 번 호출하면 2 개의 다른 당사자가 생성됩니다. 이는 새로운 당사자가 원장에서 이전 계약을 볼 수 없기 때문에 테스트에 매우 편리합니다. 따라서 각 테스트에 대해 새 당사자를 사용하면 원장을 재설정할 필요가 없습니다.

alice <- allocateParty "Alice"
bob <- allocateParty "Bob"
bank <- allocateParty "Bank"
let parties = LedgerParties bank alice bob

이제 방금 할당 한 당사자에 대해 이전에 정의한 초기화 함수를 호출합니다.

initialize parties

원장의 계약서를 검증하기 위해 query 기능을 사용합니다. 템플릿 유형(type)과 파티를 전달합니다. 그런 다음 당사자가 볼 수 있는 주어진 유형의 모든 활성 계약(active contracts)을 제공합니다. 이 예에서 우리는 은행에 대해 하나의 활성 CoinProposal 과 Alice와 Bob 각각에 대해 하나의 Coin 계약을 볼수 있는 것으로 예상합니다. query 에서 (ContractId t, t) 쌍의 목록을 가져옵니다. 테스트에서는 계약 ID가 필요하지 않으므로 map snd 를 사용하여 폐기합니다.

proposals <- query @CoinProposal bank
assertEq [CoinProposal (Coin bank bank)] (map snd proposals)

aliceCoins <- query @Coin alice
assertEq [Coin bank alice] (map snd aliceCoins)

bobCoins <- query @Coin bob
assertEq [Coin bank bob] (map snd bobCoins)

스크립트를 실행하려면 먼저 daml build 명령어를 통해 빌드 후 DAR, 스크립트 이름, 원장이 실행 중인 호스트 및 포트, 원장의 시간 모드를 지정하여 실행합니다.

daml script --dar .daml/dist/script-example-0.0.1.dar --script-name ScriptExample:test --ledger-host localhost --ledger-port 6865

지금까지 우리는 테스트에서 할당 한 당사자들과 함께 진행하였습니다. 또한 DAML-LF JSON Encodinginput 을 포함하는 파일의 경로를 전달할 수 있습니다.

{
  "alice": "Alice",
  "bob": "Bob",
  "bank": "Bank"
}

그런 다음 --input-file 을 통해 json 파일을 전달하는 원장을 초기화 할 수 있습니다.

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

네비게이터를 열면 이제 생성된 계약을 볼 수 있습니다.

여기서는 사용하지 않지만 DAML-LF JSON 인코딩을 사용하여 스크립트 결과를 파일에 쓰는 데 사용할 수 있는 --output-file 옵션도 있습니다. 이것은 다른 프로그램의 결과(consume)를 소비해야 하는 경우 특히 유용합니다.

원장 초기화에 DAML 스크립트 사용

DAML 스크립트를 사용하여 시작 시 원장을 초기화를 할 수 있습니다. 그렇게 하려면 daml.yaml에 init-script : ScriptExample : initializeFixed 필드를 지정합니다. 이것은 daml start 에 의해 자동으로 선택되어 샌드 박스를 초기화하는 데 사용됩니다.

개발 중에 특정 이름으로 당사자를 생성하는 것이 유용한 경우가 많기 때문에, 표시 이름뿐만 아니라 당사자 식별자(이름)에 대한 힌트도 허용하는 allocatePartyWithHint 함수를 사용할 수 있습니다. 샌드 박스에서 힌트는 새로 할당된 당사자의 당사자 식별자로 직접 사용됩니다. 이를 통해 우리가 위에서 정의한 initialize 함수 주위에 작은 래퍼로 initializeFixed 를 구현할 수 있습니다.

initializeFixed : Script ()
initializeFixed = do
  bank <- allocatePartyWithHint "Bank" (PartyIdHint "Bank")
  alice <- allocatePartyWithHint "Alice" (PartyIdHint "Alice")
  bob <- allocatePartyWithHint "Bob" (PartyIdHint "Bob")
  let parties = LedgerParties{..}
  initialize parties

시나리오에서 마이그레이션

원장 초기화에 사용한 시나리오를 DAML 스크립트로 변환할 수 있지만 몇 가지 유의해야 할 사항이 있습니다.

  1. daml.yaml의 종속성(dependencies) 목록에 daml-script 를 추가해야 합니다.
  2. Daml.Script 모듈을 가져와야 합니다(import).
  3. create, exercise, exerciseByKeycreateAndExercise 에 대한 호출은 Cmd (예 : createCmd)를 접미사로 추가해야 합니다.
  4. daml.yaml에 scenario 필드를 지정하는 대신 init-script 필드를 지정해야 합니다. 초기화 스크립트는 두 필드 모두에 대해 Module : identifier 를 통해 지정됩니다.
  5. DAML 스크립트는 원장 API에서 사용 가능한 명령만 지원하므로 fetch 와 같은 함수를 직접 호출할 수 없습니다. 이것은 의도적인 것입니다. 초기화 스크립트는 원장 클라이언트가 생성할 수 없는 트랜잭션을 만들 수 없어야 합니다. Ledger API를 통해 노출되지 않은 메서드를 호출하려면 선택(single choice)로 새 템플릿을 만들고 createAndExercise 를 통해 호출할 수 있습니다.
  6. getParty x 에 대한 호출을 allocatePartyWithHint x (PartyIdHint x) 로 대체해야 합니다.

분산 토폴로지에서 DAML 스크립트 사용

지금까지 단일 참여자 노드에 대해 DAML 스크립트를 실행했습니다. 다른 참가자가 다른 참가자 노드에서 호스팅 되는 설정에서 실행하는 것도 가능합니다. 이렇게 하려면 --ledger-host 및 ledger-port 대신 --participant-config parties.json 파일을 daml 스크립트에 전달합니다.
파일 구조는 다음과 같습니다.

{
    "default_participant": {"host": "localhost", "port": 6866, "access_token": "default_jwt", "application_id": "myapp"},
    "participants": {
        "one": {"host": "localhost", "port": 6865, "access_token": "jwt_for_alice", "application_id": "myapp"},
        "two": {"host": "localhost", "port": 6865, "access_token": "jwt_for_bob", "application_id": "myapp"}
    },
    "party_participants": {"alice": "one", "bob": "two"}
}

이것은 one 이라는 참가자, 기본 참가자 를 정의하고 파티 alice 가 참가자 one 에 있음을 정의합니다. 파티(party)로 무언가를 제출할 때마다 해당 파티에 대해 참가자(participant)를 사용하거나 default_participant 가 지정되지 않은 경우 참가자를 사용합니다. default_participant가 지정되지 않은 경우 참가자가 지정되지 않은 당사자를 사용하면 오류가 발생합니다.

allocatePartydefault_participant 도 사용합니다. 특정 참가자에게 파티를 할당하려는 경우 참가자 이름을 추가 인수로 허용하는 allocatePartyOn 을 사용할 수 있습니다.

권한이 있는 원장에 대해 DAML 스크립트 실행 (Running DAML Script against Ledgers with Authorization)

권한을 확인하는 원장에 대해 DAML 스크립트를 실행하려면 액세스 토큰을 지정해야 합니다. 이를 수행하는 두 가지 방법이 있습니다.

  1. --access-token-file path/to/jwt 를 통해 단일 액세스 토큰을 지정합니다. 이 토큰은 모든 요청에 사용되므로 스크립트에서 사용하는 모든 당사자에 대한 클레임을 제공(provide claims)해야 합니다.
  2. 예를 들어 단일 당사자 토큰 만 있기 때문에 여러 토큰이 필요한 경우 --participant-config 를 통해 지정된 참가자 구성의 access_token 필드를 사용할 수 있습니다. 분산 토폴로지에서 DAML 스크립트 사용에 대한 섹션에는 예제가 포함되어 있습니다. 다른 인증 토큰을 원하는 경우 동일한 참가자를 두 번 지정할 수 있습니다.

--access-token-file--participant-config 를 모두 지정하면 참여자 구성(participant config)을 우선하며 파일에서 가지고온 토큰(token from the file)은 구성에 지정된 토큰이 없는 모든 참여자에게 사용됩니다.

HTTP JSON API에서 DAML 스크립트 실행

경우에 따라 HTTP JSON API만 액세스 할 수 있지만 원장의 gRPC에는 액세스 할 수 없습니다 (예 : project : DABL). 이 사례의 경우 JSON API에 대해 DAML 스크립트를 실행할 수 있습니다. gRPC API에 대한 액세스 권한이 있는 경우 JSON API에서 DAML 스크립트를 실행해도 이점이 없습니다(does not have any advantages).

JSON API에서 DAML 스크립트를 실행하려면 --json-api 매개 변수를 daml script 에 전달해야 합니다. gRPC API에서 DAML 스크립트를 실행하는 것과 비교할 때 몇 가지 차이점과 제약 사항이 있습니다.

  1. JSON API에 대해 실행할 때 --host 인수는 http:// 또는 https:// 접두사를 포함해야 합니다 (예 : daml script --host http://localhost --port 7575 --json-api).
  2. JSON API는 단일 명령 제출만 지원합니다(single-command submissions). 즉, 제출을 위한 단일 호출 내에서 하나의 원장 API 명령 (예 : createCmd 또는 exerciseCmd 하나)만 실행할 수 있습니다.
  3. JSON API는 인증이 필요없는 원장에 대해 실행되는 경우에도 인증 토큰이 필요합니다. 인증 섹션에서는 토큰을 지정하는 방법을 설명합니다.
  4. 토큰은 actAs 및 / 또는 readAs 에 정확히 하나의 당사자를 포함해야 합니다. 이 당사자는 submitquery 에 사용됩니다. 토큰의 당사자와 다른 submitquery 에 대한 인수로 당사자를 전달시 오류가 발생합니다.
  5. DAML 스크립트 내에서 여러 당사자를 사용하는 경우 당사자 당 하나의 토큰을 지정해야 합니다.
  6. getTime 은 시간 서비스가 JSON API를 통해 노출되지 않기 때문에 항상 정적 시간 모드(static time mode)에서 Unix epoch를 반환합니다.
  7. setTime 은 지원되지 않으며 런타임 오류가 발생합니다.

DAML Script Library API 참고

출처 : DAML Script
https://docs.daml.com/1.5.0/daml-script/index.html

'기술자료 > DAML' 카테고리의 다른 글

DAML 2.0의 Party와 User 관리  (0) 2023.08.08
DAML 애플리케이션 업그레이드 및 확장  (0) 2020.10.12
DAML REPL  (0) 2020.10.12