Tendermint KVStore案例解析
概述
Tendermint 簡單的可以理解為是一個模塊化的區塊鏈開發框架,支持開發者個性化定制自己的區塊鏈,而不需要考慮共識算法以及P2P網絡的實現。
因為在 Tendermint 中,共識引擎和P2P網絡都被封裝在Tendermint Core 中,并通過ABCI與應用層進行交互。所以使用Tendermint開發時,我們只需要實現ABCI的接口,就可以快速的開發一個區塊鏈應用。
KVStore 案例
KVStore官方文檔地址:https://docs.tendermint.com/master/tutorials/go-built-in.html
KVStore application
package mainimport ("bytes"abcitypes "github.com/tendermint/tendermint/abci/types""github.com/dgraph-io/badger" )//實現abci接口 var _ abcitypes.Application = (*KVStoreApplication)(nil)//定義KVStore程序的結構體 type KVStoreApplication struct {db *badger.DBcurrentBatch *badger.Txn }// 創建一個 ABCI APP func NewKVStoreApplication(db *badger.DB) *KVStoreApplication {return &KVStoreApplication{db: db,} }// 檢查交易是否符合自己的要求,返回0時代表有效交易 func (app *KVStoreApplication) isValid(tx []byte) (code uint32) {// 格式校驗,如果不是k=v格式的返回碼為1parts := bytes.Split(tx, []byte("="))if len(parts) != 2 {return 1}key, value := parts[0], parts[1]//檢查是否存在相同的KVerr := 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) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock {app.currentBatch = app.db.NewTransaction(true)return abcitypes.ResponseBeginBlock{} }//當新的交易被添加到Tendermint Core時,它會要求應用程序進行檢查(驗證格式、簽名等),當返回0時才通過 func (app KVStoreApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx {code := app.isValid(req.Tx)return abcitypes.ResponseCheckTx{Code: code, GasUsed: 1} }//這里我們創建了一個batch,它將存儲block的交易。 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} }func (app *KVStoreApplication) Commit() abcitypes.ResponseCommit {// 往數據庫中提交事務,當 Tendermint core 提交區塊時,會調用這個函數app.currentBatch.Commit()return abcitypes.ResponseCommit{Data: []byte{}} }func (app *KVStoreApplication) Query(reqQuery abcitypes.RequestQuery) (resQuery abcitypes.ResponseQuery) {resQuery.Key = reqQuery.Dataerr := 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 = valreturn nil})}return nil})if err != nil {panic(err)}return }func (KVStoreApplication) Info(req abcitypes.RequestInfo) abcitypes.ResponseInfo {return abcitypes.ResponseInfo{} }func (KVStoreApplication) InitChain(req abcitypes.RequestInitChain) abcitypes.ResponseInitChain {return abcitypes.ResponseInitChain{} }func (KVStoreApplication) EndBlock(req abcitypes.RequestEndBlock) abcitypes.ResponseEndBlock {return abcitypes.ResponseEndBlock{} }func (KVStoreApplication) ListSnapshots(abcitypes.RequestListSnapshots) abcitypes.ResponseListSnapshots {return abcitypes.ResponseListSnapshots{} }func (KVStoreApplication) OfferSnapshot(abcitypes.RequestOfferSnapshot) abcitypes.ResponseOfferSnapshot {return abcitypes.ResponseOfferSnapshot{} }func (KVStoreApplication) LoadSnapshotChunk(abcitypes.RequestLoadSnapshotChunk) abcitypes.ResponseLoadSnapshotChunk {return abcitypes.ResponseLoadSnapshotChunk{} }func (KVStoreApplication) ApplySnapshotChunk(abcitypes.RequestApplySnapshotChunk) abcitypes.ResponseApplySnapshotChunk {return abcitypes.ResponseApplySnapshotChunk{} }Tendermint Core
package mainimport ("flag""fmt""os""os/signal""path/filepath""syscall""github.com/dgraph-io/badger""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() {//初始化Badger數據庫并創建一個應用實例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()// 創建 ABCI APPapp := NewKVStoreApplication(db)flag.Parse()// 創建一個Tendermint Core Node實例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)<-cos.Exit(0) }//創建一個本地節點 func newTendermint(app abci.Application, configFile string) (*nm.Node, error) {// 讀取默認的Validator配置config := cfg.DefaultValidatorConfig()// 設置配置文件的路徑config.RootDir = filepath.Dir(filepath.Dir(configFile))// 這里使用的是viper 工具,// 官方文檔:https://github.com/spf13/viperviper.SetConfigFile(configFile) if err := viper.ReadInConfig(); err != nil {return nil, fmt.Errorf("viper failed to read config file: %w", err)}if err := viper.Unmarshal(config); err != nil {return nil, fmt.Errorf("viper failed to unmarshal config: %w", err)}if err := config.ValidateBasic(); err != nil {return nil, fmt.Errorf("config is invalid: %w", err)}// 創建日志logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))var err errorlogger, err = tmflags.ParseLogLevel(config.LogLevel, logger, cfg.DefaultLogLevel)if err != nil {return nil, fmt.Errorf("failed to parse log level: %w", err)}// 讀取配置文件pv, _ := privval.LoadFilePV(config.PrivValidatorKeyFile(),config.PrivValidatorStateFile(),)nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile())if err != nil {return nil, fmt.Errorf("failed to load node's key: %w", err)}// 根據配置文件 創建節點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, fmt.Errorf("failed to create new Tendermint node: %w", err)}return node, nil }程序工作流程
1. 通過RPC調用broadcast_tx_commit,提交交易,也就是圖中的User Input。交易首先被存放在交易池緩存(mempool cache)中。
2. 交易池調用CheckTx驗證交易的有效性。驗證通過就放入交易池。
3. 從Validator中選出的Proposer在交易池中打包交易、形成區塊。
4. 第一輪投票。Proposer通過Gossip協議進行廣播區塊。全網的Validator對區塊進行校驗。校驗通過,同意進行Pre-vote;不通過或者超時,產生一個空塊。然后每個Validator都進行廣播
5. 第二輪投票。所有的Validator收集其他Validator的投票信息,如果有大于2/3的節點同意pre-vote,那么自己就投票Pre-commit。如果小于2/3的節點或者超時,也繼續產生一個空塊。
6. 所有Validator廣播自己的投票結果,并收集其他Validator的投票結果。如果收到的投票信息沒有超過2/3的節點投票pre-commit或者超時。那么就不提交區塊。如果有超過2/3的pre-commit那么就提交區塊。最后,區塊高度加1。
最后
這里只是通過一個簡單的案例對Tendermint工作流程進行疏通。如果有任何問題可以來群里進行交流討論。群里還有很多視頻書籍資料可以自行下載。
最后推薦一位大佬的公眾號,歡迎關注哦:區塊鏈技術棧
總結
以上是生活随笔為你收集整理的Tendermint KVStore案例解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: RNA-ATTO 390|RNA-ATT
- 下一篇: 出现[W pthreadpool-cpp