Edited at

Tendermintで簡単なアプリを動かすまで(残高を更新する)

More than 1 year has passed since last update.


 概要

前回に引き続き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アプリケーションは、次の二つの要素を提供できるように設計しなけらばなりません。


  1. サーバーソケット

  2. 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 consoleabci-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