Help us understand the problem. What is going on with this article?

TruffleとGanacheを使ったÐapps開発の環境構築 + チュートリアル

はじめに

本記事は DSL Advent Calendar 2019 2日目の記事です。M1の @romorimori が担当します。

Ðappsとは

ブロックチェーンプラットフォームの一つであるEthereumでは、
分散ネットワーク上で実行可能なスマートコントラクトと呼ばれるプログラムを開発することができます。

スマートコントラクトの仕組み自体はオブジェクト指向での「クラス」に似ていて、
クラス変数に相当するような内部状態を保持するためのストレージ部分と
関数やメソッドに相当するような実行コードであるコントラクト・コードを持っています。

スマートコントラクトは分散ネットワーク上にデプロイされ、ユーザが実行命令を出すことで実行することができます。
コントラクト・コードに任意の動作をプログラムすることで、特定管理者が存在せずともユーザー間で直接データをやり取り可能になります。

これを利用したブロックチェーン上で動くアプリケーションのことをDapps(Decentralized Applications)と呼びますが。
Ðはイーサと読むので、Ethereumを使ったDappsのことを特に、Ðappsと呼びます。

この記事ではÐappsを開発するための環境構築と、シンプルなÐappsを実際に作るところまでをやっていきます。

Solidityについて

コントラクト・コードはEthereumネットワーク上で実行されていると書きましたが、
厳密にはEthereumに接続されているノード内のEVMと呼ばれる専用の仮想マシン上で実行されています。

ここでは「EVM Code」と呼ばれるバイトコードの形式で記述され処理されているのですが、
EVM Codeは低水準な機械語であるため、人間には可読性が悪いものとなってます。

そこで、EVM Codeにコンパイル可能な言語が複数開発されています。
現時点ではSolidityと呼ばれるJavascriptに近い言語がよく使われています。

この記事でもSolidityを使ってプログラムを記述していきます。

環境

MacOS Catalina 10.15.1
node v11.14.0
npm 6.7.0

作業の手順

  • Truffle & Ganacheをインストール
  • Truffleプロジェクト作成
  • GanacheとTruffleプロジェクトをリンク
  • コントラクトコード作成、デプロイ
  • 動作確認

環境構築

Truffleインストール

TruffleはEVM Code開発フレームワークです。
スマートコントラクトのコンパイル、デプロイ、テストなど便利な機能がたくさんあり、チュートリアルも充実しています。


npm install -g truffle

Ganacheインストール

Ganacheは簡単に作成することができるEthereumのプライベートネットワークです。
起動してすぐにEthを持つアカウントが作成されたり、チェーンのリセットを行えたりと、
Ðappsを開発したりテストしたりするのに非常に便利です。デスクトップアプリとしてもコマンドラインツールとしても使えます。

公式サイトでインストーラーをダウンロードしてインストールします。

Hello Worldまでのチュートリアル

Truffleを使って「Hello World」コントラクトを作成し、Ganacheで作ったEthereumのプライベートネットワークにデプロイして、動作確認をしてみます。

Truffleプロジェクト作成

まずはプロジェクトディレクトリを作り、初期化します。
適当な場所でtruffle initをすることでプロジェクトを作成することができます。

今回は以下のようにhello_worldディレクトリを作成しました。
うまくいくとコンソールに成功ログが出て、いくつかのファイルが作成されます。


$ mkdir hello_world
$ cd hello_world/
$ truffle init

✔ Preparing to download
✔ Downloading
✔ Cleaning up temporary files
✔ Setting up box

Unbox successful. Sweet!

Commands:

  Compile:        truffle compile
  Migrate:        truffle migrate
  Test contracts: truffle test


以下のようなツリーが生成されます。

$ tree
hello_world
├── contracts
│   └── Migrations.sol
├── migrations
│   └── 1_initial_migration.js
├── test
└── truffle-config.js

3 directories, 3 files

生成されたファイルの説明

  • contracts : この中にSolidityで記述されたプログラムを配置します。
  • migrations : このディレクトリ内のファイルはマイグレーションファイルと呼ばれ、作成したコントラクトをネットワークにデプロイするために使います。
  • test : コントラクトが正しく動作するかを確認するためのテストコードが配置されます。
  • truffle-config.js : Truffleの設定ファイルです。詳細は以下で説明します。

Truffle設定

Ganacheで作るプライベートネットワークと連携するために、configファイルを変更し、ネットワークの設定をします。
Ganacheはデフォルトだとlocalhost:7545でRPC通信を行うため、それにあわせます。

module.exports = {
    networks: {
      development: {
         host: "127.0.0.1",     // Localhost (default: none)
         port: 7545,            // Standard Ethereum port (default: none)
         network_id: "*",       // Any network (default: none)
      }
    }
}

Ganache起動

