mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2025-11-01 23:03:47 +08:00
del
This commit is contained in:
95
极客时间专栏/geek/深入浅出区块链/第五章 如何从业区块链/第34讲 | 从业区块链需要了解什么?.md
Normal file
95
极客时间专栏/geek/深入浅出区块链/第五章 如何从业区块链/第34讲 | 从业区块链需要了解什么?.md
Normal file
@@ -0,0 +1,95 @@
|
||||
<audio id="audio" title="第34讲 | 从业区块链需要了解什么?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/90/66/90b1957258d55afa828cafcb1f362166.mp3"></audio>
|
||||
|
||||
今天,我们来到了最后一个专题:如何从业区块链。经过了前面33篇文章的洗礼,相信你对区块链应该有了一个比较全面的了解了。
|
||||
|
||||
在今天的文章中,我会从两个角度出发,先从行业的角度来讲讲区块链行业现状以及人才需求,再从人才角度来谈谈从业区块链需要具备哪些基础技能。
|
||||
|
||||
## 行业现状
|
||||
|
||||
那么行业现状与实际的人才需求是怎样的呢?我们一起来看看。目前区块链领域的人才需求大致可以分为以下几种。
|
||||
|
||||
1. 根据客户的需求,搭建基于DLT技术的分布式账本应用,在DLT上实现客户要求的业务需求,这类与传统解决方案型的人才十分接近。
|
||||
1. 公司已经具有了某些行业的资深经验,目标是通过技术选型选择某个公链,在此公链上开发基于区块链的应用。目前游戏与社交类的项目比较成熟,游戏类有以太养猫、LeBloc等,内容社区类有Steemit、币问、币乎等项目。这一类的特点是可以很好地与现有技术结合,在业务层面利用区块链的资产数字化特性,商业潜力大,技术发展空间也很大,进入门槛较低,风险较低。
|
||||
1. 公司获得融资或者在海外发起ICO,目标是研发新一代公链,这一类是为了通过改进现有的区块链技术不足而创建的项目,技术发展空间最大,进入门槛最高,风险也最高。
|
||||
1. 区块链生态基础设施类。数字资产交易平台、数字资产管理、移动钱包、硬件冷钱包、数字金融媒体、区块链咨询、矿池运营等都属于此类,这些都是目前商业利润最高的区块链产业,技术发展空间较大,进入门槛较低,风险也较低。
|
||||
|
||||
目前区块链的人才供应需求远远不足以支撑如此庞大的市场。换句话说就是人才极度稀缺,人才的稀缺与过高的估值形成鲜明对比,这也就是泡沫的形成。
|
||||
|
||||
与上面的分类相关的,是行业相应需求的编程语言。
|
||||
|
||||
第1类是DLT技术,由于超级账本的流行,DLT基本以Golang为主,但也会涉及应用可视化交互的问题,毕竟交付给客户的时候,指望客户使用命令行是不现实的,所以不可避免地需要具备一些前端技术。
|
||||
|
||||
第2类是公链应用,由于智能合约的存在,使用区块链的门槛大大降低,最流行的以太坊智能合约是以类JavaScript的语言Solidity编写的,目前也出现了不限定编程语言的智能合约区块链。实际上,我认为Solidity比其他完全开放式的智能合约要安全许多,所以建议你如果打算学习智能合约,还是最好从以太坊入手。
|
||||
|
||||
第3类是研发自己的公链。目前主流的是静态编译型语言,以C++和Golang最为常见,也有用Rust、Java、C#实现的公链,SPV轻钱包型多使用Java、Python、JavaScript实现。可以说公链研发几乎都涉及了主流编程语言。
|
||||
|
||||
第4类是在商业上与区块链最为紧密,但是技术上却是最不紧密的,整体技术栈与传统互联网网络技术差别不大,例如搭建一个区块链财经类网站,甚至不需要任何区块链技术,但是对内容运营有较高要求。
|
||||
|
||||
## 人才现状
|
||||
|
||||
我们分析了区块链行业的现状,接下来我们看看现有的人才构成。我们如果把现在互联网的人才划分一下,大致分为5个类别:
|
||||
|
||||
1. 技术决策者,这类主要指CTO、技术总监、技术专家等;
|
||||
1. 代码生产者和维护者,也就是广大IT从业者、开发工程师;
|
||||
1. 需求产生和归纳者,产品经理、需求分析师;
|
||||
1. 交互与可视化,前端工程师、UI/UE与视觉设计等;
|
||||
1. 产品运营,实际与用户接触,吸纳和维持用户。
|
||||
|
||||
目前的区块链产业基本以数字货币交易、数字资产管理、资讯类App为主,细分来说就是移动App钱包、炒币用行情工具、区块链资讯类App,以及一些简单的区块链App应用,对人才的需求大致符合互联网产业的需求,也就是上述5类人才都是需要吸纳的。
|
||||
|
||||
我们可以发现区块链产品的模式还比较单一,处于非常早期的状态。但就算是这些单一的App应用,从业人员也是不足的。
|
||||
|
||||
我在公司的实际运营过程中,发现有几种人才十分稀缺。
|
||||
|
||||
1. 理解传统金融交易,同时又了解互联网产品的人才。传统金融交易涉及的往往是券商或者证券交易所,区块链是新兴行业,所以具有跨界知识的综合性人才比较受欢迎。
|
||||
1. 理解社区建设和互联网产品运营,又同时了解数字货币的人才。这一类最缺的其实是内容运营,无论是原创内容还是编辑内容,都需要扎实的区块链知识作为基础。这类人群招聘中很少遇到,一般公司都选择自己培养。
|
||||
1. 了解大型开源项目建设,同时乐于和社区沟通分享的顶尖技术人才。不少顶尖技术人不擅于与人沟通,这也导致参与社区型项目过程中,其他开发者对其误解甚至产生偏见。
|
||||
|
||||
我在实际招聘工作中,经常会遇到不同的候选人,针对他们的盲区,我总结了以下经验。
|
||||
|
||||
第一点是候选人的完全匹配度非常不高,但某一技能模块匹配度又很高,直接选用的话要考虑培养成本,不选用又感觉可惜,招聘者经常处在两难的选择。
|
||||
|
||||
例如应聘者来自传统交易领域,但是完全不懂区块链,也不了解有哪些知名可靠的数字货币交易平台,那么必然就需要一个较长的培训过程。所以我建议各位求职者业余时间炒炒币感受一下,并且购买几本区块链相关书籍作为入门铺垫。
|
||||
|
||||
第二点是思维转变。很多求职者抱着跟风的心态过来尝试,由于区块链大热,所以就来试试。我认为这是好的开始,毕竟勇敢迈出第一步很重要。
|
||||
|
||||
但是在实际面试过程中,这部分求职者由于对经济和金融理解有些浅薄,有一些错误的认识,这类人才即使进入区块链行业不久也会被吓跑。区块链行业早期收益高,风险也高,大家一定要认清自己的风险偏好再作打算。
|
||||
|
||||
第三点求职者本身抗拒数字货币,认为数字货币就是泡沫,他们认为区块链技术才是未来,但是当我问为什么区块链技术是未来的时候,也答不出个所以然。这里有可能是对数字货币巨大风险的恐惧,也有可能是不愿意接触了解产生的偏见。数字货币作为区块链的第一大应用,客观接受是前提。
|
||||
|
||||
第四点是求职者以为投递区块链岗位,需要非常深的区块链理解和技术储备。但这部分求职者往往是仅通过网络上几篇文章的阅读,就过来面试区块链岗位了。
|
||||
|
||||
其实作为招聘者,我一般不问区块链技术,也不会问共识算法,我一般还是问编程语言的基础知识,例如TCP/IP协议,C++的右值引用,所以即使不懂区块链也没有关系,区块链也是构建在互联网之上的,扎实的编程基础才决定了后续学习效果和成长的速度。
|
||||
|
||||
这里,我列出了公司实际的一些岗位需求,给你参考。
|
||||
|
||||
1. 公链核心开发者:编程语言基本功非常扎实,代码风格容易阅读,精通中英文文档撰写,英语口语流畅,了解区块链技术基础即可。
|
||||
1. 钱包App以及其他基础设施开发者:了解主流语言开发包,了解移动互联网产品的开发和上架过程,了解区块链私钥管理即可。
|
||||
1. 社区运营:互联网产品社区运营,对数字货币和开源社区抱有热情,熟悉常见线上线下运营手段。
|
||||
1. 钱包App产品运营:互联网产品运营,这类与理财类App比较接近。
|
||||
1. 数字资产交易平台:这类人才需求最大,基本上会涵盖上述5种人才储备。
|
||||
1. 区块链研究:这类分为行业战略研究、技术研究两类,前者类似咨询行业,后者主要是CTO、架构师等高端技术社区、开源技术社区推进。
|
||||
|
||||
## 明确与制定自己的发展方向
|
||||
|
||||
其实无论是区块链还是前段时间大热的人工智能,是做技术还是做产品,我们首先要找准的都是自己的方向。
|
||||
|
||||
不同的人从业的出发点也不同,我曾经面试过一位C++技术大牛,他与C++之父共事过两年,是十分聪颖、沟通能力良好的一位工程师,当我力邀他加入我们公司的时候,他说道:“我要的不是某个火热的行业,只是希望有一份稳定且风险不高的工作,继续自己的兴趣技术研究。”
|
||||
|
||||
如果求职者看哪一行火热就想从业,这种随波逐流的心态可能会导致什么也做不好。所以我十分建议你先要弄清楚自己的职业喜好,如果有条件,你可以做一个职业和性格测试,找准自己未来的定位,争取让自己的性格与自己的职业摩擦最小。
|
||||
|
||||
区块链是一个新兴的行业,可选的余地大并不意味着对从业者没有要求。恰恰相反,这样的行业恰恰需要从业者有扎实的编程语言基础,相对完备的计算机和网络理论知识结构。
|
||||
|
||||
毕竟无论是人工智能还是区块链,如果你具备了扎实的基础,在上手学习的过程中,也会快人一步。
|
||||
|
||||
不过仅仅拥有领域知识是不够的,目前行业的发展都呈现出跨界跨领域的趋势,所以你也不必局限在自己的领域内,适当地扩充其他知识也是十分有助于职业发展的。
|
||||
|
||||
## 总结
|
||||
|
||||
今天我分别从行业需求和人才分布两个角度出发,谈及了区块链行业从业话题。其实无论什么样的工程师想入门区块链,都需要具备扎实的编程语言知识、计算机和网络知识,基本功扎实这个标准,放到任何一个行业都是一样的。
|
||||
|
||||
总结起来,你首先要找准自己的职业方向,其次是具备扎实的领域基础知识,最后是拓宽自己的知识面,想必这样从业区块链也会信心十足,早日实现财富和技术自由。
|
||||
|
||||
那么今天的问题,你认为从业区块链行业最重要的知识点是什么呢?你可以给我留言,我们一起讨论。感谢你的收听,我们下次再见。
|
||||
|
||||
|
||||
212
极客时间专栏/geek/深入浅出区块链/第五章 如何从业区块链/第35讲 | 搭建你的迷你区块链(设计篇 ).md
Normal file
212
极客时间专栏/geek/深入浅出区块链/第五章 如何从业区块链/第35讲 | 搭建你的迷你区块链(设计篇 ).md
Normal file
@@ -0,0 +1,212 @@
|
||||
<audio id="audio" title="第35讲 | 搭建你的迷你区块链(设计篇 )" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d2/c4/d2e10cc3dcfab8605ae0039815b3dac4.mp3"></audio>
|
||||
|
||||
程序员的天赋技能就是通过代码实践自己的想法,完成一个作品会有相当的成就感。
|
||||
|
||||
今天我们终于也来到了实践环节。我将以C++14的代码为例,和你分享设计并实现一个迷你区块链的例子。
|
||||
|
||||
## 目标和范围
|
||||
|
||||
首先我们要知道达成的目标,根据目标划定工作范围。
|
||||
|
||||
考虑到我们无法搭建一个类似比特币的庞大P2P网络,也没有太多精力实现一个真正意义上的完整功能的全节点钱包,而且完整的全节点过于复杂,会让学习者迷失在细节中。
|
||||
|
||||
所以我们的目标是:构建一个包含仅有基础功能的全节点客户端,它可能没有太炫酷的UI页面,也没有复杂的命令,它们可以提供下面的功能。
|
||||
|
||||
1. 提供P2P节点发现和同步区块的功能;
|
||||
1. 提供创建公私钥对的功能;
|
||||
1. 提供发送交易的功能;
|
||||
1. 提供交易查询的功能;
|
||||
1. 提供余额查询的功能;
|
||||
1. 提供挖矿的功能,在任意地址上都可以发起单机挖矿;
|
||||
1. 提供基础日志,方便跟踪监视。
|
||||
|
||||
以上7个功能基本涵盖了一个区块链全节点的主要功能,但是,由于我的时间有限,代码不能全部实现,主要是讲解设计和实现思路。后续我会逐渐完善代码,你也可以一起参与。
|
||||
|
||||
代码开源在:[https://github.com/betachen/tinychain](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网络:节点发现、节点维护、持久化保存、区块同步。
|
||||
1. 公私钥对:命令行,创建公私钥对并生成地址,提供私钥存储,公私钥验证。
|
||||
1. 发送交易:命令行,发送成功验证,输入是交易哈希。
|
||||
1. 交易查询:命令行,JSON格式的交易查询返回,输入是某个地址。
|
||||
1. 余额查询:命令行,JSON格式的余额查询返回,输入是某个地址。
|
||||
1. 挖矿:命令行、JSON格式挖矿信息返回,输入是某个地址。
|
||||
1. 区块共识:编织区块链的算法,包含创世区块以及调整全网挖矿难度。
|
||||
1. 交易共识:验证单个交易的算法,包含签名验证和UTXO验证。
|
||||
1. 基础日志:用于监控网络,区块验证等操作。
|
||||
1. 区块持久化存储:分叉与合并时的一致性,并为查询提供接口。
|
||||
1. 提供格式化输出交易的功能,这里的格式化主要指JSON格式。
|
||||
1. 有效防止双花交易。
|
||||
|
||||
通过详细的功能拆分我们可以发现,功能点多达三十余个,如何设计实现这三十多个功能点是我们接下来首先要解决的问题。问题是这三十多个功能点不是孤立的,而是有相互联系的,我们先从顶层开始设计。
|
||||
|
||||
最顶层是一个区块节点,一个完整的可执行程序,我们命名为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中的容器。
|
||||
|
||||
### 哈希计算
|
||||
|
||||
区块和交易的哈希计算均使用SHA-256。
|
||||
|
||||
## 开发环境搭建
|
||||
|
||||
由于选取了C++作为实现方式,搭建工程的过程会比较复杂一点。我们用的是Ubuntu 16.04开发环境,默认的gcc编译器是gcc-5.4,是支持C++14标准的。代码也是全平台可移植的,如果你使用Mac,也可以尝试搭建。
|
||||
|
||||
除了gcc之外,我们还需要Cmake来构建工程。我们也许需要Boost库的支持,例如Filesystem和Datetime等基础组件。
|
||||
|
||||
所以我们的工具链是:
|
||||
|
||||
- gcc或clang
|
||||
- cmake
|
||||
- boost 1.56+ (datetime)
|
||||
|
||||
最后我们还需要一个简单好用的轻量级Httpserver,我选取了元界代码中的Mongoose库,这里的Mongoose不是MongoDB,是由Cesanta开源的一个HTTP Server库,支持epoll和select两种网络并发机制,也支持WebSocket。
|
||||
|
||||
当然除了C++实现之外,我们也可以使用Python来实现,实际上也有不少Python实现的Demo,但我发现用Python实现的例子很多是在单进程中模拟区块链的数据结构,并不是真正意义上的分布式节点,所以我采取了使用C++实现的策略。
|
||||
|
||||
## 测试环境搭建
|
||||
|
||||
我们知道区块链是一个分布式网络环境,在开始之前,我们需要构造一个简单且容易测试的分布式网络环境。
|
||||
|
||||
我们不可能购买大量的云计算资源,所以我们推荐你购买一个基础版的ECS节点,2Core 4G就可以,性能稍好更好,接着我们选用Docker来搭建容器集群,在容器中部署节点,其中宿主机作为编译环境,将编译完成的钱包部署到全部的Docker容器中。
|
||||
|
||||
## 总结
|
||||
|
||||
今天我大致介绍了实践一个迷你区块链的思路,我们先划定了实践的范围,接着考虑了技术选型,然后细化了详细功能,考虑了一个区块链需要的数据结构,最后考虑了开发环境和测试环境的搭建。今天的问题是,你觉得搭建一个迷你区块链最难的部分是哪一部分呢?
|
||||
|
||||
### 链接:
|
||||
|
||||
[https://github.com/cesanta/mongoose](https://github.com/cesanta/mongoose)
|
||||
|
||||
### 一些Python实现迷你区块链的例子:
|
||||
|
||||
1. [https://medium.com/crypto-currently/lets-build-the-tiniest-blockchain-e70965a248b](https://medium.com/crypto-currently/lets-build-the-tiniest-blockchain-e70965a248b)
|
||||
1. [https://hackernoon.com/learn-blockchains-by-building-one-117428612f46](https://hackernoon.com/learn-blockchains-by-building-one-117428612f46)
|
||||
1. [http://adilmoujahid.com/posts/2018/03/intro-blockchain-bitcoin-python/](http://adilmoujahid.com/posts/2018/03/intro-blockchain-bitcoin-python/)
|
||||
|
||||
|
||||
538
极客时间专栏/geek/深入浅出区块链/第五章 如何从业区块链/第36讲 | 搭建你的迷你区块链(实践篇).md
Normal file
538
极客时间专栏/geek/深入浅出区块链/第五章 如何从业区块链/第36讲 | 搭建你的迷你区块链(实践篇).md
Normal file
@@ -0,0 +1,538 @@
|
||||
<audio id="audio" title="第36讲 | 搭建你的迷你区块链(实践篇)" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/fa/80/fa62b36d3b16ed11fcc1eb54c5b0f480.mp3"></audio>
|
||||
|
||||
上一篇文章中,我们介绍了实现一个迷你区块链的大致思路。今天,我们将通过代码编写,以及简单的功能测试,来完成我们的迷你区块链Tinychain。
|
||||
|
||||
除了正常的测试案例之外,我们还可以构造一些极端测试案例,来观察Tinychain的分叉合并,挖矿难度调整等情况。
|
||||
|
||||
## 代码编写
|
||||
|
||||
通过前文的分析,我们已经了解到了实践一个迷你区块链的大致思路。接下来,我将从顶层到底层来搭建区块链。
|
||||
|
||||
### 代码编写1 Server
|
||||
|
||||
从链的顶层设计来看,我们需要一个入口,那么我们就从入口开始:我需要先为整个服务做一些基础设置,最后再来Server.run()。
|
||||
|
||||
所以,我们的代码大概是这样子的。
|
||||
|
||||
```
|
||||
// server setup
|
||||
node my_node;
|
||||
mgbubble::RestServ Server{"webroot", my_node};
|
||||
auto& conn = Server.bind("0.0.0.0:8000");
|
||||
mg_set_protocol_http_websocket(&conn);
|
||||
log::info("main")<<"httpserver started";
|
||||
Server.run();
|
||||
|
||||
```
|
||||
|
||||
我们首先生成一个node实例,然后被Server装载进去,最后设置好Server启动。
|
||||
|
||||
这个Server主要有两个功用,第一是向本地用户服务,也就是接受命令行,接受本地RPC调用;第二是接受外部网络传送进来是的新交易,和新的区块。所以Server是整个节点的入口。
|
||||
|
||||
### 代码编写2 node
|
||||
|
||||
那么这里的node其实就是区块链的node,里面包含了区块链的基本设置,这些一般都是硬编码在代码中的,例如一般区块链都有个“魔法数”,实际上就是区块链ID,这个ID会被放在所有消息的开头,如果区块链ID不匹配,则抛弃接收到的消息。
|
||||
|
||||
这里的区块链ID我们设置在这里。
|
||||
|
||||
```
|
||||
blockchain(uint16_t id = 3721)9273_(id) {
|
||||
id_ = id;
|
||||
create_genesis_block();
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
代码中所展示的id_就是区块链ID,在Tinychain的案例中,我也是硬编码的。
|
||||
|
||||
在一个node当中,至少要包含network、blockchain、miner三个模块。
|
||||
|
||||
```
|
||||
public:
|
||||
void miner_run(address_t address);
|
||||
blockchain& chain() { return blockchain_; }
|
||||
network& p2p() { return network_; }
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
private:
|
||||
network network_;
|
||||
blockchain blockchain_;
|
||||
miner miner_{blockchain_};
|
||||
|
||||
```
|
||||
|
||||
network也就是P2P网络类,blockchain是区块链的核心类,miner是共识模块下的核心类,三者被聚合到node中。
|
||||
|
||||
同时,node也会提供一些blockchain和miner的接口,方便Server层调用。
|
||||
|
||||
### 代码编写3 blockchain
|
||||
|
||||
一个blockchain实例,应当包含下面的内容。
|
||||
|
||||
```
|
||||
uint16_t id_;
|
||||
block genesis_block_;
|
||||
chain_database chain_;
|
||||
key_pair_database key_pair_database_;
|
||||
memory_pool_t pool_;
|
||||
|
||||
```
|
||||
|
||||
genesis**block** 就是创世区块,这个是预先生成好的。genesis_block的信息也是被硬编码在代码中,我在Tinychain的例子为了方便测试,每个genesis_block都是可以自行生成的。
|
||||
|
||||
chain**database chain** 是相对于memory**pool而言的,chain**就是已经经过确认,并且在本地持久化存储的区块数据(由于时间有限,Tinychain的案例中还未实现持久化存储,可以后续升级替换)。
|
||||
|
||||
memory_pool 是指还未经过确认,暂时驻留在内存中的交易池,交易池中的交易会在挖矿时,被导入到新的区块中。
|
||||
|
||||
```
|
||||
// 装载交易
|
||||
new_block.setup(pool);
|
||||
|
||||
```
|
||||
|
||||
这里的pool就是交易池。
|
||||
|
||||
key_pair_database 是指专门存储用户的私钥的数据库,同时提供私钥管理。
|
||||
|
||||
同时blockchain也负责统一对外提供上述功能的接口。
|
||||
|
||||
```
|
||||
// 获取当前节点高度
|
||||
uint64_t height() { return chain_.height(); }
|
||||
// 获取当前节点最新区块
|
||||
block get_last_block();
|
||||
// 查询指定区块
|
||||
bool get_block(sha256_t block_hash, block& out);
|
||||
// 查询指定交易
|
||||
bool get_tx(sha256_t tx_hash, tx& out);
|
||||
// 查询目标地址的余额
|
||||
bool get_balance(address_t address, uint64_t balance);
|
||||
// 获取当前区块链的ID
|
||||
auto id() {return id_;}
|
||||
// 获得交易池数据
|
||||
memory_pool_t pool() { return pool_; }
|
||||
// 区块打包成功以后,用于清空交易池
|
||||
void pool_reset() { pool_.clear(); }
|
||||
// 从网络中收集未确认的交易到交易池
|
||||
void collect(tx& tx) {
|
||||
pool_.push_back(tx);
|
||||
}
|
||||
|
||||
void merge_replace(block_list_t& block_list);
|
||||
|
||||
```
|
||||
|
||||
除了上述接口之外,blockchain还负责当发现自己处于较短的分叉链上时,自动合并到最长链。
|
||||
|
||||
### 代码编写4 network
|
||||
|
||||
在network中,可用的地址簿代表了可用的其他对等节点,至少是连接过成功一次的。
|
||||
|
||||
```
|
||||
public:
|
||||
void broadcast(const block& block);
|
||||
void broadcast(const tx& transaction);
|
||||
void process(event_t ev, func_t f);
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
private:
|
||||
endpoint_book_t book_;
|
||||
channels_t channels_;
|
||||
|
||||
```
|
||||
|
||||
地址簿会随着网络的变化进行更新,实时状态的地址簿是驻留在内存中的,当节点关闭是,会被刷到持久化存储中。
|
||||
|
||||
channels代表了已经激活的连接,这些连接可以被broadcast接口使用,当本地节点产生新的区块和交易时,会调起这些channels。
|
||||
|
||||
当P2P网络产生了新的事件时,会通过process接口处理新到达的交易和区块,这一事件会传导给blockchain模块。
|
||||
|
||||
### 代码编写5 consensus
|
||||
|
||||
consensus的含义为共识,共识会在两种情况下产生,第一是对本地生产的交易进行验证,第二是外来的区块和交易进行验证。
|
||||
|
||||
无论是哪种情况,他们遵循的验证规则是一样的。validate_tx和validate_block分别承担了这样的功能。
|
||||
|
||||
```
|
||||
bool validate_tx(const tx& new_tx) ;
|
||||
|
||||
bool validate_block(const tx& new_block) ;
|
||||
|
||||
```
|
||||
|
||||
除了验证区块之外,还涉及到提供基础挖矿设施。我们知道挖矿分为两种,一种叫做solo挖矿,另外一种叫做联合挖矿。其实无论哪种挖矿类型,都必须用到miner类。
|
||||
|
||||
```
|
||||
public:
|
||||
//开始挖矿
|
||||
void start(address_t& addr);
|
||||
inline bool pow_once(block& new_block, address_t& addr);
|
||||
// 填写自己奖励——coinbase
|
||||
tx create_coinbase_tx(address_t& addr);
|
||||
|
||||
private:
|
||||
blockchain& chain_;
|
||||
|
||||
```
|
||||
|
||||
miner类展示了在solo挖矿情况下,支持开始挖矿以及计算自己的coinbase的过程。
|
||||
|
||||
实际pow_once的挖矿代码如下,pow_once被start调用,start里面是一个死循环,死循环里面包了pow_once函数。
|
||||
|
||||
```
|
||||
bool miner::pow_once(block& new_block, address_t& addr) {
|
||||
|
||||
auto&& pool = chain_.pool();
|
||||
|
||||
auto&& prev_block = chain_.get_last_block();
|
||||
|
||||
// 填充新块
|
||||
new_block.header_.height = prev_block.header_.height + 1;
|
||||
new_block.header_.prev_hash = prev_block.header_.hash;
|
||||
|
||||
new_block.header_.timestamp = get_now_timestamp();
|
||||
|
||||
new_block.header_.tx_count = pool.size();
|
||||
|
||||
// 难度调整:
|
||||
// 控制每块速度,控制最快速度,大约10秒
|
||||
uint64_t time_peroid = new_block.header_.timestamp - prev_block.header_.timestamp;
|
||||
//log::info("consensus") << "target:" << ncan;
|
||||
|
||||
if (time_peroid <= 10u) {
|
||||
new_block.header_.difficulty = prev_block.header_.difficulty + 9000;
|
||||
} else {
|
||||
new_block.header_.difficulty = prev_block.header_.difficulty - 3000;
|
||||
}
|
||||
// 计算挖矿目标值,最大值除以难度就目标值
|
||||
uint64_t target = 0xffffffffffffffff / prev_block.header_.difficulty;
|
||||
|
||||
// 设置coinbase交易
|
||||
auto&& tx = create_coinbase_tx(addr);
|
||||
pool.push_back(tx);
|
||||
|
||||
// 装载交易
|
||||
new_block.setup(pool);
|
||||
// 计算目标值
|
||||
for ( uint64_t n = 0; ; ++n) {
|
||||
//尝试候选目标值
|
||||
new_block.header_.nonce = n;
|
||||
auto&& jv_block = new_block.to_json();
|
||||
auto&& can = to_sha256(jv_block);
|
||||
uint64_t ncan = std::stoull(can.substr(0, 16), 0, 16); //截断前16位,转换uint64 后进行比较
|
||||
|
||||
// 找到了
|
||||
if (ncan < target) {
|
||||
//log::info("consensus") << "target:" << ncan;
|
||||
//log::info("consensus") << "hash :" << to_sha256(jv_block);
|
||||
new_block.header_.hash = can;
|
||||
log::info("consensus") << "new block :" << jv_block.toStyledString();
|
||||
log::info("consensus") << "new block :" << can;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
上面的代码从一开始到for循环之前,都可以提取出来,做成叫做getblocktemplate的接口,getblocktemplate是一种JSON-RPC调用。
|
||||
|
||||
通过这个调用,就可以把挖矿的状态信息分享给其他矿机,矿机拿到blocktemplate以后直接进行nonce部分暴力搜索即可。
|
||||
|
||||
### 代码编写6 database
|
||||
|
||||
database是偏底层的接口,主要的功能有两个,第一是提供区块和私钥的持久化存储,第二是提供交易和区块的查询接口。
|
||||
|
||||
上文blockchain中的blockchain_database和keypair_database都是从database派生过来的。
|
||||
|
||||
```
|
||||
|
||||
key_pair_database
|
||||
|
||||
// 相当于是本地钱包的私钥管理
|
||||
class key_pair_database
|
||||
{
|
||||
public:
|
||||
key_pair get_new_key_pair();
|
||||
const key_pair_database_t& list_keys() const;
|
||||
private:
|
||||
key_pair_database_t key_pair_database_;
|
||||
};
|
||||
|
||||
|
||||
blockchain_database
|
||||
|
||||
public:
|
||||
uint64_t height();
|
||||
|
||||
auto get_last_block();
|
||||
|
||||
bool get_block (const sha256_t block_hash, block& b);
|
||||
|
||||
bool get_tx (const sha256_t tx_hash, tx& t);
|
||||
|
||||
bool push_block (const block& b);
|
||||
|
||||
bool pop_block (cconst sha256_t block_hash);
|
||||
|
||||
private:
|
||||
chain_database_t chain_database_;
|
||||
|
||||
```
|
||||
|
||||
### 代码编写7 commands
|
||||
|
||||
commands提供了开发者命令行交互接口。
|
||||
|
||||
```
|
||||
|
||||
bool exec(Json::Value& out);
|
||||
|
||||
static const vargv_t commands_list;
|
||||
|
||||
private:
|
||||
vargv_t vargv_;
|
||||
node& node_;
|
||||
|
||||
```
|
||||
|
||||
首先得有一个可识别的命令列表,接着是执行接口,例如命令行发起生成新key_pair的过程,执行getnewkey命令。
|
||||
|
||||
先被command解析,接着执行exec,执行的时候需要用到node对象。
|
||||
|
||||
实际上command类比较繁琐,因为一个功能复杂的钱包,维护的命令和种类可能多达几十种。
|
||||
|
||||
同时命令又可以被JSON-RPC调用,所以一般命令行客户端本身就是一个轻量级的http-client。
|
||||
|
||||
```
|
||||
|
||||
std::string url{"127.0.0.1:8000/rpc"};
|
||||
// HTTP request call commands
|
||||
HttpReq req(url, 3000, reply_handler(my_impl));
|
||||
|
||||
```
|
||||
|
||||
### 代码编写8 基础类
|
||||
|
||||
基础类是实际生成公私钥对、构建交易tx的基本单元类,构建区块的基本单元类。
|
||||
|
||||
```
|
||||
key_pair:
|
||||
class key_pair
|
||||
{
|
||||
public:
|
||||
key_pair() {
|
||||
private_key_ = RSA::new_key();
|
||||
public_key_ = private_key_.public_key();
|
||||
}
|
||||
|
||||
address_t address();
|
||||
sha256_t public_key() const;
|
||||
uint64_t private_key() const;
|
||||
|
||||
// ...一些序列化接口(tinychain中是Json)
|
||||
private:
|
||||
private_key_t private_key_;
|
||||
public_key_t public_key_;
|
||||
|
||||
tx:
|
||||
public:
|
||||
input_t inputs() const { return inputs_; }
|
||||
output_t outputs() const { return outputs_; }
|
||||
sha256_t hash() const { return hash_; }
|
||||
|
||||
private:
|
||||
input_t inputs_;
|
||||
output_t outputs_;
|
||||
sha256_t hash_;
|
||||
|
||||
block:
|
||||
class block
|
||||
{
|
||||
public:
|
||||
typedef std::vector<tx> tx_list_t;
|
||||
|
||||
struct blockheader {
|
||||
uint64_t nonce{0};
|
||||
uint64_t height{0};
|
||||
uint64_t timestamp{0};
|
||||
uint64_t tx_count{0};
|
||||
uint64_t difficulty{0};
|
||||
sha256_t hash;
|
||||
sha256_t merkel_root_hash; //TODO
|
||||
sha256_t prev_hash;
|
||||
|
||||
};
|
||||
// ... 一些其他接口和序列化函数
|
||||
std::string to_string() {
|
||||
auto&& j = to_json();
|
||||
return j.toStyledString();
|
||||
}
|
||||
|
||||
sha256_t hash() const { return header_.hash; }
|
||||
|
||||
void setup(tx_list_t& txs) {tx_list_.swap(txs);}
|
||||
|
||||
private:
|
||||
blockheader header_;
|
||||
tx_list_t tx_list_;
|
||||
|
||||
```
|
||||
|
||||
## 首次运行
|
||||
|
||||
我们编写完基础类和基本结构的代码之后,就可以运行试一试。
|
||||
|
||||
编译成功是这样子的。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/32/81/32c5b165815109bf8bb088ea26840781.png" alt="">
|
||||
|
||||
我们可以看到有Tinychain和Cli-tinychain。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/51/75/513ed7a30252931af514559789292a75.png" alt="">
|
||||
|
||||
Tnychain就是我们的核心程序,cli-tinychain就是我们的命令行客户端。
|
||||
|
||||
实际上我在Server里还嵌入了一个可视化的Websocket界面。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/62/88/62e883f4177cd5d117025cee207ec188.png" alt="">
|
||||
|
||||
只需要在Tinychain可执行文件同目录底下创建webroot文件夹,将etc底下的index放入webroot下,接着打开浏览器127.0.0.1:8000就可以看到了。
|
||||
|
||||
实际上这个页面我想做成区块的监视页面,只是还没改造完成,目前支持发送命令。
|
||||
|
||||
我们开始首次运行Tinychain。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/1e/c9/1e9c369b71436faf3e4778d4ec6358c9.png" alt="">
|
||||
|
||||
运行后,等node和server全部started,就可以开始操作命令行了。
|
||||
|
||||
也可以通过日志进行监视,但是需要在代码处详细打桩,这次我偷懒了,没有好好打,所以不多,直接查看同目录下debug.log和error.log即可。
|
||||
|
||||
## 首次挖矿
|
||||
|
||||
我们通过./tinychain启动之后,开始第一次挖矿。
|
||||
|
||||
```
|
||||
✘ chenhao@chenhaodeMacBook-Pro ~/workspace/tinychain/build/bin master ./tinychain
|
||||
20180610T232347 INFO [main] started
|
||||
20180610T232347 INFO [node] node started
|
||||
20180610T232347 INFO [main] httpserver started
|
||||
20180610T232356 INFO [consensus] new block :{
|
||||
"header" :
|
||||
{
|
||||
"difficulty" : 9001,
|
||||
"hash" : "",
|
||||
"height" : 1,
|
||||
"merkel_header_hash" : "",
|
||||
"nonce" : 0,
|
||||
"prev_hash" : "00b586611d6f2580e1ea0773ec8b684dc4acf231710519e6272ed7d0c61ed43e",
|
||||
"timestamp" : 1528644236,
|
||||
"tx_count" : 0
|
||||
},
|
||||
"txs" :
|
||||
[
|
||||
{
|
||||
"hash" : "cddf6e838eff470d81155cb4c26fd3a7615b94a00e82f99b1fd9f583d7bc0659",
|
||||
"inputs" :
|
||||
[
|
||||
{
|
||||
"hash" : "00000000000000000000000000000000",
|
||||
"index" : 0
|
||||
}
|
||||
],
|
||||
"outputs" :
|
||||
[
|
||||
{
|
||||
"address" : "122b03d11a622ac3384904948c4d808",
|
||||
"value" : 1000
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
20180610T232356 INFO [consensus] new block :0de5c36420aab2f7fc9413cfbd21bece697a349106771dc58b25a6a099d6aa86
|
||||
20180610T232357 INFO [consensus] new block :{
|
||||
"header" :
|
||||
{
|
||||
"difficulty" : 18001,
|
||||
"hash" : "",
|
||||
"height" : 2,
|
||||
"merkel_header_hash" : "",
|
||||
"nonce" : 6048,
|
||||
"prev_hash" : "0de5c36420aab2f7fc9413cfbd21bece697a349106771dc58b25a6a099d6aa86",
|
||||
"timestamp" : 1528644236,
|
||||
"tx_count" : 0
|
||||
},
|
||||
"txs" :
|
||||
[
|
||||
{
|
||||
"hash" : "cddf6e838eff470d81155cb4c26fd3a7615b94a00e82f99b1fd9f583d7bc0659",
|
||||
"inputs" :
|
||||
[
|
||||
{
|
||||
"hash" : "00000000000000000000000000000000",
|
||||
"index" : 0
|
||||
}
|
||||
],
|
||||
"outputs" :
|
||||
[
|
||||
{
|
||||
"address" : "122b03d11a622ac3384904948c4d808",
|
||||
"value" : 1000
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
刚开始挖矿会比较快,随着难度提升,会趋向于稳定到10秒种左右一个块,如果长时间不出块,难度会自动降下来。曾经元界的代码在难度调整上有缺陷,遭受了严重的“难度坠落”攻击。
|
||||
|
||||
我们可以通过这个位置观察难度调整的情况。
|
||||
<img src="https://static001.geekbang.org/resource/image/e9/43/e91d8eb7857043c606266e591d5f8f43.png" alt="">
|
||||
|
||||
## 第一笔交易
|
||||
|
||||
我们保持挖矿,接下来发送一笔交易。
|
||||
我们先通过getnewkey命令获得一个新公私钥对以及对应的地址。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/76/a5/76de5f90f37883321783c99032ec62a5.png" alt="">
|
||||
|
||||
接着发送第一笔交易。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/7f/da/7f84d975e3d38641c50804e1982560da.png" alt="">
|
||||
|
||||
探测到接下来被打包到区块中。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a2/9c/a236bed57775c050b6e6d43b2156979c.png" alt="">
|
||||
|
||||
## 分叉与合并
|
||||
|
||||
区块链分叉是数据全网不一致的表现,通常是矿工节点行为不一致导致的,常见的有网络分区和协议不兼容,如果同时产生,那么必然会出现两条比较长的分叉链。
|
||||
|
||||
在现实情况中,分叉1个是最常见的,2个已经非常罕见了,3个以上基本是网络分区造成的。
|
||||
|
||||
如果我们要在Tinychain中实践网络分区和分叉,我们需要构建局域网多节点私链环境,可以通过docker来试验。
|
||||
|
||||
通过本文,你可以看到即使是搭建一个迷你区块链,它的工作量也是巨大的,其中不仅仅只是组合几个基础组件那么简单,还要涉及各个模块的设计和交互等详细的工作。
|
||||
|
||||
由于在短时间内全部搭建以及实现Tinychain所有功能是不可行的,在这里,我只为你提供了一些实践的思路。
|
||||
|
||||
目前Tinychain缺失了P2P网络实现、RSA公私钥对集成、共识模块的交易和区块的验证等内容,我会在后续逐渐完善,你也可以跟我一起补充。
|
||||
|
||||
## 总结
|
||||
|
||||
好了,通过今天的代码实践,我们实现了迷你区块链Tinychain,并且,通过运行与测试Tinychain,我们了解到了一个最简单区块链的运行原理,希望通过今天的文章,可以帮你加深对区块链技术的理解。
|
||||
|
||||
区块链技术只是作为基础设施,服务于广大的开发者和业务需求。目前区块链的发展远远不止Tinychain中所展现的样子,我们还需要去考虑区块链2.0智能合约,如何设计Token经济等一些问题。
|
||||
|
||||
随着区块链的发展和应用规模,区块链安全问题也日益突出,所以今天的问题是,如果要攻击Tinychain,可以采取什么手段呢?你可以给我留言,我们一起讨论。
|
||||
|
||||
感谢你的收听,我们下次再见。
|
||||
|
||||
Reference in New Issue
Block a user