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运行在同一进程中,因此可以得到最好的性能。
相关链接: Tendermint 区块链开发详解 | 本教程源代码下载
1、安装Go开发环境
请参考官方文档安装Go开发环境。
确认你已经安装了最新版的Go:
1 | $ go version |
确认你正确设置了GOPATH
环境变量:
1 | $ echo $GOPATH |
2、创建Go项目
首先创建一个新的Go语言项目:
1 | $ mkdir -p $GOPATH/src/github.com/me/kvstore |
在example
目录创建main.go
文件,内容如下:
1 | package main |
运行上面代码,将在标准输出设备显示指定的字符串:
1 | $ go run main.go |
3、编写Tendermint Core应用
Tendermint Core与应用之间通过ABCI(Application Blockchain Interface)通信, 使用的报文消息类型都定义在protobuf文件中,因此基于Tendermint Core可以运行任何 语言开发的应用。
创建文件app.go
,内容如下:
1 | package main |
接下来我们逐个解读上述方法并添加必要的实现逻辑。
3、CheckTx
当一个新的交易进入Tendermint Core时,它会要求应用先进行检查,比如验证格式、签名等。
1 | func (app *KVStoreApplication) isValid(tx []byte) (code uint32) { |
如果进来的交易格式不是{bytes}={bytes}
,我们将返回代码1
。如果指定的key和value
已经存在,我们返回代码2
。对于其他情况我们返回代码0
表示交易有效 —— 注意Tendermint
Core会将返回任何非零代码的交易视为无效交易。
有效的交易最终将被提交,我们使用badger 作为底层的键/值库,badger是一个嵌入式的快速KV数据库。
1 | import "github.com/dgraph-io/badger"type KVStoreApplication struct { |
4、BeginBlock -> DeliverTx -> EndBlock -> Commit
当Tendermint Core确定了新的区块后,它会分三次调用应用:
- BeginBlock:区块开始时调用
- DeliverTx:每个交易时调用
- EndBlock:区块结束时调用
注意,DeliverTx是异步调用的,但是响应是有序的。
1 | func (app *KVStoreApplication) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock { |
下面的代码创建一个数据操作批次,用来存储区块交易:
1 | func (app *KVStoreApplication) DeliverTx(req abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx { |
如果交易的格式错误,或者已经存在相同的键/值对,那么我们仍然返回 非零代码,否则,我们将该交易加入操作批次。
在目前的设计中,区块中可以包含不正确的交易 —— 那些通过了CheckTx检查 但是DeliverTx失败的交易,这样做是出于性能的考虑。
注意,我们不能在DeliverTx中提交交易,因为在这种情况下Query可能会 由于被并发调用而返回不一致的数据,例如,Query会提示指定的值已经存在, 而实际的区块还没有真正提交。
Commit
用来通知应用来持久化新的状态。
1 | func (app *KVStoreApplication) Commit() abcitypes.ResponseCommit { |
5、查询 - Query
当客户端应用希望了解指定的键/值对是否存在时,它会调用Tendermint Core
的RPC接口 /abci_query 进行查询,
该接口会调用应用的Query
方法。
基于Tendermint Core的应用可以自由地提供其自己的API。不过使用Tendermint Core 作为代理,客户端应用利用Tendermint Core的统一API的优势。另外,客户端也不需要 调用其他额外的Tendermint Core API来获得进一步的证明。
注意在下面的代码中我们没有包含证明数据。
1 | func (app *KVStoreApplication) Query(reqQuery abcitypes.RequestQuery) (resQuery abcitypes.ResponseQuery) { |
6、在同一进程内启动Tendermint Core和应用实例
将以下代码加入main.go
文件:
1 | package main |
这段代码很长,让我们分开来介绍。
首先,初始化Badger数据库,然后创建应用实例:
1 | db, err := badger.Open(badger.DefaultOptions("/tmp/badger")) |
接下来使用下面的代码创建Tendermint Core的Node实例:
1 | flag.Parse()node, err := newTendermint(app, configFile) |
NewNode
方法用来创建一个全节点实例,它需要传入一些参数,例如配置文件、
私有验证器、节点密钥等。
注意我们使用proxy.NewLocalClientCreator
来创建一个本地客户端,而不是使用
套接字或gRPC来与Tendermint Core通信。
下面的代码使用viper来读取配置文件,我们 将在下面使用tendermint的init命令来生成。
1 | config := cfg.DefaultConfig() |
我们使用FilePV
作为私有验证器,通常你应该使用SignerRemote链接到
一个外部的HSM设备。
1 | pv := privval.LoadFilePV( |
nodeKey
用来在Tendermint的P2P网络中标识当前节点。
1 | nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile()) |
我们使用内置的日志记录器:
1 | logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) |
最后,我们启动节点并添加一些处理逻辑,以便在收到SIGTERM或Ctrl-C时 可以优雅地关闭。
1 | node.Start() |
7、项目依赖管理、构建、配置生成和启动
我们使用go module进行项目依赖管理:
1 | $ go mod init hubwiz.com/tendermint-go/demo |
上面的命令将解析项目依赖并执行构建过程。
要创建默认的配置文件,可以执行tendermint init
命令。但是
在开始之前,我们需要安装Tendermint Core。
1 | $ rm -rf /tmp/example |
现在可以启动我们的一体化Tendermint Core应用了:
1 | $ ./demo -config "/tmp/example/config/config.toml" |
现在可以打开另一个终端,尝试发送一个交易:
1 | $ curl -s 'localhost:26657/broadcast_tx_commit?tx="tendermint=rocks"' |
响应中应当会包含交易提交的区块高度。
现在让我们检查指定的键是否存在并返回其对应的值:
1 | $ curl -s 'localhost:26657/abci_query?data="tendermint"' |
“dGVuZGVybWludA==” 和“cm9ja3M=” 都是base64编码的,分别对应于 “tendermint” 和“rocks” 。
8、小结
在这个指南中,我们学习了如何使用Go开发一个内置Tendermint Core 共识引擎的区块链应用,源代码可以从github下载,如果希望进一步系统 学习Tendermint的应用开发,推荐Tendermint区块链开发详解。
原文链接:Writing a built-in Tendermint Core app in Go
汇智网翻译整理,转载请标明出处