千家信息网

Go语言Tendermint Core开发方法是什么

发表于:2025-02-05 作者:千家信息网编辑
千家信息网最后更新 2025年02月05日,本篇内容主要讲解"Go语言Tendermint Core开发方法是什么",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"Go语言Tendermint Core
千家信息网最后更新 2025年02月05日Go语言Tendermint Core开发方法是什么

本篇内容主要讲解"Go语言Tendermint Core开发方法是什么",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"Go语言Tendermint Core开发方法是什么"吧!

Tendermint Core是一个用Go语言开发的支持拜占庭容错/BFT的区块链中间件,用于在一组节点之间安全地复制状态机/FSM。Tendermint Core的出色之处在于它是第一个实现BFT的区块链共识引擎,并且始终保持这一清晰的定位。这个指南将介绍如何使用Go语言开发一个基于Tendermint Core的区块链应用。

Tendermint Core为区块链应用提供了极其简洁的开发接口,支持各种开发语言,是开发自有公链/联盟链/私链的首选方案,例如Cosmos、Binance Chain、Hyperledger Burrow、Ethermint等均采用Tendermint Core共识引擎。

虽然Tendermint Core支持任何语言开发的状态机,但是如果采用Go之外的其他开发语言编写状态机,那么应用就需要通过套接字或gRPC与Tendermint Core通信,这会造成额外的性能损失。而采用Go语言开发的状态机可以和Tendermint Core运行在同一进程中,因此可以得到最好的性能。

1、安装Go开发环境

请参考官方文档安装Go开发环境。

确认你已经安装了最新版的Go:

$ go versiongo version go1.12.7 darwin/amd64

确认你正确设置了GOPATH环境变量:

$ echo $GOPATH/Users/melekes/go

2、创建Go项目

首先创建一个新的Go语言项目:

$ mkdir -p $GOPATH/src/github.com/me/kvstore$ cd $GOPATH/src/github.com/me/kvstore

example目录创建main.go文件,内容如下:

package mainimport (        "fmt")func main() {        fmt.Println("Hello, Tendermint Core")}

运行上面代码,将在标准输出设备显示指定的字符串:

$ go run main.goHello, Tendermint Core

3、编写Tendermint Core应用

Tendermint Core与应用之间通过ABCI(Application Blockchain Interface)通信,使用的报文消息类型都定义在protobuf文件中,因此基于Tendermint Core可以运行任何语言开发的应用。

创建文件app.go,内容如下:

package mainimport (        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{}}

接下来我们逐个解读上述方法并添加必要的实现逻辑。

3、CheckTx

当一个新的交易进入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已经存在,我们返回代码2。对于其他情况我们返回代码0表示交易有效 -- 注意Tendermint Core会将返回任何非零代码的交易视为无效交易。

有效的交易最终将被提交,我们使用badger 作为底层的键/值库,badger是一个嵌入式的快速KV数据库。

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,        }}

4、BeginBlock -> DeliverTx -> EndBlock -> Commit

当Tendermint Core确定了新的区块后,它会分三次调用应用:

  • BeginBlock:区块开始时调用

  • DeliverTx:每个交易时调用

  • EndBlock:区块结束时调用

注意,DeliverTx是异步调用的,但是响应是有序的。

func (app *KVStoreApplication) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock {        app.currentBatch = app.db.NewTransaction(true)        return abcitypes.ResponseBeginBlock{}}

下面的代码创建一个数据操作批次,用来存储区块交易:

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}}

如果交易的格式错误,或者已经存在相同的键/值对,那么我们仍然返回非零代码,否则,我们将该交易加入操作批次。

在目前的设计中,区块中可以包含不正确的交易 -- 那些通过了CheckTx检查但是DeliverTx失败的交易,这样做是出于性能的考虑。

注意,我们不能在DeliverTx中提交交易,因为在这种情况下Query可能会由于被并发调用而返回不一致的数据,例如,Query会提示指定的值已经存在,而实际的区块还没有真正提交。

Commit用来通知应用来持久化新的状态。

func (app *KVStoreApplication) Commit() abcitypes.ResponseCommit {        app.currentBatch.Commit()        return abcitypes.ResponseCommit{Data: []byte{}}}

5、查询 - Query

当客户端应用希望了解指定的键/值对是否存在时,它会调用Tendermint Core 的RPC接口 /abci_query 进行查询,该接口会调用应用的Query方法。

