本篇內(nèi)容主要講解“Go語言Tendermint Core開發(fā)方法是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Go語言Tendermint Core開發(fā)方法是什么”吧!
讓客戶滿意是我們工作的目標,不斷超越客戶的期望值來自于我們對這個行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡單的方式提供給客戶,將通過不懈努力成為客戶在信息化領域值得信任、有價值的長期合作伙伴,公司提供的服務項目有:域名與空間、網(wǎng)頁空間、營銷軟件、網(wǎng)站建設、墾利網(wǎng)站維護、網(wǎng)站推廣。
Tendermint Core是一個用Go語言開發(fā)的支持拜占庭容錯/BFT的區(qū)塊鏈中間件,用于在一組節(jié)點之間安全地復制狀態(tài)機/FSM。Tendermint Core的出色之處在于它是第一個實現(xiàn)BFT的區(qū)塊鏈共識引擎,并且始終保持這一清晰的定位。這個指南將介紹如何使用Go語言開發(fā)一個基于Tendermint Core的區(qū)塊鏈應用。
Tendermint Core為區(qū)塊鏈應用提供了極其簡潔的開發(fā)接口,支持各種開發(fā)語言,是開發(fā)自有公鏈/聯(lián)盟鏈/私鏈的首選方案,例如Cosmos、Binance Chain、Hyperledger Burrow、Ethermint等均采用Tendermint Core共識引擎。
雖然Tendermint Core支持任何語言開發(fā)的狀態(tài)機,但是如果采用Go之外的其他開發(fā)語言編寫狀態(tài)機,那么應用就需要通過套接字或gRPC與Tendermint Core通信,這會造成額外的性能損失。而采用Go語言開發(fā)的狀態(tài)機可以和Tendermint Core運行在同一進程中,因此可以得到最好的性能。
請參考官方文檔安裝Go開發(fā)環(huán)境。
確認你已經(jīng)安裝了最新版的Go:
$ go version go version go1.12.7 darwin/amd64
確認你正確設置了GOPATH
環(huán)境變量:
$ echo $GOPATH /Users/melekes/go
首先創(chuàng)建一個新的Go語言項目:
$ mkdir -p $GOPATH/src/github.com/me/kvstore $ cd $GOPATH/src/github.com/me/kvstore
在example
目錄創(chuàng)建main.go
文件,內(nèi)容如下:
package main import ( "fmt" ) func main() { fmt.Println("Hello, Tendermint Core") }
運行上面代碼,將在標準輸出設備顯示指定的字符串:
$ go run main.go Hello, Tendermint Core
Tendermint Core與應用之間通過ABCI(Application Blockchain Interface)通信,使用的報文消息類型都定義在protobuf文件中,因此基于Tendermint Core可以運行任何語言開發(fā)的應用。
創(chuàng)建文件app.go
,內(nèi)容如下:
package main import ( abcitypes "github.com/tendermint/tendermint/abci/types" ) type KVStoreApplication struct {} var _ abcitypes.Application = (*KVStoreApplication)(nil) func NewKVStoreApplication() *KVStoreApplication { return &KVStoreApplication{} } func (KVStoreApplication) Info(req abcitypes.RequestInfo) abcitypes.ResponseInfo { return abcitypes.ResponseInfo{} } func (KVStoreApplication) SetOption(req abcitypes.RequestSetOption) abcitypes.ResponseSetOption { return abcitypes.ResponseSetOption{} } func (KVStoreApplication) DeliverTx(req abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx { return abcitypes.ResponseDeliverTx{Code: 0} } func (KVStoreApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx { return abcitypes.ResponseCheckTx{Code: 0} } func (KVStoreApplication) Commit() abcitypes.ResponseCommit { return abcitypes.ResponseCommit{} } func (KVStoreApplication) Query(req abcitypes.RequestQuery) abcitypes.ResponseQuery { return abcitypes.ResponseQuery{Code: 0} } func (KVStoreApplication) InitChain(req abcitypes.RequestInitChain) abcitypes.ResponseInitChain { return abcitypes.ResponseInitChain{} } func (KVStoreApplication) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock { return abcitypes.ResponseBeginBlock{} } func (KVStoreApplication) EndBlock(req abcitypes.RequestEndBlock) abcitypes.ResponseEndBlock { return abcitypes.ResponseEndBlock{} }
接下來我們逐個解讀上述方法并添加必要的實現(xiàn)邏輯。
當一個新的交易進入Tendermint Core時,它會要求應用先進行檢查,比如驗證格式、簽名等。
func (app *KVStoreApplication) isValid(tx []byte) (code uint32) { // check format parts := bytes.Split(tx, []byte("=")) if len(parts) != 2 { return 1 } key, value := parts[0], parts[1] // check if the same key=value already exists err := app.db.View(func(txn *badger.Txn) error { item, err := txn.Get(key) if err != nil && err != badger.ErrKeyNotFound { return err } if err == nil { return item.Value(func(val []byte) error { if bytes.Equal(val, value) { code = 2 } return nil }) } return nil }) if err != nil { panic(err) } return code } func (app *KVStoreApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx { code := app.isValid(req.Tx) return abcitypes.ResponseCheckTx{Code: code, GasWanted: 1} }
如果進來的交易格式不是{bytes}={bytes}
,我們將返回代碼1
。如果指定的key和value已經(jīng)存在,我們返回代碼2
。對于其他情況我們返回代碼0
表示交易有效 —— 注意Tendermint Core會將返回任何非零代碼的交易視為無效交易。
有效的交易最終將被提交,我們使用badger 作為底層的鍵/值庫,badger是一個嵌入式的快速KV數(shù)據(jù)庫。
import "github.com/dgraph-io/badger"type KVStoreApplication struct { db *badger.DB currentBatch *badger.Txn }func NewKVStoreApplication(db *badger.DB) *KVStoreApplication { return &KVStoreApplication{ db: db, } }
當Tendermint Core確定了新的區(qū)塊后,它會分三次調(diào)用應用:
BeginBlock:區(qū)塊開始時調(diào)用
DeliverTx:每個交易時調(diào)用
EndBlock:區(qū)塊結(jié)束時調(diào)用
注意,DeliverTx是異步調(diào)用的,但是響應是有序的。
func (app *KVStoreApplication) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock { app.currentBatch = app.db.NewTransaction(true) return abcitypes.ResponseBeginBlock{} }
下面的代碼創(chuàng)建一個數(shù)據(jù)操作批次,用來存儲區(qū)塊交易:
func (app *KVStoreApplication) DeliverTx(req abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx { code := app.isValid(req.Tx) if code != 0 { return abcitypes.ResponseDeliverTx{Code: code} } parts := bytes.Split(req.Tx, []byte("=")) key, value := parts[0], parts[1] err := app.currentBatch.Set(key, value) if err != nil { panic(err) } return abcitypes.ResponseDeliverTx{Code: 0} }
如果交易的格式錯誤,或者已經(jīng)存在相同的鍵/值對,那么我們?nèi)匀环祷胤橇愦a,否則,我們將該交易加入操作批次。
在目前的設計中,區(qū)塊中可以包含不正確的交易 —— 那些通過了CheckTx檢查但是DeliverTx失敗的交易,這樣做是出于性能的考慮。
注意,我們不能在DeliverTx中提交交易,因為在這種情況下Query可能會由于被并發(fā)調(diào)用而返回不一致的數(shù)據(jù),例如,Query會提示指定的值已經(jīng)存在,而實際的區(qū)塊還沒有真正提交。
Commit
用來通知應用來持久化新的狀態(tài)。
func (app *KVStoreApplication) Commit() abcitypes.ResponseCommit { app.currentBatch.Commit() return abcitypes.ResponseCommit{Data: []byte{}} }
當客戶端應用希望了解指定的鍵/值對是否存在時,它會調(diào)用Tendermint Core 的RPC接口 /abci_query 進行查詢,該接口會調(diào)用應用的Query
方法。
基于Tendermint Core的應用可以自由地提供其自己的API。不過使用Tendermint Core 作為代理,客戶端應用利用Tendermint Core的統(tǒng)一API的優(yōu)勢。另外,客戶端也不需要調(diào)用其他額外的Tendermint Core API來獲得進一步的證明。
注意在下面的代碼中我們沒有包含證明數(shù)據(jù)。
func (app *KVStoreApplication) Query(reqQuery abcitypes.RequestQuery) (resQuery abcitypes.ResponseQuery) { resQuery.Key = reqQuery.Data err := app.db.View(func(txn *badger.Txn) error { item, err := txn.Get(reqQuery.Data) if err != nil && err != badger.ErrKeyNotFound { return err } if err == badger.ErrKeyNotFound { resQuery.Log = "does not exist" } else { return item.Value(func(val []byte) error { resQuery.Log = "exists" resQuery.Value = val return nil }) } return nil }) if err != nil { panic(err) } return }
將以下代碼加入main.go
文件:
package main import ( "flag" "fmt" "os" "os/signal" "path/filepath" "syscall" "github.com/dgraph-io/badger" "github.com/pkg/errors" "github.com/spf13/viper" abci "github.com/tendermint/tendermint/abci/types" cfg "github.com/tendermint/tendermint/config" tmflags "github.com/tendermint/tendermint/libs/cli/flags" "github.com/tendermint/tendermint/libs/log" nm "github.com/tendermint/tendermint/node" "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/privval" "github.com/tendermint/tendermint/proxy" ) var configFile string func init() { flag.StringVar(&configFile, "config", "$HOME/.tendermint/config/config.toml", "Path to config.toml") } func main() { db, err := badger.Open(badger.DefaultOptions("/tmp/badger")) if err != nil { fmt.Fprintf(os.Stderr, "failed to open badger db: %v", err) os.Exit(1) } defer db.Close() app := NewKVStoreApplication(db) flag.Parse() node, err := newTendermint(app, configFile) if err != nil { fmt.Fprintf(os.Stderr, "%v", err) os.Exit(2) } node.Start() defer func() { node.Stop() node.Wait() ` }() c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) <-c os.Exit(0) } func newTendermint(app abci.Application, configFile string) (*nm.Node, error) { // read config config := cfg.DefaultConfig() config.RootDir = filepath.Dir(filepath.Dir(configFile)) viper.SetConfigFile(configFile) if err := viper.ReadInConfig(); err != nil { return nil, errors.Wrap(err, "viper failed to read config file") } if err := viper.Unmarshal(config); err != nil { return nil, errors.Wrap(err, "viper failed to unmarshal config") } if err := config.ValidateBasic(); err != nil { return nil, errors.Wrap(err, "config is invalid") } // create logger logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) var err error logger, err = tmflags.ParseLogLevel(config.LogLevel, logger, cfg.DefaultLogLevel()) if err != nil { return nil, errors.Wrap(err, "failed to parse log level") } // read private validator pv := privval.LoadFilePV( config.PrivValidatorKeyFile(), config.PrivValidatorStateFile(), ) // read node key nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile()) if err != nil { return nil, errors.Wrap(err, "failed to load node's key") } // create node node, err := nm.NewNode( config, pv, nodeKey, proxy.NewLocalClientCreator(app), nm.DefaultGenesisDocProviderFunc(config), nm.DefaultDBProvider, nm.DefaultMetricsProvider(config.Instrumentation), logger) if err != nil { return nil, errors.Wrap(err, "failed to create new Tendermint node") } return node, nil }
這段代碼很長,讓我們分開來介紹。
首先,初始化Badger數(shù)據(jù)庫,然后創(chuàng)建應用實例:
db, err := badger.Open(badger.DefaultOptions("/tmp/badger")) if err != nil { fmt.Fprintf(os.Stderr, "failed to open badger db: %v", err) os.Exit(1) } defer db.Close() app := NewKVStoreApplication(db)
接下來使用下面的代碼創(chuàng)建Tendermint Core的Node實例:
flag.Parse()node, err := newTendermint(app, configFile) if err != nil { fmt.Fprintf(os.Stderr, "%v", err) os.Exit(2) } ... // create node node, err := nm.NewNode( config, pv, nodeKey, proxy.NewLocalClientCreator(app), nm.DefaultGenesisDocProviderFunc(config), nm.DefaultDBProvider, nm.DefaultMetricsProvider(config.Instrumentation), logger) if err != nil { return nil, errors.Wrap(err, "failed to create new Tendermint node") }
NewNode
方法用來創(chuàng)建一個全節(jié)點實例,它需要傳入一些參數(shù),例如配置文件、私有驗證器、節(jié)點密鑰等。
注意我們使用proxy.NewLocalClientCreator
來創(chuàng)建一個本地客戶端,而不是使用套接字或gRPC來與Tendermint Core通信。
下面的代碼使用viper來讀取配置文件,我們將在下面使用tendermint的init命令來生成。
config := cfg.DefaultConfig() config.RootDir = filepath.Dir(filepath.Dir(configFile)) viper.SetConfigFile(configFile) if err := viper.ReadInConfig(); err != nil { return nil, errors.Wrap(err, "viper failed to read config file") } if err := viper.Unmarshal(config); err != nil { return nil, errors.Wrap(err, "viper failed to unmarshal config") } if err := config.ValidateBasic(); err != nil { return nil, errors.Wrap(err, "config is invalid") }
我們使用FilePV
作為私有驗證器,通常你應該使用SignerRemote鏈接到一個外部的HSM設備。
pv := privval.LoadFilePV( config.PrivValidatorKeyFile(), config.PrivValidatorStateFile(), )
nodeKey
用來在Tendermint的P2P網(wǎng)絡中標識當前節(jié)點。
nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile()) if err != nil { return nil, errors.Wrap(err, "failed to load node's key") }
我們使用內(nèi)置的日志記錄器:
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) var err error logger, err = tmflags.ParseLogLevel(config.LogLevel, logger, cfg.DefaultLogLevel()) if err != nil { return nil, errors.Wrap(err, "failed to parse log level") }
最后,我們啟動節(jié)點并添加一些處理邏輯,以便在收到SIGTERM或Ctrl-C時可以優(yōu)雅地關閉。
node.Start() defer func() { node.Stop() node.Wait() }() c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) <-c os.Exit(0)
我們使用go module進行項目依賴管理:
$ go mod init hubwiz.com/tendermint-go/demo $ go build
上面的命令將解析項目依賴并執(zhí)行構(gòu)建過程。
要創(chuàng)建默認的配置文件,可以執(zhí)行tendermint init
命令。但是在開始之前,我們需要安裝Tendermint Core。
$ rm -rf /tmp/example $ cd $GOPATH/src/github.com/tendermint/tendermint $ make install $ TMHOME="/tmp/example" tendermint init I[2019-07-16|18:40:36.480] Generated private validator module=main keyFile=/tmp/example/config/priv_validator_key.json stateFile=/tmp/example2/data/priv_validator_state.json I[2019-07-16|18:40:36.481] Generated node key module=main path=/tmp/example/config/node_key.json I[2019-07-16|18:40:36.482] Generated genesis file module=main path=/tmp/example/config/genesis.json
現(xiàn)在可以啟動我們的一體化Tendermint Core應用了:
$ ./demo -config "/tmp/example/config/config.toml" badger 2019/07/16 18:42:25 INFO: All 0 tables opened in 0s badger 2019/07/16 18:42:25 INFO: Replaying file id: 0 at offset: 0 badger 2019/07/16 18:42:25 INFO: Replay took: 695.227s E[2019-07-16|18:42:25.818] Couldn't connect to any seeds module=p2p I[2019-07-16|18:42:26.853] Executed block module=state height=1 validTxs=0 invalidTxs=0 I[2019-07-16|18:42:26.865] Committed state module=state height=1 txs=0 appHash=
現(xiàn)在可以打開另一個終端,嘗試發(fā)送一個交易:
$ curl -s 'localhost:26657/broadcast_tx_commit?tx="tendermint=rocks"' { "jsonrpc": "2.0", "id": "", "result": { "check_tx": { "gasWanted": "1" }, "deliver_tx": {}, "hash": "1B3C5A1093DB952C331B1749A21DCCBB0F6C7F4E0055CD04D16346472FC60EC6", "height": "128" } }
響應中應當會包含交易提交的區(qū)塊高度。
現(xiàn)在讓我們檢查指定的鍵是否存在并返回其對應的值:
$ curl -s 'localhost:26657/abci_query?data="tendermint"' { "jsonrpc": "2.0", "id": "", "result": { "response": { "log": "exists", "key": "dGVuZGVybWludA==", "value": "cm9ja3M=" } } }
“dGVuZGVybWludA==” 和“cm9ja3M=” 都是base64編碼的,分別對應于“tendermint” 和“rocks” 。
到此,相信大家對“Go語言Tendermint Core開發(fā)方法是什么”有了更深的了解,不妨來實際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關內(nèi)容可以進入相關頻道進行查詢,關注我們,繼續(xù)學習!