Geth, SolidityでHello,World
Dockerコンテナ内でGethをプライベートネットで実行し、
Solidityで文字列を出力する処理を実装して、
コンソールでHello,Worldを出力させます。
Gethをプライベートネットワークで実行
GethとはEthereumクライアントの一つです。
まずはGethをDcokerを使ってプライベートネットワークで実行します。
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をプライベートネットワークで起動させるためにジェネシスブロックを作成します。
{
"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ライクな静的型付け言語です。
pragma solidity >=0.6.0 <0.9.0;
contract HelloWorld {
function helloWorld() external pure returns (string memory) {
return "Hello, World!";
}
}
スマートコントラクトをプライベートネットワークにデプロイ
デプロイするには、solファイルをコンパイルし、プライベートネットワークに送信し、マイニングをしてブロックに追加する必要があります。
コンパイル
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コンソールを使ってプライベートネットワークにデプロイします。
先ほどコンパイルした.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_contract
のaddress
に値が入ります。
デプロイされたスマートコントラクトを呼び出して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