LivenseAdventCalendar 学の5日目の記事としてネタをひねり出しました。
自分は投資の仮想通貨はあまり興味がないけれど(多分向いてない)、知り合いが随分とNEMに入れ込んでいて、話を聞いているうちにアドベントカレンダーのネタになるのでは?と思い、何かやってみるかという感じになりました。最初はNEMで何かやろうかと考えてましたが、コード書くならEthereumのほうが面白いのでは?という話を聞いて、よくわからないままとりあえず学んでみた、という記事になります。
イーサリアムとは?
- 仮想通貨の1種
- ワールドコンピュータ
- ダウンタイムなく何者にも干渉されずに、誰もが自由に利用できるプログラム
- ここのプログラムがコントラクトと呼ばれている
- コントラクトはSolidityという言語で記述できる(他にもあるがコレがメジャーっぽい)
仮想通貨とは?ブロックチェーンとは?イーサリアムとは?みたいな情報は他の方がブログに書いていたりするのでそっちを読んだほうがわかりやすいと思います。
入門する内容
- プライベートネットを作る
- マイニングしてみる
- アカウントを作って送金してみる
- コントラクトを作ってみる
まず環境構築
- ubuntu14を用意
- gethというethereumのgoで書かれたclientをインストール
- イーサリアムのネットワークをコンソールから操作できる
$ sudo add-apt-repository ppa:ethereum/ethereum
$ sudo apt update
$ sudo apt install geth
プライベートネットを作る
- まずプライベートネットの情報を格納するディレクトリと最初のブロックになるgenesis.jsonを作成します
- これらを使ってネットワークを作成
$ mkdir ~/private_net
$ touch ~/private_net/genesis.json
{
"config": {
"chainId": 15,
"homesteadBlock": 0,
"eip155Block": 0,
"eip158Block": 0
},
"difficulty": "0",
"gasLimit": "2100000",
"alloc": {
"7df9a875a174b3bc565e6424a0050ebc1b2d1d82": { "balance": "300000" }
}
}
$ geth ~/private_net init ~/private_net/genesis.json
これで初期化できたので、起動してみます
$ geth --datadir ~/private_net
マイニングしてみる
先程起動したネットワークに接続するようにもう一つタブを開き、コンソールを開きます
$ geth attach ~/private_net/geth.ipc
これでプライベートネットに接続できました
マイニングするにはアカウントが必要なので、まずアカウントを作成します
下の'test1'というのはパスフレーズになります。
> personal.newAccount("test1")
"0x61bef9d3f580b24a322f13994403c43564066686"
さっそくマイニングします。
実際にマイニングが成功しているかどうかはプライベートネットのログを見ればわかるのですが、コンソールからhashrateを確認することでも可能です。(0より大きい数字でマイニング進行中と判断できる)
> miner.start()
null
> eth.hashrate
10514
マイニングに成功したブロックを確認する
> eth.blockNumber
341
これで51ブロックマイニングしたことになります。このブロックは最初に作ったアカウントが所有していることになっています。
> miner.stop()
true
上記のコマンドでminerを止めておくこともできます。(今回の入門ではminerを最後まで止める必要はありません。)
送金してみる
先程マイニングしたブロックを他のアカウントに送金してみます。
送金するには2つ目のアカウントが必要になるので、先程と同じコマンドで2つ目のアカウントを作成します。
> personal.newAccount("test2")
"0x74816f2c208aee1c700dad88d9878a2f6050fd1e"
> eth.accounts
["0x61bef9d3f580b24a322f13994403c43564066686", "0x74816f2c208aee1c700dad88d9878a2f6050fd1e"]
2つ目のアカウントが作成されていますね。0のアドレスから、1のアドレスに対して送金します。
> eth.getBalance(eth.accounts[1]) // getBalanceで残高を確認できる
0
> eth.getBalance(eth.accounts[0])
1.705e+21
eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: 3})
Error: authentication needed: password or unlock
at web3.js:3143:20
at web3.js:6347:15
at web3.js:5081:36
at <anonymous>:1:1
ここで認証エラーが発生したので、送信元のアカウントのロックを解除する必要があります。
パスフレーズはアカウント作成時に引数として渡した文字列です。
> personal.unlockAccount(eth.accounts[0])
Unlock account 0x61bef9d3f580b24a322f13994403c43564066686
Passphrase:
true
これでもう一度送金を行います。
> eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: 3})
"0xf382985b7f32daa89fac156219f92a4a3a076c5cf5d2cf79b5b4817a526a1425"
トランザクションハッシュが帰ってきたら成功です。自分でマイニングしないとトランザクションを処理できないので、先程minerを止めていた場合はminerをstartしましょう。
> eth.getBalance(eth.accounts[1]) // 3増えている
3
> eth.getBalance(eth.accounts[0]) // 3と手数料分が減っている
1.724999999999999999997e+21
minerがトランザクションを処理すれば、送金は完了です。
コントラクトを作ってみる
今回はブロック状にデータを保存して呼び出すだけの簡単なコントラクトをsolidityで書いてみます。
まず、solidityのコンパイラであるsolcをインストールします。
$ sudo apt install solc
$ solc --version
Version: 0.4.19+commit.c4cbbb05.Linux.g++
さっそく実際にコントラクトを書いていきます。
$ vi Memo.sol
pragma solidity ^0.4.19;
contract Memo {
// string型でmsgという名前の変数を宣言(ブロックチェックに保存される)
string msg;
// recvMsgという変数でstringを受けとって、msgに保存する
function set(string recvMsg) {
msg = recvMsg;
}
// 単純にmsgを呼び出す
function get() returns (string) {
return msg;
}
}
上記のように書いたファイルをコンパイルします。
コントラクトの登録に必要なABIとBINとしてコンパイルします。
$ solc Memo.sol --abi --bin
======= Memo.sol:Memo =======
Binary:
6060604052341561000f57600080fd5b6102e38061001e6000396000f30060606040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680634ed3885e146100515780636d4ce63c146100ae575b600080fd5b341561005c57600080fd5b6100ac600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190505061013c565b005b34156100b957600080fd5b6100c1610156565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101015780820151818401526020810190506100e6565b50505050905090810190601f16801561012e5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b80600090805190602001906101529291906101fe565b5050565b61015e61027e565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101f45780601f106101c9576101008083540402835291602001916101f4565b820191906000526020600020905b8154815290600101906020018083116101d757829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061023f57805160ff191683800117855561026d565b8280016001018555821561026d579182015b8281111561026c578251825591602001919060010190610251565b5b50905061027a9190610292565b5090565b602060405190810160405280600081525090565b6102b491905b808211156102b0576000816000905550600101610298565b5090565b905600a165627a7a72305820166bd1ff67e0f4655fb2e73f95c63bbcd3ad284e57d2a05c13ed86bb3fe693ce0029
Contract JSON ABI
[{"constant":false,"inputs":[{"name":"recvMsg","type":"string"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"get","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]
コンパイルが成功したら、BINとABIをコピーして、gethのコンソールに変数として登録しておきます。
> bin = "0x6060604052341561000f57600080fd5b6102e38061001e6000396000f30060606040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680634ed3885e146100515780636d4ce63c146100ae575b600080fd5b341561005c57600080fd5b6100ac600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190505061013c565b005b34156100b957600080fd5b6100c1610156565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101015780820151818401526020810190506100e6565b50505050905090810190601f16801561012e5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b80600090805190602001906101529291906101fe565b5050565b61015e61027e565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101f45780601f106101c9576101008083540402835291602001916101f4565b820191906000526020600020905b8154815290600101906020018083116101d757829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061023f57805160ff191683800117855561026d565b8280016001018555821561026d579182015b8281111561026c578251825591602001919060010190610251565b5b50905061027a9190610292565b5090565b602060405190810160405280600081525090565b6102b491905b808211156102b0576000816000905550600101610298565b5090565b905600a165627a7a72305820166bd1ff67e0f4655fb2e73f95c63bbcd3ad284e57d2a05c13ed86bb3fe693ce0029"
undefined
> var abi = [{"constant":false,"inputs":[{"name":"recvMsg","type":"string"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"get","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]
undefined
先程コンパイルしたABIを使ってコントラクトオブジェクトを作成します。
(見やすさの都合上カットしてる部分あり)
> contract = eth.contract(abi)
{
abi: [{
constant: false,
inputs: [{...}],
name: "set",
outputs: [],
payable: false,
stateMutability: "nonpayable",
type: "function"
}, {
constant: false,
inputs: [],
name: "get",
outputs: [{...}],
payable: false,
stateMutability: "nonpayable",
type: "function"
}],
at: function(address, callback),
getData: function(),
new: function()
}
コントラクトをブロックチェーンに登録します。
認証でエラーが出た場合は先程と同様にアカウントアンロックする必要があります。
> Memo = contract.new({ from: eth.accounts[0], data: bin, gas: 1000000 })
{
abi: [{
constant: false,
inputs: [{...}],
name: "set",
outputs: [],
payable: false,
stateMutability: "nonpayable",
type: "function"
}, {
constant: false,
inputs: [],
name: "get",
outputs: [{...}],
payable: false,
stateMutability: "nonpayable",
type: "function"
}],
address: undefined,
transactionHash: "0x29326c5bf7cfec484447e731ff90eead31650864e82eeb3e0f74914beb217e19"
}
この時点ではアドレスが付加されておらず、コントラクトを利用できません。アドレスを付与するためには、コントラクト作成トランザクションをマイニングで処理する必要があります。minerが止まっている場合は、miner.start()してトランザクションを処理しましょう。
トランザクションの処理が完了すると、アドレスが付与されます。
これでコントラクトを作成することができました。
> Memo
{
abi: [{
constant: false,
inputs: [{...}],
name: "set",
outputs: [],
payable: false,
stateMutability: "nonpayable",
type: "function"
}, {
constant: false,
inputs: [],
name: "get",
outputs: [{...}],
payable: false,
stateMutability: "nonpayable",
type: "function"
}],
address: "0xfb44b33004a48f017e42d997a38463c48bcec69e",
transactionHash: "0x29326c5bf7cfec484447e731ff90eead31650864e82eeb3e0f74914beb217e19",
allEvents: function(),
get: function(),
set: function()
}
最後にコントラクトを実行してみます。
まず、何もブロックチェーンに登録していない状態でメモを取得します。
> Memo.get.call()
""
この時点では空の文字列が返されます。
それでは次に任意の文字列をセットしてみます。
> Memo.set.sendTransaction("アドベントカレンダー書く", { from: eth.accounts[0] })
"0xf6846187a4b587ba738cb39fdb962d9df80c195424bc5284162bbdf3cc56cd9a"
> Memo.get.call()
"アドベントカレンダー書く"
コントラクトを通して、setした文字列を取得できました。set関数を実行したときにもトランザクションハッシュが発行され、そのトランザクションはminerによって実行されています。
やってみて
時間がなくてSolidityについてあまり調べられなかったので、正直コントラクトを使って何ができるか理解できてません。とはいえ、ワールドコンピュータが言わんとしていることはプライベートネットを触ってみて腹落ちしてきました。イーサリアムはプログラマとして仮想通貨の中でも結構アツい部類に入ると思うので、仕組みとかちゃんと学びたいですね。
参考