[Corda]VaultQueryを使って、Vaultを検索したり、H2DBを使ったり

どーも、のぶこふです。
Cordaさんともそこそこお友達になれた気がする今日このごろです。

今回は、VaultQueryを使ったり、H2を使ったりします。
VaultはStateのHEADが保存されています。
つまり、最新のStateが取得できるということです。

[cordapp-template-kotlin](https://github.com/corda/cordapp-template-kotlin)を使い、説明していきます。

# 環境
今回は、Windows10でお送りします。

* Windows10
  * core-i7
  * 64bit
  * RAM:8GB
* Cordaを動かすための環境は整っているものとします。
  * Java8
  * IntelliJ
  * Gradle
  * [詳細はコチラ](https://docs.corda.net/getting-set-up.html#software-requirements)をご参照ください

# テンプレートインストール
コマンドプロンプトを開き、下記コマンドを実行します

```terminal:コマンドプロンプト
C:\10_corda // ディレクトリ作成
> md vault-test

C:\10_corda // ディレクトリ移動
> cd vault-test

C:\10_corda\vault-test // git clone
> git clone https://github.com/corda/cordapp-template-kotlin.git
Cloning into 'cordapp-template-kotlin'...
remote: Enumerating objects: 32, done.
remote: Counting objects: 100% (32/32), done.
remote: Compressing objects: 100% (20/20), done.
Rremote: Total 3473 (delta 3), reused 23 (delta 0), pack-reused 3441eceiving objects:                 , 2.04 MiB |
Receiving objects: 100% (3473/3473), 2.21 MiB | 2.13 MiB/s, done.
Resolving deltas: 100% (1255/1255), done.

```

# IntelliJで開く
「C:\10_corda\vault-test\cordapp-template-kotlin\build.gradle」を選択し、プロジェクトとして開きます。

# TemplateSchema
## Schemaフォルダ作成＆TemplateSchema.ktを作成
\vault-test\cordapp-template-kotlin\contracts\src\main\kotlin\com\template　に、Schemasフォルダ作成

```terminal:使うファイルとか抜粋
cordapp-template-kotlin
├ contracts\src\main\kotlin\com\template
|   ├ contracts
|   |   └ TemplateContract.kt　＜－－－ 何もしない
|   ├ schemas　＜－－－－－－－－－－－－ 作成
|   |   └ TemplateSchema.kt　＜－－－－ 作成
|   └ states
|       └ TemplateState.kt　＜－－－－ 修正
└ workflows\src\main\kotlin\com\template
    └ flows
        └ Flows.kt　＜－－－－－－－－－ 修正
```

Schemaを作成することで、Stateに永続性（Persistence）をもたせることができます。

```kotlin:TemplateSchema.kt
package com.template.schemas

import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.Table

object TemplateSchema
object TemplateSchemaV1 : MappedSchema(
    schemaFamily = TemplateSchema.javaClass,
    version = 1,
    mappedTypes = listOf(TemplateSchemaV1.PersistentTemplate::class.java)
) {
    @Entity
    @Table(name = "template_state")
    class PersistentTemplate(
        @Column(name = "id")
        var id: Int,
        @Column(name = "data")
        var data: String,
        @Column(name = "counterParty")
        var counterParty: String,
        @Column(name = "issuer")
        var issuer: String
    ) : PersistentState() {
        constructor() : this(0,"","","")
    }
}
```

# TemplateState
## LinearStateとQueryableStateを継承する
* [LinearState](https://docs.corda.net/api-states.html#linearstate)
    * `val linearId: UniqueIdentifier`のoverrideが必須
* [QueryableState](https://docs.corda.net/api-states.html#other-interfaces)
    * `fun generateMappedObject(schema: MappedSchema): PersistentState`のoverrideが必須
    * `fun supportedSchemas(): Iterable<MappedSchema>`のoverrideが必須

```kotlin:TemplateState.kt
package com.template.states

import com.template.contracts.TemplateContract
import com.template.schemas.TemplateSchemaV1
import net.corda.core.contracts.BelongsToContract
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.LinearState
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState
import net.corda.core.schemas.QueryableState

// *********
// * State *
// *********
@BelongsToContract(TemplateContract::class)
data class TemplateState(
    val issuer :Party,
    val counterParty: Party,
    val id : Int,
    val data: String,
    override val linearId: UniqueIdentifier = UniqueIdentifier(),
    override val participants: List<AbstractParty> = listOf(issuer,counterParty)
) : LinearState, QueryableState {
    override fun generateMappedObject(schema: MappedSchema): PersistentState {
        return when(schema){
            is TemplateSchemaV1 -> TemplateSchemaV1.PersistentTemplate(this.id,this.data,counterParty.name.toString(),issuer.name.toString())
            else -> throw IllegalArgumentException("Unrecognised schema $schema")
        }
    }

    override fun supportedSchemas(): Iterable<MappedSchema> = listOf(TemplateSchemaV1)
}
```

# Flows
## Initiatorのcallを実装
Flowの中で、実際にVaultを検索し、取得します。

```kotlin:Flows.kt
package com.template.flows

import co.paralleluniverse.fibers.Suspendable
import com.template.contracts.TemplateContract
import com.template.schemas.TemplateSchemaV1
import com.template.states.TemplateState
import net.corda.core.contracts.Command
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.requireThat
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.node.services.Vault
import net.corda.core.node.services.queryBy
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.builder
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker

// *********
// * Flows *
// *********
@InitiatingFlow
@StartableByRPC
class Initiator(private val counterParty: Party, private val id: Int, private val data: String) : FlowLogic<Unit>() {
    override val progressTracker = ProgressTracker()

    @Suspendable
    override fun call() {
        // Default notary
        val notary = serviceHub.networkMapCache.notaryIdentities[0]

        // Search vault
        val generalCriteria = QueryCriteria.VaultQueryCriteria(Vault.StateStatus.ALL)
        val states = builder {
            val condition1 = TemplateSchemaV1.PersistentTemplate::id.equal(id)
            val q1 = QueryCriteria.VaultCustomQueryCriteria(condition1)
            val criteria = generalCriteria.and(q1)
            serviceHub.vaultService.queryBy<TemplateState>(criteria).states
        }

        // Create Output
        val output = TemplateState(issuer = ourIdentity,counterParty = counterParty,id= this.id,data = this.data)

        // Create command
        val cmd =
            Command(TemplateContract.Commands.Action(), listOf(ourIdentity.owningKey, counterParty.owningKey))

        // Create Transaction
        val txBuilder = TransactionBuilder(notary)
            .addOutputState(output)
            .addCommand(cmd)
        if (states.isNotEmpty()) txBuilder.withItems(*states.toTypedArray())
        txBuilder.verify(serviceHub)

        // Signing Transaction
        val signedTx = serviceHub.signInitialTransaction(txBuilder)

        // Gathering Signs
        val counterPartySession = initiateFlow(counterParty)
        val fullySignedTx = subFlow(CollectSignaturesFlow(signedTx, setOf(counterPartySession)))

        // Finalize Transaction
        subFlow(FinalityFlow(fullySignedTx, setOf(counterPartySession)))
    }
}

@InitiatedBy(Initiator::class)
class Responder(val counterPartySession: FlowSession) : FlowLogic<Unit>() {
    @Suspendable
    override fun call() {
        val signTransactionFlow = object : SignTransactionFlow(counterPartySession) {
            override fun checkTransaction(stx: SignedTransaction) = requireThat {}
        }
        val txId = subFlow(signTransactionFlow).id
        subFlow(ReceiveFinalityFlow(counterPartySession, expectedTxId = txId))
    }
}
```

検索処理をしているのは、ここ（↓）です。

```kotlin:VaultQuery
// Search vault
/* Vaultの状態
* UNCONSUMED：未消費
* CONSUMED　：消費済み
* ALL　　　　：すべて
*/
val generalCriteria = QueryCriteria.VaultQueryCriteria(Vault.StateStatus.ALL)
/* builderを使用
* Flowの引数のidと、DBのidが合致するのを検索し、実行
* 戻り値はList
*/
val states = builder {
    val condition1 = TemplateSchemaV1.PersistentTemplate::id.equal(id)
    val q1 = QueryCriteria.VaultCustomQueryCriteria(condition1)
//    val q2 = QueryCriteria.VaultCustomQueryCriteria(condition1)
// andやorで連結が可能
//    val criteria = generalCriteria.and(q1).and(q2)
    val criteria = generalCriteria.and(q1)
    serviceHub.vaultService.queryBy<TemplateState>(criteria).states
// 戻り値が単数なら下記でも可
//    serviceHub.vaultService.queryBy<TemplateState>(criteria).states.single()
// 取得できなかったら、例外を発生させたり
//    serviceHub.vaultService.queryBy<TemplateState>(criteria).states..firstOrNull() ?: throw IllegalStateException("Not found.")
}
```

上記の記述だと、戻り値はList(`List<StateAndRef<TemplateState>>`)。
結果が単数なのであれば`serviceHub.vaultService.queryBy<TemplateState>(criteria).states.single()`や`serviceHub.vaultService.queryBy<TemplateState>(criteria).states.firstOrNull()`を使うほうが好ましいでしょう。
取得できなかった際に例外を投げても良いですね。

vaultに登録処理をしているのは`FinalityFlow`です。

```
// Finalize Transaction
subFlow(FinalityFlow(fullySignedTx, setOf(counterPartySession)))
```

# H2設定
## H2Port
せっかくなので、H2の設定も行いましょう。
build.gradleのノードの設定を行う箇所に`h2Port portNo`を記述します。

```gradle:vault-test\cordapp-template-kotlin\build.gradle
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
    nodeDefaults {
        projectCordapp {
            deploy = false
        }
        cordapp project(':contracts')
        cordapp project(':workflows')
    }
    node {
        name "O=Notary,L=London,C=GB"
        notary = [validating : false]
        p2pPort 10002
        rpcSettings {
            address("localhost:10003")
            adminAddress("localhost:10043")
        }
    }
    node {
        name "O=PartyA,L=London,C=GB"
        p2pPort 10005
        rpcSettings {
            address("localhost:10006")
            adminAddress("localhost:10046")
        }
        rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]]
        h2Port 10007   // <------ 追加
    }
    node {
        name "O=PartyB,L=New York,C=US"
        p2pPort 10008
        rpcSettings {
            address("localhost:10009")
            adminAddress("localhost:10049")
        }
        rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]]
        h2Port 10010   // <------ 追加
    }
}
```

## H2クライアント
H2がインストールされていない場合は、[ここから](http://www.h2database.com/html/main.html)インストールします。
インストールされていれば、設定は特に不要です。

## H2コンソール起動

H2を起動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306608/421c8292-7efa-77ef-464d-59c9f7f6f909.png)

下記のエラーが発生していたら、IPアドレス（`172.19.32.186`）を`localhost`に書き換えます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306608/e011bf55-edc2-d983-90a4-4fb5d2c62375.png)
「接続」ボタンを押下して、アクセスします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306608/2cf3ae57-ba78-b440-0961-b82c0a6926e9.png)
TEMPLATE_STATE テーブルが作成されています。
※TEMPLATE_STATE を選択すると、自動的にSELECT文が作成されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306608/a57aa7da-177b-7641-b390-db482c0d25bf.png)
実行しても、まだデータはありません
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306608/8e928daa-1aa4-606d-e79c-036e04c4a699.png)

