はじめに
Ethereumのスマートコントラクトを実装する時、ネットワーク上にデプロイして動作確認したいことは多いです。
パブリックネットワーク(メインネットワークやテストネットワーク)を使うとgas費用がかかり簡単に動作検証することが出来ないため、Ethereumではローカル環境に簡単に作成できるプライベートネットワークがあります。
今回はローカル環境でEthereumプライベートネットワーク環境を構築しアカウントを作成、そしてマイニングをすることでEtherを取得するところまで記述してみようと思います。
作業環境の構築
前回の記事でDockerコンテナにGethをインストールするところまではやりましたのでその続きからやっていきます。
Gethのインストールがまだの方は先にコッチを読んでください ⇒ 【Ethereum】DockerコンテナにGethの環境を構築してみる
まずコンテナとマウントしている作業フォルダ内に2つのファイルを作成します。ファイル名は任意です。
・genesis.json: genesisファイル
・geth.log: log出力用ファイル
genesisファイルとは、今回gethを使用しEthereumプライベートネットワークのブロックチェーンを作成します。そのためブロックチェーンを作成するにあたり一番最初のブロック(genesisブロック)に関する設定をするためのファイルです。
Ethereumのメインネットワークやテストネットワークでは既にブロックチェーンが作成・運用されているので不要なファイルです。
ファイルを作成したらgenesis.jsonに以下の内容を記述します。
{
"config": {
"chainId": 20,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"berlinBlock": 0
},
"nonce": "0x0000000000000042",
"timestamp": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "",
"gasLimit": "0x8000000",
"difficulty": "0x4000",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x3333333333333333333333333333333333333333",
"alloc": {}
}
geth.logは空のままでOKです。
ファイルを作成したら作業フォルダはこんな感じの構成になっていると思います。
$ ls
genesis.json geth.log
genesisファイルのイニシャライズ
準備が出来たらgenesisファイルをイニシャライズし、コンテナの作業フォルダにgethの環境を構築します。
gethコマンドを実行しイニシャライズします。
$ geth --datadir /work init /work/genesis.json
INFO [09-26|01:15:24.108] Maximum peer count ETH=50 LES=0 total=50
INFO [09-26|01:15:24.117] Smartcard socket not found, disabling err="stat /run/pcscd/pcscd.comm: no such file or directory"
WARN [09-26|01:15:24.173] Sanitizing cache to Go\'s GC limits provided=1024 updated=662
INFO [09-26|01:15:24.173] Set global gas cap cap=50,000,000
INFO [09-26|01:15:24.182] Allocated cache and file handles database=/work/geth/chaindata cache=16.00MiB handles=16
INFO [09-26|01:15:24.339] Writing custom genesis block
INFO [09-26|01:15:24.343] Persisted trie from memory database nodes=0 size=0.00B time="355.4µs" gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B
INFO [09-26|01:15:24.366] Successfully wrote genesis state database=chaindata hash=7b2e8b..7e0432
INFO [09-26|01:15:24.366] Allocated cache and file handles database=/work/geth/lightchaindata cache=16.00MiB handles=16
INFO [09-26|01:15:24.497] Writing custom genesis block
INFO [09-26|01:15:24.498] Persisted trie from memory database nodes=0 size=0.00B time="19.8µs" gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B
INFO [09-26|01:15:24.517] Successfully wrote genesis state database=lightchaindata hash=7b2e8b..7e0432
"Successfully wrote genesis state"と表示されればイニシャライズは完了です。
イニシャライズが完了したら作業フォルダの状況を確認してみます。
$ ls -l
total 4
-rw-r--r-- 1 root root 597 Sep 25 14:32 genesis.json
drwx------ 6 root root 192 Sep 26 01:15 geth
-rw-r--r-- 1 root root 0 Sep 25 14:33 geth.log
drwx------ 2 root root 64 Sep 26 01:15 keystore
新たにgethとkeystoreというディレクトリが作成されています。
それぞれの役割は、
・geth: このノードに関する情報やブロックチェーンデータを格納するフォルダ
・keystore: 秘密鍵を格納するフォルダ。プライベート環境でアカウントを作成するとここに秘密鍵が格納される
という感じです。
gethを起動
gethのイニシャライズが完了したらgethを起動してみます。
$ geth --networkid "20" --nodiscover --datadir "/work" console 2>> "/work/geth.log"
Welcome to the Geth JavaScript console!
instance: Geth/v1.10.7-stable-12f0ff40/linux-amd64/go1.16.4
at block: 0 (Thu Jan 01 1970 00:00:00 GMT+0000 (UTC))
datadir: /work
modules: admin:1.0 debug:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
To exit, press ctrl-d
>
コンソールが立ち上がれば起動は成功です。
各コマンドオプションの内容は、
・--networkid: genesis.jsonのchainIdで指定した値を設定します。ethereumブロックチェーンネットワークはパブリックチェーン(メインネットワークやテストネットワーク)からプライベートチェーンまでいくつか種類があります。パブリックチェーン(メインネットワークやテストネットワーク)には固有のネットワークidが割り振られているのでそれ以外を指定します。
・--nodiscover: gethは起動するとnetworkidで指定した値を指定している他のノードを探索し始めます。このオプションでその探索を行わないようにします。
・--datadir: 作業フォルダを指定。
・console: gethを対話的に実行できるコンソールを開きます。
・2>> : ログを抽出します。
作業フォルダを確認すると新たなフォルダが作成されています。
$ ls -l
total 32
-rw-r--r-- 1 yasuhiro staff 597 9 25 23:32 genesis.json
drwx------ 9 yasuhiro staff 288 9 26 11:16 geth
srw------- 1 yasuhiro staff 0 9 26 11:16 geth.ipc
-rw-r--r-- 1 yasuhiro staff 6493 9 26 11:16 geth.log
-rw------- 1 yasuhiro staff 28 9 26 11:15 history
drwx------ 2 yasuhiro staff 64 9 26 10:15 keystore
新たにgeth.ipcフォルダとhistoryファイルが作成されています。
・ipcフォルダ: geth起動時にコンソールを起動したので作成されました。
・history: コンソールで実行されたコマンドが記載されるっぽい
今はまだトランザクションも作成していないしブロックのマイニングも行っていないためブロックチェーンにはジェネシスブロック(1番目のブロック)しかありません。
ブロックチェーンの1つ目のブロックの内容を確認してみます。
> eth.getBlock(0)
{
difficulty: 16384,
extraData: "0x",
gasLimit: 134217728,
gasUsed: 0,
hash: "0x7b2e8be699df0d329cc74a99271ff7720e2875cd2c4dd0b419ec60d1fe7e0432",
logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
miner: "0x3333333333333333333333333333333333333333",
mixHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
nonce: "0x0000000000000042",
number: 0,
parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
size: 507,
stateRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
timestamp: 0,
totalDifficulty: 16384,
transactions: [],
transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
uncles: []
}
genesis.jsonで設定した値が反映されています。
ただ新しいブロックチェーンを作成した段階なので、アカウント(EOA)もスマートコントラクトも設定されていません。
> eth.accounts
[]
アカウントの作成
さっそくアカウントを作成してみます
personal.newAccount()で新しいアカウントを作成できます。
newAccountの引数にはアカウントのパスワードを文字列で渡します。
> personal.newAccount("test1")
"0x477f0f906f93949501d4c0ef42c260259d1ad030"
新しいアカウントが作成できました。eth.accountsで確認してみます。
> eth.accounts
["0x477f0f906f93949501d4c0ef42c260259d1ad030"]
アカウントが出来たらログを確認してみます。
INFO [09-26|02:46:22.781] Your new key was generated address=0x477F0f906F93949501d4C0ef42C260259D1ad030
WARN [09-26|02:46:22.782] Please backup your key file! path=/work/keystore/
key fileが格納されているようなので作業フォルダのkeystoreを確認してみます
$ cd keystore/
$ ls -l
total 8
-rw------- 1 yasuhiro staff 491 9 26 11:46 UTC--2021-09-26T02-46-18.309071400Z--477f0f906f93949501d4c0ef42c260259d1ad030
新しくアカウント情報が記載されているJSONファイルが作成されています。
外部ウォレットでこのアカウントを使用したい場合はこのJSONファイルを使用するらしいです。(Metamaskで試してみたがまだ上手くアカウント連携が出来ていません。近いうちMetamaskの使用方法を調べて記事にしたいと思います。)
1つ目のアカウントを作成出来たのでとりあえずアカウントを3つ作成しておきます。
> personal.newAccount("test2")
"0x1851ca62fa8353546e32ef2239b4bc461b16a60d"
>
> personal.newAccount("test3")
"0xfe1fc7af662327a0bc9ab4728c7c48c8782068c7"
>
> eth.accounts
["0x477f0f906f93949501d4c0ef42c260259d1ad030", "0x1851ca62fa8353546e32ef2239b4bc461b16a60d", "0xfe1fc7af662327a0bc9ab4728c7c48c8782068c7"]
アカウントが3つ出来ました。
今回作成時に使用したパスワードはアカウントロックを解除する際に使用するので作業フォルダに適当なファイルを作成しそれぞれのパスワードを保存しておきます。
今回はpassword.txtを作成し、その中にパスワードを順番に記載しておきます。
test1
test2
test3
HTTP SERVERの起動
アカウントが作成されたので、web3.jsでgethにアクセスしてみます。
web3.jsのインストール方法や使い方についてはコチラを読んでみてください ⇒ Web3.jsを使ってみる
web3.jsを使用するので、HTTP SERVERも起動しておきます。
一度gethを終了しコンソールを抜けます。
HTTP SERVERを起動するには --httpオプションでHTTP SERVERを有効化します
※以前までは--rpcを使用していましたが2021年6月で廃止されたようなのでこれからは--httpを使用します。--rpcaddr等もそれぞれ対応する--http.addr等に置き換えて実行します。
$ geth --networkid "20" --nodiscover --datadir "/work" console 2>> "/work/geth.log" --http --http.addr "localhost" --http.port "8545" --http.api "eth,net,web3,personal,miner,admin" --http.corsdomain "*"
これを実行すると
INFO [09-26|02:16:42.413] Maximum peer count ETH=50 LES=0 total=50
INFO [09-26|02:16:42.415] Smartcard socket not found, disabling err="stat /run/pcscd/pcscd.comm: no such file or directory"
WARN [09-26|02:16:42.468] Sanitizing cache to Go's GC limits provided=1024 updated=662
INFO [09-26|02:16:42.470] Set global gas cap cap=50,000,000
INFO [09-26|02:16:42.474] Allocated trie memory caches clean=99.00MiB dirty=165.00MiB
INFO [09-26|02:16:42.477] Allocated cache and file handles database=/work/geth/chaindata cache=329.00MiB handles=524,288
INFO [09-26|02:16:42.575] Opened ancient database database=/work/geth/chaindata/ancient readonly=false
INFO [09-26|02:16:42.581] Initialised chain configuration config="{ChainID: 20 Homestead: 0 DAO: <nil> DAOSupport: false EIP150: 0 EIP155: 0 EIP158: 0 Byzantium: 0 Constantinople: 0 Petersburg: 0 Istanbul: 0, Muir Glacier: <nil>, Berlin: 0, London: <nil>, Engine: unknown}"
INFO [09-26|02:16:42.613] Disk storage enabled for ethash caches dir=/work/geth/ethash count=3
INFO [09-26|02:16:42.614] Disk storage enabled for ethash DAGs dir=/root/.ethash count=2
INFO [09-26|02:16:42.615] Initialising Ethereum protocol network=20 dbversion=8
INFO [09-26|02:16:42.627] Loaded most recent local header number=0 hash=7b2e8b..7e0432 td=16384 age=52y5mo3w
INFO [09-26|02:16:42.630] Loaded most recent local full block number=0 hash=7b2e8b..7e0432 td=16384 age=52y5mo3w
INFO [09-26|02:16:42.631] Loaded most recent local fast block number=0 hash=7b2e8b..7e0432 td=16384 age=52y5mo3w
INFO [09-26|02:16:42.636] Loaded local transaction journal transactions=0 dropped=0
INFO [09-26|02:16:42.645] Regenerated local transaction journal transactions=0 accounts=0
INFO [09-26|02:16:42.650] Gasprice oracle is ignoring threshold set threshold=2
INFO [09-26|02:16:42.653] Starting peer-to-peer node instance=Geth/v1.10.7-stable-12f0ff40/linux-amd64/go1.16.4
INFO [09-26|02:16:42.738] New local node record seq=2 id=8f2ba8a8bdb73c35 ip=127.0.0.1 udp=0 tcp=30303
INFO [09-26|02:16:42.739] Started P2P networking self="enode://859ce4a5dde9e0b50a5abb15daf21da6c44ab0127919fe562b5dff34dab56e99a2960908ed8dd93230b341e74a4199ce8f860af4760dcb4e6061546c8446d4d3@127.0.0.1:30303?discport=0"
INFO [09-26|02:16:42.745] IPC endpoint opened url=/work/geth.ipc
INFO [09-26|02:16:42.756] HTTP server started endpoint=127.0.0.1:8545 prefix= cors=* vhosts=localhost
WARN [09-26|02:16:42.835] Served eth_coinbase reqid=3 t="115.5µs" err="etherbase must be explicitly specified"
下から2行目でHTTP SERVERの起動が確認出来ます。これでgethコンソールだけでなくweb3.jsも使用出来ます。
それではgethノードにアクセスするaccess_geth.jsを作成します。
const Web3 = require('web3');
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
// アカウントを取得する
web3.eth.getAccounts().then(console.log);
実行
$ node access_geth.js
[ '0x477F0f906F93949501d4C0ef42C260259D1ad030',
'0x1851Ca62fa8353546E32ef2239b4BC461b16A60D',
'0xFE1Fc7af662327a0bC9aB4728c7C48C8782068C7' ]
ちゃんとgethコンソールと同じアカウントアドレスが返ってきました。
HTTP SERVERも動作しているようです。
マイニングしEtherをゲットする
今は各アカウントは誰もEtherを所持していません。
プライベートネット環境は自分1人しかいないので簡単にマイニングができます。
なのでマイニングして残高を増やします。
ところで現況は1つのノードに3つアカウントアドレスがある状態です。
マイニングはどのアカウントで行われるのでしょう。確かめてみます。
> eth.coinbase
"0x477f0f906f93949501d4c0ef42c260259d1ad030"
eth.coinbaseはマイニングを実行するアドレスが格納される属性です。
gethではデフォルトでeth.accounts[0]のアドレスが格納されています。
それではマイニングをスタートします。
miner.start()でマイニングを開始できます。
> miner.start()
null
nullが返ってくるので不安ですが、ログを見てみましょう。
INFO [09-26|04:08:36.963] Commit new mining work number=1 sealhash=5d57c8..fbd76c uncles=0 txs=0 gas=0 fees=0 elapsed=2.384ms
INFO [09-26|04:08:40.627] Generating DAG in progress epoch=0 percentage=0 elapsed=2.922s
INFO [09-26|04:08:44.099] Generating DAG in progress epoch=0 percentage=1 elapsed=6.393s
INFO [09-26|04:08:47.308] Generating DAG in progress epoch=0 percentage=2 elapsed=9.603s
マイニングはスタートしています。
一番最初はDAGファイルを作成するので時間がかかります。
percentage=100になるとマイニングが始まりますので待ちます。
INFO [09-26|04:13:58.141] Successfully sealed new block number=1 sealhash=5d57c8..fbd76c hash=8b6033..b33374 elapsed=5m21.550s
INFO [09-26|04:13:58.142] 🔨 mined potential block number=1 hash=8b6033..b33374
INFO [09-26|04:14:23.976] Successfully sealed new block number=2 sealhash=2b3d69..a458e4 hash=76002f..a27afe elapsed=25.844s
INFO [09-26|04:14:24.001] 🔨 mined potential block number=2 hash=76002f..a27afe
INFO [09-26|04:14:23.979] Commit new mining work number=3 sealhash=9e542d..71c0ed uncles=0 txs=0 gas=0 fees=0 elapsed=1.686ms
INFO [09-26|04:14:28.557] Successfully sealed new block number=3 sealhash=9e542d..71c0ed hash=1aea21..59bacf elapsed=4.578s
INFO [09-26|04:14:28.567] 🔨 mined potential block number=3 hash=1aea21..59bacf
DAGファイルの作成が終わるとマイニングが始まります。
プライベートネットワークは自分でマイニングするのでマイニングスピードも早いです。
ある程度マイニングしたらマイニングをストップしてみます。
miner.stop()でマイニングを終了できます。
> miner.stop()
null
nullが返ってくるので不安ですが、これでマイニングはストップします。
作成されたブロック数とアカウント残高を確認してみます。
const Web3 = require('web3');
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
// ブロック数を取得する
web3.eth.getBlockNumber((error, result) => {
console.log('block number: ', result);
})
// アカウントの残高を取得する
web3.eth.getAccounts((error, result) => {
web3.eth.getBalance(result[0], 'latest', (error, response) => {
console.log('balance: ', response);
})
})
実行
$ node access_geth.js
block number: 29
balance: 58000000000000000000
29個のブロックが作成され、eth.accounts[0]のアドレスは58000000000000000000weiのEther残高を得たようです。
これで他のアカウントアドレスにEtherを送金してあげれば全てのアカウントがEtherを所持することが出来ますね。
おわりに
ローカル環境にEthereumプライベートネットワークが構築できました。これで簡単にスマートコントラクトの動作確認を実施できます。
スマートコントラクトの作成やデプロイ方法についてはコチラを読んでみてください ⇒ 【Ethereum】web3.js を使用してプライベートネットにスマートコントラクトをデプロイする
今は色々試しながらEthereumについて学んでいる最中ですので気付いた点や間違えている点があったらご指摘いただけると嬉しいです。
最後までご覧いただきありがとうございました。
参考文献、サイト
Ethereum入門
このサイトにはホントお世話になっています。EthereumやGethのことが細かく紹介されていますのでご覧になってみてください。
ブロックチェーンアプリケーション開発の教科書
この本はEthereumを使ったDApp開発のことがかなり網羅されているので、おすすめです。