本文攻略:解惑区块链开发,学习 Tendermint,给自己造一条区块链
建议玩家等级:技术小白,学生党,初级码农
阅读此文先解锁技能:
实践此文推荐装备:
上篇文章我们举了雷布斯、杰克马、坡尼马和强东哥打麻将共同记账的栗子,这回PO主见他们手写记账这么原始,就想给这四位写一个区块链记账APP,让他们从此告别手写账本。
既然重新造轮子这件事是不存在的,于是向大家介绍一款区块链轮子叫 Tendermint(以下简称 TM),如果有玩家听说过以太坊(Ethereum),这个什么坊有个分支 Ethermint 就是基于 TM 开发的,反正PO主只懂撸代码不懂炒币,不是很了解这到底是个什么工坊。
好了,先来了解一下 TM 的原理,因为实在没什么可以可视化的 UI 让玩家们一目了然。TM 主要包含两部分:
那他两到底干啥用的,说了半天好像还是不太理解,没关系先记住他们,PO主刚开始看原文档的时候也和各位玩家有着同样的感受。
1. 官方安装指南
让我们忽略更多的理论,直接撸起袖子做程序猿最喜欢做的事情,先下载轮子,命令行输入:
go get github.com/Masterminds/glide
go get github.com/tendermint/tendermint/cmd/tendermint
下载过程比较长,这个轮子比较大(可能需要科学上网),下载完成后安装,命令行输入:
cd $GOPATH/src/github.com/tendermint/tendermint
glide install
go install ./cmd/tendermint
安装过程也比较长,安装完成后验证安装是否成功,命令行输入:
tendermint version
abci-cli version
截止本文的发表时间,tendermint 版本 0.15.0,abci-cli 版本 0.9.0
2. 运行官方栗子
官方文档提供了两个栗子,已经集成在了刚才的安装里:
这里我们简单介绍一下 dummy 这个栗子,首先启动这个区块链应用,命令行输入:
abci-cli dummy
然后启动 TM,命令行输入:
tendermint init
tendermint node
顺利的话,可以看到 abci 和 tendermint 两个程序连通(Connect)了,并且 tendermint 会像心跳一样每一秒提交一个空区块,我们接下来准备写入有实质内容的新区块,命令行输入:
curl -s 'localhost:46657/broadcast_tx_commit?tx="abcd"'
curl -s 'localhost:46657/broadcast_tx_commit?tx="name=satoshi"'
我们的区块链里就有了一个记录“abcd”的区块和另一个记录“name=satoshi”的区块,如果需要对区块链内容进行查询,命令行输入:
curl -s 'localhost:46657/abci_query?data="name"'
curl 是 Linux 常用工具,玩家可以通过网页浏览器直接输入 localhost:46657/abci_query?data="name"
3. 探索官方工具
最后介绍 abci-cli 这个坑人工具登场,让我们先关闭刚才运行的 dummy 应用和 tendermint,命令行重新输入运行 dummy:
abci-cli dummy
命令行再输入:
abci-cli console
然后他两连通了(Connect),此时很多玩家会困惑于这两个都叫 abci 的家伙怎么也能互相连通,abci 到底是个什么东西?他又干了什么?让我们通过 PART 3 来重新梳理一下我们刚才究竟都在区块链里的什么地方做了些什么事。
PO主先把官方文档里用的最多并容易让人混淆的名词都罗列出来:
然后总结了三个名词来帮助理解,下文约定统一使用这三个名词:
还是把栗子举一举,假设强东哥在使用我写的的区块链记账 APP(用iOS开发的):
最后再总结几点:
总算到了撸代码环节,我们先把项目架构整理一下:
接下来正式撸代码,我们主要讲解一部分核心逻辑:
1. ClientApp
模拟了5个区块,每个区块记录10条记录,将这些记录转成 json 提交给 Tendermint
blocksNumber := 5 // how many blocks
transactionsPerBlock := 10 // how many transactions in each block
players := []string{"Lei", "Jack", "Pony", "Richard"} // 4 players
random := rand.New(rand.NewSource(time.Now().UnixNano()))
json := jsoniter.ConfigCompatibleWithStandardLibrary
for i := 0; i < blocksNumber; i++ {
time.Sleep(time.Second * 1)
transactions := []controllers.Transaction{}
for j := 0; j < transactionsPerBlock; j++ {
from := players[random.Intn(len(players))]
to := players[random.Intn(len(players))]
for from == to {
to = players[random.Intn(len(players))]
}
btc := float32(random.Intn(10) + 1)
tran := controllers.Transaction{
From: from,
To: to,
Bitcoin: btc,
}
_, _ = tran.Create()
transactions = append(transactions, tran)
}
bytes, _ := json.Marshal(&transactions)
data := strings.Replace(string(bytes), "\"", "'", -1)
tx := data
// tmAsync(tx)
tmCommit(tx)
}
func tmCommit(tx string) {
url := "http://localhost:46657/broadcast_tx_async?tx=\"" + tx + "\""
txHandle(url)
}
2. Tendermint
通过命令行运行一个定义好的 Shell 脚本,并且把运行结果打印到控制台和日志文件中去
f, err := os.Create("logs/tendermint.log")
if err != nil {
fmt.Println("Tendermint log init error:", err)
}
multiWriter := io.MultiWriter(f, os.Stdout)
go func() {
cmd := exec.Command("bash", "-c", "sh run-tm.sh")
cmd.Stdout = multiWriter
cmd.Start()
}()
run-tm.sh 脚本内容:
echo tendermint start
tendermint init
tendermint unsafe_reset_all
tendermint node --consensus.create_empty_blocks=false
echo tendermint end
unsafe_reset_all 作用是每次重置本机的区块链数据,仅供开发使用 consensus.create_empty_blocks 作用是关闭 Tendermint 自带的每秒生成新的空区块功能
3. ABCI
实现 ABCI 指令接口,这里我们直接使用了官方栗子 dummy 应用,来保存提交的 json 记录,并且在每个实现接口里做了日志打印(因篇幅有限,省略了长长的代码)
func (app *DummyApplication) CheckTx(tx []byte) types.ResponseCheckTx {
lib.Log.Debug("CheckTx")
lib.Log.Notice(string(tx))
return types.ResponseCheckTx{Code: code.CodeTypeOK}
}
func (app *DummyApplication) DeliverTx(tx []byte) types.ResponseDeliverTx {
lib.Log.Debug("DeliverTx")
lib.Log.Notice(string(tx))
// ...
return types.ResponseDeliverTx{Code: code.CodeTypeOK, Tags: tags}
}
func (app *DummyApplication) Commit() types.ResponseCommit {
lib.Log.Debug("Commit")
// ...
lib.Log.Debug("Commit Hash", hash)
return types.ResponseCommit{Code: code.CodeTypeOK, Data: hash}
}
func (app *DummyApplication) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) {
lib.Log.Debug("Query")
// ...
}
4. 编译
我们写好了编译脚本,命令行输入:
sh make.sh
编译脚本本质就是把三个运行程序编译出来:
go build ./TendermintApp/ABCIServer/
go build ./TendermintApp/ABCIClient/
go build -o Client ./ClientApp/
5. 测试
到了激动人心的一刻,我们已经离一条自造的区块链很接近了。
先运行 ABCI,命令行输入:
./ABCIServer
再运行 Tendermint,命令行输入:
./ABCIClient
可以看到 Tendermint 和 ABCI 已经有 3 个连接(Connect) Socket 握手了,表示整个区块链服务已经准备就绪,最后我们只需要使用 Client 往区块链里写数据就行了,命令行输入:
./Client
把条命令理解成使用记账 APP 记录了 5 页,每页 10 条记录,然后点击保存后送去了区块链。
附上一张运行结果图,大致能观察到三个程序都已成功运行
最终我们创造了一条区块链,一共有 7 个区块,2~6 号区块各自写有 10 条账本记录,第 1 和第 7 号区块是系统创建的空区块。
空区块是怎么回事?原先 PO 主也以为是 BUG,去了官方的开发 issue 里了解到 Tendermint 每次收到新的 tx 或者区块链 hash 值变化的时候(就是区块链状态变了)会产生一个新区块去接受 mempool 里未被提交的新 tx,这是 Tendermint 用于正常自检工作产生的。
来看看这条区块链长什么样子,这里展示了 2, 3, 4 号区块的账本记录
感觉有好多$$在这小小的几个区块里,最后献上本文代码,希望各位玩家也创造一条属于自己的区块链。PO 主也默默去写未完成的账本 APP了。
https://zhuanlan.zhihu.com/p/33154604