インストールしたGanacheを起動すると以下の画面が表示されるのでNEW WORKSPACEを選択します。ganache_new_workspace.png

ワークスペースの名前設定と「リンク」を行うための設定画面に移ります。

ADD PRPJECTをクリックして、先ほど作成したプロジェクトディレクトリのtruffle-config.jsを追加することで、
既存のTruffleプロジェクトとGanacheのワークスペースをリンクさせることができます。

下の画像のように、先ほど作ったTruffleプロフェクトをリンクさせました。リンクは後からでも行えますし、アンリンクすることもできます。

ganache_add_workspace.png

成功すると100ETHをもったアカウントが自動的に10個生成されます。便利ですね。
開発中は基本的に付けっぱなしでOKです。

ganache_first.png

これでEthereumのプライベートネットワークをローカルに作ることができました。

確認

TruffleからGanacheのプライベートネットワークに接続できるか確認してみます。
プロジェクトディレクトリでtruffle consoleと打つとコンソールへ接続できます。

$ truffle console
truffle(development)> 

こうなればOKです。コンソールから送金などの操作もできます。
.exitをタイプするかCtrl + Cを2回押せばコンソールから抜けられます。

ちなみに、ネットワークが見つからないと以下のようなエラーがでます。
truffle-config.jsの設定を見直したりしてみましょう。

$ truffle console
> Something went wrong while attempting to connect to the network. Check your network configuration.

Could not connect to your Ethereum client with the following parameters:
    - host       > 127.0.0.1
    - port       > 7545
    - network_id > *
Please check that your Ethereum client:
    - is running
    - is accepting RPC connections (i.e., "--rpc" option is used in geth)
    - is accessible over the network
    - is properly configured in your Truffle configuration file (truffle-config.js)

HelloWorldコントラクトを作成

コントラクトファイルもtruffleコマンドで作成します。

$ truffle create contract HelloWorld

これでcontractsの中にHelloWorld.solができます。
プログラムを書いていきます。

/contracts/HelloWorld.sol

pragma solidity ^0.5.0;

contract HelloWorld {
  string defaultMessage;
  constructor() public {
    defaultMessage = 'Hello World';
  }
  function getMessage() public view returns(string memory){
    return defaultMessage;
  }
}

いわゆるクラス宣言のようにcontract宣言をして、その中に変数やメソッドを記述します。

今回はdefaultMessageを宣言し、constructor()内でメッセージを設定します。コントラクトが作られた時constructor()が一度だけ実行されます。

Solidityではデータの保存にstoragememoryの2種類があり、storageはブロックチェーンにデータを保存しますが、memoryは保存しません。
なので、getMessage()stringをリターンしますが、ブロックチェーンに記録することはありません。

コンパイル

$ truffle compile

Compiling your contracts...
===========================
> Compiling ./contracts/HelloWorld.sol
> Compiling ./contracts/Migrations.sol
> Artifacts written to ~/hello_world/build/contracts
> Compiled successfully using:
   - solc: 0.5.12+commit.7709ece9.Emscripten.clang

コンパイルできました。成果物はbuild/contractsフォルダ直下に配置されます。
コードに不備があった場合はここでエラーが出て弾かれます。

マイグレーションファイル作成

HelloWorld コントラクトをネットワークにデプロイするためのマイグレーションファイルを作成します。
これもtruffleコマンドで作成します。

$ truffle create migration HelloWorld

migrationsの中に15xxxxxxxx_hello_world.jsが作成されました。
マイグレーションファイルを作成すると、ファイル名の先頭にファイル作成時のタイムスタンプが設定されます。
ファイルの中身を記述します。

migrations/15xxxxxxxx_hello_world.js

const HelloWorld = artifacts.require("HelloWorld");
module.exports = function(_deployer) {
  // Use deployer to state migration tasks.
  _deployer.deploy(HelloWorld)
};

デプロイ

migrateコマンドでmigrateディレクトリ配下のスクリプトを実行していきます。

$ truffle migrate

いろいろとログが出て成功しました

Starting migrations...
======================
> Network name:    'development'
> Network id:      5777
> Block gas limit: 0x6691b7


1_initial_migration.js
======================

   Deploying 'Migrations'
   ----------------------
   > transaction hash:    0xe6cc1b0d4f3e8a79c64be1e9a0f0daf9bae7908bfc76ee075b10c5f0cbc0364d
   > Blocks: 0            Seconds: 0
   > contract address:    0x679357F71B9B097B6546188DAb4E47aDA516DB63
   > block number:        1
   > block timestamp:     1575113348
   > account:             0x2cC796eCE69f4Bf41f4Cc6D3733FE362D921F9a5
   > balance:             99.99472518
   > gas used:            263741
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.00527482 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.00527482 ETH


