Ethereum
solidity
geth
populus
EthereumDay 17

Pythonのスマートコントラクト開発フレームワークPopulusで新言語Viperを使ってみる

この記事は Ethereum Advent Calendar 2017 の17日目の記事です。
自分用のメモも兼ねているので読みにくかったら申し訳ないです。

Populusとは

Ethereum Virtual Machine(EVM)上で実行するスマートコントラクトを開発するためのフレームワークです。
https://github.com/pipermerriam/populus

ViperやSolidityで書かれたコントラクトコードのコンパイル、テスト、ローカルやTestnet、Mainnetへのデプロイなどができます。似たような開発フレームワークとしてTruffleが有名ですが、Populusの特徴としてテストやデプロイのスクリプトなどをPythonを使って書くことができます。テストにはライブラリのpytestを使用しています。

スマートコントラクトにおけるテストの重要性はThe DaoやParityの事件から周知の事実です。笑
普段慣れている言語で書くのが一番良いかと思います。 ←あんまりテスト書いたことない

Viperとは

スマートコントラクトをPythonライクに書ける新言語です。
https://github.com/ethereum/viper

過去にSerpentというpythonライクに書ける言語がありましたが、スマートコントラクトコードの監査などを行っているZeppelin Solutionsに脆弱性を指摘されるという出来事がありました。そのため、Augurなどの有名なプロジェクトもSerpentからSolidityに書き直すことになり、スマートコントラクトを書くにはSolidityかLispライクなLLLしか選択肢がないような状態でした。

しかしViperの開発が進んできたことにより、再びpythonライクに書けるという選択肢が出てきました。

PopulusとViperをとりあえず触ってみよう!というのが今回の記事の主旨です。

Viperの特徴

監査しやすいということを重要視しているため、ガチガチの言語という印象です。
solidityでは可能なmodifyやクラスの継承ができません。
スマートコントラクトは本番にデプロイしてしまうと変更できなくなるので、ガチガチな方が良いのかもしれませんね。

開発環境

  • Mac OS X EI Capitan10.11.6
  • Python 3.6.2
  • geth 1.7.3
  • populus 2.1.0
  • web3(python) 3.16.4
  • solidity 0.4.17
  • viper 0.0.2

venvなどで適当な仮想環境を用意して、pipなどでpopulusをインストールしてください。
あとはえーっと、solidityのインストールも必要なのですが長くなりそうなので、インストールの説明は以下の記事を参考にしてください。

Populusプロジェクト作成

それでは実際にPopulusを使ってスマートコントラクトを書いていきます。
まずはプロジェクトを作成します。
適当なディレクトリを作成してコマンドを実行してください。

$ populus init

実行すると以下のような構成になります。
Hello, Worldコントラクト的なことはこれをデプロイして実行すればできます。

.
├── contracts
│   └── Greeter.sol
├── project.json
└── tests
    └── test_greeter.py

2 directories, 3 files

contractsにsolファイル、testsにテストするためのpythonファイルが置かれます。
しかし、今回はSolidityでなくViperでやりたいので、Greeter.solファイルは削除しておきましょう。
project.jsonはconfig系の設定を定義できます。

ローカルにブロックチェーンを構築

それではローカルマシン上にブロックチェーンを構築していきましょう。
このコマンドを実行するとchainsディレクトリが作成され、この中でブロックチェーンを定義していきます。
hortonってどういう意味やろう。。。

$ populus chain new horton
$ chains/horton/./init_chain.sh
INFO [12-17|10:01:58] Allocated cache and file handles         database=/populus/chains/horton/chain_data/geth/chaindata cache=16 handles=16
INFO [12-17|10:01:58] Writing custom genesis block 
INFO [12-17|10:01:58] Successfully wrote genesis state         database=chaindata                                                                              hash=9d1789…549272
INFO [12-17|10:01:58] Allocated cache and file handles         database=/populus_init/chains/horton/chain_data/geth/lightchaindata cache=16 handles=16
INFO [12-17|10:01:58] Writing custom genesis block 
INFO [12-17|10:01:58] Successfully wrote genesis state         database=lightchaindata                                                                              hash=9d1789…549272

ファイル構成

.
├── chains
│   └── horton
│       ├── chain_data
│       │   ├── geth
│       │   │   ├── chaindata
│       │   │   │   ├── 000001.log
│       │   │   │   ├── CURRENT
│       │   │   │   ├── LOCK
│       │   │   │   ├── LOG
│       │   │   │   └── MANIFEST-000000
│       │   │   └── lightchaindata
│       │   │       ├── 000001.log
│       │   │       ├── CURRENT
│       │   │       ├── LOCK
│       │   │       ├── LOG
│       │   │       └── MANIFEST-000000
│       │   └── keystore
│       │       └── UTC--2017-12-17T10-06-47.420461288Z--xxxxxxxxxxxxx
│       ├── genesis.json
│       ├── init_chain.sh
│       ├── password
│       └── run_chain.sh
├── contracts
│   └── Greeter.sol
├── project.json
└── tests
    └── test_greeter.py

9 directories, 18 files

chain_dataディレクトリの中などはgethを使ったことがある人には少し見慣れた画面かもしれません。
これだけでローカルでプライベートブロックチェーンを動作させる準備は整いました。

以下のシェルを実行してgethを動作させてブロックチェーンを起動してみます。
このシェルにはgethを実行するためのコマンドが書かれているので、portやローカルホストは適宜自分の環境に合わせて変更してください。

$ chains/horton/./run_chain.sh

gethが起動して/chains/horton/chain_dataディレクトリにgeth.ipcというファイルができたかと思います。

別タブでターミナルでgeth.ipcをattachすることで、gethのコンソールを開くことができます。

$ geth attach geth.ipc

Welcome to the Geth JavaScript console!

instance: Geth/v1.7.3-stable/darwin-amd64/go1.9.2
coinbase: 0x654e848898a93ccbc21d05e9f0ec9b47780a25a3
at block: 0 (Thu, 01 Jan 1970 09:00:00 JST)
 datadir: /chains/horton/chain_data
 modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0

genesis.jsonで定義されたcoinbase addressにちゃんとなってますね。
勝手にマイニングが始まってると思うので、止めたい場合はコンソールでminer.stop()って打つと止まります。
ブロックナンバーを確認したい場合はeth.blockNumberでできます。
gethのコンソールなので、コマンドはこの記事が参考になります。
Ethereum Geth コンソールコマンド一覧

Viperのインストールとファイル作成

viperもGitHubからpipでインストールできます。

$ pip install https://github.com/ethereum/viper/archive/master.zip

viperの拡張子はv.pyです。
これ正直微妙だなと思ったんですが、エディタによってはpythonファイルだと認識してしまって、すごいエラーばっかりみたいになります。私Pycharm使ってますが、赤の波線ばっかです。笑

contracts/Greeter.v.pyファイルを作成します。

greeting: bytes <= 20


@public
def __init__():
    self.greeting = "Hello"


@public
def setGreeting(x: bytes <= 20):
    self.greeting = x


@public
def greet() -> bytes <= 40:
    return self.greeting

サンプルコードはPopulusから拝借しています。
http://populus.readthedocs.io/en/latest/viper_support.html

私は先にgreetingを宣言して型を指定する。greetingを使うにはselfが必要といったところはちょっと違和感がありましたが、なるほどなーという感じです。

Project.json

Populusでは、config系やViper、openzeppelinなどのライブラリを使う場合はproject.jsonに追加する必要があります。

先程立ち上げたgeth.ipcのpathなどのブロックチェーンの設定もproject.jsonに追記します。

ドキュメントにはopenzeppelinを使いたい場合は"import_remappings"に入れたら良いよ的なことを書いていたんですが、コンパイルに失敗しました。
誰か知ってる方いれば教えてください。。。

{
  "version":"7",
  "compilation":{
    "contracts_source_dirs": ["./contracts"],
    "import_remappings": [],
    "backend": {
            "class": "populus.compilation.backends.ViperBackend"
        }
  },
  "chains": {
    "horton": {
      "chain": {
        "class": "populus.chain.ExternalChain"
      },
      "web3": {
        "provider": {
          "class": "web3.providers.ipc.IPCProvider",
        "settings": {
          "ipc_path":"/populus/chains/horton/chain_data/geth.ipc"
        }
       }
      },
      "contracts": {
        "backends": {
          "JSONFile": {"$ref": "contracts.backends.JSONFile"},
          "ProjectContracts": {
            "$ref": "contracts.backends.ProjectContracts"
          }
        }
      }
    }
  }
}

ここらの設定ができればコンパイルしてみます。

$ populus compile

