一步步教小白使用C++构建区块链
最近你可能听说过很多关于区块链的消息,想知道大家都在谈论什么。 区块链是一种分类账本,它的实现原理使得很难改变它所包含的数据;有人说区块链是不可改变的,它的目的是保持其正确的,不变性意味着永久性;但是硬盘上没有任何东西 永远可以被认为是永久性不变的;我们把这些哲学辩论留给非技术人员。而我现在要做的是向您展示如何使用C ++编写区块链。
免责声明
- 本教程是参考Savjee使用NodeJS编写一篇文章;
- 有一段时间没有编写任何C ++代码,代码可能会有点生疏。(译者注:运行该C++程序,可以生成区块并组成区块链,也具备工作量证明POW功能)
首先要做的是打开一个新的C++项目,我使用JetBrains中的CLion,但任何C++ IDE甚至文本编辑器都可以(译者注:译者使用的是VIM)。 我们将创建TestChain项目,继续下一步创建项目。
如果您使用CLion,您会看到main.cpp文件已经创建并打开; 我们暂时不使用该主函数文件。
在主项目文件夹中创建一个名为Block.h的文件,您应该可以通过右键单击项目工具窗口中的TestChain目录并选择:New>C/C ++ Header File来新建。
在文件内部,添加下面的代码(如果您使用CLion将所有代码放在#define TESTCHAIN_BLOCK_H和#endif的行之间)(译者注:防止头文件被重复调用)
#include <cstdint>
#include <iostream>
上面语句的意思是告诉编译器包含cstdint和iostream库。在它下面添加这一行:
using namespace std;
上面语句是为std命名空间创建了一个快捷方式。其作用是不需要通过其使用全名引用std命名空间中的声明,例如,如果有上面语句则书写string就可以使用字符串,否则需要使用全名访问字符串,如std::string。
到现在为止感觉还挺好; 让我们进一步完善充实。
区块链由一系列包含数据的区块组成,每个区块都包含前一区块的加密HASH,这就表示很难改变某一个区块的内容,而后面的区块内容也同样发生改变(译者注:如果改变中间某个区块的内容,则这个区块之后的所有区块的内容都需要被改变才有效); 因此区块链被赋予了不变的特性。
区块头结构
接下来我们创建我们的块类,将以下几行添加到Block.h头文件中:
class Block {
public:
string sPrevHash;
Block(uint32_t nIndexIn, const string &sDataIn);
string GetHash();
void MineBlock(uint32_t nDifficulty);
private:
uint32_t _nIndex;
int64_t _nNonce;
string _sData;
string _sHash;
time_t _tTime;
string _CalculateHash() const;
};
不出所料,第1行 类Block第2行 公有修饰符 public第3行 公有变量sPrevHash,保存区块的前一个区块第5行 构造函数,需要的参数为nIndexIn和sDataInconst和&一起使用,表示参数为引用传递,其值不可被修改,其目的是提高效率并且节省内存第7行 GetHash()函数,获得HASH值第9行 MineBlock()函数,挖矿,其参数nDifficulty表示指定的难度值第11行 私有修饰符private,表示后面的变量为私有变量,不能被其他类访问第12-16行 私有变量
- _nIndex 区块索引值,第几个区块,从0开始计算,
- _nNonce 区块随机数,
- _sData 区块描述字符
- _sHash 区块HASH值
- _tTime 区块生成时间
第18行 _CalculateHash(),计算HASH值。使用const关键字,是为了确保函数的输出不能被修改,这在处理区块链时非常有用。
现在是在主项目文件夹中创建Blockchain.h头文件的时候了。让我们开始添加这些行(如果您使用CLion将所有代码#define TESTCHAIN_BLOCKCHAIN_H 和 #endif之间)
#include <cstdint>
#include <vector>
#include "Block.h"
using namespace std;
上面语句告知编译器包含cstdint和vector向量库,以及调用我们刚创建的Block.h头文件,并创建std namespace快捷方式。
区块链头结构
现在让我们创建区块链类,将以下代码行添加到Blockchain.h头文件中:
class Blockchain {
public:
Blockchain();
void AddBlock(Block bNew);
private:
uint32_t _nDifficulty;
vector<Block> _vChain;
Block _GetLastBlock() const;
};
第1行 创建区块链类第2行 public修饰符第3行 默认构造函数第5行 AddBlock() 增加区块函数,参数为我们前面创建的Block类的对象bNew第7行 private修饰符,后面的变量和方法是私有的第8-9行 私有变量
- _nDifficulty难度值
- _vChain保存区块的变量第11行 _GetLastBlock()获取最新的区块,由const关键字,表示输出的内容不可更改
因为区块链使用密码学,所以我们区块链需要一些加密函数。 我们将使用SHA256哈希来创建块的哈希值,(我们可以编写自己的SHA256函数,但是没必要重复造轮子),为了让编码更加简单,引用了来自Zedwood的C++ sha256函数,该链接中有sha256.h,sha256.cpp和LICENSE.txt文件,我们将它们保存在项目文件夹中。(译者注:可以使用译者提供的centos系统可以变编译运行的代码)
好的,让我们继续
接下来我们的区块创建一个源文件Block.cpp并保存到主项目文件夹中首先添加这些行,告诉编译器包含我们之前添加的Block.h和sha256.h文件。
#include "Block.h"
#include "sha256.h"
下面实现区块构造函数:
Block::Block(uint32_t nIndexIn, const string &sDataIn) : _nIndex(nIndexIn), _sData(sDataIn) {
_nNonce = -1;
_tTime = time(nullptr);
}
第1行 将参数的内容赋值给变量_nIndex和_sData中。第2行 _nNonce变量设置为-1。第3行 _tTime变量设置为当前时间。
实现访问区块的HASH函数:
string Block::GetHash() {
return _sHash;
}
第2行 返回私有变量_sHash的值
您可能已经清楚,比特币数字货币中设计区块链技术非常受欢迎,使得分类账不能被修改或者保密;这意味着,当一个用户将比特币发送给另一个用户时,比特币网络上的节点将转账交易写到区块链中的一个区块中。另一台计算机作为一个节点运行比特币软件(因为比特币网络是P2P,所以该节点可以是世界上任何一个人运行);这个过程被称为“挖矿”,每当他们在区块链上成功创建一个有效的区块时,该节点将获得相应的比特币奖励。
如果想成功创建一个有效的区块并因此获得奖励,矿工必须创建一个有效的区块加密HASH添加到区块链中。 有效区块HASH是通过计算HASH开头零的数量来实现的;如果零的数量等于或大于网络设置的难度,则表示有效,否则将增加随机数后重新创建散列;直到生成一个有效的散列。 这个过程称为工作量证明(Proof of Work,PoW)。
挖矿过程-工作量证明
接下来,我们实现MineBlock方法来复现上述过程
void Block::MineBlock(uint32_t nDifficulty) {
char cstr[nDifficulty + 1];
for (uint32_t i = 0; i < nDifficulty; ++i) {
cstr[i] = '0';
}
cstr[nDifficulty] = '\0';
string str(cstr);
do {
_nNonce++;
_sHash = _CalculateHash();
} while (_sHash.substr(0, nDifficulty) != str);
cout << "Block mined: " << _sHash << endl;
}
第1行 MineBlock函数在block.h文件中声明,参数nDifficulty为难度第2行 创建一个难度大于nDifficulty的值的字符数据第3-6行 用0填充数组,最后一个字符填充为终止符\0第8行 将字符数组转换为string字符串第10-13行 循环增加随机数,并且计算HASH值保存到_sHash中。循环结束的条件是比较生成的HASH值和刚刚创建的零串,一旦匹配则跳出循环,否则继续生成HASH第15行 一旦找到匹配,就会向输出发送该区块的HASH值,表示该块已成功开采。
区块HASH计算
我们前面已经看到它好几次,所以接下来添加_CalculateHash方法:
inline string Block::_CalculateHash() const {
stringstream ss;
ss << _nIndex << _tTime << _sData << _nNonce << sPrevHash;
return sha256(ss.str());
}
第1行 该函数在Block.h头文件中声明,inline关键字修饰,表示编译器编译时将该函数代码嵌入到其他函数中,减少了函数的调用开销(译者注:inline通常用在循环体内)
第2行 创建一个字符串流ss。
第3行 将_nIndex,_tTime,_sData,_nNonce和sPrevHash的值附加到流字符串流ss中。
第5行 将字符串流ss的sha256值输出
增加区块链文件Blockchain.cpp到主项目文件夹中。
下面的代码编译器包含添加的Blockchain.h文件。
#include "Blockchain.h"
创世块的产生
接下来,我们实现区块链构造函数
Blockchain::Blockchain() {
_vChain.emplace_back(Block(0, "Genesis Block"));
_nDifficulty = 3;
}
第1行 区块链构造函数,生成“创世块”。
因为将区块添加到区块链中时,需要添加前一个区块HASH值,所以区块链必须从某处开始,我们称最开始的那个区块为“创世块”。
第2行 将生成的区块放在向量_vChain中。
emplace_back()函数,在容器_vChain尾部添加一个元素,这个元素原地构造,不需要触发拷贝构造和转移构造。
第3行 设置工作量POW的难度值(即区块HASH前面有多少个零)
添加区块
现在,我们实现区块增加函数
void Blockchain::AddBlock(Block bNew) {
bNew.sPrevHash = _GetLastBlock().GetHash();
bNew.MineBlock(_nDifficulty);
_vChain.push_back(bNew);
}
第1行 该函数是在Blockchain.h文件中申明,参数为区块对象第2行 将上一区块的hash值(_vChain的最后一个区块HASH值)保存到sPrevHash中第3行 挖矿,参数为_nDifficulty难度值第4行 将新挖的区块保存到_vChain向量的末尾
让我们实现区块链的最后一个方法
Block Blockchain::_GetLastBlock() const {
return _vChain.back();
}
第1行 该函数是在Blockchain.h头文件中声明
第2行 返回向量_vChain的最后一个元素
主函数
新建main.cpp(挖掘三个区块后退出程序)
#include "Blockchain.h"
int main() {
Blockchain bChain = Blockchain();
cout << "Mining block 1..." << endl;
bChain.AddBlock(Block(1, "Block 1 Data"));
cout << "Mining block 2..." << endl;
bChain.AddBlock(Block(2, "Block 2 Data"));
cout << "Mining block 3..." << endl;
bChain.AddBlock(Block(3, "Block 3 Data"));
return 0;
}
源码目录结构以及编译情况
挖矿过程
请点击下载源码
源码介绍文档请阅读《一步步教小白使用C++构建区块链》
作者:davenash
链接:http://davenash.com/2017/10/build-a-blockchain-with-c/
版权声明:作者保留权利。文章为作者独立观点,不代表B链网立场。严禁修改,转载请注明原文链接