1575112675_hello_world.js
=========================

   Deploying 'HelloWorld'
   ----------------------
   > transaction hash:    0x6fcb1f9fd8bd7761c676448d64c571da7d7945fe25ec599dba6e2ce8e17edbe9
   > Blocks: 0            Seconds: 0
   > contract address:    0x8300Be5250A6323A7A83d220157e4a6c2A980659
   > block number:        3
   > block timestamp:     1575113348
   > account:             0x2cC796eCE69f4Bf41f4Cc6D3733FE362D921F9a5
   > balance:             99.99000202
   > gas used:            194135
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.0038827 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:           0.0038827 ETH


Summary
=======
> Total deployments:   2
> Final cost:          0.00915752 ETH

ここで補足なのですが、migrateコマンドは前回のデプロイが成功だった場合、それ以降の新しいマイグレーションだけ実行します。
なので、一度デプロイしたコントラクトコードを修正した場合は以下のコマンドを実行することで
新しいコントラクトコードでデプロイしなおす必要があります。

$ truffle migrate --reset

確認

2回マイグレートを行ったのでGanacheでネットワークのログを確認してみます。
image.png

先頭のアカウントのETH残高が減っています。トランザクションのログをみてみると8個のログが記録されていました。
ここで、簡単にログの読み方を解説します。

  • 0xから始まるハッシュ値( TX HASH )はトランザクションハッシュです。EthereumではETHの送金もスマートコントラクトの実行もトランザクションをネットワークに送信することで行います。ここでは個々のトランザクションの識別子であると考えてもらえれば大丈夫です。
  • FROM ADDRESSはトランザクションの送信者です。
  • GAS USEDはトランザクションの処理にかかった「コスト」です。
  • VALUEはトランザクションで「ETHの送金」が行われていた場合のETHの量です。今回のトランザクションでは送金を行っていないので0です。

ログ
ganache_migrateLig.png
ログの下から順にコントラクトの作成、コントラクトの実行、コントラクトの作成、コントラクトの実行・・・と行われています。
ここでは下から4つ目までのログにフォーカスを当てて見ていきます。この4つがtruffle migrateを1回実行した際のログになっています。

デプロイはmigrationsのファイル名先頭のタイムスタンプの順番に行われるため、最初のコントラクト生成ログ(CONTRACT CREATION)はMigrationsコントラクトがネットワーク上にデプロイされたことを意味します。また、truffleコマンドを実行した際のコンソールのログに書かれていたTxハッシュとGanacheのTxハッシュが一致していることがわかります。
コントラクトをチェーンにデプロイすると、コントラクトにContract Addressが与えられ、このアドレスを使ってコントラクトを呼び出すことができます。CREATED CONTRACT ADDRESSが、このトランザクションで与えられたアドレスを示しています。

Migrationsコントラクトはtruffle initで自動生成されるファイルで、「現在どのマイグレーションまで完了しているか」を記憶するコントラクトです。
contracts/Migrations.sol

pragma solidity >=0.4.21 <0.6.0;

contract Migrations {
  address public owner;
  uint public last_completed_migration;

  constructor() public {
    owner = msg.sender;
  }

  modifier restricted() {
    if (msg.sender == owner) _;
  }

  function setCompleted(uint completed) public restricted {
    last_completed_migration = completed;
  }

  function upgrade(address new_address) public restricted {
    Migrations upgraded = Migrations(new_address);
    upgraded.setCompleted(last_completed_migration);
  }
}

次のコントラクト呼び出しログでは、function setCompleted(uint completed)1を引数にとり呼び出しています。これにより、「現在マイグレーションは1まで完了している」という情報をチェーンに保存しています。

次のコントラクト生成ログではHelloWorldコントラクトが生成され、
その次のコントラクト呼び出しログではMigrationコントラクトを再び呼び出してHelloWorldコントラクトを記録します。

これにより、次回以降マイグレーションコマンドを叩いた時、HelloWorldコントラクト以降のMigrationファイルのみ実行されるようになります。

HelloWorldの動作確認

デプロイがうまくいったかどうか、コンソールからHelloWorldコントラクトの動作確認をしてみます。
デプロイすると、Truffleが提供してくれているJavascriptオブジェクトを使うことで、簡単にコントラクトが利用できるようになります。

deployed()でコントラクトにアクセスして、getMessage()を呼び出してみます。

truffle(development)> let helloWorld = await HelloWorld.deployed()
truffle(development)> helloWorld.getMessage()
'Hello World'

うまくいきました。

まとめ

簡単にですが、以上がTruffleとGanacheを使った環境構築とスマートコントラクト開発の流れになります。
まだ紹介できていないTruffleのテストの機能などもいつかまとめれたらいいなと思っています。

参考

Truffle公式のチュートリアル
Ganache公式のチュートリアル
Truffle + Ganache + Metamask を使って Ðapps の世界に旅立とう
Truffle の Migration は何をやっている?
Ethereum入門

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした