本当は怖いHPC

HPC屋の趣味&実益ブログ

「ビットコインはどのようにして動いているのか? 」を読んだ

大石哲之 著「ビットコインはどのようにして動いているのか? 」を読みました

ビットコインはどのようにして動いているのか? ビザンチン将軍問題、ハッシュ関数、ブロックチェーン、PoWプロトコル

計算機科学の素養のない人にもわかるようにビットコイン(と仮想通貨)の原理が説明されています.すでに計算機科学の基礎知識(P2Pハッシュ関数公開鍵暗号方式)があれば,容易に読み進められ,軽い読み物として良書だと思います.

計算機科学の素養のない人が読んで「わかりやすい」かというと…微妙なところですね.ハッシュ関数公開鍵暗号は,説明を受けても直感的にピンと来にくいところがあります.ハッシュ関数が一方向関数であることの意味,公開鍵暗号の公開鍵と秘密鍵などは,あくまで数学的な概念であって現実のモノにたとえて理解するのは厳しいものがあります.

技術にフォーカスして書かれているので,社会的/経済的影響について考えるには別の書籍を読む必要があるでしょう.そのための基礎としても有用です.技術がわかっていなければ(例えば,「Bitcoinの埋蔵量に上限があるとは具体的にどういうことか」),議論もできません.ただし,ビットコイン推進派の著者が書いているので,やや見方が偏っている気はします(全体的に問題点とされるところへの言及が軽い).

考えたこと

この本を読んで,ビットコインについて幾つかのことを考えました.

ビットコインについて話をする際は,以下の3つの論点を切り離さないと混乱してしまいます.

  • Bitcoin」という特定の仮想通貨が普及するか? → Bitcoinが広まるか? →投機対象としてはBitcoinはどうか?
  • 亜種も含めて,仮想通貨全般(さらに,それらの技術的/経済的/政治的側面それぞれ)はどうか?
  • ブロックチェーンなどの,要素技術の応用可能性はどうか?

最初に述べておくと,Bitcoinの取引価格の上昇・下降に一喜一憂するのは単なる投機で,株・FX・先物取引と何も変わりません.この側面は無視します.

本書を読み終わった現在,自分の感想としては

  • Bitcoin」という特定の通貨については,いずれ終焉するだろう(限界が見えている)
  • 仮想通貨全般については,特定の分野で使われる仮想通貨(例えば銀行間決済)が開発され普及するだろう.一般的に広く使われるものが登場するかどうかはわからない.
  • ブロックチェーンについては,現在のBitcoinの制限をもたらしている技術的制約が解消されれば,様々な応用範囲が広がっていると思う.

以下,もう少し議論を書いてみます.(警告:正確な知識のためには,きちんとした文献を参照してください.以下の文章は,私の現時点での理解を書いたものです)

仮想通貨の具体的インスタンスとしてのBitcoinが行き詰まるだろうと予想するのは,主に3つの理由からです.

1つは,スケーラビリティが無いこと.主に4つの指標からの性能が重要となるはずです.

  • レイテンシ(1つの取引が完結するまでの時間)
  • スループット(単位時間あたりのトランザクション量)
  • 記憶容量(ブロックチェーンのデータの総量)
  • 取引所の個数(接続するピアの個数)

スケーラビリティ問題については,現時点での私の知識は非常に限られているので,詳細な議論は避け,[3-5]などに譲ります.

現時点で思うのは,Bitcoinは現在の糞すぎる国際送金インフラに対するプレッシャーにはなり得ても,本流にはならないのではないか,ということです.要はVISAとPaypalに勝てるか?ということ.そして通常の銀行間決済も本腰を入れて競争してくるでしょうから,Bitcoinについてはなかなか厳しい戦いになるだろうことは容易に想像がつきます

現時点において,「Bitcoinなら国際送金が手数料無料で即時」というのは,単にBitcoinが国家によって通貨として認められていない事によって法律の抜け穴を通っているだけです.Bitcoinが広く認められていくなら,Paypalが直面しているのと同じ法的な規制に束縛されます.さらにBitcoinが枯渇した時,果たして手数料がどれくらいになるか?という問題となります.結局,決済とはマイニングと同等の計算力が必要なのですから,即時決済したかったら高い手数料払ってね,手数料ケチりたいなら時間かかるよ,となります.さらに怪しい取引所に対する消費者保護も重要ですから,そこまで安価で便利な選択肢になるとは思えません.

また,[2]で大石さん自身が書かれているように,現在の仕様では未承認段階の取引は取り消しができるそうですから,取引が確認したのを見極めたかったら10分待ち,さらにグローバルな支払いを自分の目で確認する必要があるということです.取引所を信用するならそんなことは不要なわけですが,それは長い目で見れば取引所の手数料増加に繋がるし,結局のところ現在我々が銀行を信頼しているのと何も変わりません.

Bitcoinのスケーラビリティに関する日本語の情報を見ている限りでは,「解決策」として「データ量を25%まで圧縮」とか,「容量を8倍」とかいう数字が出てきているのですが,原理的に2桁くらいキャパが増えるようなことをしていかないと焼け石に水じゃないかなあ,と直感的に感じます.私がHPCをやっている人間だからかもしれませんが.

2点目.Bitcoinには,発行上限が定められています(詳細は[1]を参照).そして,次の項で述べるように,少なくとも現在は,Bitcoinの開発者=それなりの量の保有者であるということです.空想ですが,現代の経済が金本位制に戻った時,経済はどうなるのでしょうね.もし仮にBitcoinが主要通貨として流通するようになったとすると,現代社会(良くも悪くも)を支えている経済を,「Bitcoin本位制」を軸にもう一回全部考えなおさなくちゃいけなくなります.

3点目,すでに述べましたが,開発者=Bitcoin大量保有者なので,技術的な判断として「Bitcoinの発行総量を増やす」という決断ができるのかどうか疑問です.これはマイク・ハーンの離脱宣言の中にも書かれていますが,結局のところ,「国家による管理」が「コードの開発者による管理」に変わっただけ,というように見えます.Bitcoinの意思決定は計算力による多数決だそうですが,現代の国家だって(いろいろあれど)多数決で決まっているわけで,それが現状の国家より良い物になる気はしません.むしろ,計算能力による多数決って,一昔前の「選挙権は税金を○○円以上納めた男子」みたいな選挙制度と同じなんじゃないか?と思います.

開発者の方々疑うわけではありませんが,結局のところ皆人間ですから,インセンティブに従って動くでしょう.ここでのインセンティブとは,「Bitcoinの総量が増えれば価格は下落する」という当たり前の話です(実際に価格が下落するかどうかではなく,発行を打ち止めにした場合と比較して目減りする).現状での開発は主に技術的興味にドライブされた有志の個人が行っていますが,これが何かの拍子に組織になったと考えてみましょう.組織が,自らの財産を毀損してまでBitcoinの仕様を変更して発行を再開するという決断をするどうかはわかりません.仕様通り打ち止めになる可能性が高いのではないかと予想します.

私がそれなりのスジのそれなりの人間だったら,Bitcoinの開発者の何人かに膨大な報酬(億単位)を秘密裏に支払い,Bitcoinの総量を増加させないような方向へ開発のdirectionを誘導するように圧力をかけるでしょう.というかすでにあってもおかしくありません.

ひとまず結論

いろいろ否定的なことを書きましたが,仮想通貨という技術自体はこれからも発展していくだろうと思いますし,決済の1つの手段として国家管理の通貨と併存していくだろうと思います.上で言及したような技術的な制約が解決されるとしても,現在のBitcoinからすべてスムーズにmigrateできるとはちょっと考えにくいですし,もし移行が発生するなら大騒ぎになるでしょう.株の上場廃止のようなものです.

個人的にはBitcoinよりもblockchainの方に興味があります.全体として面白く重要な技術であることは間違いないので,これからも勉強していきたいと思っています.

次はオライリーのBlockchain本を読む予定です.

Blockchain: Blueprint for a New Economy

[1] ビットコインの発行上限と、採掘量が減っていく仕組みとは具体的にどういうことか?

[2] マイク・ハーンのビットコイン失敗宣言は本当か?価格は今後どうなるのか?【Weekly レポート017】 | ビットコイン&ブロックチェーン研究所

[3] ビットコイン、スケーラビリティ問題への対応は急を要する | BTCN|ビットコインニュース

[4] ビットコインのスケーラビリティと持続性に関するメモ | 日本デジタルマネー協会 / ビットコイン / Bitcoin

[5] Bitcoin のスケーラビリティ問題について - Qiita

DAGベース分散処理プラットフォームOnyxの紹介

この記事は Clojure Advent Calendar 2015 - Qiita の12月22日の記事です(微妙に遅刻しましたすんません)

データを関数で変換しながら次々に処理していくことを考えましょう。関数を頂点、頂点同士の処理の順番と依存関係を辺とするグラフ構造を用いて処理を表現することができることがわかるでしょうか。古き良きフローチャートと似たものです。処理がループすることを許さないとすると、このグラフ構造は有向非循環グラフ、つまりDirected Acyclic Graph (DAG)となることがわかります。

例えば、下の3つの関数を連続して呼び出すコードは

(defn f [x] 
  ;; なんとかかんとか)

(defn g [x]
  ;; なんとかかんとか)

(defn h [x]
  ;; なんとかかんとか)

(def call [x]
  (h (g (f x))))

のようになります。あるいは、スレッディングマクロを使うとこのように書くことも出来ます。

(def call [x]
  (-> x f g h))

スレッディングマクロの威力は絶大ですね。これはもうほとんどDAGに近い記法です。

ところで、処理の内容をDAGとして表現することで何が嬉しいのでしょうか?1つは分散/並列処理がしやすいことにあります。処理同士の依存関係をプログラマが明示的に記述してやることにより、システムが処理を分散配置しやすくなります。また、DAGという制約の多い記法で処理を記述することにより、分散処理の時に障害となる処理を暗黙のうちに使わないようにシステムを設計することが出来ます。

そのような分散処理システムは多数提案されています。有名なものはStormやSparkでしょうか。Hadoopを初めとするMap/Reduce処理系も、DAGの特殊な場合と言うことが出来るでしょう。

今回は、Pure Clojureで書かれたDAGベース分散処理プラットフォームOnyxを紹介します。非常に簡単なサンプルプログラムを紹介して、Onyxにおいてどのようにプログラムを作成するかを述べます。

処理する内容は、入力されたデータ列の各要素に対して、ただ整数インクリメントをするというだけのものです。また、大規模に分散させるのではなく、最低限のプロセス数で処理させていますので、分散処理プラットフォームの紹介としては物足りないかも知れません。core.asyncのchannelを入力・出力として、入力用のchannelからデータが取り出され、インクリメント処理が適用され、その後出力用のchannelに書き出されます。処理内容に比べて非常に長いソースコードですが、逆に処理内容が増えてもこれ以上の大量のコードを追加する必要なくスケーラブルに分散処理を行うことが出来ます。

関数名などをキーワードで指定しているのは、定義の順番に左右されないようにという理由です。全体的に回りくどいと感じられるかも知れませんが、分散処理に付随するいろいろな面倒ごとを吸収するためには必要なおまじないだと思ってください。

さて、普通にlein newで生成したプロジェクトに対して、この記事の末尾にコードを掲載しました。少し長いですがおつきあいください。

;; project.clj
(defproject onyx-samples "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :main onyx-samples.sample1-core-async
  :dependencies [[org.clojure/clojure "1.7.0"]
                 [org.clojure/java.jdbc "0.4.2"]
                 [org.xerial/sqlite-jdbc "3.8.11.2"]
                 [org.clojure/core.async "0.1.346.0-17112a-alpha"]
                 [org.onyxplatform/onyx "0.8.2"]
                 [org.onyxplatform/onyx-sql "0.8.2.1"]
                 [com.stuartsierra/component "0.2.3"]])
;; onyx-samples/src/onyx-samples/sample1_core_async.clj
(ns onyx-samples.sample1-core-async
  (:require [clojure.core.async :refer [chan >! >!! <! <!! go close!]]
            [onyx.plugin.core-async :refer [take-segments!]]
            [com.stuartsierra.component :as component]
            [clojure.pprint :as pp]
            [onyx.api]))

;; A very simple Onyx test code that takes segments from a channel
;; and just move them to another channel

;; ここでは、入力と出力は、core.asyncのchannelを利用します。
(def in-ch (chan 500))
(def out-ch (chan 500))

;; システムの状態管理にcom.stuartsierra.componentを使用します。
(def system nil)

