专栏名称: 聊聊架构
聊聊架构
目录
相关文章推荐
架构师之路  ·  终于找到答案了!deepseek凭啥能给出这 ... ·  2 天前  
51好读  ›  专栏  ›  聊聊架构

教你用C++搭建一条迷你区块链

聊聊架构  · 公众号  · 架构  · 2018-08-05 09:21

正文

程序员的天赋技能就是通过代码实践自己的想法,完成一个作品会有相当的成就感。

今天我们终于也来到了实践环节。我将以 C++14 的代码为例,和你分享设计并实现一个迷你区块链的例子。

目标和范围

首先我们要知道达成的目标,根据目标划定工作范围。

考虑到我们无法搭建一个类似比特币的庞大 P2P 网络,也没有太多精力实现一个真正意义上的完整功能的全节点钱包,而且完整的全节点过于复杂,会让学习者迷失在细节中。

所以我们的目标是:构建一个包含仅有基础功能的全节点客户端,它可能没有太炫酷的 UI 页面,也没有复杂的命令,它们可以提供下面的功能。

  1. 提供 P2P 节点发现和同步区块的功能;

  2. 提供创建公私钥对的功能;

  3. 提供发送交易的功能;

  4. 提供交易查询的功能;

  5. 提供余额查询的功能;

  6. 提供挖矿的功能,在任意地址上都可以发起单机挖矿;

  7. 提供基础日志,方便跟踪监视。

以上 7 个功能基本涵盖了一个区块链全节点的主要功能,但是,由于我的时间有限,代码不能全部实现,主要是讲解设计和实现思路。后续我会逐渐完善代码,你也可以一起参与。

代码开源在:https://github.com/betachen/tinychain

技术选型

我们在深入区块链技术专题中说到过,区块链的四个核心技术概念:P2P 网络、账户模型与存储、共识、加密模块。

首先,P2P 网络模块是区块链的最底层模块之一,我们主要考虑方便实现和测试,可选的方案有轻量级消息队列和 WebSocket。考虑到集成的便利性,我们首选 WebSocket,因为至少需要一个 HTTP JSON-RPC Server,我们可以复用 Server 中的 Websocket 服务。

除了通讯协议之外,还要考虑数据交换格式,我们考虑采用易读通用的 JSON 格式,而不是像比特币一样的数据序列化格式,后期更改可以考虑升级到 Protobuf,后者优势主要体现在性能上。而在我们的例子中,性能永远不是首先考虑的,更多是它的易读和易调试性。

其次,我们来说说账户加密部分,由于 ECDSA 非对称加密模块过于复杂,我们选用 OpenSSL 库中的 RSA 算法作为加密模块。而交易模型上,我们考虑使用 UTXO 模型,因为状态模型需要维护状态,可能会带来额外的代码复杂度。

再来说说数据库存储,这个模块需要考虑到易用性和易读性,我们选用 SQLite 3 作为持久化存储。

最后我来谈谈共识算法这一模块,我们选用 PoW 作为共识算法,这是考虑到 PoW 实现起来十分简单,而且交易和区块的哈希计算会涉及 SHA-256,使用 PoW 算法我们就可以复用 SHA-256 的代码,使用 SHA-256 算法作为挖矿算法会降低我们的工作量。

详细功能

有了技术选型之后,我们再对目标功能点进行细分拆解。

  1. P2P 网络:节点发现、节点维护、持久化保存、区块同步。

  2. 公私钥对:命令行,创建公私钥对并生成地址,提供私钥存储,公私钥验证。

  3. 发送交易:命令行,发送成功验证,输入是交易哈希。

  4. 交易查询:命令行,JSON 格式的交易查询返回,输入是某个地址。

  5. 余额查询:命令行,JSON 格式的余额查询返回,输入是某个地址。

  6. 挖矿:命令行、JSON 格式挖矿信息返回,输入是某个地址。

  7. 区块共识:编织区块链的算法,包含创世区块以及调整全网挖矿难度。

  8. 交易共识:验证单个交易的算法,包含签名验证和 UTXO 验证。

  9. 基础日志:用于监控网络,区块验证等操作。

  10. 区块持久化存储:分叉与合并时的一致性,并为查询提供接口。

  11. 提供格式化输出交易的功能,这里的格式化主要指 JSON 格式。

  12. 有效防止双花交易。

通过详细的功能拆分我们可以发现,功能点多达三十余个,如何设计实现这三十多个功能点是我们接下来首先要解决的问题。问题是这三十多个功能点不是孤立的,而是有相互联系的,我们先从顶层开始设计。

最顶层是一个区块节点,一个完整的可执行程序,我们命名为 Tinychain,而对应的命令行客户端为 cli-tinychain。

Tinychain 的核心程序主要包含以下结构:

tinychain
├── blockchain
├── consensus
├── database
├── network
├── http-server
└── node

我们以 node 为最顶层,那么 node 会包含其他五个模块,node 启动就会把其他 5 个服务启动。

cli-tinychain 主要包含以下结构:

 cli-tinychain
├── JSON
└── http-client

命令行就简单多了,我们把命令行的执行和计算全部都扔到 tinychian 当中,命令行只用一个 http-client 用 JSON 把 API 包起来即可。

通过分析我们知道,以下组件是必不可少的,但是我们不必自己开发,可以直接选取一些现成的开发包直接集成即可。

 基础组件
├── log
├── JSON-paser
├── sha256
└── key-pair
区块数据结构设计

有了大致的顶层设计已经分类好,那么接下来我们考虑为每个模块填充一些数据结构。一个区块链最重要的是区块,所以我们从区块开始。

一个区块包含两部分,分别是区块头和区块体,区块头是一个区块的元数据,区块体就是包含交易的列表,所以我们直接设计交易体。

区块头的设计

我们参照比特币的设计,区块头包含了前向区块哈希、默克尔根哈希、时间戳、难度目标、Nonce 值和版本号。

所以我们的结构可能是这样的。

     {
      "target_bits" : "4575460831240",
    "hash" : 
 "4a9169e2f4f8673ac9627be0fa0f9e15a9e3b1bc5cd697d96954d25acacd92df",
   "merkle_tree_hash" : "3d228afc50bc52491f5dd8aa8c416da0d9a16bf829790ea0b7635e5b4d44ab4f",
   "nonce" : "3852714822920177480",
   "height" : 1234567,
   "previous_block_hash" : "4d2544e044bfd2f342220a711b10842bb6cfae551b1bc1ed6152ff5c7f3ff654",
   "time_stamp" : 1528070857,
   "transaction_count" : 1,
   "version" : 1
}
  • target_bits 表示当前区块的目标值;

  • hash 表示这个区块的哈希;

  • merkle_tree_hash 表示这个区块当中交易列表的默克尔根;

  • nonce 表示随机数;

  • height 表示当前区块的高度;

  • previous_block_hash 指向前向区块哈希;

  • time_stamp 表示生产这个区块时的时间戳;

  • transaction_count 表示这个区块当中包含多少笔交易;

  • version 表示区块的版本号,不代表交易的版本号。

在这里,我们的区块头大小不是固定的,因为它没有经过序列化,完全以 JSON 表示,所以我们这里就不考虑字节印第安序的问题了,也不考虑固定长度的问题。

有了区块头,我们再看看交易体的设计,由于使用 UTXO 作为交易模型,那么我们先考虑一个输入、一个输出的结构。

{
    "hash": "8c14f0db3df150123e6f3dbbf30f8b955a8249b62ac1d1ff16284aefa3d06d87",
    "version": 1,
    "input_size": 1,
    "output_size": 1,
    "size": 135,
    "inputs": [{
        "prev_out": {
            "hash": "0000000000000000000000000000000000000000000000000000000000000000",
            "index":0
iq        },
    }],
    "out": [{
        "value": "5000000000",
        "address": "f3e6066078e815bb2"
        }],
}

我们可以按照这种结构来设计交易体。

地址设计

区块链地址都有通常意义上的地址,我们这里将公钥直接算作地址,不再将公钥进行哈希转换。

内存池

内存池是指缓存交易的一块交易缓冲区,这里一个节点的主要处理对象,所以对内存池的管理,是编织区块链的最重要一步。我们这里的内存池使用标准库 STL 中的容器。







请到「今天看啥」查看全文