概要
前回に引き続きtendermintについて書きます。
今回は、Tendermintと実際にトランザクションに対してどのような処理をするかなどを決めるアプリケーションレイヤーをつなぐABCI(Application BlockChain Interface)をインストールし、簡単なアプリを動かしてみルことにします。
Bitcoinの場合は、UTXOというアプリケーションが実行され、Ethererumの場合は、EVMと呼ばれるアセンブラを実行できるようになっています。(Ethererumと同じ機能をTendermintで実装したものが、Ethermintです。)
前回の記事は、以下を確認してください。
TendermintをLocal開発環境にインストールするまで
ABCIのインストール
前回に引き続き、Mac上でVitualboxを用いて作ったUbuntuにsshで接続し、はじめにABCIをインストールしていきます。公式のドキュメントには、go getを用いてインストールするように書いてあるが、この方法ではうまくいかなかったので、git cloneを用います。 git をまだインストールしていなければ、はじめに以下を実行します。
$ sudo apt-get update
$ sudo apt-get install git
$ git --version
git version x.x.x
インストールされている場合は、前回作った、tendermintディレクトリーへ移動します。
$ cd
$ cd usr/local/go/src/github.com/tendermint
ABCIをcloneするためのディレクトリーを作し、cloneします。
$ mkdir abci
$ cd abci
$ git clone https://github.com/tendermint/abci
これで、ABCIをインストールする準備が整った。ちなみに、abciディレクトリーには、次のようなファイルが配置されてあるはずです。
$ ls
CHANGELOG.md example Makefile specification.md version
client Gopkg.lock README.md specification.rst
cmd Gopkg.toml scripts tests
Dockerfile.develop LICENSE server types
最後にABCIをインストールする。Goのファイルがある、abci/cmd/abci-cli へ移動し、インストール。
$ cd cmd
$ cd abci-cli
$ go install
正しくインストールされている場合は、versionが表示されます。
$ abci-cli version
Tendermintを動かそう!!
これでやっとtendermintを動かす準備が整いました。
KV-store
まず、はじめにabci-cliでKVstoreを起動します。
KVstoreは、key-valueをMerkle Treeでメモリーに保存するアプリケーション
https://github.com/tendermint/abci/blob/master/cmd/abci-cli/abci-cli.go で実装されている。
$ abci-cli kvstore
すると、
E[06-08|05:22:38.311] Connection was closed by client module=abci-server
E[06-08|05:22:38.311] Connection was closed by client module=abci-server
E[06-08|05:22:38.311] Connection was closed by client module=abci-server
と表示されるはず。これは、Tendermintの方がActiveになっていないからです。
そこで次にブロックチェーン部分をTendermintで動かすために、ターミナルを別でもう一つ開き、こちらでnodeを起動します。
$ tendermint init
$ tendermint node
すると、ブロック生成が始まります。例えば、ここでは2601番目に生成されたBlockについての一例を載せる。各時刻でpropose,commit,prevote,voteなどが行われているのがわかります。
sus dur=978.453767ms height=2601 round=0 step=RoundStepNewHeight
I[06-09|14:51:21.761] enterNewRound(2601/0). Current: 2601/0/RoundStepNewHeight module=consensus height=2601 round=0
I[06-09|14:51:21.763] enterPropose(2601/0). Current: 2601/0/RoundStepNewRound module=consensus height=2601 round=0
I[06-09|14:51:21.764] enterPropose: Our turn to propose module=consensus height=2601 round=0 proposer=5B0BA5F9032515CEE082E8A9391963CB3F200A61 privValidator="PrivValidator{5B0BA5F9032515CEE082E8A9391963CB3F200A61 LH:2600, LR:0, LS:3}"
I[06-09|14:51:21.777] Signed proposal module=consensus height=2601 round=0 proposal="Proposal{2601/0 1:AA8E53C09AED (-1,:0:000000000000) /BC9E69644173.../ @ 2018-06-09T14:51:21.765Z}"
I[06-09|14:51:21.782] Received proposal module=consensus proposal="Proposal{2601/0 1:AA8E53C09AED (-1,:0:000000000000) /BC9E69644173.../ @ 2018-06-09T14:51:21.765Z}"
I[06-09|14:51:21.785] Received complete proposal block module=consensus height=2601 hash=AB81757ACFF46EBE53459A93B30536278DFE6FD7
I[06-09|14:51:21.786] enterPrevote(2601/0). Current: 2601/0/RoundStepPropose module=consensus
I[06-09|14:51:21.787] enterPrevote: ProposalBlock is valid module=consensus height=2601 round=0
I[06-09|14:51:21.791] Signed and pushed vote module=consensus height=2601 round=0 vote="Vote{0:5B0BA5F90325 2601/00/1(Prevote) AB81757ACFF4 /A9DAF094A48C.../ @ 2018-06-09T14:51:21.788Z}" err=null
I[06-09|14:51:21.793] Added to prevote module=consensus vote="Vote{0:5B0BA5F90325 2601/00/1(Prevote) AB81757ACFF4 /A9DAF094A48C.../ @ 2018-06-09T14:51:21.788Z}" prevotes="VoteSet{H:2601 R:0 T:1 +2/3:AB81757ACFF46EBE5345B30536278DFE6FD7:1:AA8E53C09AED(1) BA{1:x} map[]}"
I[06-09|14:51:21.795] enterPrecommit(2601/0). Current: 2601/0/RoundStepPrevote module=consensus height=2601 round=0
I[06-09|14:51:21.796] enterPrecommit: +2/3 prevoted proposal block. Locking module=consensus height=2601 round=0 hash=AB81757ACFF46EBE53459A93B30536278DFE6FD7
I[06-09|14:51:21.799] Signed and pushed vote module=consensus height=2601 round=0 vote="Vote{0:5B0BA5F90325 2601/00/2(Precommit) AB81757ACFF4 /1622EEB995CA.../ @ 2018-06-09T14:51:21.797Z}" err=null
I[06-09|14:51:21.803] Added to precommit module=consensus vote="Vote{0:5B0BA5F90325 2601/00/2(Precommit) AB81757ACFF4 /1622EEB995CA.../ @ 2018-06-09T14:51:21.797Z}" precommits="VoteSet{H:2601 R:0 T:2 +2/3:AB81757ACFF46EBE534A93B30536278DFE6FD7:1:AA8E53C09AED(1) BA{1:x} map[]}"
I[06-09|14:51:21.804] enterCommit(2601/0). Current: 2601/0/RoundStepPrecommit module=consensus height=2601 commitRound=0
I[06-09|14:51:21.805] Commit is for locked block. Set ProposalBlock=LockedBlock module=consensus height=2601 commitRound=0 blockHash=AB81757ACFF46EBE53459A930536278DFE6FD7
I[06-09|14:51:21.807] Finalizing commit of block with 0 txs module=consensus height=2601 hash=AB81757ACFF46EB3459A93B30536278DFE6FD7 root=0400000000000000
I[06-09|14:51:21.808] Block{
Header{
ChainID: test-chain-wTDnfM
Height: 2601
Time: 2018-06-09 14:51:21.765544508 +0000 UTC
NumTxs: 0
TotalTxs: 0
LastBlockID: D33328921E1FD7CBD1DC594A6DE44CE08AFB6:1:A39CFAB37A55
LastCommit: F88AC710A4D17FF1B7D2FD7ABA5B285E0DE412F
Data:
Validators: 33B01D34ED11911057391291BFCF8458F4E0
App: 0400000000000000
Consensus: F66EF1DF8B6DAC7A1ECCE40CC84E54A1CEBC6A5
Results:
Evidence:
}#AB81757ACFF46EBE459A930536278DFE6FD7
Data{
}#
Data{
}#
Commit{
BlockID: D33328921ED7CBD1DC594A6DE44CFC0E08AFB6:1:A39CFAB37A55
Precommits: Vote{0:5B0BA5F90325 2600/00/2(Precommit) D33328921E1F /0661C7433900.../ @ 2018-06-09T14:51:20.751Z}
}#F88AC710A4D117FF1B2FD7ABA5B285E0DE412F
}#AB81757ACFF46EBE53459A93B30536278DFE6FD7 module=consensus
I[06-09|14:51:21.821] Executed block module=state height=2601 validTxs=0 invalidTxs=0
I[06-09|14:51:21.824] Committed state module=state height=2601 txs=0 appHash=0400000000000000
I[06-09|14:51:21.824] Recheck txs module=mempool numtxs=0 height=2601
I[06-09|14:51:21.830] Indexed block module=txindex height=2601
このコードを見ると、 deliver_tx, check_tx, and commitなどが確認できます。
KV-storeは以下のように実装されています。
func cmdKVStore(cmd *cobra.Command, args []string) error {
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
// Create the application - in memory or persisted to disk
var app types.Application
if flagPersist == "" {
app = kvstore.NewKVStoreApplication()
} else {
app = kvstore.NewPersistentKVStoreApplication(flagPersist)
app.(*kvstore.PersistentKVStoreApplication).SetLogger(logger.With("module", "kvstore"))
}
// Start the listener
srv, err := server.NewServer(flagAddrD, flagAbci, app)
if err != nil {
return err
}
srv.SetLogger(logger.With("module", "abci-server"))
if err := srv.Start(); err != nil {
return err
}
// Wait forever
cmn.TrapSignal(func() {
// Cleanup
srv.Stop()
})
return nil
}
これを用いれば、keyとvalueをBlockchainを用いて保管できます。
ABCIアプリケーションは、次の二つの要素を提供できるように設計しなけらばなりません。
- サーバーソケット
- ABCIのメッセージを扱える
abci-clientに関するtoolを実行するとき、まずはじめにアプリケーションソケットサーバーのコネクションを開始します。abci-clientは、命令されたabciのメッセージを送信し、responseを待つように設計されています。
abci-cli info
コマンドでも、新しいコネクションが開始されます。
info()では、トランザクションの回数を教えてくれます。
Client側で
$ abci-cli info
を実行します。すると、サーバー側では、次のような更新がlogとして確認できます。
I[06-10|03:57:46.389] Accepted a new connection module=abci-server
I[06-10|03:57:46.389] Waiting for new connection... module=abci-server
E[06-10|03:57:46.394] Connection was closed by client module=abci-server
はじめに、new connectionを検出して、操作が終了すると、接続がcloseするのがわかります。
このように、全てのコマンドは、そのコマンドごとに新しいコネクションが作られ実行されコネクションが閉じるようになっています。もし、一度のコネクションで複数の操作を行いたい場合は、 abci-cli console
やabci-cli batch
を用いることで実行できます。
abci-cliコンソールを実行すると、アプリケーションにABCIメッセージを送るためのインタラクティブコンソールが表示されます。
0xabというアドレスの残高を100に設定してみます。
それでは試してみる。tendermint info などでもコマンドを実行できるが、deliver_txを実行すると、以下のようにクォテーションマークで囲っているにも関わらずエラーがでます。
abci-cli deliver_tx "abc"
I[06-10|01:34:21.307] Starting socketClient module=abci-client impl=socketClient
Error: Invalid string arg: "abc". Must be quoted or a "0x"-prefixed hex string
Usage:
abci-cli deliver_tx [flags]
Flags:
-h, --help help for deliver_tx
Global Flags:
--abci string either socket or grpc (default "socket")
--address string address of application socket (default "tcp://0.0.0.0:46658")
--log_level string set the logger level (default "debug")
-v, --verbose print the command and results as if it were a console session
Invalid string arg: "abc". Must be quoted or a "0x"-prefixed hex stringroot@vagr
そのため、ABCIのconsoleでこれらを実行します。
$ abci-cli console
>deliver_tx "0xab=100"
-> code: OK
//コミットする。
> commit
-> code: OK
-> data.hex: 0x0400000000000000
//きちんと保存されているか確認します。
> query "0xab"
-> code: OK
-> log: exists
-> height: 0
-> value: 100
-> value.hex: 313030
これでうまく言っていることがわかります。
Note that if we do deliver_tx "abc" it will store (abc, abc), but if we do deliver_tx "abc=efg" it will store (abc, efg).
残高を200に修正したかった場合は、上記と同様の操作を行えば良いです。
>deliver_tx "0xab=200"
-> code: OK
> query "0xab"
-> code: OK
-> log: exists
-> height: 0
-> value: 200
-> value.hex: 31a0b0
curlによるトランザクションの確認
curl -s localhost:46657/status
curlでtendermintで生成しているBlockの情報を手に入れられます。
{
"jsonrpc": "2.0",
"id": "",
"result": {
"node_info": {
"id": "4250d8d0178118af34a66f4edaebb763cc4291",
"listen_addr": "10.0.2.15:46656",
"network": "test-chain-wTDnfM",
"version": "0.20.0",
"channels": "4020212223303800",
"moniker": "vagrant-ubuntu-trusty-64",
"other": [
"amino_version=0.9.7",
"p2p_version=0.5.0",
"consensus_version=v1/0.2.2",
"rpc_version=0.7.0/3",
"tx_index=on",
"rpc_addr=tcp://0.0.0.0:46657"
]
},
"sync_info": {
"latest_block_hash": "8BB766A48020B74344D9BB7ABB9920782C4FB9A7",
"latest_app_hash": "1000000000000000",
"latest_block_height": 6180,
"latest_block_time": "2018-06-10T04:26:53.799625697Z",
"syncing": false
},
"validator_info": {
"address": "5B0BA5F92515CEE082E8A9391963CB3F200A61",
"pub_key": {
"type": "AC26791624DE60",
"value": "a57Dfsem2RUstwRilspsdF00+diohYHU6bPM9gEOE="
},
"voting_power": 10
}
}
次に先ほど同様に、key-valueを保存してみます。
$ curl -s 'localhost:46657/broadcast_tx_commit?tx="name=satoshi"'
{
"jsonrpc": "2.0",
"id": "",
"result": {
"check_tx": {
"fee": {}
},
"deliver_tx": {
"tags": [
{
"key": "YXBwLmNyZWF0b3I=",
//decodeするとapp.creator
"value": "amFl"
//decodeするとjae(tendermintのCEOの名前が!!!)
},
{
//decodeするとapp.keyと書いてある。
"key": "YXBwLmtleQ==",
//decodeするとnameと書いてある。
"value": "bmFtZQ=="
}
],
"fee": {}
},
"hash": "1B927707DADF01044D3D6E5D95257F53F9537BEE",
"height": 6822
}
ちなみに、Applicationの作成者にTendermintのCEOであるJaeさんの名前が書いてあるところが面白いところ!
このcommitでブロックに格納された情報は、次の様にしてみることができます。
$ curl -s 'localhost:46657/abci_query?data="name"'
{
"jsonrpc": "2.0",
"id": "",
"result": {
"response": {
"log": "exists",
"index": "-1",
"key": "bmFtZQ==",
//decodeするとsatoshiと書いてある。
"value": "c2F0b3NoaQ=="
}
}
トラブルシューティング
tendermintを動かし、その後一旦止めて、また動かすと、次のようにExecuted block とApplying blockを繰り返します。
$ tendermint init
$ tendermint node
I[06-10|01:20:39.617] Executed block module=consensus height=3911 validTxs=0 invalidTxs=0
I[06-10|01:20:39.617] Applying block module=consensus height=3912
I[06-10|01:20:39.618] Executed block module=consensus height=3912 validTxs=0 invalidTxs=0
I[06-10|01:20:39.618] Applying block module=consensus height=3913
I[06-10|01:20:39.620] Executed block module=consensus height=3913 validTxs=0 invalidTxs=0
I[06-10|01:20:39.636] Applying block module=consensus height=3914
これは、有効なバリデータを設定できていないからでです。
そこで、一度tendermintをリセットする必要があります。
tendermint unsafe_reset_all
をすれば、以前同様に、ブロック生成がきちんとされるようになります。
Bench-Mark
上記のように構成したUbuntuでtendermintのbenchmarkを行った。結果は、以下に示します。
簡単なトランザクションであるkvstoreでは、
$ tendermint node --proxy_app=kvstore
別ウィンドで、
tm-bench -T 10 -r 1000 localhost:46657
結果
Stats | Avg | StdDev | Max |
---|---|---|---|
Txs/sec | 735 | 511 | 1717 |
Blocks/sec | 0.900 | 0.300 | 1 |
TPSはいい感じ!!
reference
decodingには、以下のサイトを用いました。
http://www.convertstring.com/ja/EncodeDecode/Base64Decode