;; メインの定義。今回は、:in というタスクでchannelからデータを読み込み、
;; :incというタスクで処理をして、それを:outというタスクで別のchannelに出力します。
(def workflow
  [[:in :inc]
   [:inc :out]])

;; 起動するpeer(プロセスのようなもの)を指定します。
;; 少なくともworkflowに指定されたタスクの数以上なくてはいけません。
;; なので、上記で定義されたworkflow
(def n-peers (->> workflow (mapcat identity) set count))

;; とりあえずは重要でないパラメーター
(def batch-size 10)
(def batch-timeout 50)

;; それぞれのタスクについて、詳細を定義するデータ構造です。
;; Onyxではcatalogと呼ばれます。
(def catalog
  ;; :inタスク。ここでは標準で用意されているcore-asyncプラグインを使います。
  ;; channelの名前等の必要な情報は、後述のlifecycleで定義します。
  [{:onyx/name :in                            
    :onyx/type :input
    :onyx/plugin :onyx.plugin.core-async/input
    :onyx/medium :core.async
    :onyx/max-peers 1
    :onyx/batch-timeout batch-timeout
    :onyx/batch-size batch-size
    :onyx/doc "Reads segments from a core.async channel"}

   ;; 今回の処理のメインとなる:incタスクです。
   ;; my-incという関数を指定しています。
   {:onyx/name :inc
    :onyx/type :function
    :onyx/fn :onyx-samples.sample1-core-async/my-inc
    :onyx/batch-size batch-size}

   ;; :inタスクと同様の、:outタスクを指定します。
   {:onyx/name :out
    :onyx/type :output
    :onyx/plugin :onyx.plugin.core-async/output
    :onyx/medium :core.async
    :onyx/max-peers 1
    :onyx/batch-timeout batch-timeout
    :onyx/batch-size batch-size
    :onyx/doc "Writes segments to a core.async channel"}
   ])

;; :in用のchannelを設定するlifecycle関数。詳細は後述
(defn inject-in-ch [event lifecycle]
  (println "inject-in-ch is called.")
  {:core.async/chan in-ch})

;; :out用のchannelを設定するlifecycle関数
(defn inject-out-ch [event lifecycle]
  (println "inject-out-ch is called.")
  {:core.async/chan out-ch})

(def in-calls
  {:lifecycle/before-task-start inject-in-ch})

(def out-calls
  {:lifecycle/before-task-start inject-out-ch})

;; lifecycleとは、タスク起動の各段階で起動できる関数です。
;; channel等のパラメーターを設定したり、タスク起動の前にリソースを獲得したりといった
;; ことが出来ます。
(def lifecycles
  [
   ;; :in用のchannelを設定する関数をfully-qualified keywordで指定します。
   {:lifecycle/task :in
    :lifecycle/calls :onyx-samples.sample1-core-async/in-calls}
   
   ;; core-asyncプラグインで指定されているlifecycle関数
   {:lifecycle/task :in
    :lifecycle/calls :onyx.plugin.core-async/reader-calls}
   
   ;; :out用のchannelを設定する関数をfully-qualified keywordで指定します。
   {:lifecycle/task :out
    :lifecycle/calls :onyx-samples.sample1-core-async/out-calls}
   
   ;; core-asyncプラグインで指定されているlifecycle関数
   {:lifecycle/task :out
    :lifecycle/calls :onyx.plugin.core-async/writer-calls}
   ])

(def onyx-id (java.util.UUID/randomUUID))

;; peerやzookeeperを起動するパラメーターです
(def env-config
  {:zookeeper/address "127.0.0.1:2188"
   :zookeeper/server? true
   :zookeeper.server/port 2188
   :onyx/id onyx-id})

(def peer-config
  {:zookeeper/address "127.0.0.1:2188"
   :onyx.peer/job-scheduler :onyx.job-scheduler/balanced
   :onyx.messaging/impl :aeron
   :onyx.messaging/peer-port 40200
   :onyx.messaging/bind-addr "localhost"
   :onyx/id onyx-id})

