기술자료/DAML

DAML 애플리케이션 업그레이드 및 확장

socl 2020. 10. 12. 13:51

참고 : 교차 SDK 업그레이드(Cross-SDK upgrades)에는 DAML-LF 1.8 이상이 필요합니다. SDK 1.0부터 시작하는 기본값입니다. 이전 릴리스의 경우 daml.yamlbuild-options: ["--target=1.8"] 을 추가하여 DAML-LF 1.8을 선택합니다.

단일 운영자가 제어하는 중앙 집중식 데이터베이스로 지원되는 애플리케이션에서는 모든 기존 데이터를 새로운 데이터 모델로 마이그레이션하는 애플리케이션을 한 번에 업그레이드할 수 있습니다.

그러나 분산원장에서 실행되는 DAML 애플리케이션에서 계약 서명자들은 특정 버전의 템플릿에 동의했습니다. 예를 들어, 해당 템플릿의 계약 서명자의 동의 없이 새로운 선택(choice)으로 확장하여 템플릿의 정의를 변경하면 DAML에서 제공하는 권한 부여 보증을 완전히 깨지게 됩니다.

따라서 DAML은 업그레이드 및 확장에 대해 다른 접근방식을 취합니다. DAML이 제공하는 기본 보증을 회피하는 별도의 데이터 마이그레이션 개념을 갖는 것이 아니라, 대신 업그레이드는 DAML 계약으로 표현됩니다. 이는 다른 DAML 계약에 적용되는 동일한 보증 및 규칙이 업그레이드에도 적용됨을 의미합니다.

따라서 DAML 애플리케이션에서는 기존 계약을 해당 계약의 최신 버전으로 대체하는 작업 대신 기존 애플리케이션의 확장으로 업그레이드를 생각하는 것이 합리적입니다. 기존 템플릿은 원장에 남아 있으며 계속 사용할 수 있습니다. 기존 템플릿의 계약은 최신 버전으로 자동 대체되지 않습니다. 그러나 응용 프로그램은 새 템플릿으로 확장되며 계약의 모든 서명자가 동의하면 이전 버전의 계약을 보관하고 대신 새 계약을 만들 수 있습니다.

업그레이드 계약 구성

업그레이드 계약은 업그레이드 중인 템플릿에 따라 다릅니다. 그러나 대부분의 경우 공통 패턴이 있습니다. 여기서는 간단한 코인 템플릿의 예를 예로 사용합니다. 코인의 향후 버전이 있을 것이라는 예견이 있으므로 Coin 의 정의를 CoinV1 이라는 모듈에 배치합니다.

module CoinV1 where

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

Coin 에는 발행자와 소유자가 있으며 둘 다 서명자입니다.우리의 목표는 1000개의 동전을 나타내는 1000개의 계약이 필요 없도록 동전의 수를 나타내는 필드로 이 코인 템플릿을 확장하는 것입니다. (실제 응용 프로그램에서는 이러한 코인을 병합하고 분할하기위한 선택 사항도 필요하겠지만, 단순성을 위해 여기서는 생략합니다.) 여기에서는 새 템플릿에 다른 이름을 사용합니다. 템플릿은 트리플 (PackageId, ModuleName, TemplateName)로 식별되므로 필요하지 않습니다.

module CoinV2 where

template CoinWithAmount
  with
    issuer : Party
    owner : Party
    amount : Int
  where
    signatory issuer, owner

다음으로 서명자, 발급자 및 소유자가 업그레이드 되는 계약에 동의할 수 있는 방법을 제공해야 합니다. 발행자와 소유자가 각각의 코인 계약에 대해 개별적으로 업그레이드에 동의해야 하는 구조로 구성할 수 있습니다. 그러나 이들 모두에 대한 템플릿 정의는 동일하기 때문에 일반적으로 대부분의 응용 프로그램에서 필요하지 않습니다. 대신 서명자로부터 동의서를 한 번만 수집하고 이를 사용하여 모든 코인을 업그레이드합니다. 여기에 여러 서명자가 포함되어 있으므로 제안-수락(Proposal-Accept) 워크플로우를 사용합니다. 먼저 발행자가 생성할 UpgradeCoinProposal 템플릿을 정의합니다. 이 템플릿에는 소유자가 행사할 수 있는 수락 선택(Accept choice) 사항이 있으며 이를 통해 UpgradeCoinAgreement 가 생성됩니다.

template UpgradeCoinProposal
  with
    issuer : Party
    owner : Party
  where
    signatory issuer
    observer owner
    key (issuer, owner) : (Party, Party)
    maintainer key._1
    choice Accept : ContractId UpgradeCoinAgreement
      controller owner
      do create UpgradeCoinAgreement with ..

이제 UpgradeCoinAgreement 템플릿을 정의할 수 있습니다. 이 템플릿에는 코인 계약의 계약 ID를 가져와서 해당 코인 계약을 보관(archive)하고 동일한 발행자 및 소유자와 금액(amount)이 1로 설정된 CoinWithAmount 계약을 생성하는 하나의 비소비(nonconsuming) 선택(choice) 사항이 있습니다.

template UpgradeCoinAgreement
  with
    issuer : Party
    owner : Party
  where
    signatory issuer, owner
    key (issuer, owner) : (Party, Party)
    maintainer key._1
    nonconsuming choice Upgrade : ContractId CoinWithAmount
      with
        coinId : ContractId Coin
      controller issuer
      do coin <- fetch coinId
         assert (coin.issuer == issuer)
         assert (coin.owner == owner)
         archive coinId
         create CoinWithAmount with
           issuer = coin.issuer
           owner = coin.owner
           amount = 1

coin-1.0.0 빌드 및 배포

먼저 coin-1.0.0 을 빌드하고 배포하여 모든 것이 작동하는지 살펴보겠습니다. 그런 다음 CoinWithAmount 템플릿이 포함된 coin-2.0.0 을 배포하고 업그레이드하는 방법을 살펴보겠습니다.

먼저 배포 할 수있는 샌드 박스 원장이 필요합니다.

$ daml sandbox --port 6865

이제 우리는 코인의 원래 버전에 대한 프로젝트를 설정할 것입니다. 프로젝트에는 아래 예제에서 일부 코인을 발행 할 수있는 CoinProposal 템플릿이 포함되어 있습니다.

다음은 프로젝트 설정 입니다.

name: coin
version: 1.0.0
dependencies:
  - daml-prim
  - daml-stdlib

이제 Coin-1.0.0 을 구축하고 배포 할 수 있습니다.

$ cd example/coin-1.0.0
$ daml build
$ daml ledger upload-dar --port 6865

coin-1.0.0 코인 생성하기

코인을 만들어 봅시다!

네비게이터를 사용하여 원장에 연결하고 Alice가 발행하고 Bob이 소유 한 두 개의 코인을 만듭니다.

$ cd example/coin-1.0.0
$ daml navigator server localhost 6865

브라우저에서 http://localhost:4000 로 접속후 다음을 진행합니다.

  1. Alice로 로그인 :

    • 템플릿 탭을 선택합니다.
    • Alice가 발행자이고 Bob이 소유자 인 CoinProposal을 생성합니다.
    • 같은 방법으로 두 번째 제안을 만듭니다.
  2. Bob으로 로그인 :

    • 두 제안 계약에서 CoinProposal_Accept 선택을 행사하십시오.

coin-2.0.0 빌드 및 배포

이제 우리는 amount 필드를 포함하는 개선 된 코인을 위한 프로젝트를 설정합니다. 이 프로젝트에는 CoinWithAmount 템플릿만 포함되어 있습니다. 업그레이드 템플릿은 세 번째 코인 업그레이드 패키지에 있습니다. 동일한 패키지에 업그레이드 템플릿을 포함 할 수 있지만 이는 새로운 CoinWithAmount 템플릿을 포함하는 패키지가 이전 버전에 의존한다는 것을 의미합니다. 여기에서 업그레이드 템플릿을 별도의 패키지에 보관하는 접근 방식을 사용하면, 모든 코인을 업그레이드 한 후에는 coin-1.0.0 패키지가 더 이상 필요하지 않습니다.

여기서는 확장 프로그램이 항상 별도의 패키지로 이동해야한다는 점을 강조 할 가치가 있습니다. 원래 프로젝트에 새 정의를 추가하고 다시 빌드하고 다시 배포 할 수는 없습니다. 이는 암호화로 계산 된 패키지 식별자가 변경되어 원장에있는 coin-1.0.0 의 원래 Coin 계약의 패키지 식별자와 일치하지 않기 때문입니다.

다음은 새 프로젝트 설정 입니다.

name: coin
version: 2.0.0
dependencies:
  - daml-prim
  - daml-stdlib

이제 coin-2.0.0 을 구축하고 배포 할 수 있습니다.

$ cd example/coin-2.0.0
$ daml build
$ daml ledger upload-dar --port 6865

코인 업그레이드 빌드 및 배포

