BETA

イーサリアムのチュートリアルを実施する

投稿日:2019-09-10
最終更新:2019-09-10

環境構築

Go言語により実装された、CUIクライアント Geth(Go Ethereumu)環境を作成する。

マイニングデータなど色々ゴミが溜まるため、dockerで実行しております。
以下、簡易DockerFile

FROM ubuntu  

#LABEL maintainer="yokoyamashuuhei"  

# add-apt-repository not found -> requied software-properties-common  
RUN echo "now building" && \  
apt-get update && \  
apt-get install -y software-properties-common && \  
add-apt-repository -y ppa:ethereum/ethereum && \  
apt-get update && \  
apt-get install -y ethereum  

CMD echo "running"  

linuxやmacの場合は公式を参考に実行すればできると思います。
Gethをインストールする Ethereum入門

Genesisファイルの作成

Genesisファイルとは

ネットワークでやりとりされるブロックチェーンの最初(Block番号0)のブロックであるGenesisブロックを記述したファイル

# privateネットワーク管理用のディレクトリを作成 名前は何でもOK  
mkdir eth_private_net  
cd eth_private_net  
vim ./Genesis.json  

Genesis.json

{  
    "config": {  
        "chainId": 15,  
        "homesteadBlock": 0,  
        "eip155Block": 0,  
        "eip158Block": 0  
    },  
    "difficulty": "0",  
    "gasLimit": "2100000",  
    "alloc": {  
        "7df9a875a174b3bc565e6424a0050ebc1b2d1d82": { "balance": "300000" }  
    }  
}  

Genesis.json参考

EthereumのContractをSolidityで書いて遊んだ - Qiita
チュートリアルページのGenesis.jsonで実行したところ、トランザクションがマイニングでいつまで経っても取り込まれず、上記ページを参考しました。difficultyなどの問題?

イーサリアムの各種基本操作を実践する

  • Gethをプライベートネットワークで起動する
  • etherを発掘する
  • etherを送金する

Gethをプライベートネットワークで起動する

Genesisブロックの初期化

geth --nousb --datadir ./eth_private_net/ init ./eth_private_net/Genesis.json  

Gethの起動

geth --nousb --networkid "15" --nodiscover --datadir "./eth_private_net" console 2>> ./geth-err.log  
  • networkid "15": 練習用ネットワークの立ち上げ。0,1,2,3以外の値を設定する。Genesis.jsonで設定したchain_idと同一の値を設定する必要があります。network_idによりEthereum本番環境ネットワーク/テストネットワークなどが決まっている。
    主な Ethereum ネットワークの Network ID および Chain ID 一覧 - Qiita
  • nodiscover: 自動で同一ネットワークIDのノード(Peer)を探索しない。未知のノードとの接続を避けるため。privateネットワークでは自分のトランザクションだけ取り扱いたい。
  • --datadir: ブロック情報など利用するデータを格納するディレクトリ
  • console: gethのコンソールを起動する
  • --nousb: dockerの場合、usbドライバがなく必要だった。

etherの採掘

etherをprivateネットワークでマイニングします。
他者のトランザクションが無い状況で何故マイニングが永遠に進んでいくのかはわかっておりませんが、privateネットワークではそういう仕掛けになっているものと解釈し、進めました。

マイニングの報酬を受け取るアカウントの作成が必要になります。

DAGというデータを初期構造として持つようで、DAGの生成が終わるまである程度の時間マイニングを続ける必要があります。(10分もあれば終わると思っております。数時間以上かかる場合は、環境の見直しが必要になるかもしれません。)

アカウントの作成

アカウントにはEOAとContractが存在する。
EOA(Externally Owned Account): 一般的なアカウントで送金、コントラクトコードの実行などを行う。マイニングもこのアカウントにて実行する。

Contract: 一種のエージェント。EOAにより発行されたトランザクションをトリガーに、Contractアカウントが持つコントラクト・コードが実行される。
オブジェクト指向の状態を持つクラスのようなもの。コード実行で、Contractの持つフィールド情報も更新される。

EOAアカウントの作成

# アカウントの確認コマンド  
eth.accounts  
# EOAの作成 引数はパスワード(任意)  
personal.newAccount("password")  
# 2つアカウントを作成しておく  
personal.newAccount("password")  
# コインベースの確認コマンド マイニングの報酬を紐づけるEOAのアドレスをコインベースという  
eth.coinbase  
# コインベースの変更コマンド 確認したらaccounts[0]に戻しておく  
miner.setEtherbase(eth.accounts[1])  
eth.coinbase  
"0x979d8ecebefc45fd5331faa9587f2ed4984e95a4"  

