# Ethereumハンズオン
- 2019.09.01
- WIDE合宿ワークショップ用に作成
- 文責:chike \<chike@sfc.wide.ad.jp>
## 環境構築
- 動作確認環境
- Docker version 19.03.1
- 以下のコマンドでdocker imageをダウンロード
- 1G程度あるので、太い回線or事前にやることをおすすめ
```
docker pull chike0905/gethhandson:latest
```
- 動作確認
- 以下のコマンドでコンテナが起動しshellに入る
```
docker run -it --rm chike0905/gethhandson:latest
```
- 事前インストールされているものの確認
```
testuser@02b40ce54874:~$ geth version
testuser@02b40ce54874:~$ solc --version
```
- `exit`で抜けられる
```
testuser@02b40ce54874:~$ exit
```
### コンテナ環境概要
`ubuntu:18.04`をベースに構築した。
リポジトリは[chike0905/gethhandson](https://hub.docker.com/r/chike0905/gethhandson)
- インストール済みパッケージ
- Geth 1.9. : Ethereum Go言語実装
- solc 0.5.11 : Ethereum コントラクト実装用言語コンパイラ
- tmux 2.6: 端末多重化ソフトウェア(ターミナル分割作業用)
- 筆者ホストとキーバインドを分けるために`Ctr-q`をキーバインドとして`/home/testuser/.tumx.conf`内で設定
- vim 8.0 : 軽量で最高のエディタ
- ハンズオン実施用ユーザ`testuser`作成済み
- `/home/testuser/`以下にハンズオンに用いるスクリプトなどが入っている
```
/home/testuser
├── eth_privnet : Ethereumデータ格納用ディレクトリ
│ ├── genesis.json : 初期パラメータ
│ ├── geth : 以下Ethereumデータたち
│ │ ├── chaindata
│ ...
├── scripts : 実習で扱うコマンドスクリプトたち
│ └── deploywidetoken.js
└── widetoken.sol : 実習で扱うコントラクトソースコード
```
## ノード初期化処理および立ち上げ
- Gethでテスト用プライベートネットワークを構築する
- ここのネットワークで入手したEther(Ethereum内部仮想通貨)は価値を持たない
- **Gethをそのまま立ち上げると全世界で動いている本番ネットワークに接続するので注意**
### データ格納ディレクトリとパラメータの確認
- コンテナ内`/home/testuser/eth_privnet`を実習用データ格納場所として準備している
- `eth_privnet/genesis.json`に初期パラメータが設定されている
```
{
"coinbase": "0x0000000000000000000000000000000000000001",
"difficulty": "0x400",
"extraData": "",
"gasLimit": "0xffffffff",
"nonce": "0x0000000000000042",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp": "0x00",
"alloc": {},
"config": {
"chainid": 2000,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 1,
"petersburgBlock": 0
}
}
```
### 初期化処理の実行
- すでにコンテナビルド時に一度初期化してあるが、実習のため再度初期化を行う
- 初期化コマンドパラメータ
- `--datadir`:初期化するノードのデータ格納場所
- このネットワークで何かするときは常にこのパラメータをつける(つけなければ本番用になる)
- `init /path/to/genesis.json`:初期化パラメータのあるjsonファイルの指定
```
testuser@2055a5494ed4:~$ geth --datadir eth_privnet init eth_privnet/genesis.json
```
- 正常に実行されていれば以下のようにディレクトリが作成されている
- パラメータに応じたGenesisブロックなどが作成されている
```
testuser@2055a5494ed4:~$ tree eth_privnet
eth_privnet
├── genesis.json
├── geth
│ ├── chaindata
│ │ ├── 000002.ldb
│ │ ├── 000003.log
│ │ ├── CURRENT
│ │ ├── CURRENT.bak
│ │ ├── LOCK
│ │ ├── LOG
│ │ └── MANIFEST-000004
│ └── lightchaindata
│ ├── 000002.ldb
│ ├── 000003.log
│ ├── CURRENT
│ ├── CURRENT.bak
│ ├── LOCK
│ ├── LOG
│ └── MANIFEST-000004
└── keystore
```
### 送金用アカウントを作成
- パスワードを聞かれるので設定する
- 送金時にこの鍵を使うためのパスワードになるので、実習中忘れないように
```
testuser@2055a5494ed4:~$ geth --datadir eth_privnet account new
```
- アカウントが作成され、鍵が格納されていることを確認する
```
testuser@2055a5494ed4:~$ tree eth_privnet
eth_privnet
├── genesis.json
├── geth
│ ├── chaindata
│ │ ├── 000002.ldb
│ │ ├── 000003.log
│ │ ├── CURRENT
│ │ ├── CURRENT.bak
│ │ ├── LOCK
│ │ ├── LOG
│ │ └── MANIFEST-000004
│ └── lightchaindata
│ ├── 000002.ldb
│ ├── 000003.log
│ ├── CURRENT
│ ├── CURRENT.bak
│ ├── LOCK
│ ├── LOG
│ └── MANIFEST-000004
└── keystore
└── UTC--2019-08-22T05-06-06.944978500Z--8aa6c3aa12a0d7f95238333d64a613c5f273c929
4 directories, 16 files
```
### Gethノードの立ち上げ
- Gethノードを以下のコマンドで立ち上げる
- パラメータ
- `--networkid networkid`:今回のプライベートネットのID
- 本番環境は1など主なID一覧は[ここを参照](https://ethereum.stackexchange.com/questions/17051/how-to-select-a-network-id-or-is-there-a-list-of-network-ids)
- `--mine`:起動と同時にマイニングを実行
- `--nodiscover`:他のノードを自律して検知・接続しない
- 今回はその辺の人に勝手に繋がっては困るので設定
- `--port portnum`:ノード同士の通信ポート
- `--nousb`: 今回はコンテナ内でusbデバイスが存在しないため設定
- `--ipcpath ~/.ethereum/geth.ipc`:IPCを行うための設定
- デフォルトでgeth consoleにattachする際に`~/.ethereum/geth.ipc`を見に行くため設定
```
testuser@2055a5494ed4:~$ geth --networkid 2000 --datadir eth_privnet --mine --nodiscover --port 30303 --nousb --ipcpath ~/.ethereum/geth.ipc
```
- 色々ログが出るが最後にMiningが始まっていれば正常に立ち上がっている
- `Ctrl + C`で終了
## Geth Consoleの基本操作
- ノードを立ち上げた状態で様々な操作を行うために専用コンソールを用いる
- ターミナルを複数立ち上げておく
- コンテナにtumxを導入しているのでそれを用いる場合
- 以下を実行しtmuxを起動
```
testuser@2055a5494ed4:~$ tmux
```
- `Ctr+q`を押してから`"`で画面が横に分割される
- 画面間の移動は`Ctr-q`からの`o`
### Geth consoleへの接続
```
testuser@2055a5494ed4:~$ geth attach
```
- `Welcome to the Geth JavaScript console!`とあるように、このコンソール上ではJavascriptが実行できる
- 色々テストするのに便利
### 基本操作(アカウント残高確認)
- `eth.accounts`でアカウントのリストが見える
- 今回はまだ先ほど作ったアカウントしかない
```
eth.accounts
```
- リストなので各要素を取り出して扱うことができる
- 以下は`eth.getBalance`で残高を確認している
```
eth.accounts[0]
eth.getBalance(eth.accounts[0])
```
- Geth Console上からもアカウントの作成が行える
- 先ほどと同様にパスワードを聞かれる
```
personal.newAccount()
```
- アカウントリストに追加されていることを確認
```
eth.accounts
```
### マイニングの実施
- マイニングを並列実行する
- マイニング開始コマンドの引数にスレッド数を指定できる
- geth起動を行ったターミナルタブをみるとDAG生成作業が行われてることが確認できる
```
miner.start(100)
```
- ログ上に以下のログが出ていればマイニングが成功している
```
INFO [08-22|05:24:17.126] Successfully sealed new block number=1 sealhash=da9d29…fab6b4 hash=b5cdb4…888b81 elapsed=7m20.279s
INFO [08-22|05:24:17.128] 🔨 mined potential block number=1 hash=b5cdb4…888b81
```
- アカウントの残高を確認してマイニング報酬が得られていることを確認する
```
eth.getBalance(eth.accounts[0])
```
- 一回マイニングを止めておく
```
miner.stop()
```
### Etherの送金
- 各アカウントの残高を確認
```
eth.getBalance(eth.accounts[0])
eth.getBalance(eth.accounts[1])
```
- 送金元アカウントをアンロック
- パスワードを聞かれるので、アカウント作成時に入力したパスワードを入力
```
personal.unlockAccount(eth.accounts[0])
```
- 送金の実施
- パラメータ(json形式で指定できる)
```
{
from: 送金元アカウント,
to: 送金先アカウント,
value: 送金金額(Wei単位で指定)
}
```
- 送金金額はWei単位(Etherの最小単位)で指定
- `web3.toWei(送金金額, "ether")`でEther -> Weiの変換ができる
- 以下のコマンドを実行すると、TX IDが変数`tx`の中に入る(コンソールのJavascript利用)
```
var tx = eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(5, "ether")})
```
- TX IDの確認
```
tx
```
- 残高の変化確認
- まだブロックチェーンに取り込まれていないので、残高は変化しない
```
eth.getBalance(eth.accounts[0])
eth.getBalance(eth.accounts[1])
```
- TXデータの閲覧
- TXIDを指定することでデータを閲覧することができる
- マイニングがまだ実行されていないので、`block`のパラメータが`null`になっている
```
eth.getTransaction(tx)
```
- ブロック作成のためのTXpoolにTXが格納されていることを確認
```
web3.txpool
```
- 数ブロックマイニングを実施する
- ログを見て数ブロックマイニングされたことを確認してから止める
```
miner.start(100)
miner.stop()
```
- TXのブロックチェーンへの格納確認
- 先ほどと同様のコマンドでTXの情報にブロックの情報が載っているのを確認する
- TXPoolにも入っていない
```
eth.getTransaction(tx)
web3.txpool
```
- ブロックチェーンの閲覧
- TX情報に格納されているブロックの情報が載っている
- TXの中の`blockHash`でブロックを指定して閲覧することができる
```
eth.getBlock(eth.getTransaction(tx).blockHash)
```
- 残高の変化確認
- 今回は残高が変化している
```
eth.getBalance(eth.accounts[0])
eth.getBalance(eth.accounts[1])
```
## ERC-20コントラクトの作成
- [ERC-20](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md): Ethereum上で新規通貨を実装するための標準規格
- 今回は準拠した`widetoken.sol`というソースコードを準備してある
- 独自言語である`Solidity`で書かれている
### コンパイル
- Ethereum上で実行できるようにコンパイルする
- ターミナル上で以下のコマンドでコンパイルできる
- 問題なければコンンパイルされたコードと、インターフェース情報であるABIが出力される
```
testuser@2055a5494ed4:~$ solc widetoken.sol --bin --abi
```
- 今回はgeth環境からロードするためにjsとして以下のように出力
```
testuser@2055a5494ed4:~$ echo var widetoken= >> widetoken.js
testuser@2055a5494ed4:~$ solc --optimize --combined-json abi,bin,interface widetoken.sol >> widetoken.js
```
### ブロックチェーン上へのデプロイ
- Geth Console上で先ほど出力した`widetoken.js`をロードする
- 変数`widetoken`の中にjsonでロードできたことが確認できる
```
loadScript("widetoken.js")
widetoken
```
- インターフェース情報、バイナリコードをそれぞれ変数に取り出す
- インターフェース情報はjson文字列なのでparseする
- バイナリコードは`0x`をつける決まりなので付与する
```
var widetokenabi = JSON.parse(widetoken.contracts["widetoken.sol:Widetoken"].abi)
var widetokenbin = "0x"+widetoken.contracts["widetoken.sol:Widetoken"].bin
```
- コントラクトオブジェクトの作成
- インターフェースを定義してあるので、このオブジェクトからコントラクト操作を行う
```
var widetokencnt = eth.contract(widetokenabi)
```
- TXを作成し、ブロックチェーンへ格納、デプロイを行う
- TXの中身を作成し、アカウントをアンロック
```
var deployTX = {"from": eth.accounts[0], "data": widetokenbin, "gas":569911}
personal.unlockAccount(eth.accounts[0])
```
- `widetokencnt.new(deployTX)`実行時にログに新規コントラクトのデプロイのためのTXが発行されたことが通知されている
- `widetokeninstance`の中身を確認すると、コントラクトの情報が入っている
- まだブロックの中に入っていないのでコントラクトアドレスはundefined
```
var widetokeninstance = widetokencnt.new(deployTX)
widetokeninstance
```
- 数ブロックマイニングを実行しデプロイTXをブロックチェーンへ格納させる
```
miner.start(100)
```
```
miner.stop()
```
- 正常にブロックに取り入れられるとトランザクションの実行結果が確認できるようになる
- `widetokeninstance`のアドレスも入っている
```
eth.getTransactionReceipt(widetokeninstance.transactionHash)
widetokeninstance.address
```
- 操作用のオブジェクトを作成し、コントラクトの内部変数を覗いてみる
- ソースコードで定義されたname`Widetoken`が見える
```
widetokencnt = widetokencnt.at(widetokeninstance.address)
widetokencnt.name()
```
### コントラクト操作の基本
- コントラクトの関数はコントラクトオブジェクトのメンバとしてアクセスできる
- 状態を変化させない閲覧のみの関数はTXを発行しなくても操作できる
```
widetokencnt.totalSupply()
```
```
widetokencnt.balanceOf(eth.accounts[0])
```
- 状態を変化させ、TXを作る場合、引数の末尾にTXのパラメータを持ったJsonオブジェクトを配置することで、指定アカウントからTXを作成することが可能
### 単純な送金:Transfer
- Widetokenの各アカウント残高を確認する
- `eth.accounts[0]`にはデプロイ時に付与されている
```
widetokencnt.balanceOf(eth.accounts[0])
widetokencnt.balanceOf(eth.accounts[1])
```
- コントラクト実行に必要な手数料を推定する
```
gas = widetokencnt.transfer.estimateGas(eth.accounts[1], 10, {"from": eth.accounts[0]})
```
- アカウントをアンロックし、コントラクト操作のためのトランザクションを発行
- トランザクションデータを閲覧すると、`input`に引数となるデータが格納されていることがわかる
```
personal.unlockAccount(eth.accounts[0])
```
```
tx = widetokencnt.transfer(eth.accounts[1], 10, {"from": eth.accounts[0], "gas":gas})
eth.getTransaction(tx)
```
- 数ブロックマイニングを実行しデプロイTXをブロックチェーンへ格納させる
```
miner.start(100)
```
```
miner.stop()
```
- トランザクションがブロックに格納されていることを確認し、Widetoken残高が変化したことを確認する
```
eth.getTransactionReceipt(tx)
widetokencnt.balanceOf(eth.accounts[0])
widetokencnt.balanceOf(eth.accounts[1])
```
### 他者への送金許可: Approve/Transferfrom
- 新規アカウントを作成する
```
personal.newAccount("")
eth.accounts
widetokencnt.balanceOf(eth.accounts[2])
```
- `eth.accounts[0]`から`eth.accounts[1]`に100WIDE自分の残高を送金する許可を出す
```
gas = widetokencnt.approve.estimateGas(eth.accounts[1], 100, {"from": eth.accounts[0]})
personal.unlockAccount(eth.accounts[0], "")
tx = widetokencnt.approve(eth.accounts[1], 100, {"from": eth.accounts[0], "gas":gas})
```
- 数ブロックマイニングを実行しデプロイTXをブロックチェーンへ格納させる
```
miner.start(100)
```
```
miner.stop()
```
- トランザクションがブロックに格納されていることを確認し、送金を許可されている金額を確認する
```
eth.getTransactionReceipt(tx)
widetokencnt.allowance(eth.accounts[0], eth.accounts[1])
```
- 現状の残高を確認しておく
- まだ送金されていないので変化していない
```
widetokencnt.balanceOf(eth.accounts[0])
widetokencnt.balanceOf(eth.accounts[1])
widetokencnt.balanceOf(eth.accounts[2])
```
- `eth.accounts[0]`から`eth.accounts[2]`への送金指示を`eth.accounts[1]`からTXを発行する
```
gas = widetokencnt.transferFrom.estimateGas(eth.accounts[0], eth.accounts[2], 100, {"from":eth.accounts[1]})
personal.unlockAccount(eth.accounts[1], "")
tx = widetokencnt.transferFrom(eth.accounts[0], eth.accounts[2], 100, {"from":eth.accounts[1], "gas":gas})
```
- 数ブロックマイニングを実行しデプロイTXをブロックチェーンへ格納させる
```
miner.start(100)
```
```
miner.stop()
```
- トランザクションがブロックに格納されていることを確認し、残高を確認すると送金が実施されていることがわかる
```
eth.getTransactionReceipt(tx)
widetokencnt.balanceOf(eth.accounts[0])
widetokencnt.balanceOf(eth.accounts[1])
widetokencnt.balanceOf(eth.accounts[2])
```
- 送金を許可されている金額を確認すると、すでに送金したため`0`に戻っていることがわかる
```
widetokencnt.allowance(eth.accounts[0], eth.accounts[1])
```
## 複数ノードでの操作
- ホストマシン上で以下のコマンドで別のコンテナを立ち上げる
```
docker run -it --rm chike0905/gethhandson:latest
```
- 「scripts」ディレクトリ内のスクリプトを実行する
- ノードの立ち上げ処理と、数ブロックのマイニングの実行をする
```
sh scripts/SetupAnotherNode.sh
```
- geth consoleにattachしておく
```
geth --networkid 2000 --datadir eth_privnet --mine --nodiscover --port 30303 --nousb --ipcpath ~/.ethereum/geth.ipc
```
```
geth attach
```
- 便宜上、今まで立ち上げてハンズオンを実施したコンテナを **Node1**、いま立ち上げたノードを**Node2**と呼ぶ
### ノードの状態確認
- 自分のノード情報を`admin.nodeInfo`で閲覧できる
```
admin.nodeInfo
```
- Node1とNode2それぞれで以下を実行すると異なる出力になる
```
eth.blockNumber
eth.getBalance(eth.accounts[0])
```
- Node1上の`eth.accounts[0]`のアドレスの残高をNode2上で確認する
- Node1
```
eth.accounts[0]
```
- Node2
```
eth.getBalance("Node 1で得られたアドレス")
```
- Peerノードの確認
- 現状まだ何も出力されない
```
admin.peers
```
### ノードの相互接続
- 接続のためのidが`admin.nodeInfo.enode`で閲覧できる
```
admin.nodeInfo.enode
```
- 一回**Node2**上でコンソールを抜け、IPアドレスを確認する
- gethコンソールの中断
```
exit
```
- IPアドレスの確認
- `eth0@~` のインターフェースにコンテナ同士を繋ぐネットワークのIPが設定されているはず
```
ip a
```
- 再度geth consoleにattachし、`admin.nodeInfo.enode`の値を確認する
- **Node1**上のgethコンソールで以下のコマンドを入力する
- Node2のenodeのIPアドレスを先ほど確認したIPに書き換える
```
admin.addPeer("Node2のenode出力中IPアドレス部分を書き換えたもの")
```
- 少し待つと、Node2の上のログに同期したことが通知される
```
INFO [09-02|02:22:13.805] Block synchronisation started
INFO [09-02|02:22:13.806] Mining aborted due to sync
INFO [09-02|02:22:14.635] Imported new chain segment blocks=3 txs=0 mgas=0.000 elapsed=810.330ms mgasps=0.000 number=3 hash=93f8e7…40593e age=34m47s dirty=732.00B
INFO [09-02|02:22:14.717] Imported new chain segment blocks=10 txs=5 mgas=0.730 elapsed=81.055ms mgasps=9.008 number=13 hash=c45fc1…29bc13 age=29m40s dirty=11.85KiB
```
### ブロックチェーン同期の確認
- 先ほど各ノード上の状態確認で打ったコマンドを打ってみると、Node2上で先ほどと出力が変わっている
- Node2上でマイニングされたブロックは取り消されたので、Node2のaccount[0]の残高は0になる
```
eth.blockNumber
eth.getBalance(eth.accounts[0])
```
- Node1上の`eth.accounts[0]`のアドレスの残高をNode2上で確認する
- Node1
```
eth.accounts[0]
```
- Node2
```
eth.getBalance("Node 1で得られたアドレス")
```
# 実行コマンド一括コピペ用
- 環境構築
```
docker pull chike0905/gethhandson:latest
docker run -it --rm chike0905/gethhandson:latest
```
- ノード初期化処理および立ち上げ
```
geth --datadir eth_privnet init eth_privnet/genesis.json
geth --datadir eth_privnet account new --password <(echo "")
geth --networkid 2000 --datadir eth_privnet --mine --nodiscover --port 30303 --nousb --ipcpath ~/.ethereum/geth.ipc
```
- Geth Consoleの基本操作
```
geth attach
```
- アカウント残高の確認
```
eth.accounts
eth.accounts[0]
eth.getBalance(eth.accounts[0])
```
- アカウントの新規作成
```
personal.newAccount("")
eth.accounts
```
- マイニングの実施
```
miner.start(100)
```
```
miner.stop()
```
- Etherの送金
```
eth.getBalance(eth.accounts[0])
eth.getBalance(eth.accounts[1])
personal.unlockAccount(eth.accounts[0], "")
var tx = eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(5, "ether")})
tx
eth.getBalance(eth.accounts[0])
eth.getBalance(eth.accounts[1])
eth.getTransaction(tx)
web3.txpool
miner.start(100)
```
```
miner.stop()
eth.getTransaction(tx)
eth.getTransactionReceipt(tx)
eth.getBlock(eth.getTransaction(tx).blockHash)
eth.getBalance(eth.accounts[0])
eth.getBalance(eth.accounts[1])
```
- ERC-20コントラクトの作成
- コンパイル
```
echo var widetoken= >> widetoken.js
solc --optimize --combined-json abi,bin,interface widetoken.sol >> widetoken.js
```
- デプロイ
```
loadScript("widetoken.js")
widetoken
var widetokenabi = JSON.parse(widetoken.contracts["widetoken.sol:Widetoken"].abi)
var widetokenbin = "0x"+widetoken.contracts["widetoken.sol:Widetoken"].bin
var widetokencnt = eth.contract(widetokenabi)
var deployTX = {"from": eth.accounts[0], "data": widetokenbin, "gas":569911}
personal.unlockAccount(eth.accounts[0], "")
var widetokeninstance = widetokencnt.new(deployTX)
widetokeninstance
miner.start(100)
```
```
miner.stop()
eth.getTransactionReceipt(widetokeninstance.transactionHash)
widetokeninstance.address
widetokencnt = widetokencnt.at(widetokeninstance.address)
widetokencnt.name()
```
- コントラクト操作
- Transfer
```
widetokencnt.balanceOf(eth.accounts[0])
widetokencnt.balanceOf(eth.accounts[1])
gas = widetokencnt.transfer.estimateGas(eth.accounts[1], 10, {"from": eth.accounts[0]})
personal.unlockAccount(eth.accounts[0], "")
tx = widetokencnt.transfer(eth.accounts[1], 10, {"from": eth.accounts[0], "gas":gas})
eth.getTransaction(tx)
miner.start(100)
```
```
miner.stop()
eth.getTransactionReceipt(tx)
widetokencnt.balanceOf(eth.accounts[0])
widetokencnt.balanceOf(eth.accounts[1])
```
- Approve/Transferfrom
```
personal.newAccount("")
eth.accounts
widetokencnt.balanceOf(eth.accounts[2])
gas = widetokencnt.approve.estimateGas(eth.accounts[1], 100, {"from": eth.accounts[0]})
personal.unlockAccount(eth.accounts[0], "")
tx = widetokencnt.approve(eth.accounts[1], 100, {"from": eth.accounts[0], "gas":gas})
miner.start(100)
```
```
miner.stop()
eth.getTransactionReceipt(tx)
widetokencnt.allowance(eth.accounts[0], eth.accounts[1])
widetokencnt.balanceOf(eth.accounts[0])
widetokencnt.balanceOf(eth.accounts[1])
widetokencnt.balanceOf(eth.accounts[2])
gas = widetokencnt.transferFrom.estimateGas(eth.accounts[0], eth.accounts[2], 100, {"from":eth.accounts[1]})
personal.unlockAccount(eth.accounts[1], "")
tx = widetokencnt.transferFrom(eth.accounts[0], eth.accounts[2], 100, {"from":eth.accounts[1], "gas":gas})
miner.start(100)
```
```
miner.stop()
eth.getTransactionReceipt(tx)
widetokencnt.balanceOf(eth.accounts[0])
widetokencnt.balanceOf(eth.accounts[1])
widetokencnt.balanceOf(eth.accounts[2])
widetokencnt.allowance(eth.accounts[0], eth.accounts[1])
```
- 複数ノードでの操作
- 新規ノードの立ち上げ
```
docker run -it --rm chike0905/gethhandson:latest
```
```
sh scripts/SetupAnotherNode.sh
```
```
geth --networkid 2000 --datadir eth_privnet --mine --nodiscover --port 30303 --nousb --ipcpath ~/.ethereum/geth.ipc
```
```
geth attach
```
- ノードの状態確認
```
admin.nodeInfo
eth.blockNumber
eth.getBalance(eth.accounts[0])
admin.peers
```
- Node1上の`eth.accounts[0]`のアドレスの残高をNode2上で確認する
- Node1
```
eth.accounts[0]
```
- Node2
```
eth.getBalance("Node 1で得られたアドレス")
```
- ノードの相互接続
- Node2
```
admin.nodeInfo.enode
```
```
ip a
```
- Node1
```
admin.addPeer("enode://...@"+IPaddrofNode2+":30303?discport=0")
```
```
admin.peers
```