はじめに
こんにちは、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は小さめにしてあります。
{
"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」という文字列を返すだけのシンプルなものです。
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と呼ばれていました)。本稿では、これらの仕様を満たすのは大変なので、シンプルに送金だけを実装します。
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を使ってコントラクトを実行するのが良いみたいですね^^;
- Create a cryptocurrency contract in Ethereum
- How to create a token/coin on Ethereum
- 最小構成のEthereum Tokenのprivatenetへの登録
- EthereumにGethを使ってオレオレトークンを発行するまで
Ethereumは、Solidityでコントラクトを作成することにより、ブロックチェーン上で色々なことができるので、アイディア次第で新しいことができそうです。