LoginSignup
4
2

More than 3 years have passed since last update.

【Corda】Stateの検索方法〜VaultQueryの使用方法と、JDBCを利用したSQLの発行方法について〜

Posted at

どーも、のぶこふです。
最近、Cordaのノード起動する度に、PCがウィンウィンいうので「PC頑張れっ!みんながんばれ!むしろ、ガンガンいこうぜ!!」と心の中で叫んでいます。

今回は、引き続きVaultQueryの使用方法と、JDBCを利用したSQLの発行方法について書きます。
VaultQueryの使用方法については、前々回の記事([Corda]VaultQueryを使って、Vaultを検索したり、H2DBを使ったり)と内容が重複しますので、その時のソースが手元にある場合は、そちらをお使いください。
(SchemaとStateは流用しています)

今回の記事 is 何?

  • VaultQueryの使用方法がわかる
  • JDBCを利用したSQLの発行方法がわかる
  • するとどうなる???
    • →TransactionのInputとして利用できる!!

はじめに

お約束のCordaテンプレートを使用します。
なお、今回使用PCはMacです。

環境準備

  • 適当にディレクトリを作成します。(省略可)
  • テンプレートをgit clone します。
  • IDEを起動します。
お約束のコマンドたち
$ mkdir vaultTest
$ cd vaultTest/
$ git clone https://github.com/corda/cordapp-template-kotlin.git
Cloning into 'cordapp-template-kotlin'...
remote: Enumerating objects: 39, done.
remote: Counting objects: 100% (39/39), done.
remote: Compressing objects: 100% (27/27), done.
remote: Total 3480 (delta 7), reused 25 (delta 0), pack-reused 3441
Receiving objects: 100% (3480/3480), 2.21 MiB | 2.37 MiB/s, done.
Resolving deltas: 100% (1259/1259), done.

スクリーンショット 2019-09-04 8.51.27.png

Schemaの作成

  • DB(インメモリ=H2DB)に独自Stateの情報を保存出来るように、Schemaを作成します。
  • contracts/src/main/kotlin/com/templateにschemasディレクトリを作成し、その下にTemplateSchema.ktを作成します。

スクリーンショット 2019-09-04 8.56.42.png

TemplateSchema.kt(コピペでOKです!!)
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,"","","")
    }
}

Stateの修正

  • 検索が出来るようにQueryableStateの実装と、Stateを一意に識別出来るようにLinearStateを実装します。
TemplateState.kt(コピペでOKです!!)
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.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)
}

Flowの修正

▼VaultQueryの実装

  • Initiatorの引数を変更します。
    • この引数を使って、Outputを作成します。
    • : FlowLogic<Unit>()は、callの戻り値を指定しています。
      • callの戻り値がUnitなら上記のように、Stringなら: FlowLogic<String>()のように記述します。ここに関しては制限は無いので、自由に記述することが可能です。
      • callの結果を他のシステムに連携する時などは、戻り値は必須ですね。
Flow.kt
class Initiator(private val counterParty: Party, private val id: Int, private val data: String) : FlowLogic<Unit>() {
  • Notaryの取得と、VaultQueryの設定を行います。
    • NotaryはserviceHubnetworkMapCacheから取得します。
    • VaultQueryCriteriaVault.StateStatus.UNCONSUMEDと指定することで、ステータスが未消費のStateに絞り込みます。
      • Vault.StateStatus.CONSUMEDなら消費済み
      • Vault.StateStatus.ALLなら全て
      • Vault.StateStatus.UNCONSUMEDか、未指定の場合は、未消費のStateが対象になります
      • ただし、以下のbuilderで検索条件の絞り込みを行うと、デフォルトのVault.StateStatus.UNCONSUMEDに上書きされてしまうようです。
    • val condition1 = TemplateSchemaV1.PersistentTemplate::id.equal(id)で使用しているのはnet.corda.core.node.services.vault.builderequal()です。kotlin.anyで提供しているequals()では無いので注意が必要です。
      • たまにサジェストをミスってエラーが発生して「???」となる
Flow.kt
    @Suspendable
    override fun call() {
        // デフォルトのNotaryを指定
        val notary = serviceHub.networkMapCache.notaryIdentities[0]

        // 未使用のState、かつIDが等しいStateを検索
        val generalCriteria = QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED)
        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
        }
  • 続いてcallにOutput、Command、Transactionを作成します。
    • 引数を使用しOutput用のStateを作成します。
    • CommandはデフォルトのActionを使用します。
      • 何もしていない空のコントラクトです
    • Transactionには、作成したOutputとCommandを指定します。
      • inputを指定する場合は.addInputState(StateAndRef<*>)を指定します。
      • StateAndRef<*>は、ざっくりとqueryBy()の戻り値と認識しておけばOKです。
      • TransactionBuilderadd~は順序に指定はないので、先にInputをAddするなどもOKです。
    • 最後のtxBuilder.verify(serviceHub)で、Commandが正しく設定されているか?などTransactionのチェックを行っています。
