nativechainをKotlinで実装する
nativechainとは
nativechainはブロックチェーンの基本的な概念を200行のjavascriptで実装したものです。 https://github.com/lhartikk/naivechain
nativechainは次の実装および制約を持っています
- ノードをコントロールするHttpインタフェース
- Websocketを使ったP2Pノード
- データ(ブロックチェーン)を永続化していない
- Proof of WorkやProof of Stakeはない。無条件でチェーンにブロックを追加できる
Kotlinで実装する
ブロックチェーンの基本的な概念を学ぶには実装するのが手っ取り早いよね〜という事でnativechainを写経してもいいんだけど、どうせならKotlinで書いてみたいよね〜という事でKotlinで実装しました。
GitHub - sys1yagi/nativechain-kotlin: https://github.com/lhartikk/naivechain by kotlin
ブロックの実装
ブロックのデータ構造は極めてシンプルなのでdata classでシュッ
data class Block( val index: Long, val previousHash: String, val timestamp: Long, val data: String, val hash: String )
ハッシュはSHA-256を使っている。JavaだとMessageDigestでやれるので楽。バイト配列をhex stringに変換するのがKotlinだと少し面倒だった。
private fun calculateHash( index: Long, previousHash: String, timestamp: Long, data: String): String { return MessageDigest.getInstance("SHA-256") .digest("$index$previousHash$timestamp$data".toByteArray(StandardCharsets.UTF_8)) .joinToString(separator = "") { "%02X".format(it) } }
Httpサーバの実装
なにを使うか迷ったけどJetBrains製のktorを使うことにした。
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" compile "io.ktor:ktor-server-core:$ktor_version" compile "io.ktor:ktor-server-netty:$ktor_version" compile "io.ktor:ktor-websockets:$ktor_version" compile "ch.qos.logback:logback-classic:1.2.1"
次のようにシュッとサーバを立てられる。
embeddedServer(Netty, httpPort) { routing { get("/blocks") { val blockchain = jsonConverter.toJson(nativeChain.blockchain) call.respondText(blockchain, ContentType.Application.Json) } post("/mineBlock") { val data = call.request.receiveContent().inputStream().bufferedReader().readText() val mineBlock = jsonConverter.fromJson(data, MineBlock::class.java) nativeChain.addBlock(nativeChain.generateNextBlock(mineBlock.data)) webSocketServer.broadcastLatestMessage() call.respond(HttpStatusCode.OK) } get("/peers") { call.respondText(webSocketServer.sockets().joinToString(separator = "\n") { it.peer() }) } post("/addPeer") { val data = call.request.receiveContent().inputStream().bufferedReader().readText() val peer = jsonConverter.fromJson(data, Peer::class.java) webSocketServer.connectToPeers(listOf(peer)) call.respond(HttpStatusCode.OK) } } }.start(wait = true)
WebSocketサーバの実装
WebSocketサーバもktorを利用。embeddedServer関数でガチャガチャ書いてると動くので便利。接続が確立されるとWebSocketSessionが渡ってくる。WebSocketSessionはincoming(ReceiveChannel)とoutgoing(SendChannel)を持っている。コルーチンなので待機とかその辺がいい感じでいい。
embeddedServer(Netty, port) { install(DefaultHeaders) install(CallLogging) install(WebSockets) { pingPeriod = Duration.ofMinutes(1) } routing { webSocket { logger.debug("receive websocket connection") val socket = KtorWebSocket(this) initConnection(socket) } } }.start(wait = false)
WebSocketクライアントの実装
なんとktorにはWebSocketクライアントの実装がない(ぽい)。ので、P2P的な実装ができない。うへー。そこでTyrusを使うことにした。
compile "org.glassfish.tyrus.bundles:tyrus-standalone-client-jdk:$tyrusVersion" compile "org.glassfish.tyrus:tyrus-container-grizzly-server:$tyrusVersion"
TyrusはJavaライブラリなのでわりと古めかしいインタフェースを持っている。後述するとおりサーバとクライアントで異なるWebSocketのスタックを使っているのでコネクション全体を管理するために抽象化が必要となる。ktorのWebSocketSessionはChannelでI/Oするので、こちらもChannelを持つ形で実装をした。
nativechain-kotlin/TryusWebSocket.kt at master · sys1yagi/nativechain-kotlin · GitHub
WebSocketセッションの抽象化
WebSocketサーバで接続を受けると、io.ktor.websocket.WebSocketSession
でやりとりを行う。WebSocketクライアントで接続をするとjavax.websocket.Session
でやりとりを行う。nativechainはブロックの更新があった時などにネットワーク全体にブロードキャストを行う。このためサーバで受けた接続とクライアントとして張った接続の両方を同じように取り扱いたい。nativechainというかjavascriptはWebSocketのサーバクライアントが両方同じように作れるので複雑なことはしてないのだがktorがWebSocketサーバしか持たないのでややこしくなってしまっている。サーバもTyrusにしようか考えたけどなんかアノテーションベースでDIを前提とした設計なので諦めた。
とりあえずWebSocketInterfaceを作って、必要な関数を宣言し、
interface WebSocketInterface { fun send(message: String) fun receiveChannel(): ReceiveChannel<String> fun peer(): String }
KtorWebSocketとTryusWebSocketを実装した。ktorでWebSocketクライアント実装して( ゚д゚)ホスィ…。
動かす
あとは二個サーバ起動してcurlとかでどっちかを叩くとシュッとなる
./gradlew jar java -jar build/libs/nativechain-kotlin-1.0.jar 3001 6001 java -jar build/libs/nativechain-kotlin-1.0.jar 3002 6002 ws://localhost:6001 curl -H "Content-type:application/json" --data '{"data" : "Some data to the first block"}' http://localhost:3001/mineBlock
おわり
ブロックチェーンの仕組みはなんとなくわかった。ただProof of Workとかはやってないしその他の様々なことにはまだ触れてないので実用的な何かを作るのは当然まだ無理ぽい気配。けど面白かった。分散データベースとヒステリシス署名の組み合わせだというのをどこかで見たけどたしかにその通りだとおもった。改ざんを心配しなくていいことで得られることって色々ありそうだけど具体的にここに使えそうだってのがパッと思いつかないのでもっと学習が必要だ。WebSocket周りはなんか統一したいけどめんどくさいのでまぁいいやという気持ち。
とりあえず今はこれを読んでいる。
で、この辺も読もうと考えている。