Ethereum
solidity
MisocaDay 20

Ethereumのローカルネットワーク上で独自コインを送金する

はじめに

こんにちは、Misocaの洋食(@yoshoku)です。

この記事は、Misoca Advent Calendar 2017の20日目の記事です。

ビットコインをはじめ暗号通貨(仮想通貨)がスゴイ人気ですね。メジャーな暗号通貨は、値段が高騰しすぎて手が出せそうにありませんが、暗号通貨を支えるブロックチェーンには興味があります。今回は、オープンソースのブロックチェーンプラットフォームであるEthereumを使って、ローカルネットワーク上で独自コインを実装してみたいと思います。

準備

ネットワーク 

Ethereumのネットワークには、いわゆるパブリックブロックチェーンと、テストネットワークがあります。さらにテストネットワークには、世界中のノードが参加できるModern testnetと、限られたノードのみが参加できるlocal private testnetがあります。本稿では、local private testnetを使用します。

クライアント

Ethereumのクライアントには様々なものがあります。本稿では、Ethereum Foundationが開発している、Go言語で実装されたクライアントgo-ethereum(Geth)を使用します。Macであれば、HomebrewでEthereumをインストールすることで利用できます。

$ brew tap ethereum/ethereum
$ brew install ethereum
$ geth version
Geth
Version: 1.7.3-stable
...

テストネットワークの構築

ローカル環境にテストネットワークを構築するためには、データ保存用のディレクトリと、ブロックチェーンの最初のブロックの情報を記したGenesisファイルが必要になります。GenesisファイルはJSON形式で記述します。テストネットワークなのでdifficultyは小さめにしてあります。

genesis.json
{
  "config": {
    "chainId": 5,
    "homesteadBlock": 0,
    "eip155Block": 0,
    "eip158Block": 0
  },
  "difficulty": "0x4000",
  "gasLimit": "0x8000000",
  "alloc": {}
}

Gethを初期化します。データ保存用のディレクトリはtestnetとしました。

$ mkdir testnet
$ geth --datadir testnet init genesis.json 

アカウントの作成

本稿では、独自のコインを発行することが目的です。コインをやりとりするために、アカウントを二人分作成します。アカウントは、gethコマンドで作成できます。

$ geth --datadir testnet account new
Your new account is locked with a password. Please give a password. 
Do not forget this password.
Passphrase: foobar01 # パスフレーズは画面に表示されません。
Repeat passphrase: foobar01 # パスフレーズは画面に表示されません。
Address: {12507a4a19b059d19a45156669c5adf3a2beacdb}

同じようにして「foobar02」というパスフレーズで、二人目のアカウントを作成しました。

Etherの採掘

Ethereum上で実行するアプリケーションを(スマート)コントラクトと呼びます。コントラクトを実行するためには、Ethereum上の暗号通貨であるEtherで、手数料(Gas Fee)を支払う必要があります。そこで、ローカルプライベートネットワークで、Etherを採掘します。採掘したEtherは、ローカルプライベートネットワークでのみ使用可能です。

Etherの採掘は、Gethのコンソール上で行います。「netowrkid」は予約済みの0〜3以外の数値を指定して下さい。「nodiscover」は他ノードから見つからないようにします。「maxpeers 0」は他ノードと接続しないようにします。

$ geth --networkid 123 --nodiscover --maxpeers 0 --datadir testnet console

コマンドを実行すると、Javascriptベースのコンソールが起動します。採掘をはじめましょう。採掘中は沢山メッセージが出力されます。

> miner.start(1)
...

適当なところで、止めましょう。メッセージが出力されていても気にせず、コンソールに停止コマンドを入力します。

> miner.stop()
...
# しばらくメッセージが出ることがありますが、良きところで止まってくれます。

先ほど作成したアカウントの残高を確認します。単位がetherではなくweiなのでとても大きな額が表示されます。採掘はこれで完了です。

> eth.getBalance(eth.accounts[0])
# とても大きな額

コントラクト指向プログラミング言語Solidity

本稿では、コントラクトの実装に、Solidityを使用します。コントラクトを実装するためのプログラミング言語には、他にSerpentなどがありますが、おそらくSolidityが最も人気があり、ネットから情報を得やすいと思います。Solidityは、Javascriptに似た文法を持っています。Macであれば、Homebrewでインストールできます。

$ brew install solidity
$ solc --version
solc, the solidity compiler commandline interface
Version: 0.4.19+commit.c4cbbb05.Darwin.appleclang

Hello, world

サンプルプログラムとして「Hello, world」を表示してみましょう。greetメソッドを呼び出すと「Hello, world」という文字列を返すだけのシンプルなものです。

hello.sol
pragma solidity ^0.4.16;

contract HelloWorld {
  function greet() public pure returns (string) {
    return 'Hello, world';
  }
}

コンパイルと実行

コンパイルはsolcコマンドで行います。

$ solc --bin --abi hello.sol

======= hello.sol:HelloWorld =======
Binary: # 長いので省略します。実行時に必要です。
Contract JSON ABI
# 長いので省略します。JSONが出力されます。実行時に必要です。

実行はGethのコンソールで行います。

> personal.unlockAccount(eth.accounts[0])
Unlock account 0x12507a4a19b059d19a45156669c5adf3a2beacdb
Passphrase: # アカウント作成時のパスフレーズを入力します。
true
> data = '0x' + '上記のコンパイル時に出力されたBinaryの部分をコピペします。'
> abi = 上記のコンパイル時に出力されたJSONをコピペします。
> contract = eth.contract(abi)
> HelloWorld = contract.new({from: eth.accounts[0], data: data, gas: 1000000})
...
# 「address: undefined」となっている場合は、miner.start()で採掘します。
> miner.start(1)
> HelloWorld.address
"0xed736a6d40c96ddf35088cb695c5bdbb192d8990"
> miner.stop()
> HelloWorld.greet.call()
"Hello, world"

無事にHello, worldが表示されました。

独自コインの実装

ブロックチェーン上に作られた通貨を、一般にトークン(Token)と呼びます。Ethereum上に実装されるべきトークンの仕様については、EIP-20 Token Standardという文書にまとめられています(以前はERC20と呼ばれていました)。本稿では、これらの仕様を満たすのは大変なので、シンプルに送金だけを実装します。

mcoin.sol
pragma solidity ^0.4.16;

contract MCoin {
  /**
   * mappingは、ハッシュの様なもので、
   * balanceOfは、address型をkeyとして、
   * uint256型の残高を表すvalueを持つ。
   * address型はアカウントのアドレスを扱う。
   */
  mapping (address => uint256) public balanceOf;

  /**
   * いわゆるコンストラクタ
   * 送金元の最初の残高を設定する。
   */
  function MCoin(uint256 _initialSupply) public {
    balanceOf[msg.sender] = _initialSupply;
  }

  /**
   * コインを_toに_valueだけ送金する。
   */
  function transfer(address _to, uint256 _value) public {
    /* 送金元の残高を減らして(残高チェックなどは省略)、 */
    balanceOf[msg.sender] -= _value;
    /* 送金先に加える。 */
    balanceOf[_to] += _value;
  }
}

Hello, worldの時と同様にコンパイルして、バイナリデータとabiを得ます。

$ solc --bin --abi mcoin.sol

======= mcoin.sol:MCoin =======
Binary: # 長いので省略します。実行時に使用します。
Contract JSON ABI
# 長いので省略します。実行時に使用します。

Gethのコンソール上で実行します。

> personal.unlockAccount(eth.accounts[0])
Unlock account 0x12507a4a19b059d19a45156669c5adf3a2beacdb
Passphrase: # アカウント作成時のパスフレーズを入力します。
true
> data = '0x' + '上記のコンパイル時に出力されたBinaryの部分をコピペします。'
> abi = 上記のコンパイル時に出力されたJSONをコピペします。
> contract = eth.contract(abi)
# 初期値は100コインにしてみました。
> MCoin = contract.new(100, {from: eth.accounts[0], data: data, gas: 1000000})
...
# 「address: undefined」となっている場合は、miner.start()で採掘します。
> miner.start(1)
...
> MCoin.address
"0xb843c36d0928fe0f035d48c0b28510de098d5286"
> miner.stop()
# 初期値がセットされてるか確認します。
> MCoin.balanceOf.call(eth.accounts[0])
100
# 10コイン送金します。
> MCoin.transfer.sendTransaction(eth.accounts[1], 10, {from: eth.accounts[0]})
> miner.start(1)
...
> miner.stop()
# 送金結果を確認します。
> MCoin.balanceOf.call(eth.accounts[0])
90
> MCoin.balanceOf.call(eth.accounts[1])
10

無事に送金できました!!

おわりに

記事を書き終えた後で、類似の入門記事が幾つかあることに気づきました...環境やバージョンによって手順などが微妙に異なるということで、ネットの海を汚すことをお許し下さい。参考までにリンクを掲載しておきます。1. は、Ethereum公式のもので、本稿を書く上で参考にしました。本稿では、ターミナル上で進めてきましたが、IDEを使ってコントラクトを実行するのが良いみたいですね^^;

  1. Create a cryptocurrency contract in Ethereum
  2. How to create a token/coin on Ethereum
  3. 最小構成のEthereum Tokenのprivatenetへの登録
  4. EthereumにGethを使ってオレオレトークンを発行するまで

Ethereumは、Solidityでコントラクトを作成することにより、ブロックチェーン上で色々なことができるので、アイディア次第で新しいことができそうです。