Flow.kt
        // 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)

        // Add Input
        if (states.isNotEmpty()){
            txBuilder.addInputState(states.first())
        }

        // Verify Transaction
        txBuilder.verify(serviceHub)
  • 最後にTransactionに自身の署名、他者の署名、Notaryの署名&二重支払いチェックを行い、Transactionを確定させます。
    • signInitialTransaction(txBuilder)で自身の署名を取得します。
    • initiateFlow(counterParty)で他者のセッションを起動し、subFlowとしてCollectSignaturesFlow(signedTx, setOf(counterPartySession))で他者の署名を取得します。
    • FinalityFlow(fullySignedTx, setOf(counterPartySession))でNotaryの署名&二重支払いチェックを行い、Transactionを確定させています。
        // 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)))
    }
  • おっと、Responderの設定も必要でした。
    • ResponderInitiatorCollectSignaturesFlowを受けて実行されます。
    • 未実装の場合はnet.corda.core.flows.UnexpectedFlowEndException: Tried to access ended session SessionId(toLong=XXX) with empty bufferのようなエラーが発生します。
Flow.kt
@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))
    }
}
  • Flow.ktの全量は以下になります

Flow.ktの全量(長いので折りたたみ)
Flow.kt(コピペOKです!!)
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.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() {
        // デフォルトのNotaryを指定
        val notary = serviceHub.networkMapCache.notaryIdentities[0]
        id.equals(id)

        val generalCriteria = QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED)
        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)

        // Add Input
        if (states.isNotEmpty()){
            txBuilder.addInputState(states.first())
        }

        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が使える準備が出来ました。実際に動かして試してみましょう。
ディレクトリを移動して、いつものコマンド(ビルド→ノード起動)を実行する
$ cd cordapp-template-kotlin/
$ ./gradlew deployNodes
$ ./build/nodes/runnodes
  • ノードの起動まで終わったら、PartyAのノードで実行してみます。
    • Stateに登録されていることがわかります。
PartyAのノードでInitiatorのFlowを実行する(1回目)
>>> start Initiator counterParty: "PartyB" , id: 1 , data: "hoge" 

 ✅   Starting
          Requesting signature by notary service
              Requesting signature by Notary service
              Validating response from Notary service
     ✅   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: "62bec7c3-7b64-4fbf-8d9e-3845a0142a43"
      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: "C6D1F7540749A88E69F7A2D058C2E15B3D9BBE71E624A58F325FE073E64042B9"
    index: 0
statesMetadata:
- ref:
    txhash: "C6D1F7540749A88E69F7A2D058C2E15B3D9BBE71E624A58F325FE073E64042B9"
    index: 0
  contractStateClassName: "com.template.states.TemplateState"
  recordedTime: "2019-09-04T01:49:26.643Z"
  consumedTime: null
  status: "UNCONSUMED"
  notary: "O=Notary, L=London, C=GB"
  lockId: null
  lockUpdateTime: null
  relevancyStatus: "RELEVANT"
  constraintInfo:
    constraint:
      key: "aSq9DsNNvGhYxYyqA9wd2eduEAZ5AXWgJTbTEw3G5d2maAq8vtLE4kZHgCs5jcB1N31cx1hpsLeqG2ngSysVHqcXhbNts6SkRWDaV7xNcr6MtcbufGUchxredBb6"