> Found 1 contract source files
  - contracts/Greeter.v.py
> Compiled 1 contracts
  - contracts/Greeter.v.py:Greeter
> Wrote compiled assets to: build/contracts.json

エラーが出なければ成功です。

テスト

では、Greetorコントラクトが動くかテストしていきます。
今回は、populus initで作ったときのそのままのソースで動きます。

test_greeter.py

def test_greeter(chain):
    greeter, _ = chain.provider.get_or_deploy_contract('Greeter')

    greeting = greeter.call().greet()
    assert greeting == 'Hello'


def test_custom_greeting(chain):
    greeter, _ = chain.provider.get_or_deploy_contract('Greeter')

    set_txn_hash = greeter.transact().setGreeting('Guten Tag')
    chain.wait.for_receipt(set_txn_hash)

    greeting = greeter.call().greet()
    assert greeting == 'Guten Tag'

テストにはpythonのpytestライブラリを用いていますので、py.testなどのコマンドを実行するとテストが走ります。

「2 passed in 1.14 seconds 」のようになればOKです。

pytestを使ったことはないのですが、populusには様々なprojectのブロックチェーンの設定やデプロイを擬似的に表現するためのメソッドがありますので、これはpopulusを使うメリットじゃないかなと思います。

デプロイ

先にローカルでブロックチェーンを起動させます。

$ chains/horton/./run_chain.sh

デプロイを実行します。
--no-wait-for-syncをオプションに含めることで、ブロックチェーンと非同期で実行できます。チェーンが同期できていないと失敗するので先にgethを実行してチェーンを同期しておいた方が良いかもしれません。

$ populus deploy --chain horton Greeter --no-wait-for-sync
> Found 1 contract source files
  - contracts/Greeter.v.py
> Compiled 1 contracts
  - contracts/Greeter.v.py:Greeter
Beginning contract deployment.  Deploying 1 total contracts (1 Specified, 0 because of library dependencies).

Greeter
Deploying Greeter
Deploy Transaction Sent: 0x9eaf53f2e1b16051ce03ac6bb7789e13a197434aaa231d3488759514d57d4fa7
Waiting for confirmation...

Transaction Mined
=================
Tx Hash      : 0x9eaf53f2e1b16051ce03ac6bb7789e13a197434aaa231d3488759514d57d4fa7
Address      : 0x4C3a1C307BEBa668F41df7886FDFa2e07Bc210Cb
Gas Provided : 325305
Gas Used     : 225305


Verified contract bytecode @ 0x4C3a1C307BEBa668F41df7886FDFa2e07Bc210Cb
Deployment Successful.

Greeterコントラクトのデプロイに成功しました。

registrar.jsonというファイルができて、BlockHashやaddressが保存されているはずです。

python側からデプロイとコントラクトの確認

最後にコントラクトが成功しているかをpython側から見てみます。
project_dirに自分のディレクトリ構成を書きます。
先程コマンドからデプロイしたので、"The contract is already deployed on the chain"が出て、"Hello"が出るはずです。

hello.py

from populus.project import Project


def main():
    project = Project(project_dir="/your_project_dir/")
    with project.get_chain('horton') as chain:
        greeter, deploy_tx_hash = chain.provider.get_or_deploy_contract('Greeter')

    print("Greeter address on horton is {address}".format(address=greeter.address))
    if deploy_tx_hash is None:
        print("The contract is already deployed on the chain")
    else:
        print("Deploy Transaction {tx}".format(tx=deploy_tx_hash))

    # Get contract state with calls
    greet_call = greeter.call().greet()
    print(greet_call)

if __name__ == '__main__':
    main()

実行してみます。

$ python hello.py 
Greeter address on horton is 0x4c3a1c307beba668f41df7886fdfa2e07bc210cb
The contract is already deployed on the chain
Hello

良い感じですね。
もしデプロイしていなかった場合は、デプロイしてdeploy_tx_hashが返ってきます。(試してない)

まとめ

駆け足でしたが、populusでローカルのブロックチェーンにViperのGreeterコントラクトをのせて、実行するところまでをやりました。

Populusを始めて使ったのですが、ドキュメントも充実していますし、テストやデプロイなども便利にできて素晴らしいと思います。
Viperが盛り上がってくると、Populusも盛り上がってくるのではないかと思いました。
Viperを使うかどうかはわかりませんが、しばらくPopulusを使って勉強していきます。

参考リンク