はじめに
はじめまして。DMM.comラボ CTO室のKIMと申します。 最先端技術の調査や検証・研究開発をしております。
今回は、Ethereum DApps(Decentralized Applications: 分散型アプリケーション)開発におけるツールとその使い方についてご紹介したいと思います。記事で紹介するソースコードはここにありますので、是非参考にしてみてください。
GoでDApps開発をしたい
最近EthereumのクライアントライブラリであるWeb3.jsを各言語にコンバートしたライブラリや便利な開発ツールがいろいろ出てきてますね!
例えば
言語 | ライブラリ(ツール) |
---|---|
JavaScript | Web3.js |
Python | Web3.py |
Java | web3j(非公式) |
Ruby | ethereum.rb(非公式) |
上記の表のように他の言語には代表的な便利なツールやライブラリがありますが、 Goにはこれといった開発ツールやライブラリは今までありませんでした(ライブラリを使わなくてもGoでDAppsを開発することはできますが、その手順ややり方がややこしく、そのまま使うには少しハードルが高いものになっています)。
そこで、Go実装の開発ツールであるPerigordを使って、Ethereumに接続し、DAppsを実行するところまで試してみました。
Perigordとは
Perigordは、Goで簡単かつ効率良くDAppsが開発できるツール(フレームワーク)です。
上述したように、 GoでDAppsを開発することはできますが、その手順ややり方がややこしく、そのまま使うには少しハードルが高いものになっています。そういった部分を使いやすく、シンプルにしたのがこのPerigordです。 Truffleに似ていて、PolySwarmというプロダクトの基盤技術として使われています。
使い方
Perigordは、かなりシンプルで、コマンドを叩くだけでプロジェクト作成ができたり、コントラクトが追加できたりします(もちろんビルドも、テストもです)。 Truffleを経験したことがある人であればTruffleに似ていることがすぐわかると思います(実際に、Perigordの作者はTruffleからいろいろと影響を受けたと語っています)。
Perigordでよく使うコマンド一覧
コマンド | 説明 |
---|---|
perigord init <プロジェクト名> | 新規プロジェクト初期化 |
perigord build | ビルド |
perigord add contract <コントラクト名> | 新規Contract作成 |
perigord add migration <コントラクト名> | migration追加 |
perigord add test <コントラクト名> | test追加 |
perigord test | test実行 |
簡単なDAppsを作ってみる
ここで簡単なDAppsの作り方をご紹介したいと思います。 まずインストール方法から見てみましょう。
事前準備
Perigordを利用するには、下記のものが必須となります。
- Go
- Geth
- Solc
- abigen
今回は、Macで検証を行なったため、Macベースでのインストール方法をご紹介します。
前提条件
- Homebrewインストール済
- Goインストール済(GOPATHの設定も含む)
- ここではGethやSolidity言語に関する詳細なお話はしません。ご了承ください
Geth、Solcのインストール
Geth、Solcのインストールは、Homebrewで行います。
$ brew tap ethereum/ethereum $ brew install ethereum solidity
abigenインストール
abigenは、コントラクトを簡単かつ安全にBindingできるようにABIベースのGo Codeを生成してくれるツールです。
go getコマンドでインストールします。
$ go get github.com/ethereum/go-ethereum $ pushd $GOPATH/src/github.com/ethereum/go-ethereum $ go install ./cmd/abigen $ popd
Perigordインストール
今回の主人公です。
go getコマンドでインストールします。
$ go get -u github.com/polyswarm/perigord/... && \ go get -u github.com/jteeuwen/go-bindata/...
確認
perigordコマンドを叩いて、下記のような画面が表示されたら正常にインストール完了です。
$ perigord A golang development environment for Ethereum Usage: perigord [command] Available Commands: add Add a new contract or test to the project build (alias for compile) compile Compile contract source files deploy (alias for migrate) generate (alias for compile) help Help about any command init Initialize new Ethereum project with example contracts and tests migrate Run migrations to deploy contracts test Run go and solidity tests Flags: -h, --help help for perigord Use "perigord [command] --help" for more information about a command.
新しいプロジェクト作成
perigordコマンドを使って新しいDAppプロジェクトを作成します。
$ perigord init perigordTestDapp $ pushd $GOPATH/src/perigordTestDapp
下記のようなプロジェクトのディレクトリ構成になります。
$ tree . ├── contracts │ └── Foo.sol ├── generate.go ├── main.go ├── migrations │ └── 1_Migrations.go ├── perigord.yaml ├── stub │ ├── README.md │ └── main.go ├── stub_test.go └── tests └── Foo.go
コントラクト作成
Greeter
という名前の新しいコントラクトを生成します。
$ perigord add contract Greeter
生成したコントラクトの中身を下記のように変更します。
contracts/Greeter.sol
pragma solidity ^0.4.4; contract Greeter { string public greeting; event Result(address from, string stored); function Greeter() { greeting = "Hello"; } function setGreeting(string _greeting) public { greeting = _greeting; Result(msg.sender, greeting); } function greet() constant returns (string) { return greeting; } }
ビルド
作成したコントラクトをビルドします。
$ perigord build
ビルドが完了すると、bindings
とbuild
ディレクトリが生成されます。
また、生成された各ディレクトリの下にデプロイに必要なファイルが生成されていることがわかります。
$ tree . ├── bindings │ ├── Foo.go │ └── Greeter.go ├── build │ ├── Foo.abi │ ├── Foo.bin │ ├── Foo.link │ ├── Greeter.abi │ ├── Greeter.bin │ └── Greeter.link . . .
マイグレーション
コントラクトをデプロイするには、マイグレーションを追加する必要があります。
$ perigord add migration Greeter
migrationsディレクトリに2_Greeter.goというファイルが生成されます。
$ tree . . . ├── migrations │ └── 1_Migrations.go │ └── 2_Greeter.go . . .
GoでDAppsを操作する
GoでDAppsを操作するために、Goでコードを書きます。
今回は3つのファイルに分けて、DAppsの操作に必要な機能やデータインタフェースを作ります。
main.go
今回のサンプルのメイン処理を定義するファイルです。
main関数の中の流れは、下記の順になります。
初期化 ↓ デプロイ ↓ コントラクト接続 ↓ イベント監視用チャンネル設定 ↓ データ取得と更新 ↓ イベント取得とデータ取得
package main import ( "context" "fmt" "perigordTestDapp/bindings" _ "perigordTestDapp/migrations" "log" "github.com/polyswarm/perigord/contract" "github.com/polyswarm/perigord/migration" "github.com/polyswarm/perigord/network" ) var greeter *Greeter func main() { // 初期化 network.InitNetworks() // perigord.yamlから情報を取得し、接続を行う nw, err := network.Dial("dev") if err != nil { log.Fatalln("Could not connect to dev network: ", err) } // マイグレーション実行(デプロイ) if err := migration.RunMigrations(context.Background(), nw, false); err != nil { log.Fatalln("Error running migrations: ", err) } // コントラクト接続 session, ok := contract.Session("Greeter").(*bindings.GreeterSession) if !ok { log.Fatalln("Error retrieving session") } greeter = NewGreeter(session, nw.Client()) // イベント監視用チャンネル eventChan := make(chan *Event) if err := greeter.WatchForEvents(eventChan); err != nil { log.Println("error listening for incoming events:", err) return } // データ取得 fmt.Printf("更新前: %s\n", greeter.Greet()) // データ更新 greeter.SetGreeting("Hello, World!:D") // イベント監視 for { event := <-eventChan fmt.Println("Event結果\n") fmt.Printf("from: %v\n", event.Body.(*NewEvent).From.Hex()) fmt.Printf("stored: %v\n", event.Body.(*NewEvent).Stored) // 先ほど更新した内容("Hello, World!:D")に更新されているか確認 fmt.Printf("更新後: %s\n", greeter.Greet()) } }
greeter.go
Greeterコントラクトに定義されている機能を実行します。GetやSet、Event監視機能があります。
package main import ( "context" "encoding/json" "perigordTestDapp/bindings" "log" "strings" ethereum "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/polyswarm/perigord" "github.com/polyswarm/perigord/contract" ) type Greeter struct { session *bindings.GreeterSession client *ethclient.Client } func NewGreeter(session *bindings.GreeterSession, client *ethclient.Client) *Greeter { return &Greeter{ session: session, client: client, } } func (g *Greeter) Greet() string { greet, _ := g.session.Greet() return greet } func (g *Greeter) SetGreeting(greeting string) error { if result, _ := g.session.SetGreeting(greeting); result == nil { log.Fatalln("failed set greeting.") } return nil } // イベント監視 func (g *Greeter) WatchForEvents(eventChan chan *Event) error { topics := map[string]common.Hash{ "Result": perigord.EventSignatureToTopicHash("Result(address,string)"), } q := ethereum.FilterQuery{ Addresses: []common.Address{contract.AddressOf("Greeter")}, Topics: [][]common.Hash{{ topics["Result"], }}, } dec := json.NewDecoder(strings.NewReader(bindings.GreeterABI)) var abi abi.ABI if err := dec.Decode(&abi); err != nil { return err } logChan := make(chan types.Log) sub, err := greeter.client.SubscribeFilterLogs(context.Background(), q, logChan) if err != nil { return err } go func() { log.Println("Starting event monitor") for { select { case logMsg := <-logChan: if len(logMsg.Topics) != 1 { log.Println("incorrect number of topics") break } var nbe NewEventLog if err := abi.Unpack(&nbe, "Result", logMsg.Data); err != nil { log.Println("error unpacking log: ", err) break } event := &Event{ Type: "Result", Body: NewEventFromLog(nbe), } eventChan <- event break case err := <-sub.Err(): log.Println(err) break } } }() return nil }
event.go
Event監視の時に使うデータインタフェースを定義します。
package main import "github.com/ethereum/go-ethereum/common" type Event struct { Type string Body interface{} } type NewEventLog struct { From common.Address Stored string } type NewEvent struct { From common.Address Stored string } func NewEventFromLog(nbe NewEventLog) *NewEvent { return &NewEvent{ From: nbe.From, Stored: nbe.Stored, } }
実行
まず、testnetを実行します。
インストールしたperigordディレクトリの中に、testnetの実行スクリプトファイルがあるので、それを使ってtestnetを実行します(別のターミナルで実行することをお勧めします)。
$ $GOPATH/src/github.com/polyswarm/perigord/scripts/launch_geth_testnet.sh
次に、ビルドの前にファイルの内容を少し修正します。
Perigord buildコマンドで生成されたものをそのままビルド・実行すると、実行後にアカウントのパスワードを聞かれるので、ビルド前にmigrationsディレクトリにあるファイルを修正します。
migrations/1_Migrations.go, migrations/2_Greeter.go
. . . func (d *GreeterDeployer) Deploy(ctx context.Context, network *network.Network) (common.Address, *types.Transaction, interface{}, error) { account := network.Accounts()[0] // 下記の行をコメントアウトもしくは削除 network.UnlockWithPrompt(account) // 下記の行を追加 network.Unlock(account, "blah") . . . } func (d *GreeterDeployer) Bind(ctx context.Context, network *network.Network, address common.Address) (interface{}, error) { account := network.Accounts()[0] // 下記の行をコメントアウトもしくは削除 network.UnlockWithPrompt(account) // 下記の行を追加 network.Unlock(account, "blah") . . . } . . .
最後に、go buildコマンドでGoファイルをビルドし、実行します。
$ go build main.go greeter.go event.go $ ./main
実行すると、下記のような結果が画面に表示されます。
終わりに
まだプロジェクト自体の歴史も浅いですし、Truffleなどの他のツールに比べるとまだまだって感じがしますが、 GoでDAppsの開発ができるのはやはり大きいかなと思います。今後が楽しみですね!
参考
Introducing Perigord: Golang Tools for Ethereum DApp Development
GitHub - polyswarm/perigord: Perigord: Golang Tools for Ethereum Development
Native DApps: Go bindings to Ethereum contracts · ethereum/go-ethereum Wiki · GitHub
go ethereum - How to decode Log.Data in Go - Ethereum Stack Exchange
採用情報
現在、CTO室では、エンジニアメンバーを募集しております! 興味のある方はぜひ下記募集ページをご確認下さい! dmm-corp.com