Corda|初めてのCorDappを作ってみる

Corda|初めてのCorDappを作ってみる

はじめに

このページでは、Cordaの公式チュートリアルを参考にCorDappを作成する手順を解説していきます。今回はKotlinのCorDappテンプレートを利用してIOUコントラクト(借用コントラクト)を実装してみます。

Pre-Requisites

  1. Java 8 JDK: Veresion8u171以上。ただしVersion9以上はサポートされていません。
  2. Intellij IDEA: Version 2017.x, 2018.x, 2019.x
  3. Git
  4. Gradle: 5.4.1

参考:Getting set up for CorDapp development

Getting started

開発用に公式からCorDappのテンプレートが提供されています。テンプレートにはKotlinとJavaのものがあります。

Cordaのコードは主にKotlinが利用されているので、Kotlinを利用して開発を始めてみます。

まずは、corda-template-kotlinをクローンしましょう。

git clone https://github.com/corda/cordapp-template-kotlin.git

IntelliJ IDEAをでCorDappのサンプルを開く

IntelliJでcordapp-template-kotlinを開き以下の手順でProjectのJavaを設定。

File > Project Structure > Porject Settings > Project > SDKを選択(version 8u171以上) > OK

次に、build.gradleファイル上で右クリックしimport Gradle Projectでプロジェクトの設定をIDEに読み込みます。

プロジェクトの構造

StateのファイルとFlowのファイルが以下のディレクトリにあります。このチュートリアルではこの二つのファイルを編集します。

// 1. The state
contracts/src/main/kotlin/com/template/states/TemplateState.kt

// 2. The flow
workflows/src/main/kotlin/com/template/flows/Flows.k

Stateの記述

Cordaでstateは共有された事実を表します。まず、IOUを表すStateを定義します。

CordaState Interface

Cordaのstateは、CordaState Interfaceを実装したインスタンスです。

CordaState Interfaceは以下のように、事実が共有される当事者のリストを持ちます。

interface CordaState {
     // The list of entities considered to have a stake in this state.
    val participants: List<AbstractParty>
}

CordaStateはCordaState Interfaceの他にも、フィールドやメソッド、ヘルパー、内部クラスを定義できます。

IOUのモデリング

IOUを表現するStateを定義してみましょう。IOUは以下の要素があります。

  • 金額(value)
  • 貸主(lender)
  • 借主(borrower)

以下のようなコードになります。

import net.corda.core.contracts.ContractState
import net.corda.core.identity.Party

class IOUState(
        val value: Int,
        val lender: Party,
        val borrower: Party,
) : ContractState {
    override val participants: List<AbstractParty>
        get() = listOf(lender, borrower)
}

これでstateの作成が完成しました。CorDappでStateは、CordaState Interfaceを実装した単なるクラスです。

StateはCordaStateの他に任意のプロパティーやメソッドを定義することができます。

次にFlowを作成します。

Flowの作成

FlowはStateを更新するためにノードが実行できるワークフローをコード化したものです。ノードはFlowを実行することでLedgerに新しいステートを書き込むことができます。

Flowの概要

Flowはトランザクションの発行を管理します。トランザクションはCordaにおいて状態を変更する最小単位の処理です。

トラクザクションは0か1つ以上のStateをインプットとして消費、新しい0か1つ以上のアウトプット(State)を生成します。

IOUトランザクションをLedgerに書き込むプロセスは貸主によって開始されます。

貸主がIOUを発行するトランザクションを発行するには以下のステップが必要となります。

  • 新しいIOUを発行するトランザクションプロポーザルを貸主が作成
  • トランザクションプロポーザルにサインする
  • トランザクションをレジャーに書き込み、IOUの借主もレジャーに書き込めるように貸主にトランザクションを送信する

FlowLogic

FlowはFlowLogicのサブクラスとして実装します。FlowLogic.callをオーバーライドしてフローのステップをコーディングします。

テンプレートのInitiaterを以下のように書き換えましょう。

// Add these imports:
import net.corda.core.contracts.Command
import net.corda.core.identity.Party
import net.corda.core.transactions.TransactionBuilder

// Replace Initiator's definition with:
@InitiatingFlow
@StartableByRPC
class IOUFlow(val iouValue: Int,
              val otherParty: Party) : FlowLogic<Unit>() {

    /** The progress tracker provides checkpoints indicating the progress of the flow to observers. */
    override val progressTracker = ProgressTracker()

    /** The flow logic is encapsulated within the call() method. */
    @Suspendable
    override fun call() {
        // We retrieve the notary identity from the network map.
        val notary = serviceHub.networkMapCache.notaryIdentities[0]

        // We create the transaction components.
        val outputState = IOUState(iouValue, ourIdentity, otherParty)
        val command = Command(TemplateContract.Commands.Action(), ourIdentity.owningKey)

        // We create a transaction builder and add the components.
        val txBuilder = TransactionBuilder(notary = notary)
                .addOutputState(outputState, TemplateContract.ID)
                .addCommand(command)

        // We sign the transaction.
        val signedTx = serviceHub.signInitialTransaction(txBuilder)

        // Creating a session with the other party.
        val otherPartySession = initiateFlow(otherParty)

        // We finalise the transaction and then send it to the counterparty.
        subFlow(FinalityFlow(signedTx, otherPartySession))
    }
}

