👻

EKSで構築したプライベートチェーンにHelloコントラクトをデプロイしてHello Worldするまで

2022/09/07に公開

kubernetesとblockchainの両方の勉強になるかなというのとあんまりgethを使ったnodeの構築みたいな情報がなさそうだったので。
(kubernetesもblockchainも初学者ですが、誤った箇所などありましたらコメントくださいー)

今回の成果物
https://github.com/JY8752/kubernetes-privatenet-hello-contract

開発環境

コントラクト

  • VSCode(foundry使うから)
  • solidity 0.8.15+commit.e14f2714.Darwin.appleclang
  • foundry(コントラクトの作成、デプロイするのに)
    foundryについてはこちらの記事で紹介されています

https://zenn.dev/razokulover/articles/574eb471e6db1c

クライアント

  • Go 1.18.4

インフラ

  • kubernetes
  • Amazon EKS

コントラクト

Hello Worldするだけのコントラクト

forge init hello --vscode
Hello.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

contract Hello {
    function hello() public pure returns (string memory) {
        return "Hello World!!";
    }
}

クライアント

作成したコントラクトのメソッドを呼ぶためのクライアントはいくつか選択肢があるが今回はGoのクライアントを使用。

クライアントモジュールをインストールする。

go get -d github.com/ethereum/go-ethereum
go get github.com/ethereum/go-ethereum/rpc@v1.10.21
go get github.com/ethereum/go-ethereum/accounts/keystore@v1.10.21

abiファイルからソースコードを生成するのにabigenを使用する。
goのethereumクライアントのインストールをしておく必要があるのでここら辺を参考に。
https://geth.ethereum.org/docs/install-and-build/installing-geth

//コントラクトをビルドしてabiファイルを生成する
forge clean
forge build --extra-output-files abi

abigen --abi Hello.abi.json --pkg hello --type hello --out hello/hello.go

設定値をdotファイルで管理したかったので以下インストール

go get github.com/joho/godotenv

コントラクトに繋いでHello Worldする

env.go
package env

import (
	"log"
	"os"

	"github.com/joho/godotenv"
)

func init() {
	var err error
	if os.Getenv("ENVIROMENT") == "prod" {
		err = godotenv.Load("../.env.prod")
	} else {
		err = godotenv.Load("../.env")
	}

	if err != nil {
		log.Fatalf("環境変数の読み込みに失敗しました。.envファイルを作成してください. err: %v", err)
	}
}

func RpcUrl() string {
	return os.Getenv("RPC_URL")
}

func ContractAddress() string {
	return os.Getenv("CONTRACT_ADDRESS")
}
main.go
package main

import (
	"JY8752/kubernetes-privatenet-hello-contract/env"
	"JY8752/kubernetes-privatenet-hello-contract/hello"

	"fmt"
	"log"

	"github.com/ethereum/go-ethereum/accounts/abi/bind"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/ethclient"
)

func main() {
	//client
	cl, err := ethclient.Dial(env.RpcUrl())
	if err != nil {
		log.Fatalf("ethclientの初期化に失敗しました. err: %v", err)
	}

	//hello contract
	address := common.HexToAddress(env.ContractAddress())
	hello, err := hello.NewHello(address, cl)
	if err != nil {
		log.Fatalf("hello contractの取得に失敗しました. err: %v", err)
	}

	res, err := hello.Hello(&bind.CallOpts{})
	if err != nil {
		log.Fatalf("helloメソッドの呼び出しに失敗しました. err: %v", err)
	}

	fmt.Println(res)
}

プライベートチェーンの作成

コントラクトとクライアントはできたのでチェーンを構成する。

dockerイメージの作成

gethを起動し、ブロックチェーンにつながるためには最初のブロックを作成する必要がある。
最初のブロックの作成に以下のようなgenesisファイルを作成する。

genesis.json
{
  "config": {
    "chainId": 15,
    "homesteadBlock": 0,
    "eip150Block": 0,
    "eip155Block": 0,
    "eip158Block": 0,
    "byzantiumBlock": 0,
    "constantinopleBlock": 0
  },
  "coinbase"   : "0x0000000000000000000000000000000000000000",
  "difficulty" : "0x1",
  "extraData"  : "",
  "gasLimit"   : "0x2fefd8",
  "nonce"      : "0x0000000000000042",
  "mixhash"    : "0x0000000000000000000000000000000000000000000000000000000000000000",
  "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
  "timestamp"  : "0x00",
  "alloc": {
    "945cd603a6754cb13c3d61d8fe240990f86f9f8a": { "balance": "500000000000000000000000000" },
    "66b4e7be902300f9a15d900822bbd8803be87391": { "balance": "500000000000000000000000000" },
    "104f0d848da3f760dddadc56fc4ab78305110dba": { "balance": "500000000000000000000000000" },
    "addfaa808c59581f04cdadfc0be28ebfb520e839": { "balance": "500000000000000000000000000" },
    "450a8a99bf5ad49db301f6068c619de2400de6f7": { "balance": "500000000000000000000000000" }
  }
}

genesisファイルを元に初期化する。

geth --datadir . init genesis.json 

成功するとkeystoreディレクトリが新たに作成されている。

アカウントを作成しているので、そのパスワードをファイルに記載しておく。

password
pass
pass
pass
pass
pass

gethの初期化と起動スクリプトを作成する。

entrypoint.sh
#!bin/sh

