기술자료/Canton

Canton 시작하기(튜토리얼)

socl 2020. 9. 24. 15:06
canton 0.18.x 버전 기준으로 작성되어 있습니다.

개요

Canton은 DAML 원장 상호운용성 프로토콜입니다.

서로 다른 참여자 노드에서 호스팅 되는 파티는 DAML 및 Canton 프로토콜에 작성된 스마트 계약을 사용하여 거래할 수 있습니다. Canton 프로토콜을 사용하면 여러 DAML 원장을 단일 가상 글로벌 원장에 연결할 수 있습니다. DAML은 스마트 계약 언어로서 주어진 계약을 보거나 변경할 수 있는 권한을 가진 사람을 정의합니다.

Canton 동기화 프로토콜은 이러한 가시성 및 권한 부여 규칙을 시행하고 악의적인 행위자가 존재하는 경우에도 매우 높은 수준의 개인 정보 보호를 통해 데이터가 안정적으로 공유되도록 보장합니다. Canton 네트워크는 다른 애플리케이션을 기반으로 하는 새로운 당사자, 원장 및 애플리케이션과 마찰 없이 확장할 수 있으며, 확장에는 중앙 관리 주체나 글로벌 네트워크 내의 합의가 필요하지 않습니다. Canton은 스마트 계약 언어 DAML을 사용하여 작성된 애플리케이션을 지원합니다. DAML은 거래에 대한 승인 및 개인정보 보호 요건을 규정하는 엔터프라이즈급 언어이며 Canton은 이러한 모델을 충실하게 구현합니다.


파티는 참가자 노드(Participant Node)에서 호스팅 됩니다. 애플리케이션은 Ledger API를 사용하여 참여자 노드에 파티로 연결됩니다. 참가자 노드는 로컬에 설치된 DAML 스마트 계약 코드에 대해 DAML 인터프리터를 실행하고 스마트 계약을 개인 계약 저장소 (PCS)에 저장합니다. 참가자는 도메인 서비스를 활용하는 다른 참가자와 Canton 프로토콜 메시지를 교환함으로써 도메인에 연결하고 다른 참가자들과 상태를 동기화합니다. Canton 프로토콜을 사용하면 가상 글로벌 원장(virtual global ledger)이 생성됩니다.

Canton은 Scala로 작성되었으며 데이터베이스 (현재 H2 및 Postgres)에 대해 Java 프로세스로 실행됩니다.

시작하기

사전 지식이 필요하지 않으며 다음을 배우게됩니다.

  1. Canton을 설치하고 간단한 테스트 구성으로 실행하는 방법
  2. Canton의 주요 개념
  3. 주요 구성 옵션
  4. Canton에 대한 몇 가지 간단한 진단 명령
  5. Canton ID 관리의 기본 사항
  6. 새로운 스마트 계약 코드를 업로드하고 실행하는 방법

설치
Canton은 JVM 애플리케이션이며 기본적으로 실행하려면 시스템에 Java 11 이상이 설치되어 있어야합니다. 또는 Canton을 도커 이미지 로 사용할 수도 있습니다.

[메모]
Canton은 Java 8에서도 실행되지만 JRE 8에는 알려진 버그 및 Scala / Java 호환성 문제가 있습니다.

최신 릴리즈 링크를 통해 canton을 다운로드 받고 압축을 해제하면 다음과 같은 폴더 구성을 확인 할수 있습니다.

bin
daml
dars
demo
deployment
examples
lib
  • bin: Canton 실행 스크립트 포함(Unix 유사 시스템에서는 canton, 윈도우즈에서는 canton.bat)
  • daml: 일부 샘플 스마트 계약 소스 코드 포함
  • dars: daml 계약의 컴파일된 패키지 코드 포함
  • examples : Canton 콘솔의 샘플 구성 및 스크립트 파일 포함
  • lib: Canton 실행을 위한 Java 실행 파일(JAR) 포함
  • demo: 대화형 Canton 데모 실행에 필요한 모든 내용 포함

이 튜토리얼에서는 사용자가 Unix와 같은 셸을 실행하고 있다고 가정합니다.

캔톤 시작
Canton은 프로덕션 목적으로 데몬(damon) 모드를 지원하지만이 해장 장에서는 내장 된 대화 형 read-evaluate-print loop (REPL) 인 콘솔을 사용합니다. REPL은 모든 Canton 기능에 대한 기본 인터페이스를 제공합니다. 그러나 Ammonite를 사용하여 빌드 되었으므로 새 스크립트로 확장해야하는 경우 Scala의 모든 기능을 사용할 수 있습니다.

Canton이 압축해제된 디렉토리로 이동후 다음과 같이 명령어를 실행합니다.

./bin/canton --help

Canton이 지원하는 명령줄 옵션을 보려면 다음과 같이 명령어를 실행합니다.

bin/canton -c examples/01-simple-topology/simple-topology.conf

