アプリケーション開発ポータルサイト
ServerNote.NET
カテゴリー【仮想通貨UbuntuDebian
Ubuntu Server 21.10でイーサリアムブロックチェーン【その5】
POSTED BY
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以上の文法です。

Shellsolidity-sample-0.8.solGitHub 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】

※本記事は当サイト管理人の個人的な備忘録です。本記事の参照又は付随ソースコード利用後にいかなる損害が発生しても当サイト及び管理人は一切責任を負いません。
※本記事内容の無断転載を禁じます。
【WEBMASTER/管理人】
自営業プログラマーです。お仕事ください!
ご連絡は以下アドレスまでお願いします★

【キーワード検索】