;; componentでプログラムの初期化処理・終了処理・状態を管理します。
(defrecord OnyxDevEnv [n-peers]
  component/Lifecycle

  ;; start関数でpeerを起動します。
  (start [component]
    (println "Starting Onyx development environment")
    (let [onyx-id (java.util.UUID/randomUUID)
          env (onyx.api/start-env env-config)
          peer-group (onyx.api/start-peer-group peer-config)
          peers (onyx.api/start-peers n-peers peer-group)]
      (assoc component :env env :peer-group peer-group
             :peers peers :onyx-id onyx-id)))

  ;; stopでpeerをシャットダウンします
  (stop [component]
    (println "Stopping Onyx development environment")
    (doseq [v-peer (:peers component)]
      (onyx.api/shutdown-peer v-peer))
    (onyx.api/shutdown-peer-group (:peer-group component))
    (onyx.api/shutdown-env (:env component))
    (assoc component :env nil :peer-group nil :peers nil)))

;; 処理する関数の本体です。
;; 渡されるデータ構造は、segmentと呼ばれ、要するにClojureのmapです。
;; map以外のデータ構造は許可されていません。
(defn my-inc [segment]
  (update-in segment [:n] inc))

;; componentのインスタンスを #'system というvarに束縛します。
(defn init []
  (alter-var-root #'system (constantly (map->OnyxDevEnv {:n-peers n-peers}))))

(defn start []
  (when (nil? system)
    (init))
  (alter-var-root #'system (fn [s] (component/start s)))
  nil)

(defn stop []
  (alter-var-root #'system (fn [s] (when s (component/stop s))))
  nil)

;; 入力用のchannelにデータを流し込んでから、jobをsubmitします。
(defn submit-jobs []
  (dotimes [i 20]
    (let [segment {:n i :greeting (str "Hello" i)}]
      (>!! in-ch segment)))
  (>!! in-ch :done)
  (let [job {:workflow workflow
             :catalog catalog
             :lifecycles lifecycles
             :task-scheduler :onyx.task-scheduler/balanced}]
    (println "Submitting")
    (onyx.api/submit-job peer-config job)))

;; 一連の関数を起動するmain関数です
(defn -main [& args]
  (init)
  (start)
  (submit-jobs)
  (pp/pprint (take-segments! out-ch))
  (stop)
  (shutdown-agents))

そして、以下が実行した結果です。個々のデータ(segment)に対してmy-inc関数が適用したものが出力用channelに出力されていることがわかりますね。

# 実行結果
$ lein run                                                                                                                                                        [~/Dropbox/code/onyx-samples]
Starting Onyx development environment
Submitting
inject-out-ch is called.
inject-in-ch is called.
[{:n 1, :greeting "Hello0"}
 {:n 2, :greeting "Hello1"}
 {:n 3, :greeting "Hello2"}
 {:n 4, :greeting "Hello3"}
 {:n 5, :greeting "Hello4"}
 {:n 6, :greeting "Hello5"}
 {:n 7, :greeting "Hello6"}
 {:n 8, :greeting "Hello7"}
 {:n 9, :greeting "Hello8"}
 {:n 10, :greeting "Hello9"}
 {:n 11, :greeting "Hello10"}
 {:n 12, :greeting "Hello11"}
 {:n 13, :greeting "Hello12"}
 {:n 14, :greeting "Hello13"}
 {:n 15, :greeting "Hello14"}
 {:n 16, :greeting "Hello15"}
 {:n 17, :greeting "Hello16"}
 {:n 18, :greeting "Hello17"}
 {:n 19, :greeting "Hello18"}
 {:n 20, :greeting "Hello19"}
 :done]
Stopping Onyx development environment

Clojure記事紹介<百日修行>(6):「Java/Javascriptから移行する人のためのClojureライブラリ」

Clojure記事紹介<百日修行>では、英文で書かれたClojureの記事を要約してひたすら紹介していきます。モットーは「質より量」です。

紹介記事

Clojure landscape from Java perspective

レベル:初級〜上級

紹介されているライブラリ:たくさん

概要

JavaJavascriptの著名なツールやライブラリについて、Clojureにおける対応するソフトウェアのリストを集めたページです。現在、31個の項目が紹介されています。記事には「Javaから移行する人のためのガイド」と書かれていますが、単純に眺めているだけでも、知らないライブラリが結構あって勉強になります。

個人的には、cheshirefriendbuddyreloaded.repl、などは知らなかったので使ってみたいですね。

Clojure Applied: From Practice to Practitioner

Clojure Applied: From Practice to Practitioner

【広告】