マイニングの開始

# nullが返ってくるが正常 引数に利用するスレッド数を指定可能 例: miner.start(1)  
# 未指定時はいい感じに設定される  
> miner.start()  
null  
# マイニング状態の確認  
> eth.mining  
true  
# ブロック高の確認 ブロック高はマイニングされ、ブロックチェーンに取り込まれたブロック数  
> eth.blockNumber  
15  
# ハッシュレートの確認 マイニング速度の指標 詳細は割愛  
eth.hashrate  
# マイニング停止  
miner.stop()  

マイニングデータの保存先: $HOME/.ethash/

ブロックの内容を確認

# 引数にブロック番号を指定  
eth.getBlock(10)  

以下、主要な項目の概要 チュートリアルでは理解できてなくても大丈夫。

miner: マイニングしたEOAのアドレス
number: ブロック高
hash: ブロック・ヘッダ・ハッシュ ブロックヘッダ情報をSHA-3で暗号化した32byteのハッシュ値。ブロックを指すユニークなIDとなる。
ブロックデータ構造の中に含まれないため、ブロックヘッダの内容を元に計算する必要がある。
parentHash: 親ブロックのブロックヘッダハッシュ。ブロックのデータ構造に含まれる。

インセンティブ(報酬)の確認

# 単位はwei  
eth.getBalance(eth.accounts[0])  
# etherで表示  
web3.fromWei(eth.getBalance(eth.accounts[0]), "ether")  

etherを送金する

  • アカウントロックの解除
  • トランザクションの送信

アカウントロックの解除 & 送金

# アカウントロックの解除  
personal.unlockAccount(eth.accounts[0])  
eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(5, "ether")})  

トランザクション情報を調べる

マイニングにより、ブロックチェーンに取り込まれるまでは、blockNumberがnullになっている

# txidの部分にsendTransaction実行時に表示された、トランザクションidを渡す  
> eth.getTransaction("0x634ab46b67e80784d6161a62e1881d390b896456cab7529cb9d1f266431ebc5e")  
{  
  blockHash: null,  
  blockNumber: null,  
  from: "0x0182229474b84699cdcf23174769a8e968b3544a",  
  gas: 21000,  
  gasPrice: 1000000000,  
  hash: "0x634ab46b67e80784d6161a62e1881d390b896456cab7529cb9d1f266431ebc5e",  
  input: "0x",  
  nonce: 0,  
  r: "0x7b75d873eb55452955acd4a37e15551ed4fb05e99680bbbe9b42b06ab854f9bb",  
  s: "0x1f35ba23c7b61f69128a18ec2843fd68244fdbb3c4948a9ddffe564cf03dbafa",  
  to: "0x60a2f21513ccfdd5cc23323e985c755f5b47fe0b",  
  transactionIndex: null,  
  v: "0x42",  
  value: 5000000000000000000  
}  
  • blockHash: このトランザクションを含んだブロックのヘッダハッシュ
  • blockNumber: ブロック高 マイニングで承認されていない場合はnull
  • gas: 支払うべきGasの最大値。Genesisブロックで定義。
    勉強不足により、やや情報が曖昧です。
  • gasPrice: マイナーに支払う1gas当たりの手数料(wei)
  • from: 送信元EOAアドレス
  • to: 送信先EOAアドレス
  • value: 送金額(wei)

未送信トランザクションの確認

マイニングで取り込まれていないトランザクションを確認します。
このタイミングでは、マイニングを止めていれば確認可能です。

>eth.pendingTransactions  
[{  
    blockHash: null,  
    blockNumber: null,  
    from: "0x0182229474b84699cdcf23174769a8e968b3544a",  
    gas: 21000,  
    gasPrice: 1000000000,  
    hash: "0x634ab46b67e80784d6161a62e1881d390b896456cab7529cb9d1f266431ebc5e",  
    input: "0x",  
    nonce: 0,  
    r: "0x7b75d873eb55452955acd4a37e15551ed4fb05e99680bbbe9b42b06ab854f9bb",  
    s: "0x1f35ba23c7b61f69128a18ec2843fd68244fdbb3c4948a9ddffe564cf03dbafa",  
    to: "0x60a2f21513ccfdd5cc23323e985c755f5b47fe0b",  
    transactionIndex: null,  
    v: "0x42",  
    value: 5000000000000000000  
}]  