totalStatesAvailable: -1
stateTypes: "UNCONSUMED"
otherResults: []
  • 少し内容を変えて、実行してみます。
    • 引数のdataをhogeからFugaに変更しています。
    • こちらも無事実行されていますが、実行結果を見てお気づきの方もいらっしゃるかと思います。
PartyAのノードでInitiatorのFlowを実行する(2回目)
>>> start Initiator counterParty: "PartyB" , id: 1 , data: "Fuga"      

 ✅   Starting
     ✅   Requesting signature by notary service
         ✅   Requesting signature by Notary service
         ✅   Validating response from Notary service
     ✅   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: "Fuga"
      linearId:
        externalId: null
        id: "0332a33a-0c0d-4708-80e4-307b4322c68c"
      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: "758D3F22DE3E48409E9E29113AEB49EB5B5037BDDEE842AF1C24416317F426DC"
    index: 0
statesMetadata:
- ref:
    txhash: "758D3F22DE3E48409E9E29113AEB49EB5B5037BDDEE842AF1C24416317F426DC"
    index: 0
  contractStateClassName: "com.template.states.TemplateState"
  recordedTime: "2019-09-04T01:52:09.295Z"
  consumedTime: null
  status: "UNCONSUMED"
  notary: "O=Notary, L=London, C=GB"
  lockId: null
  lockUpdateTime: null
  relevancyStatus: "RELEVANT"
  constraintInfo:
    constraint:
      key: "aSq9DsNNvGhYxYyqA9wd2eduEAZ5AXWgJTbTEw3G5d2maAq8vtLE4kZHgCs5jcB1N31cx1hpsLeqG2ngSysVHqcXhbNts6SkRWDaV7xNcr6MtcbufGUchxredBb6"
totalStatesAvailable: -1
stateTypes: "UNCONSUMED"
otherResults: []
  • もう一度、内容を変更して実行してみます。
    • idを1から2に、dataをFugaからBarに変更しています
  • run vaultQueryの結果から見てわかる通り、件数が増えましたね。
    • run vaultQueryで表示しているのは、未消費のStateだけです。
    • 2回目に実行した時は、1回目と同じIDを指定していたのでStateをInputとして消費しています。そのため、2回目に確認したときに、1回目の結果が表示されなかったのです。
    • しかし、3回目に実行した時は、別のIDを指定していたので、Input無しでTransactionが発行されました。
PartyAのノードでInitiatorのFlowを実行する(3回目)
>>> start Initiator counterParty: "PartyB" , id: 2 , data: "Bar"      
 ✅   Starting
          Requesting signature by notary service
              Requesting signature by Notary service
              Validating response from Notary service
     ✅   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: "Fuga"
      linearId:
        externalId: null
        id: "0332a33a-0c0d-4708-80e4-307b4322c68c"
      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: "758D3F22DE3E48409E9E29113AEB49EB5B5037BDDEE842AF1C24416317F426DC"
    index: 0
- state:
    data: !<com.template.states.TemplateState>
      issuer: "O=PartyA, L=London, C=GB"
      counterParty: "O=PartyB, L=New York, C=US"
      id: 2
      data: "Bar"
      linearId:
        externalId: null
        id: "38efca42-3fe1-4c6d-be74-00404715f77e"
      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: "36218C75A486E5A9889F73136939E6121604DD1660A2A732AD9DDC76D9E0F491"
    index: 0