그러면 Canton이 구성 파일 examples/01-simple-topology/simple-topology.conf 를 사용하도록 지정하는 명령줄 매개 변수를 통해 콘솔을 시작합니다. 콘솔에서 사용 가능한 명령을 보려면 도움말 help 을 입력하세요.

   _____            _
  / ____|          | |
 | |     __ _ _ __ | |_ ___  _ __
 | |    / _` | '_ \| __/ _ \| '_ \
 | |___| (_| | | | | || (_) | | | |
  \_____\__,_|_| |_|\__\___/|_| |_|

  Welcome to Canton!
  Type `help` to get started. `exit` to leave.

@ help
Top-level Commands
------------------
exit - Leave the console
help - Help with console commands; type help("<command>") for detailed help for <command>

Generic Node References
-----------------------
domains - All domain nodes
nodes - Both, domain and participant nodes
participants - All participant nodes

Node References
---------------
mydomain - Manage local domain 'mydomain'; type 'mydomain help' or 'mydomain help("<methodName>")' for more help
participant1 - Manage participant 'participant1'; type 'participant1 help' or 'participant1 help("<methodName>")' for more help
participant2 - Manage participant 'participant2'; type 'participant2 help' or 'participant2 help("<methodName>")' for more help

Command Groups
--------------
console - Configure behaviour of console
health - Environment health inspection
logging - Logging related commands
utils - Console utilities

다음과 같이 특정 Canton 개체 및 명령에 대한 도움말을 확인할 수도 있습니다.

@ help("participant1")
participant1
Manage participant 'participant1'; type 'participant1 help' or 'participant1 help("<methodName>")' for more help
@ participant1.help("start")
start
Start the instance

예제 토폴로지
Canton의 기본 요소를 이해하기 위해 콘솔을 시작한 구성에 대해 간략하게 살펴봅시다. HOKON 형식으로 작성되었으며 아래와 같다. 두 개의 참가자 노드(로컬 별칭이 participant1 및 participant2)와 로컬 별칭 mydomain인 단일 동기화 도메인 (synchronization domain) 과 함께 실행하도록 지정합니다. 또한 각 노드가 사용해야 하는 스토리지 백엔드(이 튜토리얼에서는 인메모리 스토리지를 사용함)와 다양한 서비스에 대한 네트워크 포트도 지정하며, 이를 곧 설명하겠습니다.

canton {
  parameters {
    manual-start = yes
  }
  participants {

    participant1 {
      storage {
        type = memory
      }

      admin-api {
        port= 5012
      }

      ledger-api {
        port = 5011
      }
    }

    participant2 {
      storage {
        type = memory
      }

      admin-api {
        port= 5022
      }

      ledger-api {
        port = 5021
      }
    }
  }

  domains {
    mydomain {
      storage {
        type = memory
      }

      public-api.port = 5018
      admin-api.port = 5019

    }
  }
}

참가자 노드는 파티라고 불리는 한개 이상의 Canton 사용자에게 글로벌 가상 Canton 원장에 대한 액세스를 제공합니다. 내부적으로 참가자는 Canton 동기화 프로토콜을 실행하여 당사자의 계약 상태를 동기화합니다. 프로토콜을 실행하려면 참가자는 하나 이상의 동기화 도메인에 연결하거나 간단히(단순하게) 도메인에 연결해야 합니다. 트랜잭션 (여러 당사자의 공유 계약을 업데이트하는 변경)을 실행하려면 모든 당사자의 참여자가 연결된 단일 도메인이 있어야 합니다. 이 예제의 나머지 부분에서는 다음과 같은 네트워크 토폴로지를 구성하여 Alice, Bob 및 Bank가 서로 거래할 수 있도록 할 것입니다.

참가자 노드는 당사자들에게 원장에 접근하기 위한 수단으로 Ledger API를 제공합니다. 당사자는 Ledger API와 수동으로 상호 작용할 수 있지만 실제로는 애플리케이션을 사용하여 상호 작용을 처리하고 사용자 친화적인 인터페이스에 데이터를 표시합니다.

각 참가자 노드는 Ledger API 외에도 Admin API를 제공합니다. Admin API를 통해 관리자(즉, 너!)는 다음을 수행할 수 있습니다.

  1. 도메인에 대한 참가자의 연결을 관리
  2. 참가자에게 호스팅 할 파티를 추가하거나 제거
  3. 새 DAML 아카이브 업로드
  4. 암호화 키와 같은 참여자의 운영 데이터를 구성
  5. 진단 명령을 실행합니다.

도메인 노드는 참여자 노드가 동기화 도메인과 통신하는 데 사용하는 공용 API를 노출합니다. 참여자 노드가 호스팅 되는 곳에서 접근 할 수 있어야 합니다.

참여자 노드와 마찬가지로 도메인은 관리 서비스를 위한 Admin API도 제공합니다. 예를 들어 Admin APi를 사용하여 도메인 내의 참가자를 활성화 또는 비활성화할 수 있습니다. 콘솔은 구성된 참여자 및 도메인의 Admin API에 대한 액세스를 제공합니다.

[노트]
Canton의 Admin API를 Ledger API 패키지와 혼동해서는 안됩니다. Ledger API의 관리 패키지는 모든 DAML 참가자에 대한 당사자와 패키지를 관리하기 위한 서비스를 제공합니다. Canton의 Admin API를 통해 Canton 기반 노드를 관리할 수 있습니다. 참가자 와 도메인 노드 모두 부분적으로 겹치는 기능이 있는 Admin API를 노출합니다.

또한 참가자와 도메인은 공용(Public) API를 통해 서로 통신합니다.

보시다시피, 구성의 어떤 것도 participant1participant2가 mydomain에 연결되어야 한다고 명시하지 않습니다. Canton 연결은 정적으로 구성되지 않고 대신 동적으로 추가됩니다. 실제로 콘솔을 시작할 때 노드도 자동으로 시작되지 않습니다. 콘솔을 사용하여 데몬 모드에서 실행 중인 노드에 연결할 수도 있습니다. 먼저 노드를 시작하고 실행 한 다음 참가자를 도메인에 연결합니다.

노드 시작 및 연결

콘솔은 구성된 모든 노드를 시작하는 편리한 매크로를 제공합니다.

@ nodes.local start

또는 다음과 같이 각 노드를 개별적으로 시작하여 수동으로 수행 할 수도 있습니다.

@ mydomain start
@ participant1 start
@ participant2 start

별칭 mydomain, participant1participant2는 구성 파일에서 가져온 것임을 기억 하세요. 노드를 두 번 이상 시작려고 추가 호출을 해도 효과가 없습니다.

이것은 노드를 시작하지만 아직 서로에 대해 알지 못합니다. 이를 확인하려면 다음을 실행할 수 있습니다. health.status

@ health.status
Status for Domain 'mydomain':
Domain id: mydomain::01c8c7795ef6c984ef056fca33983d12f769c64cfd9d24cfa0f080df386ddb3810
Uptime: 7.088278s
Ports:
        public: 5018
        admin: 5019
Connected Participants: None

Status for Participant 'participant1':
Participant id: PAR::participant1::015456bdb44cfe7ddef9e81b0023347cee9a0ae48247bc547e1a8121836b76fd5c
Uptime: 4.165019s
Ports:
        ledger: 5011
        admin: 5012
Connected Domains: None

Status for Participant 'participant2':
Participant id: PAR::participant2::0143a26fac5845f265ee1520a8857a5faabc37d2394d1c37db98acbd94e5af9996
Uptime: 3.590513s
Ports:
        ledger: 5021
        admin: 5022
Connected Domains: None

또는 다음과 같은 명령어를 통해 개별적으로 결과를 확인 할수 있습니다.

@ mydomain.health.status
@ participant1.health.status
@ participant2.health.status

우선 노드 별명 뒤에 오는 긴 16 진 문자열은 무시하십시오. 이것들은 Canton의 정체성과 관련이 있습니다. 보시다시피 도메인에는 연결된 참가자가 없으며 참가자도 도메인에 연결되어 있지 않습니다. 계속해서 참가자를 도메인에 연결합니다.

@ participant1.domains.connect_local (mydomain) 
@ participant2.domains.connect_local (mydomain)

이제 상태를 다시 확인해보겠습니다. (health.status)

@ health.status
Status for Domain 'mydomain':
Domain id: mydomain::01c8c7795ef6c984ef056fca33983d12f769c64cfd9d24cfa0f080df386ddb3810
Uptime: 3m 59.090539s
Ports:
        public: 5018
        admin: 5019
Connected Participants:
        PAR::participant1::015456bd...
        PAR::participant2::0143a26f...

Status for Participant 'participant1':
Participant id: PAR::participant1::015456bdb44cfe7ddef9e81b0023347cee9a0ae48247bc547e1a8121836b76fd5c
Uptime: 3m 56.149909s
Ports:
        ledger: 5011
        admin: 5012
Connected Domains:
        mydomain::01c8c779...
...

상태 메세지에서 읽을 수 있듯이 두 참가자는 이제 도메인에 연결됩니다. ICMP ping에서 영감을 얻은 다음 진단 명령을 사용하여 연결을 테스트 할 수 있습니다.

@ participant1.health.ping(participant2)
Ping round trip time: 1060ms

모든 것이 올바르게 설정되면 두 참가자의 Ledger API 간의 "왕복 시간"이 보고됩니다. 첫 번째 시도에서 JVM이 워밍업 중이므로이 시간은 아마도 수 초가 될 것입니다. 다음 시도에 크게 감소할 것이며, JVM의 just-in-time 컴파일이 실제로 시작된 후에도 다시 한번 감소합니다.

사실, Canton을 통해 첫 번째 스마트 계약 거래를 실행했습니다. 또한 모든 참여자 노드에는 스마트 계약 상호 작용에 참여할 수 있는 관련 기본 제공 당사자가 있습니다. 이 ping명령은 기본적으로 모든 Canton 참가자에 사전 설치된 특정 스마트 계약을 사용합니다. 실제로 이 명령은 Admin API를 사용하여 사전 설치된 애플리케이션에 액세스 한 다음 이 스마트 계약에서 작동하는 Ledger API 명령을 실행합니다.

애플리케이션의 모든 스마트 계약 상호 작용에 참여자의 기본 제공 파티(당사자)를 사용할 수 있지만 참여자보다 더 많은 당사자를 갖는 것이 유용한 경우가 많습니다. 예를 들어, 각 직원이 별도의 당사자 인 회사 내에서 단일 참가자 노드를 실행할 수 있습니다. 이를 위해서는 당사자를 프로비저닝 할 수 있어야 합니다.

캔톤 ID 및 프로비저닝 당사자

Canton에서는 당사자, 참가자 및 도메인의 모든 ID가 고유 식별자로 표시됩니다. 고유 식별자는 사람이 읽을 수 있는 문자열과 공개 키의 지문(fingerprint of a public key)의 두 가지 구성 요소로 구성됩니다. Canton에서 표시될 때 구성 요소는 이중 콜론으로 구분됩니다. 콘솔에서 다음을 실행하여 참가자 및 도메인의 식별자를 볼 수 있습니다.

@ mydomain.id
DomainId(UniqueIdentifier(Identifier("mydomain"), Namespace(Fingerprint("01a5eec4ecbcb6abaed01fecf6c83dca49b2d8f23f0d75b0f83c341e24e804bd37"))))
:text: mydomain has an ID
@ participant1.id
ParticipantId = ParticipantId(UniqueIdentifier(Identifier("participant1"), Namespace(Fingerprint("0104491f90f907d06789327172843320b27bb2f4c69357fcfded24d2ec68c3c7db"))))
:text: participant1 has an ID
@ participant2.id
ParticipantId = ParticipantId(UniqueIdentifier(Identifier("participant2"), Namespace(Fingerprint("01f5e22b6d8aaa23df2bd9eb79562c10f4599831c3470d30d649159c305afeabce"))))
:text: participant2 has an ID

이러한 고유 식별자는 사람이 읽을 수 있는 문자열은 로컬 별칭(local alias)에서 파생됩니다. 네임 스페이스라고 하는 공개키는 신뢰할 수 있는 루트 인증서(root of trust for this identifier)입니다. 이는 Canton에서 이 신원의 이름으로 취해진 모든 조치는 다음 중 하나 여야 함을 의미합니다.

  • 이 네임 스페이스 키로 서명하거나
  • 네임스페이스 키로 서명된 키로 이 ID의 이름으로 직접 또는 간접적으로 말할 수 있다. (예: k1이 k2 이름으로 말할 수 있고 k2가 k3 이름으로 말할 수 있다면 k1도 k3 이름으로 말할 수 있다)
  • signed by a key that is authorized by the namespace key to speak in the name of this identity, either directly or indirectly (e.g., if k1 can speak in the name of k2 and k2 can speak in the name of k3, then k1 can also speak in the name of k3).

Canton에서는 동일한 네임 스페이스를 공유하는 여러 고유 식별자를 가질 수 있습니다. 이에 대한 예제가 곧 표시됩니다. 그러나 마지막 콘솔 명령의 결과로 생성된 ID를 살펴보면 다른 네임 스페이스에 속함을 알 수 있습니다. 기본적으로 각 Canton 노드는 처음 시작할 때 자체 네임 스페이스에 대해 새로운 비대칭 키 쌍 (비밀 및 공개 키)을 생성합니다. 그런 다음 키는 저장소에 저장되고 나중에 저장소가 영구적인 경우에 다시 사용됩니다 (simple-topology.conf는 영구적이지 않은 메모리 저장소를 사용함을 기억하십시오).

다음으로AliceBob이라는 두 개의 파티를 만듭니다. Aliceparticipant1에서 호스팅 되고 그녀의 ID는 participant1의 네임 스페이스를 사용합니다. 마찬가지로 Bobparticipant2를 사용합니다. Canton은 이에 대한 편리한 매크로를 제공합니다.

@ val alice = participant1.parties.enable("Alice")
alice: PartyId = PartyId(UniqueIdentifier(Identifier("Alice"), Namespace(Fingerprint("0104491f90f907d06789327172843320b27bb2f4c69357fcfded24d2ec68c3c7db"))))
@ val bob = participant2.parties.enable("Bob")

이렇게 하면 참가자의 각 네임 스페이스에 새 당사자가 생성됩니다. 또한 새 당사자의 도메인을 알리고 참가자가 해당 당사자를 대신하여 명령을 제출할 수 있습니다. 예를 들어 Alice의 고유 식별자는 participant1과 동일한 네임 스페이스를 사용하고 participant1은 이 네임 스페이스의 비밀 키를 보유하므로 도메인에서 이를 허용합니다. 다음을 실행하여 당사자가 이제 mydomain에 알려졌는지 확인할 수 있습니다.

@ mydomain.parties.list("Alice")
Seq[ListPartiesResponse.Result] = Vector(
Result(
"Alice::0104491f90f907d06789327172843320b27bb2f4c69357fcfded24d2ec68c3c7db",
"mydomain::01a5eec4ecbcb6abaed01fecf6c83dca49b2d8f23f0d75b0f83c341e24e804bd37",
"participant1::0104491f90f907d06789327172843320b27bb2f4c69357fcfded24d2ec68c3c7db",
Submission,
Ordinary
)
)
@ mydomain.parties.list("Bob")
Seq[ListPartiesResponse.Result] = Vector(
Result(
"Bob::01f5e22b6d8aaa23df2bd9eb79562c10f4599831c3470d30d649159c305afeabce",
"mydomain::01a5eec4ecbcb6abaed01fecf6c83dca49b2d8f23f0d75b0f83c341e24e804bd37",
"participant2::01f5e22b6d8aaa23df2bd9eb79562c10f4599831c3470d30d649159c305afeabce",
Submission,
Ordinary
)
)

스마트 계약 코드 프로비저닝

Alice와 Bob 사이에 계약을 생성하려면 먼저 두 호스트 참여자에게 계약 코드를 프로비저닝해야합니다. Canton은 DAML로 작성된 스마트 계약을 지원합니다. DAML 계약의 코드는 DAML 계약 템플릿을 사용하여 지정됩니다. 실제 계약은 템플릿 인스턴스입니다. DAML 템플릿은 DAML 아카이브 또는 줄여서 DAR 파일로 패키징됩니다. 이 자습서에서는 사전 패키지 된 dars/CantonExamples.dar 파일을 사용합니다. participant1participant2 모두에게 프로비저닝하려면 parties.all 콘솔 매크로를 사용할 수 있습니다.

@ participants.all.dars.upload("dars/CantonExamples.dar")
Map[com.digitalasset.canton.console.ParticipantReference, String] = Map(
 com.digitalasset.canton.console.LocalParticipantReference@8c80590e -> "01b8927bab3774f44cae76a20d1249b3d39e1250a2b994da1779c4e6f5bb9257a2",
 com.digitalasset.canton.console.LocalParticipantReference@8f2fb383 -> "01b8927bab3774f44cae76a20d1249b3d39e1250a2b994da1779c4e6f5bb9257a2"
)

DAR이 업로드되었는지 확인하려면 다음을 실행하십시오.

@ participant1.dars.list()
Seq[com.digitalasset.canton.participant.admin.v0.DarDescription] = Vector(DarDescription("013a2ae491a41956f1d76dcebb12bd6bc668b4c809c2344e894e07ff5b876be0e3", "CantonExamples"),
DarDescription("014ff8d452dd92189e38b318e44ee1cbfbb06fe58357383419763d2f1da2ecde6f", "AdminWorkflows")
)

@ participant2.dars.list()
Seq[com.digitalasset.canton.participant.admin.v0.DarDescription] = Vector(DarDescription("013a2ae491a41956f1d76dcebb12bd6bc668b4c809c2344e894e07ff5b876be0e3", "CantonExamples"),
DarDescription("014ff8d452dd92189e38b318e44ee1cbfbb06fe58357383419763d2f1da2ecde6f", "AdminWorkflows")
)

이제 Canton을 사용하여 스마트 계약을 실행할 준비가되었습니다.

스마트 계약 실행

DAML 계약 실행을 트리거하려면 Ledger API를 통해 적절한 명령을 보내야 합니다. Canton 콘솔은 테스트에 유용한 일부 유틸리티와 함께 이 API에 대한 대화식 인터페이스를 제공합니다. 또한 Scala Codegen을 사용하여 스마트 계약 명령을 조작하기 위한 Scala 인터페이스를 얻을 수 있습니다.

DAML 계약으로 모델링 된 간단한 시나리오를 실행해 보겠습니다. 시나리오에서 Alice와 Bob은 Bob이 집을 페인트칠을 해야 한다는 데 동의합니다. 그 대가로 Bob은 은행에서 발행 한 Alice로부터 디지털 채권(I-Owe-You, IOU)을 받게 됩니다. Canton은 Scala 스크립트 examples/scripts/PaintScenario.sc에 사전 수록된 이 시나리오와 함께 제공됩니다.

@ import $file.examples.scripts.PaintScenario
@ PaintScenario.run(participant1, participant2, alice, bob)

경로가 다른 경우 import 구문으로 조정하십시오. 경로가 ../scripts/PaintScenario.sc 인 경우 $file.^. scripts.PaintScenario를 사용합니다. 시나리오의 출처는 다음과 같습니다. Canton은 CantonExamples.dar의 계약에 대해 생성된 Scala 인터페이스와 함께 제공되며 스크립트는 일부를 가져오는 것으로 시작합니다 (Iou 및 Paint DAML 모듈의 경우). 또한 Ledger API (DecodeUtil) 작업을 위한 Canton 유틸리티와 매개 변수로 사용하는 참여자 참조 유형을 가져옵니다.

import com.digitalasset.canton.examples.{Iou, Paint}
import com.digitalasset.canton.ledger.api.client.DecodeUtil
import com.digitalasset.canton.console.LocalParticipantReference

def run(
    participant1: LocalParticipantReference,
    participant2: LocalParticipantReference,
    alice: PartyId, bob: PartyId) = {

  val bank = participant2.parties.enable("Bank")

  utils.retry_until_true() {
    mydomain.parties.list("Bank").nonEmpty
  }

  val amount = Iou.Amount(100, "USD")
  val iouCreateCmd = Iou.Iou(
    payer = bank.toPrim,
    owner = alice.toPrim,
    amount = amount,
    viewers = List.empty
  ).create.command
  val iouTx = participant2.ledger_api.commands.submit_flat(bank, Seq(iouCreateCmd))
  val iou = DecodeUtil.decodeAllCreated(Iou.Iou)(iouTx).head

  val offerCreateCmd = Paint.OfferToPaintHouseByPainter(
    houseOwner = alice.toPrim,
    painter = bob.toPrim,
    bank = bank.toPrim,
    amount = amount
  ).create.command

  val offer = DecodeUtil.decodeAllCreated(Paint.OfferToPaintHouseByPainter)(
    participant2.ledger_api.commands.submit_flat(bob, Seq(offerCreateCmd))
  ).head

  val acceptanceCmd = offer.contractId.exerciseAcceptByOwner(
    actor=alice.toPrim,
    iouId=iou.contractId
  ).command
  val bobIou = DecodeUtil.decodeAllCreatedTree(Iou.Iou)(
    participant1.ledger_api.commands.submit(alice, Seq(acceptanceCmd))
  ).head

  val callCmd = bobIou.contractId.exerciseCall(bob.toPrim).command
  val called = DecodeUtil.decodeAllCreated(Iou.GetCash)(
    participant2.ledger_api.commands.submit_flat(bob, Seq(callCmd))
  ).head
}

시나리오를 완전히 이해하려면 이러한 모듈의 DAML 소스와 DAML 문서를 참조하십시오. 여기서 우리는 그 뒤에 있는 주요 직관을 제공할 것입니다.(Here, we will just provide the main intuition behind it.) 은행을 대표하는 파티를 추가하는 것으로 시작합니다. 다음 줄의 'retry_until_true'는 동기화 포인트로 사용됩니다. 도메인이 은행과 관련된 모든 거래를 전달하기 전에 은행 파티의 생성을 볼 수 있도록 보장합니다.

[노트]
Canton은 DAML 애플리케이션을 작성할 때 대부분의 동기화 문제를 완화합니다. 그럼에도 불구하고 Canton은 동시 분산 시스템입니다. 'Bank'파티 생성은 'participant2'의 로컬 작업이며 mydomain은 지연된 당사자를 인식합니다 (자세한 내용은 Identity Transactions 참조). 모두가 동일한 순서로 도메인의 작업을 볼 수 있지만 여러 노드에 영향을 미치는 다른 모든 작업에 대해서도 처리 및 네트워크 지연이 존재합니다. 대화식으로 명령을 실행할 때 지연은 보통 너무 작아서 알아 채지 못합니다. 그러나 여러 노드와 통신하는 Canton 스크립트 또는 응용 프로그램을 프로그래밍하는 경우 몇 가지 형식의 수동 동기화가 필요할 수 있습니다. 'retry_until_true'는 이러한 목적을 위한 간단하고 편리한 유틸리티입니다.

다음으로 은행은 Alice (iou 계약)에게 디지털 은행권(I-Owe-You, IOU)을 발행합니다. Bob은 그러한 IOU (제안 계약)에 대한 대가로 Alice의 집에 페인트 칠을 제안합니다. 그런 다음 Alice는 제안을 수락합니다. 결과적으로 시나리오에서는 보이지 않지만 DAML 계약에 지정되어있는 Alice는 자신의 IOU를 Bob에게 전송합니다 (결과적으로 bobIou). 그런 다음 Bob은 IOU를 호출하여 자신과 은행 간에 GetCash 계약 (호출)을 만듭니다. 이 계약은 화가(painter)가 지정된 금액의 원장 외 인출을 할 수 있도록 은행에서 발행 한 명세서입니다.

출력물은 시나리오 실행의 효과에 대해 별로 알려주지 않습니다. 참가자가 보관하는 계약을 살펴보면 이러한 효과를 확인할 수 있습니다. DAML 계약은 활성화될 수 있으며, 이는 명령에서 사용될 수 있음을 의미하며, 이전에 사용되었던 계약이라는 것을 의미합니다. 해당 Ledger API 서비스를 사용하여 소위 '활성 계약 세트(active contract set)'를 얻을 수 있습니다.

@ participant1.ledger_api.acs.of_all()
res19: Seq[com.digitalasset.canton.admin.api.client.commands.LedgerApiTypeWrappers.WrappedCreatedEvent] = ListBuffer(
  WrappedCreatedEvent(
    CreatedEvent(
      "#015d3b083c2897c36a426f99524a2d4d5a639f3b6f302c531f87806b1fda928725:2",
      "00583da6a66746bb61d0b95be9f9c5cf0c1d108054765fd9da3b97c3b13990cf70ca0001d8370639f51a4cff2ee706baffab3db39c25b702fdd8f92b30c97653e4d
83b5e",
      Some(Identifier("6d7133b2ffe76e035ecd09d7fb2060a33d9e6a19c15d8452e95714e90a872cff", "Iou", "Iou")),
      None,
      Some(
        Record(
          Some(Identifier("6d7133b2ffe76e035ecd09d7fb2060a33d9e6a19c15d8452e95714e90a872cff", "Iou", "Iou")),
...

이 명령은 래핑 된 CreatedEvent의 시퀀스를 반환합니다. 이 Ledger API 데이터 유형은 계약 생성 이벤트를 나타냅니다. 래퍼는 Canton 콘솔에서 CreatedEvents를 조작하는 편리한 기능을 제공합니다.

@ participant2.ledger_api.acs.of_all().map(x => (x.templateId, x.arguments))
Seq[(String, Map[String, Any])] = ListBuffer(
  (
    "Iou.GetCash",
    Map(
      "payer" -> "Bank::01f5e22b6d8aaa23df2bd9eb79562c10f4599831c3470d30d649159c305afeabce",
      "owner" -> "Painter::01f5e22b6d8aaa23df2bd9eb79562c10f4599831c3470d30d649159c305afeabce",
      "value" -> "100.0000000000",
      "currency" -> "USD"
    )
  ),
  (
    "Paint.PaintHouse",
    Map(
      "painter" -> "Painter::01f5e22b6d8aaa23df2bd9eb79562c10f4599831c3470d30d649159c305afeabce",
      "houseOwner" -> "Alice::0104491f90f907d06789327172843320b27bb2f4c69357fcfded24d2ec68c3c7db"
    )
  )
)

Scala의 시퀀스 작업을 사용하여 특정 템플릿("Iou.GetCash")에 대한 필터링과 같이 출력을 필터링하고 다시 정렬할 수 있습니다.

@ participant2.ledger_api.acs.of_all().filter(_.templateId == "Iou.GetCash").map(_.arguments)
Seq[Map[String, Any]] = ListBuffer(
  Map(
    "payer" -> "Bank::01f5e22b6d8aaa23df2bd9eb79562c10f4599831c3470d30d649159c305afeabce",
    "owner" -> "Painter::0104491f90f907d06789327172843320b27bb2f4c69357fcfded24d2ec68c3c7db",
    "value" -> "100.0000000000",
    "currency" -> "USD"
  )
)

콘솔의 기능은 개발 목적으로 편리할 수 있지만 DAML SDK는 원장 콘텐츠를 검사할 수 있는 훨씬 더 편리한 도구를 제공합니다. 이러한 모든 도구는 Ledger API에 대해 작동합니다. 브라우저 기반 또는 콘솔 버전의 Navigator 사용하거나 DAML SDK 자습서에 설명된 대로 고유 한 애플리케이션을 작성할 수 있습니다.

프라이버시

이 섹션을 마치기 전에 여러분과 공유하고 싶은 중요한 관찰 사항이 하나 있습니다. 자습서에 지정된 대로 이전에 participants.parties.enable1 단계를 정확히 실행했다고 가정하면 participant2에는 저장소에 GetCash 템플릿의 인스턴스가 하나 있고 participant1에는 아무것도 없다는 것을 알게 될 것입니다.

이는 시나리오에서 생성된 GetCash 계약은 오직 화가와 은행만 포함하기 때문입니다. 둘 다 participant2 에서만 호스팅 됩니다. Canton의 동기화 프로토콜은 participant1이 이 현금 계약에 대한 데이터를 수신하지 못하도록 보장합니다. 또한 mydomain을 실행하는 노드가 이 데이터를 수신하는 동안 데이터는 암호화되어 mydomain이 읽을 수 없습니다.

DAML SDK와 Canton

이전 "Canton 시작하기"가이드를 완료했다면 이제 DAML SDK를 사용하여 자신 만의 애플리케이션을 구축하는 방법을 배우도록 하겠습니다.

이 튜토리얼에서는 Canton에서 Create DAML App 예제를 통해 아래 내용을 배우게 됩니다.

  1. DAML의 주요 개념
  2. DAML 아카이브 (DAR)를 컴파일하는 방법
  3. Canton에서 Create DAML 앱 예제를 실행하는 방법
  4. DAML 코드를 작성하는 방법
  5. 기존 애플리케이션을 Canton과 통합하는 방법

이 자습서는 DAML 자습서를 기반으로 하며 주로 DAML 샌드 박스에서 실행하는 대신 Canton을 사용하여 분산 설정에서 예제를 실행하는 차이점을 다룹니다. 여기에는 몇 가지 알려진 문제가 있으며 이 섹션에서는 해결 방법을 설명합니다.

Canton 시작

Canton 배포판의 압축을 푼 위치에서 아래 명령어를 통해 create-daml-app 템플릿을 사용한 create-daml-app이라는 프로젝트를 생성합니다. (create-daml-app디렉터리가 있는 전제로 examples/04-create-daml-app 가 구성되어 있습니다.)

-- ‘daml new --list’ 명령어를 사용하면 daml 프로젝트 생성을위한 템플릿 목록을 확인 할수 있습니다.
-- ‘daml new <신규 프로젝트명> <프로젝트 구성을위한 템플릿명>’

daml new create-daml-app create-daml-app
Created a new project in "create-daml-app" based on the template "create-daml-app".

그런 다음 DAML 코드를 DAR 파일로 컴파일 (.daml/dist/create-daml-app-0.1.0.dar 파일 생성)하고 UI에서 사용하는 코드 생성 단계를 실행합니다.

cd create-daml-app
daml build
daml codegen js .daml/dist/create-daml-app-0.1.0.dar -o daml.js

UI에 대한 종속성도 설치해야 합니다.

cd ui
yarn install

DAML에서는 daml start명령어를 사용하여 Sandbox 및 HTTP JSON API를 시작하지만 examples/04-create-daml-app의 분산 설정을 사용하여 Canton을 시작하고 나중에 HTTP JSON API를 별도로 시작합니다.

Canton 배포판의 압축을 푼 디렉터리로 돌아가서 다음명령어를 사용하여 canton을 시작합니다.
bin/canton -c examples/04-create-daml-app/canton.conf --bootstrap examples/04-create-daml-app/init.canton

이제 두 개의 참가자 노드를 시작하고 당사자 Alice와 Bob을 할당합니다. 각 참가자 노드는 자체 원장 API를 노출합니다 : #. Alice는 12011 포트의 원장 API를 사용하여 participant1에 의해 호스팅 됩니다.
#. Bob은 12021 포트의 원장 API를 사용하여 participant2에 의해 호스팅 됩니다.

examples/04-create-daml-app/init.canton 스크립트는 당사자에게 권한을 부여하고 DAR을 업로드하기 위해 몇 가지 설정 단계를 수행합니다.

Canton을 실행 상태로 두고 새 터미널 창으로 전환하십시오.

DAML 앱 예제 실행

현재 Create DAML App UI는 포트 7575에서 HTTP JSON API를 사용하도록 구성되어 있습니다. HTTP JSON API의 각 인스턴스는 한 명의 참가자에게 연결되므로 한 번에 하나의 UI 애플리케이션 인스턴스 만 실행할 수 있으며 한명의 참가자에게 연결됩니다 (UI 응용 프로그램을 조정하고 싶지 않다고 가정).

Canton이 실행되면 포트 12011 (Alice의 참여자에 해당)에서 원장 API에 연결된 HTTP JSON API를 시작하고 포트 7575에서 UI에 연결합니다.

daml json-api \
    --ledger-host localhost \
    --ledger-port 12011 \
    --http-port 7575 \
    --allow-insecure-tokens

새로운 터미널 창에서 UI 서버를 시작합니다. (아래 명령어후 자동으로 3000번 포트로 웹브라우저가 실행됩니다.)

cd create-daml-app/ui
REACT_APP_LEDGER_ID=participant1 yarn start

UI에서 사용하는 원장 ID를 실행 중인 참가자(participant)의 이름과 일치하도록 구성해야 합니다. 이것은 REACT_APP_LEDGER_ID 환경 변수를 사용하여 수행됩니다.

Alice로 로그인하려면 Alice의 파티 ID를 알아야 합니다. 실행 중인 Canton 인스턴스로 돌아가서 Canton 콘솔에 다음 명령을 입력합니다. (Canton재시작시 해당 ID값은 변경됩니다.)

@ participant1.parties.list(filterParty="Alice").head.party.filterString

결과 문자열은 Alice의 파티 ID입니다. "Alice::0110c2eed6d8f7801e0fb95d2adb3c3309664bcc7b0190c876b26bbbedb89de892"와 같은 형식 이어야 합니다. Alice로 로그인하려면 UI에 Alice의 파티 ID를 입력합니다.

Alice는 participant2.parties.list(filterParty = "Bob").head.party.filterString으로 얻은 Bob의 파티 ID를 사용하여 Bob을 팔로우 할 수 있습니다.

Sandbox는 암시 적으로 파티 할당을 수행하므로 Sandbox에 대해 DAML 앱 만들기 예제를 실행할 때 이 단계가 필요하지 않았습니다.
다른 원장 제품들과 마찬가지로 Canton은 명시적인 당사자 할당이 필요합니다. 자세한 내용은 Ledger API 문서의 프로비저닝 ID 섹션을 참조하십시오.

participant2 에 연결
participant2를 사용하여 Bob으로 로그인할 수 있습니다. 기본적으로 participant1과 동일한 프로세스를 따르고 participant2에 해당하도록 포트를 조정하면 됩니다.

먼저, 옵션 -ledger-port=12021--http-port 7576을 사용하여 HTTP JSON API의 다른 인스턴스를 시작합니다. 12021은 participant2의 원장 포트에 해당하고 7576은 HTTP JSON API의 새로운 인스턴스에 대한 포트입니다.

daml json-api \
    --ledger-host localhost \
    --ledger-port 12021 \
    --http-port 7576 \
    --allow-insecure-tokens

그런 다음 포트 3001에서 실행되고 포트 7576에서 HTTP JSON API에 연결된 Bob 용 UI의 다른 인스턴스를 시작합니다. (아래 명령어후 자동으로 3001번 포트로 웹브라우저가 실행됩니다.)

PORT=3001 REACT_APP_HTTP_JSON_PORT=7576 REACT_APP_LEDGER_ID=participant2 yarn start

Canton 콘솔에서 Bob의 파티 ID를 가져와서 UI에 로그인하는 데 사용할 수 있습니다.
participant2.parties.list(filterParty="Bob").head.party.filterString

Composability

이 자습서에서는 여러 Canton 도메인에 걸쳐 워크 플로를 구축하는 방법을 배웁니다. Composability는 여러 Canton 도메인을 애플리케이션 수준에서 하나의 개념 원장으로 전환합니다.

이 자습서에서는 다음 전제 조건을 가정합니다.

  • 시작하기 자습서를 통해 작업했으며 Canton 콘솔과 상호 작용하는 방법을 알고 있습니다.
  • DAML 소개에서 다루는 DAML 개념을 알고 있습니다.
  • 실행 예제에서는 원장 API, DAML 용 Scala 코드 생성기, Canton의 ID 관리를 사용합니다. 예제 코드를 완전히 이해하려면 위의 문서를 참조하십시오.

튜토리얼은 두 부분으로 구성됩니다.

  1. 첫 번째 부분에서는 여러 도메인에 걸쳐있는 워크 플로를 디자인하는 방법을 보여줍니다.

  2. 두 번째 부분에서는 서로 다른 도메인의 기존 워크플로를 단일 워크플로 로 구성하는 방법과 이로 인한 이점을 보여줍니다.
    DAML 모델은 모듈 Iou 및 Paint는 daml/CantonExamples 폴더에 Canton 릴리스와 함께 제공됩니다. 설정파일 및 단계(steps)는 Canton 릴리스의 examples/05-composability 폴더에서 사용할 수 있습니다. 워크 플로우를 실행하려면 다음과 같이 릴리스의 루트 폴더에서 Canton을 시작하십시오.

./bin/canton -c examples/05-composability/composability.conf

튜토리얼의 콘솔 명령을 주어진 순서대로 Canton 콘솔에 복사하여 붙여 넣어 대화식으로 실행할 수 있습니다. 모든 콘솔 명령은 부트 스트랩 스크립트 composability1.canton, composability-auto-transfer.cantoncomposability2.canton에도 요약되어 있습니다.

1 부 : 다중 도메인 워크 플로

시작하기 튜토리얼의 페인트 합의 시나리오를 고려합니다. 집주인과 화가는 화가에게 집주인의 집을 페인트 하도록 하는계약을 체결하려고 합니다. 그러한 계약을 체결하기 위해 주택 소유자는 화가에게 페인트 제안하고 화가는 수락합니다. 수락하면 페인트 계약은 돈의 소유권을 변경하여 원자 적으로 작성되며, 이는 은행이 지원하는 IOU에 의해 대표됩니다.

원자 성은 어떤 당사자도 상대방을 속일 수 없음을 보장합니다. 화가는 집주인이 지불하는 경우에만 페인트 작업을하고, 집주인은 화가가 의무를 완료 때만 지불합니다. 이를 통해 다음과 같은 나쁜 시나리오를 피할 수 있으며, 이는 예를 들어 법적 절차를 사용하여 해결해야 합니다.

집주인은 IOU를 다른 것에 사용하고 화가가 집을 칠해야 할 의무를 완료 했음에도 불구하고 화가에게 돈을 지불하지 않습니다. 그런 다음 화가는 주택 소유자에게 다른 차용 증서로 지불하거나 페인트 계약을 취소하도록 설득해야 합니다.
집주인은 돈을 화가에게 송금하지만 화가는 페인트 계약을 거부합니다. 집주인은 화가에게 돈을 돌려달라고 간청합니다.

토폴로지 설정
이 예에서는 두 개의 도메인, iou 및 paint가 있는 토폴로지를 가정합니다. 집주인과 화가의 참여자는 다음 다이어그램과 같이 두 도메인에 연결되어 있습니다.

구성 파일 composability.conf는 두 개의 도메인 iou 및 paint와 세 명의 참가자를 구성합니다. 도메인 매개 변수 설정 transfer-exclusivity-timeout은 이 튜토리얼의 두 번째 부분에서 설명합니다.

  parameters {
    enable-preview-commands = yes
    enable-testing-commands = yes
  }
  domains {
    iou {
      public-api.port = 11018
      admin-api.port = 11019
      storage.type = memory
      domain-parameters.transfer-exclusivity-timeout = 0s // disables automatic transfer-in
    }

    paint {
      public-api.port = 11028
      admin-api.port = 11029
      storage.type = memory
      domain-parameters.transfer-exclusivity-timeout = 2s
    }
  }

  participants {
    participant1 {
      ledger-api.port = 11011
      admin-api.port = 11012
      storage.type = memory
    }

    participant2 {
      ledger-api.port = 11021
      admin-api.port = 11022
      storage.type = memory
    }

    participant3 {
      ledger-api.port = 11031
      admin-api.port = 11032
      storage.type = memory
    }
  }
}

첫 번째 단계로 모든 노드가 시작되고 은행 (participant1 에서 호스팅), 주택 소유자 (participant2 에서 호스팅) 및 화가 (participant3 에서 호스팅)의 당사자가 생성됩니다. 파티 온 보딩에 대한 세부 정보는 교차 도메인 워크 플로를 보여주는 것과 관련이 없습니다.

participant1.domains.connect_local(iou)
participant2.domains.connect_local(iou)
participant3.domains.connect_local(iou)
participant2.domains.connect_local(paint)
participant3.domains.connect_local(paint)

// the connect call will use the configured domain name as an alias. the configured
// name is the one used in the configuration file.
// in reality, all participants pick the alias names they want, which means that
// aliases are not unique, whereas a `DomainId` is. However, the
// alias is convenient, while the DomainId is a rather long string including a hash.
// therefore, for commands, we prefer to use a short alias instead.
val paintAlias = paint.name
val iouAlias = iou.name

// 당사자 생성
val Bank = participant1.parties.enable("Bank")
val HouseOwner = participant2.parties.enable("House Owner")
val Painter = participant3.parties.enable("Painter")

// 파티 활성화가 적용되고 나중에 하트 비트가 전송 될 때까지 기다리십시오.
val partyAssignment = Set(HouseOwner -> participant2, Painter -> participant3)
participant2.parties.await_topology_heartbeat(partyAssignment)
participant3.parties.await_topology_heartbeat(partyAssignment)

// 모든 참가자에게 DAML 모델 업로드
val darPath = Option(System.getProperty("canton-examples.dar-path")).getOrElse("dars/CantonExamples.dar")
participants.all.dars.upload(darPath)

IOU 및 페인트 제안 생성 
원장을 초기화하기 위해 은행은 주택 소유자를 위한 IOU를 생성하고 주택 소유자는 화가를 위한 페인트 제안을 생성합니다. 이러한 단계는 DAML 모델에서 생성된 Scala 바인딩을 사용하여 아래에서 구현됩니다. 생성된 Scala 클래스는 com.digitalasset.canton.examples 패키지의 Canton 릴리스와 함께 배포됩니다. 관련 클래스는 다음과 같이 가져옵니다.
import com.digitalasset.canton.examples.Iou.{Amount, Iou}
import com.digitalasset.canton.examples.Paint.{OfferToPaintHouseByOwner, PaintHouse}
import com.digitalasset.canton.ledger.api.client.DecodeUtil.decodeAllCreated
import com.digitalasset.canton.protocol.ContractIdSyntax._

은행은 participant 1 의 원장 API 명령 서비스를 통해 명령을 제출하여 iou 도메인의 주택 소유자를 위해 USD 100의 IOU를 생성합니다. 그런 다음 주택 소유자는 화가가 소유권 변경에 영향을 미칠 수 있도록 화가와 IOU 계약을 공유합니다. 제안을 수락할 때. 공유 작업은 화가가 IOU 계약을 볼 수 있도록 화가를 IOU 계약의 관찰자로 추가합니다. Bank의 participant 1 이 iou 도메인에만 연결되어 있기 때문에 두 명령 모두 iou 도메인에서 실행됩니다.

// Bank creates IOU for the house owner
val createIouCmd = Iou(
  payer = Bank.toPrim,
  owner = HouseOwner.toPrim,
  amount = Amount(value = 100.0, currency = "USD"),
  viewers = List.empty
).create.command
val Seq(iouContractUnshared) = decodeAllCreated(Iou)(
  participant1.ledger_api.commands.submit_flat(Bank, Seq(createIouCmd)))

// Wait until the house owner sees the IOU in the active contract store
participant2.ledger_api.acs.await_active_contract(HouseOwner, iouContractUnshared.contractId.toLf)

// The house owner adds the Painter as an observer on the IOU
val shareIouCmd = iouContractUnshared.contractId.exerciseShare(actor = HouseOwner.toPrim, viewer = Painter.toPrim).command
val Seq(iouContract) = decodeAllCreated(Iou)(participant2.ledger_api.commands.submit_flat(HouseOwner, Seq(shareIouCmd)))

마찬가지로 주택 소유자는 participant 2 를 통해 페인트 도메인에 페인트 제안을 생성합니다. ledger_api.commands.submit_flat 명령에서 참가자가 명령을 이 도메인에 제출하도록 워크 플로우 ID를 페인트 도메인으로 설정합니다. 도메인이 지정되지 않은 경우 참가자는 적합한 도메인을 자동으로 결정합니다. 이 경우 각 도메인에서 모든 이해 관계자 (주택 소유자 및 화가)가 연결된 참가자에서 호스팅 되기 때문에 두 도메인 모두 자격이 있습니다.

// 주택 소유자는 participant 2 및 페인트 도메인을 사용하여 페인트 제안을 생성합니다. val paintOfferCmd = OfferToPaintHouseByOwner(
  painter = Painter.toPrim,
  houseOwner = HouseOwner.toPrim,
  bank = Bank.toPrim,
  iouId = iouContract.contractId
).create.command
val Seq(paintOffer) = decodeAllCreated(OfferToPaintHouseByOwner)(
  participant2.ledger_api.commands.submit_flat(HouseOwner, Seq(paintOfferCmd), workflowId = paint.name))

계약 양도 (Transferring a contract)
Canton에서 계약은 한 번에 최대 하나의 도메인에 있습니다. 예를 들어 IOU 계약은 iou 도메인에 제출된 명령에 의해 생성되었기 때문에 iou 도메인에 있습니다. 마찬가지로 페인트 오퍼는 페인트 도메인에 있습니다. 현재 버전의 Canton에서 거래 실행은 단일 도메인에 있는 계약만 사용할 수 있습니다. 따라서 화가가 제안을 수락하여 IOU 계약의 소유자가 되기 전에 두 계약을 공통 도메인으로 가져와야 합니다.

이 예에서 주택 소유자와 화가는 두 도메인에 연결된 참가자에서 호스팅 되는 반면 은행은 iou 도메인에만 연결됩니다. 계약의 모든 이해 관계자가 계약 있는 도메인에 연결되어야 하므로 IOU 계약을 페인트 도메인으로 이동할 수 없습니다. 반대로 페인트 제안은 iou 도메인으로 이전될 수 있으므로 화가는 iou 도메인에서 제안을 수락할 수 있습니다.

이해 관계자는 transfer.execute 명령을 사용하여 계약의 거주 도메인을 변경할 수 있습니다. 이 예에서 화가는 페인트 제안을 페인트 도메인에서 iou 도메인으로 전송합니다.

// Wait until the painter sees the paint offer in the active contract store
participant3.ledger_api.acs.await_active_contract(Painter, paintOffer.contractId.toLf)

// Painter transfers the paint offer to the IOU domain
participant3.transfer.execute(
  Painter,                      // Initiator of the transfer
  paintOffer.contractId.toLf,   // Contract to be transferred
  paintAlias,                   // Origin domain
  iouAlias                      // Target domain
)

원자적 수용 (Atomic acceptance)
페인트 제안과 IOU 계약은 모두 현재 iou 도메인에 있습니다. 따라서 화가는 제안을 수락하여 워크 플로를 완료할 수 있습니다.

// Painter accepts the paint offer on the IOU domain
val acceptCmd = paintOffer.contractId.exerciseAcceptByPainter(Painter.toPrim).command
val acceptTx = participant3.ledger_api.commands.submit_flat(Painter, Seq(acceptCmd))
val Seq(painterIou) = decodeAllCreated(Iou)(acceptTx)
val Seq(paintHouse) = decodeAllCreated(PaintHouse)(acceptTx)

이 트랜잭션은 입력 계약 (페인트 제안 및 IOU)이 거기에 있기 때문에 iou 도메인에서 실행됩니다. iou 도메인에 두 개의 계약, 즉 화가의 새 IOU와 집 페인트 계약을 원자 적으로 생성합니다. 대역 외(out-of-band) 분쟁이 필요한 불행한 시나리오는 피할 수 있습니다.

워크 플로 완료
마지막으로 페인트 계약은 실제로 속한 paint 도메인으로 다시 전송될 수 있습니다.

// Wait until the house owner sees the PaintHouse agreement
participant2.ledger_api.acs.await_active_contract(HouseOwner, paintHouse.contractId.toLf)

// The house owner moves the PaintHouse agreement back to the Paint domain
participant2.transfer.execute(
  HouseOwner,
  paintHouse.contractId.toLf,
  iouAlias,
  paintAlias
)

참고: 화가의 IOU는 iou 도메인에 남아 있습니다. 따라서 화가는 IOU를 호출하여 현금화할 수 있습니다.

// Painter converts the Iou into cash
participant3.ledger_api.commands.submit_flat(
  Painter,
  Seq(painterIou.contractId.exerciseCall(Painter.toPrim).command),
  iou.name
)

자동으로 전송 수행
Canton은 또한 여러 도메인에 있는 계약을 사용하는 트랜잭션을 수행하는 명령에 대한 자동 전송을 지원합니다. 이러한 명령이 제출되면 Canton은 사용된 계약을 전송할 수 있는 공통 도메인을 자동으로 추론할 수 있습니다. 사용된 모든 계약이 공통 도메인으로 전송되면 이 단일 도메인에서 거래가 수행됩니다. 그러나 이것은 단순히 필요한 전송을 수행 한 다음 트랜잭션 처리를 별개의 비 원자적 단계로 수행합니다.

따라서 자동 전송에 의존하지 않고(전송을 지정하지 않음) 위의 스크립트를 실행할 수 있습니다. 위의 예제에서 모든 전송 명령을 삭제하기 만하면 예제가 성공적으로 실행됩니다. 수동 전송 대신 자동 전송을 사용하는 위 예제의 수정된 버전은 다음과 같습니다.

설정 코드 및 계약 생성은 변경되지 않습니다.

// Bank creates IOU for the house owner
val createIouCmd = Iou(
  payer = Bank.toPrim,
  owner = HouseOwner.toPrim,
  amount = Amount(value = 100.0, currency = "USD"),
  viewers = List.empty
).create.command
val Seq(iouContractUnshared) = decodeAllCreated(Iou)(
  participant1.ledger_api.commands.submit_flat(Bank, Seq(createIouCmd)))

// Wait until the house owner sees the IOU in the active contract store
participant2.ledger_api.acs.await_active_contract(HouseOwner, iouContractUnshared.contractId.toLf)

// The house owner adds the Painter as an observer on the IOU
val showIouCmd = iouContractUnshared.contractId.exerciseShare(actor = HouseOwner.toPrim, viewer = Painter.toPrim).command
val Seq(iouContract) = decodeAllCreated(Iou)(participant2.ledger_api.commands.submit_flat(HouseOwner, Seq(showIouCmd)))

// The house owner creates a paint offer using participant 2 and the Paint domain
val paintOfferCmd = OfferToPaintHouseByOwner(
  painter = Painter.toPrim,
  houseOwner = HouseOwner.toPrim,
  bank = Bank.toPrim,
  iouId = iouContract.contractId
).create.command
val Seq(paintOffer) = decodeAllCreated(OfferToPaintHouseByOwner)(
  participant2.ledger_api.commands.submit_flat(HouseOwner, Seq(paintOfferCmd), workflowId = paint.name))

다음 섹션에서 화가는 페인트 제안을 수락합니다. 페인트 제안을 수락하는 거래는 페인트 제안 계약과 IOU 계약의 두 가지 계약을 사용합니다. 이러한 계약은 이전 단계에서 두 개의 서로 다른 도메인에서 생성되었습니다. 페인트 제공 계약은 paint 도메인에서 생성되고 IOU 계약은 iou 도메인에서 생성되었습니다. 위의 예에서 수동으로 수행한 것처럼 수락 트랜잭션을 성공적으로 적용하려면 페인트 제공 계약을 IOU 도메인으로 전송해야 합니다. IOU 계약의 이해 당사자 은행이 페인트 도메인에 표시되지 않기 때문에 IOU 계약을 paint 도메인으로 대신 이전할 수 없습니다.

자동 이전 거래를 사용할 때 Canton은 거래에 적합한 도메인을 추론하고 거래를 적용하기 전에 사용된 모든 계약을 해당(this) 도메인으로 이전합니다.

이 경우 화가가 페인트 제안을 수락하기에 적합한 유일한 도메인은 IOU 도메인입니다. 이것이 화가가 명시적인 이전을 수행하지 않고도 아래의 페인트 제안을 수락할 수 있는 방법입니다.

// Wait until the painter sees the paint offer in the active contract store
participant3.ledger_api.acs.await_active_contract(Painter, paintOffer.contractId.toLf)

// Painter accepts the paint offer on the IOU domain
val acceptCmd = paintOffer.contractId.exerciseAcceptByPainter(Painter.toPrim).command
val acceptTx = participant3.ledger_api.commands.submit_flat(Painter, Seq(acceptCmd))
val Seq(painterIou) = decodeAllCreated(Iou)(acceptTx)
val Seq(paintHouse) = decodeAllCreated(PaintHouse)(acceptTx)

화가는 IOU를 현금화할 수 있습니다. IOU 계약이 iou 도메인을 떠나지 않기 때문에 이는 이전과 똑같이 발생합니다.

// Wait until the painter sees the paint offer in the active contract store
participant3.ledger_api.acs.await_active_contract(Painter, paintOffer.contractId.toLf)

// Painter accepts the paint offer on the IOU domain
val acceptCmd = paintOffer.contractId.exerciseAcceptByPainter(Painter.toPrim).command
val acceptTx = participant3.ledger_api.commands.submit_flat(Painter, Seq(acceptCmd))
val Seq(painterIou) = decodeAllCreated(Iou)(acceptTx)
val Seq(paintHouse) = decodeAllCreated(PaintHouse)(acceptTx)
[노트] 
명시 적 이전이 있는 이전 예의 끝에서 페인트 제공 계약이 paint 도메인으로 다시 이전되었습니다. 이는 자동 이전 버전에서는 발생하지 않습니다. 페인트 오퍼는 표시된 스크립트의 일부로 iou 도메인 외부로 이전되지 않습니다. 그러나 페인트 제공 계약은 paint 도메인에서 발생해야 하는 트랜잭션에 사용되면 페인트 도메인으로 자동으로 다시 전송됩니다.

자동 이체 거래 내역

이전 섹션에서는 예제를 사용하여 자동 이체 트랜잭션을 설명했습니다.

자동 전송 트랜잭션을 사용하면 계약을 선택한 대상 도메인으로 전송 한 다음 트랜잭션을 수행하여 여러 도메인의 계약을 사용하여 트랜잭션을 제출할 수 있습니다. 그러나 자동 전송 트랜잭션을 사용한다고 해서 몇 가지 기본 전송 및 전송 작업 (이 작업은 transfer.execute 명령을 구성하며 다음 섹션에서 설명)을 사용하는 것 이상의 원 자성 보장을 제공하지 않습니다.

트랜잭션 도메인은 다음 기준을 사용하여 선택됩니다.

  • 필요한 전송 횟수를 최소화하십시오.
  • 우선순위가 더 높은 도메인을 먼저 선택하여 관계를 끊으십시오.
  • 먼저 알파벳순으로 더 작은 도메인 ID를 가진 도메인을 선택하여 관계를 끊으십시오.
  • 일반 트랜잭션의 경우 워크 플로 ID를 도메인 이름으로 설정하여 자동 전송 트랜잭션을 위한 도메인을 강제로 선택할 수 있습니다.

자동 전송 트랜잭션은 다음이 모두 참인 경우에만 활성화됩니다.

  • 로컬 Canton 콘솔은 미리 보기 명령(Preview commands)을 활성화합니다 (구성 섹션 참조).
  • 제출하는 참가자는 거래에서 사용하는 계약이 존재하는 모든 도메인에 연결됩니다.
  • 제출 당사자는 거래에 사용되는 모든 계약의 이해 관계자 여야 합니다.

테이크 아웃(Take away)
계약은 도메인에 있습니다.
이해 관계자는 transfer.execute를 사용하여 한 도메인에서 다른 도메인으로 계약을 이동할 수 있습니다. 모든 이해 관계자는 원본 및 대상 도메인에 연결되어야 합니다.
여러 도메인에 있는 계약을 사용하여 트랜잭션을 제출할 수 있습니다. 자동 이전은 적절한 도메인을 선택하고 거래를 수행하기 전에 해당 도메인으로 이전을 수행합니다.

2 부 : 기존 워크 플로 작성

이 부분에서는 별도의 도메인에서 작동하더라도 기존 워크 플로를 구성하는 방법을 보여줍니다. 실행 예제는 더 복잡한 토폴로지를 사용하여 첫 번째 부분의 페인트 예제를 변형한 것입니다. 따라서 이 자습서의 첫 번째 부분을 완료했다고 가정합니다. 기술적로 이 자습서는 첫 번째 부분과 동일한 단계를 거치지 만 더 많은 세부 정보가 노출됩니다. 콘솔 명령은 새로운 Canton 콘솔로 시작한다고 가정합니다.

기존 워크 플로우

두 도메인 iou와 paint가 개별적으로 진화 한 상황을 고려하십시오.

  • IOU 관리를 위한 iou 도메인
  • 페인트 계약을 관리하기 위한 paint 도메인입니다.

따라서 IOU (발행, 소유권 변경, 호출) 및 페인트 계약을 관리하기 위한 별도의 응용 프로그램이 있으며 주택 소유자와 화가는 응용 프로그램을 서로 다른 참가자에게 연결했습니다. 상황은 다음 그림에 나와 있습니다.

이 설정에서 페인트 계약을 체결하려면 주택 소유자와 화가가 다음 단계를 수행해야 합니다.

  1. 주택 소유자는 paint 도메인의 participant 2를 통해 페인트 제안을 생성합니다.
  2. 화가는 paint 도메인의 participant 3을 통해 페인트 제안을 수락합니다. 결과적으로 페인트 계약이 생성됩니다.
  3. 화가는 iou 도메인의 주택 소유자로부터 IOU를 받아야 한다는 알림을 설정합니다.
  4. 주택 소유자가 paint 도메인의 participant 2를 통해 새 페인트 계약을 관찰하면 iou 도메인의 participant 5를 통해 IOU 소유권을 화가로 변경합니다.
  5. 화가는 iou 도메인의 participant 4를 통해 새 IOU를 관찰하므로 알림을 제거합니다.

전반적으로 페인트 원장이 iou 원장과 일관되게 유지하려면 사소한 양의 대역 외 조정이(out-of-band coordination) 필요합니다. 이 조정이 무너지면 첫 번째 부분의 불행한 시나리오가 발생할 수 있습니다.

요건 변경
이제 집주인과 화가가 페인트 계약을 체결할 때 대역 외 조정의 필요성을 피할 수 있는 방법을 보여줍니다. 목표는 가능한 한 많이 IOU 및 페인트 계약을 관리하기 위해 기존 인프라를 재사용하는 것입니다. 다음 변경이 필요합니다.

  1. 주택 소유자와 화가는 페인트 계약 참가자를 iou 도메인에 연결합니다.

Canton 구성은 이에 따라 두 participant 4와 5로 확장됩니다. 연결 자체는 다음 섹션에서 설정됩니다.

canton {
  participants {
    participant4 {
      ledger-api.port = 11041
      admin-api.port = 11042
      storage.type = memory
    }

    participant5 {
      ledger-api.port = 11051
      admin-api.port = 11052
      storage.type = memory
    }
  }
}
  1. 그들은 주택 소유자가 제안에 IOU를 지정해야 하고 수락 선택으로 화가가 IOU의 새 소유자가 되도록 페인트 제안에 대한 DAML 모델을 대체합니다.
  2. 페인트 제안-수락 워크 플로를 위한 새 응용 프로그램을 만듭니다.

IOU 및 페인트 계약 자체에 대한 DAML 모델은 변경되지 않고 그대로 유지되며 이를 처리하는 애플리케이션도 마찬가지입니다.

기존 워크 플로를 사용한 준비
설명한 대로 첫 번째 부분에서 토폴로지를 확장합니다. 명령은 Canton의 ID 관리 매뉴얼에 자세히 설명되어 있습니다.

participant1.domains.connect_local(iou)
participant2.domains.connect_local(iou)
participant3.domains.connect_local(iou)
participant2.domains.connect_local(paint)
participant3.domains.connect_local(paint)
participant4.domains.connect_local(iou)
participant5.domains.connect_local(iou)

val iouAlias = iou.name
val paintAlias = paint.name

// create the parties
val Bank = participant1.parties.enable("Bank")
val HouseOwner = participant2.parties.enable("House Owner")
val Painter = participant3.parties.enable("Painter")

// enable the house owner on participant 5 and the painter on participant 4
// as explained in the identity management documentation at
// https://www.canton.io/docs/stable/user-manual/usermanual/identity_management.html#party-on-two-nodes
import com.digitalasset.canton.console.ParticipantReference
def authorizePartyParticipant(partyId: PartyId, createdAt: ParticipantReference, to: ParticipantReference): Unit = {
  val createdAtP = createdAt.id
  val toP = to.id
  createdAt.identity.party_to_participant_mappings.authorize(IdentityChangeOp.Add, None, partyId, toP, RequestSide.From)
  to.identity.party_to_participant_mappings.authorize(IdentityChangeOp.Add, None, partyId, toP, RequestSide.To)
}
authorizePartyParticipant(HouseOwner, participant2, participant5)
authorizePartyParticipant(Painter, participant3, participant4)

// Wait until the party enabling has taken effect and a heartbeat has been sent afterwards
val partyAssignment = Set(HouseOwner -> participant2, HouseOwner -> participant5, Painter -> participant3, Painter -> participant4)
participant2.parties.await_topology_heartbeat(partyAssignment)
participant3.parties.await_topology_heartbeat(partyAssignment)

// upload the DAML model to all participants
val darPath = Option(System.getProperty("canton-examples.dar-path")).getOrElse("dars/CantonExamples.dar")
participants.all.dars.upload(darPath)

이전과 마찬가지로 은행은 IOU를 만들고 주택 소유자는 IOU에 대한 기존 응용 프로그램을 사용하여 iou 도메인의 화가와 공유합니다.

import com.digitalasset.canton.examples.Iou.{Amount, Iou}
import com.digitalasset.canton.examples.Paint.{OfferToPaintHouseByOwner, PaintHouse}
import com.digitalasset.canton.ledger.api.client.DecodeUtil.decodeAllCreated
import com.digitalasset.canton.protocol.ContractIdSyntax._

val createIouCmd = Iou(
  payer = Bank.toPrim,
  owner = HouseOwner.toPrim,
  amount = Amount(value = 100.0, currency = "USD"),
  viewers = List.empty
).create.command
val Seq(iouContractUnshared) = decodeAllCreated(Iou)(
  participant1.ledger_api.commands.submit_flat(Bank, Seq(createIouCmd)))

// Wait until the house owner sees the IOU in the active contract store
participant2.ledger_api.acs.await_active_contract(HouseOwner, iouContractUnshared.contractId.toLf)

// The house owner adds the Painter as an observer on the IOU
val shareIouCmd = iouContractUnshared.contractId.exerciseShare(actor = HouseOwner.toPrim, viewer = Painter.toPrim).command
val Seq(iouContract) = decodeAllCreated(Iou)(participant2.ledger_api.commands.submit_flat(HouseOwner, Seq(shareIouCmd)))

페인트 제안-수락 워크 플로

새로운 페인트 제안-수락 워크 플로는 다음 네 단계로 이루어집니다.

  1. paint 도메인에서 오퍼를 작성하십시오.
  2. 계약을 iou 도메인으로 이전하십시오.
  3. 제안을 수락하십시오.
  4. 페인트 계약을 paint 도메인으로 전송합니다.

제안하기
주택 소유자는 paint 도메인에 페인트 제안을 생성합니다.

// The house owner creates a paint offer using participant 2 and the Paint domain
val paintOfferCmd = OfferToPaintHouseByOwner(
  painter = Painter.toPrim,
  houseOwner = HouseOwner.toPrim,
  bank = Bank.toPrim,
  iouId = iouContract.contractId
).create.command
val Seq(paintOffer) = decodeAllCreated(OfferToPaintHouseByOwner)(
  participant2.ledger_api.commands.submit_flat(HouseOwner, Seq(paintOfferCmd), workflowId = paint.name))

전송은 원자 적이 지 않습니다.
첫 번째 부분에서는 transfer.execute를 사용하여 오퍼를 iou 도메인으로 이동했습니다. 이제 우리는 이전 장을 조금 살펴볼것입니다.
계약 이전은 두 가지 원자 단계로 이루어집니다. transfer.execute는 두 단계의 약어 일뿐입니다. 특히 transfer.execute는 다른 원장 명령과 같은 원자 적 작업이 아닙니다.

이전하는 동안 계약은 원본 도메인 (이 경우 paint 도메인)에서 비활성화됩니다. 참가자가 원본 도메인 및 대상 도메인에 연결된 모든 이해 관계자는 이전을 시작할 수 있습니다. transfer.out 명령은 전송 ID를 반환합니다.

// Wait until the painter sees the paint offer in the active contract store
participant3.ledger_api.acs.await_active_contract(Painter, paintOffer.contractId.toLf)

// Painter transfers the paint offer to the IOU domain
val paintOfferTransferId = participant3.transfer.out(
  Painter,                      // Initiator of the transfer
  paintOffer.contractId.toLf,   // Contract to be transferred
  paintAlias,                   // Origin domain
  iouAlias                      // Target domain
)

transfer.in 명령은 전송 ID를 사용하고 대상 도메인에서 계약을 활성화합니다.

participant3.transfer.in(Painter, paintOfferTransferId, iouAlias)

양도와 양도 사이에 계약은 어떤 도메인에도 상주하지 않으며 명령으로 사용할 수 없습니다. 우리는 계약이 운송(transit) 중이라고 말합니다.

페인트 제안 수락

화가는 이전과 마찬가지로 제안을 수락합니다.

// Wait until the Painter sees the IOU contract on participant 3.
participant3.ledger_api.acs.await_active_contract(Painter, iouContract.contractId.toLf)

// Painter accepts the paint offer on the Iou domain
val acceptCmd = paintOffer.contractId.exerciseAcceptByPainter(Painter.toPrim).command
val acceptTx = participant3.ledger_api.commands.submit_flat(Painter, Seq(acceptCmd))
val Seq(painterIou) = decodeAllCreated(Iou)(acceptTx)
val Seq(paintHouse) = decodeAllCreated(PaintHouse)(acceptTx)

자동 입금 (Automatic transfer-in)
마지막으로 페인트 계약은 페인트 계약과 관련된 기존 인프라가 변경되지 않고 작동할 수 있도록 paint 도메인으로 다시 전송됩니다.

participant2.ledger_api.acs.await_active_contract(HouseOwner, paintHouse.contractId.toLf)

val paintHouseId = paintHouse.contractId
// The house owner moves the PaintHouse agreement back to the Paint domain
participant2.transfer.out(
  HouseOwner,
  paintHouseId.toLf,
  iouAlias,
  paintAlias
)
// After the exclusivity period, which is set to 2 seconds,
// the contract is automatically transferred into the target domain
utils.retry_until_true(java.time.Duration.ofSeconds(10)) {
    participant3.testing.acs_search(paint.name, filterId=paintHouseId.toString).nonEmpty &&
      participant2.testing.acs_search(paint.name, filterId=paintHouseId.toString).nonEmpty
}

여기서 transfer.out 명령만 있고 transfer.in 명령은 없습니다. 이는 계약 이해 관계자의 참여자가 계약을 다시 사용할 수 있도록 자동으로 계약을 대상 도메인으로 이전하려고 하기 때문입니다. 대상 도메인의 도메인 매개 변수 transfer-exclusivity-timeout은 이를 시도하기 전에 대기하는 시간을 지정합니다.
시간 초과 이전에는 전송 개시 자만 계약을 전송할 수 있습니다. 이는 개시자가 일반적으로 다른 모든 이해 관계자가 동시에 계약 양도를 시도하기 전에 양도를 완료하므로 많은 이해 관계자와의 계약에 대한 경합을 줄입니다. paint 도메인에서 이 시간제한은 구성 파일에서 2 초로 설정됩니다. 따라서 utils.retry_until_true는 일반적으로 할당된 10 초 내에 성공합니다.

iou 도메인에서와 같이 transfer-exclusivity-timeout을 0으로 설정하면 자동 이전이 비활성화됩니다. 이것이 위의 페인트 제안 양도를 수동으로 완료해야 하는 이유입니다. 예를 들어 대상 도메인의 시간 초과로 인해 자동 전송이 실패한 경우에도 수동 완료가 필요합니다. 따라서 자동 입금은 계약이 운송 중에 멈출 위험을 줄이는 안전망입니다.

기존 워크 플로 계속
이제 화가는 iou 도메인에서 IOU를 소유하고 입력된 페인트 계약은 페인트 도메인에 있습니다. 따라서 IOU 및 페인트 계약에 대한 기존 워크 플로를 변경 없이 사용할 수 있습니다. 예를 들어 화가는 IOU를 호출할 수 있습니다.

// Painter converts the Iou into cash
participant4.ledger_api.commands.submit_flat(
  Painter,
  Seq(painterIou.contractId.exerciseCall(Painter.toPrim).command),
  iou.name
)

테이크 아웃
계약이전은 두 가지 원자적 단계가 있습니다. 계약이 이전되는 동안 계약은 어떤 도메인에도 상주하지 않습니다.
Transfer-in은 대상 도메인에 구성된 transfer-exclusivity-timeout후에 정상적인 상황에서 자동으로 발생합니다. 시간 초과 0은 자동 전송을 비활성화합니다. 자동 입금이 완료되지 않은 경우 계약을 수동으로 입금 할 수 있습니다.

원문 : Getting Started
https://www.canton.io/docs/stable/user-manual/tutorials/getting\_started.html

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

아키텍처 개요 및 가정(Assumptions)  (0) 2020.09.23