Tezosは自己修正暗号台帳と呼ばれ、そのプロトコルを変更する手続きがプロトコル自体の中に入っています。

プロトコル変更周りはどのように実装されて、どう動作するのか、実際に試しながら理解するのが本稿の目的です。

実験では、

  • Sandboxedノードを動かして
  • プロトコルをsandboxedノードへ注入
  • 注入したプロトコルへの切り替え
  • 各ステップでの状況チェック

を行います。

毎度のことですが、Tezosを自分でコンパイルでき、Unixのコマンドを使える、”Tezosエンジニア”向けです。 手ぇ動かさん奴にはほぼ意味ないで。

準備

Tezos のブランチ mainnet をコンパイルしてください。 masterブランチでもかまわないのですが、mainnetではすでに使われているプロトコルが見えますから、より臨場感があり、楽しい。

Tezosのコンパイルやブランチの切り替え方は自分で調べや。

注意

もし Tezos の baking ノードを動かしているときは、 間違ってもこの実験で本番環境(普通は $HOME/.tezos-node)を上書きしないようにしてください。

ちゃんと言うたで。

Sandboxed ノードを立ち上げる

では、まず sandboxed モードで ノードを立ち上げましょ:

$ src/bin_node/tezos-sandboxed-node.sh 1 --connections 1
Generating a new identity... (level: 0.00)
Stored the new identity (idr...) into '/.../tezos-node..../identity.json'.
...
Dec  6 17:01:23 - node.main: The Tezos node is now running!

このコマンドは通常のTezos ノードとは違う、一時ディレクトリをデータ保存場所として使います。 出力の二行目にそのディレクトリ、 /.../tezos-node..../ の下にidentity.jsonというノード認識子を保存している旨あります。 (Mac やと mktemp がおかしいけど動いとるな)

このディレクトリをメモしておいてな:

export tezos_node_dir=/.../tezos-node..../

ええかメモったか?後で使うんやからな?

クライアント側の設定

次に、この sandboxed ノードにアクセスしやすいよう、ちょっと設定をします。 新しいターミナルを用意して:

$ export TEZOS_CLIENT_UNSAFE_DISABLE_DISCLAIMER=Y
$ eval `src/bin_client/tezos-init-sandboxed-client.sh 1`

を実行。これで、tezos-clientコマンドにいろいろオプションを指定しなくても、sandboxed ノードにアクセスできるようになりました。

もちろんこのevalが何をするか気になる子は調べたらええで。

初期プロトコルを確認する

Sandboxed ノードは立ち上げたばかりで、そのブロックチェーンには genesis block しかありません。 それを確認しましょう。次のコマンドで、main chainの最新ブロック、つまり、genesis block の情報を調べます:

$ tezos-client rpc get /chains/main/blocks/head
{ "protocol": "PrihK96nBAFSxVL1GLJTVhu9YnzkMFiBeuJRPA8NwuZVZCE1L6i",
              <== レスポンスのプロトコル
  "chain_id": "NetXdQprcVkpaWU",
  "hash": "BLockGenesisGenesisGenesisGenesisGenesisf79b5d1CoW2",
              <== このブロック
  "header":
    { "level": 0, "proto": 0,
      "predecessor": "BLockGenesisGenesisGenesisGenesisGenesisf79b5d1CoW2",
                     <== 自分自身が親
      "timestamp": "2018-06-30T16:07:32Z", "validation_pass": 0,
      "operations_hash":
        "LLoZS2LW3rEi7KYU4ouBQtorua37aWWCtpDmv1n2x3xoKi6sVXLWp",
      "fitness": [],
      "context": "CoVbzgT3Re2EuzcUENy3mReot9JYWGshpgiAWkSpJvQH2jWqkW3G" },
  "metadata":
    { "protocol": "PrihK96nBAFSxVL1GLJTVhu9YnzkMFiBeuJRPA8NwuZVZCE1L6i",
                  <== 現在のプロトコル(空)
      "next_protocol": "Ps9mPmXaRzmzk35gbAYNCAw6UXdE2qoABTHbN2oEEc1qM7CwT9P",
                       <== これからのプロトコル
      "test_chain_status": { "status": "not_running" },
      "max_operations_ttl": 0, "max_operation_data_length": 0,
      "max_block_header_length": 105, "max_operation_list_length": [] },
  "operations": [] }

