【Corda】StateStatusを指定してVaultQueryで検索しよう



## 1.はじめに

どーも、のぶこふです。  
最近、Corda記事への反応も見かけるので、CordaのTipsも載せていきます。

今回は、VaultQueryで検索条件を付与した時に、StateStatusがUNCONSUMEDになってしまう問題の解決方法です。（https://qiita.com/nobkovskii/items/705f80632545060e167e#vaultquery%E3%81%AE%E5%AE%9F%E8%A3%85）
環境構築までは、以前のCordaの記事を踏襲していきます。

サクッと知りたいかたは「[3.本題](https://qiita.com/nobkovskii/items/d966eca01992bf86551b#3%E6%9C%AC%E9%A1%8C)」まで飛んでください

▼以前の記事（「【Corda】Stateの検索方法〜VaultQueryの使用方法と、JDBCを利用したSQLの発行方法について〜」）
https://qiita.com/nobkovskii/items/705f80632545060e167e

### 1.1.想定読者

* Cordaで、StateStatusがCONSUMEDやALLで、検索条件も付与したい人

### 1.2.ゴール

* Cordaで、StateStatusを設定しつつ、検索条件も付与できるようになる

### 1.3.環境

* Corda OSS
* Windows10

## 2.つくる

### 2.1.サンプルダウンロード

* Kotlinテンプレートをダウンロードし、使用していきます

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

### 2.2.対象ファイル

* 修正するファイルは下記の2つと、新規作成が1つです
  * 【新規】contracts/src/main/kotlin/com/template/schemas/TemplateSchema.kt
  * 【修正】contracts/src/main/kotlin/com/template/states/TemplateState.kt
  * 【修正】workflows/src/main/kotlin/com/template/flows/Flows.kt

### 2.3.TemplateSchema.kt

* コピペでOKです

```kotlin
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,"","","")
    }
}
```

### 2.4.TemplateState.kt

* コピペでOKです

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

### 2.5.Flows.kt

* コピペでOKです


```kotlin
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() {
        val notary = serviceHub.networkMapCache.notaryIdentities[0]

        // StateStatusがすべて、IDが一致する
        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
        }
        
        println(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))
    }
}
```

### 2.6.動かしてみる

* ここまでで一旦動かしてみましょう。

```
# gradlew.bat deployNodes

～もろもろ～

BUILD SUCCESSFUL in 1m 10s
18 actionable tasks: 16 executed, 2 up-to-date

#  build\nodes\runnodes.bat
```

* Txをいくつか発行します

```
> start Initiator counterParty: "PartyB" , id: 1 , data: "Hoge"
> start Initiator counterParty: "PartyB" , id: 1 , data: "Fuga"
> start Initiator counterParty: "PartyB" , id: 1 , data: "Bar"
```

* 3つ目のTxを発行した時にお気づきだと思いますが、消費済みとなっている「data: "Hoge"」の情報がログに出てきていないかと思います。

## 3.本題

* お待たせしました。それでは、消費済みも取得できるように修正していきます。

```diff
        // StateStatusがすべて、IDが一致する
-        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)
+            val criteria = q1.withStatus(Vault.StateStatus.ALL)
            serviceHub.vaultService.queryBy<TemplateState>(criteria).states
        }
```

* 肝は「withStatus(Vault.StateStatus.status)」と設定することです。
  * Cordaの公式を触っていると、どうしても元の記述方法になりがちですが、APIにちゃんと定義されていました。

### 3.1.動かしてみる

* では、確認してみましょう。実行するコマンドは、上記と同じものです。

```
# gradlew.bat deployNodes

～もろもろ～

BUILD SUCCESSFUL in 1m 10s
18 actionable tasks: 16 executed, 2 up-to-date

#  build\nodes\runnodes.bat

> start Initiator counterParty: "PartyB" , id: 1 , data: "Hoge"
> start Initiator counterParty: "PartyB" , id: 1 , data: "Fuga"
> start Initiator counterParty: "PartyB" , id: 1 , data: "Bar"
```

* いかがでしたでしょうか？3つ目のTxを発行した時に、「data: "Hoge"」と「data: "Fuga"」の両方が表示されたでしょうか。


## 4.おわりに

今回の例のように、公式をそのまま進めていくと「あれ？」というポイントがいくつか出てきたりします。
やはり、自分の手で動かしてみて、確認するのが大事だと思います。


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