statesMetadata:
- ref:
    txhash: "758D3F22DE3E48409E9E29113AEB49EB5B5037BDDEE842AF1C24416317F426DC"
    index: 0
  contractStateClassName: "com.template.states.TemplateState"
  recordedTime: "2019-09-04T01:52:09.295Z"
  consumedTime: null
  status: "UNCONSUMED"
  notary: "O=Notary, L=London, C=GB"
  lockId: null
  lockUpdateTime: null
  relevancyStatus: "RELEVANT"
  constraintInfo:
    constraint:
      key: "aSq9DsNNvGhYxYyqA9wd2eduEAZ5AXWgJTbTEw3G5d2maAq8vtLE4kZHgCs5jcB1N31cx1hpsLeqG2ngSysVHqcXhbNts6SkRWDaV7xNcr6MtcbufGUchxredBb6"
- ref:
    txhash: "36218C75A486E5A9889F73136939E6121604DD1660A2A732AD9DDC76D9E0F491"
    index: 0
  contractStateClassName: "com.template.states.TemplateState"
  recordedTime: "2019-09-04T01:57:28.737Z"
  consumedTime: null
  status: "UNCONSUMED"
  notary: "O=Notary, L=London, C=GB"
  lockId: null
  lockUpdateTime: null
  relevancyStatus: "RELEVANT"
  constraintInfo:
    constraint:
      key: "aSq9DsNNvGhYxYyqA9wd2eduEAZ5AXWgJTbTEw3G5d2maAq8vtLE4kZHgCs5jcB1N31cx1hpsLeqG2ngSysVHqcXhbNts6SkRWDaV7xNcr6MtcbufGUchxredBb6"
totalStatesAvailable: -1
stateTypes: "UNCONSUMED"
otherResults: []

▼JDBCの実装

  • 公式ドキュメントのJDBC sessionに詳細が記載されています。
  • NodeのDatabaseのテーブル定義はNode database tablesに記載されています。
    • Schemaを作成すると、ここに追加されていきます。
  • この内容を元に、Flow.ktを拡張していきます。
    • せっかくなので、NodeのTransactionIDを取得してみます。
    • 下記をFlow.ktの末尾に追加します。
Flow.ktに追加
object CustomVaultQuery {
    @CordaService
    class Service(val services: AppServiceHub) : SingletonSerializeAsToken() {
        private companion object {
            private val log = contextLogger()
        }

        // 自ノードの全StateからTransactionIDを取得する
        fun getTxIDs(): List<String> {
            val nativeQuery = """
                select
                    TRANSACTION_ID
                from
                    VAULT_STATES
                order by
                    RECORDED_TIMESTAMP
            """
            log.info("SQL to execute: $nativeQuery")
            val session = services.jdbcSession()
            return session.prepareStatement(nativeQuery).use { prepStatement ->
                prepStatement.executeQuery().use { rs ->
                    val tx_ids: MutableList<String> = mutableListOf()
                    while (rs.next()) {
                        val tx_id = rs.getString("TRANSACTION_ID")
                        tx_ids.add(tx_id)
                    }
                    tx_ids
                }
            }
        }
    }
}
  • 次に、Flow.ktのcallに、このCordaServiceを使用するコードを追記します。
    • 特にこのTransactionIDを使うわけでもないので、標準出力するにとどめておきます。
Flow.ktのcallに記述
    @Suspendable
    override fun call() {
        // デフォルトのNotaryを指定
        val notary = serviceHub.networkMapCache.notaryIdentities[0]
        id.equals(id)

        val generalCriteria = QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED)
        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
        }

        // CordaService
        val customVaultQueryService = serviceHub.cordaService(CustomVaultQuery.Service::class.java)
        val txIDs = customVaultQueryService.getTxIDs()
        for (txID in txIDs){
            println(txID)
        }

▼試しに動かす

  • これで、JDBCも使えるようになりました。実際に動かして試してみましょう。
    • ノードが起動したままであれば、ターミナルを閉じるか、byeコマンドを実行して、ノードを停止させなす。
いつものコマンド(ビルド→ノード起動)を実行する
$ ./gradlew deployNodes
$ ./build/nodes/runnodes
  • ノードの起動まで終わったら、PartyAのノードで実行してみます。
    • おや、何も表示されませんね・・・