基于Tendermint Core的应用可以自由地提供其自己的API。不过使用Tendermint Core 作为代理,客户端应用利用Tendermint Core的统一API的优势。另外,客户端也不需要调用其他额外的Tendermint Core API来获得进一步的证明。

注意在下面的代码中我们没有包含证明数据。

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}

6、在同一进程内启动Tendermint Core和应用实例

将以下代码加入main.go文件:

package mainimport (        "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 stringfunc 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数据库,然后创建应用实例:

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)

接下来使用下面的代码创建Tendermint Core的Node实例:

flag.Parse()node, err := newTendermint(app, configFile)if err != nil {        fmt.Fprintf(os.Stderr, "%v", err)        os.Exit(2)}...// create nodenode, 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方法用来创建一个全节点实例,它需要传入一些参数,例如配置文件、私有验证器、节点密钥等。

注意我们使用proxy.NewLocalClientCreator来创建一个本地客户端,而不是使用套接字或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网络中标识当前节点。

nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile())if err != nil {        return nil, errors.Wrap(err, "failed to load node's key")}

我们使用内置的日志记录器:

logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))var err errorlogger, err = tmflags.ParseLogLevel(config.LogLevel, logger, cfg.DefaultLogLevel())if err != nil {        return nil, errors.Wrap(err, "failed to parse log level")}

最后,我们启动节点并添加一些处理逻辑,以便在收到SIGTERM或Ctrl-C时可以优雅地关闭。

node.Start()defer func() {        node.Stop()        node.Wait()}()c := make(chan os.Signal, 1)signal.Notify(c, os.Interrupt, syscall.SIGTERM)<-cos.Exit(0)

7、项目依赖管理、构建、配置生成和启动

我们使用go module进行项目依赖管理:

$ go mod init hubwiz.com/tendermint-go/demo$ go build

上面的命令将解析项目依赖并执行构建过程。

要创建默认的配置文件,可以执行tendermint init命令。但是在开始之前,我们需要安装Tendermint Core。

$ rm -rf /tmp/example$ cd $GOPATH/src/github.com/tendermint/tendermint$ make install$ TMHOME="/tmp/example" tendermint initI[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.jsonI[2019-07-16|18:40:36.481] Generated node key                           module=main path=/tmp/example/config/node_key.jsonI[2019-07-16|18:40:36.482] Generated genesis file                       module=main path=/tmp/example/config/genesis.json

现在可以启动我们的一体化Tendermint Core应用了:

$ ./demo -config "/tmp/example/config/config.toml"badger 2019/07/16 18:42:25 INFO: All 0 tables opened in 0sbadger 2019/07/16 18:42:25 INFO: Replaying file id: 0 at offset: 0badger 2019/07/16 18:42:25 INFO: Replay took: 695.227sE[2019-07-16|18:42:25.818] Couldn't connect to any seeds                module=p2pI[2019-07-16|18:42:26.853] Executed block                               module=state height=1 validTxs=0 invalidTxs=0I[2019-07-16|18:42:26.865] Committed state                              module=state height=1 txs=0 appHash=

现在可以打开另一个终端,尝试发送一个交易:

$ 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"  }}

响应中应当会包含交易提交的区块高度。

现在让我们检查指定的键是否存在并返回其对应的值:

$ 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开发方法是什么"有了更深的了解,不妨来实际操作一番吧!这里是网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

应用 交易 开发 代码 语言 区块 方法 文件 数据 状态 节点 面的 项目 内容 实例 客户 客户端 配置 命令 性能 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 php软件开发新课堂 城市json数据库 怎样使用数据库中的索引 自己的服务器如何备案 怎么用sql创建数据库英文版 上海智能化网络技术转让哪里好 怎么关闭网站服务器 数据库如何放到服务器 虹口区特定软件开发服务销售公司 国家实施网络安全什么政策 华为终端软件开发岗位怎么样 测血糖的结果有几个数据库 软件开发市场分析及定位 方舟手游比较好的服务器 geo数据库找某个基因表达量 服务器 方案 网络安全应具有四方面特征 我的世界服务器房间号电脑版 域名和虚拟主机还需要数据库吗 微信公众号连接mysql数据库 win11的网络安全模式进不去 北京时间校准显示器钟表服务器 济南鲁商网络技术有限公司 安卓9100打印服务器 易联网络技术 四年级上册网络安全的手抄报教程 商丘市网络安全课堂APP geo数据库找某个基因表达量 第七章-网络安全设计 维普数据库检索输出包括
0