少しずつこのコードをみていきましょう

FlowLogic, FlowLogic.call

以下のように、IOUFlowはFlowLogicのサブクラスとして定義されています。

FlowLogic.callをオーバーライドすることでIOUFlowのフローを定義します。

また、FlowLogicとFlowLogicは型パラメーターを取りますが、この型はFlowLogic.callの戻り値の型となります。

class IOUFlow(): FlowLogic<Unit>() {
    override fun call() {
        //...callの戻り値の型はUnit
    }    
} 

IOUFlowのコンストラクタパラメーター

Ledgerに書き込むIOUStateの値をトランザクションに含めるためにIOUFlowはコンストラクタ引数にvalue, borrowerを含みます。

class IOUFlow(val iouValue: Int, val otherParty: Party) : FlowLogic<Unit>() {
    // ... 
}

@Suspendable

Flowはトランザクションの相手方が応答することで完了します。

そのため、Flowが長い間待たされる場合はFlowは中断されデータはディスクに書き込まれる事になります。

@Suspendableアノテーションを記述するのを忘れるとエラーが発生してしまいます。

class IOUFlow(): FlowLogic<Unit>() {
    @Suspendable
    override fun call() {
        // ...
    }    
} 

@InitiatingFlow, @StartableByRPC

@InitiatingFlowはフローの開始側である事を示します。フローは当事者間の契約合意フローを表すものであり、フローの開始側と受け取り側が存在します。

@InitiatingFlowを起動すると、後で定義する受けとり側のノードのフローであるIOUFlowResponderが起動します。

@StartableByRPCはRPCコールによってこのフローを起動可能とします。

@InitiatingFlow
@StartableByRPC
class IOUFlow(): FlowLogic<Unit>() {
    @Suspendable
    override fun call() {
        // ...
    }    
} 

Transaction Building

トランザクションプロポーザルは以下の手順で作成します。

  1. トランザクションコンポーネントの作成
  2. トランザクションビルダーにコンポーネントを追加
  3. トランザクションへサイン
  4. トランザクションをファイナライズ
    これらの処理はIOUFlow.callに記述されています。

class IOUFlow(): FlowLogic<Unit>() {
    @Suspendable
    override fun call() {     
        // We retrieve the notary identity from the network map.
        val notary = serviceHub.networkMapCache.notaryIdentities[0]        
        // 1.We create the transaction components.
        val outputState = IOUState(iouValue, ourIdentity, otherParty)
        val command = Command(TemplateContract.Commands.Action(), ourIdentity.owningKey)

        // 2.We create a transaction builder and add the components.
        val txBuilder = TransactionBuilder(notary = notary)
                .addOutputState(outputState, TemplateContract.ID)
                .addCommand(command)

        // 3.We sign the transaction.
        val signedTx = serviceHub.signInitialTransaction(txBuilder)

        // Creating a session with the other party.
        val otherPartySession = initiateFlow(otherParty)

        // 4.We finalise the transaction and then send it to the counterparty.
        subFlow(FinalityFlow(signedTx, otherPartySession))
    }    
} 

それぞれの処理をみていきましょう。

Notary

トランザクションは二重支払い防止のためにNotary(ノタリー)が必要です。

ServiceHub.networkMapCacheからNotaryを取得します。

class IOUFlow(): FlowLogic<Unit>() {
    @Suspendable
    override fun call() {

    }    
} 

トランザクションの構成

トランザクションはインプットを持たず、Lender, Borrower, AmountのIOUSateがアウトプットとなっています。

ActionはIOUのLenderを署名者としてリストします。

トランザクションの構成
https://docs.corda.net/docs/corda-os/4.5/hello-world-flow.html#transaction-items

Command

Commandは以下の二つの役割を持ちます。

  • Transactionの意図を示す
  • Transactionに必要な署名者を表す

Transaction Builder

TransactionBuilderには inputs, outputs, commandsなどトランザクションに必要な情報を追加することができます。

Sign Transaction

トランザクションへ署名します。

Finalize Transaction

有効なトランザクションプロポーザルを作成したらファイナライズします。

最後に必要なのはノタリーの署名を得て、ローカルに記録し、関係する当事者にトランザクションを送信します。これらの処理が終わるとレジャーに記録されデータが永続化されます。

Borrower’s flow

Lenderからトランザクションを受け取った際、Borrowerはレスポンドするためのフローが必要となります。

// Replace Responder's definition with:
@InitiatedBy(IOUFlow::class)
class IOUFlowResponder(private val otherPartySession: FlowSession) : FlowLogic<Unit>() {
    @Suspendable
    override fun call() {
        subFlow(ReceiveFinalityFlow(otherPartySession))
    }
}

以下、記述をみていきましょう。

@InitiatedBy(IOUFlow:class)

IOUFlowからトランザクションを受け取った時にIOUFlowResponderが実行される事を記述しています。

IOUFlowResponder.call

IOUFlowと同様に, FlowLogic.callをオーバーライドしたIOUFlowResponder.callが呼ばれます。IOUFlowResponder.callによってトランザクションをファイナライズしています。

以上でCordappの記述は完了です。

残りのステップ

ここではまだ解説していないのが、Contractと実際にCordappを起動してみる部分です。公式サイトに記述があるのでリンクを載せておきます。

ブロックチェーンカテゴリの最新記事