マイニングして、送信結果を確認する

# トランザクション反映のためマイニング開始  
miner.start()  
# 未送信トランザクションが無いことを確認  
> eth.pendingTransactions  
[]  

# 送信したトランザクションにblockNumberが反映されていることを確認   
eth.getTransaction("0x634ab46b67e80784d6161a62e1881d390b896456cab7529cb9d1f266431ebc5e")  
{  
  blockHash: "0xc9159dfdb7dfb9d643c795bcf7c7852a1f7286d80200a42d2c0a069e0b094de5",  
  blockNumber: 47,  
  from: "0x0182229474b84699cdcf23174769a8e968b3544a",  
  gas: 21000,  
  gasPrice: 1000000000,  
  hash: "0x634ab46b67e80784d6161a62e1881d390b896456cab7529cb9d1f266431ebc5e",  
  input: "0x",  
  nonce: 0,  
  r: "0x7b75d873eb55452955acd4a37e15551ed4fb05e99680bbbe9b42b06ab854f9bb",  
  s: "0x1f35ba23c7b61f69128a18ec2843fd68244fdbb3c4948a9ddffe564cf03dbafa",  
  to: "0x60a2f21513ccfdd5cc23323e985c755f5b47fe0b",  
  transactionIndex: 0,  
  v: "0x42",  
  value: 5000000000000000000  
}  

# 送信先アカウントに送金されていることを確認  
>web3.fromWei(eth.getBalance(eth.accounts[1]))  
5  

スマートコントラクトを作成し実行する

コントラクトの説明

イーサリアムのアカウントは2種類

  • EOA(Externally Owned Account)
  • Contract

Contractアカウントはオブジェクト指向のクラスのイメージ

Contractアカウントに対して、etherの送金およびコントラクト・コードの実行指示をすることができる

コントラクト実行までの流れ

  1. コントラクト・コードの実装
  2. コンパイル
  3. Contractアカウントの生成
    コンパイルしたコードをトランザクションに付加して送信→Contractアカウントのアドレスが払い出される
  4. Contractアカウントへトランザクションを発行する

Solidity

コントラクトコード = EVM Code

Solidityコンパイラ(solc)インストール

apt-get install solc  
>solc --version  
solc, the solidity compiler commandline interface  
Version: 0.5.11+commit.22be8592.Linux.g++  

バージョン番号は抑えておく。後で調べてもOK

単純なコントラクトコードの実装

グローバル変数のようなものに値を更新していくサンプルプログラム
通常はスレッドセーフに制御したり、トランザクションの順序性を管理する必要があるが、ブロックチェーンの特性で制御する

[SingleNumRegister.sol]

pragma solidity ^0.5.11; // solidity version number  

contract SingleNumRegister {  

  uint storedData;  
  function set(uint x) public{  
    storedData = x;  
  }  
  function get() public view returns (uint retVal) {  
    return storedData;  
  }  
}  

コンパイル

