5分钟GET区块链 - 开发一条区块链 Develop BlockChain

5分钟GET区块链 - 开发一条区块链 Develop BlockChain

本文攻略:解惑区块链开发,学习 Tendermint,给自己造一条区块链

建议玩家等级:技术小白,学生党,初级码农


阅读此文先解锁技能:

  1. 一点点命令行基础
  2. 一点点 GO 基础:三天包学会
  3. 一点点区块链知识:5分钟学会区块链 - 关于区块链的一切 All About Blockchain

实践此文推荐装备:

  1. 操作系统:macOS Sierra(Windows也没毛病)
  2. IDE:VSCode,并自动安装一批官方建议的GO插件
  3. Restful API 工具:Paw(非必需,浏览器也可以)


PART 1: Tendermint 是什么

上篇文章我们举了雷布斯、杰克马、坡尼马和强东哥打麻将共同记账的栗子,这回PO主见他们手写记账这么原始,就想给这四位写一个区块链记账APP,让他们从此告别手写账本。

既然重新造轮子这件事是不存在的,于是向大家介绍一款区块链轮子叫 Tendermint(以下简称 TM),如果有玩家听说过以太坊(Ethereum),这个什么坊有个分支 Ethermint 就是基于 TM 开发的,反正PO主只懂撸代码不懂炒币,不是很了解这到底是个什么工坊。

好了,先来了解一下 TM 的原理,因为实在没什么可以可视化的 UI 让玩家们一目了然。TM 主要包含两部分:

  1. Tendermint Core:区块链共识引擎。它负责两件事情:节点之间的数据同步有序传输,拜占庭共识机制的实现。
  2. ABCI:区块链应用接口。它被设计成一组有接口规范的协议,目的是可以使用多种语言实现区块链应用逻辑。

那他两到底干啥用的,说了半天好像还是不太理解,没关系先记住他们,PO主刚开始看原文档的时候也和各位玩家有着同样的感受。


PART 2: Tendermint 安装运行

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. 运行官方栗子

官方文档提供了两个栗子,已经集成在了刚才的安装里:

  1. dummy:一个简单的键值存储区块链应用,使用起来有点像 Redis 或 ElasticSearch。
  2. counter:一个简单的计数器区块链应用,写入区块的数字必须递增,否则将不被区块接受。

这里我们简单介绍一下 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 来重新梳理一下我们刚才究竟都在区块链里的什么地方做了些什么事。


PART 3: Tendermint 再理解

PO主先把官方文档里用的最多并容易让人混淆的名词都罗列出来:

  1. ABCI Server
  2. ABCI Client
  3. Tendermint Core
  4. ABCI Application
  5. ABCI App

然后总结了三个名词来帮助理解,下文约定统一使用这三个名词:

  1. ABCI:区块链内部,ABCI Server,启动后加载 ABCI Application/App,为 Tendermint 提供 Socket 服务,服务地址:tcp://localhost:46658
  2. Tendermint:区块链内部,ABCI Client/Tendermint Core,启动后与 ABCI 提供的 Socket 服务创建 3 条连接(Connect),同时会提供 HTTP 服务给区块链外部客户端,服务地址:http://localhost:46657
  3. Client:区块链外部,真正的客户端,通过 Tendermint 提供的服务对区块链进行读写访问


还是把栗子举一举,假设强东哥在使用我写的的区块链记账 APP(用iOS开发的):

  1. Client 就是区块链记账 APP,强东哥记录了一条新数据 ‘Jack Pony $10’,然后保存
  2. Client 会发起请求 http://localhost:46657/broadcast_tx_commit?tx="Jack Pony $10"
  3. Tendermint 收到 tx="Jack Pony $10",通过 Socket 向 ABCI 发出指令 CheckTx:"Jack Pony $10"
  4. ABCI 运行了我写的 ABCI App(用GO开发的)里的 CheckTx 方法,作用是验证 "Jack Pony $10" 是否符合数据规范,如果不符合就要通知 Temdermint 拒绝这条 tx
  5. 假设 ABCI CheckTx 验证通过后通知 Tendermint
  6. Tendermint 把 "Jack Pony $10" 暂存在内存池(mempool)里,并把这条 tx 通过 P2P 网络复制给其它 Tendermint 节点(杰克马、坡尼马和雷布斯各自运行的 Tendermint 节点)
  7. Tendermint 发起了对 "Jack Pony $10" 这条 tx 的拜占庭共识投票,所有4个 Tendermint 节点都参与了。投票过程分三轮,第一轮预投票(PreVote),超过 2/3 认可后进入第二轮预提交(PreCommit),超过 2/3 认可后进入最后一轮正式提交(Commit)
  8. Tendermint 提交(Commit)时依次向 ABCI 发送指令:BeginBlock -> DeliverTx * n次 -> EndBlock -> Commit,大致意思就是:快接受新区快 -> 正在接受区块内容 * n条 -> 区块内容接受完了 -> 提交到区块链去吧
  9. ABCI 提交(Commit)成功后会通知 Tendermint
  10. 至此,区块链多了一个区块记录了 1 条 transaction "Jack Pony $10" ,当然我们也可以记录 n 条后再保存,那么一个区块就会记录 n 条 transaction


最后再总结几点:

  1. 说回刚才那个坑人的 abci-cli,当他运行 'abci-cli dummy' 时其实是运行了 ABCI,当他运行了 'abci-cli console' 时其实是运行了 Tendermint,所以一起运行后会进行 Socket 连接(Connect)
  2. Tendermint 和 ABCI 之间的 Socket 连接有 3 条:1 条用于验证数据(CheckTx 指令),1 条用于查询数据(Query 指令),1 条用于共识数据(BeginBlock, DeliverTx, EndBlock, Commit 指令)
  3. Tendermit 提供给 Client 的 HTTP 接口有很多,浏览器访问 http://localhost:46657 就都知道了
  4. 玩家也可以亲自拜读一下官方文档


PART 4: 开发一条区块链

总算到了撸代码环节,我们先把项目架构整理一下:

  1. ClientApp:区块链记账 APP,可以用任意语言任意平台实现(iOS/Android/H5/其它),这个版本里我们用 GO 做了最简单的控制台命令行实现,甚至没有 UI 界面。
  2. TendermintApp:区块链服务程序,这个里面包括 Tendermint 和 ABCI 两部分。
  3. ABCI:因为是接口协议,设计上可以用任何语言去实现,我们使用了 GO 语言。开发上主要是实现 CheckTx, DeliverTx, Commit 等 ABCI 指令的具体逻辑
  4. Tendermint:几乎没有需要开发的地方,前面提到了它已经为我们做了两件事情,P2P网络同步有序传输数据和拜占庭共识引擎。我们在这里就将它运行即可。


接下来正式撸代码,我们主要讲解一部分核心逻辑:

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 号区块的账本记录


感觉有好多$$在这小小的几个区块里,最后献上本文代码,希望各位玩家也创造一条属于自己的区块链。


如需合作或转载请联系本文作者,跪谢

编辑于 2019-08-14

文章被以下专栏收录