Edited at

Ethereumのスマートコントラクト教材をHyperledger Fabric上でやってみる

昨年に続いて2回めの挑戦です。よろしくお願いします。

Hyperledger Fabric v1.3の新規機能の1つでもあるEVMなChaincodeを使って、Hyperledger Fabric上でSolidityで書いたEthereumのスマートコントラクト教材の定番らしいCryptoZombiesというDappなゲームアプリを動かしてみたいと思う。


はじめに

以下のようなバックグラウンドです。


  • Hyperledger Fabricには日頃から触れている


    • Hyperledger Explorerのコミュニティで活動中



  • でもEthereumについてはさっぱり。Solidityでスマートコントラクトを書いたこともない

まずは以下に情報があるので、その手順に従って環境を作っていくことにする。

環境は


  • Ubuntu 16.04.5 LTS

  • docker 18.06.1-ce

  • go version go1.11.1 linux/amd64


結論

EVM上で未サポートなオペコードに遭遇して、結局GradesDappの実行までには至らなかった。Readmeで取り上げられている Simple Storage Dapp は動いたので、現段階ではスマートコントラクトの実装によって動く、動かないが左右されそう。複雑度の高いスマートコントラクトでは動かない可能性が高いのではないか、という予測。なので、残念ながら、より複雑そうな CryptoZombies は試してもいない。

EthereumのDAppは、パブリックなチェーンでの利用シーンが主であると個人的には認識しているので、コンソーシアムなチェーンへのスマートコントラクトの流用がどれだけの恩恵をもたらすのか、今の時点でピンとこないのが正直なところですが、今後実装が充実すれば、Hyperledger Fabricの利用シーンを増やす1つの材料になるのかもしれない。


まずはブロックチェーンネットワークを立ち上げる

ここはみなさんおなじみのfabric-samplesリポジトリのクローンから。


  • 出だしから間違えたようです。詳しくは こちら を参照

git clone https://github.com/hyperledger/fabric-samples.git -b release-1.3

cd fabric-samples
./scripts/bootstrap.sh
export PATH=$PWD/bin:$PATH

ここまでで必要なコンテナイメージやネットワーク立ち上げに必要なツールがダウンロードされ、ツールにはパスを通した状態になる。

cd first-network

./byfn.sh up

あとは上記を実行することで以下のログが出力されてネットワーク立ち上げ成功。

========= All GOOD, BYFN execution completed =========== 

_____ _ _ ____
| ____| | \ | | | _ \
| _| | \| | | | | |
| |___ | |\ | | |_| |
|_____| |_| \_| |____/


EVM chaincodeをインストールする

ネットワークの準備が整ったところでスマートコントラクト実行に必要なリソースを取得する。



  • go getで取得する必要がある。詳しくは こちら を参照

git clone https://github.com/hyperledger/fabric-chaincode-evm.git

EVMなchaincodeを取り込むためにdocker-composeファイルを以下の通り、少し修正する。

diff --git a/first-network/docker-compose-cli.yaml b/first-network/docker-compose-cli.yaml

index c7f7471..8995be3 100644
--- a/first-network/docker-compose-cli.yaml
+++ b/first-network/docker-compose-cli.yaml
@@ -83,6 +83,7 @@ services:
- ./crypto-config:/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/
- ./scripts:/opt/gopath/src/github.com/hyperledger/fabric/peer/scripts/
- ./channel-artifacts:/opt/gopath/src/github.com/hyperledger/fabric/peer/channel-artifacts
+ - ./../../fabric-chaincode-evm:/opt/gopath/src/github.com/hyperledger/fabric-chaincode-evm
depends_on:
- orderer.example.com
- peer0.org1.example.com

ちなみにここでは以下のようなディレクトリ構成で話をすすめる。

$GOPATH/src/github.com/hyperledger/

fabric-samples
fabric-samples/first-network
fabric-chaincode-evm

これにより、cliコンテナからEVM chaincodeを見えるようになり、任意のPeer上にcliコンテナからインストール可能になる。

ブロックチェーンネットワークを再起動する

cd fabric-samples/first-network

./byfn.sh down
./byfn.sh up

以下のスクリプト scripts/installevm.sh を作成し、cliコンテナ上から実行してEVM chaincodeをpeer0.org1ノードにインストールする


scripts/installevm.sh

#!/bin/bash

export CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=peer0.org1.example.com:7051
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
peer chaincode install -n evmcc -l golang -v 0 -p github.com/hyperledger/fabric-chaincode-evm/evmcc
peer chaincode instantiate -n evmcc -v 0 -C mychannel -c '{"Args":[]}' -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem

cd fabric-samples/first-network

vi scripts/installevm.sh
chmod a+x scripts/installevm.sh
docker exec -it cli scripts/installevm.sh

ここまでエラーが出なければ、dev-peer0.org1.example.com-evmcc-0という新しいコンテナがスタートしているはず。

ためしに何かスマートコントラクトをbytecode直指定でデプロイしてみる。

docker exec -it cli peer chaincode invoke -n evmcc -C mychannel  -c '{"Args":["0000000000000000000000000000000000000000","608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a723058203dbaed52da8059a841ed6d7b484bf6fa6f61a7e975a803fdedf076a121a8c4010029"]}' -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem

2018-12-11 12:48:51.579 UTC [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 payload:"38a9177411170d69038cdabb3758507d33f1cbb0"

成功したっぽい。 :smile:


もっと初歩のGradesDappを動かすことにする

あまりにもSolidityによるスマートコントラクトのデプロイ方法やアクセス方法のことを知らないので、もう1ステップ踏む。以下の成績表アプリGradesDappを動かしてみることにする。

A Beginner’s Guide to Blockchain Programming – Hacker Noon

まずEthereum環境で動かすわけだが、そこは端折ることにする。言われるがままにやればよかった。以下のポイントにだけ注意する。


ポイント1


  • solcのバージョンが新しいとコンパイルに失敗する。ので、solcのバージョンを明に指定した(^0.4.18)。


バイトコードをEVM chaincode上にデプロイ

Ethereum環境で試す過程でコントラクトのバイトコードも手に入ったので、さっそくHyperledger Fabric上のEVM chaincodeにデプロイする。


scripts/deploy_contract.sh

#!/bin/bash

docker exec -it cli peer chaincode invoke -n evmcc -C mychannel \
-c '
{
"Args":[
"0000000000000000000000000000000000000000",
"[ contract の 長いバイトコードを貼り付ける ]"
]
}
'
\
-o orderer.example.com:7050 \
--tls \
--cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem

cd $GOPAH/src/github.com/hyperledger/fabric-samples/first-network

scripts/deploy_contract.sh
2018-12-13 14:16:48.856 UTC [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 payload:"6f0c0e4cc87dd1c52204016bc6c26128294d025b"

成功! :smiley: contract address 6f0c0e4cc87dd1c52204016bc6c26128294d025b がトランザクションの返却値として得られる。

さっそくスマートコントラクトにアクセスしてみたいのだが、これがひときわ面倒。関数のハッシュ求めて、引数をHexに変換して関数ハッシュと結合して、、となるわけだ。

関数ハッシュはコンパイルした時の出力にあったが(以下、ポイント2参照)、引数指定とか果てしなく面倒なので、ここでようやくweb3.jsの登場なのである。


ポイント2


  • function hashはコンパイルしたコードの中に格納されている

funchash = compiledCode.contracts[':Grades'].functionHashes

{ 'getGradeForStudent(bytes32)': 'ed12bfb5',
'giveGradeToStudent(bytes32,string)': 'ab258bab',
'grades(bytes32)': '304823e5',
'studentList(uint256)': '7df221e9',
'validStudent(bytes32)': '34e47d50' }


RPCでEVMで会話してくれるfabproxyを動かす

fabric-chaincode-evm内に含まれる fabproxy をビルドする必要があるのだが少し手間だった。 :frowning2:


  • クローンした fabric-chaincode-evm ディレクトリをGOPATHが通ったところに移したり( or GOPATHに追加したり)

  • 足りないパッケージをgo build時のエラーメッセージに従ってコツコツインストールしたり ( スマートな依存関係解決方法がありそうな気もするがGOの日が浅いのでまだ深く追わないことにする )

  • ビルドエラーが出たり

fabproxy/cmd/main.go:73:38: cannot use client (type *"github.com/hyperledger/fabric-sdk-go/pkg/client/channel".Client) as type fabproxy.ChannelClient in argument to fabproxy.NewEthService:

*"github.com/hyperledger/fabric-sdk-go/pkg/client/channel".Client does not implement fabproxy.ChannelClient (wrong type for Execute method)
have Execute("github.com/hyperledger/fabric-sdk-go/pkg/client/channel".Request, ..."github.com/hyperledger/fabric-sdk-go/pkg/client/channel".RequestOption) ("github.com/hyperledger/fabric-sdk-go/pkg/client/channel".Response, error)
want Execute("github.com/hyperledger/fabric-chaincode-evm/vendor/github.com/hyperledger/fabric-sdk-go/pkg/client/channel".Request, ..."github.com/hyperledger/fabric-chaincode-evm/vendor/github.com/hyperledger/fabric-sdk-go/pkg/client/channel".RequestOption) ("github.com/hyperledger/fabric-chaincode-evm/vendor/github.com/hyperledger/fabric-sdk-go/pkg/client/channel".Response, error)
fabproxy/cmd/main.go:73:38: cannot use ledger (type *"github.com/hyperledger/fabric-sdk-go/pkg/client/ledger".Client) as type fabproxy.LedgerClient in argument to fabproxy.NewEthService:
*"github.com/hyperledger/fabric-sdk-go/pkg/client/ledger".Client does not implement fabproxy.LedgerClient (wrong type for QueryBlock method)
have QueryBlock(uint64, ..."github.com/hyperledger/fabric-sdk-go/pkg/client/ledger".RequestOption) (*"github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/protos/common".Block, error)
want QueryBlock(uint64, ..."github.com/hyperledger/fabric-chaincode-evm/vendor/github.com/hyperledger/fabric-sdk-go/pkg/client/ledger".RequestOption) (*"github.com/hyperledger/fabric-chaincode-evm/vendor/github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/protos/common".Block, error)
fabproxy/cmd/main.go:73:38: cannot use logger (type *"go.uber.org/zap".SugaredLogger) as type *"github.com/hyperledger/fabric-chaincode-evm/vendor/go.uber.org/zap".SugaredLogger in argument to fabproxy.NewEthService

っていうところで、日付も変わりそうなので明日以降に続く。。枠に空きはありそうなので

fabric-chaincode-evmをクローンしたのがいけなかった。go getで取得した上でビルドすると成功 :smiley:


誤りポイント



  • fabric-chaincode-evmgo getで取得する。fabric-samplesもこれに基づいて、GOPATH内に移動


    • ちなみにこの注意ポイントは間接的に Readme にも書いてあると言えなくもない(分かりにくいけど)。

    • 途中からリンクから飛んできた場合は元の説明に戻って手順を継続



go get github.com/hyperledger/fabric-chaincode-evm

cd $GOPATH/src/github.com/hyperledger/fabric-chaincode-evm
go build -o fab3 ./fabproxy/cmd
cd $GOPATH/src/github.com/hyperledger
git clone https://github.com/hyperledger/fabric-samples.git -b release-1.3

必要な環境変数を設定して生成した実行ファイル fab3 を実行


fab3-envsetup.sh

# Environment Variables for Fabproxy:

export FABPROXY_CONFIG=${GOPATH}/src/github.com/hyperledger/fabric-chaincode-evm/examples/first-network-sdk-config.yaml # Path to a compatible Fabric SDK Go config file
export FABPROXY_USER=User1 # User identity being used for the proxy (Matches the users names in the crypto-config directory specified in the config)
export FABPROXY_ORG=Org1 # Organization of the specified user
export FABPROXY_CHANNEL=mychannel # Channel to be used for the transactions
export FABPROXY_CCID=evmcc # ID of the EVM Chaincode deployed in your fabric network
export PORT=5000 # Port the proxy will listen on. If not provided default is 5000.

$ source fab3-envsetup.sh

$ ./fab3
{"level":"info","ts":1544707161.6615047,"logger":"fab3","caller":"cmd/main.go:75","msg":"Starting Fab Proxy on port 5000\n"}


nodeからweb3経由でコントラクトにアクセス

取得している成績表DAppである GradesDapp ディレクトリに移動し、指定の web3 パッケージをインストールした上で node のインタープリターを開く

npm install web3@0.20.2

node
>

以下を実行すると John に A+ をあげてみる。途中の結果出力は割愛。SolidityのコードGrades.solデプロイの手順 で取得した contract address を参照する。

code = fs.readFileSync('Grades.sol').toString()

solc = require('solc')
compiledCode = solc.compile(code)

Web3 = require('web3')
web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:5000'))

web3.eth.accounts
web3.eth.defaultAccount = web3.eth.accounts[0]

abiDefinition = JSON.parse(compiledCode.contracts[':Grades'].interface)
GradesContract = web3.eth.contract(abiDefinition)
byteCode = compiledCode.contracts[':Grades'].bytecode

myContract = GradesContract.at('6f0c0e4cc87dd1c52204016bc6c26128294d025b')
myContract.giveGradeToStudent('John', 'A+', {from: web3.eth.accounts[0]})
Error: Failed to execute transaction: Transaction processing for endorser [localhost:7051]: Chaincode status Code: (500) UNKNOWN. Description: failed to execute contract: call error: REVERT not yet fully implemented
at Object.InvalidResponse (/home/atsushi/qiita/02_advent_2018/GradesDapp/node_modules/web3/lib/web3/errors.js:38:16)
at RequestManager.send (/home/atsushi/qiita/02_advent_2018/GradesDapp/node_modules/web3/lib/web3/requestmanager.js:61:22)
at Eth.send [as sendTransaction] (/home/atsushi/qiita/02_advent_2018/GradesDapp/node_modules/web3/lib/web3/method.js:145:58)
at SolidityFunction.sendTransaction (/home/atsushi/qiita/02_advent_2018/GradesDapp/node_modules/web3/lib/web3/function.js:170:26)
at SolidityFunction.execute (/home/atsushi/qiita/02_advent_2018/GradesDapp/node_modules/web3/lib/web3/function.js:256:37)

call error: REVERT not yet fully implemented のエラー。なにやらEVMの実装上、GradesDappのバイトコード内で使っていると思われる REVERT オペコードがまだ未サポートな模様。。。


リンク

参考にさせて頂きました。 :bow: