2023-03-27
Ubuntu Server 21.10でイーサリアムブロックチェーン【その1】
Ubuntu Server 21.10でイーサリアムブロックチェーン【その2】
Ubuntu Server 21.10でイーサリアムブロックチェーン【その3】
Ubuntu Server 21.10でイーサリアムブロックチェーン【その4】
続きです。今回はイーサリアムブロックチェーン上でプログラムを実行する仕組みスマートコントラクトを実践してみます。非常にメジャーなサンプルであるノード間で変数を共有し書き換えるサンプルです。
1、Solidityコンパイラsolcの確認
スマートコントラクトはSolidityという言語で記述しコンパイラsolcでコンパイルします。
【その1】でapt install ethereum-unstableしている場合は、gethとともに最初からsolcも入っているはずですが、無い場合手動でインストールします。
sudo -s add-apt-repository -y ppa:ethereum/ethereum apt update apt install solc
バージョン確認
solc --version solc, the solidity compiler commandline interface Version: 0.8.13+commit.abaa5c0e.Linux.g++
0.8が入りました。あまたサンプルの0.4の記法ではエラーになるので注意が必要です。
2、共有整数を参照又は書き換えを行うサンプルソースを書く
以下のようなコードを書きます。バージョン0.8以上の文法です。
Shell | solidity-sample-0.8.sol | GitHub Source |
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract SingleNumRegister { uint storedData; function set(uint x) public { storedData = x; } function get() public view returns(uint) { return storedData; } }
3、コンパイルをしてバイナリコードとJSON ABIを得る
solc --abi --bin solidity-sample-0.8.sol ======= solidity-sample-0.8.sol:SingleNumRegister ======= Binary: 608060405234801561001057600080fd5b50610150806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806360fe47b11461003b5780636d4ce63c14610057575b600080fd5b610055600480360381019061005091906100c3565b610075565b005b61005f61007f565b60405161006c91906100ff565b60405180910390f35b8060008190555050565b60008054905090565b600080fd5b6000819050919050565b6100a08161008d565b81146100ab57600080fd5b50565b6000813590506100bd81610097565b92915050565b6000602082840312156100d9576100d8610088565b5b60006100e7848285016100ae565b91505092915050565b6100f98161008d565b82525050565b600060208201905061011460008301846100f0565b9291505056fea264697066735822122079735be7604de0450bfde099a98599f2bc88bd8cff4ff4661b470677c03e27ab64736f6c634300080d0033 Contract JSON ABI [{"inputs":[],"name":"get","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"x","type":"uint256"}],"name":"set","outputs":[],"stateMutability":"nonpayable","type":"function"}]
Binaryはソースコードをバイナリ化したコードそのもので、ABIはコードの動作仕様を記しています。
4、ブロックチェーン上で登録作業を行う
ここではローカルテストブロックチェーンに上記コードを登録します。以下はベーシックな立ち上げです。
geth --rpc.allow-unprotected-txs --datadir "/home/hogeuser/eth_test" --nodiscover --networkid 15 console 2>> /home/hogeuser/eth_test/geth_err.log
コンパイル結果変数データの宣言・定義
バイナリコード変数は先頭に0xをつけるのを忘れないようにします。
bin = "0x608060405234801561001057600080fd5b50610150806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806360fe47b11461003b5780636d4ce63c14610057575b600080fd5b610055600480360381019061005091906100c3565b610075565b005b61005f61007f565b60405161006c91906100ff565b60405180910390f35b8060008190555050565b60008054905090565b600080fd5b6000819050919050565b6100a08161008d565b81146100ab57600080fd5b50565b6000813590506100bd81610097565b92915050565b6000602082840312156100d9576100d8610088565b5b60006100e7848285016100ae565b91505092915050565b6100f98161008d565b82525050565b600060208201905061011460008301846100f0565b9291505056fea264697066735822122079735be7604de0450bfde099a98599f2bc88bd8cff4ff4661b470677c03e27ab64736f6c634300080d0033" abi = [{"inputs":[],"name":"get","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"x","type":"uint256"}],"name":"set","outputs":[],"stateMutability":"nonpayable","type":"function"}]
コントラクトの生成
contract = eth.contract(abi) myContract = contract.new({ from: eth.accounts[0], data: bin})
ここでError: authentication needed: password or unlockが出た場合はアンロックして再度実行します。
personal.unlockAccount(eth.accounts[0]) Unlock account 0x36d9643d7fdb4ed786293133cf9bc721509660b5 Passphrase: true myContract = contract.new({ from: eth.accounts[0], data: bin})
{ abi: [{ inputs: [], name: "get", outputs: [{...}], stateMutability: "view", type: "function" }, { inputs: [{...}], name: "set", outputs: [], stateMutability: "nonpayable", type: "function" }], address: undefined, transactionHash: "0x8dc7f8b43c245f2de881cdeb1850ae87e3b1157bdea817684836f8ffd8601962" }
マイニングが走ってない状態では、トランザクションは処理されませんので、addressはundefinedになっています。この状態でトランザクションID(transactionHash)を見てみます。
eth.getTransaction("0x8dc7f8b43c245f2de881cdeb1850ae87e3b1157bdea817684836f8ffd8601962"); { blockHash: null, blockNumber: null, from: "0x36d9643d7fdb4ed786293133cf9bc721509660b5", gas: 125653, gasPrice: 1000000000, hash: "0x8dc7f8b43c245f2de881cdeb1850ae87e3b1157bdea817684836f8ffd8601962", input: "0x608060405234801561001057600080fd5b50610150806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806360fe47b11461003b5780636d4ce63c14610057575b600080fd5b610055600480360381019061005091906100c3565b610075565b005b61005f61007f565b60405161006c91906100ff565b60405180910390f35b8060008190555050565b60008054905090565b600080fd5b6000819050919050565b6100a08161008d565b81146100ab57600080fd5b50565b6000813590506100bd81610097565b92915050565b6000602082840312156100d9576100d8610088565b5b60006100e7848285016100ae565b91505092915050565b6100f98161008d565b82525050565b600060208201905061011460008301846100f0565b9291505056fea264697066735822122079735be7604de0450bfde099a98599f2bc88bd8cff4ff4661b470677c03e27ab64736f6c634300080d0033", nonce: 3, r: "0x5580a67ef88d8f3ad9d27b4cc1264cf67ed957502d0ff306d8e268d2669230cc", s: "0x51a9a4a335b418f97be709d0aef0145f97463ebb66f6e8f7e0ed3eac7e955c6b", to: null, transactionIndex: null, type: "0x0", v: "0x42", value: 0 }
【その3】の送金トランザクションと同様、まだマイニングが走ってない状態なのでblockNumber等が割り当てられていない=処理されていないことがわかります。
それではマイニングを走らせて、このスマートコントラクトの登録をしてもらいましょう。
※マイニングにはPCの大量の電力を消費します。自己責任注意※
miner.start()
ログにはトランザクションを処理した旨の出力が出ています。
tail -f /home/hogeuser/eth_test/geth_err.log INFO [03-22|16:44:12.731] Submitted contract creation hash=0x8dc7f8b43c245f2de881cdeb1850ae87e3b1157bdea817684836f8ffd8601962 from=0x36D9643D7fDb4ED786293133CF9bc721509660B5 nonce=3 contract=0x6664bB3c5FfD7A6F1bE60ECa042499ed0D38180C value=0 INFO [03-22|16:51:19.831] Updated mining threads threads=4 INFO [03-22|16:51:19.831] Transaction pool price threshold updated price=1,000,000,000 INFO [03-22|16:51:19.832] Commit new sealing work number=2343 sealhash=fe277f..7d0031 uncles=0 txs=0 gas=0 fees=0 elapsed="463.181μs" INFO [03-22|16:51:19.833] Commit new sealing work number=2343 sealhash=ae220a..37f0f1 uncles=0 txs=1 gas=125,653 fees=0.000125653 elapsed=1.227ms INFO [03-22|16:51:21.510] Successfully sealed new block number=2343 sealhash=ae220a..37f0f1 hash=a9c856..d4c166 elapsed=1.677s
マイニングを終了して確認します。
miner.stop() myContract { abi: [{ inputs: [], name: "get", outputs: [{...}], stateMutability: "view", type: "function" }, { inputs: [{...}], name: "set", outputs: [], stateMutability: "nonpayable", type: "function" }], address: "0x6664bb3c5ffd7a6f1be60eca042499ed0d38180c", transactionHash: "0x8dc7f8b43c245f2de881cdeb1850ae87e3b1157bdea817684836f8ffd8601962", allEvents: function bound(), get: function bound(), set: function bound() }
アドレスが割り当てられている=コントラクト登録トランザクションが処理された=を意味します。当然ながら送金と同様、登録処理のガス代がかかっています。
このaddressが、コントラクトのアカウント(アドレス)ということになります。eth.accountsで表示されるパーソナルアカウントEOAと同格、ということになります。
5、他のアカウントからコントラクトを呼び出し操作する
コントラクトがブロックチェーン上に登録されたので、他の人はこれにアクセスして変数書き換えが行えます。このコントラクトに接続するために必要は情報は以下です。
・コントラクトのアドレス
0x6664bb3c5ffd7a6f1be60eca042499ed0d38180c
・コントラクトのABI JSON
[{"inputs":[],"name":"get","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"x","type":"uint256"}],"name":"set","outputs":[],"stateMutability":"nonpayable","type":"function"}]
コントラクト作成者は、この2つの情報を他の人に教えてあげれば、その人はコントラクトを実行して変数の書き換えができます。
では、アカウント2から操作してみます。なお、この書き換え操作も当然ながらトランザクションなので、マイニングの一環として行われ、処理完了の暁にはガス代が引かれます。
コントラクトオブジェクトの新規生成
教えてもらった上記情報をもとに生成。
cnt = eth.contract([{"inputs":[],"name":"get","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"x","type":"uint256"}],"name":"set","outputs":[],"stateMutability":"nonpayable","type":"function"}]).at("0x6664bb3c5ffd7a6f1be60eca042499ed0d38180c"); { abi: [{ inputs: [], name: "get", outputs: [{...}], stateMutability: "view", type: "function" }, { inputs: [{...}], name: "set", outputs: [], stateMutability: "nonpayable", type: "function" }], address: "0x6664bb3c5ffd7a6f1be60eca042499ed0d38180c", transactionHash: null, allEvents: function bound(), get: function bound(), set: function bound() }
コントラクト内の共有変数の最新状態表示
SingleNumRegister.storedDataの内容を表示してみます=SingleNumRegister.get()をコールします。
コントラクトの状態を参照するだけならトランザクションは不要なためガス代はかかりません。
cnt.get.call() 0
初期状態の0であることがわかりました。なお、ここでError: invalid opcode: SHRが出てしまった場合はこちらのような対処=ブロックチェーンの作り直し=が必要です。
変数書き換えトランザクションの発行
ではアカウント2から、この変数を書き換える=SingleNumRegister.set()をコールします。書き換えはネットワーク内の合意が必要なためトランザクションになり、ガス代がかかります。ここでは変数を777にしてみましょう。
アカウント2のETH残高を確認しロックを解除してから発行します。
eth.getBalance(eth.accounts[1]) 499999979000000000000 web3.fromWei(eth.getBalance(eth.accounts[1]),"ether") 499.999979 personal.unlockAccount(eth.accounts[1]) Unlock account 0x1a8355a4ae7465f7bf867a1a0ea88932b1c19891 Passphrase: true cnt.set.sendTransaction(777,{from:eth.accounts[1]}) "0x787c7b8e47c2bb71f7c2f2af3ac1e7deba6e1e33cec32ca58bf829ef9257156a" eth.getTransaction("0x787c7b8e47c2bb71f7c2f2af3ac1e7deba6e1e33cec32ca58bf829ef9257156a") { blockHash: null, blockNumber: null, from: "0x1a8355a4ae7465f7bf867a1a0ea88932b1c19891", gas: 43714, gasPrice: 1000000000, hash: "0x787c7b8e47c2bb71f7c2f2af3ac1e7deba6e1e33cec32ca58bf829ef9257156a", input: "0x60fe47b10000000000000000000000000000000000000000000000000000000000000309", nonce: 1, r: "0xb79dfedf639ce6b70720979ef10eee4d493e31c79bb146b1e54af71672f5f6d5", s: "0x58444e5736b030c79f3b2816d630840dd061f99fbe5bba65b55b9446f85aa562", to: "0x6664bb3c5ffd7a6f1be60eca042499ed0d38180c", transactionIndex: null, type: "0x0", v: "0x42", value: 0 } cnt.get.call() 0
例によってトランザクションIDは発行できましたが、マイニングが走っていなければまだ実行はされず、変数値も0のままです。
変数書き換えトランザクションの実行
それではマイニングを走らせて、このトランザクションを実行して変数書き換えを行います。
※マイニングにはPCの大量の電力を消費します。自己責任注意※
miner.start() …しばらく待つ… miner.stop() eth.getTransaction("0x787c7b8e47c2bb71f7c2f2af3ac1e7deba6e1e33cec32ca58bf829ef9257156a") { blockHash: "0x152f32095fa7c0f9f1b46a25da235ff953a8e18135f0dffe96122a3049633465", blockNumber: 2371, from: "0x1a8355a4ae7465f7bf867a1a0ea88932b1c19891", gas: 43714, gasPrice: 1000000000, hash: "0x787c7b8e47c2bb71f7c2f2af3ac1e7deba6e1e33cec32ca58bf829ef9257156a", input: "0x60fe47b10000000000000000000000000000000000000000000000000000000000000309", nonce: 1, r: "0xb79dfedf639ce6b70720979ef10eee4d493e31c79bb146b1e54af71672f5f6d5", s: "0x58444e5736b030c79f3b2816d630840dd061f99fbe5bba65b55b9446f85aa562", to: "0x6664bb3c5ffd7a6f1be60eca042499ed0d38180c", transactionIndex: 0, type: "0x0", v: "0x42", value: 0 } cnt.get.call() 777 myContract.get.call() 777 eth.getBalance(eth.accounts[1]) 499999935286000000000 web3.fromWei(eth.getBalance(eth.accounts[1]),"ether") 499.999935286
トランザクションが実行されブロックが割り当てられ、アカウント1、アカウント2、どちらからこのコントラクト内の変数を見ても777に更新されました。
また、書き換え実行者アカウント2の実行前残高は499.999979でしたが、実行後は499.999935286になりました。つまり、ガス代として0.000043714ETH=1ETH日本円35万円とすると=15.2999円かかった、ということになります。
本物のメインネットでは今いくらなのかわかりませんが、変数1つ書き換えるだけで15円って、とてもじゃないが実用不可能ではないか??
次回はERC-721規格に従ったコントラクトを作成しいわゆるNFTを発行してみます。
Ubuntu Server 21.10でイーサリアムブロックチェーン【その6】
※本記事内容の無断転載を禁じます。
ご連絡は以下アドレスまでお願いします★
Windowsのデスクトップ画面をそのまま配信するための下準備
WindowsでGPUの状態を確認するには(ASUS系監視ソフトの自動起動を停止する)
CORESERVER v1プランからさくらインターネットスタンダートプランへ引っ越しメモ
さくらインターネットでPython MecabをCGIから使う
さくらインターネットのPHPでAnalytics-G4 APIを使う
インクルードパスの調べ方
【Git】特定ファイルを除外する.gitignore
【Ubuntu/Debian】NVIDIA関係のドライバを自動アップデートさせない
【Python】Spacyを使用して文章から出発地と目的地を抜き出す
【Windows10】リモートデスクトップ間のコピー&ペーストができなくなった場合の対処法
Windows11+WSL2でUbuntuを使う【2】ブリッジ接続+固定IPの設定
Windows版Google Driveが使用中と言われアンインストールできない場合
VirtualBoxの仮想マシンをWindows起動時に自動起動し終了時に自動サスペンドする
【Apache】サーバーに同時接続可能なクライアント数を調整する
【C/C++】小数点以下の切り捨て・切り上げ・四捨五入
Googleファミリーリンクで子供の端末の現在地がエラーで取得できない場合
【Linux】iconv/libiconvをソースコードからインストール
Ubuntu Server 21.10でイーサリアムブロックチェーン【その5】