どーも、のぶこふです。
Cordaさんともそこそこお友達になれた気がする今日このごろです。
今回は、VaultQueryを使ったり、H2を使ったりします。
VaultはStateのHEADが保存されています。
つまり、最新のStateが取得できるということです。
cordapp-template-kotlinを使い、説明していきます。
環境
今回は、Windows10でお送りします。
- Windows10
- core-i7
- 64bit
- RAM:8GB
- Cordaを動かすための環境は整っているものとします。
- Java8
- IntelliJ
- Gradle
- 詳細はコチラをご参照ください
テンプレートインストール
コマンドプロンプトを開き、下記コマンドを実行します
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)をもたせることができます。
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が必須
-
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を検索し、取得します。
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))
}
}
検索処理をしているのは、ここ(↓)です。
// 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
を記述します。
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コンソール起動
下記のエラーが発生していたら、IPアドレス(172.19.32.186
)をlocalhost
に書き換えます
「接続」ボタンを押下して、アクセスします
TEMPLATE_STATE テーブルが作成されています。
※TEMPLATE_STATE を選択すると、自動的にSELECT文が作成されます
実行しても、まだデータはありません
実行
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
>>> 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:
おわり
あまりVaultQueryについて説明がなかったので、次回はもう少し詳しく書きます。
今回はここまでです。
ありがとうございました。