coin-1.0.0coin-2.0.0 을 빌드하고 배포했으므로 이제 업그레이드 패키지 coin-upgrade 를 빌드할 준비가 되었습니다. 프로젝트 구성은 data-dependencies 필드를 통해 coin-1.0.0coin-2.0.0 을 모두 참조합니다. 이를 통해 각 패키지에서 모듈을 가져올 수 있으므로 이미 원장에 업로드 한 패키지의 템플릿을 참조할 수 있습니다.

이 예제를 따른다면, path/to/coin-1.0.0.darpath/to/coin-2.0.0.dar 는 각 프로젝트를 빌드하여 생성된 DAR 파일은 상대 또는 절대 경로로 대체되어야 합니다. 일반적으로 coin-1.0.0coin-2.0.0 프로젝트는 파일 시스템의 동일 디렉터리이므로이 경로는 ../coin-1.0.0/.daml/dist/coin-1.0.0.dar 가 됩니다.

name: coin-upgrade
version: 1.0.0
dependencies:
  - daml-prim
  - daml-stdlib
data-dependencies:
  - path/to/coin-1.0.0.dar
  - path/to/coin-2.0.0.dar

업그레이드 계약서에 대한 DAML은 신규 및 이전 코인 버전 모두에 대한 모듈을 가져(import)옵니다.

module UpgradeFromCoinV1 where
import CoinV2
import CoinV1

이제 코인 업그레이드를 구축하고 배포할 수 있습니다. DAR을 업로드하면 종속성도 업로드되므로 이전에 coin-1.0.0coin-2.0.0 이 이미 배포되지 않은 경우 코인 업그레이드 배포의 일부로 배포될 것이라는 것에 유의하세요.

$ cd example/coin-upgrade
$ daml build
$ daml ledger upload-dar --port 6865

기존 코인을 coin-1.0.0에서 coin-2.0.0으로 업그레이드

네비게이터를 다시 시작합니다.

$ cd example/coin-upgrade
$ daml navigator server localhost 6865

마지막으로 브라우저를 http://localhost:4000 으로 접속후, 코인 업그레이드에 영향을 줄 수 있습니다.

  1. Alice로 로그인

    1. 템플릿 탭을 선택합니다.
    2. Alice가 발행자이고 Bob이 소유자 인 UpgradeCoinProposal 을 생성합니다.
  2. Bob으로 로그인

    1. UpgradeCoinAgreement 를 생성하여 Accept 선택(choice)을 행사(Exercise)하십시오.
  3. Alice로 다시 로그인

    1. UpgradeCoinAgreement 를 반복적으로 사용하여 Alice가 발행자이고 Bob이 소유자 인 모든 코인을 업그레이드하십시오.

추가 단계

위의 코인 모델 업그레이드를 위해 Navigator를 통해 모든 단계를 수동으로 수행했습니다. 그러나 Alice가 수백만 개의 코인을 발행했다면 모든 업그레이드 단계를 수동으로 수행하는 것이 불가능 해집니다. 따라서 이러한 단계를 자동화해야 합니다. 다음 섹션에서 자동 업그레이드의 잠재적인 구현방안에 대해 살펴보겠습니다.


업그레이드 프로세스 자동화 (Automating the Upgrade Process)

이 섹션에서는 DAML 스크립트 및 DAML 트리거를 사용하여 코인 프로세스의 업그레이드를 자동화할 것입니다. 업그레이드 자동화는 업그레이드 모델과 마찬가지로 개별 애플리케이션에 따라 다릅니다. 그럼에도 불구하고 우리는 여기에 나타난 패턴이 자주 발생한다는 것을 발견하게 됩니다.

업그레이드 구성

업그레이드 중에 수행되는 세 가지 종류의 작업이 있습니다.

  1. Alice는 UpgradeCoinProposal 계약을 생성합니다. 여기서 Alice가 발행 한 모든 코인 계약을 업그레이드하려고 한다고 가정합니다. UpgradeCoinProposal 제안은 각 소유자마다 다르므로 Alice는 소유자 당 하나의 UpgradeCoinProposal 을 만들어야 합니다. 잠재적으로 많은 소유자가 있을 수 있지만 이 단계는 Alice가이 시점 이후에 더 많은 코인 계약을 발행하지 않을 것이라고 가정하고 한 번만 수행하면 됩니다.
  2. Bob과 다른 소유자는 UpgradeCoinProposal 을 수락합니다. 이 예제를 간단하게 하기 위해 Alice가 발행 한 코인 만 있다고 가정합니다. 따라서 각 소유자는 최대 하나의 제안을 수락해야 합니다.
  3. 소유자가 업그레이드 제안을 수락하면 Alice는 각 코인을 업그레이드해야 합니다. 이는 Alice가 각 코인에 대해 한 번씩 업그레이드 선택(choice)을 실행해야 함을 의미합니다. 소유자는 모두 동시에 업그레이드를 수락하지 않으며 일부는 결코 수락하지 않을 수도 있습니다. 따라서 이는 업그레이드를 수락하는 즉시 주어진 소유자의 모든 코인을 업그레이드하는 장기 실행 프로세스 여야 합니다.

