visible true

技術的なメモを書く

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
}

KtorWebSocketTryusWebSocketを実装した。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周りはなんか統一したいけどめんどくさいのでまぁいいやという気持ち。

とりあえず今はこれを読んでいる。

で、この辺も読もうと考えている。