🗂

Geth, SolidityでHello,World

2022/11/07に公開

Dockerコンテナ内でGethをプライベートネットで実行し、
Solidityで文字列を出力する処理を実装して、
コンソールでHello,Worldを出力させます。

Gethをプライベートネットワークで実行

GethとはEthereumクライアントの一つです。
https://geth.ethereum.org/

まずはGethをDcokerを使ってプライベートネットワークで実行します。

docker-compose.yml
version: "3"
services:
  app1:
    image: ethereum/client-go
    entrypoint: /bin/sh
    tty: true
    volumes:
      - ./private_net:/app

docker-comppse.ymlを用意して、
docker-compose upを実行。

Gethのバージョンは1.10.22-unstableです。

mbazuki:client hide$ docker-compose exec app1 /bin/sh
/ # geth version
Geth
Version: 1.10.22-unstable
Git Commit: 141cd425310b503c5678e674a8c3872cf46b7086
Architecture: amd64
Go Version: go1.18.5
Operating System: linux
GOPATH=
GOROOT=go

ジェネシスブロックを作成

以下を参考に、Gethをプライベートネットワークで起動させるためにジェネシスブロックを作成します。
https://github.com/ethereum/go-ethereum#defining-the-private-genesis-state

private_net/genesis.json
{
  "config": {
    "chainId": 22,
    "homesteadBlock": 0,
    "eip150Block": 0,
    "eip155Block": 0,
    "eip158Block": 0,
    "byzantiumBlock": 0,
    "constantinopleBlock": 0,
    "petersburgBlock": 0,
    "istanbulBlock": 0,
    "berlinBlock": 0,
    "londonBlock": 0
  },
  "alloc": {},
  "coinbase": "0x0000000000000000000000000000000000000000",
  "difficulty": "0x20000",
  "extraData": "",
  "gasLimit": "0x2fefd8",
  "nonce": "0x0000000000000031",
  "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "timestamp": "0x00"
}

コンテナ内で初期化コマンドを実行します。
geth --datadir /app/ init /app/genesis.json
datadirはデータの保存先などで使われるディレクトリの指定です。
コンソールに
Successfully wrote genesis state
と表示されていれば初期化成功です。
新たにgeth, keystoreディレクトリが作成されています。

Gethの起動

geth --networkid 22 --nodiscover --datadir /app console 2>> /app/info.logでGethを起動できます。

/app # geth --networkid 22 --nodiscover --datadir /app console 2>> /app/info.log
Welcome to the Geth JavaScript console!

instance: Geth/v1.10.22-unstable-141cd425/linux-amd64/go1.18.5
at block: 0 (Thu Jan 01 1970 00:00:00 GMT+0000 (UTC))
 datadir: /app
 modules: admin:1.0 debug:1.0 engine: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 or type exit
> 

SolidityでHello,Worldを出力するスマートコントラクトを実装

Solidityとはスマートコントラクト開発ができるJavaScriptライクな静的型付け言語です。
https://github.com/ethereum/solidity

HelloWorld.sol
pragma solidity >=0.6.0 <0.9.0;

contract HelloWorld {
  function helloWorld() external pure returns (string memory) {
    return "Hello, World!";
  }
}

https://github.com/ethereum/solidity#example

スマートコントラクトをプライベートネットワークにデプロイ

デプロイするには、solファイルをコンパイルし、プライベートネットワークに送信し、マイニングをしてブロックに追加する必要があります。

コンパイル

Dockerでコンパイルします。
https://docs.soliditylang.org/en/latest/installing-solidity.html#docker

docker run --rm -v /solファイルがあるディレクトリの絶対パス:/sources ethereum/solc:stable -o /sources/output --abi --bin /sources/HelloWorld.sol

コンパイルすると以下のように.abi, .binファイルが作成されます。

マイニング

マイニングをしないと送信したコントラクトがブロックに追加されないのでマイニングできる状態にしておきます。

Geth起動後のコンソールでアカウントを作成します。

> personal.newAccount("hoge")
"0xa5964477ed3350865a57f9fe52a5b84340757f99"

アカウント作成後、以下のコマンドでマイニングを開始できます。

miner.start(1)

開始するとinfo.logで(Gethの起動のコマンドのオプションで指定したファイルで)マイニングが行われていることが確認できます。

デプロイ

GethのJavaScrptコンソールを使ってプライベートネットワークにデプロイします。
https://geth.ethereum.org/docs/interface/javascript-console-contracts

先ほどコンパイルした.abiファイルの中身をabi,
.binファイルの中身の先頭に0xをつけたものをbinとして定義します。

> const abi = [{"inputs":[],"name":"helloWorld","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"}]
undefined
> const bin = "0x608060405234801561001057600080fd5b5061017c806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c605f76c14610030575b600080fd5b61003861004e565b6040516100459190610124565b60405180910390f35b60606040518060400160405280600d81526020017f48656c6c6f2c20576f726c642100000000000000000000000000000000000000815250905090565b600081519050919050565b600082825260208201905092915050565b60005b838110156100c55780820151818401526020810190506100aa565b838111156100d4576000848401525b50505050565b6000601f19601f8301169050919050565b60006100f68261008b565b6101008185610096565b93506101108185602086016100a7565b610119816100da565b840191505092915050565b6000602082019050818103600083015261013e81846100eb565b90509291505056fea264697066735822122082b5e02a2553edda3d9df516bdc187878e4eefba787696b0ca3eb43cd102f84164736f6c634300080f0033"
undefined
> 

コントラクトのインスタンスを作成

> const contract = eth.contract(abi)
undefined

ガス代を決める

> const gas = eth.estimateGas({data: bin})
undefined
> gas
135079

アカウントが十分なethを保持していないと以下のエラーが出ます。

Error: gas required exceeds allowance (5003)
        at web3.js:6365:9(45)
        at send (web3.js:5099:62(34))
        at <eval>:1:29(7)

この場合は、マイニングをしてethを増やしておいてください。

デプロイ時にはアカウントのアンロックが必要なのでアンロックしておきます。

> personal.unlockAccount(eth.accounts[0])
Unlock account 0x65528211be089558caf9ae3d6cf3b5dd3c870163
Passphrase: 
true

デプロイします。

> const tx = {'from': eth.accounts[0], data: bin, gas: gas}
undefined
> const deployed_contract = contract.new(tx)
undefined
> deployed_contract
{
  abi: [[{
        inputs: [],
        name: "helloWorld",
        outputs: [...],
        stateMutability: "pure",
        type: "function"
    }]],
  address: "0xe10e49366a4d7ea01c0f9e169f204178e063aeec",
  transactionHash: "0xc78f211199cbde2ca8102b11da8b9031637b1201d1d33b17c00fdc5181081332",
  allEvents: function bound()
}
> 

マイニングされデプロイが成功すると上記のように
deployed_contractaddressに値が入ります。

デプロイされたスマートコントラクトを呼び出してHello,Worldを出力

スマートコントラクトを呼び出すには上記で用意したabiとデプロイ後のaddressが必要になります。

> const abi = [{"inputs":[],"name":"helloWorld","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"}]
undefined
> const contract = eth.contract(abi)
undefined

> const instance = contract.at("0xe10e49366a4d7ea01c0f9e169f204178e063aeec")
undefined

> instance.helloWorld.call()
"Hello, World!"

成功!

Discussion