이러한 제약을 감안할 때 업그레이드를 위해 다음 도구를 사용할 것입니다.

  1. Alice에 의해 한번 실행되고, 각 소유자에 대한 UpgradeCoinProposal 계약을 생성하는 DAML 스크립트.
  2. Navigator는 UpgradeCoinProposal 을 Bob으로 수락합니다. DAML 스크립트를 사용하여 제안을 수락할 수도 있지만, 이 단계는 웹 UI의 일부로 노출되는 경우가 많으므로 Navigator에서 대화식으로 수행하는 것은 해당 워크플로워와 더 밀접하게 유사합니다.(수동으로 참여자들에 의해 결정되므로)
  3. 해당 UpgradeCoinAgreement 가 있는 모든 코인 계약을 업그레이드하는 장기 실행 DAML 트리거입니다.

DAML 스크립트 구현

DAML 스크립트에서 먼저 ACS (Active Contract Set) 를 쿼리하여 우리가 발행 한 모든 코인 계약을 찾습니다. 다음으로 각 계약의 소유자를 추출하고 동일한 소유자에게 발행된 여러 코인의 중복을 제거합니다. 마지막으로 소유자 전체를 순환하며(loop) UpgradeCoinAgreement 계약을 생성합니다.

initiateUpgrade : Party -> Script ()
initiateUpgrade issuer = do
  coins <- query @Coin issuer
  let myCoins = filter (\(_cid, c) -> c.issuer == issuer) coins
  let owners = dedup $ map (\(_cid, c) -> c.owner) myCoins
  forA_ owners $ \owner -> do
    debug ("Creating upgrade proposal for: " <> show owner)
    submit issuer $ createCmd (UpgradeCoinProposal issuer owner)

DAML 트리거(Trigger) 구현

트리거에는 사용자 지정 사용자 상태(state) 및 하트비트(heartbeat)가 필요하지 않으므로 관심을 가질 유일한 필드는 rule 입니다.

upgradeTrigger : Trigger ()
upgradeTrigger = Trigger with
  initialize = \_acs -> ()
  updateState = \_acs _msg () -> ()
  registeredTemplates = AllInDar
  heartbeat = None
  rule = triggerRule

rule 에서 먼저 우리가 발행 한 모든 계약과 코인을 걸러(filter)냅니다. 다음으로 모든 계약을 반복합니다. 각 계약에 대해 계약 소유자별로 코인을 필터링하고 마지막으로 upgrade 선택(choice)을 실행하여 코인을 업그레이드합니다. 코인을 보류 중으로 표시하여 ACS에서 일시적으로 제거하므로 규칙이 빠르게 연속적으로 트리거 되는 경우, 트리거가 동일한 코인을 여러 번 업그레이드하려는 시도를 중지합니다.

triggerRule : Party -> ACS -> Time -> Map CommandId [Command] -> () -> TriggerA ()
triggerRule issuer acs _ _ _ = do
  let agreements =
        filter (\(_cid, agreement) -> agreement.issuer == issuer) $
        getContracts @UpgradeCoinAgreement acs
  let allCoins =
        filter (\(_cid, coin) -> coin.issuer == issuer) $
        getContracts @Coin acs
  forA_ agreements $ \(agreementCid, agreement) -> do
    let coinsForOwner = filter (\(_cid, coin) -> coin.owner == agreement.owner) allCoins
    forA_ coinsForOwner $ \(coinCid, _) ->
      emitCommands
        [exerciseCmd agreementCid (Upgrade coinCid)]
        [toAnyContractId coinCid]

트리거는 장기 실행 프로세스이며 원장 상태가 변경될 때마다 rule 이 실행됩니다. 따라서 소유자가 업그레이드 제안을 수락할 때마다 트리거가 규칙을 실행하고 해당 소유자의 모든 코인을 업그레이드합니다.

업그레이드 배포 및 실행