>solc --abi --bin ./SingleNumRegister.sol  
======= ./SingleNumRegister.sol:SingleNumRegister =======  
Binary:   
608060405234801561001057600080fd5b5060c68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c806360fe47b11460375780636d4ce63c146062575b600080fd5b606060048036036020811015604b57600080fd5b8101908080359060200190929190505050607e565b005b60686088565b6040518082815260200191505060405180910390f35b8060008190555050565b6000805490509056fea265627a7a72315820cb0c004a053cfbc586a86763f9e31ba3718884d986db2a9eeb6be1391b040b3564736f6c634300050b0032  
Contract JSON ABI   
[{"constant":false,"inputs":[{"internalType":"uint256","name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"internalType":"uint256","name":"retVal","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]  

Contractアカウントの作成

gethを起動する

var bin = "0x608060405234801561001057600080fd5b5060c68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c806360fe47b11460375780636d4ce63c146062575b600080fd5b606060048036036020811015604b57600080fd5b8101908080359060200190929190505050607e565b005b60686088565b6040518082815260200191505060405180910390f35b8060008190555050565b6000805490509056fea265627a7a72315820cb0c004a053cfbc586a86763f9e31ba3718884d986db2a9eeb6be1391b040b3564736f6c634300050b0032"  

var abi = [{"constant":false,"inputs":[{"internalType":"uint256","name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"internalType":"uint256","name":"retVal","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]  

 var contract = eth.contract(abi)  

# コントラクト生成時はアカウントのロックを解除する  
personal.unlockAccount(eth.accounts[0])  
var myContract = contract.new({ from: eth.accounts[0], data: bin })  

# マイニングで取り込まれる前はaddress: undefined  
> myContract  
{  
  abi: [{  
      constant: false,  
      inputs: [{...}],  
      name: "set",  
      outputs: [],  
      payable: false,  
      stateMutability: "nonpayable",  
      type: "function"  
  }, {  
      constant: true,  
      inputs: [],  
      name: "get",  
      outputs: [{...}],  
      payable: false,  
      stateMutability: "view",  
      type: "function"  
  }],  
  address: undefined,  
  transactionHash: "0x5abb3eb208c6b8761d5e3376337558d75e42b9ba62edaf0efe374bd9e45b87c9"  
}  

# マイニング後  
> myContract  
{  
  abi: [{  
      constant: false,  
      inputs: [{...}],  
      name: "set",  
      outputs: [],  
      payable: false,  
      stateMutability: "nonpayable",  
      type: "function"  
  }, {  
      constant: true,  
      inputs: [],  
      name: "get",  
      outputs: [{...}],  
      payable: false,  
      stateMutability: "view",  
      type: "function"  
  }],  
  address: "0x6a274ef663bb10aff93e6ea289a6622172c033aa",  
  transactionHash: "0x5abb3eb208c6b8761d5e3376337558d75e42b9ba62edaf0efe374bd9e45b87c9",  
  allEvents: function(),  
  get: function(),  
  set: function()  
}  

Contractアカウントへのアクセス

スマートコントラクトの実行に必要な情報
Contractのアドレス:
ContractのABI: APIリファレンス的な役割

構文

eth.contract(ABI_DEF).at(ADDRESS);  

コントラクトをトランザクションで送信する

var cnt = eth.contract(myContract.abi).at(myContract.address);  
# アカウントロック解除  
personal.unlockAccount(eth.accounts[0])  
# set functionをトランザクション送信することで実行する  
# チュートリアルに指定はなかったが、gas, gasPrice未指定ではエラーになった  
>cnt.set.sendTransaction(10, {from: eth.accounts[0], gas: 30000, gasPrice: web3.toWei(40, "gwei")})  
"0xa9a7233afac6fde10a9eae3df6f47b4f355d1c6c398bc9f8e9b6e1b87e53dcbe"  

結果の確認

> cnt.get()  
# 本当は10になる予定ですが・・・何故かうまくいかず  
0  

まとめ

最後のスマートコントラクトの反映がうまくいっておりません。
原因がわかる方ご教示頂けると嬉しいです。

チュートリアルの記事より時間が経過しているためか、色々チュートリアル通りではうまく
実行できないところもあり、まだ理解が浅い点が残ります。

利用したコマンドのまとめ

geth --nousb --datadir ./eth_private_net/ init ./eth_private_net/Genesis.json  

geth --nousb --networkid "15" --nodiscover --datadir "./eth_private_net" console 2>> geth-err.log  

eth.accounts  
personal.newAccount("password")  
eth.coinbase  
miner.setEtherbase(eth.accounts[1])  
miner.start(thread_num)  
eth.mining  
eth.blockNumber  
eth.hashrate  
miner.stop()  
eth.getBlock(10)  
eth.getBalance(eth.accounts[0])  
web3.fromWei(eth.getBalance(eth.accounts[0]), "ether")  
personal.unlockAccount(eth.accounts[0])  

eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(5, "ether")})    
eth.getTransaction("txid")  
eth.pendingTransactions  

contract = eth.contract(abi)  
myContract = contract.new({ from: eth.accounts[0], data: bin })  
eth.contract(ABI_DEF).at(ADDRESS);  
cnt = eth.contract(myContract.abi).at(myContract.address);  
cnt.set.sendTransaction(10, {from: eth.accounts[0], gas: 30000, gasPrice: web3.toWei(40, "gwei")})  
cnt.get()  

solc --abi --bin ./SingleNumRegister.sol  
技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

学習記録・ログ用

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう
目次をみる
技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
or 外部アカウントではじめる
10秒で技術ブログが作れます!