PartyAのノードでInitiatorのFlowを実行する(1回目)
>>> start Initiator counterParty: "PartyB" , id: 3 , data: "nobkov" 

 ✅   Starting
          Requesting signature by notary service
              Requesting signature by Notary service
              Validating response from Notary service
     ✅   Broadcasting transaction to participants
➡️   Done
Flow completed with result: kotlin.Unit
  • もう一度実行してみます。
    • それらしき文字列が表示されていますね。
    • 1回目はTransactionの発行前に検索を行っていたので、IDが無いのは当然でした。
  • run vaultQueryを見てみると、1回目のTransactionIDと合致していることがわかります。
    • reftxhashがTransactionIDです。
PartyAのノードでInitiatorのFlowを実行する(2回目)
>>> start Initiator counterParty: "PartyB" , id: 4 , data: "test"   
D52F9B1561A97BC68E6134EA20C8AC11D768C8E91E6F301DF76A18E177B75F3D

 ✅   Starting
          Requesting signature by notary service
              Requesting signature by Notary service
              Validating response from Notary service
     ✅   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: 3
      data: "nobkov"
      linearId:
        externalId: null
        id: "e5cd54ad-c992-43e9-87b3-49918ecbcce7"
      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: "D52F9B1561A97BC68E6134EA20C8AC11D768C8E91E6F301DF76A18E177B75F3D"
    index: 0
- state:
    data: !<com.template.states.TemplateState>
      issuer: "O=PartyA, L=London, C=GB"
      counterParty: "O=PartyB, L=New York, C=US"
      id: 4
      data: "test"
      linearId:
        externalId: null
        id: "d0037f1b-f085-4ce1-8022-9d89041c699c"
      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: "529B88050662CE4BD8216BCA4674FB1652D7312645CD2D1BC797DC78ABB33ED1"
    index: 0
statesMetadata:
- ref:
    txhash: "D52F9B1561A97BC68E6134EA20C8AC11D768C8E91E6F301DF76A18E177B75F3D"
    index: 0
  contractStateClassName: "com.template.states.TemplateState"
  recordedTime: "2019-09-04T02:45:05.994Z"
  consumedTime: null
  status: "UNCONSUMED"
  notary: "O=Notary, L=London, C=GB"
  lockId: null
  lockUpdateTime: null
  relevancyStatus: "RELEVANT"
  constraintInfo:
    constraint:
      key: "aSq9DsNNvGhYxYyqA9wd2eduEAZ5AXWgJTbTEw3G5d2maAq8vtLE4kZHgCs5jcB1N31cx1hpsLeqG2ngSysVHqcXhbNts6SkRWDaV7xNcr6MtcbufGUchxredBb6"
- ref:
    txhash: "529B88050662CE4BD8216BCA4674FB1652D7312645CD2D1BC797DC78ABB33ED1"
    index: 0
  contractStateClassName: "com.template.states.TemplateState"
  recordedTime: "2019-09-04T02:48:50.456Z"
  consumedTime: null
  status: "UNCONSUMED"
  notary: "O=Notary, L=London, C=GB"
  lockId: null
  lockUpdateTime: null
  relevancyStatus: "RELEVANT"
  constraintInfo:
    constraint:
      key: "aSq9DsNNvGhYxYyqA9wd2eduEAZ5AXWgJTbTEw3G5d2maAq8vtLE4kZHgCs5jcB1N31cx1hpsLeqG2ngSysVHqcXhbNts6SkRWDaV7xNcr6MtcbufGUchxredBb6"
totalStatesAvailable: -1
stateTypes: "UNCONSUMED"
otherResults: []

おわりに

今回は、Corda実装には欠かせないStateの検索方法についての記事でした。
特にJDBCでの検索方法は、SQLを記述できることもあり、柔軟な記載ができることや、任意のテーブルに対して射影ができるのが強みです。
#そういえば、ここでUPDATEとか使ったらどうなるんだろ・・・

次回は「Transactionの中身を見てみよう」とか「Transactionを遡ってみよう」とか、Transaction周りについて記事が書ければと思います。

今回はここまでです。
ありがとうございました。

4
2
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
4
2