DAML 스크립트와 트리거를 정의했으므로 이제 사용할 차례입니다! 이전 섹션에서 실행 중인 Sandbox가 여전히 있는 경우 모든 데이터를 지우려면 재시작하세요.

먼저 코인 업그레이드 DAR을 전달하는 샌드 박스를 시작합니다. DAR에는 모든 전이적(transitive) 종속성이 포함되므로 여기에는 coin-1.0.0coin-2.0.0 이 포함됩니다.

$ cd example/coin-upgrade
$ daml sandbox .daml/dist/coin-upgrade-1.0.0.dar

여기서 설정을 단순화하기 위해 DAML 스크립트를 사용하여 Alice, Bob 및 Charlie와 Alice의 3자 계약과 Alice가 발행한 2개의 코인 계약 발행 (하나는 Bob 소유, 다른 하나는 Charlie 소유)을 생성합니다.

setup : Script ()
setup = do
  alice <- allocatePartyWithHint "Alice" (PartyIdHint "Alice")
  bob <- allocatePartyWithHint "Bob" (PartyIdHint "Bob")
  charlie <- allocatePartyWithHint "Charlie" (PartyIdHint "Charlie")
  bobProposal <- submit alice $ createCmd (CoinProposal alice bob)
  submit bob $ exerciseCmd bobProposal CoinProposal_Accept
  charlieProposal <- submit alice $ createCmd (CoinProposal alice charlie)
  submit charlie $ exerciseCmd charlieProposal CoinProposal_Accept
  pure ()

다음과 같이 스크립트를 실행하십시오.

$ cd example/coin-initiate-upgrade
$ daml build
$ daml script --dar=.daml/dist/coin-initiate-upgrade-1.0.0.dar --script-name=InitiateUpgrade:setup --ledger-host=localhost --ledger-port=6865 --wall-clock-time

이제 coin-initiate-upgrade 디렉토리에서 Navigator를 시작하고 Alice로 로그인하면 두 개의 Coin 계약을 볼 수 있습니다.

다음으로 Alice에 대한 트리거를 실행합니다. 이 예제의 나머지 부분에서 트리거가 계속 실행됩니다.

$ cd example/coin-upgrade-trigger
$ daml build
$ daml trigger --dar=.daml/dist/coin-upgrade-trigger-1.0.0.dar --trigger-name=UpgradeTrigger:upgradeTrigger --ledger-host=localhost --ledger-port=6865 --ledger-party=Alice --wall-clock-time

트리거가 실행되면 이제 스크립트를 실행하여 UpgradeCoinProposal 계약을 생성할 수 있습니다 (트리거를 시작하기 전에 수행할 수도 있습니다). 스크립트는 Party 유형의 인수를 사용합니다. "Alice"를 포함하는 파일 party.json 을 가리킬 --input-file 인수를 통해 이것을 전달할 수 있습니다. 이를 통해 스크립트의 코드를 변경하지 않고도 파티를 변경할 수 있습니다.

$ cd example/coin-initiate-upgrade
$ daml build
$ daml script --dar=.daml/dist/coin-initiate-upgrade-1.0.0.dar --script-name=InitiateUpgrade:initiateUpgrade --ledger-host=localhost --ledger-port=6865 --wall-clock-time --input-file=party.json

이 시점에서 트리거가 실행되고 Bob과 Charlie에 대한 UpgradeCoinProposal 계약이 생성되었습니다. 남은 일은 제안을 받아들이는 것입니다. 그러면 트리거가 자동으로 코인 계약을 선택하고 업그레이드합니다.

먼저 Navigator를 시작하고 Bob으로 로그인합니다. UpgradeCoinProposal 을 클릭하고 수락하십시오. 이제 계약 탭으로 돌아가면 코인 계약이 보관되고 대신 새로운 CoinWithAmount 업그레이드가 있음을 알 수 있습니다. 우리의 트리거가 성공적으로 코인을 업그레이드했습니다!

다음으로 Charlie로 로그인하고 UpgradeCoinProposal 을 수락하십시오. Bob과 마찬가지로 Coin 계약이 보관되고 대신 새로운 CoinWithAmount 계약이 있음을 알 수 있습니다.

Alice가 발행 한 모든 코인 계약을 업그레이드했으므로 이제 트리거를 중지하고 업데이트 성공을 선언할 수 있습니다.

원문 : Upgrading and extending DAML applications
https://docs.daml.com/1.5.0/upgrade/index.html

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

DAML 2.0의 Party와 User 관리  (0) 2023.08.08
DAML REPL  (0) 2020.10.12
DAML 스크립트  (0) 2020.10.07