いろいろ出て来ました:

  • 現在の最新ブロックのハッシュは BLockGenesis..、つまり genesis block です。
  • このブロックを作ったプロトコルは PrihK96n..、これは、0値です。つまり、genesis block はプロトコルなしで作られた、という位くらいの意味です。(これが0値だというのは、Format.eprintf "%a@." Protocol_hash.pp Protocol_hash.zeroというコードを走らせるとこの文字列が出てくるのでわかります。)
  • 次のブロックのプロトコルは Ps9mPmXa..。これは genesis block のためのプロトコルです。 https://tezos.gitlab.io/mainnet/whitedoc/the_big_picture.html#the-embedded-economic-protocols にも書いてありますね。実装は src/proto_000_Ps9mPmXa にあります。

ノードが知っているプロトコル

ノードは当然このPs9mPmXaプロトコルの実装を持っていて、理解できるのですが、他にはどんなプロトコルを知っているでしょうか:

$ tezos-admin-client --port 18731 list protocols
  # めんどいけど port は指定してや
ProtoDemoDemoDemoDemoDemoDemoDemoDemoDemoDemoD3c8k9
PtCJ7pwoxe8JasnHY8YonnLYjcVHmhiARPJvqcC6VfHT5s8k8sY
Ps9mPmXaRzmzk35gbAYNCAw6UXdE2qoABTHbN2oEEc1qM7CwT9P
PsddFKi32cMJ2qPjf43Qv5GDWLDPZb3T3bF6fLKiF5HtvHNU7aP
PsYLVpVvgbLhAhoqAkMFUo6gudkJ9weNXhUYCiLDzcUpFpkk8Wt

2018年12月5日現在、ノードは5つのプロトコルを元から知っています。これはソースコードのプロトコル実装のディレクトリとも一致します:

$ ls -d src/proto_*
src/proto_000_Ps9mPmXa/
src/proto_001_PtCJ7pwo/
src/proto_002_PsYLVpVv/
src/proto_003_PsddFKi3/
src/proto_demo/

ここに入っているコードがノードのバイナリにリンクされていて、プロトコルハッシュの名前で紐づけられているというわけです。 各プロトコルはこんな感じです:

000_Ps9mPmXa
genesis protocol
001_PtCJ7pwo
block level 2 から( https://tzscan.io/2 ) block level 28080 まで、betanetの前に使われていたプロトコルですね。
002_PsYLVpVv
block level 28081 (src/lib_base/block_header.ml 参照)から使われていたmainnet開始時のプロトコル
003_PsddFKi3
block level 204761 から使われているプロトコル (https://tezos.gitlab.io/mainnet/protocols/003_PsddFKi3.html)
demo
ほぼ何も入っていないデモ用プロトコル

新しいプロトコルを注入する

さて、見てきたようにノードにはすでにいくつか既存のプロトコルがリンクされていますが、新しいプロトコルをノードに知らせるにはどうしたらいいでしょうか。やってみましょう。

まず、注入する新しいプロトコルのソースコードが必要、、、幸い、Tezosのソースの中にテスト用のプロトコルコードがあります:

$ ls -l src/bin_client/test/demo
total 32
-rw-r--r--  1 rakuda  staff    28 Aug 14 18:17 TEZOS_PROTOCOL
-rw-r--r--  1 rakuda  staff  5005 Aug 14 18:17 main.ml
-rw-r--r--  1 rakuda  staff  1946 Aug 14 18:17 main.mli

小さい、、、プロトコルソースコードの最低条件は:

  • TEZOS_PROTOCOLというファイルにプロトコルのモジュールリストを列挙
  • 必ず Main というモジュールがあって、そのモジュール型は Updater.PROTOCOL であること

です。

さて、ではこのdemoプロトコルをノードに注入してみましょう! とても簡単で、ディレクトリ名を指定するだけです:

$ tezos-admin-client --port 18731 inject protocol src/bin_client/test/demo
Injected protocol PshuejubNkeG successfully

何かがおこって、PshuejubNkeGというプロトコルが注入できたようです。再確認してみましょう:

$ tezos-admin-client --port 18731 list protocols
PshuejubNkeGc5nU2xwF7uGzCdujcZY7ZV3duFfffmG4z5SoMAM  <== NEW!
ProtoDemoDemoDemoDemoDemoDemoDemoDemoDemoDemoD3c8k9
PtCJ7pwoxe8JasnHY8YonnLYjcVHmhiARPJvqcC6VfHT5s8k8sY
Ps9mPmXaRzmzk35gbAYNCAw6UXdE2qoABTHbN2oEEc1qM7CwT9P
PsddFKi32cMJ2qPjf43Qv5GDWLDPZb3T3bF6fLKiF5HtvHNU7aP
PsYLVpVvgbLhAhoqAkMFUo6gudkJ9weNXhUYCiLDzcUpFpkk8Wt

ちゃんと入っています!

この時、裏では何が起きているかを確認するために、ノードのデータディレクトリを見てみましょう:

$ ls $tezos_node_dir   <== 初めに設定しましたよね?
config.json identity.json   protocol/   version.json
context/    lock        store/

なにやら protocol というディレクトリがあります。中を覗いてみると:

$ find $tezos_node_dir/protocol
/.../tezos-node.../protocol
/.../tezos-node.../protocol/PshuejubNkeG
/.../tezos-node.../protocol/PshuejubNkeG/protocol_PshuejubNkeGc5nU2xwF7uGzCdujcZY7ZV3duFfffmG4z5SoMAM.o
/.../tezos-node.../protocol/PshuejubNkeG/protocol_PshuejubNkeGc5nU2xwF7uGzCdujcZY7ZV3duFfffmG4z5SoMAM.cmx
/.../tezos-node.../protocol/PshuejubNkeG/LOG
/.../tezos-node.../protocol/PshuejubNkeG/protocol_PshuejubNkeGc5nU2xwF7uGzCdujcZY7ZV3duFfffmG4z5SoMAM.cmxs
/.../tezos-node.../protocol/PshuejubNkeG/protocol_PshuejubNkeGc5nU2xwF7uGzCdujcZY7ZV3duFfffmG4z5SoMAM.cmt
/.../tezos-node.../protocol/PshuejubNkeG/protocol_PshuejubNkeGc5nU2xwF7uGzCdujcZY7ZV3duFfffmG4z5SoMAM.cmi
/.../tezos-node.../protocol/PshuejubNkeG/src
/.../tezos-node.../protocol/PshuejubNkeG/src/main.mli
/.../tezos-node.../protocol/PshuejubNkeG/src/main.ml
/.../tezos-node.../protocol/PshuejubNkeG/src/TEZOS_PROTOCOL

新プロトコル名のディレクトリの中に、ソースファイル一式と、OCamlのコンパイルされたモジュールがあります。 tezos-admin-client inject protocol コマンドを受け取ると、 ノードは指定されたディレクトリにあるソースコードをコンパイルして自分自身に動的リンクするのですね!

新プロトコルはTezosのデータディレクトリに保存されるので、 ノードを立ち上げなおしてもこの新プロトコルはロードしなおされます。 (Sandboxedノードで実験していると毎回別のディレクトリを使うのでわかりませんが)

注入したプロトコルに切り替える

最後に、この注入したプロトコルにノードを切り替えてみます。

Sandboxed ノードで遊んだことのある人は、tezos-activate-alphaがsandboxed ノードをAlpha protocolに切り替えるコマンドだったのを 覚えている方もいると思いますが(えっそんな人いない?)、そのコマンドを参考に、同じことをこのプロトコルに対して行います:

$ tezos-client -block genesis activate protocol PshuejubNkeGc5nU2xwF7uGzCdujcZY7ZV3duFfffmG4z5SoMAM with fitness 1 and key activator and parameters scripts/protocol_parameters.json
  # 長いなあ
Injected BLxoQz3LspjZ

はい。できました。

は?

無味乾燥すぎて何かわからんね。

プロトコル切り替えを見てみる

さて、上のアクティベーションの結果、Injected BLxoQz3LspjZと出ていますが、 これは新しいブロックがbakeされてブロックチェーンに付け加わったことを意味しています。 そのブロックを見てみましょう。Genesisブロックを見た時と同様、最新ブロックを見るには次のコマンドが使えるのでした:

$ tezos-client rpc get /chains/main/blocks/head
Fatal error: unknown protocol version.

あれ?壊れてもうた?

これはノードが新プロトコルで話だしたからです。この新プロトコルは、tezos-clientは知らないので、ノードが言って返したことを解釈できません。 これはノードに特定プロトコルを話してもらうように --protocol ハッシュ オプションでお願いすることで解決可能です。 Genesis プロトコルを使って聞いてみましょう:

$ tezos-client --protocol Ps9mPmXaRzmzk35gbAYNCAw6UXdE2qoABTHbN2oEEc1qM7CwT9P rpc get /chains/main/blocks/head
{ "protocol": "Ps9mPmXaRzmzk35gbAYNCAw6UXdE2qoABTHbN2oEEc1qM7CwT9P",
  "chain_id": "NetXdQprcVkpaWU",
  "hash": "BLxoQz3LspjZpZpSDAcoP3KcrJ5BBhN6EzNSs3KD1G6jLsi9Lca",
          <== Injected BLxoQz3LspjZ って出ましたよね
  "header":
    { "level": 1, "proto": 1,
      "predecessor": "BLockGenesisGenesisGenesisGenesisGenesisf79b5d1CoW2",
                     <== 前は genesis block
      "timestamp": "2018-12-06T11:18:05Z", "validation_pass": 0,
      "operations_hash":
        "LLoZS2LW3rEi7KYU4ouBQtorua37aWWCtpDmv1n2x3xoKi6sVXLWp",
      "fitness": [ "00", "0000000000000001" ],
      "context": "CoVj1kJFie3KyQYQC3sMv9t6YrM3hBCATn6W4WtDKwQXRQNadRt5",
      "content":
        <== プロトコル PshuejubNkeG をアクティベートしたよ
        { "command": "activate",
          "hash": "PshuejubNkeGc5nU2xwF7uGzCdujcZY7ZV3duFfffmG4z5SoMAM",
          "fitness": [ "00", "0000000000000001" ],
          "protocol_parameters":
            "000007aeae0700000...."
            <== プロトコルの内容w
        },
      "signature":
        "sigp3zpuYcdMFGx7bEfg95PKpUMQb162Pyxi4KRQi9BksMEhi8s2w2yQ6YjeBxrnQJp1UbsRbCQbsitGH7zAqhRjdwTuv8Qk" },
  "metadata":
    { "protocol": "Ps9mPmXaRzmzk35gbAYNCAw6UXdE2qoABTHbN2oEEc1qM7CwT9P",
                  <== このブロックは genesis protocol で作ったよ
      "next_protocol": "PshuejubNkeGc5nU2xwF7uGzCdujcZY7ZV3duFfffmG4z5SoMAM",
                       <== でも次からは新プロトコルだよ
      "test_chain_status": { "status": "not_running" },
      "max_operations_ttl": 0, "max_operation_data_length": 42,
      "max_block_header_length": 42, "max_operation_list_length": [] },
  "operations": [] }
  • このブロックはgenesis protocol Ps9mPmXaで作成されました。なので、genesis protocol で聞くとちゃんと情報を返してくれました。
  • このブロックでは新プロトコルPshuejubNkeGがアクティベートされました
  • protocol_parametersは新プロトコルのソースファイルのtarアーカイブのhex dumpですね。
  • 次のブロックからは新プロトコルPshuejubNkeGです。

なるほど、プロトコルを入れ替えるときは、こんな風にそのオペレーションをブロックに埋め込み、その次のブロックから有効になる、ということがわかりました。

新しいプロトコル

この新しいプロトコル、最低限の実装しか提供していないので、何にもできません。

実験はここで終わりやで。

まとめ

  • tezos-admin-client list protocols でプロトコルのリスト。
  • tezos-admin-client inject protocol <DIR> でプロトコルソースコードのディレクトリを指定してプロトコル注入。
  • プロトコルソースはコンパイルされノードに動的リンクされる。
  • プロトコルをアクティベートするには tezos-client activate protocol
  • プロトコルが切り替わると、クライアントが同じプロトコルを知らないとおしゃべりできなくなる。

コードやで

説明読みながらコピペちまちまやっとられるかボケェ!つう気の早いお子には次のスクリプトあげとくさかい、自分で動かして調べたらええわ:

#!/bin/bash

set -e
set -o pipefail

if [ ! -f ./tezos-node ]; then
    echo "You must execute this script from a compiled Tezos source code directory"
    exit 1
fi

# Load useful settings
test_dir=./src/bin_client/test
source $test_dir/test_lib.inc.sh "$@"

# The PWD moves to $test_dir.
echo PWD=`pwd`

# Run a sandboxed node with the RPC port at 18731
start_node 1

# The node data are stored in $node_dir
echo NODEDIR=$node_dir

# List KNOWN protocols
tezos-admin-client --port 18731 list protocols

# Injection of a new protocol
tezos-admin-client --port 18731 inject protocol $test_dir/demo

# List KNOWN protocols, the list should have a new one
tezos-admin-client --port 18731 list protocols

# Re-injection must fail
tezos-admin-client --port 18731 inject protocol demo && { echo Re-injection somehow succeeded ; exit 1 ; }
echo Re-injection failed as planned.

# The client only knows the following protocols
echo Client understands the following protocols.
tezos-client list understood protocols

# Activate this protocol!

echo Activating the demo protocol!
tezos-client -block genesis activate protocol PshuejubNkeGc5nU2xwF7uGzCdujcZY7ZV3duFfffmG4z5SoMAM with fitness 1 and key activator and parameters ../../../scripts/protocol_parameters.json # --timestamp 2018-12-06T01:26:31Z

# This new protocol is not known to the client
echo Client cannot understand the new protocol!
tezos-client list understood protocols && { echo "The client knows the new protocol!?"  ; exit 1 ; }
echo Client failed as planned.

# Let's see things are compiled.

ls -lR $node_dir/protocol/PshuejubNkeG