再)Railsで、Ethereumのプライベートネット上のERC20Tokenの残高確認

前回上手くいかなかったので、リベンジ

やったこと

1. gethのインストール

プライベート・ネットに接続する · Ethereum入門
上記を参考にgethをインストール。
その中で、myGenesis.json を作成する部分がある。
このjsonファイルは、下記のサイトを参考に書き換える。

ブロックチェーンEthereum入門 2 | NTTデータ先端技術株式会社

下記の値を設定し直すとよかった。
difficulty: マイニングの難易度。下げると、マイニングの時間が減る。
alloc: これを設定すると初期状態で、etherが指定のアカウントが保持している状態にできる。
gasLimit: transaction 時に必要なgasの量。

参考に作ったのがこちら

{
  "config": {
    "chainId": 15
  },
  "nonce": "0x00006d6f7264656e",
  "difficulty": "0x0001",
  "mixhash": "0x00000000000000000000000000000000000000647572616c65787365646c6578",
  "coinbase": "0x0000000000000000000000000000000000000000",
  "timestamp": "0x00",
  "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "extraData": "0x",
  "gasLimit": "0xffffffffffff",
  "alloc": {
      "0x5F6bD87B34c3C1315aC82b3f6f84d5e1DF31422b": { "balance": "100000000000000000000" },
  }
}

alloc の keyの部分を何にすればいいかよくわからない方は、下記のサイトを参考にするとわかりやすいです! 一行で書くと、一度gethを起動して、アカウント作って、そのアカウントアドレスをkeyの部分に書き、再度genesisブロックの初期化を行って、もう一度gethを起動しました。

ethereum(private)の環境構築からJSON-RPC実行まで - Qiita

もし、Failed to write genesis block: database already contains ...と出たら、下記のサイト参考に、DBを削除してもう一回実行。

geth init で Failed to write genesis block: database already contains an incompatible genesis block - Qiita

2. gethの起動

自分は、下記で起動しました。

geth --networkid "15" --nodiscover --datadir "/Users/kei/eth_private_net" --rpc --rpcaddr "localhost" --rpcport "8545" --rpcapi "web3,eth,net,personal" --minerthreads 1" --rpccorsdomain "*" --mine

大事なのは、
- --mine とつけることで、勝手にminingを行ってくれる。(miningをしないと、transactionが処理できなかった。)
- --rpcaddr "localhost" --rpcport "8545" とつけて、http経由でアクセスできるようにする。(truffle というライブラリで、smart contractを作るときに、httpアクセスするため。)

起動オプションについては、こちらがわかりやすかったです! ethereum(private)の環境構築からJSON-RPC実行まで - Qiita

3. コンソールでログイン

geth attach http://127.0.0.1:8545 geth attach ipc:/Users/kei/eth_private_net/geth.ipc

どっちでもログインできる!

4. Railsで確認

こちらのgemをインストール
GitHub - EthWorks/ethereum.rb: Ethereum library for the Ruby language

$ bundle exec rails c
> client = Ethereum::IpcClient.new('/Users/kei/eth_private_net/geth.ipc')
> client.get_balance('0x5F6bD87B34c3C1315aC82b3f6f84d5e1DF31422b')
=> 100000000000000000000

こんな感じでアクセスできる。

5. ERC20のSmart Contract作成

下記のサイトを参考にして、tokenを作成。

【Truffle5.0対応】シンプルなERC20トークンを作成しよう! │ こじりょーの研究所

上記のサイトでは、Ganache を利用していますが、今回はgeth利用しているので、truffle-confing.jsに、起動したgethの設定を追記

development: {
  host: "127.0.0.1",
  port: 8545,
  network_id: "*"
},

あとは、上記サイトに書いてあるように、contract をデプロイする。
その前に、geth の console 上から、アカウントのアンロックを忘れずに。これをしないと、etherを使った操作ができない。

$ geth attach http://127.0.0.1:8545
// passwordが聞かれるので、自分がアカウント作成したときに、設定しているものを入力
> personal.unlockAccount(eth.coinbase)
$ truffle migrate
// 結果こんな感じ
⚠️  Important ⚠️
If you're using an HDWalletProvider, it must be Web3 1.0 enabled or your migration will hang.


Starting migrations...
======================
> Network name:    'development'
> Network id:      15
> Block gas limit: 208531973388668


1_initial_migration.js
======================

   Deploying 'Migrations'
   ----------------------
   > transaction hash:    0x788615947355935aa5df21b8ca16ffac7f6e562f506dd6affd4914425b9f5dc1
   > Blocks: 2            Seconds: 4
   > contract address:    0x4543dCf52aa78d32d667bBF39EEc7B98b56d120F
   > account:             0x5F6bD87B34c3C1315aC82b3f6f84d5e1DF31422b
   > balance:             1645
   > gas used:            252758
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.00505516 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.00505516 ETH


2_deploy_contracts.js
=====================

   Deploying 'SimpleToken'
   -----------------------
   > transaction hash:    0xdee089ce5f1384c5016b8f5c8d870e8fbf1fef4c4046e49f84a235d2f9f28c4e
   > Blocks: 0            Seconds: 8
   > contract address:    0x2FA965B7d62858Dc4DBCeA965271b0D7dB780A13
   > account:             0x5F6bD87B34c3C1315aC82b3f6f84d5e1DF31422b
   > balance:             1665
   > gas used:            1387474
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.02774948 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.02774948 ETH


Summary
=======
> Total deployments:   2
> Final cost:          0.03280464 ETH

この結果の、「0x2FA965B7d62858Dc4DBCeA965271b0D7dB780A13」が、contract addressとなるので大事。

6. RailsからERC20Tokenの残高確認

$ bundle exec rails c
> client = Ethereum::IpcClient.new('/Users/kei/eth_private_net/geth.ipc')
> name = '作成したコインの名前'
> contract_address = '0x2FA965B7d62858Dc4DBCeA965271b0D7dB780A13'
// ERC20Tokenを保持している人のアドレス
> account_address = '0x5F6bD87B34c3C1315aC82b3f6f84d5e1DF31422b'
// abiとは、Application Binary Interfaceの略で、このContractは、こんなファンクションもってるよって定義するもの。
// 今回は、name と、 balanceOf 以外省略してます。
> contract_abi = [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function"}]
> contract = Ethereum::Contract.create(name: name, address: contract_address, abi: contract_abi.to_json, client: client)
> contract.call.name
=> "作成したコインの名前"
> contract.call.balance_of(account_address)
=> 100000000000000000000

7. Tokenの移動

$ bundle exec rails c
> client = Ethereum::IpcClient.new('/Users/kei/eth_private_net/geth.ipc')
> name = '作成したコインの名前'
> contract_address = '0x2FA965B7d62858Dc4DBCeA965271b0D7dB780A13'
// ERC20Tokenを保持している人のアドレス
> account_address = '0x5F6bD87B34c3C1315aC82b3f6f84d5e1DF31422b'
// ERC20Tokenを送りたい人のアドレス
> target_address = '0x37543c2d55bc48d5c87c49fbdd321834ffafbf43'

// ここでは、truffle で作成したときにできる、SimpleToken.json の abi部分を全部コピーしてみました。
> contract_abi =[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x06fdde03"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x095ea7b3"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x18160ddd"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x23b872dd"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x313ce567"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x39509351"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x70a08231"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x95d89b41"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xa457c2d7"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xa9059cbb"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0xdd62ed3e"},{"inputs":[{"name":"name","type":"string"},{"name":"symbol","type":"string"},{"name":"decimals","type":"uint8"},{"name":"initSupply","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor","signature":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event","signature":"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event","signature":"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"}]

> contract = Ethereum::Contract.create(name: name, address: contract_address, abi: contract_abi.to_json, client: client)
> contract.call.name
=> "作成したコインの名前"
> contract.transact_and_wait.transfer(target_address, 100)
> contract.call.balance_of(account_address)
=> 99999999999999999900
> contract.call.balance_of(target_address)
=> 100

transferをしたい時は、geth上で、coinbase のunlockAccountが必要なので注意。
あと、privateで、transactionを実行する時は、自分でminingをしないといけないので忘れずに。(--mine をつけてれば大丈夫)

Tokenの移動(とあるアカウント => とあるアカウントへ)

$ bundle exec rails c
> client = Ethereum::IpcClient.new('/Users/kei/eth_private_net/geth.ipc')
> name = '作成したコインの名前'
> contract_address = '0x2FA965B7d62858Dc4DBCeA965271b0D7dB780A13'
// Tokenを保持している & etherもいっぱいもってるアドレス
> master_address = '0x5F6bD87B34c3C1315aC82b3f6f84d5e1DF31422b'
// さっきTokenをもらったアドレス
> sender_address = '0x37543c2d55bc48d5c87c49fbdd321834ffafbf43'
// 今からTokenをもらうアドレス
> receiver_address = '0x7c278ea950e9af850ad089f93a94fd8fd7c62725'
// abiはさっきと一緒
> abi = ...

// まず、gasのために、etherがsender_addressに必要なので、master_addressから渡す
> client.personal_unlock_account(master_address, 'password')
> client.eth_send_transaction({ from: master_address, to: sender_address, value: "0x9184e72a"})

// デフォルトのアカウントを変更する
> client.default_account = sender_address
> contract = Ethereum::Contract.create(name: name, address: contract_address, abi: contract_abi.to_json, client: client)

// 送る前
> contract.call.balance_of(sender_address)
=> 100
> contract.call.balance_of(receiver_address)
=> 0

// 送信!
> client.personal_unlock_account(sender_address, 'password')
> transact_and_wait.transfer(sender_address, 40)

// 結果
> contract.call.balance_of(sender_address)
=> 60
> contract.call.balance_of(receiver_address)
=> 40

終わり

ここまでくるのに時間かかったが、etheriumとcontractについて、いくつか理解できた... 実際に、wallet を作成する時は、contract の address さえわかれば、etherscan のAPIを経由して、contract_abi、contract_name を取得ができる。
あとは、それを使ってcontractのインスタンス生成をし、ユーザーのアドレスの残高が確認できる模様。

結構理解できてきたので、まずは、wallet を自作できるようになりたい!!