# 実行

```terminal:コマンドプロンプト
C:\10_corda\vault-test // ディレクトリ移動
> cd cordapp-template-kotlin

C:\10_corda\vault-test\cordapp-template-kotlin // ノードのデプロイ
> gradlew clean deployNodes
Starting a Gradle Daemon (subsequent builds will be faster)
～略～
BUILD SUCCESSFUL in 41s
18 actionable tasks: 14 executed, 4 up-to-date

C:\10_corda\vault-test\cordapp-template-kotlin // ノード起動
> call build\nodes\runnodes.bat
～略～
C:\10_corda\vault-test\cordapp-template-kotlin
```

```terminal:PartyAタブで実行
>>> start Initiator counterParty : "PartyB" , id : 1 , data : "hoge"
Starting
Collecting signatures from counterparties.
Starting
Collecting signatures from counterparties.
Verifying collected signatures.
Starting
Broadcasting transaction to participants
Done
Flow completed with result: kotlin.Unit

>>> run vaultQuery contractStateType: com.template.states.TemplateState
states:
- state:
    data: !<com.template.states.TemplateState>
      issuer: "O=PartyA, L=London, C=GB"
      counterParty: "O=PartyB, L=New York, C=US"
      id: 1
      data: "hoge"
      linearId:
        externalId: null
        id: "3660c47f-af08-4eea-b451-353b91c8dff0"
      participants:
      - "O=PartyA, L=London, C=GB"
      - "O=PartyB, L=New York, C=US"
    contract: "com.template.contracts.TemplateContract"
    notary: "O=Notary, L=London, C=GB"
    encumbrance: null
    constraint: !<net.corda.core.contracts.SignatureAttachmentConstraint>
      key: "aSq9DsNNvGhYxYyqA9wd2eduEAZ5AXWgJTbTEw3G5d2maAq8vtLE4kZHgCs5jcB1N31cx1hpsLeqG2ngSysVHqcXhbNts6SkRWDaV7xNcr6MtcbufGUchxredBb6"
  ref:
    txhash: "06837B21E43C93F1FD2325F15CFDE12323048A6AB33A031511F0EDF4A29E6BF3"
    index: 0
statesMetadata:
```

H2で確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306608/f6f3574c-7255-19c8-3b0a-c919928ac9f8.png)

# おわり
あまりVaultQueryについて説明がなかったので、次回はもう少し詳しく書きます。
今回はここまでです。

ありがとうございました。