プライベート型のethereumブロックチェーンを構築し、採掘、送金、スマートコントラクトを実行するハンズオンです。
geth は GitHub 上にソースコードが公開されており、 https://geth.ethereum.org/downloads/に Linux、macOS、Windows 用のパッケージが公開さています。
お使いの環境に合わせて geth パッケージをダウンロードし、インストールしてください。
ブロックチェーンを構築するときには、まず1番最初のブロックを作成する必要があります。
1番最初のブロックを定義するためのファイルを「genesisファイル」(genesis: 発生、起源、創始の意)と呼びます。
ethereum では「genesisファイル」を JSON で定義します。
以下のファイルを作成してください。
{ "config": { "chainId": 15, "homesteadBlock": 0, "eip150Block": 0, "eip155Block": 0, "eip158Block": 0, "byzantiumBlock": 0, "constantinopleBlock": 0, "petersburgBlock": 0, "istanbulBlock": 0, "berlinBlock": 0, "londonBlock": 0 }, "alloc": { "0x0000000000000000000000000000000000000001": { "balance": "111111111" }, "0x0000000000000000000000000000000000000002": { "balance": "222222222" } }, "coinbase": "0x0000000000000000000000000000000000000000", "difficulty": "0x20000", "extraData": "", "gasLimit": "0x2fefd8", "nonce": "0x0000000000000042", "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp": "0x00" }
PowerShell やコマンドラインから以下のようにコマンドを実行することで、ブロックチェーンを構築します。
(先程「インストール」した geth にパスを通すか、geth をフルパスで実行してください。)
geth --datadir private_network init genesis.json
コマンドの意味
geth --datadir private_network --nodiscover console
コマンドの意味
上記のように「geth … console」でconsole接続すると「ブロックチェーンの起動」と「console接続」が同時に行われます。そのため、コマンドを打つ度にブロックチェーンのログがconsoleに出力され、大変操作しづらくなります。おすすめの方法は、以下「ブロックチェーンの起動」と「attachでconsole接続」分けて行う方法です。
geth console で作業します。
現在、アカウントが無いことを確認する。
> eth.accounts []
アカウント2つ作成してみる。(後に送金と入金を確認するため2つ作成します)
> personal.newAccount("password01") "0xd236df380900a967dcc8a229f6a27bd03c330c6d" > eth.accounts ["0xd236df380900a967dcc8a229f6a27bd03c330c6d"] > personal.newAccount("password02") "0x7caf4b7dff26e89d7138627787d2cdf91d97c81f"
password01 と password02 はアカウントに紐づくパスワードです。 「0x」から始まる文字列がアカウントです。両方ペアでメモしておいてください。
現在の coinbase を確認する。
> eth.coinbase "0xd236df380900a967dcc8a229f6a27bd03c330c6d"
今後、採掘を行うと上記で表示された coinbase アカウントにコインが付加されます。
実行
> miner.start() null > eth.blockNumber 0 > eth.blockNumber 5 > eth.blockNumber 42 > eth.getBalance(eth.accounts[0]) 210000000000000000000 > eth.getBalance(eth.accounts[1]) 0 ETH で残高を表示 > web3.fromWei(eth.getBalance(eth.accounts[0]), 'ether') 210
停止
> miner.stop()
> personal.unlockAccount(eth.accounts[0]) Unlock account 0xd236df380900a967dcc8a229f6a27bd03c330c6d Passphrase: true > eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(5, "ether")}) "0xe5c0307ca99f89dfa135a6609d0a01a48b62855ceb3a34a8e36c66d1d7a3d893"
上記でトランザクションを送信している。 トランザクションは誰かが採掘をしているときに処理される。 よって、採掘を開始して send を完了させる。
> miner.start() null > eth.getBalance(eth.accounts[1]) 5000000000000000000 > miner.stop() null
solidity releases でコンパイラーが公開されていますので、ダウンロードします。
(上記で配布されているのでコンパイラの実行ファイルでので、解凍やインストールは必要なく、すぐにコンパイラを利用できます。)
内部にカウンターをもち、カウンターの値を取得する(get)と、カウンターの値を1増やす(inc)するスマートコントラクトを実装します。
// SPDX-License-Identifier: MIT pragma solidity ^0.8.13; contract Counter { uint public count; // Function to get the current count function get() public view returns (uint) { return count; } // Function to increment count by 1 function inc() public { count += 1; } // Function to decrement count by 1 function dec() public { count -= 1; } }
スマートコントラクトのコンパイルはコマンドラインから solc を使用して実行します。
solc-windows.exe --abi --bin Counter.sol
コマンドの意味
上記コンパイルを実行すると、以下のように「Binary」と「Contract JSON API」が出力されます。
今後利用するときに「Binary」の先頭に「0x」を付ける必要がありますので、「0x」を付けてメモしておいてください。
(例) 608060… → 0x608060…
======= Counter.sol:Counter ======= Binary: 608060405234801561001057600080fd5b50610209806100206000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c806306661abd14610051578063371303c01461006f5780636d4ce63c14610079578063b3bcfa8214610097575b600080fd5b6100596100a1565b60405161006691906100ff565b60405180910390f35b6100776100a7565b005b6100816100c2565b60405161008e91906100ff565b60405180910390f35b61009f6100cb565b005b60005481565b60016000808282546100b99190610149565b92505081905550565b60008054905090565b60016000808282546100dd919061019f565b92505081905550565b6000819050919050565b6100f9816100e6565b82525050565b600060208201905061011460008301846100f0565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610154826100e6565b915061015f836100e6565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156101945761019361011a565b5b828201905092915050565b60006101aa826100e6565b91506101b5836100e6565b9250828210156101c8576101c761011a565b5b82820390509291505056fea26469706673582212207a90fc8477fe04ed648a217280498cc223d36737bb62448b5f15b12f4496447a64736f6c634300080d0033 Contract JSON ABI [{"inputs":[],"name":"count","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dec","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"get","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"inc","outputs":[],"stateMutability":"nonpayable","type":"function"}]
スマートコントラクトのデプロイは geth console 上で行います。
geth --datadir private_network --nodiscover console > var bin = "0x608060405234801561001057600080fd5b50610209806100206000396000f3fe608060405234801561001057600080fd5 b506004361061004c5760003560e01c806306661abd14610051578063371303c01461006f5780636d4ce63c14610079578063b3bcfa82146 10097575b600080fd5b6100596100a1565b60405161006691906100ff565b60405180910390f35b6100776100a7565b005b6100816100c25 65b60405161008e91906100ff565b60405180910390f35b61009f6100cb565b005b60005481565b60016000808282546100b99190610149565b92505081905550565b60008054905090565b60016000808282546100dd919061019f565b92505081905550565b6000819050919050565b6100f9816100e6565b82525050565b600060208201905061011460008301846100f0565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610154826100e6565b915061015f836100e6565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156101945761019361011a565b5b828201905092915050565b60006101aa826100e6565b91506101b5836100e6565b9250828210156101c8576101c761011a565b5b82820390509291505056fea26469706673582212207a90fc8477fe04ed648a217280498cc223d36737bb62448b5f15b12f4496447a64736f6c634300080d0033" undefined > var abi = [{"inputs":[],"name":"count","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dec","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"get","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"inc","outputs":[],"stateMutability":"nonpayable","type":"function"}] undefined > var contract = eth.contract(abi) undefined > var myContract = contract.new({ from: eth.accounts[0], data: bin}) Error: authentication needed: password or unlock at web3.js:6365:37(47) at send (web3.js:5099:62(35)) at web3.js:3021:48(124) at <eval>:1:30(13) > personal.unlockAccount(eth.accounts[0]) Unlock account 0xd78f9a659a9a7b50689eb2bd4c4aebdc9c2e5312 Passphrase: true > var myContract = contract.new({ from: eth.accounts[0], data: bin}) undefined
上記の作業でデプロイの「登録」は完了しました。 デプロイ処理の実行は採掘時に行われます。
採掘前のスマートコントラクトの状況を見ておきます。
までデプロイが完了していないため「address: undefined」となっています。 デプロイ処理が完了すると「Contract Account」がaddressに付与されます。
> myContract { abi: [{ inputs: [], name: "count", outputs: [{...}], stateMutability: "view", type: "function" }, { inputs: [], name: "dec", outputs: [], stateMutability: "nonpayable", type: "function" }, { inputs: [], name: "get", outputs: [{...}], stateMutability: "view", type: "function" }, { inputs: [], name: "inc", outputs: [], stateMutability: "nonpayable", type: "function" }], address: undefined, transactionHash: "0x621b3dffcaf4c656ab69e65bfa0fccd27bc3f61e65440ba6b3d392c0df2bc117" }
採掘を行ってからスマートコントラクトの状況を見てみます。
デプロイ処理が完了しているため「Contract Account」がaddressに付与されています。
> miner.start() null > myContract { abi: [{ inputs: [], name: "count", outputs: [{...}], stateMutability: "view", type: "function" }, { inputs: [], name: "dec", outputs: [], stateMutability: "nonpayable", type: "function" }, { inputs: [], name: "get", outputs: [{...}], stateMutability: "view", type: "function" }, { inputs: [], name: "inc", outputs: [], stateMutability: "nonpayable", type: "function" }], address: "0x642851bf1760f1aaf0a7a3de8abc646ccd76699c", transactionHash: "0x621b3dffcaf4c656ab69e65bfa0fccd27bc3f61e65440ba6b3d392c0df2bc117", allEvents: function bound(), count: function bound(), dec: function bound(), get: function bound(), inc: function bound() } > miner.stop()
スマートコントラクトを呼び出してカウンターの値を1増やしてみます。 この処理はトランザクションを伴うため、採掘時に実行されます。
myContract.inc.sendTransaction({from:eth.accounts[0]}) Error: authentication needed: password or unlock at web3.js:6365:37(47) at send (web3.js:5099:62(35)) at web3.js:4155:41(53) at <eval>:1:35(9) > personal.unlockAccount(eth.accounts[0]) Unlock account 0xd78f9a659a9a7b50689eb2bd4c4aebdc9c2e5312 Passphrase: true > myContract.inc.sendTransaction({from:eth.accounts[0]}) "0xd254b4b2e830350da787720e9b037e1e27cc11ad93452ff79345e88d716b9cf2"
カウンターの値を参照してみましょう。
採掘前後で確認してみます。
get でカウンターの値を参照する。
> myContract.get.call() 0 > miner.start() null > myContract.get.call() 1 > miner.stop()
トランザクションの実行にはガス代と呼ばれる手数料がかかります。 そのため、トランザクションを発行したaccount[0]は想定よりも少しだけコインが減っているはずです。
確認してみましょう。
> eth.getBalance(eth.accounts[0]) 1.496999999999998206439e+21