どーも、のぶこふです。
今回は、RPCを利用して、Flowの実行(IOUの発行)を行います。
手順としては、下記の通りです。
- 公式からテンプレをクローン
- ファイル修正
- ノード起動
- RPC実行
では、一つずつ実施していきます。
0.開発環境
私の環境は以下の通りです。
- Mac High Sierra(10.13.6)
- IntelliJ(2019.1.2)
なお、修正対象のファイルはGithubに配置しているので、問題がありましたら、そちらも御覧ください。
1.テンプレからクローン
$ mkdir cordaRPCTest
$ cd cordaRPCTest/
$ git clone https://github.com/corda/cordapp-template-kotlin.git
$ cd cordapp-template-kotlin/
$ ll
total 80
-rw-r--r-- 1 nobkovskii staff 579 6 24 15:01 LICENCE
-rw-r--r-- 1 nobkovskii staff 4798 6 24 15:01 README.md
-rw-r--r-- 1 nobkovskii staff 232 6 24 15:01 TRADEMARK
-rw-r--r-- 1 nobkovskii staff 4014 6 24 15:01 build.gradle
drwxr-xr-x 4 nobkovskii staff 128 6 24 15:01 clients
drwxr-xr-x 4 nobkovskii staff 128 6 24 15:01 config
drwxr-xr-x 4 nobkovskii staff 128 6 24 15:01 contracts
drwxr-xr-x 3 nobkovskii staff 96 6 24 15:01 gradle
-rw-r--r-- 1 nobkovskii staff 65 6 24 15:01 gradle.properties
-rwxr-xr-x 1 nobkovskii staff 5296 6 24 15:01 gradlew
-rw-r--r-- 1 nobkovskii staff 2176 6 24 15:01 gradlew.bat
-rw-r--r-- 1 nobkovskii staff 57 6 24 15:01 settings.gradle
drwxr-xr-x 4 nobkovskii staff 128 6 24 15:01 workflows
2.ファイル修正
修正するファイル一覧
./workflows/src/main/kotlin/com/template/flows/Flows.kt
./contracts/src/main/kotlin/com/template/states/TemplateState.kt
./clients/src/main/kotlin/com/template/Client.kt
IntelliJでプロジェクトを開く
- IntelliJを開く
- Open >
cordapp-template-kotlin/build.gradle
を選択 > Open - Open As Project > 「Use auto-import」にチェック > OK
Stateの修正
公式の手順通り修正していきます。
下記のコピペでOKです。
修正箇所は、コード内のコメントに記述してあります。
package com.template.states
import com.template.contracts.TemplateContract
import net.corda.core.contracts.BelongsToContract
import net.corda.core.contracts.ContractState
// import net.corda.core.identity.AbstractParty // <--- [ 削除 ]
import net.corda.core.identity.Party // <--- [ 追加 ]
// *********
// * State *
// *********
@BelongsToContract(TemplateContract::class)
data class TemplateState(val value: Int,
val lender: Party,
val borrower: Party) : ContractState {
override val participants get() = listOf(lender, borrower)
} // <--- [ 引数と処理を修正 ]
Flowの修正
Stateと同様、手順通り修正します。
こちら側は、まるっと修正しています。
import co.paralleluniverse.fibers.Suspendable
import com.template.contracts.TemplateContract
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.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
@InitiatingFlow
@StartableByRPC
class TemplateFlow(val iouValue: Int,
val otherParty: Party) : FlowLogic<Unit>() {
override val progressTracker = ProgressTracker()
@Suspendable
override fun call() {
val notary = serviceHub.networkMapCache.notaryIdentities[0]
val outputState = TemplateState(iouValue, ourIdentity, otherParty)
val command = Command(TemplateContract.Commands.Action(), ourIdentity.owningKey)
val txBuilder = TransactionBuilder(notary = notary)
.addOutputState(outputState, TemplateContract.ID)
.addCommand(command)
val signedTx = serviceHub.signInitialTransaction(txBuilder)
val otherPartySession = initiateFlow(otherParty)
subFlow(FinalityFlow(signedTx, otherPartySession))
}
}
@InitiatedBy(TemplateFlow::class)
class TemplateFlowResponder(private val otherPartySession: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val signTxFlow = object : SignTransactionFlow(otherPartySession){
override fun checkTransaction(stx: SignedTransaction) = requireThat{
val output = stx.tx.outputs.single().data
val mys = output as TemplateState
}
}
val expectedTxid = subFlow(signTxFlow).id
subFlow(ReceiveFinalityFlow(otherPartySession,expectedTxid))
}
}
Clientの修正
このファイルでRPCで呼び出すFlowを指定します。(今回は固定Flow)
package com.template
import TemplateFlow // <--- [ 追加 ]
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.messaging.startTrackedFlow // <--- [ 追加 ]
import net.corda.core.utilities.NetworkHostAndPort.Companion.parse
import net.corda.core.utilities.loggerFor
/**
* Connects to a Corda node via RPC and performs RPC operations on the node.
*
* The RPC connection is configured using command line arguments.
*/
fun main(args: Array<String>) = Client().main(args)
private class Client {
companion object {
val logger = loggerFor<Client>()
}
fun main(args: Array<String>) {
// Create an RPC connection to the node.
require(args.size == 3) { "Usage: Client <node address> <rpc username> <rpc password>" }
val nodeAddress = parse(args[0])
val rpcUsername = args[1]
val rpcPassword = args[2]
val client = CordaRPCClient(nodeAddress)
val proxy = client.start(rpcUsername, rpcPassword).proxy
// Interact with the node.
// For example, here we print the nodes on the network.
val nodes = proxy.networkMapSnapshot()
logger.info("{}", nodes)
// PartyA -> Value 1 -> PartyB
proxy.startTrackedFlow(::TemplateFlow,1,nodes[2].legalIdentities[0]) // <--- [ 追加 ]
}
}
3.ノード起動
$ pwd
/cordaRPCTest/cordapp-template-kotlin
$ ./gradlew clean deployNodes
〜略〜
BUILD SUCCESSFUL in 37s
18 actionable tasks: 14 executed, 4 up-to-date
$ ./build/nodes/runnodes
ノード確認
開かれたタブの中で、PartyAのタブを開きます。
画面上部あたりのログか、Welcome to the Corda interactive shell.
と書かれているすぐ上あたりを見ると、どのノードなのかがわかるかと思います。
bash -c 'cd "/cordaRPCTest/cordapp-template-kotlin/build/nodes/PartyA" ;
〜略〜
______ __
/ ____/ _________/ /___ _
/ / __ / ___/ __ / __ `/ People used to laugh at me when I said I wanted
/ /___ /_/ / / / /_/ / /_/ / to be a comedian. Well they're not laughing now!
\____/ /_/ \__,_/\__,_/
--- Corda Open Source 4.0 (503a2ff) -------------------------------------------------------------
〜略〜
Node for "PartyA" started up and registered in 26.03 sec
Welcome to the Corda interactive shell.
Useful commands include 'help' to see what is available, and 'bye' to shut down the node.
// Flow一覧を表示する
Mon Jun 24 16:02:46 JST 2019>>> flow list
TemplateFlow
net.corda.core.flows.ContractUpgradeFlow$Authorise
net.corda.core.flows.ContractUpgradeFlow$Deauthorise
net.corda.core.flows.ContractUpgradeFlow$Initiate
// ネットワークに登録されているノード一覧を表示する
Mon Jun 24 16:03:16 JST 2019>>> run networkMapSnapshot
- addresses:
- "localhost:10005"
legalIdentitiesAndCerts:
- "O=PartyA, L=London, C=GB"
platformVersion: 4
serial: 1561359724664
- addresses:
- "localhost:10002"
legalIdentitiesAndCerts:
- "O=Notary, L=London, C=GB"
platformVersion: 4
serial: 1561359724945
- addresses:
- "localhost:10008"
legalIdentitiesAndCerts:
- "O=PartyB, L=New York, C=US"
platformVersion: 4
serial: 1561359722697
// 保持しているStateを確認する
Mon Jun 24 16:03:23 JST 2019>>> run vaultQuery contractStateType: com.template.states.TemplateState
states: []
statesMetadata: []
totalStatesAvailable: -1
stateTypes: "UNCONSUMED"
otherResults: []
無事、ノードが起動されていることが確認できました。
うまくノードが起動できていなかった場合は、再度デプロイ→起動を試してみてください
4.RPC実行
RPCは、最初のターミナルから実行します。
$ ./gradlew runTemplateClient
> Task :clients:runTemplateClient
I 16:13:40 1 RPCClient.logElapsedTime - Startup took 1143 msec
I 16:13:40 1 Client.main - [NodeInfo(addresses=[localhost:10005], legalIdentitiesAndCerts=[O=PartyA, L=London, C=GB], platformVersion=4, serial=1561359724664), NodeInfo(addresses=[localhost:10002], legalIdentitiesAndCerts=[O=Notary, L=London, C=GB], platformVersion=4, serial=1561359724945), NodeInfo(addresses=[localhost:10008], legalIdentitiesAndCerts=[O=PartyB, L=New York, C=US], platformVersion=4, serial=1561359722697)]
BUILD SUCCESSFUL in 7s
14 actionable tasks: 7 executed, 7 up-to-date
再びPartyAのタブに戻り、Stateを確認します。
Mon Jun 24 16:15:21 JST 2019>>> run vaultQuery contractStateType: com.template.states.TemplateState
states:
- state:
data: !<com.template.states.TemplateState>
value: 1
lender: "O=PartyA, L=London, C=GB"
borrower: "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: "73E84ECA842336D73E9A648F6900049545A12033D92DDCB59511AAA9E3597E79"
index: 0
statesMetadata:
- ref:
txhash: "73E84ECA842336D73E9A648F6900049545A12033D92DDCB59511AAA9E3597E79"
index: 0
contractStateClassName: "com.template.states.TemplateState"
recordedTime: "2019-06-24T07:13:43.489Z"
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: []
PartyAからPartyBへNotaryを介してIOU(Value:1)が発行されているのがわかるかと思います。
↓の部分
states:
- state:
data: !<com.template.states.TemplateState>
value: 1
lender: "O=PartyA, L=London, C=GB"
borrower: "O=PartyB, L=New York, C=US"
contract: "com.template.contracts.TemplateContract"
notary: "O=Notary, L=London, C=GB"
RPC実行で使用したrunTemplateClient
のtaskは、/clients/build.gradle
にあります。
task runTemplateClient(type: JavaExec, dependsOn: assemble) {
classpath = sourceSets.main.runtimeClasspath
main = 'com.template.ClientKt'
args 'localhost:10006', 'user1', 'test'
}
5.おわりに
PartyAのタブからでもFlowは実行できますが、このようにRPCを使ってFlowを実行することができ、より実用的?な内容だったかと思います。
まだまだ知らないことが多いです。
先日までドキュメントを片っ端から読んでまとめていて、改めてサンプルを実行したら「こういう仕組みだったのか」と、自分の浅はかさを実感することが出来ました。
ドキュメント読むの大事。
とはいえ、今回修正したファイル以外だと、いじるのはコントラクトのファイルくらいだと思うので、これらをどう組み合わせていくかー?に頭を悩ませていきそうです。
今回は以上です、ありがとうございました。