##概要:
前回Raspberry piにgeth(go-ethereum)をインストールしたので複数台を用意してプライベートネット環境を構築してみます。
Raspberry piを使用しているのでLCDも接続して文字列を表示しようと思います。
raspberry piのLCD制御はpythonが簡単なので、web3ライブラリは現在開発が進められているpythonベースのweb3.pyを使います。
##確認の流れ:
プライベートネットワークの作成にあたり、段階を踏んで次の確認をしてみます。
1.各ノードの追加。同期
↓
2.プライベートネットワークへのコントラクトのデプロイ
↓
3.Gethコンソール上で、コントラクトの参照・実行
↓
4.Web3.pyを使用してコントラクトの参照・実行・イベント監視
↓
5.Web3.py からコントラクトを実行し、参照結果を LCDに表示
##結果:
- コントラクトが実行されたときに、Raspberry pi経由でLCDに表示されるところまで確認しました。
- Raspberry piでブロックの生成のためマイニングをしたら、DAGファイル作成時にエラーが出力したので断念しました。代用の端末でブロックは生成することにします。
- web3.pyはなんとかRaspberry piにインストールできましたが、色々と依存関係で苦労したので注意が必要だと思います。
それとライブラリの読み込みには時間がかかるので、頻繁にトランザクションを送信する場合は別の手段を考えたほうがいいかも知れないです。(インストール方法は補足3に記載しました)
##環境
使用する環境は下記の3台使用します。
3台ともにgeth1.7.2を利用
名称 | IPアドレス | 備考 |
---|---|---|
RAS1 (1号機) | 192.168.100.150 | Raspberry pi modelB (512MB) 8GB |
RAS2 (2号機) | 192.168.100.160 | Raspberry pi modelB (512MB) 8GB |
Terminal (3号機) | 192.168.100.100 | Ubuntu16.04 LTS |
RAS1は、コントラクトにトランザクションを送付するノードとして用意します。
RAS2は、コントラクトの実行結果を参照し、LCDにプライベートネット上のブロック番号等を表示させます。
(LCDは秋月で売っている「SC1602BBWB-XA-LB-G」を使用します。)
※Terminal (3号機)はデスクトップPCです。ブロック生成用のマイニングとコントラクトコード作成のために使用しました。
(本当はRAS1,RAS2でマイニングをしたかったのだけど、メモリ領域が不足しエラーがでたので代用してます。)
###Web3ライブラリ:
下記のバージョンを使用します。ベータ版です。頻繁にバージョンが変わるため、いつも苦しめられます。
- Web3.py version4.0(Beta)
##[Step1] genesis.jsonを作成する。
さっそくgeth(go-ethereum)でプライベートネットワーク用のブロックチェーンを生成する準備をします。
go-ethereumのドキュメントに従ってgenesis.jsonを作成してみます。(RAS1, RAS2,Terminalそれぞれ起点のブロックを一致させないといけないので、同じgenesis.jsonを作成する必要があります。)
{
"config": {
"chainId": 65000,
"homesteadBlock": 0,
"eip155Block": 0,
"eip158Block": 0
},
"alloc" : {},
"coinbase" : "0x0000000000000000000000000000000000000000",
"difficulty" : "0x20000",
"extraData" : "",
"gasLimit" : "0x2fefd8",
"nonce" : "0x0000000000000042",
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp" : "0x00"
}
- "config"はハードフォーク対象のブロック設定(補足1参照)
- "alloc"は設定したアドレスにETH残高を最初から設定するもの。(補足2参照)
- genesis.jsonのchainIDはgeth起動時のオプションnetworkidと一致させておく必要があります(ここでは65000)。
私の場合、そうしておかないとコントラクトをデプロイする際にエラーが出てしまいました。(error:invalid sender)。
##[Step2] チェーンデータを初期化する。
genesis.jsonができたら、RAS1,RAS2,Terminalそれぞれにプライベートネット用のディレクトリを作成し、
genesis.jsonから起点ブロックの初期化を行います。
$ mkdir private_net
$ geth --datadir private_net init genesis.json
##[Step3]ノード情報を追加する
###ノード情報の確認
RAS1,RAS2,Terminalが、互いのノードを接続させるため、それぞれのノード情報の確認が必要です。
gethを起動し、コンソール上から自身のノードの情報を確認します。
(ここにつけてるオプションはremix(browser solidity)など利用する際に必要になるので残しておきます)
$ geth --datadir private_net -networkid 65000 --rpc --rpcaddr "localhost" --rpcport "8545" --rpcapi eth,web3,net,personal --rpccorsdomain "*" --nodiscover console
~
>admin.nodeInfo.enode
(例)
"enode://98f0a491691fc60c56158e98fd48334533c63c1b61973acdcd2adbcd269f0e922959875a292b945a8f459fd30572b1e33f2baeb093a063e4a01ec31e67a21091@[::]:30303"
上記の結果の"enode://~[::]:30303"が、ノード情報になり、他のノードが接続するために必要な情報です。
[::]にはIPアドレスが入ります。
###ノードの追加
一旦、gethを終了し datadirで指定したPATH内にstatic-nodes.jsonを作成します。
RAS1,RAS2,Terminalのノード情報とそれぞれのIPアドレスを次のように記録します。
それぞれ作成が終わったら、gethを再起動します。
[
"enode://6438470a92c76d066a54f64eb1c7ad67a52f9fc03b9b3b9764c94447f0b0b285c003be734d66eac4932785075c62275b5b5f012501cc046133d7c041bc976bfc@[192.168.100.100]:30303?discport=0",
"enode://c073c89226a20ddb833a87fcf195fd722d2232a260dd8bb32c743c29edb1e6f613ec1f91f80762f95bbcd8f216ff26f088e5316fc7888eab8a0ae4ef5fd9f037@[192.168.100.150]:30303?discport=0",
"enode://d33d099e425c016ffa5cfa7019f1b6a01ba4115123295bef08b25104b3d5f81ff6069bac0a5a10fb5f1df2e4acb5a98ad250ce7a67c508ef9646c6ae8fc9b65b@[192.168.100.160]:30303?discport=0"
]
この処理でRAS1,RAS2,Terminalにそれぞれのノードにプライベートネットワークに参加するノードの情報を教えてあげることができます。
###(参考)
ちなみに、gethコンソール上からピア追加ができます。例えば、RAS1からRAS2のノードを手動で追加する場合は次のようにしてあげます。
>admin.addPeer("enode://d33d099e425c016ffa5cfa7019f1b6a01ba4115123295bef08b25104b3d5f81ff6069bac0a5a10fb5f1df2e4acb5a98ad250ce7a67c508ef9646c6ae8fc9b65b@[192.168.100.160]:30303")
###ノード接続状況の確認
各ノードに互いの情報を教えてあげたあとは、それぞれのノードが互いに接続しあっているか確認してあげる必要があります。
たとえばRAS1のコンソールでnet.peerCout, admin.peersをそれぞれ入力します。
>net.peerCount
1
>admin.peers
[{
caps: ["eth/63"],
id: "bd56a8fb6a2448ce26c5f43414e9cec9a08def16d67249c0dcf61f07c8bc5cbd0291cfc5d19773eff1bae09f491644aff93c40ad2049372ffe9e2cc475779aa7",
name: "Geth/v1.7.2-stable-1db4ecdc/linux-arm/go1.9",
network: {
localAddress: "192.168.100.150:51190",
remoteAddress: "192.168.100.160:30303"
},
protocols: {
eth: {
difficulty: 131072,
head: "0x5e1fc79cb4ffa4739177b5408045cd5d51c6cf766133f23f7cd72ee1f8d790e0",
version: 63
}
}
}]
結果として、RAS1とつながっているノードは1件(ここではRAS2とつながっている想定)で
RAS2のノード情報が確認できます。
(本当であればstatic-nodes.jsonにTerminal分のノード情報も入れているので、その情報も
出てきます。今回は説明を簡略化するためTerminalの情報は省いています。)
##【Step4】同期の開始
RAS1,RAS2,Terminalそれぞれプライベートネットワークに繋がっていたとしても、同期されているかも確認が必要です。
Terminal側でブロック生成のためマイニングを行ってみます。
ブロックを生成できると、報酬であるETHが取得できるので、ETH格納用のアカウントを作成します。
アカウント生成後ブロックを生成します。
> personal.newAccount()
Passphrase:
Repeat passphrase:
"0xc0d61b692fbc5d1343ba8f1fefe0ab11c4dbbf71"
>miner.start()
~
このとき、RAS1,RAS2もTerminalのマイニングに合わせてメッセージが出力されます。
プライベートネットワーク上で、「新しいブロックができたよ」って通知がされます。
INFO [11-12|10:45:27] Imported new chain segment blocks=5 txs=0 mgas=0.000 elapsed=785.115ms mgasps=0.000 number=16 hash=45eed6…da426c ignored=11
それぞれのノードが同期完了しているか確認します。
Terminal,RAS1,RAS2それぞれ、コンソール上で「eth.syncing」を入力します。
※syncingはノード上で格納しているブロック(currentBlock)とネットワーク上で知られている最新ブロック(higestBlock)等との同期情報を取得するのに使います。
ノードが同期していない場合は次のような結果が出力されます。
currentBlockとhighestBlockが一致していないことがわかります。
> eth.syncing
{
currentBlock: 1472,
highestBlock: 1512,
knownStates: 0,
pulledStates: 0,
startingBlock: 1472
}
ノードが最新のブロックに同期している場合はfalseが出力されます。
falseが出力されるのは同期がそもそも始まってないケースもあるので注意しないといけません。
> eth.syncing
false
ノードがもつブロック番号も確認してみます。同期が完了していればRAS1,RAS2,Terminalそれぞれのブロック番号が同じ値になっていると思います。
> eth.blockNumber
1512
##【Step5】コントラクトの作成
各ノードの接続とブロックの同期の確認が取れたら、次にコントラクトをプライベートネットワーク上に展開してみます。
コントラクト作成はRemix(Browser-solidity)と呼ばれるブラウザ上でコントラクトを作成するサービスを使用してみます。
http://remix.ethereum.org/
すでに立ち上げられているTerminalのgethと連動させるため、下図のように"Environment"を「Web3 Provider」にセットしましょう。
localhostにつなぐか聞かれるので、OKにしておきます。
ブラウザのエディタ上でコントラクトを作成してみます。
pragma solidity ^0.4.18;
contract SimpleStorage {
uint storedData;
event Set(address from,uint stored);
function set(uint x) public{
storedData = x;
Set(msg.sender,x);
}
function get() public constant returns (uint retVal) {
return storedData;
}
}
あと、コントラクトの参照や実行、監視をする際に、必要になってくるので、ざっくり何が書いてあるかを確認します。
作成したコントラクの関数は2つあり、set,getそれぞれ記載しています。
set() :storeDataに値を格納する。
get() :storeDataの値を返す。
また、このコントラクトにはイベントも仕込まれています。
Set(): 関数set()中に記載されてます。
set()が実行される際にイベントとして通知されるものです。
コントラクトは作成しましたが、まだネットワークに展開していません。
Terminal側で作成する。Terminal側のethアカウント(coinbase)のロックを解除しておきます。
> personal.unlockAccount(eth.accounts[0])
Createボタンを押してみます。
Createボタンを押すと、gethのコンソール上に次のようなメッセージがでます。
(例)
INFO [11-12|10:58:35] Submitted contract creation fullhash=0x1539ef69391c6dae1c9f00fff40ba2e1591be5b18cebb8edbf09263ea5691cf4 contract=0x69c740ee06dc00Fc55cF05e2637FeB41D41EE07c
ざっくりですが「コントラクトがネットワークに送信されました。コントラクトアドレスは"0x69~"」って読めます。
0x69c740ee06dc00Fc55cF05e2637FeB41D41EE07c
※Createボタンを押しても、エラーが出る場合はgeth起動時のオプションが適切に設定されていない可能性があります。Terminal側で起動していた、gethの起動オプションで次の点を確認すると解決する場合があります。
- "-networkid"がgenesis.jsonの"chainId"と一致しているか。
- "--rpccorsdomain"が設定されているか。
$geth --datadir private_net -networkid 65000 --rpc --rpcaddr "localhost" --rpcport "8545" --rpcapi eth,web3,net,personal --rpccorsdomain "*" --nodiscover console
Terminal側のコンソールでマイニングを行い、プライベートネットワーク上のコントラクトを組み込んだブロックを作成します。
>miner.start()
Terminal側からマイニングでブロックを作成し、コントラクトをプライベートネットワークに展開できました。
##【Step6】コントラクトのABI情報を確認する。
さきほどはTerminal側でコントラクトを作成、デプロイできました。
Terminal側からでもいいですが、他のノードからもコントラクトを実行するためにabi情報を調べておきます。
BrowserSolidity上から
"Compile"⇛"Details"を選択してみます。
その後、次のように表示されるウィンドウからINTERFACE-ABIを選択し、ABI情報をコピーしておきます。
下のような文字列(abi)が取得できます。
[{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"retVal","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]
##【Step7】コンソール上でコントラクトを実行する。
gethのコンソール上でコントラクトを呼び出してみます。
ここではRAS1のgethコンソール上でコントラクトアドレス(address)とabi情報を入力し、コントラクトとabiインタフェースの関連付けを行います。
コントラクトのアドレスを変数"address"として定義します。
>var address="0xdA01D4AA02dEccb97462c4071A966664617ceE87"
undefined
>address
"0xdA01D4AA02dEccb97462c4071A966664617ceE87"
コントラクトのabi情報を変数"abi"として定義しておきます。
> var abi=[{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"retVal","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]
undefined
> abi
[{
constant: false,
inputs: [{
name: "x",
type: "uint256"
}],
name: "set",
outputs: [],
payable: false,
stateMutability: "nonpayable",
type: "function"
}, {
constant: true,
inputs: [],
name: "get",
outputs: [{
name: "retVal",
type: "uint256"
}],
payable: false,
stateMutability: "view",
type: "function"
}]
abiと関連付けたコントラクトアドレスをコンソール上にコントラクトとして定義しておきます。
>var contract=eth.contract(abi).at(address);
undefined
> contract
{
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: "0xdA01D4AA02dEccb97462c4071A966664617ceE87",
transactionHash: null,
allEvents: function(),
get: function(),
set: function()
}
###ETHの送信
トランザクションを送信するには、トランザクションを送信するノード側でgasが必要になります。
RAS1側にはETHがないのでgasが足りません。 トランザクションを送付するのにETHが必要です。
Terminal側でマイニングして得たETHから、RAS1に1ETH分送付してあげます。
(RAS1側もTerminal側で作成したようにeth.newAccountでアカウントを作成しておきます。)
※Terminal側のアカウントをRAS1側にインポートすることでも対処できると思います)
> personal.unlockAccount(eth.accounts[0])
Unlock account 0x340019208fe2dae8a710e613cff2902fb99e25d5
Passphrase:
true
>eth.sendTransaction({from:eth.coinbase,to:"0xe60210acda3b8897a689062bdee0cf25e112dc2f",value:web3.toWei(1,"ether")})
INFO [11-14|23:13:02] Submitted transaction
fullhash=0xd55c264413d23c1b069c3d5f98562492c442607f27d80d109836aeff19595add recipient=0x0D25F3c554B7Ffeb70586a973522B66DC1C77249
"0xd55c264413d23c1b069c3d5f98562492c442607f27d80d109836aeff19595add"
###コントラクトの実行
ようやくRAS1側でトランザクションを送付する準備ができました。
Step5で作成したコントラクトの概要でも記載したように、作成したコントラクトはset()を定義しています。
例えば値"92"をコントラクトのstoredataにsetするためには
次のように、コマンドを投入します。
"from:eth.accounts[0]"の記載は「RAS1側で持ってるアカウントからトランザクションを送信するよ」ってことですね。
>contract.set.sendTransaction(92,{from:eth.accounts[0]})
INFO [11-14|22:06:18] Submitted transaction fullhash=0xe17ba3c12732be3a547b9bd5441e9977cbd7185a16bea4568a0b59dbff580091 recipient=0xdA01D4AA02dEccb97462c4071A966664617ceE87
"0xe17ba3c12732be3a547b9bd5441e9977cbd7185a16bea4568a0b59dbff580091"
新しいブロックが生成されないと、コントラクトの結果が反映されません。
そのため、Terminal側でマイニングをしてみます。
> miner.start()
INFO [11-14|22:06:27] Updated mining threads threads=0
INFO [11-14|22:06:27] Transaction pool price threshold updated price=18000000000
INFO [11-14|22:06:27] Starting mining operation
null
INFO [11-14|22:06:27] Commit new mining work number=42 txs=1 uncles=0 elapsed=511.587µs
INFO [11-14|22:06:27] Successfully sealed new block number=42 hash=9fbcdb…1c4af7
INFO [11-14|22:06:27] 🔨 mined potential block number=42 hash=9fbcdb…1c4af7
RAS1からコントラクトアドレスを指定し、set()を実行しました。
実行結果はTerminal側で生成する最新のブロックに反映させれます。
###コントラクトの参照
上述したコントラクトのset()の実行結果を、RAS1側で見てみましょう。
set()はstoreDataに値を格納していましたが、storeDataの値を返すのはget()でした。
そしてコントラクトでも、実行結果を参照するだけであれば、sendTransactionは使用せず
callメソッドを使います。
> contract.get.call()
92
作成したコントラクトのset()で登録した値が表示されたことが確認できました。
RAS1側で、コントラクトの実行結果を参照できました。
##【Step8】Web3ライブラリを使用してコントラクトを実行する。
毎度コンソール上でコントラクトを実行するのは面倒です。pythonでスクリプトを組んでおき
コントラクトの実行、参照、監視をできるようにしておきます。
###コントラクトの参照
python上で、コントラクト上のメソッドを参照する際は次のようにします。
gethコンソールで確認したように、コントラクトアドレスとAPIを紐付けしておき、callメソッドでコントラクトで定義したget()を実行します。
from web3 import Web3,HTTPProvider,IPCProvider
import json
web3 = Web3(HTTPProvider('http://localhost:8545',request_kwargs={'timeout': 60})
contractAddress="0x69c740ee06dc00Fc55cF05e2637FeB41D41EE07c"
abi_str='[{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"retVal","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"from","type":"address"},{"indexed":false,"name":"stored","type":"uint256"}],"name":"Set","type":"event"}]'
abi=json.loads(abi_str)
contract = web3.eth.contract(contractAddress,abi=abi)
i=contract.call().get()
print(i)
※ちなみに、raspberry pi上でweb3ライブラリを実行する場合、ライブラリの読み込みや処理に時間がかかります(10秒程)。rpc経由でのリクエストタイムアウト値が10秒であることから、エラーが発生することがありました。そのため、下記のようにrpcのタイムアウト値を60秒程に変更してあげています。
web3 = Web3(HTTPProvider('http://localhost:8545',request_kwargs={'timeout': 60})
###コントラクトの実行
gethコンソールで確認したように、set()の実行はトランザクションの送信を要するので
スクリプト上では、アカウントのアンロック⇒トランザクションの送信という流れで実行します。
from web3 import Web3,HTTPProvider,IPCProvider
import json
web3 = Web3(HTTPProvider('http://localhost:8545'))
contractAddress="0xdA01D4AA02dEccb97462c4071A966664617ceE87"
abi_str='[{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"retVal","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]'
abi=json.loads(abi_str)
contract = web3.eth.contract(contractAddress,abi=abi)
i=contract.call().get()
web3.personal.unlockAccount(web3.eth.accounts[0],"password");
contract.transact({'from': web3.eth.accounts[0]}).set(i+1)
トランザクション送信後もネットワークに反映させるためにはブロックを生成する必要があるため、マイニングは必須になります。
web3.pyを利用しRPC経由でマイニングを行う場合(例えば10秒だけ)。
from web3 import Web3,HTTPProvider,IPCProvider
from web3.contract import ConciseContract
import json
import time
web3 = Web3(HTTPProvider('http://localhost:8545'))
web3.miner.start(2)
time.sleep(10)
web3.miner.stop()
↑を実行するためにはgeth 起動時にRPCを利用できる設定にしておきます。
(--rpcapiのオプションにminerをつける)
###コントラクトのイベント監視する。
RAS1に接続したLCDがコントラクト上のset()が実行される度に表示を切り替えるようにしたいです。
作成したコントラクトが実行されたことを検知するイベント監視方法を考えます。
Step5に記載したとおり、コントラクト上にイベントSetを仕込んでいました。
これを利用することでコントラクト実行時(ここではset() )にイベント通知可能なフィルタを設定することができます。(0x340019~のアドレスから実行されたときだけ通知)
transfer_filter = contract.eventFilter('Set',filter_params ={'filter':{'from':"0x340019208fe2dae8a710e613cff2902fb99e25d5"}})
設定しているフィルタに変化があったときに値が格納されます。
a=web3.eth.getFilterChanges(transfer_filter.filter_id)
web3.py上でのイベント監視は次のようにしてみます。
from web3 import Web3,HTTPProvider,IPCProvider
import json
import time
web3 = Web3(HTTPProvider('http://localhost:8545'))
contractAddress="0x69c740ee06dc00Fc55cF05e2637FeB41D41EE07c"
abi_str='[{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"retVal","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"from","type":"address"},{"indexed":false,"name":"stored","type":"uint256"}],"name":"Set","type":"event"}]'
abi=json.loads(abi_str)
contract = web3.eth.contract(contractAddress,abi=abi)
transfer_filter = contract.eventFilter('Set',filter_params ={'filter':{'from':"0x340019208fe2dae8a710e613cff2902fb99e25d5"}})
while True: #終了しないように待機する。
time.sleep(1)
a=web3.eth.getFilterChanges(transfer_filter.filter_id)
if not a:
print("null")
else:
for index in range(len(a)):
address=a[index]['address']
blockNum=a[index]['blockNumber']
blockHash=a[index]['blockHash']
transactionHash=a[index]['transactionHash']
print(address)
print(blockNum)
print(blockHash)
print(transactionHash)
##【Step9】Raspberry pi起動時にgethを自動で起動するようにする。
Raspberry piも起動したときに、gethを起動しておくようにします。
RaspbianもjessieでSystemdが使えるみたいなので、それを活用します。
/etc/systemd/system 配下にgeth_private.serviceを作成してみます。
[Unit]
Description = go-ethereum
[Service]
ExecStart=/usr/local/geth/geth --datadir /home/pi/private_net -networkid 65000 --rpc --rpcaddr "localhost" --rpcport "8545" --rpcapi eth,web3,net,personal --rpccorsdomain "*" --nodiscover
Restart=always
Type=simple
User=pi
Group=pi
[Install]
WantedBy=multi-user.target
登録状況を確認してみます。 disabled(利用不可)になっています。
$ systemctl list-unit-files --type=service|grep geth_private
geth_private.service disabled
サービスを有効にして起動してみます。
$ sudo systemctl enable geth_private
$ sudo systemctl start geth_private
raspberry piの再起動後、gethが起動出来ていることを確認します。
$ ps -aux|grep geth
pi 419 4.5 5.1 885232 22976 ? Ssl 22:01 0:01 /usr/local/geth/geth --datadir /home/pi/private_net -networkid 65000 --rpc --rpcaddr localhost --rpcport 8545 --rpcapi eth,web3,net,personal --rpccorsdomain * --nodiscover
すでに裏で動いているgethのコンソールを開きたい場合は次のようにします。
$ geth attach ipc:/home/pi/private_net/geth.ipc
##Step10 コントラクトの実行を監視して、結果をLCD表示する。
ほぼ、プライベートネットワークの環境も仕上がったので、最後にコントラクトの実行結果を監視しながら、LCDを表示してみます。Step8のコントラクトのイベント監視がベースになります。
LCD表示については補足4に記載しています。
全体の記述の流れは次のようになります。
- LCD表示のための設定
- イベント監視のためのフィルタ設定
- イベント監視
- イベント通知があったらLCDに表示
from web3 import Web3,HTTPProvider,IPCProvider
import json
import time
import Adafruit_CharLCD as LCD
#LCD表示のための設定
lcd_rs = 14
lcd_en = 18
lcd_d4 = 24
lcd_d5 = 7
lcd_d6 = 25
lcd_d7 = 8
lcd_columns = 16
lcd_rows = 2
lcd = LCD.Adafruit_CharLCD(lcd_rs, lcd_en, lcd_d4, lcd_d5, lcd_d6, lcd_d7,lcd_columns, lcd_rows)
#イベント監視のためのフィルタ設定
web3 = Web3(HTTPProvider('http://localhost:8545'))
contractAddress="0x69c740ee06dc00Fc55cF05e2637FeB41D41EE07c"
abi_str='[{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"retVal","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"from","type":"address"},{"indexed":false,"name":"stored","type":"uint256"}],"name":"Set","type":"event"}]'
abi=json.loads(abi_str)
contract = web3.eth.contract(contractAddress,abi=abi)
transfer_filter = contract.eventFilter('Set',filter_params ={'filter':{'from':"0x340019208fe2dae8a710e613cff2902fb99e25d5"}})
#イベント監視
while True: #終了しないように待機する。
time.sleep(1)
a=web3.eth.getFilterChanges(transfer_filter.filter_id)
if not a:
print("null")
else:
#イベント通知があったらLCDに表示
for index in range(len(a)):
address=a[index]['address']
blockNum=a[index]['blockNumber']
blockHash=a[index]['blockHash']
transactionHash=a[index]['transactionHash']
result=contract.call().get()
print(result)
#LCDの表示
lcd.clear()
lcd.message(str(result))
###実行結果:
RAS1からトランザクションを送信。( コントラクトを実行 set(494) )
Terminalでマイニング
RAS2でイベント通知を検知してLCDに表示
RAS2に接続したLCDの表示結果。
###最後に
長くなりましたが、Raspberry piを利用してプライベートネットワークを作れました。
心残りはRaspberry piでマイニングができなかったことです。
誰かマイニングできたよって人いたら教えてください。
Raspbery piなどがあるとGPIO制御ができるので、色々と楽しくなりそうです。
プライベートネットワーク用にトークンを生成して、送付したトークンの量だけステッピングモータをまわし続けるなどしたら、意味はないけど面白いかもしれませんね。
##補足1
genesis.jsonの"config"という記載について
gethのconfig.goを参考にする。
MainnetChainConfig = &ChainConfig{
ChainId: MainNetChainID,
HomesteadBlock: MainNetHomesteadBlock,
DAOForkBlock: MainNetDAOForkBlock,
DAOForkSupport: true,
EIP150Block: MainNetHomesteadGasRepriceBlock,
EIP150Hash: MainNetHomesteadGasRepriceHash,
EIP155Block: MainNetSpuriousDragon,
EIP158Block: MainNetSpuriousDragon,
Ethash: new(EthashConfig),
}
要は、Ethereumのメインネットでこれまでにハードフォークした設定の記載となる。
- Chain id:private chainを構築する上で一意となる値。
- homesteadBlock: 「HomeStead」のハードフォークを適用するブロック。
- eip155Block: 「MainNetSpuriousDragon」のハードフォークを適用するブロック。
- eip158Block:同上
プライベートネットを作成する上では「0」と設定すれば良い。
##補足2
"alloc"追加すれば事前に設定したアドレスのETH残高を設定できる
"alloc": {
"7df9a875a174b3bc565e6424a0050ebc1b2d1d82": { "balance": "300000" },
"f41c74c9ae680c1aa78f42e5647a62f353b7bdde": { "balance": "400000" }
}
##補足3: Raspberry piでのWeb3.pyのインストール
Raspberry piにWeb3.pyをインストールする方法
(pipのバージョンが古かったり、色々と必要な依存関係が足りないものがあったりする。
次の方法で対処。30分くらい時間がかかる。)
$ sudo apt-get install python-virtualenv libffi-dev autoconf libtool
$ sudo apt-get install pandoc
$ virtualenv -p python3 venv
$ source venv
(venv)$ pip3 install --upgrade pip
(venv)$ git clone https://github.com/pipermerriam/web3.py.git
(venv)$ cd web3.py
(venv)$ pip3 install -r requirements-dev.txt
(venv)$ pip3 install --upgrade setuptools
(venv)$ pip3 install -e .
##補足4:LCDの表示方法
raspberry piに接続したLCDを手っ取り早く表示させるには、Adafruitが提供するライブラリを使用する。
ライブラリのインストールは次のとおり。
(venv)$ git clone https://github.com/adafruit/Adafruit_Python_CharLCD.git
(venv)$ cd Adafruit_Python_CharLCD/
(venv)$ python3 setup.py install
(venv)$ pip3 install rpi.gpio
LCDのピンとRaspberry piのピンを接続する。
###ピンアサイン
PIN Number(LCD) | LCD | Raspberry Pi |
---|---|---|
1 | VDD | +3.3V |
2 | VSS | GND |
3 | Vo-- | GND |
4 | RS | GPIO14 |
5 | R/W | GND |
6 | enable signal | GPIO18 |
7 | DB0 | - |
8 | DB1 | - |
9 | DB2 | - |
10 | DB3 | - |
11 | DB4 | GPIO24 |
12 | DB5 | GPIO7 |
13 | DB6 | GPIO25 |
14 | DB7 | GPIO8 |
- LCDに書き込むだけなのでR/WはWriteのみ。GNDに接続すればいい。
- Vo-- はコントラストを調整するのに必要。(接続しないと液晶に文字として表示されないので意外と重要。
本来VDD-VSSの間で抵抗値を当てた電圧を設定するが、ここでは簡略的に0Vに接続しておく)
Adafruitのライブラリを使用する場合、次のような書き方になる。
GPIOの設定ピンを指定するだけ。簡単。
import Adafruit_CharLCD as LCD
lcd_rs = 14
lcd_en = 18
lcd_d4 = 24
lcd_d5 = 7
lcd_d6 = 25
lcd_d7 = 8
lcd_columns = 16
lcd_rows = 2
lcd = LCD.Adafruit_CharLCD(lcd_rs, lcd_en, lcd_d4, lcd_d5, lcd_d6, lcd_d7, lcd_columns,lcd_rows)
lcd.clear()
lcd.message('Sample...')
参考にしたもの