LoginSignup
3
4

More than 3 years have passed since last update.

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

Posted at

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

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

cordapp-template-kotlinを使い、説明していきます。

環境

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

  • Windows10
    • core-i7
    • 64bit
    • RAM:8GB
  • Cordaを動かすための環境は整っているものとします。

テンプレートインストール

コマンドプロンプトを開き、下記コマンドを実行します

コマンドプロンプト
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フォルダ作成

使うファイルとか抜粋
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)をもたせることができます。

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
    • val linearId: UniqueIdentifierのoverrideが必須
  • QueryableState
    • fun generateMappedObject(schema: MappedSchema): PersistentStateのoverrideが必須
    • fun supportedSchemas(): Iterable<MappedSchema>のoverrideが必須
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を検索し、取得します。

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))
    }
}

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

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を記述します。

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がインストールされていない場合は、ここからインストールします。
インストールされていれば、設定は特に不要です。

H2コンソール起動

H2を起動します。
image.png

下記のエラーが発生していたら、IPアドレス(172.19.32.186)をlocalhostに書き換えます
image.png
「接続」ボタンを押下して、アクセスします
image.png
TEMPLATE_STATE テーブルが作成されています。
※TEMPLATE_STATE を選択すると、自動的にSELECT文が作成されます
image.png
実行しても、まだデータはありません
image.png

実行

コマンドプロンプト
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
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

おわり

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

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

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4