geth --datadir /var/share/ethereum --nodiscover --maxpeers 0 \
init /var/share/ethereum/genesis.json \
&& \
geth --datadir /var/share/ethereum --networkid 15 \
--nodiscover --maxpeers 0 --mine --miner.threads 1 \
--http --http.addr "0.0.0.0" --http.corsdomain "*" \
--http.vhosts "*" --http.api "eth,web3,personal,net" \
--ipcpath /tmp/geth.ipc --ws --ws.addr "0.0.0.0" \
--ws.api "eth,web3,personal,net" --ws.origins "*" \
--unlock 0,1,2,3,4 --password /var/share/ethereum/password --allow-insecure-unlock

Dockerfileを下記のように作成する。

Dockerfile
FROM ethereum/client-go:v1.10.23

COPY genesis.json /var/share/ethereum/
COPY keystore /var/share/ethereum/keystore/
COPY password /var/share/ethereum/
COPY entrypoint.sh /
RUN chmod 744 /entrypoint.sh

EXPOSE 8545 8546 30303 30303/udp

ENTRYPOINT ["/entrypoint.sh"]

kindを使ったkubernetesマルチノード上にチェーンを構築

EKSにデプロイするのが本題だが、一旦ローカルでkubernetesクラスターを作成し、そこにデプロイしてみる。
ローカル上にkubernetesを使用してマルチノードを構築するのにkindを使用。
https://kind.sigs.k8s.io/

以下のようなkind.ymlを作成する。

kind.yml
apiVersion: kind.x-k8s.io/v1alpha4
kind: Cluster
name: blockchaine-cluster
nodes:
- role: control-plane
- role: worker
- role: worker
- role: worker
  extraPortMappings:
  - containerPort: 30045
    hostPort: 30045
    protocol: TCP
kind create cluster --config kind.yml

control-plane1つとworkerノードが3つできる。

次にkubernetesのマニフェストを以下のように作成する。

private-net-deploy.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    apps: private-net
  name: private-net
spec:
  replicas: 1
  selector:
    matchLabels:
      app: private-net
  template:
    metadata:
      labels:
        app: private-net
    spec:
      containers:
        - image: ghcr.io/jy8752/kubernetes-privatenet-hello-contract:main
          name: private-net

---

apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: private-net
  name: private-net
spec:
  ports:
  - name: rpc
    port: 8545
    nodePort: 30045
  - name: ws
    port: 8546
    nodePort: 30046
  selector:
    app: private-net
  type: NodePort
status:
  loadBalancer: {}

dockerイメージはGithub Packagesを使用して公開している。

作成したマニフェストファイルを適用する。

kubectl apply -f private-net-deploy.yml

port30045で接続できるので確認。

geth attach http://localhost:30045
> Welcome to the Geth JavaScript console!

EKS上にチェーンを構築する

eksctlを使用してEKSクラスターを作成する。(20 - 30分くらいかかる)

eksctl create cluster \                     
--name eks-private-net \
--nodegroup-name ng-private-net \
--node-type t3.large \
--nodes 3

作成が完了したらノードを確認しておく。

kubectl get node -o json | jq ".items[]|{name: .metadata.name, ExternalIP: .status.addresses[1].address}"
{
  "name": "ip-192-168-20-197.ap-northeast-1.compute.internal",
  "ExternalIP": "13.114.138.50"
}
{
  "name": "ip-192-168-55-191.ap-northeast-1.compute.internal",
  "ExternalIP": "35.79.222.168"
}
{
  "name": "ip-192-168-87-66.ap-northeast-1.compute.internal",
  "ExternalIP": "3.112.83.0"
}

マニフェストを適用する。

kubectl apply -f private-net-deploy.yml

security groupを設定してportに穴を開けておく。

aws ec2 authorize-security-group-ingress \
    --group-id sg-0b543374e4be3930d \
    --ip-permissions IpProtocol=tcp,FromPort=30045,ToPort=30045,IpRanges='[{CidrIp=0.0.0.0/0,Description="allow geth attach"}]'

attachしてみる。

geth attach http://13.114.138.50:30045

> Welcome to the Geth JavaScript console!

コントラクトをデプロイしてみる。(エラーが出るので--legacyオプションをつける必要がある。)

source .env.prod; forge create --rpc-url $RPC_URL --private-key $PRIVATE_KEY src/Hello.sol:Hello --legacy

クライアントでリクエストしてみる。

ENVIROMENT=prod go run main.go

> Hello World!!

できた!!!!

まとめ

開発中にローカルにnodeを立ち上げるのはganacheやhardhat、foundryでもanvilを使えば簡単に構築できる。テストネットやメインネットへの接続やデプロイもalchemyなどのサービスを使えば簡単にできる。わざわざ自前でチェーンを構築するのはだいぶ手間かもしれないが便利なライブラリやサービスが隠蔽してくれていた部分の理解が深まりいい学習にはなった。

今回やったこと

  • kindを使用しローカルに構築したkubernetesマルチノードにプライベートチェーンを構築し、コントラクトをデプロイした。
  • EKSに構築したプライベートチェーンにコントラクトをデプロイし、go-ethereumクライアントを使用してコントラクトのメソッドを呼び出した。

参考

eksctl
https://dev.classmethod.jp/articles/getting-started-amazon-eks-with-eksctl/
foundryのlegacyオプションについて
https://github.com/foundry-rs/foundry/issues/579
Error: Returned error: invalid opcode: SHR
https://ethereum.stackexchange.com/questions/87245/error-returned-error-invalid-opcode-shr

GitHubで編集を提案

Discussion