区块链源码学习笔记1 - 前期知识库准备
地址:比特币源码解析教程,传送门
学习笔记步骤
- Step1:看源码前补充基础知识库。
- Boost:C++库
- OpenSSL:安全套接字层库
- Libevent:网络编程库
- QT:GUI库
- Step2:了解核心数据结构
- 交易 CTransaction
- 区块 CBlock
- 交易池 CTxMemPool
- 共识 Consensus
- 脚本 CScript
- Step3:分析可执行程序的代码(编译后7个可执行程序)
- bench_bitcoin
- bitcoin-cli
- bitcoind
- bitcoin-qt
- bitcoin-tx
- test_bitcoin
- test_bitcoin-qt
1. Boost库
- Boost是一套开放源代码、高度可移植的C++库,并且是除了STL外最常用的库,实现了很多基本操作,涉及数值计算、泛型编程、元编程、平台API,能让开发变得更加简单快捷。
1.1 Boost 安装 in Mac
- 安装Homebrew,如果没有安装的话,直接看官网,一行命令就搞定安装。安装好之后可以用
brew help
检查是否安装完成。 - 安装boost。任何在Mac上需要安装的库都可以先试试
brew install ***
来试一试。安装命令brew install boost
1.2 Boost中与比特币源码相关的库
- Signals2、Bind、Thread、Chrono、Test、Program option
1.2.1 Signals2
- 是什么
- Signals2是基于Boost的另一个signals库,实现了线程安全的观察者模式。在Signals2中,所谓的观察者模式被称为信号/插槽(signals and slots)
- 说白了,它是一种函数回调机制,定义signal类型数据,把执行代码放在单独放在函数中,美其名曰slot,一个signal可以关联多个slot,当signal发出的时候,所有关联他的slot都会被调用。
- 为什么学
- 比特币中涉及Qt编程,而Signals/slots机制是Qt编程的核心机制,要读懂Qt编程就必须对Signals/slots有所了解
- 优点
- 降低耦合性。Signals/slots 可以让各个对象只管处理自己的事情。响应事件和处理事件区分开来,使得对象的职责更加明确,减少了代码的耦合。
- 打个比方,按钮事件 vs 按下后的处理函数,可以分开来写,而不用写在同一个函数里。按钮事件只负责通知,不负责处理。处理函数只负责处理,不负责通知。
- UI使用这种设计的话,可以在更换响应代码的时候完全不用改UI层的代码。反之亦然。
- 例程
// example1-signals2.cpp
#include <iostream>
#include "boost/signals2.hpp"
using namespace std;
void slot1(){
cout << "solt1 call" << endl;
}
void slot2(){
cout << "solt2 call" << endl;
}
int main(){
boost::signals2::signal<void()> sig; // 定义信号
sig.connect(&slot1); // 信号关联插槽,按顺序关联也会按顺序执行
sig.connect(&slot2);
sig(); // 出发信号
return 0;
}
/*
Mac下安装完boost后,执行代码 g++ example1-signals2.cpp
编译后执行 ./a.out
打印结果
slot1 call
slot2 call
*/
1.2.2 Bind
- 这个还是比较常见的,可以简单理解为下面这样,就是绑定函数和参数。灵活调用函数
cout << boost::bind(f, x, y)() << endl; //等价于 cout << f(x, y) << endl;
- 其中占位符_1到_9的功能很强大,看两行代码就可以理解了
cout << boost::bind(f, _1, _2, _3)(x, y, z) << endl; //等价于 cout << f(x, y, z) << endl;
cout << boost::bind(f, y, _3, _2)(x, y, z) << endl; //等价于 cout << f(y, z, y) << endl;
- 有篇博客对于boost::bind的使用和原理讲述的很全面,例程也丰富。包括绑定普通函数、成员函数、成员变量、函数对象、ref库,以及嵌套绑定(实现类似f(g(x))的形式,最好不要超过两层嵌套)。
- 另一篇博客给出了boost::bind四种经典应用场景的例子。
1.2.3 Thread
- 线程,跟操作系统相关了
- 一般都会涉及多线程,而多线程中最经典的问题就是同步访问共享资源
- boost和其他语言一样提供互斥锁来解决,特别的是boost还提供了互斥类。
- 和Bitcoin源码的关系是,bitcoin core中多线程处理rpc请求的代码中有很多涉及Thread库,详见这篇博客。
1.2.4 Chrono
- Chrono是Boost库中用于时间处理的库,主要包含三个概念时间段(duration),时间点(time_point)和时钟(clock)。
// durations 表示一段时间间隔
typedef boost::chrono::hours hours;
typedef boost::chrono::minutes minutes;
typedef boost::chrono::seconds seconds;
typedef boost::chrono::milliseconds milliseconds;
typedef boost::chrono::microseconds microseconds;
typedef boost::chrono::nanoseconds nanoseconds;
// clock 表示当前时间,是在不断的变化
typedef boost::chrono::system_clock system_clock;
typedef boost::chrono::steady_clock steady_clock;
typedef boost::chrono::high_resolution_clock high_resolution_clock;
// time point 表示某一个具体的时间点
typedef system_clock::time_point sys_tp;
1.2.5 Test
- 用来给代码做单元测试,白盒测试。
- 实际过程:给定输入,得到输出,判断是否和预期的输出相同
BOOST_AUTO_TEST_SUITE(BOOST_TEST_MODULE)
- 依照教程动手试一下代码就能很容易的理解用法
1.2.6 Program_options
- 用来处理那些,带参数的命令。
cmd -option
- 编写命令行程序的时候,经常会碰到一个问题,有一些带参数的命令比较难解析参数。而boost::program_options模块就是用来处理命令行传入的参数,统一放到一个数组中方便处理。
- 可以看这篇博客对这个模块的图文理解。
1.3 OpenSSL
- OpenSSL 是一个安全套接字层密码库,囊括主要的密码算法、常用的密钥和证书封装管理功能及SSL协议,并提供丰富的应用程序供测试或其它目的使用。
- 是一个开源项目,主要包括三个组件
- openssl:多用途的命令行工具
- libcryto:加密算法库
- libssl:加密模块应用库
- 功能:OpenSSL可以实现:秘钥证书管理、对称秘钥和非对称秘钥。直接调用库函数即可。
- 详细用法参考这篇博客,主要包括:
- 对称加密
openssl enc
- 单向加密
openssl dgst
- 生成密码
openssl passwd
- 生成随机数
openssl rand
- 生成秘钥对
openssl genrsa
- 创建CA和申请证书
- 创建自签证书
- 颁发证书
- 吊销证书
- 对称加密
1.4 libevent
- 一个轻量级的开源的高性能的事件触发网络库
- 支持多种I/O多路复用技术(epoll、poll、dev/poll、select和kqueue)
- 用途
- 事件通知:当文件描述符可读可写时将执行回调函数。
- Io缓存:缓存事件提供了输入输出缓存,能自动的读入和写入,用户不必直接操作io。
- 定时器:libevent提供了定时器的机制,能够在一定的时间间隔之后调用回调函数。
- 信号:触发信号,执行回调。
- 异步的dns解析:libevent提供了异步解析dns服务器的dns解析函数集。
- 事件驱动的http服务器:libevent提供了一个简单的,可集成到应用程序中的HTTP服务器。
- RPC客户端服务器框架:libevent为创建RPC服务器和客户端创建了一个RPC框架,能自动的封装和解封数据结构。
- 详细说明和例程可以看这篇博客。
1.5 Qt
- Qt更多的是作为一个GUI库为大家所知,但是其实它是一个著名的C++应用程序框架,十分庞大,而不仅仅有GUI组件。内置了STL、XML解析、数据库连接、访问网络等等的各种第三方库。
- 比特币源码中,QT是用来编写钱包的图形界面。
- 一篇很全面也很丰富的Qt介绍教程,作者用了四年的时间编写的,令人敬佩。
以上就是Bitcoin源码阅读前需要了解的技术基础,不需要深究,知道怎么用,有什么样的表现形式即可,后面阅读源码的时候可以返回来,知道再哪里查看。
区块链是由许多技术组成的,比如P2P,RPC,密码学,共识等等,用到了第三方库如leveldb,libevent,boost,zeromq,berkeley db,openssl等,然后除了第三方库,核心代码就近10万行,是比较大的项目,如果能把里面的东西搞明白,对自己能力的提升非常大。
目前在这边主要做了一些工作,按照时间来:
0)环境搭建及项目的安装,git Bitcoin Core源代码,各种第三方库安装编译,然后编译Bitcoin Core,遇到一些问题记录下来,启动看日志文件有没有问题什么的,同步区块数据,gdb看一些关键数据。
1)研究Bitcoin Core的整个架构,即单进程多线程,每个线程各司其职,有只管收发数据的线程,监听端口连接的线程,DNS种子节点发现的线程,处理RPC请求的线程等,程序启动时每个step做了什么工作,比如初始化全局变量,日志,系统环境等,代码中加锁的地方较多,毕竟整个Bitcoin Core重点不是性能,只要能保证区块数据正确同步,没bug,稳定健壮,安全即可。
2)分析RPC实现原理,代码中直接使用httpsvr,而httpsvr是由libevent实现,后者是个网络库,比较重,基于事件驱动的,性能比较好,不是很难,主要是序列化格式要注意,后期会专门介绍RPC的通用架构和实现,这里使用的比较简单,没有负载均衡,可用性等一说。
3)POW的实现原理及代码分析,POW主要是共识机制,全称是Proof of Work,即工作量证明。就是根据全网难度目标值,你需要计算一个hash值,使得这个hash小于给定的目标值,输入是一个区块的头部,主要包括前一个区块的hash值,难度值,时间,nonce,merkle root hash等,一共80字节,然后hash,如果不满足就对nonce++,整个头部就只能更新nonce,是uint32_t的,如果一个轮循结束后,还没有找到合适nonce使得value < targetvalue,那么更新merkle root hash值,然后nonce从0开始再计算。这个计算过程要消耗电力,这个谁来计算会在下面介绍。找到合适的value后就会全网广播,怎么广播的也会在下面介绍,节点收到区块头部即做简单的难证就行。其它的一些也很重要就不展开说明了,里面会涉及交易的打包计算等等。除了POW还有其他共识如POS,dBFT等,相关原理说明也可以在网上找到。
4)P2P实现原理及代码分析,这里的P2P不是网贷,也不是下载软件,虽然思想是一样的。Bitcoin Core节点在启动时会有专门的线程dns发现节点,或从命令终端addnode指定节点并与之建立对等关系,比如peer1要和peer2建立对等关系,这里假设不通过proxy,那么peer1先与peer2建立socket关系,然后peer1发version消息,peer2收到后做些处理,然后回verack消息,然后peer2发送version消息,peer1收到后做些处理并回复verack消息,这样四条消息是建立对等关系,都会保存对等节点的状态信息,然后peer1通过getaddr消息请求peer2把peer2的对等节点告诉peer1,此时peer1会有线程定时去处理这些消息并向peer2发送addr消息,把自己的对等节点告诉peer2,包括后期的区块数据同步,交易数据的同步等等,都是如此,但是会先发一个inv消息,里面是type--hash的结构,这样会节省流量,比如peer1有10个区块,那么peer1会给peer2发送inv消息,内容是{type=block,hash=x,type=block,hash=y....},peer2收到后,通过hash去判断自己有没有,把没有的hash再以相同的消息内容以getdata消息发给peer1,peer1收到后,就开始同步区块数据,然后标记已同步的,这里标记是通过bloom filer,下次再同步的时候,会对hash作过滤,去查找有没有同步过,可能会存在一些误判,这是没关系的,可能peer3或其他节点会同步的,这个P2P功能非常重要,过程不是特别复杂,但每个节点的状态数据很多,先介绍到这里了。